Merge branch 'main' into fix-pom-parent

This commit is contained in:
wxiaoguang 2024-12-24 21:52:42 +08:00 committed by GitHub
commit 5050f7f45a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
318 changed files with 2300 additions and 1732 deletions

View File

@ -1,5 +1,5 @@
# Build stage # Build stage
FROM docker.io/library/golang:1.23-alpine3.20 AS build-env FROM docker.io/library/golang:1.23-alpine3.21 AS build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct} ENV GOPROXY=${GOPROXY:-direct}
@ -41,7 +41,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \
/go/src/code.gitea.io/gitea/environment-to-ini /go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
FROM docker.io/library/alpine:3.20 FROM docker.io/library/alpine:3.21
LABEL maintainer="maintainers@gitea.io" LABEL maintainer="maintainers@gitea.io"
EXPOSE 22 3000 EXPOSE 22 3000
@ -78,7 +78,7 @@ ENV GITEA_CUSTOM=/data/gitea
VOLUME ["/data"] VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/entrypoint"] ENTRYPOINT ["/usr/bin/entrypoint"]
CMD ["/bin/s6-svscan", "/etc/s6"] CMD ["/usr/bin/s6-svscan", "/etc/s6"]
COPY --from=build-env /tmp/local / COPY --from=build-env /tmp/local /
COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea COPY --from=build-env /go/src/code.gitea.io/gitea/gitea /app/gitea/gitea

View File

@ -1,5 +1,5 @@
# Build stage # Build stage
FROM docker.io/library/golang:1.23-alpine3.20 AS build-env FROM docker.io/library/golang:1.23-alpine3.21 AS build-env
ARG GOPROXY ARG GOPROXY
ENV GOPROXY=${GOPROXY:-direct} ENV GOPROXY=${GOPROXY:-direct}
@ -39,7 +39,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \
/go/src/code.gitea.io/gitea/environment-to-ini /go/src/code.gitea.io/gitea/environment-to-ini
RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete
FROM docker.io/library/alpine:3.20 FROM docker.io/library/alpine:3.21
LABEL maintainer="maintainers@gitea.io" LABEL maintainer="maintainers@gitea.io"
EXPOSE 2222 3000 EXPOSE 2222 3000

View File

@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user" 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/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
) )
@ -51,7 +52,7 @@ func GetRunnerToken(ctx context.Context, token string) (*ActionRunnerToken, erro
if err != nil { if err != nil {
return nil, err return nil, err
} else if !has { } 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 return &runnerToken, nil
} }
@ -68,19 +69,15 @@ func UpdateRunnerToken(ctx context.Context, r *ActionRunnerToken, cols ...string
return err 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. // 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 { if ownerID != 0 && repoID != 0 {
// It's trying to create a runner token that belongs to a repository, but OwnerID has been set accidentally. // 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. // Remove OwnerID to avoid confusion; it's not worth returning an error here.
ownerID = 0 ownerID = 0
} }
token, err := util.CryptoRandomString(40)
if err != nil {
return nil, err
}
runnerToken := &ActionRunnerToken{ runnerToken := &ActionRunnerToken{
OwnerID: ownerID, OwnerID: ownerID,
RepoID: repoID, RepoID: repoID,
@ -95,11 +92,19 @@ func NewRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerTo
return err return err
} }
_, err = db.GetEngine(ctx).Insert(runnerToken) _, err := db.GetEngine(ctx).Insert(runnerToken)
return err 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 // GetLatestRunnerToken returns the latest runner token
func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) { func GetLatestRunnerToken(ctx context.Context, ownerID, repoID int64) (*ActionRunnerToken, error) {
if ownerID != 0 && repoID != 0 { if ownerID != 0 && repoID != 0 {

View File

@ -13,9 +13,9 @@ import (
"testing" "testing"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/testlogger" "code.gitea.io/gitea/modules/testlogger"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -92,10 +92,7 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu
func MainTest(m *testing.M) { func MainTest(m *testing.M) {
testlogger.Init() testlogger.Init()
giteaRoot := base.SetupGiteaRoot() giteaRoot := test.SetupGiteaRoot()
if giteaRoot == "" {
testlogger.Fatalf("Environment variable $GITEA_ROOT not set\n")
}
giteaBinary := "gitea" giteaBinary := "gitea"
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
giteaBinary += ".exe" giteaBinary += ".exe"

View File

@ -14,13 +14,13 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/system" "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/auth/password/hash" "code.gitea.io/gitea/modules/auth/password/hash"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/setting/config" "code.gitea.io/gitea/modules/setting/config"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -206,7 +206,7 @@ func CreateTestEngine(opts FixturesOptions) error {
x, err := xorm.NewEngine("sqlite3", "file::memory:?cache=shared&_txlock=immediate") x, err := xorm.NewEngine("sqlite3", "file::memory:?cache=shared&_txlock=immediate")
if err != nil { if err != nil {
if strings.Contains(err.Error(), "unknown driver") { 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 return err
} }
@ -235,5 +235,5 @@ func PrepareTestEnv(t testing.TB) {
assert.NoError(t, PrepareTestDatabase()) assert.NoError(t, PrepareTestDatabase())
metaPath := filepath.Join(giteaRoot, "tests", "gitea-repositories-meta") metaPath := filepath.Join(giteaRoot, "tests", "gitea-repositories-meta")
assert.NoError(t, SyncDirs(metaPath, setting.RepoRootPath)) assert.NoError(t, SyncDirs(metaPath, setting.RepoRootPath))
base.SetupGiteaRoot() // Makes sure GITEA_ROOT is set test.SetupGiteaRoot() // Makes sure GITEA_ROOT is set
} }

View File

@ -1,9 +0,0 @@
// Copyright 2014 The Gogs Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package base
type (
// TplName template relative path type
TplName string
)

View File

@ -13,9 +13,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"hash" "hash"
"os"
"path/filepath"
"runtime"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@ -189,49 +186,3 @@ func EntryIcon(entry *git.TreeEntry) string {
return "file" return "file"
} }
// SetupGiteaRoot Sets GITEA_ROOT if it is not already set and returns the value
func SetupGiteaRoot() string {
giteaRoot := os.Getenv("GITEA_ROOT")
if giteaRoot == "" {
_, filename, _, _ := runtime.Caller(0)
giteaRoot = strings.TrimSuffix(filename, "modules/base/tool.go")
wd, err := os.Getwd()
if err != nil {
rel, err := filepath.Rel(giteaRoot, wd)
if err != nil && strings.HasPrefix(filepath.ToSlash(rel), "../") {
giteaRoot = wd
}
}
if _, err := os.Stat(filepath.Join(giteaRoot, "gitea")); os.IsNotExist(err) {
giteaRoot = ""
} else if err := os.Setenv("GITEA_ROOT", giteaRoot); err != nil {
giteaRoot = ""
}
}
return giteaRoot
}
// FormatNumberSI format a number
func FormatNumberSI(data any) string {
var num int64
if num1, ok := data.(int64); ok {
num = num1
} else if num1, ok := data.(int); ok {
num = int64(num1)
} else {
return ""
}
if num < 1000 {
return fmt.Sprintf("%d", num)
} else if num < 1000000 {
num2 := float32(num) / float32(1000.0)
return fmt.Sprintf("%.1fk", num2)
} else if num < 1000000000 {
num2 := float32(num) / float32(1000000.0)
return fmt.Sprintf("%.1fM", num2)
}
num2 := float32(num) / float32(1000000000.0)
return fmt.Sprintf("%.1fG", num2)
}

View File

@ -169,18 +169,3 @@ func TestInt64sToStrings(t *testing.T) {
} }
// TODO: Test EntryIcon // TODO: Test EntryIcon
func TestSetupGiteaRoot(t *testing.T) {
t.Setenv("GITEA_ROOT", "test")
assert.Equal(t, "test", SetupGiteaRoot())
t.Setenv("GITEA_ROOT", "")
assert.NotEqual(t, "test", SetupGiteaRoot())
}
func TestFormatNumberSI(t *testing.T) {
assert.Equal(t, "125", FormatNumberSI(int(125)))
assert.Equal(t, "1.3k", FormatNumberSI(int64(1317)))
assert.Equal(t, "21.3M", FormatNumberSI(21317675))
assert.Equal(t, "45.7G", FormatNumberSI(45721317675))
assert.Equal(t, "", FormatNumberSI("test"))
}

View File

@ -216,8 +216,6 @@ type CommitsByFileAndRangeOptions struct {
// CommitsByFileAndRange return the commits according revision file and the page // CommitsByFileAndRange return the commits according revision file and the page
func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) ([]*Commit, error) { func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions) ([]*Commit, error) {
skip := (opts.Page - 1) * setting.Git.CommitsRangeSize
stdoutReader, stdoutWriter := io.Pipe() stdoutReader, stdoutWriter := io.Pipe()
defer func() { defer func() {
_ = stdoutReader.Close() _ = stdoutReader.Close()
@ -226,8 +224,8 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
go func() { go func() {
stderr := strings.Builder{} stderr := strings.Builder{}
gitCmd := NewCommand(repo.Ctx, "rev-list"). gitCmd := NewCommand(repo.Ctx, "rev-list").
AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize*opts.Page). AddOptionFormat("--max-count=%d", setting.Git.CommitsRangeSize).
AddOptionFormat("--skip=%d", skip) AddOptionFormat("--skip=%d", (opts.Page-1)*setting.Git.CommitsRangeSize)
gitCmd.AddDynamicArguments(opts.Revision) gitCmd.AddDynamicArguments(opts.Revision)
if opts.Not != "" { if opts.Not != "" {

View File

@ -8,7 +8,11 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestRepository_GetCommitBranches(t *testing.T) { 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)
}

View File

@ -233,72 +233,34 @@ func parseDiffStat(stdout string) (numFiles, totalAdditions, totalDeletions int,
return numFiles, totalAdditions, totalDeletions, err return numFiles, totalAdditions, totalDeletions, err
} }
// GetDiffOrPatch generates either diff or formatted patch data between given revisions
func (repo *Repository) GetDiffOrPatch(base, head string, w io.Writer, patch, binary bool) error {
if patch {
return repo.GetPatch(base, head, w)
}
if binary {
return repo.GetDiffBinary(base, head, w)
}
return repo.GetDiff(base, head, w)
}
// GetDiff generates and returns patch data between given revisions, optimized for human readability // GetDiff generates and returns patch data between given revisions, optimized for human readability
func (repo *Repository) GetDiff(base, head string, w io.Writer) error { func (repo *Repository) GetDiff(compareArg string, w io.Writer) error {
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
err := NewCommand(repo.Ctx, "diff", "-p").AddDynamicArguments(base + "..." + head). return NewCommand(repo.Ctx, "diff", "-p").AddDynamicArguments(compareArg).
Run(&RunOpts{ Run(&RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: w, Stdout: w,
Stderr: stderr, Stderr: stderr,
}) })
if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
return NewCommand(repo.Ctx, "diff", "-p").AddDynamicArguments(base, head).
Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
})
}
return err
} }
// GetDiffBinary generates and returns patch data between given revisions, including binary diffs. // GetDiffBinary generates and returns patch data between given revisions, including binary diffs.
func (repo *Repository) GetDiffBinary(base, head string, w io.Writer) error { func (repo *Repository) GetDiffBinary(compareArg string, w io.Writer) error {
stderr := new(bytes.Buffer) return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram").AddDynamicArguments(compareArg).Run(&RunOpts{
err := NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram").AddDynamicArguments(base + "..." + head).
Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
})
if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
return NewCommand(repo.Ctx, "diff", "-p", "--binary", "--histogram").AddDynamicArguments(base, head).
Run(&RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: w, Stdout: w,
}) })
}
return err
} }
// GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply` // GetPatch generates and returns format-patch data between given revisions, able to be used with `git apply`
func (repo *Repository) GetPatch(base, head string, w io.Writer) error { func (repo *Repository) GetPatch(compareArg string, w io.Writer) error {
stderr := new(bytes.Buffer) stderr := new(bytes.Buffer)
err := NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout").AddDynamicArguments(base + "..." + head). return NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout").AddDynamicArguments(compareArg).
Run(&RunOpts{ Run(&RunOpts{
Dir: repo.Path, Dir: repo.Path,
Stdout: w, Stdout: w,
Stderr: stderr, Stderr: stderr,
}) })
if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
return NewCommand(repo.Ctx, "format-patch", "--binary", "--stdout").AddDynamicArguments(base, head).
Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
})
}
return err
} }
// GetFilesChangedBetween returns a list of all files that have been changed between the given commits // GetFilesChangedBetween returns a list of all files that have been changed between the given commits
@ -329,21 +291,6 @@ func (repo *Repository) GetFilesChangedBetween(base, head string) ([]string, err
return split, err return split, err
} }
// GetDiffFromMergeBase generates and return patch data from merge base to head
func (repo *Repository) GetDiffFromMergeBase(base, head string, w io.Writer) error {
stderr := new(bytes.Buffer)
err := NewCommand(repo.Ctx, "diff", "-p", "--binary").AddDynamicArguments(base + "..." + head).
Run(&RunOpts{
Dir: repo.Path,
Stdout: w,
Stderr: stderr,
})
if err != nil && bytes.Contains(stderr.Bytes(), []byte("no merge base")) {
return repo.GetDiffBinary(base, head, w)
}
return err
}
// ReadPatchCommit will check if a diff patch exists and return stats // ReadPatchCommit will check if a diff patch exists and return stats
func (repo *Repository) ReadPatchCommit(prID int64) (commitSHA string, err error) { func (repo *Repository) ReadPatchCommit(prID int64) (commitSHA string, err error) {
// Migrated repositories download patches to "pulls" location // Migrated repositories download patches to "pulls" location

View File

@ -28,7 +28,7 @@ func TestGetFormatPatch(t *testing.T) {
defer repo.Close() defer repo.Close()
rd := &bytes.Buffer{} rd := &bytes.Buffer{}
err = repo.GetPatch("8d92fc95^", "8d92fc95", rd) err = repo.GetPatch("8d92fc95^...8d92fc95", rd)
if err != nil { if err != nil {
assert.NoError(t, err) assert.NoError(t, err)
return return

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "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. // contextKey is a value for use with context.WithValue.
type contextKey struct { type contextKey struct {
name string repoPath 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
} }
// RepositoryFromContextOrOpen attempts to get the repository from the context or just opens it // 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) { func RepositoryFromContextOrOpen(ctx context.Context, repo Repository) (*git.Repository, io.Closer, error) {
gitRepo := repositoryFromContext(ctx, repo) ds := reqctx.GetRequestDataStore(ctx)
if gitRepo != nil { if ds != nil {
return gitRepo, util.NopCloser{}, nil gitRepo, err := RepositoryFromRequestContextOrOpen(ctx, ds, repo)
return gitRepo, util.NopCloser{}, err
} }
gitRepo, err := OpenRepository(ctx, repo) gitRepo, err := OpenRepository(ctx, repo)
return gitRepo, gitRepo, err return gitRepo, gitRepo, err
} }
// repositoryFromContextPath attempts to get the repository from the context // RepositoryFromRequestContextOrOpen opens the repository at the given relative path in the provided request context
func repositoryFromContextPath(ctx context.Context, path string) *git.Repository { // The repo will be automatically closed when the request context is done
value := ctx.Value(RepositoryContextKey) func RepositoryFromRequestContextOrOpen(ctx context.Context, ds reqctx.RequestDataStore, repo Repository) (*git.Repository, error) {
if value == nil { ck := contextKey{repoPath: repoPath(repo)}
return nil if gitRepo, ok := ctx.Value(ck).(*git.Repository); ok {
return gitRepo, nil
} }
gitRepo, err := git.OpenRepository(ctx, ck.repoPath)
if repo, ok := value.(*git.Repository); ok && repo != nil { if err != nil {
if repo.Path == path { return nil, err
return repo
} }
} ds.AddCloser(gitRepo)
ds.SetContextValue(ck, gitRepo)
return nil return gitRepo, 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
} }

View File

@ -14,15 +14,11 @@ import (
// WalkReferences walks all the references from the repository // WalkReferences walks all the references from the repository
// refname is empty, ObjectTag or ObjectBranch. All other values should be treated as equivalent to empty. // 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) { func WalkReferences(ctx context.Context, repo Repository, walkfn func(sha1, refname string) error) (int, error) {
gitRepo := repositoryFromContext(ctx, repo) gitRepo, closer, err := RepositoryFromContextOrOpen(ctx, repo)
if gitRepo == nil {
var err error
gitRepo, err = OpenRepository(ctx, repo)
if err != nil { if err != nil {
return 0, err return 0, err
} }
defer gitRepo.Close() defer closer.Close()
}
i := 0 i := 0
iter, err := gitRepo.GoGitRepo().References() iter, err := gitRepo.GoGitRepo().References()

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/references"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"golang.org/x/net/html" "golang.org/x/net/html"
@ -194,3 +195,21 @@ func hashCurrentPatternProcessor(ctx *RenderContext, node *html.Node) {
node = node.NextSibling.NextSibling 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
}
}

View File

@ -4,9 +4,9 @@
package markup package markup
import ( import (
"strconv"
"strings" "strings"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/references"
@ -16,8 +16,16 @@ import (
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"golang.org/x/net/html" "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) { func fullIssuePatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.RenderOptions.Metas == nil { if ctx.RenderOptions.Metas == nil {
return 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) { func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
if ctx.RenderOptions.Metas == nil { if ctx.RenderOptions.Metas == nil {
return return
@ -76,32 +105,28 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
// old logic: crossLinkOnly := ctx.RenderOptions.Metas["mode"] == "document" && !ctx.IsWiki // old logic: crossLinkOnly := ctx.RenderOptions.Metas["mode"] == "document" && !ctx.IsWiki
crossLinkOnly := ctx.RenderOptions.Metas["markupAllowShortIssuePattern"] != "true" crossLinkOnly := ctx.RenderOptions.Metas["markupAllowShortIssuePattern"] != "true"
var ( var ref *references.RenderizableReference
found bool
ref *references.RenderizableReference
)
next := node.NextSibling next := node.NextSibling
for node != nil && node != next { for node != nil && node != next {
_, hasExtTrackFormat := ctx.RenderOptions.Metas["format"] _, hasExtTrackFormat := ctx.RenderOptions.Metas["format"]
// Repos with external issue trackers might still need to reference local PRs // 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 // 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 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"] { switch ctx.RenderOptions.Metas["style"] {
case "", IssueNameStyleNumeric: case "", IssueNameStyleNumeric:
found, ref = foundNumeric, refNumeric ref = refNumeric
case IssueNameStyleAlphanumeric: case IssueNameStyleAlphanumeric:
found, ref = references.FindRenderizableReferenceAlphanumeric(node.Data) ref = references.FindRenderizableReferenceAlphanumeric(node.Data)
case IssueNameStyleRegexp: case IssueNameStyleRegexp:
pattern, err := regexplru.GetCompiled(ctx.RenderOptions.Metas["regexp"]) pattern, err := regexplru.GetCompiled(ctx.RenderOptions.Metas["regexp"])
if err != nil { if err != nil {
return 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 // 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 hasExtTrackFormat && !isNumericStyle && refNumeric != nil {
// If numeric (PR) was found, and it was BEFORE the non-numeric pattern, use that // 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. // Allow a free-pass when non-numeric pattern wasn't found.
if found && (ref == nil || refNumeric.RefLocation.Start < ref.RefLocation.Start) { if ref == nil || refNumeric.RefLocation.Start < ref.RefLocation.Start {
found = foundNumeric
ref = refNumeric ref = refNumeric
} }
} }
if !found {
if ref == nil {
return return
} }
var link *html.Node 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 { if hasExtTrackFormat && !ref.IsPull {
ctx.RenderOptions.Metas["index"] = ref.Issue 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) 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 { } else {
// Path determines the type of link that will be rendered. It's unknown at this point whether // 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 // 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. // 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") issuePath := util.Iif(ref.IsPull, "pulls", "issues")
if ref.Owner == "" { linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(issueOwner, issueRepo, issuePath, ref.Issue), LinkTypeApp)
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") // at the moment, only render the issue index in a full line (or simple line) as icon+title
} else { // otherwise it would be too noisy for "take #1 as an example" in a sentence
linkHref := ctx.RenderHelper.ResolveLink(util.URLJoin(ref.Owner, ref.Name, issuePath, ref.Issue), LinkTypeApp) if node.Parent.DataAtom == atom.Li && ref.RefLocation.Start < 20 && ref.RefLocation.End == len(node.Data) {
link = createLink(ctx, linkHref, reftext, "ref-issue") 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 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
}
}

View 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>`,
)
})
}

View File

@ -38,6 +38,7 @@ type RenderHelper interface {
type RenderHelperFuncs struct { type RenderHelperFuncs struct {
IsUsernameMentionable func(ctx context.Context, username string) bool IsUsernameMentionable func(ctx context.Context, username string) bool
RenderRepoFileCodePreview func(ctx context.Context, options RenderCodePreviewOptions) (template.HTML, error) RenderRepoFileCodePreview func(ctx context.Context, options RenderCodePreviewOptions) (template.HTML, error)
RenderRepoIssueIconTitle func(ctx context.Context, options RenderIssueIconTitleOptions) (template.HTML, error)
} }
var DefaultRenderHelperFuncs *RenderHelperFuncs var DefaultRenderHelperFuncs *RenderHelperFuncs

View File

@ -32,7 +32,7 @@ var (
// issueNumericPattern matches string that references to a numeric issue, e.g. #1287 // issueNumericPattern matches string that references to a numeric issue, e.g. #1287
issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\")([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$)`) issueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\'|\")([#!][0-9]+)(?:\s|$|\)|\]|\'|\"|[:;,.?!]\s|[:;,.?!]$)`)
// issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234 // issueAlphanumericPattern matches string that references to an alphanumeric issue, e.g. ABC-1234
issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\"|\')([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$)|\"|\')`) issueAlphanumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[|\"|\')([A-Z]{1,10}-[1-9][0-9]*)(?:\s|$|\)|\]|:|\.(\s|$)|\"|\'|,)`)
// crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository // crossReferenceIssueNumericPattern matches string that references a numeric issue in a different repository
// e.g. org/repo#12345 // e.g. org/repo#12345
crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) crossReferenceIssueNumericPattern = regexp.MustCompile(`(?:\s|^|\(|\[)([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+[#!][0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`)
@ -330,22 +330,22 @@ func FindAllIssueReferences(content string) []IssueReference {
} }
// FindRenderizableReferenceNumeric returns the first unvalidated reference found in a string. // 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 var match []int
if !crossLinkOnly { if !crossLinkOnly {
match = issueNumericPattern.FindStringSubmatchIndex(content) match = issueNumericPattern.FindStringSubmatchIndex(content)
} }
if match == nil { if match == nil {
if match = crossReferenceIssueNumericPattern.FindStringSubmatchIndex(content); 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) r := getCrossReference(util.UnsafeStringToBytes(content), match[2], match[3], false, prOnly)
if r == nil { if r == nil {
return false, nil return nil
} }
return true, &RenderizableReference{ return &RenderizableReference{
Issue: r.issue, Issue: r.issue,
Owner: r.owner, Owner: r.owner,
Name: r.name, Name: r.name,
@ -372,15 +372,14 @@ func FindRenderizableCommitCrossReference(content string) (bool, *RenderizableRe
} }
// FindRenderizableReferenceRegexp returns the first regexp unvalidated references found in a string. // 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) match := pattern.FindStringSubmatchIndex(content)
if len(match) < 4 { if len(match) < 4 {
return false, nil return nil
} }
action, location := findActionKeywords([]byte(content), match[2]) action, location := findActionKeywords([]byte(content), match[2])
return &RenderizableReference{
return true, &RenderizableReference{
Issue: content[match[2]:match[3]], Issue: content[match[2]:match[3]],
RefLocation: &RefSpan{Start: match[0], End: match[1]}, RefLocation: &RefSpan{Start: match[0], End: match[1]},
Action: action, 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. // 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) match := issueAlphanumericPattern.FindStringSubmatchIndex(content)
if match == nil { if match == nil {
return false, nil return nil
} }
action, location := findActionKeywords([]byte(content), match[2]) action, location := findActionKeywords([]byte(content), match[2])
return &RenderizableReference{
return true, &RenderizableReference{
Issue: content[match[2]:match[3]], Issue: content[match[2]:match[3]],
RefLocation: &RefSpan{Start: match[2], End: match[3]}, RefLocation: &RefSpan{Start: match[2], End: match[3]},
Action: action, Action: action,

View File

@ -249,11 +249,10 @@ func TestFindAllIssueReferences(t *testing.T) {
} }
for _, fixture := range alnumFixtures { for _, fixture := range alnumFixtures {
found, ref := FindRenderizableReferenceAlphanumeric(fixture.input) ref := FindRenderizableReferenceAlphanumeric(fixture.input)
if fixture.issue == "" { if fixture.issue == "" {
assert.False(t, found, "Failed to parse: {%s}", fixture.input) assert.Nil(t, ref, "Failed to parse: {%s}", fixture.input)
} else { } 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.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.refLocation, ref.RefLocation, "Failed to parse: {%s}", fixture.input)
assert.Equal(t, fixture.action, ref.Action, "Failed to parse: {%s}", fixture.input) assert.Equal(t, fixture.action, ref.Action, "Failed to parse: {%s}", fixture.input)
@ -463,6 +462,7 @@ func TestRegExp_issueAlphanumericPattern(t *testing.T) {
"ABC-123:", "ABC-123:",
"\"ABC-123\"", "\"ABC-123\"",
"'ABC-123'", "'ABC-123'",
"ABC-123, unknown PR",
} }
falseTestCases := []string{ falseTestCases := []string{
"RC-08", "RC-08",

View File

@ -31,12 +31,7 @@ func Test_getLicense(t *testing.T) {
Copyright (c) 2023 Gitea Copyright (c) 2023 Gitea
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted`,
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
`,
wantErr: assert.NoError, wantErr: assert.NoError,
}, },
{ {
@ -53,7 +48,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
if !tt.wantErr(t, err, fmt.Sprintf("GetLicense(%v, %v)", tt.args.name, tt.args.values)) { if !tt.wantErr(t, err, fmt.Sprintf("GetLicense(%v, %v)", tt.args.name, tt.args.values)) {
return return
} }
assert.Equalf(t, tt.want, string(got), "GetLicense(%v, %v)", tt.args.name, tt.args.values) assert.Contains(t, string(got), tt.want, "GetLicense(%v, %v)", tt.args.name, tt.args.values)
}) })
} }
} }

123
modules/reqctx/datastore.go Normal file
View 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)}}
}

View File

@ -235,3 +235,9 @@ func checkOverlappedPath(name, path string) {
} }
configuredPaths[path] = name configuredPaths[path] = name
} }
func PanicInDevOrTesting(msg string, a ...any) {
if !IsProd || IsInTesting {
panic(fmt.Sprintf(msg, a...))
}
}

View File

@ -10,7 +10,7 @@ import (
"sync" "sync"
) )
type normalizeVarsStruct struct { type globalVarsStruct struct {
reXMLDoc, reXMLDoc,
reComment, reComment,
reAttrXMLNs, reAttrXMLNs,
@ -18,16 +18,8 @@ type normalizeVarsStruct struct {
reAttrClassPrefix *regexp.Regexp reAttrClassPrefix *regexp.Regexp
} }
var ( var globalVars = sync.OnceValue(func() *globalVarsStruct {
normalizeVars *normalizeVarsStruct return &globalVarsStruct{
normalizeVarsOnce sync.Once
)
// 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.*?>`), reXMLDoc: regexp.MustCompile(`(?s)<\?xml.*?>`),
reComment: regexp.MustCompile(`(?s)<!--.*?-->`), reComment: regexp.MustCompile(`(?s)<!--.*?-->`),
@ -35,9 +27,14 @@ func Normalize(data []byte, size int) []byte {
reAttrSize: regexp.MustCompile(`(?s)\s+(width|height)\s*=\s*"[^"]+"`), reAttrSize: regexp.MustCompile(`(?s)\s+(width|height)\s*=\s*"[^"]+"`),
reAttrClassPrefix: regexp.MustCompile(`(?s)\s+class\s*=\s*"`), reAttrClassPrefix: regexp.MustCompile(`(?s)\s+class\s*=\s*"`),
} }
}) })
data = normalizeVars.reXMLDoc.ReplaceAll(data, nil)
data = normalizeVars.reComment.ReplaceAll(data, nil) // 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 {
vars := globalVars()
data = vars.reXMLDoc.ReplaceAll(data, nil)
data = vars.reComment.ReplaceAll(data, nil)
data = bytes.TrimSpace(data) data = bytes.TrimSpace(data)
svgTag, svgRemaining, ok := bytes.Cut(data, []byte(">")) svgTag, svgRemaining, ok := bytes.Cut(data, []byte(">"))
@ -45,9 +42,9 @@ func Normalize(data []byte, size int) []byte {
return data return data
} }
normalized := bytes.Clone(svgTag) normalized := bytes.Clone(svgTag)
normalized = normalizeVars.reAttrXMLNs.ReplaceAll(normalized, nil) normalized = vars.reAttrXMLNs.ReplaceAll(normalized, nil)
normalized = normalizeVars.reAttrSize.ReplaceAll(normalized, nil) normalized = vars.reAttrSize.ReplaceAll(normalized, nil)
normalized = normalizeVars.reAttrClassPrefix.ReplaceAll(normalized, []byte(` class="`)) normalized = vars.reAttrClassPrefix.ReplaceAll(normalized, []byte(` class="`))
normalized = bytes.TrimSpace(normalized) normalized = bytes.TrimSpace(normalized)
normalized = fmt.Appendf(normalized, ` width="%d" height="%d"`, size, size) normalized = fmt.Appendf(normalized, ` width="%d" height="%d"`, size, size)
if !bytes.Contains(normalized, []byte(` class="`)) { if !bytes.Contains(normalized, []byte(` class="`)) {

View File

@ -9,7 +9,6 @@ import (
"html" "html"
"html/template" "html/template"
"net/url" "net/url"
"reflect"
"strings" "strings"
"time" "time"
@ -69,7 +68,7 @@ func NewFuncMap() template.FuncMap {
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// time / number / format // time / number / format
"FileSize": base.FileSize, "FileSize": base.FileSize,
"CountFmt": base.FormatNumberSI, "CountFmt": countFmt,
"Sec2Time": util.SecToTime, "Sec2Time": util.SecToTime,
"TimeEstimateString": timeEstimateString, "TimeEstimateString": timeEstimateString,
@ -239,29 +238,8 @@ func iif(condition any, vals ...any) any {
} }
func isTemplateTruthy(v any) bool { func isTemplateTruthy(v any) bool {
if v == nil { truth, _ := template.IsTrue(v)
return false return truth
}
rv := reflect.ValueOf(v)
switch rv.Kind() {
case reflect.Bool:
return rv.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return rv.Int() != 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return rv.Uint() != 0
case reflect.Float32, reflect.Float64:
return rv.Float() != 0
case reflect.Complex64, reflect.Complex128:
return rv.Complex() != 0
case reflect.String, reflect.Slice, reflect.Array, reflect.Map:
return rv.Len() > 0
case reflect.Struct:
return true
default:
return !rv.IsNil()
}
} }
// evalTokens evaluates the expression by tokens and returns the result, see the comment of eval.Expr for details. // evalTokens evaluates the expression by tokens and returns the result, see the comment of eval.Expr for details.
@ -286,14 +264,6 @@ func userThemeName(user *user_model.User) string {
return setting.UI.DefaultTheme return setting.UI.DefaultTheme
} }
func timeEstimateString(timeSec any) string {
v, _ := util.ToInt64(timeSec)
if v == 0 {
return ""
}
return util.TimeEstimateString(v)
}
// QueryBuild builds a query string from a list of key-value pairs. // QueryBuild builds a query string from a list of key-value pairs.
// It omits the nil and empty strings, but it doesn't omit other zero values, // It omits the nil and empty strings, but it doesn't omit other zero values,
// because the zero value of number types may have a meaning. // because the zero value of number types may have a meaning.
@ -361,7 +331,5 @@ func QueryBuild(a ...any) template.URL {
} }
func panicIfDevOrTesting() { func panicIfDevOrTesting() {
if !setting.IsProd || setting.IsInTesting { setting.PanicInDevOrTesting("legacy template functions are for backward compatibility only, do not use them in new code")
panic("legacy template functions are for backward compatibility only, do not use them in new code")
}
} }

View File

@ -8,6 +8,7 @@ import (
"strings" "strings"
"testing" "testing"
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -65,31 +66,12 @@ func TestSanitizeHTML(t *testing.T) {
assert.Equal(t, template.HTML(`<a href="/" rel="nofollow">link</a> xss <div>inline</div>`), SanitizeHTML(`<a href="/">link</a> <a href="javascript:">xss</a> <div style="dangerous">inline</div>`)) assert.Equal(t, template.HTML(`<a href="/" rel="nofollow">link</a> xss <div>inline</div>`), SanitizeHTML(`<a href="/">link</a> <a href="javascript:">xss</a> <div style="dangerous">inline</div>`))
} }
func TestTemplateTruthy(t *testing.T) { func TestTemplateIif(t *testing.T) {
tmpl := template.New("test") tmpl := template.New("test")
tmpl.Funcs(template.FuncMap{"Iif": iif}) tmpl.Funcs(template.FuncMap{"Iif": iif})
template.Must(tmpl.Parse(`{{if .Value}}true{{else}}false{{end}}:{{Iif .Value "true" "false"}}`)) template.Must(tmpl.Parse(`{{if .Value}}true{{else}}false{{end}}:{{Iif .Value "true" "false"}}`))
cases := []any{ cases := []any{nil, false, true, "", "string", 0, 1}
nil, false, true, "", "string", 0, 1,
byte(0), byte(1), int64(0), int64(1), float64(0), float64(1),
complex(0, 0), complex(1, 0),
(chan int)(nil), make(chan int),
(func())(nil), func() {},
util.ToPointer(0), util.ToPointer(util.ToPointer(0)),
util.ToPointer(1), util.ToPointer(util.ToPointer(1)),
[0]int{},
[1]int{0},
[]int(nil),
[]int{},
[]int{0},
map[any]any(nil),
map[any]any{},
map[any]any{"k": "v"},
(*struct{})(nil),
struct{}{},
util.ToPointer(struct{}{}),
}
w := &strings.Builder{} w := &strings.Builder{}
truthyCount := 0 truthyCount := 0
for i, v := range cases { for i, v := range cases {
@ -102,3 +84,37 @@ func TestTemplateTruthy(t *testing.T) {
} }
assert.True(t, truthyCount != 0 && truthyCount != len(cases)) assert.True(t, truthyCount != 0 && truthyCount != len(cases))
} }
func TestTemplateEscape(t *testing.T) {
execTmpl := func(code string) string {
tmpl := template.New("test")
tmpl.Funcs(template.FuncMap{"QueryBuild": QueryBuild, "HTMLFormat": htmlutil.HTMLFormat})
template.Must(tmpl.Parse(code))
w := &strings.Builder{}
assert.NoError(t, tmpl.Execute(w, nil))
return w.String()
}
t.Run("Golang URL Escape", func(t *testing.T) {
// Golang template considers "href", "*src*", "*uri*", "*url*" (and more) ... attributes as contentTypeURL and does auto-escaping
actual := execTmpl(`<a href="?a={{"%"}}"></a>`)
assert.Equal(t, `<a href="?a=%25"></a>`, actual)
actual = execTmpl(`<a data-xxx-url="?a={{"%"}}"></a>`)
assert.Equal(t, `<a data-xxx-url="?a=%25"></a>`, actual)
})
t.Run("Golang URL No-escape", func(t *testing.T) {
// non-URL content isn't auto-escaped
actual := execTmpl(`<a data-link="?a={{"%"}}"></a>`)
assert.Equal(t, `<a data-link="?a=%"></a>`, actual)
})
t.Run("QueryBuild", func(t *testing.T) {
actual := execTmpl(`<a href="{{QueryBuild "?" "a" "%"}}"></a>`)
assert.Equal(t, `<a href="?a=%25"></a>`, actual)
actual = execTmpl(`<a href="?{{QueryBuild "a" "%"}}"></a>`)
assert.Equal(t, `<a href="?a=%25"></a>`, actual)
})
t.Run("HTMLFormat", func(t *testing.T) {
actual := execTmpl("{{HTMLFormat `<a k=\"%s\">%s</a>` `\"` `<>`}}")
assert.Equal(t, `<a k="&#34;">&lt;&gt;</a>`, actual)
})
}

View File

@ -29,6 +29,8 @@ import (
type TemplateExecutor scopedtmpl.TemplateExecutor type TemplateExecutor scopedtmpl.TemplateExecutor
type TplName string
type HTMLRender struct { type HTMLRender struct {
templates atomic.Pointer[scopedtmpl.ScopedTemplate] templates atomic.Pointer[scopedtmpl.ScopedTemplate]
} }
@ -40,7 +42,8 @@ var (
var ErrTemplateNotInitialized = errors.New("template system is not initialized, check your log for errors") var ErrTemplateNotInitialized = errors.New("template system is not initialized, check your log for errors")
func (h *HTMLRender) HTML(w io.Writer, status int, name string, data any, ctx context.Context) error { //nolint:revive func (h *HTMLRender) HTML(w io.Writer, status int, tplName TplName, data any, ctx context.Context) error { //nolint:revive
name := string(tplName)
if respWriter, ok := w.(http.ResponseWriter); ok { if respWriter, ok := w.(http.ResponseWriter); ok {
if respWriter.Header().Get("Content-Type") == "" { if respWriter.Header().Get("Content-Type") == "" {
respWriter.Header().Set("Content-Type", "text/html; charset=utf-8") respWriter.Header().Set("Content-Type", "text/html; charset=utf-8")

View File

@ -0,0 +1,37 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package templates
import (
"fmt"
"code.gitea.io/gitea/modules/util"
)
func timeEstimateString(timeSec any) string {
v, _ := util.ToInt64(timeSec)
if v == 0 {
return ""
}
return util.TimeEstimateString(v)
}
func countFmt(data any) string {
// legacy code, not ideal, still used in some places
num, err := util.ToInt64(data)
if err != nil {
return ""
}
if num < 1000 {
return fmt.Sprintf("%d", num)
} else if num < 1_000_000 {
num2 := float32(num) / 1000.0
return fmt.Sprintf("%.1fk", num2)
} else if num < 1_000_000_000 {
num2 := float32(num) / 1_000_000.0
return fmt.Sprintf("%.1fM", num2)
}
num2 := float32(num) / 1_000_000_000.0
return fmt.Sprintf("%.1fG", num2)
}

View File

@ -0,0 +1,18 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package templates
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCountFmt(t *testing.T) {
assert.Equal(t, "125", countFmt(125))
assert.Equal(t, "1.3k", countFmt(int64(1317)))
assert.Equal(t, "21.3M", countFmt(21317675))
assert.Equal(t, "45.7G", countFmt(45721317675))
assert.Equal(t, "", countFmt("test"))
}

View File

@ -4,11 +4,16 @@
package test package test
import ( import (
"fmt"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os"
"path/filepath"
"runtime"
"strings" "strings"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/util"
) )
// RedirectURL returns the redirect URL of a http response. // RedirectURL returns the redirect URL of a http response.
@ -41,3 +46,19 @@ func MockVariableValue[T any](p *T, v ...T) (reset func()) {
} }
return func() { *p = old } return func() { *p = old }
} }
// SetupGiteaRoot Sets GITEA_ROOT if it is not already set and returns the value
func SetupGiteaRoot() string {
giteaRoot := os.Getenv("GITEA_ROOT")
if giteaRoot != "" {
return giteaRoot
}
_, filename, _, _ := runtime.Caller(0)
giteaRoot = filepath.Dir(filepath.Dir(filepath.Dir(filename)))
fixturesDir := filepath.Join(giteaRoot, "models", "fixtures")
if exist, _ := util.IsDir(fixturesDir); !exist {
panic(fmt.Sprintf("fixtures directory not found: %s", fixturesDir))
}
_ = os.Setenv("GITEA_ROOT", giteaRoot)
return giteaRoot
}

View File

@ -0,0 +1,17 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package test
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSetupGiteaRoot(t *testing.T) {
t.Setenv("GITEA_ROOT", "test")
assert.Equal(t, "test", SetupGiteaRoot())
t.Setenv("GITEA_ROOT", "")
assert.NotEqual(t, "test", SetupGiteaRoot())
}

View File

@ -4,7 +4,6 @@
package web package web
import ( import (
goctx "context"
"fmt" "fmt"
"net/http" "net/http"
"reflect" "reflect"
@ -51,7 +50,6 @@ func (r *responseWriter) WriteHeader(statusCode int) {
var ( var (
httpReqType = reflect.TypeOf((*http.Request)(nil)) httpReqType = reflect.TypeOf((*http.Request)(nil))
respWriterType = reflect.TypeOf((*http.ResponseWriter)(nil)).Elem() 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 // 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 { if !hasStatusProvider {
panic(fmt.Sprintf("handler should have at least one ResponseStatusProvider argument, but got %s", fn.Type())) 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 { if fn.Type().NumOut() != 0 {
panic(fmt.Sprintf("handler should have no return value or only one argument, but got %s", fn.Type())) panic(fmt.Sprintf("handler should have no return value other than registered ones, 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()))
} }
} }
@ -105,16 +100,10 @@ func prepareHandleArgsIn(resp http.ResponseWriter, req *http.Request, fn reflect
return argsIn return argsIn
} }
func handleResponse(fn reflect.Value, ret []reflect.Value) goctx.CancelFunc { func handleResponse(fn reflect.Value, ret []reflect.Value) {
if len(ret) == 1 { if len(ret) != 0 {
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 {
panic(fmt.Sprintf("unsupported return values: %s", fn.Type())) panic(fmt.Sprintf("unsupported return values: %s", fn.Type()))
} }
return nil
} }
func hasResponseBeenWritten(argsIn []reflect.Value) bool { 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) routing.UpdateFuncInfo(req.Context(), funcInfo)
ret := fn.Call(argsIn) ret := fn.Call(argsIn)
// handle the return value, and defer the cancel function if there is one // handle the return value (no-op at the moment)
cancelFunc := handleResponse(fn, ret) handleResponse(fn, ret)
if cancelFunc != nil {
defer cancelFunc()
}
// if the response has not been written, call the next handler // if the response has not been written, call the next handler
if next != nil && !hasResponseBeenWritten(argsIn) { if next != nil && !hasResponseBeenWritten(argsIn) {

View File

@ -7,46 +7,21 @@ import (
"context" "context"
"time" "time"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting" "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" const ContextDataKeySignedUser = "SignedUser"
type contextDataKeyType struct{} func GetContextData(c context.Context) reqctx.ContextData {
if rc := reqctx.GetRequestDataStore(c); rc != nil {
var contextDataKey contextDataKeyType return rc.GetData()
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
} }
return nil return nil
} }
func CommonTemplateContextData() ContextData { func CommonTemplateContextData() reqctx.ContextData {
return ContextData{ return reqctx.ContextData{
"IsLandingPageOrganizations": setting.LandingPageURL == setting.LandingPageOrganizations, "IsLandingPageOrganizations": setting.LandingPageURL == setting.LandingPageOrganizations,
"ShowRegistrationButton": setting.Service.ShowRegistrationButton, "ShowRegistrationButton": setting.Service.ShowRegistrationButton,

View File

@ -7,11 +7,13 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"net/url" "net/url"
"code.gitea.io/gitea/modules/reqctx"
) )
// Flash represents a one time data transfer between two requests. // Flash represents a one time data transfer between two requests.
type Flash struct { type Flash struct {
DataStore ContextDataStore DataStore reqctx.RequestDataStore
url.Values url.Values
ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string
} }

View File

@ -10,6 +10,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/htmlutil" "code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/modules/web/middleware"
@ -29,12 +30,12 @@ func Bind[T any](_ T) http.HandlerFunc {
} }
// SetForm set the form object // SetForm set the form object
func SetForm(dataStore middleware.ContextDataStore, obj any) { func SetForm(dataStore reqctx.ContextDataProvider, obj any) {
dataStore.GetData()["__form"] = obj dataStore.GetData()["__form"] = obj
} }
// GetForm returns the validate form information // GetForm returns the validate form information
func GetForm(dataStore middleware.ContextDataStore) any { func GetForm(dataStore reqctx.RequestDataStore) any {
return dataStore.GetData()["__form"] return dataStore.GetData()["__form"]
} }

View File

@ -0,0 +1,31 @@
# gitignore template for B&R Automation Studio (AS) 4
# website: https://www.br-automation.com/en-us/products/software/automation-software/automation-studio/
# AS temporary directories
Binaries/
Diagnosis/
Temp/
TempObjects/
# AS transfer files
*artransfer.br
*arTrsfmode.nv
# 'ignored' directory
ignored/
# ARNC0ext
*arnc0ext.br
# AS File types
*.bak
*.isopen
*.orig
*.log
*.asar
*.csvlog*
*.set
!**/Physical/**/*.set
# RevInfo variables
*RevInfo.var

View File

@ -0,0 +1,28 @@
# Firebase build and deployment files
/firebase-debug.log
/firebase-debug.*.log
.firebaserc
# Firebase Hosting
/firebase.json
*.cache
hosting/.cache
# Firebase Functions
/functions/node_modules/
/functions/.env
/functions/package-lock.json
# Firebase Emulators
/firebase-*.zip
/.firebase/
/emulator-ui/
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Environment files (local configs)
/.env.*

View File

@ -0,0 +1,42 @@
# Modelica - an object-oriented language for modeling of cyber-physical systems
# https://modelica.org/
# Ignore temporary files, build results, simulation files
## Modelica-specific files
*~
*.bak
*.bak-mo
*.mof
\#*\#
*.moe
*.mol
## Build artefacts
*.exe
*.exp
*.o
*.pyc
## Simulation files
*.mat
## Package files
*.gz
*.rar
*.tar
*.zip
## Dymola-specific files
buildlog.txt
dsfinal.txt
dsin.txt
dslog.txt
dsmodel*
dsres.txt
dymosim*
request
stat
status
stop
success
*.

View File

@ -166,3 +166,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear # and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder. # option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/ #.idea/
# PyPI configuration file
.pypirc

View File

@ -26,6 +26,7 @@
## Bibliography auxiliary files (bibtex/biblatex/biber): ## Bibliography auxiliary files (bibtex/biblatex/biber):
*.bbl *.bbl
*.bbl-SAVE-ERROR
*.bcf *.bcf
*.blg *.blg
*-blx.aux *-blx.aux

View File

@ -2,18 +2,18 @@ Elastic License 2.0
URL: https://www.elastic.co/licensing/elastic-license URL: https://www.elastic.co/licensing/elastic-license
## Acceptance Acceptance
By using the software, you agree to all of the terms and conditions below. By using the software, you agree to all of the terms and conditions below.
## Copyright License Copyright License
The licensor grants you a non-exclusive, royalty-free, worldwide, The licensor grants you a non-exclusive, royalty-free, worldwide,
non-sublicensable, non-transferable license to use, copy, distribute, make non-sublicensable, non-transferable license to use, copy, distribute, make
available, and prepare derivative works of the software, in each case subject to available, and prepare derivative works of the software, in each case subject to
the limitations and conditions below. the limitations and conditions below.
## Limitations Limitations
You may not provide the software to third parties as a hosted or managed You may not provide the software to third parties as a hosted or managed
service, where the service provides users with access to any substantial set of service, where the service provides users with access to any substantial set of
@ -27,7 +27,7 @@ You may not alter, remove, or obscure any licensing, copyright, or other notices
of the licensor in the software. Any use of the licensors trademarks is subject of the licensor in the software. Any use of the licensors trademarks is subject
to applicable law. to applicable law.
## Patents Patents
The licensor grants you a license, under any patent claims the licensor can The licensor grants you a license, under any patent claims the licensor can
license, or becomes able to license, to make, have made, use, sell, offer for license, or becomes able to license, to make, have made, use, sell, offer for
@ -40,7 +40,7 @@ the software granted under these terms ends immediately. If your company makes
such a claim, your patent license ends immediately for work on behalf of your such a claim, your patent license ends immediately for work on behalf of your
company. company.
## Notices Notices
You must ensure that anyone who gets a copy of any part of the software from you You must ensure that anyone who gets a copy of any part of the software from you
also gets a copy of these terms. also gets a copy of these terms.
@ -53,7 +53,7 @@ software prominent notices stating that you have modified the software.
These terms do not imply any licenses other than those expressly granted in These terms do not imply any licenses other than those expressly granted in
these terms. these terms.
## Termination Termination
If you use the software in violation of these terms, such use is not licensed, If you use the software in violation of these terms, such use is not licensed,
and your licenses will automatically terminate. If the licensor provides you and your licenses will automatically terminate. If the licensor provides you
@ -63,31 +63,31 @@ reinstated retroactively. However, if you violate these terms after such
reinstatement, any additional violation of these terms will cause your licenses reinstatement, any additional violation of these terms will cause your licenses
to terminate automatically and permanently. to terminate automatically and permanently.
## No Liability No Liability
*As far as the law allows, the software comes as is, without any warranty or As far as the law allows, the software comes as is, without any warranty or
condition, and the licensor will not be liable to you for any damages arising condition, and the licensor will not be liable to you for any damages arising
out of these terms or the use or nature of the software, under any kind of out of these terms or the use or nature of the software, under any kind of
legal claim.* legal claim.
## Definitions Definitions
The **licensor** is the entity offering these terms, and the **software** is the The licensor is the entity offering these terms, and the software is the
software the licensor makes available under these terms, including any portion software the licensor makes available under these terms, including any portion
of it. of it.
**you** refers to the individual or entity agreeing to these terms. you refers to the individual or entity agreeing to these terms.
**your company** is any legal entity, sole proprietorship, or other kind of your company is any legal entity, sole proprietorship, or other kind of
organization that you work for, plus all organizations that have control over, organization that you work for, plus all organizations that have control over,
are under the control of, or are under common control with that are under the control of, or are under common control with that
organization. **control** means ownership of substantially all the assets of an organization. control means ownership of substantially all the assets of an
entity, or the power to direct its management and policies by vote, contract, or entity, or the power to direct its management and policies by vote, contract, or
otherwise. Control can be direct or indirect. otherwise. Control can be direct or indirect.
**your licenses** are all the licenses granted to you for the software under your licenses are all the licenses granted to you for the software under
these terms. these terms.
**use** means anything you do with the software requiring one of your licenses. use means anything you do with the software requiring one of your licenses.
**trademark** means trademarks, service marks, and similar rights. trademark means trademarks, service marks, and similar rights.

View File

@ -2,8 +2,17 @@ MIT License
Copyright (c) <year> <copyright holders> Copyright (c) <year> <copyright holders>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -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.failed=Úprava proměnné se nezdařila.
variables.update.success=Proměnná byla upravena. variables.update.success=Proměnná byla upravena.
[projects] [projects]
deleted.display_name=Odstraněný projekt deleted.display_name=Odstraněný projekt
type-1.display_name=Samostatný projekt type-1.display_name=Samostatný projekt

View File

@ -3556,6 +3556,7 @@ variables.creation.success=Die Variable „%s“ wurde hinzugefügt.
variables.update.failed=Fehler beim Bearbeiten der Variable. variables.update.failed=Fehler beim Bearbeiten der Variable.
variables.update.success=Die Variable wurde bearbeitet. variables.update.success=Die Variable wurde bearbeitet.
[projects] [projects]
type-1.display_name=Individuelles Projekt type-1.display_name=Individuelles Projekt
type-2.display_name=Repository-Projekt type-2.display_name=Repository-Projekt

View File

@ -3439,6 +3439,7 @@ variables.creation.success=Η μεταβλητή "%s" έχει προστεθε
variables.update.failed=Αποτυχία επεξεργασίας μεταβλητής. variables.update.failed=Αποτυχία επεξεργασίας μεταβλητής.
variables.update.success=Η μεταβλητή έχει τροποποιηθεί. variables.update.success=Η μεταβλητή έχει τροποποιηθεί.
[projects] [projects]
type-1.display_name=Ατομικό Έργο type-1.display_name=Ατομικό Έργο
type-2.display_name=Έργο Αποθετηρίου type-2.display_name=Έργο Αποθετηρίου

View File

@ -1946,8 +1946,8 @@ pulls.delete.title = Delete this pull request?
pulls.delete.text = Do you really want to delete this pull request? (This will permanently remove all content. Consider closing it instead, if you intend to keep it archived) pulls.delete.text = Do you really want to delete this pull request? (This will permanently remove all content. Consider closing it instead, if you intend to keep it archived)
pulls.recently_pushed_new_branches = You pushed on branch <strong>%[1]s</strong> %[2]s pulls.recently_pushed_new_branches = You pushed on branch <strong>%[1]s</strong> %[2]s
pulls.upstream_diverging_prompt_behind_1 = This branch is %d commit behind %s pulls.upstream_diverging_prompt_behind_1 = This branch is %[1]d commit behind %[2]s
pulls.upstream_diverging_prompt_behind_n = This branch is %d commits behind %s pulls.upstream_diverging_prompt_behind_n = This branch is %[1]d commits behind %[2]s
pulls.upstream_diverging_prompt_base_newer = The base branch %s has new changes pulls.upstream_diverging_prompt_base_newer = The base branch %s has new changes
pulls.upstream_diverging_merge = Sync fork pulls.upstream_diverging_merge = Sync fork
@ -3722,6 +3722,7 @@ runners.status.active = Active
runners.status.offline = Offline runners.status.offline = Offline
runners.version = Version runners.version = Version
runners.reset_registration_token = Reset registration token 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 runners.reset_registration_token_success = Runner registration token reset successfully
runs.all_workflows = All Workflows runs.all_workflows = All Workflows
@ -3773,6 +3774,9 @@ variables.creation.success = The variable "%s" has been added.
variables.update.failed = Failed to edit variable. variables.update.failed = Failed to edit variable.
variables.update.success = The variable has been edited. variables.update.success = The variable has been edited.
logs.always_auto_scroll = Always auto scroll logs
logs.always_expand_running = Always expand running logs
[projects] [projects]
deleted.display_name = Deleted Project deleted.display_name = Deleted Project
type-1.display_name = Individual Project type-1.display_name = Individual Project

View File

@ -3415,6 +3415,7 @@ variables.creation.success=La variable "%s" ha sido añadida.
variables.update.failed=Error al editar la variable. variables.update.failed=Error al editar la variable.
variables.update.success=La variable ha sido editada. variables.update.success=La variable ha sido editada.
[projects] [projects]
type-1.display_name=Proyecto individual type-1.display_name=Proyecto individual
type-2.display_name=Proyecto repositorio type-2.display_name=Proyecto repositorio

View File

@ -2529,6 +2529,7 @@ runs.commit=کامیت
[projects] [projects]
[git.filemode] [git.filemode]

View File

@ -1707,6 +1707,7 @@ runs.commit=Commit
[projects] [projects]
[git.filemode] [git.filemode]

View File

@ -3772,6 +3772,7 @@ variables.creation.success=La variable « %s » a été ajoutée.
variables.update.failed=Impossible déditer la variable. variables.update.failed=Impossible déditer la variable.
variables.update.success=La variable a bien été modifiée. variables.update.success=La variable a bien été modifiée.
[projects] [projects]
deleted.display_name=Projet supprimé deleted.display_name=Projet supprimé
type-1.display_name=Projet personnel type-1.display_name=Projet personnel

View File

@ -1945,8 +1945,6 @@ pulls.delete.title=Scrios an t-iarratas tarraingthe seo?
pulls.delete.text=An bhfuil tú cinnte gur mhaith leat an t-iarratas tarraingthe seo a scriosadh? (Bainfidh sé seo an t-inneachar go léir go buan. Smaoinigh ar é a dhúnadh ina ionad sin, má tá sé i gceist agat é a choinneáil i gcartlann) pulls.delete.text=An bhfuil tú cinnte gur mhaith leat an t-iarratas tarraingthe seo a scriosadh? (Bainfidh sé seo an t-inneachar go léir go buan. Smaoinigh ar é a dhúnadh ina ionad sin, má tá sé i gceist agat é a choinneáil i gcartlann)
pulls.recently_pushed_new_branches=Bhrúigh tú ar bhrainse <strong>%[1]s</strong> %[2]s pulls.recently_pushed_new_branches=Bhrúigh tú ar bhrainse <strong>%[1]s</strong> %[2]s
pulls.upstream_diverging_prompt_behind_1=Tá an brainse seo %d tiomantas taobh thiar de %s
pulls.upstream_diverging_prompt_behind_n=Tá an brainse seo %d geallta taobh thiar de %s
pulls.upstream_diverging_prompt_base_newer=Tá athruithe nua ar an mbunbhrainse %s pulls.upstream_diverging_prompt_base_newer=Tá athruithe nua ar an mbunbhrainse %s
pulls.upstream_diverging_merge=Forc sionc pulls.upstream_diverging_merge=Forc sionc
@ -3772,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.failed=Theip ar athróg a chur in eagar.
variables.update.success=Tá an t-athróg curtha in eagar. variables.update.success=Tá an t-athróg curtha in eagar.
[projects] [projects]
deleted.display_name=Tionscadal scriosta deleted.display_name=Tionscadal scriosta
type-1.display_name=Tionscadal Aonair type-1.display_name=Tionscadal Aonair

View File

@ -1615,6 +1615,7 @@ runs.commit=Commit
[projects] [projects]
[git.filemode] [git.filemode]

View File

@ -1444,6 +1444,7 @@ variables.creation.success=Variabel "%s" telah ditambahkan.
variables.update.failed=Gagal mengedit variabel. variables.update.failed=Gagal mengedit variabel.
variables.update.success=Variabel telah diedit. variables.update.success=Variabel telah diedit.
[projects] [projects]
type-1.display_name=Proyek Individu type-1.display_name=Proyek Individu
type-2.display_name=Proyek Repositori type-2.display_name=Proyek Repositori

View File

@ -1342,6 +1342,7 @@ runs.commit=Framlag
[projects] [projects]
[git.filemode] [git.filemode]

View File

@ -2807,6 +2807,7 @@ runs.commit=Commit
[projects] [projects]
[git.filemode] [git.filemode]

View File

@ -1940,8 +1940,6 @@ pulls.delete.title=このプルリクエストを削除しますか?
pulls.delete.text=本当にこのプルリクエストを削除しますか? (これはすべてのコンテンツを完全に削除します。 保存しておきたい場合は、代わりにクローズすることを検討してください) pulls.delete.text=本当にこのプルリクエストを削除しますか? (これはすべてのコンテンツを完全に削除します。 保存しておきたい場合は、代わりにクローズすることを検討してください)
pulls.recently_pushed_new_branches=%[2]s 、あなたはブランチ <strong>%[1]s</strong> にプッシュしました 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_prompt_base_newer=ベースブランチ %s に新しい変更があります
pulls.upstream_diverging_merge=フォークを同期 pulls.upstream_diverging_merge=フォークを同期
@ -3764,6 +3762,7 @@ variables.creation.success=変数 "%s" を追加しました。
variables.update.failed=変数を更新できませんでした。 variables.update.failed=変数を更新できませんでした。
variables.update.success=変数を更新しました。 variables.update.success=変数を更新しました。
[projects] [projects]
deleted.display_name=削除されたプロジェクト deleted.display_name=削除されたプロジェクト
type-1.display_name=個人プロジェクト type-1.display_name=個人プロジェクト

View File

@ -1563,6 +1563,7 @@ runs.commit=커밋
[projects] [projects]
[git.filemode] [git.filemode]

View File

@ -3443,6 +3443,7 @@ variables.creation.success=Mainīgais "%s" tika pievienots.
variables.update.failed=Neizdevās labot mainīgo. variables.update.failed=Neizdevās labot mainīgo.
variables.update.success=Mainīgais tika labots. variables.update.success=Mainīgais tika labots.
[projects] [projects]
type-1.display_name=Individuālais projekts type-1.display_name=Individuālais projekts
type-2.display_name=Repozitorija projekts type-2.display_name=Repozitorija projekts

View File

@ -2537,6 +2537,7 @@ runs.commit=Commit
[projects] [projects]
[git.filemode] [git.filemode]

View File

@ -2430,6 +2430,7 @@ runs.commit=Commit
[projects] [projects]
[git.filemode] [git.filemode]

View File

@ -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. need_approval_desc=Precisa de aprovação para executar workflows para pull request do fork.
[projects] [projects]
type-1.display_name=Projeto individual type-1.display_name=Projeto individual
type-2.display_name=Projeto do repositório type-2.display_name=Projeto do repositório

View File

@ -1945,8 +1945,6 @@ pulls.delete.title=Eliminar este pedido de integração?
pulls.delete.text=Tem a certeza que quer eliminar este pedido de integração? Isso irá remover todo o conteúdo permanentemente. Como alternativa considere fechá-lo, se pretender mantê-lo em arquivo. pulls.delete.text=Tem a certeza que quer eliminar este pedido de integração? Isso irá remover todo o conteúdo permanentemente. Como alternativa considere fechá-lo, se pretender mantê-lo em arquivo.
pulls.recently_pushed_new_branches=Enviou para o ramo <strong>%[1]s</strong> %[2]s pulls.recently_pushed_new_branches=Enviou para o ramo <strong>%[1]s</strong> %[2]s
pulls.upstream_diverging_prompt_behind_1=Este ramo está %d cometimento atrás de %s
pulls.upstream_diverging_prompt_behind_n=Este ramo está %d cometimentos atrás de %s
pulls.upstream_diverging_prompt_base_newer=O ramo base %s tem novas modificações pulls.upstream_diverging_prompt_base_newer=O ramo base %s tem novas modificações
pulls.upstream_diverging_merge=Sincronizar derivação pulls.upstream_diverging_merge=Sincronizar derivação
@ -3772,6 +3770,9 @@ variables.creation.success=A variável "%s" foi adicionada.
variables.update.failed=Falha ao editar a variável. variables.update.failed=Falha ao editar a variável.
variables.update.success=A variável foi editada. 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] [projects]
deleted.display_name=Planeamento eliminado deleted.display_name=Planeamento eliminado
type-1.display_name=Planeamento individual type-1.display_name=Planeamento individual

View File

@ -3373,6 +3373,7 @@ variables.creation.success=Переменная «%s» добавлена.
variables.update.failed=Не удалось изменить переменную. variables.update.failed=Не удалось изменить переменную.
variables.update.success=Переменная изменена. variables.update.success=Переменная изменена.
[projects] [projects]
type-1.display_name=Индивидуальный проект type-1.display_name=Индивидуальный проект
type-2.display_name=Проект репозитория type-2.display_name=Проект репозитория

View File

@ -2470,6 +2470,7 @@ runs.commit=කැප
[projects] [projects]
[git.filemode] [git.filemode]

View File

@ -1328,6 +1328,7 @@ runners.labels=Štítky
[projects] [projects]
[git.filemode] [git.filemode]

View File

@ -2005,6 +2005,7 @@ runs.commit=Commit
[projects] [projects]
[git.filemode] [git.filemode]

View File

@ -3633,6 +3633,7 @@ variables.creation.success=`"%s" değişkeni eklendi.`
variables.update.failed=Değişken düzenlenemedi. variables.update.failed=Değişken düzenlenemedi.
variables.update.success=Değişken düzenlendi. variables.update.success=Değişken düzenlendi.
[projects] [projects]
deleted.display_name=Silinmiş Proje deleted.display_name=Silinmiş Proje
type-1.display_name=Kişisel Proje type-1.display_name=Kişisel Proje

View File

@ -2538,6 +2538,7 @@ runs.commit=Коміт
[projects] [projects]
[git.filemode] [git.filemode]

View File

@ -1945,8 +1945,8 @@ pulls.delete.title=删除此合并请求?
pulls.delete.text=你真的要删除这个合并请求吗? (这将永久删除所有内容。如果你打算将内容存档,请考虑关闭它) pulls.delete.text=你真的要删除这个合并请求吗? (这将永久删除所有内容。如果你打算将内容存档,请考虑关闭它)
pulls.recently_pushed_new_branches=您已经于%[2]s推送了分支 <strong>%[1]s</strong> pulls.recently_pushed_new_branches=您已经于%[2]s推送了分支 <strong>%[1]s</strong>
pulls.upstream_diverging_prompt_behind_1=该分支落后于 %s %d 个提交 pulls.upstream_diverging_prompt_behind_1=该分支落后于 %[2]s %[1]d 个提交
pulls.upstream_diverging_prompt_behind_n=该分支落后于 %s %d 个提交 pulls.upstream_diverging_prompt_behind_n=该分支落后于 %[2]s %[1]d 个提交
pulls.upstream_diverging_prompt_base_newer=基础分支 %s 有新的更改 pulls.upstream_diverging_prompt_base_newer=基础分支 %s 有新的更改
pulls.upstream_diverging_merge=同步派生 pulls.upstream_diverging_merge=同步派生
@ -3772,6 +3772,7 @@ variables.creation.success=变量 “%s” 添加成功。
variables.update.failed=编辑变量失败。 variables.update.failed=编辑变量失败。
variables.update.success=该变量已被编辑。 variables.update.success=该变量已被编辑。
[projects] [projects]
deleted.display_name=已删除项目 deleted.display_name=已删除项目
type-1.display_name=个人项目 type-1.display_name=个人项目

View File

@ -975,6 +975,7 @@ runners.task_list.repository=儲存庫
[projects] [projects]
[git.filemode] [git.filemode]

View File

@ -1938,8 +1938,6 @@ pulls.delete.title=刪除此合併請求?
pulls.delete.text=您真的要刪除此合併請求嗎?(這將會永久移除所有內容。若您還想保留,請考慮改為關閉它。) pulls.delete.text=您真的要刪除此合併請求嗎?(這將會永久移除所有內容。若您還想保留,請考慮改為關閉它。)
pulls.recently_pushed_new_branches=您在分支 <strong>%[1]s</strong> 上推送了 %[2]s pulls.recently_pushed_new_branches=您在分支 <strong>%[1]s</strong> 上推送了 %[2]s
pulls.upstream_diverging_prompt_behind_1=此分支落後 %s %d 次提交
pulls.upstream_diverging_prompt_behind_n=此分支落後 %s %d 次提交
pulls.upstream_diverging_prompt_base_newer=基底分支 %s 有新變更 pulls.upstream_diverging_prompt_base_newer=基底分支 %s 有新變更
pulls.upstream_diverging_merge=同步 fork pulls.upstream_diverging_merge=同步 fork
@ -3765,6 +3763,7 @@ variables.creation.success=已新增變數「%s」。
variables.update.failed=編輯變數失敗。 variables.update.failed=編輯變數失敗。
variables.update.success=已編輯變數。 variables.update.success=已編輯變數。
[projects] [projects]
deleted.display_name=已刪除的專案 deleted.display_name=已刪除的專案
type-1.display_name=個人專案 type-1.display_name=個人專案

View File

@ -126,11 +126,10 @@ func ArtifactsRoutes(prefix string) *web.Router {
func ArtifactContexter() func(next http.Handler) http.Handler { func ArtifactContexter() func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
base, baseCleanUp := context.NewBaseContext(resp, req) base := context.NewBaseContext(resp, req)
defer baseCleanUp()
ctx := &ArtifactContext{Base: base} ctx := &ArtifactContext{Base: base}
ctx.AppendContextValue(artifactContextKey, ctx) ctx.SetContextValue(artifactContextKey, ctx)
// action task call server api with Bearer ACTIONS_RUNTIME_TOKEN // action task call server api with Bearer ACTIONS_RUNTIME_TOKEN
// we should verify the ACTIONS_RUNTIME_TOKEN // we should verify the ACTIONS_RUNTIME_TOKEN

View File

@ -126,12 +126,9 @@ type artifactV4Routes struct {
func ArtifactV4Contexter() func(next http.Handler) http.Handler { func ArtifactV4Contexter() func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
base, baseCleanUp := context.NewBaseContext(resp, req) base := context.NewBaseContext(resp, req)
defer baseCleanUp()
ctx := &ArtifactContext{Base: base} ctx := &ArtifactContext{Base: base}
ctx.AppendContextValue(artifactContextKey, ctx) ctx.SetContextValue(artifactContextKey, ctx)
next.ServeHTTP(ctx.Resp, ctx.Req) next.ServeHTTP(ctx.Resp, ctx.Req)
}) })
} }

View File

@ -80,8 +80,8 @@ func AdoptRepository(ctx *context.APIContext) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
ownerName := ctx.PathParam(":username") ownerName := ctx.PathParam("username")
repoName := ctx.PathParam(":reponame") repoName := ctx.PathParam("reponame")
ctxUser, err := user_model.GetUserByName(ctx, ownerName) ctxUser, err := user_model.GetUserByName(ctx, ownerName)
if err != nil { if err != nil {
@ -142,8 +142,8 @@ func DeleteUnadoptedRepository(ctx *context.APIContext) {
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
ownerName := ctx.PathParam(":username") ownerName := ctx.PathParam("username")
repoName := ctx.PathParam(":reponame") repoName := ctx.PathParam("reponame")
ctxUser, err := user_model.GetUserByName(ctx, ownerName) ctxUser, err := user_model.GetUserByName(ctx, ownerName)
if err != nil { if err != nil {

View File

@ -74,7 +74,7 @@ func PostCronTask(ctx *context.APIContext) {
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
task := cron.GetTask(ctx.PathParam(":task")) task := cron.GetTask(ctx.PathParam("task"))
if task == nil { if task == nil {
ctx.NotFound() ctx.NotFound()
return return

View File

@ -38,7 +38,7 @@ func GetAllEmails(ctx *context.APIContext) {
listOptions := utils.GetListOptions(ctx) listOptions := utils.GetListOptions(ctx)
emails, maxResults, err := user_model.SearchEmails(ctx, &user_model.SearchEmailOptions{ emails, maxResults, err := user_model.SearchEmails(ctx, &user_model.SearchEmailOptions{
Keyword: ctx.PathParam(":email"), Keyword: ctx.PathParam("email"),
ListOptions: listOptions, ListOptions: listOptions,
}) })
if err != nil { if err != nil {
@ -82,6 +82,6 @@ func SearchEmail(ctx *context.APIContext) {
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
ctx.SetPathParam(":email", ctx.FormTrim("q")) ctx.SetPathParam("email", ctx.FormTrim("q"))
GetAllEmails(ctx) GetAllEmails(ctx)
} }

View File

@ -73,7 +73,7 @@ func GetHook(ctx *context.APIContext) {
// "200": // "200":
// "$ref": "#/responses/Hook" // "$ref": "#/responses/Hook"
hookID := ctx.PathParamInt64(":id") hookID := ctx.PathParamInt64("id")
hook, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID) hook, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID)
if err != nil { if err != nil {
if errors.Is(err, util.ErrNotExist) { if errors.Is(err, util.ErrNotExist) {
@ -142,7 +142,7 @@ func EditHook(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.EditHookOption) form := web.GetForm(ctx).(*api.EditHookOption)
// TODO in body params // TODO in body params
hookID := ctx.PathParamInt64(":id") hookID := ctx.PathParamInt64("id")
utils.EditSystemHook(ctx, form, hookID) utils.EditSystemHook(ctx, form, hookID)
} }
@ -164,7 +164,7 @@ func DeleteHook(ctx *context.APIContext) {
// "204": // "204":
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
hookID := ctx.PathParamInt64(":id") hookID := ctx.PathParamInt64("id")
if err := webhook.DeleteDefaultSystemWebhook(ctx, hookID); err != nil { if err := webhook.DeleteDefaultSystemWebhook(ctx, hookID); err != nil {
if errors.Is(err, util.ErrNotExist) { if errors.Is(err, util.ErrNotExist) {
ctx.NotFound() ctx.NotFound()

View File

@ -375,7 +375,7 @@ func DeleteUserPublicKey(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
if err := asymkey_service.DeletePublicKey(ctx, ctx.ContextUser, ctx.PathParamInt64(":id")); err != nil { if err := asymkey_service.DeletePublicKey(ctx, ctx.ContextUser, ctx.PathParamInt64("id")); err != nil {
if asymkey_model.IsErrKeyNotExist(err) { if asymkey_model.IsErrKeyNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else if asymkey_model.IsErrKeyAccessDenied(err) { } else if asymkey_model.IsErrKeyAccessDenied(err) {

View File

@ -596,12 +596,12 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
var err error var err error
if assignOrg { if assignOrg {
ctx.Org.Organization, err = organization.GetOrgByName(ctx, ctx.PathParam(":org")) ctx.Org.Organization, err = organization.GetOrgByName(ctx, ctx.PathParam("org"))
if err != nil { if err != nil {
if organization.IsErrOrgNotExist(err) { if organization.IsErrOrgNotExist(err) {
redirectUserID, err := user_model.LookupUserRedirect(ctx, ctx.PathParam(":org")) redirectUserID, err := user_model.LookupUserRedirect(ctx, ctx.PathParam("org"))
if err == nil { if err == nil {
context.RedirectToUser(ctx.Base, ctx.PathParam(":org"), redirectUserID) context.RedirectToUser(ctx.Base, ctx.PathParam("org"), redirectUserID)
} else if user_model.IsErrUserRedirectNotExist(err) { } else if user_model.IsErrUserRedirectNotExist(err) {
ctx.NotFound("GetOrgByName", err) ctx.NotFound("GetOrgByName", err)
} else { } else {
@ -616,7 +616,7 @@ func orgAssignment(args ...bool) func(ctx *context.APIContext) {
} }
if assignTeam { if assignTeam {
ctx.Org.Team, err = organization.GetTeamByID(ctx, ctx.PathParamInt64(":teamid")) ctx.Org.Team, err = organization.GetTeamByID(ctx, ctx.PathParamInt64("teamid"))
if err != nil { if err != nil {
if organization.IsErrTeamNotExist(err) { if organization.IsErrTeamNotExist(err) {
ctx.NotFound() ctx.NotFound()

View File

@ -101,7 +101,7 @@ func ReadThread(ctx *context.APIContext) {
} }
func getThread(ctx *context.APIContext) *activities_model.Notification { func getThread(ctx *context.APIContext) *activities_model.Notification {
n, err := activities_model.GetNotificationByID(ctx, ctx.PathParamInt64(":id")) n, err := activities_model.GetNotificationByID(ctx, ctx.PathParamInt64("id"))
if err != nil { if err != nil {
if db.IsErrNotExist(err) { if db.IsErrNotExist(err) {
ctx.Error(http.StatusNotFound, "GetNotificationByID", err) ctx.Error(http.StatusNotFound, "GetNotificationByID", err)

View File

@ -139,7 +139,7 @@ func GetLabel(ctx *context.APIContext) {
label *issues_model.Label label *issues_model.Label
err error err error
) )
strID := ctx.PathParam(":id") strID := ctx.PathParam("id")
if intID, err2 := strconv.ParseInt(strID, 10, 64); err2 != nil { if intID, err2 := strconv.ParseInt(strID, 10, 64); err2 != nil {
label, err = issues_model.GetLabelInOrgByName(ctx, ctx.Org.Organization.ID, strID) label, err = issues_model.GetLabelInOrgByName(ctx, ctx.Org.Organization.ID, strID)
} else { } else {
@ -190,7 +190,7 @@ func EditLabel(ctx *context.APIContext) {
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
form := web.GetForm(ctx).(*api.EditLabelOption) form := web.GetForm(ctx).(*api.EditLabelOption)
l, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.PathParamInt64(":id")) l, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.PathParamInt64("id"))
if err != nil { if err != nil {
if issues_model.IsErrOrgLabelNotExist(err) { if issues_model.IsErrOrgLabelNotExist(err) {
ctx.NotFound() ctx.NotFound()
@ -249,7 +249,7 @@ func DeleteLabel(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
if err := issues_model.DeleteLabel(ctx, ctx.Org.Organization.ID, ctx.PathParamInt64(":id")); err != nil { if err := issues_model.DeleteLabel(ctx, ctx.Org.Organization.ID, ctx.PathParamInt64("id")); err != nil {
ctx.Error(http.StatusInternalServerError, "DeleteLabel", err) ctx.Error(http.StatusInternalServerError, "DeleteLabel", err)
return return
} }

View File

@ -143,7 +143,7 @@ func IsMember(ctx *context.APIContext) {
// "404": // "404":
// description: user is not a member // description: user is not a member
userToCheck := user.GetUserByParams(ctx) userToCheck := user.GetContextUserByPathParam(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
@ -194,7 +194,7 @@ func IsPublicMember(ctx *context.APIContext) {
// "404": // "404":
// description: user is not a public member // description: user is not a public member
userToCheck := user.GetUserByParams(ctx) userToCheck := user.GetContextUserByPathParam(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
@ -236,7 +236,7 @@ func PublicizeMember(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
userToPublicize := user.GetUserByParams(ctx) userToPublicize := user.GetContextUserByPathParam(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
@ -278,7 +278,7 @@ func ConcealMember(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
userToConceal := user.GetUserByParams(ctx) userToConceal := user.GetContextUserByPathParam(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
@ -318,7 +318,7 @@ func DeleteMember(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
member := user.GetUserByParams(ctx) member := user.GetContextUserByPathParam(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }

View File

@ -131,7 +131,7 @@ func GetUserOrgsPermissions(ctx *context.APIContext) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
var o *user_model.User var o *user_model.User
if o = user.GetUserByParamsName(ctx, ":org"); o == nil { if o = user.GetUserByPathParam(ctx, "org"); o == nil {
return return
} }

View File

@ -449,7 +449,7 @@ func GetTeamMember(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
u := user.GetUserByParams(ctx) u := user.GetContextUserByPathParam(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
@ -492,7 +492,7 @@ func AddTeamMember(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
u := user.GetUserByParams(ctx) u := user.GetContextUserByPathParam(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
@ -532,7 +532,7 @@ func RemoveTeamMember(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
u := user.GetUserByParams(ctx) u := user.GetContextUserByPathParam(ctx)
if ctx.Written() { if ctx.Written() {
return return
} }
@ -645,7 +645,7 @@ func GetTeamRepo(ctx *context.APIContext) {
// getRepositoryByParams get repository by a team's organization ID and repo name // getRepositoryByParams get repository by a team's organization ID and repo name
func getRepositoryByParams(ctx *context.APIContext) *repo_model.Repository { func getRepositoryByParams(ctx *context.APIContext) *repo_model.Repository {
repo, err := repo_model.GetRepositoryByName(ctx, ctx.Org.Team.OrgID, ctx.PathParam(":reponame")) repo, err := repo_model.GetRepositoryByName(ctx, ctx.Org.Team.OrgID, ctx.PathParam("reponame"))
if err != nil { if err != nil {
if repo_model.IsErrRepoNotExist(err) { if repo_model.IsErrRepoNotExist(err) {
ctx.NotFound() ctx.NotFound()

View File

@ -487,7 +487,7 @@ func GetBranchProtection(ctx *context.APIContext) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
bpName := ctx.PathParam(":name") bpName := ctx.PathParam("name")
bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName) bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err) ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
@ -729,15 +729,11 @@ func CreateBranchProtection(ctx *context.APIContext) {
} else { } else {
if !isPlainRule { if !isPlainRule {
if ctx.Repo.GitRepo == nil { 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 { if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err) ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return 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 // 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) matchedBranches, err := git_model.FindAllMatchedBranches(ctx, ctx.Repo.Repository.ID, ruleName)
@ -809,7 +805,7 @@ func EditBranchProtection(ctx *context.APIContext) {
// "$ref": "#/responses/repoArchivedError" // "$ref": "#/responses/repoArchivedError"
form := web.GetForm(ctx).(*api.EditBranchProtectionOption) form := web.GetForm(ctx).(*api.EditBranchProtectionOption)
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
bpName := ctx.PathParam(":name") bpName := ctx.PathParam("name")
protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName) protectBranch, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err) ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)
@ -1061,15 +1057,11 @@ func EditBranchProtection(ctx *context.APIContext) {
} else { } else {
if !isPlainRule { if !isPlainRule {
if ctx.Repo.GitRepo == nil { 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 { if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err) ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return 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 // FIXME: since we only need to recheck files protected rules, we could improve this
@ -1132,7 +1124,7 @@ func DeleteBranchProtection(ctx *context.APIContext) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
repo := ctx.Repo.Repository repo := ctx.Repo.Repository
bpName := ctx.PathParam(":name") bpName := ctx.PathParam("name")
bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName) bp, err := git_model.GetProtectedBranchRuleByName(ctx, repo.ID, bpName)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err) ctx.Error(http.StatusInternalServerError, "GetProtectedBranchByID", err)

View File

@ -103,7 +103,7 @@ func IsCollaborator(ctx *context.APIContext) {
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
user, err := user_model.GetUserByName(ctx, ctx.PathParam(":collaborator")) user, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
if err != nil { if err != nil {
if user_model.IsErrUserNotExist(err) { if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err) ctx.Error(http.StatusUnprocessableEntity, "", err)
@ -163,7 +163,7 @@ func AddOrUpdateCollaborator(ctx *context.APIContext) {
form := web.GetForm(ctx).(*api.AddCollaboratorOption) form := web.GetForm(ctx).(*api.AddCollaboratorOption)
collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam(":collaborator")) collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
if err != nil { if err != nil {
if user_model.IsErrUserNotExist(err) { if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err) ctx.Error(http.StatusUnprocessableEntity, "", err)
@ -226,7 +226,7 @@ func DeleteCollaborator(ctx *context.APIContext) {
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam(":collaborator")) collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
if err != nil { if err != nil {
if user_model.IsErrUserNotExist(err) { if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err) ctx.Error(http.StatusUnprocessableEntity, "", err)
@ -274,12 +274,12 @@ func GetRepoPermissions(ctx *context.APIContext) {
// "403": // "403":
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
if !ctx.Doer.IsAdmin && ctx.Doer.LoginName != ctx.PathParam(":collaborator") && !ctx.IsUserRepoAdmin() { if !ctx.Doer.IsAdmin && ctx.Doer.LoginName != ctx.PathParam("collaborator") && !ctx.IsUserRepoAdmin() {
ctx.Error(http.StatusForbidden, "User", "Only admins can query all permissions, repo admins can query all repo permissions, collaborators can query only their own") ctx.Error(http.StatusForbidden, "User", "Only admins can query all permissions, repo admins can query all repo permissions, collaborators can query only their own")
return return
} }
collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam(":collaborator")) collaborator, err := user_model.GetUserByName(ctx, ctx.PathParam("collaborator"))
if err != nil { if err != nil {
if user_model.IsErrUserNotExist(err) { if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusNotFound, "GetUserByName", err) ctx.Error(http.StatusNotFound, "GetUserByName", err)

View File

@ -63,7 +63,7 @@ func GetSingleCommit(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
sha := ctx.PathParam(":sha") sha := ctx.PathParam("sha")
if !git.IsValidRefPattern(sha) { if !git.IsValidRefPattern(sha) {
ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha)) ctx.Error(http.StatusUnprocessableEntity, "no valid ref or sha", fmt.Sprintf("no valid ref or sha: %s", sha))
return return
@ -312,8 +312,8 @@ func DownloadCommitDiffOrPatch(ctx *context.APIContext) {
// "$ref": "#/responses/string" // "$ref": "#/responses/string"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
sha := ctx.PathParam(":sha") sha := ctx.PathParam("sha")
diffType := git.RawDiffType(ctx.PathParam(":diffType")) diffType := git.RawDiffType(ctx.PathParam("diffType"))
if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, diffType, ctx.Resp); err != nil { if err := git.GetRawDiff(ctx.Repo.GitRepo, sha, diffType, ctx.Resp); err != nil {
if git.IsErrNotExist(err) { if git.IsErrNotExist(err) {

View File

@ -44,13 +44,12 @@ func CompareDiff(ctx *context.APIContext) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
if ctx.Repo.GitRepo == nil { 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 { if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err) ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return return
} }
ctx.Repo.GitRepo = gitRepo
defer gitRepo.Close()
} }
infoPath := ctx.PathParam("*") infoPath := ctx.PathParam("*")

View File

@ -28,13 +28,12 @@ func DownloadArchive(ctx *context.APIContext) {
} }
if ctx.Repo.GitRepo == nil { 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 { if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err) ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return return
} }
ctx.Repo.GitRepo = gitRepo
defer gitRepo.Close()
} }
r, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*"), tp) r, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*"), tp)

View File

@ -287,13 +287,12 @@ func GetArchive(ctx *context.APIContext) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
if ctx.Repo.GitRepo == nil { 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 { if err != nil {
ctx.Error(http.StatusInternalServerError, "OpenRepository", err) ctx.Error(http.StatusInternalServerError, "OpenRepository", err)
return return
} }
ctx.Repo.GitRepo = gitRepo
defer gitRepo.Close()
} }
archiveDownload(ctx) archiveDownload(ctx)

View File

@ -79,7 +79,7 @@ func GetGitHook(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
hookID := ctx.PathParam(":id") hookID := ctx.PathParam("id")
hook, err := ctx.Repo.GitRepo.GetHook(hookID) hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil { if err != nil {
if err == git.ErrNotValidHook { if err == git.ErrNotValidHook {
@ -126,7 +126,7 @@ func EditGitHook(ctx *context.APIContext) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.EditGitHookOption) form := web.GetForm(ctx).(*api.EditGitHookOption)
hookID := ctx.PathParam(":id") hookID := ctx.PathParam("id")
hook, err := ctx.Repo.GitRepo.GetHook(hookID) hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil { if err != nil {
if err == git.ErrNotValidHook { if err == git.ErrNotValidHook {
@ -175,7 +175,7 @@ func DeleteGitHook(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
hookID := ctx.PathParam(":id") hookID := ctx.PathParam("id")
hook, err := ctx.Repo.GitRepo.GetHook(hookID) hook, err := ctx.Repo.GitRepo.GetHook(hookID)
if err != nil { if err != nil {
if err == git.ErrNotValidHook { if err == git.ErrNotValidHook {

View File

@ -109,7 +109,7 @@ func GetHook(ctx *context.APIContext) {
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
repo := ctx.Repo repo := ctx.Repo
hookID := ctx.PathParamInt64(":id") hookID := ctx.PathParamInt64("id")
hook, err := utils.GetRepoHook(ctx, repo.Repository.ID, hookID) hook, err := utils.GetRepoHook(ctx, repo.Repository.ID, hookID)
if err != nil { if err != nil {
return return
@ -168,7 +168,7 @@ func TestHook(ctx *context.APIContext) {
ref = r ref = r
} }
hookID := ctx.PathParamInt64(":id") hookID := ctx.PathParamInt64("id")
hook, err := utils.GetRepoHook(ctx, ctx.Repo.Repository.ID, hookID) hook, err := utils.GetRepoHook(ctx, ctx.Repo.Repository.ID, hookID)
if err != nil { if err != nil {
return return
@ -263,7 +263,7 @@ func EditHook(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.EditHookOption) form := web.GetForm(ctx).(*api.EditHookOption)
hookID := ctx.PathParamInt64(":id") hookID := ctx.PathParamInt64("id")
utils.EditRepoHook(ctx, form, hookID) utils.EditRepoHook(ctx, form, hookID)
} }
@ -296,7 +296,7 @@ func DeleteHook(ctx *context.APIContext) {
// "$ref": "#/responses/empty" // "$ref": "#/responses/empty"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":id")); err != nil { if err := webhook.DeleteWebhookByRepoID(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("id")); err != nil {
if webhook.IsErrWebhookNotExist(err) { if webhook.IsErrWebhookNotExist(err) {
ctx.NotFound() ctx.NotFound()
} else { } else {

View File

@ -18,7 +18,7 @@ func TestTestHook(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/wiki/_pages") ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/wiki/_pages")
ctx.SetPathParam(":id", "1") ctx.SetPathParam("id", "1")
contexttest.LoadRepo(t, ctx, 1) contexttest.LoadRepo(t, ctx, 1)
contexttest.LoadRepoCommit(t, ctx) contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2) contexttest.LoadUser(t, ctx, 2)

View File

@ -613,7 +613,7 @@ func GetIssue(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
@ -793,7 +793,7 @@ func EditIssue(ctx *context.APIContext) {
// "$ref": "#/responses/error" // "$ref": "#/responses/error"
form := web.GetForm(ctx).(*api.EditIssueOption) form := web.GetForm(ctx).(*api.EditIssueOption)
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
@ -976,7 +976,7 @@ func DeleteIssue(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
@ -1032,7 +1032,7 @@ func UpdateIssueDeadline(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
form := web.GetForm(ctx).(*api.EditDeadlineOption) form := web.GetForm(ctx).(*api.EditDeadlineOption)
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()

View File

@ -68,7 +68,7 @@ func ListIssueComments(ctx *context.APIContext) {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return return
} }
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err) ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
return return
@ -172,7 +172,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) {
ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err)
return return
} }
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err) ctx.Error(http.StatusInternalServerError, "GetRawIssueByIndex", err)
return return
@ -380,7 +380,7 @@ func CreateIssueComment(ctx *context.APIContext) {
// "$ref": "#/responses/repoArchivedError" // "$ref": "#/responses/repoArchivedError"
form := web.GetForm(ctx).(*api.CreateIssueCommentOption) form := web.GetForm(ctx).(*api.CreateIssueCommentOption)
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err) ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err)
return return
@ -445,7 +445,7 @@ func GetIssueComment(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id")) comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil { if err != nil {
if issues_model.IsErrCommentNotExist(err) { if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
@ -579,7 +579,7 @@ func EditIssueCommentDeprecated(ctx *context.APIContext) {
} }
func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) { func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption) {
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id")) comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil { if err != nil {
if issues_model.IsErrCommentNotExist(err) { if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
@ -696,7 +696,7 @@ func DeleteIssueCommentDeprecated(ctx *context.APIContext) {
} }
func deleteIssueComment(ctx *context.APIContext) { func deleteIssueComment(ctx *context.APIContext) {
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id")) comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil { if err != nil {
if issues_model.IsErrCommentNotExist(err) { if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)

View File

@ -61,7 +61,7 @@ func GetIssueDependencies(ctx *context.APIContext) {
return return
} }
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound("IsErrIssueNotExist", err) ctx.NotFound("IsErrIssueNotExist", err)
@ -499,7 +499,7 @@ func RemoveIssueBlocking(ctx *context.APIContext) {
} }
func getParamsIssue(ctx *context.APIContext) *issues_model.Issue { func getParamsIssue(ctx *context.APIContext) *issues_model.Issue {
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound("IsErrIssueNotExist", err) ctx.NotFound("IsErrIssueNotExist", err)

View File

@ -47,7 +47,7 @@ func ListIssueLabels(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
@ -163,7 +163,7 @@ func DeleteIssueLabel(ctx *context.APIContext) {
// "422": // "422":
// "$ref": "#/responses/validationError" // "$ref": "#/responses/validationError"
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
@ -178,7 +178,7 @@ func DeleteIssueLabel(ctx *context.APIContext) {
return return
} }
label, err := issues_model.GetLabelByID(ctx, ctx.PathParamInt64(":id")) label, err := issues_model.GetLabelByID(ctx, ctx.PathParamInt64("id"))
if err != nil { if err != nil {
if issues_model.IsErrLabelNotExist(err) { if issues_model.IsErrLabelNotExist(err) {
ctx.Error(http.StatusUnprocessableEntity, "", err) ctx.Error(http.StatusUnprocessableEntity, "", err)
@ -285,7 +285,7 @@ func ClearIssueLabels(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
@ -309,7 +309,7 @@ func ClearIssueLabels(ctx *context.APIContext) {
} }
func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) (*issues_model.Issue, []*issues_model.Label, error) { func prepareForReplaceOrAdd(ctx *context.APIContext, form api.IssueLabelsOption) (*issues_model.Issue, []*issues_model.Label, error) {
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()

View File

@ -41,7 +41,7 @@ func PinIssue(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
@ -98,7 +98,7 @@ func UnpinIssue(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
@ -159,7 +159,7 @@ func MoveIssuePin(ctx *context.APIContext) {
// "$ref": "#/responses/forbidden" // "$ref": "#/responses/forbidden"
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
@ -169,7 +169,7 @@ func MoveIssuePin(ctx *context.APIContext) {
return return
} }
err = issue.MovePin(ctx, int(ctx.PathParamInt64(":position"))) err = issue.MovePin(ctx, int(ctx.PathParamInt64("position")))
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "MovePin", err) ctx.Error(http.StatusInternalServerError, "MovePin", err)
return return

View File

@ -51,7 +51,7 @@ func GetIssueCommentReactions(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id")) comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil { if err != nil {
if issues_model.IsErrCommentNotExist(err) { if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
@ -188,7 +188,7 @@ func DeleteIssueCommentReaction(ctx *context.APIContext) {
} }
func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) { func changeIssueCommentReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64(":id")) comment, err := issues_model.GetCommentByID(ctx, ctx.PathParamInt64("id"))
if err != nil { if err != nil {
if issues_model.IsErrCommentNotExist(err) { if issues_model.IsErrCommentNotExist(err) {
ctx.NotFound(err) ctx.NotFound(err)
@ -295,7 +295,7 @@ func GetIssueReactions(ctx *context.APIContext) {
// "404": // "404":
// "$ref": "#/responses/notFound" // "$ref": "#/responses/notFound"
issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()
@ -419,7 +419,7 @@ func DeleteIssueReaction(ctx *context.APIContext) {
} }
func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) { func changeIssueReaction(ctx *context.APIContext, form api.EditReactionOption, isCreateType bool) {
issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueWithAttrsByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()

View File

@ -161,7 +161,7 @@ func DeleteIssueStopwatch(ctx *context.APIContext) {
} }
func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*issues_model.Issue, error) { func prepareIssueStopwatch(ctx *context.APIContext, shouldExist bool) (*issues_model.Issue, error) {
issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64(":index")) issue, err := issues_model.GetIssueByIndex(ctx, ctx.Repo.Repository.ID, ctx.PathParamInt64("index"))
if err != nil { if err != nil {
if issues_model.IsErrIssueNotExist(err) { if issues_model.IsErrIssueNotExist(err) {
ctx.NotFound() ctx.NotFound()

Some files were not shown because too many files have changed in this diff Show More