0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-05-06 19:18:35 +02:00

Merge branch 'main' into feature/workflow-graph

Signed-off-by: Semenets V. Pavel <p.semenets@gmail.com>
This commit is contained in:
Semenets V. Pavel 2026-02-16 08:23:27 +03:00 committed by GitHub
commit 4b87e2a3b8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 442 additions and 147 deletions

View File

@ -0,0 +1,22 @@
name: cron-flake-updater
on:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0' # runs weekly on Sunday at 00:00
jobs:
nix-flake-update:
permissions:
contents: write
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: DeterminateSystems/determinate-nix-action@v3
- uses: DeterminateSystems/update-flake-lock@main
with:
pr-title: "Update Nix flake"
pr-labels: |
dependencies

View File

@ -6,3 +6,5 @@
- Before committing `go.mod` changes, run `make tidy`
- Before committing new `.go` files, add the current year into the copyright header
- Before committing any files, remove all trailing whitespace from source code lines
- Never force-push to pull request branches
- Always start issue and pull request comments with an authorship attribution

View File

@ -12,30 +12,30 @@ import (
"xorm.io/builder"
)
// BuildCaseInsensitiveLike returns a condition to check if the given value is like the given key case-insensitively.
// Handles especially SQLite correctly as UPPER there only transforms ASCII letters.
// BuildCaseInsensitiveLike returns a case-insensitive LIKE condition for the given key and value.
// Cast the search value and the database column value to the same case for case-insensitive matching.
// * SQLite: only cast ASCII chars because it doesn't handle complete Unicode case folding
// * Other databases: use database's string function, assuming that they are able to handle complete Unicode case folding correctly
func BuildCaseInsensitiveLike(key, value string) builder.Cond {
// ToLowerASCII is about 7% faster than ToUpperASCII (according to Golang's benchmark)
if setting.Database.Type.IsSQLite3() {
return builder.Like{"UPPER(" + key + ")", util.ToUpperASCII(value)}
return builder.Like{"LOWER(" + key + ")", util.ToLowerASCII(value)}
}
return builder.Like{"UPPER(" + key + ")", strings.ToUpper(value)}
return builder.Like{"LOWER(" + key + ")", strings.ToLower(value)}
}
// BuildCaseInsensitiveIn returns a condition to check if the given value is in the given values case-insensitively.
// Handles especially SQLite correctly as UPPER there only transforms ASCII letters.
// See BuildCaseInsensitiveLike for more details
func BuildCaseInsensitiveIn(key string, values []string) builder.Cond {
uppers := make([]string, 0, len(values))
incaseValues := make([]string, len(values))
caseCast := strings.ToLower
if setting.Database.Type.IsSQLite3() {
for _, value := range values {
uppers = append(uppers, util.ToUpperASCII(value))
}
} else {
for _, value := range values {
uppers = append(uppers, strings.ToUpper(value))
}
caseCast = util.ToLowerASCII
}
return builder.In("UPPER("+key+")", uppers)
for i, value := range values {
incaseValues[i] = caseCast(value)
}
return builder.In("LOWER("+key+")", incaseValues)
}
// BuilderDialect returns the xorm.Builder dialect of the engine

View File

@ -151,7 +151,7 @@ func GetRepoAssignees(ctx context.Context, repo *Repository) (_ []*user_model.Us
func GetIssuePostersWithSearch(ctx context.Context, repo *Repository, isPull bool, search string, isShowFullName bool) ([]*user_model.User, error) {
users := make([]*user_model.User, 0, 30)
var prefixCond builder.Cond = builder.Like{"lower_name", strings.ToLower(search) + "%"}
if isShowFullName {
if search != "" && isShowFullName {
prefixCond = prefixCond.Or(db.BuildCaseInsensitiveLike("full_name", "%"+search+"%"))
}

View File

@ -13,6 +13,7 @@ import (
"net/url"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"time"
@ -212,7 +213,7 @@ func (u *User) SetLastLogin() {
// GetPlaceholderEmail returns an noreply email
func (u *User) GetPlaceholderEmail() string {
return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress)
return fmt.Sprintf("%d+%s@%s", u.ID, u.LowerName, setting.Service.NoReplyAddress)
}
// GetEmail returns a noreply email, if the user has set to keep his
@ -495,10 +496,10 @@ func (u *User) ShortName(length int) string {
return util.EllipsisDisplayString(u.Name, length)
}
// IsMailable checks if a user is eligible
// to receive emails.
// IsMailable checks if a user is eligible to receive emails.
// System users like Ghost and Gitea Actions are excluded.
func (u *User) IsMailable() bool {
return u.IsActive
return u.IsActive && !u.IsGiteaActions() && !u.IsGhost()
}
// IsUserExist checks if given username exist,
@ -1197,14 +1198,18 @@ func GetUsersByEmails(ctx context.Context, emails []string) (*EmailUserMap, erro
needCheckEmails := make(container.Set[string])
needCheckUserNames := make(container.Set[string])
needCheckUserIDs := make(container.Set[int64])
noReplyAddressSuffix := "@" + strings.ToLower(setting.Service.NoReplyAddress)
for _, email := range emails {
emailLower := strings.ToLower(email)
if noReplyUserNameLower, ok := strings.CutSuffix(emailLower, noReplyAddressSuffix); ok {
needCheckUserNames.Add(noReplyUserNameLower)
needCheckEmails.Add(emailLower)
} else {
needCheckEmails.Add(emailLower)
needCheckEmails.Add(emailLower)
if localPart, ok := strings.CutSuffix(emailLower, noReplyAddressSuffix); ok {
name, id := parseLocalPartToNameID(localPart)
if id != 0 {
needCheckUserIDs.Add(id)
} else if name != "" {
needCheckUserNames.Add(name)
}
}
}
@ -1234,16 +1239,59 @@ func GetUsersByEmails(ctx context.Context, emails []string) (*EmailUserMap, erro
}
}
users := make(map[int64]*User, len(needCheckUserNames))
if err := db.GetEngine(ctx).In("lower_name", needCheckUserNames.Values()).Find(&users); err != nil {
return nil, err
usersByIDs := make(map[int64]*User)
if len(needCheckUserIDs) > 0 || len(needCheckUserNames) > 0 {
cond := builder.NewCond()
if len(needCheckUserIDs) > 0 {
cond = cond.Or(builder.In("id", needCheckUserIDs.Values()))
}
if len(needCheckUserNames) > 0 {
cond = cond.Or(builder.In("lower_name", needCheckUserNames.Values()))
}
if err := db.GetEngine(ctx).Where(cond).Find(&usersByIDs); err != nil {
return nil, err
}
}
for _, user := range users {
results[strings.ToLower(user.GetPlaceholderEmail())] = user
usersByName := make(map[string]*User)
for _, user := range usersByIDs {
usersByName[user.LowerName] = user
}
for _, email := range emails {
emailLower := strings.ToLower(email)
if _, ok := results[emailLower]; ok {
continue
}
localPart, ok := strings.CutSuffix(emailLower, noReplyAddressSuffix)
if !ok {
continue
}
name, id := parseLocalPartToNameID(localPart)
if user, ok := usersByIDs[id]; ok {
results[emailLower] = user
} else if user, ok := usersByName[name]; ok {
results[emailLower] = user
}
}
return &EmailUserMap{results}, nil
}
// parseLocalPartToNameID attempts to unparse local-part of email that's in format id+user
// returns user and id if possible
func parseLocalPartToNameID(localPart string) (string, int64) {
var id int64
idstr, name, hasPlus := strings.Cut(localPart, "+")
if hasPlus {
id, _ = strconv.ParseInt(idstr, 10, 64)
} else {
name = idstr
}
return name, id
}
// GetUserByEmail returns the user object by given e-mail if exists.
func GetUserByEmail(ctx context.Context, email string) (*User, error) {
if len(email) == 0 {
@ -1262,16 +1310,12 @@ func GetUserByEmail(ctx context.Context, email string) (*User, error) {
}
// Finally, if email address is the protected email address:
if before, ok := strings.CutSuffix(email, "@"+setting.Service.NoReplyAddress); ok {
username := before
user := &User{}
has, err := db.GetEngine(ctx).Where("lower_name=?", username).Get(user)
if err != nil {
return nil, err
}
if has {
return user, nil
if localPart, ok := strings.CutSuffix(email, strings.ToLower("@"+setting.Service.NoReplyAddress)); ok {
name, id := parseLocalPartToNameID(localPart)
if id != 0 {
return GetUserByID(ctx, id)
}
return GetUserByName(ctx, name)
}
return nil, ErrUserNotExist{Name: email}

View File

@ -51,12 +51,27 @@ func TestOAuth2Application_LoadUser(t *testing.T) {
func TestUserEmails(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
defer test.MockVariableValue(&setting.Service.NoReplyAddress, "NoReply.gitea.internal")()
t.Run("GetUserEmailsByNames", func(t *testing.T) {
// ignore none active user email
// ignore not active user email
assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(t.Context(), []string{"user8", "user9"}))
assert.ElementsMatch(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(t.Context(), []string{"user8", "user5"}))
assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(t.Context(), []string{"user8", "org7"}))
})
cases := []struct {
Email string
UID int64
}{
{"UseR1@example.com", 1},
{"user1-2@example.COM", 1},
{"USER2@" + setting.Service.NoReplyAddress, 2},
{"2+user2@" + setting.Service.NoReplyAddress, 2},
{"2+oldUser2UsernameWhichDoesNotMatterForQuery@" + setting.Service.NoReplyAddress, 2},
{"99999+badUser@" + setting.Service.NoReplyAddress, 0},
{"user4@example.com", 4},
{"no-such", 0},
}
t.Run("GetUsersByEmails", func(t *testing.T) {
defer test.MockVariableValue(&setting.Service.NoReplyAddress, "NoReply.gitea.internal")()
testGetUserByEmail := func(t *testing.T, email string, uid int64) {
@ -70,15 +85,27 @@ func TestUserEmails(t *testing.T) {
require.NotNil(t, user)
assert.Equal(t, uid, user.ID)
}
cases := []struct {
Email string
UID int64
}{
{"UseR1@example.com", 1},
{"user1-2@example.COM", 1},
{"USER2@" + setting.Service.NoReplyAddress, 2},
{"user4@example.com", 4},
{"no-such", 0},
for _, c := range cases {
t.Run(c.Email, func(t *testing.T) {
testGetUserByEmail(t, c.Email, c.UID)
})
}
t.Run("NoReplyConflict", func(t *testing.T) {
setting.Service.NoReplyAddress = "example.com"
testGetUserByEmail(t, "user1-2@example.COM", 1)
})
})
t.Run("GetUserByEmail", func(t *testing.T) {
testGetUserByEmail := func(t *testing.T, email string, uid int64) {
user, err := user_model.GetUserByEmail(t.Context(), email)
if uid == 0 {
require.Error(t, err)
assert.Nil(t, user)
} else {
require.NotNil(t, user)
assert.Equal(t, uid, user.ID)
}
}
for _, c := range cases {
t.Run(c.Email, func(t *testing.T) {

View File

@ -108,7 +108,7 @@ func GetLanguageStats(repo *git_module.Repository, commitID string) (map[string]
if (!isVendored.Has() && analyze.IsVendor(f.Name)) ||
enry.IsDotFile(f.Name) ||
(!isDocumentation.Has() && enry.IsDocumentation(f.Name)) ||
enry.IsConfiguration(f.Name) {
(!isDetectable.Has() && enry.IsConfiguration(f.Name)) {
return nil
}

View File

@ -132,7 +132,7 @@ func GetLanguageStats(repo *git.Repository, commitID string) (map[string]int64,
if (!isVendored.Has() && analyze.IsVendor(f.Name())) ||
enry.IsDotFile(f.Name()) ||
(!isDocumentation.Has() && enry.IsDocumentation(f.Name())) ||
enry.IsConfiguration(f.Name()) {
(!isDetectable.Has() && enry.IsConfiguration(f.Name())) {
continue
}

View File

@ -21,7 +21,8 @@ const mapKeyLowerPrefix = "lower/"
// chromaLexers is fully managed by us to do fast lookup for chroma lexers by file name or language name
// Don't use lexers.Get because it is very slow in many cases (iterate all rules, filepath glob match, etc.)
var chromaLexers = sync.OnceValue(func() (ret struct {
conflictingExtLangMap map[string]string
conflictingExtLangMap map[string]string
conflictingAliasLangMap map[string]string
lowerNameMap map[string]chroma.Lexer // lexer name (lang name) in lower-case
fileBaseMap map[string]chroma.Lexer
@ -36,9 +37,9 @@ var chromaLexers = sync.OnceValue(func() (ret struct {
ret.fileBaseMap = make(map[string]chroma.Lexer)
ret.fileExtMap = make(map[string]chroma.Lexer)
// Chroma has overlaps in file extension for different languages,
// Chroma has conflicts in file extension for different languages,
// When we need to do fast render, there is no way to detect the language by content,
// So we can only choose some default languages for the overlapped file extensions.
// So we can only choose some default languages for the conflicted file extensions.
ret.conflictingExtLangMap = map[string]string{
".as": "ActionScript 3", // ActionScript
".asm": "NASM", // TASM, NASM, RGBDS Assembly, Z80 Assembly
@ -71,12 +72,17 @@ var chromaLexers = sync.OnceValue(func() (ret struct {
".v": "V", // verilog
".xslt": "HTML", // XML
}
// use widely used language names as the default mapping to resolve name alias conflict
ret.conflictingAliasLangMap = map[string]string{
"hcl": "HCL", // Terraform
"v": "V", // verilog
}
isPlainPattern := func(key string) bool {
return !strings.ContainsAny(key, "*?[]") // only support simple patterns
}
setMapWithLowerKey := func(m map[string]chroma.Lexer, key string, lexer chroma.Lexer) {
setFileNameMapWithLowerKey := func(m map[string]chroma.Lexer, key string, lexer chroma.Lexer) {
if _, conflict := m[key]; conflict {
panic("duplicate key in lexer map: " + key + ", need to add it to conflictingExtLangMap")
}
@ -87,7 +93,7 @@ var chromaLexers = sync.OnceValue(func() (ret struct {
processFileName := func(fileName string, lexer chroma.Lexer) bool {
if isPlainPattern(fileName) {
// full base name match
setMapWithLowerKey(ret.fileBaseMap, fileName, lexer)
setFileNameMapWithLowerKey(ret.fileBaseMap, fileName, lexer)
return true
}
if strings.HasPrefix(fileName, "*") {
@ -96,7 +102,7 @@ var chromaLexers = sync.OnceValue(func() (ret struct {
if isPlainPattern(fileExt) {
presetName := ret.conflictingExtLangMap[fileExt]
if presetName == "" || lexer.Config().Name == presetName {
setMapWithLowerKey(ret.fileExtMap, fileExt, lexer)
setFileNameMapWithLowerKey(ret.fileExtMap, fileExt, lexer)
}
return true
}
@ -134,13 +140,30 @@ var chromaLexers = sync.OnceValue(func() (ret struct {
return patterns
}
// add lexers to our map, for fast lookup
processLexerNameAliases := func(lexer chroma.Lexer) {
cfg := lexer.Config()
lowerName := strings.ToLower(cfg.Name)
if _, conflicted := ret.lowerNameMap[lowerName]; conflicted {
panic("duplicate language name in lexer map: " + lowerName)
}
ret.lowerNameMap[lowerName] = lexer
for _, name := range cfg.Aliases {
lowerName := strings.ToLower(name)
if overriddenName, overridden := ret.conflictingAliasLangMap[lowerName]; overridden && overriddenName != cfg.Name {
continue
}
if existingLexer, conflict := ret.lowerNameMap[lowerName]; conflict && existingLexer.Config().Name != cfg.Name {
panic("duplicate alias in lexer map: " + name + ", conflict between " + existingLexer.Config().Name + " and " + cfg.Name)
}
ret.lowerNameMap[lowerName] = lexer
}
}
// the main loop: build our lookup maps for lexers
for _, lexer := range lexers.GlobalLexerRegistry.Lexers {
cfg := lexer.Config()
ret.lowerNameMap[strings.ToLower(lexer.Config().Name)] = lexer
for _, alias := range cfg.Aliases {
ret.lowerNameMap[strings.ToLower(alias)] = lexer
}
processLexerNameAliases(lexer)
for _, s := range expandGlobPatterns(cfg.Filenames) {
if !processFileName(s, lexer) {
panic("unsupported file name pattern in lexer: " + s)
@ -153,7 +176,12 @@ var chromaLexers = sync.OnceValue(func() (ret struct {
}
}
// final check: make sure the default ext-lang mapping is correct, nothing is missing
// final check: make sure the default overriding mapping is correct, nothing is missing
for lowerName, lexerName := range ret.conflictingAliasLangMap {
if lexer, ok := ret.lowerNameMap[lowerName]; !ok || lexer.Config().Name != lexerName {
panic("missing default name-lang mapping for: " + lowerName)
}
}
for ext, lexerName := range ret.conflictingExtLangMap {
if lexer, ok := ret.fileExtMap[ext]; !ok || lexer.Config().Name != lexerName {
panic("missing default ext-lang mapping for: " + ext)

View File

@ -45,7 +45,7 @@ func BenchmarkRenderCodeByLexer(b *testing.B) {
lexer := DetectChromaLexerByFileName("a.sql", "")
b.StartTimer()
for b.Loop() {
// Really slow .......
// Really slow ....... the regexp2 used by Chroma takes most of the time
// BenchmarkRenderCodeByLexer-12 22 47159038 ns/op
RenderCodeByLexer(lexer, code)
}
@ -55,13 +55,14 @@ func TestDetectChromaLexer(t *testing.T) {
globalVars().highlightMapping[".my-html"] = "HTML"
t.Cleanup(func() { delete(globalVars().highlightMapping, ".my-html") })
cases := []struct {
casesWithContent := []struct {
fileName string
language string
content string
expected string
}{
{"test.py", "", "", "Python"},
{"test.v", "", "", "V"},
{"test.v", "any-lang-name", "", "V"},
{"any-file", "javascript", "", "JavaScript"},
{"any-file", "", "/* vim: set filetype=python */", "Python"},
@ -80,11 +81,36 @@ func TestDetectChromaLexer(t *testing.T) {
{"a.sql", "", "", "SQL"},
{"dhcpd.conf", "", "", "ISCdhcpd"},
{".env.my-production", "", "", "Bash"},
{"a.hcl", "", "", "HCL"}, // not the same as Chroma, enry detects "*.hcl" as "HCL"
{"a.hcl", "HCL", "", "HCL"},
{"a.hcl", "Terraform", "", "Terraform"},
}
for _, c := range cases {
for _, c := range casesWithContent {
lexer := detectChromaLexerWithAnalyze(c.fileName, c.language, []byte(c.content))
if assert.NotNil(t, lexer, "case: %+v", c) {
assert.Equal(t, c.expected, lexer.Config().Name, "case: %+v", c)
}
}
casesNameLang := []struct {
fileName string
language string
expected string
byLang bool
}{
{"a.v", "", "V", false},
{"a.v", "V", "V", true},
{"a.v", "verilog", "verilog", true},
{"a.v", "any-lang-name", "V", false},
{"a.hcl", "", "Terraform", false}, // not the same as enry
{"a.hcl", "HCL", "HCL", true},
{"a.hcl", "Terraform", "Terraform", true},
}
for _, c := range casesNameLang {
lexer, byLang := detectChromaLexerByFileName(c.fileName, c.language)
assert.Equal(t, c.expected, lexer.Config().Name, "case: %+v", c)
assert.Equal(t, c.byLang, byLang, "case: %+v", c)
}
}

View File

@ -90,12 +90,12 @@ func CryptoRandomBytes(length int64) ([]byte, error) {
return buf, err
}
// ToUpperASCII returns s with all ASCII letters mapped to their upper case.
func ToUpperASCII(s string) string {
// ToLowerASCII returns s with all ASCII letters mapped to their lower case.
func ToLowerASCII(s string) string {
b := []byte(s)
for i, c := range b {
if 'a' <= c && c <= 'z' {
b[i] -= 'a' - 'A'
if 'A' <= c && c <= 'Z' {
b[i] += 'a' - 'A'
}
}
return string(b)

View File

@ -178,30 +178,26 @@ type StringTest struct {
in, out string
}
var upperTests = []StringTest{
var lowerTests = []StringTest{
{"", ""},
{"ONLYUPPER", "ONLYUPPER"},
{"abc", "ABC"},
{"AbC123", "ABC123"},
{"azAZ09_", "AZAZ09_"},
{"longStrinGwitHmixofsmaLLandcAps", "LONGSTRINGWITHMIXOFSMALLANDCAPS"},
{"long\u0250string\u0250with\u0250nonascii\u2C6Fchars", "LONG\u0250STRING\u0250WITH\u0250NONASCII\u2C6FCHARS"},
{"\u0250\u0250\u0250\u0250\u0250", "\u0250\u0250\u0250\u0250\u0250"},
{"a\u0080\U0010FFFF", "A\u0080\U0010FFFF"},
{"lél", "LéL"},
{"ABC", "abc"},
{"AbC123_", "abc123_"},
{"LONG\u0250string\u0250WITH\u0250non-ascii\u2C6FCHARS\u0080\uFFFF", "long\u0250string\u0250with\u0250non-ascii\u2C6Fchars\u0080\uFFFF"},
{"lél", "lél"},
{"LÉL", "lÉl"},
}
func TestToUpperASCII(t *testing.T) {
for _, tc := range upperTests {
assert.Equal(t, ToUpperASCII(tc.in), tc.out)
func TestToLowerASCII(t *testing.T) {
for _, tc := range lowerTests {
assert.Equal(t, ToLowerASCII(tc.in), tc.out)
}
}
func BenchmarkToUpper(b *testing.B) {
for _, tc := range upperTests {
func BenchmarkToLower(b *testing.B) {
for _, tc := range lowerTests {
b.Run(tc.in, func(b *testing.B) {
for b.Loop() {
ToUpperASCII(tc.in)
ToLowerASCII(tc.in)
}
})
}

View File

@ -1,10 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
For more information, please refer to <https://unlicense.org/>

View File

@ -12,6 +12,7 @@
"link_account": "アカウント連携",
"register": "登録",
"version": "バージョン",
"powered_by": "Powered by %s",
"page": "ページ",
"template": "テンプレート",
"language": "言語",
@ -31,6 +32,7 @@
"password": "パスワード",
"access_token": "アクセストークン",
"re_type": "パスワード確認",
"captcha": "CAPTCHA",
"twofa": "2要素認証",
"twofa_scratch": "2要素認証スクラッチコード",
"passcode": "パスコード",
@ -74,6 +76,7 @@
"pull_requests": "プルリクエスト",
"issues": "イシュー",
"milestones": "マイルストーン",
"ok": "OK",
"cancel": "キャンセル",
"retry": "再試行",
"rerun": "再実行",
@ -130,6 +133,7 @@
"confirm_delete_selected": "選択したすべてのアイテムを削除してよろしいですか?",
"name": "名称",
"value": "値",
"readme": "Readme",
"filter_title": "フィルター",
"filter.clear": "フィルターをクリア",
"filter.is_archived": "アーカイブ",
@ -144,6 +148,13 @@
"filter.private": "プライベート",
"no_results_found": "見つかりません。",
"internal_error_skipped": "内部エラーが発生しましたがスキップされました: %s",
"characters_spaces": "スペース",
"characters_tabs": "タブ",
"text_indent_style": "インデントスタイル",
"text_indent_size": "インデントサイズ",
"text_line_wrap": "折り返す",
"text_line_nowrap": "折り返さない",
"text_line_wrap_mode": "行折り返しモード",
"search.search": "検索…",
"search.type_tooltip": "検索タイプ",
"search.fuzzy": "あいまい",
@ -183,6 +194,7 @@
"editor.buttons.heading.tooltip": "見出し追加",
"editor.buttons.bold.tooltip": "太字追加",
"editor.buttons.italic.tooltip": "イタリック体追加",
"editor.buttons.strikethrough.tooltip": "取り消し線のテキストを追加",
"editor.buttons.quote.tooltip": "引用",
"editor.buttons.code.tooltip": "コード追加",
"editor.buttons.link.tooltip": "リンク追加",
@ -198,6 +210,8 @@
"editor.buttons.switch_to_legacy.tooltip": "レガシーエディタを使用する",
"editor.buttons.enable_monospace_font": "等幅フォントを有効にする",
"editor.buttons.disable_monospace_font": "等幅フォントを無効にする",
"filter.string.asc": "AZ",
"filter.string.desc": "ZA",
"error.occurred": "エラーが発生しました",
"error.report_message": "Gitea のバグが疑われる場合は、<a href=\"%s\" target=\"_blank\">GitHub</a>でIssueを検索して、見つからなければ新しいIssueを作成してください。",
"error.not_found": "ターゲットが見つかりませんでした。",
@ -224,6 +238,7 @@
"install.db_name": "データベース名",
"install.db_schema": "スキーマ",
"install.db_schema_helper": "空の場合はデータベースのデフォルト(\"public\")となります。",
"install.ssl_mode": "SSL",
"install.path": "パス",
"install.sqlite_helper": "SQLite3のデータベースファイルパス。<br>Giteaをサービスとして実行する場合は絶対パスを入力します。",
"install.reinstall_error": "既存のGiteaデータベースへインストールしようとしています",
@ -401,6 +416,7 @@
"auth.twofa_scratch_token_incorrect": "スクラッチコードが正しくありません。",
"auth.twofa_required": "リポジトリにアクセスするには2段階認証を設定するか、再度ログインしてください。",
"auth.login_userpass": "サインイン",
"auth.login_openid": "OpenID",
"auth.oauth_signup_tab": "新規アカウント登録",
"auth.oauth_signup_title": "新規アカウントの仕上げ",
"auth.oauth_signup_submit": "アカウント登録完了",
@ -505,6 +521,7 @@
"form.Password": "パスワード",
"form.Retype": "パスワード確認",
"form.SSHTitle": "SSHキー名",
"form.HttpsUrl": "HTTPS URL",
"form.PayloadUrl": "ペイロードのURL",
"form.TeamName": "チーム名",
"form.AuthName": "承認名",
@ -648,6 +665,7 @@
"settings.twofa": "2要素認証 (TOTP)",
"settings.account_link": "連携アカウント",
"settings.organization": "組織",
"settings.uid": "UID",
"settings.webauthn": "2要素認証 (セキュリティキー)",
"settings.public_profile": "公開プロフィール",
"settings.biography_placeholder": "自己紹介してください!(Markdownを使うことができます)",
@ -740,6 +758,7 @@
"settings.add_email": "メールアドレスを追加",
"settings.add_openid": "OpenID URIを追加する",
"settings.add_email_confirmation_sent": "\"%s\" に確認メールを送信しました。 %s以内に受信トレイを確認し、メールアドレス確認を行ってください。",
"settings.email_primary_not_found": "選択したメールアドレスが見つかりませんでした。",
"settings.add_email_success": "新しいメールアドレスを追加しました。",
"settings.email_preference_set_success": "メール設定を保存しました。",
"settings.add_openid_success": "新しいOpenIDアドレスを追加しました。",
@ -966,6 +985,7 @@
"repo.fork.blocked_user": "リポジトリのオーナーがあなたをブロックしているため、リポジトリをフォークできません。",
"repo.use_template": "このテンプレートを使用",
"repo.open_with_editor": "%s で開く",
"repo.download_directory_as": "%sとしてディレクトリをダウンロード",
"repo.download_zip": "ZIPファイルをダウンロード",
"repo.download_tar": "TAR.GZファイルをダウンロード",
"repo.download_bundle": "バンドルをダウンロード",
@ -985,6 +1005,7 @@
"repo.multiple_licenses": "複数のライセンス",
"repo.object_format": "オブジェクトのフォーマット",
"repo.object_format_helper": "リポジトリのオブジェクトフォーマット。後で変更することはできません。SHA1 は最も互換性があります。",
"repo.readme": "README",
"repo.readme_helper": "READMEファイル テンプレートを選択してください。",
"repo.readme_helper_desc": "プロジェクトについての説明をひととおり書く場所です。",
"repo.auto_init": "リポジトリの初期設定 (.gitignore、ライセンスファイル、READMEファイルの追加)",
@ -997,6 +1018,7 @@
"repo.default_branch": "デフォルトブランチ",
"repo.default_branch_label": "デフォルト",
"repo.default_branch_helper": "デフォルトブランチはプルリクエストとコードコミットのベースブランチとなります。",
"repo.mirror_prune": "Prune",
"repo.mirror_prune_desc": "不要になった古いリモートトラッキング参照を削除",
"repo.mirror_interval": "ミラー間隔 (有効な時間の単位は'h'、'm'、's')。 定期的な同期を無効にする場合は0。(最小間隔: %s)",
"repo.mirror_interval_invalid": "ミラー間隔が不正です。",
@ -1006,6 +1028,7 @@
"repo.mirror_address_desc": "必要な資格情報は「認証」セクションに設定してください。",
"repo.mirror_address_url_invalid": "入力したURLは無効です。 URLの構成要素はすべて正しくエスケープしてください。",
"repo.mirror_address_protocol_invalid": "入力したURLは無効です。 ミラーできるのは、http(s):// または git:// からだけです。",
"repo.mirror_lfs": "Large File Storage (LFS)",
"repo.mirror_lfs_desc": "LFS データのミラーリングを有効にする。",
"repo.mirror_lfs_endpoint": "LFS エンドポイント",
"repo.mirror_lfs_endpoint_desc": "同期するときは、クローンURLをもとに<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">LFSサーバーを決定</a>しようとします。 リポジトリのLFSデータがほかの場所に保存されている場合は、独自のエンドポイントを指定することができます。",
@ -1047,6 +1070,7 @@
"repo.desc.template": "テンプレート",
"repo.desc.internal": "内部",
"repo.desc.archived": "アーカイブ",
"repo.desc.sha256": "SHA256",
"repo.template.items": "テンプレート項目",
"repo.template.git_content": "Gitコンテンツ (デフォルトブランチ)",
"repo.template.git_hooks": "Gitフック",
@ -1075,6 +1099,7 @@
"repo.migrate_options_lfs_endpoint.description.local": "ローカルサーバーのパスもサポートされています。",
"repo.migrate_options_lfs_endpoint.placeholder": "空にするとエンドポイントはクローン URL から決定されます。",
"repo.migrate_items": "移行する項目",
"repo.migrate_items_wiki": "Wiki",
"repo.migrate_items_milestones": "マイルストーン",
"repo.migrate_items_labels": "ラベル",
"repo.migrate_items_issues": "イシュー",
@ -1124,6 +1149,7 @@
"repo.migration_status": "移行状況",
"repo.mirror_from": "ミラー元",
"repo.forked_from": "フォーク元",
"repo.generated_from": "generated from",
"repo.fork_from_self": "自分が所有しているリポジトリはフォークできません。",
"repo.fork_guest_user": "リポジトリをフォークするにはサインインしてください。",
"repo.watch_guest_user": "リポジトリをウォッチするにはサインインしてください。",
@ -1157,6 +1183,7 @@
"repo.pulls": "プルリクエスト",
"repo.projects": "プロジェクト",
"repo.packages": "パッケージ",
"repo.actions": "Actions",
"repo.labels": "ラベル",
"repo.org_labels_desc": "組織で定義されているラベル (組織の<strong>すべてのリポジトリ</strong>で使用可能なもの)",
"repo.org_labels_desc_manage": "編集",
@ -1167,8 +1194,11 @@
"repo.release": "リリース",
"repo.releases": "リリース",
"repo.tag": "タグ",
"repo.git_tag": "Gitタグ",
"repo.released_this": "がこれをリリース",
"repo.tagged_this": "がタグ付け",
"repo.file.title": "%s at %s",
"repo.file_raw": "Raw",
"repo.file_history": "履歴",
"repo.file_view_source": "ソースを表示",
"repo.file_view_rendered": "レンダリング表示",
@ -1204,6 +1234,7 @@
"repo.commit.contained_in_default_branch": "このコミットはデフォルトブランチに含まれています",
"repo.commit.load_referencing_branches_and_tags": "このコミットを参照しているブランチやタグを取得",
"repo.commit.merged_in_pr": "このコミットはプルリクエスト %s でマージされました。",
"repo.blame": "Blame",
"repo.download_file": "ファイルをダウンロード",
"repo.normal_view": "通常表示",
"repo.line": "行",
@ -1467,6 +1498,7 @@
"repo.issues.filter_sort.feweststars": "スターが少ない順",
"repo.issues.filter_sort.mostforks": "フォークが多い順",
"repo.issues.filter_sort.fewestforks": "フォークが少ない順",
"repo.issues.quick_goto": "イシューへ移動",
"repo.issues.action_open": "オープン",
"repo.issues.action_close": "クローズ",
"repo.issues.action_label": "ラベル",
@ -1627,6 +1659,7 @@
"repo.issues.push_commits_n": "が %d コミット追加 %s",
"repo.issues.force_push_codes": "が %[1]s を強制プッシュ ( <a class=\"ui sha\" href=\"%[3]s\"><code>%[2]s</code></a> から <a class=\"ui sha\" href=\"%[5]s\"><code>%[4]s</code></a> へ ) %[6]s",
"repo.issues.force_push_compare": "比較",
"repo.issues.due_date_form": "yyyy-mm-dd",
"repo.issues.due_date_form_add": "期日の追加",
"repo.issues.due_date_form_edit": "変更",
"repo.issues.due_date_form_remove": "削除",
@ -1678,6 +1711,7 @@
"repo.issues.review.content.empty": "修正を指示するコメントを残す必要があります。",
"repo.issues.review.reject": "が変更を要請 %s",
"repo.issues.review.wait": "にレビュー依頼 %s",
"repo.issues.review.codeowners_rules": "CODEOWNERSルール",
"repo.issues.review.add_review_request": "が %s にレビューを依頼 %s",
"repo.issues.review.remove_review_request": "が %s へのレビュー依頼を取り消し %s",
"repo.issues.review.remove_review_request_self": "がレビューを辞退 %s",
@ -1713,17 +1747,20 @@
"repo.issues.reference_link": "リファレンス: %s",
"repo.compare.compare_base": "基準",
"repo.compare.compare_head": "比較",
"repo.compare.title": "変更の比較",
"repo.compare.description": "ふたつのブランチまたはタグを選び、変更された内容を確認、あるいは新しいプルリクエストを開始してください。",
"repo.pulls.desc": "プルリクエストとコードレビューの有効化。",
"repo.pulls.new": "新しいプルリクエスト",
"repo.pulls.new.description": "この比較における変更点について議論し、レビューします。",
"repo.pulls.new.blocked_user": "リポジトリのオーナーがあなたをブロックしているため、プルリクエストを作成できません。",
"repo.pulls.new.must_collaborator": "プルリクエストを作成するには、共同作業者である必要があります。",
"repo.pulls.new.already_existed": "これらのブランチのプルリクエストはすでに存在します",
"repo.pulls.edit.already_changed": "プルリクエストの変更を保存できません。 他のユーザーによって内容がすでに変更されているようです。 変更を上書きしないようにするため、ページを更新してからもう一度編集してください。",
"repo.pulls.view": "プルリクエストを表示",
"repo.pulls.compare_changes": "新規プルリクエスト",
"repo.pulls.allow_edits_from_maintainers": "メンテナーからの編集を許可する",
"repo.pulls.allow_edits_from_maintainers_desc": "ベースブランチへの書き込みアクセス権を持つユーザーは、このブランチにプッシュすることもできます",
"repo.pulls.allow_edits_from_maintainers_err": "更新に失敗しました",
"repo.pulls.compare_changes_desc": "マージ先ブランチとプル元ブランチを選択。",
"repo.pulls.has_viewed_file": "閲覧済",
"repo.pulls.has_changed_since_last_review": "前回のレビュー後に変更あり",
"repo.pulls.viewed_files_label": "%[1]d / %[2]d ファイル閲覧済み",
@ -1749,6 +1786,8 @@
"repo.pulls.title_desc": "が <code>%[2]s</code> から <code id=\"branch_target\">%[3]s</code> への %[1]d コミットのマージを希望しています",
"repo.pulls.merged_title_desc": "が %[1]d 個のコミットを <code>%[2]s</code> から <code>%[3]s</code> へマージ %[4]s",
"repo.pulls.change_target_branch_at": "がターゲットブランチを <b>%s</b> から <b>%s</b> に変更 %s",
"repo.pulls.marked_as_work_in_progress_at": "がこのプルリクエストを作業中(WIP)とマーク %s",
"repo.pulls.marked_as_ready_for_review_at": "がこのプルリクエストをレビュー可とマーク %s",
"repo.pulls.tab_conversation": "会話",
"repo.pulls.tab_commits": "コミット",
"repo.pulls.tab_files": "変更されたファイル",
@ -1767,6 +1806,7 @@
"repo.pulls.remove_prefix": "先頭の <strong>%s</strong> を除去",
"repo.pulls.data_broken": "このプルリクエストは、フォークの情報が見つからないため壊れています。",
"repo.pulls.files_conflicted": "このプルリクエストは、ターゲットブランチと競合する変更を含んでいます。",
"repo.pulls.files_conflicted_no_listed_files": "(競合するファイルはありません)",
"repo.pulls.is_checking": "マージのコンフリクトを確認中…",
"repo.pulls.is_ancestor": "このブランチは既にターゲットブランチに含まれています。マージするものはありません。",
"repo.pulls.is_empty": "このブランチの変更は既にターゲットブランチにあります。これは空のコミットになります。",
@ -1821,7 +1861,8 @@
"repo.pulls.status_checking": "いくつかのステータスチェックが待機中です",
"repo.pulls.status_checks_success": "ステータスチェックはすべて成功しました",
"repo.pulls.status_checks_warning": "ステータスチェックにより警告が出ています",
"repo.pulls.status_checks_failure": "失敗したステータスチェックがあります",
"repo.pulls.status_checks_failure_required": "必須チェックに失敗しています",
"repo.pulls.status_checks_failure_optional": "必須ではないチェックが失敗しています",
"repo.pulls.status_checks_error": "ステータスチェックによりエラーが出ています",
"repo.pulls.status_checks_requested": "必須",
"repo.pulls.status_checks_details": "詳細",
@ -1911,6 +1952,7 @@
"repo.signing.wont_sign.not_signed_in": "サインインしていません。",
"repo.ext_wiki": "外部Wikiへのアクセス",
"repo.ext_wiki.desc": "外部Wikiへのリンク。",
"repo.wiki": "Wiki",
"repo.wiki.welcome": "Wikiへようこそ。",
"repo.wiki.welcome_desc": "Wikiを使って共同作業者とドキュメンテーションの作成と共有ができます。",
"repo.wiki.desc": "共同作業者とのドキュメンテーションの作成と共有。",
@ -1937,6 +1979,7 @@
"repo.wiki.page_name_desc": "この Wiki ページの名前を入力してください。いくつかの特別な名前として 'Home', '_Sidebar' と '_Footer' があります。",
"repo.wiki.original_git_entry_tooltip": "フレンドリーリンクを使用する代わりにオリジナルのGitファイルを表示します。",
"repo.activity": "アクティビティ",
"repo.activity.navbar.pulse": "Pulse",
"repo.activity.navbar.code_frequency": "コード更新頻度",
"repo.activity.navbar.contributors": "貢献者",
"repo.activity.navbar.recent_commits": "最近のコミット",
@ -2089,6 +2132,8 @@
"repo.settings.pulls.ignore_whitespace": "空白文字のコンフリクトを無視する",
"repo.settings.pulls.enable_autodetect_manual_merge": "手動マージの自動検出を有効にする (注意: 特殊なケースでは判定ミスが発生する場合があります)",
"repo.settings.pulls.allow_rebase_update": "リベースでプルリクエストのブランチの更新を可能にする",
"repo.settings.pulls.default_target_branch": "新しいプルリクエストのデフォルトのターゲットブランチ",
"repo.settings.pulls.default_target_branch_default": "デフォルトブランチ (%s)",
"repo.settings.pulls.default_delete_branch_after_merge": "デフォルトでプルリクエストのブランチをマージ後に削除する",
"repo.settings.pulls.default_allow_edits_from_maintainers": "デフォルトでメンテナからの編集を許可する",
"repo.settings.releases_desc": "リリースを有効にする",
@ -2210,6 +2255,8 @@
"repo.settings.add_webhook_desc": "GiteaはターゲットURLに、指定したContent Typeで<code>POST</code>リクエストを送ります。 詳細は<a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">Webhookガイド</a>へ。",
"repo.settings.payload_url": "ターゲットURL",
"repo.settings.http_method": "HTTPメソッド",
"repo.settings.content_type": "POST Content Type",
"repo.settings.secret": "シークレット",
"repo.settings.webhook_secret_desc": "Webhookサーバーがsecretの使用をサポートしている場合は、webhookのマニュアルに従いここにsecretを入力できます。",
"repo.settings.slack_username": "ユーザー名",
"repo.settings.slack_icon_url": "アイコンのURL",
@ -2227,6 +2274,7 @@
"repo.settings.event_delete_desc": "ブランチやタグが削除されたとき。",
"repo.settings.event_fork": "フォーク",
"repo.settings.event_fork_desc": "リポジトリがフォークされたとき。",
"repo.settings.event_wiki": "Wiki",
"repo.settings.event_wiki_desc": "Wikiページが作成・名前変更・編集・削除されたとき。",
"repo.settings.event_statuses": "ステータス",
"repo.settings.event_statuses_desc": "APIによってコミットのステータスが更新されたとき。",
@ -2292,6 +2340,19 @@
"repo.settings.slack_domain": "ドメイン",
"repo.settings.slack_channel": "チャンネル",
"repo.settings.add_web_hook_desc": "<a target=\"_blank\" rel=\"noreferrer\" href=\"%s\">%s</a> をリポジトリと組み合わせます。",
"repo.settings.web_hook_name_gitea": "Gitea",
"repo.settings.web_hook_name_gogs": "Gogs",
"repo.settings.web_hook_name_slack": "Slack",
"repo.settings.web_hook_name_discord": "Discord",
"repo.settings.web_hook_name_dingtalk": "DingTalk",
"repo.settings.web_hook_name_telegram": "Telegram",
"repo.settings.web_hook_name_matrix": "Matrix",
"repo.settings.web_hook_name_msteams": "Microsoft Teams",
"repo.settings.web_hook_name_feishu_or_larksuite": "Feishu / Lark Suite",
"repo.settings.web_hook_name_feishu": "Feishu",
"repo.settings.web_hook_name_larksuite": "Lark Suite",
"repo.settings.web_hook_name_wechatwork": "WeCom (Wechat Work)",
"repo.settings.web_hook_name_packagist": "Packagist",
"repo.settings.packagist_username": "Packagist ユーザー名",
"repo.settings.packagist_api_token": "API トークン",
"repo.settings.packagist_package_url": "Packagist パッケージ URL",
@ -2385,7 +2446,8 @@
"repo.settings.block_outdated_branch_desc": "baseブランチがheadブランチより進んでいる場合、マージできないようにします。",
"repo.settings.block_admin_merge_override": "管理者もブランチ保護のルールに従う",
"repo.settings.block_admin_merge_override_desc": "管理者はブランチ保護のルールに従う必要があり、回避することはできません。",
"repo.settings.default_branch_desc": "プルリクエストやコミット表示のデフォルトのブランチを選択:",
"repo.settings.default_branch_desc": "コミット表示のデフォルトのブランチを選択します。",
"repo.settings.default_target_branch_desc": "プルリクエストでは、リポジトリ拡張設定の「プルリクエスト」セクションで設定することで、別のデフォルトターゲットブランチを使用できます。",
"repo.settings.merge_style_desc": "マージ スタイル",
"repo.settings.default_merge_style_desc": "デフォルトのマージスタイル",
"repo.settings.choose_branch": "ブランチを選択…",
@ -2437,6 +2499,7 @@
"repo.settings.unarchive.success": "リポジトリのアーカイブを解除しました。",
"repo.settings.unarchive.error": "リポジトリのアーカイブ解除でエラーが発生しました。 詳細はログを確認してください。",
"repo.settings.update_avatar_success": "リポジトリのアバターを更新しました。",
"repo.settings.lfs": "LFS",
"repo.settings.lfs_filelist": "このリポジトリに含まれているLFSファイル",
"repo.settings.lfs_no_lfs_files": "このリポジトリにLFSファイルはありません",
"repo.settings.lfs_findcommits": "コミットを検索",
@ -2455,6 +2518,8 @@
"repo.settings.lfs_lock_file_no_exist": "ロックしたファイルがデフォルトブランチにありません",
"repo.settings.lfs_force_unlock": "強制ロック解除",
"repo.settings.lfs_pointers.found": "%d件のblobポインタ — 登録済 %d件、未登録 %d件 (実体ファイルなし %d件)",
"repo.settings.lfs_pointers.sha": "Blob SHA",
"repo.settings.lfs_pointers.oid": "OID",
"repo.settings.lfs_pointers.inRepo": "Repo内",
"repo.settings.lfs_pointers.exists": "実ファイルあり",
"repo.settings.lfs_pointers.accessible": "アクセス可",
@ -2468,6 +2533,7 @@
"repo.diff.browse_source": "ソースを参照",
"repo.diff.parent": "親",
"repo.diff.commit": "コミット",
"repo.diff.git-notes": "Notes",
"repo.diff.data_not_available": "差分はありません",
"repo.diff.options_button": "差分オプション",
"repo.diff.download_patch": "Patchファイルをダウンロード",
@ -2494,6 +2560,8 @@
"repo.diff.too_many_files": "変更されたファイルが多すぎるため、一部のファイルは表示されません",
"repo.diff.show_more": "さらに表示",
"repo.diff.load": "差分を読み込み",
"repo.diff.generated": "生成ファイル",
"repo.diff.vendored": "ベンダーファイル",
"repo.diff.comment.add_line_comment": "行コメントを追加",
"repo.diff.comment.placeholder": "コメントを残す",
"repo.diff.comment.add_single_comment": "単独のコメントを追加",
@ -2508,6 +2576,7 @@
"repo.diff.review.self_reject": "プルリクエストの作成者は自分のプルリクエストで変更要請できません",
"repo.diff.review.reject": "変更要請",
"repo.diff.review.self_approve": "プルリクエストの作成者は自分のプルリクエストを承認できません",
"repo.diff.committed_by": "committed by",
"repo.diff.protected": "保護されているファイル",
"repo.diff.image.side_by_side": "並べて表示",
"repo.diff.image.swipe": "スワイプ",
@ -2566,6 +2635,13 @@
"repo.release.add_tag": "タグのみ作成",
"repo.release.releases_for": "%s のリリース",
"repo.release.tags_for": "%s のタグ",
"repo.release.notes": "リリースノート",
"repo.release.generate_notes": "リリースノートを生成",
"repo.release.generate_notes_desc": "マージされたプルリクエストと変更履歴のリンクを自動的に追加します。",
"repo.release.previous_tag": "前回のタグ",
"repo.release.generate_notes_tag_not_found": "このリポジトリにタグ \"%s\" は存在しません。",
"repo.release.generate_notes_target_not_found": "リリースターゲット \"%s\" が見つかりません。",
"repo.release.generate_notes_missing_tag": "リリースノートを生成するタグ名を入力してください。",
"repo.branch.name": "ブランチ名",
"repo.branch.already_exists": "ブランチ \"%s\" は既に存在します。",
"repo.branch.delete_head": "削除",
@ -2585,7 +2661,7 @@
"repo.branch.restore_success": "ブランチ \"%s\" を復元しました。",
"repo.branch.restore_failed": "ブランチ \"%s\" の復元に失敗しました。",
"repo.branch.protected_deletion_failed": "ブランチ \"%s\" は保護されています。 削除できません。",
"repo.branch.default_deletion_failed": "ブランチ \"%s\" はデフォルトブランチです。 削除できません。",
"repo.branch.default_deletion_failed": "ブランチ \"%s\" はデフォルトブランチまたはプルリクエストのターゲットブランチです。 削除できません。",
"repo.branch.default_branch_not_exist": "デフォルトブランチ \"%s\" がありません。",
"repo.branch.restore": "ブランチ \"%s\" の復元",
"repo.branch.download": "ブランチ \"%s\" をダウンロード",
@ -2602,7 +2678,7 @@
"repo.branch.new_branch_from": "\"%s\" から新しいブランチを作成",
"repo.branch.renamed": "ブランチ %s は %s にリネームされました。",
"repo.branch.rename_default_or_protected_branch_error": "デフォルトブランチや保護ブランチのリネームが可能なのは管理者だけです。",
"repo.branch.rename_protected_branch_failed": "このブランチはglobベースの保護ルールに従って保護されています。",
"repo.branch.rename_protected_branch_failed": "ブランチ保護ルールにより、ブランチ名の変更は失敗しました。",
"repo.branch.commits_divergence_from": "コミットの乖離: %[3]s より %[1]d 件遅れ %[2]d 件先行",
"repo.branch.commits_no_divergence": "%[1]s ブランチと一致",
"repo.tag.create_tag": "タグ %s を作成",
@ -2809,6 +2885,7 @@
"admin.dashboard.task.finished": "タスク: %[2]s が開始したタスク %[1]s が完了",
"admin.dashboard.task.unknown": "不明なタスクです: %[1]s",
"admin.dashboard.cron.started": "Cronを開始しました: %[1]s",
"admin.dashboard.cron.process": "Cron: %[1]s",
"admin.dashboard.cron.cancelled": "Cron: %[1]s をキャンセル: %[3]s",
"admin.dashboard.cron.error": "Cronでエラー: %s: %[3]s",
"admin.dashboard.cron.finished": "Cron: %[1]s が完了",
@ -2886,7 +2963,9 @@
"admin.users.admin": "管理者",
"admin.users.restricted": "制限あり",
"admin.users.reserved": "予約済み",
"admin.users.bot": "Bot",
"admin.users.remote": "リモート",
"admin.users.2fa": "2FA",
"admin.users.repos": "リポジトリ",
"admin.users.created": "作成日",
"admin.users.last_login": "前回のサインイン",
@ -3008,6 +3087,7 @@
"admin.auths.attribute_mail": "メールアドレス",
"admin.auths.attribute_ssh_public_key": "SSH公開鍵",
"admin.auths.attribute_avatar": "アバター",
"admin.auths.ssh_keys_are_verified": "LDAPからのSSHキーを検証済みとする",
"admin.auths.attributes_in_bind": "バインドDNのコンテクストから属性を取得する",
"admin.auths.allow_deactivate_all": "サーチ結果が空のときは全ユーザーを非アクティブ化",
"admin.auths.use_paged_search": "ページ分割検索を使用",
@ -3141,6 +3221,7 @@
"admin.config.db_name": "データベース名",
"admin.config.db_user": "ユーザー名",
"admin.config.db_schema": "スキーマ",
"admin.config.db_ssl_mode": "SSL",
"admin.config.db_path": "パス",
"admin.config.service_config": "サービス設定",
"admin.config.register_email_confirm": "登録にはメールによる確認が必要",
@ -3179,6 +3260,7 @@
"admin.config.mailer_sendmail_path": "Sendmailのパス",
"admin.config.mailer_sendmail_args": "Sendmailの追加引数",
"admin.config.mailer_sendmail_timeout": "Sendmail のタイムアウト",
"admin.config.mailer_use_dummy": "Dummy",
"admin.config.test_email_placeholder": "メールアドレス (例 test@example.com)",
"admin.config.send_test_mail": "テストメールを送信",
"admin.config.send_test_mail_submit": "送信",
@ -3217,8 +3299,6 @@
"admin.config.git_gc_args": "GC引数",
"admin.config.git_migrate_timeout": "移行タイムアウト",
"admin.config.git_mirror_timeout": "ミラー更新タイムアウト",
"admin.config.git_clone_timeout": "クローン操作のタイムアウト",
"admin.config.git_pull_timeout": "プル操作のタイムアウト",
"admin.config.git_gc_timeout": "GC操作のタイムアウト",
"admin.config.log_config": "ログ設定",
"admin.config.logger_name_fmt": "ロガー: %s",
@ -3393,6 +3473,7 @@
"packages.assets": "アセット",
"packages.versions": "バージョン",
"packages.versions.view_all": "すべて表示",
"packages.dependency.id": "ID",
"packages.dependency.version": "バージョン",
"packages.search_in_external_registry": "%s で検索",
"packages.alpine.registry": "あなたの <code>/etc/apk/repositories</code> ファイルにURLを追加して、このレジストリをセットアップします:",
@ -3402,10 +3483,12 @@
"packages.alpine.repository": "リポジトリ情報",
"packages.alpine.repository.branches": "ブランチ",
"packages.alpine.repository.repositories": "リポジトリ",
"packages.alpine.repository.architectures": "Architectures",
"packages.arch.registry": "<code>/etc/pacman.conf</code> にリポジトリとアーキテクチャを含めてサーバーを追加します:",
"packages.arch.install": "pacmanでパッケージを同期します:",
"packages.arch.repository": "リポジトリ情報",
"packages.arch.repository.repositories": "リポジトリ",
"packages.arch.repository.architectures": "Architectures",
"packages.cargo.registry": "Cargo 設定ファイルでこのレジストリをセットアップします。(例 <code>~/.cargo/config.toml</code>):",
"packages.cargo.install": "Cargo を使用してパッケージをインストールするには、次のコマンドを実行します:",
"packages.chef.registry": "あなたの <code>~/.chef/config.rb</code> ファイルに、このレジストリをセットアップします:",
@ -3435,6 +3518,9 @@
"packages.debian.registry.info": "$distribution と $component は下にあるリストから選んでください。",
"packages.debian.install": "パッケージをインストールするには、次のコマンドを実行します:",
"packages.debian.repository": "リポジトリ情報",
"packages.debian.repository.distributions": "Distributions",
"packages.debian.repository.components": "Components",
"packages.debian.repository.architectures": "Architectures",
"packages.generic.download": "コマンドラインでパッケージをダウンロードします:",
"packages.go.install": "コマンドラインでパッケージをインストール:",
"packages.helm.registry": "このレジストリをコマンドラインからセットアップします:",
@ -3463,6 +3549,7 @@
"packages.rpm.distros.suse": "SUSE系ディストリビューションの場合",
"packages.rpm.install": "パッケージをインストールするには、次のコマンドを実行します:",
"packages.rpm.repository": "リポジトリ情報",
"packages.rpm.repository.architectures": "Architectures",
"packages.rpm.repository.multiple_groups": "このパッケージは複数のグループで利用可能です。",
"packages.rubygems.install": "gem を使用してパッケージをインストールするには、次のコマンドを実行します:",
"packages.rubygems.install2": "または Gemfile に追加します:",
@ -3536,6 +3623,7 @@
"secrets.deletion.success": "シークレットを削除しました。",
"secrets.deletion.failed": "シークレットの削除に失敗しました。",
"secrets.management": "シークレット管理",
"actions.actions": "Actions",
"actions.unit.desc": "Actionsの管理",
"actions.status.unknown": "不明",
"actions.status.waiting": "待機中",
@ -3550,6 +3638,7 @@
"actions.runners.new": "新しいランナーを作成",
"actions.runners.new_notice": "ランナーの開始方法",
"actions.runners.status": "ステータス",
"actions.runners.id": "ID",
"actions.runners.name": "名称",
"actions.runners.owner_type": "タイプ",
"actions.runners.description": "説明",
@ -3584,6 +3673,7 @@
"actions.runs.all_workflows": "すべてのワークフロー",
"actions.runs.commit": "コミット",
"actions.runs.scheduled": "スケジュール済み",
"actions.runs.pushed_by": "pushed by",
"actions.runs.invalid_workflow_helper": "ワークフロー設定ファイルは無効です。あなたの設定ファイルを確認してください: %s",
"actions.runs.no_matching_online_runner_helper": "ラベルに一致するオンラインのランナーが見つかりません: %s",
"actions.runs.no_job_without_needs": "ワークフローには依存関係のないジョブが少なくとも1つ含まれている必要があります。",
@ -3648,6 +3738,7 @@
"projects.type-3.display_name": "組織プロジェクト",
"projects.enter_fullscreen": "フルスクリーン",
"projects.exit_fullscreen": "フルスクリーンを終了",
"git.filemode.changed_filemode": "%[1]s → %[2]s",
"git.filemode.directory": "ディレクトリ",
"git.filemode.normal_file": "ノーマルファイル",
"git.filemode.executable_file": "実行可能ファイル",

View File

@ -148,6 +148,13 @@
"filter.private": "Özel",
"no_results_found": "Sonuç bulunamadı.",
"internal_error_skipped": "Dahili bir hata oluştu ama atlandı: %s",
"characters_spaces": "Boşluklar",
"characters_tabs": "Sekmeler",
"text_indent_style": "Girinti biçimi",
"text_indent_size": "Girinti boyutu",
"text_line_wrap": "Metni kaydır",
"text_line_nowrap": "Metni kaydırma",
"text_line_wrap_mode": "Satır sarma kipi",
"search.search": "Ara...",
"search.type_tooltip": "Arama türü",
"search.fuzzy": "Bulanık",
@ -751,6 +758,7 @@
"settings.add_email": "E-posta Adresi Ekle",
"settings.add_openid": "Açık Kimlik URI 'si ekle",
"settings.add_email_confirmation_sent": "\"%s\" adresine bir doğrulama e-postası gönderildi. E-postanızı doğrulamak için %s içinde gelen kutunuzu kontrol ediniz.",
"settings.email_primary_not_found": "Seçilen e-posta adresi bulunamıyor.",
"settings.add_email_success": "Yeni e-posta adresi eklendi.",
"settings.email_preference_set_success": "E-posta tercihi başarıyla ayarlandı.",
"settings.add_openid_success": "Yeni OpenID adresi eklendi.",
@ -1778,6 +1786,8 @@
"repo.pulls.title_desc": "<code>%[2]s</code> içindeki %[1]d işlemeyi <code id=\"branch_target\">%[3]s</code> ile birleştirmek istiyor",
"repo.pulls.merged_title_desc": "%[4]s <code>%[2]s</code> içindeki %[1]d işlemeyi <code>%[3]s</code> ile birleştirdi",
"repo.pulls.change_target_branch_at": "hedef dal <b>%s</b> adresinden <b>%s</b>%s adresine değiştirildi",
"repo.pulls.marked_as_work_in_progress_at": "değişiklik isteğini devam eden iş olarak işaretledi %s",
"repo.pulls.marked_as_ready_for_review_at": "değişiklik isteğini incelemeye hazır olarak işaretledi %s",
"repo.pulls.tab_conversation": "Sohbet",
"repo.pulls.tab_commits": "İşleme",
"repo.pulls.tab_files": "Değiştirilen Dosyalar",
@ -2122,6 +2132,8 @@
"repo.settings.pulls.ignore_whitespace": "Çakışmalar için Boşlukları Gözardı Et",
"repo.settings.pulls.enable_autodetect_manual_merge": "Kendiliğinden algılamalı elle birleştirmeyi etkinleştir (Not: Bazı özel durumlarda yanlış kararlar olabilir)",
"repo.settings.pulls.allow_rebase_update": "Değişiklik isteği dalının yeniden yapılandırmayla güncellenmesine izin ver",
"repo.settings.pulls.default_target_branch": "Yeni değişiklik istekleri için varsayılan hedef dal",
"repo.settings.pulls.default_target_branch_default": "Varsayılan dal (%s)",
"repo.settings.pulls.default_delete_branch_after_merge": "Varsayılan olarak birleştirmeden sonra değişiklik isteği dalını sil",
"repo.settings.pulls.default_allow_edits_from_maintainers": "Bakımcıların düzenlemelerine izin ver",
"repo.settings.releases_desc": "Depo Sürümlerini Etkinleştir",
@ -2434,9 +2446,10 @@
"repo.settings.block_outdated_branch_desc": "Baş dal taban dalın arkasındayken birleştirme mümkün olmayacaktır.",
"repo.settings.block_admin_merge_override": "Yöneticiler dal koruma kurallarına uymalıdır",
"repo.settings.block_admin_merge_override_desc": "Yöneticiler dal koruma kurallarına uymalıdır ve kurallardan kaçınamazlar.",
"repo.settings.default_branch_desc": "Değişiklik istekleri ve kod işlemeleri için varsayılan bir depo dalı seçin:",
"repo.settings.default_branch_desc": "Kod işlemeleri için varsayılan bir depo dalı seçin.",
"repo.settings.default_target_branch_desc": "Değişiklik istekleri, Depo Gelişmiş Ayarları'nın Değişiklik İstekleri bölümünde ayarlanmışsa farklı varsayılan hedef dal kullanabilir.",
"repo.settings.merge_style_desc": "Biçimleri Birleştir",
"repo.settings.default_merge_style_desc": "Değişiklik istekleri için varsayılan birleştirme tarzı",
"repo.settings.default_merge_style_desc": "Varsayılan birleştirme tarzı",
"repo.settings.choose_branch": "Bir dal seç…",
"repo.settings.no_protected_branch": "Korumalı dal yok.",
"repo.settings.edit_protected_branch": "Düzenle",
@ -2648,7 +2661,7 @@
"repo.branch.restore_success": "\"%s\" dalı geri yüklendi.",
"repo.branch.restore_failed": "\"%s\" dalı geri yüklenemedi.",
"repo.branch.protected_deletion_failed": "\"%s\" dalı korunuyor. Silinemez.",
"repo.branch.default_deletion_failed": "\"%s\" dalı varsayılan daldır. Silinemez.",
"repo.branch.default_deletion_failed": "\"%s\" dalı varsayılan veya değişiklik isteği hedef dalıdır. Silinemez.",
"repo.branch.default_branch_not_exist": "Varsayılan dal \"%s\" mevcut değil.",
"repo.branch.restore": "\"%s\" Dalını Geri Yükle",
"repo.branch.download": "\"%s\" Dalını İndir",

View File

@ -148,6 +148,13 @@
"filter.private": "私有",
"no_results_found": "未找到结果",
"internal_error_skipped": "发生内部错误,但已跳过: %s",
"characters_spaces": "空格",
"characters_tabs": "制表符",
"text_indent_style": "缩进风格",
"text_indent_size": "缩进大小",
"text_line_wrap": "换行",
"text_line_nowrap": "无换行",
"text_line_wrap_mode": "换行模式",
"search.search": "搜索…",
"search.type_tooltip": "搜索类型",
"search.fuzzy": "模糊",
@ -751,6 +758,7 @@
"settings.add_email": "新增邮箱地址",
"settings.add_openid": "添加 OpenID URI",
"settings.add_email_confirmation_sent": "一封确认邮件已经发送至「%s」请检查您的收件箱并在 %s 内完成确认注册操作。",
"settings.email_primary_not_found": "找不到选定的电子邮件地址。",
"settings.add_email_success": "新邮箱地址已添加。",
"settings.email_preference_set_success": "邮件首选项已成功设置。",
"settings.add_openid_success": "新的 OpenID 地址已添加。",
@ -1635,7 +1643,7 @@
"repo.issues.cancel_tracking": "取消",
"repo.issues.cancel_tracking_history": "取消时间跟踪 %s",
"repo.issues.del_time": "删除此时间跟踪日志",
"repo.issues.add_time_history": "于 %[2]s 添加计时 <b>%[1]</b>",
"repo.issues.add_time_history": "于 %[2]s 添加计时 <b>%[1]s</b>",
"repo.issues.del_time_history": "已删除时间 %s",
"repo.issues.add_time_manually": "手动添加时间",
"repo.issues.add_time_hours": "小时",
@ -1778,6 +1786,8 @@
"repo.pulls.title_desc": "请求将 %[1]d 次代码提交从 <code>%[2]s</code> 合并至 <code id=\"branch_target\">%[3]s</code>",
"repo.pulls.merged_title_desc": "于 %[4]s 将 %[1]d 次代码提交从 <code>%[2]s</code>合并至 <code>%[3]s</code>",
"repo.pulls.change_target_branch_at": "将目标分支从 <b>%s</b> 更改为 <b>%s</b> %s",
"repo.pulls.marked_as_work_in_progress_at": "已将合并请求标记为进行中 %s",
"repo.pulls.marked_as_ready_for_review_at": "已将合并请求标记为准备评审 %s",
"repo.pulls.tab_conversation": "对话内容",
"repo.pulls.tab_commits": "代码提交",
"repo.pulls.tab_files": "文件变动",
@ -2122,6 +2132,8 @@
"repo.settings.pulls.ignore_whitespace": "忽略空白冲突",
"repo.settings.pulls.enable_autodetect_manual_merge": "启用自动检查手动合并(注意:在某些特殊情况下可能会出现误判)",
"repo.settings.pulls.allow_rebase_update": "允许通过变基更新合并请求分支",
"repo.settings.pulls.default_target_branch": "新合并请求的默认目标分支",
"repo.settings.pulls.default_target_branch_default": "默认分支(%s",
"repo.settings.pulls.default_delete_branch_after_merge": "默认合并后删除合并请求分支",
"repo.settings.pulls.default_allow_edits_from_maintainers": "默认允许维护者编辑",
"repo.settings.releases_desc": "启用仓库发布",
@ -2434,7 +2446,8 @@
"repo.settings.block_outdated_branch_desc": "当头部分支落后基础分支时,不能合并。",
"repo.settings.block_admin_merge_override": "管理员须遵守分支保护规则",
"repo.settings.block_admin_merge_override_desc": "管理员须遵守分支保护规则,不能规避该规则。",
"repo.settings.default_branch_desc": "请选择一个默认的分支用于合并请求和提交:",
"repo.settings.default_branch_desc": "选择一个默认分支用于提交代码。",
"repo.settings.default_target_branch_desc": "如果在仓库高级设置的合并请求部分中进行了设置,则合并请求可以使用不同的默认目标分支。",
"repo.settings.merge_style_desc": "合并方式",
"repo.settings.default_merge_style_desc": "默认合并风格",
"repo.settings.choose_branch": "选择一个分支…",
@ -2548,7 +2561,7 @@
"repo.diff.show_more": "显示更多",
"repo.diff.load": "加载差异",
"repo.diff.generated": "自动生成",
"repo.diff.vendored": "vendored",
"repo.diff.vendored": "第三方依赖",
"repo.diff.comment.add_line_comment": "添加行内评论",
"repo.diff.comment.placeholder": "留下评论",
"repo.diff.comment.add_single_comment": "添加单条评论",
@ -2648,7 +2661,7 @@
"repo.branch.restore_success": "分支「%s」已还原。",
"repo.branch.restore_failed": "分支「%s」还原失败。",
"repo.branch.protected_deletion_failed": "不能删除受保护的分支「%s」。",
"repo.branch.default_deletion_failed": "不能删除默认分支「%s」。",
"repo.branch.default_deletion_failed": "分支「%s」是默认分支或合并请求目标分支,无法删除。",
"repo.branch.default_branch_not_exist": "默认分支「%s」不存在。",
"repo.branch.restore": "还原分支「%s」",
"repo.branch.download": "下载分支「%s」",
@ -2672,7 +2685,7 @@
"repo.tag.create_tag_operation": "创建 Git 标签",
"repo.tag.confirm_create_tag": "创建 Git 标签",
"repo.tag.create_tag_from": "基于「%s」创建新 Git 标签",
"repo.tag.create_success": "Git 标签「%s」已存在。",
"repo.tag.create_success": "Git 标签「%s」创建成功。",
"repo.topic.manage_topics": "管理主题",
"repo.topic.done": "保存",
"repo.topic.count_prompt": "您最多选择25个主题",

View File

@ -149,30 +149,31 @@ func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo
return nil
}
func MailActionsTrigger(ctx context.Context, sender *user_model.User, repo *repo_model.Repository, run *actions_model.ActionRun) error {
func MailActionsTrigger(ctx context.Context, recipient *user_model.User, repo *repo_model.Repository, run *actions_model.ActionRun) error {
if setting.MailService == nil {
return nil
}
if !run.Status.IsDone() || run.Status.IsSkipped() {
return nil
}
recipients := make([]*user_model.User, 0)
if !sender.IsGiteaActions() && !sender.IsGhost() && sender.IsMailable() {
notifyPref, err := user_model.GetUserSetting(ctx, sender.ID,
user_model.SettingsKeyEmailNotificationGiteaActions, user_model.SettingEmailNotificationGiteaActionsFailureOnly)
if err != nil {
return err
}
if notifyPref == user_model.SettingEmailNotificationGiteaActionsAll || !run.Status.IsSuccess() && notifyPref != user_model.SettingEmailNotificationGiteaActionsDisabled {
recipients = append(recipients, sender)
}
if !recipient.IsMailable() {
return nil
}
if len(recipients) > 0 {
log.Debug("MailActionsTrigger: Initiate email composition")
return composeAndSendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients)
notifyPref, err := user_model.GetUserSetting(ctx, recipient.ID,
user_model.SettingsKeyEmailNotificationGiteaActions, user_model.SettingEmailNotificationGiteaActionsFailureOnly)
if err != nil {
return err
}
return nil
// "disabled" never sends
if notifyPref == user_model.SettingEmailNotificationGiteaActionsDisabled {
return nil
}
// "failure-only" skips non-failure runs
if notifyPref != user_model.SettingEmailNotificationGiteaActionsAll && !run.Status.IsFailure() {
return nil
}
log.Debug("MailActionsTrigger: Initiate email composition")
return composeAndSendActionsWorkflowRunStatusEmail(ctx, repo, run, recipient, []*user_model.User{recipient})
}

View File

@ -16,7 +16,7 @@
</div>
<div class="field tw-inline-block tw-mr-4">
<label>{{ctx.Locale.Tr "actions.runners.labels"}}</label>
<span>
<span class="flex-text-inline tw-flex-wrap">
{{range .Runner.AgentLabels}}
<span class="ui label">{{.}}</span>
{{end}}
@ -66,7 +66,7 @@
<td><span class="ui label task-status-{{.Status.String}}">{{.Status.LocaleString ctx.Locale}}</span></td>
<td><a href="{{.GetRepoLink}}" target="_blank">{{.GetRepoName}}</a></td>
<td>
<strong><a href="{{.GetCommitLink}}" target="_blank">{{ShortSha .CommitSHA}}</a></strong>
<a class="ui sha label" href="{{.GetCommitLink}}" target="_blank">{{ShortSha .CommitSHA}}</a>
</td>
<td>{{if .IsStopped}}
<span>{{DateUtils.TimeSince .Stopped}}</span>

View File

@ -258,7 +258,7 @@ func testEditorWebGitCommitEmail(t *testing.T) {
t.Run("DefaultEmailKeepPrivate", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
paramsForKeepPrivate["commit_email"] = ""
resp1 = makeReq(t, linkForKeepPrivate, paramsForKeepPrivate, "User Two", "user2@noreply.example.org")
resp1 = makeReq(t, linkForKeepPrivate, paramsForKeepPrivate, "User Two", "2+user2@noreply.example.org")
})
t.Run("ChooseEmail", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()

View File

@ -214,6 +214,17 @@ func TestLinguist(t *testing.T) {
},
ExpectedLanguageOrder: []string{"Markdown"},
},
// case 14: linguist-detectable on a configuration/data file (YAML) without linguist-language
{
GitAttributesContent: "*.yaml linguist-detectable",
FilesToAdd: []*files_service.ChangeRepoFile{
{
TreePath: "config.yaml",
ContentReader: strings.NewReader("name: test\ndescription: A test yaml file\n"),
},
},
ExpectedLanguageOrder: []string{"YAML"},
},
}
for i, c := range cases {

View File

@ -132,14 +132,14 @@ func getExpectedFileResponseForRepoFilesCreate(commitID string, lastCommit *git.
Author: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "user2@noreply.example.org",
Email: "2+user2@noreply.example.org",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "user2@noreply.example.org",
Email: "2+user2@noreply.example.org",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
@ -202,14 +202,14 @@ func getExpectedFileResponseForRepoFilesUpdate(commitID, filename, lastCommitSHA
Author: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "user2@noreply.example.org",
Email: "2+user2@noreply.example.org",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "user2@noreply.example.org",
Email: "2+user2@noreply.example.org",
},
Date: time.Now().UTC().Format(time.RFC3339),
},
@ -312,13 +312,13 @@ func getExpectedFileResponseForRepoFilesUpdateRename(commitID, lastCommitSHA str
Author: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "user2@noreply.example.org",
Email: "2+user2@noreply.example.org",
},
},
Committer: &api.CommitUser{
Identity: api.Identity{
Name: "User Two",
Email: "user2@noreply.example.org",
Email: "2+user2@noreply.example.org",
},
},
Parents: []*api.CommitMeta{

View File

@ -111,6 +111,8 @@ type LocaleStorageOptions = {
autoScroll: boolean;
expandRunning: boolean;
showSummary: boolean;
actionsLogShowSeconds: boolean;
actionsLogShowTimestamps: boolean;
};
export default defineComponent({
@ -140,8 +142,8 @@ export default defineComponent({
},
data() {
const defaultViewOptions: LocaleStorageOptions = {autoScroll: true, expandRunning: false, showSummary: false};
const {autoScroll, expandRunning, showSummary} = localUserSettings.getJsonObject('actions-view-options', defaultViewOptions);
const defaultViewOptions: LocaleStorageOptions = {autoScroll: true, expandRunning: false, showSummary: false, actionsLogShowSeconds: false, actionsLogShowTimestamps: false};
const {autoScroll, expandRunning, showSummary, actionsLogShowSeconds, actionsLogShowTimestamps} = localUserSettings.getJsonObject('actions-view-options', defaultViewOptions);
return {
// internal state
loadingAbortController: null as AbortController | null,
@ -152,11 +154,11 @@ export default defineComponent({
isFullScreen: false,
showSummary: showSummary ?? false,
timeVisible: {
'log-time-stamp': false,
'log-time-seconds': false,
'log-time-stamp': actionsLogShowTimestamps,
'log-time-seconds': actionsLogShowSeconds,
},
optionAlwaysAutoScroll: autoScroll ?? false,
optionAlwaysExpandRunning: expandRunning ?? false,
optionAlwaysAutoScroll: autoScroll,
optionAlwaysExpandRunning: expandRunning,
// provided by backend
run: {
@ -262,7 +264,13 @@ export default defineComponent({
methods: {
saveLocaleStorageOptions() {
const opts: LocaleStorageOptions = {autoScroll: this.optionAlwaysAutoScroll, expandRunning: this.optionAlwaysExpandRunning, showSummary: this.showSummary};
const opts: LocaleStorageOptions = {
autoScroll: this.optionAlwaysAutoScroll,
expandRunning: this.optionAlwaysExpandRunning,
showSummary: this.showSummary,
actionsLogShowSeconds: this.timeVisible['log-time-seconds'],
actionsLogShowTimestamps: this.timeVisible['log-time-stamp'],
};
localUserSettings.setJsonObject('actions-view-options', opts);
},
@ -479,6 +487,7 @@ export default defineComponent({
for (const el of this.elStepsContainer().querySelectorAll(`.log-time-${type}`)) {
toggleElem(el, this.timeVisible[`log-time-${type}`]);
}
this.saveLocaleStorageOptions();
},
toggleFullScreen() {

View File

@ -97,11 +97,9 @@ async function tryOnEditContent(e: Event) {
cancelButton.addEventListener('click', cancelAndReset);
form.addEventListener('submit', saveAndRefresh);
}
// FIXME: ideally here should reload content and attachment list from backend for existing editor, to avoid losing data
if (!comboMarkdownEditor.value()) {
comboMarkdownEditor.value(rawContent.textContent);
}
// when the content has changed on server side, there is no sync, and this page doesn't have the latest content,
// the editor still shows the old content, server will reject end user's submit by "data-content-version" check
comboMarkdownEditor.value(rawContent.textContent);
comboMarkdownEditor.switchTabToEditor();
comboMarkdownEditor.focus();
triggerUploadStateChanged(comboMarkdownEditor.container);

View File

@ -58,8 +58,8 @@ export const localUserSettings = {
getJsonObject: <T extends Record<string, any>>(key: string, def: T): T => {
const value = getLocalStorageUserSetting(key);
try {
const decoded = value !== null ? JSON.parse(value) : def;
return decoded ?? def;
const decoded = value !== null ? JSON.parse(value) : null;
return {...def, ...decoded};
} catch (e) {
console.error(`Unable to parse JSON value for local user settings ${key}=${value}`, e);
}