mirror of
https://github.com/go-gitea/gitea.git
synced 2025-01-19 14:03:40 +03:00
Merge branch 'main' into add-file-tree-to-file-view-page
This commit is contained in:
commit
a86c9e43a9
@ -10,6 +10,7 @@ import (
|
||||
"code.gitea.io/gitea/models/db"
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
@ -51,7 +52,7 @@ func GetRunnerToken(ctx context.Context, token string) (*ActionRunnerToken, erro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, fmt.Errorf("runner token %q: %w", token, util.ErrNotExist)
|
||||
return nil, fmt.Errorf(`runner token "%s...": %w`, base.TruncateString(token, 3), util.ErrNotExist)
|
||||
}
|
||||
return &runnerToken, nil
|
||||
}
|
||||
@ -68,19 +69,15 @@ func UpdateRunnerToken(ctx context.Context, r *ActionRunnerToken, cols ...string
|
||||
return err
|
||||
}
|
||||
|
||||
// NewRunnerToken creates a new active runner token and invalidate all old tokens
|
||||
// NewRunnerTokenWithValue creates a new active runner token and invalidate all old tokens
|
||||
// ownerID will be ignored and treated as 0 if repoID is non-zero.
|
||||
func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
|
||||
func NewRunnerTokenWithValue(ctx context.Context, ownerID, repoID int64, token string) (*ActionRunnerToken, error) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
// It's trying to create a runner token that belongs to a repository, but OwnerID has been set accidentally.
|
||||
// Remove OwnerID to avoid confusion; it's not worth returning an error here.
|
||||
ownerID = 0
|
||||
}
|
||||
|
||||
token, err := util.CryptoRandomString(40)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runnerToken := &ActionRunnerToken{
|
||||
OwnerID: ownerID,
|
||||
RepoID: repoID,
|
||||
@ -95,11 +92,19 @@ func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerTo
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.GetEngine(ctx).Insert(runnerToken)
|
||||
_, err := db.GetEngine(ctx).Insert(runnerToken)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
|
||||
token, err := util.CryptoRandomString(40)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewRunnerTokenWithValue(ctx, ownerID, repoID, token)
|
||||
}
|
||||
|
||||
// GetLatestRunnerToken returns the latest runner token
|
||||
func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
|
||||
if ownerID != 0 && repoID != 0 {
|
||||
|
@ -206,7 +206,7 @@ func CreateTestEngine(opts FixturesOptions) error {
|
||||
x, err := xorm.NewEngine("sqlite3", "file::memory:?cache=shared&_txlock=immediate")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "unknown driver") {
|
||||
return fmt.Errorf(`sqlite3 requires: import _ "github.com/mattn/go-sqlite3" or -tags sqlite,sqlite_unlock_notify%s%w`, "\n", err)
|
||||
return fmt.Errorf(`sqlite3 requires: -tags sqlite,sqlite_unlock_notify%s%w`, "\n", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
@ -216,8 +216,6 @@ type CommitsByFileAndRangeOptions struct {
|
||||
|
||||
// CommitsByFileAndRange return the commits according revision file and the page
|
||||
func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) ([]*Commit, error) {
|
||||
skip := (opts.Page - 1) * setting.Git.CommitsRangeSize
|
||||
|
||||
stdoutReader, stdoutWriter := io.Pipe()
|
||||
defer func() {
|
||||
_ = stdoutReader.Close()
|
||||
@ -226,8 +224,8 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
|
||||
go func() {
|
||||
stderr := strings.Builder{}
|
||||
gitCmd := NewCommand(repo.Ctx, "rev-list").
|
||||
AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize*opts.Page).
|
||||
AddOptionFormat("--skip=%d", skip)
|
||||
AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
|
||||
AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize)
|
||||
gitCmd.AddDynamicArguments(opts.Revision)
|
||||
|
||||
if opts.Not != "" {
|
||||
|
@ -8,7 +8,11 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRepository_GetCommitBranches(t *testing.T) {
|
||||
@ -126,3 +130,21 @@ func TestGetRefCommitID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitsByFileAndRange(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.Git.CommitsRangeSize, 2)()
|
||||
|
||||
bareRepo1Path := filepath.Join(testReposDir, "repo1_bare")
|
||||
bareRepo1, err := openRepositoryWithDefaultContext(bareRepo1Path)
|
||||
require.NoError(t, err)
|
||||
defer bareRepo1.Close()
|
||||
|
||||
// "foo" has 3 commits in "master" branch
|
||||
commits, err := bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 1})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, commits, 2)
|
||||
|
||||
commits, err = bareRepo1.CommitsByFileAndRange(CommitsByFileAndRangeOptions{Revision: "master", File: "foo", Page: 2})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, commits, 1)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
)
|
||||
@ -38,63 +39,32 @@ func OpenWikiRepository(ctx context.Context, repo Repository) (*git.Repository,
|
||||
|
||||
// contextKey is a value for use with context.WithValue.
|
||||
type contextKey struct {
|
||||
name string
|
||||
}
|
||||
|
||||
// RepositoryContextKey is a context key. It is used with context.Value() to get the current Repository for the context
|
||||
var RepositoryContextKey = &contextKey{"repository"}
|
||||
|
||||
// RepositoryFromContext attempts to get the repository from the context
|
||||
func repositoryFromContext(ctx context.Context, repo Repository) *git.Repository {
|
||||
value := ctx.Value(RepositoryContextKey)
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if gitRepo, ok := value.(*git.Repository); ok && gitRepo != nil {
|
||||
if gitRepo.Path == repoPath(repo) {
|
||||
return gitRepo
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
repoPath string
|
||||
}
|
||||
|
||||
// RepositoryFromContextOrOpen attempts to get the repository from the context or just opens it
|
||||
func RepositoryFromContextOrOpen(ctx context.Context, repo Repository) (*git.Repository, io.Closer, error) {
|
||||
gitRepo := repositoryFromContext(ctx, repo)
|
||||
if gitRepo != nil {
|
||||
return gitRepo, util.NopCloser{}, nil
|
||||
ds := reqctx.GetRequestDataStore(ctx)
|
||||
if ds != nil {
|
||||
gitRepo, err := RepositoryFromRequestContextOrOpen(ctx, ds, repo)
|
||||
return gitRepo, util.NopCloser{}, err
|
||||
}
|
||||
|
||||
gitRepo, err := OpenRepository(ctx, repo)
|
||||
return gitRepo, gitRepo, err
|
||||
}
|
||||
|
||||
// repositoryFromContextPath attempts to get the repository from the context
|
||||
func repositoryFromContextPath(ctx context.Context, path string) *git.Repository {
|
||||
value := ctx.Value(RepositoryContextKey)
|
||||
if value == nil {
|
||||
return nil
|
||||
// RepositoryFromRequestContextOrOpen opens the repository at the given relative path in the provided request context
|
||||
// The repo will be automatically closed when the request context is done
|
||||
func RepositoryFromRequestContextOrOpen(ctx context.Context, ds reqctx.RequestDataStore, repo Repository) (*git.Repository, error) {
|
||||
ck := contextKey{repoPath: repoPath(repo)}
|
||||
if gitRepo, ok := ctx.Value(ck).(*git.Repository); ok {
|
||||
return gitRepo, nil
|
||||
}
|
||||
|
||||
if repo, ok := value.(*git.Repository); ok && repo != nil {
|
||||
if repo.Path == path {
|
||||
return repo
|
||||
}
|
||||
gitRepo, err := git.OpenRepository(ctx, ck.repoPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RepositoryFromContextOrOpenPath attempts to get the repository from the context or just opens it
|
||||
// Deprecated: Use RepositoryFromContextOrOpen instead
|
||||
func RepositoryFromContextOrOpenPath(ctx context.Context, path string) (*git.Repository, io.Closer, error) {
|
||||
gitRepo := repositoryFromContextPath(ctx, path)
|
||||
if gitRepo != nil {
|
||||
return gitRepo, util.NopCloser{}, nil
|
||||
}
|
||||
|
||||
gitRepo, err := git.OpenRepository(ctx, path)
|
||||
return gitRepo, gitRepo, err
|
||||
ds.AddCloser(gitRepo)
|
||||
ds.SetContextValue(ck, gitRepo)
|
||||
return gitRepo, nil
|
||||
}
|
||||
|
@ -14,15 +14,11 @@ import (
|
||||
// WalkReferences walks all the references from the repository
|
||||
// refname is empty, ObjectTag or ObjectBranch. All other values should be treated as equivalent to empty.
|
||||
func WalkReferences(ctx context.Context, repo Repository, walkfn func(sha1, refname string) error) (int, error) {
|
||||
gitRepo := repositoryFromContext(ctx, repo)
|
||||
if gitRepo == nil {
|
||||
var err error
|
||||
gitRepo, err = OpenRepository(ctx, repo)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer gitRepo.Close()
|
||||
gitRepo, closer, err := RepositoryFromContextOrOpen(ctx, repo)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer closer.Close()
|
||||
|
||||
i := 0
|
||||
iter, err := gitRepo.GoGitRepo().References()
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/references"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
@ -194,3 +195,21 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
node = node.NextSibling.NextSibling
|
||||
}
|
||||
}
|
||||
|
||||
func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
next := node.NextSibling
|
||||
|
||||
for node != nil && node != next {
|
||||
found, ref := references.FindRenderizableCommitCrossReference(node.Data)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha)
|
||||
linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, "commit", ref.CommitSha), LinkTypeApp)
|
||||
link := createLink(ctx, linkHref, reftext, "commit")
|
||||
|
||||
replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link)
|
||||
node = node.NextSibling.NextSibling
|
||||
}
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
package markup
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/base"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/references"
|
||||
@ -16,8 +16,16 @@ import (
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
"golang.org/x/net/html/atom"
|
||||
)
|
||||
|
||||
type RenderIssueIconTitleOptions struct {
|
||||
OwnerName string
|
||||
RepoName string
|
||||
LinkHref string
|
||||
IssueIndex int64
|
||||
}
|
||||
|
||||
func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
if ctx.RenderOptions.Metas == nil {
|
||||
return
|
||||
@ -66,6 +74,27 @@ func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
}
|
||||
}
|
||||
|
||||
func createIssueLinkContentWithSummary(ctx *RenderContext, linkHref string, ref *references.RenderizableReference) *html.Node {
|
||||
if DefaultRenderHelperFuncs.RenderRepoIssueIconTitle == nil {
|
||||
return nil
|
||||
}
|
||||
issueIndex, _ := strconv.ParseInt(ref.Issue, 10, 64)
|
||||
h, err := DefaultRenderHelperFuncs.RenderRepoIssueIconTitle(ctx, RenderIssueIconTitleOptions{
|
||||
OwnerName: ref.Owner,
|
||||
RepoName: ref.Name,
|
||||
LinkHref: linkHref,
|
||||
IssueIndex: issueIndex,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("RenderRepoIssueIconTitle failed: %v", err)
|
||||
return nil
|
||||
}
|
||||
if h == "" {
|
||||
return nil
|
||||
}
|
||||
return &html.Node{Type: html.RawNode, Data: string(ctx.RenderInternal.ProtectSafeAttrs(h))}
|
||||
}
|
||||
|
||||
func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
if ctx.RenderOptions.Metas == nil {
|
||||
return
|
||||
@ -76,32 +105,28 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
// old logic: crossLinkOnly := ctx.RenderOptions.Metas["mode"] == "document" && !ctx.IsWiki
|
||||
crossLinkOnly := ctx.RenderOptions.Metas["markupAllowShortIssuePattern"] != "true"
|
||||
|
||||
var (
|
||||
found bool
|
||||
ref *references.RenderizableReference
|
||||
)
|
||||
var ref *references.RenderizableReference
|
||||
|
||||
next := node.NextSibling
|
||||
|
||||
for node != nil && node != next {
|
||||
_, hasExtTrackFormat := ctx.RenderOptions.Metas["format"]
|
||||
|
||||
// Repos with external issue trackers might still need to reference local PRs
|
||||
// We need to concern with the first one that shows up in the text, whichever it is
|
||||
isNumericStyle := ctx.RenderOptions.Metas["style"] == "" || ctx.RenderOptions.Metas["style"] == IssueNameStyleNumeric
|
||||
foundNumeric, refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle, crossLinkOnly)
|
||||
refNumeric := references.FindRenderizableReferenceNumeric(node.Data, hasExtTrackFormat && !isNumericStyle, crossLinkOnly)
|
||||
|
||||
switch ctx.RenderOptions.Metas["style"] {
|
||||
case "", IssueNameStyleNumeric:
|
||||
found, ref = foundNumeric, refNumeric
|
||||
ref = refNumeric
|
||||
case IssueNameStyleAlphanumeric:
|
||||
found, ref = references.FindRenderizableReferenceAlphanumeric(node.Data)
|
||||
ref = references.FindRenderizableReferenceAlphanumeric(node.Data)
|
||||
case IssueNameStyleRegexp:
|
||||
pattern, err := regexplru.GetCompiled(ctx.RenderOptions.Metas["regexp"])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
found, ref = references.FindRenderizableReferenceRegexp(node.Data, pattern)
|
||||
ref = references.FindRenderizableReferenceRegexp(node.Data, pattern)
|
||||
}
|
||||
|
||||
// Repos with external issue trackers might still need to reference local PRs
|
||||
@ -109,17 +134,17 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
if hasExtTrackFormat && !isNumericStyle && refNumeric != nil {
|
||||
// If numeric (PR) was found, and it was BEFORE the non-numeric pattern, use that
|
||||
// Allow a free-pass when non-numeric pattern wasn't found.
|
||||
if found && (ref == nil || refNumeric.RefLocation.Start < ref.RefLocation.Start) {
|
||||
found = foundNumeric
|
||||
if ref == nil || refNumeric.RefLocation.Start < ref.RefLocation.Start {
|
||||
ref = refNumeric
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
|
||||
if ref == nil {
|
||||
return
|
||||
}
|
||||
|
||||
var link *html.Node
|
||||
reftext := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
|
||||
refText := node.Data[ref.RefLocation.Start:ref.RefLocation.End]
|
||||
if hasExtTrackFormat && !ref.IsPull {
|
||||
ctx.RenderOptions.Metas["index"] = ref.Issue
|
||||
|
||||
@ -129,18 +154,23 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
log.Error("unable to expand template vars for ref %s, err: %v", ref.Issue, err)
|
||||
}
|
||||
|
||||
link = createLink(ctx, res, reftext, "ref-issue ref-external-issue")
|
||||
link = createLink(ctx, res, refText, "ref-issue ref-external-issue")
|
||||
} else {
|
||||
// Path determines the type of link that will be rendered. It's unknown at this point whether
|
||||
// the linked item is actually a PR or an issue. Luckily it's of no real consequence because
|
||||
// Gitea will redirect on click as appropriate.
|
||||
issueOwner := util.Iif(ref.Owner == "", ctx.RenderOptions.Metas["user"], ref.Owner)
|
||||
issueRepo := util.Iif(ref.Owner == "", ctx.RenderOptions.Metas["repo"], ref.Name)
|
||||
issuePath := util.Iif(ref.IsPull, "pulls", "issues")
|
||||
if ref.Owner == "" {
|
||||
linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ctx.RenderOptions.Metas["user"], ctx.RenderOptions.Metas["repo"], issuePath, ref.Issue), LinkTypeApp)
|
||||
link = createLink(ctx, linkHref, reftext, "ref-issue")
|
||||
} else {
|
||||
linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, issuePath, ref.Issue), LinkTypeApp)
|
||||
link = createLink(ctx, linkHref, reftext, "ref-issue")
|
||||
linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(issueOwner, issueRepo, issuePath, ref.Issue), LinkTypeApp)
|
||||
|
||||
// at the moment, only render the issue index in a full line (or simple line) as icon+title
|
||||
// otherwise it would be too noisy for "take #1 as an example" in a sentence
|
||||
if node.Parent.DataAtom == atom.Li && ref.RefLocation.Start < 20 && ref.RefLocation.End == len(node.Data) {
|
||||
link = createIssueLinkContentWithSummary(ctx, linkHref, ref)
|
||||
}
|
||||
if link == nil {
|
||||
link = createLink(ctx, linkHref, refText, "ref-issue")
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,21 +198,3 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
node = node.NextSibling.NextSibling.NextSibling.NextSibling
|
||||
}
|
||||
}
|
||||
|
||||
func commitCrossReferencePatternProcessor(ctx *RenderContext, node *html.Node) {
|
||||
next := node.NextSibling
|
||||
|
||||
for node != nil && node != next {
|
||||
found, ref := references.FindRenderizableCommitCrossReference(node.Data)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
reftext := ref.Owner + "/" + ref.Name + "@" + base.ShortSha(ref.CommitSha)
|
||||
linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, "commit", ref.CommitSha), LinkTypeApp)
|
||||
link := createLink(ctx, linkHref, reftext, "commit")
|
||||
|
||||
replaceContent(node, ref.RefLocation.Start, ref.RefLocation.End, link)
|
||||
node = node.NextSibling.NextSibling
|
||||
}
|
||||
}
|
||||
|
72
modules/markup/html_issue_test.go
Normal file
72
modules/markup/html_issue_test.go
Normal file
@ -0,0 +1,72 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package markup_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"html/template"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/modules/htmlutil"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/markup/markdown"
|
||||
testModule "code.gitea.io/gitea/modules/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRender_IssueList(t *testing.T) {
|
||||
defer testModule.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||
markup.Init(&markup.RenderHelperFuncs{
|
||||
RenderRepoIssueIconTitle: func(ctx context.Context, opts markup.RenderIssueIconTitleOptions) (template.HTML, error) {
|
||||
return htmlutil.HTMLFormat("<div>issue #%d</div>", opts.IssueIndex), nil
|
||||
},
|
||||
})
|
||||
|
||||
test := func(input, expected string) {
|
||||
rctx := markup.NewTestRenderContext(markup.TestAppURL, map[string]string{
|
||||
"user": "test-user", "repo": "test-repo",
|
||||
"markupAllowShortIssuePattern": "true",
|
||||
})
|
||||
out, err := markdown.RenderString(rctx, input)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(out)))
|
||||
}
|
||||
|
||||
t.Run("NormalIssueRef", func(t *testing.T) {
|
||||
test(
|
||||
"#12345",
|
||||
`<p><a href="http://localhost:3000/test-user/test-repo/issues/12345" class="ref-issue" rel="nofollow">#12345</a></p>`,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("ListIssueRef", func(t *testing.T) {
|
||||
test(
|
||||
"* #12345",
|
||||
`<ul>
|
||||
<li><div>issue #12345</div></li>
|
||||
</ul>`,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("ListIssueRefNormal", func(t *testing.T) {
|
||||
test(
|
||||
"* foo #12345 bar",
|
||||
`<ul>
|
||||
<li>foo <a href="http://localhost:3000/test-user/test-repo/issues/12345" class="ref-issue" rel="nofollow">#12345</a> bar</li>
|
||||
</ul>`,
|
||||
)
|
||||
})
|
||||
|
||||
t.Run("ListTodoIssueRef", func(t *testing.T) {
|
||||
test(
|
||||
"* [ ] #12345",
|
||||
`<ul>
|
||||
<li class="task-list-item"><input type="checkbox" disabled="" data-source-position="2"/><div>issue #12345</div></li>
|
||||
</ul>`,
|
||||
)
|
||||
})
|
||||
}
|
@ -38,6 +38,7 @@ type RenderHelper interface {
|
||||
type RenderHelperFuncs struct {
|
||||
IsUsernameMentionable func(ctx context.Context, username string) bool
|
||||
RenderRepoFileCodePreview func(ctx context.Context, options RenderCodePreviewOptions) (template.HTML, error)
|
||||
RenderRepoIssueIconTitle func(ctx context.Context, options RenderIssueIconTitleOptions) (template.HTML, error)
|
||||
}
|
||||
|
||||
var DefaultRenderHelperFuncs *RenderHelperFuncs
|
||||
|
@ -330,22 +330,22 @@ func FindAllIssueReferences(content string) []IssueReference {
|
||||
}
|
||||
|
||||
// FindRenderizableReferenceNumeric returns the first unvalidated reference found in a string.
|
||||
func FindRenderizableReferenceNumeric(content string, prOnly, crossLinkOnly bool) (bool, *RenderizableReference) {
|
||||
func FindRenderizableReferenceNumeric(content string, prOnly, crossLinkOnly bool) *RenderizableReference {
|
||||
var match []int
|
||||
if !crossLinkOnly {
|
||||
match = issueNumericPattern.FindStringSubmatchIndex(content)
|
||||
}
|
||||
if match == nil {
|
||||
if match = crossReferenceIssueNumericPattern.FindStringSubmatchIndex(content); match == nil {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
}
|
||||
r := getCrossReference(util.UnsafeStringToBytes(content), match[2], match[3], false, prOnly)
|
||||
if r == nil {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return true, &RenderizableReference{
|
||||
return &RenderizableReference{
|
||||
Issue: r.issue,
|
||||
Owner: r.owner,
|
||||
Name: r.name,
|
||||
@ -372,15 +372,14 @@ func FindRenderizableCommitCrossReference(content string) (bool, *RenderizableRe
|
||||
}
|
||||
|
||||
// FindRenderizableReferenceRegexp returns the first regexp unvalidated references found in a string.
|
||||
func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) (bool, *RenderizableReference) {
|
||||
func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) *RenderizableReference {
|
||||
match := pattern.FindStringSubmatchIndex(content)
|
||||
if len(match) < 4 {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
action, location := findActionKeywords([]byte(content), match[2])
|
||||
|
||||
return true, &RenderizableReference{
|
||||
return &RenderizableReference{
|
||||
Issue: content[match[2]:match[3]],
|
||||
RefLocation: &RefSpan{Start: match[0], End: match[1]},
|
||||
Action: action,
|
||||
@ -390,15 +389,14 @@ func FindRenderizableReferenceRegexp(content string, pattern *regexp.Regexp) (bo
|
||||
}
|
||||
|
||||
// FindRenderizableReferenceAlphanumeric returns the first alphanumeric unvalidated references found in a string.
|
||||
func FindRenderizableReferenceAlphanumeric(content string) (bool, *RenderizableReference) {
|
||||
func FindRenderizableReferenceAlphanumeric(content string) *RenderizableReference {
|
||||
match := issueAlphanumericPattern.FindStringSubmatchIndex(content)
|
||||
if match == nil {
|
||||
return false, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
action, location := findActionKeywords([]byte(content), match[2])
|
||||
|
||||
return true, &RenderizableReference{
|
||||
return &RenderizableReference{
|
||||
Issue: content[match[2]:match[3]],
|
||||
RefLocation: &RefSpan{Start: match[2], End: match[3]},
|
||||
Action: action,
|
||||
|
@ -249,11 +249,10 @@ func TestFindAllIssueReferences(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, fixture := range alnumFixtures {
|
||||
found, ref := FindRenderizableReferenceAlphanumeric(fixture.input)
|
||||
ref := FindRenderizableReferenceAlphanumeric(fixture.input)
|
||||
if fixture.issue == "" {
|
||||
assert.False(t, found, "Failed to parse: {%s}", fixture.input)
|
||||
assert.Nil(t, ref, "Failed to parse: {%s}", fixture.input)
|
||||
} else {
|
||||
assert.True(t, found, "Failed to parse: {%s}", fixture.input)
|
||||
assert.Equal(t, fixture.issue, ref.Issue, "Failed to parse: {%s}", fixture.input)
|
||||
assert.Equal(t, fixture.refLocation, ref.RefLocation, "Failed to parse: {%s}", fixture.input)
|
||||
assert.Equal(t, fixture.action, ref.Action, "Failed to parse: {%s}", fixture.input)
|
||||
|
123
modules/reqctx/datastore.go
Normal file
123
modules/reqctx/datastore.go
Normal file
@ -0,0 +1,123 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package reqctx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
)
|
||||
|
||||
type ContextDataProvider interface {
|
||||
GetData() ContextData
|
||||
}
|
||||
|
||||
type ContextData map[string]any
|
||||
|
||||
func (ds ContextData) GetData() ContextData {
|
||||
return ds
|
||||
}
|
||||
|
||||
func (ds ContextData) MergeFrom(other ContextData) ContextData {
|
||||
for k, v := range other {
|
||||
ds[k] = v
|
||||
}
|
||||
return ds
|
||||
}
|
||||
|
||||
// RequestDataStore is a short-lived context-related object that is used to store request-specific data.
|
||||
type RequestDataStore interface {
|
||||
GetData() ContextData
|
||||
SetContextValue(k, v any)
|
||||
GetContextValue(key any) any
|
||||
AddCleanUp(f func())
|
||||
AddCloser(c io.Closer)
|
||||
}
|
||||
|
||||
type requestDataStoreKeyType struct{}
|
||||
|
||||
var RequestDataStoreKey requestDataStoreKeyType
|
||||
|
||||
type requestDataStore struct {
|
||||
data ContextData
|
||||
|
||||
mu sync.RWMutex
|
||||
values map[any]any
|
||||
cleanUpFuncs []func()
|
||||
}
|
||||
|
||||
func (r *requestDataStore) GetContextValue(key any) any {
|
||||
if key == RequestDataStoreKey {
|
||||
return r
|
||||
}
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return r.values[key]
|
||||
}
|
||||
|
||||
func (r *requestDataStore) SetContextValue(k, v any) {
|
||||
r.mu.Lock()
|
||||
r.values[k] = v
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
// GetData and the underlying ContextData are not thread-safe, callers should ensure thread-safety.
|
||||
func (r *requestDataStore) GetData() ContextData {
|
||||
if r.data == nil {
|
||||
r.data = make(ContextData)
|
||||
}
|
||||
return r.data
|
||||
}
|
||||
|
||||
func (r *requestDataStore) AddCleanUp(f func()) {
|
||||
r.mu.Lock()
|
||||
r.cleanUpFuncs = append(r.cleanUpFuncs, f)
|
||||
r.mu.Unlock()
|
||||
}
|
||||
|
||||
func (r *requestDataStore) AddCloser(c io.Closer) {
|
||||
r.AddCleanUp(func() { _ = c.Close() })
|
||||
}
|
||||
|
||||
func (r *requestDataStore) cleanUp() {
|
||||
for _, f := range r.cleanUpFuncs {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
func GetRequestDataStore(ctx context.Context) RequestDataStore {
|
||||
if req, ok := ctx.Value(RequestDataStoreKey).(*requestDataStore); ok {
|
||||
return req
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type requestContext struct {
|
||||
context.Context
|
||||
dataStore *requestDataStore
|
||||
}
|
||||
|
||||
func (c *requestContext) Value(key any) any {
|
||||
if v := c.dataStore.GetContextValue(key); v != nil {
|
||||
return v
|
||||
}
|
||||
return c.Context.Value(key)
|
||||
}
|
||||
|
||||
func NewRequestContext(parentCtx context.Context, profDesc string) (_ context.Context, finished func()) {
|
||||
ctx, _, processFinished := process.GetManager().AddTypedContext(parentCtx, profDesc, process.RequestProcessType, true)
|
||||
reqCtx := &requestContext{Context: ctx, dataStore: &requestDataStore{values: make(map[any]any)}}
|
||||
return reqCtx, func() {
|
||||
reqCtx.dataStore.cleanUp()
|
||||
processFinished()
|
||||
}
|
||||
}
|
||||
|
||||
// NewRequestContextForTest creates a new RequestContext for testing purposes
|
||||
// It doesn't add the context to the process manager, nor do cleanup
|
||||
func NewRequestContextForTest(parentCtx context.Context) context.Context {
|
||||
return &requestContext{Context: parentCtx, dataStore: &requestDataStore{values: make(map[any]any)}}
|
||||
}
|
@ -10,7 +10,7 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type normalizeVarsStruct struct {
|
||||
type globalVarsStruct struct {
|
||||
reXMLDoc,
|
||||
reComment,
|
||||
reAttrXMLNs,
|
||||
@ -18,26 +18,23 @@ type normalizeVarsStruct struct {
|
||||
reAttrClassPrefix *regexp.Regexp
|
||||
}
|
||||
|
||||
var (
|
||||
normalizeVars *normalizeVarsStruct
|
||||
normalizeVarsOnce sync.Once
|
||||
)
|
||||
var globalVars = sync.OnceValue(func() *globalVarsStruct {
|
||||
return &globalVarsStruct{
|
||||
reXMLDoc: regexp.MustCompile(`(?s)<\?xml.*?>`),
|
||||
reComment: regexp.MustCompile(`(?s)<!--.*?-->`),
|
||||
|
||||
reAttrXMLNs: regexp.MustCompile(`(?s)\s+xmlns\s*=\s*"[^"]*"`),
|
||||
reAttrSize: regexp.MustCompile(`(?s)\s+(width|height)\s*=\s*"[^"]+"`),
|
||||
reAttrClassPrefix: regexp.MustCompile(`(?s)\s+class\s*=\s*"`),
|
||||
}
|
||||
})
|
||||
|
||||
// Normalize normalizes the SVG content: set default width/height, remove unnecessary tags/attributes
|
||||
// It's designed to work with valid SVG content. For invalid SVG content, the returned content is not guaranteed.
|
||||
func Normalize(data []byte, size int) []byte {
|
||||
normalizeVarsOnce.Do(func() {
|
||||
normalizeVars = &normalizeVarsStruct{
|
||||
reXMLDoc: regexp.MustCompile(`(?s)<\?xml.*?>`),
|
||||
reComment: regexp.MustCompile(`(?s)<!--.*?-->`),
|
||||
|
||||
reAttrXMLNs: regexp.MustCompile(`(?s)\s+xmlns\s*=\s*"[^"]*"`),
|
||||
reAttrSize: regexp.MustCompile(`(?s)\s+(width|height)\s*=\s*"[^"]+"`),
|
||||
reAttrClassPrefix: regexp.MustCompile(`(?s)\s+class\s*=\s*"`),
|
||||
}
|
||||
})
|
||||
data = normalizeVars.reXMLDoc.ReplaceAll(data, nil)
|
||||
data = normalizeVars.reComment.ReplaceAll(data, nil)
|
||||
vars := globalVars()
|
||||
data = vars.reXMLDoc.ReplaceAll(data, nil)
|
||||
data = vars.reComment.ReplaceAll(data, nil)
|
||||
|
||||
data = bytes.TrimSpace(data)
|
||||
svgTag, svgRemaining, ok := bytes.Cut(data, []byte(">"))
|
||||
@ -45,9 +42,9 @@ func Normalize(data []byte, size int) []byte {
|
||||
return data
|
||||
}
|
||||
normalized := bytes.Clone(svgTag)
|
||||
normalized = normalizeVars.reAttrXMLNs.ReplaceAll(normalized, nil)
|
||||
normalized = normalizeVars.reAttrSize.ReplaceAll(normalized, nil)
|
||||
normalized = normalizeVars.reAttrClassPrefix.ReplaceAll(normalized, []byte(` class="`))
|
||||
normalized = vars.reAttrXMLNs.ReplaceAll(normalized, nil)
|
||||
normalized = vars.reAttrSize.ReplaceAll(normalized, nil)
|
||||
normalized = vars.reAttrClassPrefix.ReplaceAll(normalized, []byte(` class="`))
|
||||
normalized = bytes.TrimSpace(normalized)
|
||||
normalized = fmt.Appendf(normalized, ` width="%d" height="%d"`, size, size)
|
||||
if !bytes.Contains(normalized, []byte(` class="`)) {
|
||||
|
@ -4,7 +4,6 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
goctx "context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@ -51,7 +50,6 @@ func (r *responseWriter) WriteHeader(statusCode int) {
|
||||
var (
|
||||
httpReqType = reflect.TypeOf((*http.Request)(nil))
|
||||
respWriterType = reflect.TypeOf((*http.ResponseWriter)(nil)).Elem()
|
||||
cancelFuncType = reflect.TypeOf((*goctx.CancelFunc)(nil)).Elem()
|
||||
)
|
||||
|
||||
// preCheckHandler checks whether the handler is valid, developers could get first-time feedback, all mistakes could be found at startup
|
||||
@ -65,11 +63,8 @@ func preCheckHandler(fn reflect.Value, argsIn []reflect.Value) {
|
||||
if !hasStatusProvider {
|
||||
panic(fmt.Sprintf("handler should have at least one ResponseStatusProvider argument, but got %s", fn.Type()))
|
||||
}
|
||||
if fn.Type().NumOut() != 0 && fn.Type().NumIn() != 1 {
|
||||
panic(fmt.Sprintf("handler should have no return value or only one argument, but got %s", fn.Type()))
|
||||
}
|
||||
if fn.Type().NumOut() == 1 && fn.Type().Out(0) != cancelFuncType {
|
||||
panic(fmt.Sprintf("handler should return a cancel function, but got %s", fn.Type()))
|
||||
if fn.Type().NumOut() != 0 {
|
||||
panic(fmt.Sprintf("handler should have no return value other than registered ones, but got %s", fn.Type()))
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,16 +100,10 @@ func prepareHandleArgsIn(resp http.ResponseWriter, req *http.Request, fn reflect
|
||||
return argsIn
|
||||
}
|
||||
|
||||
func handleResponse(fn reflect.Value, ret []reflect.Value) goctx.CancelFunc {
|
||||
if len(ret) == 1 {
|
||||
if cancelFunc, ok := ret[0].Interface().(goctx.CancelFunc); ok {
|
||||
return cancelFunc
|
||||
}
|
||||
panic(fmt.Sprintf("unsupported return type: %s", ret[0].Type()))
|
||||
} else if len(ret) > 1 {
|
||||
func handleResponse(fn reflect.Value, ret []reflect.Value) {
|
||||
if len(ret) != 0 {
|
||||
panic(fmt.Sprintf("unsupported return values: %s", fn.Type()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasResponseBeenWritten(argsIn []reflect.Value) bool {
|
||||
@ -171,11 +160,8 @@ func toHandlerProvider(handler any) func(next http.Handler) http.Handler {
|
||||
routing.UpdateFuncInfo(req.Context(), funcInfo)
|
||||
ret := fn.Call(argsIn)
|
||||
|
||||
// handle the return value, and defer the cancel function if there is one
|
||||
cancelFunc := handleResponse(fn, ret)
|
||||
if cancelFunc != nil {
|
||||
defer cancelFunc()
|
||||
}
|
||||
// handle the return value (no-op at the moment)
|
||||
handleResponse(fn, ret)
|
||||
|
||||
// if the response has not been written, call the next handler
|
||||
if next != nil && !hasResponseBeenWritten(argsIn) {
|
||||
|
@ -7,46 +7,21 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
|
||||
// ContextDataStore represents a data store
|
||||
type ContextDataStore interface {
|
||||
GetData() ContextData
|
||||
}
|
||||
|
||||
type ContextData map[string]any
|
||||
|
||||
func (ds ContextData) GetData() ContextData {
|
||||
return ds
|
||||
}
|
||||
|
||||
func (ds ContextData) MergeFrom(other ContextData) ContextData {
|
||||
for k, v := range other {
|
||||
ds[k] = v
|
||||
}
|
||||
return ds
|
||||
}
|
||||
|
||||
const ContextDataKeySignedUser = "SignedUser"
|
||||
|
||||
type contextDataKeyType struct{}
|
||||
|
||||
var contextDataKey contextDataKeyType
|
||||
|
||||
func WithContextData(c context.Context) context.Context {
|
||||
return context.WithValue(c, contextDataKey, make(ContextData, 10))
|
||||
}
|
||||
|
||||
func GetContextData(c context.Context) ContextData {
|
||||
if ds, ok := c.Value(contextDataKey).(ContextData); ok {
|
||||
return ds
|
||||
func GetContextData(c context.Context) reqctx.ContextData {
|
||||
if rc := reqctx.GetRequestDataStore(c); rc != nil {
|
||||
return rc.GetData()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CommonTemplateContextData() ContextData {
|
||||
return ContextData{
|
||||
func CommonTemplateContextData() reqctx.ContextData {
|
||||
return reqctx.ContextData{
|
||||
"IsLandingPageOrganizations": setting.LandingPageURL == setting.LandingPageOrganizations,
|
||||
|
||||
"ShowRegistrationButton": setting.Service.ShowRegistrationButton,
|
||||
|
@ -7,11 +7,13 @@ import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/url"
|
||||
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
)
|
||||
|
||||
// Flash represents a one time data transfer between two requests.
|
||||
type Flash struct {
|
||||
DataStore ContextDataStore
|
||||
DataStore reqctx.RequestDataStore
|
||||
url.Values
|
||||
ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/htmlutil"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
|
||||
@ -29,12 +30,12 @@ func Bind[T any](_ T) http.HandlerFunc {
|
||||
}
|
||||
|
||||
// SetForm set the form object
|
||||
func SetForm(dataStore middleware.ContextDataStore, obj any) {
|
||||
func SetForm(dataStore reqctx.ContextDataProvider, obj any) {
|
||||
dataStore.GetData()["__form"] = obj
|
||||
}
|
||||
|
||||
// GetForm returns the validate form information
|
||||
func GetForm(dataStore middleware.ContextDataStore) any {
|
||||
func GetForm(dataStore reqctx.RequestDataStore) any {
|
||||
return dataStore.GetData()["__form"]
|
||||
}
|
||||
|
||||
|
@ -3742,6 +3742,7 @@ variables.creation.success=Proměnná „%s“ byla přidána.
|
||||
variables.update.failed=Úprava proměnné se nezdařila.
|
||||
variables.update.success=Proměnná byla upravena.
|
||||
|
||||
|
||||
[projects]
|
||||
deleted.display_name=Odstraněný projekt
|
||||
type-1.display_name=Samostatný projekt
|
||||
|
@ -3556,6 +3556,7 @@ variables.creation.success=Die Variable „%s“ wurde hinzugefügt.
|
||||
variables.update.failed=Fehler beim Bearbeiten der Variable.
|
||||
variables.update.success=Die Variable wurde bearbeitet.
|
||||
|
||||
|
||||
[projects]
|
||||
type-1.display_name=Individuelles Projekt
|
||||
type-2.display_name=Repository-Projekt
|
||||
|
@ -3439,6 +3439,7 @@ variables.creation.success=Η μεταβλητή "%s" έχει προστεθε
|
||||
variables.update.failed=Αποτυχία επεξεργασίας μεταβλητής.
|
||||
variables.update.success=Η μεταβλητή έχει τροποποιηθεί.
|
||||
|
||||
|
||||
[projects]
|
||||
type-1.display_name=Ατομικό Έργο
|
||||
type-2.display_name=Έργο Αποθετηρίου
|
||||
|
@ -3722,6 +3722,7 @@ runners.status.active = Active
|
||||
runners.status.offline = Offline
|
||||
runners.version = Version
|
||||
runners.reset_registration_token = Reset registration token
|
||||
runners.reset_registration_token_confirm = Would you like to invalidate the current token and generate a new one?
|
||||
runners.reset_registration_token_success = Runner registration token reset successfully
|
||||
|
||||
runs.all_workflows = All Workflows
|
||||
|
@ -3415,6 +3415,7 @@ variables.creation.success=La variable "%s" ha sido añadida.
|
||||
variables.update.failed=Error al editar la variable.
|
||||
variables.update.success=La variable ha sido editada.
|
||||
|
||||
|
||||
[projects]
|
||||
type-1.display_name=Proyecto individual
|
||||
type-2.display_name=Proyecto repositorio
|
||||
|
@ -2529,6 +2529,7 @@ runs.commit=کامیت
|
||||
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
|
||||
[git.filemode]
|
||||
|
@ -1707,6 +1707,7 @@ runs.commit=Commit
|
||||
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
|
||||
[git.filemode]
|
||||
|
@ -1945,6 +1945,8 @@ pulls.delete.title=Supprimer cette demande d'ajout ?
|
||||
pulls.delete.text=Voulez-vous vraiment supprimer cet demande d'ajout ? (Cela supprimera définitivement tout le contenu. Envisagez de le fermer à la place, si vous avez l'intention de le garder archivé)
|
||||
|
||||
pulls.recently_pushed_new_branches=Vous avez soumis sur la branche <strong>%[1]s</strong> %[2]s
|
||||
pulls.upstream_diverging_prompt_behind_1=Cette branche est en retard de %d révision sur %s
|
||||
pulls.upstream_diverging_prompt_behind_n=Cette branche est en retard de %d révisions sur %s
|
||||
pulls.upstream_diverging_prompt_base_newer=La branche de base %s a de nouveaux changements
|
||||
pulls.upstream_diverging_merge=Synchroniser la bifurcation
|
||||
|
||||
@ -3770,6 +3772,7 @@ variables.creation.success=La variable « %s » a été ajoutée.
|
||||
variables.update.failed=Impossible d’éditer la variable.
|
||||
variables.update.success=La variable a bien été modifiée.
|
||||
|
||||
|
||||
[projects]
|
||||
deleted.display_name=Projet supprimé
|
||||
type-1.display_name=Projet personnel
|
||||
|
@ -3770,6 +3770,7 @@ variables.creation.success=Tá an athróg "%s" curtha leis.
|
||||
variables.update.failed=Theip ar athróg a chur in eagar.
|
||||
variables.update.success=Tá an t-athróg curtha in eagar.
|
||||
|
||||
|
||||
[projects]
|
||||
deleted.display_name=Tionscadal scriosta
|
||||
type-1.display_name=Tionscadal Aonair
|
||||
|
@ -1615,6 +1615,7 @@ runs.commit=Commit
|
||||
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
|
||||
[git.filemode]
|
||||
|
@ -1444,6 +1444,7 @@ variables.creation.success=Variabel "%s" telah ditambahkan.
|
||||
variables.update.failed=Gagal mengedit variabel.
|
||||
variables.update.success=Variabel telah diedit.
|
||||
|
||||
|
||||
[projects]
|
||||
type-1.display_name=Proyek Individu
|
||||
type-2.display_name=Proyek Repositori
|
||||
|
@ -1342,6 +1342,7 @@ runs.commit=Framlag
|
||||
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
|
||||
[git.filemode]
|
||||
|
@ -2807,6 +2807,7 @@ runs.commit=Commit
|
||||
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
|
||||
[git.filemode]
|
||||
|
@ -3762,6 +3762,7 @@ variables.creation.success=変数 "%s" を追加しました。
|
||||
variables.update.failed=変数を更新できませんでした。
|
||||
variables.update.success=変数を更新しました。
|
||||
|
||||
|
||||
[projects]
|
||||
deleted.display_name=削除されたプロジェクト
|
||||
type-1.display_name=個人プロジェクト
|
||||
|
@ -1563,6 +1563,7 @@ runs.commit=커밋
|
||||
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
|
||||
[git.filemode]
|
||||
|
@ -3443,6 +3443,7 @@ variables.creation.success=Mainīgais "%s" tika pievienots.
|
||||
variables.update.failed=Neizdevās labot mainīgo.
|
||||
variables.update.success=Mainīgais tika labots.
|
||||
|
||||
|
||||
[projects]
|
||||
type-1.display_name=Individuālais projekts
|
||||
type-2.display_name=Repozitorija projekts
|
||||
|
@ -2537,6 +2537,7 @@ runs.commit=Commit
|
||||
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
|
||||
[git.filemode]
|
||||
|
@ -2430,6 +2430,7 @@ runs.commit=Commit
|
||||
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
|
||||
[git.filemode]
|
||||
|
@ -3353,6 +3353,7 @@ runs.empty_commit_message=(mensagem de commit vazia)
|
||||
need_approval_desc=Precisa de aprovação para executar workflows para pull request do fork.
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
type-1.display_name=Projeto individual
|
||||
type-2.display_name=Projeto do repositório
|
||||
|
@ -3770,6 +3770,9 @@ variables.creation.success=A variável "%s" foi adicionada.
|
||||
variables.update.failed=Falha ao editar a variável.
|
||||
variables.update.success=A variável foi editada.
|
||||
|
||||
logs.always_auto_scroll=Rolar registos de forma automática e permanente
|
||||
logs.always_expand_running=Expandir sempre os registos que vão rolando
|
||||
|
||||
[projects]
|
||||
deleted.display_name=Planeamento eliminado
|
||||
type-1.display_name=Planeamento individual
|
||||
|
@ -3373,6 +3373,7 @@ variables.creation.success=Переменная «%s» добавлена.
|
||||
variables.update.failed=Не удалось изменить переменную.
|
||||
variables.update.success=Переменная изменена.
|
||||
|
||||
|
||||
[projects]
|
||||
type-1.display_name=Индивидуальный проект
|
||||
type-2.display_name=Проект репозитория
|
||||
|
@ -2470,6 +2470,7 @@ runs.commit=කැප
|
||||
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
|
||||
[git.filemode]
|
||||
|
@ -1328,6 +1328,7 @@ runners.labels=Štítky
|
||||
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
|
||||
[git.filemode]
|
||||
|
@ -2005,6 +2005,7 @@ runs.commit=Commit
|
||||
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
|
||||
[git.filemode]
|
||||
|
@ -3633,6 +3633,7 @@ variables.creation.success=`"%s" değişkeni eklendi.`
|
||||
variables.update.failed=Değişken düzenlenemedi.
|
||||
variables.update.success=Değişken düzenlendi.
|
||||
|
||||
|
||||
[projects]
|
||||
deleted.display_name=Silinmiş Proje
|
||||
type-1.display_name=Kişisel Proje
|
||||
|
@ -2538,6 +2538,7 @@ runs.commit=Коміт
|
||||
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
|
||||
[git.filemode]
|
||||
|
@ -1945,6 +1945,8 @@ pulls.delete.title=删除此合并请求?
|
||||
pulls.delete.text=你真的要删除这个合并请求吗? (这将永久删除所有内容。如果你打算将内容存档,请考虑关闭它)
|
||||
|
||||
pulls.recently_pushed_new_branches=您已经于%[2]s推送了分支 <strong>%[1]s</strong>
|
||||
pulls.upstream_diverging_prompt_behind_1=该分支落后于 %[2]s %[1]d 个提交
|
||||
pulls.upstream_diverging_prompt_behind_n=该分支落后于 %[2]s %[1]d 个提交
|
||||
pulls.upstream_diverging_prompt_base_newer=基础分支 %s 有新的更改
|
||||
pulls.upstream_diverging_merge=同步派生
|
||||
|
||||
@ -3770,6 +3772,7 @@ variables.creation.success=变量 “%s” 添加成功。
|
||||
variables.update.failed=编辑变量失败。
|
||||
variables.update.success=该变量已被编辑。
|
||||
|
||||
|
||||
[projects]
|
||||
deleted.display_name=已删除项目
|
||||
type-1.display_name=个人项目
|
||||
|
@ -975,6 +975,7 @@ runners.task_list.repository=儲存庫
|
||||
|
||||
|
||||
|
||||
|
||||
[projects]
|
||||
|
||||
[git.filemode]
|
||||
|
@ -3763,6 +3763,7 @@ variables.creation.success=已新增變數「%s」。
|
||||
variables.update.failed=編輯變數失敗。
|
||||
variables.update.success=已編輯變數。
|
||||
|
||||
|
||||
[projects]
|
||||
deleted.display_name=已刪除的專案
|
||||
type-1.display_name=個人專案
|
||||
|
@ -126,11 +126,10 @@ func ArtifactsRoutes(prefix string) *web.Router {
|
||||
func ArtifactContexter() func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
base, baseCleanUp := context.NewBaseContext(resp, req)
|
||||
defer baseCleanUp()
|
||||
base := context.NewBaseContext(resp, req)
|
||||
|
||||
ctx := &ArtifactContext{Base: base}
|
||||
ctx.AppendContextValue(artifactContextKey, ctx)
|
||||
ctx.SetContextValue(artifactContextKey, ctx)
|
||||
|
||||
// action task call server api with Bearer ACTIONS_RUNTIME_TOKEN
|
||||
// we should verify the ACTIONS_RUNTIME_TOKEN
|
||||
|
@ -126,12 +126,9 @@ type artifactV4Routes struct {
|
||||
func ArtifactV4Contexter() func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
base, baseCleanUp := context.NewBaseContext(resp, req)
|
||||
defer baseCleanUp()
|
||||
|
||||
base := context.NewBaseContext(resp, req)
|
||||
ctx := &ArtifactContext{Base: base}
|
||||
ctx.AppendContextValue(artifactContextKey, ctx)
|
||||
|
||||
ctx.SetContextValue(artifactContextKey, ctx)
|
||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
||||
})
|
||||
}
|
||||
|
@ -729,15 +729,11 @@ func CreateBranchProtection(ctx *context.APIContext) {
|
||||
} else {
|
||||
if !isPlainRule {
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
|
||||
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
ctx.Repo.GitRepo = nil
|
||||
}()
|
||||
}
|
||||
// FIXME: since we only need to recheck files protected rules, we could improve this
|
||||
matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName)
|
||||
@ -1061,15 +1057,11 @@ func EditBranchProtection(ctx *context.APIContext) {
|
||||
} else {
|
||||
if !isPlainRule {
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
|
||||
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
ctx.Repo.GitRepo = nil
|
||||
}()
|
||||
}
|
||||
|
||||
// FIXME: since we only need to recheck files protected rules, we could improve this
|
||||
|
@ -44,13 +44,12 @@ func CompareDiff(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
|
||||
var err error
|
||||
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
defer gitRepo.Close()
|
||||
}
|
||||
|
||||
infoPath := ctx.PathParam("*")
|
||||
|
@ -28,13 +28,12 @@ func DownloadArchive(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
|
||||
var err error
|
||||
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, 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.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*"), tp)
|
||||
|
@ -287,13 +287,12 @@ func GetArchive(ctx *context.APIContext) {
|
||||
// "$ref": "#/responses/notFound"
|
||||
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
|
||||
var err error
|
||||
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
defer gitRepo.Close()
|
||||
}
|
||||
|
||||
archiveDownload(ctx)
|
||||
|
@ -726,12 +726,11 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err
|
||||
|
||||
if ctx.Repo.GitRepo == nil && !repo.IsEmpty {
|
||||
var err error
|
||||
ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, repo)
|
||||
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, repo)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "Unable to OpenRepository", err)
|
||||
return err
|
||||
}
|
||||
defer ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
|
||||
// Default branch only updated if changed and exist or the repository is empty
|
||||
|
@ -100,7 +100,7 @@ func Transfer(ctx *context.APIContext) {
|
||||
}
|
||||
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
_ = ctx.Repo.GitRepo.Close()
|
||||
ctx.Repo.GitRepo = nil
|
||||
}
|
||||
|
||||
|
@ -12,8 +12,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/test"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -21,7 +21,7 @@ import (
|
||||
func TestRenderPanicErrorPage(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
req := &http.Request{URL: &url.URL{}}
|
||||
req = req.WithContext(middleware.WithContextData(context.Background()))
|
||||
req = req.WithContext(reqctx.NewRequestContextForTest(context.Background()))
|
||||
RenderPanicErrorPage(w, req, errors.New("fake panic error (for test only)"))
|
||||
respContent := w.Body.String()
|
||||
assert.Contains(t, respContent, `class="page-content status-page-500"`)
|
||||
|
@ -4,16 +4,14 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
go_context "context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/process"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/modules/web/routing"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
|
||||
@ -24,54 +22,12 @@ import (
|
||||
|
||||
// ProtocolMiddlewares returns HTTP protocol related middlewares, and it provides a global panic recovery
|
||||
func ProtocolMiddlewares() (handlers []any) {
|
||||
// make sure chi uses EscapedPath(RawPath) as RoutePath, then "%2f" could be handled correctly
|
||||
handlers = append(handlers, func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
ctx := chi.RouteContext(req.Context())
|
||||
if req.URL.RawPath == "" {
|
||||
ctx.RoutePath = req.URL.EscapedPath()
|
||||
} else {
|
||||
ctx.RoutePath = req.URL.RawPath
|
||||
}
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
})
|
||||
// the order is important
|
||||
handlers = append(handlers, ChiRoutePathHandler()) // make sure chi has correct paths
|
||||
handlers = append(handlers, RequestContextHandler()) // // prepare the context and panic recovery
|
||||
|
||||
// prepare the ContextData and panic recovery
|
||||
handlers = append(handlers, func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
RenderPanicErrorPage(resp, req, err) // it should never panic
|
||||
}
|
||||
}()
|
||||
req = req.WithContext(middleware.WithContextData(req.Context()))
|
||||
req = req.WithContext(go_context.WithValue(req.Context(), httplib.RequestContextKey, req))
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
})
|
||||
|
||||
// wrap the request and response, use the process context and add it to the process manager
|
||||
handlers = append(handlers, func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
ctx, _, finished := process.GetManager().AddTypedContext(req.Context(), fmt.Sprintf("%s: %s", req.Method, req.RequestURI), process.RequestProcessType, true)
|
||||
defer finished()
|
||||
next.ServeHTTP(context.WrapResponseWriter(resp), req.WithContext(cache.WithCacheContext(ctx)))
|
||||
})
|
||||
})
|
||||
|
||||
if setting.ReverseProxyLimit > 0 {
|
||||
opt := proxy.NewForwardedHeadersOptions().
|
||||
WithForwardLimit(setting.ReverseProxyLimit).
|
||||
ClearTrustedProxies()
|
||||
for _, n := range setting.ReverseProxyTrustedProxies {
|
||||
if !strings.Contains(n, "/") {
|
||||
opt.AddTrustedProxy(n)
|
||||
} else {
|
||||
opt.AddTrustedNetwork(n)
|
||||
}
|
||||
}
|
||||
handlers = append(handlers, proxy.ForwardedHeaders(opt))
|
||||
if setting.ReverseProxyLimit > 0 && len(setting.ReverseProxyTrustedProxies) > 0 {
|
||||
handlers = append(handlers, ForwardedHeadersHandler(setting.ReverseProxyLimit, setting.ReverseProxyTrustedProxies))
|
||||
}
|
||||
|
||||
if setting.IsRouteLogEnabled() {
|
||||
@ -85,6 +41,59 @@ func ProtocolMiddlewares() (handlers []any) {
|
||||
return handlers
|
||||
}
|
||||
|
||||
func RequestContextHandler() func(h http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
profDesc := fmt.Sprintf("%s: %s", req.Method, req.RequestURI)
|
||||
ctx, finished := reqctx.NewRequestContext(req.Context(), profDesc)
|
||||
defer finished()
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
RenderPanicErrorPage(resp, req, err) // it should never panic
|
||||
}
|
||||
}()
|
||||
|
||||
ds := reqctx.GetRequestDataStore(ctx)
|
||||
req = req.WithContext(cache.WithCacheContext(ctx))
|
||||
ds.SetContextValue(httplib.RequestContextKey, req)
|
||||
ds.AddCleanUp(func() {
|
||||
if req.MultipartForm != nil {
|
||||
_ = req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
|
||||
}
|
||||
})
|
||||
next.ServeHTTP(context.WrapResponseWriter(resp), req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ChiRoutePathHandler() func(h http.Handler) http.Handler {
|
||||
// make sure chi uses EscapedPath(RawPath) as RoutePath, then "%2f" could be handled correctly
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
ctx := chi.RouteContext(req.Context())
|
||||
if req.URL.RawPath == "" {
|
||||
ctx.RoutePath = req.URL.EscapedPath()
|
||||
} else {
|
||||
ctx.RoutePath = req.URL.RawPath
|
||||
}
|
||||
next.ServeHTTP(resp, req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ForwardedHeadersHandler(limit int, trustedProxies []string) func(h http.Handler) http.Handler {
|
||||
opt := proxy.NewForwardedHeadersOptions().WithForwardLimit(limit).ClearTrustedProxies()
|
||||
for _, n := range trustedProxies {
|
||||
if !strings.Contains(n, "/") {
|
||||
opt.AddTrustedProxy(n)
|
||||
} else {
|
||||
opt.AddTrustedNetwork(n)
|
||||
}
|
||||
}
|
||||
return proxy.ForwardedHeaders(opt)
|
||||
}
|
||||
|
||||
func Sessioner() func(next http.Handler) http.Handler {
|
||||
return session.Sessioner(session.Options{
|
||||
Provider: setting.SessionConfig.Provider,
|
||||
|
@ -133,7 +133,7 @@ func InitWebInstalled(ctx context.Context) {
|
||||
|
||||
highlight.NewContext()
|
||||
external.RegisterRenderers()
|
||||
markup.Init(markup_service.ProcessorHelper())
|
||||
markup.Init(markup_service.FormalRenderHelperFuncs())
|
||||
|
||||
if setting.EnableSQLite3 {
|
||||
log.Info("SQLite3 support is enabled")
|
||||
@ -171,7 +171,7 @@ func InitWebInstalled(ctx context.Context) {
|
||||
auth.Init()
|
||||
mustInit(svg.Init)
|
||||
|
||||
actions_service.Init()
|
||||
mustInitCtx(ctx, actions_service.Init)
|
||||
|
||||
mustInit(repo_service.InitLicenseClassifier)
|
||||
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
@ -61,15 +62,11 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||
envConfigKeys := setting.CollectEnvConfigKeys()
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
base, baseCleanUp := context.NewBaseContext(resp, req)
|
||||
defer baseCleanUp()
|
||||
|
||||
base := context.NewBaseContext(resp, req)
|
||||
ctx := context.NewWebContext(base, rnd, session.GetSession(req))
|
||||
ctx.AppendContextValue(context.WebContextKey, ctx)
|
||||
ctx.SetContextValue(context.WebContextKey, ctx)
|
||||
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
|
||||
ctx.Data.MergeFrom(middleware.ContextData{
|
||||
"Context": ctx, // TODO: use "ctx" in template and remove this
|
||||
"locale": ctx.Locale,
|
||||
ctx.Data.MergeFrom(reqctx.ContextData{
|
||||
"Title": ctx.Locale.Tr("install.install"),
|
||||
"PageIsInstall": true,
|
||||
"DbTypeNames": dbTypeNames,
|
||||
|
@ -63,8 +63,8 @@ func Routes() *web.Router {
|
||||
r.Post("/ssh/{id}/update/{repoid}", UpdatePublicKeyInRepo)
|
||||
r.Post("/ssh/log", bind(private.SSHLogOption{}), SSHLog)
|
||||
r.Post("/hook/pre-receive/{owner}/{repo}", RepoAssignment, bind(private.HookOptions{}), HookPreReceive)
|
||||
r.Post("/hook/post-receive/{owner}/{repo}", context.OverrideContext, bind(private.HookOptions{}), HookPostReceive)
|
||||
r.Post("/hook/proc-receive/{owner}/{repo}", context.OverrideContext, RepoAssignment, bind(private.HookOptions{}), HookProcReceive)
|
||||
r.Post("/hook/post-receive/{owner}/{repo}", context.OverrideContext(), bind(private.HookOptions{}), HookPostReceive)
|
||||
r.Post("/hook/proc-receive/{owner}/{repo}", context.OverrideContext(), RepoAssignment, bind(private.HookOptions{}), HookProcReceive)
|
||||
r.Post("/hook/set-default-branch/{owner}/{repo}/{branch}", RepoAssignment, SetDefaultBranch)
|
||||
r.Get("/serv/none/{keyid}", ServNoCommand)
|
||||
r.Get("/serv/command/{keyid}/{owner}/{repo}", ServCommand)
|
||||
@ -88,7 +88,7 @@ func Routes() *web.Router {
|
||||
// Fortunately, the LFS handlers are able to handle requests without a complete web context
|
||||
common.AddOwnerRepoGitLFSRoutes(r, func(ctx *context.PrivateContext) {
|
||||
webContext := &context.Context{Base: ctx.Base}
|
||||
ctx.AppendContextValue(context.WebContextKey, webContext)
|
||||
ctx.SetContextValue(context.WebContextKey, webContext)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -4,7 +4,6 @@
|
||||
package private
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@ -17,40 +16,29 @@ import (
|
||||
|
||||
// This file contains common functions relating to setting the Repository for the internal routes
|
||||
|
||||
// RepoAssignment assigns the repository and gitrepository to the private context
|
||||
func RepoAssignment(ctx *gitea_context.PrivateContext) context.CancelFunc {
|
||||
// RepoAssignment assigns the repository and git repository to the private context
|
||||
func RepoAssignment(ctx *gitea_context.PrivateContext) {
|
||||
ownerName := ctx.PathParam(":owner")
|
||||
repoName := ctx.PathParam(":repo")
|
||||
|
||||
repo := loadRepository(ctx, ownerName, repoName)
|
||||
if ctx.Written() {
|
||||
// Error handled in loadRepository
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||
gitRepo, err := gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, repo)
|
||||
if err != nil {
|
||||
log.Error("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err)
|
||||
ctx.JSON(http.StatusInternalServerError, private.Response{
|
||||
Err: fmt.Sprintf("Failed to open repository: %s/%s Error: %v", ownerName, repoName, err),
|
||||
})
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Repo = &gitea_context.Repository{
|
||||
Repository: repo,
|
||||
GitRepo: gitRepo,
|
||||
}
|
||||
|
||||
// We opened it, we should close it
|
||||
cancel := func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return cancel
|
||||
}
|
||||
|
||||
func loadRepository(ctx *gitea_context.PrivateContext, ownerName, repoName string) *repo_model.Repository {
|
||||
|
@ -136,9 +136,8 @@ func RunnerResetRegistrationToken(ctx *context.Context, ownerID, repoID int64, r
|
||||
ctx.ServerError("ResetRunnerRegistrationToken", err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Flash.Success(ctx.Tr("actions.runners.reset_registration_token_success"))
|
||||
ctx.Redirect(redirectTo)
|
||||
ctx.JSONRedirect(redirectTo)
|
||||
}
|
||||
|
||||
// RunnerDeletePost response for deleting a runner
|
||||
|
@ -4,7 +4,6 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
gocontext "context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
@ -463,7 +462,7 @@ func registerRoutes(m *web.Router) {
|
||||
m.Combo("/{runnerid}").Get(repo_setting.RunnersEdit).
|
||||
Post(web.Bind(forms.EditRunnerForm{}), repo_setting.RunnersEditPost)
|
||||
m.Post("/{runnerid}/delete", repo_setting.RunnerDeletePost)
|
||||
m.Get("/reset_registration_token", repo_setting.ResetRunnerRegistrationToken)
|
||||
m.Post("/reset_registration_token", repo_setting.ResetRunnerRegistrationToken)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1526,24 +1525,23 @@ func registerRoutes(m *web.Router) {
|
||||
|
||||
m.Group("/blob_excerpt", func() {
|
||||
m.Get("/{sha}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.ExcerptBlob)
|
||||
}, func(ctx *context.Context) gocontext.CancelFunc {
|
||||
}, func(ctx *context.Context) {
|
||||
// FIXME: refactor this function, use separate routes for wiki/code
|
||||
if ctx.FormBool("wiki") {
|
||||
ctx.Data["PageIsWiki"] = true
|
||||
repo.MustEnableWiki(ctx)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
if ctx.Written() {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
cancel := context.RepoRef()(ctx)
|
||||
context.RepoRef()(ctx)
|
||||
if ctx.Written() {
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
|
||||
repo.MustBeNotEmpty(ctx)
|
||||
return cancel
|
||||
})
|
||||
|
||||
m.Group("/media", func() {
|
||||
|
@ -4,23 +4,68 @@
|
||||
package actions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/modules/graceful"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/queue"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
notify_service "code.gitea.io/gitea/services/notify"
|
||||
)
|
||||
|
||||
func Init() {
|
||||
func initGlobalRunnerToken(ctx context.Context) error {
|
||||
// use the same env name as the runner, for consistency
|
||||
token := os.Getenv("GITEA_RUNNER_REGISTRATION_TOKEN")
|
||||
tokenFile := os.Getenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE")
|
||||
if token != "" && tokenFile != "" {
|
||||
return errors.New("both GITEA_RUNNER_REGISTRATION_TOKEN and GITEA_RUNNER_REGISTRATION_TOKEN_FILE are set, only one can be used")
|
||||
}
|
||||
if tokenFile != "" {
|
||||
file, err := os.ReadFile(tokenFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read GITEA_RUNNER_REGISTRATION_TOKEN_FILE: %w", err)
|
||||
}
|
||||
token = strings.TrimSpace(string(file))
|
||||
}
|
||||
if token == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(token) < 32 {
|
||||
return errors.New("GITEA_RUNNER_REGISTRATION_TOKEN must be at least 32 random characters")
|
||||
}
|
||||
|
||||
existing, err := actions_model.GetRunnerToken(ctx, token)
|
||||
if err != nil && !errors.Is(err, util.ErrNotExist) {
|
||||
return fmt.Errorf("unable to check existing token: %w", err)
|
||||
}
|
||||
if existing != nil {
|
||||
if !existing.IsActive {
|
||||
log.Warn("The token defined by GITEA_RUNNER_REGISTRATION_TOKEN is already invalidated, please use the latest one from web UI")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
_, err = actions_model.NewRunnerTokenWithValue(ctx, 0, 0, token)
|
||||
return err
|
||||
}
|
||||
|
||||
func Init(ctx context.Context) error {
|
||||
if !setting.Actions.Enabled {
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
jobEmitterQueue = queue.CreateUniqueQueue(graceful.GetManager().ShutdownContext(), "actions_ready_job", jobEmitterQueueHandler)
|
||||
if jobEmitterQueue == nil {
|
||||
log.Fatal("Unable to create actions_ready_job queue")
|
||||
return errors.New("unable to create actions_ready_job queue")
|
||||
}
|
||||
go graceful.GetManager().RunWithCancel(jobEmitterQueue)
|
||||
|
||||
notify_service.RegisterNotifier(NewNotifier())
|
||||
return initGlobalRunnerToken(ctx)
|
||||
}
|
||||
|
80
services/actions/init_test.go
Normal file
80
services/actions/init_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package actions
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
actions_model "code.gitea.io/gitea/models/actions"
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m, &unittest.TestOptions{
|
||||
FixtureFiles: []string{"action_runner_token.yml"},
|
||||
})
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestInitToken(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
t.Run("NoToken", func(t *testing.T) {
|
||||
_, _ = db.Exec(db.DefaultContext, "DELETE FROM action_runner_token")
|
||||
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", "")
|
||||
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE", "")
|
||||
err := initGlobalRunnerToken(db.DefaultContext)
|
||||
require.NoError(t, err)
|
||||
notEmpty, err := db.IsTableNotEmpty(&actions_model.ActionRunnerToken{})
|
||||
require.NoError(t, err)
|
||||
assert.False(t, notEmpty)
|
||||
})
|
||||
|
||||
t.Run("EnvToken", func(t *testing.T) {
|
||||
tokenValue, _ := util.CryptoRandomString(32)
|
||||
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", tokenValue)
|
||||
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE", "")
|
||||
err := initGlobalRunnerToken(db.DefaultContext)
|
||||
require.NoError(t, err)
|
||||
token := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
|
||||
assert.True(t, token.IsActive)
|
||||
|
||||
// init with the same token again, should not create a new token
|
||||
err = initGlobalRunnerToken(db.DefaultContext)
|
||||
require.NoError(t, err)
|
||||
token2 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
|
||||
assert.Equal(t, token.ID, token2.ID)
|
||||
assert.True(t, token.IsActive)
|
||||
})
|
||||
|
||||
t.Run("EnvFileToken", func(t *testing.T) {
|
||||
tokenValue, _ := util.CryptoRandomString(32)
|
||||
f := t.TempDir() + "/token"
|
||||
_ = os.WriteFile(f, []byte(tokenValue), 0o644)
|
||||
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", "")
|
||||
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN_FILE", f)
|
||||
err := initGlobalRunnerToken(db.DefaultContext)
|
||||
require.NoError(t, err)
|
||||
token := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
|
||||
assert.True(t, token.IsActive)
|
||||
|
||||
// if the env token is invalidated by another new token, then it shouldn't be active anymore
|
||||
_, err = actions_model.NewRunnerToken(db.DefaultContext, 0, 0)
|
||||
require.NoError(t, err)
|
||||
token = unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunnerToken{Token: tokenValue})
|
||||
assert.False(t, token.IsActive)
|
||||
})
|
||||
|
||||
t.Run("InvalidToken", func(t *testing.T) {
|
||||
t.Setenv("GITEA_RUNNER_REGISTRATION_TOKEN", "abc")
|
||||
err := initGlobalRunnerToken(db.DefaultContext)
|
||||
assert.ErrorContains(t, err, "must be at least")
|
||||
})
|
||||
}
|
@ -8,12 +8,11 @@ import (
|
||||
"net/http"
|
||||
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/session"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
)
|
||||
|
||||
// DataStore represents a data store
|
||||
type DataStore middleware.ContextDataStore
|
||||
type DataStore = reqctx.ContextDataProvider
|
||||
|
||||
// SessionStore represents a session store
|
||||
type SessionStore session.Store
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/services/actions"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -23,7 +23,7 @@ func TestUserIDFromToken(t *testing.T) {
|
||||
token, err := actions.CreateAuthorizationToken(RunningTaskID, 1, 2)
|
||||
assert.NoError(t, err)
|
||||
|
||||
ds := make(middleware.ContextData)
|
||||
ds := make(reqctx.ContextData)
|
||||
|
||||
o := OAuth2{}
|
||||
uid := o.userIDFromToken(context.Background(), token, ds)
|
||||
|
@ -5,7 +5,6 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@ -212,17 +211,15 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) {
|
||||
func APIContexter() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
base, baseCleanUp := NewBaseContext(w, req)
|
||||
base := NewBaseContext(w, req)
|
||||
ctx := &APIContext{
|
||||
Base: base,
|
||||
Cache: cache.GetCache(),
|
||||
Repo: &Repository{PullRequest: &PullRequest{}},
|
||||
Org: &APIOrganization{},
|
||||
}
|
||||
defer baseCleanUp()
|
||||
|
||||
ctx.Base.AppendContextValue(apiContextKey, ctx)
|
||||
ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
|
||||
ctx.SetContextValue(apiContextKey, ctx)
|
||||
|
||||
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
|
||||
if ctx.Req.Method == "POST" && strings.Contains(ctx.Req.Header.Get("Content-Type"), "multipart/form-data") {
|
||||
@ -267,31 +264,22 @@ func (ctx *APIContext) NotFound(objs ...any) {
|
||||
|
||||
// ReferencesGitRepo injects the GitRepo into the Context
|
||||
// you can optional skip the IsEmpty check
|
||||
func ReferencesGitRepo(allowEmpty ...bool) func(ctx *APIContext) (cancel context.CancelFunc) {
|
||||
return func(ctx *APIContext) (cancel context.CancelFunc) {
|
||||
func ReferencesGitRepo(allowEmpty ...bool) func(ctx *APIContext) {
|
||||
return func(ctx *APIContext) {
|
||||
// Empty repository does not have reference information.
|
||||
if ctx.Repo.Repository.IsEmpty && !(len(allowEmpty) != 0 && allowEmpty[0]) {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// For API calls.
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
|
||||
var err error
|
||||
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, fmt.Sprintf("Open Repository %v failed", ctx.Repo.Repository.FullName()), err)
|
||||
return cancel
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
// We opened it, we should close it
|
||||
return func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
_ = ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return cancel
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,12 +12,12 @@ import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
@ -25,65 +25,25 @@ import (
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type contextValuePair struct {
|
||||
key any
|
||||
valueFn func() any
|
||||
}
|
||||
|
||||
type BaseContextKeyType struct{}
|
||||
|
||||
var BaseContextKey BaseContextKeyType
|
||||
|
||||
type Base struct {
|
||||
originCtx context.Context
|
||||
contextValues []contextValuePair
|
||||
context.Context
|
||||
reqctx.RequestDataStore
|
||||
|
||||
Resp ResponseWriter
|
||||
Req *http.Request
|
||||
|
||||
// Data is prepared by ContextDataStore middleware, this field only refers to the pre-created/prepared ContextData.
|
||||
// Although it's mainly used for MVC templates, sometimes it's also used to pass data between middlewares/handler
|
||||
Data middleware.ContextData
|
||||
Data reqctx.ContextData
|
||||
|
||||
// Locale is mainly for Web context, although the API context also uses it in some cases: message response, form validation
|
||||
Locale translation.Locale
|
||||
}
|
||||
|
||||
func (b *Base) Deadline() (deadline time.Time, ok bool) {
|
||||
return b.originCtx.Deadline()
|
||||
}
|
||||
|
||||
func (b *Base) Done() <-chan struct{} {
|
||||
return b.originCtx.Done()
|
||||
}
|
||||
|
||||
func (b *Base) Err() error {
|
||||
return b.originCtx.Err()
|
||||
}
|
||||
|
||||
func (b *Base) Value(key any) any {
|
||||
for _, pair := range b.contextValues {
|
||||
if pair.key == key {
|
||||
return pair.valueFn()
|
||||
}
|
||||
}
|
||||
return b.originCtx.Value(key)
|
||||
}
|
||||
|
||||
func (b *Base) AppendContextValueFunc(key any, valueFn func() any) any {
|
||||
b.contextValues = append(b.contextValues, contextValuePair{key, valueFn})
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Base) AppendContextValue(key, value any) any {
|
||||
b.contextValues = append(b.contextValues, contextValuePair{key, func() any { return value }})
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Base) GetData() middleware.ContextData {
|
||||
return b.Data
|
||||
}
|
||||
|
||||
// AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header
|
||||
func (b *Base) AppendAccessControlExposeHeaders(names ...string) {
|
||||
val := b.RespHeader().Get("Access-Control-Expose-Headers")
|
||||
@ -295,13 +255,6 @@ func (b *Base) ServeContent(r io.ReadSeeker, opts *ServeHeaderOptions) {
|
||||
http.ServeContent(b.Resp, b.Req, opts.Filename, opts.LastModified, r)
|
||||
}
|
||||
|
||||
// Close frees all resources hold by Context
|
||||
func (b *Base) cleanUp() {
|
||||
if b.Req != nil && b.Req.MultipartForm != nil {
|
||||
_ = b.Req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Base) Tr(msg string, args ...any) template.HTML {
|
||||
return b.Locale.Tr(msg, args...)
|
||||
}
|
||||
@ -310,17 +263,28 @@ func (b *Base) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
|
||||
return b.Locale.TrN(cnt, key1, keyN, args...)
|
||||
}
|
||||
|
||||
func NewBaseContext(resp http.ResponseWriter, req *http.Request) (b *Base, closeFunc func()) {
|
||||
b = &Base{
|
||||
originCtx: req.Context(),
|
||||
Req: req,
|
||||
Resp: WrapResponseWriter(resp),
|
||||
Locale: middleware.Locale(resp, req),
|
||||
Data: middleware.GetContextData(req.Context()),
|
||||
func NewBaseContext(resp http.ResponseWriter, req *http.Request) *Base {
|
||||
ds := reqctx.GetRequestDataStore(req.Context())
|
||||
b := &Base{
|
||||
Context: req.Context(),
|
||||
RequestDataStore: ds,
|
||||
Req: req,
|
||||
Resp: WrapResponseWriter(resp),
|
||||
Locale: middleware.Locale(resp, req),
|
||||
Data: ds.GetData(),
|
||||
}
|
||||
b.Req = b.Req.WithContext(b)
|
||||
b.AppendContextValue(BaseContextKey, b)
|
||||
b.AppendContextValue(translation.ContextKey, b.Locale)
|
||||
b.AppendContextValue(httplib.RequestContextKey, b.Req)
|
||||
return b, b.cleanUp
|
||||
ds.SetContextValue(BaseContextKey, b)
|
||||
ds.SetContextValue(translation.ContextKey, b.Locale)
|
||||
ds.SetContextValue(httplib.RequestContextKey, b.Req)
|
||||
return b
|
||||
}
|
||||
|
||||
func NewBaseContextForTest(resp http.ResponseWriter, req *http.Request) *Base {
|
||||
if !setting.IsInTesting {
|
||||
panic("This function is only for testing")
|
||||
}
|
||||
ctx := reqctx.NewRequestContextForTest(req.Context())
|
||||
*req = *req.WithContext(ctx)
|
||||
return NewBaseContext(resp, req)
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func TestRedirect(t *testing.T) {
|
||||
setting.IsInTesting = true
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
|
||||
cases := []struct {
|
||||
@ -28,10 +29,9 @@ func TestRedirect(t *testing.T) {
|
||||
}
|
||||
for _, c := range cases {
|
||||
resp := httptest.NewRecorder()
|
||||
b, cleanup := NewBaseContext(resp, req)
|
||||
b := NewBaseContextForTest(resp, req)
|
||||
resp.Header().Add("Set-Cookie", (&http.Cookie{Name: setting.SessionConfig.CookieName, Value: "dummy"}).String())
|
||||
b.Redirect(c.url)
|
||||
cleanup()
|
||||
has := resp.Header().Get("Set-Cookie") == "i_like_gitea=dummy"
|
||||
assert.Equal(t, c.keep, has, "url = %q", c.url)
|
||||
}
|
||||
@ -39,9 +39,8 @@ func TestRedirect(t *testing.T) {
|
||||
req, _ = http.NewRequest("GET", "/", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
req.Header.Add("HX-Request", "true")
|
||||
b, cleanup := NewBaseContext(resp, req)
|
||||
b := NewBaseContextForTest(resp, req)
|
||||
b.Redirect("/other")
|
||||
cleanup()
|
||||
assert.Equal(t, "/other", resp.Header().Get("HX-Redirect"))
|
||||
assert.Equal(t, http.StatusNoContent, resp.Code)
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ import (
|
||||
"code.gitea.io/gitea/models/unit"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/httpcache"
|
||||
"code.gitea.io/gitea/modules/session"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
@ -153,14 +152,9 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||
}
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
base, baseCleanUp := NewBaseContext(resp, req)
|
||||
defer baseCleanUp()
|
||||
base := NewBaseContext(resp, req)
|
||||
ctx := NewWebContext(base, rnd, session.GetContextSession(req))
|
||||
|
||||
ctx.Data.MergeFrom(middleware.CommonTemplateContextData())
|
||||
if setting.IsProd && !setting.IsInTesting {
|
||||
ctx.Data["Context"] = ctx // TODO: use "ctx" in template and remove this
|
||||
}
|
||||
ctx.Data["CurrentURL"] = setting.AppSubURL + req.URL.RequestURI()
|
||||
ctx.Data["Link"] = ctx.Link
|
||||
|
||||
@ -168,9 +162,7 @@ func Contexter() func(next http.Handler) http.Handler {
|
||||
ctx.PageData = map[string]any{}
|
||||
ctx.Data["PageData"] = ctx.PageData
|
||||
|
||||
ctx.Base.AppendContextValue(WebContextKey, ctx)
|
||||
ctx.Base.AppendContextValueFunc(gitrepo.RepositoryContextKey, func() any { return ctx.Repo.GitRepo })
|
||||
|
||||
ctx.Base.SetContextValue(WebContextKey, ctx)
|
||||
ctx.Csrf = NewCSRFProtector(csrfOpts)
|
||||
|
||||
// Get the last flash message from cookie
|
||||
|
@ -106,7 +106,7 @@ func (ctx *Context) JSONTemplate(tmpl templates.TplName) {
|
||||
}
|
||||
|
||||
// RenderToHTML renders the template content to a HTML string
|
||||
func (ctx *Context) RenderToHTML(name templates.TplName, data map[string]any) (template.HTML, error) {
|
||||
func (ctx *Context) RenderToHTML(name templates.TplName, data any) (template.HTML, error) {
|
||||
var buf strings.Builder
|
||||
err := ctx.Render.HTML(&buf, 0, name, data, ctx.TemplateContext)
|
||||
return template.HTML(buf.String()), err
|
||||
|
@ -26,6 +26,7 @@ func TestRemoveSessionCookieHeader(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRedirectToCurrentSite(t *testing.T) {
|
||||
setting.IsInTesting = true
|
||||
defer test.MockVariableValue(&setting.AppURL, "http://localhost:3000/sub/")()
|
||||
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||
cases := []struct {
|
||||
@ -40,8 +41,7 @@ func TestRedirectToCurrentSite(t *testing.T) {
|
||||
t.Run(c.location, func(t *testing.T) {
|
||||
req := &http.Request{URL: &url.URL{Path: "/"}}
|
||||
resp := httptest.NewRecorder()
|
||||
base, baseCleanUp := NewBaseContext(resp, req)
|
||||
defer baseCleanUp()
|
||||
base := NewBaseContextForTest(resp, req)
|
||||
ctx := NewWebContext(base, nil, nil)
|
||||
ctx.RedirectToCurrentSite(c.location)
|
||||
redirect := test.RedirectURL(resp)
|
||||
|
@ -153,12 +153,10 @@ func PackageContexter() func(next http.Handler) http.Handler {
|
||||
renderer := templates.HTMLRenderer()
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
base, baseCleanUp := NewBaseContext(resp, req)
|
||||
defer baseCleanUp()
|
||||
|
||||
base := NewBaseContext(resp, req)
|
||||
// it is still needed when rendering 500 page in a package handler
|
||||
ctx := NewWebContext(base, renderer, nil)
|
||||
ctx.Base.AppendContextValue(WebContextKey, ctx)
|
||||
ctx.SetContextValue(WebContextKey, ctx)
|
||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
||||
})
|
||||
}
|
||||
|
@ -64,11 +64,9 @@ func GetPrivateContext(req *http.Request) *PrivateContext {
|
||||
func PrivateContexter() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
base, baseCleanUp := NewBaseContext(w, req)
|
||||
base := NewBaseContext(w, req)
|
||||
ctx := &PrivateContext{Base: base}
|
||||
defer baseCleanUp()
|
||||
ctx.Base.AppendContextValue(privateContextKey, ctx)
|
||||
|
||||
ctx.SetContextValue(privateContextKey, ctx)
|
||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
||||
})
|
||||
}
|
||||
@ -78,8 +76,15 @@ func PrivateContexter() func(http.Handler) http.Handler {
|
||||
// This function should be used when there is a need for work to continue even if the request has been cancelled.
|
||||
// Primarily this affects hook/post-receive and hook/proc-receive both of which need to continue working even if
|
||||
// the underlying request has timed out from the ssh/http push
|
||||
func OverrideContext(ctx *PrivateContext) (cancel context.CancelFunc) {
|
||||
// We now need to override the request context as the base for our work because even if the request is cancelled we have to continue this work
|
||||
ctx.Override, _, cancel = process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI), process.RequestProcessType, true)
|
||||
return cancel
|
||||
func OverrideContext() func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
// We now need to override the request context as the base for our work because even if the request is cancelled we have to continue this work
|
||||
ctx := GetPrivateContext(req)
|
||||
var finished func()
|
||||
ctx.Override, _, finished = process.GetManager().AddTypedContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PrivateContext: %s", ctx.Req.RequestURI), process.RequestProcessType, true)
|
||||
defer finished()
|
||||
next.ServeHTTP(ctx.Resp, ctx.Req)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -397,11 +397,13 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) {
|
||||
}
|
||||
|
||||
// RepoAssignment returns a middleware to handle repository assignment
|
||||
func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
func RepoAssignment(ctx *Context) {
|
||||
if _, repoAssignmentOnce := ctx.Data["repoAssignmentExecuted"]; repoAssignmentOnce {
|
||||
// FIXME: it should panic in dev/test modes to have a clear behavior
|
||||
log.Trace("RepoAssignment was exec already, skipping second call ...")
|
||||
return nil
|
||||
if !setting.IsProd || setting.IsInTesting {
|
||||
panic("RepoAssignment should not be executed twice")
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx.Data["repoAssignmentExecuted"] = true
|
||||
|
||||
@ -429,7 +431,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
// https://github.com/golang/go/issues/19760
|
||||
if ctx.FormString("go-get") == "1" {
|
||||
EarlyResponseForGoGetMeta(ctx)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
if redirectUserID, err := user_model.LookupUserRedirect(ctx, userName); err == nil {
|
||||
@ -442,7 +444,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
} else {
|
||||
ctx.ServerError("GetUserByName", err)
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Repo.Owner = owner
|
||||
@ -467,7 +469,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
redirectPath += "?" + ctx.Req.URL.RawQuery
|
||||
}
|
||||
ctx.Redirect(path.Join(setting.AppSubURL, redirectPath))
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// Get repository.
|
||||
@ -480,7 +482,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
} else if repo_model.IsErrRedirectNotExist(err) {
|
||||
if ctx.FormString("go-get") == "1" {
|
||||
EarlyResponseForGoGetMeta(ctx)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
ctx.NotFound("GetRepositoryByName", nil)
|
||||
} else {
|
||||
@ -489,13 +491,13 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
} else {
|
||||
ctx.ServerError("GetRepositoryByName", err)
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
repo.Owner = owner
|
||||
|
||||
repoAssignment(ctx, repo)
|
||||
if ctx.Written() {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Repo.RepoLink = repo.Link()
|
||||
@ -520,7 +522,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetReleaseCountByRepoID", err)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
ctx.Data["NumReleases"], err = db.Count[repo_model.Release](ctx, repo_model.FindReleasesOptions{
|
||||
// only show draft releases for users who can write, read-only users shouldn't see draft releases.
|
||||
@ -529,7 +531,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
})
|
||||
if err != nil {
|
||||
ctx.ServerError("GetReleaseCountByRepoID", err)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = owner.Name + "/" + repo.Name
|
||||
@ -546,14 +548,14 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
canSignedUserFork, err := repo_module.CanUserForkRepo(ctx, ctx.Doer, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.ServerError("CanUserForkRepo", err)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
ctx.Data["CanSignedUserFork"] = canSignedUserFork
|
||||
|
||||
userAndOrgForks, err := repo_model.GetForksByUserAndOrgs(ctx, ctx.Doer, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetForksByUserAndOrgs", err)
|
||||
return nil
|
||||
return
|
||||
}
|
||||
ctx.Data["UserAndOrgForks"] = userAndOrgForks
|
||||
|
||||
@ -587,14 +589,14 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
if repo.IsFork {
|
||||
RetrieveBaseRepo(ctx, repo)
|
||||
if ctx.Written() {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if repo.IsGenerated() {
|
||||
RetrieveTemplateRepo(ctx, repo)
|
||||
if ctx.Written() {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -609,10 +611,18 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
if !isHomeOrSettings {
|
||||
ctx.Redirect(ctx.Repo.RepoLink)
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, repo)
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
if !setting.IsProd || setting.IsInTesting {
|
||||
panic("RepoAssignment: GitRepo should be nil")
|
||||
}
|
||||
_ = ctx.Repo.GitRepo.Close()
|
||||
ctx.Repo.GitRepo = nil
|
||||
}
|
||||
|
||||
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, repo)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "repository does not exist") || strings.Contains(err.Error(), "no such file or directory") {
|
||||
log.Error("Repository %-v has a broken repository on the file system: %s Error: %v", ctx.Repo.Repository, ctx.Repo.Repository.RepoPath(), err)
|
||||
@ -622,28 +632,16 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
if !isHomeOrSettings {
|
||||
ctx.Redirect(ctx.Repo.RepoLink)
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
ctx.ServerError("RepoAssignment Invalid repo "+repo.FullName(), err)
|
||||
return nil
|
||||
}
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
|
||||
// We opened it, we should close it
|
||||
cancel := func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Stop at this point when the repo is empty.
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
|
||||
branchOpts := git_model.FindBranchOptions{
|
||||
@ -654,7 +652,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
branchesTotal, err := db.Count[git_model.Branch](ctx, branchOpts)
|
||||
if err != nil {
|
||||
ctx.ServerError("CountBranches", err)
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
|
||||
// non-empty repo should have at least 1 branch, so this repository's branches haven't been synced yet
|
||||
@ -662,7 +660,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
branchesTotal, err = repo_module.SyncRepoBranches(ctx, ctx.Repo.Repository.ID, 0)
|
||||
if err != nil {
|
||||
ctx.ServerError("SyncRepoBranches", err)
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -670,7 +668,7 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
|
||||
// If no branch is set in the request URL, try to guess a default one.
|
||||
if len(ctx.Repo.BranchName) == 0 {
|
||||
if len(ctx.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
|
||||
if len(ctx.Repo.Repository.DefaultBranch) > 0 && ctx.Repo.GitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
|
||||
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
|
||||
} else {
|
||||
ctx.Repo.BranchName, _ = gitrepo.GetDefaultBranch(ctx, ctx.Repo.Repository)
|
||||
@ -711,12 +709,12 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
repoTransfer, err := repo_model.GetPendingRepositoryTransfer(ctx, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetPendingRepositoryTransfer", err)
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
|
||||
if err := repoTransfer.LoadAttributes(ctx); err != nil {
|
||||
ctx.ServerError("LoadRecipient", err)
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Data["RepoTransfer"] = repoTransfer
|
||||
@ -731,7 +729,6 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||
ctx.Data["GoDocDirectory"] = fullURLPrefix + "{/dir}"
|
||||
ctx.Data["GoDocFile"] = fullURLPrefix + "{/dir}/{file}#L{line}"
|
||||
}
|
||||
return cancel
|
||||
}
|
||||
|
||||
// RepoRefType type of repo reference
|
||||
@ -750,7 +747,7 @@ const headRefName = "HEAD"
|
||||
|
||||
// RepoRef handles repository reference names when the ref name is not
|
||||
// explicitly given
|
||||
func RepoRef() func(*Context) context.CancelFunc {
|
||||
func RepoRef() func(*Context) {
|
||||
// since no ref name is explicitly specified, ok to just use branch
|
||||
return RepoRefByType(RepoRefBranch)
|
||||
}
|
||||
@ -865,9 +862,9 @@ type RepoRefByTypeOptions struct {
|
||||
|
||||
// RepoRefByType handles repository reference name for a specific type
|
||||
// of repository reference
|
||||
func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func(*Context) context.CancelFunc {
|
||||
func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func(*Context) {
|
||||
opt := util.OptionalArg(opts)
|
||||
return func(ctx *Context) (cancel context.CancelFunc) {
|
||||
return func(ctx *Context) {
|
||||
refType := detectRefType
|
||||
// Empty repository does not have reference information.
|
||||
if ctx.Repo.Repository.IsEmpty {
|
||||
@ -875,7 +872,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
||||
ctx.Repo.IsViewBranch = true
|
||||
ctx.Repo.BranchName = ctx.Repo.Repository.DefaultBranch
|
||||
ctx.Data["TreePath"] = ""
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
@ -884,17 +881,10 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
||||
)
|
||||
|
||||
if ctx.Repo.GitRepo == nil {
|
||||
ctx.Repo.GitRepo, err = gitrepo.OpenRepository(ctx, ctx.Repo.Repository)
|
||||
ctx.Repo.GitRepo, err = gitrepo.RepositoryFromRequestContextOrOpen(ctx, ctx, ctx.Repo.Repository)
|
||||
if err != nil {
|
||||
ctx.ServerError(fmt.Sprintf("Open Repository %v failed", ctx.Repo.Repository.FullName()), err)
|
||||
return nil
|
||||
}
|
||||
// We opened it, we should close it
|
||||
cancel = func() {
|
||||
// If it's been set to nil then assume someone else has closed it.
|
||||
if ctx.Repo.GitRepo != nil {
|
||||
ctx.Repo.GitRepo.Close()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -924,7 +914,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
||||
ctx.Repo.Repository.MarkAsBrokenEmpty()
|
||||
} else {
|
||||
ctx.ServerError("GetBranchCommit", err)
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
ctx.Repo.IsViewBranch = true
|
||||
} else {
|
||||
@ -941,7 +931,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
||||
ctx.Flash.Info(ctx.Tr("repo.branch.renamed", refName, renamedBranchName))
|
||||
link := setting.AppSubURL + strings.Replace(ctx.Req.URL.EscapedPath(), util.PathEscapeSegments(refName), util.PathEscapeSegments(renamedBranchName), 1)
|
||||
ctx.Redirect(link)
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
|
||||
if refType == RepoRefBranch && ctx.Repo.GitRepo.IsBranchExist(refName) {
|
||||
@ -951,7 +941,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetBranchCommit(refName)
|
||||
if err != nil {
|
||||
ctx.ServerError("GetBranchCommit", err)
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if refType == RepoRefTag && ctx.Repo.GitRepo.IsTagExist(refName) {
|
||||
@ -962,10 +952,10 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
||||
if err != nil {
|
||||
if git.IsErrNotExist(err) {
|
||||
ctx.NotFound("GetTagCommit", err)
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
ctx.ServerError("GetTagCommit", err)
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
|
||||
} else if git.IsStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refName, 7) {
|
||||
@ -975,7 +965,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
||||
ctx.Repo.Commit, err = ctx.Repo.GitRepo.GetCommit(refName)
|
||||
if err != nil {
|
||||
ctx.NotFound("GetCommit", err)
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
// If short commit ID add canonical link header
|
||||
if len(refName) < ctx.Repo.GetObjectFormat().FullLength() {
|
||||
@ -984,10 +974,10 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
||||
}
|
||||
} else {
|
||||
if opt.IgnoreNotExistErr {
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
|
||||
if guessLegacyPath {
|
||||
@ -999,7 +989,7 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
||||
ctx.Repo.BranchNameSubURL(),
|
||||
util.PathEscapeSegments(ctx.Repo.TreePath))
|
||||
ctx.Redirect(redirect)
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@ -1017,12 +1007,10 @@ func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func
|
||||
ctx.Repo.CommitsCount, err = ctx.Repo.GetCommitsCount()
|
||||
if err != nil {
|
||||
ctx.ServerError("GetCommitsCount", err)
|
||||
return cancel
|
||||
return
|
||||
}
|
||||
ctx.Data["CommitsCount"] = ctx.Repo.CommitsCount
|
||||
ctx.Repo.GitRepo.LastCommitCache = git.NewLastCommitCache(ctx.Repo.CommitsCount, ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, cache.GetCache())
|
||||
|
||||
return cancel
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/cache"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/reqctx"
|
||||
"code.gitea.io/gitea/modules/session"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/translation"
|
||||
@ -40,7 +41,7 @@ func mockRequest(t *testing.T, reqPath string) *http.Request {
|
||||
requestURL, err := url.Parse(path)
|
||||
assert.NoError(t, err)
|
||||
req := &http.Request{Method: method, Host: requestURL.Host, URL: requestURL, Form: maps.Clone(requestURL.Query()), Header: http.Header{}}
|
||||
req = req.WithContext(middleware.WithContextData(req.Context()))
|
||||
req = req.WithContext(reqctx.NewRequestContextForTest(req.Context()))
|
||||
return req
|
||||
}
|
||||
|
||||
@ -60,17 +61,16 @@ func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*cont
|
||||
}
|
||||
resp := httptest.NewRecorder()
|
||||
req := mockRequest(t, reqPath)
|
||||
base, baseCleanUp := context.NewBaseContext(resp, req)
|
||||
_ = baseCleanUp // during test, it doesn't need to do clean up. TODO: this can be improved later
|
||||
base := context.NewBaseContext(resp, req)
|
||||
base.Data = middleware.GetContextData(req.Context())
|
||||
base.Locale = &translation.MockLocale{}
|
||||
|
||||
chiCtx := chi.NewRouteContext()
|
||||
ctx := context.NewWebContext(base, opt.Render, nil)
|
||||
ctx.AppendContextValue(context.WebContextKey, ctx)
|
||||
ctx.AppendContextValue(chi.RouteCtxKey, chiCtx)
|
||||
ctx.SetContextValue(context.WebContextKey, ctx)
|
||||
ctx.SetContextValue(chi.RouteCtxKey, chiCtx)
|
||||
if opt.SessionStore != nil {
|
||||
ctx.AppendContextValue(session.MockStoreContextKey, opt.SessionStore)
|
||||
ctx.SetContextValue(session.MockStoreContextKey, opt.SessionStore)
|
||||
ctx.Session = opt.SessionStore
|
||||
}
|
||||
ctx.Cache = cache.GetCache()
|
||||
@ -83,27 +83,24 @@ func MockContext(t *testing.T, reqPath string, opts ...MockContextOption) (*cont
|
||||
func MockAPIContext(t *testing.T, reqPath string) (*context.APIContext, *httptest.ResponseRecorder) {
|
||||
resp := httptest.NewRecorder()
|
||||
req := mockRequest(t, reqPath)
|
||||
base, baseCleanUp := context.NewBaseContext(resp, req)
|
||||
base := context.NewBaseContext(resp, req)
|
||||
base.Data = middleware.GetContextData(req.Context())
|
||||
base.Locale = &translation.MockLocale{}
|
||||
ctx := &context.APIContext{Base: base}
|
||||
_ = baseCleanUp // during test, it doesn't need to do clean up. TODO: this can be improved later
|
||||
|
||||
chiCtx := chi.NewRouteContext()
|
||||
ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx)
|
||||
ctx.SetContextValue(chi.RouteCtxKey, chiCtx)
|
||||
return ctx, resp
|
||||
}
|
||||
|
||||
func MockPrivateContext(t *testing.T, reqPath string) (*context.PrivateContext, *httptest.ResponseRecorder) {
|
||||
resp := httptest.NewRecorder()
|
||||
req := mockRequest(t, reqPath)
|
||||
base, baseCleanUp := context.NewBaseContext(resp, req)
|
||||
base := context.NewBaseContext(resp, req)
|
||||
base.Data = middleware.GetContextData(req.Context())
|
||||
base.Locale = &translation.MockLocale{}
|
||||
ctx := &context.PrivateContext{Base: base}
|
||||
_ = baseCleanUp // during test, it doesn't need to do clean up. TODO: this can be improved later
|
||||
chiCtx := chi.NewRouteContext()
|
||||
ctx.Base.AppendContextValue(chi.RouteCtxKey, chiCtx)
|
||||
ctx.SetContextValue(chi.RouteCtxKey, chiCtx)
|
||||
return ctx, resp
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,6 @@ import (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m, &unittest.TestOptions{
|
||||
FixtureFiles: []string{"user.yml", "repository.yml", "access.yml", "repo_unit.yml"},
|
||||
FixtureFiles: []string{"user.yml", "repository.yml", "access.yml", "repo_unit.yml", "issue.yml"},
|
||||
})
|
||||
}
|
||||
|
@ -11,9 +11,10 @@ import (
|
||||
gitea_context "code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
func ProcessorHelper() *markup.RenderHelperFuncs {
|
||||
func FormalRenderHelperFuncs() *markup.RenderHelperFuncs {
|
||||
return &markup.RenderHelperFuncs{
|
||||
RenderRepoFileCodePreview: renderRepoFileCodePreview,
|
||||
RenderRepoIssueIconTitle: renderRepoIssueIconTitle,
|
||||
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
||||
mentionedUser, err := user.GetUserByName(ctx, username)
|
||||
if err != nil {
|
@ -18,6 +18,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/indexer/code"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
gitea_context "code.gitea.io/gitea/services/context"
|
||||
"code.gitea.io/gitea/services/repository/files"
|
||||
)
|
||||
@ -46,7 +47,7 @@ func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePrevie
|
||||
return "", err
|
||||
}
|
||||
if !perms.CanRead(unit.TypeCode) {
|
||||
return "", fmt.Errorf("no permission")
|
||||
return "", util.ErrPermissionDenied
|
||||
}
|
||||
|
||||
gitRepo, err := gitrepo.OpenRepository(ctx, dbRepo)
|
@ -9,12 +9,13 @@ import (
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/contexttest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProcessorHelperCodePreview(t *testing.T) {
|
||||
func TestRenderHelperCodePreview(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
|
||||
@ -79,5 +80,5 @@ func TestProcessorHelperCodePreview(t *testing.T) {
|
||||
LineStart: 1,
|
||||
LineStop: 10,
|
||||
})
|
||||
assert.ErrorContains(t, err, "no permission")
|
||||
assert.ErrorIs(t, err, util.ErrPermissionDenied)
|
||||
}
|
66
services/markup/renderhelper_issueicontitle.go
Normal file
66
services/markup/renderhelper_issueicontitle.go
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package markup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
|
||||
"code.gitea.io/gitea/models/issues"
|
||||
"code.gitea.io/gitea/models/perm/access"
|
||||
"code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/htmlutil"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
gitea_context "code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
func renderRepoIssueIconTitle(ctx context.Context, opts markup.RenderIssueIconTitleOptions) (_ template.HTML, err error) {
|
||||
webCtx, ok := ctx.Value(gitea_context.WebContextKey).(*gitea_context.Context)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("context is not a web context")
|
||||
}
|
||||
|
||||
textIssueIndex := fmt.Sprintf("(#%d)", opts.IssueIndex)
|
||||
dbRepo := webCtx.Repo.Repository
|
||||
if opts.OwnerName != "" {
|
||||
dbRepo, err = repo.GetRepositoryByOwnerAndName(ctx, opts.OwnerName, opts.RepoName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
textIssueIndex = fmt.Sprintf("(%s/%s#%d)", dbRepo.OwnerName, dbRepo.Name, opts.IssueIndex)
|
||||
}
|
||||
if dbRepo == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
issue, err := issues.GetIssueByIndex(ctx, dbRepo.ID, opts.IssueIndex)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if webCtx.Repo.Repository == nil || dbRepo.ID != webCtx.Repo.Repository.ID {
|
||||
perms, err := access.GetUserRepoPermission(ctx, dbRepo, webCtx.Doer)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !perms.CanReadIssuesOrPulls(issue.IsPull) {
|
||||
return "", util.ErrPermissionDenied
|
||||
}
|
||||
}
|
||||
|
||||
if issue.IsPull {
|
||||
if err = issue.LoadPullRequest(ctx); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
htmlIcon, err := webCtx.RenderToHTML("shared/issueicon", issue)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return htmlutil.HTMLFormat(`<a href="%s">%s %s %s</a>`, opts.LinkHref, htmlIcon, issue.Title, textIssueIndex), nil
|
||||
}
|
49
services/markup/renderhelper_issueicontitle_test.go
Normal file
49
services/markup/renderhelper_issueicontitle_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package markup
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/services/contexttest"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRenderHelperIssueIconTitle(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
ctx, _ := contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
|
||||
ctx.Repo.Repository = unittest.AssertExistsAndLoadBean(t, &repo.Repository{ID: 1})
|
||||
htm, err := renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{
|
||||
LinkHref: "/link",
|
||||
IssueIndex: 1,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `<a href="/link"><span>octicon-issue-opened(16/text green)</span> issue1 (#1)</a>`, string(htm))
|
||||
|
||||
ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
|
||||
htm, err = renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{
|
||||
OwnerName: "user2",
|
||||
RepoName: "repo1",
|
||||
LinkHref: "/link",
|
||||
IssueIndex: 1,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, `<a href="/link"><span>octicon-issue-opened(16/text green)</span> issue1 (user2/repo1#1)</a>`, string(htm))
|
||||
|
||||
ctx, _ = contexttest.MockContext(t, "/", contexttest.MockContextOption{Render: templates.HTMLRenderer()})
|
||||
_, err = renderRepoIssueIconTitle(ctx, markup.RenderIssueIconTitleOptions{
|
||||
OwnerName: "user2",
|
||||
RepoName: "repo2",
|
||||
LinkHref: "/link",
|
||||
IssueIndex: 2,
|
||||
})
|
||||
assert.ErrorIs(t, err, util.ErrPermissionDenied)
|
||||
}
|
@ -18,7 +18,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProcessorHelper(t *testing.T) {
|
||||
func TestRenderHelperMention(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
userPublic := "user1"
|
||||
@ -32,23 +32,22 @@ func TestProcessorHelper(t *testing.T) {
|
||||
unittest.AssertCount(t, &user.User{Name: userNoSuch}, 0)
|
||||
|
||||
// when using general context, use user's visibility to check
|
||||
assert.True(t, ProcessorHelper().IsUsernameMentionable(context.Background(), userPublic))
|
||||
assert.False(t, ProcessorHelper().IsUsernameMentionable(context.Background(), userLimited))
|
||||
assert.False(t, ProcessorHelper().IsUsernameMentionable(context.Background(), userPrivate))
|
||||
assert.False(t, ProcessorHelper().IsUsernameMentionable(context.Background(), userNoSuch))
|
||||
assert.True(t, FormalRenderHelperFuncs().IsUsernameMentionable(context.Background(), userPublic))
|
||||
assert.False(t, FormalRenderHelperFuncs().IsUsernameMentionable(context.Background(), userLimited))
|
||||
assert.False(t, FormalRenderHelperFuncs().IsUsernameMentionable(context.Background(), userPrivate))
|
||||
assert.False(t, FormalRenderHelperFuncs().IsUsernameMentionable(context.Background(), userNoSuch))
|
||||
|
||||
// when using web context, use user.IsUserVisibleToViewer to check
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
assert.NoError(t, err)
|
||||
base, baseCleanUp := gitea_context.NewBaseContext(httptest.NewRecorder(), req)
|
||||
defer baseCleanUp()
|
||||
base := gitea_context.NewBaseContextForTest(httptest.NewRecorder(), req)
|
||||
giteaCtx := gitea_context.NewWebContext(base, &contexttest.MockRender{}, nil)
|
||||
|
||||
assert.True(t, ProcessorHelper().IsUsernameMentionable(giteaCtx, userPublic))
|
||||
assert.False(t, ProcessorHelper().IsUsernameMentionable(giteaCtx, userPrivate))
|
||||
assert.True(t, FormalRenderHelperFuncs().IsUsernameMentionable(giteaCtx, userPublic))
|
||||
assert.False(t, FormalRenderHelperFuncs().IsUsernameMentionable(giteaCtx, userPrivate))
|
||||
|
||||
giteaCtx.Doer, err = user.GetUserByName(db.DefaultContext, userPrivate)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, ProcessorHelper().IsUsernameMentionable(giteaCtx, userPublic))
|
||||
assert.True(t, ProcessorHelper().IsUsernameMentionable(giteaCtx, userPrivate))
|
||||
assert.True(t, FormalRenderHelperFuncs().IsUsernameMentionable(giteaCtx, userPublic))
|
||||
assert.True(t, FormalRenderHelperFuncs().IsUsernameMentionable(giteaCtx, userPrivate))
|
||||
}
|
@ -10,7 +10,6 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/container"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
)
|
||||
@ -25,12 +24,12 @@ func getAuthorSignatureSquash(ctx *mergeContext) (*git.Signature, error) {
|
||||
// Try to get an signature from the same user in one of the commits, as the
|
||||
// poster email might be private or commits might have a different signature
|
||||
// than the primary email address of the poster.
|
||||
gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpenPath(ctx, ctx.tmpBasePath)
|
||||
gitRepo, err := git.OpenRepository(ctx, ctx.tmpBasePath)
|
||||
if err != nil {
|
||||
log.Error("%-v Unable to open base repository: %v", ctx.pr, err)
|
||||
return nil, err
|
||||
}
|
||||
defer closer.Close()
|
||||
defer gitRepo.Close()
|
||||
|
||||
commits, err := gitRepo.CommitsBetweenIDs(trackingBranch, "HEAD")
|
||||
if err != nil {
|
||||
|
@ -1,4 +1,4 @@
|
||||
{{if eq .PackageDescriptor.Package.Type "arch"}}
|
||||
{{range .PackageDescriptor.Metadata.Licenses}}<div class="item" title="{{$.locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.}}</div>{{end}}
|
||||
{{range .PackageDescriptor.Metadata.Licenses}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law"}} {{.}}</div>{{end}}
|
||||
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}}
|
||||
{{end}}
|
||||
|
@ -693,7 +693,7 @@
|
||||
{{else if eq .Type 38}}
|
||||
<div class="timeline-item event" id="{{.HashTag}}">
|
||||
<span class="badge">{{svg "octicon-clock"}}</span>
|
||||
{{template "shared/user/avatarlink" dict "Context" $.Context "user" .Poster}}
|
||||
{{template "shared/user/avatarlink" dict "user" .Poster}}
|
||||
<span class="text grey muted-links">
|
||||
{{template "shared/user/authorlink" .Poster}}
|
||||
{{$timeStr := .Content|TimeEstimateString}}
|
||||
|
@ -3,7 +3,7 @@
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "actions.runners.runner_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .Total}})
|
||||
<div class="ui right">
|
||||
<div class="ui top right pointing dropdown">
|
||||
<div class="ui top right pointing dropdown jump">
|
||||
<button class="ui primary tiny button">
|
||||
{{ctx.Locale.Tr "actions.runners.new"}}
|
||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||
@ -17,14 +17,18 @@
|
||||
Registration Token
|
||||
</div>
|
||||
<div class="ui input">
|
||||
<input type="text" value="{{.RegistrationToken}}">
|
||||
<input type="text" value="{{.RegistrationToken}}" readonly>
|
||||
<button class="ui basic label button" aria-label="{{ctx.Locale.Tr "copy"}}" data-clipboard-text="{{.RegistrationToken}}">
|
||||
{{svg "octicon-copy" 14}}
|
||||
</button>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="item">
|
||||
<a href="{{$.Link}}/reset_registration_token">{{ctx.Locale.Tr "actions.runners.reset_registration_token"}}</a>
|
||||
<a class="link-action" data-url="{{$.Link}}/reset_registration_token"
|
||||
data-modal-confirm="{{ctx.Locale.Tr "actions.runners.reset_registration_token_confirm"}}"
|
||||
>
|
||||
{{ctx.Locale.Tr "actions.runners.reset_registration_token"}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,25 +1,25 @@
|
||||
{{if .IsPull}}
|
||||
{{if not .PullRequest}}
|
||||
{{- if .IsPull -}}
|
||||
{{- if not .PullRequest -}}
|
||||
No PullRequest
|
||||
{{else}}
|
||||
{{if .IsClosed}}
|
||||
{{if .PullRequest.HasMerged}}
|
||||
{{svg "octicon-git-merge" 16 "text purple"}}
|
||||
{{else}}
|
||||
{{svg "octicon-git-pull-request" 16 "text red"}}
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{if .PullRequest.IsWorkInProgress ctx}}
|
||||
{{svg "octicon-git-pull-request-draft" 16 "text grey"}}
|
||||
{{else}}
|
||||
{{svg "octicon-git-pull-request" 16 "text green"}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{else}}
|
||||
{{if .IsClosed}}
|
||||
{{svg "octicon-issue-closed" 16 "text red"}}
|
||||
{{else}}
|
||||
{{svg "octicon-issue-opened" 16 "text green"}}
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{- else -}}
|
||||
{{- if .IsClosed -}}
|
||||
{{- if .PullRequest.HasMerged -}}
|
||||
{{- svg "octicon-git-merge" 16 "text purple" -}}
|
||||
{{- else -}}
|
||||
{{- svg "octicon-git-pull-request" 16 "text red" -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- if .PullRequest.IsWorkInProgress ctx -}}
|
||||
{{- svg "octicon-git-pull-request-draft" 16 "text grey" -}}
|
||||
{{- else -}}
|
||||
{{- svg "octicon-git-pull-request" 16 "text green" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
{{- else -}}
|
||||
{{- if .IsClosed -}}
|
||||
{{- svg "octicon-issue-closed" 16 "text red" -}}
|
||||
{{- else -}}
|
||||
{{- svg "octicon-issue-opened" 16 "text green" -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
@ -58,7 +58,7 @@ func InitTest(requireGitea bool) {
|
||||
_ = os.Setenv("GITEA_CONF", giteaConf)
|
||||
fmt.Printf("Environment variable $GITEA_CONF not set, use default: %s\n", giteaConf)
|
||||
if !setting.EnableSQLite3 {
|
||||
testlogger.Fatalf(`sqlite3 requires: import _ "github.com/mattn/go-sqlite3" or -tags sqlite,sqlite_unlock_notify` + "\n")
|
||||
testlogger.Fatalf(`sqlite3 requires: -tags sqlite,sqlite_unlock_notify` + "\n")
|
||||
}
|
||||
}
|
||||
if !filepath.IsAbs(giteaConf) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user