diff options
247 files changed, 1927 insertions, 1315 deletions
diff --git a/.dockerignore b/.dockerignore index 94aca6b8d3..843f12a7be 100644 --- a/.dockerignore +++ b/.dockerignore @@ -36,15 +36,6 @@ _testmain.go coverage.all cpu.out -/modules/migration/bindata.go -/modules/migration/bindata.go.hash -/modules/options/bindata.go -/modules/options/bindata.go.hash -/modules/public/bindata.go -/modules/public/bindata.go.hash -/modules/templates/bindata.go -/modules/templates/bindata.go.hash - *.db *.log @@ -1,9 +1,6 @@ *.min.css *.min.js /assets/*.json -/modules/options/bindata.go -/modules/public/bindata.go -/modules/templates/bindata.go /options/gitignore /options/license /public/assets @@ -120,7 +120,7 @@ WEBPACK_CONFIGS := webpack.config.js tailwind.config.js WEBPACK_DEST := public/assets/js/index.js public/assets/css/index.css WEBPACK_DEST_ENTRIES := public/assets/js public/assets/css public/assets/fonts -BINDATA_DEST := modules/public/bindata.dat modules/options/bindata.dat modules/templates/bindata.dat +BINDATA_DEST_WILDCARD := modules/migration/bindata.* modules/public/bindata.* modules/options/bindata.* modules/templates/bindata.* GENERATED_GO_DEST := modules/charset/invisible_gen.go modules/charset/ambiguous_gen.go @@ -219,7 +219,7 @@ clean-all: clean ## delete backend, frontend and integration files .PHONY: clean clean: ## delete backend and integration files - rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) \ + rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST_WILDCARD) \ integrations*.test \ e2e*.test \ tests/integration/gitea-integration-* \ diff --git a/cmd/cert.go b/cmd/cert.go index 8cc9f43528..53b4f9dcb4 100644 --- a/cmd/cert.go +++ b/cmd/cert.go @@ -156,8 +156,8 @@ func runCert(_ context.Context, c *cli.Command) error { BasicConstraintsValid: true, } - hosts := strings.Split(c.String("host"), ",") - for _, h := range hosts { + hosts := strings.SplitSeq(c.String("host"), ",") + for h := range hosts { if ip := net.ParseIP(h); ip != nil { template.IPAddresses = append(template.IPAddresses, ip) } else { diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go index 8dd4fd86e7..a75b2d1b94 100644 --- a/cmd/dump_repo.go +++ b/cmd/dump_repo.go @@ -137,8 +137,8 @@ func runDumpRepository(ctx context.Context, cmd *cli.Command) error { opts.PullRequests = true opts.ReleaseAssets = true } else { - units := strings.Split(cmd.String("units"), ",") - for _, unit := range units { + units := strings.SplitSeq(cmd.String("units"), ",") + for unit := range units { switch strings.ToLower(strings.TrimSpace(unit)) { case "": continue diff --git a/cmd/hook.go b/cmd/hook.go index 4621137e01..2ce272b411 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -480,7 +480,7 @@ func hookPrintResult(output, isCreate bool, branch, url string) { func pushOptions() map[string]string { opts := make(map[string]string) if pushCount, err := strconv.Atoi(os.Getenv(private.GitPushOptionCount)); err == nil { - for idx := 0; idx < pushCount; idx++ { + for idx := range pushCount { opt := os.Getenv(fmt.Sprintf("GIT_PUSH_OPTION_%d", idx)) kv := strings.SplitN(opt, "=", 2) if len(kv) == 2 { @@ -732,7 +732,7 @@ func readPktLine(ctx context.Context, in *bufio.Reader, requestType pktLineType) // read prefix lengthBytes := make([]byte, 4) - for i := 0; i < 4; i++ { + for i := range 4 { lengthBytes[i], err = in.ReadByte() if err != nil { return nil, fail(ctx, "Protocol: stdin error", "Pkt-Line: read stdin failed : %v", err) diff --git a/contrib/backport/backport.go b/contrib/backport/backport.go index 630bd77531..6fbd610e62 100644 --- a/contrib/backport/backport.go +++ b/contrib/backport/backport.go @@ -337,8 +337,8 @@ func determineRemote(ctx context.Context, forkUser string) (string, string, erro fmt.Fprintf(os.Stderr, "Unable to list git remotes:\n%s\n", string(out)) return "", "", fmt.Errorf("unable to determine forked remote: %w", err) } - lines := strings.Split(string(out), "\n") - for _, line := range lines { + lines := strings.SplitSeq(string(out), "\n") + for line := range lines { fields := strings.Split(line, "\t") name, remote := fields[0], fields[1] // only look at pushers @@ -356,12 +356,12 @@ func determineRemote(ctx context.Context, forkUser string) (string, string, erro if !strings.Contains(remote, forkUser) { continue } - if strings.HasPrefix(remote, "git@github.com:") { - forkUser = strings.TrimPrefix(remote, "git@github.com:") - } else if strings.HasPrefix(remote, "https://github.com/") { - forkUser = strings.TrimPrefix(remote, "https://github.com/") - } else if strings.HasPrefix(remote, "https://www.github.com/") { - forkUser = strings.TrimPrefix(remote, "https://www.github.com/") + if after, ok := strings.CutPrefix(remote, "git@github.com:"); ok { + forkUser = after + } else if after, ok := strings.CutPrefix(remote, "https://github.com/"); ok { + forkUser = after + } else if after, ok := strings.CutPrefix(remote, "https://www.github.com/"); ok { + forkUser = after } else if forkUser == "" { return "", "", fmt.Errorf("unable to extract forkUser from remote %s: %s", name, remote) } @@ -91,7 +91,7 @@ require ( github.com/minio/minio-go/v7 v7.0.91 github.com/msteinert/pam v1.2.0 github.com/nektos/act v0.2.63 - github.com/niklasfasching/go-org v1.7.0 + github.com/niklasfasching/go-org v1.8.0 github.com/olivere/elastic/v7 v7.0.32 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 @@ -551,8 +551,8 @@ github.com/msteinert/pam v1.2.0 h1:mYfjlvN2KYs2Pb9G6nb/1f/nPfAttT/Jee5Sq9r3bGE= github.com/msteinert/pam v1.2.0/go.mod h1:d2n0DCUK8rGecChV3JzvmsDjOY4R7AYbsNxAT+ftQl0= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/niklasfasching/go-org v1.7.0 h1:vyMdcMWWTe/XmANk19F4k8XGBYg0GQ/gJGMimOjGMek= -github.com/niklasfasching/go-org v1.7.0/go.mod h1:WuVm4d45oePiE0eX25GqTDQIt/qPW1T9DGkRscqLW5o= +github.com/niklasfasching/go-org v1.8.0 h1:WyGLaajLLp8JbQzkmapZ1y0MOzKuKV47HkZRloi+HGY= +github.com/niklasfasching/go-org v1.8.0/go.mod h1:e2A9zJs7cdONrEGs3gvxCcaAEpwwPNPG7csDpXckMNg= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= diff --git a/models/actions/status.go b/models/actions/status.go index eda2234137..2b1d70613c 100644 --- a/models/actions/status.go +++ b/models/actions/status.go @@ -4,6 +4,8 @@ package actions import ( + "slices" + "code.gitea.io/gitea/modules/translation" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" @@ -88,12 +90,7 @@ func (s Status) IsBlocked() bool { // In returns whether s is one of the given statuses func (s Status) In(statuses ...Status) bool { - for _, v := range statuses { - if s == v { - return true - } - } - return false + return slices.Contains(statuses, s) } func (s Status) AsResult() runnerv1.Result { diff --git a/models/activities/action.go b/models/activities/action.go index 6f1837d9f6..1a0dfe6412 100644 --- a/models/activities/action.go +++ b/models/activities/action.go @@ -9,6 +9,7 @@ import ( "fmt" "net/url" "path" + "slices" "strconv" "strings" "time" @@ -125,12 +126,7 @@ func (at ActionType) String() string { } func (at ActionType) InActions(actions ...string) bool { - for _, action := range actions { - if action == at.String() { - return true - } - } - return false + return slices.Contains(actions, at.String()) } // Action represents user operation type and other information to @@ -191,7 +187,7 @@ func (a *Action) LoadActUser(ctx context.Context) { return } var err error - a.ActUser, err = user_model.GetUserByID(ctx, a.ActUserID) + a.ActUser, err = user_model.GetPossibleUserByID(ctx, a.ActUserID) if err == nil { return } else if user_model.IsErrUserNotExist(err) { diff --git a/models/activities/notification_list.go b/models/activities/notification_list.go index 0cbb91df3c..b47f5dc404 100644 --- a/models/activities/notification_list.go +++ b/models/activities/notification_list.go @@ -208,10 +208,7 @@ func (nl NotificationList) LoadRepos(ctx context.Context) (repo_model.Repository repos := make(map[int64]*repo_model.Repository, len(repoIDs)) left := len(repoIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := db.GetEngine(ctx). In("id", repoIDs[:limit]). Rows(new(repo_model.Repository)) @@ -282,10 +279,7 @@ func (nl NotificationList) LoadIssues(ctx context.Context) ([]int, error) { issues := make(map[int64]*issues_model.Issue, len(issueIDs)) left := len(issueIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := db.GetEngine(ctx). In("id", issueIDs[:limit]). Rows(new(issues_model.Issue)) @@ -377,10 +371,7 @@ func (nl NotificationList) LoadUsers(ctx context.Context) ([]int, error) { users := make(map[int64]*user_model.User, len(userIDs)) left := len(userIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := db.GetEngine(ctx). In("id", userIDs[:limit]). Rows(new(user_model.User)) @@ -428,10 +419,7 @@ func (nl NotificationList) LoadComments(ctx context.Context) ([]int, error) { comments := make(map[int64]*issues_model.Comment, len(commentIDs)) left := len(commentIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := db.GetEngine(ctx). In("id", commentIDs[:limit]). Rows(new(issues_model.Comment)) diff --git a/models/activities/repo_activity.go b/models/activities/repo_activity.go index 3ccdbd47d3..aeaa452c9e 100644 --- a/models/activities/repo_activity.go +++ b/models/activities/repo_activity.go @@ -139,10 +139,7 @@ func GetActivityStatsTopAuthors(ctx context.Context, repo *repo_model.Repository return v[i].Commits > v[j].Commits }) - cnt := count - if cnt > len(v) { - cnt = len(v) - } + cnt := min(count, len(v)) return v[:cnt], nil } diff --git a/models/auth/access_token_scope.go b/models/auth/access_token_scope.go index 2293fd89a0..3eae19b2a5 100644 --- a/models/auth/access_token_scope.go +++ b/models/auth/access_token_scope.go @@ -213,12 +213,7 @@ func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTok // ContainsCategory checks if a list of categories contains a specific category func ContainsCategory(categories []AccessTokenScopeCategory, category AccessTokenScopeCategory) bool { - for _, c := range categories { - if c == category { - return true - } - } - return false + return slices.Contains(categories, category) } // GetScopeLevelFromAccessMode converts permission access mode to scope level diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index c270e4856e..c2b6690116 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -12,6 +12,7 @@ import ( "fmt" "net" "net/url" + "slices" "strings" "code.gitea.io/gitea/models/db" @@ -511,12 +512,7 @@ func (grant *OAuth2Grant) IncreaseCounter(ctx context.Context) error { // ScopeContains returns true if the grant scope contains the specified scope func (grant *OAuth2Grant) ScopeContains(scope string) bool { - for _, currentScope := range strings.Split(grant.Scope, " ") { - if scope == currentScope { - return true - } - } - return false + return slices.Contains(strings.Split(grant.Scope, " "), scope) } // SetNonce updates the current nonce value of a grant diff --git a/models/db/context.go b/models/db/context.go index 4b98796ef0..05d7d72daa 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -67,7 +67,7 @@ func contextSafetyCheck(e Engine) { _ = e.SQL("SELECT 1").Iterate(&m{}, func(int, any) error { callers := make([]uintptr, 32) callerNum := runtime.Callers(1, callers) - for i := 0; i < callerNum; i++ { + for i := range callerNum { if funcName := runtime.FuncForPC(callers[i]).Name(); funcName == "xorm.io/xorm.(*Session).Iterate" { contextSafetyDeniedFuncPCs = append(contextSafetyDeniedFuncPCs, callers[i]) } @@ -82,7 +82,7 @@ func contextSafetyCheck(e Engine) { // it should be very fast: xxxx ns/op callers := make([]uintptr, 32) callerNum := runtime.Callers(3, callers) // skip 3: runtime.Callers, contextSafetyCheck, GetEngine - for i := 0; i < callerNum; i++ { + for i := range callerNum { if slices.Contains(contextSafetyDeniedFuncPCs, callers[i]) { panic(errors.New("using database context in an iterator would cause corrupted results")) } diff --git a/models/db/name.go b/models/db/name.go index 0e11c78372..48c7fdbce5 100644 --- a/models/db/name.go +++ b/models/db/name.go @@ -5,6 +5,7 @@ package db import ( "fmt" + "slices" "strings" "unicode/utf8" @@ -80,10 +81,8 @@ func IsUsableName(reservedNames, reservedPatterns []string, name string) error { return util.NewInvalidArgumentErrorf("name is empty") } - for i := range reservedNames { - if name == reservedNames[i] { - return ErrNameReserved{name} - } + if slices.Contains(reservedNames, name) { + return ErrNameReserved{name} } for _, pat := range reservedPatterns { diff --git a/models/dbfs/dbfile.go b/models/dbfs/dbfile.go index dd27b5c36b..eaf506fbe6 100644 --- a/models/dbfs/dbfile.go +++ b/models/dbfs/dbfile.go @@ -46,10 +46,7 @@ func (f *file) readAt(fileMeta *dbfsMeta, offset int64, p []byte) (n int, err er blobPos := int(offset % f.blockSize) blobOffset := offset - int64(blobPos) blobRemaining := int(f.blockSize) - blobPos - needRead := len(p) - if needRead > blobRemaining { - needRead = blobRemaining - } + needRead := min(len(p), blobRemaining) if blobOffset+int64(blobPos)+int64(needRead) > fileMeta.FileSize { needRead = int(fileMeta.FileSize - blobOffset - int64(blobPos)) } @@ -66,14 +63,8 @@ func (f *file) readAt(fileMeta *dbfsMeta, offset int64, p []byte) (n int, err er blobData = nil } - canCopy := len(blobData) - blobPos - if canCopy <= 0 { - canCopy = 0 - } - realRead := needRead - if realRead > canCopy { - realRead = canCopy - } + canCopy := max(len(blobData)-blobPos, 0) + realRead := min(needRead, canCopy) if realRead > 0 { copy(p[:realRead], fileData.BlobData[blobPos:blobPos+realRead]) } @@ -113,10 +104,7 @@ func (f *file) Write(p []byte) (n int, err error) { blobPos := int(f.offset % f.blockSize) blobOffset := f.offset - int64(blobPos) blobRemaining := int(f.blockSize) - blobPos - needWrite := len(p) - if needWrite > blobRemaining { - needWrite = blobRemaining - } + needWrite := min(len(p), blobRemaining) buf := make([]byte, f.blockSize) readBytes, err := f.readAt(fileMeta, blobOffset, buf) if err != nil && !errors.Is(err, io.EOF) { diff --git a/models/fixtures/branch.yml b/models/fixtures/branch.yml index 6536e1dda7..03e21d04b4 100644 --- a/models/fixtures/branch.yml +++ b/models/fixtures/branch.yml @@ -201,3 +201,15 @@ is_deleted: false deleted_by_id: 0 deleted_unix: 0 + +- + id: 25 + repo_id: 54 + name: 'master' + commit_id: '73cf03db6ece34e12bf91e8853dc58f678f2f82d' + commit_message: 'Initial commit' + commit_time: 1671663402 + pusher_id: 2 + is_deleted: false + deleted_by_id: 0 + deleted_unix: 0 diff --git a/models/git/protected_branch.go b/models/git/protected_branch.go index a3caed73c4..19b02ccab9 100644 --- a/models/git/protected_branch.go +++ b/models/git/protected_branch.go @@ -246,7 +246,7 @@ func (protectBranch *ProtectedBranch) GetUnprotectedFilePatterns() []glob.Glob { func getFilePatterns(filePatterns string) []glob.Glob { extarr := make([]glob.Glob, 0, 10) - for _, expr := range strings.Split(strings.ToLower(filePatterns), ";") { + for expr := range strings.SplitSeq(strings.ToLower(filePatterns), ";") { expr = strings.TrimSpace(expr) if expr != "" { if g, err := glob.Compile(expr, '.', '/'); err != nil { diff --git a/models/issues/comment.go b/models/issues/comment.go index ab9b2042f3..9bef96d0dd 100644 --- a/models/issues/comment.go +++ b/models/issues/comment.go @@ -9,6 +9,7 @@ import ( "context" "fmt" "html/template" + "slices" "strconv" "unicode/utf8" @@ -196,12 +197,7 @@ func (t CommentType) HasMailReplySupport() bool { } func (t CommentType) CountedAsConversation() bool { - for _, ct := range ConversationCountedCommentType() { - if t == ct { - return true - } - } - return false + return slices.Contains(ConversationCountedCommentType(), t) } // ConversationCountedCommentType returns the comment types that are counted as a conversation @@ -614,7 +610,7 @@ func UpdateCommentAttachments(ctx context.Context, c *Comment, uuids []string) e if err != nil { return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err) } - for i := 0; i < len(attachments); i++ { + for i := range attachments { attachments[i].IssueID = c.IssueID attachments[i].CommentID = c.ID if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil { diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go index c483ada75a..f6c485449f 100644 --- a/models/issues/comment_list.go +++ b/models/issues/comment_list.go @@ -57,10 +57,7 @@ func (comments CommentList) loadLabels(ctx context.Context) error { commentLabels := make(map[int64]*Label, len(labelIDs)) left := len(labelIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := db.GetEngine(ctx). In("id", labelIDs[:limit]). Rows(new(Label)) @@ -107,10 +104,7 @@ func (comments CommentList) loadMilestones(ctx context.Context) error { milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) left := len(milestoneIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) err := db.GetEngine(ctx). In("id", milestoneIDs[:limit]). Find(&milestoneMaps) @@ -146,10 +140,7 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error { milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) left := len(milestoneIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) err := db.GetEngine(ctx). In("id", milestoneIDs[:limit]). Find(&milestoneMaps) @@ -184,10 +175,7 @@ func (comments CommentList) loadAssignees(ctx context.Context) error { assignees := make(map[int64]*user_model.User, len(assigneeIDs)) left := len(assigneeIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := db.GetEngine(ctx). In("id", assigneeIDs[:limit]). Rows(new(user_model.User)) @@ -256,10 +244,7 @@ func (comments CommentList) LoadIssues(ctx context.Context) error { issues := make(map[int64]*Issue, len(issueIDs)) left := len(issueIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := db.GetEngine(ctx). In("id", issueIDs[:limit]). Rows(new(Issue)) @@ -313,10 +298,7 @@ func (comments CommentList) loadDependentIssues(ctx context.Context) error { issues := make(map[int64]*Issue, len(issueIDs)) left := len(issueIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := e. In("id", issueIDs[:limit]). Rows(new(Issue)) @@ -392,10 +374,7 @@ func (comments CommentList) LoadAttachments(ctx context.Context) (err error) { commentsIDs := comments.getAttachmentCommentIDs() left := len(commentsIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := db.GetEngine(ctx). In("comment_id", commentsIDs[:limit]). Rows(new(repo_model.Attachment)) diff --git a/models/issues/issue_list.go b/models/issues/issue_list.go index 6c74b533b3..26b93189b8 100644 --- a/models/issues/issue_list.go +++ b/models/issues/issue_list.go @@ -42,10 +42,7 @@ func (issues IssueList) LoadRepositories(ctx context.Context) (repo_model.Reposi repoMaps := make(map[int64]*repo_model.Repository, len(repoIDs)) left := len(repoIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) err := db.GetEngine(ctx). In("id", repoIDs[:limit]). Find(&repoMaps) @@ -116,10 +113,7 @@ func (issues IssueList) LoadLabels(ctx context.Context) error { issueIDs := issues.getIssueIDs() left := len(issueIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := db.GetEngine(ctx).Table("label"). Join("LEFT", "issue_label", "issue_label.label_id = label.id"). In("issue_label.issue_id", issueIDs[:limit]). @@ -171,10 +165,7 @@ func (issues IssueList) LoadMilestones(ctx context.Context) error { milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) left := len(milestoneIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) err := db.GetEngine(ctx). In("id", milestoneIDs[:limit]). Find(&milestoneMaps) @@ -203,10 +194,7 @@ func (issues IssueList) LoadProjects(ctx context.Context) error { } for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) projects := make([]*projectWithIssueID, 0, limit) err := db.GetEngine(ctx). @@ -245,10 +233,7 @@ func (issues IssueList) LoadAssignees(ctx context.Context) error { issueIDs := issues.getIssueIDs() left := len(issueIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := db.GetEngine(ctx).Table("issue_assignees"). Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id"). In("`issue_assignees`.issue_id", issueIDs[:limit]).OrderBy(user_model.GetOrderByName()). @@ -306,10 +291,7 @@ func (issues IssueList) LoadPullRequests(ctx context.Context) error { pullRequestMaps := make(map[int64]*PullRequest, len(issuesIDs)) left := len(issuesIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := db.GetEngine(ctx). In("issue_id", issuesIDs[:limit]). Rows(new(PullRequest)) @@ -354,10 +336,7 @@ func (issues IssueList) LoadAttachments(ctx context.Context) (err error) { issuesIDs := issues.getIssueIDs() left := len(issuesIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := db.GetEngine(ctx). In("issue_id", issuesIDs[:limit]). Rows(new(repo_model.Attachment)) @@ -399,10 +378,7 @@ func (issues IssueList) loadComments(ctx context.Context, cond builder.Cond) (er issuesIDs := issues.getIssueIDs() left := len(issuesIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) rows, err := db.GetEngine(ctx).Table("comment"). Join("INNER", "issue", "issue.id = comment.issue_id"). In("issue.id", issuesIDs[:limit]). @@ -466,10 +442,7 @@ func (issues IssueList) loadTotalTrackedTimes(ctx context.Context) (err error) { left := len(ids) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) // select issue_id, sum(time) from tracked_time where issue_id in (<issue ids in current page>) group by issue_id rows, err := db.GetEngine(ctx).Table("tracked_time"). diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go index 16f808acd1..84d5948640 100644 --- a/models/issues/issue_search.go +++ b/models/issues/issue_search.go @@ -73,8 +73,8 @@ func (o *IssuesOptions) Copy(edit ...func(options *IssuesOptions)) *IssuesOption // sortType string func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) { // Since this sortType is dynamically created, it has to be treated specially. - if strings.HasPrefix(sortType, ScopeSortPrefix) { - scope := strings.TrimPrefix(sortType, ScopeSortPrefix) + if after, ok := strings.CutPrefix(sortType, ScopeSortPrefix); ok { + scope := after sess.Join("LEFT", "issue_label", "issue.id = issue_label.issue_id") // "exclusive_order=0" means "no order is set", so exclude it from the JOIN criteria and then "LEFT JOIN" result is also null sess.Join("LEFT", "label", "label.id = issue_label.label_id AND label.exclusive_order <> 0 AND label.name LIKE ?", scope+"/%") diff --git a/models/issues/issue_stats.go b/models/issues/issue_stats.go index 50409fbbd8..adedaa3d3a 100644 --- a/models/issues/issue_stats.go +++ b/models/issues/issue_stats.go @@ -94,10 +94,7 @@ func GetIssueStats(ctx context.Context, opts *IssuesOptions) (*IssueStats, error // ids in a temporary table and join from them. accum := &IssueStats{} for i := 0; i < len(opts.IssueIDs); { - chunk := i + MaxQueryParameters - if chunk > len(opts.IssueIDs) { - chunk = len(opts.IssueIDs) - } + chunk := min(i+MaxQueryParameters, len(opts.IssueIDs)) stats, err := getIssueStatsChunk(ctx, opts, opts.IssueIDs[i:chunk]) if err != nil { return nil, err diff --git a/models/issues/issue_test.go b/models/issues/issue_test.go index 18571e3aaa..1c5db55bbc 100644 --- a/models/issues/issue_test.go +++ b/models/issues/issue_test.go @@ -5,6 +5,7 @@ package issues_test import ( "fmt" + "slices" "sort" "sync" "testing" @@ -270,7 +271,7 @@ func TestIssue_ResolveMentions(t *testing.T) { for i, user := range resolved { ids[i] = user.ID } - sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] }) + slices.Sort(ids) assert.Equal(t, expected, ids) } @@ -292,7 +293,7 @@ func TestResourceIndex(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) var wg sync.WaitGroup - for i := 0; i < 100; i++ { + for i := range 100 { wg.Add(1) go func(i int) { testInsertIssue(t, fmt.Sprintf("issue %d", i+1), "my issue", 0) @@ -314,7 +315,7 @@ func TestCorrectIssueStats(t *testing.T) { issueAmount := issues_model.MaxQueryParameters + 10 var wg sync.WaitGroup - for i := 0; i < issueAmount; i++ { + for i := range issueAmount { wg.Add(1) go func(i int) { testInsertIssue(t, fmt.Sprintf("Issue %d", i+1), "Bugs are nasty", 0) diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go index 2466fcf30f..9b99787e3b 100644 --- a/models/issues/issue_update.go +++ b/models/issues/issue_update.go @@ -304,7 +304,7 @@ func UpdateIssueAttachments(ctx context.Context, issueID int64, uuids []string) if err != nil { return fmt.Errorf("getAttachmentsByUUIDs [uuids: %v]: %w", uuids, err) } - for i := 0; i < len(attachments); i++ { + for i := range attachments { attachments[i].IssueID = issueID if err := repo_model.UpdateAttachment(ctx, attachments[i]); err != nil { return fmt.Errorf("update attachment [id: %d]: %w", attachments[i].ID, err) diff --git a/models/issues/pull.go b/models/issues/pull.go index e65b214dab..0ff32e2473 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -649,12 +649,6 @@ func GetAllUnmergedAgitPullRequestByPoster(ctx context.Context, uid int64) ([]*P return pulls, err } -// Update updates all fields of pull request. -func (pr *PullRequest) Update(ctx context.Context) error { - _, err := db.GetEngine(ctx).ID(pr.ID).AllCols().Update(pr) - return err -} - // UpdateCols updates specific fields of pull request. func (pr *PullRequest) UpdateCols(ctx context.Context, cols ...string) error { _, err := db.GetEngine(ctx).ID(pr.ID).Cols(cols...).Update(pr) diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go index 53898cb42e..39efaa5792 100644 --- a/models/issues/pull_test.go +++ b/models/issues/pull_test.go @@ -248,19 +248,6 @@ func TestGetPullRequestByIssueID(t *testing.T) { assert.True(t, issues_model.IsErrPullRequestNotExist(err)) } -func TestPullRequest_Update(t *testing.T) { - assert.NoError(t, unittest.PrepareTestDatabase()) - pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: 1}) - pr.BaseBranch = "baseBranch" - pr.HeadBranch = "headBranch" - pr.Update(db.DefaultContext) - - pr = unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ID: pr.ID}) - assert.Equal(t, "baseBranch", pr.BaseBranch) - assert.Equal(t, "headBranch", pr.HeadBranch) - unittest.CheckConsistencyFor(t, pr) -} - func TestPullRequest_UpdateCols(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) pr := &issues_model.PullRequest{ diff --git a/models/issues/review_list.go b/models/issues/review_list.go index 928f24fb2d..bbb8c489fa 100644 --- a/models/issues/review_list.go +++ b/models/issues/review_list.go @@ -22,7 +22,7 @@ type ReviewList []*Review // LoadReviewers loads reviewers func (reviews ReviewList) LoadReviewers(ctx context.Context) error { reviewerIDs := make([]int64, len(reviews)) - for i := 0; i < len(reviews); i++ { + for i := range reviews { reviewerIDs[i] = reviews[i].ReviewerID } reviewers, err := user_model.GetPossibleUserByIDs(ctx, reviewerIDs) diff --git a/models/issues/tracked_time.go b/models/issues/tracked_time.go index ea404d36cd..2afbe272ed 100644 --- a/models/issues/tracked_time.go +++ b/models/issues/tracked_time.go @@ -350,10 +350,7 @@ func GetIssueTotalTrackedTime(ctx context.Context, opts *IssuesOptions, isClosed // we get the statistics in smaller chunks and get accumulates var accum int64 for i := 0; i < len(opts.IssueIDs); { - chunk := i + MaxQueryParameters - if chunk > len(opts.IssueIDs) { - chunk = len(opts.IssueIDs) - } + chunk := min(i+MaxQueryParameters, len(opts.IssueIDs)) time, err := getIssueTotalTrackedTimeChunk(ctx, opts, isClosed, opts.IssueIDs[i:chunk]) if err != nil { return 0, err diff --git a/models/migrations/base/db.go b/models/migrations/base/db.go index 4ecc930f10..479a46379c 100644 --- a/models/migrations/base/db.go +++ b/models/migrations/base/db.go @@ -518,7 +518,7 @@ func ModifyColumn(x *xorm.Engine, tableName string, col *schemas.Column) error { func removeAllWithRetry(dir string) error { var err error - for i := 0; i < 20; i++ { + for range 20 { err = os.RemoveAll(dir) if err == nil { break diff --git a/models/migrations/v1_11/v111.go b/models/migrations/v1_11/v111.go index ff108479a9..1c8527b2aa 100644 --- a/models/migrations/v1_11/v111.go +++ b/models/migrations/v1_11/v111.go @@ -5,6 +5,7 @@ package v1_11 //nolint import ( "fmt" + "slices" "xorm.io/xorm" ) @@ -344,10 +345,8 @@ func AddBranchProtectionCanPushAndEnableWhitelist(x *xorm.Engine) error { } return AccessModeWrite <= perm.UnitsMode[UnitTypeCode], nil } - for _, id := range protectedBranch.ApprovalsWhitelistUserIDs { - if id == reviewer.ID { - return true, nil - } + if slices.Contains(protectedBranch.ApprovalsWhitelistUserIDs, reviewer.ID) { + return true, nil } // isUserInTeams diff --git a/models/migrations/v1_11/v115.go b/models/migrations/v1_11/v115.go index 8c631cfd0b..c44c6d88e4 100644 --- a/models/migrations/v1_11/v115.go +++ b/models/migrations/v1_11/v115.go @@ -146,7 +146,7 @@ func copyOldAvatarToNewLocation(userID int64, oldAvatar string) (string, error) return "", fmt.Errorf("io.ReadAll: %w", err) } - newAvatar := fmt.Sprintf("%x", md5.Sum([]byte(fmt.Sprintf("%d-%x", userID, md5.Sum(data))))) + newAvatar := fmt.Sprintf("%x", md5.Sum(fmt.Appendf(nil, "%d-%x", userID, md5.Sum(data)))) if newAvatar == oldAvatar { return newAvatar, nil } diff --git a/models/migrations/v1_20/v259.go b/models/migrations/v1_20/v259.go index 5b8ced4ad7..0fdeb45957 100644 --- a/models/migrations/v1_20/v259.go +++ b/models/migrations/v1_20/v259.go @@ -329,7 +329,7 @@ func ConvertScopedAccessTokens(x *xorm.Engine) error { for _, token := range tokens { var scopes []string allNewScopesMap := make(map[AccessTokenScope]bool) - for _, oldScope := range strings.Split(token.Scope, ",") { + for oldScope := range strings.SplitSeq(token.Scope, ",") { if newScopes, exists := accessTokenScopeMap[OldAccessTokenScope(oldScope)]; exists { for _, newScope := range newScopes { allNewScopesMap[newScope] = true diff --git a/models/packages/container/search.go b/models/packages/container/search.go index a513da08b9..9321d9eb41 100644 --- a/models/packages/container/search.go +++ b/models/packages/container/search.go @@ -44,7 +44,7 @@ func (opts *BlobSearchOptions) toConds() builder.Cond { cond = cond.And(builder.Eq{"package_version.lower_version": strings.ToLower(opts.Tag)}) } if opts.IsManifest { - cond = cond.And(builder.Eq{"package_file.lower_name": ManifestFilename}) + cond = cond.And(builder.Eq{"package_file.lower_name": container_module.ManifestFilename}) } if opts.OnlyLead { cond = cond.And(builder.Eq{"package_file.is_lead": true}) @@ -235,7 +235,7 @@ func SearchImageTags(ctx context.Context, opts *ImageTagsSearchOptions) ([]*pack func SearchExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) ([]*packages.PackageFile, error) { var cond builder.Cond = builder.Eq{ "package_version.is_internal": true, - "package_version.lower_version": UploadVersion, + "package_version.lower_version": container_module.UploadVersion, "package.type": packages.TypeContainer, } cond = cond.And(builder.Lt{"package_file.created_unix": time.Now().Add(-olderThan).Unix()}) diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go index 1ea181c723..2d43dc3046 100644 --- a/models/packages/descriptor.go +++ b/models/packages/descriptor.go @@ -103,10 +103,10 @@ func (pd *PackageDescriptor) CalculateBlobSize() int64 { // GetPackageDescriptor gets the package description for a version func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDescriptor, error) { - return getPackageDescriptor(ctx, pv, cache.NewEphemeralCache()) + return GetPackageDescriptorWithCache(ctx, pv, cache.NewEphemeralCache()) } -func getPackageDescriptor(ctx context.Context, pv *PackageVersion, c *cache.EphemeralCache) (*PackageDescriptor, error) { +func GetPackageDescriptorWithCache(ctx context.Context, pv *PackageVersion, c *cache.EphemeralCache) (*PackageDescriptor, error) { p, err := cache.GetWithEphemeralCache(ctx, c, "package", pv.PackageID, GetPackageByID) if err != nil { return nil, err @@ -270,7 +270,7 @@ func GetPackageDescriptors(ctx context.Context, pvs []*PackageVersion) ([]*Packa func getPackageDescriptors(ctx context.Context, pvs []*PackageVersion, c *cache.EphemeralCache) ([]*PackageDescriptor, error) { pds := make([]*PackageDescriptor, 0, len(pvs)) for _, pv := range pvs { - pd, err := getPackageDescriptor(ctx, pv, c) + pd, err := GetPackageDescriptorWithCache(ctx, pv, c) if err != nil { return nil, err } diff --git a/models/project/column_test.go b/models/project/column_test.go index 5b93e7760f..6a615090a5 100644 --- a/models/project/column_test.go +++ b/models/project/column_test.go @@ -110,7 +110,7 @@ func Test_NewColumn(t *testing.T) { assert.NoError(t, err) assert.Len(t, columns, 3) - for i := 0; i < maxProjectColumns-3; i++ { + for i := range maxProjectColumns - 3 { err := NewColumn(db.DefaultContext, &Column{ Title: fmt.Sprintf("column-%d", i+4), ProjectID: project1.ID, diff --git a/models/pull/review_state.go b/models/pull/review_state.go index e46a22a49d..137af00eab 100644 --- a/models/pull/review_state.go +++ b/models/pull/review_state.go @@ -6,6 +6,7 @@ package pull import ( "context" "fmt" + "maps" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/log" @@ -100,9 +101,7 @@ func mergeFiles(oldFiles, newFiles map[string]ViewedState) map[string]ViewedStat return oldFiles } - for file, viewed := range newFiles { - oldFiles[file] = viewed - } + maps.Copy(oldFiles, newFiles) return oldFiles } diff --git a/models/repo/release.go b/models/repo/release.go index 06cfa37342..59f4caf5aa 100644 --- a/models/repo/release.go +++ b/models/repo/release.go @@ -180,7 +180,7 @@ func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs } attachments[i].ReleaseID = releaseID // No assign value could be 0, so ignore AllCols(). - if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Update(attachments[i]); err != nil { + if _, err = db.GetEngine(ctx).ID(attachments[i].ID).Cols("release_id").Update(attachments[i]); err != nil { return fmt.Errorf("update attachment [%d]: %w", attachments[i].ID, err) } } diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index b2f722dbb3..f2cdd2f284 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -449,7 +449,7 @@ func SearchRepositoryCondition(opts SearchRepoOptions) builder.Cond { if opts.Keyword != "" { // separate keyword subQueryCond := builder.NewCond() - for _, v := range strings.Split(opts.Keyword, ",") { + for v := range strings.SplitSeq(opts.Keyword, ",") { if opts.TopicOnly { subQueryCond = subQueryCond.Or(builder.Eq{"topic.name": strings.ToLower(v)}) } else { @@ -464,7 +464,7 @@ func SearchRepositoryCondition(opts SearchRepoOptions) builder.Cond { keywordCond := builder.In("id", subQuery) if !opts.TopicOnly { likes := builder.NewCond() - for _, v := range strings.Split(opts.Keyword, ",") { + for v := range strings.SplitSeq(opts.Keyword, ",") { likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)}) // If the string looks like "org/repo", match against that pattern too diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index 8a7dbfe340..a5207bc22a 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -185,10 +185,8 @@ func (cfg *ActionsConfig) IsWorkflowDisabled(file string) bool { } func (cfg *ActionsConfig) DisableWorkflow(file string) { - for _, workflow := range cfg.DisabledWorkflows { - if file == workflow { - return - } + if slices.Contains(cfg.DisabledWorkflows, file) { + return } cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file) diff --git a/models/repo/update.go b/models/repo/update.go index 8a15477a80..f82ff7c76c 100644 --- a/models/repo/update.go +++ b/models/repo/update.go @@ -42,12 +42,18 @@ func UpdateRepositoryUpdatedTime(ctx context.Context, repoID int64, updateTime t // UpdateRepositoryColsWithAutoTime updates repository's columns func UpdateRepositoryColsWithAutoTime(ctx context.Context, repo *Repository, cols ...string) error { + if len(cols) == 0 { + return nil + } _, err := db.GetEngine(ctx).ID(repo.ID).Cols(cols...).Update(repo) return err } // UpdateRepositoryColsNoAutoTime updates repository's columns and but applies time change automatically func UpdateRepositoryColsNoAutoTime(ctx context.Context, repo *Repository, cols ...string) error { + if len(cols) == 0 { + return nil + } _, err := db.GetEngine(ctx).ID(repo.ID).Cols(cols...).NoAutoTime().Update(repo) return err } diff --git a/models/repo/upload.go b/models/repo/upload.go index fb57fb6c51..20a8fa26fe 100644 --- a/models/repo/upload.go +++ b/models/repo/upload.go @@ -124,7 +124,7 @@ func DeleteUploads(ctx context.Context, uploads ...*Upload) (err error) { defer committer.Close() ids := make([]int64, len(uploads)) - for i := 0; i < len(uploads); i++ { + for i := range uploads { ids[i] = uploads[i].ID } if err = db.DeleteByIDs[Upload](ctx, ids...); err != nil { diff --git a/models/unit/unit.go b/models/unit/unit.go index 4ca676802f..c0560678ca 100644 --- a/models/unit/unit.go +++ b/models/unit/unit.go @@ -6,6 +6,7 @@ package unit import ( "errors" "fmt" + "slices" "strings" "sync/atomic" @@ -204,22 +205,12 @@ func LoadUnitConfig() error { // UnitGlobalDisabled checks if unit type is global disabled func (u Type) UnitGlobalDisabled() bool { - for _, ud := range DisabledRepoUnitsGet() { - if u == ud { - return true - } - } - return false + return slices.Contains(DisabledRepoUnitsGet(), u) } // CanBeDefault checks if the unit type can be a default repo unit func (u *Type) CanBeDefault() bool { - for _, nadU := range NotAllowedDefaultRepoUnits { - if *u == nadU { - return false - } - } - return true + return !slices.Contains(NotAllowedDefaultRepoUnits, *u) } // Unit is a section of one repository diff --git a/models/user/email_address_test.go b/models/user/email_address_test.go index 0e52950cfd..c0666246b0 100644 --- a/models/user/email_address_test.go +++ b/models/user/email_address_test.go @@ -4,6 +4,7 @@ package user_test import ( + "slices" "testing" "code.gitea.io/gitea/models/db" @@ -100,12 +101,7 @@ func TestListEmails(t *testing.T) { assert.Greater(t, count, int64(5)) contains := func(match func(s *user_model.SearchEmailResult) bool) bool { - for _, v := range emails { - if match(v) { - return true - } - } - return false + return slices.ContainsFunc(emails, match) } assert.True(t, contains(func(s *user_model.SearchEmailResult) bool { return s.UID == 18 })) diff --git a/models/user/user_list.go b/models/user/user_list.go index 4241905058..1b6a27dd86 100644 --- a/models/user/user_list.go +++ b/models/user/user_list.go @@ -17,10 +17,7 @@ func GetUsersMapByIDs(ctx context.Context, userIDs []int64) (map[int64]*User, er left := len(userIDs) for left > 0 { - limit := db.DefaultMaxInSize - if left < limit { - limit = left - } + limit := min(left, db.DefaultMaxInSize) err := db.GetEngine(ctx). In("id", userIDs[:limit]). Find(&userMaps) diff --git a/models/user/user_test.go b/models/user/user_test.go index 93b6e68a8d..a2597ba3f5 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -204,9 +204,9 @@ func TestHashPasswordDeterministic(t *testing.T) { b := make([]byte, 16) u := &user_model.User{} algos := hash.RecommendedHashAlgorithms - for j := 0; j < len(algos); j++ { + for j := range algos { u.PasswdHashAlgo = algos[j] - for i := 0; i < 50; i++ { + for range 50 { // generate a random password rand.Read(b) pass := string(b) diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go index 97ad373027..b234d9ffee 100644 --- a/models/webhook/webhook.go +++ b/models/webhook/webhook.go @@ -240,7 +240,7 @@ func CreateWebhooks(ctx context.Context, ws []*Webhook) error { if len(ws) == 0 { return nil } - for i := 0; i < len(ws); i++ { + for i := range ws { ws[i].Type = strings.TrimSpace(ws[i].Type) } return db.Insert(ctx, ws) diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 84b3225338..5cdd45b046 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -6,6 +6,7 @@ package actions import ( "bytes" "io" + "slices" "strings" "code.gitea.io/gitea/modules/git" @@ -566,11 +567,8 @@ func matchPullRequestReviewEvent(prPayload *api.PullRequestPayload, evt *jobpars matched := false for _, val := range vals { - for _, action := range actions { - if glob.MustCompile(val, '/').Match(action) { - matched = true - break - } + if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) { + matched = true } if matched { break @@ -615,11 +613,8 @@ func matchPullRequestReviewCommentEvent(prPayload *api.PullRequestPayload, evt * matched := false for _, val := range vals { - for _, action := range actions { - if glob.MustCompile(val, '/').Match(action) { - matched = true - break - } + if slices.ContainsFunc(actions, glob.MustCompile(val, '/').Match) { + matched = true } if matched { break diff --git a/modules/auth/password/password.go b/modules/auth/password/password.go index c66b62937f..a1e101dd62 100644 --- a/modules/auth/password/password.go +++ b/modules/auth/password/password.go @@ -101,7 +101,7 @@ func Generate(n int) (string, error) { buffer := make([]byte, n) maxInt := big.NewInt(int64(len(validChars))) for { - for j := 0; j < n; j++ { + for j := range n { rnd, err := rand.Int(rand.Reader, maxInt) if err != nil { return "", err diff --git a/modules/auth/password/password_test.go b/modules/auth/password/password_test.go index 6c35dc86bd..0fea593c85 100644 --- a/modules/auth/password/password_test.go +++ b/modules/auth/password/password_test.go @@ -50,7 +50,7 @@ func TestComplexity_Generate(t *testing.T) { test := func(t *testing.T, modes []string) { testComplextity(modes) - for i := 0; i < maxCount; i++ { + for range maxCount { pwd, err := Generate(pwdLen) assert.NoError(t, err) assert.Len(t, pwd, pwdLen) diff --git a/modules/auth/password/pwn/pwn.go b/modules/auth/password/pwn/pwn.go index f77ce9f40b..99a6ca6cea 100644 --- a/modules/auth/password/pwn/pwn.go +++ b/modules/auth/password/pwn/pwn.go @@ -101,7 +101,7 @@ func (c *Client) CheckPassword(pw string, padding bool) (int, error) { } defer resp.Body.Close() - for _, pair := range strings.Split(string(body), "\n") { + for pair := range strings.SplitSeq(string(body), "\n") { parts := strings.Split(pair, ":") if len(parts) != 2 { continue diff --git a/modules/avatar/identicon/block.go b/modules/avatar/identicon/block.go index cb1803a231..fc8ce90212 100644 --- a/modules/avatar/identicon/block.go +++ b/modules/avatar/identicon/block.go @@ -24,8 +24,8 @@ func drawBlock(img *image.Paletted, x, y, size, angle int, points []int) { rotate(points, m, m, angle) } - for i := 0; i < size; i++ { - for j := 0; j < size; j++ { + for i := range size { + for j := range size { if pointInPolygon(i, j, points) { img.SetColorIndex(x+i, y+j, 1) } diff --git a/modules/avatar/identicon/identicon.go b/modules/avatar/identicon/identicon.go index 87bd87796e..ee92416a53 100644 --- a/modules/avatar/identicon/identicon.go +++ b/modules/avatar/identicon/identicon.go @@ -134,7 +134,7 @@ func drawBlocks(p *image.Paletted, size int, c, b1, b2 blockFunc, b1Angle, b2Ang // then we make it left-right mirror, so we didn't draw 3/6/9 before for x := 0; x < size/2; x++ { - for y := 0; y < size; y++ { + for y := range size { p.SetColorIndex(size-x, y, p.ColorIndexAt(x, y)) } } diff --git a/modules/cache/cache.go b/modules/cache/cache.go index a434c13b67..039caa9fbc 100644 --- a/modules/cache/cache.go +++ b/modules/cache/cache.go @@ -24,7 +24,7 @@ func Init() error { if err != nil { return err } - for i := 0; i < 10; i++ { + for range 10 { if err = c.Ping(); err == nil { break } diff --git a/modules/charset/charset.go b/modules/charset/charset.go index 1855446a98..597ce5120c 100644 --- a/modules/charset/charset.go +++ b/modules/charset/charset.go @@ -164,7 +164,7 @@ func DetectEncoding(content []byte) (string, error) { } times := 1024 / len(content) detectContent = make([]byte, 0, times*len(content)) - for i := 0; i < times; i++ { + for range times { detectContent = append(detectContent, content...) } } else { diff --git a/modules/charset/charset_test.go b/modules/charset/charset_test.go index 1fb362654d..cd2e3b9aaa 100644 --- a/modules/charset/charset_test.go +++ b/modules/charset/charset_test.go @@ -242,7 +242,7 @@ func stringMustEndWith(t *testing.T, expected, value string) { func TestToUTF8WithFallbackReader(t *testing.T) { resetDefaultCharsetsOrder() - for testLen := 0; testLen < 2048; testLen++ { + for testLen := range 2048 { pattern := " test { () }\n" input := "" for len(input) < testLen { diff --git a/modules/git/commit.go b/modules/git/commit.go index 44e8725bbe..1c1648eb8b 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -277,8 +277,8 @@ func NewSearchCommitsOptions(searchString string, forAllRefs bool) SearchCommits var keywords, authors, committers []string var after, before string - fields := strings.Fields(searchString) - for _, k := range fields { + fields := strings.FieldsSeq(searchString) + for k := range fields { switch { case strings.HasPrefix(k, "author:"): authors = append(authors, strings.TrimPrefix(k, "author:")) diff --git a/modules/git/commit_info_nogogit.go b/modules/git/commit_info_nogogit.go index 7a6af0410b..1b45fc8a6c 100644 --- a/modules/git/commit_info_nogogit.go +++ b/modules/git/commit_info_nogogit.go @@ -7,8 +7,7 @@ package git import ( "context" - "fmt" - "io" + "maps" "path" "sort" @@ -40,9 +39,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath return nil, nil, err } - for pth, found := range commits { - revs[pth] = found - } + maps.Copy(revs, commits) } } else { sort.Strings(entryPaths) @@ -124,48 +121,25 @@ func GetLastCommitForPaths(ctx context.Context, commit *Commit, treePath string, return nil, err } - batchStdinWriter, batchReader, cancel, err := commit.repo.CatFileBatch(ctx) - if err != nil { - return nil, err - } - defer cancel() - commitsMap := map[string]*Commit{} commitsMap[commit.ID.String()] = commit commitCommits := map[string]*Commit{} for path, commitID := range revs { - c, ok := commitsMap[commitID] - if ok { - commitCommits[path] = c + if len(commitID) == 0 { continue } - if len(commitID) == 0 { + c, ok := commitsMap[commitID] + if ok { + commitCommits[path] = c continue } - _, err := batchStdinWriter.Write([]byte(commitID + "\n")) - if err != nil { - return nil, err - } - _, typ, size, err := ReadBatchLine(batchReader) + c, err := commit.repo.GetCommit(commitID) // Ensure the commit exists in the repository if err != nil { return nil, err } - if typ != "commit" { - if err := DiscardFull(batchReader, size+1); err != nil { - return nil, err - } - return nil, fmt.Errorf("unexpected type: %s for commit id: %s", typ, commitID) - } - c, err = CommitFromReader(commit.repo, MustIDFromString(commitID), io.LimitReader(batchReader, size)) - if err != nil { - return nil, err - } - if _, err := batchReader.Discard(1); err != nil { - return nil, err - } commitCommits[path] = c } diff --git a/modules/git/diff_test.go b/modules/git/diff_test.go index 9a09347b30..7671fffcc1 100644 --- a/modules/git/diff_test.go +++ b/modules/git/diff_test.go @@ -154,7 +154,7 @@ func TestCutDiffAroundLine(t *testing.T) { } func BenchmarkCutDiffAroundLine(b *testing.B) { - for n := 0; n < b.N; n++ { + for b.Loop() { CutDiffAroundLine(strings.NewReader(exampleDiff), 3, true, 3) } } diff --git a/modules/git/foreachref/format.go b/modules/git/foreachref/format.go index 97e8ee4724..d9573a55d6 100644 --- a/modules/git/foreachref/format.go +++ b/modules/git/foreachref/format.go @@ -76,7 +76,7 @@ func (f Format) Parser(r io.Reader) *Parser { // would turn into "%0a%00". func (f Format) hexEscaped(delim []byte) string { escaped := "" - for i := 0; i < len(delim); i++ { + for i := range delim { escaped += "%" + hex.EncodeToString([]byte{delim[i]}) } return escaped diff --git a/modules/git/hook.go b/modules/git/hook.go index a6f6b18855..548a59971d 100644 --- a/modules/git/hook.go +++ b/modules/git/hook.go @@ -8,6 +8,7 @@ import ( "errors" "os" "path/filepath" + "slices" "strings" "code.gitea.io/gitea/modules/util" @@ -25,12 +26,7 @@ var ErrNotValidHook = errors.New("not a valid Git hook") // IsValidHookName returns true if given name is a valid Git hook. func IsValidHookName(name string) bool { - for _, hn := range hookNames { - if hn == name { - return true - } - } - return false + return slices.Contains(hookNames, name) } // Hook represents a Git hook. diff --git a/modules/git/last_commit_cache.go b/modules/git/last_commit_cache.go index cf9c10d7b4..cff2556083 100644 --- a/modules/git/last_commit_cache.go +++ b/modules/git/last_commit_cache.go @@ -13,7 +13,7 @@ import ( ) func getCacheKey(repoPath, commitID, entryPath string) string { - hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%s:%s:%s", repoPath, commitID, entryPath))) + hashBytes := sha256.Sum256(fmt.Appendf(nil, "%s:%s:%s", repoPath, commitID, entryPath)) return fmt.Sprintf("last_commit:%x", hashBytes) } diff --git a/modules/git/log_name_status.go b/modules/git/log_name_status.go index 3ee462f68e..dfdef38ef9 100644 --- a/modules/git/log_name_status.go +++ b/modules/git/log_name_status.go @@ -346,10 +346,7 @@ func WalkGitLog(ctx context.Context, repo *Repository, head *Commit, treepath st results := make([]string, len(paths)) remaining := len(paths) - nextRestart := (len(paths) * 3) / 4 - if nextRestart > 70 { - nextRestart = 70 - } + nextRestart := min((len(paths)*3)/4, 70) lastEmptyParent := head.ID.String() commitSinceLastEmptyParent := uint64(0) commitSinceNextRestart := uint64(0) diff --git a/modules/git/ref.go b/modules/git/ref.go index f20a175e42..56b2db858a 100644 --- a/modules/git/ref.go +++ b/modules/git/ref.go @@ -109,8 +109,8 @@ func (ref RefName) IsFor() bool { } func (ref RefName) nameWithoutPrefix(prefix string) string { - if strings.HasPrefix(string(ref), prefix) { - return strings.TrimPrefix(string(ref), prefix) + if after, ok := strings.CutPrefix(string(ref), prefix); ok { + return after } return "" } diff --git a/modules/git/repo.go b/modules/git/repo.go index 239866fe9d..f1f6902773 100644 --- a/modules/git/repo.go +++ b/modules/git/repo.go @@ -44,9 +44,9 @@ func (repo *Repository) parsePrettyFormatLogToList(logs []byte) ([]*Commit, erro return commits, nil } - parts := bytes.Split(logs, []byte{'\n'}) + parts := bytes.SplitSeq(logs, []byte{'\n'}) - for _, commitID := range parts { + for commitID := range parts { commit, err := repo.GetCommit(string(commitID)) if err != nil { return nil, err diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index a44fd8c0e1..4066a1ca7b 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -547,11 +547,11 @@ func (repo *Repository) GetCommitBranchStart(env []string, branch, endCommitID s return "", runErr } - parts := bytes.Split(bytes.TrimSpace(stdout), []byte{'\n'}) + parts := bytes.SplitSeq(bytes.TrimSpace(stdout), []byte{'\n'}) // check the commits one by one until we find a commit contained by another branch // and we think this commit is the divergence point - for _, commitID := range parts { + for commitID := range parts { branches, err := repo.getBranches(env, string(commitID), 2) if err != nil { return "", err diff --git a/modules/git/repo_index.go b/modules/git/repo_index.go index 443a3a20d1..4879121a41 100644 --- a/modules/git/repo_index.go +++ b/modules/git/repo_index.go @@ -86,7 +86,7 @@ func (repo *Repository) LsFiles(filenames ...string) ([]string, error) { return nil, err } filelist := make([]string, 0, len(filenames)) - for _, line := range bytes.Split(res, []byte{'\000'}) { + for line := range bytes.SplitSeq(res, []byte{'\000'}) { filelist = append(filelist, string(line)) } diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index c74618471a..c8d72eee02 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -39,8 +39,8 @@ func (repo *Repository) GetTagNameBySHA(sha string) (string, error) { return "", err } - tagRefs := strings.Split(stdout, "\n") - for _, tagRef := range tagRefs { + tagRefs := strings.SplitSeq(stdout, "\n") + for tagRef := range tagRefs { if len(strings.TrimSpace(tagRef)) > 0 { fields := strings.Fields(tagRef) if strings.HasPrefix(fields[0], sha) && strings.HasPrefix(fields[1], TagPrefix) { @@ -62,7 +62,7 @@ func (repo *Repository) GetTagID(name string) (string, error) { return "", err } // Make sure exact match is used: "v1" != "release/v1" - for _, line := range strings.Split(stdout, "\n") { + for line := range strings.SplitSeq(stdout, "\n") { fields := strings.Fields(line) if len(fields) == 2 && fields[1] == "refs/tags/"+name { return fields[0], nil diff --git a/modules/git/tree.go b/modules/git/tree.go index f6fdff97d0..38fb45f3b1 100644 --- a/modules/git/tree.go +++ b/modules/git/tree.go @@ -56,7 +56,7 @@ func (repo *Repository) LsTree(ref string, filenames ...string) ([]string, error return nil, err } filelist := make([]string, 0, len(filenames)) - for _, line := range bytes.Split(res, []byte{'\000'}) { + for line := range bytes.SplitSeq(res, []byte{'\000'}) { filelist = append(filelist, string(line)) } diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index a2e1579290..57856d90ee 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -78,7 +78,7 @@ func (te *TreeEntry) FollowLinks(optLimit ...int) (*TreeEntry, error) { } limit := util.OptionalArg(optLimit, 10) entry := te - for i := 0; i < limit; i++ { + for range limit { if !entry.IsLink() { break } diff --git a/modules/git/tree_test.go b/modules/git/tree_test.go index 61e5482538..cae11c4b1b 100644 --- a/modules/git/tree_test.go +++ b/modules/git/tree_test.go @@ -19,7 +19,7 @@ func TestSubTree_Issue29101(t *testing.T) { assert.NoError(t, err) // old code could produce a different error if called multiple times - for i := 0; i < 10; i++ { + for range 10 { _, err = commit.SubTree("file1.txt") assert.Error(t, err) assert.True(t, IsErrNotExist(err)) diff --git a/modules/globallock/globallock_test.go b/modules/globallock/globallock_test.go index 0143fc6833..8d55d9f699 100644 --- a/modules/globallock/globallock_test.go +++ b/modules/globallock/globallock_test.go @@ -70,7 +70,7 @@ func testLockAndDo(t *testing.T) { count := 0 wg := sync.WaitGroup{} wg.Add(concurrency) - for i := 0; i < concurrency; i++ { + for range concurrency { go func() { defer wg.Done() err := LockAndDo(ctx, "test", func(ctx context.Context) error { diff --git a/modules/hostmatcher/hostmatcher.go b/modules/hostmatcher/hostmatcher.go index 1069310316..15c6371422 100644 --- a/modules/hostmatcher/hostmatcher.go +++ b/modules/hostmatcher/hostmatcher.go @@ -6,6 +6,7 @@ package hostmatcher import ( "net" "path/filepath" + "slices" "strings" ) @@ -38,7 +39,7 @@ func isBuiltin(s string) bool { // ParseHostMatchList parses the host list HostMatchList func ParseHostMatchList(settingKeyHint, hostList string) *HostMatchList { hl := &HostMatchList{SettingKeyHint: settingKeyHint, SettingValue: hostList} - for _, s := range strings.Split(hostList, ",") { + for s := range strings.SplitSeq(hostList, ",") { s = strings.ToLower(strings.TrimSpace(s)) if s == "" { continue @@ -61,7 +62,7 @@ func ParseSimpleMatchList(settingKeyHint, matchList string) *HostMatchList { SettingKeyHint: settingKeyHint, SettingValue: matchList, } - for _, s := range strings.Split(matchList, ",") { + for s := range strings.SplitSeq(matchList, ",") { s = strings.ToLower(strings.TrimSpace(s)) if s == "" { continue @@ -98,10 +99,8 @@ func (hl *HostMatchList) checkPattern(host string) bool { } func (hl *HostMatchList) checkIP(ip net.IP) bool { - for _, pattern := range hl.patterns { - if pattern == "*" { - return true - } + if slices.Contains(hl.patterns, "*") { + return true } for _, builtin := range hl.builtins { switch builtin { diff --git a/modules/httpcache/httpcache.go b/modules/httpcache/httpcache.go index 045b00d944..dd3efab7a5 100644 --- a/modules/httpcache/httpcache.go +++ b/modules/httpcache/httpcache.go @@ -79,7 +79,7 @@ func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag strin func checkIfNoneMatchIsValid(req *http.Request, etag string) bool { ifNoneMatch := req.Header.Get("If-None-Match") if len(ifNoneMatch) > 0 { - for _, item := range strings.Split(ifNoneMatch, ",") { + for item := range strings.SplitSeq(ifNoneMatch, ",") { item = strings.TrimPrefix(strings.TrimSpace(item), "W/") // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag#directives if item == etag { return true diff --git a/modules/indexer/code/bleve/token/path/path.go b/modules/indexer/code/bleve/token/path/path.go index ae24e84974..6dfc12f146 100644 --- a/modules/indexer/code/bleve/token/path/path.go +++ b/modules/indexer/code/bleve/token/path/path.go @@ -51,7 +51,7 @@ func generatePathTokens(input analysis.TokenStream, reversed bool) analysis.Toke slices.Reverse(input) } - for i := 0; i < len(input); i++ { + for i := range input { var sb strings.Builder sb.Write(input[0].Term) diff --git a/modules/indexer/code/git.go b/modules/indexer/code/git.go index 0089dd259f..41bc74e6ec 100644 --- a/modules/indexer/code/git.go +++ b/modules/indexer/code/git.go @@ -129,8 +129,8 @@ func nonGenesisChanges(ctx context.Context, repo *repo_model.Repository, revisio changes.Updates = append(changes.Updates, updates...) return nil } - lines := strings.Split(stdout, "\n") - for _, line := range lines { + lines := strings.SplitSeq(stdout, "\n") + for line := range lines { line = strings.TrimSpace(line) if len(line) == 0 { continue diff --git a/modules/indexer/code/search.go b/modules/indexer/code/search.go index e37aff8e59..a7a5d7d2e3 100644 --- a/modules/indexer/code/search.go +++ b/modules/indexer/code/search.go @@ -77,7 +77,7 @@ func HighlightSearchResultCode(filename, language string, lineNums []int, code s // The lineNums outputted by highlight.Code might not match the original lineNums, because "highlight" removes the last `\n` lines := make([]*ResultLine, min(len(highlightedLines), len(lineNums))) - for i := 0; i < len(lines); i++ { + for i := range lines { lines[i] = &ResultLine{ Num: lineNums[i], FormattedContent: template.HTML(highlightedLines[i]), diff --git a/modules/issue/template/template.go b/modules/issue/template/template.go index 84ae90e4ed..192aaf8e01 100644 --- a/modules/issue/template/template.go +++ b/modules/issue/template/template.go @@ -8,6 +8,7 @@ import ( "fmt" "net/url" "regexp" + "slices" "strconv" "strings" @@ -447,12 +448,7 @@ func (o *valuedOption) IsChecked() bool { case api.IssueFormFieldTypeDropdown: checks := strings.Split(o.field.Get("form-field-"+o.field.ID), ",") idx := strconv.Itoa(o.index) - for _, v := range checks { - if v == idx { - return true - } - } - return false + return slices.Contains(checks, idx) case api.IssueFormFieldTypeCheckboxes: return o.field.Get(fmt.Sprintf("form-field-%s-%d", o.field.ID, o.index)) == "on" } diff --git a/modules/label/parser.go b/modules/label/parser.go index 511bac823f..2a10152062 100644 --- a/modules/label/parser.go +++ b/modules/label/parser.go @@ -72,7 +72,7 @@ func parseYamlFormat(fileName string, data []byte) ([]*Label, error) { func parseLegacyFormat(fileName string, data []byte) ([]*Label, error) { lines := strings.Split(string(data), "\n") list := make([]*Label, 0, len(lines)) - for i := 0; i < len(lines); i++ { + for i := range lines { line := strings.TrimSpace(lines[i]) if len(line) == 0 { continue @@ -108,7 +108,7 @@ func LoadTemplateDescription(fileName string) (string, error) { return "", err } - for i := 0; i < len(list); i++ { + for i := range list { if i > 0 { buf.WriteString(", ") } diff --git a/modules/log/event_format.go b/modules/log/event_format.go index c23b3b411b..4cf471d223 100644 --- a/modules/log/event_format.go +++ b/modules/log/event_format.go @@ -212,7 +212,7 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms } } if hasColorValue { - msg = []byte(fmt.Sprintf(msgFormat, msgArgs...)) + msg = fmt.Appendf(nil, msgFormat, msgArgs...) } } // try to re-use the pre-formatted simple text message @@ -243,8 +243,8 @@ func EventFormatTextMessage(mode *WriterMode, event *Event, msgFormat string, ms buf = append(buf, msg...) if event.Stacktrace != "" && mode.StacktraceLevel <= event.Level { - lines := bytes.Split([]byte(event.Stacktrace), []byte("\n")) - for _, line := range lines { + lines := bytes.SplitSeq([]byte(event.Stacktrace), []byte("\n")) + for line := range lines { buf = append(buf, "\n\t"...) buf = append(buf, line...) } diff --git a/modules/log/flags.go b/modules/log/flags.go index 8064c91745..f409261150 100644 --- a/modules/log/flags.go +++ b/modules/log/flags.go @@ -123,7 +123,7 @@ func FlagsFromString(from string, def ...uint32) Flags { return Flags{defined: true, flags: def[0]} } flags := uint32(0) - for _, flag := range strings.Split(strings.ToLower(from), ",") { + for flag := range strings.SplitSeq(strings.ToLower(from), ",") { flags |= flagFromString[strings.TrimSpace(flag)] } return Flags{defined: true, flags: flags} diff --git a/modules/log/level_test.go b/modules/log/level_test.go index cd18a807d8..0e59af6cb7 100644 --- a/modules/log/level_test.go +++ b/modules/log/level_test.go @@ -32,11 +32,11 @@ func TestLevelMarshalUnmarshalJSON(t *testing.T) { assert.NoError(t, err) assert.Equal(t, INFO, testLevel.Level) - err = json.Unmarshal([]byte(fmt.Sprintf(`{"level":%d}`, 2)), &testLevel) + err = json.Unmarshal(fmt.Appendf(nil, `{"level":%d}`, 2), &testLevel) assert.NoError(t, err) assert.Equal(t, INFO, testLevel.Level) - err = json.Unmarshal([]byte(fmt.Sprintf(`{"level":%d}`, 10012)), &testLevel) + err = json.Unmarshal(fmt.Appendf(nil, `{"level":%d}`, 10012), &testLevel) assert.NoError(t, err) assert.Equal(t, INFO, testLevel.Level) @@ -51,5 +51,5 @@ func TestLevelMarshalUnmarshalJSON(t *testing.T) { } func makeTestLevelBytes(level string) []byte { - return []byte(fmt.Sprintf(`{"level":"%s"}`, level)) + return fmt.Appendf(nil, `{"level":"%s"}`, level) } diff --git a/modules/markup/html.go b/modules/markup/html.go index e8391341d9..51afd4be00 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "regexp" + "slices" "strings" "sync" @@ -109,13 +110,7 @@ func CustomLinkURLSchemes(schemes []string) { if !validScheme.MatchString(s) { continue } - without := false - for _, sna := range xurls.SchemesNoAuthority { - if s == sna { - without = true - break - } - } + without := slices.Contains(xurls.SchemesNoAuthority, s) if without { s += ":" } else { diff --git a/modules/markup/html_commit.go b/modules/markup/html_commit.go index 967c327f36..fe7a034967 100644 --- a/modules/markup/html_commit.go +++ b/modules/markup/html_commit.go @@ -62,7 +62,7 @@ func anyHashPatternExtract(s string) (ret anyHashPatternResult, ok bool) { // if url ends in '.', it's very likely that it is not part of the actual url but used to finish a sentence. ret.PosEnd-- ret.FullURL = ret.FullURL[:len(ret.FullURL)-1] - for i := 0; i < len(m); i++ { + for i := range m { m[i] = min(m[i], ret.PosEnd) } } diff --git a/modules/markup/html_link.go b/modules/markup/html_link.go index 1ea0b14028..43faef1681 100644 --- a/modules/markup/html_link.go +++ b/modules/markup/html_link.go @@ -31,8 +31,8 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { // It makes page handling terrible, but we prefer GitHub syntax // And fall back to MediaWiki only when it is obvious from the look // Of text and link contents - sl := strings.Split(content, "|") - for _, v := range sl { + sl := strings.SplitSeq(content, "|") + for v := range sl { if equalPos := strings.IndexByte(v, '='); equalPos == -1 { // There is no equal in this argument; this is a mandatory arg if props["name"] == "" { diff --git a/modules/markup/markdown/markdown.go b/modules/markup/markdown/markdown.go index 79df547c2c..3b788432ba 100644 --- a/modules/markup/markdown/markdown.go +++ b/modules/markup/markdown/markdown.go @@ -182,10 +182,7 @@ func render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error rc := &RenderConfig{Meta: markup.RenderMetaAsDetails} buf, _ = ExtractMetadataBytes(buf, rc) - metaLength := bufWithMetadataLength - len(buf) - if metaLength < 0 { - metaLength = 0 - } + metaLength := max(bufWithMetadataLength-len(buf), 0) rc.metaLength = metaLength pc.Set(renderConfigKey, rc) diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 99f590c950..76434ac8b3 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -252,7 +252,7 @@ This PR has been generated by [Renovate Bot](https://github.com/renovatebot/reno return username == "r-lyeh" }, }) - for i := 0; i < len(sameCases); i++ { + for i := range sameCases { line, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), sameCases[i]) assert.NoError(t, err) assert.Equal(t, testAnswers[i], string(line)) diff --git a/modules/markup/markdown/math/block_renderer.go b/modules/markup/markdown/math/block_renderer.go index 412e4d0dee..427ed842ec 100644 --- a/modules/markup/markdown/math/block_renderer.go +++ b/modules/markup/markdown/math/block_renderer.go @@ -42,7 +42,7 @@ func (r *BlockRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) { func (r *BlockRenderer) writeLines(w util.BufWriter, source []byte, n gast.Node) { l := n.Lines().Len() - for i := 0; i < l; i++ { + for i := range l { line := n.Lines().At(i) _, _ = w.Write(util.EscapeHTML(line.Value(source))) } diff --git a/modules/markup/markdown/meta_test.go b/modules/markup/markdown/meta_test.go index 3f74adeaef..283d289d48 100644 --- a/modules/markup/markdown/meta_test.go +++ b/modules/markup/markdown/meta_test.go @@ -60,7 +60,7 @@ func TestExtractMetadata(t *testing.T) { func TestExtractMetadataBytes(t *testing.T) { t.Run("ValidFrontAndBody", func(t *testing.T) { var meta IssueTemplate - body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest)), &meta) + body, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s\n%s", sepTest, frontTest, sepTest, bodyTest), &meta) assert.NoError(t, err) assert.Equal(t, bodyTest, string(body)) assert.Equal(t, metaTest, meta) @@ -69,19 +69,19 @@ func TestExtractMetadataBytes(t *testing.T) { t.Run("NoFirstSeparator", func(t *testing.T) { var meta IssueTemplate - _, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", frontTest, sepTest, bodyTest)), &meta) + _, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", frontTest, sepTest, bodyTest), &meta) assert.Error(t, err) }) t.Run("NoLastSeparator", func(t *testing.T) { var meta IssueTemplate - _, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, bodyTest)), &meta) + _, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", sepTest, frontTest, bodyTest), &meta) assert.Error(t, err) }) t.Run("NoBody", func(t *testing.T) { var meta IssueTemplate - body, err := ExtractMetadataBytes([]byte(fmt.Sprintf("%s\n%s\n%s", sepTest, frontTest, sepTest)), &meta) + body, err := ExtractMetadataBytes(fmt.Appendf(nil, "%s\n%s\n%s", sepTest, frontTest, sepTest), &meta) assert.NoError(t, err) assert.Empty(t, string(body)) assert.Equal(t, metaTest, meta) diff --git a/modules/markup/markdown/transform_heading.go b/modules/markup/markdown/transform_heading.go index 5f8a12794d..cac3cd6617 100644 --- a/modules/markup/markdown/transform_heading.go +++ b/modules/markup/markdown/transform_heading.go @@ -16,7 +16,7 @@ import ( func (g *ASTTransformer) transformHeading(_ *markup.RenderContext, v *ast.Heading, reader text.Reader, tocList *[]Header) { for _, attr := range v.Attributes() { if _, ok := attr.Value.([]byte); !ok { - v.SetAttribute(attr.Name, []byte(fmt.Sprintf("%v", attr.Value))) + v.SetAttribute(attr.Name, fmt.Appendf(nil, "%v", attr.Value)) } } txt := v.Text(reader.Source()) //nolint:staticcheck diff --git a/modules/optional/option.go b/modules/optional/option.go index ccbad259c2..6075c6347e 100644 --- a/modules/optional/option.go +++ b/modules/optional/option.go @@ -5,6 +5,12 @@ package optional import "strconv" +// Option is a generic type that can hold a value of type T or be empty (None). +// +// It must use the slice type to work with "chi" form values binding: +// * non-existing value are represented as an empty slice (None) +// * existing value is represented as a slice with one element (Some) +// * multiple values are represented as a slice with multiple elements (Some), the Value is the first element (not well-defined in this case) type Option[T any] []T func None[T any]() Option[T] { diff --git a/models/packages/container/const.go b/modules/packages/container/const.go index 6c7c9b46d1..6c7c9b46d1 100644 --- a/models/packages/container/const.go +++ b/modules/packages/container/const.go diff --git a/modules/packages/npm/creator.go b/modules/packages/npm/creator.go index 8ba4dbfba7..11b5123c27 100644 --- a/modules/packages/npm/creator.go +++ b/modules/packages/npm/creator.go @@ -58,7 +58,7 @@ type PackageMetadata struct { Time map[string]time.Time `json:"time,omitempty"` Homepage string `json:"homepage,omitempty"` Keywords []string `json:"keywords,omitempty"` - Repository Repository `json:"repository,omitempty"` + Repository Repository `json:"repository"` Author User `json:"author"` ReadmeFilename string `json:"readmeFilename,omitempty"` Users map[string]bool `json:"users,omitempty"` @@ -75,7 +75,7 @@ type PackageMetadataVersion struct { Author User `json:"author"` Homepage string `json:"homepage,omitempty"` License string `json:"license,omitempty"` - Repository Repository `json:"repository,omitempty"` + Repository Repository `json:"repository"` Keywords []string `json:"keywords,omitempty"` Dependencies map[string]string `json:"dependencies,omitempty"` BundleDependencies []string `json:"bundleDependencies,omitempty"` diff --git a/modules/packages/npm/metadata.go b/modules/packages/npm/metadata.go index d1d0263387..362d0470d5 100644 --- a/modules/packages/npm/metadata.go +++ b/modules/packages/npm/metadata.go @@ -23,5 +23,5 @@ type Metadata struct { OptionalDependencies map[string]string `json:"optional_dependencies,omitempty"` Bin map[string]string `json:"bin,omitempty"` Readme string `json:"readme,omitempty"` - Repository Repository `json:"repository,omitempty"` + Repository Repository `json:"repository"` } diff --git a/modules/packages/nuget/metadata.go b/modules/packages/nuget/metadata.go index 1e98ddffde..a122590bf1 100644 --- a/modules/packages/nuget/metadata.go +++ b/modules/packages/nuget/metadata.go @@ -57,14 +57,24 @@ type Package struct { // Metadata represents the metadata of a Nuget package type Metadata struct { - Description string `json:"description,omitempty"` - ReleaseNotes string `json:"release_notes,omitempty"` - Readme string `json:"readme,omitempty"` - Authors string `json:"authors,omitempty"` - ProjectURL string `json:"project_url,omitempty"` - RepositoryURL string `json:"repository_url,omitempty"` - RequireLicenseAcceptance bool `json:"require_license_acceptance"` - Dependencies map[string][]Dependency `json:"dependencies,omitempty"` + Authors string `json:"authors,omitempty"` + Copyright string `json:"copyright,omitempty"` + Description string `json:"description,omitempty"` + DevelopmentDependency bool `json:"development_dependency,omitempty"` + IconURL string `json:"icon_url,omitempty"` + Language string `json:"language,omitempty"` + LicenseURL string `json:"license_url,omitempty"` + MinClientVersion string `json:"min_client_version,omitempty"` + Owners string `json:"owners,omitempty"` + ProjectURL string `json:"project_url,omitempty"` + Readme string `json:"readme,omitempty"` + ReleaseNotes string `json:"release_notes,omitempty"` + RepositoryURL string `json:"repository_url,omitempty"` + RequireLicenseAcceptance bool `json:"require_license_acceptance"` + Tags string `json:"tags,omitempty"` + Title string `json:"title,omitempty"` + + Dependencies map[string][]Dependency `json:"dependencies,omitempty"` } // Dependency represents a dependency of a Nuget package @@ -74,24 +84,30 @@ type Dependency struct { } // https://learn.microsoft.com/en-us/nuget/reference/nuspec +// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Packaging/compiler/resources/nuspec.xsd type nuspecPackage struct { Metadata struct { - ID string `xml:"id"` - Version string `xml:"version"` - Authors string `xml:"authors"` - RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"` + // required fields + Authors string `xml:"authors"` + Description string `xml:"description"` + ID string `xml:"id"` + Version string `xml:"version"` + + // optional fields + Copyright string `xml:"copyright"` + DevelopmentDependency bool `xml:"developmentDependency"` + IconURL string `xml:"iconUrl"` + Language string `xml:"language"` + LicenseURL string `xml:"licenseUrl"` + MinClientVersion string `xml:"minClientVersion,attr"` + Owners string `xml:"owners"` ProjectURL string `xml:"projectUrl"` - Description string `xml:"description"` - ReleaseNotes string `xml:"releaseNotes"` Readme string `xml:"readme"` - PackageTypes struct { - PackageType []struct { - Name string `xml:"name,attr"` - } `xml:"packageType"` - } `xml:"packageTypes"` - Repository struct { - URL string `xml:"url,attr"` - } `xml:"repository"` + ReleaseNotes string `xml:"releaseNotes"` + RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"` + Tags string `xml:"tags"` + Title string `xml:"title"` + Dependencies struct { Dependency []struct { ID string `xml:"id,attr"` @@ -107,6 +123,14 @@ type nuspecPackage struct { } `xml:"dependency"` } `xml:"group"` } `xml:"dependencies"` + PackageTypes struct { + PackageType []struct { + Name string `xml:"name,attr"` + } `xml:"packageType"` + } `xml:"packageTypes"` + Repository struct { + URL string `xml:"url,attr"` + } `xml:"repository"` } `xml:"metadata"` } @@ -167,13 +191,23 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) { } m := &Metadata{ - Description: p.Metadata.Description, - ReleaseNotes: p.Metadata.ReleaseNotes, Authors: p.Metadata.Authors, + Copyright: p.Metadata.Copyright, + Description: p.Metadata.Description, + DevelopmentDependency: p.Metadata.DevelopmentDependency, + IconURL: p.Metadata.IconURL, + Language: p.Metadata.Language, + LicenseURL: p.Metadata.LicenseURL, + MinClientVersion: p.Metadata.MinClientVersion, + Owners: p.Metadata.Owners, ProjectURL: p.Metadata.ProjectURL, + ReleaseNotes: p.Metadata.ReleaseNotes, RepositoryURL: p.Metadata.Repository.URL, RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance, - Dependencies: make(map[string][]Dependency), + Tags: p.Metadata.Tags, + Title: p.Metadata.Title, + + Dependencies: make(map[string][]Dependency), } if p.Metadata.Readme != "" { @@ -227,13 +261,13 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) { func toNormalizedVersion(v *version.Version) string { var buf bytes.Buffer segments := v.Segments64() - fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2]) + _, _ = fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2]) if len(segments) > 3 && segments[3] > 0 { - fmt.Fprintf(&buf, ".%d", segments[3]) + _, _ = fmt.Fprintf(&buf, ".%d", segments[3]) } pre := v.Prerelease() if pre != "" { - fmt.Fprint(&buf, "-", pre) + _, _ = fmt.Fprint(&buf, "-", pre) } return buf.String() } diff --git a/modules/packages/nuget/metadata_test.go b/modules/packages/nuget/metadata_test.go index f466492f8a..90c3e8dfeb 100644 --- a/modules/packages/nuget/metadata_test.go +++ b/modules/packages/nuget/metadata_test.go @@ -12,44 +12,62 @@ import ( ) const ( - id = "System.Gitea" - semver = "1.0.1" - authors = "Gitea Authors" - projectURL = "https://gitea.io" - description = "Package Description" - releaseNotes = "Package Release Notes" - readme = "Readme" - repositoryURL = "https://gitea.io/gitea/gitea" - targetFramework = ".NETStandard2.1" - dependencyID = "System.Text.Json" - dependencyVersion = "5.0.0" + authors = "Gitea Authors" + copyright = "Package Copyright" + dependencyID = "System.Text.Json" + dependencyVersion = "5.0.0" + developmentDependency = true + description = "Package Description" + iconURL = "https://gitea.io/favicon.png" + id = "System.Gitea" + language = "Package Language" + licenseURL = "https://gitea.io/license" + minClientVersion = "1.0.0.0" + owners = "Package Owners" + projectURL = "https://gitea.io" + readme = "Readme" + releaseNotes = "Package Release Notes" + repositoryURL = "https://gitea.io/gitea/gitea" + requireLicenseAcceptance = true + tags = "tag_1 tag_2 tag_3" + targetFramework = ".NETStandard2.1" + title = "Package Title" + versionStr = "1.0.1" ) const nuspecContent = `<?xml version="1.0" encoding="utf-8"?> <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> - <metadata> - <id>` + id + `</id> - <version>` + semver + `</version> - <authors>` + authors + `</authors> - <requireLicenseAcceptance>true</requireLicenseAcceptance> - <projectUrl>` + projectURL + `</projectUrl> - <description>` + description + `</description> - <releaseNotes>` + releaseNotes + `</releaseNotes> - <repository url="` + repositoryURL + `" /> - <readme>README.md</readme> - <dependencies> - <group targetFramework="` + targetFramework + `"> - <dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" /> - </group> - </dependencies> - </metadata> + <metadata minClientVersion="` + minClientVersion + `"> + <authors>` + authors + `</authors> + <copyright>` + copyright + `</copyright> + <description>` + description + `</description> + <developmentDependency>true</developmentDependency> + <iconUrl>` + iconURL + `</iconUrl> + <id>` + id + `</id> + <language>` + language + `</language> + <licenseUrl>` + licenseURL + `</licenseUrl> + <owners>` + owners + `</owners> + <projectUrl>` + projectURL + `</projectUrl> + <readme>README.md</readme> + <releaseNotes>` + releaseNotes + `</releaseNotes> + <repository url="` + repositoryURL + `" /> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <tags>` + tags + `</tags> + <title>` + title + `</title> + <version>` + versionStr + `</version> + <dependencies> + <group targetFramework="` + targetFramework + `"> + <dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" /> + </group> + </dependencies> + </metadata> </package>` const symbolsNuspecContent = `<?xml version="1.0" encoding="utf-8"?> <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> <metadata> <id>` + id + `</id> - <version>` + semver + `</version> + <version>` + versionStr + `</version> <description>` + description + `</description> <packageTypes> <packageType name="SymbolsPackage" /> @@ -140,14 +158,26 @@ func TestParsePackageMetaData(t *testing.T) { assert.NotNil(t, np) assert.Equal(t, DependencyPackage, np.PackageType) - assert.Equal(t, id, np.ID) - assert.Equal(t, semver, np.Version) assert.Equal(t, authors, np.Metadata.Authors) - assert.Equal(t, projectURL, np.Metadata.ProjectURL) assert.Equal(t, description, np.Metadata.Description) - assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes) + assert.Equal(t, id, np.ID) + assert.Equal(t, versionStr, np.Version) + + assert.Equal(t, copyright, np.Metadata.Copyright) + assert.Equal(t, developmentDependency, np.Metadata.DevelopmentDependency) + assert.Equal(t, iconURL, np.Metadata.IconURL) + assert.Equal(t, language, np.Metadata.Language) + assert.Equal(t, licenseURL, np.Metadata.LicenseURL) + assert.Equal(t, minClientVersion, np.Metadata.MinClientVersion) + assert.Equal(t, owners, np.Metadata.Owners) + assert.Equal(t, projectURL, np.Metadata.ProjectURL) assert.Equal(t, readme, np.Metadata.Readme) + assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes) assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL) + assert.Equal(t, requireLicenseAcceptance, np.Metadata.RequireLicenseAcceptance) + assert.Equal(t, tags, np.Metadata.Tags) + assert.Equal(t, title, np.Metadata.Title) + assert.Len(t, np.Metadata.Dependencies, 1) assert.Contains(t, np.Metadata.Dependencies, targetFramework) deps := np.Metadata.Dependencies[targetFramework] @@ -180,7 +210,7 @@ func TestParsePackageMetaData(t *testing.T) { assert.Equal(t, SymbolsPackage, np.PackageType) assert.Equal(t, id, np.ID) - assert.Equal(t, semver, np.Version) + assert.Equal(t, versionStr, np.Version) assert.Equal(t, description, np.Metadata.Description) assert.Empty(t, np.Metadata.Dependencies) }) diff --git a/modules/packages/nuget/symbol_extractor.go b/modules/packages/nuget/symbol_extractor.go index 81bf0371a0..9c952e1f10 100644 --- a/modules/packages/nuget/symbol_extractor.go +++ b/modules/packages/nuget/symbol_extractor.go @@ -34,7 +34,7 @@ type PortablePdbList []*PortablePdb func (l PortablePdbList) Close() { for _, pdb := range l { - pdb.Content.Close() + _ = pdb.Content.Close() } } @@ -65,7 +65,7 @@ func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) { buf, err := packages.CreateHashedBufferFromReader(f) - f.Close() + _ = f.Close() if err != nil { return err @@ -73,12 +73,12 @@ func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) { id, err := ParseDebugHeaderID(buf) if err != nil { - buf.Close() + _ = buf.Close() return fmt.Errorf("Invalid PDB file: %w", err) } if _, err := buf.Seek(0, io.SeekStart); err != nil { - buf.Close() + _ = buf.Close() return err } diff --git a/modules/packages/nuget/symbol_extractor_test.go b/modules/packages/nuget/symbol_extractor_test.go index 711ad6d096..e841e377d9 100644 --- a/modules/packages/nuget/symbol_extractor_test.go +++ b/modules/packages/nuget/symbol_extractor_test.go @@ -24,14 +24,14 @@ func TestExtractPortablePdb(t *testing.T) { var buf bytes.Buffer archive := zip.NewWriter(&buf) w, _ := archive.Create(name) - w.Write(content) - archive.Close() + _, _ = w.Write(content) + _ = archive.Close() return buf.Bytes() } t.Run("MissingPdbFiles", func(t *testing.T) { var buf bytes.Buffer - zip.NewWriter(&buf).Close() + _ = zip.NewWriter(&buf).Close() pdbs, err := ExtractPortablePdb(bytes.NewReader(buf.Bytes()), int64(buf.Len())) assert.ErrorIs(t, err, ErrMissingPdbFiles) diff --git a/modules/packages/rubygems/marshal.go b/modules/packages/rubygems/marshal.go index 4e6a5fc5f8..1505221acc 100644 --- a/modules/packages/rubygems/marshal.go +++ b/modules/packages/rubygems/marshal.go @@ -250,7 +250,7 @@ func (e *MarshalEncoder) marshalArray(arr reflect.Value) error { return err } - for i := 0; i < length; i++ { + for i := range length { if err := e.marshal(arr.Index(i).Interface()); err != nil { return err } diff --git a/modules/packages/swift/metadata.go b/modules/packages/swift/metadata.go index 24c4262ab7..85beb57607 100644 --- a/modules/packages/swift/metadata.go +++ b/modules/packages/swift/metadata.go @@ -47,7 +47,7 @@ type Metadata struct { Keywords []string `json:"keywords,omitempty"` RepositoryURL string `json:"repository_url,omitempty"` License string `json:"license,omitempty"` - Author Person `json:"author,omitempty"` + Author Person `json:"author"` Manifests map[string]*Manifest `json:"manifests,omitempty"` } diff --git a/modules/public/public.go b/modules/public/public.go index 2bc55b7869..9bc04f3f7e 100644 --- a/modules/public/public.go +++ b/modules/public/public.go @@ -44,7 +44,7 @@ func FileHandlerFunc() http.HandlerFunc { func parseAcceptEncoding(val string) container.Set[string] { parts := strings.Split(val, ";") types := make(container.Set[string]) - for _, v := range strings.Split(parts[0], ",") { + for v := range strings.SplitSeq(parts[0], ",") { types.Add(strings.TrimSpace(v)) } return types diff --git a/modules/queue/base_levelqueue_common.go b/modules/queue/base_levelqueue_common.go index 78d3b85a8a..d37093b84d 100644 --- a/modules/queue/base_levelqueue_common.go +++ b/modules/queue/base_levelqueue_common.go @@ -83,7 +83,7 @@ func prepareLevelDB(cfg *BaseConfig) (conn string, db *leveldb.DB, err error) { } conn = cfg.ConnStr } - for i := 0; i < 10; i++ { + for range 10 { if db, err = nosql.GetManager().GetLevelDB(conn); err == nil { break } diff --git a/modules/queue/base_redis.go b/modules/queue/base_redis.go index a1e234943d..bea0fd7a98 100644 --- a/modules/queue/base_redis.go +++ b/modules/queue/base_redis.go @@ -29,7 +29,7 @@ func newBaseRedisGeneric(cfg *BaseConfig, unique bool) (baseQueue, error) { client := nosql.GetManager().GetRedisClient(cfg.ConnStr) var err error - for i := 0; i < 10; i++ { + for range 10 { err = client.Ping(graceful.GetManager().ShutdownContext()).Err() if err == nil { break diff --git a/modules/queue/base_test.go b/modules/queue/base_test.go index 1a96ac1e1d..8e7c18d740 100644 --- a/modules/queue/base_test.go +++ b/modules/queue/base_test.go @@ -87,7 +87,7 @@ func testQueueBasic(t *testing.T, newFn func(cfg *BaseConfig) (baseQueue, error) // test blocking push if queue is full for i := 0; i < cfg.Length; i++ { - err = q.PushItem(ctx, []byte(fmt.Sprintf("item-%d", i))) + err = q.PushItem(ctx, fmt.Appendf(nil, "item-%d", i)) assert.NoError(t, err) } ctxTimed, cancel = context.WithTimeout(ctx, 10*time.Millisecond) diff --git a/modules/queue/manager.go b/modules/queue/manager.go index 079e2bee7a..ae6c51872d 100644 --- a/modules/queue/manager.go +++ b/modules/queue/manager.go @@ -6,6 +6,7 @@ package queue import ( "context" "errors" + "maps" "sync" "time" @@ -70,9 +71,7 @@ func (m *Manager) ManagedQueues() map[int64]ManagedWorkerPoolQueue { defer m.mu.Unlock() queues := make(map[int64]ManagedWorkerPoolQueue, len(m.Queues)) - for k, v := range m.Queues { - queues[k] = v - } + maps.Copy(queues, m.Queues) return queues } diff --git a/modules/queue/workerqueue_test.go b/modules/queue/workerqueue_test.go index 487c2f1a92..a6c369d5f9 100644 --- a/modules/queue/workerqueue_test.go +++ b/modules/queue/workerqueue_test.go @@ -77,17 +77,17 @@ func TestWorkerPoolQueueUnhandled(t *testing.T) { runCount := 2 // we can run these tests even hundreds times to see its stability t.Run("1/1", func(t *testing.T) { - for i := 0; i < runCount; i++ { + for range runCount { test(t, setting.QueueSettings{BatchLength: 1, MaxWorkers: 1}) } }) t.Run("3/1", func(t *testing.T) { - for i := 0; i < runCount; i++ { + for range runCount { test(t, setting.QueueSettings{BatchLength: 3, MaxWorkers: 1}) } }) t.Run("4/5", func(t *testing.T) { - for i := 0; i < runCount; i++ { + for range runCount { test(t, setting.QueueSettings{BatchLength: 4, MaxWorkers: 5}) } }) @@ -96,17 +96,17 @@ func TestWorkerPoolQueueUnhandled(t *testing.T) { func TestWorkerPoolQueuePersistence(t *testing.T) { runCount := 2 // we can run these tests even hundreds times to see its stability t.Run("1/1", func(t *testing.T) { - for i := 0; i < runCount; i++ { + for range runCount { testWorkerPoolQueuePersistence(t, setting.QueueSettings{BatchLength: 1, MaxWorkers: 1, Length: 100}) } }) t.Run("3/1", func(t *testing.T) { - for i := 0; i < runCount; i++ { + for range runCount { testWorkerPoolQueuePersistence(t, setting.QueueSettings{BatchLength: 3, MaxWorkers: 1, Length: 100}) } }) t.Run("4/5", func(t *testing.T) { - for i := 0; i < runCount; i++ { + for range runCount { testWorkerPoolQueuePersistence(t, setting.QueueSettings{BatchLength: 4, MaxWorkers: 5, Length: 100}) } }) @@ -141,7 +141,7 @@ func testWorkerPoolQueuePersistence(t *testing.T, queueSetting setting.QueueSett q, _ := newWorkerPoolQueueForTest("pr_patch_checker_test", queueSetting, testHandler, true) stop := runWorkerPoolQueue(q) - for i := 0; i < testCount; i++ { + for i := range testCount { _ = q.Push("task-" + strconv.Itoa(i)) } close(startWhenAllReady) @@ -186,7 +186,7 @@ func TestWorkerPoolQueueActiveWorkers(t *testing.T) { q, _ := newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 1, Length: 100}, handler, false) stop := runWorkerPoolQueue(q) - for i := 0; i < 5; i++ { + for i := range 5 { assert.NoError(t, q.Push(i)) } @@ -202,7 +202,7 @@ func TestWorkerPoolQueueActiveWorkers(t *testing.T) { q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 3, Length: 100}, handler, false) stop = runWorkerPoolQueue(q) - for i := 0; i < 15; i++ { + for i := range 15 { assert.NoError(t, q.Push(i)) } @@ -274,7 +274,7 @@ func TestWorkerPoolQueueWorkerIdleReset(t *testing.T) { } q, _ = newWorkerPoolQueueForTest("test-workpoolqueue", setting.QueueSettings{Type: "channel", BatchLength: 1, MaxWorkers: 2, Length: 100}, handler, false) stop := runWorkerPoolQueue(q) - for i := 0; i < 100; i++ { + for i := range 100 { assert.NoError(t, q.Push(i)) } time.Sleep(500 * time.Millisecond) diff --git a/modules/repository/init.go b/modules/repository/init.go index 91d4889782..12e9606c74 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -125,7 +125,7 @@ func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg } labels := make([]*issues_model.Label, len(list)) - for i := 0; i < len(list); i++ { + for i := range list { labels[i] = &issues_model.Label{ Name: list[i].Name, Exclusive: list[i].Exclusive, diff --git a/modules/reqctx/datastore.go b/modules/reqctx/datastore.go index d025dad7f3..1d4bee613f 100644 --- a/modules/reqctx/datastore.go +++ b/modules/reqctx/datastore.go @@ -6,6 +6,7 @@ package reqctx import ( "context" "io" + "maps" "sync" "code.gitea.io/gitea/modules/process" @@ -22,9 +23,7 @@ func (ds ContextData) GetData() ContextData { } func (ds ContextData) MergeFrom(other ContextData) ContextData { - for k, v := range other { - ds[k] = v - } + maps.Copy(ds, other) return ds } diff --git a/modules/setting/indexer.go b/modules/setting/indexer.go index e34baae012..ace7eec70e 100644 --- a/modules/setting/indexer.go +++ b/modules/setting/indexer.go @@ -96,7 +96,7 @@ func loadIndexerFrom(rootCfg ConfigProvider) { // IndexerGlobFromString parses a comma separated list of patterns and returns a glob.Glob slice suited for repo indexing func IndexerGlobFromString(globstr string) []*GlobMatcher { extarr := make([]*GlobMatcher, 0, 10) - for _, expr := range strings.Split(strings.ToLower(globstr), ",") { + for expr := range strings.SplitSeq(strings.ToLower(globstr), ",") { expr = strings.TrimSpace(expr) if expr != "" { if g, err := GlobMatcherCompile(expr, '.', '/'); err != nil { diff --git a/modules/setting/log.go b/modules/setting/log.go index 614d9ee75a..59866c7605 100644 --- a/modules/setting/log.go +++ b/modules/setting/log.go @@ -227,8 +227,8 @@ func initLoggerByName(manager *log.LoggerManager, rootCfg ConfigProvider, logger } var eventWriters []log.EventWriter - modes := strings.Split(modeVal, ",") - for _, modeName := range modes { + modes := strings.SplitSeq(modeVal, ",") + for modeName := range modes { modeName = strings.TrimSpace(modeName) if modeName == "" { continue diff --git a/modules/setting/markup.go b/modules/setting/markup.go index 365af05fcf..afaaaa2831 100644 --- a/modules/setting/markup.go +++ b/modules/setting/markup.go @@ -149,8 +149,8 @@ func loadMarkupFrom(rootCfg ConfigProvider) { func newMarkupSanitizer(name string, sec ConfigSection) { rule, ok := createMarkupSanitizerRule(name, sec) if ok { - if strings.HasPrefix(name, "sanitizer.") { - names := strings.SplitN(strings.TrimPrefix(name, "sanitizer."), ".", 2) + if after, ok0 := strings.CutPrefix(name, "sanitizer."); ok0 { + names := strings.SplitN(after, ".", 2) name = names[0] } for _, renderer := range ExternalMarkupRenderers { diff --git a/modules/setting/mirror.go b/modules/setting/mirror.go index 3aa530a1f4..300711789d 100644 --- a/modules/setting/mirror.go +++ b/modules/setting/mirror.go @@ -48,11 +48,7 @@ func loadMirrorFrom(rootCfg ConfigProvider) { Mirror.MinInterval = 1 * time.Minute } if Mirror.DefaultInterval < Mirror.MinInterval { - if time.Hour*8 < Mirror.MinInterval { - Mirror.DefaultInterval = Mirror.MinInterval - } else { - Mirror.DefaultInterval = time.Hour * 8 - } + Mirror.DefaultInterval = max(time.Hour*8, Mirror.MinInterval) log.Warn("Mirror.DefaultInterval is less than Mirror.MinInterval, set to %s", Mirror.DefaultInterval.String()) } } diff --git a/modules/setting/storage.go b/modules/setting/storage.go index e1d9b1fa7a..f43af1a8c0 100644 --- a/modules/setting/storage.go +++ b/modules/setting/storage.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "path/filepath" + "slices" "strings" ) @@ -30,12 +31,7 @@ var storageTypes = []StorageType{ // IsValidStorageType returns true if the given storage type is valid func IsValidStorageType(storageType StorageType) bool { - for _, t := range storageTypes { - if t == storageType { - return true - } - } - return false + return slices.Contains(storageTypes, storageType) } // MinioStorageConfig represents the configuration for a minio storage diff --git a/modules/structs/issue.go b/modules/structs/issue.go index 6a6b74c34e..df0be8f9ec 100644 --- a/modules/structs/issue.go +++ b/modules/structs/issue.go @@ -203,7 +203,7 @@ func (l *IssueTemplateStringSlice) UnmarshalYAML(value *yaml.Node) error { if err != nil { return err } - for _, v := range strings.Split(str, ",") { + for v := range strings.SplitSeq(str, ",") { if v = strings.TrimSpace(v); v == "" { continue } diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 6fb23d9357..abc8076387 100644 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -113,7 +113,7 @@ type Repository struct { // enum: sha1,sha256 ObjectFormatName string `json:"object_format_name"` // swagger:strfmt date-time - MirrorUpdated time.Time `json:"mirror_updated,omitempty"` + MirrorUpdated time.Time `json:"mirror_updated"` RepoTransfer *RepoTransfer `json:"repo_transfer"` Topics []string `json:"topics"` Licenses []string `json:"licenses"` diff --git a/modules/structs/repo_actions.go b/modules/structs/repo_actions.go index 75f8e188dd..c501470a37 100644 --- a/modules/structs/repo_actions.go +++ b/modules/structs/repo_actions.go @@ -57,7 +57,7 @@ type ActionWorkflow struct { HTMLURL string `json:"html_url"` BadgeURL string `json:"badge_url"` // swagger:strfmt date-time - DeletedAt time.Time `json:"deleted_at,omitempty"` + DeletedAt time.Time `json:"deleted_at"` } // ActionWorkflowResponse returns a ActionWorkflow @@ -104,9 +104,9 @@ type ActionWorkflowStep struct { Status string `json:"status"` Conclusion string `json:"conclusion,omitempty"` // swagger:strfmt date-time - StartedAt time.Time `json:"started_at,omitempty"` + StartedAt time.Time `json:"started_at"` // swagger:strfmt date-time - CompletedAt time.Time `json:"completed_at,omitempty"` + CompletedAt time.Time `json:"completed_at"` } // ActionWorkflowJob represents a WorkflowJob @@ -129,9 +129,9 @@ type ActionWorkflowJob struct { // swagger:strfmt date-time CreatedAt time.Time `json:"created_at"` // swagger:strfmt date-time - StartedAt time.Time `json:"started_at,omitempty"` + StartedAt time.Time `json:"started_at"` // swagger:strfmt date-time - CompletedAt time.Time `json:"completed_at,omitempty"` + CompletedAt time.Time `json:"completed_at"` } // ActionRunnerLabel represents a Runner Label diff --git a/modules/structs/user.go b/modules/structs/user.go index 5ed677f239..7338e45739 100644 --- a/modules/structs/user.go +++ b/modules/structs/user.go @@ -35,9 +35,9 @@ type User struct { // Is the user an administrator IsAdmin bool `json:"is_admin"` // swagger:strfmt date-time - LastLogin time.Time `json:"last_login,omitempty"` + LastLogin time.Time `json:"last_login"` // swagger:strfmt date-time - Created time.Time `json:"created,omitempty"` + Created time.Time `json:"created"` // Is user restricted Restricted bool `json:"restricted"` // Is user active diff --git a/modules/structs/user_gpgkey.go b/modules/structs/user_gpgkey.go index ff9b0aea1d..deae70de33 100644 --- a/modules/structs/user_gpgkey.go +++ b/modules/structs/user_gpgkey.go @@ -21,9 +21,9 @@ type GPGKey struct { CanCertify bool `json:"can_certify"` Verified bool `json:"verified"` // swagger:strfmt date-time - Created time.Time `json:"created_at,omitempty"` + Created time.Time `json:"created_at"` // swagger:strfmt date-time - Expires time.Time `json:"expires_at,omitempty"` + Expires time.Time `json:"expires_at"` } // GPGKeyEmail an email attached to a GPGKey diff --git a/modules/structs/user_key.go b/modules/structs/user_key.go index c4c41207e1..16225a852a 100644 --- a/modules/structs/user_key.go +++ b/modules/structs/user_key.go @@ -15,8 +15,8 @@ type PublicKey struct { Title string `json:"title,omitempty"` Fingerprint string `json:"fingerprint,omitempty"` // swagger:strfmt date-time - Created time.Time `json:"created_at,omitempty"` - Updated time.Time `json:"last_used_at,omitempty"` + Created time.Time `json:"created_at"` + Updated time.Time `json:"last_used_at"` Owner *User `json:"user,omitempty"` ReadOnly bool `json:"read_only,omitempty"` KeyType string `json:"key_type,omitempty"` diff --git a/modules/templates/eval/eval_test.go b/modules/templates/eval/eval_test.go index c9e514b5eb..f956f6cbdf 100644 --- a/modules/templates/eval/eval_test.go +++ b/modules/templates/eval/eval_test.go @@ -12,7 +12,7 @@ import ( ) func tokens(s string) (a []any) { - for _, v := range strings.Fields(s) { + for v := range strings.FieldsSeq(s) { a = append(a, v) } return a diff --git a/modules/templates/htmlrenderer.go b/modules/templates/htmlrenderer.go index 529284f7e8..f51936354e 100644 --- a/modules/templates/htmlrenderer.go +++ b/modules/templates/htmlrenderer.go @@ -251,7 +251,7 @@ func extractErrorLine(code []byte, lineNum, posNum int, target string) string { b := bufio.NewReader(bytes.NewReader(code)) var line []byte var err error - for i := 0; i < lineNum; i++ { + for i := range lineNum { if line, err = b.ReadBytes('\n'); err != nil { if i == lineNum-1 && errors.Is(err, io.EOF) { err = nil diff --git a/modules/templates/scopedtmpl/scopedtmpl.go b/modules/templates/scopedtmpl/scopedtmpl.go index 2722ba97a2..0d84f8598b 100644 --- a/modules/templates/scopedtmpl/scopedtmpl.go +++ b/modules/templates/scopedtmpl/scopedtmpl.go @@ -7,6 +7,7 @@ import ( "fmt" "html/template" "io" + "maps" "reflect" "sync" texttemplate "text/template" @@ -40,9 +41,7 @@ func (t *ScopedTemplate) Funcs(funcMap template.FuncMap) { panic("cannot add new functions to frozen template set") } t.all.Funcs(funcMap) - for k, v := range funcMap { - t.parseFuncs[k] = v - } + maps.Copy(t.parseFuncs, funcMap) } func (t *ScopedTemplate) New(name string) *template.Template { @@ -159,9 +158,7 @@ func newScopedTemplateSet(all *template.Template, name string) (*scopedTemplateS textTmplPtr.muFuncs.Lock() ts.execFuncs = map[string]reflect.Value{} - for k, v := range textTmplPtr.execFuncs { - ts.execFuncs[k] = v - } + maps.Copy(ts.execFuncs, textTmplPtr.execFuncs) textTmplPtr.muFuncs.Unlock() var collectTemplates func(nodes []parse.Node) @@ -220,9 +217,7 @@ func (ts *scopedTemplateSet) newExecutor(funcMap map[string]any) TemplateExecuto tmpl := texttemplate.New("") tmplPtr := ptr[textTemplate](tmpl) tmplPtr.execFuncs = map[string]reflect.Value{} - for k, v := range ts.execFuncs { - tmplPtr.execFuncs[k] = v - } + maps.Copy(tmplPtr.execFuncs, ts.execFuncs) if funcMap != nil { tmpl.Funcs(funcMap) } diff --git a/modules/testlogger/testlogger.go b/modules/testlogger/testlogger.go index 8e970aa2be..60e281d403 100644 --- a/modules/testlogger/testlogger.go +++ b/modules/testlogger/testlogger.go @@ -92,7 +92,7 @@ func (w *testLoggerWriterCloser) Reset() { // Printf takes a format and args and prints the string to os.Stdout func Printf(format string, args ...any) { if !log.CanColorStdout { - for i := 0; i < len(args); i++ { + for i := range args { if c, ok := args[i].(*log.ColoredValue); ok { args[i] = c.Value() } diff --git a/modules/util/remove.go b/modules/util/remove.go index d1e38faf5f..3db0b5a796 100644 --- a/modules/util/remove.go +++ b/modules/util/remove.go @@ -15,7 +15,7 @@ const windowsSharingViolationError syscall.Errno = 32 // Remove removes the named file or (empty) directory with at most 5 attempts. func Remove(name string) error { var err error - for i := 0; i < 5; i++ { + for range 5 { err = os.Remove(name) if err == nil { break @@ -44,7 +44,7 @@ func Remove(name string) error { // RemoveAll removes the named file or (empty) directory with at most 5 attempts. func RemoveAll(name string) error { var err error - for i := 0; i < 5; i++ { + for range 5 { err = os.RemoveAll(name) if err == nil { break @@ -73,7 +73,7 @@ func RemoveAll(name string) error { // Rename renames (moves) oldpath to newpath with at most 5 attempts. func Rename(oldpath, newpath string) error { var err error - for i := 0; i < 5; i++ { + for i := range 5 { err = os.Rename(oldpath, newpath) if err == nil { break diff --git a/modules/util/rotatingfilewriter/writer_test.go b/modules/util/rotatingfilewriter/writer_test.go index 88392797b3..f6ea1d50ae 100644 --- a/modules/util/rotatingfilewriter/writer_test.go +++ b/modules/util/rotatingfilewriter/writer_test.go @@ -23,7 +23,7 @@ func TestCompressOldFile(t *testing.T) { ng, err := os.OpenFile(nonGzip, os.O_CREATE|os.O_WRONLY, 0o660) assert.NoError(t, err) - for i := 0; i < 999; i++ { + for range 999 { f.WriteString("This is a test file\n") ng.WriteString("This is a test file\n") } diff --git a/modules/util/string.go b/modules/util/string.go index 19cf75b8b3..03c0df96a3 100644 --- a/modules/util/string.go +++ b/modules/util/string.go @@ -103,7 +103,7 @@ func UnsafeStringToBytes(s string) []byte { func SplitTrimSpace(input, sep string) []string { input = strings.TrimSpace(input) var stringList []string - for _, s := range strings.Split(input, sep) { + for s := range strings.SplitSeq(input, sep) { if s = strings.TrimSpace(s); s != "" { stringList = append(stringList, s) } diff --git a/modules/validation/helpers.go b/modules/validation/helpers.go index 9f6cf5201a..ba383ba195 100644 --- a/modules/validation/helpers.go +++ b/modules/validation/helpers.go @@ -7,6 +7,7 @@ import ( "net" "net/url" "regexp" + "slices" "strings" "sync" @@ -55,12 +56,7 @@ func IsValidSiteURL(uri string) bool { return false } - for _, scheme := range setting.Service.ValidSiteURLSchemes { - if scheme == u.Scheme { - return true - } - } - return false + return slices.Contains(setting.Service.ValidSiteURLSchemes, u.Scheme) } // IsEmailDomainListed checks whether the domain of an email address diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index 03e188f509..ee4eca976e 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -50,7 +50,7 @@ func AssignForm(form any, data map[string]any) { } func getRuleBody(field reflect.StructField, prefix string) string { - for _, rule := range strings.Split(field.Tag.Get("binding"), ";") { + for rule := range strings.SplitSeq(field.Tag.Get("binding"), ";") { if strings.HasPrefix(rule, prefix) { return rule[len(prefix) : len(rule)-1] } diff --git a/modules/web/router.go b/modules/web/router.go index da06b955b1..5812ff69d4 100644 --- a/modules/web/router.go +++ b/modules/web/router.go @@ -125,8 +125,8 @@ func (r *Router) Methods(methods, pattern string, h ...any) { middlewares, handlerFunc := wrapMiddlewareAndHandler(r.curMiddlewares, h) fullPattern := r.getPattern(pattern) if strings.Contains(methods, ",") { - methods := strings.Split(methods, ",") - for _, method := range methods { + methods := strings.SplitSeq(methods, ",") + for method := range methods { r.chiRouter.With(middlewares...).Method(strings.TrimSpace(method), fullPattern, handlerFunc) } } else { diff --git a/modules/web/router_path.go b/modules/web/router_path.go index baf1b522af..1531ccd01c 100644 --- a/modules/web/router_path.go +++ b/modules/web/router_path.go @@ -99,7 +99,7 @@ func isValidMethod(name string) bool { func newRouterPathMatcher(methods, pattern string, h ...any) *routerPathMatcher { middlewares, handlerFunc := wrapMiddlewareAndHandler(nil, h) p := &routerPathMatcher{methods: make(container.Set[string]), middlewares: middlewares, handlerFunc: handlerFunc} - for _, method := range strings.Split(methods, ",") { + for method := range strings.SplitSeq(methods, ",") { method = strings.TrimSpace(method) if !isValidMethod(method) { panic("invalid HTTP method: " + method) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 8797ef4bc0..6d8aaef4cd 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1332,7 +1332,9 @@ editor.upload_file = Upload File editor.edit_file = Edit File editor.preview_changes = Preview Changes editor.cannot_edit_lfs_files = LFS files cannot be edited in the web interface. +editor.cannot_edit_too_large_file = The file is too large to be edited. editor.cannot_edit_non_text_files = Binary files cannot be edited in the web interface. +editor.file_not_editable_hint = But you can still rename or move it. editor.edit_this_file = Edit File editor.this_file_locked = File is locked editor.must_be_on_a_branch = You must be on a branch to make or propose changes to this file. diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index 2cc0ca7202..5dd918fb91 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -1332,7 +1332,9 @@ editor.upload_file=Téléverser un fichier editor.edit_file=Modifier le fichier editor.preview_changes=Aperçu des modifications editor.cannot_edit_lfs_files=Les fichiers LFS ne peuvent pas être modifiés dans l'interface web. +editor.cannot_edit_too_large_file=Le fichier est trop gros pour être édité. editor.cannot_edit_non_text_files=Les fichiers binaires ne peuvent pas être édités dans l'interface web. +editor.file_not_editable_hint=Mais vous pouvez toujours le renommer ou le déplacer. editor.edit_this_file=Modifier le fichier editor.this_file_locked=Le fichier est verrouillé editor.must_be_on_a_branch=Vous devez être sur une branche pour appliquer ou proposer des modifications à ce fichier. diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 543858e50b..a62a24ff1d 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1331,7 +1331,9 @@ editor.upload_file=Carregar ficheiro editor.edit_file=Editar ficheiro editor.preview_changes=Pré-visualizar modificações editor.cannot_edit_lfs_files=Ficheiros LFS não podem ser editados na interface web. +editor.cannot_edit_too_large_file=O ficheiro é demasiado grande para ser editado. editor.cannot_edit_non_text_files=Ficheiros binários não podem ser editados na interface da web. +editor.file_not_editable_hint=Mas ainda pode renomeá-lo ou movê-lo. editor.edit_this_file=Editar ficheiro editor.this_file_locked=Ficheiro bloqueado editor.must_be_on_a_branch=Tem que estar num ramo para fazer ou propor modificações neste ficheiro. diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 43ff6495c1..1f46369fe0 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -1690,7 +1690,12 @@ issues.delete.title=Bu konu silinsin mi? issues.delete.text=Bu konuyu gerçekten silmek istiyor musunuz? (Bu iÅŸlem tüm içeriÄŸi kalıcı olarak silecektir. ArÅŸivde tutma niyetiniz varsa silmek yerine kapatmayı düşünün) issues.tracker=Zaman Takibi +issues.timetracker_timer_manually_add=Zaman Ekle +issues.time_estimate_set=Tahmini zaman ayarla +issues.time_estimate_display=Tahmin: %s +issues.remove_time_estimate_at=zaman tahmini %s kaldırıldı +issues.time_estimate_invalid=Zaman tahmini biçimi hatalı issues.tracker_auto_close=Bu konu kapatıldığında zamanlayıcı otomatik olarak durur issues.tracking_already_started=`<a href="%s">baÅŸka bir konuda</a> zaten zaman izleyici baÅŸlattınız!` issues.cancel_tracking_history=`%s zaman takibini iptal etti` diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 68f5e42d47..a740ac1ff5 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -145,6 +145,7 @@ confirm_delete_selected=Підтверджуєте Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… Ð name=Ðазва value=Ð—Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ +readme=Файл readme filter=Фільтр filter.clear=ОчиÑтити фільтр @@ -156,24 +157,34 @@ filter.is_mirror=Віддзеркалено filter.not_mirror=Ðе віддзеркалено filter.is_template=Шаблон filter.not_template=Ðе шаблон -filter.public=Публічний +filter.public=Публічна filter.private=Приватний no_results_found=Ðічого не знайдено. +internal_error_skipped=ТрапилаÑÑŒ Ð²Ð½ÑƒÑ‚Ñ€Ñ–ÑˆÐ½Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°, але пропущена: %s [search] search=Пошук... type_tooltip=Тип пошуку fuzzy=Ðеточний +fuzzy_tooltip=Включити результати, Ñкі також близько відповідають пошуковому запиту +words=Слова +words_tooltip=Включати тільки результати, Ñкі відповідають пошуковим Ñловам regexp=РегулÑрний вираз +regexp_tooltip=Включати тільки результати, Ñкі відповідають пошуковому запиту регулÑрного виразу +exact=Точний +exact_tooltip=Включати тільки результати, Ñкі відповідають точному пошуковому запиту +repo_kind=Пошук Ñховищ... user_kind=Пошук кориÑтувачів... org_kind=Пошук організацій... team_kind=Пошук команд... code_kind=Пошук коду... code_search_unavailable=Пошук коду наразі недоÑтупний. Будь лаÑка, звернітьÑÑ Ð´Ð¾ адмініÑтратора Ñайту. code_search_by_git_grep=Поточні результати пошуку коду надаютьÑÑ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¾ÑŽ "git grep". Результати можуть бути кращими, Ñкщо адмініÑтратор Ñайту увімкне індекÑатор Ñховища. +package_kind=Пошук пакетів... project_kind=Пошук проєктів... branch_kind=Пошук гілок... +tag_kind=Пошук за мітками... commit_kind=Пошук комітів... no_results=Ðе знайдено жодного збігу. issue_kind=Пошук задач... @@ -182,6 +193,7 @@ keyword_search_unavailable=Пошук за ключовими Ñловами нР[aria] navbar=Панель навігації +footer=Ðижній колонтитул footer.software=Про програмне Ð·Ð°Ð±ÐµÐ·Ð¿ÐµÑ‡ÐµÐ½Ð½Ñ footer.links=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ @@ -207,6 +219,7 @@ buttons.table.rows=Ð Ñдки buttons.table.cols=Стовпці buttons.mention.tooltip=Згадати кориÑтувача або команду buttons.ref.tooltip=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° задачу або запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +buttons.switch_to_legacy.tooltip=ВикориÑтовувати заÑтарілий редактор buttons.enable_monospace_font=Увімкнути моноширинний шрифт buttons.disable_monospace_font=Вимкнути моноширинний шрифт @@ -216,14 +229,18 @@ string.desc=Я - Ð [error] occurred=СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° +report_message=Якщо ви вважаєте, що це помилка Gitea, будь лаÑка, Ñпробуйте відшукати відповідну проблему на <a href="https://github.com/go-gitea/gitea/issues">GitHub</a> або Ñтворіть нову, Ñкщо необхідно. not_found=Ціль не знайдено. network_error=Помилка мережі [startpage] app_desc=Зручний влаÑний ÑÐµÑ€Ð²Ñ–Ñ Ñ…Ð¾Ñтингу репозиторіїв Git install=Легко вÑтановити +install_desc=ПроÑто <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-binary">запуÑтіть двійковий файл</a> Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ñ— платформи, ÑкориÑтайтеÑÑ <a target="_blank" rel="noopener noreferrer" href="https://github.com/go-gitea/gitea/tree/master/docker">Docker</a>, або вÑтановіть <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.com/installation/install-from-package">ÑиÑтемою ÐºÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°ÐºÑƒÐ½ÐºÐ°Ð¼Ð¸</a>. platform=ПлатформонезалежніÑть +platform_desc=Gitea запуÑкаєтьÑÑ Ð±ÑƒÐ´ÑŒ-де, де <a target=«_blank» rel=«noopener noreferrer» href=«%s»>Go</a> може компілюватиÑÑŒ: на Windows, macOS, Linux, ARM тощо. Виберіть платформу, Ñку любите! lightweight=ÐевибагливіÑть +lightweight_desc=Gitea має мінімальні вимоги Ñ– може працювати на недорогому Raspberry Pi. Заощаджуйте реÑурÑи вашої машини! license=Відкритий вихідний код [install] @@ -264,6 +281,7 @@ repo_path_helper=До цього каталогу буде збережено в lfs_path=Кореневий шлÑÑ… Git LFS lfs_path_helper=У цій теці будуть зберігатиÑÑ Ñ„Ð°Ð¹Ð»Ð¸ Git LFS. Залиште порожнім, щоб вимкнути. run_user=Виконати Ñк +run_user_helper=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача операційної ÑиÑтеми, від імені Ñкого запуÑкаєтьÑÑ Gitea. Зауважте, що цей кориÑтувач повинен мати доÑтуп до кореневого шлÑху Ñховища. domain=Домен Ñервера domain_helper=Домен або хоÑÑ‚-адреÑа Ñервера. ssh_port=Порт SSH Ñервера @@ -279,6 +297,7 @@ optional_title=Ðеобов'Ñзкові Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ email_title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти smtp_addr=Сервер SMTP smtp_port=Порт SMTP +smtp_from=Відправити лиÑта від імені smtp_from_invalid=ÐдреÑа "ÐадіÑлати лиÑта Ñк" недійÑна smtp_from_helper=ÐдреÑа електронної пошти, Ñку буде викориÑтовувати Gitea. Введіть звичайну адреÑу електронної пошти або викориÑтовуйте формат «Ім'Ñ» <email@example.com>. mailer_user=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача SMTP @@ -302,6 +321,7 @@ openid_signup_popup=Увімкнути ÑамореєÑтрацію кориÑÑ‚ enable_captcha=Увімкнути CAPTCHA Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації enable_captcha_popup=Вимагати CAPTCHA Ð´Ð»Ñ ÑамореєÑтрації кориÑтувачів. require_sign_in_view=Вимагати авторизації Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ³Ð»Ñду Ñторінок +require_sign_in_view_popup=Обмежити доÑтуп до Ñторінки лише Ð´Ð»Ñ Ð·Ð°Ñ€ÐµÑ”Ñтрованих кориÑтувачів. Відвідувачі побачать тільки Ñторінки входу Ñ– реєÑтрації. admin_setting_desc=Ð¡Ñ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора необов'Ñзково. Перший зареєÑтрований кориÑтувач автоматично Ñтає адмініÑтратором. admin_title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу адмініÑтратора admin_name=Ім'Ñ ÐºÑ€Ð¸Ñтувача ÐдмініÑтратора @@ -331,7 +351,9 @@ no_reply_address=Прихований поштовий домен no_reply_address_helper=Доменне ім'Ñ Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів із прихованою електронною адреÑою. Ðаприклад, ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача 'Joe' буде зображатиÑÑ Ð² Git Ñк 'joe@noreply.example.org', Ñкщо прихований домен електронної пошти вÑтановлено 'noreply.example.org'. password_algorithm=Ðлгоритм Ñ…ÐµÑˆÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ invalid_password_algorithm=ÐедійÑний хеш-алгоритм Ð¿Ð°Ñ€Ð¾Ð»Ñ +password_algorithm_helper=Ð’Ñтановіть алгоритм Ñ…ÐµÑˆÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ. Ðлгоритми мають різні вимоги та ÑтійкіÑть. Ðлгоритм argon2 Ñ” доÑить безпечним, але викориÑтовує багато пам'Ñті Ñ– може бути недоречним Ð´Ð»Ñ Ð¼Ð°Ð»Ð¸Ñ… ÑиÑтем. enable_update_checker=Увімкнути перевірку оновлень +enable_update_checker_helper=Періодично перевірÑти наÑвніÑть нових верÑій, підключаючиÑÑŒ до gitea.io. env_config_keys=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ñередовища env_config_keys_prompt=ÐаÑтупні змінні Ñередовища також будуть заÑтоÑовані до вашого файлу конфігурації: config_write_file_prompt=Ці параметри будуть запиÑані в: %s @@ -364,36 +386,57 @@ show_only_public=Показано тільки публічні issues.in_your_repos=У ваших Ñховищах guide_title=Жодної активноÑті +guide_desc=Ðаразі ви не Ñтежите за жодним Ñховищем або кориÑтувачем, тому нема чого відображати. Ви можете переглÑнути Ñховища або кориÑтувачів, Ñкі Ð²Ð°Ñ Ñ†Ñ–ÐºÐ°Ð²Ð»Ñть, за поÑиланнÑми нижче. +explore_repos=ОглÑд Ñховищ +explore_users=ОглÑд кориÑтувачів +empty_org=Організацій поки що немає. +empty_repo=Сховищ поки що немає. [explore] repos=Сховища users=КориÑтувачі organizations=Організації +go_to=Перейти до code=Код code_last_indexed_at=ОÑтанні індекÑовані %s +relevant_repositories_tooltip=Сховища, Ñкі Ñ” відгалуженими або не мають теми, піктограми й опиÑу, приховуютьÑÑ. +relevant_repositories=Показано лише важливі Ñховища, <a href="%s">показати нефільтровані результати</a>. [auth] create_new_account=РеєÑÑ‚Ñ€Ð°Ñ†Ñ–Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу +already_have_account=Вже зареєÑтровані? +sign_in_now=Увійдіть зараз! disable_register_prompt=РеєÑтрацію вимкнено. Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором Ñайту. disable_register_mail=ÐŸÑ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ñ€ÐµÑ”Ñтрації електронною поштою вимкнено. +manual_activation_only=ЗвернітьÑÑ Ð´Ð¾ адмініÑтратора Ñайту Ð´Ð»Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ—. remember_me=Запам’Ñтати цей приÑтрій +remember_me.compromised=Токен Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ більше не дійÑний, що може Ñвідчити про Ñкомпрометований обліковий запиÑ. Перевірте Ñвій обліковий Ð·Ð°Ð¿Ð¸Ñ Ð½Ð° наÑвніÑть незвичайних дій. forgot_password_title=Забув пароль forgot_password=Забули пароль? +need_account=Потрібен обліковий запиÑ? +sign_up_now=ЗареєÑтруватиÑÑ. +sign_up_successful=Обліковий Ð·Ð°Ð¿Ð¸Ñ Ñтворено уÑпішно. Вітаю! +confirmation_mail_sent_prompt_ex=Ðовий лиÑÑ‚ з підтвердженнÑм було надіÑлано на <b>%s</b>. Будь лаÑка, перевірте Ñвою поштову Ñкриньку протÑгом наÑтупних %s, щоб завершити Ð¿Ñ€Ð¾Ñ†ÐµÑ Ñ€ÐµÑ”Ñтрації. Якщо ви вказали невірну адреÑу електронної пошти, ви можете увійти ще раз Ñ– змінити Ñ—Ñ—. must_change_password=Оновити пароль allow_password_change=Вимагати від кориÑтувача змінити пароль (рекомендовано) reset_password_mail_sent_prompt=Ðа адреÑу <b>%s</b> було надіÑлано лиÑÑ‚ із підтвердженнÑм. Будь лаÑка, перевірте Ñвою поштову Ñкриньку протÑгом наÑтупних %s, щоб завершити Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу. active_your_account=Ðктивувати обліковий Ð·Ð°Ð¿Ð¸Ñ account_activated=Обліковий Ð·Ð°Ð¿Ð¸Ñ Ð°ÐºÑ‚Ð¸Ð²Ð¾Ð²Ð°Ð½Ð¾ prohibit_login=Вхід заборонено +prohibit_login_desc=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð¾ Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ, звернітьÑÑ Ð´Ð¾ адмініÑтратора Ñайту. resent_limit_prompt=Ви вже надÑилали запит на активацію нещодавно. Зачекайте 3 хвилини Ñ– Ñпробуйте ще раз. has_unconfirmed_mail= Привіт %s, у Ð²Ð°Ñ Ð½ÐµÐ¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð° адреÑа електронної пошти (<b>%s</b>). Якщо ви не отримали лиÑта з підтвердженнÑм або вам потрібно надіÑлати новий, будь лаÑка, натиÑніть кнопку нижче. +change_unconfirmed_mail_address=Якщо ваша адреÑа електронної пошти Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації невірна, ви можете змінити Ñ—Ñ— тут Ñ– надіÑлати новий лиÑÑ‚ з підтвердженнÑм. resend_mail=ÐатиÑніть тут, щоб повторно надіÑлати лиÑÑ‚ з активацією email_not_associate=Ð¦Ñ Ð°Ð´Ñ€ÐµÑа електронної пошти не пов'Ñзана з жодним обліковим запиÑом. send_reset_mail=ÐадіÑлати лиÑÑ‚ Ð´Ð»Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу reset_password=Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу invalid_code=Ваш код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð½ÐµÐ´Ñ–Ð¹Ñний або його термін дії закінчивÑÑ. +invalid_code_forgot_password=Ваш код Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð½ÐµÐ´Ñ–Ð¹Ñний або термін дії минув. ÐатиÑніть <a href="%s">тут</a>, щоб почати новий ÑеанÑ. +invalid_password=Ваш пароль не збігаєтьÑÑ Ð· паролем, Ñкий викориÑтовувавÑÑ Ð¿Ñ€Ð¸ Ñтворенні облікового запиÑу. reset_password_helper=Відновити обліковий Ð·Ð°Ð¿Ð¸Ñ +reset_password_wrong_user=Ви увійшли Ñк %s, але поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу призначене Ð´Ð»Ñ %s password_too_short=Довжина Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð½Ðµ може бути меншою за %d Ñимволів. non_local_account=Ðелокальні кориÑтувачі не можуть оновити Ñвій пароль через Ð²ÐµÐ±Ñ–Ð½Ñ‚ÐµÑ€Ñ„ÐµÐ¹Ñ Gitea. verify=Підтвердити @@ -410,11 +453,15 @@ oauth_signup_submit=Поповнити обліковий Ð·Ð°Ð¿Ð¸Ñ oauth_signin_tab=Прив'Ñзати до Ñ–Ñнуючого облікового запиÑу oauth_signin_title=Увійдіть щоб авторизувати пов'Ñзаний обліковий Ð·Ð°Ð¿Ð¸Ñ oauth_signin_submit=Прив'Ñзати обліковий Ð·Ð°Ð¿Ð¸Ñ +oauth.signin.error.general=Під Ñ‡Ð°Ñ Ð¾Ð±Ñ€Ð¾Ð±ÐºÐ¸ запиту на авторизацію ÑталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ°: %s. Якщо Ñ†Ñ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° повторитьÑÑ, звернітьÑÑ Ð´Ð¾ адмініÑтратора Ñайту. +oauth.signin.error.access_denied=Запит на авторизацію відхилено. +oauth.signin.error.temporarily_unavailable=Помилка авторизації, Ñервер автентифікації тимчаÑово недоÑтупний. Спробуйте пізніше. openid_connect_submit=Під’єднатиÑÑ openid_connect_title=ПідключитиÑÑ Ð´Ð¾ Ñ–Ñнуючого облікового запиÑу openid_connect_desc=Обраний OpenID URI невідомий. Зв'Ñжіть його тут з новим обліковим запиÑом. openid_register_title=Створити новий обліковий Ð·Ð°Ð¿Ð¸Ñ openid_register_desc=Обраний OpenID URI невідомий. Зв'Ñжіть його тут з новим обліковим запиÑом. +openid_signin_desc=Введіть Ñвій OpenID URI. Ðаприклад: alice.openid.example.org або https://openid.example.org/alice. disable_forgot_password_mail=Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу вимкнено, оÑкільки не налаштована електронна пошта. Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором Ñайту. disable_forgot_password_mail_admin=Ð’Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу доÑтупне лише за наÑвноÑті електронної пошти. Будь лаÑка, налаштуйте електронну пошту, щоб увімкнути Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу. email_domain_blacklisted=Ви не можете зареєÑтруватиÑÑ Ð· адреÑою електронної пошти. @@ -424,8 +471,13 @@ authorize_application_created_by=Ð¦Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð° Ñтворена %s. authorize_application_description=Якщо ви авторизуєте цю програму, їй буде надано дозвіл на Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð±Ð¾ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð²Ñієї інформації вашого облікового запиÑу, включно з приватними Ñховищами та організаціÑми. authorize_title=Ðвторизувати "%s" Ð´Ð»Ñ Ð´Ð¾Ñтупу до вашого облікового запиÑу? authorization_failed=Помилка авторизації +authorization_failed_desc=ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ñ–Ñ Ð½Ðµ вдалаÑÑ, оÑкільки ми виÑвили недійÑний запит. ЗвернітьÑÑ Ð´Ð¾ розробника програми, Ñку ви намагалиÑÑ Ð°Ð²Ñ‚Ð¾Ñ€Ð¸Ð·ÑƒÐ²Ð°Ñ‚Ð¸. sspi_auth_failed=Помилка автентифікації SSPI +password_pwned=Пароль, Ñкий ви обрали, знаходитьÑÑ Ð² <a target="_blank" rel="noopener noreferrer" href="https://haveibeenpwned.com/Passwords">ÑпиÑку викрадених паролів</a>, раніше викритих в публічних витоках даних. Спробуйте ще раз з іншим паролем Ñ– подумайте про зміну цього Ð¿Ð°Ñ€Ð¾Ð»Ñ Ð´ÐµÑ–Ð½Ð´Ðµ. password_pwned_err=Ðе вдалоÑÑ Ð²Ð¸ÐºÐ¾Ð½Ð°Ñ‚Ð¸ запит до HaveIBeenPwed +last_admin=Ðе можна видалити оÑтаннього адмініÑтратора. Повинен бути хоча б один адмініÑтратор. +signin_passkey=Увійти за допомогою ключа доÑтупу +back_to_sign_in=ПовернутиÑÑ Ð´Ð¾ авторизації [mail] view_it_on=ПереглÑнути на %s @@ -442,6 +494,7 @@ activate_email=Підтвердіть адреÑу електронної пош activate_email.title=%s, будь лаÑка, підтвердіть вашу адреÑу електронної пошти activate_email.text=Будь лаÑка, перейдіть за наÑтупним поÑиланнÑм протÑгом <b>%s</b>, щоб підтвердити Ñвою електронну адреÑу: +register_notify=ЛаÑкаво проÑимо до %s register_notify.title=%[1]s, лаÑкаво проÑимо до %[2]s register_notify.text_1=це лиÑÑ‚ з підтвердженнÑм реєÑтрації на %s! register_notify.text_2=Тепер ви можете увійти Ñк: %s. @@ -489,6 +542,7 @@ repo.collaborator.added.text=Ð’Ð°Ñ Ð´Ð¾Ð´Ð°Ð»Ð¸ Ñк Ñпівавтора до team_invite.subject=%[1]s запрошує Ð²Ð°Ñ Ð¿Ñ€Ð¸Ñ”Ð´Ð½Ð°Ñ‚Ð¸ÑÑ Ð´Ð¾ організації %[2]s team_invite.text_1=%[1]s запрошує Ð²Ð°Ñ Ð´Ð¾ команди %[2]s в організації %[3]s. team_invite.text_2=Перейдіть за поÑиланнÑм, щоб приєднатиÑÑ Ð´Ð¾ команди: +team_invite.text_3=Примітка: Це Ð·Ð°Ð¿Ñ€Ð¾ÑˆÐµÐ½Ð½Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ðµ Ð´Ð»Ñ %[1]s. Якщо ви не очікували цього запрошеннÑ, ви можете проігнорувати це повідомленнÑ. [modal] yes=Так @@ -532,6 +586,7 @@ url_error=`"%s" не Ñ” дійÑною URL-адреÑою.` include_error=` повинен міÑтити підрÑдок "%s".` glob_pattern_error=` недійÑний шаблон glob: %s.` regex_pattern_error=` недійÑний шаблон регулÑрного виразу: %s.` +username_error=` може міÑтити лише алфавітно-цифрові Ñимволи ('0-9', 'a-z', 'A-Z'), Ð´ÐµÑ„Ñ–Ñ ('-'), підкреÑÐ»ÐµÐ½Ð½Ñ ('_') та крапку ('.'). Ðе може починатиÑÑ Ð°Ð±Ð¾ закінчуватиÑÑ Ð½ÐµÐ°Ð»Ñ„Ð°Ð²Ñ–Ñ‚Ð½Ð¸Ð¼Ð¸ Ñимволами; поÑлідовні неалфавітні Ñимволи також заборонені.` unknown_error=Ðевідома помилка: captcha_incorrect=Код CAPTCHA неправильний. password_not_match=Паролі не збігаютьÑÑ. @@ -539,8 +594,11 @@ lang_select_error=Оберіть мову зі ÑпиÑку. username_been_taken=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача вже зайнÑте. username_change_not_local_user=Ðелокальні кориÑтувачі не можуть змінювати Ñвоє ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача. +change_username_disabled=Зміна імені кориÑтувача відключена. +change_full_name_disabled=Зміна повного імені відключена. username_has_not_been_changed=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача не змінено repo_name_been_taken=Ðазва Ñховища вже викориÑтовуєтьÑÑ. +repository_force_private=ПримуÑову приватніÑть ввімкнено: приватні Ñховища не можна зробити загальнодоÑтупними. repository_files_already_exist=Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñховища вже Ñ–Ñнують файли. ЗвернітьÑÑ Ð´Ð¾ ÑиÑтемного адмініÑтратора. repository_files_already_exist.adopt=Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñховища вже Ñ–Ñнують файли, Ñ– Ñ—Ñ… можна лише прийнÑти. repository_files_already_exist.delete=Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñховища вже Ñ–Ñнують файли. Ви повинні видалити Ñ—Ñ…. @@ -552,6 +610,7 @@ team_name_been_taken=Ðазва команди вже зайнÑта. team_no_units_error=Дозволити доÑтуп до принаймні одного розділу Ñховища. email_been_used=ÐдреÑа електронної пошти вже викориÑтовуєтьÑÑ. email_invalid=ÐдреÑа електронної пошти недійÑна. +email_domain_is_not_allowed=Домен електронної пошти кориÑтувача <b>%s</b> конфліктує з EMAIL_DOMAIN_ALLOWLIST або EMAIL_DOMAIN_BLOCKLIST. ПереконайтеÑÑ, що ваша Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¾Ñ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð°. openid_been_used=ÐдреÑа OpenID '%s' вже викориÑтовуєтьÑÑ. username_password_incorrect=Ðеправильне ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача або пароль. password_complexity=Пароль не відповідає вимогам ÑкладноÑті: @@ -564,16 +623,26 @@ enterred_invalid_org_name=Ви ввели неправильну назву ор enterred_invalid_owner_name=Ім'Ñ Ð½Ð¾Ð²Ð¾Ð³Ð¾ влаÑника недійÑне. enterred_invalid_password=Ви ввели неправильний пароль. unset_password=КориÑтувач не вÑтановив пароль. +unsupported_login_type=Тип входу не підтримуєтьÑÑ Ð´Ð»Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу. user_not_exist=КориÑтувач не Ñ–Ñнує. team_not_exist=Команда не Ñ–Ñнує. last_org_owner=Ви не можете видалити оÑтаннього кориÑтувача з групи 'влаÑників'. Ð’ організації має бути принаймні один влаÑник. cannot_add_org_to_team=Організацію неможливо додати Ñк учаÑника команди. +duplicate_invite_to_team=КориÑтувача вже запрошено Ñк члена команди. +organization_leave_success=Ви уÑпішно покинули організацію %s. invalid_ssh_key=Ðе вдаєтьÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ ключ SSH: %s invalid_gpg_key=Ðе вдаєтьÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ ключ GPG: %s invalid_ssh_principal=Ðевірна ідентичніÑть: %s +must_use_public_key=Ðаданий вами ключ — приватний. Будь лаÑка, нікуди не завантажуйте Ñвій приватний ключ. ÐатоміÑть викориÑтовуйте публічний ключ. +unable_verify_ssh_key=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ ключ SSH, перевірте його на наÑвніÑть помилок. auth_failed=Помилка автентифікації: %v +still_own_repo=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ð¾Ð»Ð¾Ð´Ñ–Ñ” одним або декількома Ñховищами, Ñпершу видаліть або перенеÑіть Ñ—Ñ…. +still_has_org=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ñ” учаÑником однієї або декількох організацій, Ñпершу залиште Ñ—Ñ…. +still_own_packages=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ð¾Ð»Ð¾Ð´Ñ–Ñ” одним або декількома пакетами, Ñпершу видаліть Ñ—Ñ…. +org_still_own_repo=Ð¦Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð²Ñе ще володіє одним або декількома Ñховищами, Ñпочатку видаліть або перенеÑіть Ñ—Ñ…. +org_still_own_packages=Ð¦Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð²Ñе ще має один або декілька пакетів, Ñпочатку видаліть Ñ—Ñ…. target_branch_not_exist=Цільової гілки не Ñ–Ñнує. target_ref_not_exist=Цільове поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ðµ Ñ–Ñнує %s @@ -582,6 +651,7 @@ admin_cannot_delete_self=Ви не можете видалити Ñебе, доР[user] change_avatar=Змінити аватар… +joined_on=ПриєднавÑÑ(-лаÑÑŒ) %s repositories=Сховища activity=Публічна активніÑть followers=ПоÑлідовники @@ -591,15 +661,19 @@ watched=ВідÑтежувані Ñховища code=Код projects=Проєкти overview=ОглÑд +following=ВідÑтежувані follow=Стежити unfollow=Ðе Ñтежити user_bio=Ð‘Ñ–Ð¾Ð³Ñ€Ð°Ñ„Ñ–Ñ disabled_public_activity=Цей кориÑтувач вимкнув публічну видиміÑть активноÑті. +email_visibility.limited=Ваша електронна пошта видима Ð´Ð»Ñ Ð²ÑÑ–Ñ… автентифікованих кориÑтувачів +email_visibility.private=Вашу адреÑу електронної пошти бачитимете лише ви та адмініÑтратори show_on_map=Показати це міÑце на карті settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача form.name_reserved=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача "%s" зарезервовано. form.name_pattern_not_allowed=Шаблон "%s" не дозволено в імені кориÑтувача. +form.name_chars_not_allowed=Ð†Ð¼â€™Ñ ÐºÐ¾Ñ€Ð¸Ñтувача "%s" міÑтить неприпуÑтимі Ñимволи. block.block=Заблокувати block.block.user=Заблокувати кориÑтувача @@ -610,10 +684,15 @@ block.unblock.failure=Ðе вдалоÑÑ Ñ€Ð¾Ð·Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ñ‚Ð¸ кориÑÑ‚Ñ block.blocked=Ви заблокували цього кориÑтувача. block.title=Заблокувати кориÑтувача block.info=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача не дозволÑÑ” йому взаємодіÑти зі Ñховищами, наприклад, відкривати або коментувати запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð°Ð±Ð¾ задачі. ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ про Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача. +block.info_1=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ ÐºÐ¾Ñ€Ð¸Ñтувача запобігає наÑтупним діÑм у вашому обліковому запиÑÑ– та ваших Ñховищах: block.info_2=Ñлідкують за вашим обліковим запиÑом block.info_3=надÑилати вам ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ @згадавши ваше ім'Ñ +block.info_6=Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° ÐºÐ¾Ð¼ÐµÐ½Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡ або запитів на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +block.user_to_block=Блокувати КориÑтувача block.note=Примітка block.note.title=Ðеобов’Ñзкова примітка: +block.note.info=Ðотатка не видима Ð´Ð»Ñ Ð·Ð°Ð±Ð»Ð¾ÐºÐ¾Ð²Ð°Ð½Ð¾Ð³Ð¾ кориÑтувача. +block.note.edit=Редагувати нотатку block.list=Заблоковані кориÑтувачі block.list.none=Ви не заблокували жодного кориÑтувача. @@ -633,28 +712,42 @@ delete=Видалити обліковий Ð·Ð°Ð¿Ð¸Ñ twofa=Двофакторна Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ (TOTP) account_link=Прив'Ñзані облікові запиÑи organization=Організації +uid=UID +webauthn=Двофакторна Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ (ключі безпеки) public_profile=ЗагальнодоÑтупний профіль +biography_placeholder=Розкажіть нам трохи про Ñебе! (Ви можете викориÑтовувати Markdown) +location_placeholder=ДілитиÑÑ Ñвоїм приблизним географічним положеннÑм з іншими +profile_desc=Керуйте тим, Ñк ваш профіль буде показано іншим кориÑтувачам. Ваша оÑновна адреÑа електронної пошти викориÑтовуватиметьÑÑ Ð´Ð»Ñ Ñповіщень, Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ Ñ‚Ð° веб-операцій Git. +password_username_disabled=Вам не дозволено змінювати ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача. Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором Ñайту Ð´Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð±Ñ–Ð»ÑŒÑˆ докладної інформації. +password_full_name_disabled=Вам не дозволено змінювати ваше ім'Ñ. Будь лаÑка, зв'ÑжітьÑÑ Ð· адмініÑтратором Ñайту Ð´Ð»Ñ Ð±Ñ–Ð»ÑŒÑˆ докладної інформації. full_name=Повне ім'Ñ website=Веб-Ñайт location=МіÑÑ†ÐµÐ·Ð½Ð°Ñ…Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ update_theme=Оновити тему update_profile=Оновити профіль update_language=Оновити мову +update_language_not_found=Мова "%s" недоÑтупна. update_language_success=Мову оновлено. update_profile_success=Профіль уÑпішно оновлено. change_username=Ваше ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача змінено. +change_username_prompt=Примітка: Зміна імені кориÑтувача також змінює URL-адреÑу облікового запиÑу. +change_username_redirect_prompt=Старе ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача буде перенаправлÑтиÑÑ Ð½Ð° нове, поки хтоÑÑŒ не викориÑтає його. continue=Продовжити cancel=Відмінити language=Мова ui=Тема hidden_comment_types=Приховані типи коментарів +hidden_comment_types_description=Позначені тут типи коментарів не будуть показані на Ñторінках проблем. Ðаприклад, позначка «Мітка» вилучає вÑÑ– коментарі «{user} додав/вилучив {label}». +hidden_comment_types.ref_tooltip=Коментарі, де на цю задачу було зроблено поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð· іншого випуÑку/коміту/… +hidden_comment_types.issue_ref_tooltip=Коментарі, в Ñких кориÑтувач змінює гілку/тег, пов'Ñзані з нею comment_type_group_reference=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ comment_type_group_label=Мітка comment_type_group_milestone=Етап comment_type_group_assignee=Виконавець comment_type_group_title=Заголовок comment_type_group_branch=Гілка +comment_type_group_time_tracking=Облік чаÑу comment_type_group_deadline=Крайній Ñтрок comment_type_group_dependency=ЗалежніÑть comment_type_group_lock=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ @@ -691,13 +784,17 @@ emails=ÐдреÑа електронної пошти manage_emails=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð´Ñ€ÐµÑами електронної пошти manage_themes=Обрати типову тему manage_openid=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð´Ñ€ÐµÑами OpenID +email_desc=Ваша оÑновна адреÑа електронної пошти викориÑтовуватиметьÑÑ Ð´Ð»Ñ Ñповіщень, Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð°Ñ€Ð¾Ð»Ñ Ñ–, за умови, що вона не прихована, Ð´Ð»Ñ Ð²ÐµÐ±-операцій з Git. theme_desc=Ð¦Ñ Ñ‚ÐµÐ¼Ð° буде типовою Ð´Ð»Ñ Ð²Ñього Ñайту. +theme_colorblindness_help=Підтримка тем колірної Ñліпоти +theme_colorblindness_prompt=Gitea щойно отримала деÑкі теми з базовою підтримкою колірної Ñліпоти, в Ñких визначено лише кілька кольорів. Робота над вÑе ще триває. Ще більше покращень можна зробити, визначивши більше кольорів у CSS-файлах теми. primary=ОÑновний activated=Ðктивовано requires_activation=Потрібна Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ primary_email=Зробити оÑновною activate_email=ÐадіÑлати активацію activations_pending=ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ— +can_not_add_email_activations_pending=ВідбуваєтьÑÑ Ð°ÐºÑ‚Ð¸Ð²Ð°Ñ†Ñ–Ñ, Ñпробуйте ще раз через кілька хвилин, Ñкщо хочете додати нову адреÑу електронної пошти. delete_email=Видалити email_deletion=Видалити адреÑу електронної пошти email_deletion_desc=ÐдреÑа електронної пошти та пов'Ñзана з нею Ñ–Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð±ÑƒÐ´Ðµ видалена з вашого облікового запиÑу. Коміти Git, здійÑнені через цю адреÑу електронну пошту, залишитьÑÑ Ð±ÐµÐ· змін. Продовжити? @@ -711,6 +808,7 @@ add_new_email=Додати нову адреÑу електронної пошт add_new_openid=Додати новий OpenID URI add_email=Додати адреÑу електронної пошти add_openid=Додати OpenID URI +add_email_confirmation_sent=Електронний лиÑÑ‚ із підтвердженнÑм було відправлено на '%s', будь лаÑка, перевірте вашу поштову Ñкриньку протÑгом наÑтупних %s, щоб підтвердити адреÑу. add_email_success=Додано нову адреÑу електронної пошти. email_preference_set_success=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð¾Ñ— пошти уÑпішно вÑтановлено. add_openid_success=Додано нову адреÑу OpenID. @@ -749,10 +847,14 @@ gpg_token_signature=ТекÑтовий (armored) Ð¿Ñ–Ð´Ð¿Ð¸Ñ GPG key_signature_gpg_placeholder=`ПочинаєтьÑÑ Ð· "-----BEGIN PGP SIGNATURE-----"` verify_gpg_key_success=Ключ GPG '%s' перевірено. ssh_key_verified=Перевірений ключ +ssh_key_verified_long=Ключ було перевірено за допомогою токена. Його можна викориÑтовувати Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ комітів, що відповідають будь-Ñким активованим адреÑам електронної пошти цього кориÑтувача. ssh_key_verify=Перевірити +ssh_invalid_token_signature=Ключ SSH, Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ñ– токен не збігаютьÑÑ Ð°Ð±Ð¾ токен заÑтарілий. ssh_token_required=Вам потрібно надати Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð´Ð»Ñ Ð½Ð¸Ð¶Ñ‡ÐµÐ²ÐºÐ°Ð·Ð°Ð½Ð¾Ð³Ð¾ токена ssh_token=Токен ssh_token_help=Ви можете Ñтворити Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð·Ð° допомогою: +ssh_token_signature=ТекÑтовий Ð¿Ñ–Ð´Ð¿Ð¸Ñ SSH +key_signature_ssh_placeholder=`ПочинаєтьÑÑ Ð· "-----BEGIN SSH SIGNATURE-----"` verify_ssh_key_success=Ключ SSH '%s' перевірено. subkeys=Підключі key_id=Ідентифікатор ключа @@ -761,6 +863,7 @@ key_content=ЗміÑÑ‚ principal_content=ЗміÑÑ‚ add_key_success=Ключ SSH '%s' додано. add_gpg_key_success=Ключ GPG '%s' додано. +add_principal_success=Було додано SSH Ñертифікат ідентичноÑті '%s'. delete_key=Видалити ssh_key_deletion=Видалити ключ SSH gpg_key_deletion=Видалити ключ GPG @@ -787,7 +890,9 @@ ssh_disabled=SSH вимкнено ssh_signonly=SSH наразі вимкнено, тому ці ключі викориÑтовуютьÑÑ Ð»Ð¸ÑˆÐµ Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ підпиÑу комітів. ssh_externally_managed=Цей ключ SSH керуєтьÑÑ Ð·Ð·Ð¾Ð²Ð½Ñ– Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ кориÑтувача manage_social=Керувати пов'Ñзаними обліковими запиÑами Ñоціальних мереж +social_desc=Ці облікові запиÑи Ñоціальних мереж можна викориÑтовувати Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ в ваш обліковий запиÑ. ПереконайтеÑÑ, що вÑÑ– вони належать вам. unbind=Від'єднати +unbind_success=Соціальний обліковий Ð·Ð°Ð¿Ð¸Ñ ÑƒÑпішно видалено. manage_access_token=Керувати токенами доÑтупу generate_new_token=Створити новий токен @@ -800,15 +905,20 @@ delete_token=Видалити access_token_deletion=Видалити токен доÑтупу access_token_deletion_cancel_action=Відмінити access_token_deletion_confirm_action=Видалити +access_token_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ‚Ð¾ÐºÐµÐ½Ð° призведе до Ð²Ñ–Ð´ÐºÐ»Ð¸ÐºÐ°Ð½Ð½Ñ Ð´Ð¾Ñтупу до вашого облікового запиÑу Ð´Ð»Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÑ–Ð², Ñкі його викориÑтовують. Це неможливо ÑкаÑувати. Продовжити? delete_token_success=Токен знищено. Додатки, що викориÑтовують його, більше не мають доÑтупу до вашого облікового запиÑу. +repo_and_org_access=ДоÑтуп до Ñховища та організації +permissions_public_only=Лише загальнодоÑтупні permissions_access_all=Ð’ÑÑ– (загальнодоÑтупні, приватні та з обмеженим доÑтупом) permission_not_set=Ðе вÑтановлено permission_no_access=Ðемає доÑтупу -permission_read=Прочитані +permission_read=Ð§Ð¸Ñ‚Ð°Ð½Ð½Ñ permission_write=Ð§Ð¸Ñ‚Ð°Ð½Ð½Ñ Ñ– Ð·Ð°Ð¿Ð¸Ñ permission_anonymous_read=Ðнонімне Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ permission_everyone_read=Ð§Ð¸Ñ‚Ð°Ð½Ð½Ñ Ð´Ð»Ñ Ð²ÑÑ–Ñ… permission_everyone_write=Ð—Ð°Ð¿Ð¸Ñ Ð´Ð»Ñ Ð²ÑÑ–Ñ… +access_token_desc=Обрані дозволи токена обмежують авторизацію лише відповідними маршрутами <a %s>API</a>. Читайте <a %s>документацію</a> Ð´Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації. +at_least_one_permission=Ðеобхідно вибрати хоча б одне право доÑтупу Ð´Ð»Ñ ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ‚Ð¾ÐºÐµÐ½Ð° permissions_list=Дозволи: manage_oauth2_applications=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ°Ð¼Ð¸ OAuth2 @@ -822,6 +932,9 @@ create_oauth2_application_button=Створити додаток create_oauth2_application_success=Ви уÑпішно Ñтворили новий додаток OAuth2. update_oauth2_application_success=Ви уÑпішно оновили додаток OAuth2. oauth2_application_name=Ðазва додатка +oauth2_confidential_client=Конфіденційний клієнт. Виберіть Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼, Ñкі зберігають конфіденційніÑть, наприклад, веб-програм. Ðе обирайте Ð´Ð»Ñ Ð½Ð°Ñ‚Ð¸Ð²Ð½Ð¸Ñ… додатків, зокрема ПК та мобільних додатків. +oauth2_skip_secondary_authorization=ПропуÑтити авторизацію Ð´Ð»Ñ Ð¿ÑƒÐ±Ð»Ñ–Ñ‡Ð½Ð¸Ñ… клієнтів піÑÐ»Ñ Ð½Ð°Ð´Ð°Ð½Ð½Ñ Ð´Ð¾Ñтупу один раз. <strong>Може Ñтановити ризик Ð´Ð»Ñ Ð±ÐµÐ·Ð¿ÐµÐºÐ¸.</strong> +oauth2_redirect_uris=URI Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ. Будь лаÑка, викориÑтовуйте новий Ñ€Ñдок Ð´Ð»Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ URI. save_application=Зберегти oauth2_client_id=Ідентифікатор клієнта oauth2_client_secret=Ключ клієнта @@ -829,18 +942,27 @@ oauth2_regenerate_secret=Відновити ключ oauth2_regenerate_secret_hint=Втратили ключ? oauth2_application_edit=Редагувати oauth2_application_create_description=Програми OAuth2 надають вашим Ñтороннім програмам доÑтуп до облікових запиÑів кориÑтувачів у цьому екземплÑрі. +oauth2_application_remove_description=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ OAuth2 не дозволить додатку отримати доÑтуп до авторизованих облікових запиÑів кориÑтувачів на цьому Ñервері. Продовжити? +oauth2_application_locked=Gitea попередньо реєÑтрує деÑкі програми OAuth2 під Ñ‡Ð°Ñ Ð·Ð°Ð¿ÑƒÑку, Ñкщо це ввімкнено в конфігурації. Щоб запобігти неÑподіваній поведінці, Ñ—Ñ… не можна ні редагувати, ні видалÑти. Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації звернітьÑÑ Ð´Ð¾ документації OAuth2. authorized_oauth2_applications=Ðвторизовані програми OAuth2 +authorized_oauth2_applications_description=Ви надали цим Ñтороннім додаткам доÑтуп до Ñвого облікового запиÑу Gitea. СкаÑуйте доÑтуп Ð´Ð»Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÑ–Ð², Ñкі вам більше не потрібні. revoke_key=Відкликати revoke_oauth2_grant=СкаÑувати доÑтуп revoke_oauth2_grant_description=СкаÑÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¾Ñтупу Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñтороннього додатка не дозволить йому отримувати доÑтуп до ваших даних. Ви впевнені? +revoke_oauth2_grant_success=ДоÑтуп уÑпішно ÑкаÑовано. +twofa_desc=Щоб захиÑтити Ñвій обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ñ–Ð´ крадіжки паролÑ, ви можете викориÑтовувати Ñмартфон або інший приÑтрій Ð´Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð¾Ð´Ð½Ð¾Ñ€Ð°Ð·Ð¾Ð²Ð¸Ñ… паролів, прив'Ñзаних до чаÑу (TOTP). +twofa_recovery_tip=Якщо ви втратите Ñвій приÑтрій, ви зможете ÑкориÑтатиÑÑ Ð¾Ð´Ð½Ð¾Ñ€Ð°Ð·Ð¾Ð²Ð¸Ð¼ ключем відновленнÑ, щоб відновити доÑтуп до Ñвого облікового запиÑу. twofa_is_enrolled=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð½Ð°Ñ€Ð°Ð·Ñ– <strong>викориÑтовує</strong> двофакторну автентифікацію. twofa_not_enrolled=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð½Ð°Ñ€Ð°Ð·Ñ– не викориÑтовує двофакторну автентифікацію. twofa_disable=Вимкнути двофакторну автентифікацію +twofa_scratch_token_regenerate=Регенерувати одноразовий ключ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ +twofa_scratch_token_regenerated=Ваш одноразовий ключ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ‚ÐµÐ¿ÐµÑ€ %s. Зберігайте його у безпечному міÑці, оÑкільки його більше не буде показано. twofa_enroll=Увімкнути двофакторну автентифікацію twofa_disable_note=За потреби ви можете вимкнути двофакторну автентифікацію. twofa_disable_desc=Ð’Ð¸Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ð´Ð²Ð¾Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ñ— автентифікації зробить ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð¼ÐµÐ½Ñˆ безпечним. Продовжити? +regenerate_scratch_token_desc=Якщо ви втратили ключ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð°Ð±Ð¾ вже викориÑтовували його Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ, ви можете Ñкинути його тут. twofa_disabled=Двофакторну автентифікацію вимкнено. scan_this_image=ВідÑкануйте це Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ð²Ð°ÑˆÐ¸Ð¼ додатком Ð´Ð»Ñ Ð´Ð²Ð¾Ñ„Ð°ÐºÑ‚Ð¾Ñ€Ð½Ð¾Ñ— автентифікації: or_enter_secret=Ðбо введіть код: %s @@ -848,6 +970,11 @@ then_enter_passcode=І введіть пароль, Ñкий Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð¶Ð°Ñ passcode_invalid=Ðекоректний пароль. Спробуй ще раз. twofa_failed_get_secret=Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ код. +webauthn_register_key=Додати ключ безпеки +webauthn_delete_key=Видалити ключ безпеки +webauthn_delete_key_desc=Якщо ви видалите ключ безпеки, ви більше не зможете ввійти за його допомогою. Продовжити? +webauthn_key_loss_warning=Якщо ви втратите ключі безпеки, ви втратите доÑтуп до Ñвого облікового запиÑу. +webauthn_alternative_tip=Ви можете налаштувати додатковий метод автентифікації. manage_account_links=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸Ð²'Ñзаними обліковими запиÑами manage_account_links_desc=Ці зовнішні облікові запиÑи прив'Ñзані до вашого облікового запиÑу Gitea. @@ -857,8 +984,10 @@ remove_account_link=Видалити обліковий Ð·Ð°Ð¿Ð¸Ñ remove_account_link_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾Ð²'Ñзаного облікового запиÑу відкликає його доÑтуп до вашого облікового запиÑу Gitea. Продовжити? remove_account_link_success=Прив'Ñзаний обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð¾. +hooks.desc=Додайте веб-хуки, Ñкі запуÑкатимутьÑÑ Ð´Ð»Ñ <strong>уÑÑ–Ñ… репозиторіїв</strong>, Ñкими ви володієте. orgs_none=Ви не Ñ” членом організації. +repos_none=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” Ñховищ. delete_account=Видалити обліковий Ð·Ð°Ð¿Ð¸Ñ delete_prompt=Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¾Ñтаточно видалить ваш обліковий запиÑ. Її <strong>ÐЕ МОЖЛИВО</strong> ÑкаÑувати. @@ -876,12 +1005,17 @@ email_notifications.andyourown=І ваші влаÑні Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ visibility=ВидиміÑть кориÑтувача visibility.public=Публічний visibility.limited=Обмежений +visibility.limited_tooltip=ДоÑтупно лише Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð¸Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ… кориÑтувачів visibility.private=Приватний +visibility.private_tooltip=ДоÑтупно лише Ð´Ð»Ñ Ñ‡Ð»ÐµÐ½Ñ–Ð² організацій, до Ñких ви долучилиÑÑ [repo] +new_repo_helper=Сховище міÑтить уÑÑ– файли проєкту, включно з Ñ–Ñторією ревізій. Ви вже розміщуєте його деінде? <a href="%s">ПеренеÑти Ñховище.</a> owner=ВлаÑник owner_helper=ДеÑкі організації можуть не відображатиÑÑ Ñƒ ÑпиÑку через Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ Ð½Ð° макÑимальну кількіÑть Ñховищ. repo_name=Ðазва Ñховища +repo_name_profile_public_hint=.profile - це Ñпеціальне Ñховище, за допомогою Ñкого ви можете додати README.md до профілю вашої публічної організації, Ñкий буде видимим Ð´Ð»Ñ Ð²ÑÑ–Ñ…. ПереконайтеÑÑ, що він Ñ” публічним, та ініціалізуйте його за допомогою README у каталозі профілю. +repo_name_helper=У хороших назвах Ñховищ викориÑтовуютьÑÑ ÐºÐ¾Ñ€Ð¾Ñ‚ÐºÑ– ключові Ñлова, Ñкі легко запам'ÑтовуютьÑÑ Ñ‚Ð° Ñ” унікальними. Сховище з назвою «.profile» або «.profile-private» можна викориÑтовувати Ð´Ð»Ñ Ð´Ð¾Ð´Ð°Ð²Ð°Ð½Ð½Ñ README.md Ð´Ð»Ñ Ð¿Ñ€Ð¾Ñ„Ñ–Ð»ÑŽ кориÑтувача/організації. repo_size=Розмір Ñховища template=Шаблон template_select=Обрати шаблон. @@ -898,6 +1032,7 @@ fork_from=Форк з fork_visibility_helper=Ðеможливо змінити видиміÑть розгалуженого Ñховища. all_branches=УÑÑ– гілки view_all_branches=ПереглÑнути вÑÑ– гілки +view_all_tags=ПереглÑнути вÑÑ– мітки use_template=ЗаÑтоÑувати цей шаблон open_with_editor=Відкрити в %s download_zip=Завантажити ZIP @@ -918,6 +1053,7 @@ license_helper=Обрати файл ліцензії. license_helper_desc=Ð›Ñ–Ñ†ÐµÐ½Ð·Ñ–Ñ Ð²Ð¸Ð·Ð½Ð°Ñ‡Ð°Ñ”, що інші можуть робити з вашим кодом, а що ні. Ðе впевнені, Ñка підходить Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ проєкту? ДивітьÑÑ <a target="_blank" rel="noopener noreferrer" href="%s">Вибір ліцензії.</a> multiple_licenses=Кілька ліцензій object_format=Формат об'єкту +object_format_helper=Формат об'єкту Ñховища. Ðеможливо буде змінити пізніше. SHA1 Ñ” найбільш ÑуміÑним. readme=README readme_helper=Виберіть шаблон README. readme_helper_desc=Тут ви можете повніÑтю опиÑати ваш проєкт. @@ -933,11 +1069,14 @@ default_branch_label=типово default_branch_helper=Типова гілка Ñ” базовою гілкою Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñ–Ð² на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° комітів. mirror_prune=ОчиÑтити mirror_prune_desc=Видалити заÑтарілі поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° віддалені відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ +mirror_interval=Інтервал Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ (допуÑтимі одиниці виміру чаÑу 'h', 'm', 's'). 0 - щоб вимкнути періодичну Ñинхронізацію. (Мінімальний інтервал: %s) mirror_interval_invalid=Інтервал Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ Ð½ÐµÐ´Ñ–Ð¹Ñний. mirror_sync=Ñинхронізовано mirror_sync_on_commit=Синхронізувати, коли надÑилаютьÑÑ ÐºÐ¾Ð¼Ñ–Ñ‚Ð¸ mirror_address=Клонувати з URL-адреÑи mirror_address_desc=Введіть необхідні облікові дані в розділі ÐвторизаціÑ. +mirror_address_url_invalid=URL-адреÑа недійÑна. Ðеобхідно правильно екранувати вÑÑ– компоненти URL-адреÑи. +mirror_address_protocol_invalid=URL-адреÑа недійÑна. ДопуÑтимі лише адреÑи http(s):// або git://. mirror_lfs=Сховище великих файлів (LFS) mirror_lfs_desc=Ðктивувати Ð´Ð·ÐµÑ€ÐºÐ°Ð»ÑŽÐ²Ð°Ð½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… LFS. mirror_lfs_endpoint=Кінцева точка LFS @@ -964,6 +1103,7 @@ delete_preexisting=Видалити попередньо Ñтворені фай delete_preexisting_content=Видалити файли з %s delete_preexisting_success=Видалено неприйнÑті файли в %s blame_prior=ПереглÑнути анотацію, що передує цій зміні +blame.ignore_revs.failed=Ðе вдалоÑÑ Ð¿Ñ€Ð¾Ñ–Ð³Ð½Ð¾Ñ€ÑƒÐ²Ð°Ñ‚Ð¸ ревізії у <a href="%s">.git-blame-ignore-revs</a>. user_search_tooltip=Показує не більше 30 кориÑтувачів tree_path_not_found=ШлÑÑ… %[1]s не Ñ–Ñнує в %[2]s @@ -972,6 +1112,8 @@ transfer.accept=Дозволити Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ transfer.accept_desc=`ПереміÑтити до "%s"` transfer.reject=Відхилити Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ transfer.reject_desc=`СкаÑувати Ð¿ÐµÑ€ÐµÐ¼Ñ–Ñ‰ÐµÐ½Ð½Ñ Ð´Ð¾ "%s"` +transfer.no_permission_to_accept=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” дозволу приймати цю передачу. +transfer.no_permission_to_reject=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” дозволу на Ð²Ñ–Ð´Ñ…Ð¸Ð»ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— передачі. desc.private=Приватний desc.public=Публічний @@ -984,6 +1126,7 @@ desc.sha256=SHA256 template.items=Елементи шаблону template.git_content=ВміÑÑ‚ Git (типова гілка) template.git_hooks=Хуки Git +template.git_hooks_tooltip=Ðаразі ви не можете змінювати або видалÑти Git-хуки піÑÐ»Ñ Ñ—Ñ… додаваннÑ. Виберіть це лише Ñкщо ви довірÑєте Ñховищу шаблонів. template.webhooks=Веб-хуки template.topics=Теми template.avatar=Ðватар @@ -991,11 +1134,15 @@ template.issue_labels=Мітки задачі template.one_item=Слід обрати хоча б один елемент шаблону template.invalid=Слід обрати шаблонне Ñховище +archive.title=Це архівне Ñховище. Ви можете переглÑдати й клонувати файли, але не можете завантажувати Ñвої зміни або відкривати задачі чи запити на злиттÑ. +archive.title_date=Це архівне Ñховище на %s. Ви можете переглÑдати файли й клонувати його, але не можете завантажувати Ñвої зміни або відкривати задачі чи запити на злиттÑ. archive.issue.nocomment=Це Ñховище архівовано. Ви не можете коментувати задачі. archive.pull.nocomment=Це Ñховище архівовано. Ви не можете коментувати запити на злиттÑ. form.reach_limit_of_creation_1=Ви доÑÑгли макÑимальної кількоÑті %d Ñховища. form.reach_limit_of_creation_n=Ви доÑÑгли макÑимальної кількоÑті %d Ñховищ. +form.name_reserved=Ðазву Ñховища '%s' зарезервовано. +form.name_pattern_not_allowed=Шаблон '%s' не дозволено в назві Ñховища. need_auth=ÐÐ²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ñ–Ñ migrate_options=Параметри міграції @@ -1016,9 +1163,11 @@ migrate_items_releases=Релізи migrate_repo=ПеренеÑти репозиторій migrate.clone_address=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ / клонувати з URL-адреÑи migrate.clone_address_desc=URL-адреÑа HTTP(S) або Git "clone" Ñ–Ñнуючого Ñховища +migrate.github_token_desc=Ви можете додати один або декілька токенів через кому, щоб пришвидшити міграцію через Ð¾Ð±Ð¼ÐµÐ¶ÐµÐ½Ð½Ñ ÑˆÐ²Ð¸Ð´ÐºÐ¾Ñті API GitHub. ПОПЕРЕДЖЕÐÐЯ: Ð—Ð»Ð¾Ð²Ð¶Ð¸Ð²Ð°Ð½Ð½Ñ Ñ†Ñ–Ñ”ÑŽ функцією може порушити політику поÑтачальника поÑлуг Ñ– призвеÑти до Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ð±Ð»Ñ–ÐºÐ¾Ð²Ð¾Ð³Ð¾ запиÑу. migrate.clone_local_path=або шлÑÑ… до локального Ñерверу migrate.permission_denied=Вам не дозволено імпортувати локальні репозиторії. migrate.permission_denied_blocked=Ви не можете імпортувати з заборонених вузлів, будь лаÑка, попроÑіть адмініÑтратора перевірити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ ALLOWED_DOMAINS/ALLOW_LOCALNETWORKS/BLOCKED_DOMAINS. +migrate.invalid_local_path=Локальний шлÑÑ… недійÑний. Він не Ñ–Ñнує або не Ñ” каталогом. migrate.invalid_lfs_endpoint=Кінцева точка LFS недійÑна. migrate.failed=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð½Ðµ вдалаÑÑ: %v migrate.migrate_items_options=Ð”Ð»Ñ Ð¿ÐµÑ€ÐµÐ½ÐµÑÐµÐ½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¸Ñ… елементів потрібен токен доÑтупу @@ -1029,6 +1178,7 @@ migrate.migrating=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· <b>%s</b>... migrate.migrating_failed=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ñ–Ð· <b>%s</b> не вдалаÑÑ. migrate.migrating_failed.error=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ½ÐµÑти: %s migrate.migrating_failed_no_addr=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ð½Ðµ вдалаÑÑ. +migrate.github.description=ПеренеÑти дані з github.com чи інших Ñерверів Github. migrate.git.description=ПеренеÑти Ñховище з будь-Ñкого ÑервіÑу Git'у. migrate.gitlab.description=ПеренеÑти дані з gitlab.com та інших екземплÑрів GitLab. migrate.gitea.description=ПеренеÑти дані з gitea.com та інших екземплÑрів Gitea. @@ -1036,6 +1186,10 @@ migrate.gogs.description=ПеренеÑти дані з notabug.org та іншРmigrate.onedev.description=ПеренеÑти дані з code.onedev.io та інших екземплÑрів OneDev. migrate.codebase.description=ПеренеÑти дані з codebasehq.com. migrate.gitbucket.description=ПеренеÑти дані з екземплÑрів GitBucket. +migrate.codecommit.description=ПеренеÑÐµÐ½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… з AWS CodeCommit. +migrate.codecommit.aws_access_key_id=ID ключа доÑтупу AWS +migrate.codecommit.https_git_credentials_username=Ð†Ð¼â€™Ñ ÐºÐ¾Ñ€Ð¸Ñтувача HTTPS Git +migrate.codecommit.https_git_credentials_password=Пароль кориÑтувача HTTPS Git migrate.migrating_git=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Git даних migrate.migrating_topics=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ Ñ‚ÐµÐ¼ migrate.migrating_milestones=ÐœÑ–Ð³Ñ€Ð°Ñ†Ñ–Ñ ÐµÑ‚Ð°Ð¿Ñ–Ð² @@ -1061,6 +1215,7 @@ star=Ð’ обрані fork=Форк action.blocked_user=Ðеможливо виконати дію, оÑкільки ви заблоковані влаÑником Ñховища. download_archive=Скачати репозиторій +more_operations=Інші операції quick_guide=Короткий поÑібник clone_this_repo=Кнонувати цей репозиторій @@ -1068,6 +1223,8 @@ cite_this_repo=ПоÑлатиÑÑ Ð½Ð° це Ñховище create_new_repo_command=Створити новий репозиторій з командного Ñ€Ñдка push_exist_repo=Опублікувати Ñ–Ñнуючий репозиторій з командного Ñ€Ñдка empty_message=Це Ñховище порожнє. +broken_message=Ðеможливо прочитати дані Git, що лежать в оÑнові цього Ñховища. ЗвернітьÑÑ Ð´Ð¾ адмініÑтратора Ñервера або видаліть Ñховище. +no_branch=Це Ñховище не має гілок. code=Код code.desc=ДоÑтуп до коду, файлів, комітів та гілок. @@ -1081,6 +1238,7 @@ tags=Теги issues=Задачі pulls=Запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ projects=Проєкти +packages=Пакети actions=Дії labels=Мітки org_labels_desc=Мітки Ñ€Ñ–Ð²Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ— можуть викориÑтовуватиÑÑ <strong>в уÑÑ–Ñ… репозиторіÑÑ…</strong> цієї організації @@ -1094,6 +1252,7 @@ release=Реліз releases=Релізи tag=Тег released_this=випуÑтив(-ла) +file.title=%s в %s file_raw=Ðеформатований file_history=ІÑÑ‚Ð¾Ñ€Ñ–Ñ file_view_source=ПереглÑнути вихідний код @@ -1104,8 +1263,12 @@ file_is_empty=Файл порожній. code_preview_line_from_to=Ð Ñдки від %[1]d до %[2]d в %[3]s code_preview_line_in=Ð Ñдок %[1]d в %[2]s invisible_runes_header=`Цей файл міÑтить невидимі Ñимволи Юнікоду` +invisible_runes_description=`Цей файл міÑтить невидимі Ñимволи Юнікоду, Ñкі не розрізнÑютьÑÑ Ð»ÑŽÐ´Ð¸Ð½Ð¾ÑŽ, але можуть по-різному оброблÑтиÑÑ ÐºÐ¾Ð¼Ð¿'ютером. Якщо ви вважаєте, що це зроблено навмиÑно, можете Ñміливо ігнорувати це попередженнÑ. Щоб показати Ñ—Ñ…, ÑкориÑтайтеÑÑ ÐºÐ½Ð¾Ð¿ÐºÐ¾ÑŽ Escape.` ambiguous_runes_header=`Цей файл міÑтить неоднозначні Ñимволи Юнікоду` +ambiguous_runes_description=`Цей файл міÑтить Ñимволи Юнікоду, Ñкі можна Ñплутати з іншими Ñимволами. Якщо ви вважаєте, що це зроблено навмиÑно, можете Ñміливо ігнорувати це попередженнÑ. Щоб показати Ñ—Ñ…, ÑкориÑтайтеÑÑ ÐºÐ½Ð¾Ð¿ÐºÐ¾ÑŽ Escape.` invisible_runes_line=`Цей Ñ€Ñдок міÑтить невидимі Ñимволи Юнікоду` +ambiguous_runes_line=`Цей Ñ€Ñдок міÑтить неоднозначні Ñимволи Юнікоду` +ambiguous_character=`%[1]c [U+%04[1]X] можна Ñплутати з %[2]c [U+%04[2]X]` escape_control_characters=Екранувати unescape_control_characters=Відмінити ÐµÐºÑ€Ð°Ð½ÑƒÐ²Ð°Ð½Ð½Ñ @@ -1121,6 +1284,8 @@ commit_graph.hide_pr_refs=Приховати запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ commit_graph.monochrome=Монохромний commit_graph.color=Колір commit.contained_in=Цей коміт міÑтитьÑÑ Ð²: +commit.contained_in_default_branch=Цей коміт Ñ” чаÑтиною типової гілки +commit.load_referencing_branches_and_tags=Завантажити гілки та мітки, Ñкі поÑилаютьÑÑ Ð½Ð° цей коміт blame=ÐÐ½Ð¾Ñ‚Ð°Ñ†Ñ–Ñ download_file=Завантажити файл normal_view=Звичайний виглÑд @@ -1134,7 +1299,9 @@ editor.upload_file=Завантажити файл editor.edit_file=Редагувати файл editor.preview_changes=Попередній переглÑд змін editor.cannot_edit_lfs_files=Файли LFS не можна редагувати в веб-інтерфейÑÑ–. +editor.cannot_edit_too_large_file=Файл занадто великий Ð´Ð»Ñ Ñ€ÐµÐ´Ð°Ð³ÑƒÐ²Ð°Ð½Ð½Ñ. editor.cannot_edit_non_text_files=Бінарні файли не можливо редагувати у веб-інтерфейÑÑ–. +editor.file_not_editable_hint=Ðле ви вÑе ще можете перейменувати або переміÑтити його. editor.edit_this_file=Редагувати файл editor.this_file_locked=Файл заблоковано editor.must_be_on_a_branch=Ви повинні бути у гілці щоб робити або пропонувати зміни до цього файлу. @@ -1147,20 +1314,30 @@ editor.filename_help=Щоб додати каталог, наберіть йог editor.or=або editor.cancel_lower=СкаÑувати editor.commit_signed_changes=ВнеÑти підпиÑані зміни +editor.commit_changes=ЗафікÑувати зміни editor.add_tmpl=Додати '{filename}' editor.add=Додати %s editor.update=Оновити %s editor.delete=Видалити %s +editor.patch=ЗаÑтоÑувати патч +editor.patching=ЗаÑтоÑÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¸Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½ÑŒ: +editor.fail_to_apply_patch=`Ðе вдалоÑÑ Ð·Ð°ÑтоÑувати патч "%s"` +editor.new_patch=Ðовий патч editor.commit_message_desc=Додати необов'Ñзковий розширений опиÑ… editor.signoff_desc=Додати «ПідпиÑано комітером» в кінці Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ. editor.commit_directly_to_this_branch=Зробити коміт безпоÑередньо в гілку <strong class="branch-name">%s</strong>. editor.create_new_branch=Створити <strong>нову гілку</strong> Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ коміту та відкрити запит на злиттÑ. editor.create_new_branch_np=Створити <strong>нову гілку</strong> Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ коміту. editor.propose_file_change=Запропонувати зміну файлу +editor.new_branch_name=Ðазвіть нову гілку Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ коміту editor.new_branch_name_desc=Ðазва нової гілки… editor.cancel=Відмінити editor.filename_cannot_be_empty=Ðазва файлу не може бути порожньою. +editor.filename_is_invalid=Ðазва файлу недійÑна: "%s". editor.invalid_commit_email=ÐдреÑа електронної пошти Ð´Ð»Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ недійÑна. +editor.file_is_a_symlink=`"%s" - це Ñимволічне поÑиланнÑ. Символічні поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ðµ можна редагувати у веб-редакторі` +editor.filename_is_a_directory=Ðазва файлу '%s' вже викориÑтовуєтьÑÑ Ñк назва каталогу у цьому Ñховищі. +editor.file_deleting_no_longer_exists=Видалений файл '%s' більше не Ñ–Ñнує в цьому Ñховищі. editor.file_changed_while_editing=ЗміÑÑ‚ файлу змінивÑÑ Ð· моменту початку редагуваннÑ. <a target="_blank" rel="noopener" href="%s"> ÐатиÑніть тут </a>, щоб переглÑнути що було змінено, або <strong>закомітьте зміни ще раз</strong>, щоб перепиÑати Ñ—Ñ…. editor.commit_empty_file_header=Закомітити порожній файл editor.commit_empty_file_text=Файл, Ñкий ви збираєтеÑÑ Ð·Ð°ÐºÐ¾Ð¼Ñ–Ñ‚Ð¸Ñ‚Ð¸, порожній. Продовжувати? @@ -1171,6 +1348,7 @@ editor.push_rejected_no_message=Зміну відхилено Ñервером Ð editor.push_rejected=Зміну відхилено Ñервером. Будь лаÑка, перевірте Git-хуки. editor.push_rejected_summary=Повне Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¿Ñ€Ð¾ відмову: editor.add_subdir=Додати каталог… +editor.unable_to_upload_files=Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ файли до '"%s". Помилка: %v editor.upload_file_is_locked=Файл "%s" заблоковано %s. editor.upload_files_to_dir=`Завантажити файли до "%s"` editor.no_commit_to_branch=Ðе вдалоÑÑ Ð²Ð½ÐµÑти коміт безпоÑередньо до гілки, тому що: @@ -1193,6 +1371,8 @@ commits.gpg_key_id=Ідентифікатор GPG ключа commits.ssh_key_fingerprint=Відбиток ключа SSH commits.view_file_diff=ПереглÑнути зміни до цього файлу в цьому коміті +commit.revert=Повернути до попереднього Ñтану +commit.revert-header=Повернути: %s commit.revert-content=Виберіть гілку, до Ñкої хочете повернутиÑÑ: commitstatus.error=Помилка @@ -1228,10 +1408,12 @@ projects.column.new_title=Ðазва projects.column.new_submit=Створити Ñтовпець projects.column.new=Ðовий Ñтовпець projects.column.set_default=Ð’Ñтановити типово +projects.column.set_default_desc=Ð’Ñтановіть цей Ñтовпець типовим Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡ Ñ– запитів на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð±ÐµÐ· категорії projects.column.delete=Видалити Ñтовпець projects.column.color=Колір projects.open=Відкрити projects.close=Закрити +projects.column.assigned_to=Призначено projects.card_type.desc=Попередні переглÑди картки projects.card_type.images_and_text=Ð—Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ Ñ– текÑÑ‚ projects.card_type.text_only=Лише текÑÑ‚ @@ -1262,6 +1444,7 @@ issues.new.assignees=Виконавці issues.new.clear_assignees=Прибрати виконавців issues.new.no_assignees=Ðемає виконавців issues.new.no_reviewers=Ðемає рецензентів +issues.new.blocked_user=Ðе вдалоÑÑ Ñтворити задачу, тому що ви заблоковані влаÑником Ñховища. issues.choose.get_started=Розпочати issues.choose.open_external_link=Відкрити issues.choose.blank=Типово @@ -1304,6 +1487,7 @@ issues.delete_branch_at=`видалена гілка <b>%s</b> %s` issues.filter_label=Мітка issues.filter_label_exclude=`ВикориÑтовуйте <code>Alt</code> + <code>клік/Enter</code> Ð´Ð»Ñ Ð²Ð¸ÐºÐ»ÑŽÑ‡ÐµÐ½Ð½Ñ Ð¼Ñ–Ñ‚Ð¾Ðº` issues.filter_label_no_select=Ð’ÑÑ– мітки +issues.filter_label_select_no_label=Без мітки issues.filter_milestone=Етап issues.filter_milestone_all=Ð’ÑÑ– етапи issues.filter_milestone_none=Етапи відÑутні @@ -1358,21 +1542,33 @@ issues.context.quote_reply=Цитувати відповідь issues.context.reference_issue=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð² новій задачі issues.context.edit=Редагувати issues.context.delete=Видалити +issues.comment_manually_pull_merged_at=вручну об'єднав(-ла) коміти %[1]s в %[2]s %[3]s +issues.close_comment_issue=Закрити з коментарем issues.reopen_issue=Відкрити знову issues.reopen_comment_issue=Повторно відкрити з коментарем issues.create_comment=Коментар +issues.comment.blocked_user=Ðеможливо Ñтворити або редагувати коментар, тому що ви заблоковані автором або влаÑником Ñховища. issues.closed_at=`закрив(ла) цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.reopened_at=`повторно відкрив(ла) цю задачу <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.commit_ref_at=`згадано цю задачу в коміті <a id="%[1]s" href="#%[1]s">%[2]s</a>` +issues.ref_issue_from=`<a href="%[3]s"> вказав(ла) на цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_pull_from=`<a href="%[3]s">поÑлавÑÑ Ð½Ð° цей запит Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_closing_from=`<a href="%[3]s">згадав запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[4]s, Ñкі закриють цю задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` +issues.ref_reopening_from=`<a href="%[3]s">згадав запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ %[4]s, Ñкий знову відкриє цю задачу</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_closed_from=`<a href="%[3]s">закрив цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_reopened_from=`<a href="%[3]s">повторно відкрито цю задачу %[4]s</a> <a id="%[1]s" href="#%[1]s">%[2]s</a>` issues.ref_from=`із %[1]s` issues.author=Ðвтор issues.author_helper=Цей кориÑтувач Ñ” автором. issues.role.owner=ВлаÑник +issues.role.owner_helper=Цей кориÑтувач — влаÑник Ñховища. issues.role.member=УчаÑник +issues.role.member_helper=Цей кориÑтувач Ñ” учаÑником організації, Ñкій належить це Ñховище. +issues.role.collaborator=Співавтор +issues.role.collaborator_helper=Цей кориÑтувач був запрошений до Ñпівпраці у Ñховищі. +issues.role.first_time_contributor=УчаÑник, Ñкий вперше долучивÑÑ +issues.role.first_time_contributor_helper=Це перший внеÑок цього кориÑтувача в Ñховищі. +issues.role.contributor_helper=Цей кориÑтувач раніше вже вноÑив зміни до Ñховища. issues.re_request_review=Повторно попроÑити рецензію issues.is_stale=З чаÑу оÑтанньої перевірки в цей PR було внеÑено деÑкі зміни issues.remove_request_review=Видалити запит Ñ€ÐµÑ†ÐµÐ½Ð·ÑƒÐ²Ð°Ð½Ð½Ñ @@ -1386,6 +1582,10 @@ issues.save=Зберегти issues.label_title=Ðазва мітки issues.label_description=ÐžÐ¿Ð¸Ñ Ð¼Ñ–Ñ‚ÐºÐ¸ issues.label_color=Колір +issues.label_color_invalid=ÐедійÑний колір +issues.label_archive=Мітка архіву +issues.label_archived_filter=Показати архівовані мітки +issues.label_archive_tooltip=Ðрхівовані мітки типово виключаютьÑÑ Ð· пропозицій під Ñ‡Ð°Ñ Ð¿Ð¾ÑˆÑƒÐºÑƒ за мітками. issues.label_exclusive_order=ПорÑдок ÑÐ¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ issues.label_count=%d міток issues.label_open_issues=%d відкритих задач @@ -1406,6 +1606,8 @@ issues.subscribe=ПідпиÑатиÑÑ issues.unsubscribe=ВідпиÑатиÑÑ issues.unpin=Відкріпити issues.max_pinned=Ви не можете прикріпити більше задач +issues.pin_comment=прикріпив(-ла) %s +issues.unpin_comment=відкріпив(-ла) %s issues.lock=Блокувати Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ issues.unlock=Розблокувати Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ issues.lock_duplicate=ÐžÐ±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ– не може бути заблоковано двічі. @@ -1425,6 +1627,8 @@ issues.lock.title=Заблокувати Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— задРissues.unlock.title=Розблокувати Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ñ†Ñ–Ñ”Ñ— задачі. issues.comment_on_locked=Ви не можете коментувати заблоковану задачу. issues.delete=Видалити +issues.delete.title=Видалити цю задачу? +issues.delete.text=Ви дійÑно хочете видалити цю задачу? (Це видалить веÑÑŒ вміÑÑ‚. ÐатоміÑть подумайте про те, щоб закрити Ñ—Ñ— та зберегти в архіві) issues.tracker=ВідÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу issues.timetracker_timer_start=ЗапуÑтити таймер @@ -1432,11 +1636,19 @@ issues.timetracker_timer_stop=Зупинити таймер issues.timetracker_timer_discard=Скинути таймер issues.timetracker_timer_manually_add=Додати Ñ‡Ð°Ñ +issues.time_estimate_set=Ð’Ñтановити орієнтовний Ñ‡Ð°Ñ +issues.time_estimate_display=Оцінка: %s +issues.change_time_estimate_at=змінено приблизний Ñ‡Ð°Ñ Ð½Ð° <b>%[1]s</b> %[2]s +issues.remove_time_estimate_at=видалено оцінку чаÑу %s +issues.time_estimate_invalid=Ðевірний формат розрахунку чаÑу issues.tracker_auto_close=Таймер буде автоматично зупинено, коли Ñ†Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° буде закрита issues.tracking_already_started=`Ви вже почали відÑтежувати Ñ‡Ð°Ñ Ð´Ð»Ñ <a href="%s">іншої задачі</a>!` issues.stop_tracking=Зупинити таймер +issues.stop_tracking_history=працював Ð´Ð»Ñ <b>%[1]s</b> %[2]s issues.cancel_tracking=Скинути +issues.cancel_tracking_history=`ÑкаÑував(-ла) відÑÑ‚ÐµÐ¶ÐµÐ½Ð½Ñ Ñ‡Ð°Ñу %s` issues.del_time=Видалити цей журнал чаÑу +issues.add_time_history=додав(ла) витрачений Ñ‡Ð°Ñ <b>%[1]s</b> %[2]s issues.del_time_history=`видалив витрачений Ñ‡Ð°Ñ %s` issues.add_time_manually=Вручну додати Ñ‡Ð°Ñ issues.add_time_hours=Години @@ -1457,8 +1669,10 @@ issues.due_date_form=рррр-мм-дд issues.due_date_form_add=Додати дату Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ issues.due_date_form_edit=Редагувати issues.due_date_form_remove=Видалити +issues.due_date_not_writer=Вам потрібен доÑтуп на Ð·Ð°Ð¿Ð¸Ñ Ð´Ð¾ цього Ñховища, щоб оновити дату Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð·Ð°Ð´Ð°Ñ‡Ñ–. issues.due_date_not_set=Термін Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð½Ðµ вÑтановлений. issues.due_date_added=додав(ла) дату Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ %s %s +issues.due_date_modified=змінив(-ла) термін Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð· %[2]s на %[1]s %[3]s issues.due_date_remove=видалив(ла) дату Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ %s %s issues.due_date_overdue=ПроÑтрочено issues.due_date_invalid=Термін дії не дійÑний або знаходитьÑÑ Ð·Ð° межами діапазону. Будь лаÑка, викориÑтовуйте формат 'рррр-мм-дд'. @@ -1479,6 +1693,7 @@ issues.dependency.issue_closing_blockedby=Ð—Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ñ†Ñ–Ñ”Ñ— задачі issues.dependency.issue_close_blocks=Ð¦Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° блокує Ð·Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð½Ð°Ñтупних задач issues.dependency.pr_close_blocks=Цей запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð±Ð»Ð¾ÐºÑƒÑ” Ð·Ð°ÐºÑ€Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ð»ÐµÐ¶Ð½Ð¸Ñ… задач issues.dependency.issue_close_blocked=Перш ніж закрити це завданнÑ, вам потрібно закрити вÑÑ– завданнÑ, що блокують його. +issues.dependency.issue_batch_close_blocked=Ðеможливо пакетно закрити обрані задачі, оÑкільки задача #%d вÑе ще має відкриті залежноÑті issues.dependency.pr_close_blocked=Вам потрібно закрити вÑÑ– задачі, що блокують цей запит, перед його злиттÑм. issues.dependency.blocks_short=Блоки issues.dependency.blocked_by_short=Залежить від @@ -1520,8 +1735,12 @@ issues.review.hide_resolved=Приховати вирішене issues.review.resolve_conversation=Завершити Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ issues.review.un_resolve_conversation=Поновити Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ issues.review.resolved_by=позначив Ð¾Ð±Ð³Ð¾Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ð¼ -issues.review.commented=Коментар +issues.review.commented=Коментувати issues.review.official=Затверджено +issues.review.requested=ОчікуєтьÑÑ Ñ€Ð¾Ð·Ð³Ð»Ñд +issues.review.rejected=Запит на зміни +issues.review.stale=Оновлено з моменту Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ +issues.review.unofficial=Ðевраховане Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ issues.assignee.error=Додано не вÑÑ–Ñ… виконавців через непередбачену помилку. issues.reference_issue.body=Тіло issues.content_history.deleted=видалено @@ -1537,10 +1756,17 @@ compare.compare_head=порівнÑти pulls.desc=Увімкнути запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ‚Ð° оглÑд коду. pulls.new=Ðовий запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +pulls.new.blocked_user=Ðеможливо Ñтворити запит на злиттÑ, тому що ви заблоковані влаÑником Ñховища. +pulls.new.must_collaborator=Ви повинні бути учаÑником, щоб Ñтворити запит на злиттÑ. +pulls.edit.already_changed=Ðе вдалоÑÑ Ð·Ð±ÐµÑ€ÐµÐ³Ñ‚Ð¸ зміни до запиту на злиттÑ. Схоже, вміÑÑ‚ вже змінено іншим кориÑтувачем. Будь лаÑка, оновіть Ñторінку Ñ– Ñпробуйте редагувати ще раз, щоб уникнути перезапиÑу Ñ—Ñ… змін pulls.view=ПереглÑнути запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ pulls.compare_changes=Ðовий запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +pulls.allow_edits_from_maintainers_desc=КориÑтувачі з доÑтупом на Ð·Ð°Ð¿Ð¸Ñ Ð´Ð¾ базової гілки також можуть завантажувати Ñвої зміни до цієї гілки +pulls.allow_edits_from_maintainers_err=ÐžÐ½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ð½Ðµ вдалоÑÑ pulls.compare_changes_desc=ПорівнÑти дві гілки Ñ– Ñтворити запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð»Ñ Ð·Ð¼Ñ–Ð½. pulls.has_viewed_file=ПереглÑдів +pulls.has_changed_since_last_review=Змінено з моменту вашого оÑтаннього відгуку +pulls.viewed_files_label=ПереглÑнуто %[1]d / %[2]d файлів pulls.expand_files=Розгорнути вÑÑ– файли pulls.collapse_files=Згорнути вÑÑ– файли pulls.compare_base=злити в @@ -1675,9 +1901,18 @@ milestones.filter_sort.most_issues=Ðайбільше задач milestones.filter_sort.least_issues=Ðайменше задач signing.will_sign=Цей коміт буде підпиÑано ключем "%s". +signing.wont_sign.error=Виникла помилка під Ñ‡Ð°Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ можливоÑті підпиÑÐ°Ð½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ. signing.wont_sign.nokey=Ðемає ключа Ð´Ð»Ñ Ð¿Ñ–Ð´Ð¿Ð¸ÑÐ°Ð½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ коміту. signing.wont_sign.never=Коміти ніколи не підпиÑуютьÑÑ. signing.wont_sign.always=Коміти завжди підпиÑуютьÑÑ. +signing.wont_sign.pubkey=Коміт не буде підпиÑано, оÑкільки у Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” публічного ключа, пов'Ñзаного з вашим обліковим запиÑом. +signing.wont_sign.twofa=Ð”Ð»Ñ Ð¿Ñ–Ð´Ð¿Ð¸ÑÐ°Ð½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð² у Ð²Ð°Ñ Ð¼Ð°Ñ” бути увімкнена двофакторна автентифікаціÑ. +signing.wont_sign.parentsigned=Цей коміт не буде підпиÑано, оÑкільки не підпиÑано батьківÑький коміт. +signing.wont_sign.basesigned=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ буде підпиÑане, оÑкільки базовий коміт не підпиÑано. +signing.wont_sign.headsigned=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ буде підпиÑане, оÑкільки не підпиÑано головний коміт. +signing.wont_sign.commitssigned=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ буде підпиÑане, оÑкільки вÑÑ– пов'Ñзані з ним коміти не підпиÑані. +signing.wont_sign.approved=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½Ðµ буде підпиÑане, оÑкільки Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð½Ðµ затверджено. +signing.wont_sign.not_signed_in=Ви не увійшли до ÑиÑтеми. ext_wiki=ДоÑтуп до зовнішньої вікі ext_wiki.desc=ПоÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° зовнішню вікі. @@ -1707,10 +1942,13 @@ wiki.reserved_page=Ðазва Ñторінки вікі "%s" зарезервоРwiki.pages=Сторінки wiki.last_updated=ОÑтанні Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ %s wiki.page_name_desc=Введіть назву вікі-Ñторінки. ДеÑкі із Ñпеціальних імен: 'Home', '_Sidebar' та '_Footer'. +wiki.original_git_entry_tooltip=ПереглÑд оригінального файлу Git заміÑть викориÑÑ‚Ð°Ð½Ð½Ñ Ð´Ñ€ÑƒÐ¶Ð½ÑŒÐ¾Ð³Ð¾ поÑиланнÑ. activity=ÐктивніÑть activity.navbar.pulse=ÐŸÑƒÐ»ÑŒÑ activity.navbar.code_frequency=ЧаÑтота коду +activity.navbar.contributors=Співавтори +activity.navbar.recent_commits=Ðещодавні коміти activity.period.filter_label=Період: activity.period.daily=1 день activity.period.halfweekly=3 дні @@ -1784,6 +2022,8 @@ settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ settings.desc=У налаштуваннÑÑ… ви можете керувати параметрами Ñховища settings.options=Сховище settings.public_access=Публічний доÑтуп +settings.public_access.docs.everyone_read=Ð’ÑÑ– читають: вÑÑ– зареєÑтровані кориÑтувачі можуть читати розділ. Це також дозволÑÑ” кориÑтувачам Ñтворювати нові задачі/запити на злиттÑ. +settings.public_access.docs.everyone_write=Ð’ÑÑ– пишуть: вÑÑ– зареєÑтровані кориÑтувачі можуть вноÑити зміни до розділу. Тільки розділ Wiki підтримує цей дозвіл. settings.collaboration=Співавтори settings.collaboration.admin=ÐдмініÑтратор settings.collaboration.write=Ð—Ð°Ð¿Ð¸Ñ @@ -1794,13 +2034,22 @@ settings.hooks=Веб-хуки settings.githooks=Git хуки settings.basic_settings=Базові Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ settings.mirror_settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð·ÐµÑ€ÐºÐ°Ð»Ð° +settings.mirror_settings.docs=Ðалаштуйте Ñховище на автоматичну Ñинхронізацію комітів, міток Ñ– гілок з іншим Ñховищем. +settings.mirror_settings.docs.disabled_pull_mirror.instructions=Ðалаштуйте ваш проєкт на автоматичне перенеÑÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð², міток Ñ– гілок до іншого Ñховища. ÐžÑ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð·Ð¼Ñ–Ð½ з дзеркал було вимкнено адмініÑтратором вашого Ñайту. +settings.mirror_settings.docs.disabled_push_mirror.instructions=Ðалаштуйте Ñвій проєкт на автоматичне Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñ–Ð², міток Ñ– гілок з іншого Ñховища. +settings.mirror_settings.docs.disabled_push_mirror.pull_mirror_warning=Ðаразі це можна зробити лише в меню «Ðова міграціÑ». Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації звернітьÑÑ Ð´Ð¾: +settings.mirror_settings.docs.no_new_mirrors=Ваше Ñховище віддзеркалює зміни до іншого Ñховища або з нього. Будь лаÑка, майте на увазі, що наразі ви не можете Ñтворювати нові дзеркала. +settings.mirror_settings.docs.can_still_use=Хоча ви не можете змінювати наÑвні дзеркала чи Ñтворювати нові, ви вÑе одно можете викориÑтовувати чинне дзеркало. settings.mirror_settings.docs.doc_link_title=Як віддзеркалити Ñховища? +settings.mirror_settings.docs.doc_link_pull_section=розділ документації "Ð—Ð»Ð¸Ñ‚Ñ‚Ñ Ð· віддаленого Ñховища". +settings.mirror_settings.docs.pulling_remote_title=Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ Ð· віддаленого Ñховища settings.mirror_settings.mirrored_repository=Віддзеркалене Ñховища settings.mirror_settings.direction=ÐапрÑмок settings.mirror_settings.direction.pull=Pull settings.mirror_settings.direction.push=Push settings.mirror_settings.last_update=ОÑтаннє Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ settings.mirror_settings.push_mirror.remote_url=URL віддаленого Ñховища Git +settings.mirror_settings.push_mirror.edit_sync_time=Редагувати інтервал Ñинхронізації дзеркал settings.sync_mirror=Синхронізувати зараз settings.site=Веб-Ñайт @@ -1812,6 +2061,8 @@ settings.branches.add_new_rule=Додати нове правило settings.advanced_settings=Розширені Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ settings.wiki_desc=Увімкнути Вікі Ñховища settings.use_internal_wiki=ВикориÑтовувати вбудовану Вікі +settings.default_wiki_branch_name=Ðазва типової гілки Вікі +settings.failed_to_change_default_wiki_branch=Ðе вдалоÑÑ Ð·Ð¼Ñ–Ð½Ð¸Ñ‚Ð¸ Ñтандартну гілку вікі. settings.use_external_wiki=ВикориÑтовувати зовнішню Вікі settings.external_wiki_url=URL зовнішньої вікі settings.external_wiki_url_error=Ð—Ð¾Ð²Ð½Ñ–ÑˆÐ½Ñ URL-адреÑа Вікі Ñ” недійÑною. @@ -1837,10 +2088,13 @@ settings.pulls.ignore_whitespace=Ігнорувати пробіл у конфл settings.pulls.enable_autodetect_manual_merge=Увімкнути автоматичне Ð²Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñ€ÑƒÑ‡Ð½Ð¾Ð³Ð¾ об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ (Примітка: у деÑких оÑобливих випадках можуть виникати помилкові оцінки) settings.pulls.default_delete_branch_after_merge=ВидалÑти гілку запиту злиттÑ, коли його прийнÑто settings.releases_desc=Увімкнути релізи Ñховища +settings.packages_desc=Увімкнути реєÑтр пакетів Ñховища settings.projects_desc=Увімкнути проєкти +settings.projects_mode_desc=Режим проєктів (Ñкі типи проєктів показувати) settings.projects_mode_repo=Тільки проєкти Ñховища settings.projects_mode_owner=Тільки проєкти кориÑтувачів або організацій settings.projects_mode_all=Ð’ÑÑ– проєкти +settings.actions_desc=Увімкнути дії Ñховища settings.admin_settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð°Ð´Ð¼Ñ–Ð½Ñ–Ñтратора settings.admin_enable_health_check=Увімкнути перевірку Ñтану Ñховища (git fsck) settings.admin_code_indexer=ІндекÑатор коду @@ -1848,15 +2102,15 @@ settings.admin_stats_indexer=ІндекÑатор ÑтатиÑтики коду settings.admin_indexer_commit_sha=ОÑтанній індекÑований SHA settings.admin_indexer_unindexed=Ðе індекÑовано settings.reindex_button=Додати до черги на реіндекÑацію -settings.reindex_requested=Запит на реіндекÑацію +settings.reindex_requested=Запит на переіндекÑацію settings.admin_enable_close_issues_via_commit_in_any_branch=Закрити задачу за допомогою коміта, зробленого не в головній гілці settings.danger_zone=Ðебезпечна зона settings.new_owner_has_same_repo=Ðовий влаÑник вже має Ñховище з такою назвою. Будь лаÑка, виберіть іншу назву. settings.convert=Перетворити на звичайне Ñховище -settings.convert_desc=Ви можете Ñконвертувати це дзеркало у звичайний репозиторій. Це не може бути ÑкаÑовано. +settings.convert_desc=Ви можете перетворити це дзеркало на звичайне Ñховище. Це неможливо ÑкаÑувати. settings.convert_notices_1=Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€Ð¸Ñ‚ÑŒ дзеркало у звичайний репозиторій Ñ– не може бути ÑкаÑована. settings.convert_confirm=Перетворити репозиторій -settings.convert_succeed=Репозиторій уÑпішно перетворений в звичайний. +settings.convert_succeed=Дзеркало було перетворено на звичайне Ñховище. settings.convert_fork=Перетворити на звичайний репозиторій settings.convert_fork_desc=Ви можете перетворити цей форк на звичайний репозиторій. Цю дію неможливо ÑкаÑувати. settings.convert_fork_notices_1=Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¿ÐµÑ€ÐµÑ‚Ð²Ð¾Ñ€Ð¸Ñ‚ÑŒ форк на звичайний репозиторій та не може бути ÑкаÑованою. @@ -1866,89 +2120,91 @@ settings.transfer=Передати новому влаÑнику settings.transfer.rejected=ПеренеÑÐµÐ½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ відхилено. settings.transfer.success=ПеренеÑÐµÐ½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ виконано. settings.transfer_abort=СкаÑувати перенеÑÐµÐ½Ð½Ñ -settings.transfer_abort_invalid=Ви не можете ÑкаÑувати неіÑнуюче перенеÑÐµÐ½Ð½Ñ Ñховища. -settings.transfer_desc=Передати репозиторій кориÑтувачеві або організації, де ви маєте права адмініÑтратора. -settings.transfer_form_title=Введіть ім'Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ñк підтвердженнÑ: -settings.transfer_in_progress=Ð’ даний Ñ‡Ð°Ñ Ð²Ñ–Ð´Ð±ÑƒÐ²Ð°Ñ”Ñ‚ÑŒÑÑ Ð¿ÐµÑ€ÐµÐ½ÐµÑеннÑ. Будь лаÑка, ÑкаÑуйте його, Ñкщо ви бажаєте перенеÑти цей репозиторій іншому кориÑтувачу. -settings.transfer_notices_1=- Ви втратите доÑтуп до репозиторіÑ, Ñкщо ви переведете його окремому кориÑтувачеві. -settings.transfer_notices_2=- Ви збережете доÑтуп, Ñкщо новим влаÑником Ñтане організаціÑ, влаÑником Ñкої ви Ñ”. -settings.transfer_notices_3=- Якщо репозиторій Ñ” приватним Ñ– передаєтьÑÑ Ð¾ÐºÑ€ÐµÐ¼Ð¾Ð¼Ñƒ кориÑтувачеві, Ñ†Ñ Ð´Ñ–Ñ Ð³Ð°Ñ€Ð°Ð½Ñ‚ÑƒÑ”, що кориÑтувач має хоча б дозвіл на Ñ‡Ð¸Ñ‚Ð°Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð°Ñ€Ñ–ÑŽ (Ñ– при необхідноÑті змінює права дозволів). +settings.transfer_abort_invalid=Ви не можете ÑкаÑувати перенеÑÐµÐ½Ð½Ñ Ð½ÐµÑ–Ñнуючого Ñховища. +settings.transfer_abort_success=ПеренеÑÐµÐ½Ð½Ñ Ñховища до %s уÑпішно ÑкаÑовано. +settings.transfer_desc=Передати це Ñховище кориÑтувачеві або організації, Ð´Ð»Ñ Ñкої ви маєте права адмініÑтратора. +settings.transfer_form_title=Введіть назву Ñховища Ð´Ð»Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ: +settings.transfer_in_progress=Ðаразі триває передача. Будь лаÑка, ÑкаÑуйте його, Ñкщо ви хочете передати це Ñховище іншому кориÑтувачеві. +settings.transfer_notices_1=- Ви втратите доÑтуп до Ñховища, Ñкщо передаÑте його окремому кориÑтувачеві. +settings.transfer_notices_2=- Ви збережете доÑтуп до Ñховища, Ñкщо передаÑте його організації, Ñкою ви (Ñпів)володієте. +settings.transfer_notices_3=- Якщо Ñховище Ñ” приватним Ñ– передаєтьÑÑ Ð¾ÐºÑ€ÐµÐ¼Ð¾Ð¼Ñƒ кориÑтувачеві, Ñ†Ñ Ð´Ñ–Ñ Ð³Ð°Ñ€Ð°Ð½Ñ‚ÑƒÑ”, що кориÑтувач має принаймні права на Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ (Ñ– змінює ці права, Ñкщо необхідно). settings.transfer_owner=Ðовий влаÑник settings.transfer_perform=ЗдіÑнити перенеÑÐµÐ½Ð½Ñ -settings.transfer_started=`Цей репозиторій чекає Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð¿ÐµÑ€ÐµÐ½ÐµÑÐµÐ½Ð½Ñ Ð²Ñ–Ð´ "%s"` -settings.transfer_succeed=Репозиторій був перенеÑений. +settings.transfer_started=Це Ñховище було позначено Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ´Ð°Ñ‡Ñ– та очікує на Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ Ð²Ñ–Ð´ «%s» +settings.transfer_succeed=Сховище перенеÑено. settings.signing_settings=Параметри перевірки підпиÑу -settings.trust_model=Модель довіри Ð´Ð»Ñ Ð¿Ñ–Ð´Ð¿Ð¸Ñу -settings.trust_model.default=Модель довіри за замовчуваннÑм -settings.trust_model.default.desc=ВикориÑтовувати модель довіри репозиторію за замовчуваннÑм Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñайту. +settings.trust_model=Модель довіри до підпиÑу +settings.trust_model.default=Типова модель довіри +settings.trust_model.default.desc=ВикориÑтовувати типову модель довіри до Ñховища Ð´Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ Ñайту. settings.trust_model.collaborator=Співавтор settings.trust_model.collaborator.long=Співавтор: підпиÑи довіри від Ñпівавторів -settings.trust_model.collaborator.desc=ДопуÑтимі підпиÑи Ñпівавторів цього репозиторію буде позначано Ñк "довірені" - (Ñкщо вони відповідають комітеру чи ні). Ð’ іншому випадку дійÑні підпиÑи будуть позначені Ñк «ненадійні», Ñкщо Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ñпівпадає з комітером Ñ– «невідповідні», Ñкщо ні. -settings.trust_model.committer=Коммітер -settings.trust_model.committer.long=Коммітер: ДовірÑти підпиÑам Ñкі відповідають комітерам (Так Ñк Ñ– на GitHub, Ñ– змуÑить підпиÑати коміти Gitea в ÑкоÑті коммітера) -settings.trust_model.collaboratorcommitter=Співавтор+Коммітер -settings.trust_model.collaboratorcommitter.long=Співавтор+Коммітер: ДовірÑти підпиÑам від Ñпівавторів, Ñкі відповідають комітеру -settings.trust_model.collaboratorcommitter.desc=ДопуÑтимі підпиÑи Ñпівавторів цього репозиторію будуть позначатиÑÑ Ñк "довірені", Ñкщо вони відповідають комітеру. Ð’ іншому випадку дійÑні підпиÑи будуть позначені Ñк «ненадійні», Ñкщо Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ñпівпадає з комітером Ñ– Ñк «невідповіді» в іншому випадку. Це змуÑить Gitea бути відміченим Ñк комітер піÑÐ»Ñ Ð¿Ñ–Ð´Ð¿Ð¸ÑÐ°Ð½Ð½Ñ Ñ„Ð°ÐºÑ‚Ð¸Ñ‡Ð½Ð¸Ð¼ комітером, позначеним Co-Authored-By: Ñ– Co-Committed-By: прикріпленим до комміту. Типовий ключ Gitea повинен відповідати кориÑтувачу в базі даних. -settings.wiki_delete=Видалити вікі-дані -settings.wiki_delete_desc=Будьте уважні! Як тільки ви видалите Вікі - шлÑху назад не буде. -settings.wiki_delete_notices_1=- Це назавжди знищить Ñ– відключить wiki Ð´Ð»Ñ %s. -settings.confirm_wiki_delete=Видалити Вікі-дані -settings.wiki_deletion_success=Дані wiki були видалені. +settings.trust_model.collaborator.desc=ДійÑні підпиÑи Ñпівавторів цього Ñховища будуть позначені Ñк «довірені» - (незалежно від того, чи збігаютьÑÑ Ð²Ð¾Ð½Ð¸ з підпиÑом комітера чи ні). Ð’ іншому випадку дійÑні підпиÑи будуть позначені Ñк «недійÑні», Ñкщо Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð·Ð±Ñ–Ð³Ð°Ñ”Ñ‚ÑŒÑÑ Ð· комітером Ñ– «невідповідні», Ñкщо ні. +settings.trust_model.committer=Комітер +settings.trust_model.committer.long=Комітер: ДовірÑти підпиÑам, Ñкі відповідають комітерам (це відповідає GitHub Ñ– змуÑить підпиÑані Gitea коміти мати Gitea в ÑкоÑті комітера) +settings.trust_model.collaboratorcommitter=Співавтор+Комітер +settings.trust_model.collaboratorcommitter.long=Співавтор+Комітер: ДовірÑти підпиÑам від Ñпівавторів, Ñкі відповідають комітеру +settings.trust_model.collaboratorcommitter.desc=ДійÑні підпиÑи Ñпівавторів цього Ñховища будуть позначені Ñк «довірені», Ñкщо вони збігаютьÑÑ Ð· комітером. Ð’ іншому випадку, дійÑні підпиÑи будуть позначені Ñк «недійÑні», Ñкщо Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð·Ð±Ñ–Ð³Ð°Ñ”Ñ‚ÑŒÑÑ Ð· комітером, Ñ– «невідповідні» у протилежному випадку. Це призведе до того, що Gitea буде позначено комітером у підпиÑаних комітах, а Ñправжній комітер буде позначений Ñк Co-Author-By: та Co-Committed-By: у трейлері коміта. Типовий ключ Gitea має відповідати кориÑтувачеві у базі даних. +settings.wiki_delete=Видалити дані Вікі +settings.wiki_delete_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… Вікі Ñховища Ñ” оÑтаточним Ñ– не може бути ÑкаÑоване. +settings.wiki_delete_notices_1=- Це назавжди видалить Ñ– вимкне вікі Ñховища Ð´Ð»Ñ %s. +settings.confirm_wiki_delete=Видалити дані Вікі +settings.wiki_deletion_success=Дані Вікі видалено. settings.delete=Видалити цей репозиторій -settings.delete_desc=Будьте уважні! Як тільки ви видалите репозиторій - шлÑху назад не буде. -settings.delete_notices_1=- Цю операцію <strong>ÐЕ МОЖÐÐ</strong> відмінити. -settings.delete_notices_2=- Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð¾Ñтаточно видалить <strong>%s</strong> репозиторій, включаючи код, задачі, коментарі, вікі та Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñпівавторів. +settings.delete_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñховища Ñ” оÑтаточним Ñ– не може бути ÑкаÑоване. +settings.delete_notices_1=- Цю операцію <strong>ÐЕМОЖЛИВО</strong> ÑкаÑувати. +settings.delete_notices_2=- Ð¦Ñ Ð¾Ð¿ÐµÑ€Ð°Ñ†Ñ–Ñ Ð½Ð°Ð·Ð°Ð²Ð¶Ð´Ð¸ видалить Ñховище <strong>%s</strong>, включно з кодом, проблемами, коментарÑми, даними вікі та налаштуваннÑми Ñпівавторів. settings.delete_notices_fork_1=- Ð’ÑÑ– форки Ñтануть незалежними репозиторіÑми піÑÐ»Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ. settings.deletion_success=Репозиторій уÑпішно видалено. -settings.update_settings_success=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–ÑŽ було оновлено. +settings.update_settings_success=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñховища оновлено. +settings.update_settings_no_unit=Сховище повинно дозволÑти хоча б ÑкуÑÑŒ взаємодію. settings.confirm_delete=Видалити репозиторій settings.add_collaborator=Додати Ñпівавтора settings.add_collaborator_success=Додано Ñпівавтора. -settings.add_collaborator_inactive_user=Ðе можливо додати неактивного кориÑтувача ÑкоÑті Ñпівавтора. +settings.add_collaborator_inactive_user=Ðеможливо додати неактивного кориÑтувача Ñк Ñпівавтора. +settings.add_collaborator_owner=Ðеможливо додати влаÑника Ñк Ñпівавтора. settings.add_collaborator_duplicate=Співавтора уже додано до цього репозиторію. settings.delete_collaborator=Видалити settings.collaborator_deletion=Видалити Ñпівавтора -settings.collaborator_deletion_desc=Цей кориÑтувач більше не матиме доÑтупу Ð´Ð»Ñ Ñпільної роботи в цьому репозиторії піÑÐ»Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ. Ви хочете продовжити? -settings.remove_collaborator_success=Співавтор видалений. +settings.collaborator_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñпівавтора призведе до Ð²Ñ–Ð´ÐºÐ»Ð¸ÐºÐ°Ð½Ð½Ñ Ð¹Ð¾Ð³Ð¾ доÑтупу до цього Ñховища. Продовжити? +settings.remove_collaborator_success=Співавтора видалено. settings.org_not_allowed_to_be_collaborator=Організації не можуть бути додані Ñк Ñпівавтори. settings.change_team_access_not_allowed=Зміна доÑтупу команди до репозитарію обмежена влаÑником організації -settings.team_not_in_organization=Команда та репозитарій мають привÑзки до різних організацій +settings.team_not_in_organization=Команда не належить до тієї ж організації, що й Ñховище settings.teams=Команди -settings.add_team=Додати Команду -settings.add_team_duplicate=Команда вже має привÑзку до репозитарію -settings.add_team_success=Команда отримала доÑтуп до репозиторію. -settings.change_team_permission_tip=Дозволи команди вÑтановлюютьÑÑ Ð½Ð° Ñторінці налаштувань команди та не можуть бути заданими Ð´Ð»Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ з репозиторіїв окремо +settings.add_team=Додати команду +settings.add_team_duplicate=Команда вже має Ñховище +settings.add_team_success=Команда тепер має доÑтуп до Ñховища. +settings.change_team_permission_tip=Дозвіл команди вÑтановлюєтьÑÑ Ð½Ð° Ñторінці налаштувань команди Ñ– не може бути змінений Ð´Ð»Ñ ÐºÐ¾Ð¶Ð½Ð¾Ð³Ð¾ Ñховища settings.delete_team_tip=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° має доÑтуп до вÑÑ–Ñ… репозиторіїв та не може бути видалена -settings.remove_team_success=ДоÑтуп команди до репозиторію видалений. +settings.remove_team_success=ДоÑтуп команди до Ñховища видалено. settings.add_webhook=Додати веб-хук -settings.add_webhook.invalid_channel_name=Ðазва каналу Webhook не може бути порожньою Ñ– не може міÑтити лише Ñимвол #. -settings.hooks_desc=Веб-хуки автоматично робить HTTP POST-запити на Ñервер, коли відбуваютьÑÑ Ð¿ÐµÐ²Ð½Ñ– події Gitea. ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ в <a target="_blank" rel="noopener" href="%s"> інÑтрукції по викориÑтанню web-хуків </a>. +settings.add_webhook.invalid_channel_name=Ðазва каналу веб-хука не може бути порожньою Ñ– міÑтити лише Ñимвол #. +settings.hooks_desc=Веб-хуки автоматично роблÑть HTTP POST запити до Ñервера, коли відбуваютьÑÑ Ð¿ÐµÐ²Ð½Ñ– події Gitea. ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ в <a target="_blank" rel="noopener noreferrer" href="%s">інÑтрукції по викориÑтанню веб-хуків</a>. settings.webhook_deletion=Видалити веб-хук -settings.webhook_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ веб-хука призведе до Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²Ñієї пов'Ñзаної з ним інформації, включаючи Ñ–Ñторію. Бажаєте продовжити? -settings.webhook_deletion_success=Webhook видалено. +settings.webhook_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÐµÐ±-хука видалÑÑ” його Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñ‚Ð° Ñ–Ñторію доÑтавки. Продовжити? +settings.webhook_deletion_success=Веб-хук видалено. settings.webhook.test_delivery=Перевірити доÑтавку -settings.webhook.test_delivery_desc=Перевірте цей веб-хук з підробленою подією. +settings.webhook.test_delivery_desc=Перевірте цей веб-хук з фальшивою подією. settings.webhook.request=Запит settings.webhook.response=Відповідь settings.webhook.headers=Заголовки settings.webhook.payload=ЗміÑÑ‚ settings.webhook.body=Тіло -settings.githook_edit_desc=Якщо хук неактивний, буде предÑтавлено зразок зміÑту. Порожнє Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ñƒ цьому полі призведе до Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð½Ñ Ñ…ÑƒÐºÑƒ. -settings.githook_name=Ім'Ñ Ñ…ÑƒÐºÑƒ +settings.githook_edit_desc=Якщо хук неактивний, буде показано зразок вміÑту. Якщо залишити вміÑÑ‚ порожнім, хук буде вимкнено. +settings.githook_name=Ðазва хуку settings.githook_content=ЗміÑÑ‚ хука settings.update_githook=Оновити хук -settings.add_webhook_desc=Gitea буде відправлÑти <code>POST</code> запити на вказану URL адреÑу, з інформацією про події, що відбуваютьÑÑ. Подробиці на Ñторінці <a target="_blank" rel="noopener" href="%s"> інÑтрукції по викориÑтанню web-хуків </a>. +settings.add_webhook_desc=Gitea надішле запити <code>POST</code> із зазначеним типом зміÑту на цільову URL-адреÑу. ДізнайтеÑÑ Ð±Ñ–Ð»ÑŒÑˆÐµ в <a target="_blank" rel="noopener noreferrer" href="%s">інÑтрукції по викориÑтанню веб-хуків</a>. settings.payload_url=Цільова URL-адреÑа settings.http_method=Метод HTTP settings.content_type=Тип зміÑту settings.secret=Секрет settings.slack_username=Ім'Ñ ÐºÑ€Ð¸Ñтувача -settings.slack_icon_url=URL іконки +settings.slack_icon_url=URL піктограми settings.slack_color=Колір settings.discord_username=Ім'Ñ ÐºÑ€Ð¸Ñтувача -settings.discord_icon_url=URL іконки +settings.discord_icon_url=URL піктограми settings.event_desc=Тригер: -settings.event_push_only=Push події settings.event_send_everything=Ð’ÑÑ– події settings.event_choose=ВлаÑні події… settings.event_header_repository=Події репозиторію @@ -1957,42 +2213,38 @@ settings.event_create_desc=Гілку або тег Ñтворено. settings.event_delete=Видалити settings.event_delete_desc=Гілку або мітку було видалено. settings.event_fork=Форк -settings.event_fork_desc=Репозиторій було форкнуто. settings.event_wiki=Вікі settings.event_statuses=СтатуÑи settings.event_statuses_desc=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ оновлено з API. settings.event_release=Реліз -settings.event_release_desc=Реліз опублікований, оновлений або видалений з репозиторіÑ. +settings.event_release_desc=Реліз опубліковано, оновлено або видалено зі Ñховища. settings.event_push=Push -settings.event_push_desc=Git push до репозиторію. settings.event_repository=Репозиторій settings.event_repository_desc=Репозиторій Ñтворений або видалено. settings.event_header_issue=Події задачі settings.event_issues=Задачі -settings.event_issues_desc=Задача відкрита, закрита, повторно відкрита або відредагована. -settings.event_issue_assign=Задача прив'Ñзана +settings.event_issues_desc=Задачу відкрито, закрито, повторно відкрито або відредаговано. +settings.event_issue_assign=Задачу призначено settings.event_issue_assign_desc=Задачу призначено або ÑкаÑовано. -settings.event_issue_label=Задача з міткою settings.event_issue_label_desc=Мітки задачі оновлено або видалено. -settings.event_issue_milestone=Задача з етапом -settings.event_issue_milestone_desc=Задача призначена на етап або видалена з етапу. settings.event_issue_comment=Коментар задачі settings.event_issue_comment_desc=Коментар задачі Ñтворено, видалено чи відредаговано. settings.event_header_pull_request=Події запиту Ð·Ð»Ð¸Ñ‚Ñ‚Ñ -settings.event_pull_request=Запити до Ð·Ð»Ð¸Ñ‚Ñ‚Ñ -settings.event_pull_request_desc=Запит до Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¾, закрито, перевідкрито або відредаговано. +settings.event_pull_request=Запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ +settings.event_pull_request_desc=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð²Ñ–Ð´ÐºÑ€Ð¸Ñ‚Ð¾, закрито, повторно відкрито або відредаговано. settings.event_pull_request_assign=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¾ -settings.event_pull_request_assign_desc=Запит про Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¾ або ÑкаÑовано. +settings.event_pull_request_assign_desc=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¾ або ÑкаÑовано. settings.event_pull_request_label=Запиту на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð° мітка settings.event_pull_request_label_desc=Мітка запиту на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð° або очищена. -settings.event_pull_request_milestone=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ð¹ на етап -settings.event_pull_request_milestone_desc=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ð¹ на етап або видалений з етапу. -settings.event_pull_request_comment=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¾ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð¾Ð²Ð°Ð½Ð¸Ð¹ +settings.event_pull_request_milestone=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð¾Ð´Ð°Ð½Ð¾ до етапу +settings.event_pull_request_milestone_desc=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð´Ð¾Ð´Ð°Ð½Ð¾ до етапу або видалено з етапу. +settings.event_pull_request_comment=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¾ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð¾Ð²Ð°Ð½Ð¾ settings.event_pull_request_comment_desc=Коментар запиту на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñтворено, відредаговано чи видалено. settings.event_pull_request_review=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ€ÐµÑ†ÐµÐ½Ð·Ð¾Ð²Ð°Ð½Ð¾ -settings.event_pull_request_review_desc=Коментар запиту до Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð¸Ð¹, відхилений або рецензований. +settings.event_pull_request_review_desc=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ–Ð´Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð¾, відхилено або прокоментовано. settings.event_pull_request_sync=Запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ ÑинхронізуєтьÑÑ settings.event_pull_request_sync_desc=Запит до Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñинхронізовано. +settings.event_package=Пакет settings.branch_filter=Фільтр гілок settings.active=Ðктивний settings.active_helper=Інформацію про викликані події буде надіÑлано за цією веб-хук URL-адреÑою. @@ -2020,34 +2272,31 @@ settings.web_hook_name_wechatwork=WeCom (Wechat Work) settings.web_hook_name_packagist=Packagist settings.packagist_username=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача Packagist settings.packagist_api_token=Токен API -settings.deploy_keys=Ключі Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ -settings.add_deploy_key=Додати ключ Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ -settings.deploy_key_desc=Ключі Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð´Ð¾Ñтупні тільки Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ. Це не те ж Ñаме що Ñ– SSH-ключі аккаунта. +settings.deploy_keys=Ключі Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ +settings.add_deploy_key=Додати ключ Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ +settings.deploy_key_desc=Ключі Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð¼Ð°ÑŽÑ‚ÑŒ доÑтуп до Ñховища лише Ð´Ð»Ñ Ñ‡Ð¸Ñ‚Ð°Ð½Ð½Ñ. settings.is_writable=Увімкнути доÑтуп Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñу -settings.is_writable_info=Чи може цей ключ бути викориÑтаний Ð´Ð»Ñ Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ <strong>push</strong> в репозиторій? Ключі Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð·Ð°Ð²Ð¶Ð´Ð¸ мають доÑтуп на pull. -settings.no_deploy_keys=Ви не додавали ключі розгортаннÑ. +settings.no_deploy_keys=Ключів Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ñ‰Ðµ немає. settings.title=Заголовок settings.deploy_key_content=ЗміÑÑ‚ -settings.key_been_used=ЗміÑÑ‚ ключа Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð²Ð¶Ðµ викориÑтовуєтьÑÑ. -settings.key_name_used=Ключ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð· таким заголовком вже Ñ–Ñнує. -settings.deploy_key_deletion=Видалити ключ Ð´Ð»Ñ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚ÑƒÐ²Ð°Ð½Ð½Ñ -settings.deploy_key_deletion_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð° розгортки унеможливить доÑтуп до Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ð· його допомогою. Ви впевнені? +settings.key_been_used=Ключ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð· ідентичним вміÑтом вже викориÑтовуєтьÑÑ. +settings.key_name_used=Ключ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð· такою ж назвою вже Ñ–Ñнує. +settings.deploy_key_deletion=Видалити ключ Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ +settings.deploy_key_deletion_desc=Ð’Ð¸Ð»ÑƒÑ‡ÐµÐ½Ð½Ñ ÐºÐ»ÑŽÑ‡Ð° Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¸Ð·Ð²ÐµÐ´Ðµ до Ð²Ñ–Ð´ÐºÐ»Ð¸ÐºÐ°Ð½Ð½Ñ Ð¹Ð¾Ð³Ð¾ доÑтупу до цього Ñховища. Продовжити? settings.deploy_key_deletion_success=Ключі Ñ€Ð¾Ð·Ð³Ð¾Ñ€Ñ‚Ð°Ð½Ð½Ñ Ð±ÑƒÐ»Ð¾ видалено. settings.branches=Гілки settings.protected_branch=ЗахиÑÑ‚ гілки settings.protected_branch.save_rule=Зберегти правило settings.protected_branch.delete_rule=Видалити правило -settings.protected_branch_can_push=Дозволити push? -settings.protected_branch_can_push_yes=Ви можете виконувати push settings.branch_protection=ЗахиÑÑ‚ гілки '<b>%s</b>' -settings.protect_this_branch=ЗахиÑтити цю гілку -settings.protect_this_branch_desc=Запобігає видаленню гілки та обмежує Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ Ð² ній push та злиттÑ. +settings.protect_this_branch=Увімкнути захиÑÑ‚ гілок settings.protect_disable_push=Заборонити Push settings.protect_disable_push_desc=Ð”Ð»Ñ Ñ†Ñ–Ñ”Ñ— гілки буде заборонено Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ push. settings.protect_enable_push=Дозволити Push settings.protect_enable_push_desc=Будь-хто із правом запиÑу зможе виконувати push Ð´Ð»Ñ Ñ†Ñ–Ñ”Ñ— гілки (за виключеннÑм force push). settings.protect_check_status_contexts=Увімкнути перевірку Ñтану settings.protect_status_check_patterns=Шаблони перевірки Ñтану: +settings.protect_status_check_patterns_desc=Введіть шаблони, щоб вказати, Ñкі перевірки Ñтану повинні пройти гілки, перш ніж Ñ—Ñ… буде об'єднано у гілку, що відповідає цьому правилу. Кожен Ñ€Ñдок визначає шаблон. Шаблони не можуть бути порожніми. settings.protect_check_status_contexts_list=Перевірки ÑтатуÑу знайдено Ð´Ð»Ñ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð°Ñ€Ñ–ÑŽ за минулий тиждень settings.protect_status_check_matched=Збіг settings.protect_invalid_status_check_pattern=ÐедійÑний шаблон перевірки Ñтану: "%s". @@ -2055,42 +2304,57 @@ settings.protect_no_valid_status_check_patterns=Ðемає дійÑних шаб settings.protect_required_approvals=Ðеобхідно ÑхваленнÑ: settings.dismiss_stale_approvals=Відхилити заÑтарілі Ð¿Ð¾Ð³Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ settings.dismiss_stale_approvals_desc=Коли нові коміти що змінюють вміÑÑ‚ пулл-запиту відправлÑютьÑÑ Ð² гілку, Ñтарі Ð¿Ð¾Ð³Ð¾Ð´Ð¶ÐµÐ½Ð½Ñ Ð±ÑƒÐ´ÑƒÑ‚ÑŒ відхилені. +settings.ignore_stale_approvals=Ігнорувати заÑтарілі Ð·Ð°Ñ‚Ð²ÐµÑ€Ð´Ð¶ÐµÐ½Ð½Ñ settings.require_signed_commits=Потрібно підпиÑані коміти settings.require_signed_commits_desc=ВідхилÑти push до цієї гілки, Ñкщо вони не підпиÑані або Ð¿Ñ–Ð´Ð¿Ð¸Ñ Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ð¾ перевірити. +settings.protect_branch_name_pattern=Шаблон назви захищених гілок +settings.protect_branch_name_pattern_desc=Шаблони назв захищених гілок. ПереглÑньте <a href="https://github.com/gobwas/glob">документацію</a> щодо ÑинтакÑиÑу шаблону. Приклади: main, release/** +settings.protect_patterns=Шаблони +settings.protect_protected_file_patterns=Шаблони захищених файлів (розділені крапками з комою ';'): +settings.protect_unprotected_file_patterns=Шаблони незахищених файлів (розділені крапкою з комою ';'): settings.add_protected_branch=Увімкнути захиÑÑ‚ settings.delete_protected_branch=Вимкнути захиÑÑ‚ +settings.update_protect_branch_success=Оновлено захиÑÑ‚ гілок Ð´Ð»Ñ Ð¿Ñ€Ð°Ð²Ð¸Ð»Ð° "%s". +settings.remove_protected_branch_success=Видалено захиÑÑ‚ гілок Ð´Ð»Ñ Ð¿Ñ€Ð°Ð²Ð¸Ð»Ð° "%s". +settings.remove_protected_branch_failed=Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ правило захиÑту гілки "%s. settings.protected_branch_deletion_desc=Будь-Ñкий кориÑтувач з дозволами на Ð·Ð°Ð¿Ð¸Ñ Ð·Ð¼Ð¾Ð¶Ðµ виконувати push в цю гілку. Ви впевнені? -settings.block_rejected_reviews=Блокувати Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸ відкидаючих рецензіÑÑ… -settings.block_rejected_reviews_desc=Ð—Ð»Ð¸Ñ‚Ñ‚Ñ Ð±ÑƒÐ´Ðµ недоÑтупним, Ñкщо Ñ” запит змін від офіційних рецензентів, навіть за наÑвноÑті доÑтатньої кількоÑті Ñхвалень. -settings.block_on_official_review_requests=Блокувати Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð¿Ñ€Ð¸ запиті на офіціальний розглÑд +settings.block_rejected_reviews=Блокувати об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ñкщо рецензії відхилено +settings.block_rejected_reviews_desc=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð±ÑƒÐ´Ðµ неможливим, Ñкщо офіційні рецензенти вимагають внеÑÐµÐ½Ð½Ñ Ð·Ð¼Ñ–Ð½, навіть Ñкщо Ñ” доÑÑ‚Ð°Ñ‚Ð½Ñ ÐºÑ–Ð»ÑŒÐºÑ–Ñть Ñхвалень. +settings.block_on_official_review_requests=Блокувати об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð·Ð° офіційними запитами на Ñ€ÐµÑ†ÐµÐ½Ð·ÑƒÐ²Ð°Ð½Ð½Ñ settings.block_on_official_review_requests_desc=ÐžÐ±â€™Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð½ÐµÐ¼Ð¾Ð¶Ð»Ð¸Ð²Ðµ, коли воно має офіційні запити на розглÑд, навіть Ñкщо доÑтатньо Ñхвалень. -settings.block_outdated_branch=Блокувати злиттÑ, Ñкщо запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ñтарів -settings.block_outdated_branch_desc=Ð—Ð»Ð¸Ñ‚Ñ‚Ñ Ð±ÑƒÐ´Ðµ неможливим, коли головна гілка позаду оÑновної. -settings.default_branch_desc=Головна гілка Ñ” 'базовою' Ð´Ð»Ñ Ð²Ð°ÑˆÐ¾Ð³Ð¾ репозиторіÑ, на Ñку за замовчуваннÑм ÑпрÑмовані вÑÑ– запити на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ– Ñка Ñ” обличчÑм вашого репозиторіÑ. Перше, що побачить відвідувач - це зміÑÑ‚ головної гілки. Виберіть Ñ—Ñ— з уже Ñ–Ñнуючих: +settings.block_outdated_branch=Блокувати об'єднаннÑ, Ñкщо запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ð·Ð°Ñтарів +settings.block_outdated_branch_desc=Об'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð±ÑƒÐ´Ðµ неможливим, Ñкщо головна гілка позаду оÑновної. +settings.block_admin_merge_override=ÐдмініÑтратори повинні дотримуватиÑÑ Ð¿Ñ€Ð°Ð²Ð¸Ð» захиÑту гілки +settings.block_admin_merge_override_desc=ÐдмініÑтратори повинні дотримуватиÑÑ Ð¿Ñ€Ð°Ð²Ð¸Ð» захиÑту гілки Ñ– не можуть Ñ—Ñ… обійти. +settings.default_branch_desc=Обрати типову гілку Ñховища Ð´Ð»Ñ Ð·Ð°Ð¿Ð¸Ñ‚Ñ–Ð² на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ Ñ– комітів: settings.choose_branch=Оберіть гілку… settings.no_protected_branch=Ðемає захищених гілок. settings.edit_protected_branch=Редагувати +settings.protected_branch_required_rule_name=Ðеобхідна назва правила +settings.protected_branch_duplicate_rule_name=Ðазва правила вже Ñ–Ñнує settings.protected_branch_required_approvals_min=ЧиÑло необхідних Ñхвалень не може бути від'ємним. settings.tags=Мітки settings.tags.protection=ЗахиÑÑ‚ мітки -settings.tags.protection.pattern=Шаблон тега +settings.tags.protection.pattern=Шаблон мітки settings.tags.protection.allowed=Дозволено settings.tags.protection.allowed.users=Дозволені кориÑтувачі settings.tags.protection.allowed.teams=Дозволені команди settings.tags.protection.allowed.noone=Ðіхто -settings.tags.protection.create=ЗахиÑтна мітка +settings.tags.protection.create=ЗахиÑтити мітку settings.tags.protection.none=Там не немає захищених міток. +settings.tags.protection.pattern.description=Ви можете викориÑтовувати єдине ім’Ñ, глобальний шаблон або регулÑрний вираз, щоб відповідати кільком міткам. Докладніше читайте в <a target="_blank" rel="noopener" href="https://docs.gitea.com/usage/protected-tags">поÑібнику із захищених міток</a>. settings.bot_token=Токен Ð´Ð»Ñ Ð±Ð¾Ñ‚Ð° -settings.chat_id=Чат ID +settings.chat_id=ID чату settings.matrix.homeserver_url=URL домашньої Ñторінки -settings.matrix.room_id=Ðомер кімнати +settings.matrix.room_id=ID кімнати settings.matrix.message_type=Тип Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ settings.archive.header=Відправити репозиторій в архів -settings.archive.success=Репозиторію уÑпішно приÑвоєно ÑÑ‚Ð°Ñ‚ÑƒÑ Ð°Ñ€Ñ…Ñ–Ð²Ð½Ð¾Ð³Ð¾. -settings.archive.error=СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при Ñпробі архівувати репозиторій. Докладнішу інформацію див. у журналі. -settings.archive.error_ismirror=Ðеможливо архівувати дзеркальний репозиротрій. +settings.archive.success=Сховище уÑпішно заархівовано. +settings.archive.error=СталаÑÑ Ð¿Ð¾Ð¼Ð¸Ð»ÐºÐ° при Ñпробі архівувати репозиторій. Докладнішу інформацію дивітьÑÑ Ñƒ журналі. +settings.archive.error_ismirror=Ðеможливо архівувати дзеркальне Ñховище. settings.archive.branchsettings_unavailable=Параметри гілки не доÑтупні, Ñкщо репозиторій архівний. settings.archive.tagsettings_unavailable=Параметри міток недоÑтупні, Ñкщо репозиторій архівний. +settings.unarchive.success=Сховище уÑпішно розархівовано. settings.update_avatar_success=Ðватар репозиторію оновлений. settings.lfs=LFS settings.lfs_filelist=Файли LFS, Ñкі зберігаютьÑÑ Ð² цьому репозиторії @@ -2102,9 +2366,9 @@ settings.lfs_delete=Видалити файл LFS з OID %s settings.lfs_delete_warning=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ñ„Ð°Ð¹Ð»Ñƒ LFS може Ñпричинити помилки "Об'єкт не Ñ–Ñнує" під Ñ‡Ð°Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸. Ви впевнені? settings.lfs_findpointerfiles=Знайти файли-поÑÐ¸Ð»Ð°Ð½Ð½Ñ settings.lfs_locks=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ -settings.lfs_invalid_locking_path=ÐеприпуÑтимий шлÑÑ…: %s +settings.lfs_invalid_locking_path=ÐедійÑний шлÑÑ…: %s settings.lfs_invalid_lock_directory=Ðе можливо заблокувати каталог: %s -settings.lfs_lock_already_exists=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¶Ðµ викориÑтовуєтьÑÑ: %s +settings.lfs_lock_already_exists=Ð‘Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ Ð²Ð¶Ðµ Ñ–Ñнує: %s settings.lfs_lock=Блокувати settings.lfs_lock_path=ШлÑÑ… до файлу Ð´Ð»Ñ Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ... settings.lfs_locks_no_locks=ВідÑутнє Ð±Ð»Ð¾ÐºÑƒÐ²Ð°Ð½Ð½Ñ @@ -2144,7 +2408,7 @@ diff.stats_desc_file=%d змін: %d доповнень та %d видалень diff.bin=BIN diff.bin_not_shown=Бінарний файл не відображаєтьÑÑ. diff.view_file=ПереглÑнути файл -diff.file_before=Перед +diff.file_before=До diff.file_after=ПіÑÐ»Ñ diff.file_image_width=Ширина diff.file_image_height=ВиÑота @@ -2156,6 +2420,7 @@ diff.show_more=Показати більше diff.load=Завантажити різницю diff.generated=згенерований diff.vendored=Ñторонній +diff.comment.add_line_comment=Додати проÑтий коментар diff.comment.placeholder=Залишити коментар diff.comment.add_single_comment=Додати проÑтий коментар diff.comment.add_review_comment=Додати коментар @@ -2163,15 +2428,15 @@ diff.comment.start_review=Розпочати рецензію diff.comment.reply=Відповідь diff.review=Ð ÐµÑ†ÐµÐ½Ð·Ñ–Ñ diff.review.header=ÐадіÑлати рецензію -diff.review.placeholder=Рецензійований коментарій +diff.review.placeholder=Ð ÐµÑ†ÐµÐ½Ð·Ñ–Ñ ÐºÐ¾Ð¼ÐµÐ½Ñ‚Ð°Ñ€Ñ diff.review.comment=Коментар diff.review.approve=Затвердити diff.review.reject=Запит змін diff.committed_by=зафікÑовано diff.protected=Захищений -diff.image.side_by_side=Пліч-о-пліч -diff.image.swipe=Свайп -diff.image.overlay=Оверлей +diff.image.side_by_side=Поруч +diff.image.swipe=ПровеÑти пальцем +diff.image.overlay=ÐаклаÑти diff.show_file_tree=Показати дерево файлів diff.hide_file_tree=Сховати дерево файлів diff.submodule_added=Підмодуль %[1]s додано в %[2]s @@ -2181,60 +2446,67 @@ diff.submodule_updated=Підмодуль %[1]s оновлено: %[2]s releases.desc=ВідÑлідковувати верÑÑ–Ñ— проєкту Ñ– завантаженнÑ. release.releases=Релізи release.detail=Деталі релізу -release.tags=Теги +release.tags=Мітки release.new_release=Ðовий реліз release.draft=Чернетка release.prerelease=Пре-реліз release.stable=Стабільний +release.latest=ОÑтанні release.compare=ПорівнÑти release.edit=редагувати release.ahead.commits=<strong>%d</strong> коміт(ів) release.ahead.target=до %s з моменту цього випуÑку release.source_code=Код -release.new_subheader=ÐŸÑƒÐ±Ð»Ñ–ÐºÐ°Ñ†Ñ–Ñ Ñ€ÐµÐ»Ñ–Ð·Ñ–Ð² допоможе вам організувати верÑÑ–ÑŽ проєкту. -release.edit_subheader=ÐŸÑƒÐ±Ð»Ñ–ÐºÐ°Ñ†Ñ–Ñ Ñ€ÐµÐ»Ñ–Ð·Ñ–Ð² допоможе вам організувати верÑÑ–ÑŽ проєкту. -release.tag_name=Ðазва тегу +release.new_subheader=Релізи впорÑдковують верÑÑ–Ñ— проєкту. +release.edit_subheader=Релізи впорÑдковують верÑÑ–Ñ— проєкту. +release.tag_name=Ðазва мітки release.target=Ціль -release.tag_helper=Виберіть Ñ–Ñнуючий тег або Ñтворіть новий. +release.tag_helper=Вибрати Ñ–Ñнуючу мітку або Ñтворити нову. release.title=Ðазва релізу release.title_empty=Заголовок не може бути порожнім. release.message=Опишіть цей реліз release.prerelease_desc=Позначити Ñк пре-реліз -release.prerelease_helper=Позначте цей випуÑк непридатним Ð´Ð»Ñ ÐŸÐ ÐžÐ” викориÑтаннÑ. +release.prerelease_helper=Позначите випуÑк Ñк непридатний Ð´Ð»Ñ Ð¿Ñ€Ð¾Ð´ÑƒÐºÑ‚Ð¸Ð²Ð½Ð¾Ð³Ð¾ викориÑтаннÑ. release.cancel=Відмінити release.publish=Опублікувати реліз release.save_draft=Зберегти чернетку release.edit_release=Оновити реліз release.delete_release=Видалити реліз -release.delete_tag=Видалити тег +release.delete_tag=Видалити мітку release.deletion=Видалити реліз -release.deletion_success=Реліз, було видалено. +release.deletion_success=Реліз видалено. release.deletion_tag_desc=Буде видалено цей тег із репозиторію. ВміÑÑ‚ Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ñ‚Ð° Ñ–ÑÑ‚Ð¾Ñ€Ñ–Ñ Ð·Ð°Ð»Ð¸ÑˆÐ°Ñ‚ÑŒÑÑ Ð½ÐµÐ·Ð¼Ñ–Ð½Ð½Ð¸Ð¼Ð¸. Продовжити? release.deletion_tag_success=Мітка видалена. -release.tag_name_already_exist=Реліз з цим ім'Ñм мітки вже Ñ–Ñнує. -release.tag_name_invalid=ÐеприпуÑтиме ім'Ñ Ñ‚ÐµÐ³Ð°. -release.tag_name_protected=Ім'Ñ Ñ‚ÐµÐ³Ð° захищене. -release.tag_already_exist=Цей тег вже викориÑтовуєтьÑÑ. -release.downloads=Завантажити +release.tag_name_already_exist=Реліз з такою ж міткою вже Ñ–Ñнує. +release.tag_name_invalid=Ðазва мітки недійÑна. +release.tag_name_protected=Ðазва мітки захищена. +release.tag_already_exist=Ðазва мітки вже Ñ–Ñнує. +release.downloads=Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ release.download_count=ЗавантаженнÑ: %s -release.add_tag_msg=ВикориÑтовуйте заголовок Ñ– зміÑÑ‚ релізу Ñк Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ñк тег повідомленнÑ. +release.add_tag_msg=ВикориÑтовуйте заголовок Ñ– зміÑÑ‚ релізу Ñк Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ Ð¼Ñ–Ñ‚ÐºÐ¸. release.add_tag=Створити тільки мітку release.releases_for=Релізи Ð´Ð»Ñ %s +release.tags_for=Мітки Ð´Ð»Ñ %s -branch.name=Ім'Ñ Ð³Ñ–Ð»ÐºÐ¸ +branch.name=Ðазва гілки branch.already_exists=Гілка з назвою "%s" вже Ñ–Ñнує. branch.delete_head=Видалити branch.delete=`Видалити гілку "%s"` branch.delete_html=Видалити гілку +branch.delete_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð³Ñ–Ð»ÐºÐ¸ Ñ” незворотним. Хоча видалена гілка може продовжувати Ñ–Ñнувати ще деÑкий Ñ‡Ð°Ñ Ð´Ð¾ того, Ñк Ñ—Ñ— буде видалено оÑтаточно, у більшоÑті випадків це ÐЕМОЖЛИВО ÑкаÑувати. Продовжити? branch.deletion_success=Гілку "%s" видалено. branch.deletion_failed=Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ гілку "%s". +branch.delete_branch_has_new_commits=Гілку "%s" не можна видалити, оÑкільки піÑÐ»Ñ Ð·'Ñ”Ð´Ð½Ð°Ð½Ð½Ñ Ð±ÑƒÐ»Ð¾ додано нові коміти. branch.create_branch=Створити гілку %s branch.create_from=`з "%s"` branch.create_success=Створено гілку "%s". +branch.branch_name_conflict=Ðазва гілки "%s" конфліктує з уже Ñ–Ñнуючою гілкою "%s". +branch.tag_collision=Гілка "%s" не може бути Ñтворена, оÑкільки у Ñховищі вже Ñ–Ñнує мітка з такою назвою. branch.deleted_by=Видалено %s branch.restore_success=Гілку "%s" відновлено. branch.restore_failed=Ðе вдалоÑÑ Ð²Ñ–Ð´Ð½Ð¾Ð²Ð¸Ñ‚Ð¸ гілку "%s". branch.protected_deletion_failed=Гілка "%s" захищена. Її неможливо видалити. +branch.default_deletion_failed=Гілка "%s" Ñтандартна. Її неможливо видалити. branch.restore=`Відновити гілку "%s"` branch.download=`Завантажити гілку "%s"` branch.rename=`Перейменувати гілку "%s"` @@ -2242,6 +2514,7 @@ branch.included_desc=Ð¦Ñ Ð³Ñ–Ð»ÐºÐ° Ñ” чаÑтиною типової гілк branch.included=Включено branch.create_new_branch=Створити гілку з гілки: branch.confirm_create_branch=Створити гілку +branch.warning_rename_default_branch=Ви перейменовуєте Ñтандартну гілку. branch.rename_branch_to=Перейменувати "%s" на: branch.confirm_rename_branch=Перейменувати гілку branch.create_branch_operation=Створити гілку @@ -2249,16 +2522,22 @@ branch.new_branch=Створити нову гілку branch.new_branch_from=`Створити нову гілку з "%s"` branch.renamed=Гілку %s перейменовано на %s. branch.rename_default_or_protected_branch_error=Лише адмініÑтратори можуть перейменовувати типові або захищені гілки. +branch.rename_protected_branch_failed=Ð¦Ñ Ð³Ñ–Ð»ÐºÐ° захищена правилами захиÑту на оÑнові глобальних правил. tag.create_tag=Створити тег %s +tag.create_tag_operation=Створити мітку +tag.confirm_create_tag=Створити мітку +tag.create_tag_from=`Створити нову мітку з "%s"` +tag.create_success=Мітку "%s" Ñтворено. -topic.manage_topics=Керувати тематичними мітками +topic.manage_topics=Керувати темами topic.done=Готово topic.count_prompt=Ви не можете вибрати більше ніж 25 тем topic.format_prompt=Теми мають починатиÑÑ Ð· літери або цифри, можуть міÑтити дефіÑи ('-') Ñ– крапки ('.'), мати довжину до 35 Ñимволів. Літери повинні бути малими. find_file.go_to_file=Перейти до файлу +find_file.no_matching=Ðе знайдено відповідного файлу error.csv.too_large=Ðе вдаєтьÑÑ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð·Ð¸Ñ‚Ð¸ цей файл, тому що він завеликий. error.csv.unexpected=Ðе вдаєтьÑÑ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð·Ð¸Ñ‚Ð¸ цей файл, тому що він міÑтить неочікуваний Ñимвол в Ñ€Ñдку %d Ñ– Ñтовпці %d. @@ -2267,9 +2546,11 @@ error.csv.invalid_field_count=Ðе вдаєтьÑÑ Ð²Ñ–Ð´Ð¾Ð±Ñ€Ð°Ð·Ð¸Ñ‚Ð¸ цеР[graphs] component_loading=Ð—Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶ÐµÐ½Ð½Ñ %s... component_loading_failed=Ðе вдалоÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚Ð°Ð¶Ð¸Ñ‚Ð¸ %s +component_loading_info=Це може зайнÑти трохи чаÑу… component_failed_to_load=СталаÑÑŒ непередбачена помилка. code_frequency.what=чаÑтота коду contributors.what=внеÑки +recent_commits.what=нові коміти [org] org_name_holder=Ðазва організації @@ -2289,12 +2570,13 @@ team_name=Ðазва команди team_desc=ÐžÐ¿Ð¸Ñ team_name_helper=Ðазва команди має бути проÑтою та зрозумілою. team_desc_helper=Опишіть мету або роль команди. -team_access_desc=ДоÑтуп до Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ +team_access_desc=ДоÑтуп до Ñховища team_permission_desc=Права доÑтупу team_unit_desc=Дозволити доÑтуп до розділів репозиторію team_unit_disabled=(Вимкнено) form.name_reserved=Ðазву організації "%s" зарезервовано. +form.name_pattern_not_allowed=Шаблон "%s" не допуÑкаєтьÑÑ Ð² назві організації. form.create_org_not_allowed=Вам не дозволено Ñтворювати організації. settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ @@ -2306,28 +2588,30 @@ settings.location=Ð Ð¾Ð·Ñ‚Ð°ÑˆÑƒÐ²Ð°Ð½Ð½Ñ settings.permission=Дозволи settings.repoadminchangeteam=ÐдмініÑтратор репозитарію може додавати та видалÑти доÑтуп Ð´Ð»Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´ settings.visibility=ВидиміÑть -settings.visibility.public=Публічний -settings.visibility.limited_shortname=Обмежений -settings.visibility.private=Приватний (Видимий лише членам організації) +settings.visibility.public=Публічна +settings.visibility.limited=Обмежений (Видимий лише Ð´Ð»Ñ Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ¾Ð²Ð°Ð½Ð¸Ñ… кориÑтувачів) +settings.visibility.limited_shortname=Обмежена +settings.visibility.private=Приватна (Видима лише членам організації) settings.visibility.private_shortname=Приватний settings.update_settings=Оновити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ settings.update_setting_success=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ— оновлені. -settings.change_orgname_redirect_prompt=Старе ім'Ñ Ð±ÑƒÐ´Ðµ перенаправлено до тих пір, поки воно не буде заброньовано. +settings.change_orgname_prompt=Примітка: Зміна назви організації також змінить URL-адреÑу вашої організації та звільнить Ñтару назву. +settings.change_orgname_redirect_prompt=Стара назва буде перенаправлÑтиÑÑ Ð´Ð¾ тих пір, поки не буде заброньована. settings.update_avatar_success=Ðватар організації оновлений. settings.delete=Видалити організацію settings.delete_account=Видалити цю організацію -settings.delete_prompt=ÐžÑ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð±ÑƒÐ´Ðµ оÑтаточно видалена. Це <strong>ÐЕ МОЖЛИВО</strong> відмінити! -settings.confirm_delete_account=Підтвердіть Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ +settings.delete_prompt=Організацію буде оÑтаточно видалено. Це <strong>ÐЕМОЖЛИВО</strong> ÑкаÑувати! +settings.confirm_delete_account=Підтвердити Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ settings.delete_org_title=Видалити організацію settings.delete_org_desc=Ð¦Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ Ð±ÑƒÐ´Ðµ безповоротно видалена. Продовжити? -settings.hooks_desc=Додайте webhooks, Ñкий буде викликатиÑÑ Ð´Ð»Ñ <strong>вÑÑ–Ñ… репозиторіїв</strong> Ñкими володіє Ñ†Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñ. +settings.hooks_desc=Додайте веб-хуки, Ñкі Ñпрацьовуватимуть Ð´Ð»Ñ <strong>вÑÑ–Ñ… Ñховищ</strong> у цій організації. -settings.labels_desc=Додати мітки, Ñкі можуть бути викориÑтані Ð´Ð»Ñ Ð·Ð°Ð´Ð°Ñ‡ Ð´Ð»Ñ <strong>вÑÑ–Ñ… репозиторіїв</strong> в цій організації. +settings.labels_desc=Додайте мітки, Ñкі можна викориÑтовувати у задачах Ð´Ð»Ñ <strong>уÑÑ–Ñ… Ñховищ</strong> у цій організації. members.membership_visibility=ВидиміÑть учаÑника: members.public=Показувати -members.public_helper=зробити прихованим +members.public_helper=приховати members.private=Прихований members.private_helper=зробити видимим members.member_role=Роль учаÑника: @@ -2347,6 +2631,7 @@ teams.can_create_org_repo=Створити репозиторії teams.can_create_org_repo_helper=УчаÑники можуть Ñтворювати нові репозиторії в організації. Ðвтор отримає доÑтуп адмініÑтратора до нового репозиторію. teams.none_access=Ðемає доÑтупу teams.general_access=Загальний доÑтуп +teams.general_access_helper=Дозволи учаÑників будуть визначатиÑÑ Ð²Ñ–Ð´Ð¿Ð¾Ð²Ñ–Ð´Ð½Ð¾ до наведеної нижче таблиці дозволів. teams.read_access=Ð§Ð¸Ñ‚Ð°Ð½Ð½Ñ teams.read_access_helper=УчаÑники можуть переглÑдати та клонувати репозиторії команд. teams.write_access=Ð—Ð°Ð¿Ð¸Ñ @@ -2355,7 +2640,7 @@ teams.admin_access=ДоÑтуп адмініÑтратора teams.admin_access_helper=УчаÑники можуть виконувати pull, push в репозиторії команд Ñ– додавати Ñпівавторів в команду. teams.no_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° не має опиÑу teams.settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ -teams.owners_permission_desc=ВлаÑник має повний доÑтуп до <strong>уÑÑ–Ñ… репозиторіїв</strong> та має <strong>права адмініÑтратора</strong> організації. +teams.owners_permission_desc=ВлаÑники мають повний доÑтуп до <strong>уÑÑ–Ñ… репозиторіїв</strong> та <strong>права адмініÑтратора</strong> організації. teams.members=УчаÑники команди teams.update_settings=Оновити Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ teams.delete_team=Видалити команду @@ -2364,7 +2649,7 @@ teams.invite_team_member=ЗапроÑити до %s teams.delete_team_title=Видалити команду teams.delete_team_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð¸ ÑкаÑовує доÑтуп до Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ Ð´Ð»Ñ Ñ—Ñ— учаÑників. Продовжити? teams.delete_team_success=Команду було видалено. -teams.read_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° має доÑтуп Ð´Ð»Ñ <strong>читаннÑ</strong>: учаÑники можуть переглÑдати та клонувати репозиторії. +teams.read_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° має доÑтуп на <strong>читаннÑ</strong>: учаÑники можуть переглÑдати та клонувати репозиторії. teams.write_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає доÑтуп на <strong>запиÑ</strong>: учаÑники можуть отримувати й виконувати push команди до репозитрію. teams.admin_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає <strong>адмініÑтраторÑький</strong> доÑтуп: учаÑники можуть читати, виконувати push команди та додавати Ñпівробітників до репозиторію. teams.create_repo_permission_desc=Крім того, Ñ†Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає дозвіл <strong>Створити репозиторій</strong>: учаÑники можуть Ñтворювати нові репозиторії в організації. @@ -2385,6 +2670,7 @@ teams.all_repositories_read_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає доРteams.all_repositories_write_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає дозвіл <strong>ЗапиÑ</strong> Ð´Ð»Ñ <strong>вÑÑ–Ñ… репозиторіїв</strong>: учаÑники можуть переглÑдати та виконувати push в репозиторіÑÑ…. teams.all_repositories_admin_permission_desc=Ð¦Ñ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° надає дозвіл <strong>ÐдмініÑтруваннÑ</strong> Ð´Ð»Ñ <strong>вÑÑ–Ñ… репозиторіїв</strong>: учаÑники можуть переглÑдати, виконувати push та додавати Ñпівробітників. teams.invite.title=Ð’Ð°Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñили приєднатиÑÑ Ð´Ð¾ команди <strong>%s</strong> в організації <strong>%s</strong>. +teams.invite.by=Запрошений %s teams.invite.description=Будь лаÑка, натиÑніть на кнопку нижче, щоб приєднатиÑÑ Ð´Ð¾ команди. view_as_role=ПереглÑнути Ñк: %s @@ -2397,11 +2683,13 @@ worktime.date_range_end=Дата Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ worktime.query=Запит worktime.time=Ð§Ð°Ñ worktime.by_milestones=За етапами +worktime.by_members=За учаÑниками [admin] maintenance=Технічне обÑÐ»ÑƒÐ³Ð¾Ð²ÑƒÐ²Ð°Ð½Ð½Ñ dashboard=Панель ÑƒÐ¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ self_check=Самоперевірка +identity_access=Ð†Ð´ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ñ‚Ð° доÑтуп users=Облікові запиÑи кориÑтувачів organizations=Організації assets=РеÑурÑи коду @@ -2409,7 +2697,7 @@ repositories=Репозиторії hooks=Веб-хуки integrations=Інтеграції authentication=Джерела автентифікації -emails=Електронні адреÑи КориÑтувача +emails=Електронна пошта кориÑтувача config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ config_summary=ПідÑумок config_settings=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ @@ -2420,7 +2708,9 @@ last_page=ОÑÑ‚Ð°Ð½Ð½Ñ total=Разом: %d settings=ÐдмініÑтративні Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ +dashboard.new_version_hint=Gitea %s тепер доÑтупна, ви викориÑтовуєте %s. Перевірте <a target="_blank" rel="noreferrer" href="https://blog.gitea.io">блог</a> Ð´Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації. dashboard.statistic=ПідÑумок +dashboard.maintenance_operations=Операції з технічного обÑÐ»ÑƒÐ³Ð¾Ð²ÑƒÐ²Ð°Ð½Ð½Ñ dashboard.system_status=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ ÑиÑтеми dashboard.operation_name=Ðазва операції dashboard.operation_switch=Перемкнути @@ -2429,20 +2719,23 @@ dashboard.clean_unbind_oauth=ОчиÑтити ÑпиÑок незавершенРdashboard.clean_unbind_oauth_success=Ð’ÑÑ– незавершені зв'Ñзки OAuth були видалені. dashboard.task.started=Запущено завданнÑ: %[1]s dashboard.task.process=ЗавданнÑ: %[1]s +dashboard.task.cancelled=ЗавданнÑ: %[1]s ÑкаÑовано: %[3]s dashboard.task.error=Помилка у завданні: %[1]s:%[3]s dashboard.task.finished=ЗавершилоÑÑ Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ, Ñке запуÑтив %[2]s: %[1]s dashboard.task.unknown=Ðевідоме завданнÑ: %[1]s dashboard.cron.started=Запущено Cron: %[1]s dashboard.cron.process=Cron: %[1]s +dashboard.cron.cancelled=Планувальник: %[1]s ÑкаÑовано: %[3]s dashboard.cron.error=Помилка в Cron: %s: %[3]s dashboard.cron.finished=Cron: %[1]s завершено dashboard.delete_inactive_accounts=Видалити вÑÑ– неактивовані облікові запиÑи -dashboard.delete_inactive_accounts.started=Запущено Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ– неактивованих облікових запиÑів. +dashboard.delete_inactive_accounts.started=Запущено Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… неактивованих облікових запиÑів. dashboard.delete_repo_archives=Видалити вÑÑ– архіви репозиторіїв (ZIP, TAR.GZ, Ñ– Ñ‚. д..) dashboard.delete_repo_archives.started=Запущено Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… архівів репозиторіїв. -dashboard.delete_missing_repos=Видалити вÑÑ– запиÑи про репозиторії з відÑутніми файлами Git +dashboard.delete_missing_repos=Видаліть уÑÑ– Ñховища, в Ñких відÑутні файли Git dashboard.delete_missing_repos.started=Запущено Ð·Ð°Ð²Ð´Ð°Ð½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð²ÑÑ–Ñ… репозиторіїв, в Ñких відÑутні файли Git. -dashboard.delete_generated_repository_avatars=Видалити репозиторій з згенерованими аватарами +dashboard.delete_generated_repository_avatars=Видалити згенеровані аватарки Ñховища +dashboard.sync_repo_branches=Синхронізувати пропущені гілки з даних git до баз даних dashboard.update_mirrors=Оновити дзеркала dashboard.repo_health_check=Перевірка Ñтану вÑÑ–Ñ… репозиторіїв dashboard.check_repo_stats=Перевірити ÑтатиÑтику вÑÑ–Ñ… репозиторіїв @@ -2452,11 +2745,11 @@ dashboard.update_migration_poster_id=Оновити мігровані ID авт dashboard.git_gc_repos=Виконати очиÑтку ÑÐ¼Ñ–Ñ‚Ñ‚Ñ Ð´Ð»Ñ Ð²ÑÑ–Ñ… репозиторіїв dashboard.resync_all_sshkeys=Оновити файл '.ssh/authorized_keys' з SSH ключами Gitea. dashboard.resync_all_sshprincipals=Оновіть файл '.ssh/authorized_princÑ‚ipals' з SSH даними кориÑтувача Gitea. -dashboard.resync_all_hooks=ПереÑинхронізувати перед-прийнÑтні, оновлюючі та поÑÑ‚-прийнÑтні хуки в уÑÑ–Ñ… репозиторіÑÑ…. -dashboard.reinit_missing_repos=Переініціалізувати уÑÑ– репозитрії git-файли Ñких втрачено +dashboard.resync_all_hooks=Заново Ñинхронізувати хуки попереднього отриманнÑ, Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ Ñ‚Ð° поÑÑ‚-Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð²ÑÑ–Ñ… Ñховищ. +dashboard.reinit_missing_repos=Заново ініціалізувати вÑÑ– відÑутні Ñховища Git'а, Ð´Ð»Ñ Ñких Ñ–Ñнують запиÑи dashboard.sync_external_users=Синхронізувати дані зовнішніх кориÑтувачів -dashboard.cleanup_hook_task_table=ОчиÑтити hook_task таблицю -dashboard.server_uptime=Uptime Ñерверу +dashboard.cleanup_hook_task_table=ОчиÑтити таблицю hook_task +dashboard.server_uptime=Ð§Ð°Ñ Ñ€Ð¾Ð±Ð¾Ñ‚Ð¸ Ñервера dashboard.current_goroutine=Поточна кількіÑть Goroutines dashboard.current_memory_usage=Поточне викориÑÑ‚Ð°Ð½Ð½Ñ Ð¿Ð°Ð¼'Ñті dashboard.total_memory_allocated=Виділено пам'Ñті загалом @@ -2499,6 +2792,7 @@ users.admin=ÐдмініÑтратор users.restricted=Обмежено users.reserved=Зарезервовано users.bot=Бот +users.remote=Віддалений users.2fa=2FA users.repos=Репозиторії users.created=Створено @@ -2526,8 +2820,8 @@ users.allow_create_organization=Може Ñтворювати організац users.update_profile=Оновити обліковий Ð·Ð°Ð¿Ð¸Ñ users.delete_account=Видалити цей обліковий Ð·Ð°Ð¿Ð¸Ñ users.cannot_delete_self=Ви не можете видалити Ñебе -users.still_own_repo=Ваш обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ñе ще володіє одним або кількома репозиторіÑми, Ñпочатку вам потрібно видалити або передати Ñ—Ñ…. -users.still_has_org=Цей обліковий Ð·Ð°Ð¿Ð¸Ñ Ð²Ñе ще Ñ” учаÑником однієї або декількох організацій. Ð”Ð»Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð²Ð¶ÐµÐ½Ð½Ñ, покиньте або видаліть організації. +users.still_own_repo=Цей кориÑтувач вÑе ще володіє одним або кількома Ñховищами. Спочатку видаліть або передайте ці Ñховища. +users.still_has_org=Цей кориÑтувач Ñ” членом організації. Спочатку видаліть кориÑтувача з уÑÑ–Ñ… організацій. users.purge=Видалити кориÑтувача users.deletion_success=Обліковий Ð·Ð°Ð¿Ð¸Ñ ÐºÐ¾Ñ€Ð¸Ñтувача було видалено. users.reset_2fa=Скинути 2FA @@ -2539,14 +2833,14 @@ users.list_status_filter.is_admin=ÐдмініÑтратор users.list_status_filter.not_admin=Ðе адмініÑтратор users.list_status_filter.is_restricted=З обмеженнÑми users.list_status_filter.not_restricted=Без обмежень -users.list_status_filter.is_prohibit_login=Вхід заборонено -users.list_status_filter.not_prohibit_login=Вхід дозволено +users.list_status_filter.is_prohibit_login=Заборонити вхід +users.list_status_filter.not_prohibit_login=Дозволити вхід users.list_status_filter.is_2fa_enabled=2FA увімкнена users.list_status_filter.not_2fa_enabled=2FA вимкнена users.details=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ кориÑтувача emails.email_manage_panel=Ð£Ð¿Ñ€Ð°Ð²Ð»Ñ–Ð½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¾ÑŽ кориÑтувача -emails.primary=Головний +emails.primary=ОÑновна emails.activated=Ðктивовано emails.filter_sort.email=Електронна пошта emails.filter_sort.email_reverse=Електронна пошта (зворотна) @@ -2554,12 +2848,12 @@ emails.filter_sort.name=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача emails.filter_sort.name_reverse=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача (зворотне) emails.updated=Електронну пошту оновлено emails.not_updated=Ðе вдалоÑÑŒ оновити адреÑу електронної пошти: %v -emails.duplicate_active=Ð¦Ñ ÐµÐ»ÐµÐºÑ‚Ñ€Ð¾Ð½Ð½Ð° адреÑа вже активна Ð´Ð»Ñ Ñ–Ð½ÑˆÐ¾Ð³Ð¾ кориÑтувача. +emails.duplicate_active=Ð¦Ñ Ð°Ð´Ñ€ÐµÑа електронної пошти вже активна Ð´Ð»Ñ Ñ–Ð½ÑˆÐ¾Ð³Ð¾ кориÑтувача. emails.change_email_header=Редагувати влаÑтивоÑті електронної пошти emails.change_email_text=Ви впевнені, що хочете оновити адреÑу електронної пошти? -emails.delete_desc=Ви впевнені, що бажаєте видалити цю електронну адреÑу? -emails.deletion_success=Електронну адреÑу видалено. -emails.delete_primary_email_error=Ви не можете видалити оÑновну електронну адреÑу. +emails.delete_desc=Ви впевнені, що хочете видалити адреÑу електронної пошти? +emails.deletion_success=ÐдреÑу електронної пошти видалено. +emails.delete_primary_email_error=Ви не можете видалити оÑновну адреÑу електронної пошти. orgs.org_manage_panel=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð¾Ñ€Ð³Ð°Ð½Ñ–Ð·Ð°Ñ†Ñ–Ñми orgs.name=Ðазва @@ -2600,7 +2894,7 @@ systemhooks.update_webhook=Оновити ÑиÑтемний вебхук auths.auth_manage_panel=ÐšÐµÑ€ÑƒÐ²Ð°Ð½Ð½Ñ Ð´Ð¶ÐµÑ€ÐµÐ»Ð¾Ð¼ автентифікації auths.new=Додати джерело автентифікації -auths.name=Ім'Ñ +auths.name=Ðазва auths.type=Тип auths.enabled=Увімкнено auths.syncenabled=Увімкнути Ñинхронізацію кориÑтувача @@ -2617,13 +2911,13 @@ auths.user_base=База пошуку кориÑтувачів auths.user_dn=DN кориÑтувача auths.attribute_username=Ðтрибут імені кориÑтувача auths.attribute_username_placeholder=Залиште порожнім, щоб викориÑтовувати ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача Ð´Ð»Ñ Ñ€ÐµÑ”Ñтрації. -auths.attribute_name=Ðтрибут імені -auths.attribute_surname=Ðтрибут Surname -auths.attribute_mail=Ðтрибут Email -auths.attribute_ssh_public_key=Ðтрибут Відкритий SSH ключ +auths.attribute_name=ВлаÑтивоÑті імені +auths.attribute_surname=ВлаÑтивоÑті прізвища +auths.attribute_mail=ВлаÑтивоÑті електронної пошти +auths.attribute_ssh_public_key=ВлаÑтивоÑті публічного ключа SSH auths.attribute_avatar=ВлаÑтивоÑті аватару auths.attributes_in_bind=ВитÑгувати атрибути в контекÑті Bind DN -auths.allow_deactivate_all=Дозволити порожньому результату пошуку відключити вÑÑ–Ñ… кориÑтувачів +auths.allow_deactivate_all=Дозволити порожній результат пошуку, щоб деактивувати вÑÑ–Ñ… кориÑтувачів auths.use_paged_search=ВикориÑтовувати поÑторінковий пошук auths.search_page_size=Розмір Ñторінки auths.filter=КориÑтувацький фільтр @@ -2649,7 +2943,7 @@ auths.disable_helo=Вимкнути HELO auths.pam_service_name=Ім'Ñ Ñлужби PAM auths.pam_email_domain=Поштовий домен PAM (необов'Ñзково) auths.oauth2_provider=ПоÑтачальник OAuth2 -auths.oauth2_icon_url=URL іконки +auths.oauth2_icon_url=URL піктограми auths.oauth2_clientID=ID клієнта (ключ) auths.oauth2_clientSecret=Ключ клієнта auths.openIdConnectAutoDiscoveryURL=OpenID Connect URL Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ð·Ð°Ñ†Ñ–Ñ— входу @@ -2661,6 +2955,7 @@ auths.oauth2_emailURL=URL електронної пошти auths.skip_local_two_fa=ПропуÑтити локальну 2FA auths.skip_local_two_fa_helper=Якщо Ð·Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ Ð½Ðµ вказано, локальнам кориÑтувачам, що викориÑтовують двофакторну автентифікацію, вÑе одно проходитимуть Ñ—Ñ— Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ в ÑиÑтему auths.oauth2_tenant=Tenant +auths.oauth2_map_group_to_team_removal=Видалити кориÑтувачів із Ñинхронізованих команд, Ñкщо кориÑтувач не належить до відповідної групи. auths.enable_auto_register=Увімкнути автоматичну реєÑтрацію auths.sspi_auto_create_users=Ðвтоматично Ñтворювати кориÑтувачів auths.sspi_auto_create_users_helper=Дозволити автоматичне ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð½Ð¾Ð²Ð¸Ñ… облікових запиÑів Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів, Ñкі вперше увійшли з викориÑÑ‚Ð°Ð½Ð½Ñ Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ— SSPI @@ -2674,28 +2969,33 @@ auths.sspi_default_language=Типова мова кориÑтувача auths.sspi_default_language_helper=Типова мова Ð´Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів, Ñкі ÑтворюютьÑÑ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡Ð½Ð¾ при SSPI-автентифікації. Залиште не вказаним, Ñкщо надаєте перевагу автоматичному визначенню мови. auths.tips=Поради auths.tips.oauth2.general=OAuth2 Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ +auths.tips.oauth2.general.tip=Під Ñ‡Ð°Ñ Ñ€ÐµÑ”Ñтрації нової автентифікації OAuth2 URL-адреÑа зворотного виклику/Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð½Ñ Ð¿Ð¾Ð²Ð¸Ð½Ð½Ð° бути такою: auths.tip.oauth2_provider=ПоÑтачальник OAuth2 +auths.tip.bitbucket=`ЗареєÑтруйте нового Ñпоживача OAuth на %s Ñ– додайте дозвіл "Обліковий запиÑ" - "ЧитаннÑ"` auths.tip.nextcloud=`ЗареєÑтруйте нового Ñпоживача OAuth у вашому екземплÑрі за допомогою наÑтупного меню "ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ -> Безпека -> клієнт OAuth 2.0"` auths.tip.dropbox=Створити новий додаток на %s -auths.tip.gitlab_new=ЗареєÑтрувати новий додаток на %s +auths.tip.facebook=`ЗареєÑтруйте новий додаток на %s Ñ– додайте модуль "Facebook Login"` +auths.tip.github=ЗареєÑтруйте новий додаток OAuth на %s +auths.tip.gitlab_new=ЗареєÑтруйте новий додаток на %s auths.tip.google_plus=Отримайте облікові дані клієнта OAuth2 з конÑолі Google API за адреÑою %s +auths.tip.twitter=Перейдіть до %s, Ñтворіть додаток Ñ– переконайтеÑÑ, що параметр «Дозволити викориÑтовувати цей додаток Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ в Twitter» увімкнено auths.tip.discord=ЗареєÑтрувати новий додаток на %s auths.tip.gitea=ЗареєÑтруйте новий додаток OAuth2. ПоÑібник можна знайти за поÑиланнÑм %s auths.tip.mastodon=Введіть URL Ñпеціального екземплÑра Ð´Ð»Ñ ÐµÐºÐ·ÐµÐ¼Ð¿Ð»Ñра mastodon, Ñкий ви хочете автентифікувати за допомогою (або викориÑтовувати за замовчуваннÑм) auths.edit=Редагувати джерело автентифікації -auths.activated=Ð¦Ñ Ð°ÑƒÑ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ Ð°ÐºÑ‚Ð¸Ð²Ð¾Ð²Ð°Ð½Ð° +auths.activated=Це джерело автентифікації активовано auths.new_success=Ðвтентифікацію "%s" додано. -auths.update_success=Параметри аутентифікації оновлені. +auths.update_success=Джерело автентифікації оновлено. auths.update=Оновити джерело автентифікації auths.delete=Видалити джерело автентифікації auths.delete_auth_title=Видалити джерело автентифікації -auths.delete_auth_desc=Це джерело аутентифікації буде видалене, ви впевнені, що ви хочете продовжити? -auths.still_in_used=Ð¦Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ° ÑправжноÑті доÑÑ– викориÑтовуєтьÑÑ Ð´ÐµÑкими кориÑтувачами. Видаліть або змініть Ð´Ð»Ñ Ñ†Ð¸Ñ… кориÑтувачів тип входу в ÑиÑтему. -auths.deletion_success=Канал аутентифікації уÑпішно знищений. +auths.delete_auth_desc=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð´Ð¶ÐµÑ€ÐµÐ»Ð° автентифікації заборонÑÑ” кориÑтувачам викориÑтовувати його Ð´Ð»Ñ Ð²Ñ…Ð¾Ð´Ñƒ. Продовжити? +auths.still_in_used=Джерело автентифікації вÑе ще викориÑтовуєтьÑÑ. Спочатку перетворіть або видаліть кориÑтувачів, Ñкі викориÑтовують це джерело автентифікації. +auths.deletion_success=Джерело автентифікації видалено. auths.login_source_exist=Джерело автентифікації "%s" вже Ñ–Ñнує. -auths.login_source_of_type_exist=Джерело автентифікації такого типу вже наÑвне. +auths.login_source_of_type_exist=Джерело автентифікації такого типу вже Ñ–Ñнує. -config.server_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ñервера +config.server_config=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ñервера config.app_name=Ðазва Ñайту config.app_ver=ВерÑÑ–Ñ Gitea config.app_url=Базова URL-адреÑа Gitea @@ -2703,14 +3003,14 @@ config.custom_conf=ШлÑÑ… до файлу конфігурації config.custom_file_root_path=ШлÑÑ… до файлу кориÑтувача config.domain=Домен Ñервера config.offline_mode=Локальний режим -config.disable_router_log=Вимкнути Ð»Ð¾Ð³ÑƒÐ²Ð°Ð½Ð½Ñ Ñ€Ð¾ÑƒÑ‚ÐµÑ€Ñƒ +config.disable_router_log=Вимкнути журнал маршрутизатора config.run_user=ЗапуÑк від імені КориÑтувача config.run_mode=Режим Ð²Ð¸ÐºÐ¾Ð½Ð°Ð½Ð½Ñ config.git_version=ВерÑÑ–Ñ Git config.app_data_path=ШлÑÑ… до даних додатка config.repo_root_path=Кореневий шлÑÑ… Ñ€ÐµÐ¿Ð¾Ð·Ð¸Ñ‚Ð¾Ñ€Ñ–Ñ -config.lfs_root_path=Кореневої шлÑÑ… LFS -config.log_file_root_path=ШлÑÑ… до лог файлу +config.lfs_root_path=Кореневий шлÑÑ… LFS +config.log_file_root_path=ШлÑÑ… до журналу config.script_type=Тип Ñкрипта config.reverse_auth_user=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача Ð´Ð»Ñ Ð°Ð²Ñ‚Ð¾Ñ€Ð¸Ð·Ð°Ñ†Ñ–Ñ— на reverse proxy @@ -2720,7 +3020,7 @@ config.ssh_start_builtin_server=ВикориÑтовувати вбудованРconfig.ssh_domain=Домен SSH Ñервера config.ssh_port=Порт config.ssh_listen_port=Порт що проÑлуховуєтьÑÑ -config.ssh_root_path=ШлÑÑ… до кореню +config.ssh_root_path=ШлÑÑ… до ÐºÐ¾Ñ€ÐµÐ½Ñ config.ssh_minimum_key_size_check=Мінімальний розмір ключа перевірки config.ssh_minimum_key_sizes=Мінімальні розміри ключів @@ -2732,7 +3032,7 @@ config.lfs_http_auth_expiry=ЗаÑтаріла LFS HTTP Ð°ÑƒÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ config.db_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ð±Ð°Ð·Ð¸ даних config.db_type=Тип config.db_host=ХоÑÑ‚ -config.db_name=Ім'Ñ +config.db_name=Ðазва config.db_user=Ім'Ñ ÐºÐ¾Ñ€Ð¸Ñтувача config.db_schema=Схема config.db_ssl_mode=SSL @@ -2768,7 +3068,7 @@ config.skip_tls_verify=ПропуÑтити перевірку TLS config.mailer_config=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð¾ÑˆÑ‚Ð¸ config.mailer_enabled=Увімкнено config.mailer_enable_helo=Увімкнути HELO -config.mailer_name=Ім'Ñ +config.mailer_name=Ðазва config.mailer_protocol=Протокол config.mailer_smtp_addr=ÐдреÑа SMTP config.mailer_smtp_port=Порт SMTP @@ -2777,6 +3077,7 @@ config.mailer_use_sendmail=ВикориÑтовувати Sendmail config.mailer_sendmail_path=ШлÑÑ… до Sendmail config.mailer_sendmail_args=Додаткові аргументи до Sendmail config.mailer_sendmail_timeout=Тайм-аут Sendmail +config.mailer_use_dummy=Даммі config.test_email_placeholder=ÐдреÑа електронної пошти (наприклад, test@example.com) config.send_test_mail=Відправити теÑтового лиÑта config.send_test_mail_submit=ÐадіÑлати @@ -2794,6 +3095,7 @@ config.cache_item_ttl=Ð§Ð°Ñ Ð·Ð±ÐµÑ€Ñ–Ð³Ð°Ð½Ð½Ñ Ð´Ð°Ð½Ð¸Ñ… кешу config.cache_test=Перевірити кеш config.cache_test_failed=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€Ð¸Ñ‚Ð¸ кеш: %v. config.cache_test_slow=Перевірка кешу уÑпішна, але відповідь повільна: %s. +config.cache_test_succeeded=ТеÑÑ‚ кешу уÑпішно завершено, отримано відповідь за %s. config.session_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ ÑеÑÑ–Ñ— config.session_provider=Провайдер ÑеÑÑ–Ñ— @@ -2825,6 +3127,7 @@ config.log_config=ÐšÐ¾Ð½Ñ„Ñ–Ð³ÑƒÑ€Ð°Ñ†Ñ–Ñ Ð¶ÑƒÑ€Ð½Ð°Ð»Ñƒ config.logger_name_fmt=Журнал: %s config.disabled_logger=Вимкнено config.access_log_mode=Режим доÑтупу до журналу +config.access_log_template=Шаблон журналу доÑтупу config.xorm_log_sql=Журнал SQL config.set_setting_failed=Ðе вдалоÑÑ Ð²Ñтановити параметр %s @@ -2832,12 +3135,14 @@ config.set_setting_failed=Ðе вдалоÑÑ Ð²Ñтановити Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ monitor.stats=СтатиÑтика monitor.cron=Ð—Ð°Ð²Ð´Ð°Ð½Ð½Ñ cron -monitor.name=Ім'Ñ +monitor.name=Ðазва monitor.schedule=Розклад monitor.next=ÐаÑтупного разу monitor.previous=Попереднього разу monitor.execute_times=КількіÑть виконань monitor.process=Запущені процеÑи +monitor.performance_logs=Журнал швидкодії +monitor.processes_count=%d процеÑи(-ів) monitor.download_diagnosis_report=Завантажити звіт діагноÑтики monitor.desc=ÐžÐ¿Ð¸Ñ monitor.start=Ð§Ð°Ñ Ð¿Ð¾Ñ‡Ð°Ñ‚ÐºÑƒ @@ -2854,6 +3159,7 @@ monitor.queue.type=Тип monitor.queue.exemplar=Приклад типу monitor.queue.numberworkers=КількіÑть робочих потоків monitor.queue.maxnumberworkers=МакÑимальна кількіÑть робочих потоків +monitor.queue.numberinqueue=Ðомер у черзі monitor.queue.settings.title=ÐÐ°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿ÑƒÐ»Ñƒ monitor.queue.settings.maxnumberworkers=МакÑимальна кількіÑть робочих потоків monitor.queue.settings.maxnumberworkers.placeholder=Поточний %[1]d @@ -2879,6 +3185,12 @@ notices.delete_success=Ð¡Ð¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ ÑиÑтеми були видале self_check.no_problem_found=Ðаразі проблем не виÑвлено. self_check.startup_warnings=ÐŸÐ¾Ð¿ÐµÑ€ÐµÐ´Ð¶ÐµÐ½Ð½Ñ Ð¿Ñ–Ð´ Ñ‡Ð°Ñ Ð·Ð°Ð¿ÑƒÑку: +self_check.database_collation_mismatch=ОчікуєтьÑÑ, що база даних викориÑтає зіÑтавленнÑ: %s +self_check.database_collation_case_insensitive=У базі даних викориÑтовуєтьÑÑ Ð·Ñ–ÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð½Ñ %s, що Ñ” нечутливим. Хоч Gitea може працювати з ним, у деÑких рідкіÑних випадках він може працювати не так, Ñк очікуєтьÑÑ. +self_check.database_inconsistent_collation_columns=База даних викориÑтовує зіÑÑ‚Ð°Ð²Ð»ÐµÐ½Ð½Ñ %s, але ці Ñтовпці викориÑтовують невідповідні зіÑтавленнÑ. Це може Ñпричинити деÑкі неÑподівані проблеми. +self_check.database_fix_mysql=Ð”Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів MySQL/MariaDB можна викориÑтати команду "gitea doctor convert" Ð´Ð»Ñ Ñ€Ð¾Ð·Ð²'ÑÐ·Ð°Ð½Ð½Ñ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼ з ÑортуваннÑм, або ви також можете розв'Ñзати проблему SQL командою "ALTER ... COLLATE ..." вручну. +self_check.database_fix_mssql=`Ð”Ð»Ñ ÐºÐ¾Ñ€Ð¸Ñтувачів MSSQL, наразі ви можете виправити цю проблему тільки через SQL запит "ALTER ... COLATE ..."` +self_check.location_origin_mismatch=Поточна URL-адреÑа (%[1]) не відповідає URL-адреÑÑ– Gitea (%[2]). Якщо ви викориÑтовуєте зворотний прокÑÑ–, переконайтеÑÑ, що заголовки "Host" та "X-Forwarded-Proto" вÑтановлені правильно. [action] create_repo=Ñтворив(ла) репозиторій <a href="%s">%s</a> @@ -2893,6 +3205,7 @@ reopen_pull_request=`повторно відкрив запит Ð·Ð»Ð¸Ñ‚Ñ‚Ñ <a comment_issue=`прокоментував задачу <a href="%[1]s">%[3]s#%[2]s</a>` comment_pull=`прокоментував запит Ð·Ð»Ð¸Ñ‚Ñ‚Ñ <a href="%[1]s">%[3]s#%[2]s</a>` merge_pull_request=`прийнÑв запит Ð·Ð»Ð¸Ñ‚Ñ‚Ñ <a href="%[1]s">%[3]s#%[2]s</a>` +auto_merge_pull_request=`автоматично об'єднано запит на Ð·Ð»Ð¸Ñ‚Ñ‚Ñ <a href="%[1]s">%[3]s#%[2]s</a>` transfer_repo=перенеÑено репозиторій <code>%s</code> у <a href="%s">%s</a> push_tag=Ñтворив мітку <a href="%[2]s">%[3]s</a> в <a href="%[1]s">%[4]s</a> delete_tag=видалено мітку %[2]s з <a href="%[1]s">%[3]s</a> @@ -2946,30 +3259,43 @@ pin=Прикріпити ÑÐ¿Ð¾Ð²Ñ–Ñ‰ÐµÐ½Ð½Ñ mark_as_read=Позначити Ñк прочитане mark_as_unread=Позначити Ñк непрочитане mark_all_as_read=Позначити вÑÑ– Ñк прочитані +subscriptions=Ðбонементи +watching=Стежить +no_subscriptions=Ðемає абонементів [gpg] default_key=ПідпиÑано типовим ключем error.extract_sign=Ðе вдалоÑÑ Ð²Ð¸Ñ‚Ñгти Ð¿Ñ–Ð´Ð¿Ð¸Ñ error.generate_hash=Ðе вдалоÑÑ Ð·Ð³ÐµÐ½ÐµÑ€ÑƒÐ²Ð°Ñ‚Ð¸ хеш коміту -error.no_committer_account=Ðккаунт кориÑтувача з таким Email не знайдено -error.no_gpg_keys_found=Ðе вдалоÑÑ Ð·Ð½Ð°Ð¹Ñ‚Ð¸ GPG ключ що відповідає даному підпиÑу +error.no_committer_account=Ðемає облікового запиÑу, прив'Ñзаного до адреÑи електронної пошти комітера +error.no_gpg_keys_found=Ð”Ð»Ñ Ñ†ÑŒÐ¾Ð³Ð¾ підпиÑу в базі даних не знайдено жодного відомого ключа error.not_signed_commit=ÐепідпиÑаний коміт -error.failed_retrieval_gpg_keys=Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ відповідний GPG ключ кориÑтувача +error.failed_retrieval_gpg_keys=Ðе вдалоÑÑ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ñ‚Ð¸ жодного ключа, прив'Ñзаного до облікового запиÑу комітера error.probable_bad_signature=УВÐГÐ! Хоча ключ з таким ID Ñ– Ñ” в базі, коміт не може бути ним перевірено! Цей коміт ПІДОЗРІЛИЙ. error.probable_bad_default_signature=УВÐГÐ! Хоча типовий ключ має цей ID, коміт не може бути ним перевірено! Цей коміт ПІДОЗРІЛИЙ. [units] -error.no_unit_allowed_repo=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” доÑтупу до жодного розділу цього репозиториÑ. -error.unit_not_allowed=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” доÑтупу до жодного розділу цього репозиториÑ. +unit=ÐžÐ´Ð¸Ð½Ð¸Ñ†Ñ Ð²Ð¸Ð¼Ñ–Ñ€ÑŽÐ²Ð°Ð½Ð½Ñ +error.no_unit_allowed_repo=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” доÑтупу до жодного розділу цього Ñховища. +error.unit_not_allowed=У Ð²Ð°Ñ Ð½ÐµÐ¼Ð°Ñ” доÑтупу до жодного розділу цього Ñховища. [packages] +title=Пакети +desc=Керувати пакетами Ñховища. +empty=Ðаразі пакети відÑутні. no_metadata=Ðемає метаданих. +empty.documentation=Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації про реєÑтр пакетів, дивітьÑÑ <a target="_blank" rel="noopener noreferrer" href="%s">документацію</a>. +empty.repo=Ви завантажили пакунок, але він тут не відображаєтьÑÑ? Перейдіть до <a href="%[1]s">Ð½Ð°Ð»Ð°ÑˆÑ‚ÑƒÐ²Ð°Ð½Ð½Ñ Ð¿Ð°ÐºÐµÑ‚Ñƒ</a> та під'єднайте його до цього Ñховища. +registry.documentation=Докладнішу інформацію про реєÑтр %s наведено у <a target="_blank" rel="noopener noreferrer" href="%s">документації</a>. filter.type=Тип filter.type.all=Ð’ÑÑ– filter.no_result=Ваш фільтр не дав результатів. filter.container.tagged=З міткою filter.container.untagged=Без мітки +published_by=%[1]s опубліковано <a href="%[2]s">%[3]s</a> +published_by_in=%[1]s опубліковано <a href="%[2]s">%[3]s</a> у <a href="%[4]s"><strong>%[5]s</strong></a> installation=Ð’ÑÑ‚Ð°Ð½Ð¾Ð²Ð»ÐµÐ½Ð½Ñ +about=Про цей пакет requirements=Вимоги dependencies=ЗалежноÑті keywords=Ключові Ñлова @@ -2985,73 +3311,132 @@ versions.view_all=ПереглÑнути вÑе dependency.version=ВерÑÑ–Ñ search_in_external_registry=Шукати в %s alpine.registry=Ðалаштуйте цей реєÑтр, додавши URL у ваш файл <code>/etc/apk/repositories</code>: +alpine.registry.key=Завантажте публічний ключ RSA реєÑтру в теку <code>/etc/apk/keys/</code> Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÐ²Ñ–Ñ€ÐºÐ¸ підпиÑу індекÑу: +alpine.registry.info=Виберіть $branch та $repository зі ÑпиÑку нижче. +alpine.install=Щоб вÑтановити пакет, виконайте наÑтупну команду: alpine.repository=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñховище alpine.repository.branches=Гілки alpine.repository.repositories=Репозиторії alpine.repository.architectures=Ðрхітектури +arch.registry=Додати Ñервер з відповідним Ñховищем та архітектурою до <code>/etc/pacman.conf</code>: +arch.install=Синхронізувати пакет з pacman: arch.repository=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñховище arch.repository.repositories=Репозиторії arch.repository.architectures=Ðрхітектури +cargo.registry=Ðалаштуйте цей реєÑтр у файлі конфігурації Cargo (наприклад, <code>~/.cargo/config.toml</code>): +cargo.install=Щоб вÑтановити пакет за допомогою Cargo, виконайте наÑтупну команду: +chef.registry=Ðалаштуйте цей реєÑтр у вашому файлі <code>~/.chef/config.rb</code>: +chef.install=Щоб вÑтановити пакет, виконайте наÑтупну команду: +composer.registry=Ðалаштуйте цей реєÑтр у вашому файлі <code>~/.composer/config.json</code>: +composer.install=Щоб вÑтановити пакет за допомогою Composer, виконайте наÑтупну команду: composer.dependencies=ЗалежноÑті -conan.details.repository=Репозиторій +composer.dependencies.development=ЗалежноÑті розробки +conan.details.repository=Сховище conan.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка: +conan.install=Щоб вÑтановити пакет за допомогою Conan, виконайте наÑтупну команду: +conda.registry=Ðалаштуйте цей реєÑтр Ñк Ñховище Conda у влаÑному файлі <code>.condarc</code>: +conda.install=Щоб вÑтановити пакет за допомогою Conda, виконайте наÑтупну команду: container.details.type=Тип Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð½Ñ container.details.platform=Платформа container.pull=Завантажити образ з командного Ñ€Ñдка: container.images=Образи container.multi_arch=ОС / Ðрхітектура +container.layers=Шари образів container.labels=Мітки container.labels.key=Ключ container.labels.value=Ð—Ð½Ð°Ñ‡ÐµÐ½Ð½Ñ cran.registry=Ðалаштуйте цей реєÑтр у вашому файлі <code>Rprofile.site</code>: +cran.install=Щоб вÑтановити пакет, виконайте наÑтупну команду: debian.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка: debian.registry.info=Оберіть $distribution та $component зі ÑпиÑку нижче. +debian.install=Щоб вÑтановити пакет, виконайте наÑтупну команду: debian.repository=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñховище debian.repository.distributions=ДиÑтрибутиви debian.repository.components=Компоненти debian.repository.architectures=Ðрхітектури +generic.download=Завантажити пакет з командного Ñ€Ñдка: +go.install=Ð’Ñтановити пакет із командного Ñ€Ñдка: helm.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка: +helm.install=Щоб вÑтановити пакет, виконайте наÑтупну команду: maven.registry=Ðалаштуйте цей реєÑтр у файлі <code>pom.xml</code> вашого проєкту: +maven.install=Щоб викориÑтати пакет, додайте наÑтупне до блоку <code>dependencies</code> у файлі <code>pom.xml</code>: maven.install2=Виконати з командного Ñ€Ñдка: maven.download=Щоб завантажити залежніÑть, виконайте в командному Ñ€Ñдку: nuget.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка: +nuget.install=Щоб вÑтановити пакет за допомогою NuGet, запуÑтіть наÑтупну команду: +nuget.dependency.framework=Цільовий фреймворк +npm.registry=Ðалаштувати цей реєÑтр у файлі вашого проєкту <code>.npmrc</code>: +npm.install=Щоб вÑтановити пакет за допомогою npm, виконайте наÑтупну команду: +npm.install2=або додайте до файлу package.json: npm.dependencies=ЗалежноÑті +npm.dependencies.development=ЗалежноÑті розробки npm.dependencies.optional=ÐеобовʼÑзкові залежноÑті +npm.details.tag=Мітка pypi.requires=Потрібен Python rpm.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка: +rpm.distros.redhat=на диÑтрибутивах на оÑнові RedHat +rpm.distros.suse=на диÑтрибутивах на оÑнові SUSE +rpm.install=Щоб вÑтановити пакет, виконайте наÑтупну команду: rpm.repository=Ð†Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ñ–Ñ Ð¿Ñ€Ð¾ Ñховище rpm.repository.architectures=Ðрхітектури +rpm.repository.multiple_groups=Цей пакет доÑтупний у багатьох групах. +rubygems.install=Щоб вÑтановити пакет за допомогою gem, виконайте наÑтупну команду: rubygems.install2=або додайте до Gemfile: +rubygems.dependencies.development=ЗалежноÑті розробки rubygems.required.ruby=Вимагає верÑÑ–ÑŽ Ruby rubygems.required.rubygems=Вимагає верÑÑ–ÑŽ RubyGem swift.registry=Ðалаштувати реєÑтр із командного Ñ€Ñдка: +swift.install=Додайте пакет у ваш файл <code>Package.swift</code>: +swift.install2=Ñ– виконайте наÑтупну команду: vagrant.install=Щоб додати Ð±Ð¾ÐºÑ Vagrant, виконайте наÑтупну команду: +settings.link=Прив'Ñзати пакет до Ñховища +settings.link.description=Якщо ви зв'Ñжете пакет зі Ñховищем, його буде вказано у ÑпиÑку пакетів Ñховища. settings.link.select=Обрати Ñховище settings.link.button=Оновити поÑÐ¸Ð»Ð°Ð½Ð½Ñ Ð½Ð° Ñховище +settings.delete=Видалити пакет +settings.delete.description=Ð’Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ Ð¿Ð°ÐºÐµÑ‚Ð° Ñ” оÑтаточним Ñ– не може бути ÑкаÑоване. +settings.delete.notice=Ви збираєтеÑÑŒ видалити %s (%s). Цю операцію неможливо ÑкаÑувати, ви впевнені? +settings.delete.success=Пакет видалено. +settings.delete.error=Ðе вдалоÑÑ Ð²Ð¸Ð´Ð°Ð»Ð¸Ñ‚Ð¸ пакет. owner.settings.cargo.title=Ð†Ð½Ð´ÐµÐºÑ Ñ€ÐµÑ”Ñтру Cargo owner.settings.cargo.initialize=Ініціалізувати Ñ–Ð½Ð´ÐµÐºÑ owner.settings.cargo.initialize.description=Ð”Ð»Ñ Ð²Ð¸ÐºÐ¾Ñ€Ð¸ÑÑ‚Ð°Ð½Ð½Ñ Ñ€ÐµÑ”Ñтру Cargo потрібне Ñпеціальне Ñховище індекÑів Git. ВикориÑÑ‚Ð°Ð½Ð½Ñ Ñ†ÑŒÐ¾Ð³Ð¾ параметра дозволить (повторно) Ñтворити та автоматично налаштувати Ñховище. owner.settings.cargo.initialize.error=Ðе вдалоÑÑ Ñ–Ð½Ñ–Ñ†Ñ–Ð°Ð»Ñ–Ð·ÑƒÐ²Ð°Ñ‚Ð¸ Ñ–Ð½Ð´ÐµÐºÑ Cargo: %v owner.settings.cargo.initialize.success=Ð†Ð½Ð´ÐµÐºÑ Cargo уÑпішно Ñтворено. owner.settings.cargo.rebuild=Перебудувати Ñ–Ð½Ð´ÐµÐºÑ +owner.settings.cargo.rebuild.description=Повторна збірка може бути кориÑною, Ñкщо Ñ–Ð½Ð´ÐµÐºÑ Ð½Ðµ Ñинхронізовано зі збереженими пакетами Cargo. owner.settings.cargo.rebuild.error=Ðе вдалоÑÑ Ð¿ÐµÑ€ÐµÐ±ÑƒÐ´ÑƒÐ²Ð°Ñ‚Ð¸ Ñ–Ð½Ð´ÐµÐºÑ Cargo: %v owner.settings.cargo.rebuild.success=Ð†Ð½Ð´ÐµÐºÑ Cargo уÑпішно перебудовано. +owner.settings.cleanuprules.title=Керувати правилами Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ owner.settings.cleanuprules.add=Додати правило Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ owner.settings.cleanuprules.edit=Редагувати правило Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ owner.settings.cleanuprules.none=Правила Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð²Ñ–Ð´Ñутні. Будь лаÑка, звернітьÑÑ Ð´Ð¾ документації. owner.settings.cleanuprules.preview=Попередній переглÑд правила Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ +owner.settings.cleanuprules.preview.overview=Заплановано Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð½Ñ %d пакетів. owner.settings.cleanuprules.preview.none=Правило Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð½Ðµ відповідає жодному пакету. owner.settings.cleanuprules.enabled=Увімкнено +owner.settings.cleanuprules.pattern_full_match=ЗаÑтоÑувати шаблон до повної назви пакета +owner.settings.cleanuprules.keep.title=ВерÑÑ–Ñ—, Ñкі відповідають цим правилам, зберігаютьÑÑ, навіть Ñкщо вони відповідають наведеному нижче правилу видаленнÑ. owner.settings.cleanuprules.keep.count=Залишити найновіші +owner.settings.cleanuprules.keep.count.1=1 верÑÑ–Ñ Ð½Ð° пакет +owner.settings.cleanuprules.keep.count.n=%d верÑÑ–Ñ—(-й) на пакет +owner.settings.cleanuprules.keep.pattern=Зберігати верÑÑ–Ñ—, що збігаютьÑÑ owner.settings.cleanuprules.remove.title=ВерÑÑ–Ñ—, Ñкі відповідають цим правилам, видалÑютьÑÑ, Ñкщо правило вище не вимагає Ñ—Ñ… збереженнÑ. +owner.settings.cleanuprules.remove.days=Видалити верÑÑ–Ñ—, Ñтаріші за +owner.settings.cleanuprules.remove.pattern=Видалити верÑÑ–Ñ—, що збігаютьÑÑ owner.settings.cleanuprules.success.update=Правило Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð¾Ð½Ð¾Ð²Ð»ÐµÐ½Ð¾. owner.settings.cleanuprules.success.delete=Правило Ð¾Ñ‡Ð¸Ñ‰ÐµÐ½Ð½Ñ Ð²Ð¸Ð´Ð°Ð»ÐµÐ½Ð¾. owner.settings.chef.title=РеєÑтр Chef +owner.settings.chef.keypair=Згенерувати ключову пару +owner.settings.chef.keypair.description=Ключова пара необхідна Ð´Ð»Ñ Ð°Ð²Ñ‚ÐµÐ½Ñ‚Ð¸Ñ„Ñ–ÐºÐ°Ñ†Ñ–Ñ— у реєÑтрі Chef. Якщо ви Ñтворювали ключову пару раніше, ÑÑ‚Ð²Ð¾Ñ€ÐµÐ½Ð½Ñ Ð½Ð¾Ð²Ð¾Ñ— пари призведе до ÑкаÑÑƒÐ²Ð°Ð½Ð½Ñ Ñтарої. [secrets] ; 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=Введіть короткий Ð¾Ð¿Ð¸Ñ (необов'Ñзково). @@ -3060,6 +3445,7 @@ actions=Дії unit.desc=Керувати діÑми +status.unknown=Ðевідомий status.success=УÑпіх status.failure=Ðевдача status.cancelled=СкаÑовано @@ -3071,12 +3457,15 @@ runners.name=Ðазва runners.owner_type=Тип runners.description=ÐžÐ¿Ð¸Ñ runners.labels=Мітки +runners.last_online=ОÑтанній раз онлайн runners.task_list.no_tasks=Ðаразі завдань немає. runners.task_list.run=ЗапуÑтити runners.task_list.status=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ runners.task_list.repository=Репозиторій runners.task_list.commit=Коміт runners.task_list.done_at=Завершено о +runners.update_runner=Оновити зміни +runners.status.unspecified=Ðевідомий runners.status.idle=ÐžÑ‡Ñ–ÐºÑƒÐ²Ð°Ð½Ð½Ñ runners.status.active=Ðктивний runners.version=ВерÑÑ–Ñ @@ -3084,10 +3473,14 @@ runners.reset_registration_token=Скинути реєÑтраційний тоРruns.all_workflows=Ð’ÑÑ– робочі процеÑи runs.commit=Коміт +runs.scheduled=Заплановано runs.pushed_by=завантажено runs.no_job=Робочий Ð¿Ñ€Ð¾Ñ†ÐµÑ Ð¿Ð¾Ð²Ð¸Ð½ÐµÐ½ міÑтити принаймні одну задачу +runs.actor=Ðктор runs.status=Ð¡Ñ‚Ð°Ñ‚ÑƒÑ +runs.actors_no_select=УÑÑ– актори runs.status_no_select=Ð’ÑÑ– ÑтатуÑи +runs.no_results=Збігів немає. runs.no_workflows.documentation=Ð”Ð»Ñ Ð¾Ñ‚Ñ€Ð¸Ð¼Ð°Ð½Ð½Ñ Ð´Ð¾Ð´Ð°Ñ‚ÐºÐ¾Ð²Ð¾Ñ— інформації про Gitea Дії, переглÑньте <a target="_blank" rel="noopener noreferrer" href="%s">документацію</a>. runs.empty_commit_message=(порожнє Ð¿Ð¾Ð²Ñ–Ð´Ð¾Ð¼Ð»ÐµÐ½Ð½Ñ ÐºÐ¾Ð¼Ñ–Ñ‚Ñƒ) runs.view_workflow_file=ПереглÑд файлу робочого процеÑу diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 989802b9db..b88377cce8 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -463,7 +463,7 @@ oauth_signin_submit=ç»‘å®šè´¦å· oauth.signin.error.general=å¤„ç†æŽˆæƒè¯·æ±‚时出错:%s。如果æ¤é”™è¯¯ä»ç„¶å˜åœ¨ï¼Œè¯·ä¸Žç«™ç‚¹ç®¡ç†å‘˜è”系。 oauth.signin.error.access_denied=授æƒè¯·æ±‚被拒ç»ã€‚ oauth.signin.error.temporarily_unavailable=授æƒå¤±è´¥ï¼Œå› ä¸ºè®¤è¯æœåŠ¡å™¨æš‚æ—¶ä¸å¯ç”¨ã€‚请ç¨åŽå†è¯•。 -oauth_callback_unable_auto_reg=自动注册已å¯ç”¨ï¼Œä½†OAuth2 æä¾›å•† %[1]s è¿”å›žç¼ºå¤±çš„å—æ®µï¼š%[2]sï¼Œæ— æ³•è‡ªåŠ¨åˆ›å»ºå¸æˆ·ï¼Œè¯·åˆ›å»ºæˆ–é“¾æŽ¥åˆ°ä¸€ä¸ªå¸æˆ·ï¼Œæˆ–è”系站点管ç†å‘˜ã€‚ +oauth_callback_unable_auto_reg=自动注册已å¯ç”¨ï¼Œä½† OAuth2 æä¾›å•† %[1]s è¿”å›žç¼ºå¤±çš„å—æ®µï¼š%[2]sï¼Œæ— æ³•è‡ªåŠ¨åˆ›å»ºå¸æˆ·ï¼Œè¯·åˆ›å»ºæˆ–é“¾æŽ¥åˆ°ä¸€ä¸ªå¸æˆ·ï¼Œæˆ–è”系站点管ç†å‘˜ã€‚ openid_connect_submit=连接 openid_connect_title=è¿žæŽ¥åˆ°çŽ°æœ‰çš„å¸æˆ· openid_connect_desc=所选的 OpenID URI 未知。在这里关è”ä¸€ä¸ªæ–°å¸æˆ·ã€‚ @@ -1161,8 +1161,8 @@ template.issue_labels=工啿 ‡ç¾ template.one_item=必须至少选择一个模æ¿é¡¹ template.invalid=必须选择一个模æ¿ä»“库 -archive.title=该仓库已被归档。您å¯ä»¥æŸ¥çœ‹æ–‡ä»¶å’Œå…‹éš†å®ƒï¼Œä½†ä¸èƒ½æŽ¨é€ã€æ‰“开工啿ˆ–åˆå¹¶è¯·æ±‚。 -archive.title_date=该仓库已于 %s 归档。您å¯ä»¥æŸ¥çœ‹æ–‡ä»¶æˆ–克隆它,但ä¸èƒ½æŽ¨é€ã€æ‰“开工啿ˆ–åˆå¹¶è¯·æ±‚。 +archive.title=该仓库已被归档。您å¯ä»¥æŸ¥çœ‹æ–‡ä»¶å’Œå…‹éš†å®ƒï¼Œä½†ä¸èƒ½æŽ¨é€ã€åˆ›å»ºå·¥å•或åˆå¹¶è¯·æ±‚。 +archive.title_date=该仓库已于 %s 归档。您å¯ä»¥æŸ¥çœ‹æ–‡ä»¶æˆ–克隆它,但ä¸èƒ½æŽ¨é€ã€åˆ›å»ºå·¥å•或åˆå¹¶è¯·æ±‚。 archive.issue.nocomment=æ¤ä»“åº“å·²å˜æ¡£ï¼Œæ‚¨ä¸èƒ½åœ¨æ¤å·¥å•æ·»åŠ è¯„è®ºã€‚ archive.pull.nocomment=æ¤ä»“åº“å·²å˜æ¡£ï¼Œæ‚¨ä¸èƒ½åœ¨æ¤åˆå¹¶è¯·æ±‚æ·»åŠ è¯„è®ºã€‚ @@ -1506,7 +1506,7 @@ issues.choose.blank_about=从默认模æ¿åˆ›å»ºä¸€ä¸ªå·¥å•。 issues.choose.ignore_invalid_templates=å·²å¿½ç•¥æ— æ•ˆæ¨¡æ¿ issues.choose.invalid_templates=å‘现了 %v ä¸ªæ— æ•ˆæ¨¡æ¿ issues.choose.invalid_config=问题é…置包å«é”™è¯¯ï¼š -issues.no_ref=分支/æ ‡è®°æœªæŒ‡å®š +issues.no_ref=分支/Gitæ ‡ç¾æœªæŒ‡å®š issues.create=åˆ›å»ºå·¥å• issues.new_label=åˆ›å»ºæ ‡ç¾ issues.new_label_placeholder=æ ‡ç¾åç§° @@ -2375,7 +2375,7 @@ settings.event_issues=å·¥å• settings.event_issues_desc=å·¥å•已打开ã€å·²å…³é—ã€å·²é‡æ–°æ‰“开或已编辑。 settings.event_issue_assign=å·¥å•已指派 settings.event_issue_assign_desc=å·¥å•å·²æŒ‡æ´¾æˆ–å–æ¶ˆæŒ‡æ´¾ã€‚ -settings.event_issue_label=å·²æ ‡è®°å·¥å• +settings.event_issue_label=å·¥å•å¢žåˆ æ ‡ç¾ settings.event_issue_label_desc=工啿 ‡ç¾å·²æ›´æ–°æˆ–清除。 settings.event_issue_milestone=å·¥å•å·²æ”¶å…¥é‡Œç¨‹ç¢‘ä¸ settings.event_issue_milestone_desc=å·¥å•å·²æ”¶å…¥æˆ–å–æ¶ˆæ”¶å…¥é‡Œç¨‹ç¢‘ä¸ã€‚ diff --git a/routers/api/packages/cargo/cargo.go b/routers/api/packages/cargo/cargo.go index 710c614c6e..57cb83404f 100644 --- a/routers/api/packages/cargo/cargo.go +++ b/routers/api/packages/cargo/cargo.go @@ -95,10 +95,7 @@ type SearchResultMeta struct { // https://doc.rust-lang.org/cargo/reference/registries.html#search func SearchPackages(ctx *context.Context) { - page := ctx.FormInt("page") - if page < 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) perPage := ctx.FormInt("per_page") paginator := db.ListOptions{ Page: page, diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go index c6c14e5cf4..3713805579 100644 --- a/routers/api/packages/composer/composer.go +++ b/routers/api/packages/composer/composer.go @@ -53,10 +53,7 @@ func ServiceIndex(ctx *context.Context) { // SearchPackages searches packages, only "q" is supported // https://packagist.org/apidoc#search-packages func SearchPackages(ctx *context.Context) { - page := ctx.FormInt("page") - if page < 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) perPage := ctx.FormInt("per_page") paginator := db.ListOptions{ Page: page, diff --git a/routers/api/packages/container/blob.go b/routers/api/packages/container/blob.go index c1ccbeb10d..2ea9b3839c 100644 --- a/routers/api/packages/container/blob.go +++ b/routers/api/packages/container/blob.go @@ -130,8 +130,8 @@ func getOrCreateUploadVersion(ctx context.Context, pi *packages_service.PackageI pv := &packages_model.PackageVersion{ PackageID: p.ID, CreatorID: pi.Owner.ID, - Version: container_model.UploadVersion, - LowerVersion: container_model.UploadVersion, + Version: container_module.UploadVersion, + LowerVersion: container_module.UploadVersion, IsInternal: true, MetadataJSON: "null", } diff --git a/routers/api/packages/container/manifest.go b/routers/api/packages/container/manifest.go index ea4de7e42c..0cbd46e943 100644 --- a/routers/api/packages/container/manifest.go +++ b/routers/api/packages/container/manifest.go @@ -151,7 +151,7 @@ func processOciImageManifest(ctx context.Context, mci *manifestCreationInfo, buf return err } - uploadVersion, err := packages_model.GetInternalVersionByNameAndVersion(ctx, mci.Owner.ID, packages_model.TypeContainer, mci.Image, container_model.UploadVersion) + uploadVersion, err := packages_model.GetInternalVersionByNameAndVersion(ctx, mci.Owner.ID, packages_model.TypeContainer, mci.Image, container_module.UploadVersion) if err != nil && err != packages_model.ErrPackageNotExist { return err } @@ -492,7 +492,7 @@ func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *pack pf, err := createFileFromBlobReference(ctx, pv, nil, &blobReference{ Digest: digest.Digest(manifestDigest), MediaType: mci.MediaType, - Name: container_model.ManifestFilename, + Name: container_module.ManifestFilename, File: &packages_model.PackageFileDescriptor{Blob: pb}, ExpectedSize: pb.Size, IsLead: true, @@ -505,7 +505,7 @@ func createManifestBlob(ctx context.Context, mci *manifestCreationInfo, pv *pack OwnerID: mci.Owner.ID, PackageType: packages_model.TypeContainer, VersionID: pv.ID, - Query: container_model.ManifestFilename, + Query: container_module.ManifestFilename, }) if err != nil { return nil, false, "", err diff --git a/routers/api/packages/nuget/api_v2.go b/routers/api/packages/nuget/api_v2.go index a726065ad0..801c60af13 100644 --- a/routers/api/packages/nuget/api_v2.go +++ b/routers/api/packages/nuget/api_v2.go @@ -246,21 +246,30 @@ type TypedValue[T any] struct { } type FeedEntryProperties struct { - Version string `xml:"d:Version"` - NormalizedVersion string `xml:"d:NormalizedVersion"` Authors string `xml:"d:Authors"` + Copyright string `xml:"d:Copyright,omitempty"` + Created TypedValue[time.Time] `xml:"d:Created"` Dependencies string `xml:"d:Dependencies"` Description string `xml:"d:Description"` - VersionDownloadCount TypedValue[int64] `xml:"d:VersionDownloadCount"` + DevelopmentDependency TypedValue[bool] `xml:"d:DevelopmentDependency"` DownloadCount TypedValue[int64] `xml:"d:DownloadCount"` - PackageSize TypedValue[int64] `xml:"d:PackageSize"` - Created TypedValue[time.Time] `xml:"d:Created"` + ID string `xml:"d:Id"` + IconURL string `xml:"d:IconUrl,omitempty"` + Language string `xml:"d:Language,omitempty"` LastUpdated TypedValue[time.Time] `xml:"d:LastUpdated"` - Published TypedValue[time.Time] `xml:"d:Published"` + LicenseURL string `xml:"d:LicenseUrl,omitempty"` + MinClientVersion string `xml:"d:MinClientVersion,omitempty"` + NormalizedVersion string `xml:"d:NormalizedVersion"` + Owners string `xml:"d:Owners,omitempty"` + PackageSize TypedValue[int64] `xml:"d:PackageSize"` ProjectURL string `xml:"d:ProjectUrl,omitempty"` + Published TypedValue[time.Time] `xml:"d:Published"` ReleaseNotes string `xml:"d:ReleaseNotes,omitempty"` RequireLicenseAcceptance TypedValue[bool] `xml:"d:RequireLicenseAcceptance"` - Title string `xml:"d:Title"` + Tags string `xml:"d:Tags,omitempty"` + Title string `xml:"d:Title,omitempty"` + Version string `xml:"d:Version"` + VersionDownloadCount TypedValue[int64] `xml:"d:VersionDownloadCount"` } type FeedEntry struct { @@ -353,21 +362,30 @@ func createEntry(l *linkBuilder, pd *packages_model.PackageDescriptor, withNames Author: metadata.Authors, Content: content, Properties: &FeedEntryProperties{ - Version: pd.Version.Version, - NormalizedVersion: pd.Version.Version, Authors: metadata.Authors, + Copyright: metadata.Copyright, + Created: createdValue, Dependencies: buildDependencyString(metadata), Description: metadata.Description, - VersionDownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount}, + DevelopmentDependency: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.DevelopmentDependency}, DownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount}, - PackageSize: TypedValue[int64]{Type: "Edm.Int64", Value: pd.CalculateBlobSize()}, - Created: createdValue, + ID: pd.Package.Name, + IconURL: metadata.IconURL, + Language: metadata.Language, LastUpdated: createdValue, - Published: createdValue, + LicenseURL: metadata.LicenseURL, + MinClientVersion: metadata.MinClientVersion, + NormalizedVersion: pd.Version.Version, + Owners: metadata.Owners, + PackageSize: TypedValue[int64]{Type: "Edm.Int64", Value: pd.CalculateBlobSize()}, ProjectURL: metadata.ProjectURL, + Published: createdValue, ReleaseNotes: metadata.ReleaseNotes, RequireLicenseAcceptance: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.RequireLicenseAcceptance}, - Title: pd.Package.Name, + Tags: metadata.Tags, + Title: metadata.Title, + Version: pd.Version.Version, + VersionDownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount}, }, } diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go index de8c7ef3ed..cb880c8bdb 100644 --- a/routers/api/packages/rubygems/rubygems.go +++ b/routers/api/packages/rubygems/rubygems.go @@ -14,6 +14,7 @@ import ( "strings" packages_model "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/optional" packages_module "code.gitea.io/gitea/modules/packages" rubygems_module "code.gitea.io/gitea/modules/packages/rubygems" @@ -309,7 +310,7 @@ func GetPackageInfo(ctx *context.Context) { apiError(ctx, http.StatusNotFound, nil) return } - infoContent, err := makePackageInfo(ctx, versions) + infoContent, err := makePackageInfo(ctx, versions, cache.NewEphemeralCache()) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return @@ -317,7 +318,7 @@ func GetPackageInfo(ctx *context.Context) { ctx.PlainText(http.StatusOK, infoContent) } -// GetAllPackagesVersions returns a custom text based format containing information about all versions of all rubygems. +// GetAllPackagesVersions returns a custom text-based format containing information about all versions of all rubygems. // ref: https://guides.rubygems.org/rubygems-org-compact-index-api/ func GetAllPackagesVersions(ctx *context.Context) { packages, err := packages_model.GetPackagesByType(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems) @@ -326,6 +327,7 @@ func GetAllPackagesVersions(ctx *context.Context) { return } + ephemeralCache := cache.NewEphemeralCache() out := &strings.Builder{} out.WriteString("---\n") for _, pkg := range packages { @@ -338,7 +340,7 @@ func GetAllPackagesVersions(ctx *context.Context) { continue } - info, err := makePackageInfo(ctx, versions) + info, err := makePackageInfo(ctx, versions, ephemeralCache) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return @@ -348,7 +350,14 @@ func GetAllPackagesVersions(ctx *context.Context) { _, _ = fmt.Fprintf(out, "%s ", pkg.Name) for i, v := range versions { sep := util.Iif(i == len(versions)-1, "", ",") - _, _ = fmt.Fprintf(out, "%s%s", v.Version, sep) + pd, err := packages_model.GetPackageDescriptorWithCache(ctx, v, ephemeralCache) + if errors.Is(err, util.ErrNotExist) { + continue + } else if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + writePackageVersionForList(pd.Metadata, v.Version, sep, out) } _, _ = fmt.Fprintf(out, " %x\n", md5.Sum([]byte(info))) } @@ -356,6 +365,16 @@ func GetAllPackagesVersions(ctx *context.Context) { ctx.PlainText(http.StatusOK, out.String()) } +func writePackageVersionForList(metadata any, version, sep string, out *strings.Builder) { + if metadata, _ := metadata.(*rubygems_module.Metadata); metadata != nil && metadata.Platform != "" && metadata.Platform != "ruby" { + // VERSION_PLATFORM (see comment above in GetAllPackagesVersions) + _, _ = fmt.Fprintf(out, "%s_%s%s", version, metadata.Platform, sep) + } else { + // VERSION only + _, _ = fmt.Fprintf(out, "%s%s", version, sep) + } +} + func writePackageVersionRequirements(prefix string, reqs []rubygems_module.VersionRequirement, out *strings.Builder) { out.WriteString(prefix) if len(reqs) == 0 { @@ -367,11 +386,21 @@ func writePackageVersionRequirements(prefix string, reqs []rubygems_module.Versi } } -func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion) (string, error) { +func writePackageVersionForDependency(version, platform string, out *strings.Builder) { + if platform != "" && platform != "ruby" { + // VERSION-PLATFORM (see comment below in makePackageVersionDependency) + _, _ = fmt.Fprintf(out, "%s-%s ", version, platform) + } else { + // VERSION only + _, _ = fmt.Fprintf(out, "%s ", version) + } +} + +func makePackageVersionDependency(ctx *context.Context, version *packages_model.PackageVersion, c *cache.EphemeralCache) (string, error) { // format: VERSION[-PLATFORM] [DEPENDENCY[,DEPENDENCY,...]]|REQUIREMENT[,REQUIREMENT,...] // DEPENDENCY: GEM:CONSTRAINT[&CONSTRAINT] // REQUIREMENT: KEY:VALUE (always contains "checksum") - pd, err := packages_model.GetPackageDescriptor(ctx, version) + pd, err := packages_model.GetPackageDescriptorWithCache(ctx, version, c) if err != nil { return "", err } @@ -388,8 +417,7 @@ func makePackageVersionDependency(ctx *context.Context, version *packages_model. } buf := &strings.Builder{} - buf.WriteString(version.Version) - buf.WriteByte(' ') + writePackageVersionForDependency(version.Version, metadata.Platform, buf) for i, dep := range metadata.RuntimeDependencies { sep := util.Iif(i == 0, "", ",") writePackageVersionRequirements(fmt.Sprintf("%s%s:", sep, dep.Name), dep.Version, buf) @@ -404,10 +432,10 @@ func makePackageVersionDependency(ctx *context.Context, version *packages_model. return buf.String(), nil } -func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion) (string, error) { +func makePackageInfo(ctx *context.Context, versions []*packages_model.PackageVersion, c *cache.EphemeralCache) (string, error) { ret := "---\n" for _, v := range versions { - dep, err := makePackageVersionDependency(ctx, v) + dep, err := makePackageVersionDependency(ctx, v, c) if err != nil { return "", err } diff --git a/routers/api/packages/rubygems/rubygems_test.go b/routers/api/packages/rubygems/rubygems_test.go new file mode 100644 index 0000000000..a07e12a7d3 --- /dev/null +++ b/routers/api/packages/rubygems/rubygems_test.go @@ -0,0 +1,41 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package rubygems + +import ( + "strings" + "testing" + + rubygems_module "code.gitea.io/gitea/modules/packages/rubygems" + + "github.com/stretchr/testify/assert" +) + +func TestWritePackageVersion(t *testing.T) { + buf := &strings.Builder{} + + writePackageVersionForList(nil, "1.0", " ", buf) + assert.Equal(t, "1.0 ", buf.String()) + buf.Reset() + + writePackageVersionForList(&rubygems_module.Metadata{Platform: "ruby"}, "1.0", " ", buf) + assert.Equal(t, "1.0 ", buf.String()) + buf.Reset() + + writePackageVersionForList(&rubygems_module.Metadata{Platform: "linux"}, "1.0", " ", buf) + assert.Equal(t, "1.0_linux ", buf.String()) + buf.Reset() + + writePackageVersionForDependency("1.0", "", buf) + assert.Equal(t, "1.0 ", buf.String()) + buf.Reset() + + writePackageVersionForDependency("1.0", "ruby", buf) + assert.Equal(t, "1.0 ", buf.String()) + buf.Reset() + + writePackageVersionForDependency("1.0", "os", buf) + assert.Equal(t, "1.0-os ", buf.String()) + buf.Reset() +} diff --git a/routers/api/v1/repo/issue_dependency.go b/routers/api/v1/repo/issue_dependency.go index 2048c76ea0..1b58beb7b6 100644 --- a/routers/api/v1/repo/issue_dependency.go +++ b/routers/api/v1/repo/issue_dependency.go @@ -77,10 +77,7 @@ func GetIssueDependencies(ctx *context.APIContext) { return } - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) limit := ctx.FormInt("limit") if limit == 0 { limit = setting.API.DefaultPagingNum @@ -328,10 +325,7 @@ func GetIssueBlocks(ctx *context.APIContext) { return } - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) limit := ctx.FormInt("limit") if limit <= 1 { limit = setting.API.DefaultPagingNum diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index d5840b4149..3094c1947c 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -298,10 +298,7 @@ func ListWikiPages(ctx *context.APIContext) { return } - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) limit := ctx.FormInt("limit") if limit <= 1 { limit = setting.API.DefaultPagingNum @@ -434,10 +431,7 @@ func ListPageRevisions(ctx *context.APIContext) { // get commit count - wiki revisions commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename) - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) // get Commit Count commitsHistory, err := wikiRepo.CommitsByFileAndRange( diff --git a/routers/install/install.go b/routers/install/install.go index 2962f3948f..4b3eba78b3 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "slices" "strconv" "strings" "time" @@ -99,11 +100,8 @@ func Install(ctx *context.Context) { curDBType := setting.Database.Type.String() var isCurDBTypeSupported bool - for _, dbType := range setting.SupportedDatabaseTypes { - if dbType == curDBType { - isCurDBTypeSupported = true - break - } + if slices.Contains(setting.SupportedDatabaseTypes, curDBType) { + isCurDBTypeSupported = true } if !isCurDBTypeSupported { curDBType = "mysql" diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 80d554b6e3..0f6f31b884 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -177,7 +177,7 @@ func parseOAuth2Config(form forms.AuthenticationForm) *oauth2.Source { customURLMapping = nil } var scopes []string - for _, s := range strings.Split(form.Oauth2Scopes, ",") { + for s := range strings.SplitSeq(form.Oauth2Scopes, ",") { s = strings.TrimSpace(s) if s != "" { scopes = append(scopes, s) diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go index 520f14e89f..5387d2b26f 100644 --- a/routers/web/admin/config.go +++ b/routers/web/admin/config.go @@ -61,7 +61,7 @@ func TestCache(ctx *context.Context) { func shadowPasswordKV(cfgItem, splitter string) string { fields := strings.Split(cfgItem, splitter) - for i := 0; i < len(fields); i++ { + for i := range fields { if strings.HasPrefix(fields[i], "password=") { fields[i] = "password=******" break diff --git a/routers/web/admin/diagnosis.go b/routers/web/admin/diagnosis.go index d040dbe0ba..5395529d66 100644 --- a/routers/web/admin/diagnosis.go +++ b/routers/web/admin/diagnosis.go @@ -16,13 +16,7 @@ import ( ) func MonitorDiagnosis(ctx *context.Context) { - seconds := ctx.FormInt64("seconds") - if seconds <= 1 { - seconds = 1 - } - if seconds > 300 { - seconds = 300 - } + seconds := min(max(ctx.FormInt64("seconds"), 1), 300) httplib.ServeSetHeaders(ctx.Resp, &httplib.ServeHeaderOptions{ ContentType: "application/zip", diff --git a/routers/web/admin/notice.go b/routers/web/admin/notice.go index 21a8ab0d17..e9d6abbe92 100644 --- a/routers/web/admin/notice.go +++ b/routers/web/admin/notice.go @@ -26,10 +26,7 @@ func Notices(ctx *context.Context) { ctx.Data["PageIsAdminNotices"] = true total := system_model.CountNotices(ctx) - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) notices, err := system_model.Notices(ctx, page, setting.UI.Admin.NoticePagingNum) if err != nil { diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go index 5122342259..1904bfee11 100644 --- a/routers/web/admin/packages.go +++ b/routers/web/admin/packages.go @@ -24,10 +24,7 @@ const ( // Packages shows all packages func Packages(ctx *context.Context) { - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) query := ctx.FormTrim("q") packageType := ctx.FormTrim("type") sort := ctx.FormTrim("sort") diff --git a/routers/web/auth/openid.go b/routers/web/auth/openid.go index 8c2d3276a8..2ef4a86022 100644 --- a/routers/web/auth/openid.go +++ b/routers/web/auth/openid.go @@ -349,10 +349,7 @@ func RegisterOpenIDPost(ctx *context.Context) { context.VerifyCaptcha(ctx, tplSignUpOID, form) } - length := setting.MinPasswordLength - if length < 256 { - length = 256 - } + length := max(setting.MinPasswordLength, 256) password, err := util.CryptoRandomString(int64(length)) if err != nil { ctx.RenderWithErr(err.Error(), tplSignUpOID, form) diff --git a/routers/web/devtest/devtest.go b/routers/web/devtest/devtest.go index 765931a730..a22d376579 100644 --- a/routers/web/devtest/devtest.go +++ b/routers/web/devtest/devtest.go @@ -132,7 +132,7 @@ func prepareMockDataBadgeActionsSvg(ctx *context.Context) { selectedStyle := ctx.FormString("style", badge.DefaultStyle) var badges []badge.Badge badges = append(badges, badge.GenerateBadge("啊啊啊啊啊啊啊啊啊啊啊啊", "🌞🌞🌞🌞🌞", "green")) - for r := rune(0); r < 256; r++ { + for r := range rune(256) { if unicode.IsPrint(r) { s := strings.Repeat(string(r), 15) badges = append(badges, badge.GenerateBadge(s, util.TruncateRunes(s, 7), "green")) diff --git a/routers/web/explore/code.go b/routers/web/explore/code.go index 8f6518a4fc..8bde983e30 100644 --- a/routers/web/explore/code.go +++ b/routers/web/explore/code.go @@ -5,6 +5,7 @@ package explore import ( "net/http" + "slices" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" @@ -94,11 +95,8 @@ func Code(ctx *context.Context) { loadRepoIDs := make([]int64, 0, len(searchResults)) for _, result := range searchResults { var find bool - for _, id := range loadRepoIDs { - if id == result.RepoID { - find = true - break - } + if slices.Contains(loadRepoIDs, result.RepoID) { + find = true } if !find { loadRepoIDs = append(loadRepoIDs, result.RepoID) diff --git a/routers/web/org/members.go b/routers/web/org/members.go index 2cbe75989a..61022d3f09 100644 --- a/routers/web/org/members.go +++ b/routers/web/org/members.go @@ -28,10 +28,7 @@ func Members(ctx *context.Context) { ctx.Data["Title"] = org.FullName ctx.Data["PageIsOrgMembers"] = true - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) opts := &organization.FindOrgMembersOpts{ Doer: ctx.Doer, diff --git a/routers/web/org/projects.go b/routers/web/org/projects.go index f423e9cb36..059cce8281 100644 --- a/routers/web/org/projects.go +++ b/routers/web/org/projects.go @@ -53,10 +53,7 @@ func Projects(ctx *context.Context) { isShowClosed := strings.ToLower(ctx.FormTrim("state")) == "closed" keyword := ctx.FormTrim("q") - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) var projectType project_model.Type if ctx.ContextUser.IsOrganization() { diff --git a/routers/web/repo/actions/actions.go b/routers/web/repo/actions/actions.go index b01d57084a..7f219811bd 100644 --- a/routers/web/repo/actions/actions.go +++ b/routers/web/repo/actions/actions.go @@ -377,10 +377,8 @@ func workflowDispatchConfig(w *model.Workflow) *WorkflowDispatch { if !decodeNode(w.RawOn, &val) { return nil } - for _, v := range val { - if v == "workflow_dispatch" { - return &WorkflowDispatch{} - } + if slices.Contains(val, "workflow_dispatch") { + return &WorkflowDispatch{} } case yaml.MappingNode: var val map[string]yaml.Node diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index 5d382ebd71..dc8a90b2ae 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -45,10 +45,7 @@ func Branches(ctx *context.Context) { ctx.Data["PageIsViewCode"] = true ctx.Data["PageIsBranches"] = true - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) pageSize := setting.Git.BranchesRangeSize kw := ctx.FormString("q") diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 9dd6988825..b3af138461 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -67,10 +67,7 @@ func Commits(ctx *context.Context) { commitsCount := ctx.Repo.CommitsCount - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) pageSize := ctx.FormInt("limit") if pageSize <= 0 { @@ -230,10 +227,7 @@ func FileHistory(ctx *context.Context) { return } - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) commits, err := ctx.Repo.GitRepo.CommitsByFileAndRange( git.CommitsByFileAndRangeOptions{ diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 8b99dd95da..de34a9375c 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -575,7 +575,13 @@ func PrepareCompareDiff( ctx.Data["CommitRepoLink"] = ci.HeadRepo.Link() ctx.Data["AfterCommitID"] = headCommitID - ctx.Data["ExpandNewPrForm"] = ctx.FormBool("expand") + + // follow GitHub's behavior: autofill the form and expand + newPrFormTitle := ctx.FormTrim("title") + newPrFormBody := ctx.FormTrim("body") + ctx.Data["ExpandNewPrForm"] = ctx.FormBool("expand") || ctx.FormBool("quick_pull") || newPrFormTitle != "" || newPrFormBody != "" + ctx.Data["TitleQuery"] = newPrFormTitle + ctx.Data["BodyQuery"] = newPrFormBody if (headCommitID == ci.CompareInfo.MergeBase && !ci.DirectComparison) || headCommitID == ci.CompareInfo.BaseCommitID { diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index cbcb3a3b21..1a090c9437 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -145,10 +145,6 @@ func editFile(ctx *context.Context, isNewFile bool) { } blob := entry.Blob() - if blob.Size() >= setting.UI.MaxDisplayFileSize { - ctx.NotFound(err) - return - } buf, dataRc, fInfo, err := getFileReader(ctx, ctx.Repo.Repository.ID, blob) if err != nil { @@ -162,22 +158,37 @@ func editFile(ctx *context.Context, isNewFile bool) { defer dataRc.Close() - ctx.Data["FileSize"] = blob.Size() - - // Only some file types are editable online as text. - if !fInfo.st.IsRepresentableAsText() || fInfo.isLFSFile { - ctx.NotFound(nil) - return + if fInfo.isLFSFile { + lfsLock, err := git_model.GetTreePathLock(ctx, ctx.Repo.Repository.ID, ctx.Repo.TreePath) + if err != nil { + ctx.ServerError("GetTreePathLock", err) + return + } + if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID { + ctx.NotFound(nil) + return + } } - d, _ := io.ReadAll(dataRc) + ctx.Data["FileSize"] = fInfo.fileSize - buf = append(buf, d...) - if content, err := charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true}); err != nil { - log.Error("ToUTF8: %v", err) - ctx.Data["FileContent"] = string(buf) + // Only some file types are editable online as text. + if fInfo.isLFSFile { + ctx.Data["NotEditableReason"] = ctx.Tr("repo.editor.cannot_edit_lfs_files") + } else if !fInfo.st.IsRepresentableAsText() { + ctx.Data["NotEditableReason"] = ctx.Tr("repo.editor.cannot_edit_non_text_files") + } else if fInfo.fileSize >= setting.UI.MaxDisplayFileSize { + ctx.Data["NotEditableReason"] = ctx.Tr("repo.editor.cannot_edit_too_large_file") } else { - ctx.Data["FileContent"] = content + d, _ := io.ReadAll(dataRc) + + buf = append(buf, d...) + if content, err := charset.ToUTF8(buf, charset.ConvertOpts{KeepBOM: true}); err != nil { + log.Error("ToUTF8: %v", err) + ctx.Data["FileContent"] = string(buf) + } else { + ctx.Data["FileContent"] = content + } } } else { // Append filename from query, or empty string to allow username the new file. @@ -277,9 +288,20 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b return } - operation := "update" + var operation string if isNewFile { operation = "create" + } else if form.Content.Has() { + // The form content only has data if the file is representable as text, is not too large and not in lfs. + operation = "update" + } else if ctx.Repo.TreePath != form.TreePath { + // If it doesn't have data, the only possible operation is a "rename" + operation = "rename" + } else { + // It should never happen, just in case + ctx.Flash.Error(ctx.Tr("error.occurred")) + ctx.HTML(http.StatusOK, tplEditFile) + return } if _, err := files_service.ChangeRepoFiles(ctx, ctx.Repo.Repository, ctx.Doer, &files_service.ChangeRepoFilesOptions{ @@ -292,7 +314,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b Operation: operation, FromTreePath: ctx.Repo.TreePath, TreePath: form.TreePath, - ContentReader: strings.NewReader(strings.ReplaceAll(form.Content, "\r", "")), + ContentReader: strings.NewReader(strings.ReplaceAll(form.Content.Value(), "\r", "")), }, }, Signoff: form.Signoff, @@ -795,7 +817,7 @@ func cleanUploadFileName(name string) string { // Rebase the filename name = util.PathJoinRel(name) // Git disallows any filenames to have a .git directory in them. - for _, part := range strings.Split(name, "/") { + for part := range strings.SplitSeq(name, "/") { if strings.ToLower(part) == ".git" { return "" } diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 61606f8c5f..deb3ae4f3a 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -13,6 +13,7 @@ import ( "os" "path/filepath" "regexp" + "slices" "strconv" "strings" "sync" @@ -363,12 +364,7 @@ func containsParentDirectorySeparator(v string) bool { if !strings.Contains(v, "..") { return false } - for _, ent := range strings.FieldsFunc(v, isSlashRune) { - if ent == ".." { - return true - } - } - return false + return slices.Contains(strings.FieldsFunc(v, isSlashRune), "..") } func isSlashRune(r rune) bool { return r == '/' || r == '\\' } diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index a4747964c6..54b7e5df2a 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -212,7 +212,7 @@ func getActionIssues(ctx *context.Context) issues_model.IssueList { return nil } issueIDs := make([]int64, 0, 10) - for _, stringIssueID := range strings.Split(commaSeparatedIssueIDs, ",") { + for stringIssueID := range strings.SplitSeq(commaSeparatedIssueIDs, ",") { issueID, err := strconv.ParseInt(stringIssueID, 10, 64) if err != nil { ctx.ServerError("ParseInt", err) diff --git a/routers/web/repo/issue_new.go b/routers/web/repo/issue_new.go index d8863961ff..887019b146 100644 --- a/routers/web/repo/issue_new.go +++ b/routers/web/repo/issue_new.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "html/template" + "maps" "net/http" "slices" "sort" @@ -136,9 +137,7 @@ func NewIssue(ctx *context.Context) { ret := issue_service.ParseTemplatesFromDefaultBranch(ctx.Repo.Repository, ctx.Repo.GitRepo) templateLoaded, errs := setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates, pageMetaData) - for k, v := range errs { - ret.TemplateErrors[k] = v - } + maps.Copy(ret.TemplateErrors, errs) if ctx.Written() { return } diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go index 8a26a0dcc3..dd53b1d3f1 100644 --- a/routers/web/repo/milestone.go +++ b/routers/web/repo/milestone.go @@ -38,10 +38,7 @@ func Milestones(ctx *context.Context) { isShowClosed := ctx.FormString("state") == "closed" sortType := ctx.FormString("sort") keyword := ctx.FormTrim("q") - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) miles, total, err := db.FindAndCount[issues_model.Milestone](ctx, issues_model.FindMilestoneOptions{ ListOptions: db.ListOptions{ diff --git a/routers/web/repo/packages.go b/routers/web/repo/packages.go index 65a340a799..d09a57c03f 100644 --- a/routers/web/repo/packages.go +++ b/routers/web/repo/packages.go @@ -21,10 +21,7 @@ const ( // Packages displays a list of all packages in the repository func Packages(ctx *context.Context) { - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) query := ctx.FormTrim("q") packageType := ctx.FormTrim("type") diff --git a/routers/web/repo/patch.go b/routers/web/repo/patch.go index ca346b7e6c..3ffd8f89c4 100644 --- a/routers/web/repo/patch.go +++ b/routers/web/repo/patch.go @@ -99,7 +99,7 @@ func NewDiffPatchPost(ctx *context.Context) { OldBranch: ctx.Repo.BranchName, NewBranch: branchName, Message: message, - Content: strings.ReplaceAll(form.Content, "\r", ""), + Content: strings.ReplaceAll(form.Content.Value(), "\r", ""), Author: gitCommitter, Committer: gitCommitter, }) diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index 0bf1f64d09..a57976b4ca 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -61,10 +61,7 @@ func Projects(ctx *context.Context) { isShowClosed := strings.ToLower(ctx.FormTrim("state")) == "closed" keyword := ctx.FormTrim("q") repo := ctx.Repo.Repository - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) ctx.Data["OpenCount"] = repo.NumOpenProjects ctx.Data["ClosedCount"] = repo.NumClosedProjects diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index 8e586adde9..1592bd4ced 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -103,7 +103,7 @@ func getReleaseInfos(ctx *context.Context, opts *repo_model.FindReleasesOptions) releaseInfos := make([]*ReleaseInfo, 0, len(releases)) for _, r := range releases { if r.Publisher, ok = cacheUsers[r.PublisherID]; !ok { - r.Publisher, err = user_model.GetUserByID(ctx, r.PublisherID) + r.Publisher, err = user_model.GetPossibleUserByID(ctx, r.PublisherID) if err != nil { if user_model.IsErrUserNotExist(err) { r.Publisher = user_model.NewGhostUser() diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 269ba17498..828ec08a8a 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -88,7 +88,7 @@ func checkContextUser(ctx *context.Context, uid int64) *user_model.User { } var orgsAvailable []*organization.Organization - for i := 0; i < len(orgs); i++ { + for i := range orgs { if ctx.Doer.CanCreateRepoIn(orgs[i].AsUser()) { orgsAvailable = append(orgsAvailable, orgs[i]) } diff --git a/routers/web/repo/setting/lfs.go b/routers/web/repo/setting/lfs.go index a065620b2b..bbbb99dc89 100644 --- a/routers/web/repo/setting/lfs.go +++ b/routers/web/repo/setting/lfs.go @@ -45,10 +45,7 @@ func LFSFiles(ctx *context.Context) { ctx.NotFound(nil) return } - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) total, err := git_model.CountLFSMetaObjects(ctx, ctx.Repo.Repository.ID) if err != nil { ctx.ServerError("LFSFiles", err) @@ -77,10 +74,7 @@ func LFSLocks(ctx *context.Context) { } ctx.Data["LFSFilesLink"] = ctx.Repo.RepoLink + "/settings/lfs" - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) total, err := git_model.CountLFSLockByRepoID(ctx, ctx.Repo.Repository.ID) if err != nil { ctx.ServerError("LFSLocks", err) diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go index f43433fb0d..ec0ad02828 100644 --- a/routers/web/repo/view_file.go +++ b/routers/web/repo/view_file.go @@ -285,10 +285,10 @@ func prepareToRenderFile(ctx *context.Context, entry *git.TreeEntry) { } } - prepareToRenderButtons(ctx, fInfo.isLFSFile, isRepresentableAsText, lfsLock) + prepareToRenderButtons(ctx, lfsLock) } -func prepareToRenderButtons(ctx *context.Context, isLFSFile, isRepresentableAsText bool, lfsLock *git_model.LFSLock) { +func prepareToRenderButtons(ctx *context.Context, lfsLock *git_model.LFSLock) { // archived or mirror repository, the buttons should not be shown if ctx.Repo.Repository.IsArchived || !ctx.Repo.Repository.CanEnableEditor() { return @@ -301,33 +301,16 @@ func prepareToRenderButtons(ctx *context.Context, isLFSFile, isRepresentableAsTe return } - if isLFSFile { - ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_lfs_files") - } else if !isRepresentableAsText { - ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.cannot_edit_non_text_files") - } - if !ctx.Repo.CanWriteToBranch(ctx, ctx.Doer, ctx.Repo.BranchName) { - if !isLFSFile { // lfs file cannot be edited after fork - ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit") - } + ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit") ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access") return } // it's a lfs file and the user is not the owner of the lock - if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID { - ctx.Data["CanEditFile"] = false - ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked") - ctx.Data["CanDeleteFile"] = false - ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked") - return - } - - if !isLFSFile { // lfs file cannot be edited - ctx.Data["CanEditFile"] = true - ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.edit_this_file") - } - ctx.Data["CanDeleteFile"] = true - ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.delete_this_file") + isLFSLocked := lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID + ctx.Data["CanEditFile"] = !isLFSLocked + ctx.Data["EditFileTooltip"] = util.Iif(isLFSLocked, ctx.Tr("repo.editor.this_file_locked"), ctx.Tr("repo.editor.edit_this_file")) + ctx.Data["CanDeleteFile"] = !isLFSLocked + ctx.Data["DeleteFileTooltip"] = util.Iif(isLFSLocked, ctx.Tr("repo.editor.this_file_locked"), ctx.Tr("repo.editor.delete_this_file")) } diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index 41bf9f5adb..a1e10c380d 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -417,10 +417,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) ctx.Data["CommitCount"] = commitsCount // get page - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) // get Commit Count commitsHistory, err := wikiRepo.CommitsByFileAndRange( diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go index d0139f6613..73f9970a07 100644 --- a/routers/web/repo/wiki_test.go +++ b/routers/web/repo/wiki_test.go @@ -245,7 +245,12 @@ func TestDefaultWikiBranch(t *testing.T) { assert.NoError(t, wiki_service.ChangeDefaultWikiBranch(db.DefaultContext, repoWithNoWiki, "main")) // repo with wiki - assert.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime(db.DefaultContext, &repo_model.Repository{ID: 1, DefaultWikiBranch: "wrong-branch"})) + assert.NoError(t, repo_model.UpdateRepositoryColsNoAutoTime( + db.DefaultContext, + &repo_model.Repository{ID: 1, DefaultWikiBranch: "wrong-branch"}, + "default_wiki_branch", + ), + ) ctx, _ := contexttest.MockContext(t, "user2/repo1/wiki") ctx.SetPathParam("*", "Home") diff --git a/routers/web/shared/actions/runners.go b/routers/web/shared/actions/runners.go index a642cfd66d..648f8046a4 100644 --- a/routers/web/shared/actions/runners.go +++ b/routers/web/shared/actions/runners.go @@ -108,10 +108,7 @@ func Runners(ctx *context.Context) { return } - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) opts := actions_model.FindRunnerOptions{ ListOptions: db.ListOptions{ @@ -179,10 +176,7 @@ func RunnersEdit(ctx *context.Context) { return } - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) runnerID := ctx.PathParamInt64("runnerid") ownerID := rCtx.OwnerID diff --git a/routers/web/user/code.go b/routers/web/user/code.go index f2153c6d54..20ab1405dd 100644 --- a/routers/web/user/code.go +++ b/routers/web/user/code.go @@ -5,6 +5,7 @@ package user import ( "net/http" + "slices" "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" @@ -87,11 +88,8 @@ func CodeSearch(ctx *context.Context) { loadRepoIDs := make([]int64, 0, len(searchResults)) for _, result := range searchResults { var find bool - for _, id := range loadRepoIDs { - if id == result.RepoID { - find = true - break - } + if slices.Contains(loadRepoIDs, result.RepoID) { + find = true } if !find { loadRepoIDs = append(loadRepoIDs, result.RepoID) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 607fb62acb..b53a3daedb 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -197,7 +197,7 @@ func Milestones(ctx *context.Context) { reposQuery = reposQuery[1 : len(reposQuery)-1] // for each ID (delimiter ",") add to int to repoIDs - for _, rID := range strings.Split(reposQuery, ",") { + for rID := range strings.SplitSeq(reposQuery, ",") { // Ensure nonempty string entries if rID != "" && rID != "0" { rIDint64, err := strconv.ParseInt(rID, 10, 64) @@ -520,10 +520,7 @@ func buildIssueOverview(ctx *context.Context, unitType unit.Type) { opts.IsClosed = optional.Some(isShowClosed) // Make sure page number is at least 1. Will be posted to ctx.Data. - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) opts.Paginator = &db.ListOptions{ Page: page, PageSize: setting.UI.IssuePagingNum, diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go index 2b758ba461..610a9b8076 100644 --- a/routers/web/user/notification.go +++ b/routers/web/user/notification.go @@ -203,10 +203,7 @@ func NotificationPurgePost(ctx *context.Context) { // NotificationSubscriptions returns the list of subscribed issues func NotificationSubscriptions(ctx *context.Context) { - page := ctx.FormInt("page") - if page < 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) sortType := ctx.FormString("sort") ctx.Data["SortType"] = sortType @@ -331,10 +328,7 @@ func NotificationSubscriptions(ctx *context.Context) { // NotificationWatching returns the list of watching repos func NotificationWatching(ctx *context.Context) { - page := ctx.FormInt("page") - if page < 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) keyword := ctx.FormTrim("q") ctx.Data["Keyword"] = keyword diff --git a/routers/web/user/package.go b/routers/web/user/package.go index 92af6d14ef..8c85fc22c7 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -46,10 +46,7 @@ func ListPackages(ctx *context.Context) { ctx.ServerError("RenderUserOrgHeader", err) return } - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) query := ctx.FormTrim("q") packageType := ctx.FormTrim("type") @@ -320,10 +317,7 @@ func ListPackageVersions(ctx *context.Context) { return } - page := ctx.FormInt("page") - if page <= 1 { - page = 1 - } + page := max(ctx.FormInt("page"), 1) pagination := &db.ListOptions{ PageSize: setting.UI.PackagesPagingNum, Page: page, diff --git a/services/auth/source/oauth2/urlmapping.go b/services/auth/source/oauth2/urlmapping.go index d0442d58a8..b9f445caa7 100644 --- a/services/auth/source/oauth2/urlmapping.go +++ b/services/auth/source/oauth2/urlmapping.go @@ -14,11 +14,11 @@ type CustomURLMapping struct { // CustomURLSettings describes the urls values and availability to use when customizing OAuth2 provider URLs type CustomURLSettings struct { - AuthURL Attribute `json:",omitempty"` - TokenURL Attribute `json:",omitempty"` - ProfileURL Attribute `json:",omitempty"` - EmailURL Attribute `json:",omitempty"` - Tenant Attribute `json:",omitempty"` + AuthURL Attribute + TokenURL Attribute + ProfileURL Attribute + EmailURL Attribute + Tenant Attribute } // Attribute describes the availability, and required status for a custom url configuration diff --git a/services/context/api.go b/services/context/api.go index 28f0e43d88..ab50a360f4 100644 --- a/services/context/api.go +++ b/services/context/api.go @@ -9,6 +9,7 @@ import ( "fmt" "net/http" "net/url" + "slices" "strconv" "strings" @@ -364,11 +365,5 @@ func (ctx *APIContext) IsUserRepoAdmin() bool { // IsUserRepoWriter returns true if current user has "write" privilege in current repo func (ctx *APIContext) IsUserRepoWriter(unitTypes []unit.Type) bool { - for _, unitType := range unitTypes { - if ctx.Repo.CanWrite(unitType) { - return true - } - } - - return false + return slices.ContainsFunc(unitTypes, ctx.Repo.CanWrite) } diff --git a/services/context/permission.go b/services/context/permission.go index 7055f798da..c0a5a98724 100644 --- a/services/context/permission.go +++ b/services/context/permission.go @@ -5,6 +5,7 @@ package context import ( "net/http" + "slices" auth_model "code.gitea.io/gitea/models/auth" repo_model "code.gitea.io/gitea/models/repo" @@ -34,10 +35,8 @@ func CanWriteToBranch() func(ctx *Context) { // RequireUnitWriter returns a middleware for requiring repository write to one of the unit permission func RequireUnitWriter(unitTypes ...unit.Type) func(ctx *Context) { return func(ctx *Context) { - for _, unitType := range unitTypes { - if ctx.Repo.CanWrite(unitType) { - return - } + if slices.ContainsFunc(unitTypes, ctx.Repo.CanWrite) { + return } ctx.NotFound(nil) } diff --git a/services/context/upload/upload.go b/services/context/upload/upload.go index 12aa485aa7..5edddc6f27 100644 --- a/services/context/upload/upload.go +++ b/services/context/upload/upload.go @@ -39,7 +39,7 @@ func Verify(buf []byte, fileName, allowedTypesStr string) error { allowedTypesStr = strings.ReplaceAll(allowedTypesStr, "|", ",") // compat for old config format allowedTypes := []string{} - for _, entry := range strings.Split(allowedTypesStr, ",") { + for entry := range strings.SplitSeq(allowedTypesStr, ",") { entry = strings.ToLower(strings.TrimSpace(entry)) if entry != "" { allowedTypes = append(allowedTypes, entry) diff --git a/services/feed/feed_test.go b/services/feed/feed_test.go index a3492938c8..705d42a2eb 100644 --- a/services/feed/feed_test.go +++ b/services/feed/feed_test.go @@ -147,7 +147,7 @@ func TestRepoActions(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) _ = db.TruncateBeans(db.DefaultContext, &activities_model.Action{}) - for i := 0; i < 3; i++ { + for i := range 3 { _ = db.Insert(db.DefaultContext, &activities_model.Action{ UserID: 2 + int64(i), ActUserID: 2, diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index a2827e516a..d11ad0a54c 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -10,6 +10,7 @@ import ( issues_model "code.gitea.io/gitea/models/issues" project_model "code.gitea.io/gitea/models/project" + "code.gitea.io/gitea/modules/optional" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web/middleware" "code.gitea.io/gitea/services/context" @@ -689,7 +690,7 @@ func (f *NewWikiForm) Validate(req *http.Request, errs binding.Errors) binding.E // EditRepoFileForm form for changing repository file type EditRepoFileForm struct { TreePath string `binding:"Required;MaxSize(500)"` - Content string + Content optional.Option[string] CommitSummary string `binding:"MaxSize(100)"` CommitMessage string CommitChoice string `binding:"Required;MaxSize(50)"` diff --git a/services/gitdiff/csv.go b/services/gitdiff/csv.go index 8db73c56a3..c10ee14490 100644 --- a/services/gitdiff/csv.go +++ b/services/gitdiff/csv.go @@ -134,7 +134,7 @@ func createCsvDiffSingle(reader *csv.Reader, celltype TableDiffCellType) ([]*Tab return nil, err } cells := make([]*TableDiffCell, len(row)) - for j := 0; j < len(row); j++ { + for j := range row { if celltype == TableDiffCellDel { cells[j] = &TableDiffCell{LeftCell: row[j], Type: celltype} } else { @@ -365,11 +365,11 @@ func getColumnMapping(baseCSVReader, headCSVReader *csvReader) ([]int, []int) { } // Loops through the baseRow and see if there is a match in the head row - for i := 0; i < len(baseRow); i++ { + for i := range baseRow { base2HeadColMap[i] = unmappedColumn baseCell, err := getCell(baseRow, i) if err == nil { - for j := 0; j < len(headRow); j++ { + for j := range headRow { if head2BaseColMap[j] == -1 { headCell, err := getCell(headRow, j) if err == nil && baseCell == headCell { @@ -390,7 +390,7 @@ func getColumnMapping(baseCSVReader, headCSVReader *csvReader) ([]int, []int) { // tryMapColumnsByContent tries to map missing columns by the content of the first lines. func tryMapColumnsByContent(baseCSVReader *csvReader, base2HeadColMap []int, headCSVReader *csvReader, head2BaseColMap []int) { - for i := 0; i < len(base2HeadColMap); i++ { + for i := range base2HeadColMap { headStart := 0 for base2HeadColMap[i] == unmappedColumn && headStart < len(head2BaseColMap) { if head2BaseColMap[headStart] == unmappedColumn { @@ -424,7 +424,7 @@ func getCell(row []string, column int) (string, error) { // countUnmappedColumns returns the count of unmapped columns. func countUnmappedColumns(mapping []int) int { count := 0 - for i := 0; i < len(mapping); i++ { + for i := range mapping { if mapping[i] == unmappedColumn { count++ } diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index a859945378..895cbe5a2f 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -540,10 +540,7 @@ func ParsePatch(ctx context.Context, maxLines, maxLineCharacters, maxFiles int, // OK let's set a reasonable buffer size. // This should be at least the size of maxLineCharacters or 4096 whichever is larger. - readerSize := maxLineCharacters - if readerSize < 4096 { - readerSize = 4096 - } + readerSize := max(maxLineCharacters, 4096) input := bufio.NewReaderSize(reader, readerSize) line, err := input.ReadString('\n') diff --git a/services/gitdiff/gitdiff_test.go b/services/gitdiff/gitdiff_test.go index 71394b1915..b84530043a 100644 --- a/services/gitdiff/gitdiff_test.go +++ b/services/gitdiff/gitdiff_test.go @@ -416,7 +416,7 @@ index 0000000..6bb8f39 ` diffBuilder.WriteString(diff) - for i := 0; i < 35; i++ { + for i := range 35 { diffBuilder.WriteString("+line" + strconv.Itoa(i) + "\n") } diff = diffBuilder.String() @@ -453,11 +453,11 @@ index 0000000..6bb8f39 diffBuilder.Reset() diffBuilder.WriteString(diff) - for i := 0; i < 33; i++ { + for i := range 33 { diffBuilder.WriteString("+line" + strconv.Itoa(i) + "\n") } diffBuilder.WriteString("+line33") - for i := 0; i < 512; i++ { + for range 512 { diffBuilder.WriteString("0123456789ABCDEF") } diffBuilder.WriteByte('\n') diff --git a/services/gitdiff/submodule_test.go b/services/gitdiff/submodule_test.go index 3047b23103..152c5b7066 100644 --- a/services/gitdiff/submodule_test.go +++ b/services/gitdiff/submodule_test.go @@ -203,7 +203,6 @@ index 0000000..68972a9 } for _, testcase := range tests { - testcase := testcase t.Run(testcase.name, func(t *testing.T) { diff, err := ParsePatch(db.DefaultContext, setting.Git.MaxGitDiffLines, setting.Git.MaxGitDiffLineCharacters, setting.Git.MaxGitDiffFiles, strings.NewReader(testcase.gitdiff), "") assert.NoError(t, err) diff --git a/services/lfs/locks.go b/services/lfs/locks.go index 1d464f4a66..264001f0f9 100644 --- a/services/lfs/locks.go +++ b/services/lfs/locks.go @@ -74,10 +74,7 @@ func GetListLockHandler(ctx *context.Context) { } ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType) - cursor := ctx.FormInt("cursor") - if cursor < 0 { - cursor = 0 - } + cursor := max(ctx.FormInt("cursor"), 0) limit := ctx.FormInt("limit") if limit > setting.LFS.LocksPagingNum && setting.LFS.LocksPagingNum > 0 { limit = setting.LFS.LocksPagingNum @@ -239,10 +236,7 @@ func VerifyLockHandler(ctx *context.Context) { ctx.Resp.Header().Set("Content-Type", lfs_module.MediaType) - cursor := ctx.FormInt("cursor") - if cursor < 0 { - cursor = 0 - } + cursor := max(ctx.FormInt("cursor"), 0) limit := ctx.FormInt("limit") if limit > setting.LFS.LocksPagingNum && setting.LFS.LocksPagingNum > 0 { limit = setting.LFS.LocksPagingNum diff --git a/services/lfs/server.go b/services/lfs/server.go index 0a99287ed9..59c9884fa8 100644 --- a/services/lfs/server.go +++ b/services/lfs/server.go @@ -11,6 +11,7 @@ import ( "errors" "fmt" "io" + "maps" "net/http" "net/url" "path" @@ -480,9 +481,7 @@ func buildObjectResponse(rc *requestContext, pointer lfs_module.Pointer, downloa rep.Actions["upload"] = &lfs_module.Link{Href: rc.UploadLink(pointer), Header: header} verifyHeader := make(map[string]string) - for key, value := range header { - verifyHeader[key] = value - } + maps.Copy(verifyHeader, header) // This is only needed to workaround https://github.com/git-lfs/git-lfs/issues/3662 verifyHeader["Accept"] = lfs_module.AcceptHeader diff --git a/services/mailer/sender/message_test.go b/services/mailer/sender/message_test.go index 63d0bc349a..ae153ebf05 100644 --- a/services/mailer/sender/message_test.go +++ b/services/mailer/sender/message_test.go @@ -108,9 +108,9 @@ func extractMailHeaderAndContent(t *testing.T, mail string) (map[string]string, } content := strings.TrimSpace("boundary=" + parts[1]) - hParts := strings.Split(parts[0], "\n") + hParts := strings.SplitSeq(parts[0], "\n") - for _, hPart := range hParts { + for hPart := range hParts { parts := strings.SplitN(hPart, ":", 2) hk := strings.TrimSpace(parts[0]) if hk != "" { diff --git a/services/migrations/codecommit.go b/services/migrations/codecommit.go index 9317156ab0..d08b2e6d4a 100644 --- a/services/migrations/codecommit.go +++ b/services/migrations/codecommit.go @@ -155,10 +155,7 @@ func (c *CodeCommitDownloader) GetPullRequests(ctx context.Context, page, perPag } startIndex := (page - 1) * perPage - endIndex := page * perPage - if endIndex > len(allPullRequestIDs) { - endIndex = len(allPullRequestIDs) - } + endIndex := min(page*perPage, len(allPullRequestIDs)) batch := allPullRequestIDs[startIndex:endIndex] prs := make([]*base.PullRequest, 0, len(batch)) diff --git a/services/migrations/github.go b/services/migrations/github.go index 662908756a..2ce11615c6 100644 --- a/services/migrations/github.go +++ b/services/migrations/github.go @@ -89,8 +89,8 @@ func NewGithubDownloaderV3(_ context.Context, baseURL, userName, password, token } if token != "" { - tokens := strings.Split(token, ",") - for _, token := range tokens { + tokens := strings.SplitSeq(token, ",") + for token := range tokens { token = strings.TrimSpace(token) ts := oauth2.StaticTokenSource( &oauth2.Token{AccessToken: token}, diff --git a/services/oauth2_provider/access_token.go b/services/oauth2_provider/access_token.go index 4173b0fe87..b9e6d7517b 100644 --- a/services/oauth2_provider/access_token.go +++ b/services/oauth2_provider/access_token.go @@ -85,7 +85,7 @@ func GrantAdditionalScopes(grantScopes string) auth.AccessTokenScope { } var accessScopes []string // the scopes for access control, but not for general information - for _, scope := range strings.Split(grantScopes, " ") { + for scope := range strings.SplitSeq(grantScopes, " ") { if scope != "" && !slices.Contains(generalScopesSupported, scope) { accessScopes = append(accessScopes, scope) } diff --git a/services/org/team_test.go b/services/org/team_test.go index aa39771cd2..201d58d20f 100644 --- a/services/org/team_test.go +++ b/services/org/team_test.go @@ -204,7 +204,7 @@ func TestIncludesAllRepositoriesTeams(t *testing.T) { // Create repos. repoIDs := make([]int64, 0) - for i := 0; i < 3; i++ { + for i := range 3 { r, err := repo_service.CreateRepositoryDirectly(db.DefaultContext, user, org.AsUser(), repo_service.CreateRepoOptions{Name: fmt.Sprintf("repo-%d", i)}, true) assert.NoError(t, err, "CreateRepository %d", i) diff --git a/services/packages/arch/vercmp.go b/services/packages/arch/vercmp.go index 0d33dda0f1..6dcd0df419 100644 --- a/services/packages/arch/vercmp.go +++ b/services/packages/arch/vercmp.go @@ -34,12 +34,7 @@ func parseEVR(evr string) (epoch, version, release string) { func compareSegments(a, b []string) int { lenA, lenB := len(a), len(b) - var l int - if lenA > lenB { - l = lenB - } else { - l = lenA - } + l := min(lenA, lenB) for i := 0; i < l; i++ { if r := compare(a[i], b[i]); r != 0 { return r diff --git a/services/packages/cargo/index.go b/services/packages/cargo/index.go index e8a2f189c8..605335d0f1 100644 --- a/services/packages/cargo/index.go +++ b/services/packages/cargo/index.go @@ -310,7 +310,7 @@ func alterRepositoryContent(ctx context.Context, doer *user_model.User, repo *re } func writeObjectToIndex(ctx context.Context, t *files_service.TemporaryUploadRepository, path string, r io.Reader) error { - hash, err := t.HashObject(ctx, r) + hash, err := t.HashObjectAndWrite(ctx, r) if err != nil { return err } diff --git a/services/packages/container/cleanup.go b/services/packages/container/cleanup.go index 3f5f43bbc0..d15d6b6c84 100644 --- a/services/packages/container/cleanup.go +++ b/services/packages/container/cleanup.go @@ -57,7 +57,7 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e Type: packages_model.TypeContainer, Version: packages_model.SearchValue{ ExactMatch: true, - Value: container_model.UploadVersion, + Value: container_module.UploadVersion, }, IsInternal: optional.Some(true), HasFiles: optional.Some(false), diff --git a/services/pull/merge.go b/services/pull/merge.go index 2a2f47e880..7a74bf55ae 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -8,6 +8,7 @@ import ( "context" "errors" "fmt" + "maps" "os" "path/filepath" "regexp" @@ -95,9 +96,7 @@ func getMergeMessage(ctx context.Context, baseGitRepo *git.Repository, pr *issue vars["HeadRepoOwnerName"] = pr.HeadRepo.OwnerName vars["HeadRepoName"] = pr.HeadRepo.Name } - for extraKey, extraValue := range extraVars { - vars[extraKey] = extraValue - } + maps.Copy(vars, extraVars) refs, err := pr.ResolveCrossReferences(ctx) if err == nil { closeIssueIndexes := make([]string, 0, len(refs)) diff --git a/services/repository/adopt.go b/services/repository/adopt.go index 3b958e0d4c..2bd1c55de4 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -196,8 +196,13 @@ func adoptRepository(ctx context.Context, repo *repo_model.Repository, defaultBr return fmt.Errorf("setDefaultBranch: %w", err) } } - if err = updateRepository(ctx, repo, false); err != nil { - return fmt.Errorf("updateRepository: %w", err) + + if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_empty", "default_branch"); err != nil { + return fmt.Errorf("UpdateRepositoryCols: %w", err) + } + + if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { + log.Error("Failed to update size for repository: %v", err) } return nil diff --git a/services/repository/adopt_test.go b/services/repository/adopt_test.go index 6e1dc417b3..86f586c748 100644 --- a/services/repository/adopt_test.go +++ b/services/repository/adopt_test.go @@ -29,7 +29,7 @@ func TestCheckUnadoptedRepositories_Add(t *testing.T) { } total := 30 - for i := 0; i < total; i++ { + for range total { unadopted.add("something") } diff --git a/services/repository/commitstatus/commitstatus.go b/services/repository/commitstatus/commitstatus.go index 44cf61df43..fa7a89882a 100644 --- a/services/repository/commitstatus/commitstatus.go +++ b/services/repository/commitstatus/commitstatus.go @@ -24,7 +24,7 @@ import ( ) func getCacheKey(repoID int64, brancheName string) string { - hashBytes := sha256.Sum256([]byte(fmt.Sprintf("%d:%s", repoID, brancheName))) + hashBytes := sha256.Sum256(fmt.Appendf(nil, "%d:%s", repoID, brancheName)) return fmt.Sprintf("commit_status:%x", hashBytes) } diff --git a/services/repository/create.go b/services/repository/create.go index 83d7d84c08..9758b3eb1c 100644 --- a/services/repository/create.go +++ b/services/repository/create.go @@ -100,8 +100,8 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir // .gitignore if len(opts.Gitignores) > 0 { var buf bytes.Buffer - names := strings.Split(opts.Gitignores, ",") - for _, name := range names { + names := strings.SplitSeq(opts.Gitignores, ",") + for name := range names { data, err = options.Gitignore(name) if err != nil { return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err) @@ -191,10 +191,14 @@ func initRepository(ctx context.Context, u *user_model.User, repo *repo_model.Re } } - if err = UpdateRepository(ctx, repo, false); err != nil { + if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_empty", "default_branch", "default_wiki_branch"); err != nil { return fmt.Errorf("updateRepository: %w", err) } + if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { + log.Error("Failed to update size for repository: %v", err) + } + return nil } diff --git a/services/repository/files/diff.go b/services/repository/files/diff.go index 0b3550452a..50d01f9d7c 100644 --- a/services/repository/files/diff.go +++ b/services/repository/files/diff.go @@ -29,7 +29,7 @@ func GetDiffPreview(ctx context.Context, repo *repo_model.Repository, branch, tr } // Add the object to the database - objectHash, err := t.HashObject(ctx, strings.NewReader(content)) + objectHash, err := t.HashObjectAndWrite(ctx, strings.NewReader(content)) if err != nil { return nil, err } diff --git a/services/repository/files/file.go b/services/repository/files/file.go index c4991b458d..0e1100a098 100644 --- a/services/repository/files/file.go +++ b/services/repository/files/file.go @@ -139,7 +139,7 @@ func CleanUploadFileName(name string) string { // Rebase the filename name = util.PathJoinRel(name) // Git disallows any filenames to have a .git directory in them. - for _, part := range strings.Split(name, "/") { + for part := range strings.SplitSeq(name, "/") { if strings.ToLower(part) == ".git" { return "" } diff --git a/services/repository/files/temp_repo.go b/services/repository/files/temp_repo.go index 1cf30edc7b..c2f61c8223 100644 --- a/services/repository/files/temp_repo.go +++ b/services/repository/files/temp_repo.go @@ -128,7 +128,7 @@ func (t *TemporaryUploadRepository) LsFiles(ctx context.Context, filenames ...st } fileList := make([]string, 0, len(filenames)) - for _, line := range bytes.Split(stdOut.Bytes(), []byte{'\000'}) { + for line := range bytes.SplitSeq(stdOut.Bytes(), []byte{'\000'}) { fileList = append(fileList, string(line)) } @@ -164,8 +164,8 @@ func (t *TemporaryUploadRepository) RemoveFilesFromIndex(ctx context.Context, fi return nil } -// HashObject writes the provided content to the object db and returns its hash -func (t *TemporaryUploadRepository) HashObject(ctx context.Context, content io.Reader) (string, error) { +// HashObjectAndWrite writes the provided content to the object db and returns its hash +func (t *TemporaryUploadRepository) HashObjectAndWrite(ctx context.Context, content io.Reader) (string, error) { stdOut := new(bytes.Buffer) stdErr := new(bytes.Buffer) diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index 8427fcbacc..a3c3d20238 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -94,11 +94,7 @@ func GetTreeBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git if len(entries) > perPage { tree.Truncated = true } - if rangeStart+perPage < len(entries) { - rangeEnd = rangeStart + perPage - } else { - rangeEnd = len(entries) - } + rangeEnd = min(rangeStart+perPage, len(entries)) tree.Entries = make([]api.GitEntry, rangeEnd-rangeStart) for e := rangeStart; e < rangeEnd; e++ { i := e - rangeStart diff --git a/services/repository/files/update.go b/services/repository/files/update.go index 712914a27e..99c1215c9f 100644 --- a/services/repository/files/update.go +++ b/services/repository/files/update.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "path" + "slices" "strings" "time" @@ -203,13 +204,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use } // Find the file we want to delete in the index - inFilelist := false - for _, indexFile := range filesInIndex { - if indexFile == file.TreePath { - inFilelist = true - break - } - } + inFilelist := slices.Contains(filesInIndex, file.TreePath) if !inFilelist { return nil, ErrRepoFileDoesNotExist{ Path: file.TreePath, @@ -225,7 +220,7 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use return nil, err // Couldn't get a commit for the branch } - // Assigned LastCommitID in opts if it hasn't been set + // Assigned LastCommitID in "opts" if it hasn't been set if opts.LastCommitID == "" { opts.LastCommitID = commit.ID.String() } else { @@ -237,22 +232,21 @@ func ChangeRepoFiles(ctx context.Context, repo *repo_model.Repository, doer *use } for _, file := range opts.Files { - if err := handleCheckErrors(file, commit, opts); err != nil { + if err = handleCheckErrors(file, commit, opts); err != nil { return nil, err } } } - contentStore := lfs.NewContentStore() + lfsContentStore := lfs.NewContentStore() for _, file := range opts.Files { switch file.Operation { - case "create", "update": - if err := CreateOrUpdateFile(ctx, t, file, contentStore, repo.ID, hasOldBranch); err != nil { + case "create", "update", "rename": + if err = CreateUpdateRenameFile(ctx, t, file, lfsContentStore, repo.ID, hasOldBranch); err != nil { return nil, err } case "delete": - // Remove the file from the index - if err := t.RemoveFilesFromIndex(ctx, file.TreePath); err != nil { + if err = t.RemoveFilesFromIndex(ctx, file.TreePath); err != nil { return nil, err } default: @@ -372,13 +366,13 @@ func (err ErrSHAOrCommitIDNotProvided) Error() string { // handles the check for various issues for ChangeRepoFiles func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRepoFilesOptions) error { - if file.Operation == "update" || file.Operation == "delete" { + if file.Operation == "update" || file.Operation == "delete" || file.Operation == "rename" { fromEntry, err := commit.GetTreeEntryByPath(file.Options.fromTreePath) if err != nil { return err } if file.SHA != "" { - // If a SHA was given and the SHA given doesn't match the SHA of the fromTreePath, throw error + // If the SHA given doesn't match the SHA of the fromTreePath, throw error if file.SHA != fromEntry.ID.String() { return pull_service.ErrSHADoesNotMatch{ Path: file.Options.treePath, @@ -387,7 +381,7 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep } } } else if opts.LastCommitID != "" { - // If a lastCommitID was given and it doesn't match the commitID of the head of the branch throw + // If a lastCommitID given doesn't match the branch head's commitID throw // an error, but only if we aren't creating a new branch. if commit.ID.String() != opts.LastCommitID && opts.OldBranch == opts.NewBranch { if changed, err := commit.FileChangedSinceCommit(file.Options.treePath, opts.LastCommitID); err != nil { @@ -405,13 +399,14 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep // haven't been made. We throw an error if one wasn't provided. return ErrSHAOrCommitIDNotProvided{} } + // FIXME: legacy hacky approach, it shouldn't prepare the "Options" in the "check" function file.Options.executable = fromEntry.IsExecutable() } - if file.Operation == "create" || file.Operation == "update" { - // For the path where this file will be created/updated, we need to make - // sure no parts of the path are existing files or links except for the last - // item in the path which is the file name, and that shouldn't exist IF it is - // a new file OR is being moved to a new path. + + if file.Operation == "create" || file.Operation == "update" || file.Operation == "rename" { + // For operation's target path, we need to make sure no parts of the path are existing files or links + // except for the last item in the path (which is the file name). + // And that shouldn't exist IF it is a new file OR is being moved to a new path. treePathParts := strings.Split(file.Options.treePath, "/") subTreePath := "" for index, part := range treePathParts { @@ -448,7 +443,7 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep Type: git.EntryModeTree, } } else if file.Options.fromTreePath != file.Options.treePath || file.Operation == "create" { - // The entry shouldn't exist if we are creating new file or moving to a new path + // The entry shouldn't exist if we are creating the new file or moving to a new path return ErrRepoFileAlreadyExists{ Path: file.Options.treePath, } @@ -459,8 +454,7 @@ func handleCheckErrors(file *ChangeRepoFile, commit *git.Commit, opts *ChangeRep return nil } -// CreateOrUpdateFile handles creating or updating a file for ChangeRepoFiles -func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file *ChangeRepoFile, contentStore *lfs.ContentStore, repoID int64, hasOldBranch bool) error { +func CreateUpdateRenameFile(ctx context.Context, t *TemporaryUploadRepository, file *ChangeRepoFile, contentStore *lfs.ContentStore, repoID int64, hasOldBranch bool) error { // Get the two paths (might be the same if not moving) from the index if they exist filesInIndex, err := t.LsFiles(ctx, file.TreePath, file.FromTreePath) if err != nil { @@ -468,11 +462,9 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file } // If is a new file (not updating) then the given path shouldn't exist if file.Operation == "create" { - for _, indexFile := range filesInIndex { - if indexFile == file.TreePath { - return ErrRepoFileAlreadyExists{ - Path: file.TreePath, - } + if slices.Contains(filesInIndex, file.TreePath) { + return ErrRepoFileAlreadyExists{ + Path: file.TreePath, } } } @@ -481,78 +473,177 @@ func CreateOrUpdateFile(ctx context.Context, t *TemporaryUploadRepository, file if file.Options.fromTreePath != file.Options.treePath && len(filesInIndex) > 0 { for _, indexFile := range filesInIndex { if indexFile == file.Options.fromTreePath { - if err := t.RemoveFilesFromIndex(ctx, file.FromTreePath); err != nil { + if err = t.RemoveFilesFromIndex(ctx, file.FromTreePath); err != nil { return err } } } } + var writeObjectRet *writeRepoObjectRet + switch file.Operation { + case "create", "update": + writeObjectRet, err = writeRepoObjectForCreateOrUpdate(ctx, t, file) + case "rename": + writeObjectRet, err = writeRepoObjectForRename(ctx, t, file) + default: + return util.NewInvalidArgumentErrorf("unknown file modification operation: '%s'", file.Operation) + } + if err != nil { + return err + } + + // Add the object to the index, the "file.Options.executable" is set in handleCheckErrors by the caller (legacy hacky approach) + if err = t.AddObjectToIndex(ctx, util.Iif(file.Options.executable, "100755", "100644"), writeObjectRet.ObjectHash, file.Options.treePath); err != nil { + return err + } + + if writeObjectRet.LfsContent == nil { + return nil // No LFS pointer, so nothing to do + } + defer writeObjectRet.LfsContent.Close() + + // Now we must store the content into an LFS object + lfsMetaObject, err := git_model.NewLFSMetaObject(ctx, repoID, writeObjectRet.LfsPointer) + if err != nil { + return err + } + if exist, err := contentStore.Exists(lfsMetaObject.Pointer); err != nil { + return err + } else if exist { + return nil + } + + err = contentStore.Put(lfsMetaObject.Pointer, writeObjectRet.LfsContent) + if err != nil { + if _, errRemove := git_model.RemoveLFSMetaObjectByOid(ctx, repoID, lfsMetaObject.Oid); errRemove != nil { + return fmt.Errorf("unable to remove failed inserted LFS object %s: %v (Prev Error: %w)", lfsMetaObject.Oid, errRemove, err) + } + } + return err +} + +func checkIsLfsFileInGitAttributes(ctx context.Context, t *TemporaryUploadRepository, paths []string) (ret []bool, err error) { + attributesMap, err := attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{ + Attributes: []string{attribute.Filter}, + Filenames: paths, + }) + if err != nil { + return nil, err + } + for _, p := range paths { + isLFSFile := attributesMap[p] != nil && attributesMap[p].Get(attribute.Filter).ToString().Value() == "lfs" + ret = append(ret, isLFSFile) + } + return ret, nil +} + +type writeRepoObjectRet struct { + ObjectHash string + LfsContent io.ReadCloser // if not nil, then the caller should store its content in LfsPointer, then close it + LfsPointer lfs.Pointer +} + +// writeRepoObjectForCreateOrUpdate hashes the git object for create or update operations +func writeRepoObjectForCreateOrUpdate(ctx context.Context, t *TemporaryUploadRepository, file *ChangeRepoFile) (ret *writeRepoObjectRet, err error) { + ret = &writeRepoObjectRet{} treeObjectContentReader := file.ContentReader - var lfsMetaObject *git_model.LFSMetaObject - if setting.LFS.StartServer && hasOldBranch { - // Check there is no way this can return multiple infos - attributesMap, err := attribute.CheckAttributes(ctx, t.gitRepo, "" /* use temp repo's working dir */, attribute.CheckAttributeOpts{ - Attributes: []string{attribute.Filter}, - Filenames: []string{file.Options.treePath}, - }) + if setting.LFS.StartServer { + checkIsLfsFiles, err := checkIsLfsFileInGitAttributes(ctx, t, []string{file.Options.treePath}) if err != nil { - return err + return nil, err } - - if attributesMap[file.Options.treePath] != nil && attributesMap[file.Options.treePath].Get(attribute.Filter).ToString().Value() == "lfs" { - // OK so we are supposed to LFS this data! - pointer, err := lfs.GeneratePointer(treeObjectContentReader) + if checkIsLfsFiles[0] { + // OK, so we are supposed to LFS this data! + ret.LfsPointer, err = lfs.GeneratePointer(file.ContentReader) if err != nil { - return err + return nil, err } - lfsMetaObject = &git_model.LFSMetaObject{Pointer: pointer, RepositoryID: repoID} - treeObjectContentReader = strings.NewReader(pointer.StringContent()) + if _, err = file.ContentReader.Seek(0, io.SeekStart); err != nil { + return nil, err + } + ret.LfsContent = io.NopCloser(file.ContentReader) + treeObjectContentReader = strings.NewReader(ret.LfsPointer.StringContent()) } } - // Add the object to the database - objectHash, err := t.HashObject(ctx, treeObjectContentReader) + ret.ObjectHash, err = t.HashObjectAndWrite(ctx, treeObjectContentReader) if err != nil { - return err + return nil, err } + return ret, nil +} - // Add the object to the index - if file.Options.executable { - if err := t.AddObjectToIndex(ctx, "100755", objectHash, file.Options.treePath); err != nil { - return err - } - } else { - if err := t.AddObjectToIndex(ctx, "100644", objectHash, file.Options.treePath); err != nil { - return err +// writeRepoObjectForRename the same as writeRepoObjectForCreateOrUpdate buf for "rename" +func writeRepoObjectForRename(ctx context.Context, t *TemporaryUploadRepository, file *ChangeRepoFile) (ret *writeRepoObjectRet, err error) { + lastCommitID, err := t.GetLastCommit(ctx) + if err != nil { + return nil, err + } + commit, err := t.GetCommit(lastCommitID) + if err != nil { + return nil, err + } + oldEntry, err := commit.GetTreeEntryByPath(file.Options.fromTreePath) + if err != nil { + return nil, err + } + + ret = &writeRepoObjectRet{ObjectHash: oldEntry.ID.String()} + if !setting.LFS.StartServer { + return ret, nil + } + + checkIsLfsFiles, err := checkIsLfsFileInGitAttributes(ctx, t, []string{file.Options.fromTreePath, file.Options.treePath}) + if err != nil { + return nil, err + } + oldIsLfs, newIsLfs := checkIsLfsFiles[0], checkIsLfsFiles[1] + + // If the old and new paths are both in lfs or both not in lfs, the object hash of the old file can be used directly + // as the object doesn't change + if oldIsLfs == newIsLfs { + return ret, nil + } + + oldEntryBlobPointerBy := func(f func(r io.Reader) (lfs.Pointer, error)) (lfsPointer lfs.Pointer, err error) { + r, err := oldEntry.Blob().DataAsync() + if err != nil { + return lfsPointer, err } + defer r.Close() + return f(r) } - if lfsMetaObject != nil { - // We have an LFS object - create it - lfsMetaObject, err = git_model.NewLFSMetaObject(ctx, lfsMetaObject.RepositoryID, lfsMetaObject.Pointer) + var treeObjectContentReader io.ReadCloser + if oldIsLfs { + // If the old is in lfs but the new isn't, read the content from lfs and add it as a normal git object + pointer, err := oldEntryBlobPointerBy(lfs.ReadPointer) if err != nil { - return err + return nil, err } - exist, err := contentStore.Exists(lfsMetaObject.Pointer) + treeObjectContentReader, err = lfs.ReadMetaObject(pointer) if err != nil { - return err + return nil, err } - if !exist { - _, err := file.ContentReader.Seek(0, io.SeekStart) - if err != nil { - return err - } - if err := contentStore.Put(lfsMetaObject.Pointer, file.ContentReader); err != nil { - if _, err2 := git_model.RemoveLFSMetaObjectByOid(ctx, repoID, lfsMetaObject.Oid); err2 != nil { - return fmt.Errorf("unable to remove failed inserted LFS object %s: %v (Prev Error: %w)", lfsMetaObject.Oid, err2, err) - } - return err - } + defer treeObjectContentReader.Close() + } else { + // If the new is in lfs but the old isn't, read the content from the git object and generate a lfs pointer of it + ret.LfsPointer, err = oldEntryBlobPointerBy(lfs.GeneratePointer) + if err != nil { + return nil, err + } + ret.LfsContent, err = oldEntry.Blob().DataAsync() + if err != nil { + return nil, err } + treeObjectContentReader = io.NopCloser(strings.NewReader(ret.LfsPointer.StringContent())) } - - return nil + ret.ObjectHash, err = t.HashObjectAndWrite(ctx, treeObjectContentReader) + if err != nil { + return nil, err + } + return ret, nil } // VerifyBranchProtection verify the branch protection for modifying the given treePath on the given branch diff --git a/services/repository/files/upload.go b/services/repository/files/upload.go index 68a071cd28..b004e3cc4c 100644 --- a/services/repository/files/upload.go +++ b/services/repository/files/upload.go @@ -201,10 +201,10 @@ func copyUploadedLFSFileIntoRepository(ctx context.Context, info *uploadInfo, at info.lfsMetaObject = &git_model.LFSMetaObject{Pointer: pointer, RepositoryID: t.repo.ID} - if objectHash, err = t.HashObject(ctx, strings.NewReader(pointer.StringContent())); err != nil { + if objectHash, err = t.HashObjectAndWrite(ctx, strings.NewReader(pointer.StringContent())); err != nil { return err } - } else if objectHash, err = t.HashObject(ctx, file); err != nil { + } else if objectHash, err = t.HashObjectAndWrite(ctx, file); err != nil { return err } diff --git a/services/repository/fork.go b/services/repository/fork.go index bd1554f163..d0568e6072 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -209,7 +209,7 @@ func ForkRepository(ctx context.Context, doer, owner *user_model.User, opts Fork // ConvertForkToNormalRepository convert the provided repo from a forked repo to normal repo func ConvertForkToNormalRepository(ctx context.Context, repo *repo_model.Repository) error { - err := db.WithTx(ctx, func(ctx context.Context) error { + return db.WithTx(ctx, func(ctx context.Context) error { repo, err := repo_model.GetRepositoryByID(ctx, repo.ID) if err != nil { return err @@ -226,16 +226,8 @@ func ConvertForkToNormalRepository(ctx context.Context, repo *repo_model.Reposit repo.IsFork = false repo.ForkID = 0 - - if err := updateRepository(ctx, repo, false); err != nil { - log.Error("Unable to update repository %-v whilst converting from fork. Error: %v", repo, err) - return err - } - - return nil + return repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_fork", "fork_id") }) - - return err } type findForksOptions struct { diff --git a/services/repository/generate.go b/services/repository/generate.go index 77a43b4e39..867b5d7855 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -253,43 +253,35 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r return initRepoCommit(ctx, tmpDir, repo, repo.Owner, defaultBranch) } -func generateGitContent(ctx context.Context, repo, templateRepo, generateRepo *repo_model.Repository) (err error) { - tmpDir, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-" + repo.Name) +// GenerateGitContent generates git content from a template repository +func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) (err error) { + tmpDir, cleanup, err := setting.AppDataTempDir("git-repo-content").MkdirTempRandom("gitea-" + generateRepo.Name) if err != nil { - return fmt.Errorf("failed to create temp dir for repository %s: %w", repo.FullName(), err) + return fmt.Errorf("failed to create temp dir for repository %s: %w", generateRepo.FullName(), err) } defer cleanup() - if err = generateRepoCommit(ctx, repo, templateRepo, generateRepo, tmpDir); err != nil { + if err = generateRepoCommit(ctx, generateRepo, templateRepo, generateRepo, tmpDir); err != nil { return fmt.Errorf("generateRepoCommit: %w", err) } // re-fetch repo - if repo, err = repo_model.GetRepositoryByID(ctx, repo.ID); err != nil { + if generateRepo, err = repo_model.GetRepositoryByID(ctx, generateRepo.ID); err != nil { return fmt.Errorf("getRepositoryByID: %w", err) } // if there was no default branch supplied when generating the repo, use the default one from the template - if strings.TrimSpace(repo.DefaultBranch) == "" { - repo.DefaultBranch = templateRepo.DefaultBranch + if strings.TrimSpace(generateRepo.DefaultBranch) == "" { + generateRepo.DefaultBranch = templateRepo.DefaultBranch } - if err = gitrepo.SetDefaultBranch(ctx, repo, repo.DefaultBranch); err != nil { + if err = gitrepo.SetDefaultBranch(ctx, generateRepo, generateRepo.DefaultBranch); err != nil { return fmt.Errorf("setDefaultBranch: %w", err) } - if err = UpdateRepository(ctx, repo, false); err != nil { + if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, generateRepo, "default_branch"); err != nil { return fmt.Errorf("updateRepository: %w", err) } - return nil -} - -// GenerateGitContent generates git content from a template repository -func GenerateGitContent(ctx context.Context, templateRepo, generateRepo *repo_model.Repository) error { - if err := generateGitContent(ctx, generateRepo, templateRepo, generateRepo); err != nil { - return err - } - if err := repo_module.UpdateRepoSize(ctx, generateRepo); err != nil { return fmt.Errorf("failed to update size for repository: %w", err) } diff --git a/services/repository/gitgraph/graph_models.go b/services/repository/gitgraph/graph_models.go index 31379410b2..02b0268cd9 100644 --- a/services/repository/gitgraph/graph_models.go +++ b/services/repository/gitgraph/graph_models.go @@ -232,8 +232,8 @@ func newRefsFromRefNames(refNames []byte) []git.Reference { continue } refName := string(refNameBytes) - if strings.HasPrefix(refName, "tag: ") { - refName = strings.TrimPrefix(refName, "tag: ") + if after, ok := strings.CutPrefix(refName, "tag: "); ok { + refName = after } else { refName = strings.TrimPrefix(refName, "HEAD -> ") } diff --git a/services/repository/gitgraph/graph_test.go b/services/repository/gitgraph/graph_test.go index 4c48b94aa2..93fa1aec6a 100644 --- a/services/repository/gitgraph/graph_test.go +++ b/services/repository/gitgraph/graph_test.go @@ -6,6 +6,7 @@ package gitgraph import ( "bytes" "fmt" + "slices" "strings" "testing" @@ -117,13 +118,7 @@ func TestReleaseUnusedColors(t *testing.T) { if parser.firstAvailable == -1 { // All in use for _, color := range parser.availableColors { - found := false - for _, oldColor := range parser.oldColors { - if oldColor == color { - found = true - break - } - } + found := slices.Contains(parser.oldColors, color) if !found { t.Errorf("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should be available but is not", testcase.availableColors, @@ -141,13 +136,7 @@ func TestReleaseUnusedColors(t *testing.T) { // Some in use for i := parser.firstInUse; i != parser.firstAvailable; i = (i + 1) % len(parser.availableColors) { color := parser.availableColors[i] - found := false - for _, oldColor := range parser.oldColors { - if oldColor == color { - found = true - break - } - } + found := slices.Contains(parser.oldColors, color) if !found { t.Errorf("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should be available but is not", testcase.availableColors, @@ -163,13 +152,7 @@ func TestReleaseUnusedColors(t *testing.T) { } for i := parser.firstAvailable; i != parser.firstInUse; i = (i + 1) % len(parser.availableColors) { color := parser.availableColors[i] - found := false - for _, oldColor := range parser.oldColors { - if oldColor == color { - found = true - break - } - } + found := slices.Contains(parser.oldColors, color) if found { t.Errorf("In testcase:\n%d\t%d\t%d %d =>\n%d\t%d\t%d %d: %d should not be available but is", testcase.availableColors, diff --git a/services/repository/migrate.go b/services/repository/migrate.go index 0859158b89..0a3dc45339 100644 --- a/services/repository/migrate.go +++ b/services/repository/migrate.go @@ -220,10 +220,14 @@ func MigrateRepositoryGitData(ctx context.Context, u *user_model.User, } repo.IsMirror = true - if err = UpdateRepository(ctx, repo, false); err != nil { + if err = repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "num_watches", "is_empty", "default_branch", "default_wiki_branch", "is_mirror"); err != nil { return nil, err } + if err = repo_module.UpdateRepoSize(ctx, repo); err != nil { + log.Error("Failed to update size for repository: %v", err) + } + // this is necessary for sync local tags from remote configName := fmt.Sprintf("remote.%s.fetch", mirrorModel.GetRemoteName()) if stdout, _, err := git.NewCommand("config"). diff --git a/services/repository/push.go b/services/repository/push.go index 3735c5f3a4..af3c873d15 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -232,7 +232,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { } if len(addTags)+len(delTags) > 0 { - if err := PushUpdateAddDeleteTags(ctx, repo, gitRepo, addTags, delTags); err != nil { + if err := PushUpdateAddDeleteTags(ctx, repo, gitRepo, pusher, addTags, delTags); err != nil { return fmt.Errorf("PushUpdateAddDeleteTags: %w", err) } } @@ -342,17 +342,17 @@ func pushDeleteBranch(ctx context.Context, repo *repo_model.Repository, pusher * } // PushUpdateAddDeleteTags updates a number of added and delete tags -func PushUpdateAddDeleteTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, addTags, delTags []string) error { +func PushUpdateAddDeleteTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, pusher *user_model.User, addTags, delTags []string) error { return db.WithTx(ctx, func(ctx context.Context) error { if err := repo_model.PushUpdateDeleteTags(ctx, repo, delTags); err != nil { return err } - return pushUpdateAddTags(ctx, repo, gitRepo, addTags) + return pushUpdateAddTags(ctx, repo, gitRepo, pusher, addTags) }) } // pushUpdateAddTags updates a number of add tags -func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, tags []string) error { +func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, pusher *user_model.User, tags []string) error { if len(tags) == 0 { return nil } @@ -378,8 +378,6 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo newReleases := make([]*repo_model.Release, 0, len(lowerTags)-len(relMap)) - emailToUser := make(map[string]*user_model.User) - for i, lowerTag := range lowerTags { tag, err := gitRepo.GetTag(tags[i]) if err != nil { @@ -397,21 +395,9 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo if sig == nil { sig = commit.Committer } - var author *user_model.User - createdAt := time.Unix(1, 0) + createdAt := time.Unix(1, 0) if sig != nil { - var ok bool - author, ok = emailToUser[sig.Email] - if !ok { - author, err = user_model.GetUserByEmail(ctx, sig.Email) - if err != nil && !user_model.IsErrUserNotExist(err) { - return fmt.Errorf("GetUserByEmail: %w", err) - } - if author != nil { - emailToUser[sig.Email] = author - } - } createdAt = sig.When } @@ -435,11 +421,9 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo IsDraft: false, IsPrerelease: false, IsTag: true, + PublisherID: pusher.ID, CreatedUnix: timeutil.TimeStamp(createdAt.Unix()), } - if author != nil { - rel.PublisherID = author.ID - } newReleases = append(newReleases, rel) } else { @@ -448,12 +432,10 @@ func pushUpdateAddTags(ctx context.Context, repo *repo_model.Repository, gitRepo if rel.IsTag { rel.Title = parts[0] rel.Note = note - if author != nil { - rel.PublisherID = author.ID - } } else { rel.IsDraft = false } + rel.PublisherID = pusher.ID if err = repo_model.UpdateRelease(ctx, rel); err != nil { return fmt.Errorf("Update: %w", err) } diff --git a/services/repository/repository.go b/services/repository/repository.go index 739ef1ec38..0cdce336d4 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -127,9 +127,42 @@ func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibili func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error) { return db.WithTx(ctx, func(ctx context.Context) error { repo.IsPrivate = false - if err = updateRepository(ctx, repo, true); err != nil { - return fmt.Errorf("MakeRepoPublic: %w", err) + if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil { + return err + } + + if err = repo.LoadOwner(ctx); err != nil { + return fmt.Errorf("LoadOwner: %w", err) } + if repo.Owner.IsOrganization() { + // Organization repository need to recalculate access table when visibility is changed. + if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil { + return fmt.Errorf("recalculateTeamAccesses: %w", err) + } + } + + // Create/Remove git-daemon-export-ok for git-daemon... + if err := checkDaemonExportOK(ctx, repo); err != nil { + return err + } + + forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID) + if err != nil { + return fmt.Errorf("getRepositoriesByForkID: %w", err) + } + + if repo.Owner.Visibility != structs.VisibleTypePrivate { + for i := range forkRepos { + if err = MakeRepoPublic(ctx, forkRepos[i]); err != nil { + return fmt.Errorf("MakeRepoPublic[%d]: %w", forkRepos[i].ID, err) + } + } + } + + // If visibility is changed, we need to update the issue indexer. + // Since the data in the issue indexer have field to indicate if the repo is public or not. + issue_indexer.UpdateRepoIndexer(ctx, repo.ID) + return nil }) } @@ -137,9 +170,51 @@ func MakeRepoPublic(ctx context.Context, repo *repo_model.Repository) (err error func MakeRepoPrivate(ctx context.Context, repo *repo_model.Repository) (err error) { return db.WithTx(ctx, func(ctx context.Context) error { repo.IsPrivate = true - if err = updateRepository(ctx, repo, true); err != nil { - return fmt.Errorf("MakeRepoPrivate: %w", err) + if err := repo_model.UpdateRepositoryColsNoAutoTime(ctx, repo, "is_private"); err != nil { + return err + } + + if err = repo.LoadOwner(ctx); err != nil { + return fmt.Errorf("LoadOwner: %w", err) } + if repo.Owner.IsOrganization() { + // Organization repository need to recalculate access table when visibility is changed. + if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil { + return fmt.Errorf("recalculateTeamAccesses: %w", err) + } + } + + // If repo has become private, we need to set its actions to private. + _, err = db.GetEngine(ctx).Where("repo_id = ?", repo.ID).Cols("is_private").Update(&activities_model.Action{ + IsPrivate: true, + }) + if err != nil { + return err + } + + if err = repo_model.ClearRepoStars(ctx, repo.ID); err != nil { + return err + } + + // Create/Remove git-daemon-export-ok for git-daemon... + if err := checkDaemonExportOK(ctx, repo); err != nil { + return err + } + + forkRepos, err := repo_model.GetRepositoriesByForkID(ctx, repo.ID) + if err != nil { + return fmt.Errorf("getRepositoriesByForkID: %w", err) + } + for i := range forkRepos { + if err = MakeRepoPrivate(ctx, forkRepos[i]); err != nil { + return fmt.Errorf("MakeRepoPrivate[%d]: %w", forkRepos[i].ID, err) + } + } + + // If visibility is changed, we need to update the issue indexer. + // Since the data in the issue indexer have field to indicate if the repo is public or not. + issue_indexer.UpdateRepoIndexer(ctx, repo.ID) + return nil }) } diff --git a/services/webtheme/webtheme.go b/services/webtheme/webtheme.go index 58aea3bc74..4e89d6dbac 100644 --- a/services/webtheme/webtheme.go +++ b/services/webtheme/webtheme.go @@ -70,11 +70,11 @@ func parseThemeMetaInfoToMap(cssContent string) map[string]string { m := map[string]string{} for _, item := range matchedItems { v := item[3] - if strings.HasPrefix(v, `"`) { - v = strings.TrimSuffix(strings.TrimPrefix(v, `"`), `"`) + if after, ok := strings.CutPrefix(v, `"`); ok { + v = strings.TrimSuffix(after, `"`) v = strings.ReplaceAll(v, `\"`, `"`) - } else if strings.HasPrefix(v, `'`) { - v = strings.TrimSuffix(strings.TrimPrefix(v, `'`), `'`) + } else if after, ok := strings.CutPrefix(v, `'`); ok { + v = strings.TrimSuffix(after, `'`) v = strings.ReplaceAll(v, `\'`, `'`) } m[item[2]] = v diff --git a/services/wiki/wiki_test.go b/services/wiki/wiki_test.go index f441c2939b..6ea3ca9c1b 100644 --- a/services/wiki/wiki_test.go +++ b/services/wiki/wiki_test.go @@ -116,9 +116,9 @@ func TestGitPathToWebPath(t *testing.T) { func TestUserWebGitPathConsistency(t *testing.T) { maxLen := 20 b := make([]byte, maxLen) - for i := 0; i < 1000; i++ { + for range 1000 { l := rand.Intn(maxLen) - for j := 0; j < l; j++ { + for j := range l { r := rand.Intn(0x80-0x20) + 0x20 b[j] = byte(r) } diff --git a/tailwind.config.js b/tailwind.config.js index fe285432f3..01740d816b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -33,7 +33,6 @@ export default { '!./templates/swagger/v1_json.tmpl', '!./templates/user/auth/oidc_wellknown.tmpl', '!**/*_test.go', - '!./modules/{public,options,templates}/bindata.go', './{build,models,modules,routers,services}/**/*.go', './templates/**/*.tmpl', './web_src/js/**/*.{ts,js,vue}', diff --git a/templates/repo/diff/box.tmpl b/templates/repo/diff/box.tmpl index fa96d2f0e2..22abf9a219 100644 --- a/templates/repo/diff/box.tmpl +++ b/templates/repo/diff/box.tmpl @@ -148,7 +148,7 @@ <a class="item" rel="nofollow" href="{{$.BeforeSourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a> {{else}} <a class="item" rel="nofollow" href="{{$.SourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a> - {{if and $.Repository.CanEnableEditor $.CanEditFile (not $file.IsLFSFile) (not $file.IsBin)}} + {{if and $.Repository.CanEnableEditor $.CanEditFile}} <a class="item" rel="nofollow" href="{{$.HeadRepoLink}}/_edit/{{PathEscapeSegments $.HeadBranchName}}/{{PathEscapeSegments $file.Name}}?return_uri={{print $.BackToLink "#diff-" $file.NameHash | QueryEscape}}">{{ctx.Locale.Tr "repo.editor.edit_this_file"}}</a> {{end}} {{end}} diff --git a/templates/repo/editor/commit_form.tmpl b/templates/repo/editor/commit_form.tmpl index 8f46c47b96..7efed77349 100644 --- a/templates/repo/editor/commit_form.tmpl +++ b/templates/repo/editor/commit_form.tmpl @@ -77,7 +77,7 @@ </div> {{end}} </div> - <button id="commit-button" type="submit" class="ui primary button"> + <button id="commit-button" type="submit" class="ui primary button" {{if .PageIsEdit}}disabled{{end}}> {{if eq .commit_choice "commit-to-new-branch"}}{{ctx.Locale.Tr "repo.editor.propose_file_change"}}{{else}}{{ctx.Locale.Tr "repo.editor.commit_changes"}}{{end}} </button> <a class="ui button red" href="{{if .ReturnURI}}{{.ReturnURI}}{{else}}{{$.BranchLink}}/{{PathEscapeSegments .TreePath}}{{end}}">{{ctx.Locale.Tr "repo.editor.cancel"}}</a> diff --git a/templates/repo/editor/edit.tmpl b/templates/repo/editor/edit.tmpl index ae8a60c20c..e1bf46d53d 100644 --- a/templates/repo/editor/edit.tmpl +++ b/templates/repo/editor/edit.tmpl @@ -28,31 +28,40 @@ <input type="hidden" id="tree_path" name="tree_path" value="{{.TreePath}}" required> </div> </div> - <div class="field"> - <div class="ui top attached header"> - <div class="ui compact small menu small-menu-items repo-editor-menu"> - <a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.new_file"}}{{else}}{{ctx.Locale.Tr "repo.editor.edit_file"}}{{end}}</a> - <a class="item" data-tab="preview" data-preview-url="{{.Repository.Link}}/markup" data-preview-context-ref="{{.RepoLink}}/src/{{.RefTypeNameSubURL}}">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a> - {{if not .IsNewFile}} - <a class="item" data-tab="diff" hx-params="context,content" hx-vals='{"context":"{{.BranchLink}}"}' hx-include="#edit_area" hx-swap="innerHTML" hx-target=".tab[data-tab='diff']" hx-indicator=".tab[data-tab='diff']" hx-post="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a> - {{end}} - </div> - </div> - <div class="ui bottom attached segment tw-p-0"> - <div class="ui active tab tw-rounded-b" data-tab="write"> - <textarea id="edit_area" name="content" class="tw-hidden" data-id="repo-{{.Repository.Name}}-{{.TreePath}}" - data-previewable-extensions="{{.PreviewableExtensions}}" - data-line-wrap-extensions="{{.LineWrapExtensions}}">{{.FileContent}}</textarea> - <div class="editor-loading is-loading"></div> + {{if not .NotEditableReason}} + <div class="field"> + <div class="ui top attached header"> + <div class="ui compact small menu small-menu-items repo-editor-menu"> + <a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.new_file"}}{{else}}{{ctx.Locale.Tr "repo.editor.edit_file"}}{{end}}</a> + <a class="item" data-tab="preview" data-preview-url="{{.Repository.Link}}/markup" data-preview-context-ref="{{.RepoLink}}/src/{{.RefTypeNameSubURL}}">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a> + {{if not .IsNewFile}} + <a class="item" data-tab="diff" hx-params="context,content" hx-vals='{"context":"{{.BranchLink}}"}' hx-include="#edit_area" hx-swap="innerHTML" hx-target=".tab[data-tab='diff']" hx-indicator=".tab[data-tab='diff']" hx-post="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a> + {{end}} + </div> </div> - <div class="ui tab tw-px-4 tw-py-3" data-tab="preview"> - {{ctx.Locale.Tr "loading"}} + <div class="ui bottom attached segment tw-p-0"> + <div class="ui active tab tw-rounded-b" data-tab="write"> + <textarea id="edit_area" name="content" class="tw-hidden" data-id="repo-{{.Repository.Name}}-{{.TreePath}}" + data-previewable-extensions="{{.PreviewableExtensions}}" + data-line-wrap-extensions="{{.LineWrapExtensions}}">{{.FileContent}}</textarea> + <div class="editor-loading is-loading"></div> + </div> + <div class="ui tab tw-px-4 tw-py-3" data-tab="preview"> + {{ctx.Locale.Tr "loading"}} + </div> + <div class="ui tab" data-tab="diff"> + <div class="tw-p-16"></div> + </div> </div> - <div class="ui tab" data-tab="diff"> - <div class="tw-p-16"></div> + </div> + {{else}} + <div class="field"> + <div class="ui segment tw-text-center"> + <h4 class="tw-font-semibold tw-mb-2">{{.NotEditableReason}}</h4> + <p>{{ctx.Locale.Tr "repo.editor.file_not_editable_hint"}}</p> </div> </div> - </div> + {{end}} {{template "repo/editor/commit_form" .}} </form> </div> diff --git a/templates/repo/release/list.tmpl b/templates/repo/release/list.tmpl index 1a7d911acb..77e19b6b86 100644 --- a/templates/repo/release/list.tmpl +++ b/templates/repo/release/list.tmpl @@ -50,7 +50,11 @@ {{svg (MigrationIcon $release.Repo.GetOriginalURLHostname) 20 "tw-mr-1"}}{{$release.OriginalAuthor}} {{else if $release.Publisher}} {{ctx.AvatarUtils.Avatar $release.Publisher 20 "tw-mr-1"}} - <a href="{{$release.Publisher.HomeLink}}">{{$release.Publisher.GetDisplayName}}</a> + {{if gt $release.PublisherID 0}} + <a href="{{$release.Publisher.HomeLink}}">{{$release.Publisher.GetDisplayName}}</a> + {{else}} + {{$release.Publisher.GetDisplayName}} + {{end}} {{else}} Ghost {{end}} diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go index 083535a9a5..b30cdfd0fc 100644 --- a/tests/integration/api_helper_for_declarative_test.go +++ b/tests/integration/api_helper_for_declarative_test.go @@ -263,7 +263,7 @@ func doAPIMergePullRequest(ctx APITestContext, owner, repo string, index int64) var req *RequestWrapper var resp *httptest.ResponseRecorder - for i := 0; i < 6; i++ { + for range 6 { req = NewRequestWithJSON(t, http.MethodPost, urlStr, &forms.MergePullRequestForm{ MergeMessageField: "doAPIMergePullRequest Merge", Do: string(repo_model.MergeStyleMerge), diff --git a/tests/integration/api_issue_test.go b/tests/integration/api_issue_test.go index e035f7200b..ce9c33c049 100644 --- a/tests/integration/api_issue_test.go +++ b/tests/integration/api_issue_test.go @@ -166,7 +166,7 @@ func TestAPICreateIssueParallel(t *testing.T) { urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner.Name, repoBefore.Name) var wg sync.WaitGroup - for i := 0; i < 10; i++ { + for i := range 10 { wg.Add(1) go func(parentT *testing.T, i int) { parentT.Run(fmt.Sprintf("ParallelCreateIssue_%d", i), func(t *testing.T) { @@ -267,10 +267,9 @@ func TestAPISearchIssues(t *testing.T) { defer tests.PrepareTestEnv(t)() // as this API was used in the frontend, it uses UI page size - expectedIssueCount := 20 // from the fixtures - if expectedIssueCount > setting.UI.IssuePagingNum { - expectedIssueCount = setting.UI.IssuePagingNum - } + expectedIssueCount := min( + // from the fixtures + 20, setting.UI.IssuePagingNum) link, _ := url.Parse("/api/v1/repos/issues/search") token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue) @@ -371,10 +370,9 @@ func TestAPISearchIssuesWithLabels(t *testing.T) { defer tests.PrepareTestEnv(t)() // as this API was used in the frontend, it uses UI page size - expectedIssueCount := 20 // from the fixtures - if expectedIssueCount > setting.UI.IssuePagingNum { - expectedIssueCount = setting.UI.IssuePagingNum - } + expectedIssueCount := min( + // from the fixtures + 20, setting.UI.IssuePagingNum) link, _ := url.Parse("/api/v1/repos/issues/search") token := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue) diff --git a/tests/integration/api_packages_chef_test.go b/tests/integration/api_packages_chef_test.go index 86b3be9d0c..8f2c2592e7 100644 --- a/tests/integration/api_packages_chef_test.go +++ b/tests/integration/api_packages_chef_test.go @@ -181,7 +181,7 @@ nwIDAQAB var data []byte if version == "1.3" { - data = []byte(fmt.Sprintf( + data = fmt.Appendf(nil, "Method:%s\nPath:%s\nX-Ops-Content-Hash:%s\nX-Ops-Sign:version=%s\nX-Ops-Timestamp:%s\nX-Ops-UserId:%s\nX-Ops-Server-API-Version:%s", req.Method, path.Clean(req.URL.Path), @@ -190,17 +190,17 @@ nwIDAQAB req.Header.Get("X-Ops-Timestamp"), username, req.Header.Get("X-Ops-Server-Api-Version"), - )) + ) } else { sum := sha1.Sum([]byte(path.Clean(req.URL.Path))) - data = []byte(fmt.Sprintf( + data = fmt.Appendf(nil, "Method:%s\nHashed Path:%s\nX-Ops-Content-Hash:%s\nX-Ops-Timestamp:%s\nX-Ops-UserId:%s", req.Method, base64.StdEncoding.EncodeToString(sum[:]), req.Header.Get("X-Ops-Content-Hash"), req.Header.Get("X-Ops-Timestamp"), username, - )) + ) } for k := range req.Header { diff --git a/tests/integration/api_packages_container_test.go b/tests/integration/api_packages_container_test.go index 351807c0da..90bf1c3d21 100644 --- a/tests/integration/api_packages_container_test.go +++ b/tests/integration/api_packages_container_test.go @@ -18,7 +18,6 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" - container_model "code.gitea.io/gitea/models/packages/container" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" container_module "code.gitea.io/gitea/modules/packages/container" @@ -71,8 +70,8 @@ func TestPackageContainer(t *testing.T) { configContent := `{"architecture":"amd64","config":{"Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/true"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"container":"b89fe92a887d55c0961f02bdfbfd8ac3ddf66167db374770d2d9e9fab3311510","container_config":{"Hostname":"b89fe92a887d","Env":["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"],"Cmd":["/bin/sh","-c","#(nop) ","CMD [\"/true\"]"],"ArgsEscaped":true,"Image":"sha256:9bd8b88dc68b80cffe126cc820e4b52c6e558eb3b37680bfee8e5f3ed7b8c257"},"created":"2022-01-01T00:00:00.000000000Z","docker_version":"20.10.12","history":[{"created":"2022-01-01T00:00:00.000000000Z","created_by":"/bin/sh -c #(nop) COPY file:0e7589b0c800daaf6fa460d2677101e4676dd9491980210cb345480e513f3602 in /true "},{"created":"2022-01-01T00:00:00.000000001Z","created_by":"/bin/sh -c #(nop) CMD [\"/true\"]","empty_layer":true}],"os":"linux","rootfs":{"type":"layers","diff_ids":["sha256:0ff3b91bdf21ecdf2f2f3d4372c2098a14dbe06cd678e8f0a85fd4902d00e2e2"]}}` manifestDigest := "sha256:4f10484d1c1bb13e3956b4de1cd42db8e0f14a75be1617b60f2de3cd59c803c6" - manifestContent := `{"schemaVersion":2,"mediaType":"` + container_model.ContentTypeDockerDistributionManifestV2 + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` - manifestContentType := container_model.ContentTypeDockerDistributionManifestV2 + manifestContent := `{"schemaVersion":2,"mediaType":"` + container_module.ContentTypeDockerDistributionManifestV2 + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` + manifestContentType := container_module.ContentTypeDockerDistributionManifestV2 untaggedManifestDigest := "sha256:4305f5f5572b9a426b88909b036e52ee3cf3d7b9c1b01fac840e90747f56623d" untaggedManifestContent := `{"schemaVersion":2,"mediaType":"` + oci.MediaTypeImageManifest + `","config":{"mediaType":"application/vnd.docker.container.image.v1+json","digest":"sha256:4607e093bec406eaadb6f3a340f63400c9d3a7038680744c406903766b938f0d","size":1069},"layers":[{"mediaType":"application/vnd.docker.image.rootfs.diff.tar.gzip","digest":"sha256:a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4","size":32}]}` @@ -252,7 +251,7 @@ func TestPackageContainer(t *testing.T) { assert.Equal(t, fmt.Sprintf("/v2/%s/%s/blobs/%s", user.Name, image, blobDigest), resp.Header().Get("Location")) assert.Equal(t, blobDigest, resp.Header().Get("Docker-Content-Digest")) - pv, err := packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, container_model.UploadVersion) + pv, err := packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, image, container_module.UploadVersion) assert.NoError(t, err) pfs, err := packages_model.GetFilesByVersionID(db.DefaultContext, pv.ID) @@ -432,7 +431,7 @@ func TestPackageContainer(t *testing.T) { assert.Len(t, pd.Files, 3) for _, pfd := range pd.Files { switch pfd.File.Name { - case container_model.ManifestFilename: + case container_module.ManifestFilename: assert.True(t, pfd.File.IsLead) assert.Equal(t, "application/vnd.docker.distribution.manifest.v2+json", pfd.Properties.GetByName(container_module.PropertyMediaType)) assert.Equal(t, manifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) @@ -534,7 +533,7 @@ func TestPackageContainer(t *testing.T) { assert.Len(t, pd.Files, 3) for _, pfd := range pd.Files { - if pfd.File.Name == container_model.ManifestFilename { + if pfd.File.Name == container_module.ManifestFilename { assert.True(t, pfd.File.IsLead) assert.Equal(t, oci.MediaTypeImageManifest, pfd.Properties.GetByName(container_module.PropertyMediaType)) assert.Equal(t, untaggedManifestDigest, pfd.Properties.GetByName(container_module.PropertyDigest)) @@ -752,7 +751,7 @@ func TestPackageContainer(t *testing.T) { url := fmt.Sprintf("%sv2/%s/parallel", setting.AppURL, user.Name) var wg sync.WaitGroup - for i := 0; i < 10; i++ { + for i := range 10 { wg.Add(1) content := []byte{byte(i)} diff --git a/tests/integration/api_packages_debian_test.go b/tests/integration/api_packages_debian_test.go index 98027d774c..3ae60d2aa2 100644 --- a/tests/integration/api_packages_debian_test.go +++ b/tests/integration/api_packages_debian_test.go @@ -284,7 +284,7 @@ func TestPackageDebian(t *testing.T) { // because "Iterate" keeps a dangling SQL session but the callback function still uses the same session to execute statements. // The "Iterate" problem has been checked by TestContextSafety now, so here we only need to check the cleanup logic with a small number packagesCount := 2 - for i := 0; i < packagesCount; i++ { + for i := range packagesCount { uploadURL := fmt.Sprintf("%s/pool/%s/%s/upload", rootURL, "test", "main") req := NewRequestWithBody(t, "PUT", uploadURL, createArchive(packageName, "1.0."+strconv.Itoa(i), "all")).AddBasicAuth(user.Name) MakeRequest(t, req, http.StatusCreated) diff --git a/tests/integration/api_packages_maven_test.go b/tests/integration/api_packages_maven_test.go index 408c8805c2..30ef1884cd 100644 --- a/tests/integration/api_packages_maven_test.go +++ b/tests/integration/api_packages_maven_test.go @@ -321,7 +321,7 @@ func TestPackageMavenConcurrent(t *testing.T) { defer tests.PrintCurrentTest(t)() var wg sync.WaitGroup - for i := 0; i < 10; i++ { + for i := range 10 { wg.Add(1) go func(i int) { putFile(t, fmt.Sprintf("/%s/%s.jar", packageVersion, strconv.Itoa(i)), "test", http.StatusCreated) diff --git a/tests/integration/api_packages_nuget_test.go b/tests/integration/api_packages_nuget_test.go index c0e69a82cd..65b1b9845a 100644 --- a/tests/integration/api_packages_nuget_test.go +++ b/tests/integration/api_packages_nuget_test.go @@ -46,21 +46,30 @@ func TestPackageNuGet(t *testing.T) { defer tests.PrepareTestEnv(t)() type FeedEntryProperties struct { - Version string `xml:"Version"` - NormalizedVersion string `xml:"NormalizedVersion"` Authors string `xml:"Authors"` + Copyright string `xml:"Copyright,omitempty"` + Created nuget.TypedValue[time.Time] `xml:"Created"` Dependencies string `xml:"Dependencies"` Description string `xml:"Description"` - VersionDownloadCount nuget.TypedValue[int64] `xml:"VersionDownloadCount"` + DevelopmentDependency nuget.TypedValue[bool] `xml:"DevelopmentDependency"` DownloadCount nuget.TypedValue[int64] `xml:"DownloadCount"` - PackageSize nuget.TypedValue[int64] `xml:"PackageSize"` - Created nuget.TypedValue[time.Time] `xml:"Created"` + ID string `xml:"Id"` + IconURL string `xml:"IconUrl,omitempty"` + Language string `xml:"Language,omitempty"` LastUpdated nuget.TypedValue[time.Time] `xml:"LastUpdated"` - Published nuget.TypedValue[time.Time] `xml:"Published"` + LicenseURL string `xml:"LicenseUrl,omitempty"` + MinClientVersion string `xml:"MinClientVersion,omitempty"` + NormalizedVersion string `xml:"NormalizedVersion"` + Owners string `xml:"Owners,omitempty"` + PackageSize nuget.TypedValue[int64] `xml:"PackageSize"` ProjectURL string `xml:"ProjectUrl,omitempty"` + Published nuget.TypedValue[time.Time] `xml:"Published"` ReleaseNotes string `xml:"ReleaseNotes,omitempty"` RequireLicenseAcceptance nuget.TypedValue[bool] `xml:"RequireLicenseAcceptance"` + Tags string `xml:"Tags,omitempty"` Title string `xml:"Title"` + Version string `xml:"Version"` + VersionDownloadCount nuget.TypedValue[int64] `xml:"VersionDownloadCount"` } type FeedEntry struct { @@ -86,28 +95,54 @@ func TestPackageNuGet(t *testing.T) { readToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadPackage) badToken := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadNotification) - packageName := "test.package" + packageName := "test.package" // id + packageID := packageName packageVersion := "1.0.3" packageAuthors := "KN4CK3R" packageDescription := "Gitea Test Package" + symbolFilename := "test.pdb" symbolID := "d910bb6948bd4c6cb40155bcf52c3c94" + packageCopyright := "Package Copyright" + packageIconURL := "https://gitea.io/favicon.png" + packageLanguage := "Package Language" + packageLicenseURL := "https://gitea.io/license" + packageMinClientVersion := "1.0.0.0" + packageOwners := "Package Owners" + packageProjectURL := "https://gitea.io" + packageReleaseNotes := "Package Release Notes" + packageTags := "tag_1 tag_2 tag_3" + packageTitle := "Package Title" + packageDevelopmentDependency := true + packageRequireLicenseAcceptance := true + createNuspec := func(id, version string) string { return `<?xml version="1.0" encoding="utf-8"?> -<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> - <metadata> - <id>` + id + `</id> - <version>` + version + `</version> - <authors>` + packageAuthors + `</authors> - <description>` + packageDescription + `</description> - <dependencies> - <group targetFramework=".NETStandard2.0"> - <dependency id="Microsoft.CSharp" version="4.5.0" /> - </group> - </dependencies> - </metadata> -</package>` + <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> + <metadata minClientVersion="` + packageMinClientVersion + `"> + <authors>` + packageAuthors + `</authors> + <copyright>` + packageCopyright + `</copyright> + <description>` + packageDescription + `</description> + <developmentDependency>true</developmentDependency> + <iconUrl>` + packageIconURL + `</iconUrl> + <id>` + id + `</id> + <language>` + packageLanguage + `</language> + <licenseUrl>` + packageLicenseURL + `</licenseUrl> + <owners>` + packageOwners + `</owners> + <projectUrl>` + packageProjectURL + `</projectUrl> + <releaseNotes>` + packageReleaseNotes + `</releaseNotes> + <requireLicenseAcceptance>true</requireLicenseAcceptance> + <tags>` + packageTags + `</tags> + <title>` + packageTitle + `</title> + <version>` + version + `</version> + <dependencies> + <group targetFramework=".NETStandard2.0"> + <dependency id="Microsoft.CSharp" version="4.5.0" /> + </group> + </dependencies> + </metadata> + </package>` } createPackage := func(id, version string) *bytes.Buffer { @@ -393,7 +428,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) assert.NoError(t, err) - assert.Equal(t, int64(412), pb.Size) + assert.Equal(t, int64(610), pb.Size) case fmt.Sprintf("%s.%s.snupkg", packageName, packageVersion): assert.False(t, pf.IsLead) @@ -405,7 +440,7 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) pb, err := packages.GetBlobByID(db.DefaultContext, pf.BlobID) assert.NoError(t, err) - assert.Equal(t, int64(427), pb.Size) + assert.Equal(t, int64(996), pb.Size) case symbolFilename: assert.False(t, pf.IsLead) @@ -736,10 +771,24 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`) var result FeedEntry decodeXML(t, resp, &result) - assert.Equal(t, packageName, result.Properties.Title) - assert.Equal(t, packageVersion, result.Properties.Version) assert.Equal(t, packageAuthors, result.Properties.Authors) assert.Equal(t, packageDescription, result.Properties.Description) + assert.Equal(t, packageID, result.Properties.ID) + assert.Equal(t, packageVersion, result.Properties.Version) + + assert.Equal(t, packageCopyright, result.Properties.Copyright) + assert.Equal(t, packageDevelopmentDependency, result.Properties.DevelopmentDependency.Value) + assert.Equal(t, packageIconURL, result.Properties.IconURL) + assert.Equal(t, packageLanguage, result.Properties.Language) + assert.Equal(t, packageLicenseURL, result.Properties.LicenseURL) + assert.Equal(t, packageMinClientVersion, result.Properties.MinClientVersion) + assert.Equal(t, packageOwners, result.Properties.Owners) + assert.Equal(t, packageProjectURL, result.Properties.ProjectURL) + assert.Equal(t, packageReleaseNotes, result.Properties.ReleaseNotes) + assert.Equal(t, packageRequireLicenseAcceptance, result.Properties.RequireLicenseAcceptance.Value) + assert.Equal(t, packageTags, result.Properties.Tags) + assert.Equal(t, packageTitle, result.Properties.Title) + assert.Equal(t, "Microsoft.CSharp:4.5.0:.NETStandard2.0", result.Properties.Dependencies) }) diff --git a/tests/integration/api_packages_test.go b/tests/integration/api_packages_test.go index 786addbd76..b6a79940cb 100644 --- a/tests/integration/api_packages_test.go +++ b/tests/integration/api_packages_test.go @@ -15,9 +15,9 @@ import ( auth_model "code.gitea.io/gitea/models/auth" "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" - container_model "code.gitea.io/gitea/models/packages/container" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + container_module "code.gitea.io/gitea/modules/packages/container" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" @@ -538,7 +538,7 @@ func TestPackageCleanup(t *testing.T) { assert.NoError(t, err) assert.NotEmpty(t, pbs) - _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion) + _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_module.UploadVersion) assert.NoError(t, err) err = packages_cleanup_service.CleanupTask(db.DefaultContext, duration) @@ -548,7 +548,7 @@ func TestPackageCleanup(t *testing.T) { assert.NoError(t, err) assert.Empty(t, pbs) - _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_model.UploadVersion) + _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, user.ID, packages_model.TypeContainer, "cleanup-test", container_module.UploadVersion) assert.ErrorIs(t, err, packages_model.ErrPackageNotExist) }) diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go index 0a7f37facb..df0fc3dd05 100644 --- a/tests/integration/api_repo_file_create_test.go +++ b/tests/integration/api_repo_file_create_test.go @@ -130,7 +130,7 @@ func BenchmarkAPICreateFileSmall(b *testing.B) { repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo b.ResetTimer() - for n := 0; n < b.N; n++ { + for n := 0; b.Loop(); n++ { treePath := fmt.Sprintf("update/file%d.txt", n) _, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath) } @@ -145,7 +145,7 @@ func BenchmarkAPICreateFileMedium(b *testing.B) { repo1 := unittest.AssertExistsAndLoadBean(b, &repo_model.Repository{ID: 1}) // public repo b.ResetTimer() - for n := 0; n < b.N; n++ { + for n := 0; b.Loop(); n++ { treePath := fmt.Sprintf("update/file%d.txt", n) copy(data, treePath) _, _ = createFileInBranch(user2, repo1, treePath, repo1.DefaultBranch, treePath) diff --git a/tests/integration/git_push_test.go b/tests/integration/git_push_test.go index bac7b4f48b..4296022021 100644 --- a/tests/integration/git_push_test.go +++ b/tests/integration/git_push_test.go @@ -27,7 +27,7 @@ func TestGitPush(t *testing.T) { func testGitPush(t *testing.T, u *url.URL) { t.Run("Push branches at once", func(t *testing.T) { runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) { - for i := 0; i < 100; i++ { + for i := range 100 { branchName := fmt.Sprintf("branch-%d", i) pushed = append(pushed, branchName) doGitCreateBranch(gitPath, branchName)(t) @@ -40,7 +40,7 @@ func testGitPush(t *testing.T, u *url.URL) { t.Run("Push branches exists", func(t *testing.T) { runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) { - for i := 0; i < 10; i++ { + for i := range 10 { branchName := fmt.Sprintf("branch-%d", i) if i < 5 { pushed = append(pushed, branchName) @@ -54,7 +54,7 @@ func testGitPush(t *testing.T, u *url.URL) { pushed = pushed[:0] // do some changes for the first 5 branches created above - for i := 0; i < 5; i++ { + for i := range 5 { branchName := fmt.Sprintf("branch-%d", i) pushed = append(pushed, branchName) @@ -75,7 +75,7 @@ func testGitPush(t *testing.T, u *url.URL) { t.Run("Push branches one by one", func(t *testing.T) { runTestGitPush(t, u, func(t *testing.T, gitPath string) (pushed, deleted []string) { - for i := 0; i < 100; i++ { + for i := range 100 { branchName := fmt.Sprintf("branch-%d", i) doGitCreateBranch(gitPath, branchName)(t) doGitPushTestRepository(gitPath, "origin", branchName)(t) @@ -101,14 +101,14 @@ func testGitPush(t *testing.T, u *url.URL) { doGitPushTestRepository(gitPath, "origin", "master")(t) // make sure master is the default branch instead of a branch we are going to delete pushed = append(pushed, "master") - for i := 0; i < 100; i++ { + for i := range 100 { branchName := fmt.Sprintf("branch-%d", i) pushed = append(pushed, branchName) doGitCreateBranch(gitPath, branchName)(t) } doGitPushTestRepository(gitPath, "origin", "--all")(t) - for i := 0; i < 10; i++ { + for i := range 10 { branchName := fmt.Sprintf("branch-%d", i) doGitPushTestRepository(gitPath, "origin", "--delete", branchName)(t) deleted = append(deleted, branchName) diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index 2e6a12df2c..7b803cd54d 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -76,14 +76,11 @@ func TestViewIssuesSortByType(t *testing.T) { htmlDoc := NewHTMLParser(t, resp.Body) issuesSelection := getIssuesSelection(t, htmlDoc) - expectedNumIssues := unittest.GetCount(t, + expectedNumIssues := min(unittest.GetCount(t, &issues_model.Issue{RepoID: repo.ID, PosterID: user.ID}, unittest.Cond("is_closed=?", false), unittest.Cond("is_pull=?", false), - ) - if expectedNumIssues > setting.UI.IssuePagingNum { - expectedNumIssues = setting.UI.IssuePagingNum - } + ), setting.UI.IssuePagingNum) assert.Equal(t, expectedNumIssues, issuesSelection.Length()) issuesSelection.Each(func(_ int, selection *goquery.Selection) { @@ -491,10 +488,9 @@ func TestSearchIssues(t *testing.T) { session := loginUser(t, "user2") - expectedIssueCount := 20 // from the fixtures - if expectedIssueCount > setting.UI.IssuePagingNum { - expectedIssueCount = setting.UI.IssuePagingNum - } + expectedIssueCount := min( + // from the fixtures + 20, setting.UI.IssuePagingNum) link, _ := url.Parse("/issues/search") req := NewRequest(t, "GET", link.String()) @@ -585,10 +581,9 @@ func TestSearchIssues(t *testing.T) { func TestSearchIssuesWithLabels(t *testing.T) { defer tests.PrepareTestEnv(t)() - expectedIssueCount := 20 // from the fixtures - if expectedIssueCount > setting.UI.IssuePagingNum { - expectedIssueCount = setting.UI.IssuePagingNum - } + expectedIssueCount := min( + // from the fixtures + 20, setting.UI.IssuePagingNum) session := loginUser(t, "user1") link, _ := url.Parse("/issues/search") diff --git a/tests/integration/org_test.go b/tests/integration/org_test.go index 9a93455858..0675648391 100644 --- a/tests/integration/org_test.go +++ b/tests/integration/org_test.go @@ -40,7 +40,7 @@ func TestOrgRepos(t *testing.T) { sel := htmlDoc.doc.Find("a.name") assert.Len(t, repos, len(sel.Nodes)) - for i := 0; i < len(repos); i++ { + for i := range repos { assert.Equal(t, repos[i], strings.TrimSpace(sel.Eq(i).Text())) } } diff --git a/tests/integration/project_test.go b/tests/integration/project_test.go index 13213c254d..43a489d4c4 100644 --- a/tests/integration/project_test.go +++ b/tests/integration/project_test.go @@ -47,7 +47,7 @@ func TestMoveRepoProjectColumns(t *testing.T) { err := project_model.NewProject(db.DefaultContext, &project1) assert.NoError(t, err) - for i := 0; i < 3; i++ { + for i := range 3 { err = project_model.NewColumn(db.DefaultContext, &project_model.Column{ Title: fmt.Sprintf("column %d", i+1), ProjectID: project1.ID, diff --git a/tests/integration/release_test.go b/tests/integration/release_test.go index 05b90c4c68..88a58787af 100644 --- a/tests/integration/release_test.go +++ b/tests/integration/release_test.go @@ -114,7 +114,7 @@ func TestCreateReleasePaging(t *testing.T) { session := loginUser(t, "user2") // Create enough releases to have paging - for i := 0; i < 12; i++ { + for i := range 12 { version := fmt.Sprintf("v0.0.%d", i) createNewRelease(t, session, "/user2/repo1", version, version, false, false) } diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go index bef957597a..0097a7f62e 100644 --- a/tests/integration/repo_commits_test.go +++ b/tests/integration/repo_commits_test.go @@ -169,7 +169,7 @@ func TestRepoCommitsStatusParallel(t *testing.T) { assert.NotEmpty(t, commitURL) var wg sync.WaitGroup - for i := 0; i < 10; i++ { + for i := range 10 { wg.Add(1) go func(parentT *testing.T, i int) { parentT.Run(fmt.Sprintf("ParallelCreateStatus_%d", i), func(t *testing.T) { diff --git a/tests/integration/repofiles_change_test.go b/tests/integration/repofiles_change_test.go index ce55a2f943..461175e1cc 100644 --- a/tests/integration/repofiles_change_test.go +++ b/tests/integration/repofiles_change_test.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/contexttest" files_service "code.gitea.io/gitea/services/repository/files" @@ -58,6 +59,40 @@ func getUpdateRepoFilesOptions(repo *repo_model.Repository) *files_service.Chang } } +func getUpdateRepoFilesRenameOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions { + return &files_service.ChangeRepoFilesOptions{ + Files: []*files_service.ChangeRepoFile{ + // move normally + { + Operation: "rename", + FromTreePath: "README.md", + TreePath: "README.txt", + }, + // move from in lfs + { + Operation: "rename", + FromTreePath: "crypt.bin", + TreePath: "crypt1.bin", + }, + // move from lfs to normal + { + Operation: "rename", + FromTreePath: "jpeg.jpg", + TreePath: "jpeg.jpeg", + }, + // move from normal to lfs + { + Operation: "rename", + FromTreePath: "CONTRIBUTING.md", + TreePath: "CONTRIBUTING.md.bin", + }, + }, + OldBranch: repo.DefaultBranch, + NewBranch: repo.DefaultBranch, + Message: "Rename files", + } +} + func getDeleteRepoFilesOptions(repo *repo_model.Repository) *files_service.ChangeRepoFilesOptions { return &files_service.ChangeRepoFilesOptions{ Files: []*files_service.ChangeRepoFile{ @@ -248,6 +283,106 @@ func getExpectedFileResponseForRepoFilesUpdate(commitID, filename, lastCommitSHA } } +func getExpectedFileResponseForRepoFilesUpdateRename(commitID, lastCommitSHA string) *api.FilesResponse { + details := []struct { + filename, sha, content string + size int64 + }{ + { + filename: "README.txt", + sha: "8276d2a29779af982c0afa976bdb793b52d442a8", + size: 22, + content: "IyBBbiBMRlMtZW5hYmxlZCByZXBvCg==", + }, + { + filename: "crypt1.bin", + sha: "d4a41a0d4db4949e129bd22f871171ea988103ef", + size: 129, + content: "dmVyc2lvbiBodHRwczovL2dpdC1sZnMuZ2l0aHViLmNvbS9zcGVjL3YxCm9pZCBzaGEyNTY6MmVjY2RiNDM4MjVkMmE0OWQ5OWQ1NDJkYWEyMDA3NWNmZjFkOTdkOWQyMzQ5YTg5NzdlZmU5YzAzNjYxNzM3YwpzaXplIDIwNDgK", + }, + { + filename: "jpeg.jpeg", + sha: "71911bf48766c7181518c1070911019fbb00b1fc", + size: 107, + content: "/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k=", + }, + { + filename: "CONTRIBUTING.md.bin", + sha: "2b6c6c4eaefa24b22f2092c3d54b263ff26feb58", + size: 127, + content: "dmVyc2lvbiBodHRwczovL2dpdC1sZnMuZ2l0aHViLmNvbS9zcGVjL3YxCm9pZCBzaGEyNTY6N2I2YjJjODhkYmE5Zjc2MGExYTU4NDY5YjY3ZmVlMmI2OThlZjdlOTM5OWM0Y2E0ZjM0YTE0Y2NiZTM5ZjYyMwpzaXplIDI3Cg==", + }, + } + + var responses []*api.ContentsResponse + for _, detail := range details { + selfURL := setting.AppURL + "api/v1/repos/user2/lfs/contents/" + detail.filename + "?ref=master" + htmlURL := setting.AppURL + "user2/lfs/src/branch/master/" + detail.filename + gitURL := setting.AppURL + "api/v1/repos/user2/lfs/git/blobs/" + detail.sha + downloadURL := setting.AppURL + "user2/lfs/raw/branch/master/" + detail.filename + // don't set time related fields because there might be different time in one operation + responses = append(responses, &api.ContentsResponse{ + Name: detail.filename, + Path: detail.filename, + SHA: detail.sha, + LastCommitSHA: lastCommitSHA, + Type: "file", + Size: detail.size, + Encoding: util.ToPointer("base64"), + Content: &detail.content, + URL: &selfURL, + HTMLURL: &htmlURL, + GitURL: &gitURL, + DownloadURL: &downloadURL, + Links: &api.FileLinksResponse{ + Self: &selfURL, + GitURL: &gitURL, + HTMLURL: &htmlURL, + }, + }) + } + + return &api.FilesResponse{ + Files: responses, + Commit: &api.FileCommitResponse{ + CommitMeta: api.CommitMeta{ + URL: setting.AppURL + "api/v1/repos/user2/lfs/git/commits/" + commitID, + SHA: commitID, + }, + HTMLURL: setting.AppURL + "user2/lfs/commit/" + commitID, + Author: &api.CommitUser{ + Identity: api.Identity{ + Name: "User Two", + Email: "user2@noreply.example.org", + }, + }, + Committer: &api.CommitUser{ + Identity: api.Identity{ + Name: "User Two", + Email: "user2@noreply.example.org", + }, + }, + Parents: []*api.CommitMeta{ + { + URL: setting.AppURL + "api/v1/repos/user2/lfs/git/commits/73cf03db6ece34e12bf91e8853dc58f678f2f82d", + SHA: "73cf03db6ece34e12bf91e8853dc58f678f2f82d", + }, + }, + Message: "Rename files\n", + Tree: &api.CommitMeta{ + URL: setting.AppURL + "api/v1/repos/user2/lfs/git/trees/5307376dc3a5557dc1c403c29a8984668ca9ecb5", + SHA: "5307376dc3a5557dc1c403c29a8984668ca9ecb5", + }, + }, + Verification: &api.PayloadCommitVerification{ + Verified: false, + Reason: "gpg.error.not_signed_commit", + Signature: "", + Payload: "", + }, + } +} + func TestChangeRepoFilesForCreate(t *testing.T) { // setup onGiteaRun(t, func(t *testing.T, u *url.URL) { @@ -369,6 +504,38 @@ func TestChangeRepoFilesForUpdateWithFileMove(t *testing.T) { }) } +func TestChangeRepoFilesForUpdateWithFileRename(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + ctx, _ := contexttest.MockContext(t, "user2/lfs") + ctx.SetPathParam("id", "54") + contexttest.LoadRepo(t, ctx, 54) + contexttest.LoadRepoCommit(t, ctx) + contexttest.LoadUser(t, ctx, 2) + contexttest.LoadGitRepo(t, ctx) + defer ctx.Repo.GitRepo.Close() + + repo := ctx.Repo.Repository + opts := getUpdateRepoFilesRenameOptions(repo) + + // test + filesResponse, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, ctx.Doer, opts) + + // asserts + assert.NoError(t, err) + gitRepo, _ := gitrepo.OpenRepository(git.DefaultContext, repo) + defer gitRepo.Close() + + commit, _ := gitRepo.GetBranchCommit(repo.DefaultBranch) + lastCommit, _ := commit.GetCommitByPath(opts.Files[0].TreePath) + expectedFileResponse := getExpectedFileResponseForRepoFilesUpdateRename(commit.ID.String(), lastCommit.ID.String()) + for _, file := range filesResponse.Files { + file.LastCommitterDate, file.LastAuthorDate = time.Time{}, time.Time{} // there might be different time in one operation, so we ignore them + } + assert.Len(t, filesResponse.Files, 4) + assert.Equal(t, expectedFileResponse.Files, filesResponse.Files) + }) +} + // Test opts with branch names removed, should get same results as above test func TestChangeRepoFilesWithoutBranchNames(t *testing.T) { // setup diff --git a/tests/integration/ssh_key_test.go b/tests/integration/ssh_key_test.go index fbdda9b3af..b34a986be3 100644 --- a/tests/integration/ssh_key_test.go +++ b/tests/integration/ssh_key_test.go @@ -27,7 +27,7 @@ func doCheckRepositoryEmptyStatus(ctx APITestContext, isEmpty bool) func(*testin func doAddChangesToCheckout(dstPath, filename string) func(*testing.T) { return func(t *testing.T) { - assert.NoError(t, os.WriteFile(filepath.Join(dstPath, filename), []byte(fmt.Sprintf("# Testing Repository\n\nOriginally created in: %s at time: %v", dstPath, time.Now())), 0o644)) + assert.NoError(t, os.WriteFile(filepath.Join(dstPath, filename), fmt.Appendf(nil, "# Testing Repository\n\nOriginally created in: %s at time: %v", dstPath, time.Now()), 0o644)) assert.NoError(t, git.AddChanges(dstPath, true)) signature := git.Signature{ Email: "test@example.com", diff --git a/web_src/js/features/comp/EditorUpload.test.ts b/web_src/js/features/comp/EditorUpload.test.ts index 55f3f74389..e6e5f4de13 100644 --- a/web_src/js/features/comp/EditorUpload.test.ts +++ b/web_src/js/features/comp/EditorUpload.test.ts @@ -1,4 +1,4 @@ -import {removeAttachmentLinksFromMarkdown} from './EditorUpload.ts'; +import {pasteAsMarkdownLink, removeAttachmentLinksFromMarkdown} from './EditorUpload.ts'; test('removeAttachmentLinksFromMarkdown', () => { expect(removeAttachmentLinksFromMarkdown('a foo b', 'foo')).toBe('a foo b'); @@ -12,3 +12,13 @@ test('removeAttachmentLinksFromMarkdown', () => { expect(removeAttachmentLinksFromMarkdown('a <img src="/attachments/foo"> b', 'foo')).toBe('a b'); expect(removeAttachmentLinksFromMarkdown('a <img src="/attachments/foo" width="100"/> b', 'foo')).toBe('a b'); }); + +test('preparePasteAsMarkdownLink', () => { + expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 0}, 'bar')).toBeNull(); + expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 0}, 'https://gitea.com')).toBeNull(); + expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 3}, 'bar')).toBeNull(); + expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 3}, 'https://gitea.com')).toBe('[foo](https://gitea.com)'); + expect(pasteAsMarkdownLink({value: '..(url)', selectionStart: 3, selectionEnd: 6}, 'https://gitea.com')).toBe('[url](https://gitea.com)'); + expect(pasteAsMarkdownLink({value: '[](url)', selectionStart: 3, selectionEnd: 6}, 'https://gitea.com')).toBeNull(); + expect(pasteAsMarkdownLink({value: 'https://example.com', selectionStart: 0, selectionEnd: 19}, 'https://gitea.com')).toBeNull(); +}); diff --git a/web_src/js/features/comp/EditorUpload.ts b/web_src/js/features/comp/EditorUpload.ts index f6d5731422..3f6d26658d 100644 --- a/web_src/js/features/comp/EditorUpload.ts +++ b/web_src/js/features/comp/EditorUpload.ts @@ -118,17 +118,26 @@ export function removeAttachmentLinksFromMarkdown(text: string, fileUuid: string return text; } -function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, text: string, isShiftDown: boolean) { +export function pasteAsMarkdownLink(textarea: {value: string, selectionStart: number, selectionEnd: number}, pastedText: string): string | null { + const {value, selectionStart, selectionEnd} = textarea; + const selectedText = value.substring(selectionStart, selectionEnd); + const trimmedText = pastedText.trim(); + const beforeSelection = value.substring(0, selectionStart); + const afterSelection = value.substring(selectionEnd); + const isInMarkdownLink = beforeSelection.endsWith('](') && afterSelection.startsWith(')'); + const asMarkdownLink = selectedText && isUrl(trimmedText) && !isUrl(selectedText) && !isInMarkdownLink; + return asMarkdownLink ? `[${selectedText}](${trimmedText})` : null; +} + +function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, pastedText: string, isShiftDown: boolean) { // pasting with "shift" means "paste as original content" in most applications if (isShiftDown) return; // let the browser handle it // when pasting links over selected text, turn it into [text](link) - const {value, selectionStart, selectionEnd} = textarea; - const selectedText = value.substring(selectionStart, selectionEnd); - const trimmedText = text.trim(); - if (selectedText && isUrl(trimmedText) && !isUrl(selectedText)) { + const pastedAsMarkdown = pasteAsMarkdownLink(textarea, pastedText); + if (pastedText) { e.preventDefault(); - replaceTextareaSelection(textarea, `[${selectedText}](${trimmedText})`); + replaceTextareaSelection(textarea, pastedAsMarkdown); } // else, let the browser handle it } diff --git a/web_src/js/features/repo-editor.ts b/web_src/js/features/repo-editor.ts index 0f77508f70..acf4127399 100644 --- a/web_src/js/features/repo-editor.ts +++ b/web_src/js/features/repo-editor.ts @@ -141,38 +141,39 @@ export function initRepoEditor() { } }); + const elForm = document.querySelector<HTMLFormElement>('.repository.editor .edit.form'); + + // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage + // to enable or disable the commit button + const commitButton = document.querySelector<HTMLButtonElement>('#commit-button'); + const dirtyFileClass = 'dirty-file'; + + // Enabling the button at the start if the page has posted + if (document.querySelector<HTMLInputElement>('input[name="page_has_posted"]')?.value === 'true') { + commitButton.disabled = false; + } + + // Registering a custom listener for the file path and the file content + // FIXME: it is not quite right here (old bug), it causes double-init, the global areYouSure "dirty" class will also be added + applyAreYouSure(elForm, { + silent: true, + dirtyClass: dirtyFileClass, + fieldSelector: ':input:not(.commit-form-wrapper :input)', + change($form: any) { + const dirty = $form[0]?.classList.contains(dirtyFileClass); + commitButton.disabled = !dirty; + }, + }); + // on the upload page, there is no editor(textarea) const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area'); if (!editArea) return; - const elForm = document.querySelector<HTMLFormElement>('.repository.editor .edit.form'); initEditPreviewTab(elForm); (async () => { const editor = await createCodeEditor(editArea, filenameInput); - // Using events from https://github.com/codedance/jquery.AreYouSure#advanced-usage - // to enable or disable the commit button - const commitButton = document.querySelector<HTMLButtonElement>('#commit-button'); - const dirtyFileClass = 'dirty-file'; - - // Disabling the button at the start - if (document.querySelector<HTMLInputElement>('input[name="page_has_posted"]').value !== 'true') { - commitButton.disabled = true; - } - - // Registering a custom listener for the file path and the file content - // FIXME: it is not quite right here (old bug), it causes double-init, the global areYouSure "dirty" class will also be added - applyAreYouSure(elForm, { - silent: true, - dirtyClass: dirtyFileClass, - fieldSelector: ':input:not(.commit-form-wrapper :input)', - change($form: any) { - const dirty = $form[0]?.classList.contains(dirtyFileClass); - commitButton.disabled = !dirty; - }, - }); - // Update the editor from query params, if available, // only after the dirtyFileClass initialization const params = new URLSearchParams(window.location.search); diff --git a/web_src/js/modules/fomantic/dropdown.ts b/web_src/js/modules/fomantic/dropdown.ts index 0360b8ef95..02fee5a267 100644 --- a/web_src/js/modules/fomantic/dropdown.ts +++ b/web_src/js/modules/fomantic/dropdown.ts @@ -72,10 +72,10 @@ function updateSelectionLabel(label: HTMLElement) { } function onAfterFiltered(this: any) { - const $dropdown = $(this); + const $dropdown = $(this).closest('.ui.dropdown'); // "this" can be the "ui dropdown" or "<select>" const hideEmptyDividers = $dropdown.dropdown('setting', 'hideDividers') === 'empty'; const itemsMenu = $dropdown[0].querySelector('.scrolling.menu') || $dropdown[0].querySelector('.menu'); - if (hideEmptyDividers) hideScopedEmptyDividers(itemsMenu); + if (hideEmptyDividers && itemsMenu) hideScopedEmptyDividers(itemsMenu); } // delegate the dropdown's template functions and callback functions to add aria attributes. |