mirror of
https://github.com/go-gitea/gitea.git
synced 2024-09-30 03:26:01 -04:00
Compare commits
15 Commits
d3f415d105
...
990584f9bd
Author | SHA1 | Date | |
---|---|---|---|
|
990584f9bd | ||
|
123a02790c | ||
|
0f09c22663 | ||
|
25f3ec5b65 | ||
|
597d1da96b | ||
|
f5dfd7d73c | ||
|
129206da45 | ||
|
f446e3b4ab | ||
|
c70d4f03c9 | ||
|
78e8296e11 | ||
|
e37ecd1732 | ||
|
42718d32af | ||
|
84cbb6c4d2 | ||
|
23147494a7 | ||
|
d4e4226c3c |
@ -81,6 +81,10 @@ RUN_USER = ; git
|
|||||||
;; Overwrite the automatically generated public URL. Necessary for proxies and docker.
|
;; Overwrite the automatically generated public URL. Necessary for proxies and docker.
|
||||||
;ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
;ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
||||||
;;
|
;;
|
||||||
|
;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy.
|
||||||
|
;; DO NOT USE IT IN PRODUCTION!!!
|
||||||
|
;USE_SUB_URL_PATH = false
|
||||||
|
;;
|
||||||
;; when STATIC_URL_PREFIX is empty it will follow ROOT_URL
|
;; when STATIC_URL_PREFIX is empty it will follow ROOT_URL
|
||||||
;STATIC_URL_PREFIX =
|
;STATIC_URL_PREFIX =
|
||||||
;;
|
;;
|
||||||
|
@ -110,6 +110,19 @@ func GetProtectedTagByID(ctx context.Context, id int64) (*ProtectedTag, error) {
|
|||||||
return tag, nil
|
return tag, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetProtectedTagByNamePattern gets protected tag by name_pattern
|
||||||
|
func GetProtectedTagByNamePattern(ctx context.Context, repoID int64, pattern string) (*ProtectedTag, error) {
|
||||||
|
tag := &ProtectedTag{NamePattern: pattern, RepoID: repoID}
|
||||||
|
has, err := db.GetEngine(ctx).Get(tag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !has {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return tag, nil
|
||||||
|
}
|
||||||
|
|
||||||
// IsUserAllowedToControlTag checks if a user can control the specific tag.
|
// IsUserAllowedToControlTag checks if a user can control the specific tag.
|
||||||
// It returns true if the tag name is not protected or the user is allowed to control it.
|
// It returns true if the tag name is not protected or the user is allowed to control it.
|
||||||
func IsUserAllowedToControlTag(ctx context.Context, tags []*ProtectedTag, tagName string, userID int64) (bool, error) {
|
func IsUserAllowedToControlTag(ctx context.Context, tags []*ProtectedTag, tagName string, userID int64) (bool, error) {
|
||||||
|
28
models/repo/avatar_test.go
Normal file
28
models/repo/avatar_test.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepoAvatarLink(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.AppURL, "https://localhost/")()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "")()
|
||||||
|
|
||||||
|
repo := &Repository{ID: 1, Avatar: "avatar.png"}
|
||||||
|
link := repo.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/repo-avatars/avatar.png", link)
|
||||||
|
|
||||||
|
setting.AppURL = "https://localhost/sub-path/"
|
||||||
|
setting.AppSubURL = "/sub-path"
|
||||||
|
link = repo.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/sub-path/repo-avatars/avatar.png", link)
|
||||||
|
}
|
@ -5,72 +5,48 @@ package repo
|
|||||||
|
|
||||||
import "code.gitea.io/gitea/models/db"
|
import "code.gitea.io/gitea/models/db"
|
||||||
|
|
||||||
// Strings for sorting result
|
// OrderByMap represents all possible search order
|
||||||
const (
|
var OrderByMap = map[string]map[string]db.SearchOrderBy{
|
||||||
// only used for repos
|
|
||||||
SearchOrderByAlphabetically db.SearchOrderBy = "owner_name ASC, name ASC"
|
|
||||||
SearchOrderByAlphabeticallyReverse db.SearchOrderBy = "owner_name DESC, name DESC"
|
|
||||||
SearchOrderBySize db.SearchOrderBy = "size ASC"
|
|
||||||
SearchOrderBySizeReverse db.SearchOrderBy = "size DESC"
|
|
||||||
SearchOrderByGitSize db.SearchOrderBy = "git_size ASC"
|
|
||||||
SearchOrderByGitSizeReverse db.SearchOrderBy = "git_size DESC"
|
|
||||||
SearchOrderByLFSSize db.SearchOrderBy = "lfs_size ASC"
|
|
||||||
SearchOrderByLFSSizeReverse db.SearchOrderBy = "lfs_size DESC"
|
|
||||||
// alias as also used elsewhere
|
|
||||||
SearchOrderByLeastUpdated db.SearchOrderBy = db.SearchOrderByLeastUpdated
|
|
||||||
SearchOrderByRecentUpdated db.SearchOrderBy = db.SearchOrderByRecentUpdated
|
|
||||||
SearchOrderByOldest db.SearchOrderBy = db.SearchOrderByOldest
|
|
||||||
SearchOrderByNewest db.SearchOrderBy = db.SearchOrderByNewest
|
|
||||||
SearchOrderByID db.SearchOrderBy = db.SearchOrderByID
|
|
||||||
SearchOrderByIDReverse db.SearchOrderBy = db.SearchOrderByIDReverse
|
|
||||||
SearchOrderByStars db.SearchOrderBy = db.SearchOrderByStars
|
|
||||||
SearchOrderByStarsReverse db.SearchOrderBy = db.SearchOrderByStarsReverse
|
|
||||||
SearchOrderByForks db.SearchOrderBy = db.SearchOrderByForks
|
|
||||||
SearchOrderByForksReverse db.SearchOrderBy = db.SearchOrderByForksReverse
|
|
||||||
)
|
|
||||||
|
|
||||||
// SearchOrderByMap represents all possible search order
|
|
||||||
var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
|
|
||||||
"asc": {
|
"asc": {
|
||||||
"alpha": SearchOrderByAlphabetically,
|
"alpha": "owner_name ASC, name ASC",
|
||||||
"created": SearchOrderByOldest,
|
"created": db.SearchOrderByOldest,
|
||||||
"updated": SearchOrderByLeastUpdated,
|
"updated": db.SearchOrderByLeastUpdated,
|
||||||
"size": SearchOrderBySize,
|
"size": "size ASC",
|
||||||
"git_size": SearchOrderByGitSize,
|
"git_size": "git_size ASC",
|
||||||
"lfs_size": SearchOrderByLFSSize,
|
"lfs_size": "lfs_size ASC",
|
||||||
"id": SearchOrderByID,
|
"id": db.SearchOrderByID,
|
||||||
"stars": SearchOrderByStars,
|
"stars": db.SearchOrderByStars,
|
||||||
"forks": SearchOrderByForks,
|
"forks": db.SearchOrderByForks,
|
||||||
},
|
},
|
||||||
"desc": {
|
"desc": {
|
||||||
"alpha": SearchOrderByAlphabeticallyReverse,
|
"alpha": "owner_name DESC, name DESC",
|
||||||
"created": SearchOrderByNewest,
|
"created": db.SearchOrderByNewest,
|
||||||
"updated": SearchOrderByRecentUpdated,
|
"updated": db.SearchOrderByRecentUpdated,
|
||||||
"size": SearchOrderBySizeReverse,
|
"size": "size DESC",
|
||||||
"git_size": SearchOrderByGitSizeReverse,
|
"git_size": "git_size DESC",
|
||||||
"lfs_size": SearchOrderByLFSSizeReverse,
|
"lfs_size": "lfs_size DESC",
|
||||||
"id": SearchOrderByIDReverse,
|
"id": db.SearchOrderByIDReverse,
|
||||||
"stars": SearchOrderByStarsReverse,
|
"stars": db.SearchOrderByStarsReverse,
|
||||||
"forks": SearchOrderByForksReverse,
|
"forks": db.SearchOrderByForksReverse,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchOrderByFlatMap is similar to SearchOrderByMap but use human language keywords
|
// OrderByFlatMap is similar to OrderByMap but use human language keywords
|
||||||
// to decide between asc and desc
|
// to decide between asc and desc
|
||||||
var SearchOrderByFlatMap = map[string]db.SearchOrderBy{
|
var OrderByFlatMap = map[string]db.SearchOrderBy{
|
||||||
"newest": SearchOrderByMap["desc"]["created"],
|
"newest": OrderByMap["desc"]["created"],
|
||||||
"oldest": SearchOrderByMap["asc"]["created"],
|
"oldest": OrderByMap["asc"]["created"],
|
||||||
"leastupdate": SearchOrderByMap["asc"]["updated"],
|
"leastupdate": OrderByMap["asc"]["updated"],
|
||||||
"reversealphabetically": SearchOrderByMap["desc"]["alpha"],
|
"reversealphabetically": OrderByMap["desc"]["alpha"],
|
||||||
"alphabetically": SearchOrderByMap["asc"]["alpha"],
|
"alphabetically": OrderByMap["asc"]["alpha"],
|
||||||
"reversesize": SearchOrderByMap["desc"]["size"],
|
"reversesize": OrderByMap["desc"]["size"],
|
||||||
"size": SearchOrderByMap["asc"]["size"],
|
"size": OrderByMap["asc"]["size"],
|
||||||
"reversegitsize": SearchOrderByMap["desc"]["git_size"],
|
"reversegitsize": OrderByMap["desc"]["git_size"],
|
||||||
"gitsize": SearchOrderByMap["asc"]["git_size"],
|
"gitsize": OrderByMap["asc"]["git_size"],
|
||||||
"reverselfssize": SearchOrderByMap["desc"]["lfs_size"],
|
"reverselfssize": OrderByMap["desc"]["lfs_size"],
|
||||||
"lfssize": SearchOrderByMap["asc"]["lfs_size"],
|
"lfssize": OrderByMap["asc"]["lfs_size"],
|
||||||
"moststars": SearchOrderByMap["desc"]["stars"],
|
"moststars": OrderByMap["desc"]["stars"],
|
||||||
"feweststars": SearchOrderByMap["asc"]["stars"],
|
"feweststars": OrderByMap["asc"]["stars"],
|
||||||
"mostforks": SearchOrderByMap["desc"]["forks"],
|
"mostforks": OrderByMap["desc"]["forks"],
|
||||||
"fewestforks": SearchOrderByMap["asc"]["forks"],
|
"fewestforks": OrderByMap["asc"]["forks"],
|
||||||
}
|
}
|
||||||
|
@ -89,9 +89,11 @@ func (u *User) AvatarLinkWithSize(ctx context.Context, size int) string {
|
|||||||
return avatars.GenerateEmailAvatarFastLink(ctx, u.AvatarEmail, size)
|
return avatars.GenerateEmailAvatarFastLink(ctx, u.AvatarEmail, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// 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
|
||||||
func (u *User) AvatarLink(ctx context.Context) string {
|
func (u *User) AvatarLink(ctx context.Context) string {
|
||||||
return httplib.MakeAbsoluteURL(ctx, u.AvatarLinkWithSize(ctx, 0))
|
relLink := u.AvatarLinkWithSize(ctx, 0) // it can't be empty
|
||||||
|
return httplib.MakeAbsoluteURL(ctx, relLink)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data
|
// IsUploadAvatarChanged returns true if the current user's avatar would be changed with the provided data
|
||||||
|
28
models/user/avatar_test.go
Normal file
28
models/user/avatar_test.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUserAvatarLink(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.AppURL, "https://localhost/")()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "")()
|
||||||
|
|
||||||
|
u := &User{ID: 1, Avatar: "avatar.png"}
|
||||||
|
link := u.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/avatars/avatar.png", link)
|
||||||
|
|
||||||
|
setting.AppURL = "https://localhost/sub-path/"
|
||||||
|
setting.AppSubURL = "/sub-path"
|
||||||
|
link = u.AvatarLink(db.DefaultContext)
|
||||||
|
assert.Equal(t, "https://localhost/sub-path/avatars/avatar.png", link)
|
||||||
|
}
|
@ -4,12 +4,67 @@
|
|||||||
package base
|
package base
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"golang.org/x/text/collate"
|
"golang.org/x/text/collate"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func naturalSortGetRune(str string, pos int) (r rune, size int, has bool) {
|
||||||
|
if pos >= len(str) {
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
r, size = utf8.DecodeRuneInString(str[pos:])
|
||||||
|
if r == utf8.RuneError {
|
||||||
|
r, size = rune(str[pos]), 1 // if invalid input, treat it as a single byte ascii
|
||||||
|
}
|
||||||
|
return r, size, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func naturalSortAdvance(str string, pos int) (end int, isNumber bool) {
|
||||||
|
end = pos
|
||||||
|
for {
|
||||||
|
r, size, has := naturalSortGetRune(str, end)
|
||||||
|
if !has {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
isCurRuneNum := '0' <= r && r <= '9'
|
||||||
|
if end == pos {
|
||||||
|
isNumber = isCurRuneNum
|
||||||
|
end += size
|
||||||
|
} else if isCurRuneNum == isNumber {
|
||||||
|
end += size
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return end, isNumber
|
||||||
|
}
|
||||||
|
|
||||||
// NaturalSortLess compares two strings so that they could be sorted in natural order
|
// NaturalSortLess compares two strings so that they could be sorted in natural order
|
||||||
func NaturalSortLess(s1, s2 string) bool {
|
func NaturalSortLess(s1, s2 string) bool {
|
||||||
|
// There is a bug in Golang's collate package: https://github.com/golang/go/issues/67997
|
||||||
|
// text/collate: CompareString(collate.Numeric) returns wrong result for "0.0" vs "1.0" #67997
|
||||||
|
// So we need to handle the number parts by ourselves
|
||||||
c := collate.New(language.English, collate.Numeric)
|
c := collate.New(language.English, collate.Numeric)
|
||||||
return c.CompareString(s1, s2) < 0
|
pos1, pos2 := 0, 0
|
||||||
|
for pos1 < len(s1) && pos2 < len(s2) {
|
||||||
|
end1, isNum1 := naturalSortAdvance(s1, pos1)
|
||||||
|
end2, isNum2 := naturalSortAdvance(s2, pos2)
|
||||||
|
part1, part2 := s1[pos1:end1], s2[pos2:end2]
|
||||||
|
if isNum1 && isNum2 {
|
||||||
|
if part1 != part2 {
|
||||||
|
if len(part1) != len(part2) {
|
||||||
|
return len(part1) < len(part2)
|
||||||
|
}
|
||||||
|
return part1 < part2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if cmp := c.CompareString(part1, part2); cmp != 0 {
|
||||||
|
return cmp < 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos1, pos2 = end1, end2
|
||||||
|
}
|
||||||
|
return len(s1) < len(s2)
|
||||||
}
|
}
|
||||||
|
@ -10,21 +10,36 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestNaturalSortLess(t *testing.T) {
|
func TestNaturalSortLess(t *testing.T) {
|
||||||
test := func(s1, s2 string, less bool) {
|
testLess := func(s1, s2 string) {
|
||||||
assert.Equal(t, less, NaturalSortLess(s1, s2), "s1=%q, s2=%q", s1, s2)
|
assert.True(t, NaturalSortLess(s1, s2), "s1<s2 should be true: s1=%q, s2=%q", s1, s2)
|
||||||
|
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
|
||||||
|
}
|
||||||
|
testEqual := func(s1, s2 string) {
|
||||||
|
assert.False(t, NaturalSortLess(s1, s2), "s1<s2 should be false: s1=%q, s2=%q", s1, s2)
|
||||||
|
assert.False(t, NaturalSortLess(s2, s1), "s2<s1 should be false: s1=%q, s2=%q", s1, s2)
|
||||||
}
|
}
|
||||||
test("v1.20.0", "v1.2.0", false)
|
|
||||||
test("v1.20.0", "v1.29.0", true)
|
|
||||||
test("v1.20.0", "v1.20.0", false)
|
|
||||||
test("abc", "bcd", true)
|
|
||||||
test("a-1-a", "a-1-b", true)
|
|
||||||
test("2", "12", true)
|
|
||||||
test("a", "ab", true)
|
|
||||||
|
|
||||||
test("A", "b", true)
|
testEqual("", "")
|
||||||
test("a", "B", true)
|
testLess("", "a")
|
||||||
|
testLess("", "1")
|
||||||
|
|
||||||
test("cafe", "café", true)
|
testLess("v1.2", "v1.2.0")
|
||||||
test("café", "cafe", false)
|
testLess("v1.2.0", "v1.10.0")
|
||||||
test("caff", "café", false)
|
testLess("v1.20.0", "v1.29.0")
|
||||||
|
testEqual("v1.20.0", "v1.20.0")
|
||||||
|
|
||||||
|
testLess("a", "A")
|
||||||
|
testLess("a", "B")
|
||||||
|
testLess("A", "b")
|
||||||
|
testLess("A", "ab")
|
||||||
|
|
||||||
|
testLess("abc", "bcd")
|
||||||
|
testLess("a-1-a", "a-1-b")
|
||||||
|
testLess("2", "12")
|
||||||
|
|
||||||
|
testLess("cafe", "café")
|
||||||
|
testLess("café", "caff")
|
||||||
|
|
||||||
|
testLess("A-2", "A-11")
|
||||||
|
testLess("0.txt", "1.txt")
|
||||||
}
|
}
|
||||||
|
@ -57,11 +57,16 @@ func getForwardedHost(req *http.Request) string {
|
|||||||
return req.Header.Get("X-Forwarded-Host")
|
return req.Header.Get("X-Forwarded-Host")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GuessCurrentAppURL tries to guess the current full URL by http headers. It always has a '/' suffix, exactly the same as setting.AppURL
|
// 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
|
||||||
func GuessCurrentAppURL(ctx context.Context) string {
|
func GuessCurrentAppURL(ctx context.Context) string {
|
||||||
|
return GuessCurrentHostURL(ctx) + setting.AppSubURL + "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GuessCurrentHostURL tries to guess the current full host URL (no sub-path) by http headers, there is no trailing slash.
|
||||||
|
func GuessCurrentHostURL(ctx context.Context) string {
|
||||||
req, ok := ctx.Value(RequestContextKey).(*http.Request)
|
req, ok := ctx.Value(RequestContextKey).(*http.Request)
|
||||||
if !ok {
|
if !ok {
|
||||||
return setting.AppURL
|
return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/")
|
||||||
}
|
}
|
||||||
// If no scheme provided by reverse proxy, then do not guess the AppURL, use the configured one.
|
// If no scheme provided by reverse proxy, then do not guess the AppURL, use the configured one.
|
||||||
// At the moment, if site admin doesn't configure the proxy headers correctly, then Gitea would guess wrong.
|
// 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 {
|
|||||||
// So in the future maybe it should introduce a new config option, to let site admin decide how to guess the AppURL.
|
// So in the future maybe it should introduce a new config option, to let site admin decide how to guess the AppURL.
|
||||||
reqScheme := getRequestScheme(req)
|
reqScheme := getRequestScheme(req)
|
||||||
if reqScheme == "" {
|
if reqScheme == "" {
|
||||||
return setting.AppURL
|
return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/")
|
||||||
}
|
}
|
||||||
reqHost := getForwardedHost(req)
|
reqHost := getForwardedHost(req)
|
||||||
if reqHost == "" {
|
if reqHost == "" {
|
||||||
reqHost = req.Host
|
reqHost = req.Host
|
||||||
}
|
}
|
||||||
return reqScheme + "://" + reqHost + setting.AppSubURL + "/"
|
return reqScheme + "://" + reqHost
|
||||||
}
|
}
|
||||||
|
|
||||||
func MakeAbsoluteURL(ctx context.Context, s string) string {
|
// MakeAbsoluteURL tries to make a link to an absolute URL:
|
||||||
if IsRelativeURL(s) {
|
// * If link is empty, it returns the current app URL.
|
||||||
return GuessCurrentAppURL(ctx) + strings.TrimPrefix(s, "/")
|
// * If link is absolute, it returns the link.
|
||||||
|
// * Otherwise, it returns the current host URL + link, the link itself should have correct sub-path (AppSubURL) if needed.
|
||||||
|
func MakeAbsoluteURL(ctx context.Context, link string) string {
|
||||||
|
if link == "" {
|
||||||
|
return GuessCurrentAppURL(ctx)
|
||||||
}
|
}
|
||||||
return s
|
if !IsRelativeURL(link) {
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
return GuessCurrentHostURL(ctx) + "/" + strings.TrimPrefix(link, "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsCurrentGiteaSiteURL(ctx context.Context, s string) bool {
|
func IsCurrentGiteaSiteURL(ctx context.Context, s string) bool {
|
||||||
|
@ -46,14 +46,14 @@ func TestMakeAbsoluteURL(t *testing.T) {
|
|||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
assert.Equal(t, "http://cfg-host/sub/", MakeAbsoluteURL(ctx, ""))
|
assert.Equal(t, "http://cfg-host/sub/", MakeAbsoluteURL(ctx, ""))
|
||||||
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "foo"))
|
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "foo"))
|
||||||
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||||
assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo"))
|
assert.Equal(t, "http://other/foo", MakeAbsoluteURL(ctx, "http://other/foo"))
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||||
Host: "user-host",
|
Host: "user-host",
|
||||||
})
|
})
|
||||||
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||||
Host: "user-host",
|
Host: "user-host",
|
||||||
@ -61,7 +61,7 @@ func TestMakeAbsoluteURL(t *testing.T) {
|
|||||||
"X-Forwarded-Host": {"forwarded-host"},
|
"X-Forwarded-Host": {"forwarded-host"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.Equal(t, "http://cfg-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
assert.Equal(t, "http://cfg-host/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{
|
||||||
Host: "user-host",
|
Host: "user-host",
|
||||||
@ -70,7 +70,7 @@ func TestMakeAbsoluteURL(t *testing.T) {
|
|||||||
"X-Forwarded-Proto": {"https"},
|
"X-Forwarded-Proto": {"https"},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
assert.Equal(t, "https://forwarded-host/sub/foo", MakeAbsoluteURL(ctx, "/foo"))
|
assert.Equal(t, "https://forwarded-host/foo", MakeAbsoluteURL(ctx, "/foo"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
func TestIsCurrentGiteaSiteURL(t *testing.T) {
|
||||||
|
@ -86,10 +86,10 @@ type RenderContext struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Links struct {
|
type Links struct {
|
||||||
AbsolutePrefix bool
|
AbsolutePrefix bool // add absolute URL prefix to auto-resolved links like "#issue", but not for pre-provided links and medias
|
||||||
Base string
|
Base string // base prefix for pre-provided links and medias (images, videos)
|
||||||
BranchPath string
|
BranchPath string // actually it is the ref path, eg: "branch/features/feat-12", "tag/v1.0"
|
||||||
TreePath string
|
TreePath string // the dir of the file, eg: "doc" if the file "doc/CHANGE.md" is being rendered
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Links) Prefix() string {
|
func (l *Links) Prefix() string {
|
||||||
|
18
modules/setting/global.go
Normal file
18
modules/setting/global.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package setting
|
||||||
|
|
||||||
|
// Global settings
|
||||||
|
var (
|
||||||
|
// RunUser is the OS user that Gitea is running as. ini:"RUN_USER"
|
||||||
|
RunUser string
|
||||||
|
// RunMode is the running mode of Gitea, it only accepts two values: "dev" and "prod".
|
||||||
|
// Non-dev values will be replaced by "prod". ini: "RUN_MODE"
|
||||||
|
RunMode string
|
||||||
|
// IsProd is true if RunMode is not "dev"
|
||||||
|
IsProd bool
|
||||||
|
|
||||||
|
// AppName is the Application name, used in the page title. ini: "APP_NAME"
|
||||||
|
AppName string
|
||||||
|
)
|
@ -40,16 +40,16 @@ const (
|
|||||||
LandingPageLogin LandingPage = "/user/login"
|
LandingPageLogin LandingPage = "/user/login"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Server settings
|
||||||
var (
|
var (
|
||||||
// AppName is the Application name, used in the page title.
|
|
||||||
// It maps to ini:"APP_NAME"
|
|
||||||
AppName string
|
|
||||||
// AppURL is the Application ROOT_URL. It always has a '/' suffix
|
// AppURL is the Application ROOT_URL. It always has a '/' suffix
|
||||||
// It maps to ini:"ROOT_URL"
|
// It maps to ini:"ROOT_URL"
|
||||||
AppURL string
|
AppURL string
|
||||||
// AppSubURL represents the sub-url mounting point for gitea. It is either "" or starts with '/' and ends without '/', such as '/{subpath}'.
|
// AppSubURL represents the sub-url mounting point for gitea. It is either "" or starts with '/' and ends without '/', such as '/{subpath}'.
|
||||||
// This value is empty if site does not have sub-url.
|
// This value is empty if site does not have sub-url.
|
||||||
AppSubURL string
|
AppSubURL string
|
||||||
|
// 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.
|
||||||
|
UseSubURLPath bool
|
||||||
// AppDataPath is the default path for storing data.
|
// AppDataPath is the default path for storing data.
|
||||||
// It maps to ini:"APP_DATA_PATH" in [server] and defaults to AppWorkPath + "/data"
|
// It maps to ini:"APP_DATA_PATH" in [server] and defaults to AppWorkPath + "/data"
|
||||||
AppDataPath string
|
AppDataPath string
|
||||||
@ -59,8 +59,6 @@ var (
|
|||||||
// AssetVersion holds a opaque value that is used for cache-busting assets
|
// AssetVersion holds a opaque value that is used for cache-busting assets
|
||||||
AssetVersion string
|
AssetVersion string
|
||||||
|
|
||||||
// Server settings
|
|
||||||
|
|
||||||
Protocol Scheme
|
Protocol Scheme
|
||||||
UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"`
|
UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"`
|
||||||
ProxyProtocolTLSBridging bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"`
|
ProxyProtocolTLSBridging bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"`
|
||||||
@ -275,9 +273,10 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
|||||||
// This should be TrimRight to ensure that there is only a single '/' at the end of AppURL.
|
// This should be TrimRight to ensure that there is only a single '/' at the end of AppURL.
|
||||||
AppURL = strings.TrimRight(appURL.String(), "/") + "/"
|
AppURL = strings.TrimRight(appURL.String(), "/") + "/"
|
||||||
|
|
||||||
// Suburl should start with '/' and end without '/', such as '/{subpath}'.
|
// AppSubURL should start with '/' and end without '/', such as '/{subpath}'.
|
||||||
// This value is empty if site does not have sub-url.
|
// This value is empty if site does not have sub-url.
|
||||||
AppSubURL = strings.TrimSuffix(appURL.Path, "/")
|
AppSubURL = strings.TrimSuffix(appURL.Path, "/")
|
||||||
|
UseSubURLPath = sec.Key("USE_SUB_URL_PATH").MustBool(false)
|
||||||
StaticURLPrefix = strings.TrimSuffix(sec.Key("STATIC_URL_PREFIX").MustString(AppSubURL), "/")
|
StaticURLPrefix = strings.TrimSuffix(sec.Key("STATIC_URL_PREFIX").MustString(AppSubURL), "/")
|
||||||
|
|
||||||
// Check if Domain differs from AppURL domain than update it to AppURL's domain
|
// Check if Domain differs from AppURL domain than update it to AppURL's domain
|
||||||
|
@ -25,12 +25,7 @@ var (
|
|||||||
// AppStartTime store time gitea has started
|
// AppStartTime store time gitea has started
|
||||||
AppStartTime time.Time
|
AppStartTime time.Time
|
||||||
|
|
||||||
// Other global setting objects
|
|
||||||
|
|
||||||
CfgProvider ConfigProvider
|
CfgProvider ConfigProvider
|
||||||
RunMode string
|
|
||||||
RunUser string
|
|
||||||
IsProd bool
|
|
||||||
IsWindows bool
|
IsWindows bool
|
||||||
|
|
||||||
// IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing
|
// IsInTesting indicates whether the testing is running. A lot of unreliable code causes a lot of nonsense error logs during testing
|
||||||
|
@ -25,7 +25,8 @@ type MarkupOption struct {
|
|||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Mode string
|
Mode string
|
||||||
// Context to render
|
// URL path for rendering issue, media and file links
|
||||||
|
// Expected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}
|
||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Context string
|
Context string
|
||||||
@ -53,7 +54,8 @@ type MarkdownOption struct {
|
|||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Mode string
|
Mode string
|
||||||
// Context to render
|
// URL path for rendering issue, media and file links
|
||||||
|
// Expected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}
|
||||||
//
|
//
|
||||||
// in: body
|
// in: body
|
||||||
Context string
|
Context string
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
package structs
|
package structs
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
// Tag represents a repository tag
|
// Tag represents a repository tag
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@ -38,3 +40,29 @@ type CreateTagOption struct {
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Target string `json:"target"`
|
Target string `json:"target"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagProtection represents a tag protection
|
||||||
|
type TagProtection struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
NamePattern string `json:"name_pattern"`
|
||||||
|
WhitelistUsernames []string `json:"whitelist_usernames"`
|
||||||
|
WhitelistTeams []string `json:"whitelist_teams"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
Created time.Time `json:"created_at"`
|
||||||
|
// swagger:strfmt date-time
|
||||||
|
Updated time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTagProtectionOption options for creating a tag protection
|
||||||
|
type CreateTagProtectionOption struct {
|
||||||
|
NamePattern string `json:"name_pattern"`
|
||||||
|
WhitelistUsernames []string `json:"whitelist_usernames"`
|
||||||
|
WhitelistTeams []string `json:"whitelist_teams"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditTagProtectionOption options for editing a tag protection
|
||||||
|
type EditTagProtectionOption struct {
|
||||||
|
NamePattern *string `json:"name_pattern"`
|
||||||
|
WhitelistUsernames []string `json:"whitelist_usernames"`
|
||||||
|
WhitelistTeams []string `json:"whitelist_teams"`
|
||||||
|
}
|
||||||
|
47
options/gitignore/IAR
Normal file
47
options/gitignore/IAR
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# Compiled binaries
|
||||||
|
*.o
|
||||||
|
*.bin
|
||||||
|
*.elf
|
||||||
|
*.hex
|
||||||
|
*.map
|
||||||
|
*.out
|
||||||
|
*.obj
|
||||||
|
|
||||||
|
# Trash
|
||||||
|
*.bak
|
||||||
|
thumbs.db
|
||||||
|
*.~*
|
||||||
|
|
||||||
|
# IAR Settings
|
||||||
|
**/settings/*.crun
|
||||||
|
**/settings/*.dbgdt
|
||||||
|
**/settings/*.cspy
|
||||||
|
**/settings/*.cspy.*
|
||||||
|
**/settings/*.xcl
|
||||||
|
**/settings/*.dni
|
||||||
|
**/settings/*.wsdt
|
||||||
|
**/settings/*.wspos
|
||||||
|
|
||||||
|
# IAR Debug Exe
|
||||||
|
**/Exe/*.sim
|
||||||
|
|
||||||
|
# IAR Debug Obj
|
||||||
|
**/Obj/*.pbd
|
||||||
|
**/Obj/*.pbd.*
|
||||||
|
**/Obj/*.pbi
|
||||||
|
**/Obj/*.pbi.*
|
||||||
|
|
||||||
|
# IAR project "Debug" directory
|
||||||
|
Debug/
|
||||||
|
|
||||||
|
# IAR project "Release" directory
|
||||||
|
Release/
|
||||||
|
|
||||||
|
# IAR project settings directory
|
||||||
|
settings/
|
||||||
|
|
||||||
|
# IAR backup files
|
||||||
|
Backup*
|
||||||
|
|
||||||
|
# IAR .dep files
|
||||||
|
*.dep
|
@ -42,10 +42,3 @@ fastlane/report.xml
|
|||||||
fastlane/Preview.html
|
fastlane/Preview.html
|
||||||
fastlane/screenshots/**/*.png
|
fastlane/screenshots/**/*.png
|
||||||
fastlane/test_output
|
fastlane/test_output
|
||||||
|
|
||||||
# Code Injection
|
|
||||||
#
|
|
||||||
# After new code Injection tools there's a generated folder /iOSInjectionProject
|
|
||||||
# https://github.com/johnno1962/injectionforxcode
|
|
||||||
|
|
||||||
iOSInjectionProject/
|
|
||||||
|
@ -35,6 +35,3 @@ override.tf.json
|
|||||||
# Ignore CLI configuration files
|
# Ignore CLI configuration files
|
||||||
.terraformrc
|
.terraformrc
|
||||||
terraform.rc
|
terraform.rc
|
||||||
|
|
||||||
# Ignore hcl file
|
|
||||||
.terraform.lock.hcl
|
|
||||||
|
@ -1238,6 +1238,7 @@ file_view_rendered=Zobrazit vykreslené
|
|||||||
file_view_raw=Zobrazit v surovém stavu
|
file_view_raw=Zobrazit v surovém stavu
|
||||||
file_permalink=Trvalý odkaz
|
file_permalink=Trvalý odkaz
|
||||||
file_too_large=Soubor je příliš velký pro zobrazení.
|
file_too_large=Soubor je příliš velký pro zobrazení.
|
||||||
|
file_is_empty=Soubor je prázdný.
|
||||||
code_preview_line_from_to=Řádky %[1]d do%[2]d v %[3]s
|
code_preview_line_from_to=Řádky %[1]d do%[2]d v %[3]s
|
||||||
code_preview_line_in=Řádek %[1]d v %[2]s
|
code_preview_line_in=Řádek %[1]d v %[2]s
|
||||||
invisible_runes_header=`Tento soubor obsahuje neviditelné znaky Unicode`
|
invisible_runes_header=`Tento soubor obsahuje neviditelné znaky Unicode`
|
||||||
@ -1323,6 +1324,7 @@ editor.file_deleting_no_longer_exists=Odstraňovaný soubor „%s“ již není
|
|||||||
editor.file_changed_while_editing=Obsah souboru byl změněn od doby, kdy jste začaly s úpravou. <a target="_blank" rel="noopener noreferrer" href="%s">Klikněte zde</a>, abyste je zobrazili, nebo <strong>potvrďte změny ještě jednou</strong> pro jejich přepsání.
|
editor.file_changed_while_editing=Obsah souboru byl změněn od doby, kdy jste začaly s úpravou. <a target="_blank" rel="noopener noreferrer" href="%s">Klikněte zde</a>, abyste je zobrazili, nebo <strong>potvrďte změny ještě jednou</strong> pro jejich přepsání.
|
||||||
editor.file_already_exists=Soubor „%s“ již existuje v tomto repozitáři.
|
editor.file_already_exists=Soubor „%s“ již existuje v tomto repozitáři.
|
||||||
editor.commit_id_not_matching=ID commitu se neshoduje s ID, když jsi začal/a s úpravami. Odevzdat do záplatové větve a poté sloučit.
|
editor.commit_id_not_matching=ID commitu se neshoduje s ID, když jsi začal/a s úpravami. Odevzdat do záplatové větve a poté sloučit.
|
||||||
|
editor.push_out_of_date=Nahrání se zdá být zastaralé.
|
||||||
editor.commit_empty_file_header=Odevzdat prázdný soubor
|
editor.commit_empty_file_header=Odevzdat prázdný soubor
|
||||||
editor.commit_empty_file_text=Soubor, který se chystáte odevzdat, je prázdný. Pokračovat?
|
editor.commit_empty_file_text=Soubor, který se chystáte odevzdat, je prázdný. Pokračovat?
|
||||||
editor.no_changes_to_show=Žádné změny k zobrazení.
|
editor.no_changes_to_show=Žádné změny k zobrazení.
|
||||||
@ -2080,6 +2082,7 @@ settings.mirror_settings.docs.doc_link_title=Jak mohu zrcadlit repozitáře?
|
|||||||
settings.mirror_settings.docs.doc_link_pull_section=sekci "stahovat ze vzdáleného úložiště" v dokumentaci.
|
settings.mirror_settings.docs.doc_link_pull_section=sekci "stahovat ze vzdáleného úložiště" v dokumentaci.
|
||||||
settings.mirror_settings.docs.pulling_remote_title=Stažení ze vzdáleného úložiště
|
settings.mirror_settings.docs.pulling_remote_title=Stažení ze vzdáleného úložiště
|
||||||
settings.mirror_settings.mirrored_repository=Zrcadlený repozitář
|
settings.mirror_settings.mirrored_repository=Zrcadlený repozitář
|
||||||
|
settings.mirror_settings.pushed_repository=Odeslaný repozitář
|
||||||
settings.mirror_settings.direction=Směr
|
settings.mirror_settings.direction=Směr
|
||||||
settings.mirror_settings.direction.pull=Natáhnout
|
settings.mirror_settings.direction.pull=Natáhnout
|
||||||
settings.mirror_settings.direction.push=Nahrát
|
settings.mirror_settings.direction.push=Nahrát
|
||||||
@ -3284,6 +3287,7 @@ monitor.queue.name=Název
|
|||||||
monitor.queue.type=Typ
|
monitor.queue.type=Typ
|
||||||
monitor.queue.exemplar=Typ vzoru
|
monitor.queue.exemplar=Typ vzoru
|
||||||
monitor.queue.numberworkers=Počet workerů
|
monitor.queue.numberworkers=Počet workerů
|
||||||
|
monitor.queue.activeworkers=Aktivní workery
|
||||||
monitor.queue.maxnumberworkers=Maximální počet workerů
|
monitor.queue.maxnumberworkers=Maximální počet workerů
|
||||||
monitor.queue.numberinqueue=Číslo ve frontě
|
monitor.queue.numberinqueue=Číslo ve frontě
|
||||||
monitor.queue.review_add=Posoudit / přidat workery
|
monitor.queue.review_add=Posoudit / přidat workery
|
||||||
@ -3593,6 +3597,8 @@ status.cancelled=Zrušeno
|
|||||||
status.skipped=Přeskočeno
|
status.skipped=Přeskočeno
|
||||||
status.blocked=Blokováno
|
status.blocked=Blokováno
|
||||||
|
|
||||||
|
runners=Runnery
|
||||||
|
runners.runner_manage_panel=Správa runnerů
|
||||||
runners.new=Vytvořit nový runner
|
runners.new=Vytvořit nový runner
|
||||||
runners.new_notice=Jak spustit runner
|
runners.new_notice=Jak spustit runner
|
||||||
runners.status=Status
|
runners.status=Status
|
||||||
@ -3619,6 +3625,7 @@ runners.delete_runner_success=Runner byl úspěšně odstraněn
|
|||||||
runners.delete_runner_failed=Odstranění runneru selhalo
|
runners.delete_runner_failed=Odstranění runneru selhalo
|
||||||
runners.delete_runner_header=Potvrdit odstranění tohoto runneru
|
runners.delete_runner_header=Potvrdit odstranění tohoto runneru
|
||||||
runners.delete_runner_notice=Pokud na tomto runneru běží úloha, bude ukončena a označena jako neúspěšná. Může dojít k přerušení vytváření pracovního postupu.
|
runners.delete_runner_notice=Pokud na tomto runneru běží úloha, bude ukončena a označena jako neúspěšná. Může dojít k přerušení vytváření pracovního postupu.
|
||||||
|
runners.none=Žádné runnery nejsou k dispozici
|
||||||
runners.status.unspecified=Neznámý
|
runners.status.unspecified=Neznámý
|
||||||
runners.status.idle=Nečinný
|
runners.status.idle=Nečinný
|
||||||
runners.status.active=Aktivní
|
runners.status.active=Aktivní
|
||||||
|
@ -1238,6 +1238,7 @@ file_view_rendered=レンダリング表示
|
|||||||
file_view_raw=Rawデータを見る
|
file_view_raw=Rawデータを見る
|
||||||
file_permalink=パーマリンク
|
file_permalink=パーマリンク
|
||||||
file_too_large=このファイルは大きすぎるため、表示できません。
|
file_too_large=このファイルは大きすぎるため、表示できません。
|
||||||
|
file_is_empty=ファイルは空です。
|
||||||
code_preview_line_from_to=%[1]d 行目から %[2]d 行目 in %[3]s
|
code_preview_line_from_to=%[1]d 行目から %[2]d 行目 in %[3]s
|
||||||
code_preview_line_in=%[1]d 行目 in %[2]s
|
code_preview_line_in=%[1]d 行目 in %[2]s
|
||||||
invisible_runes_header=このファイルには不可視のUnicode文字が含まれています
|
invisible_runes_header=このファイルには不可視のUnicode文字が含まれています
|
||||||
@ -1442,6 +1443,7 @@ issues.new.clear_assignees=担当者をクリア
|
|||||||
issues.new.no_assignees=担当者なし
|
issues.new.no_assignees=担当者なし
|
||||||
issues.new.no_reviewers=レビューアなし
|
issues.new.no_reviewers=レビューアなし
|
||||||
issues.new.blocked_user=リポジトリのオーナーがあなたをブロックしているため、イシューを作成できません。
|
issues.new.blocked_user=リポジトリのオーナーがあなたをブロックしているため、イシューを作成できません。
|
||||||
|
issues.edit.already_changed=イシューの変更を保存できません。 他のユーザーによって内容がすでに変更されているようです。 変更を上書きしないようにするため、ページを更新してからもう一度編集してください
|
||||||
issues.edit.blocked_user=投稿者またはリポジトリのオーナーがあなたをブロックしているため、内容を編集できません。
|
issues.edit.blocked_user=投稿者またはリポジトリのオーナーがあなたをブロックしているため、内容を編集できません。
|
||||||
issues.choose.get_started=始める
|
issues.choose.get_started=始める
|
||||||
issues.choose.open_external_link=オープン
|
issues.choose.open_external_link=オープン
|
||||||
@ -1757,6 +1759,7 @@ compare.compare_head=比較
|
|||||||
pulls.desc=プルリクエストとコードレビューの有効化。
|
pulls.desc=プルリクエストとコードレビューの有効化。
|
||||||
pulls.new=新しいプルリクエスト
|
pulls.new=新しいプルリクエスト
|
||||||
pulls.new.blocked_user=リポジトリのオーナーがあなたをブロックしているため、プルリクエストを作成できません。
|
pulls.new.blocked_user=リポジトリのオーナーがあなたをブロックしているため、プルリクエストを作成できません。
|
||||||
|
pulls.edit.already_changed=プルリクエストの変更を保存できません。 他のユーザーによって内容がすでに変更されているようです。 変更を上書きしないようにするため、ページを更新してからもう一度編集してください
|
||||||
pulls.view=プルリクエストを表示
|
pulls.view=プルリクエストを表示
|
||||||
pulls.compare_changes=新規プルリクエスト
|
pulls.compare_changes=新規プルリクエスト
|
||||||
pulls.allow_edits_from_maintainers=メンテナーからの編集を許可する
|
pulls.allow_edits_from_maintainers=メンテナーからの編集を許可する
|
||||||
@ -1902,6 +1905,7 @@ pulls.recently_pushed_new_branches=%[2]s 、あなたはブランチ <strong>%[1
|
|||||||
|
|
||||||
pull.deleted_branch=(削除済み):%s
|
pull.deleted_branch=(削除済み):%s
|
||||||
|
|
||||||
|
comments.edit.already_changed=コメントの変更を保存できません。 他のユーザーによって内容がすでに変更されているようです。 変更を上書きしないようにするため、ページを更新してからもう一度編集してください
|
||||||
|
|
||||||
milestones.new=新しいマイルストーン
|
milestones.new=新しいマイルストーン
|
||||||
milestones.closed=%s にクローズ
|
milestones.closed=%s にクローズ
|
||||||
|
@ -1238,6 +1238,7 @@ file_view_rendered=Ver resultado processado
|
|||||||
file_view_raw=Ver em bruto
|
file_view_raw=Ver em bruto
|
||||||
file_permalink=Ligação permanente
|
file_permalink=Ligação permanente
|
||||||
file_too_large=O ficheiro é demasiado grande para ser apresentado.
|
file_too_large=O ficheiro é demasiado grande para ser apresentado.
|
||||||
|
file_is_empty=O ficheiro está vazio.
|
||||||
code_preview_line_from_to=Linhas %[1]d até %[2]d em %[3]s
|
code_preview_line_from_to=Linhas %[1]d até %[2]d em %[3]s
|
||||||
code_preview_line_in=Linha %[1]d em %[2]s
|
code_preview_line_in=Linha %[1]d em %[2]s
|
||||||
invisible_runes_header=`Este ficheiro contém caracteres Unicode invisíveis`
|
invisible_runes_header=`Este ficheiro contém caracteres Unicode invisíveis`
|
||||||
@ -1554,7 +1555,9 @@ issues.no_content=Nenhuma descrição fornecida.
|
|||||||
issues.close=Encerrar questão
|
issues.close=Encerrar questão
|
||||||
issues.comment_pull_merged_at=cometimento %[1]s integrado em %[2]s %[3]s
|
issues.comment_pull_merged_at=cometimento %[1]s integrado em %[2]s %[3]s
|
||||||
issues.comment_manually_pull_merged_at=cometimento %[1]s integrado manualmente em %[2]s %[3]s
|
issues.comment_manually_pull_merged_at=cometimento %[1]s integrado manualmente em %[2]s %[3]s
|
||||||
|
issues.close_comment_issue=Fechar com comentário
|
||||||
issues.reopen_issue=Reabrir
|
issues.reopen_issue=Reabrir
|
||||||
|
issues.reopen_comment_issue=Reabrir com comentário
|
||||||
issues.create_comment=Comentar
|
issues.create_comment=Comentar
|
||||||
issues.comment.blocked_user=Não pode criar ou editar o comentário porque foi bloqueado/a pelo remetente ou pelo/a proprietário/a do repositório.
|
issues.comment.blocked_user=Não pode criar ou editar o comentário porque foi bloqueado/a pelo remetente ou pelo/a proprietário/a do repositório.
|
||||||
issues.closed_at=`encerrou esta questão <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
issues.closed_at=`encerrou esta questão <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||||
|
@ -588,6 +588,8 @@ func CommonRoutes() *web.Route {
|
|||||||
r.Get("/prerelease_specs.4.8.gz", rubygems.EnumeratePackagesPreRelease)
|
r.Get("/prerelease_specs.4.8.gz", rubygems.EnumeratePackagesPreRelease)
|
||||||
r.Get("/quick/Marshal.4.8/{filename}", rubygems.ServePackageSpecification)
|
r.Get("/quick/Marshal.4.8/{filename}", rubygems.ServePackageSpecification)
|
||||||
r.Get("/gems/{filename}", rubygems.DownloadPackageFile)
|
r.Get("/gems/{filename}", rubygems.DownloadPackageFile)
|
||||||
|
r.Get("/info/{packagename}", rubygems.GetPackageInfo)
|
||||||
|
r.Get("/versions", rubygems.GetAllPackagesVersions)
|
||||||
r.Group("/api/v1/gems", func() {
|
r.Group("/api/v1/gems", func() {
|
||||||
r.Post("/", rubygems.UploadPackageFile)
|
r.Post("/", rubygems.UploadPackageFile)
|
||||||
r.Delete("/yank", rubygems.DeletePackage)
|
r.Delete("/yank", rubygems.DeletePackage)
|
||||||
|
@ -117,7 +117,7 @@ func apiErrorDefined(ctx *context.Context, err *namedError) {
|
|||||||
|
|
||||||
func apiUnauthorizedError(ctx *context.Context) {
|
func apiUnauthorizedError(ctx *context.Context) {
|
||||||
// container registry requires that the "/v2" must be in the root, so the sub-path in AppURL should be removed
|
// container registry requires that the "/v2" must be in the root, so the sub-path in AppURL should be removed
|
||||||
realmURL := strings.TrimSuffix(httplib.GuessCurrentAppURL(ctx), setting.AppSubURL+"/") + "/v2/token"
|
realmURL := httplib.GuessCurrentHostURL(ctx) + "/v2/token"
|
||||||
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+realmURL+`",service="container_registry",scope="*"`)
|
ctx.Resp.Header().Add("WWW-Authenticate", `Bearer realm="`+realmURL+`",service="container_registry",scope="*"`)
|
||||||
apiErrorDefined(ctx, errUnauthorized)
|
apiErrorDefined(ctx, errUnauthorized)
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ package rubygems
|
|||||||
import (
|
import (
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"compress/zlib"
|
"compress/zlib"
|
||||||
|
"crypto/md5"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -227,12 +228,7 @@ func UploadPackageFile(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var filename string
|
filename := makeGemFullFileName(rp.Name, rp.Version, rp.Metadata.Platform)
|
||||||
if rp.Metadata.Platform == "" || rp.Metadata.Platform == "ruby" {
|
|
||||||
filename = strings.ToLower(fmt.Sprintf("%s-%s.gem", rp.Name, rp.Version))
|
|
||||||
} else {
|
|
||||||
filename = strings.ToLower(fmt.Sprintf("%s-%s-%s.gem", rp.Name, rp.Version, rp.Metadata.Platform))
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = packages_service.CreatePackageAndAddFile(
|
_, _, err = packages_service.CreatePackageAndAddFile(
|
||||||
ctx,
|
ctx,
|
||||||
@ -300,6 +296,136 @@ func DeletePackage(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetPackageInfo returns a custom text based format for the single rubygem with a line for each version of the rubygem
|
||||||
|
// ref: https://guides.rubygems.org/rubygems-org-compact-index-api/
|
||||||
|
func GetPackageInfo(ctx *context.Context) {
|
||||||
|
packageName := ctx.Params("packagename")
|
||||||
|
versions, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, packageName)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(versions) == 0 {
|
||||||
|
apiError(ctx, http.StatusNotFound, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
infoContent, err := makePackageInfo(ctx, versions)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.PlainText(http.StatusOK, infoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllPackagesVersions returns a custom text based format containing information about all versions of all rubygems.
|
||||||
|
// ref: https://guides.rubygems.org/rubygems-org-compact-index-api/
|
||||||
|
func GetAllPackagesVersions(ctx *context.Context) {
|
||||||
|
packages, err := packages_model.GetPackagesByType(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
out := &strings.Builder{}
|
||||||
|
out.WriteString("---\n")
|
||||||
|
for _, pkg := range packages {
|
||||||
|
versions, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, pkg.Name)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(versions) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := makePackageInfo(ctx, versions)
|
||||||
|
if err != nil {
|
||||||
|
apiError(ctx, http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// format: RUBYGEM [-]VERSION_PLATFORM[,VERSION_PLATFORM],...] MD5
|
||||||
|
_, _ = fmt.Fprintf(out, "%s ", pkg.Name)
|
||||||
|
for i, v := range versions {
|
||||||
|
sep := util.Iif(i == len(versions)-1, "", ",")
|
||||||
|
_, _ = fmt.Fprintf(out, "%s%s", v.Version, sep)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(out, " %x\n", md5.Sum([]byte(info)))
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.PlainText(http.StatusOK, out.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func writePackageVersionRequirements(prefix string, reqs []rubygems_module.VersionRequirement, out *strings.Builder) {
|
||||||
|
out.WriteString(prefix)
|
||||||
|
if len(reqs) == 0 {
|
||||||
|
reqs = []rubygems_module.VersionRequirement{{Restriction: ">=", Version: "0"}}
|
||||||
|
}
|
||||||
|
for i, req := range reqs {
|
||||||
|
sep := util.Iif(i == 0, "", "&")
|
||||||
|
_, _ = fmt.Fprintf(out, "%s%s %s", sep, req.Restriction, req.Version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion) (string, error) {
|
||||||
|
// format: VERSION[-PLATFORM] [DEPENDENCY[,DEPENDENCY,...]]|REQUIREMENT[,REQUIREMENT,...]
|
||||||
|
// DEPENDENCY: GEM:CONSTRAINT[&CONSTRAINT]
|
||||||
|
// REQUIREMENT: KEY:VALUE (always contains "checksum")
|
||||||
|
pd, err := packages_model.GetPackageDescriptor(ctx, version)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := pd.Metadata.(*rubygems_module.Metadata)
|
||||||
|
fullFilename := makeGemFullFileName(pd.Package.Name, version.Version, metadata.Platform)
|
||||||
|
file, err := packages_model.GetFileForVersionByName(ctx, version.ID, fullFilename, "")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
blob, err := packages_model.GetBlobByID(ctx, file.BlobID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := &strings.Builder{}
|
||||||
|
buf.WriteString(version.Version)
|
||||||
|
buf.WriteByte(' ')
|
||||||
|
for i, dep := range metadata.RuntimeDependencies {
|
||||||
|
sep := util.Iif(i == 0, "", ",")
|
||||||
|
writePackageVersionRequirements(fmt.Sprintf("%s%s:", sep, dep.Name), dep.Version, buf)
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(buf, "|checksum:%s", blob.HashSHA256)
|
||||||
|
if len(metadata.RequiredRubyVersion) != 0 {
|
||||||
|
writePackageVersionRequirements(",ruby:", metadata.RequiredRubyVersion, buf)
|
||||||
|
}
|
||||||
|
if len(metadata.RequiredRubygemsVersion) != 0 {
|
||||||
|
writePackageVersionRequirements(",rubygems:", metadata.RequiredRubygemsVersion, buf)
|
||||||
|
}
|
||||||
|
return buf.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion) (string, error) {
|
||||||
|
ret := "---\n"
|
||||||
|
for _, v := range versions {
|
||||||
|
dep, err := makePackageVersionDependency(ctx, v)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
ret += dep + "\n"
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeGemFullFileName(gemName, version, platform string) string {
|
||||||
|
var basename string
|
||||||
|
if platform == "" || platform == "ruby" {
|
||||||
|
basename = fmt.Sprintf("%s-%s", gemName, version)
|
||||||
|
} else {
|
||||||
|
basename = fmt.Sprintf("%s-%s-%s", gemName, version, platform)
|
||||||
|
}
|
||||||
|
return strings.ToLower(basename) + ".gem"
|
||||||
|
}
|
||||||
|
|
||||||
func getVersionsByFilename(ctx *context.Context, filename string) ([]*packages_model.PackageVersion, error) {
|
func getVersionsByFilename(ctx *context.Context, filename string) ([]*packages_model.PackageVersion, error) {
|
||||||
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
||||||
OwnerID: ctx.Package.Owner.ID,
|
OwnerID: ctx.Package.Owner.ID,
|
||||||
|
@ -1168,6 +1168,15 @@ func Routes() *web.Route {
|
|||||||
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag)
|
m.Post("", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, bind(api.CreateTagOption{}), repo.CreateTag)
|
||||||
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
|
m.Delete("/*", reqToken(), reqRepoWriter(unit.TypeCode), mustNotBeArchived, repo.DeleteTag)
|
||||||
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
|
}, reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(true))
|
||||||
|
m.Group("/tag_protections", func() {
|
||||||
|
m.Combo("").Get(repo.ListTagProtection).
|
||||||
|
Post(bind(api.CreateTagProtectionOption{}), mustNotBeArchived, repo.CreateTagProtection)
|
||||||
|
m.Group("/{id}", func() {
|
||||||
|
m.Combo("").Get(repo.GetTagProtection).
|
||||||
|
Patch(bind(api.EditTagProtectionOption{}), mustNotBeArchived, repo.EditTagProtection).
|
||||||
|
Delete(repo.DeleteTagProtection)
|
||||||
|
})
|
||||||
|
}, reqToken(), reqAdmin())
|
||||||
m.Group("/actions", func() {
|
m.Group("/actions", func() {
|
||||||
m.Get("/tasks", repo.ListActionTasks)
|
m.Get("/tasks", repo.ListActionTasks)
|
||||||
}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
|
}, reqRepoReader(unit.TypeActions), context.ReferencesGitRepo(true))
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
go_context "context"
|
go_context "context"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -19,36 +20,40 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const AppURL = "http://localhost:3000/"
|
||||||
AppURL = "http://localhost:3000/"
|
|
||||||
Repo = "gogits/gogs"
|
|
||||||
FullURL = AppURL + Repo + "/"
|
|
||||||
)
|
|
||||||
|
|
||||||
func testRenderMarkup(t *testing.T, mode, filePath, text, responseBody string, responseCode int) {
|
func testRenderMarkup(t *testing.T, mode string, wiki bool, filePath, text, expectedBody string, expectedCode int) {
|
||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
|
context := "/gogits/gogs"
|
||||||
|
if !wiki {
|
||||||
|
context += path.Join("/src/branch/main", path.Dir(filePath))
|
||||||
|
}
|
||||||
options := api.MarkupOption{
|
options := api.MarkupOption{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Text: text,
|
Text: text,
|
||||||
Context: Repo,
|
Context: context,
|
||||||
Wiki: true,
|
Wiki: wiki,
|
||||||
FilePath: filePath,
|
FilePath: filePath,
|
||||||
}
|
}
|
||||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
|
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
|
||||||
web.SetForm(ctx, &options)
|
web.SetForm(ctx, &options)
|
||||||
Markup(ctx)
|
Markup(ctx)
|
||||||
assert.Equal(t, responseBody, resp.Body.String())
|
assert.Equal(t, expectedBody, resp.Body.String())
|
||||||
assert.Equal(t, responseCode, resp.Code)
|
assert.Equal(t, expectedCode, resp.Code)
|
||||||
resp.Body.Reset()
|
resp.Body.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRenderMarkdown(t *testing.T, mode, text, responseBody string, responseCode int) {
|
func testRenderMarkdown(t *testing.T, mode string, wiki bool, text, responseBody string, responseCode int) {
|
||||||
setting.AppURL = AppURL
|
setting.AppURL = AppURL
|
||||||
|
context := "/gogits/gogs"
|
||||||
|
if !wiki {
|
||||||
|
context += "/src/branch/main"
|
||||||
|
}
|
||||||
options := api.MarkdownOption{
|
options := api.MarkdownOption{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
Text: text,
|
Text: text,
|
||||||
Context: Repo,
|
Context: context,
|
||||||
Wiki: true,
|
Wiki: wiki,
|
||||||
}
|
}
|
||||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||||
web.SetForm(ctx, &options)
|
web.SetForm(ctx, &options)
|
||||||
@ -65,7 +70,7 @@ func TestAPI_RenderGFM(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
testCasesCommon := []string{
|
testCasesWiki := []string{
|
||||||
// dear imgui wiki markdown extract: special wiki syntax
|
// dear imgui wiki markdown extract: special wiki syntax
|
||||||
`Wiki! Enjoy :)
|
`Wiki! Enjoy :)
|
||||||
- [[Links, Language bindings, Engine bindings|Links]]
|
- [[Links, Language bindings, Engine bindings|Links]]
|
||||||
@ -74,20 +79,20 @@ func TestAPI_RenderGFM(t *testing.T) {
|
|||||||
// rendered
|
// rendered
|
||||||
`<p>Wiki! Enjoy :)</p>
|
`<p>Wiki! Enjoy :)</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="` + FullURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
<li><a href="http://localhost:3000/gogits/gogs/wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
||||||
<li><a href="` + FullURL + `wiki/Tips" rel="nofollow">Tips</a></li>
|
<li><a href="http://localhost:3000/gogits/gogs/wiki/Tips" rel="nofollow">Tips</a></li>
|
||||||
<li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li>
|
<li>Bezier widget (by <a href="http://localhost:3000/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="https://github.com/ocornut/imgui/issues/786" rel="nofollow">https://github.com/ocornut/imgui/issues/786</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
`,
|
`,
|
||||||
// Guard wiki sidebar: special syntax
|
// Guard wiki sidebar: special syntax
|
||||||
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
|
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
|
||||||
// rendered
|
// rendered
|
||||||
`<p><a href="` + FullURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
|
`<p><a href="http://localhost:3000/gogits/gogs/wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
|
||||||
`,
|
`,
|
||||||
// special syntax
|
// special syntax
|
||||||
`[[Name|Link]]`,
|
`[[Name|Link]]`,
|
||||||
// rendered
|
// rendered
|
||||||
`<p><a href="` + FullURL + `wiki/Link" rel="nofollow">Name</a></p>
|
`<p><a href="http://localhost:3000/gogits/gogs/wiki/Link" rel="nofollow">Name</a></p>
|
||||||
`,
|
`,
|
||||||
// empty
|
// empty
|
||||||
``,
|
``,
|
||||||
@ -95,7 +100,7 @@ func TestAPI_RenderGFM(t *testing.T) {
|
|||||||
``,
|
``,
|
||||||
}
|
}
|
||||||
|
|
||||||
testCasesDocument := []string{
|
testCasesWikiDocument := []string{
|
||||||
// wine-staging wiki home extract: special wiki syntax, images
|
// wine-staging wiki home extract: special wiki syntax, images
|
||||||
`## What is Wine Staging?
|
`## What is Wine Staging?
|
||||||
**Wine Staging** on website [wine-staging.com](http://wine-staging.com).
|
**Wine Staging** on website [wine-staging.com](http://wine-staging.com).
|
||||||
@ -111,31 +116,48 @@ Here are some links to the most important topics. You can find the full list of
|
|||||||
<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
|
<p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
|
||||||
<h2 id="user-content-quick-links">Quick Links</h2>
|
<h2 id="user-content-quick-links">Quick Links</h2>
|
||||||
<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
|
<p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
|
||||||
<p><a href="` + FullURL + `wiki/Configuration" rel="nofollow">Configuration</a>
|
<p><a href="http://localhost:3000/gogits/gogs/wiki/Configuration" rel="nofollow">Configuration</a>
|
||||||
<a href="` + FullURL + `wiki/raw/images/icon-bug.png" rel="nofollow"><img src="` + FullURL + `wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
|
<a href="http://localhost:3000/gogits/gogs/wiki/raw/images/icon-bug.png" rel="nofollow"><img src="http://localhost:3000/gogits/gogs/wiki/raw/images/icon-bug.png" title="icon-bug.png" alt="images/icon-bug.png"/></a></p>
|
||||||
`,
|
`,
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(testCasesCommon); i += 2 {
|
for i := 0; i < len(testCasesWiki); i += 2 {
|
||||||
text := testCasesCommon[i]
|
text := testCasesWiki[i]
|
||||||
response := testCasesCommon[i+1]
|
response := testCasesWiki[i+1]
|
||||||
testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
|
testRenderMarkdown(t, "gfm", true, text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
|
testRenderMarkup(t, "gfm", true, "", text, response, http.StatusOK)
|
||||||
testRenderMarkdown(t, "comment", text, response, http.StatusOK)
|
testRenderMarkdown(t, "comment", true, text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "comment", "", text, response, http.StatusOK)
|
testRenderMarkup(t, "comment", true, "", text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
|
testRenderMarkup(t, "file", true, "path/test.md", text, response, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(testCasesDocument); i += 2 {
|
for i := 0; i < len(testCasesWikiDocument); i += 2 {
|
||||||
text := testCasesDocument[i]
|
text := testCasesWikiDocument[i]
|
||||||
response := testCasesDocument[i+1]
|
response := testCasesWikiDocument[i+1]
|
||||||
testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
|
testRenderMarkdown(t, "gfm", true, text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
|
testRenderMarkup(t, "gfm", true, "", text, response, http.StatusOK)
|
||||||
testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
|
testRenderMarkup(t, "file", true, "path/test.md", text, response, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
testRenderMarkup(t, "file", "path/test.unknown", "## Test", "Unsupported render extension: .unknown\n", http.StatusUnprocessableEntity)
|
input := "[Link](test.md)\n![Image](image.png)"
|
||||||
testRenderMarkup(t, "unknown", "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
|
testRenderMarkdown(t, "gfm", false, input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
|
||||||
|
<a href="http://localhost:3000/gogits/gogs/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/image.png" alt="Image"/></a></p>
|
||||||
|
`, http.StatusOK)
|
||||||
|
|
||||||
|
testRenderMarkdown(t, "gfm", false, input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
|
||||||
|
<a href="http://localhost:3000/gogits/gogs/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/image.png" alt="Image"/></a></p>
|
||||||
|
`, http.StatusOK)
|
||||||
|
|
||||||
|
testRenderMarkup(t, "gfm", false, "", input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/test.md" rel="nofollow">Link</a>
|
||||||
|
<a href="http://localhost:3000/gogits/gogs/media/branch/main/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/image.png" alt="Image"/></a></p>
|
||||||
|
`, http.StatusOK)
|
||||||
|
|
||||||
|
testRenderMarkup(t, "file", false, "path/new-file.md", input, `<p><a href="http://localhost:3000/gogits/gogs/src/branch/main/path/test.md" rel="nofollow">Link</a>
|
||||||
|
<a href="http://localhost:3000/gogits/gogs/media/branch/main/path/image.png" target="_blank" rel="nofollow noopener"><img src="http://localhost:3000/gogits/gogs/media/branch/main/path/image.png" alt="Image"/></a></p>
|
||||||
|
`, http.StatusOK)
|
||||||
|
|
||||||
|
testRenderMarkup(t, "file", true, "path/test.unknown", "## Test", "Unsupported render extension: .unknown\n", http.StatusUnprocessableEntity)
|
||||||
|
testRenderMarkup(t, "unknown", true, "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
var simpleCases = []string{
|
var simpleCases = []string{
|
||||||
@ -160,7 +182,7 @@ func TestAPI_RenderSimple(t *testing.T) {
|
|||||||
options := api.MarkdownOption{
|
options := api.MarkdownOption{
|
||||||
Mode: "markdown",
|
Mode: "markdown",
|
||||||
Text: "",
|
Text: "",
|
||||||
Context: Repo,
|
Context: "/gogits/gogs",
|
||||||
}
|
}
|
||||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||||
for i := 0; i < len(simpleCases); i += 2 {
|
for i := 0; i < len(simpleCases); i += 2 {
|
||||||
|
@ -184,7 +184,7 @@ func Search(ctx *context.APIContext) {
|
|||||||
if len(sortOrder) == 0 {
|
if len(sortOrder) == 0 {
|
||||||
sortOrder = "asc"
|
sortOrder = "asc"
|
||||||
}
|
}
|
||||||
if searchModeMap, ok := repo_model.SearchOrderByMap[sortOrder]; ok {
|
if searchModeMap, ok := repo_model.OrderByMap[sortOrder]; ok {
|
||||||
if orderBy, ok := searchModeMap[sortMode]; ok {
|
if orderBy, ok := searchModeMap[sortMode]; ok {
|
||||||
opts.OrderBy = orderBy
|
opts.OrderBy = orderBy
|
||||||
} else {
|
} else {
|
||||||
|
@ -7,9 +7,13 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
|
"code.gitea.io/gitea/models/organization"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
@ -287,3 +291,349 @@ func DeleteTag(ctx *context.APIContext) {
|
|||||||
|
|
||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListTagProtection lists tag protections for a repo
|
||||||
|
func ListTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/tag_protections repository repoListTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: List tag protections for a repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/TagProtectionList"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
pts, err := git_model.GetProtectedTags(ctx, repo.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTags", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiPts := make([]*api.TagProtection, len(pts))
|
||||||
|
for i := range pts {
|
||||||
|
apiPts[i] = convert.ToTagProtection(ctx, pts[i], repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, apiPts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTagProtection gets a tag protection
|
||||||
|
func GetTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/tag_protections/{id} repository repoGetTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: Get a specific tag protection for the repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: id of the tag protect to get
|
||||||
|
// type: integer
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/TagProtection"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
id := ctx.ParamsInt64(":id")
|
||||||
|
pt, err := git_model.GetProtectedTagByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || repo.ID != pt.RepoID {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToTagProtection(ctx, pt, repo))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateTagProtection creates a tag protection for a repo
|
||||||
|
func CreateTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /repos/{owner}/{repo}/tag_protections repository repoCreateTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: Create a tag protections for a repository
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/CreateTagProtectionOption"
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// "$ref": "#/responses/TagProtection"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
// "423":
|
||||||
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
form := web.GetForm(ctx).(*api.CreateTagProtectionOption)
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
|
||||||
|
namePattern := strings.TrimSpace(form.NamePattern)
|
||||||
|
if namePattern == "" {
|
||||||
|
ctx.Error(http.StatusBadRequest, "name_pattern are empty", "name_pattern are empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(form.WhitelistUsernames) == 0 && len(form.WhitelistTeams) == 0 {
|
||||||
|
ctx.Error(http.StatusBadRequest, "both whitelist_usernames and whitelist_teams are empty", "both whitelist_usernames and whitelist_teams are empty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pt, err := git_model.GetProtectedTagByNamePattern(ctx, repo.ID, namePattern)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectTagOfRepo", err)
|
||||||
|
return
|
||||||
|
} else if pt != nil {
|
||||||
|
ctx.Error(http.StatusForbidden, "Create tag protection", "Tag protection already exist")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var whitelistUsers, whitelistTeams []int64
|
||||||
|
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.WhitelistUsernames, false)
|
||||||
|
if err != nil {
|
||||||
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if repo.Owner.IsOrganization() {
|
||||||
|
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.WhitelistTeams, false)
|
||||||
|
if err != nil {
|
||||||
|
if organization.IsErrTeamNotExist(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protectTag := &git_model.ProtectedTag{
|
||||||
|
RepoID: repo.ID,
|
||||||
|
NamePattern: strings.TrimSpace(namePattern),
|
||||||
|
AllowlistUserIDs: whitelistUsers,
|
||||||
|
AllowlistTeamIDs: whitelistTeams,
|
||||||
|
}
|
||||||
|
if err := git_model.InsertProtectedTag(ctx, protectTag); err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "InsertProtectedTag", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pt, err = git_model.GetProtectedTagByID(ctx, protectTag.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || pt.RepoID != repo.ID {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "New tag protection not found", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusCreated, convert.ToTagProtection(ctx, pt, repo))
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditTagProtection edits a tag protection for a repo
|
||||||
|
func EditTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PATCH /repos/{owner}/{repo}/tag_protections/{id} repository repoEditTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: Edit a tag protections for a repository. Only fields that are set will be changed
|
||||||
|
// consumes:
|
||||||
|
// - application/json
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: id of protected tag
|
||||||
|
// type: integer
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/EditTagProtectionOption"
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/TagProtection"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "422":
|
||||||
|
// "$ref": "#/responses/validationError"
|
||||||
|
// "423":
|
||||||
|
// "$ref": "#/responses/repoArchivedError"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
form := web.GetForm(ctx).(*api.EditTagProtectionOption)
|
||||||
|
|
||||||
|
id := ctx.ParamsInt64(":id")
|
||||||
|
pt, err := git_model.GetProtectedTagByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || pt.RepoID != repo.ID {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.NamePattern != nil {
|
||||||
|
pt.NamePattern = *form.NamePattern
|
||||||
|
}
|
||||||
|
|
||||||
|
var whitelistUsers, whitelistTeams []int64
|
||||||
|
if form.WhitelistTeams != nil {
|
||||||
|
if repo.Owner.IsOrganization() {
|
||||||
|
whitelistTeams, err = organization.GetTeamIDsByNames(ctx, repo.OwnerID, form.WhitelistTeams, false)
|
||||||
|
if err != nil {
|
||||||
|
if organization.IsErrTeamNotExist(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "Team does not exist", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetTeamIDsByNames", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pt.AllowlistTeamIDs = whitelistTeams
|
||||||
|
}
|
||||||
|
|
||||||
|
if form.WhitelistUsernames != nil {
|
||||||
|
whitelistUsers, err = user_model.GetUserIDsByNames(ctx, form.WhitelistUsernames, false)
|
||||||
|
if err != nil {
|
||||||
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
ctx.Error(http.StatusUnprocessableEntity, "User does not exist", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetUserIDsByNames", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pt.AllowlistUserIDs = whitelistUsers
|
||||||
|
}
|
||||||
|
|
||||||
|
err = git_model.UpdateProtectedTag(ctx, pt)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "UpdateProtectedTag", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pt, err = git_model.GetProtectedTagByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || pt.RepoID != repo.ID {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "New tag protection not found", "New tag protection not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToTagProtection(ctx, pt, repo))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteTagProtection
|
||||||
|
func DeleteTagProtection(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /repos/{owner}/{repo}/tag_protections/{id} repository repoDeleteTagProtection
|
||||||
|
// ---
|
||||||
|
// summary: Delete a specific tag protection for the repository
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: id of protected tag
|
||||||
|
// type: integer
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
repo := ctx.Repo.Repository
|
||||||
|
id := ctx.ParamsInt64(":id")
|
||||||
|
pt, err := git_model.GetProtectedTagByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetProtectedTagByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pt == nil || pt.RepoID != repo.ID {
|
||||||
|
ctx.NotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = git_model.DeleteProtectedTag(ctx, pt)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "DeleteProtectedTag", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
@ -170,6 +170,12 @@ type swaggerParameterBodies struct {
|
|||||||
// in:body
|
// in:body
|
||||||
CreateTagOption api.CreateTagOption
|
CreateTagOption api.CreateTagOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
CreateTagProtectionOption api.CreateTagProtectionOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
EditTagProtectionOption api.EditTagProtectionOption
|
||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
CreateAccessTokenOption api.CreateAccessTokenOption
|
CreateAccessTokenOption api.CreateAccessTokenOption
|
||||||
|
|
||||||
|
@ -70,6 +70,20 @@ type swaggerResponseAnnotatedTag struct {
|
|||||||
Body api.AnnotatedTag `json:"body"`
|
Body api.AnnotatedTag `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TagProtectionList
|
||||||
|
// swagger:response TagProtectionList
|
||||||
|
type swaggerResponseTagProtectionList struct {
|
||||||
|
// in:body
|
||||||
|
Body []api.TagProtection `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagProtection
|
||||||
|
// swagger:response TagProtection
|
||||||
|
type swaggerResponseTagProtection struct {
|
||||||
|
// in:body
|
||||||
|
Body api.TagProtection `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
// Reference
|
// Reference
|
||||||
// swagger:response Reference
|
// swagger:response Reference
|
||||||
type swaggerResponseReference struct {
|
type swaggerResponseReference struct {
|
||||||
|
@ -7,26 +7,31 @@ package common
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/markup/markdown"
|
"code.gitea.io/gitea/modules/markup/markdown"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
|
||||||
"mvdan.cc/xurls/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RenderMarkup renders markup text for the /markup and /markdown endpoints
|
// RenderMarkup renders markup text for the /markup and /markdown endpoints
|
||||||
func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPrefix, filePath string, wiki bool) {
|
func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPathContext, filePath string, wiki bool) {
|
||||||
var markupType string
|
// urlPathContext format is "/subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}"
|
||||||
relativePath := ""
|
// filePath is the path of the file to render if the end user is trying to preview a repo file (mode == "file")
|
||||||
|
// filePath will be used as RenderContext.RelativePath
|
||||||
|
|
||||||
if len(text) == 0 {
|
// for example, when previewing file "/gitea/owner/repo/src/branch/features/feat-123/doc/CHANGE.md", then filePath is "doc/CHANGE.md"
|
||||||
_, _ = ctx.Write([]byte(""))
|
// and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
|
||||||
return
|
|
||||||
|
var markupType, relativePath string
|
||||||
|
|
||||||
|
links := markup.Links{AbsolutePrefix: true}
|
||||||
|
if urlPathContext != "" {
|
||||||
|
links.Base = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mode {
|
switch mode {
|
||||||
@ -34,36 +39,35 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
|
|||||||
// Raw markdown
|
// Raw markdown
|
||||||
if err := markdown.RenderRaw(&markup.RenderContext{
|
if err := markdown.RenderRaw(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Links: markup.Links{
|
Links: links,
|
||||||
AbsolutePrefix: true,
|
|
||||||
Base: urlPrefix,
|
|
||||||
},
|
|
||||||
}, strings.NewReader(text), ctx.Resp); err != nil {
|
}, strings.NewReader(text), ctx.Resp); err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
case "comment":
|
case "comment":
|
||||||
// Comment as markdown
|
// Issue & comment content
|
||||||
markupType = markdown.MarkupName
|
markupType = markdown.MarkupName
|
||||||
case "gfm":
|
case "gfm":
|
||||||
// Github Flavored Markdown as document
|
// GitHub Flavored Markdown
|
||||||
markupType = markdown.MarkupName
|
markupType = markdown.MarkupName
|
||||||
case "file":
|
case "file":
|
||||||
// File as document based on file extension
|
markupType = "" // render the repo file content by its extension
|
||||||
markupType = ""
|
|
||||||
relativePath = filePath
|
relativePath = filePath
|
||||||
default:
|
default:
|
||||||
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
|
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(setting.AppSubURL+"/", urlPrefix) {
|
fields := strings.SplitN(strings.TrimPrefix(urlPathContext, setting.AppSubURL+"/"), "/", 5)
|
||||||
// check if urlPrefix is already set to a URL
|
if len(fields) == 5 && fields[2] == "src" && (fields[3] == "branch" || fields[3] == "commit" || fields[3] == "tag") {
|
||||||
linkRegex, _ := xurls.StrictMatchingScheme("https?://")
|
// absolute base prefix is something like "https://host/subpath/{user}/{repo}"
|
||||||
m := linkRegex.FindStringIndex(urlPrefix)
|
absoluteBasePrefix := fmt.Sprintf("%s%s/%s", httplib.GuessCurrentAppURL(ctx), fields[0], fields[1])
|
||||||
if m == nil {
|
|
||||||
urlPrefix = util.URLJoin(setting.AppURL, urlPrefix)
|
fileDir := path.Dir(filePath) // it is "doc" if filePath is "doc/CHANGE.md"
|
||||||
}
|
refPath := strings.Join(fields[3:], "/") // it is "branch/features/feat-12/doc"
|
||||||
|
refPath = strings.TrimSuffix(refPath, "/"+fileDir) // now we get the correct branch path: "branch/features/feat-12"
|
||||||
|
|
||||||
|
links = markup.Links{AbsolutePrefix: true, Base: absoluteBasePrefix, BranchPath: refPath, TreePath: fileDir}
|
||||||
}
|
}
|
||||||
|
|
||||||
meta := map[string]string{}
|
meta := map[string]string{}
|
||||||
@ -83,10 +87,7 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
|
|||||||
if err := markup.Render(&markup.RenderContext{
|
if err := markup.Render(&markup.RenderContext{
|
||||||
Ctx: ctx,
|
Ctx: ctx,
|
||||||
Repo: repoCtx,
|
Repo: repoCtx,
|
||||||
Links: markup.Links{
|
Links: links,
|
||||||
AbsolutePrefix: true,
|
|
||||||
Base: urlPrefix,
|
|
||||||
},
|
|
||||||
Metas: meta,
|
Metas: meta,
|
||||||
IsWiki: wiki,
|
IsWiki: wiki,
|
||||||
Type: markupType,
|
Type: markupType,
|
||||||
|
@ -25,7 +25,7 @@ import (
|
|||||||
// ProtocolMiddlewares returns HTTP protocol related middlewares, and it provides a global panic recovery
|
// ProtocolMiddlewares returns HTTP protocol related middlewares, and it provides a global panic recovery
|
||||||
func ProtocolMiddlewares() (handlers []any) {
|
func ProtocolMiddlewares() (handlers []any) {
|
||||||
// first, normalize the URL path
|
// first, normalize the URL path
|
||||||
handlers = append(handlers, stripSlashesMiddleware)
|
handlers = append(handlers, normalizeRequestPathMiddleware)
|
||||||
|
|
||||||
// prepare the ContextData and panic recovery
|
// prepare the ContextData and panic recovery
|
||||||
handlers = append(handlers, func(next http.Handler) http.Handler {
|
handlers = append(handlers, func(next http.Handler) http.Handler {
|
||||||
@ -75,9 +75,9 @@ func ProtocolMiddlewares() (handlers []any) {
|
|||||||
return handlers
|
return handlers
|
||||||
}
|
}
|
||||||
|
|
||||||
func stripSlashesMiddleware(next http.Handler) http.Handler {
|
func normalizeRequestPathMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||||
// First of all escape the URL RawPath to ensure that all routing is done using a correctly escaped URL
|
// escape the URL RawPath to ensure that all routing is done using a correctly escaped URL
|
||||||
req.URL.RawPath = req.URL.EscapedPath()
|
req.URL.RawPath = req.URL.EscapedPath()
|
||||||
|
|
||||||
urlPath := req.URL.RawPath
|
urlPath := req.URL.RawPath
|
||||||
@ -86,19 +86,42 @@ func stripSlashesMiddleware(next http.Handler) http.Handler {
|
|||||||
urlPath = rctx.RoutePath
|
urlPath = rctx.RoutePath
|
||||||
}
|
}
|
||||||
|
|
||||||
sanitizedPath := &strings.Builder{}
|
normalizedPath := strings.TrimRight(urlPath, "/")
|
||||||
|
// the following code block is a slow-path for replacing all repeated slashes "//" to one single "/"
|
||||||
|
// if the path doesn't have repeated slashes, then no need to execute it
|
||||||
|
if strings.Contains(normalizedPath, "//") {
|
||||||
|
buf := &strings.Builder{}
|
||||||
prevWasSlash := false
|
prevWasSlash := false
|
||||||
for _, chr := range strings.TrimRight(urlPath, "/") {
|
for _, chr := range normalizedPath {
|
||||||
if chr != '/' || !prevWasSlash {
|
if chr != '/' || !prevWasSlash {
|
||||||
sanitizedPath.WriteRune(chr)
|
buf.WriteRune(chr)
|
||||||
}
|
}
|
||||||
prevWasSlash = chr == '/'
|
prevWasSlash = chr == '/'
|
||||||
}
|
}
|
||||||
|
normalizedPath = buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if setting.UseSubURLPath {
|
||||||
|
remainingPath, ok := strings.CutPrefix(normalizedPath, setting.AppSubURL+"/")
|
||||||
|
if ok {
|
||||||
|
normalizedPath = "/" + remainingPath
|
||||||
|
} else if normalizedPath == setting.AppSubURL {
|
||||||
|
normalizedPath = "/"
|
||||||
|
} else if !strings.HasPrefix(normalizedPath+"/", "/v2/") {
|
||||||
|
// do not respond to other requests, to simulate a real sub-path environment
|
||||||
|
http.Error(resp, "404 page not found, sub-path is: "+setting.AppSubURL, http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: it's not quite clear about how req.URL and rctx.RoutePath work together.
|
||||||
|
// Fortunately, it is only used for debug purpose, we have enough time to figure it out in the future.
|
||||||
|
req.URL.RawPath = normalizedPath
|
||||||
|
req.URL.Path = normalizedPath
|
||||||
|
}
|
||||||
|
|
||||||
if rctx == nil {
|
if rctx == nil {
|
||||||
req.URL.Path = sanitizedPath.String()
|
req.URL.Path = normalizedPath
|
||||||
} else {
|
} else {
|
||||||
rctx.RoutePath = sanitizedPath.String()
|
rctx.RoutePath = normalizedPath
|
||||||
}
|
}
|
||||||
next.ServeHTTP(resp, req)
|
next.ServeHTTP(resp, req)
|
||||||
})
|
})
|
||||||
|
@ -61,7 +61,7 @@ func TestStripSlashesMiddleware(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// pass the test middleware to validate the changes
|
// pass the test middleware to validate the changes
|
||||||
handlerToTest := stripSlashesMiddleware(testMiddleware)
|
handlerToTest := normalizeRequestPathMiddleware(testMiddleware)
|
||||||
// create a mock request to use
|
// create a mock request to use
|
||||||
req := httptest.NewRequest("GET", tt.inputPath, nil)
|
req := httptest.NewRequest("GET", tt.inputPath, nil)
|
||||||
// call the handler using a mock response recorder
|
// call the handler using a mock response recorder
|
||||||
|
@ -63,7 +63,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) {
|
|||||||
sortOrder = setting.UI.ExploreDefaultSort
|
sortOrder = setting.UI.ExploreDefaultSort
|
||||||
}
|
}
|
||||||
|
|
||||||
if order, ok := repo_model.SearchOrderByFlatMap[sortOrder]; ok {
|
if order, ok := repo_model.OrderByFlatMap[sortOrder]; ok {
|
||||||
orderBy = order
|
orderBy = order
|
||||||
} else {
|
} else {
|
||||||
sortOrder = "recentupdate"
|
sortOrder = "recentupdate"
|
||||||
|
@ -120,9 +120,8 @@ func Home(ctx *context.Context) {
|
|||||||
ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
|
ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
|
||||||
ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0
|
ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0
|
||||||
|
|
||||||
prepareOrgProfileReadme(ctx)
|
if !prepareOrgProfileReadme(ctx) {
|
||||||
if ctx.Written() {
|
ctx.Data["PageIsViewRepositories"] = true
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -154,7 +153,6 @@ func Home(ctx *context.Context) {
|
|||||||
|
|
||||||
ctx.Data["Repos"] = repos
|
ctx.Data["Repos"] = repos
|
||||||
ctx.Data["Total"] = count
|
ctx.Data["Total"] = count
|
||||||
ctx.Data["PageIsViewRepositories"] = true
|
|
||||||
|
|
||||||
pager := context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5)
|
pager := context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5)
|
||||||
pager.SetDefaultParams(ctx)
|
pager.SetDefaultParams(ctx)
|
||||||
@ -164,18 +162,18 @@ func Home(ctx *context.Context) {
|
|||||||
ctx.HTML(http.StatusOK, tplOrgHome)
|
ctx.HTML(http.StatusOK, tplOrgHome)
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareOrgProfileReadme(ctx *context.Context) {
|
func prepareOrgProfileReadme(ctx *context.Context) bool {
|
||||||
profileDbRepo, profileGitRepo, profileReadme, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer)
|
profileDbRepo, profileGitRepo, profileReadme, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer)
|
||||||
defer profileClose()
|
defer profileClose()
|
||||||
ctx.Data["HasProfileReadme"] = profileReadme != nil
|
ctx.Data["HasProfileReadme"] = profileReadme != nil
|
||||||
|
|
||||||
if profileGitRepo == nil || profileReadme == nil {
|
if profileGitRepo == nil || profileReadme == nil {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
viewRepositorys := ctx.FormOptionalBool("view_repositorys")
|
viewRepositorys := ctx.FormOptionalBool("view_repositorys")
|
||||||
if viewRepositorys.Value() {
|
if viewRepositorys.Value() {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
||||||
@ -199,5 +197,5 @@ func prepareOrgProfileReadme(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["PageIsViewOverview"] = true
|
ctx.Data["PageIsViewOverview"] = true
|
||||||
ctx.HTML(http.StatusOK, tplOrgHome)
|
return true
|
||||||
}
|
}
|
||||||
|
@ -418,6 +418,7 @@ func RedirectDownload(ctx *context.Context) {
|
|||||||
tagNames := []string{vTag}
|
tagNames := []string{vTag}
|
||||||
curRepo := ctx.Repo.Repository
|
curRepo := ctx.Repo.Repository
|
||||||
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
releases, err := db.Find[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
||||||
|
IncludeDrafts: ctx.Repo.CanWrite(unit.TypeReleases),
|
||||||
RepoID: curRepo.ID,
|
RepoID: curRepo.ID,
|
||||||
TagNames: tagNames,
|
TagNames: tagNames,
|
||||||
})
|
})
|
||||||
@ -615,7 +616,7 @@ func SearchRepo(ctx *context.Context) {
|
|||||||
if len(sortOrder) == 0 {
|
if len(sortOrder) == 0 {
|
||||||
sortOrder = "asc"
|
sortOrder = "asc"
|
||||||
}
|
}
|
||||||
if searchModeMap, ok := repo_model.SearchOrderByMap[sortOrder]; ok {
|
if searchModeMap, ok := repo_model.OrderByMap[sortOrder]; ok {
|
||||||
if orderBy, ok := searchModeMap[sortMode]; ok {
|
if orderBy, ok := searchModeMap[sortMode]; ok {
|
||||||
opts.OrderBy = orderBy
|
opts.OrderBy = orderBy
|
||||||
} else {
|
} else {
|
||||||
|
@ -408,6 +408,32 @@ func ToAnnotatedTagObject(repo *repo_model.Repository, commit *git.Commit) *api.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToTagProtection convert a git.ProtectedTag to an api.TagProtection
|
||||||
|
func ToTagProtection(ctx context.Context, pt *git_model.ProtectedTag, repo *repo_model.Repository) *api.TagProtection {
|
||||||
|
readers, err := access_model.GetRepoReaders(ctx, repo)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("GetRepoReaders: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelistUsernames := getWhitelistEntities(readers, pt.AllowlistUserIDs)
|
||||||
|
|
||||||
|
teamReaders, err := organization.OrgFromUser(repo.Owner).TeamsWithAccessToRepo(ctx, repo.ID, perm.AccessModeRead)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Repo.Owner.TeamsWithAccessToRepo: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelistTeams := getWhitelistEntities(teamReaders, pt.AllowlistTeamIDs)
|
||||||
|
|
||||||
|
return &api.TagProtection{
|
||||||
|
ID: pt.ID,
|
||||||
|
NamePattern: pt.NamePattern,
|
||||||
|
WhitelistUsernames: whitelistUsernames,
|
||||||
|
WhitelistTeams: whitelistTeams,
|
||||||
|
Created: pt.CreatedUnix.AsTime(),
|
||||||
|
Updated: pt.UpdatedUnix.AsTime(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ToTopicResponse convert from models.Topic to api.TopicResponse
|
// ToTopicResponse convert from models.Topic to api.TopicResponse
|
||||||
func ToTopicResponse(topic *repo_model.Topic) *api.TopicResponse {
|
func ToTopicResponse(topic *repo_model.Topic) *api.TopicResponse {
|
||||||
return &api.TopicResponse{
|
return &api.TopicResponse{
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/references"
|
"code.gitea.io/gitea/modules/references"
|
||||||
repo_module "code.gitea.io/gitea/modules/repository"
|
repo_module "code.gitea.io/gitea/modules/repository"
|
||||||
@ -56,7 +57,7 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue
|
|||||||
issueReference = "!"
|
issueReference = "!"
|
||||||
}
|
}
|
||||||
|
|
||||||
reviewedOn := fmt.Sprintf("Reviewed-on: %s/%s", setting.AppURL, pr.Issue.Link())
|
reviewedOn := fmt.Sprintf("Reviewed-on: %s", httplib.MakeAbsoluteURL(ctx, pr.Issue.Link()))
|
||||||
reviewedBy := pr.GetApprovers(ctx)
|
reviewedBy := pr.GetApprovers(ctx)
|
||||||
|
|
||||||
if mergeStyle != "" {
|
if mergeStyle != "" {
|
||||||
|
@ -6,10 +6,13 @@ package repository
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -83,3 +86,13 @@ func TestListUnadoptedRepositories_ListOptions(t *testing.T) {
|
|||||||
assert.Equal(t, 2, count)
|
assert.Equal(t, 2, count)
|
||||||
assert.Equal(t, unadoptedList[1], repoNames[0])
|
assert.Equal(t, unadoptedList[1], repoNames[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdoptRepository(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
assert.NoError(t, unittest.CopyDir(filepath.Join(setting.RepoRootPath, "user2", "repo1.git"), filepath.Join(setting.RepoRootPath, "user2", "test-adopt.git")))
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
_, err := AdoptRepository(db.DefaultContext, user2, user2, CreateRepoOptions{Name: "test-adopt"})
|
||||||
|
assert.NoError(t, err)
|
||||||
|
repoTestAdopt := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: "test-adopt"})
|
||||||
|
assert.Equal(t, "sha1", repoTestAdopt.ObjectFormatName)
|
||||||
|
}
|
||||||
|
@ -8,11 +8,9 @@
|
|||||||
{{if .ProfileReadme}}
|
{{if .ProfileReadme}}
|
||||||
<div id="readme_profile" class="markup">{{.ProfileReadme}}</div>
|
<div id="readme_profile" class="markup">{{.ProfileReadme}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if .PageIsViewRepositories}}
|
|
||||||
{{template "shared/repo_search" .}}
|
{{template "shared/repo_search" .}}
|
||||||
{{template "explore/repo_list" .}}
|
{{template "explore/repo_list" .}}
|
||||||
{{template "base/paginate" .}}
|
{{template "base/paginate" .}}
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{if .ShowMemberAndTeamTab}}
|
{{if .ShowMemberAndTeamTab}}
|
||||||
|
336
templates/swagger/v1_json.tmpl
generated
336
templates/swagger/v1_json.tmpl
generated
@ -13797,6 +13797,233 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/tag_protections": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "List tag protections for a repository",
|
||||||
|
"operationId": "repoListTagProtection",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/TagProtectionList"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Create a tag protections for a repository",
|
||||||
|
"operationId": "repoCreateTagProtection",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/CreateTagProtectionOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"$ref": "#/responses/TagProtection"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"$ref": "#/responses/forbidden"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
},
|
||||||
|
"423": {
|
||||||
|
"$ref": "#/responses/repoArchivedError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/repos/{owner}/{repo}/tag_protections/{id}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Get a specific tag protection for the repository",
|
||||||
|
"operationId": "repoGetTagProtection",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "id of the tag protect to get",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/TagProtection"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Delete a specific tag protection for the repository",
|
||||||
|
"operationId": "repoDeleteTagProtection",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "id of protected tag",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"$ref": "#/responses/empty"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Edit a tag protections for a repository. Only fields that are set will be changed",
|
||||||
|
"operationId": "repoEditTagProtection",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"description": "id of protected tag",
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "body",
|
||||||
|
"in": "body",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/EditTagProtectionOption"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/TagProtection"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"422": {
|
||||||
|
"$ref": "#/responses/validationError"
|
||||||
|
},
|
||||||
|
"423": {
|
||||||
|
"$ref": "#/responses/repoArchivedError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/tags": {
|
"/repos/{owner}/{repo}/tags": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -19954,6 +20181,31 @@
|
|||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"CreateTagProtectionOption": {
|
||||||
|
"description": "CreateTagProtectionOption options for creating a tag protection",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name_pattern": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "NamePattern"
|
||||||
|
},
|
||||||
|
"whitelist_teams": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "WhitelistTeams"
|
||||||
|
},
|
||||||
|
"whitelist_usernames": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "WhitelistUsernames"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"CreateTeamOption": {
|
"CreateTeamOption": {
|
||||||
"description": "CreateTeamOption options for creating a team",
|
"description": "CreateTeamOption options for creating a team",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -20870,6 +21122,31 @@
|
|||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"EditTagProtectionOption": {
|
||||||
|
"description": "EditTagProtectionOption options for editing a tag protection",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name_pattern": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "NamePattern"
|
||||||
|
},
|
||||||
|
"whitelist_teams": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "WhitelistTeams"
|
||||||
|
},
|
||||||
|
"whitelist_usernames": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "WhitelistUsernames"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"EditTeamOption": {
|
"EditTeamOption": {
|
||||||
"description": "EditTeamOption options for editing a team",
|
"description": "EditTeamOption options for editing a team",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -22127,7 +22404,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"Context": {
|
"Context": {
|
||||||
"description": "Context to render\n\nin: body",
|
"description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}\n\nin: body",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"Mode": {
|
"Mode": {
|
||||||
@ -22150,7 +22427,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"Context": {
|
"Context": {
|
||||||
"description": "Context to render\n\nin: body",
|
"description": "URL path for rendering issue, media and file links\nExpected format: /subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}\n\nin: body",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"FilePath": {
|
"FilePath": {
|
||||||
@ -24024,6 +24301,46 @@
|
|||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"TagProtection": {
|
||||||
|
"description": "TagProtection represents a tag protection",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "Created"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "ID"
|
||||||
|
},
|
||||||
|
"name_pattern": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "NamePattern"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"x-go-name": "Updated"
|
||||||
|
},
|
||||||
|
"whitelist_teams": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "WhitelistTeams"
|
||||||
|
},
|
||||||
|
"whitelist_usernames": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"x-go-name": "WhitelistUsernames"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"Team": {
|
"Team": {
|
||||||
"description": "Team represents a team in an organization",
|
"description": "Team represents a team in an organization",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -25635,6 +25952,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"TagProtection": {
|
||||||
|
"description": "TagProtection",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/TagProtection"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"TagProtectionList": {
|
||||||
|
"description": "TagProtectionList",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/TagProtection"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"TasksList": {
|
"TasksList": {
|
||||||
"description": "TasksList",
|
"description": "TasksList",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -4,7 +4,11 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"compress/gzip"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
@ -21,101 +25,167 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type tarFile struct {
|
||||||
|
Name string
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeArchiveFileTar(files []*tarFile) []byte {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
tarWriter := tar.NewWriter(buf)
|
||||||
|
for _, file := range files {
|
||||||
|
_ = tarWriter.WriteHeader(&tar.Header{
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
Name: file.Name,
|
||||||
|
Mode: 0o644,
|
||||||
|
Size: int64(len(file.Data)),
|
||||||
|
})
|
||||||
|
_, _ = tarWriter.Write(file.Data)
|
||||||
|
}
|
||||||
|
_ = tarWriter.Close()
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeArchiveFileGz(data []byte) []byte {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
gzWriter, _ := gzip.NewWriterLevel(buf, gzip.NoCompression)
|
||||||
|
_, _ = gzWriter.Write(data)
|
||||||
|
_ = gzWriter.Close()
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRubyGem(name, version string) []byte {
|
||||||
|
metadataContent := fmt.Sprintf(`--- !ruby/object:Gem::Specification
|
||||||
|
name: %s
|
||||||
|
version: !ruby/object:Gem::Version
|
||||||
|
version: %s
|
||||||
|
platform: ruby
|
||||||
|
authors:
|
||||||
|
- Gitea
|
||||||
|
autorequire:
|
||||||
|
bindir: bin
|
||||||
|
cert_chain: []
|
||||||
|
date: 2021-08-23 00:00:00.000000000 Z
|
||||||
|
dependencies:
|
||||||
|
- !ruby/object:Gem::Dependency
|
||||||
|
name: runtime-dep
|
||||||
|
requirement: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - ">="
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: 1.2.0
|
||||||
|
- - "<"
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: '2.0'
|
||||||
|
type: :runtime
|
||||||
|
prerelease: false
|
||||||
|
version_requirements: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - ">="
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: 1.2.0
|
||||||
|
- - "<"
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: '2.0'
|
||||||
|
- !ruby/object:Gem::Dependency
|
||||||
|
name: dev-dep
|
||||||
|
requirement: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - "~>"
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: '5.2'
|
||||||
|
type: :development
|
||||||
|
prerelease: false
|
||||||
|
version_requirements: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - "~>"
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: '5.2'
|
||||||
|
description: RubyGems package test
|
||||||
|
email: rubygems@gitea.io
|
||||||
|
executables: []
|
||||||
|
extensions: []
|
||||||
|
extra_rdoc_files: []
|
||||||
|
files:
|
||||||
|
- lib/gitea.rb
|
||||||
|
homepage: https://gitea.io/
|
||||||
|
licenses:
|
||||||
|
- MIT
|
||||||
|
metadata: {}
|
||||||
|
post_install_message:
|
||||||
|
rdoc_options: []
|
||||||
|
require_paths:
|
||||||
|
- lib
|
||||||
|
required_ruby_version: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - ">="
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: 2.3.0
|
||||||
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
||||||
|
requirements:
|
||||||
|
- - ">="
|
||||||
|
- !ruby/object:Gem::Version
|
||||||
|
version: '1.0'
|
||||||
|
requirements: []
|
||||||
|
rubyforge_project:
|
||||||
|
rubygems_version: 2.7.6.2
|
||||||
|
signing_key:
|
||||||
|
specification_version: 4
|
||||||
|
summary: Gitea package
|
||||||
|
test_files: []
|
||||||
|
`, name, version)
|
||||||
|
|
||||||
|
metadataGz := makeArchiveFileGz([]byte(metadataContent))
|
||||||
|
dataTarGz := makeArchiveFileGz(makeArchiveFileTar([]*tarFile{
|
||||||
|
{
|
||||||
|
Name: "lib/gitea.rb",
|
||||||
|
Data: []byte("class Gitea\nend"),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
|
checksumsYaml := fmt.Sprintf(`---
|
||||||
|
SHA256:
|
||||||
|
metadata.gz: %x
|
||||||
|
data.tar.gz: %x
|
||||||
|
SHA512:
|
||||||
|
metadata.gz: %x
|
||||||
|
data.tar.gz: %x
|
||||||
|
`, sha256.Sum256(metadataGz), sha256.Sum256(dataTarGz), sha512.Sum512(metadataGz), sha512.Sum512(dataTarGz))
|
||||||
|
|
||||||
|
files := []*tarFile{
|
||||||
|
{
|
||||||
|
Name: "data.tar.gz",
|
||||||
|
Data: dataTarGz,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "metadata.gz",
|
||||||
|
Data: metadataGz,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "checksums.yaml.gz",
|
||||||
|
Data: makeArchiveFileGz([]byte(checksumsYaml)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return makeArchiveFileTar(files)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPackageRubyGems(t *testing.T) {
|
func TestPackageRubyGems(t *testing.T) {
|
||||||
defer tests.PrepareTestEnv(t)()
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
packageName := "gitea"
|
testGemName := "gitea"
|
||||||
packageVersion := "1.0.5"
|
testGemVersion := "1.0.5"
|
||||||
packageFilename := "gitea-1.0.5.gem"
|
testGemContent := makeRubyGem(testGemName, testGemVersion)
|
||||||
|
testGemContentChecksum := fmt.Sprintf("%x", sha256.Sum256(testGemContent))
|
||||||
|
|
||||||
gemContent, _ := base64.StdEncoding.DecodeString(`bWV0YWRhdGEuZ3oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
testAnotherGemName := "gitea-another"
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA0NDQAMDAwMDAw
|
testAnotherGemVersion := "0.99"
|
||||||
MAAwMDAwMDAwADAwMDAwMDAxMDQxADE0MTEwNzcyMzY2ADAxMzQ0MQAgMAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMHdoZWVsAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAd2hlZWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAw
|
|
||||||
MDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf
|
|
||||||
iwgA9vQjYQID1VVNb9QwEL37V5he9pRsmlJAFlQckCoOXAriQIUix5nNmsYf2JOqKwS/nYmz2d3Q
|
|
||||||
qqCCKpFdadfjmfdm5nmcLMv4k9DXm6Wrv4BCcQ5GiPcelF5pJVE7y6w0IHirESS7hhDJJu4I+jhu
|
|
||||||
Mc53Tsd5kZ8y30lcuWAEH2KY7HHtQhQs4+cJkwwuwNdeB6JhtbaNDoLTL1MQsFJrqQnr8jNrJJJH
|
|
||||||
WZTHWfEiK094UYj0zYvp4Z9YAx5sA1ZpSCS3M30zeWwo2bG60FvUBjIKJts2GwMW76r0Yr9NzjN3
|
|
||||||
YhwsGX2Ozl4dpcWwvK9d43PQtDIv9igvHwSyIIwFmXHjqTqxLY8MPkCADmQk80p2EfZ6VbM6/ue6
|
|
||||||
/1D0Bq7/qeA/zh6W82leHmhFWUHn/JbsEfT6q7QbiCpoj8l0QcEUFLmX6kq2wBEiMjBSd+Pwt7T5
|
|
||||||
Ot0kuXYMbkD1KOuOBnWYb7hBsAP4bhlkFRqnqpWefMZ/pHCn6+WIFGq2dgY8EQq+RvRRLJcTyZJ1
|
|
||||||
WhHqGPTu7QdmACXdJFLwb9+ZdxErbSPKrqsMxJhAWCJ1qaqRdtu6yktcT/STsamG0qp7rsa5EL/K
|
|
||||||
MBua30uw4ynzExqYWRJDfx8/kQWN3PwsDh2jYLr1W+pZcAmCs9splvnz/Flesqhbq21bXcGG/OLh
|
|
||||||
+2fv/JTF3hgZyCW9OaZjxoZjdnBGfgKpxZyJ1QYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZGF0
|
|
||||||
YS50YXIuZ3oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA0NDQAMDAwMDAwMAAw
|
|
||||||
MDAwMDAwADAwMDAwMDAwMjQyADE0MTEwNzcyMzY2ADAxMzM2MQAgMAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB1c3RhcgAwMHdoZWVsAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAd2hlZWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwMDAwADAwMDAwMDAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfiwgA
|
|
||||||
9vQjYQID7M/NCsMgDABgz32KrA/QxersK/Q17ExXIcyhlr7+HLv1sJ02KPhBCPk5JOyn881nsl2c
|
|
||||||
xI+gRDRaC3zbZ8RBCamlxGHolTFlX11kLwDFH6wp21hO2RYi/rD3bb5/7iCubFOCMbBtABzNkIjn
|
|
||||||
bvGlAnisOUE7EnOALUR2p7b06e6aV4iqqqrquJ4AAAD//wMA+sA/NQAIAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGNoZWNr
|
|
||||||
c3Vtcy55YW1sLmd6AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwMDAwNDQ0ADAwMDAwMDAAMDAw
|
|
||||||
MDAwMAAwMDAwMDAwMDQ1MAAxNDExMDc3MjM2NgAwMTQ2MTIAIDAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdXN0YXIAMDB3aGVlbAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAHdoZWVsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMDAwMDAwMAAwMDAwMDAwAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH4sIAPb0
|
|
||||||
I2ECA2WQOa4UQAxE8znFXGCQ21vbPyMj5wRuL0Qk6EecnmZCyKyy9FSvXq/X4/u3ryj68Xg+f/Zn
|
|
||||||
VHzGlx+/P57qvU4XxWalBKftSXOgCjNYkdRycrC5Axem+W4HqS12PNEv7836jF9vnlHxwSyxKY+y
|
|
||||||
go0cPblyHzkrZ4HF1GSVhe7mOOoasXNk2fnbUxb+19Pp9tobD/QlJKMX7y204PREh6nQ5hG9Alw6
|
|
||||||
x4TnmtA+aekGfm6wAseog2LSgpR4Q7cYnAH3K4qAQa6A6JCC1gpuY7P+9YxE5SZ+j0eVGbaBTwBQ
|
|
||||||
iIqRUyyzLCoFCBdYNWxniapTavD97blXTzFvgoVoAsKBAtlU48cdaOmeZDpwV01OtcGwjscfeUrY
|
|
||||||
B9QBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
|
||||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`)
|
|
||||||
|
|
||||||
root := fmt.Sprintf("/api/packages/%s/rubygems", user.Name)
|
root := fmt.Sprintf("/api/packages/%s/rubygems", user.Name)
|
||||||
|
|
||||||
uploadFile := func(t *testing.T, expectedStatus int) {
|
uploadFile := func(t *testing.T, content []byte, expectedStatus int) {
|
||||||
req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/api/v1/gems", root), bytes.NewReader(gemContent)).
|
req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/api/v1/gems", root), bytes.NewReader(content)).
|
||||||
AddBasicAuth(user.Name)
|
AddBasicAuth(user.Name)
|
||||||
MakeRequest(t, req, expectedStatus)
|
MakeRequest(t, req, expectedStatus)
|
||||||
}
|
}
|
||||||
@ -123,7 +193,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`)
|
|||||||
t.Run("Upload", func(t *testing.T) {
|
t.Run("Upload", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
uploadFile(t, http.StatusCreated)
|
uploadFile(t, testGemContent, http.StatusCreated)
|
||||||
|
|
||||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems)
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -133,34 +203,33 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`)
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, pd.SemVer)
|
assert.NotNil(t, pd.SemVer)
|
||||||
assert.IsType(t, &rubygems.Metadata{}, pd.Metadata)
|
assert.IsType(t, &rubygems.Metadata{}, pd.Metadata)
|
||||||
assert.Equal(t, packageName, pd.Package.Name)
|
assert.Equal(t, testGemName, pd.Package.Name)
|
||||||
assert.Equal(t, packageVersion, pd.Version.Version)
|
assert.Equal(t, testGemVersion, pd.Version.Version)
|
||||||
|
|
||||||
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
|
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, pfs, 1)
|
assert.Len(t, pfs, 1)
|
||||||
assert.Equal(t, packageFilename, pfs[0].Name)
|
assert.Equal(t, fmt.Sprintf("%s-%s.gem", testGemName, testGemVersion), pfs[0].Name)
|
||||||
assert.True(t, pfs[0].IsLead)
|
assert.True(t, pfs[0].IsLead)
|
||||||
|
|
||||||
pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
|
pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, int64(4608), pb.Size)
|
assert.EqualValues(t, len(testGemContent), pb.Size)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UploadExists", func(t *testing.T) {
|
t.Run("UploadExists", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
uploadFile(t, testGemContent, http.StatusConflict)
|
||||||
uploadFile(t, http.StatusConflict)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Download", func(t *testing.T) {
|
t.Run("Download", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/gems/%s", root, packageFilename)).
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/gems/%s-%s.gem", root, testGemName, testGemVersion)).
|
||||||
AddBasicAuth(user.Name)
|
AddBasicAuth(user.Name)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
assert.Equal(t, gemContent, resp.Body.Bytes())
|
assert.Equal(t, testGemContent, resp.Body.Bytes())
|
||||||
|
|
||||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems)
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -171,7 +240,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`)
|
|||||||
t.Run("DownloadGemspec", func(t *testing.T) {
|
t.Run("DownloadGemspec", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
req := NewRequest(t, "GET", fmt.Sprintf("%s/quick/Marshal.4.8/%sspec.rz", root, packageFilename)).
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/quick/Marshal.4.8/%s-%s.gemspec.rz", root, testGemName, testGemVersion)).
|
||||||
AddBasicAuth(user.Name)
|
AddBasicAuth(user.Name)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
@ -206,22 +275,63 @@ gAAAAP//MS06Gw==`)
|
|||||||
enumeratePackages(t, "prerelease_specs.4.8.gz", b)
|
enumeratePackages(t, "prerelease_specs.4.8.gz", b)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Delete", func(t *testing.T) {
|
t.Run("UploadAnother", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
uploadFile(t, makeRubyGem(testAnotherGemName, testAnotherGemVersion), http.StatusCreated)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PackageInfo", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/info/%s", root, testGemName)).AddBasicAuth(user.Name)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
expected := fmt.Sprintf(`---
|
||||||
|
1.0.5 runtime-dep:>= 1.2.0&< 2.0|checksum:%s,ruby:>= 2.3.0,rubygems:>= 1.0
|
||||||
|
`, testGemContentChecksum)
|
||||||
|
assert.Equal(t, expected, resp.Body.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Versions", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/versions", root)).AddBasicAuth(user.Name)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, `---
|
||||||
|
gitea 1.0.5 08843c2dd0ea19910e6b056b98e38f1c
|
||||||
|
gitea-another 0.99 8b639e4048d282941485368ec42609be
|
||||||
|
`, resp.Body.String())
|
||||||
|
})
|
||||||
|
|
||||||
|
deleteGemPackage := func(t *testing.T, packageName, packageVersion string) {
|
||||||
body := bytes.Buffer{}
|
body := bytes.Buffer{}
|
||||||
writer := multipart.NewWriter(&body)
|
writer := multipart.NewWriter(&body)
|
||||||
writer.WriteField("gem_name", packageName)
|
_ = writer.WriteField("gem_name", packageName)
|
||||||
writer.WriteField("version", packageVersion)
|
_ = writer.WriteField("version", packageVersion)
|
||||||
writer.Close()
|
_ = writer.Close()
|
||||||
|
|
||||||
req := NewRequestWithBody(t, "DELETE", fmt.Sprintf("%s/api/v1/gems/yank", root), &body).
|
req := NewRequestWithBody(t, "DELETE", fmt.Sprintf("%s/api/v1/gems/yank", root), &body).
|
||||||
SetHeader("Content-Type", writer.FormDataContentType()).
|
SetHeader("Content-Type", writer.FormDataContentType()).
|
||||||
AddBasicAuth(user.Name)
|
AddBasicAuth(user.Name)
|
||||||
MakeRequest(t, req, http.StatusOK)
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("DeleteAll", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
deleteGemPackage(t, testGemName, testGemVersion)
|
||||||
|
deleteGemPackage(t, testAnotherGemName, testAnotherGemVersion)
|
||||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems)
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Empty(t, pvs)
|
assert.Empty(t, pvs)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("PackageInfoAfterDelete", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/info/%s", root, testGemName)).AddBasicAuth(user.Name)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("VersionsAfterDelete", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("%s/versions", root)).AddBasicAuth(user.Name)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, "---\n", resp.Body.String())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -272,7 +272,7 @@ export function initRepoCommentForm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$list.find('.selected').html(`
|
$list.find('.selected').html(`
|
||||||
<a class="item muted sidebar-item-link" href=${htmlEscape(this.getAttribute('href'))}>
|
<a class="item muted sidebar-item-link" href="${htmlEscape(this.getAttribute('data-href'))}">
|
||||||
${icon}
|
${icon}
|
||||||
${htmlEscape(this.textContent)}
|
${htmlEscape(this.textContent)}
|
||||||
</a>
|
</a>
|
||||||
|
Loading…
Reference in New Issue
Block a user