0
0
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:
bytedream 2025-05-23 01:05:17 +02:00
commit bec5dced70
177 changed files with 2796 additions and 1060 deletions

View File

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

View File

@ -24,7 +24,7 @@ import (
) )
const ( const (
hookBatchSize = 30 hookBatchSize = 500
) )
var ( var (

View File

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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"]'

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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=Το μυστικό έχει αφαιρεθεί.

View File

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

View File

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

View File

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

View File

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

View File

@ -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 lartefact « %s » ? confirm_delete_artifact=Êtes-vous sûr de vouloir supprimer lartefact « %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 daccès nest pas correct. Si vous avez égaré votre appareil, utilisez votre code de secours pour vous connecter. twofa_passcode_incorrect=Votre code daccès nest 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 lauthentification à 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 dajout est impossible par manque d'informations de bifurcation. pulls.data_broken=Cette demande dajout 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 nimporte quoi. Les blancs cernant seront taillés. creation.value_placeholder=Entrez nimporte 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 denregistrer 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 dinformations 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 nest 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é.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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=Секрет удалён.

View File

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

View File

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

View File

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

View File

@ -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=ık Kaynak license=ı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=ık Kimlik login_openid=ı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=ıklama repo_desc=ı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=ıklama creation.description=ı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ı.

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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