1
0
mirror of https://github.com/go-gitea/gitea.git synced 2024-06-26 01:15:32 +00:00

Compare commits

...

22 Commits

Author SHA1 Message Date
József Fényes
60a1111b2f
Merge 56410b177c into 4b6eb46e69 2024-06-17 19:50:52 +03:00
silverwind
4b6eb46e69
Fix double border in system status table (#31363)
Fix regression from https://github.com/go-gitea/gitea/pull/30712 where
the introduction of this `<div>` caused the `.ui.attached:not(.message)
+ .ui.attached.segment:not(.top)` CSS selector to no longer work and
cause a double border.

Before:

<img width="200" alt="Screenshot 2024-06-13 at 19 06 12"
src="https://github.com/go-gitea/gitea/assets/115237/a9fa0688-adf0-4b2d-a958-6a7679a62031">

After:
<img width="232" alt="Screenshot 2024-06-13 at 19 05 57"
src="https://github.com/go-gitea/gitea/assets/115237/025b780f-f72f-4049-86de-a5d84851bd1d">

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-06-17 15:21:59 +02:00
wxiaoguang
0f09c22663
Improve rubygems package registry (#31357)
To make it work with Bundler:
https://guides.rubygems.org/rubygems-org-compact-index-api/

It only adds 2 new API endpoints and improves some tests, existing logic
is not changed.
2024-06-17 08:42:46 +00:00
wxiaoguang
25f3ec5b65
Fix natural sort (#31384)
Fix #31374
2024-06-17 06:45:12 +00:00
Brecht Van Lommel
597d1da96b
Fix missing images in editor preview due to wrong links (#31299)
Parse base path and tree path so that media links can be correctly
created with /media/.

Resolves #31294

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2024-06-17 14:16:14 +08:00
wxiaoguang
f5dfd7d73c
Add a simple test for AdoptRepository (#31391)
Follow #31333
2024-06-17 01:18:35 +00:00
GiteaBot
129206da45 [skip ci] Updated licenses and gitignores 2024-06-17 00:27:39 +00:00
József Fényes
56410b177c
Merge branch 'go-gitea:main' into main 2024-05-28 12:14:13 +02:00
József Fényes
c1f075931b filepath, logging modification 2024-05-24 15:15:13 +02:00
József Fényes
4d72886b00 add new line for config cheatsheet 2024-05-24 11:32:32 +02:00
József Fényes
bfc0cd6e49 use filepath for hooks 2024-05-24 11:14:58 +02:00
József Fényes
8b5debbfcc reject paths passed in for hook names in configuration 2024-05-24 11:13:46 +02:00
József Fényes
9acbb58769 add documentation for hook name configuration 2024-05-24 11:11:34 +02:00
József Fényes
23f341e3b6 remove hooks from helper 2024-05-24 11:07:29 +02:00
József Fényes
007c2e07c2 update config template 2024-05-24 11:06:59 +02:00
József Fényes
d3c8e5de27
Merge branch 'main' into main 2024-05-24 10:55:53 +02:00
József Fényes
3214160864
Merge branch 'main' into main 2024-05-23 15:03:53 +02:00
József Fényes
d1dc24972a Add SPDX-License-Identifiers 2024-05-23 14:55:47 +02:00
József Fényes
b9017f7d11 remove ineffectual assignment to i (#30985) 2024-05-23 14:40:33 +02:00
József Fényes
08bae4cfd0 add english translations (#30985) 2024-05-23 14:22:29 +02:00
József Fényes
cdf233b3ea
Merge branch 'go-gitea:main' into main 2024-05-23 14:07:56 +02:00
József Fényes
fa31d9c6b0 hook configuration (#30985) 2024-05-23 14:07:20 +02:00
24 changed files with 704 additions and 236 deletions

View File

@ -421,6 +421,19 @@ USER = root
;;
;SLOW_QUERY_THRESHOLD = 5s
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[git.hooks]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; Git prereceive hook name
;GIT_HOOK_PRERECEIVE_NAME = pre-receive
;; Git update hook name
;GIT_HOOK_UPDATE_NAME = update
;; Git post receive hook name
;GIT_HOOK_POSTRECEIVE_NAME = post-receive
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[security]

View File

@ -1125,6 +1125,12 @@ This section only does "set" config, a removed config key from this section won'
- `core.logAllRefUpdates`: **true**
- `gc.reflogExpire`: **90**
### Git - Hook options (`git.hooks`)
- `GIT_HOOK_PRERECEIVE_NAME`: **pre-receive**: Specifies the name of the pre-receive hook. Wont accept paths as value, will only accept filenames.
- `GIT_HOOK_UPDATE_NAME`: **update**: Specifies the name of the update hook. Wont accept paths as value, will only accept filenames.
- `GIT_HOOK_POSTRECEIVE_NAME`: **post-receive**: Specifies the name of the post-receive hook. Wont accept paths as value, will only accept filenames.
## Metrics (`metrics`)
- `ENABLED`: **false**: Enables /metrics endpoint for prometheus.

View File

@ -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)
}

View File

@ -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")
}

View File

@ -25,8 +25,7 @@ import (
const RequiredVersion = "2.0.0" // the minimum Git version required
type Features struct {
gitVersion *version.Version
gitVersion *version.Version
UsingGogit bool
SupportProcReceive bool // >= 2.29
SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an experimental curiosity

View File

@ -12,27 +12,19 @@ import (
"strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
)
// hookNames is a list of Git server hooks' name that are supported.
var hookNames = []string{
"pre-receive",
"update",
"post-receive",
}
var hookNames = []string{"pre-receive", "update", "post-receive"}
// ErrNotValidHook error when a git hook is not valid
var ErrNotValidHook = errors.New("not a valid Git hook")
// IsValidHookName returns true if given name is a valid Git hook.
func IsValidHookName(name string) bool {
for _, hn := range hookNames {
if hn == name {
return true
}
// hookNames contains the hook name (key) linked with the filename of the resulting hook.
func GetHookNames() map[string]string {
return map[string]string{
"pre-receive": setting.GitHookPrereceiveName,
"update": setting.GitHookUpdateName,
"post-receive": setting.GitHookPostreceiveName,
}
return false
}
// Hook represents a Git hook.
@ -44,6 +36,19 @@ type Hook struct {
path string // Hook file path.
}
// ErrNotValidHook error when a git hook is not valid
var ErrNotValidHook = errors.New("not a valid Git hook")
// IsValidHookName returns true if given name is a valid Git hook.
func IsValidHookName(name string) bool {
for hn := range GetHookNames() {
if hn == name {
return true
}
}
return false
}
// GetHook returns a Git hook by given name and repository.
func GetHook(repoPath, name string) (*Hook, error) {
if !IsValidHookName(name) {
@ -51,7 +56,7 @@ func GetHook(repoPath, name string) (*Hook, error) {
}
h := &Hook{
name: name,
path: path.Join(repoPath, "hooks", name+".d", name),
path: filepath.Join(repoPath, "hooks", name+".d", GetHookNames()[name]),
}
samplePath := filepath.Join(repoPath, "hooks", name+".sample")
if isFile(h.path) {
@ -107,9 +112,9 @@ func ListHooks(repoPath string) (_ []*Hook, err error) {
return nil, errors.New("hooks path does not exist")
}
hooks := make([]*Hook, len(hookNames))
for i, name := range hookNames {
hooks[i], err = GetHook(repoPath, name)
hooks := make([]*Hook, len(GetHookNames()))
for i := range hookNames {
hooks[i], err = GetHook(repoPath, hookNames[i])
if err != nil {
return nil, err
}

View File

@ -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 {

42
modules/setting/hooks.go Normal file
View File

@ -0,0 +1,42 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package setting
import (
"fmt"
"path/filepath"
"code.gitea.io/gitea/modules/log"
)
var (
// Git hook settings
GitHookPrereceiveName string
GitHookPostreceiveName string
GitHookUpdateName string
)
func isValidFileName(filename string) error {
if filepath.Base(filename) != filename || filepath.IsAbs(filename) || filename == "." || filename == ".." {
return fmt.Errorf("can only contain filenames, not other directories")
}
return nil
}
func loadHooksFrom(rootCfg ConfigProvider) {
githooks := rootCfg.Section("git.hooks")
GitHookPrereceiveName = githooks.Key("GIT_HOOK_PRERECEIVE_NAME").MustString("pre-receive")
GitHookUpdateName = githooks.Key("GIT_HOOK_UPDATE_NAME").MustString("update")
GitHookPostreceiveName = githooks.Key("GIT_HOOK_POSTRECEIVE_NAME").MustString("post-receive")
if err := isValidFileName(GitHookPrereceiveName); err != nil {
log.Fatal("'%s' is an invalid [git.hooks].GIT_HOOK_PRERECEIVE_NAME: %v", GitHookPrereceiveName, err)
}
if err := isValidFileName(GitHookUpdateName); err != nil {
log.Fatal("'%s' is an invalid [git.hooks].GIT_HOOK_UPDATE_NAME: %v", GitHookUpdateName, err)
}
if err := isValidFileName(GitHookPostreceiveName); err != nil {
log.Fatal("'%s' is an invalid [git.hooks].GIT_HOOK_POSTRECEIVE_NAME: %v", GitHookPostreceiveName, err)
}
}

View File

@ -118,6 +118,7 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error {
loadOAuth2From(cfg)
loadSecurityFrom(cfg)
loadHooksFrom(cfg)
if err := loadAttachmentFrom(cfg); err != nil {
return err
}

View File

@ -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

47
options/gitignore/IAR Normal file
View 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

View File

@ -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/

View File

@ -35,6 +35,3 @@ override.tf.json
# Ignore CLI configuration files
.terraformrc
terraform.rc
# Ignore hcl file
.terraform.lock.hcl

View File

@ -3138,6 +3138,9 @@ config.disable_router_log = Disable Router Log
config.run_user = Run As Username
config.run_mode = Run Mode
config.git_version = Git Version
config.git_hookprereceivename = Git Prereceive Hook Name
config.git_hookupdatename = Git Update Hook Name
config.git_postreceivename = Git Postreceive Hook Name
config.app_data_path = App Data Path
config.repo_root_path = Repository Root Path
config.lfs_root_path = LFS Root Path

View File

@ -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)

View File

@ -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,

View File

@ -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 {

View File

@ -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,

View File

@ -113,7 +113,9 @@ func Config(ctx *context.Context) {
ctx.Data["RunUser"] = setting.RunUser
ctx.Data["RunMode"] = util.ToTitleCase(setting.RunMode)
ctx.Data["GitVersion"] = git.DefaultFeatures().VersionInfo()
ctx.Data["GitHookPrereceiveName"] = setting.GitHookPrereceiveName
ctx.Data["GitHookUpdateName"] = setting.GitHookUpdateName
ctx.Data["GitHookPostreceiveName"] = setting.GitHookPostreceiveName
ctx.Data["AppDataPath"] = setting.AppDataPath
ctx.Data["RepoRootPath"] = setting.RepoRootPath
ctx.Data["CustomRootPath"] = setting.CustomPath

View File

@ -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)
}

View File

@ -311,6 +311,18 @@
<dd>{{.Git.Timeout.Pull}} {{ctx.Locale.Tr "tool.raw_seconds"}}</dd>
<dt>{{ctx.Locale.Tr "admin.config.git_gc_timeout"}}</dt>
<dd>{{.Git.Timeout.GC}} {{ctx.Locale.Tr "tool.raw_seconds"}}</dd>
<div class="divider"></div>
<dt>{{ctx.Locale.Tr "admin.config.git_hookprereceivename"}}</dt>
<dd>{{.GitHookPrereceiveName}}</dd>
<dt>{{ctx.Locale.Tr "admin.config.git_hookupdatename"}}</dt>
<dd>{{.GitHookUpdateName}}</dd>
<dt>{{ctx.Locale.Tr "admin.config.git_postreceivename"}}</dt>
<dd>{{.GitHookPostreceiveName}}</dd>
</dl>
</div>

View File

@ -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" .}}

View File

@ -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": {

View File

@ -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())
})
}