mirror of
https://github.com/go-gitea/gitea.git
synced 2024-06-26 01:15:32 +00:00
Compare commits
13 Commits
109a9a3fe2
...
e411257389
Author | SHA1 | Date | |
---|---|---|---|
|
e411257389 | ||
|
d32648b204 | ||
|
5a7376c060 | ||
|
363c123598 | ||
|
4b6eb46e69 | ||
|
990584f9bd | ||
|
123a02790c | ||
|
0f09c22663 | ||
|
25f3ec5b65 | ||
|
597d1da96b | ||
|
f5dfd7d73c | ||
|
129206da45 | ||
|
d3f415d105 |
|
@ -4,12 +4,67 @@
|
|||
package base
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/collate"
|
||||
"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
|
||||
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)
|
||||
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) {
|
||||
test := func(s1, s2 string, less bool) {
|
||||
assert.Equal(t, less, NaturalSortLess(s1, s2), "s1=%q, s2=%q", s1, s2)
|
||||
testLess := func(s1, s2 string) {
|
||||
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)
|
||||
test("a", "B", true)
|
||||
testEqual("", "")
|
||||
testLess("", "a")
|
||||
testLess("", "1")
|
||||
|
||||
test("cafe", "café", true)
|
||||
test("café", "cafe", false)
|
||||
test("caff", "café", false)
|
||||
testLess("v1.2", "v1.2.0")
|
||||
testLess("v1.2.0", "v1.10.0")
|
||||
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")
|
||||
}
|
||||
|
|
32
modules/cache/cache.go
vendored
32
modules/cache/cache.go
vendored
|
@ -4,6 +4,7 @@
|
|||
package cache
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -35,6 +36,37 @@ func Init() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
testCacheKey = "DefaultCache.TestKey"
|
||||
SlowCacheThreshold = 100 * time.Microsecond
|
||||
)
|
||||
|
||||
func Test() (time.Duration, error) {
|
||||
if defaultCache == nil {
|
||||
return 0, fmt.Errorf("default cache not initialized")
|
||||
}
|
||||
|
||||
testData := fmt.Sprintf("%x", make([]byte, 500))
|
||||
|
||||
start := time.Now()
|
||||
|
||||
if err := defaultCache.Delete(testCacheKey); err != nil {
|
||||
return 0, fmt.Errorf("expect cache to delete data based on key if exist but got: %w", err)
|
||||
}
|
||||
if err := defaultCache.Put(testCacheKey, testData, 10); err != nil {
|
||||
return 0, fmt.Errorf("expect cache to store data but got: %w", err)
|
||||
}
|
||||
testVal, hit := defaultCache.Get(testCacheKey)
|
||||
if !hit {
|
||||
return 0, fmt.Errorf("expect cache hit but got none")
|
||||
}
|
||||
if testVal != testData {
|
||||
return 0, fmt.Errorf("expect cache to return same value as stored but got other")
|
||||
}
|
||||
|
||||
return time.Since(start), nil
|
||||
}
|
||||
|
||||
// GetCache returns the currently configured cache
|
||||
func GetCache() StringCache {
|
||||
return defaultCache
|
||||
|
|
12
modules/cache/cache_test.go
vendored
12
modules/cache/cache_test.go
vendored
|
@ -34,6 +34,18 @@ func TestNewContext(t *testing.T) {
|
|||
assert.Nil(t, con)
|
||||
}
|
||||
|
||||
func TestTest(t *testing.T) {
|
||||
defaultCache = nil
|
||||
_, err := Test()
|
||||
assert.Error(t, err)
|
||||
|
||||
createTestCache()
|
||||
elapsed, err := Test()
|
||||
assert.NoError(t, err)
|
||||
// mem cache should take from 300ns up to 1ms on modern hardware ...
|
||||
assert.Less(t, elapsed, SlowCacheThreshold)
|
||||
}
|
||||
|
||||
func TestGetCache(t *testing.T) {
|
||||
createTestCache()
|
||||
|
||||
|
|
|
@ -144,20 +144,6 @@ func CustomLinkURLSchemes(schemes []string) {
|
|||
common.LinkRegex, _ = xurls.StrictMatchingScheme(strings.Join(withAuth, "|"))
|
||||
}
|
||||
|
||||
// IsSameDomain checks if given url string has the same hostname as current Gitea instance
|
||||
func IsSameDomain(s string) bool {
|
||||
if strings.HasPrefix(s, "/") {
|
||||
return true
|
||||
}
|
||||
if uapp, err := url.Parse(setting.AppURL); err == nil {
|
||||
if u, err := url.Parse(s); err == nil {
|
||||
return u.Host == uapp.Host
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type postProcessError struct {
|
||||
context string
|
||||
err error
|
||||
|
@ -429,7 +415,7 @@ func visitNode(ctx *RenderContext, procs []processor, node *html.Node) *html.Nod
|
|||
// We ignore code and pre.
|
||||
switch node.Type {
|
||||
case html.TextNode:
|
||||
textNode(ctx, procs, node)
|
||||
processTextNodes(ctx, procs, node)
|
||||
case html.ElementNode:
|
||||
if node.Data == "img" {
|
||||
next := node.NextSibling
|
||||
|
@ -465,15 +451,16 @@ func visitNode(ctx *RenderContext, procs []processor, node *html.Node) *html.Nod
|
|||
for n := node.FirstChild; n != nil; {
|
||||
n = visitNode(ctx, procs, n)
|
||||
}
|
||||
default:
|
||||
}
|
||||
return node.NextSibling
|
||||
}
|
||||
|
||||
// textNode runs the passed node through various processors, in order to handle
|
||||
// processTextNodes runs the passed node through various processors, in order to handle
|
||||
// all kinds of special links handled by the post-processing.
|
||||
func textNode(ctx *RenderContext, procs []processor, node *html.Node) {
|
||||
for _, processor := range procs {
|
||||
processor(ctx, node)
|
||||
func processTextNodes(ctx *RenderContext, procs []processor, node *html.Node) {
|
||||
for _, p := range procs {
|
||||
p(ctx, node)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -939,14 +926,11 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
// Path determines the type of link that will be rendered. It's unknown at this point whether
|
||||
// the linked item is actually a PR or an issue. Luckily it's of no real consequence because
|
||||
// Gitea will redirect on click as appropriate.
|
||||
path := "issues"
|
||||
if ref.IsPull {
|
||||
path = "pulls"
|
||||
}
|
||||
issuePath := util.Iif(ref.IsPull, "pulls", "issues")
|
||||
if ref.Owner == "" {
|
||||
link = createLink(util.URLJoin(ctx.Links.Prefix(), ctx.Metas["user"], ctx.Metas["repo"], path, ref.Issue), reftext, "ref-issue")
|
||||
link = createLink(util.URLJoin(ctx.Links.Prefix(), ctx.Metas["user"], ctx.Metas["repo"], issuePath, ref.Issue), reftext, "ref-issue")
|
||||
} else {
|
||||
link = createLink(util.URLJoin(ctx.Links.Prefix(), ref.Owner, ref.Name, path, ref.Issue), reftext, "ref-issue")
|
||||
link = createLink(util.URLJoin(ctx.Links.Prefix(), ref.Owner, ref.Name, issuePath, ref.Issue), reftext, "ref-issue")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1207,7 +1191,7 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
|
|||
return
|
||||
}
|
||||
ctx.AddCancel(func() {
|
||||
closer.Close()
|
||||
_ = closer.Close()
|
||||
ctx.GitRepo = nil
|
||||
})
|
||||
}
|
||||
|
|
|
@ -144,17 +144,6 @@ func TestRender_CrossReferences(t *testing.T) {
|
|||
`<p><a href="`+inputURL+`" rel="nofollow"><code>0123456789/foo.txt (L2-L3)</code></a></p>`)
|
||||
}
|
||||
|
||||
func TestMisc_IsSameDomain(t *testing.T) {
|
||||
setting.AppURL = markup.TestAppURL
|
||||
|
||||
sha := "b6dd6210eaebc915fd5be5579c58cce4da2e2579"
|
||||
commit := util.URLJoin(markup.TestRepoURL, "commit", sha)
|
||||
|
||||
assert.True(t, markup.IsSameDomain(commit))
|
||||
assert.False(t, markup.IsSameDomain("http://google.com/ncr"))
|
||||
assert.False(t, markup.IsSameDomain("favicon.ico"))
|
||||
}
|
||||
|
||||
func TestRender_links(t *testing.T) {
|
||||
setting.AppURL = markup.TestAppURL
|
||||
|
||||
|
|
|
@ -86,10 +86,10 @@ type RenderContext struct {
|
|||
}
|
||||
|
||||
type Links struct {
|
||||
AbsolutePrefix bool
|
||||
Base string
|
||||
BranchPath string
|
||||
TreePath string
|
||||
AbsolutePrefix bool // add absolute URL prefix to auto-resolved links like "#issue", but not for pre-provided links and medias
|
||||
Base string // base prefix for pre-provided links and medias (images, videos)
|
||||
BranchPath string // actually it is the ref path, eg: "branch/features/feat-12", "tag/v1.0"
|
||||
TreePath string // the dir of the file, eg: "doc" if the file "doc/CHANGE.md" is being rendered
|
||||
}
|
||||
|
||||
func (l *Links) Prefix() string {
|
||||
|
|
|
@ -25,7 +25,8 @@ type MarkupOption struct {
|
|||
//
|
||||
// in: body
|
||||
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
|
||||
Context string
|
||||
|
@ -53,7 +54,8 @@ type MarkdownOption struct {
|
|||
//
|
||||
// in: body
|
||||
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
|
||||
Context string
|
||||
|
|
|
@ -5,8 +5,10 @@ package web
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
|
||||
"gitea.com/go-chi/binding"
|
||||
|
@ -160,7 +162,7 @@ func (r *Route) Patch(pattern string, h ...any) {
|
|||
|
||||
// ServeHTTP implements http.Handler
|
||||
func (r *Route) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
r.R.ServeHTTP(w, req)
|
||||
r.normalizeRequestPath(w, req, r.R)
|
||||
}
|
||||
|
||||
// NotFound defines a handler to respond whenever a route could not be found.
|
||||
|
@ -168,6 +170,61 @@ func (r *Route) NotFound(h http.HandlerFunc) {
|
|||
r.R.NotFound(h)
|
||||
}
|
||||
|
||||
func (r *Route) normalizeRequestPath(resp http.ResponseWriter, req *http.Request, next http.Handler) {
|
||||
normalized := false
|
||||
normalizedPath := req.URL.EscapedPath()
|
||||
if normalizedPath == "" {
|
||||
normalizedPath, normalized = "/", true
|
||||
} else if normalizedPath != "/" {
|
||||
normalized = strings.HasSuffix(normalizedPath, "/")
|
||||
normalizedPath = strings.TrimRight(normalizedPath, "/")
|
||||
}
|
||||
removeRepeatedSlashes := strings.Contains(normalizedPath, "//")
|
||||
normalized = normalized || removeRepeatedSlashes
|
||||
|
||||
// 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 removeRepeatedSlashes {
|
||||
buf := &strings.Builder{}
|
||||
for i := 0; i < len(normalizedPath); i++ {
|
||||
if i == 0 || normalizedPath[i-1] != '/' || normalizedPath[i] != '/' {
|
||||
buf.WriteByte(normalizedPath[i])
|
||||
}
|
||||
}
|
||||
normalizedPath = buf.String()
|
||||
}
|
||||
|
||||
// If the config tells Gitea to use a sub-url path directly without reverse proxy,
|
||||
// then we need to remove the sub-url path from the request URL path.
|
||||
// But "/v2" is special for OCI container registry, it should always be in the root of the site.
|
||||
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
|
||||
}
|
||||
normalized = true
|
||||
}
|
||||
|
||||
// if the path is normalized, then fill it back to the request
|
||||
if normalized {
|
||||
decodedPath, err := url.PathUnescape(normalizedPath)
|
||||
if err != nil {
|
||||
http.Error(resp, "400 Bad Request: unable to unescape path "+normalizedPath, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
req.URL.RawPath = normalizedPath
|
||||
req.URL.Path = decodedPath
|
||||
}
|
||||
|
||||
next.ServeHTTP(resp, req)
|
||||
}
|
||||
|
||||
// Combo delegates requests to Combo
|
||||
func (r *Route) Combo(pattern string, h ...any) *Combo {
|
||||
return &Combo{r, pattern, h}
|
||||
|
|
|
@ -10,6 +10,9 @@ import (
|
|||
"strconv"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
chi "github.com/go-chi/chi/v5"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -176,3 +179,44 @@ func TestRoute3(t *testing.T) {
|
|||
assert.EqualValues(t, http.StatusOK, recorder.Code)
|
||||
assert.EqualValues(t, 4, hit)
|
||||
}
|
||||
|
||||
func TestRouteNormalizePath(t *testing.T) {
|
||||
type paths struct {
|
||||
EscapedPath, RawPath, Path string
|
||||
}
|
||||
testPath := func(reqPath string, expectedPaths paths) {
|
||||
recorder := httptest.NewRecorder()
|
||||
recorder.Body = bytes.NewBuffer(nil)
|
||||
|
||||
actualPaths := paths{EscapedPath: "(none)", RawPath: "(none)", Path: "(none)"}
|
||||
r := NewRoute()
|
||||
r.Get("/*", func(resp http.ResponseWriter, req *http.Request) {
|
||||
actualPaths.EscapedPath = req.URL.EscapedPath()
|
||||
actualPaths.RawPath = req.URL.RawPath
|
||||
actualPaths.Path = req.URL.Path
|
||||
})
|
||||
|
||||
req, err := http.NewRequest("GET", reqPath, nil)
|
||||
assert.NoError(t, err)
|
||||
r.ServeHTTP(recorder, req)
|
||||
assert.Equal(t, expectedPaths, actualPaths, "req path = %q", reqPath)
|
||||
}
|
||||
|
||||
// RawPath could be empty if the EscapedPath is the same as escape(Path) and it is already normalized
|
||||
testPath("/", paths{EscapedPath: "/", RawPath: "", Path: "/"})
|
||||
testPath("//", paths{EscapedPath: "/", RawPath: "/", Path: "/"})
|
||||
testPath("/%2f", paths{EscapedPath: "/%2f", RawPath: "/%2f", Path: "//"})
|
||||
testPath("///a//b/", paths{EscapedPath: "/a/b", RawPath: "/a/b", Path: "/a/b"})
|
||||
|
||||
defer test.MockVariableValue(&setting.UseSubURLPath, true)()
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "/sub-path")()
|
||||
testPath("/", paths{EscapedPath: "(none)", RawPath: "(none)", Path: "(none)"}) // 404
|
||||
testPath("/sub-path", paths{EscapedPath: "/", RawPath: "/", Path: "/"})
|
||||
testPath("/sub-path/", paths{EscapedPath: "/", RawPath: "/", Path: "/"})
|
||||
testPath("/sub-path//a/b///", paths{EscapedPath: "/a/b", RawPath: "/a/b", Path: "/a/b"})
|
||||
testPath("/sub-path/%2f/", paths{EscapedPath: "/%2f", RawPath: "/%2f", Path: "//"})
|
||||
// "/v2" is special for OCI container registry, it should always be in the root of the site
|
||||
testPath("/v2", paths{EscapedPath: "/v2", RawPath: "/v2", Path: "/v2"})
|
||||
testPath("/v2/", paths{EscapedPath: "/v2", RawPath: "/v2", Path: "/v2"})
|
||||
testPath("/v2/%2f", paths{EscapedPath: "/v2/%2f", RawPath: "/v2/%2f", Path: "/v2//"})
|
||||
}
|
||||
|
|
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/screenshots/**/*.png
|
||||
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
|
||||
.terraformrc
|
||||
terraform.rc
|
||||
|
||||
# Ignore hcl file
|
||||
.terraform.lock.hcl
|
||||
|
|
|
@ -93,6 +93,7 @@ remove_all = Remove All
|
|||
remove_label_str = Remove item "%s"
|
||||
edit = Edit
|
||||
view = View
|
||||
test = Test
|
||||
|
||||
enabled = Enabled
|
||||
disabled = Disabled
|
||||
|
@ -3225,6 +3226,10 @@ config.cache_adapter = Cache Adapter
|
|||
config.cache_interval = Cache Interval
|
||||
config.cache_conn = Cache Connection
|
||||
config.cache_item_ttl = Cache Item TTL
|
||||
config.cache_test = Test Cache
|
||||
config.cache_test_failed = Failed to probe the cache: %v.
|
||||
config.cache_test_slow = Cache test successful, but response is slow: %s.
|
||||
config.cache_test_succeeded = Cache test successful, got a response in %s.
|
||||
|
||||
config.session_config = Session Configuration
|
||||
config.session_provider = Session Provider
|
||||
|
|
|
@ -588,6 +588,8 @@ func CommonRoutes() *web.Route {
|
|||
r.Get("/prerelease_specs.4.8.gz", rubygems.EnumeratePackagesPreRelease)
|
||||
r.Get("/quick/Marshal.4.8/{filename}", rubygems.ServePackageSpecification)
|
||||
r.Get("/gems/{filename}", rubygems.DownloadPackageFile)
|
||||
r.Get("/info/{packagename}", rubygems.GetPackageInfo)
|
||||
r.Get("/versions", rubygems.GetAllPackagesVersions)
|
||||
r.Group("/api/v1/gems", func() {
|
||||
r.Post("/", rubygems.UploadPackageFile)
|
||||
r.Delete("/yank", rubygems.DeletePackage)
|
||||
|
|
|
@ -6,6 +6,7 @@ package rubygems
|
|||
import (
|
||||
"compress/gzip"
|
||||
"compress/zlib"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -227,12 +228,7 @@ func UploadPackageFile(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
var filename string
|
||||
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))
|
||||
}
|
||||
filename := makeGemFullFileName(rp.Name, rp.Version, rp.Metadata.Platform)
|
||||
|
||||
_, _, err = packages_service.CreatePackageAndAddFile(
|
||||
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) {
|
||||
pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
|
||||
OwnerID: ctx.Package.Owner.ID,
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
go_context "context"
|
||||
"io"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -19,36 +20,40 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
AppURL = "http://localhost:3000/"
|
||||
Repo = "gogits/gogs"
|
||||
FullURL = AppURL + Repo + "/"
|
||||
)
|
||||
const AppURL = "http://localhost:3000/"
|
||||
|
||||
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
|
||||
context := "/gogits/gogs"
|
||||
if !wiki {
|
||||
context += path.Join("/src/branch/main", path.Dir(filePath))
|
||||
}
|
||||
options := api.MarkupOption{
|
||||
Mode: mode,
|
||||
Text: text,
|
||||
Context: Repo,
|
||||
Wiki: true,
|
||||
Context: context,
|
||||
Wiki: wiki,
|
||||
FilePath: filePath,
|
||||
}
|
||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markup")
|
||||
web.SetForm(ctx, &options)
|
||||
Markup(ctx)
|
||||
assert.Equal(t, responseBody, resp.Body.String())
|
||||
assert.Equal(t, responseCode, resp.Code)
|
||||
assert.Equal(t, expectedBody, resp.Body.String())
|
||||
assert.Equal(t, expectedCode, resp.Code)
|
||||
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
|
||||
context := "/gogits/gogs"
|
||||
if !wiki {
|
||||
context += "/src/branch/main"
|
||||
}
|
||||
options := api.MarkdownOption{
|
||||
Mode: mode,
|
||||
Text: text,
|
||||
Context: Repo,
|
||||
Wiki: true,
|
||||
Context: context,
|
||||
Wiki: wiki,
|
||||
}
|
||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||
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
|
||||
`Wiki! Enjoy :)
|
||||
- [[Links, Language bindings, Engine bindings|Links]]
|
||||
|
@ -74,20 +79,20 @@ func TestAPI_RenderGFM(t *testing.T) {
|
|||
// rendered
|
||||
`<p>Wiki! Enjoy :)</p>
|
||||
<ul>
|
||||
<li><a href="` + FullURL + `wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
||||
<li><a href="` + FullURL + `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><a href="http://localhost:3000/gogits/gogs/wiki/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
|
||||
<li><a href="http://localhost:3000/gogits/gogs/wiki/Tips" rel="nofollow">Tips</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>
|
||||
`,
|
||||
// Guard wiki sidebar: special syntax
|
||||
`[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
|
||||
// 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
|
||||
`[[Name|Link]]`,
|
||||
// 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
|
||||
``,
|
||||
|
@ -95,7 +100,7 @@ func TestAPI_RenderGFM(t *testing.T) {
|
|||
``,
|
||||
}
|
||||
|
||||
testCasesDocument := []string{
|
||||
testCasesWikiDocument := []string{
|
||||
// wine-staging wiki home extract: special wiki syntax, images
|
||||
`## What is Wine Staging?
|
||||
**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>
|
||||
<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><a href="` + FullURL + `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>
|
||||
<p><a href="http://localhost:3000/gogits/gogs/wiki/Configuration" rel="nofollow">Configuration</a>
|
||||
<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 {
|
||||
text := testCasesCommon[i]
|
||||
response := testCasesCommon[i+1]
|
||||
testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
|
||||
testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
|
||||
testRenderMarkdown(t, "comment", text, response, http.StatusOK)
|
||||
testRenderMarkup(t, "comment", "", text, response, http.StatusOK)
|
||||
testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
|
||||
for i := 0; i < len(testCasesWiki); i += 2 {
|
||||
text := testCasesWiki[i]
|
||||
response := testCasesWiki[i+1]
|
||||
testRenderMarkdown(t, "gfm", true, text, response, http.StatusOK)
|
||||
testRenderMarkup(t, "gfm", true, "", text, response, http.StatusOK)
|
||||
testRenderMarkdown(t, "comment", true, text, response, http.StatusOK)
|
||||
testRenderMarkup(t, "comment", true, "", text, response, http.StatusOK)
|
||||
testRenderMarkup(t, "file", true, "path/test.md", text, response, http.StatusOK)
|
||||
}
|
||||
|
||||
for i := 0; i < len(testCasesDocument); i += 2 {
|
||||
text := testCasesDocument[i]
|
||||
response := testCasesDocument[i+1]
|
||||
testRenderMarkdown(t, "gfm", text, response, http.StatusOK)
|
||||
testRenderMarkup(t, "gfm", "", text, response, http.StatusOK)
|
||||
testRenderMarkup(t, "file", "path/test.md", text, response, http.StatusOK)
|
||||
for i := 0; i < len(testCasesWikiDocument); i += 2 {
|
||||
text := testCasesWikiDocument[i]
|
||||
response := testCasesWikiDocument[i+1]
|
||||
testRenderMarkdown(t, "gfm", true, text, response, http.StatusOK)
|
||||
testRenderMarkup(t, "gfm", true, "", 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)
|
||||
testRenderMarkup(t, "unknown", "", "## Test", "Unknown mode: unknown\n", http.StatusUnprocessableEntity)
|
||||
input := "[Link](test.md)\n![Image](image.png)"
|
||||
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{
|
||||
|
@ -160,7 +182,7 @@ func TestAPI_RenderSimple(t *testing.T) {
|
|||
options := api.MarkdownOption{
|
||||
Mode: "markdown",
|
||||
Text: "",
|
||||
Context: Repo,
|
||||
Context: "/gogits/gogs",
|
||||
}
|
||||
ctx, resp := contexttest.MockAPIContext(t, "POST /api/v1/markdown")
|
||||
for i := 0; i < len(simpleCases); i += 2 {
|
||||
|
|
|
@ -7,63 +7,67 @@ package common
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
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/markdown"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
|
||||
"mvdan.cc/xurls/v2"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
var markupType string
|
||||
relativePath := ""
|
||||
func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPathContext, filePath string, wiki bool) {
|
||||
// urlPathContext format is "/subpath/{user}/{repo}/src/{branch, commit, tag}/{identifier/path}/{file/dir}"
|
||||
// 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 {
|
||||
_, _ = ctx.Write([]byte(""))
|
||||
return
|
||||
// for example, when previewing file "/gitea/owner/repo/src/branch/features/feat-123/doc/CHANGE.md", then filePath is "doc/CHANGE.md"
|
||||
// and the urlPathContext is "/gitea/owner/repo/src/branch/features/feat-123/doc"
|
||||
|
||||
var markupType, relativePath string
|
||||
|
||||
links := markup.Links{AbsolutePrefix: true}
|
||||
if urlPathContext != "" {
|
||||
links.Base = fmt.Sprintf("%s%s", httplib.GuessCurrentHostURL(ctx), urlPathContext)
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case "markdown":
|
||||
// Raw markdown
|
||||
if err := markdown.RenderRaw(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
Links: markup.Links{
|
||||
AbsolutePrefix: true,
|
||||
Base: urlPrefix,
|
||||
},
|
||||
Ctx: ctx,
|
||||
Links: links,
|
||||
}, strings.NewReader(text), ctx.Resp); err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, err.Error())
|
||||
}
|
||||
return
|
||||
case "comment":
|
||||
// Comment as markdown
|
||||
// Issue & comment content
|
||||
markupType = markdown.MarkupName
|
||||
case "gfm":
|
||||
// Github Flavored Markdown as document
|
||||
// GitHub Flavored Markdown
|
||||
markupType = markdown.MarkupName
|
||||
case "file":
|
||||
// File as document based on file extension
|
||||
markupType = ""
|
||||
markupType = "" // render the repo file content by its extension
|
||||
relativePath = filePath
|
||||
default:
|
||||
ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Unknown mode: %s", mode))
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(setting.AppSubURL+"/", urlPrefix) {
|
||||
// check if urlPrefix is already set to a URL
|
||||
linkRegex, _ := xurls.StrictMatchingScheme("https?://")
|
||||
m := linkRegex.FindStringIndex(urlPrefix)
|
||||
if m == nil {
|
||||
urlPrefix = util.URLJoin(setting.AppURL, urlPrefix)
|
||||
}
|
||||
fields := strings.SplitN(strings.TrimPrefix(urlPathContext, setting.AppSubURL+"/"), "/", 5)
|
||||
if len(fields) == 5 && fields[2] == "src" && (fields[3] == "branch" || fields[3] == "commit" || fields[3] == "tag") {
|
||||
// absolute base prefix is something like "https://host/subpath/{user}/{repo}"
|
||||
absoluteBasePrefix := fmt.Sprintf("%s%s/%s", httplib.GuessCurrentAppURL(ctx), fields[0], fields[1])
|
||||
|
||||
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{}
|
||||
|
@ -81,12 +85,9 @@ func RenderMarkup(ctx *context.Base, repo *context.Repository, mode, text, urlPr
|
|||
}
|
||||
|
||||
if err := markup.Render(&markup.RenderContext{
|
||||
Ctx: ctx,
|
||||
Repo: repoCtx,
|
||||
Links: markup.Links{
|
||||
AbsolutePrefix: true,
|
||||
Base: urlPrefix,
|
||||
},
|
||||
Ctx: ctx,
|
||||
Repo: repoCtx,
|
||||
Links: links,
|
||||
Metas: meta,
|
||||
IsWiki: wiki,
|
||||
Type: markupType,
|
||||
|
|
|
@ -19,13 +19,23 @@ import (
|
|||
|
||||
"gitea.com/go-chi/session"
|
||||
"github.com/chi-middleware/proxy"
|
||||
chi "github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// ProtocolMiddlewares returns HTTP protocol related middlewares, and it provides a global panic recovery
|
||||
func ProtocolMiddlewares() (handlers []any) {
|
||||
// first, normalize the URL path
|
||||
handlers = append(handlers, normalizeRequestPathMiddleware)
|
||||
// make sure chi uses EscapedPath(RawPath) as RoutePath, then "%2f" could be handled correctly
|
||||
handlers = append(handlers, func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
ctx := chi.RouteContext(req.Context())
|
||||
if req.URL.RawPath == "" {
|
||||
ctx.RoutePath = req.URL.EscapedPath()
|
||||
} else {
|
||||
ctx.RoutePath = req.URL.RawPath
|
||||
}
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
})
|
||||
|
||||
// prepare the ContextData and panic recovery
|
||||
handlers = append(handlers, func(next http.Handler) http.Handler {
|
||||
|
@ -75,58 +85,6 @@ func ProtocolMiddlewares() (handlers []any) {
|
|||
return handlers
|
||||
}
|
||||
|
||||
func normalizeRequestPathMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
// escape the URL RawPath to ensure that all routing is done using a correctly escaped URL
|
||||
req.URL.RawPath = req.URL.EscapedPath()
|
||||
|
||||
urlPath := req.URL.RawPath
|
||||
rctx := chi.RouteContext(req.Context())
|
||||
if rctx != nil && rctx.RoutePath != "" {
|
||||
urlPath = rctx.RoutePath
|
||||
}
|
||||
|
||||
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
|
||||
for _, chr := range normalizedPath {
|
||||
if chr != '/' || !prevWasSlash {
|
||||
buf.WriteRune(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 {
|
||||
req.URL.Path = normalizedPath
|
||||
} else {
|
||||
rctx.RoutePath = normalizedPath
|
||||
}
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
|
||||
func Sessioner() func(next http.Handler) http.Handler {
|
||||
return session.Sessioner(session.Options{
|
||||
Provider: setting.SessionConfig.Provider,
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
// Copyright 2022 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
package common
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStripSlashesMiddleware(t *testing.T) {
|
||||
type test struct {
|
||||
name string
|
||||
expectedPath string
|
||||
inputPath string
|
||||
}
|
||||
|
||||
tests := []test{
|
||||
{
|
||||
name: "path with multiple slashes",
|
||||
inputPath: "https://github.com///go-gitea//gitea.git",
|
||||
expectedPath: "/go-gitea/gitea.git",
|
||||
},
|
||||
{
|
||||
name: "path with no slashes",
|
||||
inputPath: "https://github.com/go-gitea/gitea.git",
|
||||
expectedPath: "/go-gitea/gitea.git",
|
||||
},
|
||||
{
|
||||
name: "path with slashes in the middle",
|
||||
inputPath: "https://git.data.coop//halfd/new-website.git",
|
||||
expectedPath: "/halfd/new-website.git",
|
||||
},
|
||||
{
|
||||
name: "path with slashes in the middle",
|
||||
inputPath: "https://git.data.coop//halfd/new-website.git",
|
||||
expectedPath: "/halfd/new-website.git",
|
||||
},
|
||||
{
|
||||
name: "path with slashes in the end",
|
||||
inputPath: "/user2//repo1/",
|
||||
expectedPath: "/user2/repo1",
|
||||
},
|
||||
{
|
||||
name: "path with slashes and query params",
|
||||
inputPath: "/repo//migrate?service_type=3",
|
||||
expectedPath: "/repo/migrate",
|
||||
},
|
||||
{
|
||||
name: "path with encoded slash",
|
||||
inputPath: "/user2/%2F%2Frepo1",
|
||||
expectedPath: "/user2/%2F%2Frepo1",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testMiddleware := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assert.Equal(t, tt.expectedPath, r.URL.Path)
|
||||
})
|
||||
|
||||
// pass the test middleware to validate the changes
|
||||
handlerToTest := normalizeRequestPathMiddleware(testMiddleware)
|
||||
// create a mock request to use
|
||||
req := httptest.NewRequest("GET", tt.inputPath, nil)
|
||||
// call the handler using a mock response recorder
|
||||
handlerToTest.ServeHTTP(httptest.NewRecorder(), req)
|
||||
}
|
||||
}
|
|
@ -191,7 +191,8 @@ func NormalRoutes() *web.Route {
|
|||
if setting.Packages.Enabled {
|
||||
// This implements package support for most package managers
|
||||
r.Mount("/api/packages", packages_router.CommonRoutes())
|
||||
// This implements the OCI API (Note this is not preceded by /api but is instead /v2)
|
||||
// This implements the OCI API, this container registry "/v2" endpoint must be in the root of the site.
|
||||
// If site admin deploys Gitea in a sub-path, they must configure their reverse proxy to map the "https://host/v2" endpoint to Gitea.
|
||||
r.Mount("/v2", packages_router.ContainerRoutes())
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
activities_model "code.gitea.io/gitea/models/activities"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
|
@ -222,6 +223,14 @@ func SelfCheck(ctx *context.Context) {
|
|||
|
||||
ctx.Data["DatabaseCheckHasProblems"] = hasProblem
|
||||
}
|
||||
|
||||
elapsed, err := cache.Test()
|
||||
if err != nil {
|
||||
ctx.Data["CacheError"] = err
|
||||
} else if elapsed > cache.SlowCacheThreshold {
|
||||
ctx.Data["CacheSlow"] = fmt.Sprint(elapsed)
|
||||
}
|
||||
|
||||
ctx.HTML(http.StatusOK, tplSelfCheck)
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
|
||||
system_model "code.gitea.io/gitea/models/system"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
@ -42,6 +43,22 @@ func SendTestMail(ctx *context.Context) {
|
|||
ctx.Redirect(setting.AppSubURL + "/admin/config")
|
||||
}
|
||||
|
||||
// TestCache test the cache settings
|
||||
func TestCache(ctx *context.Context) {
|
||||
elapsed, err := cache.Test()
|
||||
if err != nil {
|
||||
ctx.Flash.Error(ctx.Tr("admin.config.cache_test_failed", err))
|
||||
} else {
|
||||
if elapsed > cache.SlowCacheThreshold {
|
||||
ctx.Flash.Warning(ctx.Tr("admin.config.cache_test_slow", elapsed))
|
||||
} else {
|
||||
ctx.Flash.Info(ctx.Tr("admin.config.cache_test_succeeded", elapsed))
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Redirect(setting.AppSubURL + "/admin/config")
|
||||
}
|
||||
|
||||
func shadowPasswordKV(cfgItem, splitter string) string {
|
||||
fields := strings.Split(cfgItem, splitter)
|
||||
for i := 0; i < len(fields); i++ {
|
||||
|
|
|
@ -12,7 +12,6 @@ import (
|
|||
"code.gitea.io/gitea/models/organization"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
|
@ -100,10 +99,34 @@ func Home(ctx *context.Context) {
|
|||
private := ctx.FormOptionalBool("private")
|
||||
ctx.Data["IsPrivate"] = private
|
||||
|
||||
err := shared_user.LoadHeaderCount(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadHeaderCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
opts := &organization.FindOrgMembersOpts{
|
||||
OrgID: org.ID,
|
||||
PublicOnly: ctx.Org.PublicMemberOnly,
|
||||
ListOptions: db.ListOptions{Page: 1, PageSize: 25},
|
||||
}
|
||||
members, _, err := organization.FindOrgMembers(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.ServerError("FindOrgMembers", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["Members"] = members
|
||||
ctx.Data["Teams"] = ctx.Org.Teams
|
||||
ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
|
||||
ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0
|
||||
|
||||
if !prepareOrgProfileReadme(ctx) {
|
||||
ctx.Data["PageIsViewRepositories"] = true
|
||||
}
|
||||
|
||||
var (
|
||||
repos []*repo_model.Repository
|
||||
count int64
|
||||
err error
|
||||
)
|
||||
repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
|
||||
ListOptions: db.ListOptions{
|
||||
|
@ -128,47 +151,29 @@ func Home(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
opts := &organization.FindOrgMembersOpts{
|
||||
OrgID: org.ID,
|
||||
PublicOnly: ctx.Org.PublicMemberOnly,
|
||||
ListOptions: db.ListOptions{Page: 1, PageSize: 25},
|
||||
}
|
||||
members, _, err := organization.FindOrgMembers(ctx, opts)
|
||||
if err != nil {
|
||||
ctx.ServerError("FindOrgMembers", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Repos"] = repos
|
||||
ctx.Data["Total"] = count
|
||||
ctx.Data["Members"] = members
|
||||
ctx.Data["Teams"] = ctx.Org.Teams
|
||||
ctx.Data["DisableNewPullMirrors"] = setting.Mirror.DisableNewPull
|
||||
ctx.Data["PageIsViewRepositories"] = true
|
||||
|
||||
err = shared_user.LoadHeaderCount(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadHeaderCount", err)
|
||||
return
|
||||
}
|
||||
|
||||
pager := context.NewPagination(int(count), setting.UI.User.RepoPagingNum, page, 5)
|
||||
pager.SetDefaultParams(ctx)
|
||||
pager.AddParamString("language", language)
|
||||
ctx.Data["Page"] = pager
|
||||
|
||||
ctx.Data["ShowMemberAndTeamTab"] = ctx.Org.IsMember || len(members) > 0
|
||||
|
||||
profileDbRepo, profileGitRepo, profileReadmeBlob, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer)
|
||||
defer profileClose()
|
||||
prepareOrgProfileReadme(ctx, profileGitRepo, profileDbRepo, profileReadmeBlob)
|
||||
|
||||
ctx.HTML(http.StatusOK, tplOrgHome)
|
||||
}
|
||||
|
||||
func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repository, profileDbRepo *repo_model.Repository, profileReadme *git.Blob) {
|
||||
func prepareOrgProfileReadme(ctx *context.Context) bool {
|
||||
profileDbRepo, profileGitRepo, profileReadme, profileClose := shared_user.FindUserProfileReadme(ctx, ctx.Doer)
|
||||
defer profileClose()
|
||||
ctx.Data["HasProfileReadme"] = profileReadme != nil
|
||||
|
||||
if profileGitRepo == nil || profileReadme == nil {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
viewRepositorys := ctx.FormOptionalBool("view_repositorys")
|
||||
if viewRepositorys.Value() {
|
||||
return false
|
||||
}
|
||||
|
||||
if bytes, err := profileReadme.GetBlobContent(setting.UI.MaxDisplayFileSize); err != nil {
|
||||
|
@ -190,4 +195,7 @@ func prepareOrgProfileReadme(ctx *context.Context, profileGitRepo *git.Repositor
|
|||
ctx.Data["ProfileReadme"] = profileContent
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["PageIsViewOverview"] = true
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -54,9 +54,9 @@ func Members(ctx *context.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
err = shared_user.LoadHeaderCount(ctx)
|
||||
err = shared_user.RenderOrgHeader(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadHeaderCount", err)
|
||||
ctx.ServerError("RenderOrgHeader", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -59,9 +59,9 @@ func Teams(ctx *context.Context) {
|
|||
}
|
||||
ctx.Data["Teams"] = ctx.Org.Teams
|
||||
|
||||
err := shared_user.LoadHeaderCount(ctx)
|
||||
err := shared_user.RenderOrgHeader(ctx)
|
||||
if err != nil {
|
||||
ctx.ServerError("LoadHeaderCount", err)
|
||||
ctx.ServerError("RenderOrgHeader", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -162,3 +162,15 @@ func LoadHeaderCount(ctx *context.Context) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func RenderOrgHeader(ctx *context.Context) error {
|
||||
if err := LoadHeaderCount(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, _, profileReadmeBlob, profileClose := FindUserProfileReadme(ctx, ctx.Doer)
|
||||
defer profileClose()
|
||||
ctx.Data["HasProfileReadme"] = profileReadmeBlob != nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -692,6 +692,7 @@ func registerRoutes(m *web.Route) {
|
|||
m.Get("", admin.Config)
|
||||
m.Post("", admin.ChangeConfig)
|
||||
m.Post("/test_mail", admin.SendTestMail)
|
||||
m.Post("/test_cache", admin.TestCache)
|
||||
m.Get("/settings", admin.ConfigSettings)
|
||||
})
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
|
||||
|
@ -142,23 +143,27 @@ func (b *Base) RemoteAddr() string {
|
|||
return b.Req.RemoteAddr
|
||||
}
|
||||
|
||||
// Params returns the param on route
|
||||
func (b *Base) Params(p string) string {
|
||||
s, _ := url.PathUnescape(chi.URLParam(b.Req, strings.TrimPrefix(p, ":")))
|
||||
// Params returns the param in request path, eg: "/{var}" => "/a%2fb", then `var == "a/b"`
|
||||
func (b *Base) Params(name string) string {
|
||||
s, err := url.PathUnescape(b.PathParamRaw(name))
|
||||
if err != nil && !setting.IsProd {
|
||||
panic("Failed to unescape path param: " + err.Error() + ", there seems to be a double-unescaping bug")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (b *Base) PathParamRaw(p string) string {
|
||||
return chi.URLParam(b.Req, strings.TrimPrefix(p, ":"))
|
||||
// PathParamRaw returns the raw param in request path, eg: "/{var}" => "/a%2fb", then `var == "a%2fb"`
|
||||
func (b *Base) PathParamRaw(name string) string {
|
||||
return chi.URLParam(b.Req, strings.TrimPrefix(name, ":"))
|
||||
}
|
||||
|
||||
// ParamsInt64 returns the param on route as int64
|
||||
// ParamsInt64 returns the param in request path as int64
|
||||
func (b *Base) ParamsInt64(p string) int64 {
|
||||
v, _ := strconv.ParseInt(b.Params(p), 10, 64)
|
||||
return v
|
||||
}
|
||||
|
||||
// SetParams set params into routes
|
||||
// SetParams set request path params into routes
|
||||
func (b *Base) SetParams(k, v string) {
|
||||
chiCtx := chi.RouteContext(b)
|
||||
chiCtx.URLParams.Add(strings.TrimPrefix(k, ":"), url.PathEscape(v))
|
||||
|
|
|
@ -1006,12 +1006,12 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
|
|||
if refType == RepoRefLegacy {
|
||||
// redirect from old URL scheme to new URL scheme
|
||||
prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*"))), strings.ToLower(ctx.Repo.RepoLink))
|
||||
|
||||
ctx.Redirect(path.Join(
|
||||
redirect := path.Join(
|
||||
ctx.Repo.RepoLink,
|
||||
util.PathEscapeSegments(prefix),
|
||||
ctx.Repo.BranchNameSubURL(),
|
||||
util.PathEscapeSegments(ctx.Repo.TreePath)))
|
||||
util.PathEscapeSegments(ctx.Repo.TreePath))
|
||||
ctx.Redirect(redirect)
|
||||
return cancel
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,13 @@ package repository
|
|||
import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
@ -83,3 +86,13 @@ func TestListUnadoptedRepositories_ListOptions(t *testing.T) {
|
|||
assert.Equal(t, 2, count)
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -229,8 +229,8 @@
|
|||
<dt>{{ctx.Locale.Tr "admin.config.mailer_user"}}</dt>
|
||||
<dd>{{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}</dd>
|
||||
<div class="divider"></div>
|
||||
<dt class="tw-py-1">{{ctx.Locale.Tr "admin.config.send_test_mail"}}</dt>
|
||||
<dd>
|
||||
<dt class="tw-py-1 tw-flex tw-items-center">{{ctx.Locale.Tr "admin.config.send_test_mail"}}</dt>
|
||||
<dd class="tw-py-0">
|
||||
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/admin/config/test_mail" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="ui tiny input">
|
||||
|
@ -260,6 +260,14 @@
|
|||
<dt>{{ctx.Locale.Tr "admin.config.cache_item_ttl"}}</dt>
|
||||
<dd><code>{{.CacheItemTTL}}</code></dd>
|
||||
{{end}}
|
||||
<div class="divider"></div>
|
||||
<dt class="tw-py-1 tw-flex tw-items-center">{{ctx.Locale.Tr "admin.config.cache_test"}}</dt>
|
||||
<dd class="tw-py-0">
|
||||
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/admin/config/test_cache" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<button class="ui tiny primary button">{{ctx.Locale.Tr "test"}}</button>
|
||||
</form>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -76,9 +76,11 @@
|
|||
{{ctx.Locale.Tr "admin.dashboard.system_status"}}
|
||||
</h4>
|
||||
{{/* TODO: make these stats work in multi-server deployments, likely needs per-server stats in DB */}}
|
||||
<div class="no-loading-indicator tw-hidden"></div>
|
||||
<div hx-get="{{$.Link}}/system_status" hx-swap="morph:innerHTML" hx-trigger="every 5s" hx-indicator=".no-loading-indicator" class="ui attached table segment">
|
||||
{{template "admin/system_status" .}}
|
||||
<div class="ui attached table segment">
|
||||
<div class="no-loading-indicator tw-hidden"></div>
|
||||
<div hx-get="{{$.Link}}/system_status" hx-swap="morph:innerHTML" hx-trigger="every 5s" hx-indicator=".no-loading-indicator">
|
||||
{{template "admin/system_status" .}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "admin/layout_footer" .}}
|
||||
|
|
|
@ -17,32 +17,40 @@
|
|||
<div class="ui attached segment tw-hidden self-check-problem" id="self-check-by-frontend"></div>
|
||||
|
||||
{{if .DatabaseCheckHasProblems}}
|
||||
<div class="ui attached segment self-check-problem">
|
||||
{{if .DatabaseType.IsMySQL}}
|
||||
<div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mysql"}}</div>
|
||||
{{else if .DatabaseType.IsMSSQL}}
|
||||
<div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mssql"}}</div>
|
||||
{{end}}
|
||||
{{if .DatabaseCheckCollationMismatch}}
|
||||
<div class="ui red message">{{ctx.Locale.Tr "admin.self_check.database_collation_mismatch" .DatabaseCheckResult.ExpectedCollation}}</div>
|
||||
{{end}}
|
||||
{{if .DatabaseCheckCollationCaseInsensitive}}
|
||||
<div class="ui warning message">{{ctx.Locale.Tr "admin.self_check.database_collation_case_insensitive" .DatabaseCheckResult.DatabaseCollation}}</div>
|
||||
{{end}}
|
||||
{{if .DatabaseCheckInconsistentCollationColumns}}
|
||||
<div class="ui red message">
|
||||
<details>
|
||||
<summary>{{ctx.Locale.Tr "admin.self_check.database_inconsistent_collation_columns" .DatabaseCheckResult.DatabaseCollation}}</summary>
|
||||
<ul class="tw-w-full">
|
||||
{{range .DatabaseCheckInconsistentCollationColumns}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</details>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="ui attached segment self-check-problem">
|
||||
{{if .DatabaseType.IsMySQL}}
|
||||
<div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mysql"}}</div>
|
||||
{{else if .DatabaseType.IsMSSQL}}
|
||||
<div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mssql"}}</div>
|
||||
{{end}}
|
||||
{{if .DatabaseCheckCollationMismatch}}
|
||||
<div class="ui red message">{{ctx.Locale.Tr "admin.self_check.database_collation_mismatch" .DatabaseCheckResult.ExpectedCollation}}</div>
|
||||
{{end}}
|
||||
{{if .DatabaseCheckCollationCaseInsensitive}}
|
||||
<div class="ui warning message">{{ctx.Locale.Tr "admin.self_check.database_collation_case_insensitive" .DatabaseCheckResult.DatabaseCollation}}</div>
|
||||
{{end}}
|
||||
{{if .DatabaseCheckInconsistentCollationColumns}}
|
||||
<div class="ui red message">
|
||||
<details>
|
||||
<summary>{{ctx.Locale.Tr "admin.self_check.database_inconsistent_collation_columns" .DatabaseCheckResult.DatabaseCollation}}</summary>
|
||||
<ul class="tw-w-full">
|
||||
{{range .DatabaseCheckInconsistentCollationColumns}}
|
||||
<li>{{.}}</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</details>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .CacheError}}
|
||||
<div class="ui red message">{{ctx.Locale.Tr "admin.config.cache_test_failed" .CacheError}}</div>
|
||||
{{end}}
|
||||
{{if .CacheSlow}}
|
||||
<div class="ui warning message">{{ctx.Locale.Tr "admin.config.cache_test_slow" .CacheSlow}}</div>
|
||||
{{end}}
|
||||
|
||||
{{/* only shown when there is no visible "self-check-problem" */}}
|
||||
<div class="ui attached segment tw-hidden self-check-no-problem">
|
||||
{{ctx.Locale.Tr "admin.self_check.no_problem_found"}}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
<div class="ui container">
|
||||
<overflow-menu class="ui secondary pointing tabular borderless menu tw-mb-4">
|
||||
<div class="overflow-menu-items">
|
||||
<a class="{{if .PageIsViewRepositories}}active {{end}}item" href="{{$.Org.HomeLink}}">
|
||||
{{if .HasProfileReadme}}
|
||||
<a class="{{if .PageIsViewOverview}}active {{end}}item" href="{{$.Org.HomeLink}}">
|
||||
{{svg "octicon-info"}} {{ctx.Locale.Tr "user.overview"}}
|
||||
</a>
|
||||
{{end}}
|
||||
<a class="{{if .PageIsViewRepositories}}active {{end}}item" href="{{$.Org.HomeLink}}{{if .HasProfileReadme}}?view_repositorys=true{{end}}">
|
||||
{{svg "octicon-repo"}} {{ctx.Locale.Tr "user.repositories"}}
|
||||
{{if .RepoCount}}
|
||||
<div class="ui small label">{{.RepoCount}}</div>
|
||||
|
|
4
templates/swagger/v1_json.tmpl
generated
4
templates/swagger/v1_json.tmpl
generated
|
@ -22404,7 +22404,7 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"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"
|
||||
},
|
||||
"Mode": {
|
||||
|
@ -22427,7 +22427,7 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"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"
|
||||
},
|
||||
"FilePath": {
|
||||
|
|
|
@ -4,7 +4,11 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
|
@ -21,101 +25,167 @@ import (
|
|||
"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) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
|
||||
packageName := "gitea"
|
||||
packageVersion := "1.0.5"
|
||||
packageFilename := "gitea-1.0.5.gem"
|
||||
testGemName := "gitea"
|
||||
testGemVersion := "1.0.5"
|
||||
testGemContent := makeRubyGem(testGemName, testGemVersion)
|
||||
testGemContentChecksum := fmt.Sprintf("%x", sha256.Sum256(testGemContent))
|
||||
|
||||
gemContent, _ := base64.StdEncoding.DecodeString(`bWV0YWRhdGEuZ3oAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAwMDA0NDQAMDAwMDAw
|
||||
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`)
|
||||
testAnotherGemName := "gitea-another"
|
||||
testAnotherGemVersion := "0.99"
|
||||
|
||||
root := fmt.Sprintf("/api/packages/%s/rubygems", user.Name)
|
||||
|
||||
uploadFile := func(t *testing.T, expectedStatus int) {
|
||||
req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/api/v1/gems", root), bytes.NewReader(gemContent)).
|
||||
uploadFile := func(t *testing.T, content []byte, expectedStatus int) {
|
||||
req := NewRequestWithBody(t, "POST", fmt.Sprintf("%s/api/v1/gems", root), bytes.NewReader(content)).
|
||||
AddBasicAuth(user.Name)
|
||||
MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
@ -123,7 +193,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`)
|
|||
t.Run("Upload", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
uploadFile(t, http.StatusCreated)
|
||||
uploadFile(t, testGemContent, http.StatusCreated)
|
||||
|
||||
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeRubyGems)
|
||||
assert.NoError(t, err)
|
||||
|
@ -133,34 +203,33 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`)
|
|||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pd.SemVer)
|
||||
assert.IsType(t, &rubygems.Metadata{}, pd.Metadata)
|
||||
assert.Equal(t, packageName, pd.Package.Name)
|
||||
assert.Equal(t, packageVersion, pd.Version.Version)
|
||||
assert.Equal(t, testGemName, pd.Package.Name)
|
||||
assert.Equal(t, testGemVersion, pd.Version.Version)
|
||||
|
||||
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID)
|
||||
assert.NoError(t, err)
|
||||
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)
|
||||
|
||||
pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID)
|
||||
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) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
uploadFile(t, http.StatusConflict)
|
||||
uploadFile(t, testGemContent, http.StatusConflict)
|
||||
})
|
||||
|
||||
t.Run("Download", func(t *testing.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)
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
|
@ -171,7 +240,7 @@ AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA`)
|
|||
t.Run("DownloadGemspec", func(t *testing.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)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
|
@ -206,22 +275,63 @@ gAAAAP//MS06Gw==`)
|
|||
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)()
|
||||
|
||||
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{}
|
||||
writer := multipart.NewWriter(&body)
|
||||
writer.WriteField("gem_name", packageName)
|
||||
writer.WriteField("version", packageVersion)
|
||||
writer.Close()
|
||||
|
||||
_ = writer.WriteField("gem_name", packageName)
|
||||
_ = writer.WriteField("version", packageVersion)
|
||||
_ = writer.Close()
|
||||
req := NewRequestWithBody(t, "DELETE", fmt.Sprintf("%s/api/v1/gems/yank", root), &body).
|
||||
SetHeader("Content-Type", writer.FormDataContentType()).
|
||||
AddBasicAuth(user.Name)
|
||||
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)
|
||||
assert.NoError(t, err)
|
||||
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())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
@ -14,22 +15,6 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testSrcRouteRedirect(t *testing.T, session *TestSession, user, repo, route, expectedLocation string, expectedStatus int) {
|
||||
prefix := path.Join("/", user, repo, "src")
|
||||
|
||||
// Make request
|
||||
req := NewRequest(t, "GET", path.Join(prefix, route))
|
||||
resp := session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
|
||||
// Check Location header
|
||||
location := resp.Header().Get("Location")
|
||||
assert.Equal(t, path.Join(prefix, expectedLocation), location)
|
||||
|
||||
// Perform redirect
|
||||
req = NewRequest(t, "GET", location)
|
||||
session.MakeRequest(t, req, expectedStatus)
|
||||
}
|
||||
|
||||
func setDefaultBranch(t *testing.T, session *TestSession, user, repo, branch string) {
|
||||
location := path.Join("/", user, repo, "settings/branches")
|
||||
csrf := GetCSRF(t, session, location)
|
||||
|
@ -41,7 +26,7 @@ func setDefaultBranch(t *testing.T, session *TestSession, user, repo, branch str
|
|||
session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func TestNonasciiBranches(t *testing.T) {
|
||||
func TestNonAsciiBranches(t *testing.T) {
|
||||
testRedirects := []struct {
|
||||
from string
|
||||
to string
|
||||
|
@ -98,6 +83,7 @@ func TestNonasciiBranches(t *testing.T) {
|
|||
to: "branch/%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81",
|
||||
status: http.StatusOK,
|
||||
},
|
||||
|
||||
// Tags
|
||||
{
|
||||
from: "Тэг",
|
||||
|
@ -119,6 +105,7 @@ func TestNonasciiBranches(t *testing.T) {
|
|||
to: "tag/%E3%82%BF%E3%82%B0/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.md",
|
||||
status: http.StatusOK,
|
||||
},
|
||||
|
||||
// Files
|
||||
{
|
||||
from: "README.md",
|
||||
|
@ -135,6 +122,7 @@ func TestNonasciiBranches(t *testing.T) {
|
|||
to: "branch/Plus+Is+Not+Space/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.md",
|
||||
status: http.StatusNotFound, // it's not on default branch
|
||||
},
|
||||
|
||||
// Same but url-encoded (few tests)
|
||||
{
|
||||
from: "%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81",
|
||||
|
@ -205,10 +193,23 @@ func TestNonasciiBranches(t *testing.T) {
|
|||
session := loginUser(t, user)
|
||||
|
||||
setDefaultBranch(t, session, user, repo, "Plus+Is+Not+Space")
|
||||
defer setDefaultBranch(t, session, user, repo, "master")
|
||||
|
||||
for _, test := range testRedirects {
|
||||
testSrcRouteRedirect(t, session, user, repo, test.from, test.to, test.status)
|
||||
}
|
||||
t.Run(test.from, func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/src/%s", user, repo, test.from))
|
||||
resp := session.MakeRequest(t, req, http.StatusSeeOther)
|
||||
if resp.Code != http.StatusSeeOther {
|
||||
return
|
||||
}
|
||||
|
||||
setDefaultBranch(t, session, user, repo, "master")
|
||||
redirectLocation := resp.Header().Get("Location")
|
||||
if !assert.Equal(t, fmt.Sprintf("/%s/%s/src/%s", user, repo, test.to), redirectLocation) {
|
||||
return
|
||||
}
|
||||
|
||||
req = NewRequest(t, "GET", redirectLocation)
|
||||
session.MakeRequest(t, req, test.status)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user