mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-27 06:03:51 +03:00
Merge pull request '[gitea] week 2024-49 cherry pick (gitea/main -> forgejo)' (#6110) from earl-warren/wcp/2024-49 into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6110 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
commit
56007ff3a2
24 changed files with 388 additions and 61 deletions
|
@ -1102,7 +1102,7 @@ func FindComments(ctx context.Context, opts *FindCommentsOptions) (CommentList,
|
||||||
sess.Join("INNER", "issue", "issue.id = comment.issue_id")
|
sess.Join("INNER", "issue", "issue.id = comment.issue_id")
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Page != 0 {
|
if opts.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, opts)
|
sess = db.SetSessionPagination(sess, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -663,7 +663,7 @@ func (issue *Issue) BlockedByDependencies(ctx context.Context, opts db.ListOptio
|
||||||
Where("issue_id = ?", issue.ID).
|
Where("issue_id = ?", issue.ID).
|
||||||
// sort by repo id then created date, with the issues of the same repo at the beginning of the list
|
// sort by repo id then created date, with the issues of the same repo at the beginning of the list
|
||||||
OrderBy("CASE WHEN issue.repo_id = ? THEN 0 ELSE issue.repo_id END, issue.created_unix DESC", issue.RepoID)
|
OrderBy("CASE WHEN issue.repo_id = ? THEN 0 ELSE issue.repo_id END, issue.created_unix DESC", issue.RepoID)
|
||||||
if opts.Page != 0 {
|
if opts.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &opts)
|
sess = db.SetSessionPagination(sess, &opts)
|
||||||
}
|
}
|
||||||
err = sess.Find(&issueDeps)
|
err = sess.Find(&issueDeps)
|
||||||
|
|
|
@ -105,7 +105,7 @@ func GetIssueWatchers(ctx context.Context, issueID int64, listOptions db.ListOpt
|
||||||
And("`user`.prohibit_login = ?", false).
|
And("`user`.prohibit_login = ?", false).
|
||||||
Join("INNER", "`user`", "`user`.id = `issue_watch`.user_id")
|
Join("INNER", "`user`", "`user`.id = `issue_watch`.user_id")
|
||||||
|
|
||||||
if listOptions.Page != 0 {
|
if listOptions.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
sess = db.SetSessionPagination(sess, &listOptions)
|
||||||
watches := make([]*IssueWatch, 0, listOptions.PageSize)
|
watches := make([]*IssueWatch, 0, listOptions.PageSize)
|
||||||
return watches, sess.Find(&watches)
|
return watches, sess.Find(&watches)
|
||||||
|
|
|
@ -394,7 +394,7 @@ func GetLabelsByRepoID(ctx context.Context, repoID int64, sortType string, listO
|
||||||
sess.Asc("name")
|
sess.Asc("name")
|
||||||
}
|
}
|
||||||
|
|
||||||
if listOptions.Page != 0 {
|
if listOptions.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
sess = db.SetSessionPagination(sess, &listOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,7 +466,7 @@ func GetLabelsByOrgID(ctx context.Context, orgID int64, sortType string, listOpt
|
||||||
sess.Asc("name")
|
sess.Asc("name")
|
||||||
}
|
}
|
||||||
|
|
||||||
if listOptions.Page != 0 {
|
if listOptions.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
sess = db.SetSessionPagination(sess, &listOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -163,7 +163,7 @@ func FindReactions(ctx context.Context, opts FindReactionsOptions) (ReactionList
|
||||||
Where(opts.toConds()).
|
Where(opts.toConds()).
|
||||||
In("reaction.`type`", setting.UI.Reactions).
|
In("reaction.`type`", setting.UI.Reactions).
|
||||||
Asc("reaction.issue_id", "reaction.comment_id", "reaction.created_unix", "reaction.id")
|
Asc("reaction.issue_id", "reaction.comment_id", "reaction.created_unix", "reaction.id")
|
||||||
if opts.Page != 0 {
|
if opts.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &opts)
|
sess = db.SetSessionPagination(sess, &opts)
|
||||||
|
|
||||||
reactions := make([]*Reaction, 0, opts.PageSize)
|
reactions := make([]*Reaction, 0, opts.PageSize)
|
||||||
|
|
|
@ -81,7 +81,7 @@ func GetUIDsAndStopwatch(ctx context.Context) (map[int64][]*Stopwatch, error) {
|
||||||
func GetUserStopwatches(ctx context.Context, userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) {
|
func GetUserStopwatches(ctx context.Context, userID int64, listOptions db.ListOptions) ([]*Stopwatch, error) {
|
||||||
sws := make([]*Stopwatch, 0, 8)
|
sws := make([]*Stopwatch, 0, 8)
|
||||||
sess := db.GetEngine(ctx).Where("stopwatch.user_id = ?", userID)
|
sess := db.GetEngine(ctx).Where("stopwatch.user_id = ?", userID)
|
||||||
if listOptions.Page != 0 {
|
if listOptions.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
sess = db.SetSessionPagination(sess, &listOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -139,7 +139,7 @@ func (opts *FindTrackedTimesOptions) toSession(e db.Engine) db.Engine {
|
||||||
|
|
||||||
sess = sess.Where(opts.ToConds())
|
sess = sess.Where(opts.ToConds())
|
||||||
|
|
||||||
if opts.Page != 0 {
|
if opts.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, opts)
|
sess = db.SetSessionPagination(sess, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -339,7 +339,7 @@ func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListO
|
||||||
And("`user`.type=?", UserTypeIndividual).
|
And("`user`.type=?", UserTypeIndividual).
|
||||||
And(isUserVisibleToViewerCond(viewer))
|
And(isUserVisibleToViewerCond(viewer))
|
||||||
|
|
||||||
if listOptions.Page != 0 {
|
if listOptions.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
sess = db.SetSessionPagination(sess, &listOptions)
|
||||||
|
|
||||||
users := make([]*User, 0, listOptions.PageSize)
|
users := make([]*User, 0, listOptions.PageSize)
|
||||||
|
@ -361,7 +361,7 @@ func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListO
|
||||||
And("`user`.type IN (?, ?)", UserTypeIndividual, UserTypeOrganization).
|
And("`user`.type IN (?, ?)", UserTypeIndividual, UserTypeOrganization).
|
||||||
And(isUserVisibleToViewerCond(viewer))
|
And(isUserVisibleToViewerCond(viewer))
|
||||||
|
|
||||||
if listOptions.Page != 0 {
|
if listOptions.Page > 0 {
|
||||||
sess = db.SetSessionPagination(sess, &listOptions)
|
sess = db.SetSessionPagination(sess, &listOptions)
|
||||||
|
|
||||||
users := make([]*User, 0, listOptions.PageSize)
|
users := make([]*User, 0, listOptions.PageSize)
|
||||||
|
|
|
@ -41,6 +41,8 @@ func SplitStringAtByteN(input string, n int) (left, right string) {
|
||||||
|
|
||||||
// SplitTrimSpace splits the string at given separator and trims leading and trailing space
|
// SplitTrimSpace splits the string at given separator and trims leading and trailing space
|
||||||
func SplitTrimSpace(input, sep string) []string {
|
func SplitTrimSpace(input, sep string) []string {
|
||||||
|
// Trim initial leading & trailing space
|
||||||
|
input = strings.TrimSpace(input)
|
||||||
// replace CRLF with LF
|
// replace CRLF with LF
|
||||||
input = strings.ReplaceAll(input, "\r\n", "\n")
|
input = strings.ReplaceAll(input, "\r\n", "\n")
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/auth"
|
"code.gitea.io/gitea/modules/auth"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
"code.forgejo.org/go-chi/binding"
|
"code.forgejo.org/go-chi/binding"
|
||||||
"github.com/gobwas/glob"
|
"github.com/gobwas/glob"
|
||||||
|
@ -33,6 +34,7 @@ const (
|
||||||
// AddBindingRules adds additional binding rules
|
// AddBindingRules adds additional binding rules
|
||||||
func AddBindingRules() {
|
func AddBindingRules() {
|
||||||
addGitRefNameBindingRule()
|
addGitRefNameBindingRule()
|
||||||
|
addValidURLListBindingRule()
|
||||||
addValidURLBindingRule()
|
addValidURLBindingRule()
|
||||||
addValidSiteURLBindingRule()
|
addValidSiteURLBindingRule()
|
||||||
addGlobPatternRule()
|
addGlobPatternRule()
|
||||||
|
@ -47,7 +49,7 @@ func addGitRefNameBindingRule() {
|
||||||
// Git refname validation rule
|
// Git refname validation rule
|
||||||
binding.AddRule(&binding.Rule{
|
binding.AddRule(&binding.Rule{
|
||||||
IsMatch: func(rule string) bool {
|
IsMatch: func(rule string) bool {
|
||||||
return strings.HasPrefix(rule, "GitRefName")
|
return rule == "GitRefName"
|
||||||
},
|
},
|
||||||
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
||||||
str := fmt.Sprintf("%v", val)
|
str := fmt.Sprintf("%v", val)
|
||||||
|
@ -61,11 +63,38 @@ func addGitRefNameBindingRule() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addValidURLListBindingRule() {
|
||||||
|
// URL validation rule
|
||||||
|
binding.AddRule(&binding.Rule{
|
||||||
|
IsMatch: func(rule string) bool {
|
||||||
|
return rule == "ValidUrlList"
|
||||||
|
},
|
||||||
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
||||||
|
str := fmt.Sprintf("%v", val)
|
||||||
|
if len(str) == 0 {
|
||||||
|
errs.Add([]string{name}, binding.ERR_URL, "Url")
|
||||||
|
return false, errs
|
||||||
|
}
|
||||||
|
|
||||||
|
ok := true
|
||||||
|
urls := util.SplitTrimSpace(str, "\n")
|
||||||
|
for _, u := range urls {
|
||||||
|
if !IsValidURL(u) {
|
||||||
|
ok = false
|
||||||
|
errs.Add([]string{name}, binding.ERR_URL, u)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ok, errs
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func addValidURLBindingRule() {
|
func addValidURLBindingRule() {
|
||||||
// URL validation rule
|
// URL validation rule
|
||||||
binding.AddRule(&binding.Rule{
|
binding.AddRule(&binding.Rule{
|
||||||
IsMatch: func(rule string) bool {
|
IsMatch: func(rule string) bool {
|
||||||
return strings.HasPrefix(rule, "ValidUrl")
|
return rule == "ValidUrl"
|
||||||
},
|
},
|
||||||
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
||||||
str := fmt.Sprintf("%v", val)
|
str := fmt.Sprintf("%v", val)
|
||||||
|
@ -83,7 +112,7 @@ func addValidSiteURLBindingRule() {
|
||||||
// URL validation rule
|
// URL validation rule
|
||||||
binding.AddRule(&binding.Rule{
|
binding.AddRule(&binding.Rule{
|
||||||
IsMatch: func(rule string) bool {
|
IsMatch: func(rule string) bool {
|
||||||
return strings.HasPrefix(rule, "ValidSiteUrl")
|
return rule == "ValidSiteUrl"
|
||||||
},
|
},
|
||||||
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
||||||
str := fmt.Sprintf("%v", val)
|
str := fmt.Sprintf("%v", val)
|
||||||
|
@ -174,7 +203,7 @@ func addUsernamePatternRule() {
|
||||||
func addValidGroupTeamMapRule() {
|
func addValidGroupTeamMapRule() {
|
||||||
binding.AddRule(&binding.Rule{
|
binding.AddRule(&binding.Rule{
|
||||||
IsMatch: func(rule string) bool {
|
IsMatch: func(rule string) bool {
|
||||||
return strings.HasPrefix(rule, "ValidGroupTeamMap")
|
return rule == "ValidGroupTeamMap"
|
||||||
},
|
},
|
||||||
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
IsValid: func(errs binding.Errors, name string, val any) (bool, binding.Errors) {
|
||||||
_, err := auth.UnmarshalGroupTeamMapping(fmt.Sprintf("%v", val))
|
_, err := auth.UnmarshalGroupTeamMapping(fmt.Sprintf("%v", val))
|
||||||
|
|
|
@ -27,6 +27,7 @@ type (
|
||||||
TestForm struct {
|
TestForm struct {
|
||||||
BranchName string `form:"BranchName" binding:"GitRefName"`
|
BranchName string `form:"BranchName" binding:"GitRefName"`
|
||||||
URL string `form:"ValidUrl" binding:"ValidUrl"`
|
URL string `form:"ValidUrl" binding:"ValidUrl"`
|
||||||
|
URLs string `form:"ValidUrls" binding:"ValidUrlList"`
|
||||||
GlobPattern string `form:"GlobPattern" binding:"GlobPattern"`
|
GlobPattern string `form:"GlobPattern" binding:"GlobPattern"`
|
||||||
RegexPattern string `form:"RegexPattern" binding:"RegexPattern"`
|
RegexPattern string `form:"RegexPattern" binding:"RegexPattern"`
|
||||||
}
|
}
|
||||||
|
|
157
modules/validation/validurllist_test.go
Normal file
157
modules/validation/validurllist_test.go
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package validation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.forgejo.org/go-chi/binding"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This is a copy of all the URL tests cases, plus additional ones to
|
||||||
|
// account for multiple URLs
|
||||||
|
var urlListValidationTestCases = []validationTestCase{
|
||||||
|
{
|
||||||
|
description: "Empty URL",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "URL without port",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://test.lan/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "URL with port",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://test.lan:3000/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "URL with IPv6 address without port",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://[::1]/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "URL with IPv6 address with port",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://[::1]:3000/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Invalid URL",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http//test.lan/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "http//test.lan/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Invalid schema",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "ftp://test.lan/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "ftp://test.lan/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Invalid port",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://test.lan:3x4/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "http://test.lan:3x4/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Invalid port with IPv6 address",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://[::1]:3x4/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "http://[::1]:3x4/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Multi URLs",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://test.lan:3000/\nhttp://test.local/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "Multi URLs with newline",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://test.lan:3000/\nhttp://test.local/\n",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "List with invalid entry",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "http://test.lan:3000/\nhttp://[::1]:3x4/",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "http://[::1]:3x4/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "List with two invalid entries",
|
||||||
|
data: TestForm{
|
||||||
|
URLs: "ftp://test.lan:3000/\nhttp://[::1]:3x4/\n",
|
||||||
|
},
|
||||||
|
expectedErrors: binding.Errors{
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "ftp://test.lan:3000/",
|
||||||
|
},
|
||||||
|
binding.Error{
|
||||||
|
FieldNames: []string{"URLs"},
|
||||||
|
Classification: binding.ERR_URL,
|
||||||
|
Message: "http://[::1]:3x4/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_ValidURLListValidation(t *testing.T) {
|
||||||
|
AddBindingRules()
|
||||||
|
|
||||||
|
for _, testCase := range urlListValidationTestCases {
|
||||||
|
t.Run(testCase.description, func(t *testing.T) {
|
||||||
|
performValidationTest(t, testCase)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
1
release-notes/6110.md
Normal file
1
release-notes/6110.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
feat: [commit](https://codeberg.org/forgejo/forgejo/commit/3973f1022d57a3134e8f775e1c1cc6d398681bb4) Add github compatible tarball download API endpoints
|
|
@ -1352,6 +1352,8 @@ func Routes() *web.Route {
|
||||||
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
|
m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar)
|
||||||
m.Delete("", repo.DeleteAvatar)
|
m.Delete("", repo.DeleteAvatar)
|
||||||
}, reqAdmin(), reqToken())
|
}, reqAdmin(), reqToken())
|
||||||
|
|
||||||
|
m.Get("/{ball_type:tarball|zipball|bundle}/*", reqRepoReader(unit.TypeCode), repo.DownloadArchive)
|
||||||
}, repoAssignment(), checkTokenPublicOnly())
|
}, repoAssignment(), checkTokenPublicOnly())
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
||||||
|
|
||||||
|
|
53
routers/api/v1/repo/download.go
Normal file
53
routers/api/v1/repo/download.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
archiver_service "code.gitea.io/gitea/services/repository/archiver"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DownloadArchive(ctx *context.APIContext) {
|
||||||
|
var tp git.ArchiveType
|
||||||
|
switch ballType := ctx.Params("ball_type"); ballType {
|
||||||
|
case "tarball":
|
||||||
|
tp = git.TARGZ
|
||||||
|
case "zipball":
|
||||||
|
tp = git.ZIP
|
||||||
|
case "bundle":
|
||||||
|
tp = git.BUNDLE
|
||||||
|
default:
|
||||||
|
ctx.Error(http.StatusBadRequest, "", fmt.Sprintf("Unknown archive type: %s", ballType))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Repo.GitRepo == nil {
|
||||||
|
gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Repo.GitRepo = gitRepo
|
||||||
|
defer gitRepo.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
r, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.Params("*"), tp)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("NewRequest", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
archive, err := r.Await(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("archive.Await", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
download(ctx, r.GetArchiveName(), archive)
|
||||||
|
}
|
|
@ -308,7 +308,13 @@ func GetArchive(ctx *context.APIContext) {
|
||||||
|
|
||||||
func archiveDownload(ctx *context.APIContext) {
|
func archiveDownload(ctx *context.APIContext) {
|
||||||
uri := ctx.Params("*")
|
uri := ctx.Params("*")
|
||||||
aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
|
ext, tp, err := archiver_service.ParseFileName(uri)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusBadRequest, "ParseFileName", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, strings.TrimSuffix(uri, ext), tp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
|
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
|
||||||
ctx.Error(http.StatusBadRequest, "unknown archive format", err)
|
ctx.Error(http.StatusBadRequest, "unknown archive format", err)
|
||||||
|
@ -334,9 +340,12 @@ func download(ctx *context.APIContext, archiveName string, archiver *repo_model.
|
||||||
|
|
||||||
// Add nix format link header so tarballs lock correctly:
|
// Add nix format link header so tarballs lock correctly:
|
||||||
// https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md
|
// https://github.com/nixos/nix/blob/56763ff918eb308db23080e560ed2ea3e00c80a7/doc/manual/src/protocols/tarball-fetcher.md
|
||||||
ctx.Resp.Header().Add("Link", fmt.Sprintf("<%s/archive/%s.tar.gz?rev=%s>; rel=\"immutable\"",
|
ctx.Resp.Header().Add("Link", fmt.Sprintf(`<%s/archive/%s.%s?rev=%s>; rel="immutable"`,
|
||||||
ctx.Repo.Repository.APIURL(),
|
ctx.Repo.Repository.APIURL(),
|
||||||
archiver.CommitID, archiver.CommitID))
|
archiver.CommitID,
|
||||||
|
archiver.Type.String(),
|
||||||
|
archiver.CommitID,
|
||||||
|
))
|
||||||
|
|
||||||
rPath := archiver.RelativePath()
|
rPath := archiver.RelativePath()
|
||||||
if setting.RepoArchive.Storage.MinioConfig.ServeDirect {
|
if setting.RepoArchive.Storage.MinioConfig.ServeDirect {
|
||||||
|
|
|
@ -472,7 +472,12 @@ func RedirectDownload(ctx *context.Context) {
|
||||||
// Download an archive of a repository
|
// Download an archive of a repository
|
||||||
func Download(ctx *context.Context) {
|
func Download(ctx *context.Context) {
|
||||||
uri := ctx.Params("*")
|
uri := ctx.Params("*")
|
||||||
aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
|
ext, tp, err := archiver_service.ParseFileName(uri)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("ParseFileName", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, strings.TrimSuffix(uri, ext), tp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
|
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
|
||||||
ctx.Error(http.StatusBadRequest, err.Error())
|
ctx.Error(http.StatusBadRequest, err.Error())
|
||||||
|
@ -547,7 +552,12 @@ func download(ctx *context.Context, archiveName string, archiver *repo_model.Rep
|
||||||
// kind of drop it on the floor if this is the case.
|
// kind of drop it on the floor if this is the case.
|
||||||
func InitiateDownload(ctx *context.Context) {
|
func InitiateDownload(ctx *context.Context) {
|
||||||
uri := ctx.Params("*")
|
uri := ctx.Params("*")
|
||||||
aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, uri)
|
ext, tp, err := archiver_service.ParseFileName(uri)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("ParseFileName", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
aReq, err := archiver_service.NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, strings.TrimSuffix(uri, ext), tp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("archiver_service.NewRequest", err)
|
ctx.ServerError("archiver_service.NewRequest", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -47,7 +47,6 @@ func (oa *OAuth2CommonHandlers) AddApp(ctx *context.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO validate redirect URI
|
|
||||||
app, err := auth.CreateOAuth2Application(ctx, auth.CreateOAuth2ApplicationOptions{
|
app, err := auth.CreateOAuth2Application(ctx, auth.CreateOAuth2ApplicationOptions{
|
||||||
Name: form.Name,
|
Name: form.Name,
|
||||||
RedirectURIs: util.SplitTrimSpace(form.RedirectURIs, "\n"),
|
RedirectURIs: util.SplitTrimSpace(form.RedirectURIs, "\n"),
|
||||||
|
@ -95,11 +94,25 @@ func (oa *OAuth2CommonHandlers) EditSave(ctx *context.Context) {
|
||||||
form := web.GetForm(ctx).(*forms.EditOAuth2ApplicationForm)
|
form := web.GetForm(ctx).(*forms.EditOAuth2ApplicationForm)
|
||||||
|
|
||||||
if ctx.HasError() {
|
if ctx.HasError() {
|
||||||
|
app, err := auth.GetOAuth2ApplicationByID(ctx, ctx.ParamsInt64("id"))
|
||||||
|
if err != nil {
|
||||||
|
if auth.IsErrOAuthApplicationNotFound(err) {
|
||||||
|
ctx.NotFound("Application not found", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.ServerError("GetOAuth2ApplicationByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if app.UID != oa.OwnerID {
|
||||||
|
ctx.NotFound("Application not found", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Data["App"] = app
|
||||||
|
|
||||||
oa.renderEditPage(ctx)
|
oa.renderEditPage(ctx)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO validate redirect URI
|
|
||||||
var err error
|
var err error
|
||||||
if ctx.Data["App"], err = auth.UpdateOAuth2Application(ctx, auth.UpdateOAuth2ApplicationOptions{
|
if ctx.Data["App"], err = auth.UpdateOAuth2Application(ctx, auth.UpdateOAuth2ApplicationOptions{
|
||||||
ID: ctx.ParamsInt64("id"),
|
ID: ctx.ParamsInt64("id"),
|
||||||
|
|
|
@ -388,7 +388,7 @@ func (f *NewAccessTokenForm) GetScope() (auth_model.AccessTokenScope, error) {
|
||||||
// EditOAuth2ApplicationForm form for editing oauth2 applications
|
// EditOAuth2ApplicationForm form for editing oauth2 applications
|
||||||
type EditOAuth2ApplicationForm struct {
|
type EditOAuth2ApplicationForm struct {
|
||||||
Name string `binding:"Required;MaxSize(255)" form:"application_name"`
|
Name string `binding:"Required;MaxSize(255)" form:"application_name"`
|
||||||
RedirectURIs string `binding:"Required" form:"redirect_uris"`
|
RedirectURIs string `binding:"Required;ValidUrlList" form:"redirect_uris"`
|
||||||
ConfidentialClient bool `form:"confidential_client"`
|
ConfidentialClient bool `form:"confidential_client"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,30 +68,36 @@ func (e RepoRefNotFoundError) Is(err error) bool {
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequest creates an archival request, based on the URI. The
|
func ParseFileName(uri string) (ext string, tp git.ArchiveType, err error) {
|
||||||
// resulting ArchiveRequest is suitable for being passed to Await()
|
|
||||||
// if it's determined that the request still needs to be satisfied.
|
|
||||||
func NewRequest(ctx context.Context, repoID int64, repo *git.Repository, uri string) (*ArchiveRequest, error) {
|
|
||||||
r := &ArchiveRequest{
|
|
||||||
RepoID: repoID,
|
|
||||||
}
|
|
||||||
|
|
||||||
var ext string
|
|
||||||
switch {
|
switch {
|
||||||
case strings.HasSuffix(uri, ".zip"):
|
case strings.HasSuffix(uri, ".zip"):
|
||||||
ext = ".zip"
|
ext = ".zip"
|
||||||
r.Type = git.ZIP
|
tp = git.ZIP
|
||||||
case strings.HasSuffix(uri, ".tar.gz"):
|
case strings.HasSuffix(uri, ".tar.gz"):
|
||||||
ext = ".tar.gz"
|
ext = ".tar.gz"
|
||||||
r.Type = git.TARGZ
|
tp = git.TARGZ
|
||||||
case strings.HasSuffix(uri, ".bundle"):
|
case strings.HasSuffix(uri, ".bundle"):
|
||||||
ext = ".bundle"
|
ext = ".bundle"
|
||||||
r.Type = git.BUNDLE
|
tp = git.BUNDLE
|
||||||
default:
|
default:
|
||||||
return nil, ErrUnknownArchiveFormat{RequestFormat: uri}
|
return "", 0, ErrUnknownArchiveFormat{RequestFormat: uri}
|
||||||
|
}
|
||||||
|
return ext, tp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequest creates an archival request, based on the URI. The
|
||||||
|
// resulting ArchiveRequest is suitable for being passed to Await()
|
||||||
|
// if it's determined that the request still needs to be satisfied.
|
||||||
|
func NewRequest(ctx context.Context, repoID int64, repo *git.Repository, refName string, fileType git.ArchiveType) (*ArchiveRequest, error) {
|
||||||
|
if fileType < git.ZIP || fileType > git.BUNDLE {
|
||||||
|
return nil, ErrUnknownArchiveFormat{RequestFormat: fileType.String()}
|
||||||
}
|
}
|
||||||
|
|
||||||
r.refName = strings.TrimSuffix(uri, ext)
|
r := &ArchiveRequest{
|
||||||
|
RepoID: repoID,
|
||||||
|
refName: refName,
|
||||||
|
Type: fileType,
|
||||||
|
}
|
||||||
|
|
||||||
// Get corresponding commit.
|
// Get corresponding commit.
|
||||||
commitID, err := repo.ConvertToGitID(r.refName)
|
commitID, err := repo.ConvertToGitID(r.refName)
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/services/contexttest"
|
"code.gitea.io/gitea/services/contexttest"
|
||||||
|
|
||||||
_ "code.gitea.io/gitea/models/actions"
|
_ "code.gitea.io/gitea/models/actions"
|
||||||
|
@ -32,47 +33,47 @@ func TestArchive_Basic(t *testing.T) {
|
||||||
contexttest.LoadGitRepo(t, ctx)
|
contexttest.LoadGitRepo(t, ctx)
|
||||||
defer ctx.Repo.GitRepo.Close()
|
defer ctx.Repo.GitRepo.Close()
|
||||||
|
|
||||||
bogusReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
bogusReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.ZIP)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, bogusReq)
|
assert.NotNil(t, bogusReq)
|
||||||
assert.EqualValues(t, firstCommit+".zip", bogusReq.GetArchiveName())
|
assert.EqualValues(t, firstCommit+".zip", bogusReq.GetArchiveName())
|
||||||
|
|
||||||
// Check a series of bogus requests.
|
// Check a series of bogus requests.
|
||||||
// Step 1, valid commit with a bad extension.
|
// Step 1, valid commit with a bad extension.
|
||||||
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".dilbert")
|
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, 100)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Nil(t, bogusReq)
|
assert.Nil(t, bogusReq)
|
||||||
|
|
||||||
// Step 2, missing commit.
|
// Step 2, missing commit.
|
||||||
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff.zip")
|
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "dbffff", git.ZIP)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Nil(t, bogusReq)
|
assert.Nil(t, bogusReq)
|
||||||
|
|
||||||
// Step 3, doesn't look like branch/tag/commit.
|
// Step 3, doesn't look like branch/tag/commit.
|
||||||
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "db.zip")
|
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "db", git.ZIP)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.Nil(t, bogusReq)
|
assert.Nil(t, bogusReq)
|
||||||
|
|
||||||
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master.zip")
|
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "master", git.ZIP)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, bogusReq)
|
assert.NotNil(t, bogusReq)
|
||||||
assert.EqualValues(t, "master.zip", bogusReq.GetArchiveName())
|
assert.EqualValues(t, "master.zip", bogusReq.GetArchiveName())
|
||||||
|
|
||||||
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive.zip")
|
bogusReq, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, "test/archive", git.ZIP)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, bogusReq)
|
assert.NotNil(t, bogusReq)
|
||||||
assert.EqualValues(t, "test-archive.zip", bogusReq.GetArchiveName())
|
assert.EqualValues(t, "test-archive.zip", bogusReq.GetArchiveName())
|
||||||
|
|
||||||
// Now two valid requests, firstCommit with valid extensions.
|
// Now two valid requests, firstCommit with valid extensions.
|
||||||
zipReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
zipReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.ZIP)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, zipReq)
|
assert.NotNil(t, zipReq)
|
||||||
|
|
||||||
tgzReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".tar.gz")
|
tgzReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.TARGZ)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, tgzReq)
|
assert.NotNil(t, tgzReq)
|
||||||
|
|
||||||
secondReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".zip")
|
secondReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit, git.ZIP)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, secondReq)
|
assert.NotNil(t, secondReq)
|
||||||
|
|
||||||
|
@ -92,7 +93,7 @@ func TestArchive_Basic(t *testing.T) {
|
||||||
// Sleep two seconds to make sure the queue doesn't change.
|
// Sleep two seconds to make sure the queue doesn't change.
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
|
||||||
zipReq2, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
zipReq2, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.ZIP)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// This zipReq should match what's sitting in the queue, as we haven't
|
// This zipReq should match what's sitting in the queue, as we haven't
|
||||||
// let it release yet. From the consumer's point of view, this looks like
|
// let it release yet. From the consumer's point of view, this looks like
|
||||||
|
@ -107,12 +108,12 @@ func TestArchive_Basic(t *testing.T) {
|
||||||
// Now we'll submit a request and TimedWaitForCompletion twice, before and
|
// Now we'll submit a request and TimedWaitForCompletion twice, before and
|
||||||
// after we release it. We should trigger both the timeout and non-timeout
|
// after we release it. We should trigger both the timeout and non-timeout
|
||||||
// cases.
|
// cases.
|
||||||
timedReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit+".tar.gz")
|
timedReq, err := NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, secondCommit, git.TARGZ)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
assert.NotNil(t, timedReq)
|
assert.NotNil(t, timedReq)
|
||||||
doArchive(db.DefaultContext, timedReq)
|
doArchive(db.DefaultContext, timedReq)
|
||||||
|
|
||||||
zipReq2, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit+".zip")
|
zipReq2, err = NewRequest(ctx, ctx.Repo.Repository.ID, ctx.Repo.GitRepo, firstCommit, git.ZIP)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// Now, we're guaranteed to have released the original zipReq from the queue.
|
// Now, we're guaranteed to have released the original zipReq from the queue.
|
||||||
// Ensure that we don't get handed back the released entry somehow, but they
|
// Ensure that we don't get handed back the released entry somehow, but they
|
||||||
|
|
|
@ -144,12 +144,12 @@ func TestActionsArtifactDownload(t *testing.T) {
|
||||||
var downloadResp downloadArtifactResponse
|
var downloadResp downloadArtifactResponse
|
||||||
DecodeJSON(t, resp, &downloadResp)
|
DecodeJSON(t, resp, &downloadResp)
|
||||||
assert.Len(t, downloadResp.Value, 1)
|
assert.Len(t, downloadResp.Value, 1)
|
||||||
assert.Equal(t, "artifact-download/abc.txt", downloadResp.Value[artifactIdx].Path)
|
assert.Equal(t, "artifact-download/abc.txt", downloadResp.Value[0].Path)
|
||||||
assert.Equal(t, "file", downloadResp.Value[artifactIdx].ItemType)
|
assert.Equal(t, "file", downloadResp.Value[0].ItemType)
|
||||||
assert.Contains(t, downloadResp.Value[artifactIdx].ContentLocation, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts")
|
assert.Contains(t, downloadResp.Value[0].ContentLocation, "/api/actions_pipeline/_apis/pipelines/workflows/791/artifacts")
|
||||||
|
|
||||||
idx = strings.Index(downloadResp.Value[artifactIdx].ContentLocation, "/api/actions_pipeline/_apis/pipelines/")
|
idx = strings.Index(downloadResp.Value[0].ContentLocation, "/api/actions_pipeline/_apis/pipelines/")
|
||||||
url = downloadResp.Value[artifactIdx].ContentLocation[idx:]
|
url = downloadResp.Value[0].ContentLocation[idx:]
|
||||||
req = NewRequest(t, "GET", url).
|
req = NewRequest(t, "GET", url).
|
||||||
AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
|
AddTokenAuth("8061e833a55f6fc0157c98b883e91fcfeeb1a71a")
|
||||||
resp = MakeRequest(t, req, http.StatusOK)
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
|
@ -63,3 +63,43 @@ func TestAPIDownloadArchive(t *testing.T) {
|
||||||
link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master", user2.Name, repo.Name))
|
link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master", user2.Name, repo.Name))
|
||||||
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusBadRequest)
|
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIDownloadArchive2(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
session := loginUser(t, user2.LowerName)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/zipball/master", user2.Name, repo.Name))
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||||
|
bs, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, bs, 320)
|
||||||
|
|
||||||
|
link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/tarball/master", user2.Name, repo.Name))
|
||||||
|
resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||||
|
bs, err = io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, bs, 266)
|
||||||
|
|
||||||
|
// Must return a link to a commit ID as the "immutable" archive link
|
||||||
|
linkHeaderRe := regexp.MustCompile(`^<(https?://.*/api/v1/repos/user2/repo1/archive/[a-f0-9]+\.tar\.gz.*)>; rel="immutable"$`)
|
||||||
|
m := linkHeaderRe.FindStringSubmatch(resp.Header().Get("Link"))
|
||||||
|
assert.NotEmpty(t, m[1])
|
||||||
|
resp = MakeRequest(t, NewRequest(t, "GET", m[1]).AddTokenAuth(token), http.StatusOK)
|
||||||
|
bs2, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
// The locked URL should give the same bytes as the non-locked one
|
||||||
|
assert.EqualValues(t, bs, bs2)
|
||||||
|
|
||||||
|
link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/bundle/master", user2.Name, repo.Name))
|
||||||
|
resp = MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||||
|
bs, err = io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Len(t, bs, 382)
|
||||||
|
|
||||||
|
link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/archive/master", user2.Name, repo.Name))
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
|
@ -57,16 +57,12 @@ export async function renderMermaid() {
|
||||||
mermaidBlock.append(btn);
|
mermaidBlock.append(btn);
|
||||||
|
|
||||||
const updateIframeHeight = () => {
|
const updateIframeHeight = () => {
|
||||||
iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`;
|
const body = iframe.contentWindow?.document?.body;
|
||||||
|
if (body) {
|
||||||
|
iframe.style.height = `${body.clientHeight}px`;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// update height when element's visibility state changes, for example when the diagram is inside
|
|
||||||
// a <details> + <summary> block and the <details> block becomes visible upon user interaction, it
|
|
||||||
// would initially set a incorrect height and the correct height is set during this callback.
|
|
||||||
(new IntersectionObserver(() => {
|
|
||||||
updateIframeHeight();
|
|
||||||
}, {root: document.documentElement})).observe(iframe);
|
|
||||||
|
|
||||||
iframe.addEventListener('load', () => {
|
iframe.addEventListener('load', () => {
|
||||||
pre.replaceWith(mermaidBlock);
|
pre.replaceWith(mermaidBlock);
|
||||||
mermaidBlock.classList.remove('tw-hidden');
|
mermaidBlock.classList.remove('tw-hidden');
|
||||||
|
@ -75,6 +71,13 @@ export async function renderMermaid() {
|
||||||
mermaidBlock.classList.remove('is-loading');
|
mermaidBlock.classList.remove('is-loading');
|
||||||
iframe.classList.remove('tw-invisible');
|
iframe.classList.remove('tw-invisible');
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
|
// update height when element's visibility state changes, for example when the diagram is inside
|
||||||
|
// a <details> + <summary> block and the <details> block becomes visible upon user interaction, it
|
||||||
|
// would initially set a incorrect height and the correct height is set during this callback.
|
||||||
|
(new IntersectionObserver(() => {
|
||||||
|
updateIframeHeight();
|
||||||
|
}, {root: document.documentElement})).observe(iframe);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.append(mermaidBlock);
|
document.body.append(mermaidBlock);
|
||||||
|
|
Loading…
Reference in a new issue