aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmd/serv.go89
-rw-r--r--models/activities/statistic.go18
-rw-r--r--models/renderhelper/commit_checker.go2
-rw-r--r--models/repo/repo.go27
-rw-r--r--models/repo/repo_test.go15
-rw-r--r--models/user/user.go9
-rw-r--r--models/user/user_test.go24
-rw-r--r--modules/git/attribute/checker.go7
-rw-r--r--modules/git/attribute/checker_test.go12
-rw-r--r--modules/git/cmdverb.go36
-rwxr-xr-xmodules/metrics/collector.go11
-rw-r--r--modules/private/serv.go10
-rw-r--r--modules/templates/util_date_test.go2
-rw-r--r--modules/templates/util_render.go21
-rw-r--r--modules/templates/util_render_legacy.go16
-rw-r--r--modules/templates/util_render_test.go128
-rw-r--r--options/locale/locale_cs-CZ.ini7
-rw-r--r--options/locale/locale_de-DE.ini7
-rw-r--r--options/locale/locale_el-GR.ini7
-rw-r--r--options/locale/locale_en-US.ini11
-rw-r--r--options/locale/locale_es-ES.ini7
-rw-r--r--options/locale/locale_fa-IR.ini4
-rw-r--r--options/locale/locale_fi-FI.ini4
-rw-r--r--options/locale/locale_fr-FR.ini10
-rw-r--r--options/locale/locale_ga-IE.ini8
-rw-r--r--options/locale/locale_hu-HU.ini4
-rw-r--r--options/locale/locale_id-ID.ini4
-rw-r--r--options/locale/locale_is-IS.ini4
-rw-r--r--options/locale/locale_it-IT.ini4
-rw-r--r--options/locale/locale_ja-JP.ini7
-rw-r--r--options/locale/locale_ko-KR.ini4
-rw-r--r--options/locale/locale_lv-LV.ini7
-rw-r--r--options/locale/locale_nl-NL.ini4
-rw-r--r--options/locale/locale_pl-PL.ini4
-rw-r--r--options/locale/locale_pt-BR.ini7
-rw-r--r--options/locale/locale_pt-PT.ini13
-rw-r--r--options/locale/locale_ru-RU.ini7
-rw-r--r--options/locale/locale_si-LK.ini4
-rw-r--r--options/locale/locale_sk-SK.ini4
-rw-r--r--options/locale/locale_sv-SE.ini4
-rw-r--r--options/locale/locale_tr-TR.ini7
-rw-r--r--options/locale/locale_uk-UA.ini4
-rw-r--r--options/locale/locale_zh-CN.ini7
-rw-r--r--options/locale/locale_zh-HK.ini4
-rw-r--r--options/locale/locale_zh-TW.ini7
-rw-r--r--package-lock.json8
-rw-r--r--package.json2
-rw-r--r--routers/private/serv.go8
-rw-r--r--routers/web/feed/convert.go4
-rw-r--r--routers/web/repo/actions/view.go6
-rw-r--r--routers/web/shared/secrets/secrets.go4
-rw-r--r--services/repository/branch.go5
-rw-r--r--services/repository/files/upload.go7
-rw-r--r--templates/admin/auth/edit.tmpl2
-rw-r--r--templates/admin/user/edit.tmpl6
-rw-r--r--templates/org/settings/options.tmpl2
-rw-r--r--templates/repo/branch/list.tmpl10
-rw-r--r--templates/repo/commit_page.tmpl4
-rw-r--r--templates/repo/commits_list.tmpl4
-rw-r--r--templates/repo/commits_list_small.tmpl4
-rw-r--r--templates/repo/diff/compare.tmpl2
-rw-r--r--templates/repo/graph/commits.tmpl2
-rw-r--r--templates/repo/issue/view_title.tmpl2
-rw-r--r--templates/repo/latest_commit.tmpl4
-rw-r--r--templates/repo/settings/collaboration.tmpl2
-rw-r--r--templates/repo/settings/options.tmpl2
-rw-r--r--templates/repo/view.tmpl2
-rw-r--r--templates/repo/view_list.tmpl2
-rw-r--r--templates/shared/secrets/add_list.tmpl24
-rw-r--r--templates/user/dashboard/feeds.tmpl2
-rw-r--r--templates/user/settings/profile.tmpl2
-rw-r--r--tests/integration/change_default_branch_test.go97
-rw-r--r--tests/integration/git_general_test.go40
-rw-r--r--tests/integration/repo_commits_test.go36
-rw-r--r--web_src/css/editor/combomarkdowneditor.css64
-rw-r--r--web_src/css/features/expander.css96
-rw-r--r--web_src/css/features/tribute.css32
-rw-r--r--web_src/css/index.css2
-rw-r--r--web_src/css/repo/home.css5
-rw-r--r--web_src/js/features/common-button.test.ts14
-rw-r--r--web_src/js/features/common-button.ts34
-rw-r--r--web_src/js/features/common-issue-list.ts6
-rw-r--r--web_src/js/features/comp/TextExpander.ts1
-rw-r--r--web_src/js/features/repo-issue-list.ts6
-rw-r--r--web_src/js/utils/dom.test.ts6
-rw-r--r--web_src/js/utils/dom.ts49
86 files changed, 777 insertions, 435 deletions
diff --git a/cmd/serv.go b/cmd/serv.go
index b18508459f..26a3af50f3 100644
--- a/cmd/serv.go
+++ b/cmd/serv.go
@@ -11,7 +11,6 @@ import (
"os"
"os/exec"
"path/filepath"
- "regexp"
"strconv"
"strings"
"time"
@@ -20,7 +19,7 @@ import (
asymkey_model "code.gitea.io/gitea/models/asymkey"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/perm"
- "code.gitea.io/gitea/modules/container"
+ "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/lfstransfer"
@@ -37,14 +36,6 @@ import (
"github.com/urfave/cli/v2"
)
-const (
- verbUploadPack = "git-upload-pack"
- verbUploadArchive = "git-upload-archive"
- verbReceivePack = "git-receive-pack"
- verbLfsAuthenticate = "git-lfs-authenticate"
- verbLfsTransfer = "git-lfs-transfer"
-)
-
// CmdServ represents the available serv sub-command.
var CmdServ = &cli.Command{
Name: "serv",
@@ -78,22 +69,6 @@ func setup(ctx context.Context, debug bool) {
}
}
-var (
- // keep getAccessMode() in sync
- allowedCommands = container.SetOf(
- verbUploadPack,
- verbUploadArchive,
- verbReceivePack,
- verbLfsAuthenticate,
- verbLfsTransfer,
- )
- allowedCommandsLfs = container.SetOf(
- verbLfsAuthenticate,
- verbLfsTransfer,
- )
- alphaDashDotPattern = regexp.MustCompile(`[^\w-\.]`)
-)
-
// fail prints message to stdout, it's mainly used for git serv and git hook commands.
// The output will be passed to git client and shown to user.
func fail(ctx context.Context, userMessage, logMsgFmt string, args ...any) error {
@@ -139,19 +114,20 @@ func handleCliResponseExtra(extra private.ResponseExtra) error {
func getAccessMode(verb, lfsVerb string) perm.AccessMode {
switch verb {
- case verbUploadPack, verbUploadArchive:
+ case git.CmdVerbUploadPack, git.CmdVerbUploadArchive:
return perm.AccessModeRead
- case verbReceivePack:
+ case git.CmdVerbReceivePack:
return perm.AccessModeWrite
- case verbLfsAuthenticate, verbLfsTransfer:
+ case git.CmdVerbLfsAuthenticate, git.CmdVerbLfsTransfer:
switch lfsVerb {
- case "upload":
+ case git.CmdSubVerbLfsUpload:
return perm.AccessModeWrite
- case "download":
+ case git.CmdSubVerbLfsDownload:
return perm.AccessModeRead
}
}
// should be unreachable
+ setting.PanicInDevOrTesting("unknown verb: %s %s", verb, lfsVerb)
return perm.AccessModeNone
}
@@ -230,12 +206,12 @@ func runServ(c *cli.Context) error {
log.Debug("SSH_ORIGINAL_COMMAND: %s", os.Getenv("SSH_ORIGINAL_COMMAND"))
}
- words, err := shellquote.Split(cmd)
+ sshCmdArgs, err := shellquote.Split(cmd)
if err != nil {
return fail(ctx, "Error parsing arguments", "Failed to parse arguments: %v", err)
}
- if len(words) < 2 {
+ if len(sshCmdArgs) < 2 {
if git.DefaultFeatures().SupportProcReceive {
// for AGit Flow
if cmd == "ssh_info" {
@@ -246,25 +222,21 @@ func runServ(c *cli.Context) error {
return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd)
}
- verb := words[0]
- repoPath := strings.TrimPrefix(words[1], "/")
-
- var lfsVerb string
-
- rr := strings.SplitN(repoPath, "/", 2)
- if len(rr) != 2 {
+ repoPath := strings.TrimPrefix(sshCmdArgs[1], "/")
+ repoPathFields := strings.SplitN(repoPath, "/", 2)
+ if len(repoPathFields) != 2 {
return fail(ctx, "Invalid repository path", "Invalid repository path: %v", repoPath)
}
- username := rr[0]
- reponame := strings.TrimSuffix(rr[1], ".git")
+ username := repoPathFields[0]
+ reponame := strings.TrimSuffix(repoPathFields[1], ".git") // β€œthe-repo-name" or "the-repo-name.wiki"
// LowerCase and trim the repoPath as that's how they are stored.
// This should be done after splitting the repoPath into username and reponame
// so that username and reponame are not affected.
repoPath = strings.ToLower(strings.TrimSpace(repoPath))
- if alphaDashDotPattern.MatchString(reponame) {
+ if !repo.IsValidSSHAccessRepoName(reponame) {
return fail(ctx, "Invalid repo name", "Invalid repo name: %s", reponame)
}
@@ -286,22 +258,23 @@ func runServ(c *cli.Context) error {
}()
}
- if allowedCommands.Contains(verb) {
- if allowedCommandsLfs.Contains(verb) {
- if !setting.LFS.StartServer {
- return fail(ctx, "LFS Server is not enabled", "")
- }
- if verb == verbLfsTransfer && !setting.LFS.AllowPureSSH {
- return fail(ctx, "LFS SSH transfer is not enabled", "")
- }
- if len(words) > 2 {
- lfsVerb = words[2]
- }
- }
- } else {
+ verb, lfsVerb := sshCmdArgs[0], ""
+ if !git.IsAllowedVerbForServe(verb) {
return fail(ctx, "Unknown git command", "Unknown git command %s", verb)
}
+ if git.IsAllowedVerbForServeLfs(verb) {
+ if !setting.LFS.StartServer {
+ return fail(ctx, "LFS Server is not enabled", "")
+ }
+ if verb == git.CmdVerbLfsTransfer && !setting.LFS.AllowPureSSH {
+ return fail(ctx, "LFS SSH transfer is not enabled", "")
+ }
+ if len(sshCmdArgs) > 2 {
+ lfsVerb = sshCmdArgs[2]
+ }
+ }
+
requestedMode := getAccessMode(verb, lfsVerb)
results, extra := private.ServCommand(ctx, keyID, username, reponame, requestedMode, verb, lfsVerb)
@@ -310,7 +283,7 @@ func runServ(c *cli.Context) error {
}
// LFS SSH protocol
- if verb == verbLfsTransfer {
+ if verb == git.CmdVerbLfsTransfer {
token, err := getLFSAuthToken(ctx, lfsVerb, results)
if err != nil {
return err
@@ -319,7 +292,7 @@ func runServ(c *cli.Context) error {
}
// LFS token authentication
- if verb == verbLfsAuthenticate {
+ if verb == git.CmdVerbLfsAuthenticate {
url := fmt.Sprintf("%s%s/%s.git/info/lfs", setting.AppURL, url.PathEscape(results.OwnerName), url.PathEscape(results.RepoName))
token, err := getLFSAuthToken(ctx, lfsVerb, results)
diff --git a/models/activities/statistic.go b/models/activities/statistic.go
index ff81ad78a1..983a124550 100644
--- a/models/activities/statistic.go
+++ b/models/activities/statistic.go
@@ -17,13 +17,15 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
)
// Statistic contains the database statistics
type Statistic struct {
Counter struct {
- User, Org, PublicKey,
+ UsersActive, UsersNotActive,
+ Org, PublicKey,
Repo, Watch, Star, Access,
Issue, IssueClosed, IssueOpen,
Comment, Oauth, Follow,
@@ -53,7 +55,19 @@ type IssueByRepositoryCount struct {
// GetStatistic returns the database statistics
func GetStatistic(ctx context.Context) (stats Statistic) {
e := db.GetEngine(ctx)
- stats.Counter.User = user_model.CountUsers(ctx, nil)
+
+ // Number of active users
+ usersActiveOpts := user_model.CountUserFilter{
+ IsActive: optional.Some(true),
+ }
+ stats.Counter.UsersActive = user_model.CountUsers(ctx, &usersActiveOpts)
+
+ // Number of inactive users
+ usersNotActiveOpts := user_model.CountUserFilter{
+ IsActive: optional.Some(false),
+ }
+ stats.Counter.UsersNotActive = user_model.CountUsers(ctx, &usersNotActiveOpts)
+
stats.Counter.Org, _ = db.Count[organization.Organization](ctx, organization.FindOrgOptions{IncludePrivate: true})
stats.Counter.PublicKey, _ = e.Count(new(asymkey_model.PublicKey))
stats.Counter.Repo, _ = repo_model.CountRepositories(ctx, repo_model.CountRepositoryOptions{})
diff --git a/models/renderhelper/commit_checker.go b/models/renderhelper/commit_checker.go
index 4815643e67..407e45fb54 100644
--- a/models/renderhelper/commit_checker.go
+++ b/models/renderhelper/commit_checker.go
@@ -47,7 +47,7 @@ func (c *commitChecker) IsCommitIDExisting(commitID string) bool {
c.gitRepo, c.gitRepoCloser = r, closer
}
- exist = c.gitRepo.IsReferenceExist(commitID) // Don't use IsObjectExist since it doesn't support short hashs with gogit edition.
+ exist = c.gitRepo.IsReferenceExist(commitID) // Don't use IsObjectExist since it doesn't support short hashes with gogit edition.
c.commitCache[commitID] = exist
return exist
}
diff --git a/models/repo/repo.go b/models/repo/repo.go
index 2977dfb9f1..5aae02c6d8 100644
--- a/models/repo/repo.go
+++ b/models/repo/repo.go
@@ -64,18 +64,18 @@ func (err ErrRepoIsArchived) Error() string {
}
type globalVarsStruct struct {
- validRepoNamePattern *regexp.Regexp
- invalidRepoNamePattern *regexp.Regexp
- reservedRepoNames []string
- reservedRepoPatterns []string
+ validRepoNamePattern *regexp.Regexp
+ invalidRepoNamePattern *regexp.Regexp
+ reservedRepoNames []string
+ reservedRepoNamePatterns []string
}
var globalVars = sync.OnceValue(func() *globalVarsStruct {
return &globalVarsStruct{
- validRepoNamePattern: regexp.MustCompile(`[-.\w]+`),
- invalidRepoNamePattern: regexp.MustCompile(`[.]{2,}`),
- reservedRepoNames: []string{".", "..", "-"},
- reservedRepoPatterns: []string{"*.git", "*.wiki", "*.rss", "*.atom"},
+ validRepoNamePattern: regexp.MustCompile(`^[-.\w]+$`),
+ invalidRepoNamePattern: regexp.MustCompile(`[.]{2,}`),
+ reservedRepoNames: []string{".", "..", "-"},
+ reservedRepoNamePatterns: []string{"*.wiki", "*.git", "*.rss", "*.atom"},
}
})
@@ -86,7 +86,16 @@ func IsUsableRepoName(name string) error {
// Note: usually this error is normally caught up earlier in the UI
return db.ErrNameCharsNotAllowed{Name: name}
}
- return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoPatterns, name)
+ return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoNamePatterns, name)
+}
+
+// IsValidSSHAccessRepoName is like IsUsableRepoName, but it allows "*.wiki" because wiki repo needs to be accessed in SSH code
+func IsValidSSHAccessRepoName(name string) bool {
+ vars := globalVars()
+ if !vars.validRepoNamePattern.MatchString(name) || vars.invalidRepoNamePattern.MatchString(name) {
+ return false
+ }
+ return db.IsUsableName(vars.reservedRepoNames, vars.reservedRepoNamePatterns[1:], name) == nil
}
// TrustModelType defines the types of trust model for this repository
diff --git a/models/repo/repo_test.go b/models/repo/repo_test.go
index b2604ab575..66abe864fc 100644
--- a/models/repo/repo_test.go
+++ b/models/repo/repo_test.go
@@ -216,8 +216,23 @@ func TestIsUsableRepoName(t *testing.T) {
assert.Error(t, IsUsableRepoName("-"))
assert.Error(t, IsUsableRepoName("🌞"))
+ assert.Error(t, IsUsableRepoName("the/repo"))
assert.Error(t, IsUsableRepoName("the..repo"))
assert.Error(t, IsUsableRepoName("foo.wiki"))
assert.Error(t, IsUsableRepoName("foo.git"))
assert.Error(t, IsUsableRepoName("foo.RSS"))
}
+
+func TestIsValidSSHAccessRepoName(t *testing.T) {
+ assert.True(t, IsValidSSHAccessRepoName("a"))
+ assert.True(t, IsValidSSHAccessRepoName("-1_."))
+ assert.True(t, IsValidSSHAccessRepoName(".profile"))
+ assert.True(t, IsValidSSHAccessRepoName("foo.wiki"))
+
+ assert.False(t, IsValidSSHAccessRepoName("-"))
+ assert.False(t, IsValidSSHAccessRepoName("🌞"))
+ assert.False(t, IsValidSSHAccessRepoName("the/repo"))
+ assert.False(t, IsValidSSHAccessRepoName("the..repo"))
+ assert.False(t, IsValidSSHAccessRepoName("foo.git"))
+ assert.False(t, IsValidSSHAccessRepoName("foo.RSS"))
+}
diff --git a/models/user/user.go b/models/user/user.go
index 100f924cc6..d7331d79f0 100644
--- a/models/user/user.go
+++ b/models/user/user.go
@@ -828,6 +828,7 @@ func IsLastAdminUser(ctx context.Context, user *User) bool {
type CountUserFilter struct {
LastLoginSince *int64
IsAdmin optional.Option[bool]
+ IsActive optional.Option[bool]
}
// CountUsers returns number of users.
@@ -848,6 +849,10 @@ func countUsers(ctx context.Context, opts *CountUserFilter) int64 {
if opts.IsAdmin.Has() {
cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.Value()})
}
+
+ if opts.IsActive.Has() {
+ cond = cond.And(builder.Eq{"is_active": opts.IsActive.Value()})
+ }
}
count, err := sess.Where(cond).Count(new(User))
@@ -1198,7 +1203,8 @@ func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, e
for _, email := range emailAddresses {
user := users[email.UID]
if user != nil {
- results[user.GetEmail()] = user
+ results[user.Email] = user
+ results[user.GetPlaceholderEmail()] = user
}
}
}
@@ -1208,6 +1214,7 @@ func GetUsersByEmails(ctx context.Context, emails []string) (map[string]*User, e
return nil, err
}
for _, user := range users {
+ results[user.Email] = user
results[user.GetPlaceholderEmail()] = user
}
return results, nil
diff --git a/models/user/user_test.go b/models/user/user_test.go
index 90e8bf13a8..dd232abe2e 100644
--- a/models/user/user_test.go
+++ b/models/user/user_test.go
@@ -23,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestIsUsableUsername(t *testing.T) {
@@ -48,14 +49,23 @@ func TestOAuth2Application_LoadUser(t *testing.T) {
assert.NotNil(t, user)
}
-func TestGetUserEmailsByNames(t *testing.T) {
+func TestUserEmails(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
-
- // ignore none active user email
- assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user9"}))
- assert.ElementsMatch(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user5"}))
-
- assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "org7"}))
+ t.Run("GetUserEmailsByNames", func(t *testing.T) {
+ // ignore none active user email
+ assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user9"}))
+ assert.ElementsMatch(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user5"}))
+ assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "org7"}))
+ })
+ t.Run("GetUsersByEmails", func(t *testing.T) {
+ m, err := user_model.GetUsersByEmails(db.DefaultContext, []string{"user1@example.com", "user2@" + setting.Service.NoReplyAddress})
+ require.NoError(t, err)
+ require.Len(t, m, 4)
+ assert.EqualValues(t, 1, m["user1@example.com"].ID)
+ assert.EqualValues(t, 1, m["user1@"+setting.Service.NoReplyAddress].ID)
+ assert.EqualValues(t, 2, m["user2@example.com"].ID)
+ assert.EqualValues(t, 2, m["user2@"+setting.Service.NoReplyAddress].ID)
+ })
}
func TestCanCreateOrganization(t *testing.T) {
diff --git a/modules/git/attribute/checker.go b/modules/git/attribute/checker.go
index c17006a154..167b31416e 100644
--- a/modules/git/attribute/checker.go
+++ b/modules/git/attribute/checker.go
@@ -39,7 +39,12 @@ func checkAttrCommand(gitRepo *git.Repository, treeish string, filenames, attrib
)
cancel = deleteTemporaryFile
}
- } // else: no treeish, assume it is a not a bare repo, read from working directory
+ } else {
+ // Read from existing index, in cases where the repo is bare and has an index,
+ // or the work tree contains unstaged changes that shouldn't affect the attribute check.
+ // It is caller's responsibility to add changed ".gitattributes" into the index if they want to respect the new changes.
+ cmd.AddArguments("--cached")
+ }
cmd.AddDynamicArguments(attributes...)
if len(filenames) > 0 {
diff --git a/modules/git/attribute/checker_test.go b/modules/git/attribute/checker_test.go
index 97db43460b..67fbda8918 100644
--- a/modules/git/attribute/checker_test.go
+++ b/modules/git/attribute/checker_test.go
@@ -57,8 +57,18 @@ func Test_Checker(t *testing.T) {
assert.Equal(t, expectedAttrs(), attrs["i-am-a-python.p"])
})
+ t.Run("Run git check-attr in bare repository using index", func(t *testing.T) {
+ attrs, err := CheckAttributes(t.Context(), gitRepo, "", CheckAttributeOpts{
+ Filenames: []string{"i-am-a-python.p"},
+ Attributes: LinguistAttributes,
+ })
+ assert.NoError(t, err)
+ assert.Len(t, attrs, 1)
+ assert.Equal(t, expectedAttrs(), attrs["i-am-a-python.p"])
+ })
+
if !git.DefaultFeatures().SupportCheckAttrOnBare {
- t.Skip("git version 2.40 is required to support run check-attr on bare repo")
+ t.Skip("git version 2.40 is required to support run check-attr on bare repo without using index")
return
}
diff --git a/modules/git/cmdverb.go b/modules/git/cmdverb.go
new file mode 100644
index 0000000000..3d6f4ae0c6
--- /dev/null
+++ b/modules/git/cmdverb.go
@@ -0,0 +1,36 @@
+// Copyright 2025 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package git
+
+const (
+ CmdVerbUploadPack = "git-upload-pack"
+ CmdVerbUploadArchive = "git-upload-archive"
+ CmdVerbReceivePack = "git-receive-pack"
+ CmdVerbLfsAuthenticate = "git-lfs-authenticate"
+ CmdVerbLfsTransfer = "git-lfs-transfer"
+
+ CmdSubVerbLfsUpload = "upload"
+ CmdSubVerbLfsDownload = "download"
+)
+
+func IsAllowedVerbForServe(verb string) bool {
+ switch verb {
+ case CmdVerbUploadPack,
+ CmdVerbUploadArchive,
+ CmdVerbReceivePack,
+ CmdVerbLfsAuthenticate,
+ CmdVerbLfsTransfer:
+ return true
+ }
+ return false
+}
+
+func IsAllowedVerbForServeLfs(verb string) bool {
+ switch verb {
+ case CmdVerbLfsAuthenticate,
+ CmdVerbLfsTransfer:
+ return true
+ }
+ return false
+}
diff --git a/modules/metrics/collector.go b/modules/metrics/collector.go
index 230260ff94..4d2ec287a9 100755
--- a/modules/metrics/collector.go
+++ b/modules/metrics/collector.go
@@ -184,7 +184,7 @@ func NewCollector() Collector {
Users: prometheus.NewDesc(
namespace+"users",
"Number of Users",
- nil, nil,
+ []string{"state"}, nil,
),
Watches: prometheus.NewDesc(
namespace+"watches",
@@ -373,7 +373,14 @@ func (c Collector) Collect(ch chan<- prometheus.Metric) {
ch <- prometheus.MustNewConstMetric(
c.Users,
prometheus.GaugeValue,
- float64(stats.Counter.User),
+ float64(stats.Counter.UsersActive),
+ "active", // state label
+ )
+ ch <- prometheus.MustNewConstMetric(
+ c.Users,
+ prometheus.GaugeValue,
+ float64(stats.Counter.UsersNotActive),
+ "inactive", // state label
)
ch <- prometheus.MustNewConstMetric(
c.Watches,
diff --git a/modules/private/serv.go b/modules/private/serv.go
index 10e9f7995c..b1dafbd81b 100644
--- a/modules/private/serv.go
+++ b/modules/private/serv.go
@@ -46,18 +46,16 @@ type ServCommandResults struct {
}
// ServCommand preps for a serv call
-func ServCommand(ctx context.Context, keyID int64, ownerName, repoName string, mode perm.AccessMode, verbs ...string) (*ServCommandResults, ResponseExtra) {
+func ServCommand(ctx context.Context, keyID int64, ownerName, repoName string, mode perm.AccessMode, verb, lfsVerb string) (*ServCommandResults, ResponseExtra) {
reqURL := setting.LocalURL + fmt.Sprintf("api/internal/serv/command/%d/%s/%s?mode=%d",
keyID,
url.PathEscape(ownerName),
url.PathEscape(repoName),
mode,
)
- for _, verb := range verbs {
- if verb != "" {
- reqURL += "&verb=" + url.QueryEscape(verb)
- }
- }
+ reqURL += "&verb=" + url.QueryEscape(verb)
+ // reqURL += "&lfs_verb=" + url.QueryEscape(lfsVerb) // TODO: actually there is no use of this parameter. In the future, the URL construction should be more flexible
+ _ = lfsVerb
req := newInternalRequestAPI(ctx, reqURL, "GET")
return requestJSONResp(req, &ServCommandResults{})
}
diff --git a/modules/templates/util_date_test.go b/modules/templates/util_date_test.go
index f3a2409a9f..9015462bbb 100644
--- a/modules/templates/util_date_test.go
+++ b/modules/templates/util_date_test.go
@@ -17,6 +17,7 @@ import (
func TestDateTime(t *testing.T) {
testTz, _ := time.LoadLocation("America/New_York")
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
+ defer test.MockVariableValue(&setting.IsProd, true)()
defer test.MockVariableValue(&setting.IsInTesting, false)()
du := NewDateUtils()
@@ -53,6 +54,7 @@ func TestDateTime(t *testing.T) {
func TestTimeSince(t *testing.T) {
testTz, _ := time.LoadLocation("America/New_York")
defer test.MockVariableValue(&setting.DefaultUILocation, testTz)()
+ defer test.MockVariableValue(&setting.IsProd, true)()
defer test.MockVariableValue(&setting.IsInTesting, false)()
du := NewDateUtils()
diff --git a/modules/templates/util_render.go b/modules/templates/util_render.go
index 521233db40..8d9ba1000c 100644
--- a/modules/templates/util_render.go
+++ b/modules/templates/util_render.go
@@ -14,6 +14,8 @@ import (
"unicode"
issues_model "code.gitea.io/gitea/models/issues"
+ "code.gitea.io/gitea/models/renderhelper"
+ "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/emoji"
"code.gitea.io/gitea/modules/htmlutil"
"code.gitea.io/gitea/modules/log"
@@ -34,11 +36,11 @@ func NewRenderUtils(ctx reqctx.RequestContext) *RenderUtils {
}
// RenderCommitMessage renders commit message with XSS-safe and special links.
-func (ut *RenderUtils) RenderCommitMessage(msg string, metas map[string]string) template.HTML {
+func (ut *RenderUtils) RenderCommitMessage(msg string, repo *repo.Repository) template.HTML {
cleanMsg := template.HTMLEscapeString(msg)
// we can safely assume that it will not return any error, since there
// shouldn't be any special HTML.
- fullMessage, err := markup.PostProcessCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), cleanMsg)
+ fullMessage, err := markup.PostProcessCommitMessage(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), cleanMsg)
if err != nil {
log.Error("PostProcessCommitMessage: %v", err)
return ""
@@ -52,7 +54,7 @@ func (ut *RenderUtils) RenderCommitMessage(msg string, metas map[string]string)
// RenderCommitMessageLinkSubject renders commit message as a XSS-safe link to
// the provided default url, handling for special links without email to links.
-func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, metas map[string]string) template.HTML {
+func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, repo *repo.Repository) template.HTML {
msgLine := strings.TrimLeftFunc(msg, unicode.IsSpace)
lineEnd := strings.IndexByte(msgLine, '\n')
if lineEnd > 0 {
@@ -63,9 +65,8 @@ func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, me
return ""
}
- // we can safely assume that it will not return any error, since there
- // shouldn't be any special HTML.
- renderedMessage, err := markup.PostProcessCommitMessageSubject(markup.NewRenderContext(ut.ctx).WithMetas(metas), urlDefault, template.HTMLEscapeString(msgLine))
+ // we can safely assume that it will not return any error, since there shouldn't be any special HTML.
+ renderedMessage, err := markup.PostProcessCommitMessageSubject(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), urlDefault, template.HTMLEscapeString(msgLine))
if err != nil {
log.Error("PostProcessCommitMessageSubject: %v", err)
return ""
@@ -74,7 +75,7 @@ func (ut *RenderUtils) RenderCommitMessageLinkSubject(msg, urlDefault string, me
}
// RenderCommitBody extracts the body of a commit message without its title.
-func (ut *RenderUtils) RenderCommitBody(msg string, metas map[string]string) template.HTML {
+func (ut *RenderUtils) RenderCommitBody(msg string, repo *repo.Repository) template.HTML {
msgLine := strings.TrimSpace(msg)
lineEnd := strings.IndexByte(msgLine, '\n')
if lineEnd > 0 {
@@ -87,7 +88,7 @@ func (ut *RenderUtils) RenderCommitBody(msg string, metas map[string]string) tem
return ""
}
- renderedMessage, err := markup.PostProcessCommitMessage(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(msgLine))
+ renderedMessage, err := markup.PostProcessCommitMessage(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), template.HTMLEscapeString(msgLine))
if err != nil {
log.Error("PostProcessCommitMessage: %v", err)
return ""
@@ -105,8 +106,8 @@ func renderCodeBlock(htmlEscapedTextToRender template.HTML) template.HTML {
}
// RenderIssueTitle renders issue/pull title with defined post processors
-func (ut *RenderUtils) RenderIssueTitle(text string, metas map[string]string) template.HTML {
- renderedText, err := markup.PostProcessIssueTitle(markup.NewRenderContext(ut.ctx).WithMetas(metas), template.HTMLEscapeString(text))
+func (ut *RenderUtils) RenderIssueTitle(text string, repo *repo.Repository) template.HTML {
+ renderedText, err := markup.PostProcessIssueTitle(renderhelper.NewRenderContextRepoComment(ut.ctx, repo), template.HTMLEscapeString(text))
if err != nil {
log.Error("PostProcessIssueTitle: %v", err)
return ""
diff --git a/modules/templates/util_render_legacy.go b/modules/templates/util_render_legacy.go
index 8f7b84c83d..df8f5e64de 100644
--- a/modules/templates/util_render_legacy.go
+++ b/modules/templates/util_render_legacy.go
@@ -32,22 +32,22 @@ func renderMarkdownToHtmlLegacy(ctx context.Context, input string) template.HTML
return NewRenderUtils(reqctx.FromContext(ctx)).MarkdownToHtml(input)
}
-func renderCommitMessageLegacy(ctx context.Context, msg string, metas map[string]string) template.HTML {
+func renderCommitMessageLegacy(ctx context.Context, msg string, _ map[string]string) template.HTML {
panicIfDevOrTesting()
- return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitMessage(msg, metas)
+ return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitMessage(msg, nil)
}
-func renderCommitMessageLinkSubjectLegacy(ctx context.Context, msg, urlDefault string, metas map[string]string) template.HTML {
+func renderCommitMessageLinkSubjectLegacy(ctx context.Context, msg, urlDefault string, _ map[string]string) template.HTML {
panicIfDevOrTesting()
- return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitMessageLinkSubject(msg, urlDefault, metas)
+ return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitMessageLinkSubject(msg, urlDefault, nil)
}
-func renderIssueTitleLegacy(ctx context.Context, text string, metas map[string]string) template.HTML {
+func renderIssueTitleLegacy(ctx context.Context, text string, _ map[string]string) template.HTML {
panicIfDevOrTesting()
- return NewRenderUtils(reqctx.FromContext(ctx)).RenderIssueTitle(text, metas)
+ return NewRenderUtils(reqctx.FromContext(ctx)).RenderIssueTitle(text, nil)
}
-func renderCommitBodyLegacy(ctx context.Context, msg string, metas map[string]string) template.HTML {
+func renderCommitBodyLegacy(ctx context.Context, msg string, _ map[string]string) template.HTML {
panicIfDevOrTesting()
- return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitBody(msg, metas)
+ return NewRenderUtils(reqctx.FromContext(ctx)).RenderCommitBody(msg, nil)
}
diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go
index 460b9dc190..9b51d0cd57 100644
--- a/modules/templates/util_render_test.go
+++ b/modules/templates/util_render_test.go
@@ -11,11 +11,11 @@ import (
"testing"
"code.gitea.io/gitea/models/issues"
- "code.gitea.io/gitea/models/unittest"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/models/repo"
+ user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/reqctx"
+ "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation"
@@ -47,19 +47,8 @@ mail@domain.com
return strings.ReplaceAll(s, "<SPACE>", " ")
}
-var testMetas = map[string]string{
- "user": "user13",
- "repo": "repo11",
- "repoPath": "../../tests/gitea-repositories-meta/user13/repo11.git/",
- "markdownNewLineHardBreak": "true",
- "markupAllowShortIssuePattern": "true",
-}
-
func TestMain(m *testing.M) {
- unittest.InitSettingsForTesting()
- if err := git.InitSimple(context.Background()); err != nil {
- log.Fatal("git init failed, err: %v", err)
- }
+ setting.Markdown.RenderOptionsComment.ShortIssuePattern = true
markup.Init(&markup.RenderHelperFuncs{
IsUsernameMentionable: func(ctx context.Context, username string) bool {
return username == "mention-user"
@@ -74,46 +63,52 @@ func newTestRenderUtils(t *testing.T) *RenderUtils {
return NewRenderUtils(ctx)
}
-func TestRenderCommitBody(t *testing.T) {
- defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
- type args struct {
- msg string
+func TestRenderRepoComment(t *testing.T) {
+ mockRepo := &repo.Repository{
+ ID: 1, OwnerName: "user13", Name: "repo11",
+ Owner: &user_model.User{ID: 13, Name: "user13"},
+ Units: []*repo.RepoUnit{},
}
- tests := []struct {
- name string
- args args
- want template.HTML
- }{
- {
- name: "multiple lines",
- args: args{
- msg: "first line\nsecond line",
+ t.Run("RenderCommitBody", func(t *testing.T) {
+ defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
+ type args struct {
+ msg string
+ }
+ tests := []struct {
+ name string
+ args args
+ want template.HTML
+ }{
+ {
+ name: "multiple lines",
+ args: args{
+ msg: "first line\nsecond line",
+ },
+ want: "second line",
},
- want: "second line",
- },
- {
- name: "multiple lines with leading newlines",
- args: args{
- msg: "\n\n\n\nfirst line\nsecond line",
+ {
+ name: "multiple lines with leading newlines",
+ args: args{
+ msg: "\n\n\n\nfirst line\nsecond line",
+ },
+ want: "second line",
},
- want: "second line",
- },
- {
- name: "multiple lines with trailing newlines",
- args: args{
- msg: "first line\nsecond line\n\n\n",
+ {
+ name: "multiple lines with trailing newlines",
+ args: args{
+ msg: "first line\nsecond line\n\n\n",
+ },
+ want: "second line",
},
- want: "second line",
- },
- }
- ut := newTestRenderUtils(t)
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, nil), "RenderCommitBody(%v, %v)", tt.args.msg, nil)
- })
- }
-
- expected := `/just/a/path.bin
+ }
+ ut := newTestRenderUtils(t)
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, mockRepo), "RenderCommitBody(%v, %v)", tt.args.msg, nil)
+ })
+ }
+
+ expected := `/just/a/path.bin
<a href="https://example.com/file.bin">https://example.com/file.bin</a>
[local link](file.bin)
[remote link](<a href="https://example.com">https://example.com</a>)
@@ -132,22 +127,22 @@ com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
<a href="/mention-user">@mention-user</a> test
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
space`
- assert.Equal(t, expected, string(newTestRenderUtils(t).RenderCommitBody(testInput(), testMetas)))
-}
+ assert.Equal(t, expected, string(newTestRenderUtils(t).RenderCommitBody(testInput(), mockRepo)))
+ })
-func TestRenderCommitMessage(t *testing.T) {
- expected := `space <a href="/mention-user" data-markdown-generated-content="">@mention-user</a> `
- assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessage(testInput(), testMetas))
-}
+ t.Run("RenderCommitMessage", func(t *testing.T) {
+ expected := `space <a href="/mention-user" data-markdown-generated-content="">@mention-user</a> `
+ assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessage(testInput(), mockRepo))
+ })
-func TestRenderCommitMessageLinkSubject(t *testing.T) {
- expected := `<a href="https://example.com/link" class="muted">space </a><a href="/mention-user" data-markdown-generated-content="">@mention-user</a>`
- assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", testMetas))
-}
+ t.Run("RenderCommitMessageLinkSubject", func(t *testing.T) {
+ expected := `<a href="https://example.com/link" class="muted">space </a><a href="/mention-user" data-markdown-generated-content="">@mention-user</a>`
+ assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", mockRepo))
+ })
-func TestRenderIssueTitle(t *testing.T) {
- defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
- expected := ` space @mention-user<SPACE><SPACE>
+ t.Run("RenderIssueTitle", func(t *testing.T) {
+ defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
+ expected := ` space @mention-user<SPACE><SPACE>
/just/a/path.bin
https://example.com/file.bin
[local link](file.bin)
@@ -168,8 +163,9 @@ mail@domain.com
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
space<SPACE><SPACE>
`
- expected = strings.ReplaceAll(expected, "<SPACE>", " ")
- assert.Equal(t, expected, string(newTestRenderUtils(t).RenderIssueTitle(testInput(), testMetas)))
+ expected = strings.ReplaceAll(expected, "<SPACE>", " ")
+ assert.Equal(t, expected, string(newTestRenderUtils(t).RenderIssueTitle(testInput(), mockRepo)))
+ })
}
func TestRenderMarkdownToHtml(t *testing.T) {
diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini
index 63ab9f9d3a..2a3bd3e743 100644
--- a/options/locale/locale_cs-CZ.ini
+++ b/options/locale/locale_cs-CZ.ini
@@ -3667,12 +3667,13 @@ owner.settings.chef.keypair.description=Pro autentizaci do registru Chef je zapo
secrets=Tajné klíče
description=TejnΓ© klíče budou pΕ™edΓ‘ny určitΓ½m akcΓ­m a nelze je pΕ™ečíst jinak.
none=Zatím zde nejsou žÑdné tajné klíče.
-creation=PΕ™idat tajnΓ½ klíč
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Popis
creation.name_placeholder=nerozliőovat velkÑ a malÑ písmena, pouze alfanumerické znaky nebo podtržítka, nemohou začínat na GITEA_ nebo GITHUB_
creation.value_placeholder=Vložte jakýkoliv obsah. Mezery na začÑtku a konci budou vynechÑny.
-creation.success=TajnΓ½ klíč β€ž%sβ€œ byl pΕ™idΓ‘n.
-creation.failed=NepodaΕ™ilo se pΕ™idat tajnΓ½ klíč.
+
+
deletion=Odstranit tajný klíč
deletion.description=OdstranΔ›nΓ­ tajnΓ©ho klíče je trvalΓ© a nelze ho vrΓ‘tit zpΔ›t. Pokračovat?
deletion.success=TajnΓ½ klíč byl odstranΔ›n.
diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini
index 43333f8ac6..f115dee247 100644
--- a/options/locale/locale_de-DE.ini
+++ b/options/locale/locale_de-DE.ini
@@ -3659,12 +3659,13 @@ owner.settings.chef.keypair.description=Ein SchlΓΌsselpaar ist notwendig, um mit
secrets=Secrets
description=Secrets werden an bestimmte Aktionen weitergegeben und kΓΆnnen nicht anderweitig ausgelesen werden.
none=Noch keine Secrets vorhanden.
-creation=Secret hinzufΓΌgen
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Beschreibung
creation.name_placeholder=Groß-/Kleinschreibung wird ignoriert, nur alphanumerische Zeichen oder Unterstriche, darf nicht mit GITEA_ oder GITHUB_ beginnen
creation.value_placeholder=Beliebigen Inhalt eingeben. Leerzeichen am Anfang und Ende werden weggelassen.
-creation.success=Das Secret "%s" wurde hinzugefΓΌgt.
-creation.failed=Secret konnte nicht hinzugefΓΌgt werden.
+
+
deletion=Secret entfernen
deletion.description=Das Entfernen eines Secrets kann nicht rΓΌckgΓ€ngig gemacht werden. Fortfahren?
deletion.success=Das Secret wurde entfernt.
diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini
index c2479bf342..444fbd26c9 100644
--- a/options/locale/locale_el-GR.ini
+++ b/options/locale/locale_el-GR.ini
@@ -3329,12 +3329,13 @@ owner.settings.chef.keypair.description=Ένα ΢Ρύγος κλΡιδιών Ξ΅Ξ
secrets=ΞœΟ…ΟƒΟ„ΞΉΞΊΞ¬
description=΀α μυστικά ΞΈΞ± πΡράσουν σΡ ορισμένΡς δράσΡις ΞΊΞ±ΞΉ δΡν μπορούν Ξ½Ξ± αναγνωστούν αλλού.
none=ΔΡν υπάρχουν ακόμα μυστικά.
-creation=ΠροσθΞΞΊΞ· ΞœΟ…ΟƒΟ„ΞΉΞΊΞΏΟ
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=ΠΡριγραφΞ
creation.name_placeholder=αλφαριθμητικοί χαρακτΞρΡς Πκάτω παύλΡς μόνο, δΡν μπορούν Ξ½Ξ± ξΡκινούν ΞΌΞ΅ GITEA_ Ξ GITHUB_
creation.value_placeholder=ΕισάγΡτΡ οποιοδΞποτΡ Ο€Ξ΅ΟΞΉΞ΅Ο‡ΟŒΞΌΞ΅Ξ½ΞΏ. ΀α κΡνά στην αρχΠπαραλΡίπονται.
-creation.success=΀ο ΞΌΟ…ΟƒΟ„ΞΉΞΊΟŒ "%s" προστέθηκΡ.
-creation.failed=Αποτυχία δημιουργίας μυστικού.
+
+
deletion=ΑφαίρΡση μυστικού
deletion.description=Ξ— αφαίρΡση Ξ΅Ξ½ΟŒΟ‚ μυστικού Ρίναι μόνιμη ΞΊΞ±ΞΉ δΡν μπορΡί Ξ½Ξ± αναιρΡθΡί. ΣυνέχΡια;
deletion.success=΀ο ΞΌΟ…ΟƒΟ„ΞΉΞΊΟŒ έχΡι αφαιρΡθΡί.
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 9091b6bc4b..af3b948a88 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -3722,13 +3722,18 @@ owner.settings.chef.keypair.description = A key pair is necessary to authenticat
secrets = Secrets
description = Secrets will be passed to certain actions and cannot be read otherwise.
none = There are no secrets yet.
-creation = Add Secret
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description = Description
creation.name_placeholder = case-insensitive, alphanumeric characters or underscores only, cannot start with GITEA_ or GITHUB_
creation.value_placeholder = Input any content. Whitespace at the start and end will be omitted.
creation.description_placeholder = Enter short description (optional).
-creation.success = The secret "%s" has been added.
-creation.failed = Failed to add secret.
+
+save_success = The secret "%s" has been saved.
+save_failed = Failed to save secret.
+
+add_secret = Add secret
+edit_secret = Edit secret
deletion = Remove secret
deletion.description = Removing a secret is permanent and cannot be undone. Continue?
deletion.success = The secret has been removed.
diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini
index 5f989f6acf..521583395e 100644
--- a/options/locale/locale_es-ES.ini
+++ b/options/locale/locale_es-ES.ini
@@ -3309,12 +3309,13 @@ owner.settings.chef.keypair.description=Un par de claves es necesario para auten
secrets=Secretos
description=Los secretos pasarΓ‘n a ciertas acciones y no se podrΓ‘n leer de otro modo.
none=TodavΓ­a no hay secretos.
-creation=AΓ±adir secreto
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=DescripciΓ³n
creation.name_placeholder=sin distinciΓ³n de mayΓΊsculas, solo carΓ‘cteres alfanumΓ©ricos o guiones bajos, no puede empezar por GITEA_ o GITHUB_
creation.value_placeholder=Introduce cualquier contenido. Se omitirΓ‘ el espacio en blanco en el inicio y el final.
-creation.success=El secreto "%s" ha sido aΓ±adido.
-creation.failed=Error al aΓ±adir secreto.
+
+
deletion=Eliminar secreto
deletion.description=Eliminar un secreto es permanente y no se puede deshacer. ΒΏContinuar?
deletion.success=El secreto ha sido eliminado.
diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini
index 5d67f03bac..18abc0f401 100644
--- a/options/locale/locale_fa-IR.ini
+++ b/options/locale/locale_fa-IR.ini
@@ -2506,8 +2506,12 @@ conan.details.repository=Ω…ΨΨ²Ω†
owner.settings.cleanuprules.enabled=فعال Ψ΄Ψ―Ω‡
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Ψ΄Ψ±Ψ­
+
+
[actions]
diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini
index 69cee090fe..b925d6f43a 100644
--- a/options/locale/locale_fi-FI.ini
+++ b/options/locale/locale_fi-FI.ini
@@ -1692,8 +1692,12 @@ conan.details.repository=Repo
owner.settings.cleanuprules.enabled=KΓ€ytΓΆssΓ€
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Kuvaus
+
+
[actions]
diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini
index b9d550eee5..eeb5e31965 100644
--- a/options/locale/locale_fr-FR.ini
+++ b/options/locale/locale_fr-FR.ini
@@ -130,6 +130,7 @@ pin=Γ‰pingler
unpin=DΓ©sΓ©pingler
artifacts=Artefacts
+expired=ExpirΓ©
confirm_delete_artifact=Êtes-vous sΓ»r de vouloir supprimer lβ€˜artefact « %sΒ Β» ?
archived=ArchivΓ©
@@ -450,6 +451,7 @@ use_scratch_code=Utiliser un code de secours
twofa_scratch_used=Vous avez utilisΓ© votre code de secours. Vous avez Γ©tΓ© redirigΓ© vers cette page de configuration afin de supprimer l'authentification Γ  deux facteurs de votre appareil ou afin de gΓ©nΓ©rer un nouveau code de secours.
twofa_passcode_incorrect=Votre code d’accΓ¨s n’est pas correct. Si vous avez Γ©garΓ© votre appareil, utilisez votre code de secours pour vous connecter.
twofa_scratch_token_incorrect=Votre code de secours est incorrect.
+twofa_required=Vous devez configurer l’authentification Γ  deux facteurs pour avoir accΓ¨s aux dΓ©pΓ΄ts, ou essayer de vous reconnecter.
login_userpass=Connexion
login_openid=OpenID
oauth_signup_tab=CrΓ©er un compte
@@ -1878,6 +1880,7 @@ pulls.add_prefix=Ajouter le prΓ©fixe <strong>%s</strong>
pulls.remove_prefix=Enlever le prΓ©fixe <strong>%s</strong>
pulls.data_broken=Cette demande d’ajout est impossible par manque d'informations de bifurcation.
pulls.files_conflicted=Cette demande d'ajout contient des modifications en conflit avec la branche ciblΓ©e.
+pulls.is_checking=Recherche de conflits de fusion…
pulls.is_ancestor=Cette branche est dΓ©jΓ  prΓ©sente dans la branche ciblΓ©e. Il n'y a rien Γ  fusionner.
pulls.is_empty=Les changements sur cette branche sont dΓ©jΓ  sur la branche cible. Cette rΓ©vision sera vide.
pulls.required_status_check_failed=Certains contrΓ΄les requis n'ont pas rΓ©ussi.
@@ -3718,13 +3721,14 @@ owner.settings.chef.keypair.description=Une paire de clΓ©s est nΓ©cessaire pour
secrets=Secrets
description=Les secrets seront transmis Γ  certaines actions et ne pourront pas Γͺtre lus autrement.
none=Il n'y a pas encore de secrets.
-creation=Ajouter un secret
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Description
creation.name_placeholder=Caractères alphanumériques ou tirets bas uniquement, insensibles à la casse, ne peut commencer par GITEA_ ou GITHUB_.
creation.value_placeholder=Entrez n’importe quoi. Les blancs cernant seront taillΓ©s.
creation.description_placeholder=Décrire brièvement votre dépôt (optionnel).
-creation.success=Le secret "%s" a Γ©tΓ© ajoutΓ©.
-creation.failed=Impossible d'ajouter le secret.
+
+
deletion=Supprimer le secret
deletion.description=La suppression d'un secret est permanente et irrΓ©versible. Continuer ?
deletion.success=Le secret a Γ©tΓ© supprimΓ©.
diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini
index b3a4a43f9d..cdde7e015d 100644
--- a/options/locale/locale_ga-IE.ini
+++ b/options/locale/locale_ga-IE.ini
@@ -130,6 +130,7 @@ pin=BiorΓ‘in
unpin=DΓ­phorΓ‘il
artifacts=DΓ©antΓ‘in
+expired=Imithe in Γ©ag
confirm_delete_artifact=An bhfuil tΓΊ cinnte gur mhaith leat an dΓ©antΓ‘n '%s' a scriosadh?
archived=Cartlann
@@ -3720,13 +3721,14 @@ owner.settings.chef.keypair.description=TΓ‘ eochairphΓ©ire riachtanach le fΓ­ord
secrets=RΓΊin
description=Cuirfear rΓΊin ar aghaidh chuig gnΓ­omhartha Γ‘irithe agus nΓ­ fΓ©idir iad a lΓ©amh ar mhalairt.
none=NΓ­l aon rΓΊin ann fΓ³s.
-creation=Cuir RΓΊnda leis
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Cur sΓ­os
creation.name_placeholder=carachtair alfanumair nΓ³ Γ­oslaghda amhΓ‘in nach fΓ©idir a thosΓΊ le GITEA_ nΓ³ GITHUB_
creation.value_placeholder=Ionchur Γ‘bhar ar bith. FΓ‘gfar spΓ‘s bΓ‘n ag tΓΊs agus ag deireadh ar lΓ‘r.
creation.description_placeholder=Cuir isteach cur sΓ­os gairid (roghnach).
-creation.success=TΓ‘ an rΓΊn "%s" curtha leis.
-creation.failed=Theip ar an rΓΊn a chur leis.
+
+
deletion=Bain rΓΊn
deletion.description=Is buan rΓΊn a bhaint agus nΓ­ fΓ©idir Γ© a chealΓΊ. Lean ort?
deletion.success=TΓ‘ an rΓΊn bainte.
diff --git a/options/locale/locale_hu-HU.ini b/options/locale/locale_hu-HU.ini
index 0dae5505aa..ebc6d5c801 100644
--- a/options/locale/locale_hu-HU.ini
+++ b/options/locale/locale_hu-HU.ini
@@ -1592,8 +1592,12 @@ conan.details.repository=TΓ‘rolΓ³
owner.settings.cleanuprules.enabled=EngedΓ©lyezett
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=LeΓ­rΓ‘s
+
+
[actions]
diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini
index 808ebaa9ec..54b0499d96 100644
--- a/options/locale/locale_id-ID.ini
+++ b/options/locale/locale_id-ID.ini
@@ -1394,8 +1394,12 @@ conan.details.repository=Repositori
owner.settings.cleanuprules.enabled=Aktif
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Deskripsi
+
+
[actions]
diff --git a/options/locale/locale_is-IS.ini b/options/locale/locale_is-IS.ini
index 999b21c608..42ecfabe22 100644
--- a/options/locale/locale_is-IS.ini
+++ b/options/locale/locale_is-IS.ini
@@ -1325,8 +1325,12 @@ npm.details.tag=Merki
pypi.requires=Þarfnast Python
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=LΓ½sing
+
+
[actions]
diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini
index f4a6767ea4..569d3f54e1 100644
--- a/options/locale/locale_it-IT.ini
+++ b/options/locale/locale_it-IT.ini
@@ -2782,8 +2782,12 @@ settings.delete.error=Impossibile eliminare il pacchetto.
owner.settings.cleanuprules.enabled=Attivo
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Descrizione
+
+
[actions]
diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini
index a6366565b2..7790dccd6b 100644
--- a/options/locale/locale_ja-JP.ini
+++ b/options/locale/locale_ja-JP.ini
@@ -3718,13 +3718,14 @@ owner.settings.chef.keypair.description=Chefγƒ¬γ‚Έγ‚Ήγƒˆγƒͺγθͺθ¨Όγ«γ―γ‚­γƒΌ
secrets=γ‚·γƒΌγ‚―γƒ¬γƒƒγƒˆ
description=γ‚·γƒΌγ‚―γƒ¬γƒƒγƒˆγ―η‰ΉεšγActionsγ«ζΈ‘γ•γ‚ŒγΎγ™γ€‚ γγ‚Œδ»₯倖でθͺ­γΏε‡Ίγ•γ‚Œγ‚‹γ“γ¨γ―γ‚γ‚ŠγΎγ›γ‚“γ€‚
none=γ‚·γƒΌγ‚―γƒ¬γƒƒγƒˆγ―γΎγ γ‚γ‚ŠγΎγ›γ‚“γ€‚
-creation=γ‚·γƒΌγ‚―γƒ¬γƒƒγƒˆγ‚’θΏ½εŠ 
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=θͺ¬ζ˜Ž
creation.name_placeholder=倧文字小文字γεŒΊεˆ₯γͺし、英数字とをンダースコをγγΏγ€GITEA_ γ‚„ GITHUB_ で始まるもγγ―不可
creation.value_placeholder=ε†…εΉγ‚’ε…₯εŠ›γ—γ¦γγ γ•γ„γ€‚ε‰εΎŒγη©Ίη™½γ―ι™€εŽ»γ•γ‚ŒγΎγ™γ€‚
creation.description_placeholder=簑単γͺθͺ¬ζ˜Žγ‚’ε…₯εŠ›γ—γ¦γγ γ•γ„γ€‚ (γ‚ͺプション)
-creation.success=γ‚·γƒΌγ‚―γƒ¬γƒƒγƒˆ "%s" γ‚’θΏ½εŠ γ—γΎγ—γŸγ€‚
-creation.failed=γ‚·γƒΌγ‚―γƒ¬γƒƒγƒˆγθΏ½εŠ γ«ε€±ζ•—γ—γΎγ—γŸγ€‚
+
+
deletion=γ‚·γƒΌγ‚―γƒ¬γƒƒγƒˆγε‰Šι™€
deletion.description=γ‚·γƒΌγ‚―γƒ¬γƒƒγƒˆγε‰Šι™€γ―ζ’δΉ…ηš„γ§ε…ƒγ«ζˆ»γ™γ“γ¨γ―γ§γγΎγ›γ‚“γ€‚ ηΆšθ‘Œγ—γΎγ™γ‹οΌŸ
deletion.success=γ‚·γƒΌγ‚―γƒ¬γƒƒγƒˆγ‚’ε‰Šι™€γ—γΎγ—γŸγ€‚
diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini
index 08f6d723de..22bf3e1641 100644
--- a/options/locale/locale_ko-KR.ini
+++ b/options/locale/locale_ko-KR.ini
@@ -1542,8 +1542,12 @@ conan.details.repository=μ €μž₯μ†Œ
owner.settings.cleanuprules.enabled=ν™œμ„±ν™”λ¨
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=μ„€λͺ…
+
+
[actions]
diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini
index 718ca0594e..a746f8738c 100644
--- a/options/locale/locale_lv-LV.ini
+++ b/options/locale/locale_lv-LV.ini
@@ -3332,12 +3332,13 @@ owner.settings.chef.keypair.description=AtslΔ“gu pāris ir nepiecieΕ‘ams, lai au
secrets=NoslΔ“pumi
description=NoslΔ“pumi tiks padoti atseviőķām darbΔ«bām un citādi nevar tikt nolasΔ«ti.
none=Pagaidām nav neviena noslΔ“puma.
-creation=Pievienot noslΔ“pumu
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Apraksts
creation.name_placeholder=reģistr-nejūtīgs, tikai burti, cipari un apakősvītras, nevar sākties ar GITEA_ vai GITHUB_
creation.value_placeholder=Ievadiet jebkādu saturu. Atstarpes sākumā un beigā tiks noΕ†emtas.
-creation.success=NoslΔ“pums "%s" tika pievienots.
-creation.failed=Neizdevās pievienot noslΔ“pumu.
+
+
deletion=DzΔ“st noslΔ“pumu
deletion.description=NoslΔ“puma dzΔ“Ε‘ana ir neatgriezeniska. Vai turpināt?
deletion.success=NoslΔ“pums tika izdzΔ“sts.
diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini
index eff4c1f85f..b6887ee9e0 100644
--- a/options/locale/locale_nl-NL.ini
+++ b/options/locale/locale_nl-NL.ini
@@ -2515,8 +2515,12 @@ settings.link.button=Repository link bijwerken
owner.settings.cleanuprules.enabled=Ingeschakeld
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Omschrijving
+
+
[actions]
diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini
index b45f0fc8e0..42a33f9ce4 100644
--- a/options/locale/locale_pl-PL.ini
+++ b/options/locale/locale_pl-PL.ini
@@ -2405,8 +2405,12 @@ conan.details.repository=Repozytorium
owner.settings.cleanuprules.enabled=WΕ‚Δ…czone
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Opis
+
+
[actions]
diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini
index 75d425417c..8ee675e6e0 100644
--- a/options/locale/locale_pt-BR.ini
+++ b/options/locale/locale_pt-BR.ini
@@ -3269,12 +3269,13 @@ owner.settings.chef.keypair=Gerar par de chaves
secrets=Segredos
description=Os segredos serΓ£o passados a certas aΓ§Γ΅es e nΓ£o poderΓ£o ser lidos de outra forma.
none=NΓ£o hΓ‘ segredos ainda.
-creation=Adicionar Segredo
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=DescriΓ§Γ£o
creation.name_placeholder=apenas caracteres alfanumΓ©ricos ou underline (_), nΓ£o pode comeΓ§ar com GITEA_ ou GITHUB_
creation.value_placeholder=Insira qualquer conteΓΊdo. EspaΓ§os em branco no inΓ­cio e no fim serΓ£o omitidos.
-creation.success=O segredo "%s" foi adicionado.
-creation.failed=Falha ao adicionar segredo.
+
+
deletion=Excluir segredo
deletion.description=A exclusΓ£o de um segredo Γ© permanente e nΓ£o pode ser desfeita. Continuar?
deletion.success=O segredo foi excluΓ­do.
diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini
index 7d00fb81c7..b47b61f6bd 100644
--- a/options/locale/locale_pt-PT.ini
+++ b/options/locale/locale_pt-PT.ini
@@ -130,6 +130,7 @@ pin=Fixar
unpin=Desafixar
artifacts=Artefactos
+expired=Expirado
confirm_delete_artifact=Tem a certeza que quer eliminar este artefacto "%s"?
archived=Arquivado
@@ -1879,6 +1880,7 @@ pulls.add_prefix=Adicione o prefixo <strong>%s</strong>
pulls.remove_prefix=Remover o prefixo <strong>%s</strong>
pulls.data_broken=Este pedido de integraΓ§Γ£o estΓ‘ danificado devido Γ  falta de informaΓ§Γ£o da derivaΓ§Γ£o.
pulls.files_conflicted=Este pedido de integraΓ§Γ£o contΓ©m modificaΓ§Γ΅es que entram em conflito com o ramo de destino.
+pulls.is_checking=Verificando se existem conflitos na integraΓ§Γ£o...
pulls.is_ancestor=Este ramo jΓ‘ estΓ‘ incluΓ­do no ramo de destino. NΓ£o hΓ‘ nada a integrar.
pulls.is_empty=As modificaΓ§Γ΅es feitas neste ramo jΓ‘ existem no ramo de destino. Este cometimento ficarΓ‘ vazio.
pulls.required_status_check_failed=Algumas das verificaΓ§Γ΅es obrigatΓ³rias nΓ£o foram bem sucedidas.
@@ -3719,13 +3721,18 @@ owner.settings.chef.keypair.description=Γ‰ necessΓ‘rio um par de chaves para aut
secrets=Segredos
description=Os segredos serΓ£o transmitidos a certas operaΓ§Γ΅es e nΓ£o poderΓ£o ser lidos de outra forma.
none=Ainda nΓ£o hΓ‘ segredos.
-creation=Adicionar segredo
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=DescriΓ§Γ£o
creation.name_placeholder=SΓ³ sublinhados ou alfanumΓ©ricos sem distinguir maiΓΊsculas, sem comeΓ§ar com GITEA_ nem GITHUB_
creation.value_placeholder=Insira um conteΓΊdo qualquer. EspaΓ§os em branco no inΓ­cio ou no fim serΓ£o omitidos.
creation.description_placeholder=Escreva uma descriΓ§Γ£o curta (opcional).
-creation.success=O segredo "%s" foi adicionado.
-creation.failed=Falhou ao adicionar o segredo.
+
+save_success=O segredo "%s" foi guardado.
+save_failed=Falhou ao guardar o segredo.
+
+add_secret=Adicionar segredo
+edit_secret=Editar segredo
deletion=Remover segredo
deletion.description=Remover um segredo Γ© permanente e nΓ£o pode ser revertido. Continuar?
deletion.success=O segredo foi removido.
diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini
index 879d7c6145..c65d08a4cf 100644
--- a/options/locale/locale_ru-RU.ini
+++ b/options/locale/locale_ru-RU.ini
@@ -3266,12 +3266,13 @@ owner.settings.chef.keypair=Π‘ΠΎΠ·Π΄Π°Ρ‚ΡŒ ΠΏΠ°Ρ€Ρƒ ΠΊΠ»ΡŽΡ‡Π΅ΠΉ
secrets=Π‘Π΅ΠΊΡ€Π΅Ρ‚Ρ‹
description=Π‘Π΅ΠΊΡ€Π΅Ρ‚Ρ‹ Π±ΡƒΠ΄ΡƒΡ‚ ΠΏΠ΅Ρ€Π΅Π΄Π°Π²Π°Ρ‚ΡŒΡΡ ΠΎΠΏΡ€Π΅Π΄Π΅Π»Π΅Π½Π½Ρ‹ΠΌ дСйствиям ΠΈ Π½Π΅ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ ΠΏΡ€ΠΎΡ‡ΠΈΡ‚Π°Π½Ρ‹ ΠΈΠ½Π°Ρ‡Π΅.
none=Π‘Π΅ΠΊΡ€Π΅Ρ‚ΠΎΠ² ΠΏΠΎΠΊΠ° Π½Π΅Ρ‚.
-creation=Π”ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ сСкрСт
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=ОписаниС
creation.name_placeholder=рСгистр Π½Π΅ Π²Π°ΠΆΠ΅Π½, Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π°Π»Ρ„Π°Π²ΠΈΡ‚Π½ΠΎ-Ρ†ΠΈΡ„Ρ€ΠΎΠ²Ρ‹Π΅ символы ΠΈ подчёркивания, Π½Π΅ ΠΌΠΎΠΆΠ΅Ρ‚ Π½Π°Ρ‡ΠΈΠ½Π°Ρ‚ΡŒΡΡ с GITEA_ ΠΈΠ»ΠΈ GITHUB_
creation.value_placeholder=Π’Π²Π΅Π΄ΠΈΡ‚Π΅ любоС содСрТимоС. ΠŸΡ€ΠΎΠ±Π΅Π»ΡŒΠ½Ρ‹Π΅ символы Π² Π½Π°Ρ‡Π°Π»Π΅ ΠΈ ΠΊΠΎΠ½Ρ†Π΅ Π±ΡƒΠ΄ΡƒΡ‚ ΠΎΠΏΡƒΡ‰Π΅Π½Ρ‹.
-creation.success=Π‘Π΅ΠΊΡ€Π΅Ρ‚ Β«%sΒ» Π΄ΠΎΠ±Π°Π²Π»Π΅Π½.
-creation.failed=НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ Π΄ΠΎΠ±Π°Π²ΠΈΡ‚ΡŒ сСкрСт.
+
+
deletion=Π£Π΄Π°Π»ΠΈΡ‚ΡŒ сСкрСт
deletion.description=Π£Π΄Π°Π»Π΅Π½ΠΈΠ΅ сСкрСта Π½Π΅ΠΎΠ±Ρ€Π°Ρ‚ΠΈΠΌΠΎ, Π΅Π³ΠΎ нСльзя ΠΎΡ‚ΠΌΠ΅Π½ΠΈΡ‚ΡŒ. ΠŸΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠΈΡ‚ΡŒ?
deletion.success=Π‘Π΅ΠΊΡ€Π΅Ρ‚ ΡƒΠ΄Π°Π»Ρ‘Π½.
diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini
index 042e8ad21b..a209187aff 100644
--- a/options/locale/locale_si-LK.ini
+++ b/options/locale/locale_si-LK.ini
@@ -2447,8 +2447,12 @@ conan.details.repository=ΰΆšΰ·ΰ·‚ΰ·ŠΰΆ¨ΰΆΊ
owner.settings.cleanuprules.enabled=ΰ·ƒΰΆΆΰΆ½ ࢚ࢻ ΰΆ‡ΰΆ­
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=ΰ·ƒΰ·€ΰ·’ΰ·ƒΰ·ŠΰΆ­ΰΆ»ΰΆΊ
+
+
[actions]
diff --git a/options/locale/locale_sk-SK.ini b/options/locale/locale_sk-SK.ini
index b1dae7c490..e461075e53 100644
--- a/options/locale/locale_sk-SK.ini
+++ b/options/locale/locale_sk-SK.ini
@@ -1321,6 +1321,10 @@ owner.settings.cleanuprules.enabled=PovolenΓ©
[secrets]
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
+
+
+
[actions]
diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini
index 6fb5a9c4cb..04428aeab2 100644
--- a/options/locale/locale_sv-SE.ini
+++ b/options/locale/locale_sv-SE.ini
@@ -1982,8 +1982,12 @@ conan.details.repository=Utvecklingskatalog
owner.settings.cleanuprules.enabled=Aktiv
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Beskrivning
+
+
[actions]
diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini
index acd0892eba..d617598057 100644
--- a/options/locale/locale_tr-TR.ini
+++ b/options/locale/locale_tr-TR.ini
@@ -3525,12 +3525,13 @@ owner.settings.chef.keypair.description=Chef kütüğünde kimlik doğrulaması
secrets=Gizlilikler
description=Gizlilikler belirli işlemlere aktarılacaktır, bunun dışında okunamaz.
none=HenΓΌz gizlilik yok.
-creation=Gizlilik Ekle
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=AΓ§Δ±klama
creation.name_placeholder=küçük-büyük harfe duyarlı değil, alfanümerik karakterler veya sadece alt tire, GITEA_ veya GITHUB_ ile başlayamaz
creation.value_placeholder=Herhangi bir içerik girin. Baştaki ve sondaki boşluklar ihmal edilecektir.
-creation.success=Gizlilik "%s" eklendi.
-creation.failed=Gizlilik eklenemedi.
+
+
deletion=Gizliliği kaldır
deletion.description=Bir gizliliği kaldırma kalıcıdır ve geri alınamaz. Devam edilsin mi?
deletion.success=Gizlilik kaldΔ±rΔ±ldΔ±.
diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini
index 3a6d1539fa..6aed70491b 100644
--- a/options/locale/locale_uk-UA.ini
+++ b/options/locale/locale_uk-UA.ini
@@ -2517,8 +2517,12 @@ conan.details.repository=Π Π΅ΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€Ρ–ΠΉ
owner.settings.cleanuprules.enabled=Π£Π²Ρ–ΠΌΠΊΠ½Π΅Π½ΠΎ
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=Опис
+
+
[actions]
diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini
index 0e7db6350c..f6d6183e52 100644
--- a/options/locale/locale_zh-CN.ini
+++ b/options/locale/locale_zh-CN.ini
@@ -3717,13 +3717,14 @@ owner.settings.chef.keypair.description=ιœ€θ¦ε―†ι’₯对才能向 Chef ζ³¨ε†ŒδΈ­ε
secrets=ε―†ι’₯
description=Secrets ε°†θ’«δΌ η»™η‰Ήεšηš„ ActionsοΌŒε…Άεƒζƒ…冡将不能读取
none=θΏ˜ζ²‘ζœ‰ε―†ι’₯。
-creation=ζ·»εŠ ε―†ι’₯
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=组织描述
creation.name_placeholder=δΈεŒΊεˆ†ε€§ε°ε†™οΌŒε­—ζ―ζ•°ε­—ζˆ–δΈ‹εˆ’ηΊΏδΈθƒ½δ»₯GITEA_ ζˆ– GITHUB_ 开倴。
creation.value_placeholder=θΎ“ε…₯任何内εΉοΌŒεΌ€ε€΄ε’Œη»“ε°Ύηš„η©Ίη™½ιƒ½δΌšθ’«ηœη•₯
creation.description_placeholder=θΎ“ε…₯η€ηŸ­ζθΏ°(可选)。
-creation.success=ζ‚¨ηš„ε―†ι’₯ '%s' ζ·»εŠ ζˆεŠŸγ€‚
-creation.failed=ζ·»εŠ ε―†ι’₯ε€±θ΄₯。
+
+
deletion=εˆ ι™€ε―†ι’₯
deletion.description=εˆ ι™€ε―†ι’₯ζ˜―ζ°ΈδΉ…ζ€§ηš„οΌŒζ— ζ³•ζ’€ζΆˆγ€‚η»§η»­ε—οΌŸ
deletion.success=ζ­€Secretε·²θ’«εˆ ι™€γ€‚
diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini
index b157a44c69..2874da3170 100644
--- a/options/locale/locale_zh-HK.ini
+++ b/options/locale/locale_zh-HK.ini
@@ -959,8 +959,12 @@ conan.details.repository=ε„²ε­˜εΊ«
owner.settings.cleanuprules.enabled=ε·²ε•Ÿη”¨
[secrets]
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=硄織描述
+
+
[actions]
diff --git a/options/locale/locale_zh-TW.ini b/options/locale/locale_zh-TW.ini
index 3b25c81be3..a52e147415 100644
--- a/options/locale/locale_zh-TW.ini
+++ b/options/locale/locale_zh-TW.ini
@@ -3635,12 +3635,13 @@ owner.settings.chef.keypair.description=ι©—θ­‰ Chef θ¨»ε†ŠδΈ­εΏƒιœ€θ¦δΈ€ε€‹ε―†ι
secrets=Secret
description=Secret ζœƒθ’«ε‚³η΅¦η‰Ήεšηš„ ActionοΌŒε…Άδ»–ζƒ…ζ³η„‘ζ³•θ€ε–。
none=ι‚„ζ²’ζœ‰ Secret。
-creation=加ε…₯ Secret
+
+; These keys are also for "edit secret", the keys are kept as-is to avoid unnecessary re-translation
creation.description=描述
creation.name_placeholder=δΈε€εˆ†ε€§ε°ε―«οΌŒεͺθƒ½εŒ…ε«θ‹±ζ–‡ε­—ζ―γ€ζ•Έε­—γ€εΊ•η·š ('_')οΌŒδΈθƒ½δ»₯ GITEA_ ζˆ– GITHUB_ 開頭。
creation.value_placeholder=θΌΈε…₯任何內εΉοΌŒι ­ε°Ύηš„η©Ίη™½ιƒ½ζœƒθ’«εΏ½η•₯。
-creation.success=ε·²ζ–°ε’ž Secretγ€Œ%s」。
-creation.failed=加ε…₯ Secret 倱敗。
+
+
deletion=移陀 Secret
deletion.description=移陀 Secret ζ˜―ζ°ΈδΉ…ηš„δΈ”δΈε―ι‚„εŽŸοΌŒζ˜―ε¦ηΉΌηΊŒοΌŸ
deletion.success=已移陀歀 Secret。
diff --git a/package-lock.json b/package-lock.json
index 4c5963d0c8..e61fe3472d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,7 @@
"@citation-js/plugin-csl": "0.7.18",
"@citation-js/plugin-software-formats": "0.6.1",
"@github/markdown-toolbar-element": "2.2.3",
- "@github/relative-time-element": "4.4.7",
+ "@github/relative-time-element": "4.4.8",
"@github/text-expander-element": "2.9.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@primer/octicons": "19.15.1",
@@ -1146,9 +1146,9 @@
"license": "MIT"
},
"node_modules/@github/relative-time-element": {
- "version": "4.4.7",
- "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.7.tgz",
- "integrity": "sha512-NZCePEFYtV7qAUI/pHYuqZ8vRhcsfH/dziUZTY9YR5+JwzDCWtEokYSDbDLZjrRl+SAFr02YHUK+UdtP6hPcbQ==",
+ "version": "4.4.8",
+ "resolved": "https://registry.npmjs.org/@github/relative-time-element/-/relative-time-element-4.4.8.tgz",
+ "integrity": "sha512-FSLYm6F3TSQnqHE1EMQUVVgi2XjbCvsESwwXfugHFpBnhyF1uhJOtu0Psp/BB/qqazfdkk7f5fVcu7WuXl3t8Q==",
"license": "MIT"
},
"node_modules/@github/text-expander-element": {
diff --git a/package.json b/package.json
index 0202b92ff4..bc2c0c87f3 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"@citation-js/plugin-csl": "0.7.18",
"@citation-js/plugin-software-formats": "0.6.1",
"@github/markdown-toolbar-element": "2.2.3",
- "@github/relative-time-element": "4.4.7",
+ "@github/relative-time-element": "4.4.8",
"@github/text-expander-element": "2.9.1",
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
"@primer/octicons": "19.15.1",
diff --git a/routers/private/serv.go b/routers/private/serv.go
index 37fbc0730c..b879be0dc2 100644
--- a/routers/private/serv.go
+++ b/routers/private/serv.go
@@ -81,6 +81,7 @@ func ServCommand(ctx *context.PrivateContext) {
ownerName := ctx.PathParam("owner")
repoName := ctx.PathParam("repo")
mode := perm.AccessMode(ctx.FormInt("mode"))
+ verb := ctx.FormString("verb")
// Set the basic parts of the results to return
results := private.ServCommandResults{
@@ -295,8 +296,11 @@ func ServCommand(ctx *context.PrivateContext) {
return
}
} else {
- // Because of the special ref "refs/for" we will need to delay write permission check
- if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode {
+ // Because of the special ref "refs/for" (AGit) we will need to delay write permission check,
+ // AGit flow needs to write its own ref when the doer has "reader" permission (allowing to create PR).
+ // The real permission check is done in HookPreReceive (routers/private/hook_pre_receive.go).
+ // Here it should relax the permission check for "git push (git-receive-pack)", but not for others like LFS operations.
+ if git.DefaultFeatures().SupportProcReceive && unitType == unit.TypeCode && verb == git.CmdVerbReceivePack {
mode = perm.AccessModeRead
}
diff --git a/routers/web/feed/convert.go b/routers/web/feed/convert.go
index b04855fa6a..7c59132841 100644
--- a/routers/web/feed/convert.go
+++ b/routers/web/feed/convert.go
@@ -201,7 +201,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
switch act.OpType {
case activities_model.ActionCommitRepo, activities_model.ActionMirrorSyncPush:
push := templates.ActionContent2Commits(act)
-
+ _ = act.LoadRepo(ctx)
for _, commit := range push.Commits {
if len(desc) != 0 {
desc += "\n\n"
@@ -209,7 +209,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions activities_model.Actio
desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s",
html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoAbsoluteLink(ctx), commit.Sha1)),
commit.Sha1,
- renderUtils.RenderCommitMessage(commit.Message, nil),
+ renderUtils.RenderCommitMessage(commit.Message, act.Repo),
)
}
diff --git a/routers/web/repo/actions/view.go b/routers/web/repo/actions/view.go
index 2ec6389263..dd18c8380d 100644
--- a/routers/web/repo/actions/view.go
+++ b/routers/web/repo/actions/view.go
@@ -200,13 +200,9 @@ func ViewPost(ctx *context_module.Context) {
}
}
- // TODO: "ComposeCommentMetas" (usually for comment) is not quite right, but it is still the same as what template "RenderCommitMessage" does.
- // need to be refactored together in the future
- metas := ctx.Repo.Repository.ComposeCommentMetas(ctx)
-
// the title for the "run" is from the commit message
resp.State.Run.Title = run.Title
- resp.State.Run.TitleHTML = templates.NewRenderUtils(ctx).RenderCommitMessage(run.Title, metas)
+ resp.State.Run.TitleHTML = templates.NewRenderUtils(ctx).RenderCommitMessage(run.Title, ctx.Repo.Repository)
resp.State.Run.Link = run.Link()
resp.State.Run.CanCancel = !run.Status.IsDone() && ctx.Repo.CanWrite(unit.TypeActions)
resp.State.Run.CanApprove = run.NeedApproval && ctx.Repo.CanWrite(unit.TypeActions)
diff --git a/routers/web/shared/secrets/secrets.go b/routers/web/shared/secrets/secrets.go
index c8b80ebb26..29f4e9520d 100644
--- a/routers/web/shared/secrets/secrets.go
+++ b/routers/web/shared/secrets/secrets.go
@@ -32,11 +32,11 @@ func PerformSecretsPost(ctx *context.Context, ownerID, repoID int64, redirectURL
s, _, err := secret_service.CreateOrUpdateSecret(ctx, ownerID, repoID, form.Name, util.ReserveLineBreakForTextarea(form.Data), form.Description)
if err != nil {
log.Error("CreateOrUpdateSecret failed: %v", err)
- ctx.JSONError(ctx.Tr("secrets.creation.failed"))
+ ctx.JSONError(ctx.Tr("secrets.save_failed"))
return
}
- ctx.Flash.Success(ctx.Tr("secrets.creation.success", s.Name))
+ ctx.Flash.Success(ctx.Tr("secrets.save_success", s.Name))
ctx.JSONRedirect(redirectURL)
}
diff --git a/services/repository/branch.go b/services/repository/branch.go
index 94c47ffdc4..dd00ca7dcd 100644
--- a/services/repository/branch.go
+++ b/services/repository/branch.go
@@ -663,6 +663,11 @@ func SetRepoDefaultBranch(ctx context.Context, repo *repo_model.Repository, newB
}
}
+ // clear divergence cache
+ if err := DelRepoDivergenceFromCache(ctx, repo.ID); err != nil {
+ log.Error("DelRepoDivergenceFromCache: %v", err)
+ }
+
notify_service.ChangeDefaultBranch(ctx, repo)
return nil
diff --git a/services/repository/files/upload.go b/services/repository/files/upload.go
index f348cb68ab..68a071cd28 100644
--- a/services/repository/files/upload.go
+++ b/services/repository/files/upload.go
@@ -107,6 +107,7 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
}
var attributesMap map[string]*attribute.Attributes
+ // when uploading to an empty repo, the old branch doesn't exist, but some "global gitattributes" or "info/attributes" may exist
if setting.LFS.StartServer {
attributesMap, err = attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{
Attributes: []string{attribute.Filter},
@@ -118,6 +119,12 @@ func UploadRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use
}
// Copy uploaded files into repository.
+ // TODO: there is a small problem: when uploading LFS files with ".gitattributes", the "check-attr" runs before this loop,
+ // so LFS files are not able to be added as LFS objects. Ideally we need to do in 3 steps in the future:
+ // 1. Add ".gitattributes" to git index
+ // 2. Run "check-attr" (the previous attribute.CheckAttributes call)
+ // 3. Add files to git index (this loop)
+ // This problem is trivial so maybe no need to spend too much time on it at the moment.
for i := range infos {
if err := copyUploadedLFSFileIntoRepository(ctx, &infos[i], attributesMap, t, opts.TreePath); err != nil {
return err
diff --git a/templates/admin/auth/edit.tmpl b/templates/admin/auth/edit.tmpl
index 15683307ed..91b84e13b6 100644
--- a/templates/admin/auth/edit.tmpl
+++ b/templates/admin/auth/edit.tmpl
@@ -15,7 +15,7 @@
</div>
<div class="required inline field {{if .Err_Name}}error{{end}}">
<label for="auth_name">{{ctx.Locale.Tr "admin.auths.auth_name"}}</label>
- <input id="auth_name" name="name" value="{{.Source.Name}}" autofocus required>
+ <input id="auth_name" name="name" value="{{.Source.Name}}" required>
</div>
<div class="inline field">
<div class="ui checkbox">
diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl
index c04d332660..879b5cb550 100644
--- a/templates/admin/user/edit.tmpl
+++ b/templates/admin/user/edit.tmpl
@@ -9,7 +9,7 @@
{{.CsrfTokenHtml}}
<div class="field {{if .Err_UserName}}error{{end}}">
<label for="user_name">{{ctx.Locale.Tr "username"}}</label>
- <input id="user_name" name="user_name" value="{{.User.Name}}" autofocus {{if not .User.IsLocal}}disabled{{end}} maxlength="40">
+ <input id="user_name" name="user_name" value="{{.User.Name}}" {{if not .User.IsLocal}}disabled{{end}} maxlength="40">
</div>
<!-- Types and name -->
<div class="inline required field {{if .Err_LoginType}}error{{end}}">
@@ -55,7 +55,7 @@
<div class="required non-local field {{if .Err_LoginName}}error{{end}} {{if eq .User.LoginSource 0}}tw-hidden{{end}}">
<label for="login_name">{{ctx.Locale.Tr "admin.users.auth_login_name"}}</label>
- <input id="login_name" name="login_name" value="{{.User.LoginName}}" autofocus>
+ <input id="login_name" name="login_name" value="{{.User.LoginName}}">
</div>
<div class="field {{if .Err_FullName}}error{{end}}">
<label for="full_name">{{ctx.Locale.Tr "settings.full_name"}}</label>
@@ -63,7 +63,7 @@
</div>
<div class="required field {{if .Err_Email}}error{{end}}">
<label for="email">{{ctx.Locale.Tr "email"}}</label>
- <input id="email" name="email" type="email" value="{{.User.Email}}" autofocus required>
+ <input id="email" name="email" type="email" value="{{.User.Email}}" required>
</div>
<div class="local field {{if .Err_Password}}error{{end}} {{if not (or (.User.IsLocal) (.User.IsOAuth2))}}tw-hidden{{end}}">
<label for="password">{{ctx.Locale.Tr "password"}}</label>
diff --git a/templates/org/settings/options.tmpl b/templates/org/settings/options.tmpl
index 76315f3eac..f4583bbe36 100644
--- a/templates/org/settings/options.tmpl
+++ b/templates/org/settings/options.tmpl
@@ -12,7 +12,7 @@
<br>{{ctx.Locale.Tr "org.settings.change_orgname_prompt"}}<br>{{ctx.Locale.Tr "org.settings.change_orgname_redirect_prompt"}}
</span>
</label>
- <input id="org_name" name="name" value="{{.Org.Name}}" data-org-name="{{.Org.Name}}" autofocus required maxlength="40">
+ <input id="org_name" name="name" value="{{.Org.Name}}" data-org-name="{{.Org.Name}}" required maxlength="40">
</div>
<div class="field {{if .Err_FullName}}error{{end}}">
<label for="full_name">{{ctx.Locale.Tr "org.org_full_name_holder"}}</label>
diff --git a/templates/repo/branch/list.tmpl b/templates/repo/branch/list.tmpl
index 19797229bf..fffe3a08cc 100644
--- a/templates/repo/branch/list.tmpl
+++ b/templates/repo/branch/list.tmpl
@@ -20,14 +20,14 @@
<tr>
<td>
<div class="flex-text-block">
- <a class="gt-ellipsis" href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{.DefaultBranchBranch.DBBranch.Name}}</a>
+ <a class="gt-ellipsis branch-name" href="{{.RepoLink}}/src/branch/{{PathEscapeSegments .DefaultBranchBranch.DBBranch.Name}}">{{.DefaultBranchBranch.DBBranch.Name}}</a>
{{if .DefaultBranchBranch.IsProtected}}
<span data-tooltip-content="{{ctx.Locale.Tr "repo.settings.protected_branch"}}">{{svg "octicon-shield-lock"}}</span>
{{end}}
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DefaultBranchBranch.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 14}}</button>
{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DefaultBranchBranch.DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DefaultBranchBranch.DBBranch.CommitID)}}
</div>
- <p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> Β· <span class="commit-message">{{ctx.RenderUtils.RenderCommitMessage .DefaultBranchBranch.DBBranch.CommitMessage (.Repository.ComposeCommentMetas ctx)}}</span> Β· {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DefaultBranchBranch.DBBranch.CommitTime}}{{if .DefaultBranchBranch.DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p>
+ <p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{.RepoLink}}/commit/{{PathEscape .DefaultBranchBranch.DBBranch.CommitID}}">{{ShortSha .DefaultBranchBranch.DBBranch.CommitID}}</a> Β· <span class="commit-message">{{ctx.RenderUtils.RenderCommitMessage .DefaultBranchBranch.DBBranch.CommitMessage .Repository}}</span> Β· {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DefaultBranchBranch.DBBranch.CommitTime}}{{if .DefaultBranchBranch.DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DefaultBranchBranch.DBBranch.Pusher}}{{template "shared/user/namelink" .DefaultBranchBranch.DBBranch.Pusher}}{{end}}</p>
</td>
{{/* FIXME: here and below, the tw-overflow-visible is not quite right but it is still needed the moment: to show the important buttons when the width is narrow */}}
<td class="tw-text-right tw-overflow-visible">
@@ -90,20 +90,20 @@
<td class="eight wide">
{{if .DBBranch.IsDeleted}}
<div class="flex-text-block">
- <span class="gt-ellipsis">{{.DBBranch.Name}}</span>
+ <span class="gt-ellipsis branch-name">{{.DBBranch.Name}}</span>
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 14}}</button>
</div>
<p class="info">{{ctx.Locale.Tr "repo.branch.deleted_by" .DBBranch.DeletedBy.Name}} {{DateUtils.TimeSince .DBBranch.DeletedUnix}}</p>
{{else}}
<div class="flex-text-block">
- <a class="gt-ellipsis" href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a>
+ <a class="gt-ellipsis branch-name" href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .DBBranch.Name}}">{{.DBBranch.Name}}</a>
{{if .IsProtected}}
<span data-tooltip-content="{{ctx.Locale.Tr "repo.settings.protected_branch"}}">{{svg "octicon-shield-lock"}}</span>
{{end}}
<button class="btn interact-fg tw-px-1" data-clipboard-text="{{.DBBranch.Name}}" data-tooltip-content="{{ctx.Locale.Tr "copy_branch"}}">{{svg "octicon-copy" 14}}</button>
{{template "repo/commit_statuses" dict "Status" (index $.CommitStatus .DBBranch.CommitID) "Statuses" (index $.CommitStatuses .DBBranch.CommitID)}}
</div>
- <p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> Β· <span class="commit-message">{{ctx.RenderUtils.RenderCommitMessage .DBBranch.CommitMessage ($.Repository.ComposeCommentMetas ctx)}}</span> Β· {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DBBranch.CommitTime}}{{if .DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} &nbsp;{{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p>
+ <p class="info tw-flex tw-items-center tw-my-1">{{svg "octicon-git-commit" 16 "tw-mr-1"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .DBBranch.CommitID}}">{{ShortSha .DBBranch.CommitID}}</a> Β· <span class="commit-message">{{ctx.RenderUtils.RenderCommitMessage .DBBranch.CommitMessage $.Repository}}</span> Β· {{ctx.Locale.Tr "org.repo_updated"}} {{DateUtils.TimeSince .DBBranch.CommitTime}}{{if .DBBranch.Pusher}} &nbsp;{{template "shared/user/avatarlink" dict "user" .DBBranch.Pusher}} &nbsp;{{template "shared/user/namelink" .DBBranch.Pusher}}{{end}}</p>
{{end}}
</td>
<td class="two wide ui">
diff --git a/templates/repo/commit_page.tmpl b/templates/repo/commit_page.tmpl
index 5639c87a82..7abd377108 100644
--- a/templates/repo/commit_page.tmpl
+++ b/templates/repo/commit_page.tmpl
@@ -5,7 +5,7 @@
<div class="ui container fluid padded">
<div class="ui top attached header clearing segment tw-relative commit-header">
<div class="tw-flex tw-mb-4 tw-gap-1">
- <h3 class="tw-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.Summary}}">{{ctx.RenderUtils.RenderCommitMessage .Commit.Message ($.Repository.ComposeCommentMetas ctx)}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3>
+ <h3 class="tw-mb-0 tw-flex-1"><span class="commit-summary" title="{{.Commit.Summary}}">{{ctx.RenderUtils.RenderCommitMessage .Commit.Message $.Repository}}</span>{{template "repo/commit_statuses" dict "Status" .CommitStatus "Statuses" .CommitStatuses}}</h3>
{{if not $.PageIsWiki}}
<div class="commit-header-buttons">
<a class="ui primary tiny button" href="{{.SourcePath}}">
@@ -122,7 +122,7 @@
{{end}}
</div>
{{if IsMultilineCommitMessage .Commit.Message}}
- <pre class="commit-body">{{ctx.RenderUtils.RenderCommitBody .Commit.Message ($.Repository.ComposeCommentMetas ctx)}}</pre>
+ <pre class="commit-body">{{ctx.RenderUtils.RenderCommitBody .Commit.Message $.Repository}}</pre>
{{end}}
{{template "repo/commit_load_branches_and_tags" .}}
</div>
diff --git a/templates/repo/commits_list.tmpl b/templates/repo/commits_list.tmpl
index 17c7240ee4..8a268a5d14 100644
--- a/templates/repo/commits_list.tmpl
+++ b/templates/repo/commits_list.tmpl
@@ -44,7 +44,7 @@
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{.Summary | ctx.RenderUtils.RenderEmoji}}</span>
{{else}}
{{$commitLink:= printf "%s/commit/%s" $commitRepoLink (PathEscape .ID.String)}}
- <span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink ($.Repository.ComposeCommentMetas ctx)}}</span>
+ <span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink $.Repository}}</span>
{{end}}
</span>
{{if IsMultilineCommitMessage .Message}}
@@ -52,7 +52,7 @@
{{end}}
{{template "repo/commit_statuses" dict "Status" .Status "Statuses" .Statuses}}
{{if IsMultilineCommitMessage .Message}}
- <pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody .Message ($.Repository.ComposeCommentMetas ctx)}}</pre>
+ <pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody .Message $.Repository}}</pre>
{{end}}
{{if $.CommitsTagsMap}}
{{range (index $.CommitsTagsMap .ID.String)}}
diff --git a/templates/repo/commits_list_small.tmpl b/templates/repo/commits_list_small.tmpl
index b054ce19a5..ee94ad7e58 100644
--- a/templates/repo/commits_list_small.tmpl
+++ b/templates/repo/commits_list_small.tmpl
@@ -15,7 +15,7 @@
{{$commitLink:= printf "%s/%s" $commitBaseLink (PathEscape .ID.String)}}
<span class="tw-flex-1 tw-font-mono gt-ellipsis" title="{{.Summary}}">
- {{- ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink ($.comment.Issue.PullRequest.BaseRepo.ComposeCommentMetas ctx) -}}
+ {{- ctx.RenderUtils.RenderCommitMessageLinkSubject .Message $commitLink $.comment.Issue.PullRequest.BaseRepo -}}
</span>
{{if IsMultilineCommitMessage .Message}}
@@ -29,7 +29,7 @@
</div>
{{if IsMultilineCommitMessage .Message}}
<pre class="commit-body tw-ml-[33px] tw-hidden" data-singular-commit-body-for="{{$tag}}">
- {{- ctx.RenderUtils.RenderCommitBody .Message ($.comment.Issue.PullRequest.BaseRepo.ComposeCommentMetas ctx) -}}
+ {{- ctx.RenderUtils.RenderCommitBody .Message $.comment.Issue.PullRequest.BaseRepo -}}
</pre>
{{end}}
{{end}}
diff --git a/templates/repo/diff/compare.tmpl b/templates/repo/diff/compare.tmpl
index 6f16ce3bd8..4e8ad1326c 100644
--- a/templates/repo/diff/compare.tmpl
+++ b/templates/repo/diff/compare.tmpl
@@ -189,7 +189,7 @@
<div class="ui segment flex-text-block tw-gap-4">
{{template "shared/issueicon" .}}
<div class="issue-title tw-break-anywhere">
- {{ctx.RenderUtils.RenderIssueTitle .PullRequest.Issue.Title ($.Repository.ComposeCommentMetas ctx)}}
+ {{ctx.RenderUtils.RenderIssueTitle .PullRequest.Issue.Title $.Repository}}
<span class="index">#{{.PullRequest.Issue.Index}}</span>
</div>
<a href="{{$.RepoLink}}/pulls/{{.PullRequest.Issue.Index}}" class="ui compact button primary">
diff --git a/templates/repo/graph/commits.tmpl b/templates/repo/graph/commits.tmpl
index 630c4579ea..34167cadc0 100644
--- a/templates/repo/graph/commits.tmpl
+++ b/templates/repo/graph/commits.tmpl
@@ -8,7 +8,7 @@
{{template "repo/commit_sign_badge" dict "Commit" $commit.Commit "CommitBaseLink" (print $.RepoLink "/commit") "CommitSignVerification" $commit.Verification}}
<span class="message tw-inline-block gt-ellipsis">
- <span>{{ctx.RenderUtils.RenderCommitMessage $commit.Subject ($.Repository.ComposeCommentMetas ctx)}}</span>
+ <span>{{ctx.RenderUtils.RenderCommitMessage $commit.Subject $.Repository}}</span>
</span>
<span class="commit-refs flex-text-inline">
diff --git a/templates/repo/issue/view_title.tmpl b/templates/repo/issue/view_title.tmpl
index a4be598540..b8f28dfd9b 100644
--- a/templates/repo/issue/view_title.tmpl
+++ b/templates/repo/issue/view_title.tmpl
@@ -13,7 +13,7 @@
{{$canEditIssueTitle := and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .Repository.IsArchived)}}
<div class="issue-title" id="issue-title-display">
<h1 class="tw-break-anywhere">
- {{ctx.RenderUtils.RenderIssueTitle .Issue.Title ($.Repository.ComposeCommentMetas ctx)}}
+ {{ctx.RenderUtils.RenderIssueTitle .Issue.Title $.Repository}}
<span class="index">#{{.Issue.Index}}</span>
</h1>
<div class="issue-title-buttons">
diff --git a/templates/repo/latest_commit.tmpl b/templates/repo/latest_commit.tmpl
index da457e423a..cff338949f 100644
--- a/templates/repo/latest_commit.tmpl
+++ b/templates/repo/latest_commit.tmpl
@@ -21,10 +21,10 @@
{{template "repo/commit_statuses" dict "Status" .LatestCommitStatus "Statuses" .LatestCommitStatuses}}
{{$commitLink:= printf "%s/commit/%s" .RepoLink (PathEscape .LatestCommit.ID.String)}}
- <span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span class="message-wrapper">{{ctx.RenderUtils.RenderCommitMessageLinkSubject .LatestCommit.Message $commitLink ($.Repository.ComposeCommentMetas ctx)}}</span>
+ <span class="grey commit-summary" title="{{.LatestCommit.Summary}}"><span class="message-wrapper">{{ctx.RenderUtils.RenderCommitMessageLinkSubject .LatestCommit.Message $commitLink $.Repository}}</span>
{{if IsMultilineCommitMessage .LatestCommit.Message}}
<button class="ui button ellipsis-button" aria-expanded="false" data-global-click="onRepoEllipsisButtonClick">...</button>
- <pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody .LatestCommit.Message ($.Repository.ComposeCommentMetas ctx)}}</pre>
+ <pre class="commit-body tw-hidden">{{ctx.RenderUtils.RenderCommitBody .LatestCommit.Message $.Repository}}</pre>
{{end}}
</span>
{{end}}
diff --git a/templates/repo/settings/collaboration.tmpl b/templates/repo/settings/collaboration.tmpl
index 4461398258..7064b4c7ba 100644
--- a/templates/repo/settings/collaboration.tmpl
+++ b/templates/repo/settings/collaboration.tmpl
@@ -90,7 +90,7 @@
<form class="ui form" id="repo-collab-team-form" action="{{.Link}}/team" method="post">
{{.CsrfTokenHtml}}
<div id="search-team-box" class="ui search input tw-align-middle" data-org-name="{{.OrgName}}">
- <input class="prompt" name="team" placeholder="{{ctx.Locale.Tr "search.team_kind"}}" autocomplete="off" autofocus required>
+ <input class="prompt" name="team" placeholder="{{ctx.Locale.Tr "search.team_kind"}}" autocomplete="off" required>
</div>
<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.add_team"}}</button>
</form>
diff --git a/templates/repo/settings/options.tmpl b/templates/repo/settings/options.tmpl
index 4d61604612..fc42056e0a 100644
--- a/templates/repo/settings/options.tmpl
+++ b/templates/repo/settings/options.tmpl
@@ -10,7 +10,7 @@
<input type="hidden" name="action" value="update">
<div class="required field {{if .Err_RepoName}}error{{end}}">
<label>{{ctx.Locale.Tr "repo.repo_name"}}</label>
- <input name="repo_name" value="{{.Repository.Name}}" data-repo-name="{{.Repository.Name}}" autofocus required>
+ <input name="repo_name" value="{{.Repository.Name}}" data-repo-name="{{.Repository.Name}}" required>
</div>
<div class="inline field">
<label>{{ctx.Locale.Tr "repo.repo_size"}}</label>
diff --git a/templates/repo/view.tmpl b/templates/repo/view.tmpl
index c3d562003d..85d09d03a1 100644
--- a/templates/repo/view.tmpl
+++ b/templates/repo/view.tmpl
@@ -17,7 +17,7 @@
{{template "repo/code/recently_pushed_new_branches" .}}
<div class="repo-view-container">
- <div class="repo-view-file-tree-container not-mobile {{if not .UserSettingCodeViewShowFileTree}}tw-hidden{{end}}" {{if .IsSigned}}data-user-is-signed-in{{end}}>
+ <div class="tw-flex tw-flex-col repo-view-file-tree-container not-mobile {{if not .UserSettingCodeViewShowFileTree}}tw-hidden{{end}}" {{if .IsSigned}}data-user-is-signed-in{{end}}>
{{template "repo/view_file_tree" .}}
</div>
<div class="repo-view-content">
diff --git a/templates/repo/view_list.tmpl b/templates/repo/view_list.tmpl
index cd832498b4..c8ee059e89 100644
--- a/templates/repo/view_list.tmpl
+++ b/templates/repo/view_list.tmpl
@@ -47,7 +47,7 @@
<div class="repo-file-cell message loading-icon-2px">
{{if $commit}}
{{$commitLink := printf "%s/commit/%s" $.RepoLink (PathEscape $commit.ID.String)}}
- {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink ($.Repository.ComposeCommentMetas ctx)}}
+ {{ctx.RenderUtils.RenderCommitMessageLinkSubject $commit.Message $commitLink $.Repository}}
{{else}}
… {{/* will be loaded again by LastCommitLoaderURL */}}
{{end}}
diff --git a/templates/shared/secrets/add_list.tmpl b/templates/shared/secrets/add_list.tmpl
index 977f308b71..a4ef2e5384 100644
--- a/templates/shared/secrets/add_list.tmpl
+++ b/templates/shared/secrets/add_list.tmpl
@@ -4,9 +4,13 @@
<button class="ui primary tiny button show-modal"
data-modal="#add-secret-modal"
data-modal-form.action="{{.Link}}"
- data-modal-header="{{ctx.Locale.Tr "secrets.creation"}}"
+ data-modal-header="{{ctx.Locale.Tr "secrets.add_secret"}}"
+ data-modal-secret-name.value=""
+ data-modal-secret-name.read-only="false"
+ data-modal-secret-data=""
+ data-modal-secret-description=""
>
- {{ctx.Locale.Tr "secrets.creation"}}
+ {{ctx.Locale.Tr "secrets.add_secret"}}
</button>
</div>
</h4>
@@ -33,6 +37,18 @@
<span class="color-text-light-2">
{{ctx.Locale.Tr "settings.added_on" (DateUtils.AbsoluteShort .CreatedUnix)}}
</span>
+ <button class="ui btn interact-bg show-modal tw-p-2"
+ data-modal="#add-secret-modal"
+ data-modal-form.action="{{$.Link}}"
+ data-modal-header="{{ctx.Locale.Tr "secrets.edit_secret"}}"
+ data-tooltip-content="{{ctx.Locale.Tr "secrets.edit_secret"}}"
+ data-modal-secret-name.value="{{.Name}}"
+ data-modal-secret-name.read-only="true"
+ data-modal-secret-data=""
+ data-modal-secret-description="{{if .Description}}{{.Description}}{{end}}"
+ >
+ {{svg "octicon-pencil"}}
+ </button>
<button class="ui btn interact-bg link-action tw-p-2"
data-url="{{$.Link}}/delete?id={{.ID}}"
data-modal-confirm="{{ctx.Locale.Tr "secrets.deletion.description"}}"
@@ -51,9 +67,7 @@
{{/* Add secret dialog */}}
<div class="ui small modal" id="add-secret-modal">
- <div class="header">
- <span id="actions-modal-header"></span>
- </div>
+ <div class="header"></div>
<form class="ui form form-fetch-action" method="post">
<div class="content">
{{.CsrfTokenHtml}}
diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl
index 97291fc42d..4ee12fa783 100644
--- a/templates/user/dashboard/feeds.tmpl
+++ b/templates/user/dashboard/feeds.tmpl
@@ -94,7 +94,7 @@
<img alt class="ui avatar" src="{{$push.AvatarLink ctx .AuthorEmail}}" title="{{.AuthorName}}" width="16" height="16">
<a class="ui sha label" href="{{$commitLink}}">{{ShortSha .Sha1}}</a>
<span class="text truncate">
- {{ctx.RenderUtils.RenderCommitMessage .Message ($repo.ComposeCommentMetas ctx)}}
+ {{ctx.RenderUtils.RenderCommitMessage .Message $repo}}
</span>
</div>
{{end}}
diff --git a/templates/user/settings/profile.tmpl b/templates/user/settings/profile.tmpl
index 03c3c18f28..d8e5e27b89 100644
--- a/templates/user/settings/profile.tmpl
+++ b/templates/user/settings/profile.tmpl
@@ -12,7 +12,7 @@
<span class="text red tw-hidden" id="name-change-prompt"> {{ctx.Locale.Tr "settings.change_username_prompt"}}</span>
<span class="text red tw-hidden" id="name-change-redirect-prompt"> {{ctx.Locale.Tr "settings.change_username_redirect_prompt"}}</span>
</label>
- <input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" autofocus required {{if or (not .SignedUser.IsLocal) ($.UserDisabledFeatures.Contains "change_username") .IsReverseProxy}}disabled{{end}} maxlength="40">
+ <input id="username" name="name" value="{{.SignedUser.Name}}" data-name="{{.SignedUser.Name}}" required {{if or (not .SignedUser.IsLocal) ($.UserDisabledFeatures.Contains "change_username") .IsReverseProxy}}disabled{{end}} maxlength="40">
{{if or (not .SignedUser.IsLocal) ($.UserDisabledFeatures.Contains "change_username") .IsReverseProxy}}
<p class="help text blue">{{ctx.Locale.Tr "settings.password_username_disabled"}}</p>
{{end}}
diff --git a/tests/integration/change_default_branch_test.go b/tests/integration/change_default_branch_test.go
index 729eb1e4ce..9b61cff9fd 100644
--- a/tests/integration/change_default_branch_test.go
+++ b/tests/integration/change_default_branch_test.go
@@ -6,12 +6,16 @@ package integration
import (
"fmt"
"net/http"
+ "strconv"
"testing"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/tests"
+
+ "github.com/stretchr/testify/assert"
)
func TestChangeDefaultBranch(t *testing.T) {
@@ -38,3 +42,96 @@ func TestChangeDefaultBranch(t *testing.T) {
})
session.MakeRequest(t, req, http.StatusNotFound)
}
+
+func checkDivergence(t *testing.T, session *TestSession, branchesURL, expectedDefaultBranch string, expectedBranchToDivergence map[string]git.DivergeObject) {
+ req := NewRequest(t, "GET", branchesURL)
+ resp := session.MakeRequest(t, req, http.StatusOK)
+
+ htmlDoc := NewHTMLParser(t, resp.Body)
+
+ branchNodes := htmlDoc.doc.Find(".branch-name").Nodes
+ branchNames := []string{}
+ for _, node := range branchNodes {
+ branchNames = append(branchNames, node.FirstChild.Data)
+ }
+
+ expectBranchCount := len(expectedBranchToDivergence)
+
+ assert.Len(t, branchNames, expectBranchCount+1)
+ assert.Equal(t, expectedDefaultBranch, branchNames[0])
+
+ allCountBehindNodes := htmlDoc.doc.Find(".count-behind").Nodes
+ allCountAheadNodes := htmlDoc.doc.Find(".count-ahead").Nodes
+
+ assert.Len(t, allCountAheadNodes, expectBranchCount)
+ assert.Len(t, allCountBehindNodes, expectBranchCount)
+
+ for i := range expectBranchCount {
+ branchName := branchNames[i+1]
+ assert.Contains(t, expectedBranchToDivergence, branchName)
+
+ expectedCountAhead := expectedBranchToDivergence[branchName].Ahead
+ expectedCountBehind := expectedBranchToDivergence[branchName].Behind
+ countAhead, err := strconv.Atoi(allCountAheadNodes[i].FirstChild.Data)
+ assert.NoError(t, err)
+ countBehind, err := strconv.Atoi(allCountBehindNodes[i].FirstChild.Data)
+ assert.NoError(t, err)
+
+ assert.Equal(t, expectedCountAhead, countAhead)
+ assert.Equal(t, expectedCountBehind, countBehind)
+ }
+}
+
+func TestChangeDefaultBranchDivergence(t *testing.T) {
+ defer tests.PrepareTestEnv(t)()
+ repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 16})
+ owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
+
+ session := loginUser(t, owner.Name)
+ branchesURL := fmt.Sprintf("/%s/%s/branches", owner.Name, repo.Name)
+ settingsBranchesURL := fmt.Sprintf("/%s/%s/settings/branches", owner.Name, repo.Name)
+
+ // check branch divergence before switching default branch
+ expectedBranchToDivergenceBefore := map[string]git.DivergeObject{
+ "not-signed": {
+ Ahead: 0,
+ Behind: 0,
+ },
+ "good-sign-not-yet-validated": {
+ Ahead: 0,
+ Behind: 1,
+ },
+ "good-sign": {
+ Ahead: 1,
+ Behind: 3,
+ },
+ }
+ checkDivergence(t, session, branchesURL, "master", expectedBranchToDivergenceBefore)
+
+ // switch default branch
+ newDefaultBranch := "good-sign-not-yet-validated"
+ csrf := GetUserCSRFToken(t, session)
+ req := NewRequestWithValues(t, "POST", settingsBranchesURL, map[string]string{
+ "_csrf": csrf,
+ "action": "default_branch",
+ "branch": newDefaultBranch,
+ })
+ session.MakeRequest(t, req, http.StatusSeeOther)
+
+ // check branch divergence after switching default branch
+ expectedBranchToDivergenceAfter := map[string]git.DivergeObject{
+ "master": {
+ Ahead: 1,
+ Behind: 0,
+ },
+ "not-signed": {
+ Ahead: 1,
+ Behind: 0,
+ },
+ "good-sign": {
+ Ahead: 1,
+ Behind: 2,
+ },
+ }
+ checkDivergence(t, session, branchesURL, newDefaultBranch, expectedBranchToDivergenceAfter)
+}
diff --git a/tests/integration/git_general_test.go b/tests/integration/git_general_test.go
index 34fe212d50..ed60bdb58a 100644
--- a/tests/integration/git_general_test.go
+++ b/tests/integration/git_general_test.go
@@ -11,8 +11,10 @@ import (
"net/http"
"net/url"
"os"
+ "os/exec"
"path"
"path/filepath"
+ "slices"
"strconv"
"testing"
"time"
@@ -30,6 +32,7 @@ import (
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests"
+ "github.com/kballard/go-shellquote"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -105,7 +108,12 @@ func testGitGeneral(t *testing.T, u *url.URL) {
// Setup key the user ssh key
withKeyFile(t, keyname, func(keyFile string) {
- t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile))
+ var keyID int64
+ t.Run("CreateUserKey", doAPICreateUserKey(sshContext, "test-key", keyFile, func(t *testing.T, key api.PublicKey) {
+ keyID = key.ID
+ }))
+ assert.NotZero(t, keyID)
+ t.Run("LFSAccessTest", doSSHLFSAccessTest(sshContext, keyID))
// Setup remote link
// TODO: get url from api
@@ -136,6 +144,36 @@ func testGitGeneral(t *testing.T, u *url.URL) {
})
}
+func doSSHLFSAccessTest(_ APITestContext, keyID int64) func(*testing.T) {
+ return func(t *testing.T) {
+ sshCommand := os.Getenv("GIT_SSH_COMMAND") // it is set in withKeyFile
+ sshCmdParts, err := shellquote.Split(sshCommand) // and parse the ssh command to construct some mocked arguments
+ require.NoError(t, err)
+
+ t.Run("User2AccessOwned", func(t *testing.T) {
+ sshCmdUser2Self := append(slices.Clone(sshCmdParts),
+ "-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost,
+ "git-lfs-authenticate", "user2/repo1.git", "upload", // accessible to own repo
+ )
+ cmd := exec.CommandContext(t.Context(), sshCmdUser2Self[0], sshCmdUser2Self[1:]...)
+ _, err := cmd.Output()
+ assert.NoError(t, err) // accessible, no error
+ })
+
+ t.Run("User2AccessOther", func(t *testing.T) {
+ sshCmdUser2Other := append(slices.Clone(sshCmdParts),
+ "-p", strconv.Itoa(setting.SSH.ListenPort), "git@"+setting.SSH.ListenHost,
+ "git-lfs-authenticate", "user5/repo4.git", "upload", // inaccessible to other's (user5/repo4)
+ )
+ cmd := exec.CommandContext(t.Context(), sshCmdUser2Other[0], sshCmdUser2Other[1:]...)
+ _, err := cmd.Output()
+ var errExit *exec.ExitError
+ require.ErrorAs(t, err, &errExit) // inaccessible, error
+ assert.Contains(t, string(errExit.Stderr), fmt.Sprintf("User: 2:user2 with Key: %d:test-key is not authorized to write to user5/repo4.", keyID))
+ })
+ }
+}
+
func ensureAnonymousClone(t *testing.T, u *url.URL) {
dstLocalPath := t.TempDir()
t.Run("CloneAnonymous", doGitClone(dstLocalPath, u))
diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go
index dee0aa6176..504d2adacc 100644
--- a/tests/integration/repo_commits_test.go
+++ b/tests/integration/repo_commits_test.go
@@ -12,8 +12,6 @@ import (
"testing"
auth_model "code.gitea.io/gitea/models/auth"
- "code.gitea.io/gitea/models/unittest"
- user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
@@ -40,40 +38,24 @@ func TestRepoCommits(t *testing.T) {
func Test_ReposGitCommitListNotMaster(t *testing.T) {
defer tests.PrepareTestEnv(t)()
- user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
- // Login as User2.
- session := loginUser(t, user.Name)
-
- // Test getting commits (Page 1)
- req := NewRequestf(t, "GET", "/%s/repo16/commits/branch/master", user.Name)
+ session := loginUser(t, "user2")
+ req := NewRequest(t, "GET", "/user2/repo16/commits/branch/master")
resp := session.MakeRequest(t, req, http.StatusOK)
doc := NewHTMLParser(t, resp.Body)
- commits := []string{}
+ var commits []string
doc.doc.Find("#commits-table .commit-id-short").Each(func(i int, s *goquery.Selection) {
- commitURL, exists := s.Attr("href")
- assert.True(t, exists)
- assert.NotEmpty(t, commitURL)
+ commitURL, _ := s.Attr("href")
commits = append(commits, path.Base(commitURL))
})
+ assert.Equal(t, []string{"69554a64c1e6030f051e5c3f94bfbd773cd6a324", "27566bd5738fc8b4e3fef3c5e72cce608537bd95", "5099b81332712fe655e34e8dd63574f503f61811"}, commits)
- assert.Len(t, commits, 3)
- assert.Equal(t, "69554a64c1e6030f051e5c3f94bfbd773cd6a324", commits[0])
- assert.Equal(t, "27566bd5738fc8b4e3fef3c5e72cce608537bd95", commits[1])
- assert.Equal(t, "5099b81332712fe655e34e8dd63574f503f61811", commits[2])
-
- userNames := []string{}
+ var userHrefs []string
doc.doc.Find("#commits-table .author-wrapper").Each(func(i int, s *goquery.Selection) {
- userPath, exists := s.Attr("href")
- assert.True(t, exists)
- assert.NotEmpty(t, userPath)
- userNames = append(userNames, path.Base(userPath))
+ userHref, _ := s.Attr("href")
+ userHrefs = append(userHrefs, userHref)
})
-
- assert.Len(t, userNames, 3)
- assert.Equal(t, "User2", userNames[0])
- assert.Equal(t, "user21", userNames[1])
- assert.Equal(t, "User2", userNames[2])
+ assert.Equal(t, []string{"/user2", "/user21", "/user2"}, userHrefs)
}
func doTestRepoCommitWithStatus(t *testing.T, state string, classes ...string) {
diff --git a/web_src/css/editor/combomarkdowneditor.css b/web_src/css/editor/combomarkdowneditor.css
index 835286b795..046010c6c8 100644
--- a/web_src/css/editor/combomarkdowneditor.css
+++ b/web_src/css/editor/combomarkdowneditor.css
@@ -100,67 +100,3 @@
border-bottom: 1px solid var(--color-secondary);
padding-bottom: 1rem;
}
-
-text-expander {
- display: block;
- position: relative;
-}
-
-text-expander .suggestions {
- position: absolute;
- min-width: 180px;
- padding: 0;
- margin-top: 24px;
- list-style: none;
- background: var(--color-box-body);
- border-radius: var(--border-radius);
- border: 1px solid var(--color-secondary);
- box-shadow: 0 .5rem 1rem var(--color-shadow);
- z-index: 100; /* needs to be > 20 to be on top of dropzone's .dz-details */
-}
-
-text-expander .suggestions li {
- display: flex;
- align-items: center;
- cursor: pointer;
- padding: 4px 8px;
- font-weight: var(--font-weight-medium);
-}
-
-text-expander .suggestions li + li {
- border-top: 1px solid var(--color-secondary-alpha-40);
-}
-
-text-expander .suggestions li:first-child {
- border-radius: var(--border-radius) var(--border-radius) 0 0;
-}
-
-text-expander .suggestions li:last-child {
- border-radius: 0 0 var(--border-radius) var(--border-radius);
-}
-
-text-expander .suggestions li:only-child {
- border-radius: var(--border-radius);
-}
-
-text-expander .suggestions li:hover {
- background: var(--color-hover);
-}
-
-text-expander .suggestions .fullname {
- font-weight: var(--font-weight-normal);
- margin-left: 4px;
- color: var(--color-text-light-1);
-}
-
-text-expander .suggestions li[aria-selected="true"],
-text-expander .suggestions li[aria-selected="true"] span {
- background: var(--color-primary);
- color: var(--color-primary-contrast);
-}
-
-text-expander .suggestions img {
- width: 24px;
- height: 24px;
- margin-right: 8px;
-}
diff --git a/web_src/css/features/expander.css b/web_src/css/features/expander.css
new file mode 100644
index 0000000000..f560b2a9fd
--- /dev/null
+++ b/web_src/css/features/expander.css
@@ -0,0 +1,96 @@
+text-expander .suggestions,
+.tribute-container {
+ position: absolute;
+ max-height: min(300px, 95vh);
+ max-width: min(500px, 95vw);
+ overflow-x: hidden;
+ overflow-y: auto;
+ white-space: nowrap;
+ background: var(--color-menu);
+ box-shadow: 0 6px 18px var(--color-shadow);
+ border-radius: var(--border-radius);
+ border: 1px solid var(--color-secondary);
+ z-index: 100; /* needs to be > 20 to be on top of dropzone's .dz-details */
+}
+
+text-expander {
+ display: block;
+ position: relative;
+}
+
+text-expander .suggestions {
+ padding: 0;
+ margin-top: 24px;
+ list-style: none;
+}
+
+text-expander .suggestions li,
+.tribute-item {
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+ gap: 6px;
+ font-weight: var(--font-weight-medium);
+}
+
+text-expander .suggestions li,
+.tribute-container li {
+ padding: 3px 6px;
+}
+
+text-expander .suggestions li + li,
+.tribute-container li + li {
+ border-top: 1px solid var(--color-secondary);
+}
+
+text-expander .suggestions li:first-child {
+ border-radius: var(--border-radius) var(--border-radius) 0 0;
+}
+
+text-expander .suggestions li:last-child {
+ border-radius: 0 0 var(--border-radius) var(--border-radius);
+}
+
+text-expander .suggestions li:only-child {
+ border-radius: var(--border-radius);
+}
+
+text-expander .suggestions .fullname,
+.tribute-container li .fullname {
+ font-weight: var(--font-weight-normal);
+ color: var(--color-text-light-1);
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+text-expander .suggestions li:hover,
+text-expander .suggestions li:hover *,
+text-expander .suggestions li[aria-selected="true"],
+text-expander .suggestions li[aria-selected="true"] *,
+.tribute-container li.highlight,
+.tribute-container li.highlight * {
+ background: var(--color-primary);
+ color: var(--color-primary-contrast);
+}
+
+text-expander .suggestions img,
+.tribute-item img {
+ width: 21px;
+ height: 21px;
+ object-fit: contain;
+ aspect-ratio: 1;
+}
+
+.tribute-container {
+ display: block;
+}
+
+.tribute-container ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+.tribute-container li.no-match {
+ cursor: default;
+}
diff --git a/web_src/css/features/tribute.css b/web_src/css/features/tribute.css
deleted file mode 100644
index 99a026b9bc..0000000000
--- a/web_src/css/features/tribute.css
+++ /dev/null
@@ -1,32 +0,0 @@
-@import "tributejs/dist/tribute.css";
-
-.tribute-container {
- box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.25);
- border-radius: var(--border-radius);
-}
-
-.tribute-container ul {
- margin-top: 0 !important;
- background: var(--color-body) !important;
-}
-
-.tribute-container li {
- padding: 3px 0.5rem !important;
-}
-
-.tribute-container li span.fullname {
- font-weight: var(--font-weight-normal);
- font-size: 0.8rem;
-}
-
-.tribute-container li.highlight,
-.tribute-container li:hover {
- background: var(--color-primary) !important;
- color: var(--color-primary-contrast) !important;
-}
-
-.tribute-item {
- display: flex;
- align-items: center;
- gap: 6px;
-}
diff --git a/web_src/css/index.css b/web_src/css/index.css
index 84795d6d27..c20aa028e4 100644
--- a/web_src/css/index.css
+++ b/web_src/css/index.css
@@ -39,7 +39,7 @@
@import "./features/imagediff.css";
@import "./features/codeeditor.css";
@import "./features/projects.css";
-@import "./features/tribute.css";
+@import "./features/expander.css";
@import "./features/cropper.css";
@import "./features/console.css";
diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css
index 69c454d611..61b0a1f962 100644
--- a/web_src/css/repo/home.css
+++ b/web_src/css/repo/home.css
@@ -58,6 +58,11 @@
flex: 0 0 15%;
min-width: 0;
max-height: 100vh;
+ position: sticky;
+ top: 0;
+ bottom: 0;
+ height: 100%;
+ overflow-y: hidden;
}
.repo-view-content {
diff --git a/web_src/js/features/common-button.test.ts b/web_src/js/features/common-button.test.ts
new file mode 100644
index 0000000000..f41bafbc79
--- /dev/null
+++ b/web_src/js/features/common-button.test.ts
@@ -0,0 +1,14 @@
+import {assignElementProperty} from './common-button.ts';
+
+test('assignElementProperty', () => {
+ const elForm = document.createElement('form');
+ assignElementProperty(elForm, 'action', '/test-link');
+ expect(elForm.action).contains('/test-link'); // the DOM always returns absolute URL
+ assignElementProperty(elForm, 'text-content', 'dummy');
+ expect(elForm.textContent).toBe('dummy');
+
+ const elInput = document.createElement('input');
+ expect(elInput.readOnly).toBe(false);
+ assignElementProperty(elInput, 'read-only', 'true');
+ expect(elInput.readOnly).toBe(true);
+});
diff --git a/web_src/js/features/common-button.ts b/web_src/js/features/common-button.ts
index 003bfbce5d..ae399e48b3 100644
--- a/web_src/js/features/common-button.ts
+++ b/web_src/js/features/common-button.ts
@@ -1,5 +1,5 @@
import {POST} from '../modules/fetch.ts';
-import {addDelegatedEventListener, hideElem, showElem, toggleElem} from '../utils/dom.ts';
+import {addDelegatedEventListener, hideElem, isElemVisible, showElem, toggleElem} from '../utils/dom.ts';
import {fomanticQuery} from '../modules/fomantic/base.ts';
import {camelize} from 'vue';
@@ -79,10 +79,11 @@ function onShowPanelClick(el: HTMLElement, e: MouseEvent) {
// if it has "toggle" class, it toggles the panel
e.preventDefault();
const sel = el.getAttribute('data-panel');
- if (el.classList.contains('toggle')) {
- toggleElem(sel);
- } else {
- showElem(sel);
+ const elems = el.classList.contains('toggle') ? toggleElem(sel) : showElem(sel);
+ for (const elem of elems) {
+ if (isElemVisible(elem as HTMLElement)) {
+ elem.querySelector<HTMLElement>('[autofocus]')?.focus();
+ }
}
}
@@ -102,6 +103,21 @@ function onHidePanelClick(el: HTMLElement, e: MouseEvent) {
throw new Error('no panel to hide'); // should never happen, otherwise there is a bug in code
}
+export function assignElementProperty(el: any, name: string, val: string) {
+ name = camelize(name);
+ const old = el[name];
+ if (typeof old === 'boolean') {
+ el[name] = val === 'true';
+ } else if (typeof old === 'number') {
+ el[name] = parseFloat(val);
+ } else if (typeof old === 'string') {
+ el[name] = val;
+ } else {
+ // in the future, we could introduce a better typing system like `data-modal-form.action:string="..."`
+ throw new Error(`cannot assign element property ${name} by value ${val}`);
+ }
+}
+
function onShowModalClick(el: HTMLElement, e: MouseEvent) {
// A ".show-modal" button will show a modal dialog defined by its "data-modal" attribute.
// Each "data-modal-{target}" attribute will be filled to target element's value or text-content.
@@ -109,7 +125,7 @@ function onShowModalClick(el: HTMLElement, e: MouseEvent) {
// * Then, try to query '[name=target]'
// * Then, try to query '.target'
// * Then, try to query 'target' as HTML tag
- // If there is a ".{attr}" part like "data-modal-form.action", then the form's "action" attribute will be set.
+ // If there is a ".{prop-name}" part like "data-modal-form.action", the "form" element's "action" property will be set, the "prop-name" will be camel-cased to "propName".
e.preventDefault();
const modalSelector = el.getAttribute('data-modal');
const elModal = document.querySelector(modalSelector);
@@ -122,7 +138,7 @@ function onShowModalClick(el: HTMLElement, e: MouseEvent) {
}
const attrTargetCombo = attrib.name.substring(modalAttrPrefix.length);
- const [attrTargetName, attrTargetAttr] = attrTargetCombo.split('.');
+ const [attrTargetName, attrTargetProp] = attrTargetCombo.split('.');
// try to find target by: "#target" -> "[name=target]" -> ".target" -> "<target> tag"
const attrTarget = elModal.querySelector(`#${attrTargetName}`) ||
elModal.querySelector(`[name=${attrTargetName}]`) ||
@@ -133,8 +149,8 @@ function onShowModalClick(el: HTMLElement, e: MouseEvent) {
continue;
}
- if (attrTargetAttr) {
- (attrTarget as any)[camelize(attrTargetAttr)] = attrib.value;
+ if (attrTargetProp) {
+ assignElementProperty(attrTarget, attrTargetProp, attrib.value);
} else if (attrTarget.matches('input, textarea')) {
(attrTarget as HTMLInputElement | HTMLTextAreaElement).value = attrib.value; // FIXME: add more supports like checkbox
} else {
diff --git a/web_src/js/features/common-issue-list.ts b/web_src/js/features/common-issue-list.ts
index e207364794..037529bd10 100644
--- a/web_src/js/features/common-issue-list.ts
+++ b/web_src/js/features/common-issue-list.ts
@@ -1,4 +1,4 @@
-import {isElemHidden, onInputDebounce, submitEventSubmitter, toggleElem} from '../utils/dom.ts';
+import {isElemVisible, onInputDebounce, submitEventSubmitter, toggleElem} from '../utils/dom.ts';
import {GET} from '../modules/fetch.ts';
const {appSubUrl} = window.config;
@@ -28,7 +28,7 @@ export function parseIssueListQuickGotoLink(repoLink: string, searchText: string
}
export function initCommonIssueListQuickGoto() {
- const goto = document.querySelector('#issue-list-quick-goto');
+ const goto = document.querySelector<HTMLElement>('#issue-list-quick-goto');
if (!goto) return;
const form = goto.closest('form');
@@ -37,7 +37,7 @@ export function initCommonIssueListQuickGoto() {
form.addEventListener('submit', (e) => {
// if there is no goto button, or the form is submitted by non-quick-goto elements, submit the form directly
- let doQuickGoto = !isElemHidden(goto);
+ let doQuickGoto = isElemVisible(goto);
const submitter = submitEventSubmitter(e);
if (submitter !== form && submitter !== input && submitter !== goto) doQuickGoto = false;
if (!doQuickGoto) return;
diff --git a/web_src/js/features/comp/TextExpander.ts b/web_src/js/features/comp/TextExpander.ts
index 5be234629d..2d79fe5029 100644
--- a/web_src/js/features/comp/TextExpander.ts
+++ b/web_src/js/features/comp/TextExpander.ts
@@ -97,6 +97,7 @@ export function initTextExpander(expander: TextExpanderElement) {
li.append(img);
const nameSpan = document.createElement('span');
+ nameSpan.classList.add('name');
nameSpan.textContent = name;
li.append(nameSpan);
diff --git a/web_src/js/features/repo-issue-list.ts b/web_src/js/features/repo-issue-list.ts
index 8cd4483357..3ea5fb70c0 100644
--- a/web_src/js/features/repo-issue-list.ts
+++ b/web_src/js/features/repo-issue-list.ts
@@ -1,5 +1,5 @@
import {updateIssuesMeta} from './repo-common.ts';
-import {toggleElem, isElemHidden, queryElems} from '../utils/dom.ts';
+import {toggleElem, queryElems, isElemVisible} from '../utils/dom.ts';
import {htmlEscape} from 'escape-goat';
import {confirmModal} from './comp/ConfirmModal.ts';
import {showErrorToast} from '../modules/toast.ts';
@@ -33,8 +33,8 @@ function initRepoIssueListCheckboxes() {
toggleElem('#issue-filters', !anyChecked);
toggleElem('#issue-actions', anyChecked);
// there are two panels but only one select-all checkbox, so move the checkbox to the visible panel
- const panels = document.querySelectorAll('#issue-filters, #issue-actions');
- const visiblePanel = Array.from(panels).find((el) => !isElemHidden(el));
+ const panels = document.querySelectorAll<HTMLElement>('#issue-filters, #issue-actions');
+ const visiblePanel = Array.from(panels).find((el) => isElemVisible(el));
const toolbarLeft = visiblePanel.querySelector('.issue-list-toolbar-left');
toolbarLeft.prepend(issueSelectAll);
};
diff --git a/web_src/js/utils/dom.test.ts b/web_src/js/utils/dom.test.ts
index 6a3af91556..057ea9808c 100644
--- a/web_src/js/utils/dom.test.ts
+++ b/web_src/js/utils/dom.test.ts
@@ -25,10 +25,14 @@ test('createElementFromAttrs', () => {
});
test('querySingleVisibleElem', () => {
- let el = createElementFromHTML('<div><span>foo</span></div>');
+ let el = createElementFromHTML('<div></div>');
+ expect(querySingleVisibleElem(el, 'span')).toBeNull();
+ el = createElementFromHTML('<div><span>foo</span></div>');
expect(querySingleVisibleElem(el, 'span').textContent).toEqual('foo');
el = createElementFromHTML('<div><span style="display: none;">foo</span><span>bar</span></div>');
expect(querySingleVisibleElem(el, 'span').textContent).toEqual('bar');
+ el = createElementFromHTML('<div><span class="some-class tw-hidden">foo</span><span>bar</span></div>');
+ expect(querySingleVisibleElem(el, 'span').textContent).toEqual('bar');
el = createElementFromHTML('<div><span>foo</span><span>bar</span></div>');
expect(() => querySingleVisibleElem(el, 'span')).toThrowError('Expected exactly one visible element');
});
diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts
index 4386d38632..83a0d9c8df 100644
--- a/web_src/js/utils/dom.ts
+++ b/web_src/js/utils/dom.ts
@@ -9,24 +9,24 @@ type ElementsCallback<T extends Element> = (el: T) => Promisable<any>;
type ElementsCallbackWithArgs = (el: Element, ...args: any[]) => Promisable<any>;
export type DOMEvent<E extends Event, T extends Element = HTMLElement> = E & { target: Partial<T>; };
-function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]) {
+function elementsCall(el: ElementArg, func: ElementsCallbackWithArgs, ...args: any[]): ArrayLikeIterable<Element> {
if (typeof el === 'string' || el instanceof String) {
el = document.querySelectorAll(el as string);
}
if (el instanceof Node) {
func(el, ...args);
+ return [el];
} else if (el.length !== undefined) {
// this works for: NodeList, HTMLCollection, Array, jQuery
- for (const e of (el as ArrayLikeIterable<Element>)) {
- func(e, ...args);
- }
- } else {
- throw new Error('invalid argument to be shown/hidden');
+ const elems = el as ArrayLikeIterable<Element>;
+ for (const elem of elems) func(elem, ...args);
+ return elems;
}
+ throw new Error('invalid argument to be shown/hidden');
}
-export function toggleClass(el: ElementArg, className: string, force?: boolean) {
- elementsCall(el, (e: Element) => {
+export function toggleClass(el: ElementArg, className: string, force?: boolean): ArrayLikeIterable<Element> {
+ return elementsCall(el, (e: Element) => {
if (force === true) {
e.classList.add(className);
} else if (force === false) {
@@ -43,23 +43,16 @@ export function toggleClass(el: ElementArg, className: string, force?: boolean)
* @param el ElementArg
* @param force force=true to show or force=false to hide, undefined to toggle
*/
-export function toggleElem(el: ElementArg, force?: boolean) {
- toggleClass(el, 'tw-hidden', force === undefined ? force : !force);
-}
-
-export function showElem(el: ElementArg) {
- toggleElem(el, true);
+export function toggleElem(el: ElementArg, force?: boolean): ArrayLikeIterable<Element> {
+ return toggleClass(el, 'tw-hidden', force === undefined ? force : !force);
}
-export function hideElem(el: ElementArg) {
- toggleElem(el, false);
+export function showElem(el: ElementArg): ArrayLikeIterable<Element> {
+ return toggleElem(el, true);
}
-export function isElemHidden(el: ElementArg) {
- const res: boolean[] = [];
- elementsCall(el, (e) => res.push(e.classList.contains('tw-hidden')));
- if (res.length > 1) throw new Error(`isElemHidden doesn't work for multiple elements`);
- return res[0];
+export function hideElem(el: ElementArg): ArrayLikeIterable<Element> {
+ return toggleElem(el, false);
}
function applyElemsCallback<T extends Element>(elems: ArrayLikeIterable<T>, fn?: ElementsCallback<T>): ArrayLikeIterable<T> {
@@ -275,14 +268,12 @@ export function initSubmitEventPolyfill() {
document.body.addEventListener('focus', submitEventPolyfillListener);
}
-/**
- * Check if an element is visible, equivalent to jQuery's `:visible` pseudo.
- * Note: This function doesn't account for all possible visibility scenarios.
- */
-export function isElemVisible(element: HTMLElement): boolean {
- if (!element) return false;
- // checking element.style.display is not necessary for browsers, but it is required by some tests with happy-dom because happy-dom doesn't really do layout
- return Boolean((element.offsetWidth || element.offsetHeight || element.getClientRects().length) && element.style.display !== 'none');
+export function isElemVisible(el: HTMLElement): boolean {
+ // Check if an element is visible, equivalent to jQuery's `:visible` pseudo.
+ // This function DOESN'T account for all possible visibility scenarios, its behavior is covered by the tests of "querySingleVisibleElem"
+ if (!el) return false;
+ // checking el.style.display is not necessary for browsers, but it is required by some tests with happy-dom because happy-dom doesn't really do layout
+ return !el.classList.contains('tw-hidden') && Boolean((el.offsetWidth || el.offsetHeight || el.getClientRects().length) && el.style.display !== 'none');
}
// replace selected text in a textarea while preserving editor history, e.g. CTRL-Z works after this