Merge remote-tracking branch 'origin/actually_disable_stars' into actually_disable_stars

pull/33208/head
HeCorr 1 week ago
commit 39a1b4c8cd
No known key found for this signature in database
GPG Key ID: 9333B03459C34098
  1. 2
      .eslintrc.cjs
  2. 4
      cmd/web.go
  3. 12
      flake.lock
  4. 5
      flake.nix
  5. 2
      models/actions/run.go
  6. 2
      models/activities/action.go
  7. 23
      models/db/engine_hook.go
  8. 2
      models/db/engine_init.go
  9. 6
      models/fixtures/access.yml
  10. 21
      models/fixtures/webhook.yml
  11. 7
      models/git/branch.go
  12. 4
      models/issues/comment.go
  13. 7
      models/issues/stopwatch.go
  14. 47
      models/perm/access/repo_permission.go
  15. 12
      models/perm/access/repo_permission_test.go
  16. 11
      models/repo/archiver.go
  17. 1
      models/repo/license.go
  18. 50
      models/unittest/fscopy.go
  19. 13
      models/webhook/webhook_system.go
  20. 37
      models/webhook/webhook_system_test.go
  21. 9
      modules/cache/cache.go
  22. 3
      modules/cache/cache_test.go
  23. 12
      modules/git/command.go
  24. 2
      modules/git/command_test.go
  25. 8
      modules/git/commit.go
  26. 10
      modules/git/diff.go
  27. 54
      modules/git/ref.go
  28. 11
      modules/git/ref_test.go
  29. 36
      modules/git/repo_archive.go
  30. 32
      modules/git/repo_archive_test.go
  31. 2
      modules/git/repo_branch_gogit.go
  32. 32
      modules/gtprof/event.go
  33. 175
      modules/gtprof/trace.go
  34. 96
      modules/gtprof/trace_builtin.go
  35. 19
      modules/gtprof/trace_const.go
  36. 93
      modules/gtprof/trace_test.go
  37. 2
      modules/markup/asciicast/asciicast.go
  38. 4
      modules/markup/render.go
  39. 4
      modules/repository/branch.go
  40. 73
      modules/tailmsg/talimsg.go
  41. 2
      modules/templates/helper.go
  42. 59
      modules/util/sec_to_time.go
  43. 21
      modules/util/sec_to_time_test.go
  44. 4
      modules/web/handler.go
  45. 25
      modules/web/routing/context.go
  46. 6
      options/gitignore/Node
  47. 3
      options/gitignore/Python
  48. 4
      options/gitignore/Rust
  49. 11
      options/locale/locale_cs-CZ.ini
  50. 11
      options/locale/locale_de-DE.ini
  51. 6
      options/locale/locale_el-GR.ini
  52. 23
      options/locale/locale_en-US.ini
  53. 6
      options/locale/locale_es-ES.ini
  54. 40
      options/locale/locale_fr-FR.ini
  55. 25
      options/locale/locale_ga-IE.ini
  56. 1
      options/locale/locale_id-ID.ini
  57. 31
      options/locale/locale_ja-JP.ini
  58. 6
      options/locale/locale_lv-LV.ini
  59. 1
      options/locale/locale_pl-PL.ini
  60. 3
      options/locale/locale_pt-BR.ini
  61. 24
      options/locale/locale_pt-PT.ini
  62. 5
      options/locale/locale_ru-RU.ini
  63. 1
      options/locale/locale_sk-SK.ini
  64. 7
      options/locale/locale_tr-TR.ini
  65. 11
      options/locale/locale_zh-CN.ini
  66. 11
      options/locale/locale_zh-TW.ini
  67. 963
      package-lock.json
  68. 38
      package.json
  69. 73
      poetry.lock
  70. 2
      pyproject.toml
  71. 14
      routers/api/actions/runner/main_test.go
  72. 146
      routers/api/actions/runner/utils.go
  73. 21
      routers/api/v1/admin/hooks.go
  74. 10
      routers/api/v1/repo/branch.go
  75. 8
      routers/api/v1/repo/download.go
  76. 9
      routers/api/v1/repo/file.go
  77. 2
      routers/api/v1/repo/hook_test.go
  78. 4
      routers/api/v1/repo/repo_test.go
  79. 27
      routers/common/middleware.go
  80. 2
      routers/init.go
  81. 1
      routers/web/admin/admin.go
  82. 18
      routers/web/admin/diagnosis.go
  83. 18
      routers/web/admin/perftrace.go
  84. 11
      routers/web/admin/stacktrace.go
  85. 11
      routers/web/auth/linkaccount.go
  86. 4
      routers/web/base.go
  87. 2
      routers/web/feed/branch.go
  88. 4
      routers/web/feed/file.go
  89. 2
      routers/web/repo/actions/view.go
  90. 4
      routers/web/repo/blame.go
  91. 17
      routers/web/repo/branch.go
  92. 4
      routers/web/repo/cherry_pick.go
  93. 2
      routers/web/repo/code_frequency.go
  94. 25
      routers/web/repo/commit.go
  95. 2
      routers/web/repo/contributors.go
  96. 8
      routers/web/repo/editor.go
  97. 15
      routers/web/repo/helper.go
  98. 2
      routers/web/repo/issue_dependency.go
  99. 12
      routers/web/repo/issue_label_test.go
  100. 10
      routers/web/repo/issue_poster.go
  101. Some files were not shown because too many files have changed in this diff Show More

@ -403,7 +403,7 @@ module.exports = {
'github/a11y-svg-has-accessible-name': [0],
'github/array-foreach': [0],
'github/async-currenttarget': [2],
'github/async-preventdefault': [2],
'github/async-preventdefault': [0], // https://github.com/github/eslint-plugin-github/issues/599
'github/authenticity-token': [0],
'github/get-attribute': [0],
'github/js-class-name': [0],

@ -18,10 +18,12 @@ import (
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/gtprof"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/public"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/routers/install"
@ -218,6 +220,8 @@ func serveInstalled(ctx *cli.Context) error {
}
}
gtprof.EnableBuiltinTracer(util.Iif(setting.IsProd, 2000*time.Millisecond, 100*time.Millisecond))
// Set up Chi routes
webRoutes := routers.NormalRoutes()
err := listen(webRoutes, true)

@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1731139594,
"narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=",
"lastModified": 1736798957,
"narHash": "sha256-qwpCtZhSsSNQtK4xYGzMiyEDhkNzOCz/Vfu4oL2ETsQ=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2",
"rev": "9abb87b552b7f55ac8916b6fc9e5cb486656a2f3",
"type": "github"
},
"original": {

@ -29,9 +29,14 @@
poetry
# backend
go_1_23
gofumpt
sqlite
];
shellHook = ''
export GO="${pkgs.go_1_23}/bin/go"
export GOROOT="${pkgs.go_1_23}/share/go"
'';
};
}
);

@ -88,7 +88,7 @@ func (run *ActionRun) RefLink() string {
if refName.IsPull() {
return run.Repo.Link() + "/pulls/" + refName.ShortName()
}
return git.RefURL(run.Repo.Link(), run.Ref)
return run.Repo.Link() + "/src/" + refName.RefWebLinkPath()
}
// PrettyRef return #id for pull ref or ShortName for others

@ -355,7 +355,7 @@ func (a *Action) GetBranch() string {
// GetRefLink returns the action's ref link.
func (a *Action) GetRefLink(ctx context.Context) string {
return git.RefURL(a.GetRepoLink(ctx), a.RefName)
return a.GetRepoLink(ctx) + "/src/" + git.RefName(a.RefName).RefWebLinkPath()
}
// GetTag returns the action's repository tag.

@ -7,23 +7,36 @@ import (
"context"
"time"
"code.gitea.io/gitea/modules/gtprof"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"xorm.io/xorm/contexts"
)
type SlowQueryHook struct {
type EngineHook struct {
Threshold time.Duration
Logger log.Logger
}
var _ contexts.Hook = (*SlowQueryHook)(nil)
var _ contexts.Hook = (*EngineHook)(nil)
func (*SlowQueryHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
return c.Ctx, nil
func (*EngineHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) {
ctx, _ := gtprof.GetTracer().Start(c.Ctx, gtprof.TraceSpanDatabase)
return ctx, nil
}
func (h *SlowQueryHook) AfterProcess(c *contexts.ContextHook) error {
func (h *EngineHook) AfterProcess(c *contexts.ContextHook) error {
span := gtprof.GetContextSpan(c.Ctx)
if span != nil {
// Do not record SQL parameters here:
// * It shouldn't expose the parameters because they contain sensitive information, end users need to report the trace details safely.
// * Some parameters contain quite long texts, waste memory and are difficult to display.
span.SetAttributeString(gtprof.TraceAttrDbSQL, c.SQL)
span.End()
} else {
setting.PanicInDevOrTesting("span in database engine hook is nil")
}
if c.ExecuteTime >= h.Threshold {
// 8 is the amount of skips passed to runtime.Caller, so that in the log the correct function
// is being displayed (the function that ultimately wants to execute the query in the code)

@ -72,7 +72,7 @@ func InitEngine(ctx context.Context) error {
xe.SetDefaultContext(ctx)
if setting.Database.SlowQueryThreshold > 0 {
xe.AddHook(&SlowQueryHook{
xe.AddHook(&EngineHook{
Threshold: setting.Database.SlowQueryThreshold,
Logger: log.GetLogger("xorm"),
})

@ -171,3 +171,9 @@
user_id: 40
repo_id: 61
mode: 4
-
id: 30
user_id: 40
repo_id: 1
mode: 2

@ -22,6 +22,7 @@
content_type: 1 # json
events: '{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}'
is_active: true
-
id: 4
repo_id: 2
@ -29,3 +30,23 @@
content_type: 1 # json
events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
is_active: true
-
id: 5
repo_id: 0
owner_id: 0
url: www.example.com/url5
content_type: 1 # json
events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
is_active: true
is_system_webhook: true
-
id: 6
repo_id: 0
owner_id: 0
url: www.example.com/url6
content_type: 1 # json
events: '{"push_only":true,"branch_filter":"{master,feature*}"}'
is_active: true
is_system_webhook: false

@ -167,6 +167,9 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e
BranchName: branchName,
}
}
// FIXME: this design is not right: it doesn't check `branch.IsDeleted`, it doesn't make sense to make callers to check IsDeleted again and again.
// It causes inconsistency with `GetBranches` and `git.GetBranch`, and will lead to strange bugs
// In the future, there should be 2 functions: `GetBranchExisting` and `GetBranchWithDeleted`
return &branch, nil
}
@ -440,6 +443,8 @@ type FindRecentlyPushedNewBranchesOptions struct {
}
type RecentlyPushedNewBranch struct {
BranchRepo *repo_model.Repository
BranchName string
BranchDisplayName string
BranchLink string
BranchCompareURL string
@ -540,7 +545,9 @@ func FindRecentlyPushedNewBranches(ctx context.Context, doer *user_model.User, o
branchDisplayName = fmt.Sprintf("%s:%s", branch.Repo.FullName(), branchDisplayName)
}
newBranches = append(newBranches, &RecentlyPushedNewBranch{
BranchRepo: branch.Repo,
BranchDisplayName: branchDisplayName,
BranchName: branch.Name,
BranchLink: fmt.Sprintf("%s/src/branch/%s", branch.Repo.Link(), util.PathEscapeSegments(branch.Name)),
BranchCompareURL: branch.Repo.ComposeBranchCompareURL(opts.BaseRepo, branch.Name),
CommitTime: branch.CommitTime,

@ -112,8 +112,8 @@ const (
CommentTypePRScheduledToAutoMerge // 34 pr was scheduled to auto merge when checks succeed
CommentTypePRUnScheduledToAutoMerge // 35 pr was un scheduled to auto merge when checks succeed
CommentTypePin // 36 pin Issue
CommentTypeUnpin // 37 unpin Issue
CommentTypePin // 36 pin Issue/PullRequest
CommentTypeUnpin // 37 unpin Issue/PullRequest
CommentTypeChangeTimeEstimate // 38 Change time estimate
)

@ -46,11 +46,6 @@ func (s Stopwatch) Seconds() int64 {
return int64(timeutil.TimeStampNow() - s.CreatedUnix)
}
// Duration returns a human-readable duration string based on local server time
func (s Stopwatch) Duration() string {
return util.SecToTime(s.Seconds())
}
func getStopwatch(ctx context.Context, userID, issueID int64) (sw *Stopwatch, exists bool, err error) {
sw = new(Stopwatch)
exists, err = db.GetEngine(ctx).
@ -201,7 +196,7 @@ func FinishIssueStopwatch(ctx context.Context, user *user_model.User, issue *Iss
Doer: user,
Issue: issue,
Repo: issue.Repo,
Content: util.SecToTime(timediff),
Content: util.SecToHours(timediff),
Type: CommentTypeStopTracking,
TimeID: tt.ID,
}); err != nil {

@ -175,10 +175,14 @@ func (p *Permission) LogString() string {
return fmt.Sprintf(format, args...)
}
func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) {
func finalProcessRepoUnitPermission(user *user_model.User, perm *Permission) {
if user == nil || user.ID <= 0 {
// for anonymous access, it could be:
// AccessMode is None or Read, units has repo units, unitModes is nil
return
}
// apply everyone access permissions
for _, u := range perm.units {
if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.everyoneAccessMode[u.Type] {
if perm.everyoneAccessMode == nil {
@ -187,17 +191,40 @@ func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) {
perm.everyoneAccessMode[u.Type] = u.EveryoneAccessMode
}
}
if perm.unitsMode == nil {
// if unitsMode is not set, then it means that the default p.AccessMode applies to all units
return
}
// remove no permission units
origPermUnits := perm.units
perm.units = make([]*repo_model.RepoUnit, 0, len(perm.units))
for _, u := range origPermUnits {
shouldKeep := false
for t := range perm.unitsMode {
if shouldKeep = u.Type == t; shouldKeep {
break
}
}
for t := range perm.everyoneAccessMode {
if shouldKeep = shouldKeep || u.Type == t; shouldKeep {
break
}
}
if shouldKeep {
perm.units = append(perm.units, u)
}
}
}
// GetUserRepoPermission returns the user permissions to the repository
func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
defer func() {
if err == nil {
applyEveryoneRepoPermission(user, &perm)
}
if log.IsTrace() {
log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm)
finalProcessRepoUnitPermission(user, &perm)
}
log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm)
}()
if err = repo.LoadUnits(ctx); err != nil {
@ -294,16 +321,6 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
}
}
// remove no permission units
perm.units = make([]*repo_model.RepoUnit, 0, len(repo.Units))
for t := range perm.unitsMode {
for _, u := range repo.Units {
if u.Type == t {
perm.units = append(perm.units, u)
}
}
}
return perm, err
}

@ -50,7 +50,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
},
}
applyEveryoneRepoPermission(nil, &perm)
finalProcessRepoUnitPermission(nil, &perm)
assert.False(t, perm.CanRead(unit.TypeWiki))
perm = Permission{
@ -59,7 +59,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
},
}
applyEveryoneRepoPermission(&user_model.User{ID: 0}, &perm)
finalProcessRepoUnitPermission(&user_model.User{ID: 0}, &perm)
assert.False(t, perm.CanRead(unit.TypeWiki))
perm = Permission{
@ -68,7 +68,7 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
},
}
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm)
assert.True(t, perm.CanRead(unit.TypeWiki))
perm = Permission{
@ -77,20 +77,22 @@ func TestApplyEveryoneRepoPermission(t *testing.T) {
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
},
}
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm)
// it should work the same as "EveryoneAccessMode: none" because the default AccessMode should be applied to units
assert.True(t, perm.CanWrite(unit.TypeWiki))
perm = Permission{
units: []*repo_model.RepoUnit{
{Type: unit.TypeCode}, // will be removed
{Type: unit.TypeWiki, EveryoneAccessMode: perm_model.AccessModeRead},
},
unitsMode: map[unit.Type]perm_model.AccessMode{
unit.TypeWiki: perm_model.AccessModeWrite,
},
}
applyEveryoneRepoPermission(&user_model.User{ID: 1}, &perm)
finalProcessRepoUnitPermission(&user_model.User{ID: 1}, &perm)
assert.True(t, perm.CanWrite(unit.TypeWiki))
assert.Len(t, perm.units, 1)
}
func TestUnitAccessMode(t *testing.T) {

@ -56,16 +56,11 @@ func repoArchiverForRelativePath(relativePath string) (*RepoArchiver, error) {
if err != nil {
return nil, util.SilentWrap{Message: fmt.Sprintf("invalid storage path: %s", relativePath), Err: util.ErrInvalidArgument}
}
nameExts := strings.SplitN(parts[2], ".", 2)
if len(nameExts) != 2 {
commitID, archiveType := git.SplitArchiveNameType(parts[2])
if archiveType == git.ArchiveUnknown {
return nil, util.SilentWrap{Message: fmt.Sprintf("invalid storage path: %s", relativePath), Err: util.ErrInvalidArgument}
}
return &RepoArchiver{
RepoID: repoID,
CommitID: parts[1] + nameExts[0],
Type: git.ToArchiveType(nameExts[1]),
}, nil
return &RepoArchiver{RepoID: repoID, CommitID: commitID, Type: archiveType}, nil
}
// GetRepoArchiver get an archiver

@ -54,6 +54,7 @@ func UpdateRepoLicenses(ctx context.Context, repo *Repository, commitID string,
for _, o := range oldLicenses {
// Update already existing license
if o.License == license {
o.CommitID = commitID
if _, err := db.GetEngine(ctx).ID(o.ID).Cols("`commit_id`").Update(o); err != nil {
return err
}

@ -11,35 +11,13 @@ import (
"code.gitea.io/gitea/modules/util"
)
// Copy copies file from source to target path.
func Copy(src, dest string) error {
// Gather file information to set back later.
si, err := os.Lstat(src)
if err != nil {
return err
}
// Handle symbolic link.
if si.Mode()&os.ModeSymlink != 0 {
target, err := os.Readlink(src)
if err != nil {
return err
}
// NOTE: os.Chmod and os.Chtimes don't recognize symbolic link,
// which will lead "no such file or directory" error.
return os.Symlink(target, dest)
}
return util.CopyFile(src, dest)
}
// Sync synchronizes the two files. This is skipped if both files
// SyncFile synchronizes the two files. This is skipped if both files
// exist and the size, modtime, and mode match.
func Sync(srcPath, destPath string) error {
func SyncFile(srcPath, destPath string) error {
dest, err := os.Stat(destPath)
if err != nil {
if os.IsNotExist(err) {
return Copy(srcPath, destPath)
return util.CopyFile(srcPath, destPath)
}
return err
}
@ -55,7 +33,7 @@ func Sync(srcPath, destPath string) error {
return nil
}
return Copy(srcPath, destPath)
return util.CopyFile(srcPath, destPath)
}
// SyncDirs synchronizes files recursively from source to target directory.
@ -66,6 +44,10 @@ func SyncDirs(srcPath, destPath string) error {
return err
}
// the keep file is used to keep the directory in a git repository, it doesn't need to be synced
// and go-git doesn't work with the ".keep" file (it would report errors like "ref is empty")
const keepFile = ".keep"
// find and delete all untracked files
destFiles, err := util.ListDirRecursively(destPath, &util.ListDirOptions{IncludeDir: true})
if err != nil {
@ -73,16 +55,20 @@ func SyncDirs(srcPath, destPath string) error {
}
for _, destFile := range destFiles {
destFilePath := filepath.Join(destPath, destFile)
shouldRemove := filepath.Base(destFilePath) == keepFile
if _, err = os.Stat(filepath.Join(srcPath, destFile)); err != nil {
if os.IsNotExist(err) {
// if src file does not exist, remove dest file
if err = os.RemoveAll(destFilePath); err != nil {
return err
}
shouldRemove = true
} else {
return err
}
}
// if src file does not exist, remove dest file
if shouldRemove {
if err = os.RemoveAll(destFilePath); err != nil {
return err
}
}
}
// sync src files to dest
@ -95,8 +81,8 @@ func SyncDirs(srcPath, destPath string) error {
// util.ListDirRecursively appends a slash to the directory name
if strings.HasSuffix(srcFile, "/") {
err = os.MkdirAll(destFilePath, os.ModePerm)
} else {
err = Sync(filepath.Join(srcPath, srcFile), destFilePath)
} else if filepath.Base(destFilePath) != keepFile {
err = SyncFile(filepath.Join(srcPath, srcFile), destFilePath)
}
if err != nil {
return err

@ -11,6 +11,19 @@ import (
"code.gitea.io/gitea/modules/optional"
)
// GetSystemOrDefaultWebhooks returns webhooks by given argument or all if argument is missing.
func GetSystemOrDefaultWebhooks(ctx context.Context, isSystemWebhook optional.Option[bool]) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5)
if !isSystemWebhook.Has() {
return webhooks, db.GetEngine(ctx).Where("repo_id=? AND owner_id=?", 0, 0).
Find(&webhooks)
}
return webhooks, db.GetEngine(ctx).
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, isSystemWebhook.Value()).
Find(&webhooks)
}
// GetDefaultWebhooks returns all admin-default webhooks.
func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5)

@ -0,0 +1,37 @@
// Copyright 2017 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package webhook
import (
"testing"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/optional"
"github.com/stretchr/testify/assert"
)
func TestGetSystemOrDefaultWebhooks(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hooks, err := GetSystemOrDefaultWebhooks(db.DefaultContext, optional.None[bool]())
assert.NoError(t, err)
if assert.Len(t, hooks, 2) {
assert.Equal(t, int64(5), hooks[0].ID)
assert.Equal(t, int64(6), hooks[1].ID)
}
hooks, err = GetSystemOrDefaultWebhooks(db.DefaultContext, optional.Some(true))
assert.NoError(t, err)
if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(5), hooks[0].ID)
}
hooks, err = GetSystemOrDefaultWebhooks(db.DefaultContext, optional.Some(false))
assert.NoError(t, err)
if assert.Len(t, hooks, 1) {
assert.Equal(t, int64(6), hooks[0].ID)
}
}

@ -37,10 +37,15 @@ func Init() error {
}
const (
testCacheKey = "DefaultCache.TestKey"
SlowCacheThreshold = 100 * time.Microsecond
testCacheKey = "DefaultCache.TestKey"
// SlowCacheThreshold marks cache tests as slow
// set to 30ms per discussion: https://github.com/go-gitea/gitea/issues/33190
// TODO: Replace with metrics histogram
SlowCacheThreshold = 30 * time.Millisecond
)
// Test performs delete, put and get operations on a predefined key
// returns
func Test() (time.Duration, error) {
if defaultCache == nil {
return 0, fmt.Errorf("default cache not initialized")

@ -43,7 +43,8 @@ func TestTest(t *testing.T) {
elapsed, err := Test()
assert.NoError(t, err)
// mem cache should take from 300ns up to 1ms on modern hardware ...
assert.Less(t, elapsed, time.Millisecond)
assert.Positive(t, elapsed)
assert.Less(t, elapsed, SlowCacheThreshold)
}
func TestGetCache(t *testing.T) {

@ -18,6 +18,7 @@ import (
"time"
"code.gitea.io/gitea/modules/git/internal" //nolint:depguard // only this file can use the internal type CmdArg, other files and packages should use AddXxx functions
"code.gitea.io/gitea/modules/gtprof"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/util"
@ -54,7 +55,7 @@ func logArgSanitize(arg string) string {
} else if filepath.IsAbs(arg) {
base := filepath.Base(arg)
dir := filepath.Dir(arg)
return filepath.Join(filepath.Base(dir), base)
return ".../" + filepath.Join(filepath.Base(dir), base)
}
return arg
}
@ -295,15 +296,20 @@ func (c *Command) run(skip int, opts *RunOpts) error {
timeout = defaultCommandExecutionTimeout
}
var desc string
cmdLogString := c.LogString()
callerInfo := util.CallerFuncName(1 /* util */ + 1 /* this */ + skip /* parent */)
if pos := strings.LastIndex(callerInfo, "/"); pos >= 0 {
callerInfo = callerInfo[pos+1:]
}
// these logs are for debugging purposes only, so no guarantee of correctness or stability
desc = fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), c.LogString())
desc := fmt.Sprintf("git.Run(by:%s, repo:%s): %s", callerInfo, logArgSanitize(opts.Dir), cmdLogString)
log.Debug("git.Command: %s", desc)
_, span := gtprof.GetTracer().Start(c.parentContext, gtprof.TraceSpanGitRun)
defer span.End()
span.SetAttributeString(gtprof.TraceAttrFuncCaller, callerInfo)
span.SetAttributeString(gtprof.TraceAttrGitCommand, cmdLogString)
var ctx context.Context
var cancel context.CancelFunc
var finished context.CancelFunc

@ -58,5 +58,5 @@ func TestCommandString(t *testing.T) {
assert.EqualValues(t, cmd.prog+` a "-m msg" "it's a test" "say \"hello\""`, cmd.LogString())
cmd = NewCommandContextNoGlobals(context.Background(), "url: https://a:b@c/", "/root/dir-a/dir-b")
assert.EqualValues(t, cmd.prog+` "url: https://sanitized-credential@c/" dir-a/dir-b`, cmd.LogString())
assert.EqualValues(t, cmd.prog+` "url: https://sanitized-credential@c/" .../dir-a/dir-b`, cmd.LogString())
}

@ -476,8 +476,12 @@ func (c *Commit) GetRepositoryDefaultPublicGPGKey(forceUpdate bool) (*GPGSetting
}
func IsStringLikelyCommitID(objFmt ObjectFormat, s string, minLength ...int) bool {
minLen := util.OptionalArg(minLength, objFmt.FullLength())
if len(s) < minLen || len(s) > objFmt.FullLength() {
maxLen := 64 // sha256
if objFmt != nil {
maxLen = objFmt.FullLength()
}
minLen := util.OptionalArg(minLength, maxLen)
if len(s) < minLen || len(s) > maxLen {
return false
}
for _, c := range s {

@ -64,7 +64,10 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
} else if commit.ParentCount() == 0 {
cmd.AddArguments("show").AddDynamicArguments(endCommit).AddDashesAndList(files...)
} else {
c, _ := commit.Parent(0)
c, err := commit.Parent(0)
if err != nil {
return err
}
cmd.AddArguments("diff", "-M").AddDynamicArguments(c.ID.String(), endCommit).AddDashesAndList(files...)
}
case RawDiffPatch:
@ -74,7 +77,10 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff
} else if commit.ParentCount() == 0 {
cmd.AddArguments("format-patch", "--no-signature", "--stdout", "--root").AddDynamicArguments(endCommit).AddDashesAndList(files...)
} else {
c, _ := commit.Parent(0)
c, err := commit.Parent(0)
if err != nil {
return err
}
query := fmt.Sprintf("%s...%s", endCommit, c.ID.String())
cmd.AddArguments("format-patch", "--no-signature", "--stdout").AddDynamicArguments(query).AddDashesAndList(files...)
}

@ -80,6 +80,10 @@ func RefNameFromTag(shortName string) RefName {
return RefName(TagPrefix + shortName)
}
func RefNameFromCommit(shortName string) RefName {
return RefName(shortName)
}
func (ref RefName) String() string {
return string(ref)
}
@ -181,32 +185,38 @@ func (ref RefName) RefGroup() string {
return ""
}
// RefType is a simple ref type of the reference, it is used for UI and webhooks
type RefType string
const (
RefTypeBranch RefType = "branch"
RefTypeTag RefType = "tag"
RefTypeCommit RefType = "commit"
)
// RefType returns the simple ref type of the reference, e.g. branch, tag
// It's different from RefGroup, which is using the name of the directory under .git/refs
// Here we using branch but not heads, using tag but not tags
func (ref RefName) RefType() string {
var refType string
if ref.IsBranch() {
refType = "branch"
} else if ref.IsTag() {
refType = "tag"
func (ref RefName) RefType() RefType {
switch {
case ref.IsBranch():
return RefTypeBranch
case ref.IsTag():
return RefTypeTag
case IsStringLikelyCommitID(nil, string(ref), 6):
return RefTypeCommit
}
return refType
return ""
}
// RefURL returns the absolute URL for a ref in a repository
func RefURL(repoURL, ref string) string {
refFullName := RefName(ref)
refName := util.PathEscapeSegments(refFullName.ShortName())
switch {
case refFullName.IsBranch():
return repoURL + "/src/branch/" + refName
case refFullName.IsTag():
return repoURL + "/src/tag/" + refName
case !Sha1ObjectFormat.IsValid(ref):
// assume they mean a branch
return repoURL + "/src/branch/" + refName
default:
return repoURL + "/src/commit/" + refName
// RefWebLinkPath returns a path for the reference that can be used in a web link:
// * "branch/<branch_name>"
// * "tag/<tag_name>"
// * "commit/<commit_id>"
// It returns an empty string if the reference is not a branch, tag or commit.
func (ref RefName) RefWebLinkPath() string {
refType := ref.RefType()
if refType == "" {
return ""
}
return string(refType) + "/" + util.PathEscapeSegments(ref.ShortName())
}

@ -20,6 +20,8 @@ func TestRefName(t *testing.T) {
// Test pull names
assert.Equal(t, "1", RefName("refs/pull/1/head").PullName())
assert.True(t, RefName("refs/pull/1/head").IsPull())
assert.True(t, RefName("refs/pull/1/merge").IsPull())
assert.Equal(t, "my/pull", RefName("refs/pull/my/pull/head").PullName())
// Test for branch names
@ -30,9 +32,8 @@ func TestRefName(t *testing.T) {
assert.Equal(t, "c0ffee", RefName("c0ffee").ShortName())
}
func TestRefURL(t *testing.T) {
repoURL := "/user/repo"
assert.Equal(t, repoURL+"/src/branch/foo", RefURL(repoURL, "refs/heads/foo"))
assert.Equal(t, repoURL+"/src/tag/foo", RefURL(repoURL, "refs/tags/foo"))
assert.Equal(t, repoURL+"/src/commit/c0ffee", RefURL(repoURL, "c0ffee"))
func TestRefWebLinkPath(t *testing.T) {
assert.Equal(t, "branch/foo", RefName("refs/heads/foo").RefWebLinkPath())
assert.Equal(t, "tag/foo", RefName("refs/tags/foo").RefWebLinkPath())
assert.Equal(t, "commit/c0ffee", RefName("c0ffee").RefWebLinkPath())
}

@ -16,37 +16,35 @@ import (
type ArchiveType int
const (
// ZIP zip archive type
ZIP ArchiveType = iota + 1
// TARGZ tar gz archive type
TARGZ
// BUNDLE bundle archive type
BUNDLE
ArchiveUnknown ArchiveType = iota
ArchiveZip // 1
ArchiveTarGz // 2
ArchiveBundle // 3
)
// String converts an ArchiveType to string
// String converts an ArchiveType to string: the extension of the archive file without prefix dot
func (a ArchiveType) String() string {
switch a {
case ZIP:
case ArchiveZip:
return "zip"
case TARGZ:
case ArchiveTarGz:
return "tar.gz"
case BUNDLE:
case ArchiveBundle:
return "bundle"
}
return "unknown"
}
func ToArchiveType(s string) ArchiveType {
switch s {
case "zip":
return ZIP
case "tar.gz":
return TARGZ
case "bundle":
return BUNDLE
func SplitArchiveNameType(s string) (string, ArchiveType) {
switch {
case strings.HasSuffix(s, ".zip"):
return strings.TrimSuffix(s, ".zip"), ArchiveZip
case strings.HasSuffix(s, ".tar.gz"):
return strings.TrimSuffix(s, ".tar.gz"), ArchiveTarGz
case strings.HasSuffix(s, ".bundle"):
return strings.TrimSuffix(s, ".bundle"), ArchiveBundle
}
return 0
return s, ArchiveUnknown
}
// CreateArchive create archive content to the target path

@ -0,0 +1,32 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package git
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestArchiveType(t *testing.T) {
name, archiveType := SplitArchiveNameType("test.tar.gz")
assert.Equal(t, "test", name)
assert.Equal(t, "tar.gz", archiveType.String())
name, archiveType = SplitArchiveNameType("a/b/test.zip")
assert.Equal(t, "a/b/test", name)
assert.Equal(t, "zip", archiveType.String())
name, archiveType = SplitArchiveNameType("1234.bundle")
assert.Equal(t, "1234", name)
assert.Equal(t, "bundle", archiveType.String())
name, archiveType = SplitArchiveNameType("test")
assert.Equal(t, "test", name)
assert.Equal(t, "unknown", archiveType.String())
name, archiveType = SplitArchiveNameType("test.xz")
assert.Equal(t, "test.xz", name)
assert.Equal(t, "unknown", archiveType.String())
}

@ -57,7 +57,7 @@ func (repo *Repository) IsBranchExist(name string) bool {
// GetBranches returns branches from the repository, skipping "skip" initial branches and
// returning at most "limit" branches, or all branches if "limit" is 0.
// Branches are returned with sort of `-commiterdate` as the nogogit
// Branches are returned with sort of `-committerdate` as the nogogit
// implementation. This requires full fetch, sort and then the
// skip/limit applies later as gogit returns in undefined order.
func (repo *Repository) GetBranchNames(skip, limit int) ([]string, int, error) {

@ -0,0 +1,32 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gtprof
type EventConfig struct {
attributes []*TraceAttribute
}
type EventOption interface {
applyEvent(*EventConfig)
}
type applyEventFunc func(*EventConfig)
func (f applyEventFunc) applyEvent(cfg *EventConfig) {
f(cfg)
}
func WithAttributes(attrs ...*TraceAttribute) EventOption {
return applyEventFunc(func(cfg *EventConfig) {
cfg.attributes = append(cfg.attributes, attrs...)
})
}
func eventConfigFromOptions(options ...EventOption) *EventConfig {
cfg := &EventConfig{}
for _, opt := range options {
opt.applyEvent(cfg)
}
return cfg
}

@ -0,0 +1,175 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gtprof
import (
"context"
"fmt"
"sync"
"time"
"code.gitea.io/gitea/modules/util"
)
type contextKey struct {
name string
}
var contextKeySpan = &contextKey{"span"}
type traceStarter interface {
start(ctx context.Context, traceSpan *TraceSpan, internalSpanIdx int) (context.Context, traceSpanInternal)
}
type traceSpanInternal interface {
addEvent(name string, cfg *EventConfig)
recordError(err error, cfg *EventConfig)
end()
}
type TraceSpan struct {
// immutable
parent *TraceSpan
internalSpans []traceSpanInternal
internalContexts []context.Context
// mutable, must be protected by mutex
mu sync.RWMutex
name string
statusCode uint32
statusDesc string
startTime time.Time
endTime time.Time
attributes []*TraceAttribute
children []*TraceSpan
}
type TraceAttribute struct {
Key string
Value TraceValue
}
type TraceValue struct {
v any
}
func (t *TraceValue) AsString() string {
return fmt.Sprint(t.v)
}
func (t *TraceValue) AsInt64() int64 {
v, _ := util.ToInt64(t.v)
return v
}
func (t *TraceValue) AsFloat64() float64 {
v, _ := util.ToFloat64(t.v)
return v
}
var globalTraceStarters []traceStarter
type Tracer struct {
starters []traceStarter
}
func (s *TraceSpan) SetName(name string) {
s.mu.Lock()
defer s.mu.Unlock()
s.name = name
}
func (s *TraceSpan) SetStatus(code uint32, desc string) {
s.mu.Lock()
defer s.mu.Unlock()
s.statusCode, s.statusDesc = code, desc
}
func (s *TraceSpan) AddEvent(name string, options ...EventOption) {
cfg := eventConfigFromOptions(options...)
for _, tsp := range s.internalSpans {
tsp.addEvent(name, cfg)
}
}
func (s *TraceSpan) RecordError(err error, options ...EventOption) {
cfg := eventConfigFromOptions(options...)
for _, tsp := range s.internalSpans {
tsp.recordError(err, cfg)
}
}
func (s *TraceSpan) SetAttributeString(key, value string) *TraceSpan {
s.mu.Lock()
defer s.mu.Unlock()
s.attributes = append(s.attributes, &TraceAttribute{Key: key, Value: TraceValue{v: value}})
return s
}
func (t *Tracer) Start(ctx context.Context, spanName string) (context.Context, *TraceSpan) {
starters := t.starters
if starters == nil {
starters = globalTraceStarters
}
ts := &TraceSpan{name: spanName, startTime: time.Now()}
parentSpan := GetContextSpan(ctx)
if parentSpan != nil {
parentSpan.mu.Lock()
parentSpan.children = append(parentSpan.children, ts)
parentSpan.mu.Unlock()
ts.parent = parentSpan
}
parentCtx := ctx
for internalSpanIdx, tsp := range starters {
var internalSpan traceSpanInternal
if parentSpan != nil {
parentCtx = parentSpan.internalContexts[internalSpanIdx]
}
ctx, internalSpan = tsp.start(parentCtx, ts, internalSpanIdx)
ts.internalContexts = append(ts.internalContexts, ctx)
ts.internalSpans = append(ts.internalSpans, internalSpan)
}
ctx = context.WithValue(ctx, contextKeySpan, ts)
return ctx, ts
}
type mutableContext interface {
context.Context
SetContextValue(key, value any)
GetContextValue(key any) any
}
// StartInContext starts a trace span in Gitea's mutable context (usually the web request context).
// Due to the design limitation of Gitea's web framework, it can't use `context.WithValue` to bind a new span into a new context.
// So here we use our "reqctx" framework to achieve the same result: web request context could always see the latest "span".
func (t *Tracer) StartInContext(ctx mutableContext, spanName string) (*TraceSpan, func()) {
curTraceSpan := GetContextSpan(ctx)
_, newTraceSpan := GetTracer().Start(ctx, spanName)
ctx.SetContextValue(contextKeySpan, newTraceSpan)
return newTraceSpan, func() {
newTraceSpan.End()
ctx.SetContextValue(contextKeySpan, curTraceSpan)
}
}
func (s *TraceSpan) End() {
s.mu.Lock()
s.endTime = time.Now()
s.mu.Unlock()
for _, tsp := range s.internalSpans {
tsp.end()
}
}
func GetTracer() *Tracer {
return &Tracer{}
}
func GetContextSpan(ctx context.Context) *TraceSpan {
ts, _ := ctx.Value(contextKeySpan).(*TraceSpan)
return ts
}

@ -0,0 +1,96 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gtprof
import (
"context"
"fmt"
"strings"
"sync/atomic"
"time"
"code.gitea.io/gitea/modules/tailmsg"
)
type traceBuiltinStarter struct{}
type traceBuiltinSpan struct {
ts *TraceSpan
internalSpanIdx int
}
func (t *traceBuiltinSpan) addEvent(name string, cfg *EventConfig) {
// No-op because builtin tracer doesn't need it.
// In the future we might use it to mark the time point between backend logic and network response.
}
func (t *traceBuiltinSpan) recordError(err error, cfg *EventConfig) {
// No-op because builtin tracer doesn't need it.
// Actually Gitea doesn't handle err this way in most cases
}
func (t *traceBuiltinSpan) toString(out *strings.Builder, indent int) {
t.ts.mu.RLock()
defer t.ts.mu.RUnlock()
out.WriteString(strings.Repeat(" ", indent))
out.WriteString(t.ts.name)
if t.ts.endTime.IsZero() {
out.WriteString(" duration: (not ended)")
} else {
out.WriteString(fmt.Sprintf(" duration=%.4fs", t.ts.endTime.Sub(t.ts.startTime).Seconds()))
}
for _, a := range t.ts.attributes {
out.WriteString(" ")
out.WriteString(a.Key)
out.WriteString("=")
value := a.Value.AsString()
if strings.ContainsAny(value, " \t\r\n") {
quoted := false
for _, c := range "\"'`" {
if quoted = !strings.Contains(value, string(c)); quoted {
value = string(c) + value + string(c)
break
}
}
if !quoted {
value = fmt.Sprintf("%q", value)
}
}
out.WriteString(value)
}
out.WriteString("\n")
for _, c := range t.ts.children {
span := c.internalSpans[t.internalSpanIdx].(*traceBuiltinSpan)
span.toString(out, indent+2)
}
}
func (t *traceBuiltinSpan) end() {
if t.ts.parent == nil {
// TODO: debug purpose only
// TODO: it should distinguish between http response network lag and actual processing time
threshold := time.Duration(traceBuiltinThreshold.Load())
if threshold != 0 && t.ts.endTime.Sub(t.ts.startTime) > threshold {
sb := &strings.Builder{}
t.toString(sb, 0)
tailmsg.GetManager().GetTraceRecorder().Record(sb.String())
}
}
}
func (t *traceBuiltinStarter) start(ctx context.Context, traceSpan *TraceSpan, internalSpanIdx int) (context.Context, traceSpanInternal) {
return ctx, &traceBuiltinSpan{ts: traceSpan, internalSpanIdx: internalSpanIdx}
}
func init() {
globalTraceStarters = append(globalTraceStarters, &traceBuiltinStarter{})
}
var traceBuiltinThreshold atomic.Int64
func EnableBuiltinTracer(threshold time.Duration) {
traceBuiltinThreshold.Store(int64(threshold))
}

@ -0,0 +1,19 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gtprof
// Some interesting names could be found in https://github.com/open-telemetry/opentelemetry-go/tree/main/semconv
const (
TraceSpanHTTP = "http"
TraceSpanGitRun = "git-run"
TraceSpanDatabase = "database"
)
const (
TraceAttrFuncCaller = "func.caller"
TraceAttrDbSQL = "db.sql"
TraceAttrGitCommand = "git.command"
TraceAttrHTTPRoute = "http.route"
)

@ -0,0 +1,93 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package gtprof
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
)
// "vendor span" is a simple demo for a span from a vendor library
var vendorContextKey any = "vendorContextKey"
type vendorSpan struct {
name string
children []*vendorSpan
}
func vendorTraceStart(ctx context.Context, name string) (context.Context, *vendorSpan) {
span := &vendorSpan{name: name}
parentSpan, ok := ctx.Value(vendorContextKey).(*vendorSpan)
if ok {
parentSpan.children = append(parentSpan.children, span)
}
ctx = context.WithValue(ctx, vendorContextKey, span)
return ctx, span
}
// below "testTrace*" integrate the vendor span into our trace system
type testTraceSpan struct {
vendorSpan *vendorSpan
}
func (t *testTraceSpan) addEvent(name string, cfg *EventConfig) {}
func (t *testTraceSpan) recordError(err error, cfg *EventConfig) {}
func (t *testTraceSpan) end() {}
type testTraceStarter struct{}
func (t *testTraceStarter) start(ctx context.Context, traceSpan *TraceSpan, internalSpanIdx int) (context.Context, traceSpanInternal) {
ctx, span := vendorTraceStart(ctx, traceSpan.name)
return ctx, &testTraceSpan{span}
}
func TestTraceStarter(t *testing.T) {
globalTraceStarters = []traceStarter{&testTraceStarter{}}
ctx := context.Background()
ctx, span := GetTracer().Start(ctx, "root")
defer span.End()
func(ctx context.Context) {
ctx, span := GetTracer().Start(ctx, "span1")
defer span.End()
func(ctx context.Context) {
_, span := GetTracer().Start(ctx, "spanA")
defer span.End()
}(ctx)
func(ctx context.Context) {
_, span := GetTracer().Start(ctx, "spanB")
defer span.End()
}(ctx)
}(ctx)
func(ctx context.Context) {
_, span := GetTracer().Start(ctx, "span2")
defer span.End()
}(ctx)
var spanFullNames []string
var collectSpanNames func(parentFullName string, s *vendorSpan)
collectSpanNames = func(parentFullName string, s *vendorSpan) {
fullName := parentFullName + "/" + s.name
spanFullNames = append(spanFullNames, fullName)
for _, c := range s.children {
collectSpanNames(fullName, c)
}
}
collectSpanNames("", span.internalSpans[0].(*testTraceSpan).vendorSpan)
assert.Equal(t, []string{
"/root",
"/root/span1",
"/root/span1/spanA",
"/root/span1/spanB",
"/root/span2",
}, spanFullNames)
}

@ -46,7 +46,7 @@ func (Renderer) Render(ctx *markup.RenderContext, _ io.Reader, output io.Writer)
setting.AppSubURL,
url.PathEscape(ctx.RenderOptions.Metas["user"]),
url.PathEscape(ctx.RenderOptions.Metas["repo"]),
ctx.RenderOptions.Metas["BranchNameSubURL"],
ctx.RenderOptions.Metas["RefTypeNameSubURL"],
url.PathEscape(ctx.RenderOptions.RelativePath),
)
return ctx.RenderInternal.FormatWithSafeAttrs(output, `<div class="%s" %s="%s"></div>`, playerClassName, playerSrcAttr, rawURL)

@ -44,7 +44,7 @@ type RenderOptions struct {
MarkupType string
// user&repo, format&style&regexp (for external issue pattern), teams&org (for mention)
// BranchNameSubURL (for iframe&asciicast)
// RefTypeNameSubURL (for iframe&asciicast)
// markupAllowShortIssuePattern
// markdownLineBreakStyle (comment, document)
Metas map[string]string
@ -170,7 +170,7 @@ sandbox="allow-scripts"
setting.AppSubURL,
url.PathEscape(ctx.RenderOptions.Metas["user"]),
url.PathEscape(ctx.RenderOptions.Metas["repo"]),
ctx.RenderOptions.Metas["BranchNameSubURL"],
ctx.RenderOptions.Metas["RefTypeNameSubURL"],
url.PathEscape(ctx.RenderOptions.RelativePath),
))
return err

@ -6,7 +6,6 @@ package repository
import (
"context"
"fmt"
"strings"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
@ -52,9 +51,6 @@ func SyncRepoBranchesWithRepo(ctx context.Context, repo *repo_model.Repository,
{
branches, _, err := gitRepo.GetBranchNames(0, 0)
if err != nil {
if strings.Contains(err.Error(), "ref file is empty") {
return 0, nil
}
return 0, err
}
log.Trace("SyncRepoBranches[%s]: branches[%d]: %v", repo.FullName(), len(branches), branches)

@ -0,0 +1,73 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package tailmsg
import (
"sync"
"time"
)
type MsgRecord struct {
Time time.Time
Content string
}
type MsgRecorder interface {
Record(content string)
GetRecords() []*MsgRecord
}
type memoryMsgRecorder struct {
mu sync.RWMutex
msgs []*MsgRecord
limit int
}
// TODO: use redis for a clustered environment
func (m *memoryMsgRecorder) Record(content string) {
m.mu.Lock()
defer m.mu.Unlock()
m.msgs = append(m.msgs, &MsgRecord{
Time: time.Now(),
Content: content,
})
if len(m.msgs) > m.limit {
m.msgs = m.msgs[len(m.msgs)-m.limit:]
}
}
func (m *memoryMsgRecorder) GetRecords() []*MsgRecord {
m.mu.RLock()
defer m.mu.RUnlock()
ret := make([]*MsgRecord, len(m.msgs))
copy(ret, m.msgs)
return ret
}
func NewMsgRecorder(limit int) MsgRecorder {
return &memoryMsgRecorder{
limit: limit,
}
}
type Manager struct {
traceRecorder MsgRecorder
logRecorder MsgRecorder
}
func (m *Manager) GetTraceRecorder() MsgRecorder {
return m.traceRecorder
}
func (m *Manager) GetLogRecorder() MsgRecorder {
return m.logRecorder
}
var GetManager = sync.OnceValue(func() *Manager {
return &Manager{
traceRecorder: NewMsgRecorder(100),
logRecorder: NewMsgRecorder(1000),
}
})

@ -69,7 +69,7 @@ func NewFuncMap() template.FuncMap {
// time / number / format
"FileSize": base.FileSize,
"CountFmt": countFmt,
"Sec2Time": util.SecToTime,
"Sec2Time": util.SecToHours,
"TimeEstimateString": timeEstimateString,

@ -8,59 +8,17 @@ import (
"strings"
)
// SecToTime converts an amount of seconds to a human-readable string. E.g.
// 66s -> 1 minute 6 seconds
// 52410s -> 14 hours 33 minutes
// 563418 -> 6 days 12 hours
// 1563418 -> 2 weeks 4 days
// 3937125s -> 1 month 2 weeks
// 45677465s -> 1 year 6 months
func SecToTime(durationVal any) string {
// SecToHours converts an amount of seconds to a human-readable hours string.
// This is stable for planning and managing timesheets.
// Here it only supports hours and minutes, because a work day could contain 6 or 7 or 8 hours.
func SecToHours(durationVal any) string {
duration, _ := ToInt64(durationVal)
formattedTime := ""
// The following four variables are calculated by taking
// into account the previously calculated variables, this avoids
// pitfalls when using remainders. As that could lead to incorrect
// results when the calculated number equals the quotient number.
remainingDays := duration / (60 * 60 * 24)
years := remainingDays / 365
remainingDays -= years * 365
months := remainingDays * 12 / 365
remainingDays -= months * 365 / 12
weeks := remainingDays / 7
remainingDays -= weeks * 7
days := remainingDays
// The following three variables are calculated without depending
// on the previous calculated variables.
hours := (duration / 3600) % 24
hours := duration / 3600
minutes := (duration / 60) % 60
seconds := duration % 60
// Extract only the relevant information of the time
// If the time is greater than a year, it makes no sense to display seconds.
switch {
case years > 0:
formattedTime = formatTime(years, "year", formattedTime)
formattedTime = formatTime(months, "month", formattedTime)
case months > 0:
formattedTime = formatTime(months, "month", formattedTime)
formattedTime = formatTime(weeks, "week", formattedTime)
case weeks > 0:
formattedTime = formatTime(weeks, "week", formattedTime)
formattedTime = formatTime(days, "day", formattedTime)
case days > 0:
formattedTime = formatTime(days, "day", formattedTime)
formattedTime = formatTime(hours, "hour", formattedTime)
case hours > 0:
formattedTime = formatTime(hours, "hour", formattedTime)
formattedTime = formatTime(minutes, "minute", formattedTime)
default:
formattedTime = formatTime(minutes, "minute", formattedTime)
formattedTime = formatTime(seconds, "second", formattedTime)
}
formattedTime := ""
formattedTime = formatTime(hours, "hour", formattedTime)
formattedTime = formatTime(minutes, "minute", formattedTime)
// The formatTime() function always appends a space at the end. This will be trimmed
return strings.TrimRight(formattedTime, " ")
@ -76,6 +34,5 @@ func formatTime(value int64, name, formattedTime string) string {
} else if value > 1 {
formattedTime = fmt.Sprintf("%s%d %ss ", formattedTime, value, name)
}
return formattedTime
}

@ -9,22 +9,17 @@ import (
"github.com/stretchr/testify/assert"
)
func TestSecToTime(t *testing.T) {
func TestSecToHours(t *testing.T) {
second := int64(1)
minute := 60 * second
hour := 60 * minute
day := 24 * hour
year := 365 * day
assert.Equal(t, "1 minute 6 seconds", SecToTime(minute+6*second))
assert.Equal(t, "1 hour", SecToTime(hour))
assert.Equal(t, "1 hour", SecToTime(hour+second))
assert.Equal(t, "14 hours 33 minutes", SecToTime(14*hour+33*minute+30*second))
assert.Equal(t, "6 days 12 hours", SecToTime(6*day+12*hour+30*minute+18*second))
assert.Equal(t, "2 weeks 4 days", SecToTime((2*7+4)*day+2*hour+16*minute+58*second))
assert.Equal(t, "4 weeks", SecToTime(4*7*day))
assert.Equal(t, "4 weeks 1 day", SecToTime((4*7+1)*day))
assert.Equal(t, "1 month 2 weeks", SecToTime((6*7+3)*day+13*hour+38*minute+45*second))
assert.Equal(t, "11 months", SecToTime(year-25*day))
assert.Equal(t, "1 year 5 months", SecToTime(year+163*day+10*hour+11*minute+5*second))
assert.Equal(t, "1 minute", SecToHours(minute+6*second))
assert.Equal(t, "1 hour", SecToHours(hour))
assert.Equal(t, "1 hour", SecToHours(hour+second))
assert.Equal(t, "14 hours 33 minutes", SecToHours(14*hour+33*minute+30*second))
assert.Equal(t, "156 hours 30 minutes", SecToHours(6*day+12*hour+30*minute+18*second))
assert.Equal(t, "98 hours 16 minutes", SecToHours(4*day+2*hour+16*minute+58*second))
assert.Equal(t, "672 hours", SecToHours(4*7*day))
}

@ -121,7 +121,7 @@ func wrapHandlerProvider[T http.Handler](hp func(next http.Handler) T, funcInfo
return func(next http.Handler) http.Handler {
h := hp(next) // this handle could be dynamically generated, so we can't use it for debug info
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
routing.UpdateFuncInfo(req.Context(), funcInfo)
defer routing.RecordFuncInfo(req.Context(), funcInfo)()
h.ServeHTTP(resp, req)
})
}
@ -157,7 +157,7 @@ func toHandlerProvider(handler any) func(next http.Handler) http.Handler {
return // it's doing pre-check, just return
}
routing.UpdateFuncInfo(req.Context(), funcInfo)
defer routing.RecordFuncInfo(req.Context(), funcInfo)()
ret := fn.Call(argsIn)
// handle the return value (no-op at the moment)

@ -6,22 +6,29 @@ package routing
import (
"context"
"net/http"
"code.gitea.io/gitea/modules/gtprof"
"code.gitea.io/gitea/modules/reqctx"
)
type contextKeyType struct{}
var contextKey contextKeyType
// UpdateFuncInfo updates a context's func info
func UpdateFuncInfo(ctx context.Context, funcInfo *FuncInfo) {
record, ok := ctx.Value(contextKey).(*requestRecord)
if !ok {
return
// RecordFuncInfo records a func info into context
func RecordFuncInfo(ctx context.Context, funcInfo *FuncInfo) (end func()) {
end = func() {}
if reqCtx := reqctx.FromContext(ctx); reqCtx != nil {
var traceSpan *gtprof.TraceSpan
traceSpan, end = gtprof.GetTracer().StartInContext(reqCtx, "http.func")
traceSpan.SetAttributeString("func", funcInfo.shortName)
}
record.lock.Lock()
record.funcInfo = funcInfo
record.lock.Unlock()
if record, ok := ctx.Value(contextKey).(*requestRecord); ok {
record.lock.Lock()
record.funcInfo = funcInfo
record.lock.Unlock()
}
return end
}
// MarkLongPolling marks the request is a long-polling request, and the logger may output different message for it

@ -104,6 +104,12 @@ dist
.temp
.cache
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus

@ -167,5 +167,8 @@ cython_debug/
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc

@ -3,10 +3,6 @@
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

@ -1115,9 +1115,6 @@ blame.ignore_revs=Ignorování revizí v <a href="%s">.git-blame-ignorerevs</a>.
blame.ignore_revs.failed=Nepodařilo se ignorovat revize v <a href="%s">.git-blame-ignore-revs</a>.
user_search_tooltip=Zobrazí maximálně 30 uživatelů
tree_path_not_found_commit=Cesta %[1]s v commitu %[2]s neexistuje
tree_path_not_found_branch=Cesta %[1]s ve větvi %[2]s neexistuje
tree_path_not_found_tag=Cesta %[1]s ve značce %[2]s neexistuje
transfer.accept=Přijmout převod
transfer.accept_desc=Převést do „%s“
@ -1651,7 +1648,7 @@ issues.attachment.open_tab=`Klikněte pro zobrazení „%s“ v nové záložce`
issues.attachment.download=`Klikněte pro stažení „%s“`
issues.subscribe=Odebírat
issues.unsubscribe=Zrušit odběr
issues.unpin_issue=Odepnout úkol
issues.unpin=Odepnout
issues.max_pinned=Nemůžete připnout další úkoly
issues.pin_comment=připnuto %s
issues.unpin_comment=odepnul/a tento %s
@ -1686,16 +1683,13 @@ issues.timetracker_timer_manually_add=Přidat čas
issues.time_estimate_set=Nastavit odhadovaný čas
issues.time_estimate_display=Odhad: %s
issues.change_time_estimate_at=změnil/a odhad času na <b>%s</b> %s
issues.remove_time_estimate_at=odstranil/a odhad času %s
issues.time_estimate_invalid=Formát odhadu času je neplatný
issues.start_tracking_history=započal/a práci %s
issues.tracker_auto_close=Časovač se automaticky zastaví po zavření tohoto úkolu
issues.tracking_already_started=`Již jste spustili sledování času na <a href="%s">jiném úkolu</a>!`
issues.stop_tracking_history=pracoval/a <b>%s</b> %s
issues.cancel_tracking_history=`zrušil/a sledování času %s`
issues.del_time=Odstranit tento časový záznam
issues.add_time_history=přidal/a strávený čas <b>%s</b> %s
issues.del_time_history=`odstranil/a strávený čas %s`
issues.add_time_manually=Přidat čas ručně
issues.add_time_hours=Hodiny
@ -2158,7 +2152,6 @@ settings.advanced_settings=Pokročilá nastavení
settings.wiki_desc=Povolit Wiki repozitáře
settings.use_internal_wiki=Používat vestavěnou Wiki
settings.default_wiki_branch_name=Výchozí název větve Wiki
settings.default_wiki_everyone_access=Výchozí přístupová práva pro přihlášené uživatele:
settings.failed_to_change_default_wiki_branch=Změna výchozí větve wiki se nezdařila.
settings.use_external_wiki=Používat externí Wiki
settings.external_wiki_url=URL externí Wiki
@ -3373,7 +3366,6 @@ monitor.execute_time=Doba provádění
monitor.last_execution_result=Výsledek
monitor.process.cancel=Zrušit proces
monitor.process.cancel_desc=Zrušení procesu může způsobit ztrátu dat
monitor.process.cancel_notices=Zrušit: <strong>%s</strong>?
monitor.process.children=Potomek
monitor.queues=Fronty
@ -3570,7 +3562,6 @@ conda.install=Pro instalaci balíčku pomocí Conda spusťte následující př
container.details.type=Typ obrazu
container.details.platform=Platforma
container.pull=Stáhněte obraz z příkazové řádky:
container.digest=Výběr:
container.multi_arch=OS/architektura
container.layers=Vrstvy obrazů
container.labels=Štítky

@ -1111,9 +1111,6 @@ blame.ignore_revs=Revisionen in <a href="%s">.git-blame-ignore-revs</a> werden i
blame.ignore_revs.failed=Fehler beim Ignorieren der Revisionen in <a href="%s">.git-blame-ignore-revs</a>.
user_search_tooltip=Zeigt maximal 30 Benutzer
tree_path_not_found_commit=Pfad %[1]s existiert nicht in Commit%[2]s
tree_path_not_found_branch=Pfad %[1]s existiert nicht in Branch %[2]s
tree_path_not_found_tag=Pfad %[1]s existiert nicht in Tag %[2]s
transfer.accept=Übertragung Akzeptieren
transfer.accept_desc=`Übertragung nach "%s"`
@ -1646,7 +1643,7 @@ issues.attachment.open_tab=`Klicken, um „%s“ in einem neuen Tab zu öffnen`
issues.attachment.download=`Klicken, um „%s“ herunterzuladen`
issues.subscribe=Abonnieren
issues.unsubscribe=Abbestellen
issues.unpin_issue=Issue abheften
issues.unpin=Loslösen
issues.max_pinned=Du kannst keine weiteren Issues anheften
issues.pin_comment=hat das %s angeheftet
issues.unpin_comment=hat das %s abgeheftet
@ -1681,16 +1678,13 @@ issues.timetracker_timer_manually_add=Zeit hinzufügen
issues.time_estimate_set=Geschätzte Zeit festlegen
issues.time_estimate_display=Schätzung: %s
issues.change_time_estimate_at=Zeitschätzung geändert zu <b>%s</b> %s
issues.remove_time_estimate_at=Zeitschätzung %s entfernt
issues.time_estimate_invalid=Format der Zeitschätzung ist ungültig
issues.start_tracking_history=hat die Zeiterfassung %s gestartet
issues.tracker_auto_close=Der Timer wird automatisch gestoppt, wenn dieser Issue geschlossen wird
issues.tracking_already_started=`Du hast die Zeiterfassung bereits in <a href="%s">diesem Issue</a> gestartet!`
issues.stop_tracking_history=hat für <b>%s</b> gearbeitet %s
issues.cancel_tracking_history=`hat die Zeiterfassung %s abgebrochen`
issues.del_time=Diese Zeiterfassung löschen
issues.add_time_history=hat <b>%s</b> gearbeitete Zeit hinzugefügt %s
issues.del_time_history=`hat %s gearbeitete Zeit gelöscht`
issues.add_time_manually=Zeit manuell hinzufügen
issues.add_time_hours=Stunden
@ -2154,7 +2148,6 @@ settings.advanced_settings=Erweiterte Einstellungen
settings.wiki_desc=Repository-Wiki aktivieren
settings.use_internal_wiki=Eingebautes Wiki verwenden
settings.default_wiki_branch_name=Standardbezeichnung für Wiki-Branch
settings.default_wiki_everyone_access=Standard-Zugriffsberechtigung für angemeldete Benutzer:
settings.failed_to_change_default_wiki_branch=Das Ändern des Standard-Wiki-Branches ist fehlgeschlagen.
settings.use_external_wiki=Externes Wiki verwenden
settings.external_wiki_url=Externe Wiki-URL
@ -3363,7 +3356,6 @@ monitor.execute_time=Ausführungszeit
monitor.last_execution_result=Ergebnis
monitor.process.cancel=Prozess abbrechen
monitor.process.cancel_desc=Abbrechen eines Prozesses kann Datenverlust verursachen
monitor.process.cancel_notices=Abbrechen: <strong>%s</strong>?
monitor.process.children=Subprozesse
monitor.queues=Warteschlangen
@ -3559,7 +3551,6 @@ conda.install=Um das Paket mit Conda zu installieren, führe den folgenden Befeh
container.details.type=Container-Image Typ
container.details.platform=Plattform
container.pull=Downloade das Container-Image aus der Kommandozeile:
container.digest=Digest:
container.multi_arch=Betriebsystem / Architektur
container.layers=Container-Image Ebenen
container.labels=Labels

@ -992,9 +992,6 @@ blame_prior=Προβολή ευθύνης πριν από αυτή την αλλ
blame.ignore_revs=Αγνόηση των αναθεωρήσεων στο <a href="%s">.git-blame-ignore-revs</a>. Πατήστε <a href="%s">εδώ</a> για να το παρακάμψετε και να δείτε την κανονική προβολή ευθυνών.
blame.ignore_revs.failed=Αποτυχία αγνόησης των αναθεωρήσεων στο <a href="%s">.git-blame-ignore-revs</a>.
tree_path_not_found_commit=Η διαδρομή %[1]s δεν υπάρχει στην υποβολή %[2]s
tree_path_not_found_branch=Η διαδρομή %[1]s δεν υπάρχει στον κλάδο %[2]s
tree_path_not_found_tag=Η διαδρομή %[1]s δεν υπάρχει στην ετικέτα %[2]s
transfer.accept=Αποδοχή Μεταφοράς
transfer.reject=Απόρριψη Μεταφοράς
@ -1495,7 +1492,7 @@ issues.attachment.open_tab=`Κάντε κλικ για να δείτε το "%s"
issues.attachment.download=`Κάντε κλικ για να λάβετε το "%s"`
issues.subscribe=Εγγραφή
issues.unsubscribe=Διαγραφή
issues.unpin_issue=Άφεση Ζητήματος
issues.unpin=Άφεση
issues.max_pinned=Δεν μπορείτε να διατηρήσετε περισσότερα ζητήματα
issues.pin_comment=διατήρησε αυτό %s
issues.unpin_comment=άφησε αυτό %s
@ -3239,7 +3236,6 @@ conda.install=Για να εγκαταστήσετε το πακέτο χρησ
container.details.type=Τύπος Εικόνας
container.details.platform=Πλατφόρμα
container.pull=Κατεβάστε την εικόνα από τη γραμμή εντολών:
container.digest=Σύνοψη:
container.multi_arch=ΛΣ / Αρχιτεκτονική
container.layers=Στρώματα Εικόνας
container.labels=Ετικέτες

@ -1115,9 +1115,7 @@ blame.ignore_revs = Ignoring revisions in <a href="%s">.git-blame-ignore-revs</a
blame.ignore_revs.failed = Failed to ignore revisions in <a href="%s">.git-blame-ignore-revs</a>.
user_search_tooltip = Shows a maximum of 30 users
tree_path_not_found_commit = Path %[1]s doesn't exist in commit %[2]s
tree_path_not_found_branch = Path %[1]s doesn't exist in branch %[2]s
tree_path_not_found_tag = Path %[1]s doesn't exist in tag %[2]s
tree_path_not_found = Path %[1]s doesn't exist in %[2]s
transfer.accept = Accept Transfer
transfer.accept_desc = Transfer to "%s"
@ -1652,7 +1650,7 @@ issues.attachment.open_tab = `Click to see "%s" in a new tab`
issues.attachment.download = `Click to download "%s"`
issues.subscribe = Subscribe
issues.unsubscribe = Unsubscribe
issues.unpin_issue = Unpin Issue
issues.unpin = Unpin
issues.max_pinned = "You can't pin more issues"
issues.pin_comment = "pinned this %s"
issues.unpin_comment = "unpinned this %s"
@ -1687,16 +1685,16 @@ issues.timetracker_timer_manually_add = Add Time
issues.time_estimate_set = Set estimated time
issues.time_estimate_display = Estimate: %s
issues.change_time_estimate_at = changed time estimate to <b>%s</b> %s
issues.change_time_estimate_at = changed time estimate to <b>%[1]s</b> %[2]s
issues.remove_time_estimate_at = removed time estimate %s
issues.time_estimate_invalid = Time estimate format is invalid
issues.start_tracking_history = started working %s
issues.tracker_auto_close = Timer will be stopped automatically when this issue gets closed
issues.tracking_already_started = `You have already started time tracking on <a href="%s">another issue</a>!`
issues.stop_tracking_history = worked for <b>%s</b> %s
issues.stop_tracking_history = worked for <b>%[1]s</b> %[2]s
issues.cancel_tracking_history = `canceled time tracking %s`
issues.del_time = Delete this time log
issues.add_time_history = added spent time <b>%s</b> %s
issues.add_time_history = added spent time <b>%[1]s</b> %[2]s
issues.del_time_history= `deleted spent time %s`
issues.add_time_manually = Manually Add Time
issues.add_time_hours = Hours
@ -1955,6 +1953,7 @@ pulls.upstream_diverging_prompt_behind_1 = This branch is %[1]d commit behind %[
pulls.upstream_diverging_prompt_behind_n = This branch is %[1]d commits behind %[2]s
pulls.upstream_diverging_prompt_base_newer = The base branch %s has new changes
pulls.upstream_diverging_merge = Sync fork
pulls.upstream_diverging_merge_confirm = Would you like to merge "%[1]s" onto "%[2]s"?
pull.deleted_branch = (deleted):%s
pull.agit_documentation = Review documentation about AGit
@ -2160,7 +2159,7 @@ settings.advanced_settings = Advanced Settings
settings.wiki_desc = Enable Repository Wiki
settings.use_internal_wiki = Use Built-In Wiki
settings.default_wiki_branch_name = Default Wiki Branch Name
settings.default_wiki_everyone_access = Default Access Permission for signed-in users:
settings.default_permission_everyone_access = Default access permission for all signed-in users:
settings.failed_to_change_default_wiki_branch = Failed to change the default wiki branch.
settings.use_external_wiki = Use External Wiki
settings.external_wiki_url = External Wiki URL
@ -2715,6 +2714,8 @@ branch.create_branch_operation = Create branch
branch.new_branch = Create new branch
branch.new_branch_from = Create new branch from "%s"
branch.renamed = Branch %s was renamed to %s.
branch.rename_default_or_protected_branch_error = Only admins can rename default or protected branches.
branch.rename_protected_branch_failed = This branch is protected by glob-based protection rules.
tag.create_tag = Create tag %s
tag.create_tag_operation = Create tag
@ -3367,6 +3368,8 @@ monitor.previous = Previous Time
monitor.execute_times = Executions
monitor.process = Running Processes
monitor.stacktrace = Stacktrace
monitor.trace = Trace
monitor.performance_logs = Performance Logs
monitor.processes_count = %d Processes
monitor.download_diagnosis_report = Download diagnosis report
monitor.desc = Description
@ -3375,7 +3378,6 @@ monitor.execute_time = Execution Time
monitor.last_execution_result = Result
monitor.process.cancel = Cancel process
monitor.process.cancel_desc = Cancelling a process may cause data loss
monitor.process.cancel_notices = Cancel: <strong>%s</strong>?
monitor.process.children = Children
monitor.queues = Queues
@ -3572,7 +3574,8 @@ conda.install = To install the package using Conda, run the following command:
container.details.type = Image Type
container.details.platform = Platform
container.pull = Pull the image from the command line:
container.digest = Digest:
container.images = Images
container.digest = Digest
container.multi_arch = OS / Arch
container.layers = Image Layers
container.labels = Labels

@ -982,9 +982,6 @@ blame_prior=Ver la culpa antes de este cambio
blame.ignore_revs=Ignorando revisiones en <a href="%s">.git-blame-ignore-revs</a>. Haga clic <a href="%s">aquí para saltar</a> y para a la vista normal.
blame.ignore_revs.failed=No se pudieron ignorar las revisiones en <a href="%s">.git-blame-ignore-revs</a>.
tree_path_not_found_commit=La ruta %[1]s no existe en el commit %[2]s
tree_path_not_found_branch=La ruta %[1]s no existe en la rama %[2]s
tree_path_not_found_tag=La ruta %[1]s no existe en la etiqueta %[2]s
transfer.accept=Aceptar transferencia
transfer.reject=Rechazar transferencia
@ -1485,7 +1482,7 @@ issues.attachment.open_tab='Haga clic para ver "%s" en una pestaña nueva'
issues.attachment.download=`Haga clic para descargar "%s"`
issues.subscribe=Suscribir
issues.unsubscribe=Desuscribirse
issues.unpin_issue=Desanclar incidencia
issues.unpin=Desanclar
issues.max_pinned=No puedes anclar más incidencias
issues.pin_comment=anclado este %s
issues.unpin_comment=desanclado este %s
@ -3218,7 +3215,6 @@ conda.install=Para instalar el paquete usando Conda, ejecute el siguiente comand
container.details.type=Tipo de imagen
container.details.platform=Plataforma
container.pull=Arrastra la imagen desde la línea de comandos:
container.digest=Resumen:
container.multi_arch=SO / Arquitectura
container.layers=Capas de imagen
container.labels=Etiquetas

@ -46,7 +46,7 @@ webauthn_unsupported_browser=Votre navigateur ne prend actuellement pas en charg
webauthn_error_unknown=Une erreur indéterminée s'est produite. Veuillez réessayer.
webauthn_error_insecure=`WebAuthn ne prend en charge que les connexions sécurisées. Pour les tests via HTTP, vous pouvez utiliser l'origine "localhost" ou "127.0.0.1"`
webauthn_error_unable_to_process=Le serveur n'a pas pu traiter votre demande.
webauthn_error_duplicated=La clé de sécurité n'est pas autorisée pour cette demande. Veuillez vous assurer que la clé n'est pas déjà enregistrée.
webauthn_error_duplicated=La clé de sécurité n’est pas autorisée pour cette demande. Veuillez vous assurer que la clé n’est pas déjà enregistrée.
webauthn_error_empty=Vous devez définir un nom pour cette clé.
webauthn_error_timeout=Le délai d'attente imparti a été atteint avant que votre clé ne puisse être lue. Veuillez recharger la page pour réessayer.
webauthn_reload=Recharger
@ -469,7 +469,7 @@ sspi_auth_failed=Échec de l'authentification SSPI
password_pwned=Le mot de passe que vous avez choisi <a target="_blank" rel="noopener noreferrer" href="%s">fait partit des mots de passe ayant fuité</a> sur internet. Veuillez réessayer avec un mot de passe différent et considérez remplacer ce mot de passe si vous l’utilisez ailleurs.
password_pwned_err=Impossible d'envoyer la demande à HaveIBeenPwned
last_admin=Vous ne pouvez pas supprimer ce compte car au moins un administrateur est requis.
signin_passkey=Se connecter avec une clé d’identification (passkey)
signin_passkey=Se connecter avec une clé d’accès (passkey)
back_to_sign_in=Revenir à la page de connexion
[mail]
@ -818,7 +818,7 @@ manage_ssh_keys=Gérer les clés SSH
manage_ssh_principals=Gérer les certificats principaux SSH
manage_gpg_keys=Gérer les clés GPG
add_key=Ajouter une clé
ssh_desc=Ces clefs SSH publiques sont associées à votre compte. Les clefs privées correspondantes permettent l'accès complet à vos repos.
ssh_desc=Ces clés SSH publiques sont associées à votre compte. Les clés privées correspondantes permettent l’accès complet à vos dépôts.
principal_desc=Ces Principaux de certificats SSH sont associés à votre compte et permettent un accès complet à vos dépôts.
gpg_desc=Ces clés GPG sont associées à votre compte. Conservez-les en lieu sûr, car elles permettent de vérifier vos révisions.
ssh_helper=<strong>Besoin d'aide ?</strong> Consultez le guide de GitHub pour <a href="%s">créer vos propres clés SSH</a> ou <a href="%s">résoudre les problèmes courants</a> que vous pourriez rencontrer en utilisant SSH.
@ -969,7 +969,7 @@ passcode_invalid=Le mot de passe est invalide. Réessayez.
twofa_enrolled=L’authentification à deux facteurs a été activée pour votre compte. Gardez votre clé de secours (%s) en lieu sûr, car il ne vous sera montré qu'une seule fois.
twofa_failed_get_secret=Impossible d'obtenir le secret.
webauthn_desc=Les clefs de sécurité sont des dispositifs matériels contenant des clefs cryptographiques. Elles peuvent être utilisées pour l’authentification à deux facteurs. La clef de sécurité doit supporter le standard <a rel="noreferrer" target="_blank" href="%s">WebAuthn Authenticator</a>.
webauthn_desc=Les clés de sécurité sont des dispositifs matériels contenant des clés cryptographiques. Elles peuvent être utilisées pour l’authentification à deux facteurs. La clé de sécurité doit supporter le standard <a rel="noreferrer" target="_blank" href="%s">WebAuthn Authenticator</a>.
webauthn_register_key=Ajouter une clé de sécurité
webauthn_nickname=Pseudonyme
webauthn_delete_key=Retirer la clé de sécurité
@ -1115,9 +1115,6 @@ blame.ignore_revs=Les révisions dans <a href="%s">.git-blame-ignore-revs</a> so
blame.ignore_revs.failed=Impossible d'ignorer les révisions dans <a href="%s">.git-blame-ignore-revs</a>.
user_search_tooltip=Affiche un maximum de 30 utilisateurs
tree_path_not_found_commit=Le chemin %[1]s n’existe pas dans la révision %[2]s.
tree_path_not_found_branch=Le chemin %[1]s n’existe pas dans la branche %[2]s.
tree_path_not_found_tag=Le chemin %[1]s n’existe pas dans l’étiquette %[2]s.
transfer.accept=Accepter le transfert
transfer.accept_desc=Transférer à « %s »
@ -1651,7 +1648,7 @@ issues.attachment.open_tab=`Cliquez ici pour voir « %s » dans un nouvel ongl
issues.attachment.download=`Cliquez pour télécharger « %s ».`
issues.subscribe=S’abonner
issues.unsubscribe=Se désabonner
issues.unpin_issue=Désépingler le ticket
issues.unpin=Désépingler
issues.max_pinned=Vous ne pouvez pas épingler plus de tickets
issues.pin_comment=a épinglé ça %s.
issues.unpin_comment=a désépinglé ça %s.
@ -1686,16 +1683,13 @@ issues.timetracker_timer_manually_add=Pointer du temps
issues.time_estimate_set=Définir le temps estimé
issues.time_estimate_display=Estimation : %s
issues.change_time_estimate_at=a changé le temps estimé à <b>%s</b> %s
issues.remove_time_estimate_at=a supprimé le temps estimé %s
issues.time_estimate_invalid=Le format du temps estimé est invalide
issues.start_tracking_history=`a commencé son travail %s.`
issues.tracker_auto_close=Le minuteur sera automatiquement arrêté quand le ticket sera fermé.
issues.tracking_already_started=`Vous avez déjà un minuteur en cours sur <a href="%s">un autre ticket</a> !`
issues.stop_tracking_history=`a fini de travailler sur <b>%s</b> %s.`
issues.cancel_tracking_history=`a abandonné son minuteur %s.`
issues.del_time=Supprimer ce minuteur du journal
issues.add_time_history=`a pointé du temps de travail %s.`
issues.del_time_history=`a supprimé son temps de travail %s.`
issues.add_time_manually=Temps pointé manuellement
issues.add_time_hours=Heures
@ -2159,7 +2153,6 @@ settings.advanced_settings=Paramètres avancés
settings.wiki_desc=Activer le wiki du dépôt
settings.use_internal_wiki=Utiliser le wiki interne
settings.default_wiki_branch_name=Nom de la branche du Wiki par défaut
settings.default_wiki_everyone_access=Autorisation d’accès par défaut pour les utilisateurs connectés :
settings.failed_to_change_default_wiki_branch=Impossible de modifier la branche du wiki par défaut.
settings.use_external_wiki=Utiliser un wiki externe
settings.external_wiki_url=URL Wiki externe
@ -2400,17 +2393,17 @@ settings.packagist_api_token=Jeton API
settings.packagist_package_url=URL du paquet Packagist
settings.deploy_keys=Clés de déploiement
settings.add_deploy_key=Ajouter une clé de déploiement
settings.deploy_key_desc=Les clefs de déploiement ont un accès en lecture seule au dépôt.
settings.deploy_key_desc=Les clés de déploiement ont un accès en lecture seule au dépôt.
settings.is_writable=Activer l'accès en écriture
settings.is_writable_info=Autoriser cette clé de déploiement à <strong>soumettre</strong> sur le dépôt.
settings.no_deploy_keys=Il n'y a pas encore de clefs de déploiement.
settings.no_deploy_keys=Il n’y a pas encore de clés de déploiement.
settings.title=Titre
settings.deploy_key_content=Contenu
settings.key_been_used=Une clef de déploiement identique est déjà en cours d'utilisation.
settings.key_name_used=Une clef de déploiement du même nom existe déjà.
settings.add_key_success=La clé de déploiement "%s" a été ajoutée.
settings.deploy_key_deletion=Supprimer une clef de déploiement
settings.deploy_key_deletion_desc=La suppression d'une clef de déploiement révoque son accès à ce dépôt. Continuer ?
settings.key_been_used=Une clé de déploiement identique est déjà en cours d’utilisation.
settings.key_name_used=Une clé de déploiement du même nom existe déjà.
settings.add_key_success=La clé de déploiement « %s » a été ajoutée.
settings.deploy_key_deletion=Supprimer une clé de déploiement
settings.deploy_key_deletion_desc=La suppression d’une clé de déploiement révoque son accès à ce dépôt. Continuer ?
settings.deploy_key_deletion_success=La clé de déploiement a été supprimée.
settings.branches=Branches
settings.protected_branch=Protection de branche
@ -2627,6 +2620,9 @@ diff.image.overlay=Superposition
diff.has_escaped=Cette ligne contient des caractères Unicode cachés
diff.show_file_tree=Afficher l’arborescence des fichiers
diff.hide_file_tree=Masquer l’arborescence des fichiers
diff.submodule_added=Sous-module %[1]s ajouté à %[2]s
diff.submodule_deleted=Sous-module %[1]s supprimé de %[2]s
diff.submodule_updated=Sous-module %[1]s mis-à-jour : %[2]s
releases.desc=Suivi des publications et des téléchargements.
release.releases=Publications
@ -3116,7 +3112,7 @@ auths.attribute_username_placeholder=Laisser vide afin d'utiliser le nom d'utili
auths.attribute_name=Attribut prénom
auths.attribute_surname=Attribut nom de famille
auths.attribute_mail=Attribut e-mail
auths.attribute_ssh_public_key=Attribut clef SSH publique
auths.attribute_ssh_public_key=Attribut clé SSH publique
auths.attribute_avatar=Attribut de l'avatar
auths.attributes_in_bind=Aller chercher les attributs dans le contexte de liaison DN
auths.allow_deactivate_all=Permettre à un résultat de recherche vide de désactiver tous les utilisateurs
@ -3240,7 +3236,7 @@ config.ssh_port=Port
config.ssh_listen_port=Port d'écoute
config.ssh_root_path=Emplacement racine
config.ssh_key_test_path=Chemin de test des clés
config.ssh_keygen_path=Chemin vers le générateur de clefs ("ssh-keygen")
config.ssh_keygen_path=Chemin vers le générateur de clés (« ssh-keygen »)
config.ssh_minimum_key_size_check=Vérification de la longueur de clé minimale
config.ssh_minimum_key_sizes=Tailles de clé minimales
@ -3371,7 +3367,6 @@ monitor.execute_time=Heure d'Éxécution
monitor.last_execution_result=Résultat
monitor.process.cancel=Annuler le processus
monitor.process.cancel_desc=L’annulation d’un processus peut entraîner une perte de données.
monitor.process.cancel_notices=Annuler : <strong>%s</strong> ?
monitor.process.children=Enfant
monitor.queues=Files d'attente
@ -3568,7 +3563,6 @@ conda.install=Pour installer le paquet en utilisant Conda, exécutez la commande
container.details.type=Type d'image
container.details.platform=Plateforme
container.pull=Tirez l'image depuis un terminal :
container.digest=Empreinte :
container.multi_arch=SE / Arch
container.layers=Calques d'image
container.labels=Labels

@ -244,6 +244,7 @@ license_desc=Téigh go bhfaighidh <a target="_blank" rel="noopener noreferrer" h
[install]
install=Suiteáil
installing_desc=Suiteáil anois, fan go fóill...
title=Cumraíocht Tosaigh
docker_helper=Má ritheann tú Gitea taobh istigh de Docker, léigh an <a target="_blank" rel="noopener noreferrer" href="%s">doiciméadúchán</a> roimh aon socruithe a athrú.
require_db_desc=Éilíonn Gitea MySQL, PostgreSQL, MSSQL, SQLite3 nó TiDB (prótacal MySQL).
@ -1015,6 +1016,9 @@ new_repo_helper=Tá gach comhad tionscadail i stór, lena n-áirítear stair ath
owner=Úinéir
owner_helper=B'fhéidir nach dtaispeánfar roinnt eagraíochtaí sa anuas mar gheall ar theorainn uasta comhaireamh stórais.
repo_name=Ainm Stórais
repo_name_profile_public_hint=Is stóras speisialta é .profile is féidir leat a úsáid chun README.md a chur le do phróifíl eagraíochta poiblí, le feiceáil ag aon duine. Cinntigh go bhfuil sé poiblí agus tosaigh é le README san eolaire próifíle le tosú.
repo_name_profile_private_hint=Is stóras speisialta é .profile-private is féidir leat a úsáid chun README.md a chur le do phróifíl bhall eagraíochta, nach féidir a fheiceáil ach ag baill eagraíochta. Cinntigh go bhfuil sé príobháideach agus tosaigh le README sa eolaire próifíle chun tús a chur leis.
repo_name_helper=Úsáideann ainmneacha maith stóras focail eochair gairide, áithnid agus uathúla. D'fhéadfaí stóras darbh ainm '.profile' nó '.profile-private' a úsáid chun README.md a chur leis an bpróifíl úsáideora/eagraíochta.
repo_size=Méid an Stóras
template=Teimpléad
template_select=Roghnaigh teimpléad.
@ -1111,9 +1115,7 @@ blame.ignore_revs=Ag déanamh neamhairde de leasuithe i <a href="%s">.git-blame-
blame.ignore_revs.failed=Theip ar neamhaird a dhéanamh ar leasuithe i <a href="%s">.git-blame-ignore-revs</a>.
user_search_tooltip=Taispeáint uasmhéid de 30 úsáideoir
tree_path_not_found_commit=Níl cosán %[1]s ann i dtiomantas %[2]s
tree_path_not_found_branch=Níl cosán %[1]s ann i mbrainse %[2]s
tree_path_not_found_tag=Níl cosán %[1]s ann i gclib %[2]s
tree_path_not_found=Níl cosán %[1]s ann i %[2]s
transfer.accept=Glac le hAistriú
transfer.accept_desc=Aistriú chuig “%s”
@ -1231,6 +1233,7 @@ create_new_repo_command=Stóras nua a chruthú ar an líne ordaithe
push_exist_repo=Stóras atá ann cheana a bhrú ón líne ordaithe
empty_message=Níl aon ábhar sa stóras seo.
broken_message=Ní féidir na sonraí Git atá mar bhunús leis an stóras seo a léamh. Déan teagmháil le riarthóir an chás seo nó scrios an stóras seo.
no_branch=Níl aon bhrainsí ag an stóras seo.
code=Cód
code.desc=Rochtain ar chód foinse, comhaid, gealltanais agus brainsí.
@ -1646,7 +1649,7 @@ issues.attachment.open_tab=`Cliceáil chun "%s" a fheiceáil i gcluaisín nua`
issues.attachment.download=`Cliceáil chun "%s" a íoslódáil
issues.subscribe=Liostáil
issues.unsubscribe=Díliostáil
issues.unpin_issue=Bain pionna an t-eagrán
issues.unpin=Díphoráil
issues.max_pinned=Ní féidir leat níos mó saincheisteanna a phionadh
issues.pin_comment=phionnáil an %s seo
issues.unpin_comment=bain pionna an %s seo
@ -1681,16 +1684,13 @@ issues.timetracker_timer_manually_add=Cuir Am leis
issues.time_estimate_set=Socraigh am measta
issues.time_estimate_display=Meastachán: %s
issues.change_time_estimate_at=d'athraigh an meastachán ama go <b>%s</b> %s
issues.remove_time_estimate_at=baineadh meastachán ama %s
issues.time_estimate_invalid=Tá formáid meastachán ama neamhbhailí
issues.start_tracking_history=thosaigh ag obair %s
issues.tracker_auto_close=Stopfar ama go huathoibríoch nuair a dhúnfar an tsaincheist seo
issues.tracking_already_started=`Tá tús curtha agat cheana féin ag rianú ama ar <a href="%s">eagrán eile</a>!`
issues.stop_tracking_history=d'oibrigh do <b>%s</b> %s
issues.cancel_tracking_history=`rianú ama curtha ar ceal %s`
issues.del_time=Scrios an log ama seo
issues.add_time_history=cuireadh am caite <b>%s</b> %s leis
issues.del_time_history=`an t-am caite scriosta %s`
issues.add_time_manually=Cuir Am leis de Láimh
issues.add_time_hours=Uaireanta
@ -2154,7 +2154,6 @@ settings.advanced_settings=Ardsocruithe
settings.wiki_desc=Cumasaigh Stór Vicí
settings.use_internal_wiki=Úsáid Vicí Insuite
settings.default_wiki_branch_name=Ainm Brainse Réamhshocraithe Vicí
settings.default_wiki_everyone_access=Cead Rochtana Réamhshocraithe d'úsáideoirí sínithe isteach:
settings.failed_to_change_default_wiki_branch=Theip ar an brainse réamhshocraithe vicí a athrú.
settings.use_external_wiki=Úsáid Vicí Seachtrach
settings.external_wiki_url=URL Vicí Seachtrach
@ -2622,6 +2621,9 @@ diff.image.overlay=Forleagan
diff.has_escaped=Tá carachtair Unicode i bhfolach ag an líne seo
diff.show_file_tree=Taispeáin crann comhad
diff.hide_file_tree=Folaigh crann comhad
diff.submodule_added=Fomhodúl %[1]s curtha leis ag %[2]s
diff.submodule_deleted=Scriosadh fomhodúl %[1]s ó %[2]s
diff.submodule_updated=Nuashonraíodh fomhodúl %[1]s: %[2]s
releases.desc=Rian leaganacha tionscadal agus íoslódálacha.
release.releases=Eisiúintí
@ -2860,6 +2862,9 @@ teams.invite.title=Tugadh cuireadh duit dul isteach i bhfoireann <strong>%s</str
teams.invite.by=Ar cuireadh ó %s
teams.invite.description=Cliceáil ar an gcnaipe thíos le do thoil chun dul isteach san fhoireann.
view_as_role=Féach mar: %s
view_as_public_hint=Tá tú ag féachaint ar an README mar úsáideoir poiblí.
view_as_member_hint=Tá tú ag féachaint ar an README mar bhall den eagraíocht seo.
[admin]
maintenance=Cothabháil
@ -3363,7 +3368,6 @@ monitor.execute_time=Am Forghníomhaithe
monitor.last_execution_result=Toradh
monitor.process.cancel=Cealaigh próiseas
monitor.process.cancel_desc=Má chuirtear próiseas ar ceal d'fhéadfadh go gcaillfí sonraí
monitor.process.cancel_notices=Cealaigh: <strong>%s</strong>?
monitor.process.children=Leanaí
monitor.queues=Scuaineanna
@ -3530,6 +3534,7 @@ versions=Leaganacha
versions.view_all=Féach ar gach
dependency.id=ID
dependency.version=Leagan
search_in_external_registry=Cuardaigh i %s
alpine.registry=Socraigh an chlár seo tríd an url a chur i do chomhad <code>/etc/apk/repositories</code>:
alpine.registry.key=Íoslódáil eochair RSA poiblí na clárlainne isteach san fhillteán <code>/etc/apk/keys/</code> chun an síniú innéacs a fhíorú:
alpine.registry.info=Roghnaigh $branch agus $repository ón liosta thíos.
@ -3559,7 +3564,6 @@ conda.install=Chun an pacáiste a shuiteáil ag úsáid Conda, reáchtáil an t-
container.details.type=Cineál Íomhá
container.details.platform=Ardán
container.pull=Tarraing an íomhá ón líne ordaithe:
container.digest=Díleáigh:
container.multi_arch=Córas Oibriúcháin / Ailtireacht
container.layers=Sraitheanna Íomhá
container.labels=Lipéid
@ -3755,6 +3759,7 @@ workflow.not_found=Níor aimsíodh sreabhadh oibre '%s'.
workflow.run_success=Ritheann sreabhadh oibre '%s' go rathúil.
workflow.from_ref=Úsáid sreabhadh oibre ó
workflow.has_workflow_dispatch=Tá comhoibriú ag an gcur i bhfeidhm seo le himeacht workflow_dispatch.
workflow.has_no_workflow_dispatch=Níl aon truicear teagmhais workflow_dispatch ag sreabhadh oibre '%s'.
need_approval_desc=Teastaíonn faomhadh chun sreafaí oibre a rith le haghaidh iarratas tarraingt forc.

@ -818,6 +818,7 @@ issues.attachment.open_tab=`Klik untuk melihat "%s" di tab baru`
issues.attachment.download=`Klik untuk mengunduh "%s"`
issues.subscribe=Berlangganan
issues.unsubscribe=Berhenti berlangganan
issues.unpin=Lepas sematan
issues.delete=Hapus

@ -1015,6 +1015,7 @@ new_repo_helper=リポジトリには、プロジェクトのすべてのファ
owner=オーナー
owner_helper=リポジトリ数の上限により、一部の組織はドロップダウンに表示されない場合があります。
repo_name=リポジトリ名
repo_name_helper=リポジトリ名は、短く、覚えやすく、他と重複しないキーワードを使用しましょう。 リポジトリ名を ".profile" または ".profile-private" にして README.md を追加すると、ユーザーや組織のプロフィールとなります。
repo_size=リポジトリサイズ
template=テンプレート
template_select=テンプレートを選択してください。
@ -1033,6 +1034,8 @@ fork_to_different_account=別のアカウントにフォークする
fork_visibility_helper=フォークしたリポジトリの公開/非公開は変更できません。
fork_branch=フォークにクローンされるブランチ
all_branches=すべてのブランチ
view_all_branches=すべてのブランチを表示
view_all_tags=すべてのタグを表示
fork_no_valid_owners=このリポジトリには有効なオーナーがいないため、フォークできません。
fork.blocked_user=リポジトリのオーナーがあなたをブロックしているため、リポジトリをフォークできません。
use_template=このテンプレートを使用
@ -1107,10 +1110,8 @@ delete_preexisting_success=%s の未登録ファイルを削除しました
blame_prior=この変更より前のBlameを表示
blame.ignore_revs=<a href="%s">.git-blame-ignore-revs</a> で指定されたリビジョンは除外しています。 これを迂回して通常のBlame表示を見るには <a href="%s">ここ</a>をクリック。
blame.ignore_revs.failed=<a href="%s">.git-blame-ignore-revs</a> によるリビジョンの無視は失敗しました。
user_search_tooltip=最大30人までのユーザーを表示
tree_path_not_found_commit=パス %[1]s はコミット %[2]s に存在しません
tree_path_not_found_branch=パス %[1]s はブランチ %[2]s に存在しません
tree_path_not_found_tag=パス %[1]s はタグ %[2]s に存在しません
transfer.accept=移転を承認
transfer.accept_desc=`"%s" に移転`
@ -1228,6 +1229,7 @@ create_new_repo_command=コマンドラインから新しいリポジトリを
push_exist_repo=コマンドラインから既存のリポジトリをプッシュ
empty_message=このリポジトリの中には何もありません。
broken_message=このリポジトリの基礎となる Git のデータを読み取れません。このインスタンスの管理者に相談するか、このリポジトリを削除してください。
no_branch=このリポジトリにはブランチがありません。
code=コード
code.desc=ソースコード、ファイル、コミット、ブランチにアクセス。
@ -1525,6 +1527,8 @@ issues.filter_assignee=担当者
issues.filter_assginee_no_select=すべての担当者
issues.filter_assginee_no_assignee=担当者なし
issues.filter_poster=作成者
issues.filter_user_placeholder=ユーザーを検索
issues.filter_user_no_select=すべてのユーザー
issues.filter_type=タイプ
issues.filter_type.all_issues=すべてのイシュー
issues.filter_type.assigned_to_you=自分が担当
@ -1641,7 +1645,7 @@ issues.attachment.open_tab=`クリックして新しいタブで "%s" を見る`
issues.attachment.download=`クリックして "%s" をダウンロード`
issues.subscribe=購読する
issues.unsubscribe=購読を解除
issues.unpin_issue=イシューのピン留め解除
issues.unpin=ピン留め解除
issues.max_pinned=これ以上イシューをピン留めできません
issues.pin_comment=がピン留め %s
issues.unpin_comment=がピン留めを解除 %s
@ -1676,16 +1680,16 @@ issues.timetracker_timer_manually_add=時間を追加
issues.time_estimate_set=見積時間を設定
issues.time_estimate_display=見積時間: %s
issues.change_time_estimate_at=が見積時間を <b>%s</b> に変更 %s
issues.change_time_estimate_at=が見積時間を <b>%[1]s</b> に変更 %[2]s
issues.remove_time_estimate_at=が見積時間を削除 %s
issues.time_estimate_invalid=見積時間のフォーマットが不正です
issues.start_tracking_history=が作業を開始 %s
issues.tracker_auto_close=タイマーは、このイシューがクローズされると自動的に終了します
issues.tracking_already_started=`<a href="%s">別のイシュー</a>で既にタイムトラッキングを開始しています!`
issues.stop_tracking_history=が <b>%s</b> の作業を終了 %s
issues.stop_tracking_history=が <b>%[1]s</b> の作業を終了 %[2]s
issues.cancel_tracking_history=`がタイムトラッキングを中止 %s`
issues.del_time=このタイムログを削除
issues.add_time_history=が作業時間 <b>%s</b> を追加 %s
issues.add_time_history=が作業時間 <b>%[1]s</b> を追加 %[2]s
issues.del_time_history=`が作業時間を削除 %s`
issues.add_time_manually=時間の手入力
issues.add_time_hours=時間
@ -1940,6 +1944,8 @@ pulls.delete.title=このプルリクエストを削除しますか?
pulls.delete.text=本当にこのプルリクエストを削除しますか? (これはすべてのコンテンツを完全に削除します。 保存しておきたい場合は、代わりにクローズすることを検討してください)
pulls.recently_pushed_new_branches=%[2]s 、あなたはブランチ <strong>%[1]s</strong> にプッシュしました
pulls.upstream_diverging_prompt_behind_1=このブランチは %[2]s よりも %[1]d コミット遅れています
pulls.upstream_diverging_prompt_behind_n=このブランチは %[2]s よりも %[1]d コミット遅れています
pulls.upstream_diverging_prompt_base_newer=ベースブランチ %s に新しい変更があります
pulls.upstream_diverging_merge=フォークを同期
@ -2147,7 +2153,6 @@ settings.advanced_settings=拡張設定
settings.wiki_desc=Wikiを有効にする
settings.use_internal_wiki=ビルトインのWikiを使用する
settings.default_wiki_branch_name=デフォルトのWikiブランチ名
settings.default_wiki_everyone_access=サインインユーザーのデフォルトのアクセス権限:
settings.failed_to_change_default_wiki_branch=デフォルトのWikiブランチを変更できませんでした。
settings.use_external_wiki=外部のWikiを使用する
settings.external_wiki_url=外部WikiのURL
@ -2624,6 +2629,7 @@ release.new_release=新しいリリース
release.draft=下書き
release.prerelease=プレリリース
release.stable=安定版
release.latest=最新
release.compare=比較
release.edit=編集
release.ahead.commits=<strong>%d</strong>件のコミット
@ -2852,6 +2858,9 @@ teams.invite.title=あなたは組織 <strong>%[2]s</strong> 内のチーム <st
teams.invite.by=%s からの招待
teams.invite.description=下のボタンをクリックしてチームに参加してください。
view_as_role=表示: %s
view_as_public_hint=READMEを公開ユーザーとして見ています。
view_as_member_hint=READMEをこの組織のメンバーとして見ています。
[admin]
maintenance=メンテナンス
@ -3355,7 +3364,6 @@ monitor.execute_time=実行時間
monitor.last_execution_result=結果
monitor.process.cancel=処理をキャンセル
monitor.process.cancel_desc=処理をキャンセルするとデータが失われる可能性があります
monitor.process.cancel_notices=キャンセル: <strong>%s</strong>?
monitor.process.children=子プロセス
monitor.queues=キュー
@ -3530,6 +3538,8 @@ alpine.repository=リポジトリ情報
alpine.repository.branches=Branches
alpine.repository.repositories=Repositories
alpine.repository.architectures=Architectures
arch.registry=<code>/etc/pacman.conf</code> にリポジトリとアーキテクチャを含めてサーバーを追加します:
arch.install=pacmanでパッケージを同期します:
arch.repository=リポジトリ情報
arch.repository.repositories=リポジトリ
arch.repository.architectures=Architectures
@ -3549,7 +3559,7 @@ conda.install=Conda を使用してパッケージをインストールするに
container.details.type=イメージタイプ
container.details.platform=プラットフォーム
container.pull=コマンドラインでイメージを取得します:
container.digest=ダイジェスト:
container.digest=ダイジェスト
container.multi_arch=OS / アーキテクチャ
container.layers=イメージレイヤー
container.labels=ラベル
@ -3712,6 +3722,7 @@ runners.status.active=稼働中
runners.status.offline=オフライン
runners.version=バージョン
runners.reset_registration_token=登録トークンをリセット
runners.reset_registration_token_confirm=現在のトークンを無効にして、新しいトークンを生成しますか?
runners.reset_registration_token_success=ランナー登録トークンをリセットしました
runs.all_workflows=すべてのワークフロー

@ -997,9 +997,6 @@ blame_prior=Aplūkot vainīgo par izmaiņām pirms šīs revīzijas
blame.ignore_revs=Neņem vērā izmaiņas no <a href="%s">.git-blame-ignore-revs</a>. Nospiediet <a href="%s">šeit, lai to apietu</a> un redzētu visu izmaiņu skatu.
blame.ignore_revs.failed=Neizdevās neņemt vērā izmaiņas no <a href="%s">.git-blam-ignore-revs</a>.
tree_path_not_found_commit=Revīzijā %[2]s neeksistē ceļš %[1]s
tree_path_not_found_branch=Atzarā %[2]s nepastāv ceļš %[1]s
tree_path_not_found_tag=Tagā %[2]s nepastāv ceļš %[1]s
transfer.accept=Apstiprināt īpašnieka maiņu
transfer.reject=Noraidīt īpašnieka maiņu
@ -1501,7 +1498,7 @@ issues.attachment.open_tab=`Noklikšķiniet, lai apskatītos "%s" jaunā logā`
issues.attachment.download=`Noklikšķiniet, lai lejupielādētu "%s"`
issues.subscribe=Abonēt
issues.unsubscribe=Atrakstīties
issues.unpin_issue=Atspraust problēmu
issues.unpin=Atspraust
issues.max_pinned=Nevar piespraust vairāk problēmas
issues.pin_comment=piesprauda šo %s
issues.unpin_comment=atsprauda šo %s
@ -3242,7 +3239,6 @@ conda.install=Lai instalētu Conda pakotni, izpildiet sekojošu komandu:
container.details.type=Attēla formāts
container.details.platform=Platforma
container.pull=Atgādājiet šo attēlu no komandrindas:
container.digest=Īssavilkums:
container.multi_arch=OS / arhitektūra
container.layers=Attēla slāņi
container.labels=Iezīmes

@ -2310,7 +2310,6 @@ monitor.start=Czas rozpoczęcia
monitor.execute_time=Czas wykonania
monitor.process.cancel=Anuluj proces
monitor.process.cancel_desc=Anulowanie procesu może spowodować utratę danych
monitor.process.cancel_notices=Anuluj: <strong>%s</strong>?
monitor.queues=Kolejki
monitor.queue=Kolejka: %s

@ -1491,7 +1491,7 @@ issues.attachment.open_tab=`Clique para ver "%s" em uma nova aba`
issues.attachment.download=`Clique para baixar "%s"`
issues.subscribe=Inscrever-se
issues.unsubscribe=Desinscrever
issues.unpin_issue=Desfixar issue
issues.unpin=Desfixar
issues.max_pinned=Você não pode fixar mais issues
issues.pin_comment=fixou isto %s
issues.unpin_comment=desafixou isto %s
@ -3180,7 +3180,6 @@ conda.install=Para instalar o pacote usando o Conda, execute o seguinte comando:
container.details.type=Tipo de Imagem
container.details.platform=Plataforma
container.pull=Puxe a imagem pela linha de comando:
container.digest=Digest:
container.multi_arch=S.O. / Arquitetura
container.layers=Camadas da Imagem
container.labels=Rótulos

@ -261,7 +261,7 @@ path=Localização
sqlite_helper=Localização do ficheiro da base de dados em SQLite3.<br>Insira um caminho absoluto se corre o Gitea como um serviço.
reinstall_error=Está a tentar instalar numa base de dados do Gitea já existente
reinstall_confirm_message=Reinstalar com uma base de dados do Gitea já existente pode causar múltiplos problemas. Na maioria dos casos deve usar o seu "app.ini" existente para correr o Gitea. Se souber o que está a fazer, confirme o seguinte:
reinstall_confirm_check_1=Os dados encriptados pela chave secreta (SECRET_KEY) no ficheiro app.ini poderão ser perdidos: utilizadores poderão não ser capazes de iniciar a sessão com autenticação em dois passos (2FA) ou com chaves de utilização única (OTP) e as réplicas poderão deixar de funcionar em condições. Ao marcar esta opção estará a confirmar que o ficheiro app.ini vigente contém a SECRET_KEY certa.
reinstall_confirm_check_1=Os dados encriptados pela chave secreta (SECRET_KEY) no ficheiro app.ini poderão ser perdidos: utilizadores poderão não ser capazes de iniciar a sessão com autenticação em dois passos (2FA) ou com chaves de utilização única (OTP) e as réplicas poderão deixar de funcionar em boas condições. Ao marcar esta opção estará a confirmar que o ficheiro app.ini vigente contém a SECRET_KEY certa.
reinstall_confirm_check_2=Os repositórios e as configurações poderão ter de voltar a ser sincronizados. Ao marcar esta opção estará a confirmar que vai voltar a sincronizar manualmente os automatismos para os repositórios e o ficheiro authorized_keys. Estará também a confirmar que vai assegurar que as configurações do repositório e das réplicas estão em condições.
reinstall_confirm_check_3=Você confirma que tem a certeza absoluta de que este Gitea está a correr com a localização certa do ficheiro app.ini e que tem a certeza de que tem de voltar a instalar. Você confirma que tomou conhecimento dos riscos acima descritos.
err_empty_db_path=A localização da base de dados SQLite3 não pode estar vazia.
@ -1051,7 +1051,7 @@ generate_from=Gerar a partir de
repo_desc=Descrição
repo_desc_helper=Insira uma descrição curta (opcional)
repo_no_desc=Descrição não fornecida
repo_lang=Idiomas
repo_lang=Linguagens
repo_gitignore_helper=Escolher modelos .gitignore.
repo_gitignore_helper_desc=Escolha os ficheiros que não são para rastrear, a partir de uma lista de modelos de linguagens comuns. Serão incluídos no ficheiro .gitignore, logo à partida, artefactos típicos gerados pelas ferramentas de construção de cada uma das linguagens.
issue_labels=Rótulos para as questões
@ -1115,9 +1115,7 @@ blame.ignore_revs=Ignorando as revisões em <a href="%s">.git-blame-ignore-revs<
blame.ignore_revs.failed=Falhou ao ignorar as revisões em <a href="%s">.git-blame-ignore-revs</a>.
user_search_tooltip=Mostra um máximo de 30 utilizadores
tree_path_not_found_commit=A localização %[1]s não existe no cometimento %[2]s
tree_path_not_found_branch=A localização %[1]s não existe no ramo %[2]s
tree_path_not_found_tag=A localização %[1]s não existe na etiqueta %[2]s
tree_path_not_found=A localização %[1]s não existe em %[2]s
transfer.accept=Aceitar transferência
transfer.accept_desc=`Transferir para "%s"`
@ -1651,7 +1649,7 @@ issues.attachment.open_tab=`Clique para ver "%s" num separador novo`
issues.attachment.download=`Clique para descarregar "%s"`
issues.subscribe=Subscrever
issues.unsubscribe=Anular subscrição
issues.unpin_issue=Desafixar questão
issues.unpin=Desafixar
issues.max_pinned=Já não pode fixar mais questões
issues.pin_comment=fixou isto %s
issues.unpin_comment=desafixou isto %s
@ -1686,16 +1684,16 @@ issues.timetracker_timer_manually_add=Adicionar tempo
issues.time_estimate_set=Definir tempo estimado
issues.time_estimate_display=Estimativa: %s
issues.change_time_estimate_at=alterou a estimativa de tempo para <b>%s</b> %s
issues.change_time_estimate_at=alterou a estimativa de tempo para <b>%[1]s</b> %[2]s
issues.remove_time_estimate_at=removeu a estimativa de tempo %s
issues.time_estimate_invalid=O formato da estimativa de tempo é inválido
issues.start_tracking_history=começou a trabalhar %s
issues.tracker_auto_close=O cronómetro será parado automaticamente quando esta questão for fechada
issues.tracking_already_started=`Você já iniciou a contagem de tempo <a href="%s">noutra questão</a>!`
issues.stop_tracking_history=trabalhou durante <b>%s</b> %s
issues.stop_tracking_history=trabalhou durante <b>%[1]s</b> %[2]s
issues.cancel_tracking_history=`cancelou a contagem de tempo %s`
issues.del_time=Eliminar este registo de tempo
issues.add_time_history=adicionou <b>%s</b> de tempo gasto %s
issues.add_time_history=adicionou <b>%[1]s</b> de tempo gasto %[2]s
issues.del_time_history=`eliminou o tempo gasto nesta questão %s`
issues.add_time_manually=Adicionar tempo manualmente
issues.add_time_hours=Horas
@ -2159,7 +2157,7 @@ settings.advanced_settings=Configurações avançadas
settings.wiki_desc=Habilitar wiki do repositório
settings.use_internal_wiki=Usar o wiki integrado
settings.default_wiki_branch_name=Nome do ramo predefinido do wiki
settings.default_wiki_everyone_access=Permissão de acesso predefinida para utilizadores registados:
settings.default_permission_everyone_access=Permissão de acesso predefinida para todos os utilizadores registados:
settings.failed_to_change_default_wiki_branch=Falhou ao mudar o nome do ramo predefinido do wiki.
settings.use_external_wiki=Usar um wiki externo
settings.external_wiki_url=URL do wiki externo
@ -2714,6 +2712,8 @@ branch.create_branch_operation=Criar ramo
branch.new_branch=Criar um novo ramo
branch.new_branch_from=`Criar um novo ramo a partir do ramo "%s"`
branch.renamed=O ramo %s foi renomeado para %s.
branch.rename_default_or_protected_branch_error=Só os administradores é que podem renomear o ramo principal ou ramos protegidos.
branch.rename_protected_branch_failed=Este ramo está protegido por regras de salvaguarda baseadas em padrões glob.
tag.create_tag=Criar etiqueta %s
tag.create_tag_operation=Criar etiqueta
@ -3374,7 +3374,6 @@ monitor.execute_time=Tempo de execução
monitor.last_execution_result=Resultado
monitor.process.cancel=Cancelar processo
monitor.process.cancel_desc=Cancelar um processo pode resultar na perda de dados
monitor.process.cancel_notices=Cancelar: <strong>%s</strong>?
monitor.process.children=Descendentes
monitor.queues=Filas
@ -3571,7 +3570,8 @@ conda.install=Para instalar o pacote usando o Conda, execute o seguinte comando:
container.details.type=Tipo de imagem
container.details.platform=Plataforma
container.pull=Puxar a imagem usando a linha de comandos:
container.digest=Resumo:
container.images=Imagens
container.digest=Resumo
container.multi_arch=S.O. / Arquit.
container.layers=Camadas de imagem
container.labels=Rótulos

@ -978,8 +978,6 @@ delete_preexisting_content=Удалить файлы из %s
delete_preexisting_success=Удалены непринятые файлы в %s
blame_prior=Показать авторство предшествующих изменений
tree_path_not_found_commit=Путь %[1]s не существует в коммите %[2]s
tree_path_not_found_branch=Путь %[1]s не существует в ветке %[2]s
transfer.accept=Принять трансфер
transfer.reject=Отказаться от перемещения
@ -1471,7 +1469,7 @@ issues.attachment.open_tab=`Нажмите, чтобы увидеть «%s» в
issues.attachment.download=`Нажмите, чтобы скачать «%s»`
issues.subscribe=Подписаться
issues.unsubscribe=Отказаться от подписки
issues.unpin_issue=Открепить задачу
issues.unpin=Открепить
issues.max_pinned=Нельзя закрепить больше задач
issues.pin_comment=закрепил(а) эту задачу %s
issues.unpin_comment=открепил(а) эту задачу %s
@ -3178,7 +3176,6 @@ conda.install=Чтобы установить пакет с помощью Conda
container.details.type=Тип образа
container.details.platform=Платформа
container.pull=Загрузите образ из командной строки:
container.digest=Отпечаток:
container.multi_arch=ОС / архитектура
container.layers=Слои образа
container.labels=Метки

@ -1068,6 +1068,7 @@ issues.dismiss_review=Zamietnuť revíziu
issues.dismiss_review_warning=Naozaj chcete zrušiť túto revíziu?
issues.cancel=Zrušiť
issues.save=Uložiť
issues.unpin=Odopnúť

@ -1083,9 +1083,6 @@ 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.failed=<a href="%s">.git-blame-ignore-revs</a> dosyasındaki sürümler yok sayılamadı.
tree_path_not_found_commit=%[1] yolu, %[2]s işlemesinde mevcut değil
tree_path_not_found_branch=%[1] yolu, %[2]s dalında mevcut değil
tree_path_not_found_tag=%[1] yolu, %[2]s etiketinde mevcut değil
transfer.accept=Aktarımı Kabul Et
transfer.reject=Aktarımı Reddet
@ -1605,7 +1602,7 @@ issues.attachment.open_tab=`Yeni bir sekmede "%s" görmek için tıkla`
issues.attachment.download=`"%s" indirmek için tıkla`
issues.subscribe=Abone Ol
issues.unsubscribe=Abonelikten Çık
issues.unpin_issue=Konuyu Sabitlemeden Kaldır
issues.unpin=Sabitlemeyi kaldır
issues.max_pinned=Daha fazla konuyu sabitleyemezsiniz
issues.pin_comment=%s sabitlendi
issues.unpin_comment=%s sabitlenmesi kaldırıldı
@ -2083,7 +2080,6 @@ settings.advanced_settings=Gelişmiş Ayarlar
settings.wiki_desc=Depo Wiki'sini Etkinkleştir
settings.use_internal_wiki=Dahili Wiki Kullan
settings.default_wiki_branch_name=Varsayılan Viki Dal Adı
settings.default_wiki_everyone_access=Oturum açmış kullanıcılar için Varsayılan Erişim İzinleri:
settings.failed_to_change_default_wiki_branch=Varsayılan viki dalı değiştirilemedi.
settings.use_external_wiki=Harici Wiki Kullan
settings.external_wiki_url=Harici Wiki bağlantısı
@ -3434,7 +3430,6 @@ conda.install=Conda ile paket kurmak için aşağıdaki komutu çalıştırın:
container.details.type=Görüntü Türü
container.details.platform=Platform
container.pull=Görüntüyü komut satırını kullanarak çekin:
container.digest=Özet:
container.multi_arch=İşletim Sistemi / Mimari
container.layers=Görüntü Katmanları
container.labels=Etiketler

@ -1111,9 +1111,6 @@ blame.ignore_revs=忽略 <a href="%s">.git-blame-ignore-revs</a> 的修订。点
blame.ignore_revs.failed=忽略 <a href="%s">.git-blame-ignore-revs</a> 版本失败。
user_search_tooltip=最多显示30名用户
tree_path_not_found_commit=路径%[1]s 在提交 %[2]s 中不存在
tree_path_not_found_branch=路径 %[1]s 不存在于分支 %[2]s 中。
tree_path_not_found_tag=路径 %[1]s 不存在于标签 %[2]s 中
transfer.accept=接受转移
transfer.accept_desc=`转移到 "%s"`
@ -1646,7 +1643,7 @@ issues.attachment.open_tab=`在新的标签页中查看 '%s'`
issues.attachment.download=`点击下载 '%s'`
issues.subscribe=订阅
issues.unsubscribe=取消订阅
issues.unpin_issue=取消置顶
issues.unpin=取消置顶
issues.max_pinned=您不能置顶更多工单
issues.pin_comment=于 %s 被置顶
issues.unpin_comment=于 %s 取消置顶
@ -1681,16 +1678,13 @@ issues.timetracker_timer_manually_add=添加时间
issues.time_estimate_set=设置预计时间
issues.time_estimate_display=预计: %s
issues.change_time_estimate_at=将预计时间修改为 <b>%s</b> %s
issues.remove_time_estimate_at=删除预计时间 %s
issues.time_estimate_invalid=预计时间格式无效
issues.start_tracking_history=`开始工作 %s`
issues.tracker_auto_close=当此工单关闭时,自动停止计时器
issues.tracking_already_started=`你已经开始对 <a href="%s">另一个工单</a> 进行时间跟踪!`
issues.stop_tracking_history=`停止工作 %s`
issues.cancel_tracking_history=`取消时间跟踪 %s`
issues.del_time=删除此时间跟踪日志
issues.add_time_history=`添加计时 %s`
issues.del_time_history=`已删除时间 %s`
issues.add_time_manually=手动添加时间
issues.add_time_hours=小时
@ -2154,7 +2148,6 @@ settings.advanced_settings=高级设置
settings.wiki_desc=启用仓库百科
settings.use_internal_wiki=使用内置百科
settings.default_wiki_branch_name=默认百科分支名称
settings.default_wiki_everyone_access=登录用户的默认访问权限:
settings.failed_to_change_default_wiki_branch=更改百科默认分支失败。
settings.use_external_wiki=使用外部百科
settings.external_wiki_url=外部 Wiki 链接
@ -3363,7 +3356,6 @@ monitor.execute_time=执行时长
monitor.last_execution_result=结果
monitor.process.cancel=中止进程
monitor.process.cancel_desc=中止一个进程可能导致数据丢失
monitor.process.cancel_notices=中止:<strong>%s</strong> ?
monitor.process.children=子进程
monitor.queues=队列
@ -3559,7 +3551,6 @@ conda.install=要使用 Conda 安装软件包,请运行以下命令:
container.details.type=镜像类型
container.details.platform=平台
container.pull=从命令行拉取镜像:
container.digest=摘要:
container.multi_arch=OS / Arch
container.layers=镜像层
container.labels=标签

@ -1108,9 +1108,6 @@ blame.ignore_revs=忽略 <a href="%s">.git-blame-ignore-revs</a> 中的修訂。
blame.ignore_revs.failed=忽略 <a href="%s">.git-blame-ignore-revs</a> 中的修訂失敗。
user_search_tooltip=顯示最多 30 個使用者
tree_path_not_found_commit=路徑 %[1]s 在提交 %[2]s 中不存在
tree_path_not_found_branch=路徑 %[1]s 在分支 %[2]s 中不存在
tree_path_not_found_tag=路徑 %[1]s 在標籤 %[2]s 中不存在
transfer.accept=同意轉移
transfer.accept_desc=轉移到「%s」
@ -1640,7 +1637,7 @@ issues.attachment.open_tab=`在新分頁中查看「%s」`
issues.attachment.download=`點擊下載「%s」`
issues.subscribe=訂閱
issues.unsubscribe=取消訂閱
issues.unpin_issue=取消固定問題
issues.unpin=取消固定
issues.max_pinned=您不能固定更多問題
issues.pin_comment=固定於 %s
issues.unpin_comment=取消固定於 %s
@ -1675,16 +1672,13 @@ issues.timetracker_timer_manually_add=手動新增時間
issues.time_estimate_set=設定預估時間
issues.time_estimate_display=預估時間:%s
issues.change_time_estimate_at=將預估時間更改為 <b>%s</b> %s
issues.remove_time_estimate_at=移除預估時間 %s
issues.time_estimate_invalid=預估時間格式無效
issues.start_tracking_history=`開始工作 %s`
issues.tracker_auto_close=當這個問題被關閉時,自動停止計時器
issues.tracking_already_started=`您已在<a href="%s">另一個問題</a>上開始時間追蹤!`
issues.stop_tracking_history=`結束工作 %s`
issues.cancel_tracking_history=`取消時間追蹤 %s`
issues.del_time=刪除此時間記錄
issues.add_time_history=`加入了花費時間 %s`
issues.del_time_history=`刪除了花費時間 %s`
issues.add_time_manually=手動新增時間
issues.add_time_hours=小時
@ -2145,7 +2139,6 @@ settings.advanced_settings=進階設定
settings.wiki_desc=啟用儲存庫 Wiki
settings.use_internal_wiki=使用內建 Wiki
settings.default_wiki_branch_name=預設 Wiki 分支名稱
settings.default_wiki_everyone_access=登入使用者的預設存取權限:
settings.failed_to_change_default_wiki_branch=更改預設 Wiki 分支失敗。
settings.use_external_wiki=使用外部 Wiki
settings.external_wiki_url=外部 Wiki 連結
@ -3354,7 +3347,6 @@ monitor.execute_time=已執行時間
monitor.last_execution_result=結果
monitor.process.cancel=結束處理程序
monitor.process.cancel_desc=結束處理程序可能造成資料遺失
monitor.process.cancel_notices=結束: <strong>%s</strong>?
monitor.process.children=子程序
monitor.queues=佇列
@ -3550,7 +3542,6 @@ conda.install=執行下列命令以使用 Conda 安裝此套件:
container.details.type=映像檔類型
container.details.platform=平台
container.pull=透過下列命令拉取映像檔:
container.digest=摘要:
container.multi_arch=作業系統 / 架構
container.layers=映像檔 Layers
container.labels=標籤

963
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -5,18 +5,18 @@
},
"dependencies": {
"@citation-js/core": "0.7.14",
"@citation-js/plugin-bibtex": "0.7.16",
"@citation-js/plugin-bibtex": "0.7.17",
"@citation-js/plugin-csl": "0.7.14",
"@citation-js/plugin-software-formats": "0.6.1",
"@github/markdown-toolbar-element": "2.2.3",
"@github/relative-time-element": "4.4.4",
"@github/relative-time-element": "4.4.5",
"@github/text-expander-element": "2.8.0",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@primer/octicons": "19.14.0",
"@silverwind/vue3-calendar-heatmap": "2.0.6",
"add-asset-webpack-plugin": "3.0.0",
"ansi_up": "6.0.2",
"asciinema-player": "3.8.1",
"asciinema-player": "3.8.2",
"chart.js": "4.4.7",
"chartjs-adapter-dayjs-4": "1.0.4",
"chartjs-plugin-zoom": "2.2.0",
@ -28,11 +28,11 @@
"easymde": "2.18.0",
"esbuild-loader": "4.2.2",
"escape-goat": "4.0.0",
"fast-glob": "3.3.2",
"fast-glob": "3.3.3",
"htmx.org": "2.0.4",
"idiomorph": "0.3.0",
"idiomorph": "0.4.0",
"jquery": "3.7.1",
"katex": "0.16.18",
"katex": "0.16.20",
"license-checker-webpack-plugin": "0.2.1",
"mermaid": "11.4.1",
"mini-css-extract-plugin": "2.9.2",
@ -41,7 +41,7 @@
"monaco-editor-webpack-plugin": "7.1.0",
"pdfobject": "2.3.0",
"perfect-debounce": "1.0.0",
"postcss": "8.4.49",
"postcss": "8.5.1",
"postcss-loader": "8.1.1",
"postcss-nesting": "13.0.1",
"sortablejs": "1.15.6",
@ -52,7 +52,7 @@
"tippy.js": "6.3.7",
"toastify-js": "1.12.0",
"tributejs": "5.1.3",
"typescript": "5.7.2",
"typescript": "5.7.3",
"uint8-to-base64": "0.2.0",
"vanilla-colorful": "0.7.2",
"vue": "3.5.13",
@ -60,15 +60,14 @@
"vue-chartjs": "5.3.2",
"vue-loader": "17.4.2",
"webpack": "5.97.1",
"webpack-cli": "5.1.4",
"webpack-cli": "6.0.1",
"wrap-ansi": "9.0.0"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "4.4.1",
"@playwright/test": "1.49.1",
"@silverwind/vue-tsc": "2.1.13",
"@stoplight/spectral-cli": "6.14.2",
"@stylistic/eslint-plugin-js": "2.12.1",
"@stylistic/eslint-plugin-js": "2.13.0",
"@stylistic/stylelint-plugin": "3.1.1",
"@types/dropzone": "5.7.9",
"@types/jquery": "3.5.32",
@ -80,8 +79,8 @@
"@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6",
"@types/toastify-js": "1.12.3",
"@typescript-eslint/eslint-plugin": "8.18.1",
"@typescript-eslint/parser": "8.18.1",
"@typescript-eslint/eslint-plugin": "8.20.0",
"@typescript-eslint/parser": "8.20.0",
"@vitejs/plugin-vue": "5.2.1",
"eslint": "8.57.0",
"eslint-import-resolver-typescript": "3.7.0",
@ -99,19 +98,20 @@
"eslint-plugin-vue": "9.32.0",
"eslint-plugin-vue-scoped-css": "2.9.0",
"eslint-plugin-wc": "2.2.0",
"happy-dom": "15.11.7",
"happy-dom": "16.6.0",
"markdownlint-cli": "0.43.0",
"nolyfill": "1.0.43",
"postcss-html": "1.7.0",
"stylelint": "16.12.0",
"postcss-html": "1.8.0",
"stylelint": "16.13.2",
"stylelint-declaration-block-no-ignored-properties": "2.8.0",
"stylelint-declaration-strict-value": "1.10.6",
"stylelint-declaration-strict-value": "1.10.7",
"stylelint-value-no-unknown-custom-properties": "6.0.1",
"svgo": "3.3.2",
"type-fest": "4.30.2",
"type-fest": "4.32.0",
"updates": "16.4.1",
"vite-string-plugin": "1.3.4",
"vitest": "2.1.8"
"vitest": "2.1.8",
"vue-tsc": "2.2.0"
},
"browserslist": [
"defaults"

73
poetry.lock generated

@ -1,14 +1,14 @@
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
[[package]]
name = "click"
version = "8.1.7"
version = "8.1.8"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
{file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"},
{file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"},
]
[package.dependencies]
@ -42,33 +42,33 @@ six = ">=1.13.0"
[[package]]
name = "djlint"
version = "1.36.3"
version = "1.36.4"
description = "HTML Template Linter and Formatter"
optional = false
python-versions = ">=3.9"
files = [
{file = "djlint-1.36.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ae7c620b58e16d6bf003bd7de3f71376a7a3daa79dc02e77f3726d5a75243f2"},
{file = "djlint-1.36.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e155ce0970d4a28d0a2e9f2e106733a2ad05910eee90e056b056d48049e4a97b"},
{file = "djlint-1.36.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e8bb0406e60cc696806aa6226df137618f3889c72f2dbdfa76c908c99151579"},
{file = "djlint-1.36.3-cp310-cp310-win_amd64.whl", hash = "sha256:76d32faf988ad58ef2e7a11d04046fc984b98391761bf1b61f9a6044da53d414"},
{file = "djlint-1.36.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:32f7a5834000fff22e94d1d35f95aaf2e06f2af2cae18af0ed2a4e215d60e730"},
{file = "djlint-1.36.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3eb1b9c0be499e63e8822a051e7e55f188ff1ab8172a85d338a8ae21c872060e"},
{file = "djlint-1.36.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c2e0dd1f26eb472b8c84eb70d6482877b6497a1fd031d7534864088f016d5ea"},
{file = "djlint-1.36.3-cp311-cp311-win_amd64.whl", hash = "sha256:a06b531ab9d049c46ad4d2365d1857004a1a9dd0c23c8eae94aa0d233c6ec00d"},
{file = "djlint-1.36.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e66361a865e5e5a4bbcb40f56af7f256fd02cbf9d48b763a40172749cc294084"},
{file = "djlint-1.36.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:36e102b80d83e9ac2e6be9a9ded32fb925945f6dbc7a7156e4415de1b0aa0dba"},
{file = "djlint-1.36.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ac4b7370d80bd82281e57a470de8923ac494ffb571b89d8787cef57c738c69a"},
{file = "djlint-1.36.3-cp312-cp312-win_amd64.whl", hash = "sha256:107cc56bbef13d60cc0ae774a4d52881bf98e37c02412e573827a3e549217e3a"},
{file = "djlint-1.36.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2a9f51971d6e63c41ea9b3831c928e1f21ae6fe57e87a3452cfe672d10232433"},
{file = "djlint-1.36.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:080c98714b55d8f0fef5c42beaee8247ebb2e3d46b0936473bd6c47808bb6302"},
{file = "djlint-1.36.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f65a80e0b5cb13d357ea51ca6570b34c2d9d18974c1e57142de760ea27d49ed0"},
{file = "djlint-1.36.3-cp313-cp313-win_amd64.whl", hash = "sha256:95ef6b67ef7f2b90d9434bba37d572031079001dc8524add85c00ef0386bda1e"},
{file = "djlint-1.36.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e2317a32094d525bc41cd11c8dc064bf38d1b442c99cc3f7c4a2616b5e6ce6e"},
{file = "djlint-1.36.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e82266c28793cd15f97b93535d72bfbc77306eaaf6b210dd90910383a814ee6c"},
{file = "djlint-1.36.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01b2101c2d1b079e8d545e6d9d03487fcca14d2371e44cbfdedee15b0bf4567c"},
{file = "djlint-1.36.3-cp39-cp39-win_amd64.whl", hash = "sha256:15cde63ef28beb5194ff4137883025f125676ece1b574b64a3e1c6daed734639"},
{file = "djlint-1.36.3-py3-none-any.whl", hash = "sha256:0c05cd5b76785de2c41a2420c06ffd112800bfc0f9c0f399cc7cea7c42557f4c"},
{file = "djlint-1.36.3.tar.gz", hash = "sha256:d85735da34bc7ac93ad8ef9b4822cc2a23d5f0ce33f25438737b8dca1d404f78"},
{file = "djlint-1.36.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a2dfb60883ceb92465201bfd392291a7597c6752baede6fbb6f1980cac8d6c5c"},
{file = "djlint-1.36.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4bc6a1320c0030244b530ac200642f883d3daa451a115920ef3d56d08b644292"},
{file = "djlint-1.36.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3164a048c7bb0baf042387b1e33f9bbbf99d90d1337bb4c3d66eb0f96f5400a1"},
{file = "djlint-1.36.4-cp310-cp310-win_amd64.whl", hash = "sha256:3196d5277da5934962d67ad6c33a948ba77a7b6eadf064648bef6ee5f216b03c"},
{file = "djlint-1.36.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d68da0ed10ee9ca1e32e225cbb8e9b98bf7e6f8b48a8e4836117b6605b88cc7"},
{file = "djlint-1.36.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c0478d5392247f1e6ee29220bbdbf7fb4e1bc0e7e83d291fda6fb926c1787ba7"},
{file = "djlint-1.36.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:962f7b83aee166e499eff916d631c6dde7f1447d7610785a60ed2a75a5763483"},
{file = "djlint-1.36.4-cp311-cp311-win_amd64.whl", hash = "sha256:53cbc450aa425c832f09bc453b8a94a039d147b096740df54a3547fada77ed08"},
{file = "djlint-1.36.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ff9faffd7d43ac20467493fa71d5355b5b330a00ade1c4d1e859022f4195223b"},
{file = "djlint-1.36.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:79489e262b5ac23a8dfb7ca37f1eea979674cfc2d2644f7061d95bea12c38f7e"},
{file = "djlint-1.36.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e58c5fa8c6477144a0be0a87273706a059e6dd0d6efae01146ae8c29cdfca675"},
{file = "djlint-1.36.4-cp312-cp312-win_amd64.whl", hash = "sha256:bb6903777bf3124f5efedcddf1f4716aef097a7ec4223fc0fa54b865829a6e08"},
{file = "djlint-1.36.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ead475013bcac46095b1bbc8cf97ed2f06e83422335734363f8a76b4ba7e47c2"},
{file = "djlint-1.36.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6c601dfa68ea253311deb4a29a7362b7a64933bdfcfb5a06618f3e70ad1fa835"},
{file = "djlint-1.36.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bda5014f295002363381969864addeb2db13955f1b26e772657c3b273ed7809f"},
{file = "djlint-1.36.4-cp313-cp313-win_amd64.whl", hash = "sha256:16ce37e085afe5a30953b2bd87cbe34c37843d94c701fc68a2dda06c1e428ff4"},
{file = "djlint-1.36.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:89678661888c03d7bc6cadd75af69db29962b5ecbf93a81518262f5c48329f04"},
{file = "djlint-1.36.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5b01a98df3e1ab89a552793590875bc6e954cad661a9304057db75363d519fa0"},
{file = "djlint-1.36.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dabbb4f7b93223d471d09ae34ed515fef98b2233cbca2449ad117416c44b1351"},
{file = "djlint-1.36.4-cp39-cp39-win_amd64.whl", hash = "sha256:7a483390d17e44df5bc23dcea29bdf6b63f3ed8b4731d844773a4829af4f5e0b"},
{file = "djlint-1.36.4-py3-none-any.whl", hash = "sha256:e9699b8ac3057a6ed04fb90835b89bee954ed1959c01541ce4f8f729c938afdd"},
{file = "djlint-1.36.4.tar.gz", hash = "sha256:17254f218b46fe5a714b224c85074c099bcb74e3b2e1f15c2ddc2cf415a408a1"},
]
[package.dependencies]
@ -82,15 +82,17 @@ pyyaml = ">=6"
regex = ">=2023"
tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""}
tqdm = ">=4.62.2"
typing-extensions = {version = ">=3.6.6", markers = "python_version < \"3.11\""}
[[package]]
name = "editorconfig"
version = "0.12.4"
version = "0.17.0"
description = "EditorConfig File Locator and Interpreter for Python"
optional = false
python-versions = "*"
files = [
{file = "EditorConfig-0.12.4.tar.gz", hash = "sha256:24857fa1793917dd9ccf0c7810a07e05404ce9b823521c7dce22a4fb5d125f80"},
{file = "EditorConfig-0.17.0-py3-none-any.whl", hash = "sha256:fe491719c5f65959ec00b167d07740e7ffec9a3f362038c72b289330b9991dfc"},
{file = "editorconfig-0.17.0.tar.gz", hash = "sha256:8739052279699840065d3a9f5c125d7d5a98daeefe53b0e5274261d77cb49aa2"},
]
[[package]]
@ -370,6 +372,17 @@ notebook = ["ipywidgets (>=6)"]
slack = ["slack-sdk"]
telegram = ["requests"]
[[package]]
name = "typing-extensions"
version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "yamllint"
version = "1.35.1"
@ -391,4 +404,4 @@ dev = ["doc8", "flake8", "flake8-import-order", "rstcheck[sphinx]", "sphinx"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "01b1e2f910276dd20a70ebb665c83415c37531709d90874f5b7a86a5305e2369"
content-hash = "f2e8260efe6e25f77ef387daff9551e41d25027e4794b42bc7a851ed0dfafd85"

@ -5,7 +5,7 @@ package-mode = false
python = "^3.10"
[tool.poetry.group.dev.dependencies]
djlint = "1.36.3"
djlint = "1.36.4"
yamllint = "1.35.1"
[tool.djlint]

@ -1,14 +0,0 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package runner
import (
"testing"
"code.gitea.io/gitea/models/unittest"
)
func TestMain(m *testing.M) {
unittest.MainTest(m)
}

@ -8,14 +8,8 @@ import (
"fmt"
actions_model "code.gitea.io/gitea/models/actions"
"code.gitea.io/gitea/models/db"
secret_model "code.gitea.io/gitea/models/secret"
actions_module "code.gitea.io/gitea/modules/actions"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/actions"
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
@ -65,82 +59,16 @@ func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv
}
func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct {
event := map[string]any{}
_ = json.Unmarshal([]byte(t.Job.Run.EventPayload), &event)
// TriggerEvent is added in https://github.com/go-gitea/gitea/pull/25229
// This fallback is for the old ActionRun that doesn't have the TriggerEvent field
// and should be removed in 1.22
eventName := t.Job.Run.TriggerEvent
if eventName == "" {
eventName = t.Job.Run.Event.Event()
}
baseRef := ""
headRef := ""
ref := t.Job.Run.Ref
sha := t.Job.Run.CommitSHA
if pullPayload, err := t.Job.Run.GetPullRequestEventPayload(); err == nil && pullPayload.PullRequest != nil && pullPayload.PullRequest.Base != nil && pullPayload.PullRequest.Head != nil {
baseRef = pullPayload.PullRequest.Base.Ref
headRef = pullPayload.PullRequest.Head.Ref
// if the TriggerEvent is pull_request_target, ref and sha need to be set according to the base of pull request
// In GitHub's documentation, ref should be the branch or tag that triggered workflow. But when the TriggerEvent is pull_request_target,
// the ref will be the base branch.
if t.Job.Run.TriggerEvent == actions_module.GithubEventPullRequestTarget {
ref = git.BranchPrefix + pullPayload.PullRequest.Base.Name
sha = pullPayload.PullRequest.Base.Sha
}
}
refName := git.RefName(ref)
giteaRuntimeToken, err := actions.CreateAuthorizationToken(t.ID, t.Job.RunID, t.JobID)
if err != nil {
log.Error("actions.CreateAuthorizationToken failed: %v", err)
}
taskContext, err := structpb.NewStruct(map[string]any{
// standard contexts, see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
"action": "", // string, The name of the action currently running, or the id of a step. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2.
"action_path": "", // string, The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action.
"action_ref": "", // string, For a step executing an action, this is the ref of the action being executed. For example, v2.
"action_repository": "", // string, For a step executing an action, this is the owner and repository name of the action. For example, actions/checkout.
"action_status": "", // string, For a composite action, the current result of the composite action.
"actor": t.Job.Run.TriggerUser.Name, // string, The username of the user that triggered the initial workflow run. If the workflow run is a re-run, this value may differ from github.triggering_actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges.
"api_url": setting.AppURL + "api/v1", // string, The URL of the GitHub REST API.
"base_ref": baseRef, // string, The base_ref or target branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target.
"env": "", // string, Path on the runner to the file that sets environment variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see "Workflow commands for GitHub Actions."
"event": event, // object, The full event webhook payload. You can access individual properties of the event using this context. This object is identical to the webhook payload of the event that triggered the workflow run, and is different for each event. The webhooks for each GitHub Actions event is linked in "Events that trigger workflows." For example, for a workflow run triggered by the push event, this object contains the contents of the push webhook payload.
"event_name": eventName, // string, The name of the event that triggered the workflow run.
"event_path": "", // string, The path to the file on the runner that contains the full event webhook payload.
"graphql_url": "", // string, The URL of the GitHub GraphQL API.
"head_ref": headRef, // string, The head_ref or source branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target.
"job": fmt.Sprint(t.JobID), // string, The job_id of the current job.
"ref": ref, // string, The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request, this is the pull request merge branch. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/<branch_name>, for pull requests it is refs/pull/<pr_number>/merge, and for tags it is refs/tags/<tag_name>. For example, refs/heads/feature-branch-1.
"ref_name": refName.ShortName(), // string, The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1.
"ref_protected": false, // boolean, true if branch protections are configured for the ref that triggered the workflow run.
"ref_type": refName.RefType(), // string, The type of ref that triggered the workflow run. Valid values are branch or tag.
"path": "", // string, Path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see "Workflow commands for GitHub Actions."
"repository": t.Job.Run.Repo.OwnerName + "/" + t.Job.Run.Repo.Name, // string, The owner and repository name. For example, Codertocat/Hello-World.
"repository_owner": t.Job.Run.Repo.OwnerName, // string, The repository owner's name. For example, Codertocat.
"repositoryUrl": t.Job.Run.Repo.HTMLURL(), // string, The Git URL to the repository. For example, git://github.com/codertocat/hello-world.git.
"retention_days": "", // string, The number of days that workflow run logs and artifacts are kept.
"run_id": fmt.Sprint(t.Job.RunID), // string, A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run.
"run_number": fmt.Sprint(t.Job.Run.Index), // string, A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run.
"run_attempt": fmt.Sprint(t.Job.Attempt), // string, A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run.
"secret_source": "Actions", // string, The source of a secret used in a workflow. Possible values are None, Actions, Dependabot, or Codespaces.
"server_url": setting.AppURL, // string, The URL of the GitHub server. For example: https://github.com.
"sha": sha, // string, The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see "Events that trigger workflows." For example, ffac537e6cbbf934b08745a378932722df287a53.
"token": t.Token, // string, A token to authenticate on behalf of the GitHub App installed on your repository. This is functionally equivalent to the GITHUB_TOKEN secret. For more information, see "Automatic token authentication."
"triggering_actor": "", // string, The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from github.actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges.
"workflow": t.Job.Run.WorkflowID, // string, The name of the workflow. If the workflow file doesn't specify a name, the value of this property is the full path of the workflow file in the repository.
"workspace": "", // string, The default working directory on the runner for steps, and the default location of your repository when using the checkout action.
// additional contexts
"gitea_default_actions_url": setting.Actions.DefaultActionsURL.URL(),
"gitea_runtime_token": giteaRuntimeToken,
})
gitCtx := actions.GenerateGiteaContext(t.Job.Run, t.Job)
gitCtx["token"] = t.Token
gitCtx["gitea_runtime_token"] = giteaRuntimeToken
taskContext, err := structpb.NewStruct(gitCtx)
if err != nil {
log.Error("structpb.NewStruct failed: %v", err)
}
@ -150,68 +78,18 @@ func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct {
func findTaskNeeds(ctx context.Context, task *actions_model.ActionTask) (map[string]*runnerv1.TaskNeed, error) {
if err := task.LoadAttributes(ctx); err != nil {
return nil, fmt.Errorf("LoadAttributes: %w", err)
}
if len(task.Job.Needs) == 0 {
return nil, nil
return nil, fmt.Errorf("task LoadAttributes: %w", err)
}
needs := container.SetOf(task.Job.Needs...)
jobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: task.Job.RunID})
taskNeeds, err := actions.FindTaskNeeds(ctx, task.Job)
if err != nil {
return nil, fmt.Errorf("FindRunJobs: %w", err)
return nil, err
}
jobIDJobs := make(map[string][]*actions_model.ActionRunJob)
for _, job := range jobs {
jobIDJobs[job.JobID] = append(jobIDJobs[job.JobID], job)
}
ret := make(map[string]*runnerv1.TaskNeed, len(needs))
for jobID, jobsWithSameID := range jobIDJobs {
if !needs.Contains(jobID) {
continue
}
var jobOutputs map[string]string
for _, job := range jobsWithSameID {
if job.TaskID == 0 || !job.Status.IsDone() {
// it shouldn't happen, or the job has been rerun
continue
}
got, err := actions_model.FindTaskOutputByTaskID(ctx, job.TaskID)
if err != nil {
return nil, fmt.Errorf("FindTaskOutputByTaskID: %w", err)
}
outputs := make(map[string]string, len(got))
for _, v := range got {
outputs[v.OutputKey] = v.OutputValue
}
if len(jobOutputs) == 0 {
jobOutputs = outputs
} else {
jobOutputs = mergeTwoOutputs(outputs, jobOutputs)
}
}
ret := make(map[string]*runnerv1.TaskNeed, len(taskNeeds))
for jobID, taskNeed := range taskNeeds {
ret[jobID] = &runnerv1.TaskNeed{
Outputs: jobOutputs,
Result: runnerv1.Result(actions_model.AggregateJobStatus(jobsWithSameID)),
Outputs: taskNeed.Outputs,
Result: runnerv1.Result(taskNeed.Result),
}
}
return ret, nil
}
// mergeTwoOutputs merges two outputs from two different ActionRunJobs
// Values with the same output name may be overridden. The user should ensure the output names are unique.
// See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#using-job-outputs-in-a-matrix-job
func mergeTwoOutputs(o1, o2 map[string]string) map[string]string {
ret := make(map[string]string, len(o1))
for k1, v1 := range o1 {
if len(v1) > 0 {
ret[k1] = v1
} else {
ret[k1] = o2[k1]
}
}
return ret
}

@ -34,11 +34,30 @@ func ListHooks(ctx *context.APIContext) {
// in: query
// description: page size of results
// type: integer
// - type: string
// enum:
// - system
// - default
// - all
// description: system, default or both kinds of webhooks
// name: type
// default: system
// in: query
//
// responses:
// "200":
// "$ref": "#/responses/HookList"
sysHooks, err := webhook.GetSystemWebhooks(ctx, optional.None[bool]())
// for compatibility the default value is true
isSystemWebhook := optional.Some(true)
typeValue := ctx.FormString("type")
if typeValue == "default" {
isSystemWebhook = optional.Some(false)
} else if typeValue == "all" {
isSystemWebhook = optional.None[bool]()
}
sysHooks, err := webhook.GetSystemOrDefaultWebhooks(ctx, isSystemWebhook)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetSystemWebhooks", err)
return

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
@ -443,7 +444,14 @@ func UpdateBranch(ctx *context.APIContext) {
msg, err := repo_service.RenameBranch(ctx, repo, ctx.Doer, ctx.Repo.GitRepo, oldName, opt.Name)
if err != nil {
ctx.Error(http.StatusInternalServerError, "RenameBranch", err)
switch {
case repo_model.IsErrUserDoesNotHaveAccessToRepo(err):
ctx.Error(http.StatusForbidden, "", "User must be a repo or site admin to rename default or protected branches.")
case errors.Is(err, git_model.ErrBranchIsProtected):
ctx.Error(http.StatusForbidden, "", "Branch is protected by glob-based protection rules.")
default:
ctx.Error(http.StatusInternalServerError, "RenameBranch", err)
}
return
}
if msg == "target_exist" {

@ -17,11 +17,11 @@ func DownloadArchive(ctx *context.APIContext) {
var tp git.ArchiveType
switch ballType := ctx.PathParam("ball_type"); ballType {
case "tarball":
tp = git.TARGZ
tp = git.ArchiveTarGz
case "zipball":
tp = git.ZIP
tp = git.ArchiveZip
case "bundle":
tp = git.BUNDLE
tp = git.ArchiveBundle
default:
ctx.Error(http.StatusBadRequest, "", fmt.Sprintf("Unknown archive type: %s", ballType))
return
@ -36,7 +36,7 @@ func DownloadArchive(ctx *context.APIContext) {
}
}
r, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*"), tp)
r, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*")+"."+tp.String())
if err != nil {
ctx.ServerError("NewRequest", err)
return

@ -293,14 +293,7 @@ func GetArchive(ctx *context.APIContext) {
}
func archiveDownload(ctx *context.APIContext) {
uri := ctx.PathParam("*")
ext, tp, err := archiver_service.ParseFileName(uri)
if err != nil {
ctx.Error(http.StatusBadRequest, "ParseFileName", err)
return
}
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, strings.TrimSuffix(uri, ext), tp)
aReq, err := archiver_service.NewRequest(ctx.Repo.Repository.ID, ctx.Repo.GitRepo, ctx.PathParam("*"))
if err != nil {
if errors.Is(err, archiver_service.ErrUnknownArchiveFormat{}) {
ctx.Error(http.StatusBadRequest, "unknown archive format", err)

@ -23,7 +23,7 @@ func TestTestHook(t *testing.T) {
contexttest.LoadRepoCommit(t, ctx)
contexttest.LoadUser(t, ctx, 2)
TestHook(ctx)
assert.EqualValues(t, http.StatusNoContent, ctx.Resp.Status())
assert.EqualValues(t, http.StatusNoContent, ctx.Resp.WrittenStatus())
unittest.AssertExistsAndLoadBean(t, &webhook.HookTask{
HookID: 1,

@ -58,7 +58,7 @@ func TestRepoEdit(t *testing.T) {
web.SetForm(ctx, &opts)
Edit(ctx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus())
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{
ID: 1,
}, unittest.Cond("name = ? AND is_archived = 1", *opts.Name))
@ -78,7 +78,7 @@ func TestRepoEditNameChange(t *testing.T) {
web.SetForm(ctx, &opts)
Edit(ctx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus())
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{
ID: 1,

@ -9,6 +9,7 @@ import (
"strings"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/gtprof"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/reqctx"
"code.gitea.io/gitea/modules/setting"
@ -43,14 +44,26 @@ func ProtocolMiddlewares() (handlers []any) {
func RequestContextHandler() func(h http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
profDesc := fmt.Sprintf("%s: %s", req.Method, req.RequestURI)
return http.HandlerFunc(func(respOrig http.ResponseWriter, req *http.Request) {
// this response writer might not be the same as the one in context.Base.Resp
// because there might be a "gzip writer" in the middle, so the "written size" here is the compressed size
respWriter := context.WrapResponseWriter(respOrig)
profDesc := fmt.Sprintf("HTTP: %s %s", req.Method, req.RequestURI)
ctx, finished := reqctx.NewRequestContext(req.Context(), profDesc)
defer finished()
ctx, span := gtprof.GetTracer().Start(ctx, gtprof.TraceSpanHTTP)
req = req.WithContext(ctx)
defer func() {
chiCtx := chi.RouteContext(req.Context())
span.SetAttributeString(gtprof.TraceAttrHTTPRoute, chiCtx.RoutePattern())
span.End()
}()
defer func() {
if err := recover(); err != nil {
RenderPanicErrorPage(resp, req, err) // it should never panic
RenderPanicErrorPage(respWriter, req, err) // it should never panic
}
}()
@ -62,7 +75,7 @@ func RequestContextHandler() func(h http.Handler) http.Handler {
_ = req.MultipartForm.RemoveAll() // remove the temp files buffered to tmp directory
}
})
next.ServeHTTP(context.WrapResponseWriter(resp), req)
next.ServeHTTP(respWriter, req)
})
}
}
@ -71,11 +84,11 @@ func ChiRoutePathHandler() func(h http.Handler) http.Handler {
// make sure chi uses EscapedPath(RawPath) as RoutePath, then "%2f" could be handled correctly
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
ctx := chi.RouteContext(req.Context())
chiCtx := chi.RouteContext(req.Context())
if req.URL.RawPath == "" {
ctx.RoutePath = req.URL.EscapedPath()
chiCtx.RoutePath = req.URL.EscapedPath()
} else {
ctx.RoutePath = req.URL.RawPath
chiCtx.RoutePath = req.URL.RawPath
}
next.ServeHTTP(resp, req)
})

@ -213,7 +213,7 @@ func NormalRoutes() *web.Router {
}
r.NotFound(func(w http.ResponseWriter, req *http.Request) {
routing.UpdateFuncInfo(req.Context(), routing.GetFuncInfo(http.NotFound, "GlobalNotFound"))
defer routing.RecordFuncInfo(req.Context(), routing.GetFuncInfo(http.NotFound, "GlobalNotFound"))()
http.NotFound(w, req)
})
return r

@ -37,6 +37,7 @@ const (
tplSelfCheck templates.TplName = "admin/self_check"
tplCron templates.TplName = "admin/cron"
tplQueue templates.TplName = "admin/queue"
tplPerfTrace templates.TplName = "admin/perftrace"
tplStacktrace templates.TplName = "admin/stacktrace"
tplQueueManage templates.TplName = "admin/queue_manage"
tplStats templates.TplName = "admin/stats"

@ -10,13 +10,15 @@ import (
"time"
"code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/tailmsg"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/context"
)
func MonitorDiagnosis(ctx *context.Context) {
seconds := ctx.FormInt64("seconds")
if seconds <= 5 {
seconds = 5
if seconds <= 1 {
seconds = 1
}
if seconds > 300 {
seconds = 300
@ -65,4 +67,16 @@ func MonitorDiagnosis(ctx *context.Context) {
return
}
_ = pprof.Lookup("heap").WriteTo(f, 0)
f, err = zipWriter.CreateHeader(&zip.FileHeader{Name: "perftrace.txt", Method: zip.Deflate, Modified: time.Now()})
if err != nil {
ctx.ServerError("Failed to create zip file", err)
return
}
for _, record := range tailmsg.GetManager().GetTraceRecorder().GetRecords() {
_, _ = f.Write(util.UnsafeStringToBytes(record.Time.Format(time.RFC3339)))
_, _ = f.Write([]byte(" "))
_, _ = f.Write(util.UnsafeStringToBytes((record.Content)))
_, _ = f.Write([]byte("\n\n"))
}
}

@ -0,0 +1,18 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package admin
import (
"net/http"
"code.gitea.io/gitea/modules/tailmsg"
"code.gitea.io/gitea/services/context"
)
func PerfTrace(ctx *context.Context) {
monitorTraceCommon(ctx)
ctx.Data["PageIsAdminMonitorPerfTrace"] = true
ctx.Data["PerfTraceRecords"] = tailmsg.GetManager().GetTraceRecorder().GetRecords()
ctx.HTML(http.StatusOK, tplPerfTrace)
}

@ -12,10 +12,17 @@ import (
"code.gitea.io/gitea/services/context"
)
func monitorTraceCommon(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.monitor")
ctx.Data["PageIsAdminMonitorTrace"] = true
// Hide the performance trace tab in production, because it shows a lot of SQLs and is not that useful for end users.
// To avoid confusing end users, do not let them know this tab. End users should "download diagnosis report" instead.
ctx.Data["ShowAdminPerformanceTraceTab"] = !setting.IsProd
}
// Stacktrace show admin monitor goroutines page
func Stacktrace(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.monitor")
ctx.Data["PageIsAdminMonitorStacktrace"] = true
monitorTraceCommon(ctx)
ctx.Data["GoroutineCount"] = runtime.NumGoroutine()

@ -29,6 +29,7 @@ var tplLinkAccount templates.TplName = "user/auth/link_account"
// LinkAccount shows the page where the user can decide to login or create a new account
func LinkAccount(ctx *context.Context) {
// FIXME: these common template variables should be prepared in one common function, but not just copy-paste again and again.
ctx.Data["DisablePassword"] = !setting.Service.RequireExternalRegistrationPassword || setting.Service.AllowOnlyExternalRegistration
ctx.Data["Title"] = ctx.Tr("link_account")
ctx.Data["LinkAccountMode"] = true
@ -43,6 +44,7 @@ func LinkAccount(ctx *context.Context) {
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
ctx.Data["ShowRegistrationButton"] = false
// use this to set the right link into the signIn and signUp templates in the link_account template
@ -50,6 +52,11 @@ func LinkAccount(ctx *context.Context) {
ctx.Data["SignUpLink"] = setting.AppSubURL + "/user/link_account_signup"
gothUser, ok := ctx.Session.Get("linkAccountGothUser").(goth.User)
// If you'd like to quickly debug the "link account" page layout, just uncomment the blow line
// Don't worry, when the below line exists, the lint won't pass: ineffectual assignment to gothUser (ineffassign)
// gothUser, ok = goth.User{Email: "invalid-email", Name: "."}, true // intentionally use invalid data to avoid pass the registration check
if !ok {
// no account in session, so just redirect to the login page, then the user could restart the process
ctx.Redirect(setting.AppSubURL + "/user/login")
@ -135,6 +142,8 @@ func LinkAccountPostSignIn(ctx *context.Context) {
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
ctx.Data["ShowRegistrationButton"] = false
// use this to set the right link into the signIn and signUp templates in the link_account template
@ -223,6 +232,8 @@ func LinkAccountPostRegister(ctx *context.Context) {
ctx.Data["McaptchaURL"] = setting.Service.McaptchaURL
ctx.Data["CfTurnstileSitekey"] = setting.Service.CfTurnstileSitekey
ctx.Data["DisableRegistration"] = setting.Service.DisableRegistration
ctx.Data["AllowOnlyInternalRegistration"] = setting.Service.AllowOnlyInternalRegistration
ctx.Data["EnablePasswordSignInForm"] = setting.Service.EnablePasswordSignInForm
ctx.Data["ShowRegistrationButton"] = false
// use this to set the right link into the signIn and signUp templates in the link_account template

@ -34,7 +34,7 @@ func storageHandler(storageSetting *setting.Storage, prefix string, objStore sto
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
routing.UpdateFuncInfo(req.Context(), funcInfo)
defer routing.RecordFuncInfo(req.Context(), funcInfo)()
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
rPath = util.PathJoinRelX(rPath)
@ -65,7 +65,7 @@ func storageHandler(storageSetting *setting.Storage, prefix string, objStore sto
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
routing.UpdateFuncInfo(req.Context(), funcInfo)
defer routing.RecordFuncInfo(req.Context(), funcInfo)()
rPath := strings.TrimPrefix(req.URL.Path, "/"+prefix+"/")
rPath = util.PathJoinRelX(rPath)

@ -23,7 +23,7 @@ func ShowBranchFeed(ctx *context.Context, repo *repo.Repository, formatType stri
}
title := fmt.Sprintf("Latest commits for branch %s", ctx.Repo.BranchName)
link := &feeds.Link{Href: repo.HTMLURL() + "/" + ctx.Repo.BranchNameSubURL()}
link := &feeds.Link{Href: repo.HTMLURL() + "/" + ctx.Repo.RefTypeNameSubURL()}
feed := &feeds.Feed{
Title: title,

@ -24,7 +24,7 @@ func ShowFileFeed(ctx *context.Context, repo *repo.Repository, formatType string
}
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: ctx.Repo.RefName,
Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName
File: fileName,
Page: 1,
})
@ -35,7 +35,7 @@ func ShowFileFeed(ctx *context.Context, repo *repo.Repository, formatType string
title := fmt.Sprintf("Latest commits for file %s", ctx.Repo.TreePath)
link := &feeds.Link{Href: repo.HTMLURL() + "/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)}
link := &feeds.Link{Href: repo.HTMLURL() + "/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath)}
feed := &feeds.Feed{
Title: title,

@ -850,7 +850,7 @@ func Run(ctx *context_module.Context) {
inputs := make(map[string]any)
if workflowDispatch := workflow.WorkflowDispatchConfig(); workflowDispatch != nil {
for name, config := range workflowDispatch.Inputs {
value := ctx.Req.PostForm.Get(name)
value := ctx.Req.PostFormValue(name)
if config.Type == "boolean" {
// https://www.w3.org/TR/html401/interact/forms.html
// https://stackoverflow.com/questions/11424037/do-checkbox-inputs-only-post-data-if-theyre-checked

@ -46,9 +46,9 @@ func RefBlame(ctx *context.Context) {
return
}
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
branchLink := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
treeLink := branchLink
rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL()
rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL()
if len(ctx.Repo.TreePath) > 0 {
treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)

@ -37,7 +37,6 @@ const (
// Branches render repository branch page
func Branches(ctx *context.Context) {
ctx.Data["Title"] = "Branches"
ctx.Data["IsRepoToolbarBranches"] = true
ctx.Data["AllowsPulls"] = ctx.Repo.Repository.AllowsPulls(ctx)
ctx.Data["IsWriter"] = ctx.Repo.CanWrite(unit.TypeCode)
ctx.Data["IsMirror"] = ctx.Repo.Repository.IsMirror
@ -185,7 +184,7 @@ func CreateBranch(ctx *context.Context) {
if ctx.HasError() {
ctx.Flash.Error(ctx.GetErrMsg())
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
return
}
@ -193,11 +192,11 @@ func CreateBranch(ctx *context.Context) {
if form.CreateTag {
target := ctx.Repo.CommitID
if ctx.Repo.IsViewBranch {
if ctx.Repo.RefFullName.IsBranch() {
target = ctx.Repo.BranchName
}
err = release_service.CreateNewTag(ctx, ctx.Doer, ctx.Repo.Repository, target, form.NewBranchName, "")
} else if ctx.Repo.IsViewBranch {
} else if ctx.Repo.RefFullName.IsBranch() {
err = repo_service.CreateNewBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.BranchName, form.NewBranchName)
} else {
err = repo_service.CreateNewBranchFromCommit(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, ctx.Repo.CommitID, form.NewBranchName)
@ -205,25 +204,25 @@ func CreateBranch(ctx *context.Context) {
if err != nil {
if release_service.IsErrProtectedTagName(err) {
ctx.Flash.Error(ctx.Tr("repo.release.tag_name_protected"))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
return
}
if release_service.IsErrTagAlreadyExists(err) {
e := err.(release_service.ErrTagAlreadyExists)
ctx.Flash.Error(ctx.Tr("repo.branch.tag_collision", e.TagName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
return
}
if git_model.IsErrBranchAlreadyExists(err) || git.IsErrPushOutOfDate(err) {
ctx.Flash.Error(ctx.Tr("repo.branch.branch_already_exists", form.NewBranchName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
return
}
if git_model.IsErrBranchNameConflict(err) {
e := err.(git_model.ErrBranchNameConflict)
ctx.Flash.Error(ctx.Tr("repo.branch.branch_name_conflict", form.NewBranchName, e.BranchName))
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
return
}
if git.IsErrPushRejected(err) {
@ -242,7 +241,7 @@ func CreateBranch(ctx *context.Context) {
}
ctx.Flash.Error(flashError)
}
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL())
ctx.Redirect(ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL())
return
}

@ -57,7 +57,7 @@ func CherryPick(ctx *context.Context) {
ctx.Data["new_branch_name"] = GetUniquePatchBranchName(ctx)
ctx.Data["last_commit"] = ctx.Repo.CommitID
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
ctx.HTML(200, tplCherryPick)
}
@ -85,7 +85,7 @@ func CherryPickPost(ctx *context.Context) {
ctx.Data["new_branch_name"] = form.NewBranchName
ctx.Data["last_commit"] = ctx.Repo.CommitID
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
if ctx.HasError() {
ctx.HTML(200, tplCherryPick)

@ -29,7 +29,7 @@ func CodeFrequency(ctx *context.Context) {
// CodeFrequencyData returns JSON of code frequency data
func CodeFrequencyData(ctx *context.Context) {
if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.CommitID); err != nil {
if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch); err != nil {
if errors.Is(err, contributors_service.ErrAwaitGeneration) {
ctx.Status(http.StatusAccepted)
return

@ -62,11 +62,7 @@ func Commits(ctx *context.Context) {
}
ctx.Data["PageIsViewCode"] = true
commitsCount, err := ctx.Repo.GetCommitsCount()
if err != nil {
ctx.ServerError("GetCommitsCount", err)
return
}
commitsCount := ctx.Repo.CommitsCount
page := ctx.FormInt("page")
if page <= 1 {
@ -129,12 +125,6 @@ func Graph(ctx *context.Context) {
ctx.Data["SelectedBranches"] = realBranches
files := ctx.FormStrings("file")
commitsCount, err := ctx.Repo.GetCommitsCount()
if err != nil {
ctx.ServerError("GetCommitsCount", err)
return
}
graphCommitsCount, err := ctx.Repo.GetCommitGraphsCount(ctx, hidePRRefs, realBranches, files)
if err != nil {
log.Warn("GetCommitGraphsCount error for generate graph exclude prs: %t branches: %s in %-v, Will Ignore branches and try again. Underlying Error: %v", hidePRRefs, branches, ctx.Repo.Repository, err)
@ -171,7 +161,6 @@ func Graph(ctx *context.Context) {
ctx.Data["Username"] = ctx.Repo.Owner.Name
ctx.Data["Reponame"] = ctx.Repo.Repository.Name
ctx.Data["CommitCount"] = commitsCount
paginator := context.NewPagination(int(graphCommitsCount), setting.UI.GraphMaxCommitNum, page, 5)
paginator.AddParamFromRequest(ctx.Req)
@ -191,7 +180,7 @@ func SearchCommits(ctx *context.Context) {
query := ctx.FormTrim("q")
if len(query) == 0 {
ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.BranchNameSubURL())
ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.RefTypeNameSubURL())
return
}
@ -222,7 +211,7 @@ func FileHistory(ctx *context.Context) {
return
}
commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(ctx.Repo.RefName, fileName)
commitsCount, err := ctx.Repo.GitRepo.FileCommitsCount(ctx.Repo.RefFullName.ShortName(), fileName) // FIXME: legacy code used ShortName
if err != nil {
ctx.ServerError("FileCommitsCount", err)
return
@ -238,7 +227,7 @@ func FileHistory(ctx *context.Context) {
commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange(
git.CommitsByFileAndRangeOptions{
Revision: ctx.Repo.RefName,
Revision: ctx.Repo.RefFullName.ShortName(), // FIXME: legacy code used ShortName
File: fileName,
Page: page,
})
@ -390,12 +379,6 @@ func Diff(ctx *context.Context) {
}
}
ctx.Data["BranchName"], err = commit.GetBranchName()
if err != nil {
ctx.ServerError("commit.GetBranchName", err)
return
}
ctx.HTML(http.StatusOK, tplCommitPage)
}

@ -26,7 +26,7 @@ func Contributors(ctx *context.Context) {
// ContributorsData renders JSON of contributors along with their weekly commit statistics
func ContributorsData(ctx *context.Context) {
if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.CommitID); err != nil {
if contributorStats, err := contributors_service.GetContributorStats(ctx, ctx.Cache, ctx.Repo.Repository, ctx.Repo.Repository.DefaultBranch); err != nil {
if errors.Is(err, contributors_service.ErrAwaitGeneration) {
ctx.Status(http.StatusAccepted)
return

@ -180,7 +180,7 @@ func editFile(ctx *context.Context, isNewFile bool) {
ctx.Data["TreeNames"] = treeNames
ctx.Data["TreePaths"] = treePaths
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
ctx.Data["commit_summary"] = ""
ctx.Data["commit_message"] = ""
if canCommit {
@ -428,7 +428,7 @@ func DiffPreviewPost(ctx *context.Context) {
// DeleteFile render delete file page
func DeleteFile(ctx *context.Context) {
ctx.Data["PageIsDelete"] = true
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
treePath := cleanUploadFileName(ctx.Repo.TreePath)
if treePath != ctx.Repo.TreePath {
@ -462,7 +462,7 @@ func DeleteFilePost(ctx *context.Context) {
}
ctx.Data["PageIsDelete"] = true
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
ctx.Data["TreePath"] = ctx.Repo.TreePath
ctx.Data["commit_summary"] = form.CommitSummary
ctx.Data["commit_message"] = form.CommitMessage
@ -604,7 +604,7 @@ func UploadFile(ctx *context.Context) {
ctx.Data["TreeNames"] = treeNames
ctx.Data["TreePaths"] = treePaths
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.BranchNameSubURL()
ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
ctx.Data["commit_summary"] = ""
ctx.Data["commit_message"] = ""
if canCommit {

@ -4,25 +4,14 @@
package repo
import (
"net/url"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/services/context"
)
func HandleGitError(ctx *context.Context, msg string, err error) {
if git.IsErrNotExist(err) {
refType := ""
switch {
case ctx.Repo.IsViewBranch:
refType = "branch"
case ctx.Repo.IsViewTag:
refType = "tag"
case ctx.Repo.IsViewCommit:
refType = "commit"
}
ctx.Data["NotFoundPrompt"] = ctx.Locale.Tr("repo.tree_path_not_found_"+refType, ctx.Repo.TreePath, url.PathEscape(ctx.Repo.RefName))
ctx.Data["NotFoundGoBackURL"] = ctx.Repo.RepoLink + "/src/" + refType + "/" + url.PathEscape(ctx.Repo.RefName)
ctx.Data["NotFoundPrompt"] = ctx.Locale.Tr("repo.tree_path_not_found", ctx.Repo.TreePath, ctx.Repo.RefTypeNameSubURL())
ctx.Data["NotFoundGoBackURL"] = ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL()
ctx.NotFound(msg, err)
} else {
ctx.ServerError(msg, err)

@ -109,7 +109,7 @@ func RemoveDependency(ctx *context.Context) {
}
// Dependency Type
depTypeStr := ctx.Req.PostForm.Get("dependencyType")
depTypeStr := ctx.Req.PostFormValue("dependencyType")
var depType issues_model.DependencyType

@ -38,7 +38,7 @@ func TestInitializeLabels(t *testing.T) {
contexttest.LoadRepo(t, ctx, 2)
web.SetForm(ctx, &forms.InitializeLabelsForm{TemplateName: "Default"})
InitializeLabels(ctx)
assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status())
assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus())
unittest.AssertExistsAndLoadBean(t, &issues_model.Label{
RepoID: 2,
Name: "enhancement",
@ -84,7 +84,7 @@ func TestNewLabel(t *testing.T) {
Color: "#abcdef",
})
NewLabel(ctx)
assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status())
assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus())
unittest.AssertExistsAndLoadBean(t, &issues_model.Label{
Name: "newlabel",
Color: "#abcdef",
@ -104,7 +104,7 @@ func TestUpdateLabel(t *testing.T) {
IsArchived: true,
})
UpdateLabel(ctx)
assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.Status())
assert.EqualValues(t, http.StatusSeeOther, ctx.Resp.WrittenStatus())
unittest.AssertExistsAndLoadBean(t, &issues_model.Label{
ID: 2,
Name: "newnameforlabel",
@ -120,7 +120,7 @@ func TestDeleteLabel(t *testing.T) {
contexttest.LoadRepo(t, ctx, 1)
ctx.Req.Form.Set("id", "2")
DeleteLabel(ctx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus())
unittest.AssertNotExistsBean(t, &issues_model.Label{ID: 2})
unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{LabelID: 2})
assert.EqualValues(t, ctx.Tr("repo.issues.label_deletion_success"), ctx.Flash.SuccessMsg)
@ -134,7 +134,7 @@ func TestUpdateIssueLabel_Clear(t *testing.T) {
ctx.Req.Form.Set("issue_ids", "1,3")
ctx.Req.Form.Set("action", "clear")
UpdateIssueLabel(ctx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus())
unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: 1})
unittest.AssertNotExistsBean(t, &issues_model.IssueLabel{IssueID: 3})
unittest.CheckConsistencyFor(t, &issues_model.Label{})
@ -160,7 +160,7 @@ func TestUpdateIssueLabel_Toggle(t *testing.T) {
ctx.Req.Form.Set("action", testCase.Action)
ctx.Req.Form.Set("id", strconv.Itoa(int(testCase.LabelID)))
UpdateIssueLabel(ctx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
assert.EqualValues(t, http.StatusOK, ctx.Resp.WrittenStatus())
for _, issueID := range testCase.IssueIDs {
if testCase.ExpectedAdd {
unittest.AssertExistsAndLoadBean(t, &issues_model.IssueLabel{IssueID: issueID, LabelID: testCase.LabelID})

@ -26,13 +26,9 @@ type userSearchResponse struct {
Results []*userSearchInfo `json:"results"`
}
// IssuePosters get posters for current repo's issues/pull requests
func IssuePosters(ctx *context.Context) {
issuePosters(ctx, false)
}
func PullPosters(ctx *context.Context) {
issuePosters(ctx, true)
func IssuePullPosters(ctx *context.Context) {
isPullList := ctx.PathParam("type") == "pulls"
issuePosters(ctx, isPullList)
}
func issuePosters(ctx *context.Context, isPullList bool) {

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

Loading…
Cancel
Save