mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-19 19:18:43 +02:00
Merge remote-tracking branch 'origin/main' into non-text-edit
This commit is contained in:
commit
bec5dced70
@ -80,6 +80,11 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runDumpRepository(ctx *cli.Context) error {
|
func runDumpRepository(ctx *cli.Context) error {
|
||||||
|
setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr)
|
||||||
|
|
||||||
|
setting.DisableLoggerInit()
|
||||||
|
setting.LoadSettings() // cannot access skip_tls_verify settings otherwise
|
||||||
|
|
||||||
stdCtx, cancel := installSignals()
|
stdCtx, cancel := installSignals()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
hookBatchSize = 30
|
hookBatchSize = 500
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
89
cmd/serv.go
89
cmd/serv.go
@ -11,7 +11,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -20,7 +19,7 @@ import (
|
|||||||
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/lfstransfer"
|
"code.gitea.io/gitea/modules/lfstransfer"
|
||||||
@ -37,14 +36,6 @@ import (
|
|||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
verbUploadPack = "git-upload-pack"
|
|
||||||
verbUploadArchive = "git-upload-archive"
|
|
||||||
verbReceivePack = "git-receive-pack"
|
|
||||||
verbLfsAuthenticate = "git-lfs-authenticate"
|
|
||||||
verbLfsTransfer = "git-lfs-transfer"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdServ represents the available serv sub-command.
|
// CmdServ represents the available serv sub-command.
|
||||||
var CmdServ = &cli.Command{
|
var CmdServ = &cli.Command{
|
||||||
Name: "serv",
|
Name: "serv",
|
||||||
@ -78,22 +69,6 @@ func setup(ctx context.Context, debug bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
// keep getAccessMode() in sync
|
|
||||||
allowedCommands = container.SetOf(
|
|
||||||
verbUploadPack,
|
|
||||||
verbUploadArchive,
|
|
||||||
verbReceivePack,
|
|
||||||
verbLfsAuthenticate,
|
|
||||||
verbLfsTransfer,
|
|
||||||
)
|
|
||||||
allowedCommandsLfs = container.SetOf(
|
|
||||||
verbLfsAuthenticate,
|
|
||||||
verbLfsTransfer,
|
|
||||||
)
|
|
||||||
alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
|
|
||||||
)
|
|
||||||
|
|
||||||
// fail prints message to stdout, it's mainly used for git serv and git hook commands.
|
// fail prints message to stdout, it's mainly used for git serv and git hook commands.
|
||||||
// The output will be passed to git client and shown to user.
|
// The output will be passed to git client and shown to user.
|
||||||
func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error {
|
func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error {
|
||||||
@ -139,19 +114,20 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
|
|||||||
|
|
||||||
func getAccessMode(verb, lfsVerb string) perm.AccessMode {
|
func getAccessMode(verb, lfsVerb string) perm.AccessMode {
|
||||||
switch verb {
|
switch verb {
|
||||||
case verbUploadPack, verbUploadArchive:
|
case git.CmdVerbUploadPack, git.CmdVerbUploadArchive:
|
||||||
return perm.AccessModeRead
|
return perm.AccessModeRead
|
||||||
case verbReceivePack:
|
case git.CmdVerbReceivePack:
|
||||||
return perm.AccessModeWrite
|
return perm.AccessModeWrite
|
||||||
case verbLfsAuthenticate, verbLfsTransfer:
|
case git.CmdVerbLfsAuthenticate, git.CmdVerbLfsTransfer:
|
||||||
switch lfsVerb {
|
switch lfsVerb {
|
||||||
case "upload":
|
case git.CmdSubVerbLfsUpload:
|
||||||
return perm.AccessModeWrite
|
return perm.AccessModeWrite
|
||||||
case "download":
|
case git.CmdSubVerbLfsDownload:
|
||||||
return perm.AccessModeRead
|
return perm.AccessModeRead
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// should be unreachable
|
// should be unreachable
|
||||||
|
setting.PanicInDevOrTesting("unknown verb: %s %s", verb, lfsVerb)
|
||||||
return perm.AccessModeNone
|
return perm.AccessModeNone
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,12 +206,12 @@ func runServ(c *cli.Context) error {
|
|||||||
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
|
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
|
||||||
}
|
}
|
||||||
|
|
||||||
words, err := shellquote.Split(cmd)
|
sshCmdArgs, err := shellquote.Split(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err)
|
return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(words) < 2 {
|
if len(sshCmdArgs) < 2 {
|
||||||
if git.DefaultFeatures().SupportProcReceive {
|
if git.DefaultFeatures().SupportProcReceive {
|
||||||
// for AGit Flow
|
// for AGit Flow
|
||||||
if cmd == "ssh_info" {
|
if cmd == "ssh_info" {
|
||||||
@ -246,25 +222,21 @@ func runServ(c *cli.Context) error {
|
|||||||
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
|
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
verb := words[0]
|
repoPath := strings.TrimPrefix(sshCmdArgs[1], "/")
|
||||||
repoPath := strings.TrimPrefix(words[1], "/")
|
repoPathFields := strings.SplitN(repoPath, "/", 2)
|
||||||
|
if len(repoPathFields) != 2 {
|
||||||
var lfsVerb string
|
|
||||||
|
|
||||||
rr := strings.SplitN(repoPath, "/", 2)
|
|
||||||
if len(rr) != 2 {
|
|
||||||
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
|
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
username := rr[0]
|
username := repoPathFields[0]
|
||||||
reponame := strings.TrimSuffix(rr[1], ".git")
|
reponame := strings.TrimSuffix(repoPathFields[1], ".git") // “the-repo-name" or "the-repo-name.wiki"
|
||||||
|
|
||||||
// LowerCase and trim the repoPath as that's how they are stored.
|
// LowerCase and trim the repoPath as that's how they are stored.
|
||||||
// This should be done after splitting the repoPath into username and reponame
|
// This should be done after splitting the repoPath into username and reponame
|
||||||
// so that username and reponame are not affected.
|
// so that username and reponame are not affected.
|
||||||
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
|
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
|
||||||
|
|
||||||
if alphaDashDotPattern.MatchString(reponame) {
|
if !repo.IsValidSSHAccessRepoName(reponame) {
|
||||||
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
|
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,22 +258,23 @@ func runServ(c *cli.Context) error {
|
|||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
if allowedCommands.Contains(verb) {
|
verb, lfsVerb := sshCmdArgs[0], ""
|
||||||
if allowedCommandsLfs.Contains(verb) {
|
if !git.IsAllowedVerbForServe(verb) {
|
||||||
if !setting.LFS.StartServer {
|
|
||||||
return fail(ctx, "LFS Server is not enabled", "")
|
|
||||||
}
|
|
||||||
if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
|
|
||||||
return fail(ctx, "LFS SSH transfer is not enabled", "")
|
|
||||||
}
|
|
||||||
if len(words) > 2 {
|
|
||||||
lfsVerb = words[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
|
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if git.IsAllowedVerbForServeLfs(verb) {
|
||||||
|
if !setting.LFS.StartServer {
|
||||||
|
return fail(ctx, "LFS Server is not enabled", "")
|
||||||
|
}
|
||||||
|
if verb == git.CmdVerbLfsTransfer && !setting.LFS.AllowPureSSH {
|
||||||
|
return fail(ctx, "LFS SSH transfer is not enabled", "")
|
||||||
|
}
|
||||||
|
if len(sshCmdArgs) > 2 {
|
||||||
|
lfsVerb = sshCmdArgs[2]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
requestedMode := getAccessMode(verb, lfsVerb)
|
requestedMode := getAccessMode(verb, lfsVerb)
|
||||||
|
|
||||||
results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb)
|
results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb)
|
||||||
@ -310,7 +283,7 @@ func runServ(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LFS SSH protocol
|
// LFS SSH protocol
|
||||||
if verb == verbLfsTransfer {
|
if verb == git.CmdVerbLfsTransfer {
|
||||||
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -319,7 +292,7 @@ func runServ(c *cli.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LFS token authentication
|
// LFS token authentication
|
||||||
if verb == verbLfsAuthenticate {
|
if verb == git.CmdVerbLfsAuthenticate {
|
||||||
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
|
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
|
||||||
|
|
||||||
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
token, err := getLFSAuthToken(ctx, lfsVerb, results)
|
||||||
|
6
flake.lock
generated
6
flake.lock
generated
@ -20,11 +20,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1739214665,
|
"lastModified": 1747179050,
|
||||||
"narHash": "sha256-26L8VAu3/1YRxS8MHgBOyOM8xALdo6N0I04PgorE7UM=",
|
"narHash": "sha256-qhFMmDkeJX9KJwr5H32f1r7Prs7XbQWtO0h3V0a0rFY=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "64e75cd44acf21c7933d61d7721e812eac1b5a0a",
|
"rev": "adaa24fbf46737f3f1b5497bf64bae750f82942e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
2
go.mod
2
go.mod
@ -317,7 +317,7 @@ replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
|
|||||||
|
|
||||||
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
|
replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142115-2c99e1ffdfa0
|
||||||
|
|
||||||
replace github.com/nektos/act => gitea.com/gitea/act v0.261.4
|
replace github.com/nektos/act => gitea.com/gitea/act v0.261.6
|
||||||
|
|
||||||
// TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why
|
// TODO: the only difference is in `PutObject`: the fork doesn't use `NewVerifyingReader(r, sha256.New(), oid, expectedSize)`, need to figure out why
|
||||||
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0
|
replace github.com/charmbracelet/git-lfs-transfer => gitea.com/gitea/git-lfs-transfer v0.2.0
|
||||||
|
4
go.sum
4
go.sum
@ -14,8 +14,8 @@ dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
|||||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
gitea.com/gitea/act v0.261.4 h1:Tf9eLlvsYFtKcpuxlMvf9yT3g4Hshb2Beqw6C1STuH8=
|
gitea.com/gitea/act v0.261.6 h1:CjZwKOyejonNFDmsXOw3wGm5Vet573hHM6VMLsxtvPY=
|
||||||
gitea.com/gitea/act v0.261.4/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
gitea.com/gitea/act v0.261.6/go.mod h1:Pg5C9kQY1CEA3QjthjhlrqOC/QOT5NyWNjOjRHw23Ok=
|
||||||
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
|
gitea.com/gitea/git-lfs-transfer v0.2.0 h1:baHaNoBSRaeq/xKayEXwiDQtlIjps4Ac/Ll4KqLMB40=
|
||||||
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
|
gitea.com/gitea/git-lfs-transfer v0.2.0/go.mod h1:UrXUCm3xLQkq15fu7qlXHUMlrhdlXHoi13KH2Dfiits=
|
||||||
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
|
gitea.com/gitea/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:BAFmdZpRW7zMQZQDClaCWobRj9uL1MR3MzpCVJvc5s4=
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@ -171,6 +172,7 @@ func (run *ActionRun) IsSchedule() bool {
|
|||||||
|
|
||||||
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
|
func updateRepoRunsNumbers(ctx context.Context, repo *repo_model.Repository) error {
|
||||||
_, err := db.GetEngine(ctx).ID(repo.ID).
|
_, err := db.GetEngine(ctx).ID(repo.ID).
|
||||||
|
NoAutoTime().
|
||||||
SetExpr("num_action_runs",
|
SetExpr("num_action_runs",
|
||||||
builder.Select("count(*)").From("action_run").
|
builder.Select("count(*)").From("action_run").
|
||||||
Where(builder.Eq{"repo_id": repo.ID}),
|
Where(builder.Eq{"repo_id": repo.ID}),
|
||||||
@ -342,13 +344,13 @@ func InsertRun(ctx context.Context, run *ActionRun, jobs []*jobparser.SingleWork
|
|||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRunByID(ctx context.Context, id int64) (*ActionRun, error) {
|
func GetRunByRepoAndID(ctx context.Context, repoID, runID int64) (*ActionRun, error) {
|
||||||
var run ActionRun
|
var run ActionRun
|
||||||
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&run)
|
has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", runID, repoID).Get(&run)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if !has {
|
} else if !has {
|
||||||
return nil, fmt.Errorf("run with id %d: %w", id, util.ErrNotExist)
|
return nil, fmt.Errorf("run with id %d: %w", runID, util.ErrNotExist)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &run, nil
|
return &run, nil
|
||||||
@ -419,17 +421,10 @@ func UpdateRun(ctx context.Context, run *ActionRun, cols ...string) error {
|
|||||||
|
|
||||||
if run.Status != 0 || slices.Contains(cols, "status") {
|
if run.Status != 0 || slices.Contains(cols, "status") {
|
||||||
if run.RepoID == 0 {
|
if run.RepoID == 0 {
|
||||||
run, err = GetRunByID(ctx, run.ID)
|
setting.PanicInDevOrTesting("RepoID should not be 0")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if run.Repo == nil {
|
if err = run.LoadRepo(ctx); err != nil {
|
||||||
repo, err := repo_model.GetRepositoryByID(ctx, run.RepoID)
|
return err
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
run.Repo = repo
|
|
||||||
}
|
}
|
||||||
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
|
if err := updateRepoRunsNumbers(ctx, run.Repo); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -51,7 +51,7 @@ func (job *ActionRunJob) Duration() time.Duration {
|
|||||||
|
|
||||||
func (job *ActionRunJob) LoadRun(ctx context.Context) error {
|
func (job *ActionRunJob) LoadRun(ctx context.Context) error {
|
||||||
if job.Run == nil {
|
if job.Run == nil {
|
||||||
run, err := GetRunByID(ctx, job.RunID)
|
run, err := GetRunByRepoAndID(ctx, job.RepoID, job.RunID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -142,7 +142,7 @@ func UpdateRunJob(ctx context.Context, job *ActionRunJob, cond builder.Cond, col
|
|||||||
{
|
{
|
||||||
// Other goroutines may aggregate the status of the run and update it too.
|
// Other goroutines may aggregate the status of the run and update it too.
|
||||||
// So we need load the run and its jobs before updating the run.
|
// So we need load the run and its jobs before updating the run.
|
||||||
run, err := GetRunByID(ctx, job.RunID)
|
run, err := GetRunByRepoAndID(ctx, job.RepoID, job.RunID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ package actions
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -298,6 +299,23 @@ func DeleteRunner(ctx context.Context, id int64) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteEphemeralRunner deletes a ephemeral runner by given ID.
|
||||||
|
func DeleteEphemeralRunner(ctx context.Context, id int64) error {
|
||||||
|
runner, err := GetRunnerByID(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !runner.Ephemeral {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.DeleteByID[ActionRunner](ctx, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// CreateRunner creates new runner.
|
// CreateRunner creates new runner.
|
||||||
func CreateRunner(ctx context.Context, t *ActionRunner) error {
|
func CreateRunner(ctx context.Context, t *ActionRunner) error {
|
||||||
if t.OwnerID != 0 && t.RepoID != 0 {
|
if t.OwnerID != 0 && t.RepoID != 0 {
|
||||||
|
@ -336,6 +336,11 @@ func UpdateTask(ctx context.Context, task *ActionTask, cols ...string) error {
|
|||||||
sess.Cols(cols...)
|
sess.Cols(cols...)
|
||||||
}
|
}
|
||||||
_, err := sess.Update(task)
|
_, err := sess.Update(task)
|
||||||
|
|
||||||
|
// Automatically delete the ephemeral runner if the task is done
|
||||||
|
if err == nil && task.Status.IsDone() && util.SliceContainsString(cols, "status") {
|
||||||
|
return DeleteEphemeralRunner(ctx, task.RunnerID)
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ func (tasks TaskList) LoadAttributes(ctx context.Context) error {
|
|||||||
type FindTaskOptions struct {
|
type FindTaskOptions struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
RepoID int64
|
RepoID int64
|
||||||
|
JobID int64
|
||||||
OwnerID int64
|
OwnerID int64
|
||||||
CommitSHA string
|
CommitSHA string
|
||||||
Status Status
|
Status Status
|
||||||
@ -61,6 +62,9 @@ func (opts FindTaskOptions) ToConds() builder.Cond {
|
|||||||
if opts.RepoID > 0 {
|
if opts.RepoID > 0 {
|
||||||
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
cond = cond.And(builder.Eq{"repo_id": opts.RepoID})
|
||||||
}
|
}
|
||||||
|
if opts.JobID > 0 {
|
||||||
|
cond = cond.And(builder.Eq{"job_id": opts.JobID})
|
||||||
|
}
|
||||||
if opts.OwnerID > 0 {
|
if opts.OwnerID > 0 {
|
||||||
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
cond = cond.And(builder.Eq{"owner_id": opts.OwnerID})
|
||||||
}
|
}
|
||||||
|
@ -82,3 +82,22 @@ func calculateDuration(started, stopped timeutil.TimeStamp, status Status) time.
|
|||||||
}
|
}
|
||||||
return timeSince(s).Truncate(time.Second)
|
return timeSince(s).Truncate(time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// best effort function to convert an action schedule to action run, to be used in GenerateGiteaContext
|
||||||
|
func (s *ActionSchedule) ToActionRun() *ActionRun {
|
||||||
|
return &ActionRun{
|
||||||
|
Title: s.Title,
|
||||||
|
RepoID: s.RepoID,
|
||||||
|
Repo: s.Repo,
|
||||||
|
OwnerID: s.OwnerID,
|
||||||
|
WorkflowID: s.WorkflowID,
|
||||||
|
TriggerUserID: s.TriggerUserID,
|
||||||
|
TriggerUser: s.TriggerUser,
|
||||||
|
Ref: s.Ref,
|
||||||
|
CommitSHA: s.CommitSHA,
|
||||||
|
Event: s.Event,
|
||||||
|
EventPayload: s.EventPayload,
|
||||||
|
Created: s.Created,
|
||||||
|
Updated: s.Updated,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -530,7 +530,7 @@ func ActivityQueryCondition(ctx context.Context, opts GetFeedsOptions) (builder.
|
|||||||
|
|
||||||
if opts.RequestedTeam != nil {
|
if opts.RequestedTeam != nil {
|
||||||
env := repo_model.AccessibleTeamReposEnv(organization.OrgFromUser(opts.RequestedUser), opts.RequestedTeam)
|
env := repo_model.AccessibleTeamReposEnv(organization.OrgFromUser(opts.RequestedUser), opts.RequestedTeam)
|
||||||
teamRepoIDs, err := env.RepoIDs(ctx, 1, opts.RequestedUser.NumRepos)
|
teamRepoIDs, err := env.RepoIDs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("GetTeamRepositories: %w", err)
|
return nil, fmt.Errorf("GetTeamRepositories: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -105,3 +105,39 @@
|
|||||||
created_unix: 1730330775
|
created_unix: 1730330775
|
||||||
updated_unix: 1730330775
|
updated_unix: 1730330775
|
||||||
expired_unix: 1738106775
|
expired_unix: 1738106775
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 24
|
||||||
|
run_id: 795
|
||||||
|
runner_id: 1
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
storage_path: "27/5/1730330775594233150.chunk"
|
||||||
|
file_size: 1024
|
||||||
|
file_compressed_size: 1024
|
||||||
|
content_encoding: "application/zip"
|
||||||
|
artifact_path: "artifact-795-1.zip"
|
||||||
|
artifact_name: "artifact-795-1"
|
||||||
|
status: 2
|
||||||
|
created_unix: 1730330775
|
||||||
|
updated_unix: 1730330775
|
||||||
|
expired_unix: 1738106775
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 25
|
||||||
|
run_id: 795
|
||||||
|
runner_id: 1
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
storage_path: "27/5/1730330775594233150.chunk"
|
||||||
|
file_size: 1024
|
||||||
|
file_compressed_size: 1024
|
||||||
|
content_encoding: "application/zip"
|
||||||
|
artifact_path: "artifact-795-2.zip"
|
||||||
|
artifact_name: "artifact-795-2"
|
||||||
|
status: 2
|
||||||
|
created_unix: 1730330775
|
||||||
|
updated_unix: 1730330775
|
||||||
|
expired_unix: 1738106775
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||||
event: "push"
|
event: "push"
|
||||||
is_fork_pull_request: 0
|
is_fork_pull_request: 0
|
||||||
status: 1
|
status: 6 # running
|
||||||
started: 1683636528
|
started: 1683636528
|
||||||
stopped: 1683636626
|
stopped: 1683636626
|
||||||
created: 1683636108
|
created: 1683636108
|
||||||
@ -74,3 +74,23 @@
|
|||||||
updated: 1683636626
|
updated: 1683636626
|
||||||
need_approval: 0
|
need_approval: 0
|
||||||
approved_by: 0
|
approved_by: 0
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 795
|
||||||
|
title: "to be deleted (test)"
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
workflow_id: "test.yaml"
|
||||||
|
index: 191
|
||||||
|
trigger_user_id: 1
|
||||||
|
ref: "refs/heads/test"
|
||||||
|
commit_sha: "c2d72f548424103f01ee1dc02889c1e2bff816b0"
|
||||||
|
event: "push"
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
status: 2
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
created: 1683636108
|
||||||
|
updated: 1683636626
|
||||||
|
need_approval: 0
|
||||||
|
approved_by: 0
|
||||||
|
@ -69,3 +69,33 @@
|
|||||||
status: 5
|
status: 5
|
||||||
started: 1683636528
|
started: 1683636528
|
||||||
stopped: 1683636626
|
stopped: 1683636626
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 198
|
||||||
|
run_id: 795
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
name: job_1
|
||||||
|
attempt: 1
|
||||||
|
job_id: job_1
|
||||||
|
task_id: 53
|
||||||
|
status: 1
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 199
|
||||||
|
run_id: 795
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
name: job_2
|
||||||
|
attempt: 1
|
||||||
|
job_id: job_2
|
||||||
|
task_id: 54
|
||||||
|
status: 2
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
@ -38,3 +38,14 @@
|
|||||||
repo_id: 0
|
repo_id: 0
|
||||||
description: "This runner is going to be deleted"
|
description: "This runner is going to be deleted"
|
||||||
agent_labels: '["runner_to_be_deleted","linux"]'
|
agent_labels: '["runner_to_be_deleted","linux"]'
|
||||||
|
-
|
||||||
|
id: 34350
|
||||||
|
name: runner_to_be_deleted-org-ephemeral
|
||||||
|
uuid: 3FF231BD-FBB7-4E4B-9602-E6F28363EF20
|
||||||
|
token_hash: 3FF231BD-FBB7-4E4B-9602-E6F28363EF20
|
||||||
|
ephemeral: true
|
||||||
|
version: "1.0.0"
|
||||||
|
owner_id: 3
|
||||||
|
repo_id: 0
|
||||||
|
description: "This runner is going to be deleted"
|
||||||
|
agent_labels: '["runner_to_be_deleted","linux"]'
|
||||||
|
@ -117,3 +117,63 @@
|
|||||||
log_length: 707
|
log_length: 707
|
||||||
log_size: 90179
|
log_size: 90179
|
||||||
log_expired: 0
|
log_expired: 0
|
||||||
|
-
|
||||||
|
id: 52
|
||||||
|
job_id: 196
|
||||||
|
attempt: 1
|
||||||
|
runner_id: 34350
|
||||||
|
status: 6 # running
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
repo_id: 4
|
||||||
|
owner_id: 1
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
token_hash: f8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784222
|
||||||
|
token_salt: ffffffffff
|
||||||
|
token_last_eight: ffffffff
|
||||||
|
log_filename: artifact-test2/2f/47.log
|
||||||
|
log_in_storage: 1
|
||||||
|
log_length: 707
|
||||||
|
log_size: 90179
|
||||||
|
log_expired: 0
|
||||||
|
-
|
||||||
|
id: 53
|
||||||
|
job_id: 198
|
||||||
|
attempt: 1
|
||||||
|
runner_id: 1
|
||||||
|
status: 1
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784223
|
||||||
|
token_salt: ffffffffff
|
||||||
|
token_last_eight: ffffffff
|
||||||
|
log_filename: artifact-test2/2f/47.log
|
||||||
|
log_in_storage: 1
|
||||||
|
log_length: 0
|
||||||
|
log_size: 0
|
||||||
|
log_expired: 0
|
||||||
|
-
|
||||||
|
id: 54
|
||||||
|
job_id: 199
|
||||||
|
attempt: 1
|
||||||
|
runner_id: 1
|
||||||
|
status: 2
|
||||||
|
started: 1683636528
|
||||||
|
stopped: 1683636626
|
||||||
|
repo_id: 2
|
||||||
|
owner_id: 2
|
||||||
|
commit_sha: c2d72f548424103f01ee1dc02889c1e2bff816b0
|
||||||
|
is_fork_pull_request: 0
|
||||||
|
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc4784224
|
||||||
|
token_salt: ffffffffff
|
||||||
|
token_last_eight: ffffffff
|
||||||
|
log_filename: artifact-test2/2f/47.log
|
||||||
|
log_in_storage: 1
|
||||||
|
log_length: 0
|
||||||
|
log_size: 0
|
||||||
|
log_expired: 0
|
||||||
|
@ -14,5 +14,8 @@ func AddIndexToActionTaskStoppedLogExpired(x *xorm.Engine) error {
|
|||||||
Stopped timeutil.TimeStamp `xorm:"index(stopped_log_expired)"`
|
Stopped timeutil.TimeStamp `xorm:"index(stopped_log_expired)"`
|
||||||
LogExpired bool `xorm:"index(stopped_log_expired)"`
|
LogExpired bool `xorm:"index(stopped_log_expired)"`
|
||||||
}
|
}
|
||||||
return x.Sync(new(ActionTask))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreDropIndices: true,
|
||||||
|
}, new(ActionTask))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
51
models/migrations/v1_23/v302_test.go
Normal file
51
models/migrations/v1_23/v302_test.go
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_23 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/migrations/base"
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_AddIndexToActionTaskStoppedLogExpired(t *testing.T) {
|
||||||
|
type ActionTask struct {
|
||||||
|
ID int64
|
||||||
|
JobID int64
|
||||||
|
Attempt int64
|
||||||
|
RunnerID int64 `xorm:"index"`
|
||||||
|
Status int `xorm:"index"`
|
||||||
|
Started timeutil.TimeStamp `xorm:"index"`
|
||||||
|
Stopped timeutil.TimeStamp `xorm:"index(stopped_log_expired)"`
|
||||||
|
|
||||||
|
RepoID int64 `xorm:"index"`
|
||||||
|
OwnerID int64 `xorm:"index"`
|
||||||
|
CommitSHA string `xorm:"index"`
|
||||||
|
IsForkPullRequest bool
|
||||||
|
|
||||||
|
Token string `xorm:"-"`
|
||||||
|
TokenHash string `xorm:"UNIQUE"` // sha256 of token
|
||||||
|
TokenSalt string
|
||||||
|
TokenLastEight string `xorm:"index token_last_eight"`
|
||||||
|
|
||||||
|
LogFilename string // file name of log
|
||||||
|
LogInStorage bool // read log from database or from storage
|
||||||
|
LogLength int64 // lines count
|
||||||
|
LogSize int64 // blob size
|
||||||
|
LogIndexes []int64 `xorm:"LONGBLOB"` // line number to offset
|
||||||
|
LogExpired bool `xorm:"index(stopped_log_expired)"` // files that are too old will be deleted
|
||||||
|
|
||||||
|
Created timeutil.TimeStamp `xorm:"created"`
|
||||||
|
Updated timeutil.TimeStamp `xorm:"updated index"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare and load the testing database
|
||||||
|
x, deferable := base.PrepareTestEnv(t, 0, new(ActionTask))
|
||||||
|
defer deferable()
|
||||||
|
|
||||||
|
assert.NoError(t, AddIndexToActionTaskStoppedLogExpired(x))
|
||||||
|
}
|
@ -9,5 +9,8 @@ func AddIndexForReleaseSha1(x *xorm.Engine) error {
|
|||||||
type Release struct {
|
type Release struct {
|
||||||
Sha1 string `xorm:"INDEX VARCHAR(64)"`
|
Sha1 string `xorm:"INDEX VARCHAR(64)"`
|
||||||
}
|
}
|
||||||
return x.Sync(new(Release))
|
_, err := x.SyncWithOptions(xorm.SyncOptions{
|
||||||
|
IgnoreDropIndices: true,
|
||||||
|
}, new(Release))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
40
models/migrations/v1_23/v304_test.go
Normal file
40
models/migrations/v1_23/v304_test.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package v1_23 //nolint
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/migrations/base"
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_AddIndexForReleaseSha1(t *testing.T) {
|
||||||
|
type Release struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
RepoID int64 `xorm:"INDEX UNIQUE(n)"`
|
||||||
|
PublisherID int64 `xorm:"INDEX"`
|
||||||
|
TagName string `xorm:"INDEX UNIQUE(n)"`
|
||||||
|
OriginalAuthor string
|
||||||
|
OriginalAuthorID int64 `xorm:"index"`
|
||||||
|
LowerTagName string
|
||||||
|
Target string
|
||||||
|
Title string
|
||||||
|
Sha1 string `xorm:"VARCHAR(64)"`
|
||||||
|
NumCommits int64
|
||||||
|
Note string `xorm:"TEXT"`
|
||||||
|
IsDraft bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
IsPrerelease bool `xorm:"NOT NULL DEFAULT false"`
|
||||||
|
IsTag bool `xorm:"NOT NULL DEFAULT false"` // will be true only if the record is a tag and has no related releases
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare and load the testing database
|
||||||
|
x, deferable := base.PrepareTestEnv(t, 0, new(Release))
|
||||||
|
defer deferable()
|
||||||
|
|
||||||
|
assert.NoError(t, AddIndexForReleaseSha1(x))
|
||||||
|
}
|
@ -334,7 +334,7 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
|
|||||||
testSuccess := func(userID int64, expectedRepoIDs []int64) {
|
testSuccess := func(userID int64, expectedRepoIDs []int64) {
|
||||||
env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID)
|
env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
repoIDs, err := env.RepoIDs(db.DefaultContext, 1, 100)
|
repoIDs, err := env.RepoIDs(db.DefaultContext)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, expectedRepoIDs, repoIDs)
|
assert.Equal(t, expectedRepoIDs, repoIDs)
|
||||||
}
|
}
|
||||||
@ -342,25 +342,6 @@ func TestAccessibleReposEnv_RepoIDs(t *testing.T) {
|
|||||||
testSuccess(4, []int64{3, 32})
|
testSuccess(4, []int64{3, 32})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAccessibleReposEnv_Repos(t *testing.T) {
|
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
|
||||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
|
||||||
testSuccess := func(userID int64, expectedRepoIDs []int64) {
|
|
||||||
env, err := repo_model.AccessibleReposEnv(db.DefaultContext, org, userID)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
repos, err := env.Repos(db.DefaultContext, 1, 100)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
expectedRepos := make(repo_model.RepositoryList, len(expectedRepoIDs))
|
|
||||||
for i, repoID := range expectedRepoIDs {
|
|
||||||
expectedRepos[i] = unittest.AssertExistsAndLoadBean(t,
|
|
||||||
&repo_model.Repository{ID: repoID})
|
|
||||||
}
|
|
||||||
assert.Equal(t, expectedRepos, repos)
|
|
||||||
}
|
|
||||||
testSuccess(2, []int64{3, 5, 32})
|
|
||||||
testSuccess(4, []int64{3, 32})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
|
func TestAccessibleReposEnv_MirrorRepos(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
org := unittest.AssertExistsAndLoadBean(t, &organization.Organization{ID: 3})
|
||||||
|
@ -47,7 +47,7 @@ func (c *commitChecker) IsCommitIDExisting(commitID string) bool {
|
|||||||
c.gitRepo, c.gitRepoCloser = r, closer
|
c.gitRepo, c.gitRepoCloser = r, closer
|
||||||
}
|
}
|
||||||
|
|
||||||
exist = c.gitRepo.IsReferenceExist(commitID) // Don't use IsObjectExist since it doesn't support short hashs with gogit edition.
|
exist = c.gitRepo.IsReferenceExist(commitID) // Don't use IsObjectExist since it doesn't support short hashes with gogit edition.
|
||||||
c.commitCache[commitID] = exist
|
c.commitCache[commitID] = exist
|
||||||
return exist
|
return exist
|
||||||
}
|
}
|
||||||
|
@ -48,8 +48,7 @@ func GetTeamRepositories(ctx context.Context, opts *SearchTeamRepoOptions) (Repo
|
|||||||
// accessible to a particular user
|
// accessible to a particular user
|
||||||
type AccessibleReposEnvironment interface {
|
type AccessibleReposEnvironment interface {
|
||||||
CountRepos(ctx context.Context) (int64, error)
|
CountRepos(ctx context.Context) (int64, error)
|
||||||
RepoIDs(ctx context.Context, page, pageSize int) ([]int64, error)
|
RepoIDs(ctx context.Context) ([]int64, error)
|
||||||
Repos(ctx context.Context, page, pageSize int) (RepositoryList, error)
|
|
||||||
MirrorRepos(ctx context.Context) (RepositoryList, error)
|
MirrorRepos(ctx context.Context) (RepositoryList, error)
|
||||||
AddKeyword(keyword string)
|
AddKeyword(keyword string)
|
||||||
SetSort(db.SearchOrderBy)
|
SetSort(db.SearchOrderBy)
|
||||||
@ -132,40 +131,18 @@ func (env *accessibleReposEnv) CountRepos(ctx context.Context) (int64, error) {
|
|||||||
return repoCount, nil
|
return repoCount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (env *accessibleReposEnv) RepoIDs(ctx context.Context, page, pageSize int) ([]int64, error) {
|
func (env *accessibleReposEnv) RepoIDs(ctx context.Context) ([]int64, error) {
|
||||||
if page <= 0 {
|
var repoIDs []int64
|
||||||
page = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
repoIDs := make([]int64, 0, pageSize)
|
|
||||||
return repoIDs, db.GetEngine(ctx).
|
return repoIDs, db.GetEngine(ctx).
|
||||||
Table("repository").
|
Table("repository").
|
||||||
Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id").
|
Join("INNER", "team_repo", "`team_repo`.repo_id=`repository`.id").
|
||||||
Where(env.cond()).
|
Where(env.cond()).
|
||||||
GroupBy("`repository`.id,`repository`."+strings.Fields(string(env.orderBy))[0]).
|
GroupBy("`repository`.id,`repository`." + strings.Fields(string(env.orderBy))[0]).
|
||||||
OrderBy(string(env.orderBy)).
|
OrderBy(string(env.orderBy)).
|
||||||
Limit(pageSize, (page-1)*pageSize).
|
|
||||||
Cols("`repository`.id").
|
Cols("`repository`.id").
|
||||||
Find(&repoIDs)
|
Find(&repoIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (env *accessibleReposEnv) Repos(ctx context.Context, page, pageSize int) (RepositoryList, error) {
|
|
||||||
repoIDs, err := env.RepoIDs(ctx, page, pageSize)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("GetUserRepositoryIDs: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
repos := make([]*Repository, 0, len(repoIDs))
|
|
||||||
if len(repoIDs) == 0 {
|
|
||||||
return repos, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return repos, db.GetEngine(ctx).
|
|
||||||
In("`repository`.id", repoIDs).
|
|
||||||
OrderBy(string(env.orderBy)).
|
|
||||||
Find(&repos)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (env *accessibleReposEnv) MirrorRepoIDs(ctx context.Context) ([]int64, error) {
|
func (env *accessibleReposEnv) MirrorRepoIDs(ctx context.Context) ([]int64, error) {
|
||||||
repoIDs := make([]int64, 0, 10)
|
repoIDs := make([]int64, 0, 10)
|
||||||
return repoIDs, db.GetEngine(ctx).
|
return repoIDs, db.GetEngine(ctx).
|
||||||
|
@ -161,6 +161,11 @@ func UpdateRelease(ctx context.Context, rel *Release) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func UpdateReleaseNumCommits(ctx context.Context, rel *Release) error {
|
||||||
|
_, err := db.GetEngine(ctx).ID(rel.ID).Cols("num_commits").Update(rel)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// AddReleaseAttachments adds a release attachments
|
// AddReleaseAttachments adds a release attachments
|
||||||
func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) {
|
func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs []string) (err error) {
|
||||||
// Check attachments
|
// Check attachments
|
||||||
@ -418,8 +423,8 @@ func UpdateReleasesMigrationsByType(ctx context.Context, gitServiceType structs.
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushUpdateDeleteTagsContext updates a number of delete tags with context
|
// PushUpdateDeleteTags updates a number of delete tags with context
|
||||||
func PushUpdateDeleteTagsContext(ctx context.Context, repo *Repository, tags []string) error {
|
func PushUpdateDeleteTags(ctx context.Context, repo *Repository, tags []string) error {
|
||||||
if len(tags) == 0 {
|
if len(tags) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -448,58 +453,6 @@ func PushUpdateDeleteTagsContext(ctx context.Context, repo *Repository, tags []s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushUpdateDeleteTag must be called for any push actions to delete tag
|
|
||||||
func PushUpdateDeleteTag(ctx context.Context, repo *Repository, tagName string) error {
|
|
||||||
rel, err := GetRelease(ctx, repo.ID, tagName)
|
|
||||||
if err != nil {
|
|
||||||
if IsErrReleaseNotExist(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("GetRelease: %w", err)
|
|
||||||
}
|
|
||||||
if rel.IsTag {
|
|
||||||
if _, err = db.DeleteByID[Release](ctx, rel.ID); err != nil {
|
|
||||||
return fmt.Errorf("Delete: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rel.IsDraft = true
|
|
||||||
rel.NumCommits = 0
|
|
||||||
rel.Sha1 = ""
|
|
||||||
if _, err = db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel); err != nil {
|
|
||||||
return fmt.Errorf("Update: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveOrUpdateTag must be called for any push actions to add tag
|
|
||||||
func SaveOrUpdateTag(ctx context.Context, repo *Repository, newRel *Release) error {
|
|
||||||
rel, err := GetRelease(ctx, repo.ID, newRel.TagName)
|
|
||||||
if err != nil && !IsErrReleaseNotExist(err) {
|
|
||||||
return fmt.Errorf("GetRelease: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if rel == nil {
|
|
||||||
rel = newRel
|
|
||||||
if _, err = db.GetEngine(ctx).Insert(rel); err != nil {
|
|
||||||
return fmt.Errorf("InsertOne: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rel.Sha1 = newRel.Sha1
|
|
||||||
rel.CreatedUnix = newRel.CreatedUnix
|
|
||||||
rel.NumCommits = newRel.NumCommits
|
|
||||||
rel.IsDraft = false
|
|
||||||
if rel.IsTag && newRel.PublisherID > 0 {
|
|
||||||
rel.PublisherID = newRel.PublisherID
|
|
||||||
}
|
|
||||||
if _, err = db.GetEngine(ctx).ID(rel.ID).AllCols().Update(rel); err != nil {
|
|
||||||
return fmt.Errorf("Update: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemapExternalUser ExternalUserRemappable interface
|
// RemapExternalUser ExternalUserRemappable interface
|
||||||
func (r *Release) RemapExternalUser(externalName string, externalID, userID int64) error {
|
func (r *Release) RemapExternalUser(externalName string, externalID, userID int64) error {
|
||||||
r.OriginalAuthor = externalName
|
r.OriginalAuthor = externalName
|
||||||
|
@ -64,18 +64,18 @@ func (err ErrRepoIsArchived) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type globalVarsStruct struct {
|
type globalVarsStruct struct {
|
||||||
validRepoNamePattern *regexp.Regexp
|
validRepoNamePattern *regexp.Regexp
|
||||||
invalidRepoNamePattern *regexp.Regexp
|
invalidRepoNamePattern *regexp.Regexp
|
||||||
reservedRepoNames []string
|
reservedRepoNames []string
|
||||||
reservedRepoPatterns []string
|
reservedRepoNamePatterns []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalVars = sync.OnceValue(func() *globalVarsStruct {
|
var globalVars = sync.OnceValue(func() *globalVarsStruct {
|
||||||
return &globalVarsStruct{
|
return &globalVarsStruct{
|
||||||
validRepoNamePattern: regexp.MustCompile(`[-.\w]+`),
|
validRepoNamePattern: regexp.MustCompile(`^[-.\w]+$`),
|
||||||
invalidRepoNamePattern: regexp.MustCompile(`[.]{2,}`),
|
invalidRepoNamePattern: regexp.MustCompile(`[.]{2,}`),
|
||||||
reservedRepoNames: []string{".", "..", "-"},
|
reservedRepoNames: []string{".", "..", "-"},
|
||||||
reservedRepoPatterns: []string{"*.git", "*.wiki", "*.rss", "*.atom"},
|
reservedRepoNamePatterns: []string{"*.wiki", "*.git", "*.rss", "*.atom"},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -86,7 +86,16 @@ func IsUsableRepoName(name string) error {
|
|||||||
// Note: usually this error is normally caught up earlier in the UI
|
// Note: usually this error is normally caught up earlier in the UI
|
||||||
return db.ErrNameCharsNotAllowed{Name: name}
|
return db.ErrNameCharsNotAllowed{Name: name}
|
||||||
}
|
}
|
||||||
return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoPatterns, name)
|
return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoNamePatterns, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsValidSSHAccessRepoName is like IsUsableRepoName, but it allows "*.wiki" because wiki repo needs to be accessed in SSH code
|
||||||
|
func IsValidSSHAccessRepoName(name string) bool {
|
||||||
|
vars := globalVars()
|
||||||
|
if !vars.validRepoNamePattern.MatchString(name) || vars.invalidRepoNamePattern.MatchString(name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoNamePatterns[1:], name) == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrustModelType defines the types of trust model for this repository
|
// TrustModelType defines the types of trust model for this repository
|
||||||
|
@ -216,8 +216,23 @@ func TestIsUsableRepoName(t *testing.T) {
|
|||||||
|
|
||||||
assert.Error(t, IsUsableRepoName("-"))
|
assert.Error(t, IsUsableRepoName("-"))
|
||||||
assert.Error(t, IsUsableRepoName("🌞"))
|
assert.Error(t, IsUsableRepoName("🌞"))
|
||||||
|
assert.Error(t, IsUsableRepoName("the/repo"))
|
||||||
assert.Error(t, IsUsableRepoName("the..repo"))
|
assert.Error(t, IsUsableRepoName("the..repo"))
|
||||||
assert.Error(t, IsUsableRepoName("foo.wiki"))
|
assert.Error(t, IsUsableRepoName("foo.wiki"))
|
||||||
assert.Error(t, IsUsableRepoName("foo.git"))
|
assert.Error(t, IsUsableRepoName("foo.git"))
|
||||||
assert.Error(t, IsUsableRepoName("foo.RSS"))
|
assert.Error(t, IsUsableRepoName("foo.RSS"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsValidSSHAccessRepoName(t *testing.T) {
|
||||||
|
assert.True(t, IsValidSSHAccessRepoName("a"))
|
||||||
|
assert.True(t, IsValidSSHAccessRepoName("-1_."))
|
||||||
|
assert.True(t, IsValidSSHAccessRepoName(".profile"))
|
||||||
|
assert.True(t, IsValidSSHAccessRepoName("foo.wiki"))
|
||||||
|
|
||||||
|
assert.False(t, IsValidSSHAccessRepoName("-"))
|
||||||
|
assert.False(t, IsValidSSHAccessRepoName("🌞"))
|
||||||
|
assert.False(t, IsValidSSHAccessRepoName("the/repo"))
|
||||||
|
assert.False(t, IsValidSSHAccessRepoName("the..repo"))
|
||||||
|
assert.False(t, IsValidSSHAccessRepoName("foo.git"))
|
||||||
|
assert.False(t, IsValidSSHAccessRepoName("foo.RSS"))
|
||||||
|
}
|
||||||
|
@ -249,7 +249,7 @@ func CreatePendingRepositoryTransfer(ctx context.Context, doer, newOwner *user_m
|
|||||||
}
|
}
|
||||||
|
|
||||||
repo.Status = RepositoryPendingTransfer
|
repo.Status = RepositoryPendingTransfer
|
||||||
if err := UpdateRepositoryCols(ctx, repo, "status"); err != nil {
|
if err := UpdateRepositoryColsNoAutoTime(ctx, repo, "status"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ func UpdateRepositoryOwnerNames(ctx context.Context, ownerID int64, ownerName st
|
|||||||
}
|
}
|
||||||
defer committer.Close()
|
defer committer.Close()
|
||||||
|
|
||||||
if _, err := db.GetEngine(ctx).Where("owner_id = ?", ownerID).Cols("owner_name").Update(&Repository{
|
if _, err := db.GetEngine(ctx).Where("owner_id = ?", ownerID).Cols("owner_name").NoAutoTime().Update(&Repository{
|
||||||
OwnerName: ownerName,
|
OwnerName: ownerName,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -40,8 +40,8 @@ func UpdateRepositoryUpdatedTime(ctx context.Context, repoID int64, updateTime t
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRepositoryCols updates repository's columns
|
// UpdateRepositoryColsWithAutoTime updates repository's columns
|
||||||
func UpdateRepositoryCols(ctx context.Context, repo *Repository, cols ...string) error {
|
func UpdateRepositoryColsWithAutoTime(ctx context.Context, repo *Repository, cols ...string) error {
|
||||||
_, err := db.GetEngine(ctx).ID(repo.ID).Cols(cols...).Update(repo)
|
_, err := db.GetEngine(ctx).ID(repo.ID).Cols(cols...).Update(repo)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1203,7 +1203,8 @@ func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, e
|
|||||||
for _, email := range emailAddresses {
|
for _, email := range emailAddresses {
|
||||||
user := users[email.UID]
|
user := users[email.UID]
|
||||||
if user != nil {
|
if user != nil {
|
||||||
results[user.GetEmail()] = user
|
results[user.Email] = user
|
||||||
|
results[user.GetPlaceholderEmail()] = user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1213,6 +1214,7 @@ func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
|
results[user.Email] = user
|
||||||
results[user.GetPlaceholderEmail()] = user
|
results[user.GetPlaceholderEmail()] = user
|
||||||
}
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestIsUsableUsername(t *testing.T) {
|
func TestIsUsableUsername(t *testing.T) {
|
||||||
@ -48,14 +49,23 @@ func TestOAuth2Application_LoadUser(t *testing.T) {
|
|||||||
assert.NotNil(t, user)
|
assert.NotNil(t, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetUserEmailsByNames(t *testing.T) {
|
func TestUserEmails(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
t.Run("GetUserEmailsByNames", func(t *testing.T) {
|
||||||
// ignore none active user email
|
// ignore none active user email
|
||||||
assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user9"}))
|
assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user9"}))
|
||||||
assert.ElementsMatch(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user5"}))
|
assert.ElementsMatch(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user5"}))
|
||||||
|
assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "org7"}))
|
||||||
assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "org7"}))
|
})
|
||||||
|
t.Run("GetUsersByEmails", func(t *testing.T) {
|
||||||
|
m, err := user_model.GetUsersByEmails(db.DefaultContext, []string{"user1@example.com", "user2@" + setting.Service.NoReplyAddress})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, m, 4)
|
||||||
|
assert.EqualValues(t, 1, m["user1@example.com"].ID)
|
||||||
|
assert.EqualValues(t, 1, m["user1@"+setting.Service.NoReplyAddress].ID)
|
||||||
|
assert.EqualValues(t, 2, m["user2@example.com"].ID)
|
||||||
|
assert.EqualValues(t, 2, m["user2@"+setting.Service.NoReplyAddress].ID)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCanCreateOrganization(t *testing.T) {
|
func TestCanCreateOrganization(t *testing.T) {
|
||||||
|
36
modules/git/cmdverb.go
Normal file
36
modules/git/cmdverb.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package git
|
||||||
|
|
||||||
|
const (
|
||||||
|
CmdVerbUploadPack = "git-upload-pack"
|
||||||
|
CmdVerbUploadArchive = "git-upload-archive"
|
||||||
|
CmdVerbReceivePack = "git-receive-pack"
|
||||||
|
CmdVerbLfsAuthenticate = "git-lfs-authenticate"
|
||||||
|
CmdVerbLfsTransfer = "git-lfs-transfer"
|
||||||
|
|
||||||
|
CmdSubVerbLfsUpload = "upload"
|
||||||
|
CmdSubVerbLfsDownload = "download"
|
||||||
|
)
|
||||||
|
|
||||||
|
func IsAllowedVerbForServe(verb string) bool {
|
||||||
|
switch verb {
|
||||||
|
case CmdVerbUploadPack,
|
||||||
|
CmdVerbUploadArchive,
|
||||||
|
CmdVerbReceivePack,
|
||||||
|
CmdVerbLfsAuthenticate,
|
||||||
|
CmdVerbLfsTransfer:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsAllowedVerbForServeLfs(verb string) bool {
|
||||||
|
switch verb {
|
||||||
|
case CmdVerbLfsAuthenticate,
|
||||||
|
CmdVerbLfsTransfer:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@ -166,6 +166,8 @@ type CommitsCountOptions struct {
|
|||||||
Not string
|
Not string
|
||||||
Revision []string
|
Revision []string
|
||||||
RelPath []string
|
RelPath []string
|
||||||
|
Since string
|
||||||
|
Until string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitsCount returns number of total commits of until given revision.
|
// CommitsCount returns number of total commits of until given revision.
|
||||||
@ -199,8 +201,8 @@ func (c *Commit) CommitsCount() (int64, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
|
// CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
|
||||||
func (c *Commit) CommitsByRange(page, pageSize int, not string) ([]*Commit, error) {
|
func (c *Commit) CommitsByRange(page, pageSize int, not, since, until string) ([]*Commit, error) {
|
||||||
return c.repo.commitsByRange(c.ID, page, pageSize, not)
|
return c.repo.commitsByRangeWithTime(c.ID, page, pageSize, not, since, until)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitsBefore returns all the commits before current revision
|
// CommitsBefore returns all the commits before current revision
|
||||||
|
@ -89,7 +89,8 @@ func (repo *Repository) GetCommitByPath(relpath string) (*Commit, error) {
|
|||||||
return commits[0], nil
|
return commits[0], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not string) ([]*Commit, error) {
|
// commitsByRangeWithTime returns the specific page commits before current revision, with not, since, until support
|
||||||
|
func (repo *Repository) commitsByRangeWithTime(id ObjectID, page, pageSize int, not, since, until string) ([]*Commit, error) {
|
||||||
cmd := NewCommand("log").
|
cmd := NewCommand("log").
|
||||||
AddOptionFormat("--skip=%d", (page-1)*pageSize).
|
AddOptionFormat("--skip=%d", (page-1)*pageSize).
|
||||||
AddOptionFormat("--max-count=%d", pageSize).
|
AddOptionFormat("--max-count=%d", pageSize).
|
||||||
@ -99,6 +100,12 @@ func (repo *Repository) commitsByRange(id ObjectID, page, pageSize int, not stri
|
|||||||
if not != "" {
|
if not != "" {
|
||||||
cmd.AddOptionValues("--not", not)
|
cmd.AddOptionValues("--not", not)
|
||||||
}
|
}
|
||||||
|
if since != "" {
|
||||||
|
cmd.AddOptionFormat("--since=%s", since)
|
||||||
|
}
|
||||||
|
if until != "" {
|
||||||
|
cmd.AddOptionFormat("--until=%s", until)
|
||||||
|
}
|
||||||
|
|
||||||
stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
|
stdout, _, err := cmd.RunStdBytes(repo.Ctx, &RunOpts{Dir: repo.Path})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -212,6 +219,8 @@ type CommitsByFileAndRangeOptions struct {
|
|||||||
File string
|
File string
|
||||||
Not string
|
Not string
|
||||||
Page int
|
Page int
|
||||||
|
Since string
|
||||||
|
Until string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitsByFileAndRange return the commits according revision file and the page
|
// CommitsByFileAndRange return the commits according revision file and the page
|
||||||
@ -231,6 +240,12 @@ func (repo *Repository) CommitsByFileAndRange(opts CommitsByFileAndRangeOptions)
|
|||||||
if opts.Not != "" {
|
if opts.Not != "" {
|
||||||
gitCmd.AddOptionValues("--not", opts.Not)
|
gitCmd.AddOptionValues("--not", opts.Not)
|
||||||
}
|
}
|
||||||
|
if opts.Since != "" {
|
||||||
|
gitCmd.AddOptionFormat("--since=%s", opts.Since)
|
||||||
|
}
|
||||||
|
if opts.Until != "" {
|
||||||
|
gitCmd.AddOptionFormat("--until=%s", opts.Until)
|
||||||
|
}
|
||||||
|
|
||||||
gitCmd.AddDashesAndList(opts.File)
|
gitCmd.AddDashesAndList(opts.File)
|
||||||
err := gitCmd.Run(repo.Ctx, &RunOpts{
|
err := gitCmd.Run(repo.Ctx, &RunOpts{
|
||||||
|
@ -40,7 +40,9 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
|
|||||||
|
|
||||||
since := fromTime.Format(time.RFC3339)
|
since := fromTime.Format(time.RFC3339)
|
||||||
|
|
||||||
stdout, _, runErr := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").AddOptionFormat("--since='%s'", since).RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
|
stdout, _, runErr := NewCommand("rev-list", "--count", "--no-merges", "--branches=*", "--date=iso").
|
||||||
|
AddOptionFormat("--since=%s", since).
|
||||||
|
RunStdString(repo.Ctx, &RunOpts{Dir: repo.Path})
|
||||||
if runErr != nil {
|
if runErr != nil {
|
||||||
return nil, runErr
|
return nil, runErr
|
||||||
}
|
}
|
||||||
@ -60,7 +62,8 @@ func (repo *Repository) GetCodeActivityStats(fromTime time.Time, branch string)
|
|||||||
_ = stdoutWriter.Close()
|
_ = stdoutWriter.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
gitCmd := NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").AddOptionFormat("--since='%s'", since)
|
gitCmd := NewCommand("log", "--numstat", "--no-merges", "--pretty=format:---%n%h%n%aN%n%aE%n", "--date=iso").
|
||||||
|
AddOptionFormat("--since=%s", since)
|
||||||
if len(branch) == 0 {
|
if len(branch) == 0 {
|
||||||
gitCmd.AddArguments("--branches=*")
|
gitCmd.AddArguments("--branches=*")
|
||||||
} else {
|
} else {
|
||||||
|
@ -46,18 +46,16 @@ type ServCommandResults struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ServCommand preps for a serv call
|
// ServCommand preps for a serv call
|
||||||
func ServCommand(ctx context.Context, keyID int64, ownerName, repoName string, mode perm.AccessMode, verbs ...string) (*ServCommandResults, ResponseExtra) {
|
func ServCommand(ctx context.Context, keyID int64, ownerName, repoName string, mode perm.AccessMode, verb, lfsVerb string) (*ServCommandResults, ResponseExtra) {
|
||||||
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/serv/command/%d/%s/%s?mode=%d",
|
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/serv/command/%d/%s/%s?mode=%d",
|
||||||
keyID,
|
keyID,
|
||||||
url.PathEscape(ownerName),
|
url.PathEscape(ownerName),
|
||||||
url.PathEscape(repoName),
|
url.PathEscape(repoName),
|
||||||
mode,
|
mode,
|
||||||
)
|
)
|
||||||
for _, verb := range verbs {
|
reqURL += "&verb=" + url.QueryEscape(verb)
|
||||||
if verb != "" {
|
// reqURL += "&lfs_verb=" + url.QueryEscape(lfsVerb) // TODO: actually there is no use of this parameter. In the future, the URL construction should be more flexible
|
||||||
reqURL += "&verb=" + url.QueryEscape(verb)
|
_ = lfsVerb
|
||||||
}
|
|
||||||
}
|
|
||||||
req := newInternalRequestAPI(ctx, reqURL, "GET")
|
req := newInternalRequestAPI(ctx, reqURL, "GET")
|
||||||
return requestJSONResp(req, &ServCommandResults{})
|
return requestJSONResp(req, &ServCommandResults{})
|
||||||
}
|
}
|
||||||
|
@ -200,5 +200,3 @@ func TestListToPushCommits(t *testing.T) {
|
|||||||
assert.Equal(t, now, pushCommits.Commits[1].Timestamp)
|
assert.Equal(t, now, pushCommits.Commits[1].Timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO TestPushUpdate
|
|
||||||
|
@ -9,13 +9,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
git_model "code.gitea.io/gitea/models/git"
|
git_model "code.gitea.io/gitea/models/git"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
|
||||||
"code.gitea.io/gitea/modules/container"
|
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/gitrepo"
|
"code.gitea.io/gitea/modules/gitrepo"
|
||||||
"code.gitea.io/gitea/modules/lfs"
|
"code.gitea.io/gitea/modules/lfs"
|
||||||
@ -59,118 +56,6 @@ func SyncRepoTags(ctx context.Context, repoID int64) error {
|
|||||||
return SyncReleasesWithTags(ctx, repo, gitRepo)
|
return SyncReleasesWithTags(ctx, repo, gitRepo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncReleasesWithTags synchronizes release table with repository tags
|
|
||||||
func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) error {
|
|
||||||
log.Debug("SyncReleasesWithTags: in Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name)
|
|
||||||
|
|
||||||
// optimized procedure for pull-mirrors which saves a lot of time (in
|
|
||||||
// particular for repos with many tags).
|
|
||||||
if repo.IsMirror {
|
|
||||||
return pullMirrorReleaseSync(ctx, repo, gitRepo)
|
|
||||||
}
|
|
||||||
|
|
||||||
existingRelTags := make(container.Set[string])
|
|
||||||
opts := repo_model.FindReleasesOptions{
|
|
||||||
IncludeDrafts: true,
|
|
||||||
IncludeTags: true,
|
|
||||||
ListOptions: db.ListOptions{PageSize: 50},
|
|
||||||
RepoID: repo.ID,
|
|
||||||
}
|
|
||||||
for page := 1; ; page++ {
|
|
||||||
opts.Page = page
|
|
||||||
rels, err := db.Find[repo_model.Release](gitRepo.Ctx, opts)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to GetReleasesByRepoID in Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
|
|
||||||
}
|
|
||||||
if len(rels) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
for _, rel := range rels {
|
|
||||||
if rel.IsDraft {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
commitID, err := gitRepo.GetTagCommitID(rel.TagName)
|
|
||||||
if err != nil && !git.IsErrNotExist(err) {
|
|
||||||
return fmt.Errorf("unable to GetTagCommitID for %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err)
|
|
||||||
}
|
|
||||||
if git.IsErrNotExist(err) || commitID != rel.Sha1 {
|
|
||||||
if err := repo_model.PushUpdateDeleteTag(ctx, repo, rel.TagName); err != nil {
|
|
||||||
return fmt.Errorf("unable to PushUpdateDeleteTag: %q in Repo[%d:%s/%s]: %w", rel.TagName, repo.ID, repo.OwnerName, repo.Name, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
existingRelTags.Add(strings.ToLower(rel.TagName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := gitRepo.WalkReferences(git.ObjectTag, 0, 0, func(sha1, refname string) error {
|
|
||||||
tagName := strings.TrimPrefix(refname, git.TagPrefix)
|
|
||||||
if existingRelTags.Contains(strings.ToLower(tagName)) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := PushUpdateAddTag(ctx, repo, gitRepo, tagName, sha1, refname); err != nil {
|
|
||||||
// sometimes, some tags will be sync failed. i.e. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tag/?h=v2.6.11
|
|
||||||
// this is a tree object, not a tag object which created before git
|
|
||||||
log.Error("unable to PushUpdateAddTag: %q to Repo[%d:%s/%s]: %v", tagName, repo.ID, repo.OwnerName, repo.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PushUpdateAddTag must be called for any push actions to add tag
|
|
||||||
func PushUpdateAddTag(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, tagName, sha1, refname string) error {
|
|
||||||
tag, err := gitRepo.GetTagWithID(sha1, tagName)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to GetTag: %w", err)
|
|
||||||
}
|
|
||||||
commit, err := gitRepo.GetTagCommit(tag.Name)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to get tag Commit: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := tag.Tagger
|
|
||||||
if sig == nil {
|
|
||||||
sig = commit.Author
|
|
||||||
}
|
|
||||||
if sig == nil {
|
|
||||||
sig = commit.Committer
|
|
||||||
}
|
|
||||||
|
|
||||||
var author *user_model.User
|
|
||||||
createdAt := time.Unix(1, 0)
|
|
||||||
|
|
||||||
if sig != nil {
|
|
||||||
author, err = user_model.GetUserByEmail(ctx, sig.Email)
|
|
||||||
if err != nil && !user_model.IsErrUserNotExist(err) {
|
|
||||||
return fmt.Errorf("unable to GetUserByEmail for %q: %w", sig.Email, err)
|
|
||||||
}
|
|
||||||
createdAt = sig.When
|
|
||||||
}
|
|
||||||
|
|
||||||
commitsCount, err := commit.CommitsCount()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to get CommitsCount: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rel := repo_model.Release{
|
|
||||||
RepoID: repo.ID,
|
|
||||||
TagName: tagName,
|
|
||||||
LowerTagName: strings.ToLower(tagName),
|
|
||||||
Sha1: commit.ID.String(),
|
|
||||||
NumCommits: commitsCount,
|
|
||||||
CreatedUnix: timeutil.TimeStamp(createdAt.Unix()),
|
|
||||||
IsTag: true,
|
|
||||||
}
|
|
||||||
if author != nil {
|
|
||||||
rel.PublisherID = author.ID
|
|
||||||
}
|
|
||||||
|
|
||||||
return repo_model.SaveOrUpdateTag(ctx, repo, &rel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreMissingLfsObjectsInRepository downloads missing LFS objects
|
// StoreMissingLfsObjectsInRepository downloads missing LFS objects
|
||||||
func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, lfsClient lfs.Client) error {
|
func StoreMissingLfsObjectsInRepository(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, lfsClient lfs.Client) error {
|
||||||
contentStore := lfs.NewContentStore()
|
contentStore := lfs.NewContentStore()
|
||||||
@ -286,18 +171,19 @@ func (shortRelease) TableName() string {
|
|||||||
return "release"
|
return "release"
|
||||||
}
|
}
|
||||||
|
|
||||||
// pullMirrorReleaseSync is a pull-mirror specific tag<->release table
|
// SyncReleasesWithTags is a tag<->release table
|
||||||
// synchronization which overwrites all Releases from the repository tags. This
|
// synchronization which overwrites all Releases from the repository tags. This
|
||||||
// can be relied on since a pull-mirror is always identical to its
|
// can be relied on since a pull-mirror is always identical to its
|
||||||
// upstream. Hence, after each sync we want the pull-mirror release set to be
|
// upstream. Hence, after each sync we want the release set to be
|
||||||
// identical to the upstream tag set. This is much more efficient for
|
// identical to the upstream tag set. This is much more efficient for
|
||||||
// repositories like https://github.com/vim/vim (with over 13000 tags).
|
// repositories like https://github.com/vim/vim (with over 13000 tags).
|
||||||
func pullMirrorReleaseSync(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) error {
|
func SyncReleasesWithTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository) error {
|
||||||
log.Trace("pullMirrorReleaseSync: rebuilding releases for pull-mirror Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name)
|
log.Debug("SyncReleasesWithTags: in Repo[%d:%s/%s]", repo.ID, repo.OwnerName, repo.Name)
|
||||||
tags, numTags, err := gitRepo.GetTagInfos(0, 0)
|
tags, _, err := gitRepo.GetTagInfos(0, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to GetTagInfos in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
|
return fmt.Errorf("unable to GetTagInfos in pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
|
||||||
}
|
}
|
||||||
|
var added, deleted, updated int
|
||||||
err = db.WithTx(ctx, func(ctx context.Context) error {
|
err = db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
dbReleases, err := db.Find[shortRelease](ctx, repo_model.FindReleasesOptions{
|
dbReleases, err := db.Find[shortRelease](ctx, repo_model.FindReleasesOptions{
|
||||||
RepoID: repo.ID,
|
RepoID: repo.ID,
|
||||||
@ -318,9 +204,7 @@ func pullMirrorReleaseSync(ctx context.Context, repo *repo_model.Repository, git
|
|||||||
TagName: tag.Name,
|
TagName: tag.Name,
|
||||||
LowerTagName: strings.ToLower(tag.Name),
|
LowerTagName: strings.ToLower(tag.Name),
|
||||||
Sha1: tag.Object.String(),
|
Sha1: tag.Object.String(),
|
||||||
// NOTE: ignored, since NumCommits are unused
|
// NOTE: ignored, The NumCommits value is calculated and cached on demand when the UI requires it.
|
||||||
// for pull-mirrors (only relevant when
|
|
||||||
// displaying releases, IsTag: false)
|
|
||||||
NumCommits: -1,
|
NumCommits: -1,
|
||||||
CreatedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()),
|
CreatedUnix: timeutil.TimeStamp(tag.Tagger.When.Unix()),
|
||||||
IsTag: true,
|
IsTag: true,
|
||||||
@ -349,13 +233,14 @@ func pullMirrorReleaseSync(ctx context.Context, repo *repo_model.Repository, git
|
|||||||
return fmt.Errorf("unable to update tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err)
|
return fmt.Errorf("unable to update tag %s for pull-mirror Repo[%d:%s/%s]: %w", tag.Name, repo.ID, repo.OwnerName, repo.Name, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
added, deleted, updated = len(deletes), len(updates), len(inserts)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to rebuild release table for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
|
return fmt.Errorf("unable to rebuild release table for pull-mirror Repo[%d:%s/%s]: %w", repo.ID, repo.OwnerName, repo.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Trace("pullMirrorReleaseSync: done rebuilding %d releases", numTags)
|
log.Trace("SyncReleasesWithTags: %d tags added, %d tags deleted, %d tags updated", added, deleted, updated)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,6 +101,8 @@ type Repository struct {
|
|||||||
AllowSquash bool `json:"allow_squash_merge"`
|
AllowSquash bool `json:"allow_squash_merge"`
|
||||||
AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge"`
|
AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge"`
|
||||||
AllowRebaseUpdate bool `json:"allow_rebase_update"`
|
AllowRebaseUpdate bool `json:"allow_rebase_update"`
|
||||||
|
AllowManualMerge bool `json:"allow_manual_merge"`
|
||||||
|
AutodetectManualMerge bool `json:"autodetect_manual_merge"`
|
||||||
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"`
|
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge"`
|
||||||
DefaultMergeStyle string `json:"default_merge_style"`
|
DefaultMergeStyle string `json:"default_merge_style"`
|
||||||
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"`
|
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit"`
|
||||||
|
@ -162,22 +162,6 @@ func NewFuncMap() template.FuncMap {
|
|||||||
|
|
||||||
"FilenameIsImage": filenameIsImage,
|
"FilenameIsImage": filenameIsImage,
|
||||||
"TabSizeClass": tabSizeClass,
|
"TabSizeClass": tabSizeClass,
|
||||||
|
|
||||||
// for backward compatibility only, do not use them anymore
|
|
||||||
"TimeSince": timeSinceLegacy,
|
|
||||||
"TimeSinceUnix": timeSinceLegacy,
|
|
||||||
"DateTime": dateTimeLegacy,
|
|
||||||
|
|
||||||
"RenderEmoji": renderEmojiLegacy,
|
|
||||||
"RenderLabel": renderLabelLegacy,
|
|
||||||
"RenderLabels": renderLabelsLegacy,
|
|
||||||
"RenderIssueTitle": renderIssueTitleLegacy,
|
|
||||||
|
|
||||||
"RenderMarkdownToHtml": renderMarkdownToHtmlLegacy,
|
|
||||||
|
|
||||||
"RenderCommitMessage": renderCommitMessageLegacy,
|
|
||||||
"RenderCommitMessageLinkSubject": renderCommitMessageLinkSubjectLegacy,
|
|
||||||
"RenderCommitBody": renderCommitBodyLegacy,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -367,7 +351,3 @@ func QueryBuild(a ...any) template.URL {
|
|||||||
}
|
}
|
||||||
return template.URL(s)
|
return template.URL(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func panicIfDevOrTesting() {
|
|
||||||
setting.PanicInDevOrTesting("legacy template functions are for backward compatibility only, do not use them in new code")
|
|
||||||
}
|
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package templates
|
|
||||||
|
|
||||||
import (
|
|
||||||
"html/template"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/translation"
|
|
||||||
)
|
|
||||||
|
|
||||||
func dateTimeLegacy(format string, datetime any, _ ...string) template.HTML {
|
|
||||||
panicIfDevOrTesting()
|
|
||||||
if s, ok := datetime.(string); ok {
|
|
||||||
datetime = parseLegacy(s)
|
|
||||||
}
|
|
||||||
return dateTimeFormat(format, datetime)
|
|
||||||
}
|
|
||||||
|
|
||||||
func timeSinceLegacy(time any, _ translation.Locale) template.HTML {
|
|
||||||
panicIfDevOrTesting()
|
|
||||||
return TimeSince(time)
|
|
||||||
}
|
|
@ -17,12 +17,12 @@ import (
|
|||||||
func TestDateTime(t *testing.T) {
|
func TestDateTime(t *testing.T) {
|
||||||
testTz, _ := time.LoadLocation("America/New_York")
|
testTz, _ := time.LoadLocation("America/New_York")
|
||||||
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
|
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
|
||||||
|
defer test.MockVariableValue(&setting.IsProd, true)()
|
||||||
defer test.MockVariableValue(&setting.IsInTesting, false)()
|
defer test.MockVariableValue(&setting.IsInTesting, false)()
|
||||||
|
|
||||||
du := NewDateUtils()
|
du := NewDateUtils()
|
||||||
|
|
||||||
refTimeStr := "2018-01-01T00:00:00Z"
|
refTimeStr := "2018-01-01T00:00:00Z"
|
||||||
refDateStr := "2018-01-01"
|
|
||||||
refTime, _ := time.Parse(time.RFC3339, refTimeStr)
|
refTime, _ := time.Parse(time.RFC3339, refTimeStr)
|
||||||
refTimeStamp := timeutil.TimeStamp(refTime.Unix())
|
refTimeStamp := timeutil.TimeStamp(refTime.Unix())
|
||||||
|
|
||||||
@ -31,18 +31,9 @@ func TestDateTime(t *testing.T) {
|
|||||||
assert.EqualValues(t, "-", du.AbsoluteShort(time.Time{}))
|
assert.EqualValues(t, "-", du.AbsoluteShort(time.Time{}))
|
||||||
assert.EqualValues(t, "-", du.AbsoluteShort(timeutil.TimeStamp(0)))
|
assert.EqualValues(t, "-", du.AbsoluteShort(timeutil.TimeStamp(0)))
|
||||||
|
|
||||||
actual := dateTimeLegacy("short", "invalid")
|
actual := du.AbsoluteShort(refTime)
|
||||||
assert.EqualValues(t, `-`, actual)
|
|
||||||
|
|
||||||
actual = dateTimeLegacy("short", refTimeStr)
|
|
||||||
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</absolute-date>`, actual)
|
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</absolute-date>`, actual)
|
||||||
|
|
||||||
actual = du.AbsoluteShort(refTime)
|
|
||||||
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00Z">2018-01-01</absolute-date>`, actual)
|
|
||||||
|
|
||||||
actual = dateTimeLegacy("short", refDateStr)
|
|
||||||
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2018-01-01T00:00:00-05:00">2018-01-01</absolute-date>`, actual)
|
|
||||||
|
|
||||||
actual = du.AbsoluteShort(refTimeStamp)
|
actual = du.AbsoluteShort(refTimeStamp)
|
||||||
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2017-12-31T19:00:00-05:00">2017-12-31</absolute-date>`, actual)
|
assert.EqualValues(t, `<absolute-date weekday="" year="numeric" month="short" day="numeric" date="2017-12-31T19:00:00-05:00">2017-12-31</absolute-date>`, actual)
|
||||||
|
|
||||||
@ -53,6 +44,7 @@ func TestDateTime(t *testing.T) {
|
|||||||
func TestTimeSince(t *testing.T) {
|
func TestTimeSince(t *testing.T) {
|
||||||
testTz, _ := time.LoadLocation("America/New_York")
|
testTz, _ := time.LoadLocation("America/New_York")
|
||||||
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
|
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
|
||||||
|
defer test.MockVariableValue(&setting.IsProd, true)()
|
||||||
defer test.MockVariableValue(&setting.IsInTesting, false)()
|
defer test.MockVariableValue(&setting.IsInTesting, false)()
|
||||||
|
|
||||||
du := NewDateUtils()
|
du := NewDateUtils()
|
||||||
@ -67,6 +59,6 @@ func TestTimeSince(t *testing.T) {
|
|||||||
actual = timeSinceTo(&refTime, time.Time{})
|
actual = timeSinceTo(&refTime, time.Time{})
|
||||||
assert.EqualValues(t, `<relative-time prefix="" tense="future" datetime="2018-01-01T00:00:00Z" data-tooltip-content data-tooltip-interactive="true">2018-01-01 00:00:00 +00:00</relative-time>`, actual)
|
assert.EqualValues(t, `<relative-time prefix="" tense="future" datetime="2018-01-01T00:00:00Z" data-tooltip-content data-tooltip-interactive="true">2018-01-01 00:00:00 +00:00</relative-time>`, actual)
|
||||||
|
|
||||||
actual = timeSinceLegacy(timeutil.TimeStampNano(refTime.UnixNano()), nil)
|
actual = du.TimeSince(timeutil.TimeStampNano(refTime.UnixNano()))
|
||||||
assert.EqualValues(t, `<relative-time prefix="" tense="past" datetime="2017-12-31T19:00:00-05:00" data-tooltip-content data-tooltip-interactive="true">2017-12-31 19:00:00 -05:00</relative-time>`, actual)
|
assert.EqualValues(t, `<relative-time prefix="" tense="past" datetime="2017-12-31T19:00:00-05:00" data-tooltip-content data-tooltip-interactive="true">2017-12-31 19:00:00 -05:00</relative-time>`, actual)
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,8 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
"code.gitea.io/gitea/models/renderhelper"
|
||||||
|
"code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/emoji"
|
"code.gitea.io/gitea/modules/emoji"
|
||||||
"code.gitea.io/gitea/modules/htmlutil"
|
"code.gitea.io/gitea/modules/htmlutil"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@ -34,11 +36,11 @@ func NewRenderUtils(ctx reqctx.RequestContext) *RenderUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RenderCommitMessage renders commit message with XSS-safe and special links.
|
// RenderCommitMessage renders commit message with XSS-safe and special links.
|
||||||
func (ut *RenderUtils) RenderCommitMessage(msg string, metas map[string]string) template.HTML {
|
func (ut *RenderUtils) RenderCommitMessage(msg string, repo *repo.Repository) template.HTML {
|
||||||
cleanMsg := template.HTMLEscapeString(msg)
|
cleanMsg := template.HTMLEscapeString(msg)
|
||||||
// we can safely assume that it will not return any error, since there
|
// we can safely assume that it will not return any error, since there
|
||||||
// shouldn't be any special HTML.
|
// shouldn't be any special HTML.
|
||||||
fullMessage, err := markup.PostProcessCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), cleanMsg)
|
fullMessage, err := markup.PostProcessCommitMessage(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), cleanMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("PostProcessCommitMessage: %v", err)
|
log.Error("PostProcessCommitMessage: %v", err)
|
||||||
return ""
|
return ""
|
||||||
@ -52,7 +54,7 @@ func (ut *RenderUtils) RenderCommitMessage(msg string, metas map[string]string)
|
|||||||
|
|
||||||
// RenderCommitMessageLinkSubject renders commit message as a XSS-safe link to
|
// RenderCommitMessageLinkSubject renders commit message as a XSS-safe link to
|
||||||
// the provided default url, handling for special links without email to links.
|
// the provided default url, handling for special links without email to links.
|
||||||
func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, metas map[string]string) template.HTML {
|
func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, repo *repo.Repository) template.HTML {
|
||||||
msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace)
|
msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace)
|
||||||
lineEnd := strings.IndexByte(msgLine, '\n')
|
lineEnd := strings.IndexByte(msgLine, '\n')
|
||||||
if lineEnd > 0 {
|
if lineEnd > 0 {
|
||||||
@ -63,9 +65,8 @@ func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, me
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// we can safely assume that it will not return any error, since there
|
// we can safely assume that it will not return any error, since there shouldn't be any special HTML.
|
||||||
// shouldn't be any special HTML.
|
renderedMessage, err := markup.PostProcessCommitMessageSubject(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), urlDefault, template.HTMLEscapeString(msgLine))
|
||||||
renderedMessage, err := markup.PostProcessCommitMessageSubject(markup.NewRenderContext(ut.ctx).WithMetas(metas), urlDefault, template.HTMLEscapeString(msgLine))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("PostProcessCommitMessageSubject: %v", err)
|
log.Error("PostProcessCommitMessageSubject: %v", err)
|
||||||
return ""
|
return ""
|
||||||
@ -74,7 +75,7 @@ func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, me
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RenderCommitBody extracts the body of a commit message without its title.
|
// RenderCommitBody extracts the body of a commit message without its title.
|
||||||
func (ut *RenderUtils) RenderCommitBody(msg string, metas map[string]string) template.HTML {
|
func (ut *RenderUtils) RenderCommitBody(msg string, repo *repo.Repository) template.HTML {
|
||||||
msgLine := strings.TrimSpace(msg)
|
msgLine := strings.TrimSpace(msg)
|
||||||
lineEnd := strings.IndexByte(msgLine, '\n')
|
lineEnd := strings.IndexByte(msgLine, '\n')
|
||||||
if lineEnd > 0 {
|
if lineEnd > 0 {
|
||||||
@ -87,7 +88,7 @@ func (ut *RenderUtils) RenderCommitBody(msg string, metas map[string]string) tem
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
renderedMessage, err := markup.PostProcessCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(msgLine))
|
renderedMessage, err := markup.PostProcessCommitMessage(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), template.HTMLEscapeString(msgLine))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("PostProcessCommitMessage: %v", err)
|
log.Error("PostProcessCommitMessage: %v", err)
|
||||||
return ""
|
return ""
|
||||||
@ -105,8 +106,8 @@ func renderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RenderIssueTitle renders issue/pull title with defined post processors
|
// RenderIssueTitle renders issue/pull title with defined post processors
|
||||||
func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
|
func (ut *RenderUtils) RenderIssueTitle(text string, repo *repo.Repository) template.HTML {
|
||||||
renderedText, err := markup.PostProcessIssueTitle(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(text))
|
renderedText, err := markup.PostProcessIssueTitle(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), template.HTMLEscapeString(text))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("PostProcessIssueTitle: %v", err)
|
log.Error("PostProcessIssueTitle: %v", err)
|
||||||
return ""
|
return ""
|
||||||
|
@ -1,53 +0,0 @@
|
|||||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package templates
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"html/template"
|
|
||||||
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
|
||||||
"code.gitea.io/gitea/modules/reqctx"
|
|
||||||
"code.gitea.io/gitea/modules/translation"
|
|
||||||
)
|
|
||||||
|
|
||||||
func renderEmojiLegacy(ctx context.Context, text string) template.HTML {
|
|
||||||
panicIfDevOrTesting()
|
|
||||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderEmoji(text)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderLabelLegacy(ctx context.Context, locale translation.Locale, label *issues_model.Label) template.HTML {
|
|
||||||
panicIfDevOrTesting()
|
|
||||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderLabel(label)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderLabelsLegacy(ctx context.Context, locale translation.Locale, labels []*issues_model.Label, repoLink string, issue *issues_model.Issue) template.HTML {
|
|
||||||
panicIfDevOrTesting()
|
|
||||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderLabels(labels, repoLink, issue)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderMarkdownToHtmlLegacy(ctx context.Context, input string) template.HTML { //nolint:revive
|
|
||||||
panicIfDevOrTesting()
|
|
||||||
return NewRenderUtils(reqctx.FromContext(ctx)).MarkdownToHtml(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderCommitMessageLegacy(ctx context.Context, msg string, metas map[string]string) template.HTML {
|
|
||||||
panicIfDevOrTesting()
|
|
||||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitMessage(msg, metas)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderCommitMessageLinkSubjectLegacy(ctx context.Context, msg, urlDefault string, metas map[string]string) template.HTML {
|
|
||||||
panicIfDevOrTesting()
|
|
||||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitMessageLinkSubject(msg, urlDefault, metas)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderIssueTitleLegacy(ctx context.Context, text string, metas map[string]string) template.HTML {
|
|
||||||
panicIfDevOrTesting()
|
|
||||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderIssueTitle(text, metas)
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderCommitBodyLegacy(ctx context.Context, msg string, metas map[string]string) template.HTML {
|
|
||||||
panicIfDevOrTesting()
|
|
||||||
return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitBody(msg, metas)
|
|
||||||
}
|
|
@ -11,11 +11,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/issues"
|
"code.gitea.io/gitea/models/issues"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/modules/git"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
"code.gitea.io/gitea/modules/markup"
|
"code.gitea.io/gitea/modules/markup"
|
||||||
"code.gitea.io/gitea/modules/reqctx"
|
"code.gitea.io/gitea/modules/reqctx"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
|
|
||||||
@ -47,19 +47,8 @@ mail@domain.com
|
|||||||
return strings.ReplaceAll(s, "<SPACE>", " ")
|
return strings.ReplaceAll(s, "<SPACE>", " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
var testMetas = map[string]string{
|
|
||||||
"user": "user13",
|
|
||||||
"repo": "repo11",
|
|
||||||
"repoPath": "../../tests/gitea-repositories-meta/user13/repo11.git/",
|
|
||||||
"markdownNewLineHardBreak": "true",
|
|
||||||
"markupAllowShortIssuePattern": "true",
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
func TestMain(m *testing.M) {
|
||||||
unittest.InitSettingsForTesting()
|
setting.Markdown.RenderOptionsComment.ShortIssuePattern = true
|
||||||
if err := git.InitSimple(context.Background()); err != nil {
|
|
||||||
log.Fatal("git init failed, err: %v", err)
|
|
||||||
}
|
|
||||||
markup.Init(&markup.RenderHelperFuncs{
|
markup.Init(&markup.RenderHelperFuncs{
|
||||||
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
||||||
return username == "mention-user"
|
return username == "mention-user"
|
||||||
@ -74,46 +63,52 @@ func newTestRenderUtils(t *testing.T) *RenderUtils {
|
|||||||
return NewRenderUtils(ctx)
|
return NewRenderUtils(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderCommitBody(t *testing.T) {
|
func TestRenderRepoComment(t *testing.T) {
|
||||||
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
mockRepo := &repo.Repository{
|
||||||
type args struct {
|
ID: 1, OwnerName: "user13", Name: "repo11",
|
||||||
msg string
|
Owner: &user_model.User{ID: 13, Name: "user13"},
|
||||||
|
Units: []*repo.RepoUnit{},
|
||||||
}
|
}
|
||||||
tests := []struct {
|
t.Run("RenderCommitBody", func(t *testing.T) {
|
||||||
name string
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||||
args args
|
type args struct {
|
||||||
want template.HTML
|
msg string
|
||||||
}{
|
}
|
||||||
{
|
tests := []struct {
|
||||||
name: "multiple lines",
|
name string
|
||||||
args: args{
|
args args
|
||||||
msg: "first line\nsecond line",
|
want template.HTML
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "multiple lines",
|
||||||
|
args: args{
|
||||||
|
msg: "first line\nsecond line",
|
||||||
|
},
|
||||||
|
want: "second line",
|
||||||
},
|
},
|
||||||
want: "second line",
|
{
|
||||||
},
|
name: "multiple lines with leading newlines",
|
||||||
{
|
args: args{
|
||||||
name: "multiple lines with leading newlines",
|
msg: "\n\n\n\nfirst line\nsecond line",
|
||||||
args: args{
|
},
|
||||||
msg: "\n\n\n\nfirst line\nsecond line",
|
want: "second line",
|
||||||
},
|
},
|
||||||
want: "second line",
|
{
|
||||||
},
|
name: "multiple lines with trailing newlines",
|
||||||
{
|
args: args{
|
||||||
name: "multiple lines with trailing newlines",
|
msg: "first line\nsecond line\n\n\n",
|
||||||
args: args{
|
},
|
||||||
msg: "first line\nsecond line\n\n\n",
|
want: "second line",
|
||||||
},
|
},
|
||||||
want: "second line",
|
}
|
||||||
},
|
ut := newTestRenderUtils(t)
|
||||||
}
|
for _, tt := range tests {
|
||||||
ut := newTestRenderUtils(t)
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
for _, tt := range tests {
|
assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, mockRepo), "RenderCommitBody(%v, %v)", tt.args.msg, nil)
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
})
|
||||||
assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, nil), "RenderCommitBody(%v, %v)", tt.args.msg, nil)
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := `/just/a/path.bin
|
expected := `/just/a/path.bin
|
||||||
<a href="https://example.com/file.bin">https://example.com/file.bin</a>
|
<a href="https://example.com/file.bin">https://example.com/file.bin</a>
|
||||||
[local link](file.bin)
|
[local link](file.bin)
|
||||||
[remote link](<a href="https://example.com">https://example.com</a>)
|
[remote link](<a href="https://example.com">https://example.com</a>)
|
||||||
@ -132,22 +127,22 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
|||||||
<a href="/mention-user">@mention-user</a> test
|
<a href="/mention-user">@mention-user</a> test
|
||||||
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||||
space`
|
space`
|
||||||
assert.Equal(t, expected, string(newTestRenderUtils(t).RenderCommitBody(testInput(), testMetas)))
|
assert.Equal(t, expected, string(newTestRenderUtils(t).RenderCommitBody(testInput(), mockRepo)))
|
||||||
}
|
})
|
||||||
|
|
||||||
func TestRenderCommitMessage(t *testing.T) {
|
t.Run("RenderCommitMessage", func(t *testing.T) {
|
||||||
expected := `space <a href="/mention-user" data-markdown-generated-content="">@mention-user</a> `
|
expected := `space <a href="/mention-user" data-markdown-generated-content="">@mention-user</a> `
|
||||||
assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessage(testInput(), testMetas))
|
assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessage(testInput(), mockRepo))
|
||||||
}
|
})
|
||||||
|
|
||||||
func TestRenderCommitMessageLinkSubject(t *testing.T) {
|
t.Run("RenderCommitMessageLinkSubject", func(t *testing.T) {
|
||||||
expected := `<a href="https://example.com/link" class="muted">space </a><a href="/mention-user" data-markdown-generated-content="">@mention-user</a>`
|
expected := `<a href="https://example.com/link" class="muted">space </a><a href="/mention-user" data-markdown-generated-content="">@mention-user</a>`
|
||||||
assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", testMetas))
|
assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", mockRepo))
|
||||||
}
|
})
|
||||||
|
|
||||||
func TestRenderIssueTitle(t *testing.T) {
|
t.Run("RenderIssueTitle", func(t *testing.T) {
|
||||||
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
||||||
expected := ` space @mention-user<SPACE><SPACE>
|
expected := ` space @mention-user<SPACE><SPACE>
|
||||||
/just/a/path.bin
|
/just/a/path.bin
|
||||||
https://example.com/file.bin
|
https://example.com/file.bin
|
||||||
[local link](file.bin)
|
[local link](file.bin)
|
||||||
@ -168,8 +163,9 @@ mail@domain.com
|
|||||||
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
||||||
space<SPACE><SPACE>
|
space<SPACE><SPACE>
|
||||||
`
|
`
|
||||||
expected = strings.ReplaceAll(expected, "<SPACE>", " ")
|
expected = strings.ReplaceAll(expected, "<SPACE>", " ")
|
||||||
assert.Equal(t, expected, string(newTestRenderUtils(t).RenderIssueTitle(testInput(), testMetas)))
|
assert.Equal(t, expected, string(newTestRenderUtils(t).RenderIssueTitle(testInput(), mockRepo)))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRenderMarkdownToHtml(t *testing.T) {
|
func TestRenderMarkdownToHtml(t *testing.T) {
|
||||||
|
13
modules/util/map.go
Normal file
13
modules/util/map.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
func GetMapValueOrDefault[T any](m map[string]any, key string, defaultValue T) T {
|
||||||
|
if value, ok := m[key]; ok {
|
||||||
|
if v, ok := value.(T); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
26
modules/util/map_test.go
Normal file
26
modules/util/map_test.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetMapValueOrDefault(t *testing.T) {
|
||||||
|
testMap := map[string]any{
|
||||||
|
"key1": "value1",
|
||||||
|
"key2": 42,
|
||||||
|
"key3": nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, "value1", GetMapValueOrDefault(testMap, "key1", "default"))
|
||||||
|
assert.Equal(t, 42, GetMapValueOrDefault(testMap, "key2", 0))
|
||||||
|
|
||||||
|
assert.Equal(t, "default", GetMapValueOrDefault(testMap, "key4", "default"))
|
||||||
|
assert.Equal(t, 100, GetMapValueOrDefault(testMap, "key5", 100))
|
||||||
|
|
||||||
|
assert.Equal(t, "default", GetMapValueOrDefault(testMap, "key3", "default"))
|
||||||
|
}
|
@ -66,6 +66,7 @@ Piotr Orzechowski <piotr AT orzechowski DOT tech>
|
|||||||
Richard Bukovansky <richard DOT bukovansky AT gmail DOT com>
|
Richard Bukovansky <richard DOT bukovansky AT gmail DOT com>
|
||||||
Robert Nuske <robert DOT nuske AT web DOT de>
|
Robert Nuske <robert DOT nuske AT web DOT de>
|
||||||
Robin Hübner <profan AT prfn DOT se>
|
Robin Hübner <profan AT prfn DOT se>
|
||||||
|
Ryo Hanafusa <ryo7gumi AT gmail DOT com>
|
||||||
SeongJae Park <sj38 DOT park AT gmail DOT com>
|
SeongJae Park <sj38 DOT park AT gmail DOT com>
|
||||||
Thiago Avelino <thiago AT avelino DOT xxx>
|
Thiago Avelino <thiago AT avelino DOT xxx>
|
||||||
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
|
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
|
||||||
|
@ -3667,12 +3667,13 @@ owner.settings.chef.keypair.description=Pro autentizaci do registru Chef je zapo
|
|||||||
secrets=Tajné klíče
|
secrets=Tajné klíče
|
||||||
description=Tejné klíče budou předány určitým akcím a nelze je přečíst jinak.
|
description=Tejné klíče budou předány určitým akcím a nelze je přečíst jinak.
|
||||||
none=Zatím zde nejsou žádné tajné klíče.
|
none=Zatím zde nejsou žádné tajné klíče.
|
||||||
creation=Přidat tajný klíč
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Popis
|
creation.description=Popis
|
||||||
creation.name_placeholder=nerozlišovat velká a malá písmena, pouze alfanumerické znaky nebo podtržítka, nemohou začínat na GITEA_ nebo GITHUB_
|
creation.name_placeholder=nerozlišovat velká a malá písmena, pouze alfanumerické znaky nebo podtržítka, nemohou začínat na GITEA_ nebo GITHUB_
|
||||||
creation.value_placeholder=Vložte jakýkoliv obsah. Mezery na začátku a konci budou vynechány.
|
creation.value_placeholder=Vložte jakýkoliv obsah. Mezery na začátku a konci budou vynechány.
|
||||||
creation.success=Tajný klíč „%s“ byl přidán.
|
|
||||||
creation.failed=Nepodařilo se přidat tajný klíč.
|
|
||||||
deletion=Odstranit tajný klíč
|
deletion=Odstranit tajný klíč
|
||||||
deletion.description=Odstranění tajného klíče je trvalé a nelze ho vrátit zpět. Pokračovat?
|
deletion.description=Odstranění tajného klíče je trvalé a nelze ho vrátit zpět. Pokračovat?
|
||||||
deletion.success=Tajný klíč byl odstraněn.
|
deletion.success=Tajný klíč byl odstraněn.
|
||||||
|
@ -3659,12 +3659,13 @@ owner.settings.chef.keypair.description=Ein Schlüsselpaar ist notwendig, um mit
|
|||||||
secrets=Secrets
|
secrets=Secrets
|
||||||
description=Secrets werden an bestimmte Aktionen weitergegeben und können nicht anderweitig ausgelesen werden.
|
description=Secrets werden an bestimmte Aktionen weitergegeben und können nicht anderweitig ausgelesen werden.
|
||||||
none=Noch keine Secrets vorhanden.
|
none=Noch keine Secrets vorhanden.
|
||||||
creation=Secret hinzufügen
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Beschreibung
|
creation.description=Beschreibung
|
||||||
creation.name_placeholder=Groß-/Kleinschreibung wird ignoriert, nur alphanumerische Zeichen oder Unterstriche, darf nicht mit GITEA_ oder GITHUB_ beginnen
|
creation.name_placeholder=Groß-/Kleinschreibung wird ignoriert, nur alphanumerische Zeichen oder Unterstriche, darf nicht mit GITEA_ oder GITHUB_ beginnen
|
||||||
creation.value_placeholder=Beliebigen Inhalt eingeben. Leerzeichen am Anfang und Ende werden weggelassen.
|
creation.value_placeholder=Beliebigen Inhalt eingeben. Leerzeichen am Anfang und Ende werden weggelassen.
|
||||||
creation.success=Das Secret "%s" wurde hinzugefügt.
|
|
||||||
creation.failed=Secret konnte nicht hinzugefügt werden.
|
|
||||||
deletion=Secret entfernen
|
deletion=Secret entfernen
|
||||||
deletion.description=Das Entfernen eines Secrets kann nicht rückgängig gemacht werden. Fortfahren?
|
deletion.description=Das Entfernen eines Secrets kann nicht rückgängig gemacht werden. Fortfahren?
|
||||||
deletion.success=Das Secret wurde entfernt.
|
deletion.success=Das Secret wurde entfernt.
|
||||||
|
@ -3329,12 +3329,13 @@ owner.settings.chef.keypair.description=Ένα ζεύγος κλειδιών ε
|
|||||||
secrets=Μυστικά
|
secrets=Μυστικά
|
||||||
description=Τα μυστικά θα περάσουν σε ορισμένες δράσεις και δεν μπορούν να αναγνωστούν αλλού.
|
description=Τα μυστικά θα περάσουν σε ορισμένες δράσεις και δεν μπορούν να αναγνωστούν αλλού.
|
||||||
none=Δεν υπάρχουν ακόμα μυστικά.
|
none=Δεν υπάρχουν ακόμα μυστικά.
|
||||||
creation=Προσθήκη Μυστικού
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Περιγραφή
|
creation.description=Περιγραφή
|
||||||
creation.name_placeholder=αλφαριθμητικοί χαρακτήρες ή κάτω παύλες μόνο, δεν μπορούν να ξεκινούν με GITEA_ ή GITHUB_
|
creation.name_placeholder=αλφαριθμητικοί χαρακτήρες ή κάτω παύλες μόνο, δεν μπορούν να ξεκινούν με GITEA_ ή GITHUB_
|
||||||
creation.value_placeholder=Εισάγετε οποιοδήποτε περιεχόμενο. Τα κενά στην αρχή παραλείπονται.
|
creation.value_placeholder=Εισάγετε οποιοδήποτε περιεχόμενο. Τα κενά στην αρχή παραλείπονται.
|
||||||
creation.success=Το μυστικό "%s" προστέθηκε.
|
|
||||||
creation.failed=Αποτυχία δημιουργίας μυστικού.
|
|
||||||
deletion=Αφαίρεση μυστικού
|
deletion=Αφαίρεση μυστικού
|
||||||
deletion.description=Η αφαίρεση ενός μυστικού είναι μόνιμη και δεν μπορεί να αναιρεθεί. Συνέχεια;
|
deletion.description=Η αφαίρεση ενός μυστικού είναι μόνιμη και δεν μπορεί να αναιρεθεί. Συνέχεια;
|
||||||
deletion.success=Το μυστικό έχει αφαιρεθεί.
|
deletion.success=Το μυστικό έχει αφαιρεθεί.
|
||||||
|
@ -1228,6 +1228,7 @@ migrate.migrating_issues = Migrating Issues
|
|||||||
migrate.migrating_pulls = Migrating Pull Requests
|
migrate.migrating_pulls = Migrating Pull Requests
|
||||||
migrate.cancel_migrating_title = Cancel Migration
|
migrate.cancel_migrating_title = Cancel Migration
|
||||||
migrate.cancel_migrating_confirm = Do you want to cancel this migration?
|
migrate.cancel_migrating_confirm = Do you want to cancel this migration?
|
||||||
|
migrating_status = Migrating status
|
||||||
|
|
||||||
mirror_from = mirror of
|
mirror_from = mirror of
|
||||||
forked_from = forked from
|
forked_from = forked from
|
||||||
@ -3724,13 +3725,18 @@ owner.settings.chef.keypair.description = A key pair is necessary to authenticat
|
|||||||
secrets = Secrets
|
secrets = Secrets
|
||||||
description = Secrets will be passed to certain actions and cannot be read otherwise.
|
description = Secrets will be passed to certain actions and cannot be read otherwise.
|
||||||
none = There are no secrets yet.
|
none = There are no secrets yet.
|
||||||
creation = Add Secret
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description = Description
|
creation.description = Description
|
||||||
creation.name_placeholder = case-insensitive, alphanumeric characters or underscores only, cannot start with GITEA_ or GITHUB_
|
creation.name_placeholder = case-insensitive, alphanumeric characters or underscores only, cannot start with GITEA_ or GITHUB_
|
||||||
creation.value_placeholder = Input any content. Whitespace at the start and end will be omitted.
|
creation.value_placeholder = Input any content. Whitespace at the start and end will be omitted.
|
||||||
creation.description_placeholder = Enter short description (optional).
|
creation.description_placeholder = Enter short description (optional).
|
||||||
creation.success = The secret "%s" has been added.
|
|
||||||
creation.failed = Failed to add secret.
|
save_success = The secret "%s" has been saved.
|
||||||
|
save_failed = Failed to save secret.
|
||||||
|
|
||||||
|
add_secret = Add secret
|
||||||
|
edit_secret = Edit secret
|
||||||
deletion = Remove secret
|
deletion = Remove secret
|
||||||
deletion.description = Removing a secret is permanent and cannot be undone. Continue?
|
deletion.description = Removing a secret is permanent and cannot be undone. Continue?
|
||||||
deletion.success = The secret has been removed.
|
deletion.success = The secret has been removed.
|
||||||
@ -3808,6 +3814,9 @@ runs.no_workflows.documentation = For more information on Gitea Actions, see <a
|
|||||||
runs.no_runs = The workflow has no runs yet.
|
runs.no_runs = The workflow has no runs yet.
|
||||||
runs.empty_commit_message = (empty commit message)
|
runs.empty_commit_message = (empty commit message)
|
||||||
runs.expire_log_message = Logs have been purged because they were too old.
|
runs.expire_log_message = Logs have been purged because they were too old.
|
||||||
|
runs.delete = Delete workflow run
|
||||||
|
runs.delete.description = Are you sure you want to permanently delete this workflow run? This action cannot be undone.
|
||||||
|
runs.not_done = This workflow run is not done.
|
||||||
|
|
||||||
workflow.disable = Disable Workflow
|
workflow.disable = Disable Workflow
|
||||||
workflow.disable_success = Workflow '%s' disabled successfully.
|
workflow.disable_success = Workflow '%s' disabled successfully.
|
||||||
|
@ -3309,12 +3309,13 @@ owner.settings.chef.keypair.description=Un par de claves es necesario para auten
|
|||||||
secrets=Secretos
|
secrets=Secretos
|
||||||
description=Los secretos pasarán a ciertas acciones y no se podrán leer de otro modo.
|
description=Los secretos pasarán a ciertas acciones y no se podrán leer de otro modo.
|
||||||
none=Todavía no hay secretos.
|
none=Todavía no hay secretos.
|
||||||
creation=Añadir secreto
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Descripción
|
creation.description=Descripción
|
||||||
creation.name_placeholder=sin distinción de mayúsculas, solo carácteres alfanuméricos o guiones bajos, no puede empezar por GITEA_ o GITHUB_
|
creation.name_placeholder=sin distinción de mayúsculas, solo carácteres alfanuméricos o guiones bajos, no puede empezar por GITEA_ o GITHUB_
|
||||||
creation.value_placeholder=Introduce cualquier contenido. Se omitirá el espacio en blanco en el inicio y el final.
|
creation.value_placeholder=Introduce cualquier contenido. Se omitirá el espacio en blanco en el inicio y el final.
|
||||||
creation.success=El secreto "%s" ha sido añadido.
|
|
||||||
creation.failed=Error al añadir secreto.
|
|
||||||
deletion=Eliminar secreto
|
deletion=Eliminar secreto
|
||||||
deletion.description=Eliminar un secreto es permanente y no se puede deshacer. ¿Continuar?
|
deletion.description=Eliminar un secreto es permanente y no se puede deshacer. ¿Continuar?
|
||||||
deletion.success=El secreto ha sido eliminado.
|
deletion.success=El secreto ha sido eliminado.
|
||||||
|
@ -2506,8 +2506,12 @@ conan.details.repository=مخزن
|
|||||||
owner.settings.cleanuprules.enabled=فعال شده
|
owner.settings.cleanuprules.enabled=فعال شده
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=شرح
|
creation.description=شرح
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1692,8 +1692,12 @@ conan.details.repository=Repo
|
|||||||
owner.settings.cleanuprules.enabled=Käytössä
|
owner.settings.cleanuprules.enabled=Käytössä
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Kuvaus
|
creation.description=Kuvaus
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -130,6 +130,7 @@ pin=Épingler
|
|||||||
unpin=Désépingler
|
unpin=Désépingler
|
||||||
|
|
||||||
artifacts=Artefacts
|
artifacts=Artefacts
|
||||||
|
expired=Expiré
|
||||||
confirm_delete_artifact=Êtes-vous sûr de vouloir supprimer l‘artefact « %s » ?
|
confirm_delete_artifact=Êtes-vous sûr de vouloir supprimer l‘artefact « %s » ?
|
||||||
|
|
||||||
archived=Archivé
|
archived=Archivé
|
||||||
@ -450,6 +451,7 @@ use_scratch_code=Utiliser un code de secours
|
|||||||
twofa_scratch_used=Vous avez utilisé votre code de secours. Vous avez été redirigé vers cette page de configuration afin de supprimer l'authentification à deux facteurs de votre appareil ou afin de générer un nouveau code de secours.
|
twofa_scratch_used=Vous avez utilisé votre code de secours. Vous avez été redirigé vers cette page de configuration afin de supprimer l'authentification à deux facteurs de votre appareil ou afin de générer un nouveau code de secours.
|
||||||
twofa_passcode_incorrect=Votre code d’accès n’est pas correct. Si vous avez égaré votre appareil, utilisez votre code de secours pour vous connecter.
|
twofa_passcode_incorrect=Votre code d’accès n’est pas correct. Si vous avez égaré votre appareil, utilisez votre code de secours pour vous connecter.
|
||||||
twofa_scratch_token_incorrect=Votre code de secours est incorrect.
|
twofa_scratch_token_incorrect=Votre code de secours est incorrect.
|
||||||
|
twofa_required=Vous devez configurer l’authentification à deux facteurs pour avoir accès aux dépôts, ou essayer de vous reconnecter.
|
||||||
login_userpass=Connexion
|
login_userpass=Connexion
|
||||||
login_openid=OpenID
|
login_openid=OpenID
|
||||||
oauth_signup_tab=Créer un compte
|
oauth_signup_tab=Créer un compte
|
||||||
@ -1878,6 +1880,7 @@ pulls.add_prefix=Ajouter le préfixe <strong>%s</strong>
|
|||||||
pulls.remove_prefix=Enlever le préfixe <strong>%s</strong>
|
pulls.remove_prefix=Enlever le préfixe <strong>%s</strong>
|
||||||
pulls.data_broken=Cette demande d’ajout est impossible par manque d'informations de bifurcation.
|
pulls.data_broken=Cette demande d’ajout est impossible par manque d'informations de bifurcation.
|
||||||
pulls.files_conflicted=Cette demande d'ajout contient des modifications en conflit avec la branche ciblée.
|
pulls.files_conflicted=Cette demande d'ajout contient des modifications en conflit avec la branche ciblée.
|
||||||
|
pulls.is_checking=Recherche de conflits de fusion…
|
||||||
pulls.is_ancestor=Cette branche est déjà présente dans la branche ciblée. Il n'y a rien à fusionner.
|
pulls.is_ancestor=Cette branche est déjà présente dans la branche ciblée. Il n'y a rien à fusionner.
|
||||||
pulls.is_empty=Les changements sur cette branche sont déjà sur la branche cible. Cette révision sera vide.
|
pulls.is_empty=Les changements sur cette branche sont déjà sur la branche cible. Cette révision sera vide.
|
||||||
pulls.required_status_check_failed=Certains contrôles requis n'ont pas réussi.
|
pulls.required_status_check_failed=Certains contrôles requis n'ont pas réussi.
|
||||||
@ -3718,13 +3721,18 @@ owner.settings.chef.keypair.description=Une paire de clés est nécessaire pour
|
|||||||
secrets=Secrets
|
secrets=Secrets
|
||||||
description=Les secrets seront transmis à certaines actions et ne pourront pas être lus autrement.
|
description=Les secrets seront transmis à certaines actions et ne pourront pas être lus autrement.
|
||||||
none=Il n'y a pas encore de secrets.
|
none=Il n'y a pas encore de secrets.
|
||||||
creation=Ajouter un secret
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Description
|
creation.description=Description
|
||||||
creation.name_placeholder=Caractères alphanumériques ou tirets bas uniquement, insensibles à la casse, ne peut commencer par GITEA_ ou GITHUB_.
|
creation.name_placeholder=Caractères alphanumériques ou tirets bas uniquement, insensibles à la casse, ne peut commencer par GITEA_ ou GITHUB_.
|
||||||
creation.value_placeholder=Entrez n’importe quoi. Les blancs cernant seront taillés.
|
creation.value_placeholder=Entrez n’importe quoi. Les blancs cernant seront taillés.
|
||||||
creation.description_placeholder=Décrire brièvement votre dépôt (optionnel).
|
creation.description_placeholder=Décrire brièvement votre dépôt (optionnel).
|
||||||
creation.success=Le secret "%s" a été ajouté.
|
|
||||||
creation.failed=Impossible d'ajouter le secret.
|
save_success=Le secret « %s » a été enregistré.
|
||||||
|
save_failed=Impossible d’enregistrer le secret.
|
||||||
|
|
||||||
|
add_secret=Ajouter un secret
|
||||||
|
edit_secret=Modifier le secret
|
||||||
deletion=Supprimer le secret
|
deletion=Supprimer le secret
|
||||||
deletion.description=La suppression d'un secret est permanente et irréversible. Continuer ?
|
deletion.description=La suppression d'un secret est permanente et irréversible. Continuer ?
|
||||||
deletion.success=Le secret a été supprimé.
|
deletion.success=Le secret a été supprimé.
|
||||||
@ -3802,6 +3810,9 @@ runs.no_workflows.documentation=Pour plus d’informations sur les actions Gitea
|
|||||||
runs.no_runs=Le flux de travail n'a pas encore d'exécution.
|
runs.no_runs=Le flux de travail n'a pas encore d'exécution.
|
||||||
runs.empty_commit_message=(message de révision vide)
|
runs.empty_commit_message=(message de révision vide)
|
||||||
runs.expire_log_message=Les journaux ont été supprimés car ils étaient trop anciens.
|
runs.expire_log_message=Les journaux ont été supprimés car ils étaient trop anciens.
|
||||||
|
runs.delete=Supprimer cette exécution
|
||||||
|
runs.delete.description=Êtes-vous sûr de vouloir supprimer définitivement cette exécution ? Cette action ne peut pas être annulée.
|
||||||
|
runs.not_done=Cette exécution du flux de travail n’est pas terminée.
|
||||||
|
|
||||||
workflow.disable=Désactiver le flux de travail
|
workflow.disable=Désactiver le flux de travail
|
||||||
workflow.disable_success=Le flux de travail « %s » a bien été désactivé.
|
workflow.disable_success=Le flux de travail « %s » a bien été désactivé.
|
||||||
|
@ -1228,6 +1228,7 @@ migrate.migrating_issues=Saincheisteanna Imirce
|
|||||||
migrate.migrating_pulls=Iarratais Tarraingthe á n-Imirce
|
migrate.migrating_pulls=Iarratais Tarraingthe á n-Imirce
|
||||||
migrate.cancel_migrating_title=Cealaigh Imirce
|
migrate.cancel_migrating_title=Cealaigh Imirce
|
||||||
migrate.cancel_migrating_confirm=Ar mhaith leat an imirce seo a chealú?
|
migrate.cancel_migrating_confirm=Ar mhaith leat an imirce seo a chealú?
|
||||||
|
migrating_status=Stádas imirce
|
||||||
|
|
||||||
mirror_from=scáthán de
|
mirror_from=scáthán de
|
||||||
forked_from=forcailte ó
|
forked_from=forcailte ó
|
||||||
@ -3721,13 +3722,18 @@ owner.settings.chef.keypair.description=Tá eochairphéire riachtanach le fíord
|
|||||||
secrets=Rúin
|
secrets=Rúin
|
||||||
description=Cuirfear rúin ar aghaidh chuig gníomhartha áirithe agus ní féidir iad a léamh ar mhalairt.
|
description=Cuirfear rúin ar aghaidh chuig gníomhartha áirithe agus ní féidir iad a léamh ar mhalairt.
|
||||||
none=Níl aon rúin ann fós.
|
none=Níl aon rúin ann fós.
|
||||||
creation=Cuir Rúnda leis
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Cur síos
|
creation.description=Cur síos
|
||||||
creation.name_placeholder=carachtair alfanumair nó íoslaghda amháin nach féidir a thosú le GITEA_ nó GITHUB_
|
creation.name_placeholder=carachtair alfanumair nó íoslaghda amháin nach féidir a thosú le GITEA_ nó GITHUB_
|
||||||
creation.value_placeholder=Ionchur ábhar ar bith. Fágfar spás bán ag tús agus ag deireadh ar lár.
|
creation.value_placeholder=Ionchur ábhar ar bith. Fágfar spás bán ag tús agus ag deireadh ar lár.
|
||||||
creation.description_placeholder=Cuir isteach cur síos gairid (roghnach).
|
creation.description_placeholder=Cuir isteach cur síos gairid (roghnach).
|
||||||
creation.success=Tá an rún "%s" curtha leis.
|
|
||||||
creation.failed=Theip ar an rún a chur leis.
|
save_success=Tá an rún "%s" sábháilte.
|
||||||
|
save_failed=Theip ar an rún a shábháil.
|
||||||
|
|
||||||
|
add_secret=Cuir rún leis
|
||||||
|
edit_secret=Cuir rún in eagar
|
||||||
deletion=Bain rún
|
deletion=Bain rún
|
||||||
deletion.description=Is buan rún a bhaint agus ní féidir é a chealú. Lean ort?
|
deletion.description=Is buan rún a bhaint agus ní féidir é a chealú. Lean ort?
|
||||||
deletion.success=Tá an rún bainte.
|
deletion.success=Tá an rún bainte.
|
||||||
@ -3805,6 +3811,9 @@ runs.no_workflows.documentation=Le haghaidh tuilleadh eolais ar Gitea Actions, f
|
|||||||
runs.no_runs=Níl aon rith ag an sreabhadh oibre fós.
|
runs.no_runs=Níl aon rith ag an sreabhadh oibre fós.
|
||||||
runs.empty_commit_message=(teachtaireacht tiomantas folamh)
|
runs.empty_commit_message=(teachtaireacht tiomantas folamh)
|
||||||
runs.expire_log_message=Glanadh logaí toisc go raibh siad ró-sean.
|
runs.expire_log_message=Glanadh logaí toisc go raibh siad ró-sean.
|
||||||
|
runs.delete=Scrios rith sreabha oibre
|
||||||
|
runs.delete.description=An bhfuil tú cinnte gur mian leat an rith sreabha oibre seo a scriosadh go buan? Ní féidir an gníomh seo a chealú.
|
||||||
|
runs.not_done=Níl an rith sreabha oibre seo críochnaithe.
|
||||||
|
|
||||||
workflow.disable=Díchumasaigh sreabhadh oibre
|
workflow.disable=Díchumasaigh sreabhadh oibre
|
||||||
workflow.disable_success=D'éirigh le sreabhadh oibre '%s' a dhíchumasú.
|
workflow.disable_success=D'éirigh le sreabhadh oibre '%s' a dhíchumasú.
|
||||||
|
@ -1592,8 +1592,12 @@ conan.details.repository=Tároló
|
|||||||
owner.settings.cleanuprules.enabled=Engedélyezett
|
owner.settings.cleanuprules.enabled=Engedélyezett
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Leírás
|
creation.description=Leírás
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1394,8 +1394,12 @@ conan.details.repository=Repositori
|
|||||||
owner.settings.cleanuprules.enabled=Aktif
|
owner.settings.cleanuprules.enabled=Aktif
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Deskripsi
|
creation.description=Deskripsi
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1325,8 +1325,12 @@ npm.details.tag=Merki
|
|||||||
pypi.requires=Þarfnast Python
|
pypi.requires=Þarfnast Python
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Lýsing
|
creation.description=Lýsing
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -2782,8 +2782,12 @@ settings.delete.error=Impossibile eliminare il pacchetto.
|
|||||||
owner.settings.cleanuprules.enabled=Attivo
|
owner.settings.cleanuprules.enabled=Attivo
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Descrizione
|
creation.description=Descrizione
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -450,6 +450,7 @@ use_scratch_code=スクラッチコードを使う
|
|||||||
twofa_scratch_used=あなたはスクラッチコードを使用しました。 2要素認証の設定ページにリダイレクトしましたので、デバイスの登録を解除するか、新しいスクラッチコードを生成しましょう。
|
twofa_scratch_used=あなたはスクラッチコードを使用しました。 2要素認証の設定ページにリダイレクトしましたので、デバイスの登録を解除するか、新しいスクラッチコードを生成しましょう。
|
||||||
twofa_passcode_incorrect=パスコードが正しくありません。デバイスを紛失した場合は、スクラッチコードを使ってサインインしてください。
|
twofa_passcode_incorrect=パスコードが正しくありません。デバイスを紛失した場合は、スクラッチコードを使ってサインインしてください。
|
||||||
twofa_scratch_token_incorrect=スクラッチコードが正しくありません。
|
twofa_scratch_token_incorrect=スクラッチコードが正しくありません。
|
||||||
|
twofa_required=リポジトリにアクセスするには2段階認証を設定するか、再度ログインしてください。
|
||||||
login_userpass=サインイン
|
login_userpass=サインイン
|
||||||
login_openid=OpenID
|
login_openid=OpenID
|
||||||
oauth_signup_tab=新規アカウント登録
|
oauth_signup_tab=新規アカウント登録
|
||||||
@ -1878,6 +1879,7 @@ pulls.add_prefix=先頭に <strong>%s</strong> を追加
|
|||||||
pulls.remove_prefix=先頭の <strong>%s</strong> を除去
|
pulls.remove_prefix=先頭の <strong>%s</strong> を除去
|
||||||
pulls.data_broken=このプルリクエストは、フォークの情報が見つからないため壊れています。
|
pulls.data_broken=このプルリクエストは、フォークの情報が見つからないため壊れています。
|
||||||
pulls.files_conflicted=このプルリクエストは、ターゲットブランチと競合する変更を含んでいます。
|
pulls.files_conflicted=このプルリクエストは、ターゲットブランチと競合する変更を含んでいます。
|
||||||
|
pulls.is_checking=マージの競合を確認しています...
|
||||||
pulls.is_ancestor=このブランチは既にターゲットブランチに含まれています。マージするものはありません。
|
pulls.is_ancestor=このブランチは既にターゲットブランチに含まれています。マージするものはありません。
|
||||||
pulls.is_empty=このブランチの変更は既にターゲットブランチにあります。これは空のコミットになります。
|
pulls.is_empty=このブランチの変更は既にターゲットブランチにあります。これは空のコミットになります。
|
||||||
pulls.required_status_check_failed=いくつかの必要なステータスチェックが成功していません。
|
pulls.required_status_check_failed=いくつかの必要なステータスチェックが成功していません。
|
||||||
@ -3718,13 +3720,18 @@ owner.settings.chef.keypair.description=Chefレジストリの認証にはキー
|
|||||||
secrets=シークレット
|
secrets=シークレット
|
||||||
description=シークレットは特定のActionsに渡されます。 それ以外で読み出されることはありません。
|
description=シークレットは特定のActionsに渡されます。 それ以外で読み出されることはありません。
|
||||||
none=シークレットはまだありません。
|
none=シークレットはまだありません。
|
||||||
creation=シークレットを追加
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=説明
|
creation.description=説明
|
||||||
creation.name_placeholder=大文字小文字の区別なし、英数字とアンダースコアのみ、GITEA_ や GITHUB_ で始まるものは不可
|
creation.name_placeholder=大文字小文字の区別なし、英数字とアンダースコアのみ、GITEA_ や GITHUB_ で始まるものは不可
|
||||||
creation.value_placeholder=内容を入力してください。前後の空白は除去されます。
|
creation.value_placeholder=内容を入力してください。前後の空白は除去されます。
|
||||||
creation.description_placeholder=簡単な説明を入力してください。 (オプション)
|
creation.description_placeholder=簡単な説明を入力してください。 (オプション)
|
||||||
creation.success=シークレット "%s" を追加しました。
|
|
||||||
creation.failed=シークレットの追加に失敗しました。
|
save_success=シークレット "%s" を保存しました。
|
||||||
|
save_failed=シークレットの保存に失敗しました。
|
||||||
|
|
||||||
|
add_secret=シークレットを追加
|
||||||
|
edit_secret=シークレットを編集
|
||||||
deletion=シークレットの削除
|
deletion=シークレットの削除
|
||||||
deletion.description=シークレットの削除は恒久的で元に戻すことはできません。 続行しますか?
|
deletion.description=シークレットの削除は恒久的で元に戻すことはできません。 続行しますか?
|
||||||
deletion.success=シークレットを削除しました。
|
deletion.success=シークレットを削除しました。
|
||||||
@ -3841,6 +3848,8 @@ deleted.display_name=削除されたプロジェクト
|
|||||||
type-1.display_name=個人プロジェクト
|
type-1.display_name=個人プロジェクト
|
||||||
type-2.display_name=リポジトリ プロジェクト
|
type-2.display_name=リポジトリ プロジェクト
|
||||||
type-3.display_name=組織プロジェクト
|
type-3.display_name=組織プロジェクト
|
||||||
|
enter_fullscreen=フルスクリーン
|
||||||
|
exit_fullscreen=フルスクリーンを終了
|
||||||
|
|
||||||
[git.filemode]
|
[git.filemode]
|
||||||
changed_filemode=%[1]s → %[2]s
|
changed_filemode=%[1]s → %[2]s
|
||||||
|
@ -1542,8 +1542,12 @@ conan.details.repository=저장소
|
|||||||
owner.settings.cleanuprules.enabled=활성화됨
|
owner.settings.cleanuprules.enabled=활성화됨
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=설명
|
creation.description=설명
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -3332,12 +3332,13 @@ owner.settings.chef.keypair.description=Atslēgu pāris ir nepieciešams, lai au
|
|||||||
secrets=Noslēpumi
|
secrets=Noslēpumi
|
||||||
description=Noslēpumi tiks padoti atsevišķām darbībām un citādi nevar tikt nolasīti.
|
description=Noslēpumi tiks padoti atsevišķām darbībām un citādi nevar tikt nolasīti.
|
||||||
none=Pagaidām nav neviena noslēpuma.
|
none=Pagaidām nav neviena noslēpuma.
|
||||||
creation=Pievienot noslēpumu
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Apraksts
|
creation.description=Apraksts
|
||||||
creation.name_placeholder=reģistr-nejūtīgs, tikai burti, cipari un apakšsvītras, nevar sākties ar GITEA_ vai GITHUB_
|
creation.name_placeholder=reģistr-nejūtīgs, tikai burti, cipari un apakšsvītras, nevar sākties ar GITEA_ vai GITHUB_
|
||||||
creation.value_placeholder=Ievadiet jebkādu saturu. Atstarpes sākumā un beigā tiks noņemtas.
|
creation.value_placeholder=Ievadiet jebkādu saturu. Atstarpes sākumā un beigā tiks noņemtas.
|
||||||
creation.success=Noslēpums "%s" tika pievienots.
|
|
||||||
creation.failed=Neizdevās pievienot noslēpumu.
|
|
||||||
deletion=Dzēst noslēpumu
|
deletion=Dzēst noslēpumu
|
||||||
deletion.description=Noslēpuma dzēšana ir neatgriezeniska. Vai turpināt?
|
deletion.description=Noslēpuma dzēšana ir neatgriezeniska. Vai turpināt?
|
||||||
deletion.success=Noslēpums tika izdzēsts.
|
deletion.success=Noslēpums tika izdzēsts.
|
||||||
|
@ -2515,8 +2515,12 @@ settings.link.button=Repository link bijwerken
|
|||||||
owner.settings.cleanuprules.enabled=Ingeschakeld
|
owner.settings.cleanuprules.enabled=Ingeschakeld
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Omschrijving
|
creation.description=Omschrijving
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -2405,8 +2405,12 @@ conan.details.repository=Repozytorium
|
|||||||
owner.settings.cleanuprules.enabled=Włączone
|
owner.settings.cleanuprules.enabled=Włączone
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Opis
|
creation.description=Opis
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -3269,12 +3269,13 @@ owner.settings.chef.keypair=Gerar par de chaves
|
|||||||
secrets=Segredos
|
secrets=Segredos
|
||||||
description=Os segredos serão passados a certas ações e não poderão ser lidos de outra forma.
|
description=Os segredos serão passados a certas ações e não poderão ser lidos de outra forma.
|
||||||
none=Não há segredos ainda.
|
none=Não há segredos ainda.
|
||||||
creation=Adicionar Segredo
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Descrição
|
creation.description=Descrição
|
||||||
creation.name_placeholder=apenas caracteres alfanuméricos ou underline (_), não pode começar com GITEA_ ou GITHUB_
|
creation.name_placeholder=apenas caracteres alfanuméricos ou underline (_), não pode começar com GITEA_ ou GITHUB_
|
||||||
creation.value_placeholder=Insira qualquer conteúdo. Espaços em branco no início e no fim serão omitidos.
|
creation.value_placeholder=Insira qualquer conteúdo. Espaços em branco no início e no fim serão omitidos.
|
||||||
creation.success=O segredo "%s" foi adicionado.
|
|
||||||
creation.failed=Falha ao adicionar segredo.
|
|
||||||
deletion=Excluir segredo
|
deletion=Excluir segredo
|
||||||
deletion.description=A exclusão de um segredo é permanente e não pode ser desfeita. Continuar?
|
deletion.description=A exclusão de um segredo é permanente e não pode ser desfeita. Continuar?
|
||||||
deletion.success=O segredo foi excluído.
|
deletion.success=O segredo foi excluído.
|
||||||
|
@ -3721,13 +3721,18 @@ owner.settings.chef.keypair.description=É necessário um par de chaves para aut
|
|||||||
secrets=Segredos
|
secrets=Segredos
|
||||||
description=Os segredos serão transmitidos a certas operações e não poderão ser lidos de outra forma.
|
description=Os segredos serão transmitidos a certas operações e não poderão ser lidos de outra forma.
|
||||||
none=Ainda não há segredos.
|
none=Ainda não há segredos.
|
||||||
creation=Adicionar segredo
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Descrição
|
creation.description=Descrição
|
||||||
creation.name_placeholder=Só sublinhados ou alfanuméricos sem distinguir maiúsculas, sem começar com GITEA_ nem GITHUB_
|
creation.name_placeholder=Só sublinhados ou alfanuméricos sem distinguir maiúsculas, sem começar com GITEA_ nem GITHUB_
|
||||||
creation.value_placeholder=Insira um conteúdo qualquer. Espaços em branco no início ou no fim serão omitidos.
|
creation.value_placeholder=Insira um conteúdo qualquer. Espaços em branco no início ou no fim serão omitidos.
|
||||||
creation.description_placeholder=Escreva uma descrição curta (opcional).
|
creation.description_placeholder=Escreva uma descrição curta (opcional).
|
||||||
creation.success=O segredo "%s" foi adicionado.
|
|
||||||
creation.failed=Falhou ao adicionar o segredo.
|
save_success=O segredo "%s" foi guardado.
|
||||||
|
save_failed=Falhou ao guardar o segredo.
|
||||||
|
|
||||||
|
add_secret=Adicionar segredo
|
||||||
|
edit_secret=Editar segredo
|
||||||
deletion=Remover segredo
|
deletion=Remover segredo
|
||||||
deletion.description=Remover um segredo é permanente e não pode ser revertido. Continuar?
|
deletion.description=Remover um segredo é permanente e não pode ser revertido. Continuar?
|
||||||
deletion.success=O segredo foi removido.
|
deletion.success=O segredo foi removido.
|
||||||
@ -3805,6 +3810,9 @@ runs.no_workflows.documentation=Para mais informação sobre o Gitea Actions vej
|
|||||||
runs.no_runs=A sequência de trabalho ainda não foi executada.
|
runs.no_runs=A sequência de trabalho ainda não foi executada.
|
||||||
runs.empty_commit_message=(mensagem de cometimento vazia)
|
runs.empty_commit_message=(mensagem de cometimento vazia)
|
||||||
runs.expire_log_message=Os registros foram removidos porque eram muito antigos.
|
runs.expire_log_message=Os registros foram removidos porque eram muito antigos.
|
||||||
|
runs.delete=Eliminar execução da sequência de trabalho
|
||||||
|
runs.delete.description=Tem a certeza que pretende eliminar permanentemente a execução desta sequência de trabalho? Esta operação não poderá ser desfeita.
|
||||||
|
runs.not_done=A execução desta sequência de trabalho ainda não terminou.
|
||||||
|
|
||||||
workflow.disable=Desabilitar sequência de trabalho
|
workflow.disable=Desabilitar sequência de trabalho
|
||||||
workflow.disable_success=A sequência de trabalho '%s' foi desabilitada com sucesso.
|
workflow.disable_success=A sequência de trabalho '%s' foi desabilitada com sucesso.
|
||||||
|
@ -3266,12 +3266,13 @@ owner.settings.chef.keypair=Создать пару ключей
|
|||||||
secrets=Секреты
|
secrets=Секреты
|
||||||
description=Секреты будут передаваться определенным действиям и не могут быть прочитаны иначе.
|
description=Секреты будут передаваться определенным действиям и не могут быть прочитаны иначе.
|
||||||
none=Секретов пока нет.
|
none=Секретов пока нет.
|
||||||
creation=Добавить секрет
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Описание
|
creation.description=Описание
|
||||||
creation.name_placeholder=регистр не важен, только алфавитно-цифровые символы и подчёркивания, не может начинаться с GITEA_ или GITHUB_
|
creation.name_placeholder=регистр не важен, только алфавитно-цифровые символы и подчёркивания, не может начинаться с GITEA_ или GITHUB_
|
||||||
creation.value_placeholder=Введите любое содержимое. Пробельные символы в начале и конце будут опущены.
|
creation.value_placeholder=Введите любое содержимое. Пробельные символы в начале и конце будут опущены.
|
||||||
creation.success=Секрет «%s» добавлен.
|
|
||||||
creation.failed=Не удалось добавить секрет.
|
|
||||||
deletion=Удалить секрет
|
deletion=Удалить секрет
|
||||||
deletion.description=Удаление секрета необратимо, его нельзя отменить. Продолжить?
|
deletion.description=Удаление секрета необратимо, его нельзя отменить. Продолжить?
|
||||||
deletion.success=Секрет удалён.
|
deletion.success=Секрет удалён.
|
||||||
|
@ -2447,8 +2447,12 @@ conan.details.repository=කෝෂ්ඨය
|
|||||||
owner.settings.cleanuprules.enabled=සබල කර ඇත
|
owner.settings.cleanuprules.enabled=සබල කර ඇත
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=සවිස්තරය
|
creation.description=සවිස්තරය
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1321,6 +1321,10 @@ owner.settings.cleanuprules.enabled=Povolené
|
|||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -1982,8 +1982,12 @@ conan.details.repository=Utvecklingskatalog
|
|||||||
owner.settings.cleanuprules.enabled=Aktiv
|
owner.settings.cleanuprules.enabled=Aktiv
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Beskrivning
|
creation.description=Beskrivning
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,9 +113,11 @@ copy_type_unsupported=Bu dosya türü kopyalanamaz
|
|||||||
write=Yaz
|
write=Yaz
|
||||||
preview=Önizleme
|
preview=Önizleme
|
||||||
loading=Yükleniyor…
|
loading=Yükleniyor…
|
||||||
|
files=Dosyalar
|
||||||
|
|
||||||
error=Hata
|
error=Hata
|
||||||
error404=Ulaşmaya çalıştığınız sayfa <strong>mevcut değil</strong> veya <strong>görüntüleme yetkiniz yok</strong>.
|
error404=Ulaşmaya çalıştığınız sayfa <strong>mevcut değil</strong> veya <strong>görüntüleme yetkiniz yok</strong>.
|
||||||
|
error503=Sunucu isteğinizi gerçekleştiremedi. Lütfen daha sonra tekrar deneyin.
|
||||||
go_back=Geri Git
|
go_back=Geri Git
|
||||||
invalid_data=Geçersiz veri: %v
|
invalid_data=Geçersiz veri: %v
|
||||||
|
|
||||||
@ -128,6 +130,7 @@ pin=Sabitle
|
|||||||
unpin=Sabitlemeyi kaldır
|
unpin=Sabitlemeyi kaldır
|
||||||
|
|
||||||
artifacts=Yapılar
|
artifacts=Yapılar
|
||||||
|
expired=Süresi doldu
|
||||||
confirm_delete_artifact=%s yapısını silmek istediğinizden emin misiniz?
|
confirm_delete_artifact=%s yapısını silmek istediğinizden emin misiniz?
|
||||||
|
|
||||||
archived=Arşivlenmiş
|
archived=Arşivlenmiş
|
||||||
@ -169,6 +172,10 @@ search=Ara...
|
|||||||
type_tooltip=Arama türü
|
type_tooltip=Arama türü
|
||||||
fuzzy=Bulanık
|
fuzzy=Bulanık
|
||||||
fuzzy_tooltip=Arama terimine benzeyen sonuçları da içer
|
fuzzy_tooltip=Arama terimine benzeyen sonuçları da içer
|
||||||
|
words=Kelimeler
|
||||||
|
words_tooltip=Sadece arama terimi kelimeleriyle eşleşen sonuçları içer
|
||||||
|
regexp=Regexp
|
||||||
|
regexp_tooltip=Sadece regexp arama terimiyle tamamen eşleşen sonuçları içer
|
||||||
exact=Tam
|
exact=Tam
|
||||||
exact_tooltip=Sadece arama terimiyle tamamen eşleşen sonuçları içer
|
exact_tooltip=Sadece arama terimiyle tamamen eşleşen sonuçları içer
|
||||||
repo_kind=Depoları ara...
|
repo_kind=Depoları ara...
|
||||||
@ -235,13 +242,17 @@ network_error=Ağ hatası
|
|||||||
[startpage]
|
[startpage]
|
||||||
app_desc=Zahmetsiz, kendi sunucunuzda barındırabileceğiniz Git servisi
|
app_desc=Zahmetsiz, kendi sunucunuzda barındırabileceğiniz Git servisi
|
||||||
install=Kurulumu kolay
|
install=Kurulumu kolay
|
||||||
|
install_desc=Platformunuz için <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-binary">ikili dosyayı çalıştırın</a>, <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea/tree/master/docker">Docker</a> ile yükleyin veya <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-package">paket</a> olarak edinin.
|
||||||
platform=Farklı platformlarda çalışablir
|
platform=Farklı platformlarda çalışablir
|
||||||
|
platform_desc=Gitea <a target="_blank" rel="noopener noreferrer" href="%s">Go</a> ile derleme yapılabilecek her yerde çalışmaktadır: Windows, macOS, Linux, ARM, vb. Hangisini seviyorsanız onu seçin!
|
||||||
lightweight=Hafif
|
lightweight=Hafif
|
||||||
lightweight_desc=Gitea'nın minimal gereksinimleri çok düşüktür ve ucuz bir Raspberry Pi üzerinde çalışabilmektedir. Makine enerjinizden tasarruf edin!
|
lightweight_desc=Gitea'nın minimal gereksinimleri çok düşüktür ve ucuz bir Raspberry Pi üzerinde çalışabilmektedir. Makine enerjinizden tasarruf edin!
|
||||||
license=Açık Kaynak
|
license=Açık Kaynak
|
||||||
|
license_desc=Gidin ve <a target="_blank" rel="noopener noreferrer" href="https://code.gitea.io/gitea">code.gitea.io/gitea</a>'yı edinin! Bu projeyi daha da iyi yapmak için <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea">katkıda bulunarak</a> bize katılın. Katkıda bulunmaktan çekinmeyin!
|
||||||
|
|
||||||
[install]
|
[install]
|
||||||
install=Kurulum
|
install=Kurulum
|
||||||
|
installing_desc=Şimdi kuruluyor, lütfen bekleyin...
|
||||||
title=Başlangıç Yapılandırması
|
title=Başlangıç Yapılandırması
|
||||||
docker_helper=Eğer Gitea'yı Docker içerisinde çalıştırıyorsanız, lütfen herhangi bir değişiklik yapmadan önce <a target="_blank" rel="noopener noreferrer" href="%s">belgeleri</a> okuyun.
|
docker_helper=Eğer Gitea'yı Docker içerisinde çalıştırıyorsanız, lütfen herhangi bir değişiklik yapmadan önce <a target="_blank" rel="noopener noreferrer" href="%s">belgeleri</a> okuyun.
|
||||||
require_db_desc=Gitea MySQL, PostgreSQL, MSSQL, SQLite3 veya TiDB (MySQL protokolü) gerektirir.
|
require_db_desc=Gitea MySQL, PostgreSQL, MSSQL, SQLite3 veya TiDB (MySQL protokolü) gerektirir.
|
||||||
@ -352,6 +363,7 @@ enable_update_checker=Güncelleme Denetleyicisini Etkinleştir
|
|||||||
enable_update_checker_helper=Düzenli olarak gitea.io'ya bağlanarak yeni yayınlanan sürümleri denetler.
|
enable_update_checker_helper=Düzenli olarak gitea.io'ya bağlanarak yeni yayınlanan sürümleri denetler.
|
||||||
env_config_keys=Ortam Yapılandırma
|
env_config_keys=Ortam Yapılandırma
|
||||||
env_config_keys_prompt=Aşağıdaki ortam değişkenleri de yapılandırma dosyanıza eklenecektir:
|
env_config_keys_prompt=Aşağıdaki ortam değişkenleri de yapılandırma dosyanıza eklenecektir:
|
||||||
|
config_write_file_prompt=Bu yapılandırma seçenekleri şuraya yazılacak: %s
|
||||||
|
|
||||||
[home]
|
[home]
|
||||||
nav_menu=Gezinti Menüsü
|
nav_menu=Gezinti Menüsü
|
||||||
@ -380,6 +392,12 @@ show_only_public=Yalnızca açık olanlar gösteriliyor
|
|||||||
|
|
||||||
issues.in_your_repos=Depolarınızda
|
issues.in_your_repos=Depolarınızda
|
||||||
|
|
||||||
|
guide_title=Etkinlik yok
|
||||||
|
guide_desc=Herhangi bir depo veya kullanıcı takip etmiyorsunuz, bu yüzden görüntülenecek bir içerik yok. Aşağıdaki bağlantıları kullanarak ilgi çekici depo ve kullanıcıları keşfedebilirsiniz.
|
||||||
|
explore_repos=Depoları keşfet
|
||||||
|
explore_users=Kullanıcıları keşfet
|
||||||
|
empty_org=Henüz bir organizasyon yok.
|
||||||
|
empty_repo=Henüz bir depo yok.
|
||||||
|
|
||||||
[explore]
|
[explore]
|
||||||
repos=Depolar
|
repos=Depolar
|
||||||
@ -433,6 +451,7 @@ use_scratch_code=Bir çizgi kodu kullanınız
|
|||||||
twofa_scratch_used=Geçici kodunuzu kullandınız. İki aşamalı ayarlar sayfasına yönlendirildiniz, burada aygıt kaydınızı kaldırabilir veya yeni bir geçici kod oluşturabilirsiniz.
|
twofa_scratch_used=Geçici kodunuzu kullandınız. İki aşamalı ayarlar sayfasına yönlendirildiniz, burada aygıt kaydınızı kaldırabilir veya yeni bir geçici kod oluşturabilirsiniz.
|
||||||
twofa_passcode_incorrect=Şifreniz yanlış. Aygıtınızı yanlış yerleştirdiyseniz, oturum açmak için çizgi kodunuzu kullanın.
|
twofa_passcode_incorrect=Şifreniz yanlış. Aygıtınızı yanlış yerleştirdiyseniz, oturum açmak için çizgi kodunuzu kullanın.
|
||||||
twofa_scratch_token_incorrect=Çizgi kodunuz doğru değildir.
|
twofa_scratch_token_incorrect=Çizgi kodunuz doğru değildir.
|
||||||
|
twofa_required=Depolara erişmek için iki aşama doğrulama kullanmanız veya tekrar oturum açmayı denemeniz gereklidir.
|
||||||
login_userpass=Oturum Aç
|
login_userpass=Oturum Aç
|
||||||
login_openid=Açık Kimlik
|
login_openid=Açık Kimlik
|
||||||
oauth_signup_tab=Yeni Hesap Oluştur
|
oauth_signup_tab=Yeni Hesap Oluştur
|
||||||
@ -441,6 +460,7 @@ oauth_signup_submit=Hesabı Tamamla
|
|||||||
oauth_signin_tab=Mevcut Hesaba Bağla
|
oauth_signin_tab=Mevcut Hesaba Bağla
|
||||||
oauth_signin_title=Bağlantılı Hesabı Yetkilendirmek için Giriş Yapın
|
oauth_signin_title=Bağlantılı Hesabı Yetkilendirmek için Giriş Yapın
|
||||||
oauth_signin_submit=Hesabı Bağla
|
oauth_signin_submit=Hesabı Bağla
|
||||||
|
oauth.signin.error.general=Yetkilendirme isteğini işlerken bir hata oluştu: %s. Eğer hata devam ederse lütfen site yöneticisiyle bağlantıya geçin.
|
||||||
oauth.signin.error.access_denied=Yetkilendirme isteği reddedildi.
|
oauth.signin.error.access_denied=Yetkilendirme isteği reddedildi.
|
||||||
oauth.signin.error.temporarily_unavailable=Yetkilendirme sunucusu geçici olarak erişilemez olduğu için yetkilendirme başarısız oldu. Lütfen daha sonra tekrar deneyin.
|
oauth.signin.error.temporarily_unavailable=Yetkilendirme sunucusu geçici olarak erişilemez olduğu için yetkilendirme başarısız oldu. Lütfen daha sonra tekrar deneyin.
|
||||||
oauth_callback_unable_auto_reg=Otomatik kayıt etkin ancak OAuth2 Sağlayıcı %[1] eksik sahalar döndürdü: %[2]s, otomatik olarak hesap oluşturulamıyor, lütfen bir hesap oluşturun veya bağlantı verin, veya site yöneticisiyle iletişim kurun.
|
oauth_callback_unable_auto_reg=Otomatik kayıt etkin ancak OAuth2 Sağlayıcı %[1] eksik sahalar döndürdü: %[2]s, otomatik olarak hesap oluşturulamıyor, lütfen bir hesap oluşturun veya bağlantı verin, veya site yöneticisiyle iletişim kurun.
|
||||||
@ -457,10 +477,12 @@ authorize_application=Uygulamayı Yetkilendir
|
|||||||
authorize_redirect_notice=Bu uygulamayı yetkilendirirseniz %s adresine yönlendirileceksiniz.
|
authorize_redirect_notice=Bu uygulamayı yetkilendirirseniz %s adresine yönlendirileceksiniz.
|
||||||
authorize_application_created_by=Bu uygulama %s tarafından oluşturuldu.
|
authorize_application_created_by=Bu uygulama %s tarafından oluşturuldu.
|
||||||
authorize_application_description=Erişime izin verirseniz, özel depolar ve organizasyonlar da dahil olmak üzere tüm hesap bilgilerinize erişebilir ve yazabilir.
|
authorize_application_description=Erişime izin verirseniz, özel depolar ve organizasyonlar da dahil olmak üzere tüm hesap bilgilerinize erişebilir ve yazabilir.
|
||||||
|
authorize_application_with_scopes=Kapsamlar: %s
|
||||||
authorize_title=Hesabınıza erişmesi için "%s" yetkilendirilsin mi?
|
authorize_title=Hesabınıza erişmesi için "%s" yetkilendirilsin mi?
|
||||||
authorization_failed=Yetkilendirme başarısız oldu
|
authorization_failed=Yetkilendirme başarısız oldu
|
||||||
authorization_failed_desc=Geçersiz bir istek tespit ettiğimiz için yetkilendirme başarısız oldu. Lütfen izin vermeye çalıştığınız uygulamanın sağlayıcısı ile iletişim kurun.
|
authorization_failed_desc=Geçersiz bir istek tespit ettiğimiz için yetkilendirme başarısız oldu. Lütfen izin vermeye çalıştığınız uygulamanın sağlayıcısı ile iletişim kurun.
|
||||||
sspi_auth_failed=SSPI kimlik doğrulaması başarısız oldu
|
sspi_auth_failed=SSPI kimlik doğrulaması başarısız oldu
|
||||||
|
password_pwned=Seçtiğiniz parola, daha önce herkese açık veri ihlallerinde açığa çıkan bir <a target="_blank" rel="noopener noreferrer" href="%s">çalınan parola listesindedir</a>. Lütfen farklı bir parola ile tekrar deneyin ve başka yerlerde de bu parolayı değiştirmeyi düşünün.
|
||||||
password_pwned_err=HaveIBeenPwned'e yapılan istek tamamlanamadı
|
password_pwned_err=HaveIBeenPwned'e yapılan istek tamamlanamadı
|
||||||
last_admin=Son yöneticiyi silemezsiniz. En azından bir yönetici olmalıdır.
|
last_admin=Son yöneticiyi silemezsiniz. En azından bir yönetici olmalıdır.
|
||||||
signin_passkey=Bir parola anahtarı ile oturum aç
|
signin_passkey=Bir parola anahtarı ile oturum aç
|
||||||
@ -583,6 +605,8 @@ lang_select_error=Listeden bir dil seçin.
|
|||||||
|
|
||||||
username_been_taken=Bu kullanıcı adı daha önce alınmış.
|
username_been_taken=Bu kullanıcı adı daha önce alınmış.
|
||||||
username_change_not_local_user=Yerel olmayan kullanıcılar kendi kullanıcı adlarını değiştiremezler.
|
username_change_not_local_user=Yerel olmayan kullanıcılar kendi kullanıcı adlarını değiştiremezler.
|
||||||
|
change_username_disabled=Kullanıcı adı değişikliği devre dışıdır.
|
||||||
|
change_full_name_disabled=Tam ad değişikliği devre dışıdır.
|
||||||
username_has_not_been_changed=Kullanıcı adı değişmedi
|
username_has_not_been_changed=Kullanıcı adı değişmedi
|
||||||
repo_name_been_taken=Depo adı zaten kullanılıyor.
|
repo_name_been_taken=Depo adı zaten kullanılıyor.
|
||||||
repository_force_private=Gizliyi Zorla devrede: gizli depolar herkese açık yapılamaz.
|
repository_force_private=Gizliyi Zorla devrede: gizli depolar herkese açık yapılamaz.
|
||||||
@ -632,6 +656,7 @@ org_still_own_repo=Bu organizasyon hala bir veya daha fazla depoya sahip, önce
|
|||||||
org_still_own_packages=Bu organizasyon hala bir veya daha fazla pakete sahip, önce onları silin.
|
org_still_own_packages=Bu organizasyon hala bir veya daha fazla pakete sahip, önce onları silin.
|
||||||
|
|
||||||
target_branch_not_exist=Hedef dal mevcut değil.
|
target_branch_not_exist=Hedef dal mevcut değil.
|
||||||
|
target_ref_not_exist=Hedef referans mevcut değil %s
|
||||||
|
|
||||||
admin_cannot_delete_self=Yöneticiyken kendinizi silemezsiniz. Lütfen önce yönetici haklarınızı kaldırın.
|
admin_cannot_delete_self=Yöneticiyken kendinizi silemezsiniz. Lütfen önce yönetici haklarınızı kaldırın.
|
||||||
|
|
||||||
@ -698,14 +723,18 @@ applications=Uygulamalar
|
|||||||
orgs=Organizasyonları Yönet
|
orgs=Organizasyonları Yönet
|
||||||
repos=Depolar
|
repos=Depolar
|
||||||
delete=Hesabı Sil
|
delete=Hesabı Sil
|
||||||
|
twofa=İki Aşamalı Kimlik Doğrulama (TOTP)
|
||||||
account_link=Bağlı Hesaplar
|
account_link=Bağlı Hesaplar
|
||||||
organization=Organizasyonlar
|
organization=Organizasyonlar
|
||||||
uid=UID
|
uid=UID
|
||||||
|
webauthn=İki-Aşamalı Kimlik Doğrulama (Güvenlik Anahtarları)
|
||||||
|
|
||||||
public_profile=Herkese Açık Profil
|
public_profile=Herkese Açık Profil
|
||||||
biography_placeholder=Bize kendiniz hakkında birşeyler söyleyin! (Markdown kullanabilirsiniz)
|
biography_placeholder=Bize kendiniz hakkında birşeyler söyleyin! (Markdown kullanabilirsiniz)
|
||||||
location_placeholder=Yaklaşık konumunuzu başkalarıyla paylaşın
|
location_placeholder=Yaklaşık konumunuzu başkalarıyla paylaşın
|
||||||
profile_desc=Profilinizin başkalarına nasıl gösterildiğini yönetin. Ana e-posta adresiniz bildirimler, parola kurtarma ve web tabanlı Git işlemleri için kullanılacaktır.
|
profile_desc=Profilinizin başkalarına nasıl gösterildiğini yönetin. Ana e-posta adresiniz bildirimler, parola kurtarma ve web tabanlı Git işlemleri için kullanılacaktır.
|
||||||
|
password_username_disabled=Yerel olmayan kullanıcılara kullanıcı adlarını değiştirme izni verilmemiştir. Daha fazla bilgi edinmek için lütfen site yöneticisi ile iletişime geçiniz.
|
||||||
|
password_full_name_disabled=Tam adınızı değiştirme izniniz yoktur. Daha fazla bilgi edinmek için lütfen site yöneticisi ile iletişime geçiniz.
|
||||||
full_name=Ad Soyad
|
full_name=Ad Soyad
|
||||||
website=Web Sitesi
|
website=Web Sitesi
|
||||||
location=Konum
|
location=Konum
|
||||||
@ -755,6 +784,7 @@ uploaded_avatar_not_a_image=Yüklenen dosya bir resim dosyası değil.
|
|||||||
uploaded_avatar_is_too_big=Yüklenen dosyanın boyutu (%d KiB), azami boyutu (%d KiB) aşıyor.
|
uploaded_avatar_is_too_big=Yüklenen dosyanın boyutu (%d KiB), azami boyutu (%d KiB) aşıyor.
|
||||||
update_avatar_success=Profil resminiz değiştirildi.
|
update_avatar_success=Profil resminiz değiştirildi.
|
||||||
update_user_avatar_success=Kullanıcının avatarı güncellendi.
|
update_user_avatar_success=Kullanıcının avatarı güncellendi.
|
||||||
|
cropper_prompt=Kaydetmeden önce resmi düzenleyebilirsiniz. Düzenlenen resim PNG biçiminde kaydedilecektir.
|
||||||
|
|
||||||
change_password=Parolayı Güncelle
|
change_password=Parolayı Güncelle
|
||||||
old_password=Mevcut Parola
|
old_password=Mevcut Parola
|
||||||
@ -797,6 +827,7 @@ add_email_success=Yeni e-posta adresi eklendi.
|
|||||||
email_preference_set_success=E-posta tercihi başarıyla ayarlandı.
|
email_preference_set_success=E-posta tercihi başarıyla ayarlandı.
|
||||||
add_openid_success=Yeni OpenID adresi eklendi.
|
add_openid_success=Yeni OpenID adresi eklendi.
|
||||||
keep_email_private=E-posta Adresini Gizle
|
keep_email_private=E-posta Adresini Gizle
|
||||||
|
keep_email_private_popup=Bu, e-posta adresinizi profilde, değişiklik isteği yaptığınızda veya web arayüzünde dosya düzenlediğinizde gizleyecektir. İtilen işlemeler değişmeyecektir.
|
||||||
openid_desc=OpenID, kimlik doğrulama işlemini harici bir sağlayıcıya devretmenize olanak sağlar.
|
openid_desc=OpenID, kimlik doğrulama işlemini harici bir sağlayıcıya devretmenize olanak sağlar.
|
||||||
|
|
||||||
manage_ssh_keys=SSH Anahtarlarını Yönet
|
manage_ssh_keys=SSH Anahtarlarını Yönet
|
||||||
@ -898,6 +929,9 @@ permission_not_set=Ayarlanmadı
|
|||||||
permission_no_access=Erişim Yok
|
permission_no_access=Erişim Yok
|
||||||
permission_read=Okunmuş
|
permission_read=Okunmuş
|
||||||
permission_write=Okuma ve Yazma
|
permission_write=Okuma ve Yazma
|
||||||
|
permission_anonymous_read=Anonim Okuma
|
||||||
|
permission_everyone_read=Herkes Okuyabilir
|
||||||
|
permission_everyone_write=Herkes Yazabilir
|
||||||
access_token_desc=Seçili token izinleri, yetkilendirmeyi ilgili <a %s>API</a> yollarıyla sınırlandıracaktır. Daha fazla bilgi için <a %s>belgeleri</a> okuyun.
|
access_token_desc=Seçili token izinleri, yetkilendirmeyi ilgili <a %s>API</a> yollarıyla sınırlandıracaktır. Daha fazla bilgi için <a %s>belgeleri</a> okuyun.
|
||||||
at_least_one_permission=Bir token oluşturmak için en azından bir izin seçmelisiniz
|
at_least_one_permission=Bir token oluşturmak için en azından bir izin seçmelisiniz
|
||||||
permissions_list=İzinler:
|
permissions_list=İzinler:
|
||||||
@ -925,6 +959,7 @@ oauth2_client_secret_hint=Bu sayfadan ayrıldıktan veya yeniledikten sonra gizl
|
|||||||
oauth2_application_edit=Düzenle
|
oauth2_application_edit=Düzenle
|
||||||
oauth2_application_create_description=OAuth2 uygulamaları, üçüncü taraf uygulamanıza bu durumda kullanıcı hesaplarına erişim sağlar.
|
oauth2_application_create_description=OAuth2 uygulamaları, üçüncü taraf uygulamanıza bu durumda kullanıcı hesaplarına erişim sağlar.
|
||||||
oauth2_application_remove_description=Bir OAuth2 uygulamasının kaldırılması, bu sunucudaki yetkili kullanıcı hesaplarına erişmesini önler. Devam edilsin mi?
|
oauth2_application_remove_description=Bir OAuth2 uygulamasının kaldırılması, bu sunucudaki yetkili kullanıcı hesaplarına erişmesini önler. Devam edilsin mi?
|
||||||
|
oauth2_application_locked=Gitea kimi OAuth2 uygulamalarının başlangıçta ön kaydını, yapılandırmada etkinleştirilmişse yapabilir. Beklenmeyen davranışı önlemek için bunlar ne düzenlenmeli ne de kaldırılmalı. Daha fazla bilgi için OAuth2 belgesine bakın.
|
||||||
|
|
||||||
authorized_oauth2_applications=Yetkili OAuth2 Uygulamaları
|
authorized_oauth2_applications=Yetkili OAuth2 Uygulamaları
|
||||||
authorized_oauth2_applications_description=Kişisel Gitea hesabınıza bu üçüncü parti uygulamalara erişim izni verdiniz. Lütfen artık ihtiyaç duyulmayan uygulamalara erişimi iptal edin.
|
authorized_oauth2_applications_description=Kişisel Gitea hesabınıza bu üçüncü parti uygulamalara erişim izni verdiniz. Lütfen artık ihtiyaç duyulmayan uygulamalara erişimi iptal edin.
|
||||||
@ -933,13 +968,17 @@ revoke_oauth2_grant=Erişimi İptal Et
|
|||||||
revoke_oauth2_grant_description=Bu üçüncü taraf uygulamasına erişimin iptal edilmesi bu uygulamanın verilerinize erişmesini önleyecektir. Emin misiniz?
|
revoke_oauth2_grant_description=Bu üçüncü taraf uygulamasına erişimin iptal edilmesi bu uygulamanın verilerinize erişmesini önleyecektir. Emin misiniz?
|
||||||
revoke_oauth2_grant_success=Erişim başarıyla kaldırıldı.
|
revoke_oauth2_grant_success=Erişim başarıyla kaldırıldı.
|
||||||
|
|
||||||
|
twofa_desc=İki aşamalı kimlik doğrulama, hesabınızın güvenliğini artırır.
|
||||||
twofa_recovery_tip=Aygıtınızı kaybetmeniz durumunda, hesabınıza tekrar erişmek için tek kullanımlık kurtarma anahtarını kullanabileceksiniz.
|
twofa_recovery_tip=Aygıtınızı kaybetmeniz durumunda, hesabınıza tekrar erişmek için tek kullanımlık kurtarma anahtarını kullanabileceksiniz.
|
||||||
twofa_is_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde <strong>kaydedilmiş</strong>.
|
twofa_is_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde <strong>kaydedilmiş</strong>.
|
||||||
twofa_not_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde kaydedilmemiş.
|
twofa_not_enrolled=Hesabınız şu anda iki faktörlü kimlik doğrulaması içinde kaydedilmemiş.
|
||||||
twofa_disable=İki Aşamalı Doğrulamayı Devre Dışı Bırak
|
twofa_disable=İki Aşamalı Doğrulamayı Devre Dışı Bırak
|
||||||
|
twofa_scratch_token_regenerate=Geçici Kodu Yeniden Üret
|
||||||
|
twofa_scratch_token_regenerated=Geçici kodunuz şimdi %s. Güvenli bir yerde saklayın, tekrar gösterilmeyecektir.
|
||||||
twofa_enroll=İki Faktörlü Kimlik Doğrulamaya Kaydolun
|
twofa_enroll=İki Faktörlü Kimlik Doğrulamaya Kaydolun
|
||||||
twofa_disable_note=Gerekirse iki faktörlü kimlik doğrulamayı devre dışı bırakabilirsiniz.
|
twofa_disable_note=Gerekirse iki faktörlü kimlik doğrulamayı devre dışı bırakabilirsiniz.
|
||||||
twofa_disable_desc=İki faktörlü kimlik doğrulamayı devre dışı bırakmak hesabınızı daha az güvenli hale getirir. Devam edilsin mi?
|
twofa_disable_desc=İki faktörlü kimlik doğrulamayı devre dışı bırakmak hesabınızı daha az güvenli hale getirir. Devam edilsin mi?
|
||||||
|
regenerate_scratch_token_desc=Geçici kodunuzu kaybettiyseniz veya oturum açmak için kullandıysanız, buradan sıfırlayabilirsiniz.
|
||||||
twofa_disabled=İki faktörlü kimlik doğrulama devre dışı bırakıldı.
|
twofa_disabled=İki faktörlü kimlik doğrulama devre dışı bırakıldı.
|
||||||
scan_this_image=Kim doğrulama uygulamanızla bu görüntüyü tarayın:
|
scan_this_image=Kim doğrulama uygulamanızla bu görüntüyü tarayın:
|
||||||
or_enter_secret=Veya gizli şeyi girin: %s
|
or_enter_secret=Veya gizli şeyi girin: %s
|
||||||
@ -993,6 +1032,8 @@ new_repo_helper=Bir depo, sürüm geçmişi dahil tüm proje dosyalarını içer
|
|||||||
owner=Sahibi
|
owner=Sahibi
|
||||||
owner_helper=Bazı organizasyonlar, en çok depo sayısı sınırı nedeniyle açılır menüde görünmeyebilir.
|
owner_helper=Bazı organizasyonlar, en çok depo sayısı sınırı nedeniyle açılır menüde görünmeyebilir.
|
||||||
repo_name=Depo İsmi
|
repo_name=Depo İsmi
|
||||||
|
repo_name_profile_public_hint=.profile herkese açık organizasyonunuzun profiline herkesin görüntüleyebileceği bir README.md dosyası eklemek için kullanabileceğiniz özel bir depodur. Başlamak için herkese açık olduğundan ve profile dizininde README ile başladığınızdan emin olun.
|
||||||
|
repo_name_profile_private_hint=.profile-private organizasyonunuzun üye profiline sadece organizasyon üyelerinin görüntüleyebileceği bir README.md eklemek için kullanabileceğiniz özel bir depodur. Başlamak için özel olduğundan ve profil dizininde README ile başladığınızdan emin olun.
|
||||||
repo_size=Depo Boyutu
|
repo_size=Depo Boyutu
|
||||||
template=Şablon
|
template=Şablon
|
||||||
template_select=Bir şablon seçin.
|
template_select=Bir şablon seçin.
|
||||||
@ -1011,6 +1052,8 @@ fork_to_different_account=Başka bir hesaba çatalla
|
|||||||
fork_visibility_helper=Çatallanmış bir deponun görünürlüğü değiştirilemez.
|
fork_visibility_helper=Çatallanmış bir deponun görünürlüğü değiştirilemez.
|
||||||
fork_branch=Çatala klonlanacak dal
|
fork_branch=Çatala klonlanacak dal
|
||||||
all_branches=Tüm dallar
|
all_branches=Tüm dallar
|
||||||
|
view_all_branches=Tüm dalları görüntüle
|
||||||
|
view_all_tags=Tüm etiketleri görüntüle
|
||||||
fork_no_valid_owners=Geçerli bir sahibi olmadığı için bu depo çatallanamaz.
|
fork_no_valid_owners=Geçerli bir sahibi olmadığı için bu depo çatallanamaz.
|
||||||
fork.blocked_user=Depo çatallanamıyor, depo sahibi tarafından engellenmişsiniz.
|
fork.blocked_user=Depo çatallanamıyor, depo sahibi tarafından engellenmişsiniz.
|
||||||
use_template=Bu şablonu kullan
|
use_template=Bu şablonu kullan
|
||||||
@ -1022,6 +1065,8 @@ generate_repo=Depo Oluştur
|
|||||||
generate_from=Şuradan Oluştur
|
generate_from=Şuradan Oluştur
|
||||||
repo_desc=Açıklama
|
repo_desc=Açıklama
|
||||||
repo_desc_helper=Kısa açıklama girin (isteğe bağlı)
|
repo_desc_helper=Kısa açıklama girin (isteğe bağlı)
|
||||||
|
repo_no_desc=Hiçbir açıklama sağlanmadı
|
||||||
|
repo_lang=Dil
|
||||||
repo_gitignore_helper=.gitignore şablonlarını seç.
|
repo_gitignore_helper=.gitignore şablonlarını seç.
|
||||||
repo_gitignore_helper_desc=Sık kullanılan diller için bir şablon listesinden hangi dosyaların izlenmeyeceğini seçin. Her dilin oluşturma araçları tarafından oluşturulan tipik yapılar, varsayılan olarak .gitignore dosyasına dahil edilmiştir.
|
repo_gitignore_helper_desc=Sık kullanılan diller için bir şablon listesinden hangi dosyaların izlenmeyeceğini seçin. Her dilin oluşturma araçları tarafından oluşturulan tipik yapılar, varsayılan olarak .gitignore dosyasına dahil edilmiştir.
|
||||||
issue_labels=Konu Etiketleri
|
issue_labels=Konu Etiketleri
|
||||||
@ -1029,6 +1074,7 @@ issue_labels_helper=Bir konu etiket seti seçin.
|
|||||||
license=Lisans
|
license=Lisans
|
||||||
license_helper=Bir lisans dosyası seçin.
|
license_helper=Bir lisans dosyası seçin.
|
||||||
license_helper_desc=Bir lisans, başkalarının kodunuzla neler yapıp yapamayacağını yönetir. Projeniz için hangisinin doğru olduğundan emin değil misiniz? <a target="_blank" rel="noopener noreferrer" href="%s">Lisans seçme</a> konusuna bakın
|
license_helper_desc=Bir lisans, başkalarının kodunuzla neler yapıp yapamayacağını yönetir. Projeniz için hangisinin doğru olduğundan emin değil misiniz? <a target="_blank" rel="noopener noreferrer" href="%s">Lisans seçme</a> konusuna bakın
|
||||||
|
multiple_licenses=Çoklu Lisans
|
||||||
object_format=Nesne Biçimi
|
object_format=Nesne Biçimi
|
||||||
object_format_helper=Deponun nesne biçimi. Daha sonra değiştirilemez. SHA1 en uyumlu olandır.
|
object_format_helper=Deponun nesne biçimi. Daha sonra değiştirilemez. SHA1 en uyumlu olandır.
|
||||||
readme=README
|
readme=README
|
||||||
@ -1082,15 +1128,20 @@ delete_preexisting_success=%s içindeki kabul edilmeyen dosyalar silindi
|
|||||||
blame_prior=Bu değişiklikten önceki suçu görüntüle
|
blame_prior=Bu değişiklikten önceki suçu görüntüle
|
||||||
blame.ignore_revs=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılıyor. Bunun yerine normal sorumlu görüntüsü için <a href="%s">buraya tıklayın</a>.
|
blame.ignore_revs=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılıyor. Bunun yerine normal sorumlu görüntüsü için <a href="%s">buraya tıklayın</a>.
|
||||||
blame.ignore_revs.failed=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılamadı.
|
blame.ignore_revs.failed=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılamadı.
|
||||||
|
user_search_tooltip=En fazla 30 kullanıcı görüntüler
|
||||||
|
|
||||||
|
tree_path_not_found=%[1] yolu, %[2]s deposunda mevcut değil
|
||||||
|
|
||||||
transfer.accept=Aktarımı Kabul Et
|
transfer.accept=Aktarımı Kabul Et
|
||||||
|
transfer.accept_desc=`"%s" tarafına aktar`
|
||||||
transfer.reject=Aktarımı Reddet
|
transfer.reject=Aktarımı Reddet
|
||||||
|
transfer.reject_desc=`"%s" tarafına aktarımı iptal et`
|
||||||
transfer.no_permission_to_accept=Bu aktarımı kabul etme izniniz yok.
|
transfer.no_permission_to_accept=Bu aktarımı kabul etme izniniz yok.
|
||||||
transfer.no_permission_to_reject=Bu aktarımı reddetme izniniz yok.
|
transfer.no_permission_to_reject=Bu aktarımı reddetme izniniz yok.
|
||||||
|
|
||||||
desc.private=Özel
|
desc.private=Özel
|
||||||
desc.public=Genel
|
desc.public=Genel
|
||||||
|
desc.public_access=Herkese Açık Erişim
|
||||||
desc.template=Şablon
|
desc.template=Şablon
|
||||||
desc.internal=Dahili
|
desc.internal=Dahili
|
||||||
desc.archived=Arşivlenmiş
|
desc.archived=Arşivlenmiş
|
||||||
@ -1160,6 +1211,10 @@ migrate.gogs.description=Notabug.org veya diğer Gogs sunucularından veri aktar
|
|||||||
migrate.onedev.description=Code.onedev.io ve diğer OneDev sunucularından veri aktar.
|
migrate.onedev.description=Code.onedev.io ve diğer OneDev sunucularından veri aktar.
|
||||||
migrate.codebase.description=Codebasehq.com sitesinden veri aktar.
|
migrate.codebase.description=Codebasehq.com sitesinden veri aktar.
|
||||||
migrate.gitbucket.description=GitBucket sunucularından veri aktar.
|
migrate.gitbucket.description=GitBucket sunucularından veri aktar.
|
||||||
|
migrate.codecommit.aws_access_key_id=AWS Erişim Anahtarı Kimliği
|
||||||
|
migrate.codecommit.aws_secret_access_key=AWS Gizli Erişim Anahtarı
|
||||||
|
migrate.codecommit.https_git_credentials_username=HTTPS Git Kimliği Kullanıcı Adı
|
||||||
|
migrate.codecommit.https_git_credentials_password=HTTPS Git Kimliği Parolası
|
||||||
migrate.migrating_git=Git Verilerini Taşıma
|
migrate.migrating_git=Git Verilerini Taşıma
|
||||||
migrate.migrating_topics=Konuları Taşıma
|
migrate.migrating_topics=Konuları Taşıma
|
||||||
migrate.migrating_milestones=Kilometre Taşlarını Taşıma
|
migrate.migrating_milestones=Kilometre Taşlarını Taşıma
|
||||||
@ -1193,6 +1248,7 @@ create_new_repo_command=Komut satırında yeni bir depo oluşturuluyor
|
|||||||
push_exist_repo=Komut satırından mevcut bir depo itiliyor
|
push_exist_repo=Komut satırından mevcut bir depo itiliyor
|
||||||
empty_message=Bu depoda herhangi bir içerik yok.
|
empty_message=Bu depoda herhangi bir içerik yok.
|
||||||
broken_message=Bu deponun altındaki Git verisi okunamıyor. Bu sunucunun yöneticisiyle bağlantıya geçin veya bu depoyu silin.
|
broken_message=Bu deponun altındaki Git verisi okunamıyor. Bu sunucunun yöneticisiyle bağlantıya geçin veya bu depoyu silin.
|
||||||
|
no_branch=Bu deponun hiç bir dalı yok.
|
||||||
|
|
||||||
code=Kod
|
code=Kod
|
||||||
code.desc=Kaynak koda, dosyalara, işlemelere ve dallara eriş.
|
code.desc=Kaynak koda, dosyalara, işlemelere ve dallara eriş.
|
||||||
@ -1302,6 +1358,8 @@ editor.new_branch_name_desc=Yeni dal ismi…
|
|||||||
editor.cancel=İptal
|
editor.cancel=İptal
|
||||||
editor.filename_cannot_be_empty=Dosya adı boş olamaz.
|
editor.filename_cannot_be_empty=Dosya adı boş olamaz.
|
||||||
editor.filename_is_invalid=Dosya adı geçersiz: "%s".
|
editor.filename_is_invalid=Dosya adı geçersiz: "%s".
|
||||||
|
editor.commit_email=İşleme e-postası
|
||||||
|
editor.invalid_commit_email=İşleme e-postası hatalı.
|
||||||
editor.branch_does_not_exist=Bu depoda "%s" dalı yok.
|
editor.branch_does_not_exist=Bu depoda "%s" dalı yok.
|
||||||
editor.branch_already_exists=Bu depoda "%s" dalı zaten var.
|
editor.branch_already_exists=Bu depoda "%s" dalı zaten var.
|
||||||
editor.directory_is_a_file=Dizin adı "%s" zaten bu depoda bir dosya adı olarak kullanılmaktadır.
|
editor.directory_is_a_file=Dizin adı "%s" zaten bu depoda bir dosya adı olarak kullanılmaktadır.
|
||||||
@ -1350,6 +1408,7 @@ commits.signed_by_untrusted_user_unmatched=İşleyici ile eşleşmeyen güvenilm
|
|||||||
commits.gpg_key_id=GPG Anahtar Kimliği
|
commits.gpg_key_id=GPG Anahtar Kimliği
|
||||||
commits.ssh_key_fingerprint=SSH Anahtar Parmak İzi
|
commits.ssh_key_fingerprint=SSH Anahtar Parmak İzi
|
||||||
commits.view_path=Geçmişte bu noktayı görüntüle
|
commits.view_path=Geçmişte bu noktayı görüntüle
|
||||||
|
commits.view_file_diff=Bu dosyanın bu işlemedeki değişikliklerini görüntüle
|
||||||
|
|
||||||
commit.operations=İşlemler
|
commit.operations=İşlemler
|
||||||
commit.revert=Geri Al
|
commit.revert=Geri Al
|
||||||
@ -1410,6 +1469,8 @@ issues.filter_milestones=Kilometre Taşı Süzgeci
|
|||||||
issues.filter_projects=Projeyi Süz
|
issues.filter_projects=Projeyi Süz
|
||||||
issues.filter_labels=Etiket Süzgeci
|
issues.filter_labels=Etiket Süzgeci
|
||||||
issues.filter_reviewers=Gözden Geçiren Süzgeci
|
issues.filter_reviewers=Gözden Geçiren Süzgeci
|
||||||
|
issues.filter_no_results=Sonuç yok
|
||||||
|
issues.filter_no_results_placeholder=Arama filtrelerinizi ayarlamayı deneyin.
|
||||||
issues.new=Yeni Konu
|
issues.new=Yeni Konu
|
||||||
issues.new.title_empty=Başlık boş olamaz
|
issues.new.title_empty=Başlık boş olamaz
|
||||||
issues.new.labels=Etiketler
|
issues.new.labels=Etiketler
|
||||||
@ -1427,6 +1488,7 @@ issues.new.clear_milestone=Kilometre Taşlarını Temizle
|
|||||||
issues.new.assignees=Atananlar
|
issues.new.assignees=Atananlar
|
||||||
issues.new.clear_assignees=Atamaları Temizle
|
issues.new.clear_assignees=Atamaları Temizle
|
||||||
issues.new.no_assignees=Atanan Kişi Yok
|
issues.new.no_assignees=Atanan Kişi Yok
|
||||||
|
issues.new.no_reviewers=Gözden geçiren yok
|
||||||
issues.new.blocked_user=Konu oluşturulamıyor, depo sahibi tarafından engellenmişsiniz.
|
issues.new.blocked_user=Konu oluşturulamıyor, depo sahibi tarafından engellenmişsiniz.
|
||||||
issues.edit.already_changed=Konuya yapılan değişiklikler kaydedilemiyor. İçerik başka kullanıcı tarafından değiştirilmiş gözüküyor. Diğerlerinin değişikliklerinin üzerine yazmamak için lütfen sayfayı yenileyin ve tekrar düzenlemeye çalışın
|
issues.edit.already_changed=Konuya yapılan değişiklikler kaydedilemiyor. İçerik başka kullanıcı tarafından değiştirilmiş gözüküyor. Diğerlerinin değişikliklerinin üzerine yazmamak için lütfen sayfayı yenileyin ve tekrar düzenlemeye çalışın
|
||||||
issues.edit.blocked_user=İçerik düzenlenemiyor, gönderen veya depo sahibi tarafından engellenmişsiniz.
|
issues.edit.blocked_user=İçerik düzenlenemiyor, gönderen veya depo sahibi tarafından engellenmişsiniz.
|
||||||
@ -1483,6 +1545,7 @@ issues.filter_project=Proje
|
|||||||
issues.filter_project_all=Tüm projeler
|
issues.filter_project_all=Tüm projeler
|
||||||
issues.filter_project_none=Proje yok
|
issues.filter_project_none=Proje yok
|
||||||
issues.filter_assignee=Atanan
|
issues.filter_assignee=Atanan
|
||||||
|
issues.filter_assignee_no_assignee=Hiç kimseye atanmamış
|
||||||
issues.filter_poster=Yazar
|
issues.filter_poster=Yazar
|
||||||
issues.filter_type=Tür
|
issues.filter_type=Tür
|
||||||
issues.filter_type.all_issues=Tüm konular
|
issues.filter_type.all_issues=Tüm konular
|
||||||
@ -2029,6 +2092,7 @@ contributors.contribution_type.deletions=Silmeler
|
|||||||
settings=Ayarlar
|
settings=Ayarlar
|
||||||
settings.desc=Ayarlar, deponun ayarlarını yönetebileceğiniz yerdir
|
settings.desc=Ayarlar, deponun ayarlarını yönetebileceğiniz yerdir
|
||||||
settings.options=Depo
|
settings.options=Depo
|
||||||
|
settings.public_access=Herkese Açık Erişim
|
||||||
settings.collaboration=Katkıcılar
|
settings.collaboration=Katkıcılar
|
||||||
settings.collaboration.admin=Yönetici
|
settings.collaboration.admin=Yönetici
|
||||||
settings.collaboration.write=Yazma
|
settings.collaboration.write=Yazma
|
||||||
@ -3525,12 +3589,13 @@ owner.settings.chef.keypair.description=Chef kütüğünde kimlik doğrulaması
|
|||||||
secrets=Gizlilikler
|
secrets=Gizlilikler
|
||||||
description=Gizlilikler belirli işlemlere aktarılacaktır, bunun dışında okunamaz.
|
description=Gizlilikler belirli işlemlere aktarılacaktır, bunun dışında okunamaz.
|
||||||
none=Henüz gizlilik yok.
|
none=Henüz gizlilik yok.
|
||||||
creation=Gizlilik Ekle
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Açıklama
|
creation.description=Açıklama
|
||||||
creation.name_placeholder=küçük-büyük harfe duyarlı değil, alfanümerik karakterler veya sadece alt tire, GITEA_ veya GITHUB_ ile başlayamaz
|
creation.name_placeholder=küçük-büyük harfe duyarlı değil, alfanümerik karakterler veya sadece alt tire, GITEA_ veya GITHUB_ ile başlayamaz
|
||||||
creation.value_placeholder=Herhangi bir içerik girin. Baştaki ve sondaki boşluklar ihmal edilecektir.
|
creation.value_placeholder=Herhangi bir içerik girin. Baştaki ve sondaki boşluklar ihmal edilecektir.
|
||||||
creation.success=Gizlilik "%s" eklendi.
|
|
||||||
creation.failed=Gizlilik eklenemedi.
|
|
||||||
deletion=Gizliliği kaldır
|
deletion=Gizliliği kaldır
|
||||||
deletion.description=Bir gizliliği kaldırma kalıcıdır ve geri alınamaz. Devam edilsin mi?
|
deletion.description=Bir gizliliği kaldırma kalıcıdır ve geri alınamaz. Devam edilsin mi?
|
||||||
deletion.success=Gizlilik kaldırıldı.
|
deletion.success=Gizlilik kaldırıldı.
|
||||||
|
@ -2517,8 +2517,12 @@ conan.details.repository=Репозиторій
|
|||||||
owner.settings.cleanuprules.enabled=Увімкнено
|
owner.settings.cleanuprules.enabled=Увімкнено
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=Опис
|
creation.description=Опис
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -117,6 +117,7 @@ files=文件
|
|||||||
|
|
||||||
error=错误
|
error=错误
|
||||||
error404=您正尝试访问的页面 <strong>不存在</strong> 或 <strong>您尚未被授权</strong> 查看该页面。
|
error404=您正尝试访问的页面 <strong>不存在</strong> 或 <strong>您尚未被授权</strong> 查看该页面。
|
||||||
|
error503=服务器无法完成您的请求,请稍后重试。
|
||||||
go_back=返回
|
go_back=返回
|
||||||
invalid_data=无效数据: %v
|
invalid_data=无效数据: %v
|
||||||
|
|
||||||
@ -129,6 +130,7 @@ pin=固定
|
|||||||
unpin=取消置顶
|
unpin=取消置顶
|
||||||
|
|
||||||
artifacts=制品
|
artifacts=制品
|
||||||
|
expired=已过期
|
||||||
confirm_delete_artifact=您确定要删除制品'%s'吗?
|
confirm_delete_artifact=您确定要删除制品'%s'吗?
|
||||||
|
|
||||||
archived=已归档
|
archived=已归档
|
||||||
@ -449,6 +451,7 @@ use_scratch_code=使用验证口令
|
|||||||
twofa_scratch_used=你已经使用了你的验证口令。你将会转到两步验证设置页面以便移除你的注册设备或者重新生成新的验证口令。
|
twofa_scratch_used=你已经使用了你的验证口令。你将会转到两步验证设置页面以便移除你的注册设备或者重新生成新的验证口令。
|
||||||
twofa_passcode_incorrect=你的验证码不正确。如果你丢失了你的设备,请使用你的验证口令。
|
twofa_passcode_incorrect=你的验证码不正确。如果你丢失了你的设备,请使用你的验证口令。
|
||||||
twofa_scratch_token_incorrect=你的验证口令不正确。
|
twofa_scratch_token_incorrect=你的验证口令不正确。
|
||||||
|
twofa_required=您必须设置两步验证来访问仓库,或者尝试重新登录。
|
||||||
login_userpass=登录
|
login_userpass=登录
|
||||||
login_openid=OpenID
|
login_openid=OpenID
|
||||||
oauth_signup_tab=注册帐号
|
oauth_signup_tab=注册帐号
|
||||||
@ -1524,7 +1527,7 @@ issues.move_to_column_of_project=`将此对象移至 %s 的 %s 中在 %s 上`
|
|||||||
issues.change_milestone_at=`%[3]s 修改了里程碑从 <b>%[1]s</b> 到 <b>%[2]s</b>`
|
issues.change_milestone_at=`%[3]s 修改了里程碑从 <b>%[1]s</b> 到 <b>%[2]s</b>`
|
||||||
issues.change_project_at=于 %[3]s 将此从项目 <b>%[1]s</b> 移到 <b>%[2]s</b>
|
issues.change_project_at=于 %[3]s 将此从项目 <b>%[1]s</b> 移到 <b>%[2]s</b>
|
||||||
issues.remove_milestone_at=`%[2]s 删除了里程碑 <b>%[1]s</b>`
|
issues.remove_milestone_at=`%[2]s 删除了里程碑 <b>%[1]s</b>`
|
||||||
issues.remove_project_at=`从 <b>%s</b> 项目 %s 中删除`
|
issues.remove_project_at=`于 %[2]s 将此工单从项目 <b>%[1]s</b> 中删除`
|
||||||
issues.deleted_milestone=(已删除)
|
issues.deleted_milestone=(已删除)
|
||||||
issues.deleted_project=`(已删除)`
|
issues.deleted_project=`(已删除)`
|
||||||
issues.self_assign_at=`于 %s 指派给自己`
|
issues.self_assign_at=`于 %s 指派给自己`
|
||||||
@ -1877,6 +1880,7 @@ pulls.add_prefix=添加 <strong>%s</strong> 前缀
|
|||||||
pulls.remove_prefix=删除 <strong>%s</strong> 前缀
|
pulls.remove_prefix=删除 <strong>%s</strong> 前缀
|
||||||
pulls.data_broken=此合并请求因为派生仓库信息缺失而中断。
|
pulls.data_broken=此合并请求因为派生仓库信息缺失而中断。
|
||||||
pulls.files_conflicted=此合并请求有变更与目标分支冲突。
|
pulls.files_conflicted=此合并请求有变更与目标分支冲突。
|
||||||
|
pulls.is_checking=正在进行合并冲突检测 ...
|
||||||
pulls.is_ancestor=此分支已经包含在目标分支中,没有什么可以合并。
|
pulls.is_ancestor=此分支已经包含在目标分支中,没有什么可以合并。
|
||||||
pulls.is_empty=此分支上的更改已经在目标分支上。这将是一个空提交。
|
pulls.is_empty=此分支上的更改已经在目标分支上。这将是一个空提交。
|
||||||
pulls.required_status_check_failed=一些必要的检查没有成功
|
pulls.required_status_check_failed=一些必要的检查没有成功
|
||||||
@ -3717,13 +3721,18 @@ owner.settings.chef.keypair.description=需要密钥对才能向 Chef 注册中
|
|||||||
secrets=密钥
|
secrets=密钥
|
||||||
description=Secrets 将被传给特定的 Actions,其它情况将不能读取
|
description=Secrets 将被传给特定的 Actions,其它情况将不能读取
|
||||||
none=还没有密钥。
|
none=还没有密钥。
|
||||||
creation=添加密钥
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=组织描述
|
creation.description=组织描述
|
||||||
creation.name_placeholder=不区分大小写,字母数字或下划线不能以GITEA_ 或 GITHUB_ 开头。
|
creation.name_placeholder=不区分大小写,字母数字或下划线不能以GITEA_ 或 GITHUB_ 开头。
|
||||||
creation.value_placeholder=输入任何内容,开头和结尾的空白都会被省略
|
creation.value_placeholder=输入任何内容,开头和结尾的空白都会被省略
|
||||||
creation.description_placeholder=输入简短描述(可选)。
|
creation.description_placeholder=输入简短描述(可选)。
|
||||||
creation.success=您的密钥 '%s' 添加成功。
|
|
||||||
creation.failed=添加密钥失败。
|
save_success=密钥 '%s' 保存成功。
|
||||||
|
save_failed=密钥保存失败。
|
||||||
|
|
||||||
|
add_secret=添加密钥
|
||||||
|
edit_secret=编辑密钥
|
||||||
deletion=删除密钥
|
deletion=删除密钥
|
||||||
deletion.description=删除密钥是永久性的,无法撤消。继续吗?
|
deletion.description=删除密钥是永久性的,无法撤消。继续吗?
|
||||||
deletion.success=此Secret已被删除。
|
deletion.success=此Secret已被删除。
|
||||||
@ -3840,6 +3849,8 @@ deleted.display_name=已删除项目
|
|||||||
type-1.display_name=个人项目
|
type-1.display_name=个人项目
|
||||||
type-2.display_name=仓库项目
|
type-2.display_name=仓库项目
|
||||||
type-3.display_name=组织项目
|
type-3.display_name=组织项目
|
||||||
|
enter_fullscreen=全屏
|
||||||
|
exit_fullscreen=退出全屏
|
||||||
|
|
||||||
[git.filemode]
|
[git.filemode]
|
||||||
changed_filemode=%[1]s -> %[2]s
|
changed_filemode=%[1]s -> %[2]s
|
||||||
|
@ -959,8 +959,12 @@ conan.details.repository=儲存庫
|
|||||||
owner.settings.cleanuprules.enabled=已啟用
|
owner.settings.cleanuprules.enabled=已啟用
|
||||||
|
|
||||||
[secrets]
|
[secrets]
|
||||||
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=組織描述
|
creation.description=組織描述
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[actions]
|
[actions]
|
||||||
|
|
||||||
|
|
||||||
|
@ -3635,12 +3635,13 @@ owner.settings.chef.keypair.description=驗證 Chef 註冊中心需要一個密
|
|||||||
secrets=Secret
|
secrets=Secret
|
||||||
description=Secret 會被傳給特定的 Action,其他情況無法讀取。
|
description=Secret 會被傳給特定的 Action,其他情況無法讀取。
|
||||||
none=還沒有 Secret。
|
none=還沒有 Secret。
|
||||||
creation=加入 Secret
|
|
||||||
|
; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
|
||||||
creation.description=描述
|
creation.description=描述
|
||||||
creation.name_placeholder=不區分大小寫,只能包含英文字母、數字、底線 ('_'),不能以 GITEA_ 或 GITHUB_ 開頭。
|
creation.name_placeholder=不區分大小寫,只能包含英文字母、數字、底線 ('_'),不能以 GITEA_ 或 GITHUB_ 開頭。
|
||||||
creation.value_placeholder=輸入任何內容,頭尾的空白都會被忽略。
|
creation.value_placeholder=輸入任何內容,頭尾的空白都會被忽略。
|
||||||
creation.success=已新增 Secret「%s」。
|
|
||||||
creation.failed=加入 Secret 失敗。
|
|
||||||
deletion=移除 Secret
|
deletion=移除 Secret
|
||||||
deletion.description=移除 Secret 是永久的且不可還原,是否繼續?
|
deletion.description=移除 Secret 是永久的且不可還原,是否繼續?
|
||||||
deletion.success=已移除此 Secret。
|
deletion.success=已移除此 Secret。
|
||||||
|
8
package-lock.json
generated
8
package-lock.json
generated
@ -10,7 +10,7 @@
|
|||||||
"@citation-js/plugin-csl": "0.7.18",
|
"@citation-js/plugin-csl": "0.7.18",
|
||||||
"@citation-js/plugin-software-formats": "0.6.1",
|
"@citation-js/plugin-software-formats": "0.6.1",
|
||||||
"@github/markdown-toolbar-element": "2.2.3",
|
"@github/markdown-toolbar-element": "2.2.3",
|
||||||
"@github/relative-time-element": "4.4.7",
|
"@github/relative-time-element": "4.4.8",
|
||||||
"@github/text-expander-element": "2.9.1",
|
"@github/text-expander-element": "2.9.1",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
"@primer/octicons": "19.15.1",
|
"@primer/octicons": "19.15.1",
|
||||||
@ -1146,9 +1146,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@github/relative-time-element": {
|
"node_modules/@github/relative-time-element": {
|
||||||
"version": "4.4.7",
|
"version": "4.4.8",
|
||||||
"resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.7.tgz",
|
"resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.8.tgz",
|
||||||
"integrity": "sha512-NZCePEFYtV7qAUI/pHYuqZ8vRhcsfH/dziUZTY9YR5+JwzDCWtEokYSDbDLZjrRl+SAFr02YHUK+UdtP6hPcbQ==",
|
"integrity": "sha512-FSLYm6F3TSQnqHE1EMQUVVgi2XjbCvsESwwXfugHFpBnhyF1uhJOtu0Psp/BB/qqazfdkk7f5fVcu7WuXl3t8Q==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@github/text-expander-element": {
|
"node_modules/@github/text-expander-element": {
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"@citation-js/plugin-csl": "0.7.18",
|
"@citation-js/plugin-csl": "0.7.18",
|
||||||
"@citation-js/plugin-software-formats": "0.6.1",
|
"@citation-js/plugin-software-formats": "0.6.1",
|
||||||
"@github/markdown-toolbar-element": "2.2.3",
|
"@github/markdown-toolbar-element": "2.2.3",
|
||||||
"@github/relative-time-element": "4.4.7",
|
"@github/relative-time-element": "4.4.8",
|
||||||
"@github/text-expander-element": "2.9.1",
|
"@github/text-expander-element": "2.9.1",
|
||||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||||
"@primer/octicons": "19.15.1",
|
"@primer/octicons": "19.15.1",
|
||||||
|
@ -1279,7 +1279,10 @@ func Routes() *web.Router {
|
|||||||
}, reqToken(), reqAdmin())
|
}, reqToken(), reqAdmin())
|
||||||
m.Group("/actions", func() {
|
m.Group("/actions", func() {
|
||||||
m.Get("/tasks", repo.ListActionTasks)
|
m.Get("/tasks", repo.ListActionTasks)
|
||||||
m.Get("/runs/{run}/artifacts", repo.GetArtifactsOfRun)
|
m.Group("/runs/{run}", func() {
|
||||||
|
m.Get("/artifacts", repo.GetArtifactsOfRun)
|
||||||
|
m.Delete("", reqToken(), reqRepoWriter(unit.TypeActions), repo.DeleteActionRun)
|
||||||
|
})
|
||||||
m.Get("/artifacts", repo.GetArtifacts)
|
m.Get("/artifacts", repo.GetArtifacts)
|
||||||
m.Group("/artifacts/{artifact_id}", func() {
|
m.Group("/artifacts/{artifact_id}", func() {
|
||||||
m.Get("", repo.GetArtifact)
|
m.Get("", repo.GetArtifact)
|
||||||
|
@ -1061,6 +1061,58 @@ func GetArtifactsOfRun(ctx *context.APIContext) {
|
|||||||
ctx.JSON(http.StatusOK, &res)
|
ctx.JSON(http.StatusOK, &res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteActionRun Delete a workflow run
|
||||||
|
func DeleteActionRun(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /repos/{owner}/{repo}/actions/runs/{run} repository deleteActionRun
|
||||||
|
// ---
|
||||||
|
// summary: Delete a workflow run
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: name of the owner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repository
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: run
|
||||||
|
// in: path
|
||||||
|
// description: runid of the workflow run
|
||||||
|
// type: integer
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// description: "No Content"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
|
runID := ctx.PathParamInt64("run")
|
||||||
|
run, err := actions_model.GetRunByRepoAndID(ctx, ctx.Repo.Repository.ID, runID)
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.APIError(http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !run.Status.IsDone() {
|
||||||
|
ctx.APIError(http.StatusBadRequest, "this workflow run is not done")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := actions_service.DeleteRun(ctx, run); err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
// GetArtifacts Lists all artifacts for a repository.
|
// GetArtifacts Lists all artifacts for a repository.
|
||||||
func GetArtifacts(ctx *context.APIContext) {
|
func GetArtifacts(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /repos/{owner}/{repo}/actions/artifacts repository getArtifacts
|
// swagger:operation GET /repos/{owner}/{repo}/actions/artifacts repository getArtifacts
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@ -116,6 +117,16 @@ func GetAllCommits(ctx *context.APIContext) {
|
|||||||
// in: query
|
// in: query
|
||||||
// description: filepath of a file/dir
|
// description: filepath of a file/dir
|
||||||
// type: string
|
// type: string
|
||||||
|
// - name: since
|
||||||
|
// in: query
|
||||||
|
// description: Only commits after this date will be returned (ISO 8601 format)
|
||||||
|
// type: string
|
||||||
|
// format: date-time
|
||||||
|
// - name: until
|
||||||
|
// in: query
|
||||||
|
// description: Only commits before this date will be returned (ISO 8601 format)
|
||||||
|
// type: string
|
||||||
|
// format: date-time
|
||||||
// - name: stat
|
// - name: stat
|
||||||
// in: query
|
// in: query
|
||||||
// description: include diff stats for every commit (disable for speedup, default 'true')
|
// description: include diff stats for every commit (disable for speedup, default 'true')
|
||||||
@ -148,6 +159,23 @@ func GetAllCommits(ctx *context.APIContext) {
|
|||||||
// "409":
|
// "409":
|
||||||
// "$ref": "#/responses/EmptyRepository"
|
// "$ref": "#/responses/EmptyRepository"
|
||||||
|
|
||||||
|
since := ctx.FormString("since")
|
||||||
|
until := ctx.FormString("until")
|
||||||
|
|
||||||
|
// Validate since/until as ISO 8601 (RFC3339)
|
||||||
|
if since != "" {
|
||||||
|
if _, err := time.Parse(time.RFC3339, since); err != nil {
|
||||||
|
ctx.APIError(http.StatusUnprocessableEntity, "invalid 'since' format, expected ISO 8601 (RFC3339)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if until != "" {
|
||||||
|
if _, err := time.Parse(time.RFC3339, until); err != nil {
|
||||||
|
ctx.APIError(http.StatusUnprocessableEntity, "invalid 'until' format, expected ISO 8601 (RFC3339)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ctx.Repo.Repository.IsEmpty {
|
if ctx.Repo.Repository.IsEmpty {
|
||||||
ctx.JSON(http.StatusConflict, api.APIError{
|
ctx.JSON(http.StatusConflict, api.APIError{
|
||||||
Message: "Git Repository is empty.",
|
Message: "Git Repository is empty.",
|
||||||
@ -198,6 +226,8 @@ func GetAllCommits(ctx *context.APIContext) {
|
|||||||
RepoPath: ctx.Repo.GitRepo.Path,
|
RepoPath: ctx.Repo.GitRepo.Path,
|
||||||
Not: not,
|
Not: not,
|
||||||
Revision: []string{baseCommit.ID.String()},
|
Revision: []string{baseCommit.ID.String()},
|
||||||
|
Since: since,
|
||||||
|
Until: until,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
@ -205,7 +235,7 @@ func GetAllCommits(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Query commits
|
// Query commits
|
||||||
commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not)
|
commits, err = baseCommit.CommitsByRange(listOptions.Page, listOptions.PageSize, not, since, until)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
@ -221,6 +251,8 @@ func GetAllCommits(ctx *context.APIContext) {
|
|||||||
Not: not,
|
Not: not,
|
||||||
Revision: []string{sha},
|
Revision: []string{sha},
|
||||||
RelPath: []string{path},
|
RelPath: []string{path},
|
||||||
|
Since: since,
|
||||||
|
Until: until,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -237,6 +269,8 @@ func GetAllCommits(ctx *context.APIContext) {
|
|||||||
File: path,
|
File: path,
|
||||||
Not: not,
|
Not: not,
|
||||||
Page: listOptions.Page,
|
Page: listOptions.Page,
|
||||||
|
Since: since,
|
||||||
|
Until: until,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
|
@ -895,6 +895,15 @@ func EditIssue(ctx *context.APIContext) {
|
|||||||
issue.MilestoneID != *form.Milestone {
|
issue.MilestoneID != *form.Milestone {
|
||||||
oldMilestoneID := issue.MilestoneID
|
oldMilestoneID := issue.MilestoneID
|
||||||
issue.MilestoneID = *form.Milestone
|
issue.MilestoneID = *form.Milestone
|
||||||
|
if issue.MilestoneID > 0 {
|
||||||
|
issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, *form.Milestone)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
issue.Milestone = nil
|
||||||
|
}
|
||||||
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
|
@ -609,15 +609,17 @@ func editIssueComment(ctx *context.APIContext, form api.EditIssueCommentOption)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
oldContent := comment.Content
|
if form.Body != comment.Content {
|
||||||
comment.Content = form.Body
|
oldContent := comment.Content
|
||||||
if err := issue_service.UpdateComment(ctx, comment, comment.ContentVersion, ctx.Doer, oldContent); err != nil {
|
comment.Content = form.Body
|
||||||
if errors.Is(err, user_model.ErrBlockedUser) {
|
if err := issue_service.UpdateComment(ctx, comment, comment.ContentVersion, ctx.Doer, oldContent); err != nil {
|
||||||
ctx.APIError(http.StatusForbidden, err)
|
if errors.Is(err, user_model.ErrBlockedUser) {
|
||||||
} else {
|
ctx.APIError(http.StatusForbidden, err)
|
||||||
ctx.APIErrorInternal(err)
|
} else {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
|
ctx.JSON(http.StatusOK, convert.ToAPIComment(ctx, ctx.Repo.Repository, comment))
|
||||||
|
@ -706,6 +706,11 @@ func EditPullRequest(ctx *context.APIContext) {
|
|||||||
issue.MilestoneID != form.Milestone {
|
issue.MilestoneID != form.Milestone {
|
||||||
oldMilestoneID := issue.MilestoneID
|
oldMilestoneID := issue.MilestoneID
|
||||||
issue.MilestoneID = form.Milestone
|
issue.MilestoneID = form.Milestone
|
||||||
|
issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, form.Milestone)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
if err = issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
|
@ -67,6 +67,28 @@ func ListRunners(ctx *context.APIContext, ownerID, repoID int64) {
|
|||||||
ctx.JSON(http.StatusOK, &res)
|
ctx.JSON(http.StatusOK, &res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getRunnerByID(ctx *context.APIContext, ownerID, repoID, runnerID int64) (*actions_model.ActionRunner, bool) {
|
||||||
|
if ownerID != 0 && repoID != 0 {
|
||||||
|
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||||
|
}
|
||||||
|
|
||||||
|
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.APIErrorNotFound("Runner not found")
|
||||||
|
} else {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !runner.EditableInContext(ownerID, repoID) {
|
||||||
|
ctx.APIErrorNotFound("No permission to access this runner")
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return runner, true
|
||||||
|
}
|
||||||
|
|
||||||
// GetRunner get the runner for api route validated ownerID and repoID
|
// GetRunner get the runner for api route validated ownerID and repoID
|
||||||
// ownerID == 0 and repoID == 0 means any runner including global runners
|
// ownerID == 0 and repoID == 0 means any runner including global runners
|
||||||
// ownerID == 0 and repoID != 0 means any runner for the given repo
|
// ownerID == 0 and repoID != 0 means any runner for the given repo
|
||||||
@ -77,13 +99,8 @@ func GetRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
|||||||
if ownerID != 0 && repoID != 0 {
|
if ownerID != 0 && repoID != 0 {
|
||||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||||
}
|
}
|
||||||
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
|
runner, ok := getRunnerByID(ctx, ownerID, repoID, runnerID)
|
||||||
if err != nil {
|
if !ok {
|
||||||
ctx.APIErrorNotFound(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !runner.EditableInContext(ownerID, repoID) {
|
|
||||||
ctx.APIErrorNotFound("No permission to get this runner")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ctx.JSON(http.StatusOK, convert.ToActionRunner(ctx, runner))
|
ctx.JSON(http.StatusOK, convert.ToActionRunner(ctx, runner))
|
||||||
@ -96,20 +113,12 @@ func GetRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
|||||||
// ownerID != 0 and repoID != 0 undefined behavior
|
// ownerID != 0 and repoID != 0 undefined behavior
|
||||||
// Access rights are checked at the API route level
|
// Access rights are checked at the API route level
|
||||||
func DeleteRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
func DeleteRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
||||||
if ownerID != 0 && repoID != 0 {
|
runner, ok := getRunnerByID(ctx, ownerID, repoID, runnerID)
|
||||||
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
if !ok {
|
||||||
}
|
|
||||||
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.APIErrorInternal(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !runner.EditableInContext(ownerID, repoID) {
|
|
||||||
ctx.APIErrorNotFound("No permission to delete this runner")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = actions_model.DeleteRunner(ctx, runner.ID)
|
err := actions_model.DeleteRunner(ctx, runner.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
|
@ -15,6 +15,7 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"code.gitea.io/gitea/modules/validation"
|
||||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
webhook_service "code.gitea.io/gitea/services/webhook"
|
webhook_service "code.gitea.io/gitea/services/webhook"
|
||||||
@ -92,6 +93,10 @@ func checkCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption)
|
|||||||
ctx.APIError(http.StatusUnprocessableEntity, "Invalid content type")
|
ctx.APIError(http.StatusUnprocessableEntity, "Invalid content type")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if !validation.IsValidURL(form.Config["url"]) {
|
||||||
|
ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,6 +159,41 @@ func pullHook(events []string, event string) bool {
|
|||||||
return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), true)
|
return util.SliceContainsString(events, event, true) || util.SliceContainsString(events, string(webhook_module.HookEventPullRequest), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateHookEvents(events []string) webhook_module.HookEvents {
|
||||||
|
if len(events) == 0 {
|
||||||
|
events = []string{"push"}
|
||||||
|
}
|
||||||
|
hookEvents := make(webhook_module.HookEvents)
|
||||||
|
hookEvents[webhook_module.HookEventCreate] = util.SliceContainsString(events, string(webhook_module.HookEventCreate), true)
|
||||||
|
hookEvents[webhook_module.HookEventPush] = util.SliceContainsString(events, string(webhook_module.HookEventPush), true)
|
||||||
|
hookEvents[webhook_module.HookEventDelete] = util.SliceContainsString(events, string(webhook_module.HookEventDelete), true)
|
||||||
|
hookEvents[webhook_module.HookEventFork] = util.SliceContainsString(events, string(webhook_module.HookEventFork), true)
|
||||||
|
hookEvents[webhook_module.HookEventRepository] = util.SliceContainsString(events, string(webhook_module.HookEventRepository), true)
|
||||||
|
hookEvents[webhook_module.HookEventWiki] = util.SliceContainsString(events, string(webhook_module.HookEventWiki), true)
|
||||||
|
hookEvents[webhook_module.HookEventRelease] = util.SliceContainsString(events, string(webhook_module.HookEventRelease), true)
|
||||||
|
hookEvents[webhook_module.HookEventPackage] = util.SliceContainsString(events, string(webhook_module.HookEventPackage), true)
|
||||||
|
hookEvents[webhook_module.HookEventStatus] = util.SliceContainsString(events, string(webhook_module.HookEventStatus), true)
|
||||||
|
hookEvents[webhook_module.HookEventWorkflowJob] = util.SliceContainsString(events, string(webhook_module.HookEventWorkflowJob), true)
|
||||||
|
|
||||||
|
// Issues
|
||||||
|
hookEvents[webhook_module.HookEventIssues] = issuesHook(events, "issues_only")
|
||||||
|
hookEvents[webhook_module.HookEventIssueAssign] = issuesHook(events, string(webhook_module.HookEventIssueAssign))
|
||||||
|
hookEvents[webhook_module.HookEventIssueLabel] = issuesHook(events, string(webhook_module.HookEventIssueLabel))
|
||||||
|
hookEvents[webhook_module.HookEventIssueMilestone] = issuesHook(events, string(webhook_module.HookEventIssueMilestone))
|
||||||
|
hookEvents[webhook_module.HookEventIssueComment] = issuesHook(events, string(webhook_module.HookEventIssueComment))
|
||||||
|
|
||||||
|
// Pull requests
|
||||||
|
hookEvents[webhook_module.HookEventPullRequest] = pullHook(events, "pull_request_only")
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestAssign] = pullHook(events, string(webhook_module.HookEventPullRequestAssign))
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestLabel] = pullHook(events, string(webhook_module.HookEventPullRequestLabel))
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestMilestone] = pullHook(events, string(webhook_module.HookEventPullRequestMilestone))
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestComment] = pullHook(events, string(webhook_module.HookEventPullRequestComment))
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestReview] = pullHook(events, "pull_request_review")
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestReviewRequest] = pullHook(events, string(webhook_module.HookEventPullRequestReviewRequest))
|
||||||
|
hookEvents[webhook_module.HookEventPullRequestSync] = pullHook(events, string(webhook_module.HookEventPullRequestSync))
|
||||||
|
return hookEvents
|
||||||
|
}
|
||||||
|
|
||||||
// addHook add the hook specified by `form`, `ownerID` and `repoID`. If there is
|
// addHook add the hook specified by `form`, `ownerID` and `repoID`. If there is
|
||||||
// an error, write to `ctx` accordingly. Return (webhook, ok)
|
// an error, write to `ctx` accordingly. Return (webhook, ok)
|
||||||
func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoID int64) (*webhook.Webhook, bool) {
|
func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoID int64) (*webhook.Webhook, bool) {
|
||||||
@ -162,9 +202,6 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
|
|||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(form.Events) == 0 {
|
|
||||||
form.Events = []string{"push"}
|
|
||||||
}
|
|
||||||
if form.Config["is_system_webhook"] != "" {
|
if form.Config["is_system_webhook"] != "" {
|
||||||
sw, err := strconv.ParseBool(form.Config["is_system_webhook"])
|
sw, err := strconv.ParseBool(form.Config["is_system_webhook"])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -183,31 +220,7 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, ownerID, repoI
|
|||||||
IsSystemWebhook: isSystemWebhook,
|
IsSystemWebhook: isSystemWebhook,
|
||||||
HookEvent: &webhook_module.HookEvent{
|
HookEvent: &webhook_module.HookEvent{
|
||||||
ChooseEvents: true,
|
ChooseEvents: true,
|
||||||
HookEvents: webhook_module.HookEvents{
|
HookEvents: updateHookEvents(form.Events),
|
||||||
webhook_module.HookEventCreate: util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true),
|
|
||||||
webhook_module.HookEventDelete: util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true),
|
|
||||||
webhook_module.HookEventFork: util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true),
|
|
||||||
webhook_module.HookEventIssues: issuesHook(form.Events, "issues_only"),
|
|
||||||
webhook_module.HookEventIssueAssign: issuesHook(form.Events, string(webhook_module.HookEventIssueAssign)),
|
|
||||||
webhook_module.HookEventIssueLabel: issuesHook(form.Events, string(webhook_module.HookEventIssueLabel)),
|
|
||||||
webhook_module.HookEventIssueMilestone: issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone)),
|
|
||||||
webhook_module.HookEventIssueComment: issuesHook(form.Events, string(webhook_module.HookEventIssueComment)),
|
|
||||||
webhook_module.HookEventPush: util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true),
|
|
||||||
webhook_module.HookEventPullRequest: pullHook(form.Events, "pull_request_only"),
|
|
||||||
webhook_module.HookEventPullRequestAssign: pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign)),
|
|
||||||
webhook_module.HookEventPullRequestLabel: pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel)),
|
|
||||||
webhook_module.HookEventPullRequestMilestone: pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone)),
|
|
||||||
webhook_module.HookEventPullRequestComment: pullHook(form.Events, string(webhook_module.HookEventPullRequestComment)),
|
|
||||||
webhook_module.HookEventPullRequestReview: pullHook(form.Events, "pull_request_review"),
|
|
||||||
webhook_module.HookEventPullRequestReviewRequest: pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest)),
|
|
||||||
webhook_module.HookEventPullRequestSync: pullHook(form.Events, string(webhook_module.HookEventPullRequestSync)),
|
|
||||||
webhook_module.HookEventWiki: util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true),
|
|
||||||
webhook_module.HookEventRepository: util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true),
|
|
||||||
webhook_module.HookEventRelease: util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true),
|
|
||||||
webhook_module.HookEventPackage: util.SliceContainsString(form.Events, string(webhook_module.HookEventPackage), true),
|
|
||||||
webhook_module.HookEventStatus: util.SliceContainsString(form.Events, string(webhook_module.HookEventStatus), true),
|
|
||||||
webhook_module.HookEventWorkflowJob: util.SliceContainsString(form.Events, string(webhook_module.HookEventWorkflowJob), true),
|
|
||||||
},
|
|
||||||
BranchFilter: form.BranchFilter,
|
BranchFilter: form.BranchFilter,
|
||||||
},
|
},
|
||||||
IsActive: form.Active,
|
IsActive: form.Active,
|
||||||
@ -324,6 +337,10 @@ func EditRepoHook(ctx *context.APIContext, form *api.EditHookOption, hookID int6
|
|||||||
func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webhook) bool {
|
func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webhook) bool {
|
||||||
if form.Config != nil {
|
if form.Config != nil {
|
||||||
if url, ok := form.Config["url"]; ok {
|
if url, ok := form.Config["url"]; ok {
|
||||||
|
if !validation.IsValidURL(url) {
|
||||||
|
ctx.APIError(http.StatusUnprocessableEntity, "Invalid url")
|
||||||
|
return false
|
||||||
|
}
|
||||||
w.URL = url
|
w.URL = url
|
||||||
}
|
}
|
||||||
if ct, ok := form.Config["content_type"]; ok {
|
if ct, ok := form.Config["content_type"]; ok {
|
||||||
@ -352,19 +369,10 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update events
|
// Update events
|
||||||
if len(form.Events) == 0 {
|
w.HookEvents = updateHookEvents(form.Events)
|
||||||
form.Events = []string{"push"}
|
|
||||||
}
|
|
||||||
w.PushOnly = false
|
w.PushOnly = false
|
||||||
w.SendEverything = false
|
w.SendEverything = false
|
||||||
w.ChooseEvents = true
|
w.ChooseEvents = true
|
||||||
w.HookEvents[webhook_module.HookEventCreate] = util.SliceContainsString(form.Events, string(webhook_module.HookEventCreate), true)
|
|
||||||
w.HookEvents[webhook_module.HookEventPush] = util.SliceContainsString(form.Events, string(webhook_module.HookEventPush), true)
|
|
||||||
w.HookEvents[webhook_module.HookEventDelete] = util.SliceContainsString(form.Events, string(webhook_module.HookEventDelete), true)
|
|
||||||
w.HookEvents[webhook_module.HookEventFork] = util.SliceContainsString(form.Events, string(webhook_module.HookEventFork), true)
|
|
||||||
w.HookEvents[webhook_module.HookEventRepository] = util.SliceContainsString(form.Events, string(webhook_module.HookEventRepository), true)
|
|
||||||
w.HookEvents[webhook_module.HookEventWiki] = util.SliceContainsString(form.Events, string(webhook_module.HookEventWiki), true)
|
|
||||||
w.HookEvents[webhook_module.HookEventRelease] = util.SliceContainsString(form.Events, string(webhook_module.HookEventRelease), true)
|
|
||||||
w.BranchFilter = form.BranchFilter
|
w.BranchFilter = form.BranchFilter
|
||||||
|
|
||||||
err := w.SetHeaderAuthorization(form.AuthorizationHeader)
|
err := w.SetHeaderAuthorization(form.AuthorizationHeader)
|
||||||
@ -373,23 +381,6 @@ func editHook(ctx *context.APIContext, form *api.EditHookOption, w *webhook.Webh
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issues
|
|
||||||
w.HookEvents[webhook_module.HookEventIssues] = issuesHook(form.Events, "issues_only")
|
|
||||||
w.HookEvents[webhook_module.HookEventIssueAssign] = issuesHook(form.Events, string(webhook_module.HookEventIssueAssign))
|
|
||||||
w.HookEvents[webhook_module.HookEventIssueLabel] = issuesHook(form.Events, string(webhook_module.HookEventIssueLabel))
|
|
||||||
w.HookEvents[webhook_module.HookEventIssueMilestone] = issuesHook(form.Events, string(webhook_module.HookEventIssueMilestone))
|
|
||||||
w.HookEvents[webhook_module.HookEventIssueComment] = issuesHook(form.Events, string(webhook_module.HookEventIssueComment))
|
|
||||||
|
|
||||||
// Pull requests
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequest] = pullHook(form.Events, "pull_request_only")
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestAssign] = pullHook(form.Events, string(webhook_module.HookEventPullRequestAssign))
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestLabel] = pullHook(form.Events, string(webhook_module.HookEventPullRequestLabel))
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestMilestone] = pullHook(form.Events, string(webhook_module.HookEventPullRequestMilestone))
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestComment] = pullHook(form.Events, string(webhook_module.HookEventPullRequestComment))
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestReview] = pullHook(form.Events, "pull_request_review")
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestReviewRequest] = pullHook(form.Events, string(webhook_module.HookEventPullRequestReviewRequest))
|
|
||||||
w.HookEvents[webhook_module.HookEventPullRequestSync] = pullHook(form.Events, string(webhook_module.HookEventPullRequestSync))
|
|
||||||
|
|
||||||
if err := w.UpdateEvent(); err != nil {
|
if err := w.UpdateEvent(); err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return false
|
return false
|
||||||
|
82
routers/api/v1/utils/hook_test.go
Normal file
82
routers/api/v1/utils/hook_test.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/services/contexttest"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTestHookValidation(t *testing.T) {
|
||||||
|
unittest.PrepareTestEnv(t)
|
||||||
|
|
||||||
|
t.Run("Test Validation", func(t *testing.T) {
|
||||||
|
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
|
||||||
|
contexttest.LoadRepo(t, ctx, 1)
|
||||||
|
contexttest.LoadRepoCommit(t, ctx)
|
||||||
|
contexttest.LoadUser(t, ctx, 2)
|
||||||
|
|
||||||
|
checkCreateHookOption(ctx, &structs.CreateHookOption{
|
||||||
|
Type: "gitea",
|
||||||
|
Config: map[string]string{
|
||||||
|
"content_type": "json",
|
||||||
|
"url": "https://example.com/webhook",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Equal(t, 0, ctx.Resp.WrittenStatus()) // not written yet
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Validation with invalid URL", func(t *testing.T) {
|
||||||
|
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
|
||||||
|
contexttest.LoadRepo(t, ctx, 1)
|
||||||
|
contexttest.LoadRepoCommit(t, ctx)
|
||||||
|
contexttest.LoadUser(t, ctx, 2)
|
||||||
|
|
||||||
|
checkCreateHookOption(ctx, &structs.CreateHookOption{
|
||||||
|
Type: "gitea",
|
||||||
|
Config: map[string]string{
|
||||||
|
"content_type": "json",
|
||||||
|
"url": "example.com/webhook",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Validation with invalid webhook type", func(t *testing.T) {
|
||||||
|
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
|
||||||
|
contexttest.LoadRepo(t, ctx, 1)
|
||||||
|
contexttest.LoadRepoCommit(t, ctx)
|
||||||
|
contexttest.LoadUser(t, ctx, 2)
|
||||||
|
|
||||||
|
checkCreateHookOption(ctx, &structs.CreateHookOption{
|
||||||
|
Type: "unknown",
|
||||||
|
Config: map[string]string{
|
||||||
|
"content_type": "json",
|
||||||
|
"url": "example.com/webhook",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Test Validation with empty content type", func(t *testing.T) {
|
||||||
|
ctx, _ := contexttest.MockAPIContext(t, "user2/repo1/hooks")
|
||||||
|
contexttest.LoadRepo(t, ctx, 1)
|
||||||
|
contexttest.LoadRepoCommit(t, ctx)
|
||||||
|
contexttest.LoadUser(t, ctx, 2)
|
||||||
|
|
||||||
|
checkCreateHookOption(ctx, &structs.CreateHookOption{
|
||||||
|
Type: "unknown",
|
||||||
|
Config: map[string]string{
|
||||||
|
"url": "https://example.com/webhook",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
assert.Equal(t, http.StatusUnprocessableEntity, ctx.Resp.WrittenStatus())
|
||||||
|
})
|
||||||
|
}
|
21
routers/api/v1/utils/main_test.go
Normal file
21
routers/api/v1/utils/main_test.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
webhook_service "code.gitea.io/gitea/services/webhook"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
unittest.MainTest(m, &unittest.TestOptions{
|
||||||
|
SetUp: func() error {
|
||||||
|
setting.LoadQueueSettings()
|
||||||
|
return webhook_service.Init()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
@ -220,7 +220,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(cols) > 0 {
|
if len(cols) > 0 {
|
||||||
if err := repo_model.UpdateRepositoryCols(ctx, repo, cols...); err != nil {
|
if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, cols...); err != nil {
|
||||||
log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
|
log.Error("Failed to Update: %s/%s Error: %v", ownerName, repoName, err)
|
||||||
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
|
ctx.JSON(http.StatusInternalServerError, private.HookPostReceiveResult{
|
||||||
Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
|
Err: fmt.Sprintf("Failed to Update: %s/%s Error: %v", ownerName, repoName, err),
|
||||||
|
@ -81,6 +81,7 @@ func ServCommand(ctx *context.PrivateContext) {
|
|||||||
ownerName := ctx.PathParam("owner")
|
ownerName := ctx.PathParam("owner")
|
||||||
repoName := ctx.PathParam("repo")
|
repoName := ctx.PathParam("repo")
|
||||||
mode := perm.AccessMode(ctx.FormInt("mode"))
|
mode := perm.AccessMode(ctx.FormInt("mode"))
|
||||||
|
verb := ctx.FormString("verb")
|
||||||
|
|
||||||
// Set the basic parts of the results to return
|
// Set the basic parts of the results to return
|
||||||
results := private.ServCommandResults{
|
results := private.ServCommandResults{
|
||||||
@ -295,8 +296,11 @@ func ServCommand(ctx *context.PrivateContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Because of the special ref "refs/for" we will need to delay write permission check
|
// Because of the special ref "refs/for" (AGit) we will need to delay write permission check,
|
||||||
if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode {
|
// AGit flow needs to write its own ref when the doer has "reader" permission (allowing to create PR).
|
||||||
|
// The real permission check is done in HookPreReceive (routers/private/hook_pre_receive.go).
|
||||||
|
// Here it should relax the permission check for "git push (git-receive-pack)", but not for others like LFS operations.
|
||||||
|
if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode && verb == git.CmdVerbReceivePack {
|
||||||
mode = perm.AccessModeRead
|
mode = perm.AccessModeRead
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
// ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed
|
// ShowBranchFeed shows tags and/or releases on the repo as RSS / Atom feed
|
||||||
func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) {
|
func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType string) {
|
||||||
commits, err := ctx.Repo.Commit.CommitsByRange(0, 10, "")
|
commits, err := ctx.Repo.Commit.CommitsByRange(0, 10, "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("ShowBranchFeed", err)
|
ctx.ServerError("ShowBranchFeed", err)
|
||||||
return
|
return
|
||||||
|
@ -201,7 +201,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
|
|||||||
switch act.OpType {
|
switch act.OpType {
|
||||||
case activities_model.ActionCommitRepo, activities_model.ActionMirrorSyncPush:
|
case activities_model.ActionCommitRepo, activities_model.ActionMirrorSyncPush:
|
||||||
push := templates.ActionContent2Commits(act)
|
push := templates.ActionContent2Commits(act)
|
||||||
|
_ = act.LoadRepo(ctx)
|
||||||
for _, commit := range push.Commits {
|
for _, commit := range push.Commits {
|
||||||
if len(desc) != 0 {
|
if len(desc) != 0 {
|
||||||
desc += "\n\n"
|
desc += "\n\n"
|
||||||
@ -209,7 +209,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
|
|||||||
desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s",
|
desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s",
|
||||||
html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(ctx), commit.Sha1)),
|
html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(ctx), commit.Sha1)),
|
||||||
commit.Sha1,
|
commit.Sha1,
|
||||||
renderUtils.RenderCommitMessage(commit.Message, nil),
|
renderUtils.RenderCommitMessage(commit.Message, act.Repo),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,6 +317,8 @@ func prepareWorkflowList(ctx *context.Context, workflows []Workflow) {
|
|||||||
pager.AddParamFromRequest(ctx.Req)
|
pager.AddParamFromRequest(ctx.Req)
|
||||||
ctx.Data["Page"] = pager
|
ctx.Data["Page"] = pager
|
||||||
ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0
|
ctx.Data["HasWorkflowsOrRuns"] = len(workflows) > 0 || len(runs) > 0
|
||||||
|
|
||||||
|
ctx.Data["AllowDeleteWorkflowRuns"] = ctx.Repo.CanWrite(unit.TypeActions)
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadIsRefDeleted loads the IsRefDeleted field for each run in the list.
|
// loadIsRefDeleted loads the IsRefDeleted field for each run in the list.
|
||||||
|
@ -200,13 +200,9 @@ func ViewPost(ctx *context_module.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: "ComposeCommentMetas" (usually for comment) is not quite right, but it is still the same as what template "RenderCommitMessage" does.
|
|
||||||
// need to be refactored together in the future
|
|
||||||
metas := ctx.Repo.Repository.ComposeCommentMetas(ctx)
|
|
||||||
|
|
||||||
// the title for the "run" is from the commit message
|
// the title for the "run" is from the commit message
|
||||||
resp.State.Run.Title = run.Title
|
resp.State.Run.Title = run.Title
|
||||||
resp.State.Run.TitleHTML = templates.NewRenderUtils(ctx).RenderCommitMessage(run.Title, metas)
|
resp.State.Run.TitleHTML = templates.NewRenderUtils(ctx).RenderCommitMessage(run.Title, ctx.Repo.Repository)
|
||||||
resp.State.Run.Link = run.Link()
|
resp.State.Run.Link = run.Link()
|
||||||
resp.State.Run.CanCancel = !run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
|
resp.State.Run.CanCancel = !run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
|
||||||
resp.State.Run.CanApprove = run.NeedApproval && ctx.Repo.CanWrite(unit.TypeActions)
|
resp.State.Run.CanApprove = run.NeedApproval && ctx.Repo.CanWrite(unit.TypeActions)
|
||||||
@ -581,6 +577,33 @@ func Approve(ctx *context_module.Context) {
|
|||||||
ctx.JSON(http.StatusOK, struct{}{})
|
ctx.JSON(http.StatusOK, struct{}{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Delete(ctx *context_module.Context) {
|
||||||
|
runIndex := getRunIndex(ctx)
|
||||||
|
repoID := ctx.Repo.Repository.ID
|
||||||
|
|
||||||
|
run, err := actions_model.GetRunByIndex(ctx, repoID, runIndex)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
|
ctx.JSONErrorNotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.ServerError("GetRunByIndex", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !run.Status.IsDone() {
|
||||||
|
ctx.JSONError(ctx.Tr("actions.runs.not_done"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := actions_service.DeleteRun(ctx, run); err != nil {
|
||||||
|
ctx.ServerError("DeleteRun", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSONOK()
|
||||||
|
}
|
||||||
|
|
||||||
// getRunJobs gets the jobs of runIndex, and returns jobs[jobIndex], jobs.
|
// getRunJobs gets the jobs of runIndex, and returns jobs[jobIndex], jobs.
|
||||||
// Any error will be written to the ctx.
|
// Any error will be written to the ctx.
|
||||||
// It never returns a nil job of an empty jobs, if the jobIndex is out of range, it will be treated as 0.
|
// It never returns a nil job of an empty jobs, if the jobIndex is out of range, it will be treated as 0.
|
||||||
@ -588,20 +611,20 @@ func getRunJobs(ctx *context_module.Context, runIndex, jobIndex int64) (*actions
|
|||||||
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
run, err := actions_model.GetRunByIndex(ctx, ctx.Repo.Repository.ID, runIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, util.ErrNotExist) {
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
ctx.HTTPError(http.StatusNotFound, err.Error())
|
ctx.NotFound(nil)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
ctx.HTTPError(http.StatusInternalServerError, err.Error())
|
ctx.ServerError("GetRunByIndex", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
run.Repo = ctx.Repo.Repository
|
run.Repo = ctx.Repo.Repository
|
||||||
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.HTTPError(http.StatusInternalServerError, err.Error())
|
ctx.ServerError("GetRunJobsByRunID", err)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if len(jobs) == 0 {
|
if len(jobs) == 0 {
|
||||||
ctx.HTTPError(http.StatusNotFound)
|
ctx.NotFound(nil)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ func MergeUpstream(ctx *context.Context) {
|
|||||||
_, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, branchName)
|
_, err := repo_service.MergeUpstream(ctx, ctx.Doer, ctx.Repo.Repository, branchName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, util.ErrNotExist) {
|
if errors.Is(err, util.ErrNotExist) {
|
||||||
ctx.JSONError(ctx.Tr("error.not_found"))
|
ctx.JSONErrorNotFound()
|
||||||
return
|
return
|
||||||
} else if pull_service.IsErrMergeConflicts(err) {
|
} else if pull_service.IsErrMergeConflicts(err) {
|
||||||
ctx.JSONError(ctx.Tr("repo.pulls.merge_conflict"))
|
ctx.JSONError(ctx.Tr("repo.pulls.merge_conflict"))
|
||||||
|
@ -78,7 +78,7 @@ func Commits(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Both `git log branchName` and `git log commitId` work.
|
// Both `git log branchName` and `git log commitId` work.
|
||||||
commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "")
|
commits, err := ctx.Repo.Commit.CommitsByRange(page, pageSize, "", "", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("CommitsByRange", err)
|
ctx.ServerError("CommitsByRange", err)
|
||||||
return
|
return
|
||||||
|
@ -391,12 +391,6 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Repo.Repository.IsEmpty {
|
|
||||||
if isEmpty, err := ctx.Repo.GitRepo.IsEmpty(); err == nil && !isEmpty {
|
|
||||||
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
redirectForCommitChoice(ctx, form.CommitChoice, branchName, form.TreePath)
|
redirectForCommitChoice(ctx, form.CommitChoice, branchName, form.TreePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -805,7 +799,7 @@ func UploadFilePost(ctx *context.Context) {
|
|||||||
|
|
||||||
if ctx.Repo.Repository.IsEmpty {
|
if ctx.Repo.Repository.IsEmpty {
|
||||||
if isEmpty, err := ctx.Repo.GitRepo.IsEmpty(); err == nil && !isEmpty {
|
if isEmpty, err := ctx.Repo.GitRepo.IsEmpty(); err == nil && !isEmpty {
|
||||||
_ = repo_model.UpdateRepositoryCols(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
|
_ = repo_model.UpdateRepositoryColsWithAutoTime(ctx, &repo_model.Repository{ID: ctx.Repo.Repository.ID, IsEmpty: false}, "is_empty")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -418,6 +418,16 @@ func UpdateIssueMilestone(ctx *context.Context) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
issue.MilestoneID = milestoneID
|
issue.MilestoneID = milestoneID
|
||||||
|
if milestoneID > 0 {
|
||||||
|
var err error
|
||||||
|
issue.Milestone, err = issues_model.GetMilestoneByRepoID(ctx, ctx.Repo.Repository.ID, milestoneID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetMilestoneByRepoID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
issue.Milestone = nil
|
||||||
|
}
|
||||||
if err := issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
if err := issue_service.ChangeMilestoneAssign(ctx, issue, ctx.Doer, oldMilestoneID); err != nil {
|
||||||
ctx.ServerError("ChangeMilestoneAssign", err)
|
ctx.ServerError("ChangeMilestoneAssign", err)
|
||||||
return
|
return
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user