diff --git a/build/update-locales.sh b/build/update-locales.sh index 046f48ee86691..b7611c0c9a747 100755 --- a/build/update-locales.sh +++ b/build/update-locales.sh @@ -1,14 +1,49 @@ -#!/bin/sh +#!/bin/bash + +set -e + +SED=sed + +if [[ $OSTYPE == 'darwin'* ]]; then + # for macOS developers, use "brew install gnu-sed" + SED=gsed +fi + +if [ ! -f ./options/locale/locale_en-US.ini ]; then + echo "please run this script in the root directory of the project" + exit 1 +fi mv ./options/locale/locale_en-US.ini ./options/ -# Make sure to only change lines that have the translation enclosed between quotes -sed -i -r -e '/^[a-zA-Z0-9_.-]+[ ]*=[ ]*".*"$/ { - s/^([a-zA-Z0-9_.-]+)[ ]*="/\1=/ - s/\\"/"/g +# the "ini" library for locale has many quirks +# * `a="xx"` gets `xx` (no quote) +# * `a=x\"y` gets `x\"y` (no unescaping) +# * `a="x\"y"` gets `"x\"y"` (no unescaping, the quotes are still there) +# * `a='x\"y'` gets `x\"y` (no unescaping, no quote) +# * `a="foo` gets `"foo` (although the quote is not closed) +# * 'a=`foo`' works like single-quote +# crowdin needs the strings to be quoted correctly and doesn't like incomplete quotes +# crowdin always outputs quoted strings if there are quotes in the strings. + +# this script helps to unquote the crowdin outputs for the quirky ini library +# * find all `key="...\"..."` lines +# * remove the leading quote +# * remove the trailing quote +# * unescape the quotes +# * eg: key="...\"..." => key=..."... +$SED -i -r -e '/^[-.A-Za-z0-9_]+[ ]*=[ ]*".*"$/ { + s/^([-.A-Za-z0-9_]+)[ ]*=[ ]*"/\1=/ s/"$// + s/\\"/"/g }' ./options/locale/*.ini +# * if the escaped line is incomplete like `key="...` or `key=..."`, quote it with backticks +# * eg: key="... => key=`"...` +# * eg: key=..." => key=`..."` +$SED -i -r -e 's/^([-.A-Za-z0-9_]+)[ ]*=[ ]*(".*[^"])$/\1=`\2`/' ./options/locale/*.ini +$SED -i -r -e 's/^([-.A-Za-z0-9_]+)[ ]*=[ ]*([^"].*")$/\1=`\2`/' ./options/locale/*.ini + # Remove translation under 25% of en_us baselines=$(wc -l "./options/locale_en-US.ini" | cut -d" " -f1) baselines=$((baselines / 4)) diff --git a/cmd/dump.go b/cmd/dump.go index c879d2fbee07b..600ec4f32eb08 100644 --- a/cmd/dump.go +++ b/cmd/dump.go @@ -272,6 +272,7 @@ func runDump(ctx *cli.Context) error { fatal("Failed to create tmp file: %v", err) } defer func() { + _ = dbDump.Close() if err := util.Remove(dbDump.Name()); err != nil { log.Warn("Unable to remove temporary file: %s: Error: %v", dbDump.Name(), err) } diff --git a/cmd/serv.go b/cmd/serv.go index 145d1b9e935f6..d7510845a5beb 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -11,6 +11,7 @@ import ( "net/url" "os" "os/exec" + "path/filepath" "regexp" "strconv" "strings" @@ -290,17 +291,21 @@ func runServ(c *cli.Context) error { return nil } - // Special handle for Windows. - if setting.IsWindows { - verb = strings.Replace(verb, "-", " ", 1) - } - var gitcmd *exec.Cmd - verbs := strings.Split(verb, " ") - if len(verbs) == 2 { - gitcmd = exec.CommandContext(ctx, verbs[0], verbs[1], repoPath) - } else { - gitcmd = exec.CommandContext(ctx, verb, repoPath) + gitBinPath := filepath.Dir(git.GitExecutable) // e.g. /usr/bin + gitBinVerb := filepath.Join(gitBinPath, verb) // e.g. /usr/bin/git-upload-pack + if _, err := os.Stat(gitBinVerb); err != nil { + // if the command "git-upload-pack" doesn't exist, try to split "git-upload-pack" to use the sub-command with git + // ps: Windows only has "git.exe" in the bin path, so Windows always uses this way + verbFields := strings.SplitN(verb, "-", 2) + if len(verbFields) == 2 { + // use git binary with the sub-command part: "C:\...\bin\git.exe", "upload-pack", ... + gitcmd = exec.CommandContext(ctx, git.GitExecutable, verbFields[1], repoPath) + } + } + if gitcmd == nil { + // by default, use the verb (it has been checked above by allowedCommands) + gitcmd = exec.CommandContext(ctx, gitBinVerb, repoPath) } process.SetSysProcAttribute(gitcmd) diff --git a/docs/content/doc/developers/guidelines-frontend.en-us.md b/docs/content/doc/developers/guidelines-frontend.en-us.md index 7f4d87d9011ed..66d3e83e938c3 100644 --- a/docs/content/doc/developers/guidelines-frontend.en-us.md +++ b/docs/content/doc/developers/guidelines-frontend.en-us.md @@ -83,6 +83,9 @@ It's not recommended to use `async` event listeners, which may lead to problems. The reason is that the code after await is executed outside the event dispatch. Reference: https://github.com/github/eslint-plugin-github/blob/main/docs/rules/async-preventdefault.md +If an event listener must be `async`, the `e.preventDefault()` should be before any `await`, +it's recommended to put it at the beginning of the function. + If we want to call an `async` function in a non-async context, it's recommended to use `const _promise = asyncFoo()` to tell readers that this is done by purpose, we want to call the async function and ignore the Promise. diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index 58f9b919acfbc..dd8facb7a3134 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -25,7 +25,7 @@ fork_id: 0 is_template: false template_id: 0 - size: 6708 + size: 7028 is_fsck_enabled: true close_issues_via_commit_in_any_branch: false diff --git a/models/issues/label.go b/models/issues/label.go index 90e4eb458f847..35c649e8f24d9 100644 --- a/models/issues/label.go +++ b/models/issues/label.go @@ -7,12 +7,12 @@ package issues import ( "context" "fmt" - "regexp" "strconv" "strings" "code.gitea.io/gitea/models/db" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/label" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -78,9 +78,6 @@ func (err ErrLabelNotExist) Unwrap() error { return util.ErrNotExist } -// LabelColorPattern is a regexp witch can validate LabelColor -var LabelColorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$") - // Label represents a label of repository for issues. type Label struct { ID int64 `xorm:"pk autoincr"` @@ -109,12 +106,12 @@ func init() { } // CalOpenIssues sets the number of open issues of a label based on the already stored number of closed issues. -func (label *Label) CalOpenIssues() { - label.NumOpenIssues = label.NumIssues - label.NumClosedIssues +func (l *Label) CalOpenIssues() { + l.NumOpenIssues = l.NumIssues - l.NumClosedIssues } // CalOpenOrgIssues calculates the open issues of a label for a specific repo -func (label *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) { +func (l *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) { counts, _ := CountIssuesByRepo(ctx, &IssuesOptions{ RepoID: repoID, LabelIDs: []int64{labelID}, @@ -122,22 +119,22 @@ func (label *Label) CalOpenOrgIssues(ctx context.Context, repoID, labelID int64) }) for _, count := range counts { - label.NumOpenRepoIssues += count + l.NumOpenRepoIssues += count } } // LoadSelectedLabelsAfterClick calculates the set of selected labels when a label is clicked -func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64, currentSelectedExclusiveScopes []string) { +func (l *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64, currentSelectedExclusiveScopes []string) { var labelQuerySlice []string labelSelected := false - labelID := strconv.FormatInt(label.ID, 10) - labelScope := label.ExclusiveScope() + labelID := strconv.FormatInt(l.ID, 10) + labelScope := l.ExclusiveScope() for i, s := range currentSelectedLabels { - if s == label.ID { + if s == l.ID { labelSelected = true - } else if -s == label.ID { + } else if -s == l.ID { labelSelected = true - label.IsExcluded = true + l.IsExcluded = true } else if s != 0 { // Exclude other labels in the same scope from selection if s < 0 || labelScope == "" || labelScope != currentSelectedExclusiveScopes[i] { @@ -148,23 +145,23 @@ func (label *Label) LoadSelectedLabelsAfterClick(currentSelectedLabels []int64, if !labelSelected { labelQuerySlice = append(labelQuerySlice, labelID) } - label.IsSelected = labelSelected - label.QueryString = strings.Join(labelQuerySlice, ",") + l.IsSelected = labelSelected + l.QueryString = strings.Join(labelQuerySlice, ",") } // BelongsToOrg returns true if label is an organization label -func (label *Label) BelongsToOrg() bool { - return label.OrgID > 0 +func (l *Label) BelongsToOrg() bool { + return l.OrgID > 0 } // BelongsToRepo returns true if label is a repository label -func (label *Label) BelongsToRepo() bool { - return label.RepoID > 0 +func (l *Label) BelongsToRepo() bool { + return l.RepoID > 0 } // Get color as RGB values in 0..255 range -func (label *Label) ColorRGB() (float64, float64, float64, error) { - color, err := strconv.ParseUint(label.Color[1:], 16, 64) +func (l *Label) ColorRGB() (float64, float64, float64, error) { + color, err := strconv.ParseUint(l.Color[1:], 16, 64) if err != nil { return 0, 0, 0, err } @@ -176,9 +173,9 @@ func (label *Label) ColorRGB() (float64, float64, float64, error) { } // Determine if label text should be light or dark to be readable on background color -func (label *Label) UseLightTextColor() bool { - if strings.HasPrefix(label.Color, "#") { - if r, g, b, err := label.ColorRGB(); err == nil { +func (l *Label) UseLightTextColor() bool { + if strings.HasPrefix(l.Color, "#") { + if r, g, b, err := l.ColorRGB(); err == nil { // Perceived brightness from: https://www.w3.org/TR/AERT/#color-contrast // In the future WCAG 3 APCA may be a better solution brightness := (0.299*r + 0.587*g + 0.114*b) / 255 @@ -190,40 +187,26 @@ func (label *Label) UseLightTextColor() bool { } // Return scope substring of label name, or empty string if none exists -func (label *Label) ExclusiveScope() string { - if !label.Exclusive { +func (l *Label) ExclusiveScope() string { + if !l.Exclusive { return "" } - lastIndex := strings.LastIndex(label.Name, "/") - if lastIndex == -1 || lastIndex == 0 || lastIndex == len(label.Name)-1 { + lastIndex := strings.LastIndex(l.Name, "/") + if lastIndex == -1 || lastIndex == 0 || lastIndex == len(l.Name)-1 { return "" } - return label.Name[:lastIndex] + return l.Name[:lastIndex] } // NewLabel creates a new label -func NewLabel(ctx context.Context, label *Label) error { - if !LabelColorPattern.MatchString(label.Color) { - return fmt.Errorf("bad color code: %s", label.Color) - } - - // normalize case - label.Color = strings.ToLower(label.Color) - - // add leading hash - if label.Color[0] != '#' { - label.Color = "#" + label.Color - } - - // convert 3-character shorthand into 6-character version - if len(label.Color) == 4 { - r := label.Color[1] - g := label.Color[2] - b := label.Color[3] - label.Color = fmt.Sprintf("#%c%c%c%c%c%c", r, r, g, g, b, b) +func NewLabel(ctx context.Context, l *Label) error { + color, err := label.NormalizeColor(l.Color) + if err != nil { + return err } + l.Color = color - return db.Insert(ctx, label) + return db.Insert(ctx, l) } // NewLabels creates new labels @@ -234,11 +217,14 @@ func NewLabels(labels ...*Label) error { } defer committer.Close() - for _, label := range labels { - if !LabelColorPattern.MatchString(label.Color) { - return fmt.Errorf("bad color code: %s", label.Color) + for _, l := range labels { + color, err := label.NormalizeColor(l.Color) + if err != nil { + return err } - if err := db.Insert(ctx, label); err != nil { + l.Color = color + + if err := db.Insert(ctx, l); err != nil { return err } } @@ -247,15 +233,18 @@ func NewLabels(labels ...*Label) error { // UpdateLabel updates label information. func UpdateLabel(l *Label) error { - if !LabelColorPattern.MatchString(l.Color) { - return fmt.Errorf("bad color code: %s", l.Color) + color, err := label.NormalizeColor(l.Color) + if err != nil { + return err } + l.Color = color + return updateLabelCols(db.DefaultContext, l, "name", "description", "color", "exclusive") } // DeleteLabel delete a label func DeleteLabel(id, labelID int64) error { - label, err := GetLabelByID(db.DefaultContext, labelID) + l, err := GetLabelByID(db.DefaultContext, labelID) if err != nil { if IsErrLabelNotExist(err) { return nil @@ -271,10 +260,10 @@ func DeleteLabel(id, labelID int64) error { sess := db.GetEngine(ctx) - if label.BelongsToOrg() && label.OrgID != id { + if l.BelongsToOrg() && l.OrgID != id { return nil } - if label.BelongsToRepo() && label.RepoID != id { + if l.BelongsToRepo() && l.RepoID != id { return nil } @@ -682,14 +671,14 @@ func newIssueLabels(ctx context.Context, issue *Issue, labels []*Label, doer *us if err = issue.LoadRepo(ctx); err != nil { return err } - for _, label := range labels { + for _, l := range labels { // Don't add already present labels and invalid labels - if HasIssueLabel(ctx, issue.ID, label.ID) || - (label.RepoID != issue.RepoID && label.OrgID != issue.Repo.OwnerID) { + if HasIssueLabel(ctx, issue.ID, l.ID) || + (l.RepoID != issue.RepoID && l.OrgID != issue.Repo.OwnerID) { continue } - if err = newIssueLabel(ctx, issue, label, doer); err != nil { + if err = newIssueLabel(ctx, issue, l, doer); err != nil { return fmt.Errorf("newIssueLabel: %w", err) } } diff --git a/models/issues/label_test.go b/models/issues/label_test.go index 0e45e0db0bac6..1f6ce4f42ee78 100644 --- a/models/issues/label_test.go +++ b/models/issues/label_test.go @@ -15,8 +15,6 @@ import ( "github.com/stretchr/testify/assert" ) -// TODO TestGetLabelTemplateFile - func TestLabel_CalOpenIssues(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) label := unittest.AssertExistsAndLoadBean(t, &issues_model.Label{ID: 1}) diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go index f4efd916c8678..9d361c5c5da91 100644 --- a/models/issues/pull_list.go +++ b/models/issues/pull_list.go @@ -52,13 +52,16 @@ func listPullRequestStatement(baseRepoID int64, opts *PullRequestsOptions) (*xor // GetUnmergedPullRequestsByHeadInfo returns all pull requests that are open and has not been merged // by given head information (repo and branch). -func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) { +// arg `includeClosed` controls whether the SQL returns closed PRs +func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string, includeClosed bool) ([]*PullRequest, error) { prs := make([]*PullRequest, 0, 2) - return prs, db.GetEngine(db.DefaultContext). - Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND issue.is_closed = ? AND flow = ?", - repoID, branch, false, false, PullRequestFlowGithub). + sess := db.GetEngine(db.DefaultContext). Join("INNER", "issue", "issue.id = pull_request.issue_id"). - Find(&prs) + Where("head_repo_id = ? AND head_branch = ? AND has_merged = ? AND flow = ?", repoID, branch, false, PullRequestFlowGithub) + if !includeClosed { + sess.Where("issue.is_closed = ?", false) + } + return prs, sess.Find(&prs) } // CanMaintainerWriteToBranch check whether user is a maintainer and could write to the branch @@ -71,7 +74,7 @@ func CanMaintainerWriteToBranch(p access_model.Permission, branch string, user * return false } - prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch) + prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch, false) if err != nil { return false } @@ -111,6 +114,7 @@ func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequ return prs, db.GetEngine(db.DefaultContext). Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?", repoID, branch, false, false). + OrderBy("issue.updated_unix DESC"). Join("INNER", "issue", "issue.id=pull_request.issue_id"). Find(&prs) } diff --git a/models/issues/pull_test.go b/models/issues/pull_test.go index 8ce8eecc4aa87..bcd33329eb9bf 100644 --- a/models/issues/pull_test.go +++ b/models/issues/pull_test.go @@ -118,7 +118,7 @@ func TestHasUnmergedPullRequestsByHeadInfo(t *testing.T) { func TestGetUnmergedPullRequestsByHeadInfo(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) - prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(1, "branch2") + prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(1, "branch2", false) assert.NoError(t, err) assert.Len(t, prs, 1) for _, pr := range prs { diff --git a/modules/git/git.go b/modules/git/git.go index 2feb242ac5dd0..24cfea8c7fe17 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -312,7 +312,7 @@ func CheckGitVersionAtLeast(atLeast string) error { } func configSet(key, value string) error { - stdout, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil) + stdout, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) if err != nil && !err.IsExitCode(1) { return fmt.Errorf("failed to get git config %s, err: %w", key, err) } @@ -331,7 +331,7 @@ func configSet(key, value string) error { } func configSetNonExist(key, value string) error { - _, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil) + _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) if err == nil { // already exist return nil @@ -349,7 +349,7 @@ func configSetNonExist(key, value string) error { } func configAddNonExist(key, value string) error { - _, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil) + _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil) if err == nil { // already exist return nil @@ -366,7 +366,7 @@ func configAddNonExist(key, value string) error { } func configUnsetAll(key, value string) error { - _, _, err := NewCommand(DefaultContext, "config", "--get").AddDynamicArguments(key).RunStdString(nil) + _, _, err := NewCommand(DefaultContext, "config", "--global", "--get").AddDynamicArguments(key).RunStdString(nil) if err == nil { // exist, need to remove _, _, err = NewCommand(DefaultContext, "config", "--global", "--unset-all").AddDynamicArguments(key, regexp.QuoteMeta(value)).RunStdString(nil) diff --git a/modules/git/repo_commit_gogit.go b/modules/git/repo_commit_gogit.go index 72de158e6e11c..ce0af936140db 100644 --- a/modules/git/repo_commit_gogit.go +++ b/modules/git/repo_commit_gogit.go @@ -7,7 +7,6 @@ package git import ( - "fmt" "strings" "github.com/go-git/go-git/v5/plumbing" @@ -67,38 +66,6 @@ func (repo *Repository) IsCommitExist(name string) bool { return err == nil } -func convertPGPSignatureForTag(t *object.Tag) *CommitGPGSignature { - if t.PGPSignature == "" { - return nil - } - - var w strings.Builder - var err error - - if _, err = fmt.Fprintf(&w, - "object %s\ntype %s\ntag %s\ntagger ", - t.Target.String(), t.TargetType.Bytes(), t.Name); err != nil { - return nil - } - - if err = t.Tagger.Encode(&w); err != nil { - return nil - } - - if _, err = fmt.Fprintf(&w, "\n\n"); err != nil { - return nil - } - - if _, err = fmt.Fprintf(&w, t.Message); err != nil { - return nil - } - - return &CommitGPGSignature{ - Signature: t.PGPSignature, - Payload: strings.TrimSpace(w.String()) + "\n", - } -} - func (repo *Repository) getCommit(id SHA1) (*Commit, error) { var tagObject *object.Tag @@ -122,12 +89,6 @@ func (repo *Repository) getCommit(id SHA1) (*Commit, error) { commit := convertCommit(gogitCommit) commit.repo = repo - if tagObject != nil { - commit.CommitMessage = strings.TrimSpace(tagObject.Message) - commit.Author = &tagObject.Tagger - commit.Signature = convertPGPSignatureForTag(tagObject) - } - tree, err := gogitCommit.Tree() if err != nil { return nil, err diff --git a/modules/git/repo_commit_nogogit.go b/modules/git/repo_commit_nogogit.go index 7373d01c8efbe..d5eb723100a73 100644 --- a/modules/git/repo_commit_nogogit.go +++ b/modules/git/repo_commit_nogogit.go @@ -107,10 +107,6 @@ func (repo *Repository) getCommitFromBatchReader(rd *bufio.Reader, id SHA1) (*Co return nil, err } - commit.CommitMessage = strings.TrimSpace(tag.Message) - commit.Author = tag.Tagger - commit.Signature = tag.Signature - return commit, nil case "commit": commit, err := CommitFromReader(repo, id, io.LimitReader(rd, size)) diff --git a/modules/git/repo_commit_test.go b/modules/git/repo_commit_test.go index af8c0592fe221..729fb0ba103ab 100644 --- a/modules/git/repo_commit_test.go +++ b/modules/git/repo_commit_test.go @@ -43,12 +43,13 @@ func TestGetTagCommitWithSignature(t *testing.T) { assert.NoError(t, err) defer bareRepo1.Close() - commit, err := bareRepo1.GetCommit("3ad28a9149a2864384548f3d17ed7f38014c9e8a") + // both the tag and the commit are signed here, this validates only the commit signature + commit, err := bareRepo1.GetCommit("28b55526e7100924d864dd89e35c1ea62e7a5a32") assert.NoError(t, err) assert.NotNil(t, commit) assert.NotNil(t, commit.Signature) // test that signature is not in message - assert.Equal(t, "tag", commit.CommitMessage) + assert.Equal(t, "signed-commit\n", commit.CommitMessage) } func TestGetCommitWithBadCommitID(t *testing.T) { diff --git a/modules/git/repo_ref_test.go b/modules/git/repo_ref_test.go index 776d7ce3e15b3..c08ea12760398 100644 --- a/modules/git/repo_ref_test.go +++ b/modules/git/repo_ref_test.go @@ -19,13 +19,14 @@ func TestRepository_GetRefs(t *testing.T) { refs, err := bareRepo1.GetRefs() assert.NoError(t, err) - assert.Len(t, refs, 5) + assert.Len(t, refs, 6) expectedRefs := []string{ BranchPrefix + "branch1", BranchPrefix + "branch2", BranchPrefix + "master", TagPrefix + "test", + TagPrefix + "signed-tag", NotesRef, } @@ -43,9 +44,12 @@ func TestRepository_GetRefsFiltered(t *testing.T) { refs, err := bareRepo1.GetRefsFiltered(TagPrefix) assert.NoError(t, err) - if assert.Len(t, refs, 1) { - assert.Equal(t, TagPrefix+"test", refs[0].Name) + if assert.Len(t, refs, 2) { + assert.Equal(t, TagPrefix+"signed-tag", refs[0].Name) assert.Equal(t, "tag", refs[0].Type) - assert.Equal(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", refs[0].Object.String()) + assert.Equal(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", refs[0].Object.String()) + assert.Equal(t, TagPrefix+"test", refs[1].Name) + assert.Equal(t, "tag", refs[1].Type) + assert.Equal(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", refs[1].Object.String()) } } diff --git a/modules/git/repo_stats_test.go b/modules/git/repo_stats_test.go index 668ed67999983..3d032385ee282 100644 --- a/modules/git/repo_stats_test.go +++ b/modules/git/repo_stats_test.go @@ -24,9 +24,9 @@ func TestRepository_GetCodeActivityStats(t *testing.T) { assert.NoError(t, err) assert.NotNil(t, code) - assert.EqualValues(t, 9, code.CommitCount) + assert.EqualValues(t, 10, code.CommitCount) assert.EqualValues(t, 3, code.AuthorCount) - assert.EqualValues(t, 9, code.CommitCountInAllBranches) + assert.EqualValues(t, 10, code.CommitCountInAllBranches) assert.EqualValues(t, 10, code.Additions) assert.EqualValues(t, 1, code.Deletions) assert.Len(t, code.Authors, 3) diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index 589349a72c9ea..8ccfec3eb23ec 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -25,11 +25,14 @@ func TestRepository_GetTags(t *testing.T) { assert.NoError(t, err) return } - assert.Len(t, tags, 1) + assert.Len(t, tags, 2) assert.Equal(t, len(tags), total) - assert.EqualValues(t, "test", tags[0].Name) - assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[0].ID.String()) + assert.EqualValues(t, "signed-tag", tags[0].Name) + assert.EqualValues(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", tags[0].ID.String()) assert.EqualValues(t, "tag", tags[0].Type) + assert.EqualValues(t, "test", tags[1].Name) + assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[1].ID.String()) + assert.EqualValues(t, "tag", tags[1].Type) } func TestRepository_GetTag(t *testing.T) { diff --git a/modules/git/repo_test.go b/modules/git/repo_test.go index 2a39148192cfc..044b9d406502f 100644 --- a/modules/git/repo_test.go +++ b/modules/git/repo_test.go @@ -14,10 +14,10 @@ func TestGetLatestCommitTime(t *testing.T) { bareRepo1Path := filepath.Join(testReposDir, "repo1_bare") lct, err := GetLatestCommitTime(DefaultContext, bareRepo1Path) assert.NoError(t, err) - // Time is Sun Jul 21 22:43:13 2019 +0200 + // Time is Sun Nov 13 16:40:14 2022 +0100 // which is the time of commit - // feaf4ba6bc635fec442f46ddd4512416ec43c2c2 (refs/heads/master) - assert.EqualValues(t, 1563741793, lct.Unix()) + // ce064814f4a0d337b333e646ece456cd39fab612 (refs/heads/master) + assert.EqualValues(t, 1668354014, lct.Unix()) } func TestRepoIsEmpty(t *testing.T) { diff --git a/modules/git/tests/repos/repo1_bare/index b/modules/git/tests/repos/repo1_bare/index new file mode 100644 index 0000000000000..65d675154f23f Binary files /dev/null and b/modules/git/tests/repos/repo1_bare/index differ diff --git a/modules/git/tests/repos/repo1_bare/logs/HEAD b/modules/git/tests/repos/repo1_bare/logs/HEAD index cef4ca2dcb904..46da5fe0b15a5 100644 --- a/modules/git/tests/repos/repo1_bare/logs/HEAD +++ b/modules/git/tests/repos/repo1_bare/logs/HEAD @@ -1 +1,2 @@ 37991dec2c8e592043f47155ce4808d4580f9123 feaf4ba6bc635fec442f46ddd4512416ec43c2c2 silverwind 1563741799 +0200 push +feaf4ba6bc635fec442f46ddd4512416ec43c2c2 ce064814f4a0d337b333e646ece456cd39fab612 silverwind 1668354026 +0100 push diff --git a/modules/git/tests/repos/repo1_bare/logs/refs/heads/master b/modules/git/tests/repos/repo1_bare/logs/refs/heads/master index cef4ca2dcb904..46da5fe0b15a5 100644 --- a/modules/git/tests/repos/repo1_bare/logs/refs/heads/master +++ b/modules/git/tests/repos/repo1_bare/logs/refs/heads/master @@ -1 +1,2 @@ 37991dec2c8e592043f47155ce4808d4580f9123 feaf4ba6bc635fec442f46ddd4512416ec43c2c2 silverwind 1563741799 +0200 push +feaf4ba6bc635fec442f46ddd4512416ec43c2c2 ce064814f4a0d337b333e646ece456cd39fab612 silverwind 1668354026 +0100 push diff --git a/modules/git/tests/repos/repo1_bare/objects/1c/91d130dc5fb75fd2d9f586a058650889cfe7fb b/modules/git/tests/repos/repo1_bare/objects/1c/91d130dc5fb75fd2d9f586a058650889cfe7fb new file mode 100644 index 0000000000000..fb50b65f9ff04 Binary files /dev/null and b/modules/git/tests/repos/repo1_bare/objects/1c/91d130dc5fb75fd2d9f586a058650889cfe7fb differ diff --git a/modules/git/tests/repos/repo1_bare/objects/28/b55526e7100924d864dd89e35c1ea62e7a5a32 b/modules/git/tests/repos/repo1_bare/objects/28/b55526e7100924d864dd89e35c1ea62e7a5a32 new file mode 100644 index 0000000000000..7779599a06c8a Binary files /dev/null and b/modules/git/tests/repos/repo1_bare/objects/28/b55526e7100924d864dd89e35c1ea62e7a5a32 differ diff --git a/modules/git/tests/repos/repo1_bare/objects/36/f97d9a96457e2bab511db30fe2db03893ebc64 b/modules/git/tests/repos/repo1_bare/objects/36/f97d9a96457e2bab511db30fe2db03893ebc64 new file mode 100644 index 0000000000000..c96b843902407 Binary files /dev/null and b/modules/git/tests/repos/repo1_bare/objects/36/f97d9a96457e2bab511db30fe2db03893ebc64 differ diff --git a/modules/git/tests/repos/repo1_bare/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/modules/git/tests/repos/repo1_bare/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 new file mode 100644 index 0000000000000..adf64119a33d7 Binary files /dev/null and b/modules/git/tests/repos/repo1_bare/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ diff --git a/modules/git/tests/repos/repo1_bare/objects/93/3305878a3c9ad485c29b87fb662a73a9675c4b b/modules/git/tests/repos/repo1_bare/objects/93/3305878a3c9ad485c29b87fb662a73a9675c4b new file mode 100644 index 0000000000000..e198e7658d2ac Binary files /dev/null and b/modules/git/tests/repos/repo1_bare/objects/93/3305878a3c9ad485c29b87fb662a73a9675c4b differ diff --git a/modules/git/tests/repos/repo1_bare/objects/ce/064814f4a0d337b333e646ece456cd39fab612 b/modules/git/tests/repos/repo1_bare/objects/ce/064814f4a0d337b333e646ece456cd39fab612 new file mode 100644 index 0000000000000..93f1525e57972 Binary files /dev/null and b/modules/git/tests/repos/repo1_bare/objects/ce/064814f4a0d337b333e646ece456cd39fab612 differ diff --git a/modules/git/tests/repos/repo1_bare/objects/cf/8b0b492a950b358a7ce7f9d01b18aef48a6b2d b/modules/git/tests/repos/repo1_bare/objects/cf/8b0b492a950b358a7ce7f9d01b18aef48a6b2d new file mode 100644 index 0000000000000..1152b25bb8318 Binary files /dev/null and b/modules/git/tests/repos/repo1_bare/objects/cf/8b0b492a950b358a7ce7f9d01b18aef48a6b2d differ diff --git a/modules/git/tests/repos/repo1_bare/refs/heads/master b/modules/git/tests/repos/repo1_bare/refs/heads/master index c5e92eb229764..9b0de228190a6 100644 --- a/modules/git/tests/repos/repo1_bare/refs/heads/master +++ b/modules/git/tests/repos/repo1_bare/refs/heads/master @@ -1 +1 @@ -feaf4ba6bc635fec442f46ddd4512416ec43c2c2 +ce064814f4a0d337b333e646ece456cd39fab612 diff --git a/modules/git/tests/repos/repo1_bare/refs/tags/signed-tag b/modules/git/tests/repos/repo1_bare/refs/tags/signed-tag new file mode 100644 index 0000000000000..3998a68507d4e --- /dev/null +++ b/modules/git/tests/repos/repo1_bare/refs/tags/signed-tag @@ -0,0 +1 @@ +36f97d9a96457e2bab511db30fe2db03893ebc64 diff --git a/modules/label/label.go b/modules/label/label.go new file mode 100644 index 0000000000000..d3ef0e1dc967a --- /dev/null +++ b/modules/label/label.go @@ -0,0 +1,46 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package label + +import ( + "fmt" + "regexp" + "strings" +) + +// colorPattern is a regexp which can validate label color +var colorPattern = regexp.MustCompile("^#?(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{3})$") + +// Label represents label information loaded from template +type Label struct { + Name string `yaml:"name"` + Color string `yaml:"color"` + Description string `yaml:"description,omitempty"` + Exclusive bool `yaml:"exclusive,omitempty"` +} + +// NormalizeColor normalizes a color string to a 6-character hex code +func NormalizeColor(color string) (string, error) { + // normalize case + color = strings.TrimSpace(strings.ToLower(color)) + + // add leading hash + if len(color) == 6 || len(color) == 3 { + color = "#" + color + } + + if !colorPattern.MatchString(color) { + return "", fmt.Errorf("bad color code: %s", color) + } + + // convert 3-character shorthand into 6-character version + if len(color) == 4 { + r := color[1] + g := color[2] + b := color[3] + color = fmt.Sprintf("#%c%c%c%c%c%c", r, r, g, g, b, b) + } + + return color, nil +} diff --git a/modules/label/parser.go b/modules/label/parser.go new file mode 100644 index 0000000000000..768c72a61b014 --- /dev/null +++ b/modules/label/parser.go @@ -0,0 +1,126 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package label + +import ( + "errors" + "fmt" + "strings" + + "code.gitea.io/gitea/modules/options" + + "gopkg.in/yaml.v3" +) + +type labelFile struct { + Labels []*Label `yaml:"labels"` +} + +// ErrTemplateLoad represents a "ErrTemplateLoad" kind of error. +type ErrTemplateLoad struct { + TemplateFile string + OriginalError error +} + +// IsErrTemplateLoad checks if an error is a ErrTemplateLoad. +func IsErrTemplateLoad(err error) bool { + _, ok := err.(ErrTemplateLoad) + return ok +} + +func (err ErrTemplateLoad) Error() string { + return fmt.Sprintf("Failed to load label template file '%s': %v", err.TemplateFile, err.OriginalError) +} + +// GetTemplateFile loads the label template file by given name, +// then parses and returns a list of name-color pairs and optionally description. +func GetTemplateFile(name string) ([]*Label, error) { + data, err := options.GetRepoInitFile("label", name+".yaml") + if err == nil && len(data) > 0 { + return parseYamlFormat(name+".yaml", data) + } + + data, err = options.GetRepoInitFile("label", name+".yml") + if err == nil && len(data) > 0 { + return parseYamlFormat(name+".yml", data) + } + + data, err = options.GetRepoInitFile("label", name) + if err != nil { + return nil, ErrTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %w", err)} + } + + return parseLegacyFormat(name, data) +} + +func parseYamlFormat(name string, data []byte) ([]*Label, error) { + lf := &labelFile{} + + if err := yaml.Unmarshal(data, lf); err != nil { + return nil, err + } + + // Validate label data and fix colors + for _, l := range lf.Labels { + l.Color = strings.TrimSpace(l.Color) + if len(l.Name) == 0 || len(l.Color) == 0 { + return nil, ErrTemplateLoad{name, errors.New("label name and color are required fields")} + } + color, err := NormalizeColor(l.Color) + if err != nil { + return nil, ErrTemplateLoad{name, fmt.Errorf("bad HTML color code '%s' in label: %s", l.Color, l.Name)} + } + l.Color = color + } + + return lf.Labels, nil +} + +func parseLegacyFormat(name string, data []byte) ([]*Label, error) { + lines := strings.Split(string(data), "\n") + list := make([]*Label, 0, len(lines)) + for i := 0; i < len(lines); i++ { + line := strings.TrimSpace(lines[i]) + if len(line) == 0 { + continue + } + + parts, description, _ := strings.Cut(line, ";") + + color, name, ok := strings.Cut(parts, " ") + if !ok { + return nil, ErrTemplateLoad{name, fmt.Errorf("line is malformed: %s", line)} + } + + color, err := NormalizeColor(color) + if err != nil { + return nil, ErrTemplateLoad{name, fmt.Errorf("bad HTML color code '%s' in line: %s", color, line)} + } + + list = append(list, &Label{ + Name: strings.TrimSpace(name), + Color: color, + Description: strings.TrimSpace(description), + }) + } + + return list, nil +} + +// LoadFormatted loads the labels' list of a template file as a string separated by comma +func LoadFormatted(name string) (string, error) { + var buf strings.Builder + list, err := GetTemplateFile(name) + if err != nil { + return "", err + } + + for i := 0; i < len(list); i++ { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(list[i].Name) + } + return buf.String(), nil +} diff --git a/modules/label/parser_test.go b/modules/label/parser_test.go new file mode 100644 index 0000000000000..5c8042f66863c --- /dev/null +++ b/modules/label/parser_test.go @@ -0,0 +1,72 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package label + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestYamlParser(t *testing.T) { + data := []byte(`labels: + - name: priority/low + exclusive: true + color: "#0000ee" + description: "Low priority" + - name: priority/medium + exclusive: true + color: "0e0" + description: "Medium priority" + - name: priority/high + exclusive: true + color: "#ee0000" + description: "High priority" + - name: type/bug + color: "#f00" + description: "Bug"`) + + labels, err := parseYamlFormat("test", data) + require.NoError(t, err) + require.Len(t, labels, 4) + assert.Equal(t, "priority/low", labels[0].Name) + assert.True(t, labels[0].Exclusive) + assert.Equal(t, "#0000ee", labels[0].Color) + assert.Equal(t, "Low priority", labels[0].Description) + assert.Equal(t, "priority/medium", labels[1].Name) + assert.True(t, labels[1].Exclusive) + assert.Equal(t, "#00ee00", labels[1].Color) + assert.Equal(t, "Medium priority", labels[1].Description) + assert.Equal(t, "priority/high", labels[2].Name) + assert.True(t, labels[2].Exclusive) + assert.Equal(t, "#ee0000", labels[2].Color) + assert.Equal(t, "High priority", labels[2].Description) + assert.Equal(t, "type/bug", labels[3].Name) + assert.False(t, labels[3].Exclusive) + assert.Equal(t, "#ff0000", labels[3].Color) + assert.Equal(t, "Bug", labels[3].Description) +} + +func TestLegacyParser(t *testing.T) { + data := []byte(`#ee0701 bug ; Something is not working +#cccccc duplicate ; This issue or pull request already exists +#84b6eb enhancement`) + + labels, err := parseLegacyFormat("test", data) + require.NoError(t, err) + require.Len(t, labels, 3) + assert.Equal(t, "bug", labels[0].Name) + assert.False(t, labels[0].Exclusive) + assert.Equal(t, "#ee0701", labels[0].Color) + assert.Equal(t, "Something is not working", labels[0].Description) + assert.Equal(t, "duplicate", labels[1].Name) + assert.False(t, labels[1].Exclusive) + assert.Equal(t, "#cccccc", labels[1].Color) + assert.Equal(t, "This issue or pull request already exists", labels[1].Description) + assert.Equal(t, "enhancement", labels[2].Name) + assert.False(t, labels[2].Exclusive) + assert.Equal(t, "#84b6eb", labels[2].Color) + assert.Empty(t, labels[2].Description) +} diff --git a/modules/markup/sanitizer.go b/modules/markup/sanitizer.go index e59f6c7c8424b..600ccbf3c6126 100644 --- a/modules/markup/sanitizer.go +++ b/modules/markup/sanitizer.go @@ -132,6 +132,8 @@ func createDefaultPolicy() *bluemonday.Policy { policy.AllowAttrs(generalSafeAttrs...).OnElements(generalSafeElements...) + policy.AllowAttrs("src", "autoplay", "controls").OnElements("video") + policy.AllowAttrs("itemscope", "itemtype").OnElements("div") // FIXME: Need to handle longdesc in img but there is no easy way to do it diff --git a/modules/options/repo.go b/modules/options/repo.go new file mode 100644 index 0000000000000..1480f7808176c --- /dev/null +++ b/modules/options/repo.go @@ -0,0 +1,44 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package options + +import ( + "fmt" + "os" + "path" + "strings" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" +) + +// GetRepoInitFile returns repository init files +func GetRepoInitFile(tp, name string) ([]byte, error) { + cleanedName := strings.TrimLeft(path.Clean("/"+name), "/") + relPath := path.Join("options", tp, cleanedName) + + // Use custom file when available. + customPath := path.Join(setting.CustomPath, relPath) + isFile, err := util.IsFile(customPath) + if err != nil { + log.Error("Unable to check if %s is a file. Error: %v", customPath, err) + } + if isFile { + return os.ReadFile(customPath) + } + + switch tp { + case "readme": + return Readme(cleanedName) + case "gitignore": + return Gitignore(cleanedName) + case "license": + return License(cleanedName) + case "label": + return Labels(cleanedName) + default: + return []byte{}, fmt.Errorf("Invalid init file type") + } +} diff --git a/modules/queue/queue_channel.go b/modules/queue/queue_channel.go index 6f75b8357eabc..baac097393956 100644 --- a/modules/queue/queue_channel.go +++ b/modules/queue/queue_channel.go @@ -124,7 +124,10 @@ func (q *ChannelQueue) Shutdown() { log.Trace("ChannelQueue: %s Flushing", q.name) // We can't use Cleanup here because that will close the channel if err := q.FlushWithContext(q.terminateCtx); err != nil { - log.Warn("ChannelQueue: %s Terminated before completed flushing", q.name) + count := atomic.LoadInt64(&q.numInQueue) + if count > 0 { + log.Warn("ChannelQueue: %s Terminated before completed flushing", q.name) + } return } log.Debug("ChannelQueue: %s Flushed", q.name) diff --git a/modules/queue/queue_disk_channel.go b/modules/queue/queue_disk_channel.go index c7526714c65cb..91f91f0dfc80d 100644 --- a/modules/queue/queue_disk_channel.go +++ b/modules/queue/queue_disk_channel.go @@ -94,7 +94,8 @@ func NewPersistableChannelQueue(handle HandlerFunc, cfg, exemplar interface{}) ( }, Workers: 0, }, - DataDir: config.DataDir, + DataDir: config.DataDir, + QueueName: config.Name + "-level", } levelQueue, err := NewLevelQueue(wrappedHandle, levelCfg, exemplar) @@ -172,16 +173,18 @@ func (q *PersistableChannelQueue) Run(atShutdown, atTerminate func(func())) { atShutdown(q.Shutdown) atTerminate(q.Terminate) - if lq, ok := q.internal.(*LevelQueue); ok && lq.byteFIFO.Len(lq.shutdownCtx) != 0 { + if lq, ok := q.internal.(*LevelQueue); ok && lq.byteFIFO.Len(lq.terminateCtx) != 0 { // Just run the level queue - we shut it down once it's flushed go q.internal.Run(func(_ func()) {}, func(_ func()) {}) go func() { - for !q.IsEmpty() { - _ = q.internal.Flush(0) + for !lq.IsEmpty() { + _ = lq.Flush(0) select { case <-time.After(100 * time.Millisecond): - case <-q.internal.(*LevelQueue).shutdownCtx.Done(): - log.Warn("LevelQueue: %s shut down before completely flushed", q.internal.(*LevelQueue).Name()) + case <-lq.shutdownCtx.Done(): + if lq.byteFIFO.Len(lq.terminateCtx) > 0 { + log.Warn("LevelQueue: %s shut down before completely flushed", q.internal.(*LevelQueue).Name()) + } return } } @@ -316,10 +319,22 @@ func (q *PersistableChannelQueue) Shutdown() { // Redirect all remaining data in the chan to the internal channel log.Trace("PersistableChannelQueue: %s Redirecting remaining data", q.delayedStarter.name) close(q.channelQueue.dataChan) + countOK, countLost := 0, 0 for data := range q.channelQueue.dataChan { - _ = q.internal.Push(data) + err := q.internal.Push(data) + if err != nil { + log.Error("PersistableChannelQueue: %s Unable redirect %v due to: %v", q.delayedStarter.name, data, err) + countLost++ + } else { + countOK++ + } atomic.AddInt64(&q.channelQueue.numInQueue, -1) } + if countLost > 0 { + log.Warn("PersistableChannelQueue: %s %d will be restored on restart, %d lost", q.delayedStarter.name, countOK, countLost) + } else if countOK > 0 { + log.Warn("PersistableChannelQueue: %s %d will be restored on restart", q.delayedStarter.name, countOK) + } log.Trace("PersistableChannelQueue: %s Done Redirecting remaining data", q.delayedStarter.name) log.Debug("PersistableChannelQueue: %s Shutdown", q.delayedStarter.name) diff --git a/modules/queue/queue_disk_channel_test.go b/modules/queue/queue_disk_channel_test.go index 318610355e433..4f14a5d79df92 100644 --- a/modules/queue/queue_disk_channel_test.go +++ b/modules/queue/queue_disk_channel_test.go @@ -39,7 +39,7 @@ func TestPersistableChannelQueue(t *testing.T) { Workers: 1, BoostWorkers: 0, MaxWorkers: 10, - Name: "first", + Name: "test-queue", }, &testData{}) assert.NoError(t, err) @@ -135,7 +135,7 @@ func TestPersistableChannelQueue(t *testing.T) { Workers: 1, BoostWorkers: 0, MaxWorkers: 10, - Name: "second", + Name: "test-queue", }, &testData{}) assert.NoError(t, err) @@ -227,7 +227,7 @@ func TestPersistableChannelQueue_Pause(t *testing.T) { Workers: 1, BoostWorkers: 0, MaxWorkers: 10, - Name: "first", + Name: "test-queue", }, &testData{}) assert.NoError(t, err) @@ -433,7 +433,7 @@ func TestPersistableChannelQueue_Pause(t *testing.T) { Workers: 1, BoostWorkers: 0, MaxWorkers: 10, - Name: "second", + Name: "test-queue", }, &testData{}) assert.NoError(t, err) pausable, ok = queue.(Pausable) diff --git a/modules/queue/unique_queue_channel.go b/modules/queue/unique_queue_channel.go index c43bd1db3f7da..62c051aa3935e 100644 --- a/modules/queue/unique_queue_channel.go +++ b/modules/queue/unique_queue_channel.go @@ -177,7 +177,9 @@ func (q *ChannelUniqueQueue) Shutdown() { go func() { log.Trace("ChannelUniqueQueue: %s Flushing", q.name) if err := q.FlushWithContext(q.terminateCtx); err != nil { - log.Warn("ChannelUniqueQueue: %s Terminated before completed flushing", q.name) + if !q.IsEmpty() { + log.Warn("ChannelUniqueQueue: %s Terminated before completed flushing", q.name) + } return } log.Debug("ChannelUniqueQueue: %s Flushed", q.name) diff --git a/modules/queue/unique_queue_channel_test.go b/modules/queue/unique_queue_channel_test.go index 9372694b87a6d..824015b834fee 100644 --- a/modules/queue/unique_queue_channel_test.go +++ b/modules/queue/unique_queue_channel_test.go @@ -8,10 +8,13 @@ import ( "testing" "time" + "code.gitea.io/gitea/modules/log" + "github.com/stretchr/testify/assert" ) func TestChannelUniqueQueue(t *testing.T) { + _ = log.NewLogger(1000, "console", "console", `{"level":"warn","stacktracelevel":"NONE","stderr":true}`) handleChan := make(chan *testData) handle := func(data ...Data) []Data { for _, datum := range data { @@ -52,6 +55,8 @@ func TestChannelUniqueQueue(t *testing.T) { } func TestChannelUniqueQueue_Batch(t *testing.T) { + _ = log.NewLogger(1000, "console", "console", `{"level":"warn","stacktracelevel":"NONE","stderr":true}`) + handleChan := make(chan *testData) handle := func(data ...Data) []Data { for _, datum := range data { @@ -98,6 +103,8 @@ func TestChannelUniqueQueue_Batch(t *testing.T) { } func TestChannelUniqueQueue_Pause(t *testing.T) { + _ = log.NewLogger(1000, "console", "console", `{"level":"warn","stacktracelevel":"NONE","stderr":true}`) + lock := sync.Mutex{} var queue Queue var err error diff --git a/modules/queue/unique_queue_disk_channel.go b/modules/queue/unique_queue_disk_channel.go index 405726182dcbc..cc8a807c67237 100644 --- a/modules/queue/unique_queue_disk_channel.go +++ b/modules/queue/unique_queue_disk_channel.go @@ -94,7 +94,8 @@ func NewPersistableChannelUniqueQueue(handle HandlerFunc, cfg, exemplar interfac }, Workers: 0, }, - DataDir: config.DataDir, + DataDir: config.DataDir, + QueueName: config.Name + "-level", } queue.channelQueue = channelUniqueQueue.(*ChannelUniqueQueue) @@ -209,17 +210,29 @@ func (q *PersistableChannelUniqueQueue) Run(atShutdown, atTerminate func(func()) atTerminate(q.Terminate) _ = q.channelQueue.AddWorkers(q.channelQueue.workers, 0) - if luq, ok := q.internal.(*LevelUniqueQueue); ok && luq.ByteFIFOUniqueQueue.byteFIFO.Len(luq.shutdownCtx) != 0 { + if luq, ok := q.internal.(*LevelUniqueQueue); ok && !luq.IsEmpty() { // Just run the level queue - we shut it down once it's flushed - go q.internal.Run(func(_ func()) {}, func(_ func()) {}) + go luq.Run(func(_ func()) {}, func(_ func()) {}) go func() { - _ = q.internal.Flush(0) - log.Debug("LevelUniqueQueue: %s flushed so shutting down", q.internal.(*LevelUniqueQueue).Name()) - q.internal.(*LevelUniqueQueue).Shutdown() - GetManager().Remove(q.internal.(*LevelUniqueQueue).qid) + _ = luq.Flush(0) + for !luq.IsEmpty() { + _ = luq.Flush(0) + select { + case <-time.After(100 * time.Millisecond): + case <-luq.shutdownCtx.Done(): + if luq.byteFIFO.Len(luq.terminateCtx) > 0 { + log.Warn("LevelUniqueQueue: %s shut down before completely flushed", luq.Name()) + } + return + } + } + log.Debug("LevelUniqueQueue: %s flushed so shutting down", luq.Name()) + luq.Shutdown() + GetManager().Remove(luq.qid) }() } else { log.Debug("PersistableChannelUniqueQueue: %s Skipping running the empty level queue", q.delayedStarter.name) + _ = q.internal.Flush(0) q.internal.(*LevelUniqueQueue).Shutdown() GetManager().Remove(q.internal.(*LevelUniqueQueue).qid) } @@ -285,8 +298,20 @@ func (q *PersistableChannelUniqueQueue) Shutdown() { // Redirect all remaining data in the chan to the internal channel close(q.channelQueue.dataChan) log.Trace("PersistableChannelUniqueQueue: %s Redirecting remaining data", q.delayedStarter.name) + countOK, countLost := 0, 0 for data := range q.channelQueue.dataChan { - _ = q.internal.Push(data) + err := q.internal.(*LevelUniqueQueue).Push(data) + if err != nil { + log.Error("PersistableChannelUniqueQueue: %s Unable redirect %v due to: %v", q.delayedStarter.name, data, err) + countLost++ + } else { + countOK++ + } + } + if countLost > 0 { + log.Warn("PersistableChannelUniqueQueue: %s %d will be restored on restart, %d lost", q.delayedStarter.name, countOK, countLost) + } else if countOK > 0 { + log.Warn("PersistableChannelUniqueQueue: %s %d will be restored on restart", q.delayedStarter.name, countOK) } log.Trace("PersistableChannelUniqueQueue: %s Done Redirecting remaining data", q.delayedStarter.name) diff --git a/modules/queue/unique_queue_disk_channel_test.go b/modules/queue/unique_queue_disk_channel_test.go new file mode 100644 index 0000000000000..fd76163f4aaca --- /dev/null +++ b/modules/queue/unique_queue_disk_channel_test.go @@ -0,0 +1,259 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package queue + +import ( + "fmt" + "strconv" + "sync" + "testing" + "time" + + "code.gitea.io/gitea/modules/log" + + "github.com/stretchr/testify/assert" +) + +func TestPersistableChannelUniqueQueue(t *testing.T) { + tmpDir := t.TempDir() + fmt.Printf("TempDir %s\n", tmpDir) + _ = log.NewLogger(1000, "console", "console", `{"level":"warn","stacktracelevel":"NONE","stderr":true}`) + + // Common function to create the Queue + newQueue := func(name string, handle func(data ...Data) []Data) Queue { + q, err := NewPersistableChannelUniqueQueue(handle, + PersistableChannelUniqueQueueConfiguration{ + Name: name, + DataDir: tmpDir, + QueueLength: 200, + MaxWorkers: 1, + BlockTimeout: 1 * time.Second, + BoostTimeout: 5 * time.Minute, + BoostWorkers: 1, + Workers: 0, + }, "task-0") + assert.NoError(t, err) + return q + } + + // runs the provided queue and provides some timer function + type channels struct { + readyForShutdown chan struct{} // closed when shutdown functions have been assigned + readyForTerminate chan struct{} // closed when terminate functions have been assigned + signalShutdown chan struct{} // Should close to signal shutdown + doneShutdown chan struct{} // closed when shutdown function is done + queueTerminate []func() // list of atTerminate functions to call atTerminate - need to be accessed with lock + } + runQueue := func(q Queue, lock *sync.Mutex) *channels { + chans := &channels{ + readyForShutdown: make(chan struct{}), + readyForTerminate: make(chan struct{}), + signalShutdown: make(chan struct{}), + doneShutdown: make(chan struct{}), + } + go q.Run(func(atShutdown func()) { + go func() { + lock.Lock() + select { + case <-chans.readyForShutdown: + default: + close(chans.readyForShutdown) + } + lock.Unlock() + <-chans.signalShutdown + atShutdown() + close(chans.doneShutdown) + }() + }, func(atTerminate func()) { + lock.Lock() + defer lock.Unlock() + select { + case <-chans.readyForTerminate: + default: + close(chans.readyForTerminate) + } + chans.queueTerminate = append(chans.queueTerminate, atTerminate) + }) + + return chans + } + + // call to shutdown and terminate the queue associated with the channels + doTerminate := func(chans *channels, lock *sync.Mutex) { + <-chans.readyForTerminate + + lock.Lock() + callbacks := []func(){} + callbacks = append(callbacks, chans.queueTerminate...) + lock.Unlock() + + for _, callback := range callbacks { + callback() + } + } + + mapLock := sync.Mutex{} + executedInitial := map[string][]string{} + hasInitial := map[string][]string{} + + fillQueue := func(name string, done chan struct{}) { + t.Run("Initial Filling: "+name, func(t *testing.T) { + lock := sync.Mutex{} + + startAt100Queued := make(chan struct{}) + stopAt20Shutdown := make(chan struct{}) // stop and shutdown at the 20th item + + handle := func(data ...Data) []Data { + <-startAt100Queued + for _, datum := range data { + s := datum.(string) + mapLock.Lock() + executedInitial[name] = append(executedInitial[name], s) + mapLock.Unlock() + if s == "task-20" { + close(stopAt20Shutdown) + } + } + return nil + } + + q := newQueue(name, handle) + + // add 100 tasks to the queue + for i := 0; i < 100; i++ { + _ = q.Push("task-" + strconv.Itoa(i)) + } + close(startAt100Queued) + + chans := runQueue(q, &lock) + + <-chans.readyForShutdown + <-stopAt20Shutdown + close(chans.signalShutdown) + <-chans.doneShutdown + _ = q.Push("final") + + // check which tasks are still in the queue + for i := 0; i < 100; i++ { + if has, _ := q.(UniqueQueue).Has("task-" + strconv.Itoa(i)); has { + mapLock.Lock() + hasInitial[name] = append(hasInitial[name], "task-"+strconv.Itoa(i)) + mapLock.Unlock() + } + } + if has, _ := q.(UniqueQueue).Has("final"); has { + mapLock.Lock() + hasInitial[name] = append(hasInitial[name], "final") + mapLock.Unlock() + } else { + assert.Fail(t, "UnqueQueue %s should have \"final\"", name) + } + doTerminate(chans, &lock) + mapLock.Lock() + assert.Equal(t, 101, len(executedInitial[name])+len(hasInitial[name])) + mapLock.Unlock() + }) + close(done) + } + + doneA := make(chan struct{}) + doneB := make(chan struct{}) + + go fillQueue("QueueA", doneA) + go fillQueue("QueueB", doneB) + + <-doneA + <-doneB + + executedEmpty := map[string][]string{} + hasEmpty := map[string][]string{} + emptyQueue := func(name string, done chan struct{}) { + t.Run("Empty Queue: "+name, func(t *testing.T) { + lock := sync.Mutex{} + stop := make(chan struct{}) + + // collect the tasks that have been executed + handle := func(data ...Data) []Data { + lock.Lock() + for _, datum := range data { + mapLock.Lock() + executedEmpty[name] = append(executedEmpty[name], datum.(string)) + mapLock.Unlock() + if datum.(string) == "final" { + close(stop) + } + } + lock.Unlock() + return nil + } + + q := newQueue(name, handle) + chans := runQueue(q, &lock) + + <-chans.readyForShutdown + <-stop + close(chans.signalShutdown) + <-chans.doneShutdown + + // check which tasks are still in the queue + for i := 0; i < 100; i++ { + if has, _ := q.(UniqueQueue).Has("task-" + strconv.Itoa(i)); has { + mapLock.Lock() + hasEmpty[name] = append(hasEmpty[name], "task-"+strconv.Itoa(i)) + mapLock.Unlock() + } + } + doTerminate(chans, &lock) + + mapLock.Lock() + assert.Equal(t, 101, len(executedInitial[name])+len(executedEmpty[name])) + assert.Equal(t, 0, len(hasEmpty[name])) + mapLock.Unlock() + }) + close(done) + } + + doneA = make(chan struct{}) + doneB = make(chan struct{}) + + go emptyQueue("QueueA", doneA) + go emptyQueue("QueueB", doneB) + + <-doneA + <-doneB + + mapLock.Lock() + t.Logf("TestPersistableChannelUniqueQueue executedInitiallyA=%v, executedInitiallyB=%v, executedToEmptyA=%v, executedToEmptyB=%v", + len(executedInitial["QueueA"]), len(executedInitial["QueueB"]), len(executedEmpty["QueueA"]), len(executedEmpty["QueueB"])) + + // reset and rerun + executedInitial = map[string][]string{} + hasInitial = map[string][]string{} + executedEmpty = map[string][]string{} + hasEmpty = map[string][]string{} + mapLock.Unlock() + + doneA = make(chan struct{}) + doneB = make(chan struct{}) + + go fillQueue("QueueA", doneA) + go fillQueue("QueueB", doneB) + + <-doneA + <-doneB + + doneA = make(chan struct{}) + doneB = make(chan struct{}) + + go emptyQueue("QueueA", doneA) + go emptyQueue("QueueB", doneB) + + <-doneA + <-doneB + + mapLock.Lock() + t.Logf("TestPersistableChannelUniqueQueue executedInitiallyA=%v, executedInitiallyB=%v, executedToEmptyA=%v, executedToEmptyB=%v", + len(executedInitial["QueueA"]), len(executedInitial["QueueB"]), len(executedEmpty["QueueA"]), len(executedEmpty["QueueB"])) + mapLock.Unlock() +} diff --git a/modules/repository/create.go b/modules/repository/create.go index 1704ea792cbf8..6a1fa41b6b87d 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -23,6 +23,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/label" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" @@ -189,7 +190,7 @@ func CreateRepository(doer, u *user_model.User, opts CreateRepoOptions) (*repo_m // Check if label template exist if len(opts.IssueLabels) > 0 { - if _, err := GetLabelTemplateFile(opts.IssueLabels); err != nil { + if _, err := label.GetTemplateFile(opts.IssueLabels); err != nil { return nil, err } } diff --git a/modules/repository/init.go b/modules/repository/init.go index 771b68a4916f8..49c8d2a904d1a 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -18,6 +18,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/label" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/setting" @@ -40,114 +41,6 @@ var ( LabelTemplates map[string]string ) -// ErrIssueLabelTemplateLoad represents a "ErrIssueLabelTemplateLoad" kind of error. -type ErrIssueLabelTemplateLoad struct { - TemplateFile string - OriginalError error -} - -// IsErrIssueLabelTemplateLoad checks if an error is a ErrIssueLabelTemplateLoad. -func IsErrIssueLabelTemplateLoad(err error) bool { - _, ok := err.(ErrIssueLabelTemplateLoad) - return ok -} - -func (err ErrIssueLabelTemplateLoad) Error() string { - return fmt.Sprintf("Failed to load label template file '%s': %v", err.TemplateFile, err.OriginalError) -} - -// GetRepoInitFile returns repository init files -func GetRepoInitFile(tp, name string) ([]byte, error) { - cleanedName := strings.TrimLeft(path.Clean("/"+name), "/") - relPath := path.Join("options", tp, cleanedName) - - // Use custom file when available. - customPath := path.Join(setting.CustomPath, relPath) - isFile, err := util.IsFile(customPath) - if err != nil { - log.Error("Unable to check if %s is a file. Error: %v", customPath, err) - } - if isFile { - return os.ReadFile(customPath) - } - - switch tp { - case "readme": - return options.Readme(cleanedName) - case "gitignore": - return options.Gitignore(cleanedName) - case "license": - return options.License(cleanedName) - case "label": - return options.Labels(cleanedName) - default: - return []byte{}, fmt.Errorf("Invalid init file type") - } -} - -// GetLabelTemplateFile loads the label template file by given name, -// then parses and returns a list of name-color pairs and optionally description. -func GetLabelTemplateFile(name string) ([][3]string, error) { - data, err := GetRepoInitFile("label", name) - if err != nil { - return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("GetRepoInitFile: %w", err)} - } - - lines := strings.Split(string(data), "\n") - list := make([][3]string, 0, len(lines)) - for i := 0; i < len(lines); i++ { - line := strings.TrimSpace(lines[i]) - if len(line) == 0 { - continue - } - - parts := strings.SplitN(line, ";", 2) - - fields := strings.SplitN(parts[0], " ", 2) - if len(fields) != 2 { - return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("line is malformed: %s", line)} - } - - color := strings.Trim(fields[0], " ") - if len(color) == 6 { - color = "#" + color - } - if !issues_model.LabelColorPattern.MatchString(color) { - return nil, ErrIssueLabelTemplateLoad{name, fmt.Errorf("bad HTML color code in line: %s", line)} - } - - var description string - - if len(parts) > 1 { - description = strings.TrimSpace(parts[1]) - } - - fields[1] = strings.TrimSpace(fields[1]) - list = append(list, [3]string{fields[1], color, description}) - } - - return list, nil -} - -func loadLabels(labelTemplate string) ([]string, error) { - list, err := GetLabelTemplateFile(labelTemplate) - if err != nil { - return nil, err - } - - labels := make([]string, len(list)) - for i := 0; i < len(list); i++ { - labels[i] = list[i][0] - } - return labels, nil -} - -// LoadLabelsFormatted loads the labels' list of a template file as a string separated by comma -func LoadLabelsFormatted(labelTemplate string) (string, error) { - labels, err := loadLabels(labelTemplate) - return strings.Join(labels, ", "), err -} - // LoadRepoConfig loads the repository config func LoadRepoConfig() { // Load .gitignore and license files and readme templates. @@ -158,6 +51,14 @@ func LoadRepoConfig() { if err != nil { log.Fatal("Failed to get %s files: %v", t, err) } + if t == "label" { + for i, f := range files { + ext := strings.ToLower(filepath.Ext(f)) + if ext == ".yaml" || ext == ".yml" { + files[i] = f[:len(f)-len(ext)] + } + } + } customPath := path.Join(setting.CustomPath, "options", t) isDir, err := util.IsDir(customPath) if err != nil { @@ -190,7 +91,7 @@ func LoadRepoConfig() { // Load label templates LabelTemplates = make(map[string]string) for _, templateFile := range LabelTemplatesFiles { - labels, err := LoadLabelsFormatted(templateFile) + labels, err := label.LoadFormatted(templateFile) if err != nil { log.Error("Failed to load labels: %v", err) } @@ -235,7 +136,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, } // README - data, err := GetRepoInitFile("readme", opts.Readme) + data, err := options.GetRepoInitFile("readme", opts.Readme) if err != nil { return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.Readme, err) } @@ -263,7 +164,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, var buf bytes.Buffer names := strings.Split(opts.Gitignores, ",") for _, name := range names { - data, err = GetRepoInitFile("gitignore", name) + data, err = options.GetRepoInitFile("gitignore", name) if err != nil { return fmt.Errorf("GetRepoInitFile[%s]: %w", name, err) } @@ -281,7 +182,7 @@ func prepareRepoCommit(ctx context.Context, repo *repo_model.Repository, tmpDir, // LICENSE if len(opts.License) > 0 { - data, err = GetRepoInitFile("license", opts.License) + data, err = options.GetRepoInitFile("license", opts.License) if err != nil { return fmt.Errorf("GetRepoInitFile[%s]: %w", opts.License, err) } @@ -443,7 +344,7 @@ func initRepository(ctx context.Context, repoPath string, u *user_model.User, re // InitializeLabels adds a label set to a repository using a template func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg bool) error { - list, err := GetLabelTemplateFile(labelTemplate) + list, err := label.GetTemplateFile(labelTemplate) if err != nil { return err } @@ -451,9 +352,10 @@ func InitializeLabels(ctx context.Context, id int64, labelTemplate string, isOrg labels := make([]*issues_model.Label, len(list)) for i := 0; i < len(list); i++ { labels[i] = &issues_model.Label{ - Name: list[i][0], - Description: list[i][2], - Color: list[i][1], + Name: list[i].Name, + Exclusive: list[i].Exclusive, + Description: list[i].Description, + Color: list[i].Color, } if isOrg { labels[i].OrgID = id diff --git a/options/label/Advanced.yaml b/options/label/Advanced.yaml new file mode 100644 index 0000000000000..27b2c146372b3 --- /dev/null +++ b/options/label/Advanced.yaml @@ -0,0 +1,70 @@ +labels: + - name: "Kind/Bug" + color: ee0701 + description: Something is not working + - name: "Kind/Feature" + color: 0288d1 + description: New functionality + - name: "Kind/Enhancement" + color: 84b6eb + description: Improve existing functionality + - name: "Kind/Security" + color: 9c27b0 + description: This is security issue + - name: "Kind/Testing" + color: 795548 + description: Issue or pull request related to testing + - name: "Kind/Breaking" + color: c62828 + description: Breaking change that won't be backward compatible + - name: "Kind/Documentation" + color: 37474f + description: Documentation changes + - name: "Reviewed/Duplicate" + exclusive: true + color: 616161 + description: This issue or pull request already exists + - name: "Reviewed/Invalid" + exclusive: true + color: 546e7a + description: Invalid issue + - name: "Reviewed/Confirmed" + exclusive: true + color: 795548 + description: Issue has been confirmed + - name: "Reviewed/Won't Fix" + exclusive: true + color: eeeeee + description: This issue won't be fixed + - name: "Status/Need More Info" + exclusive: true + color: 424242 + description: Feedback is required to reproduce issue or to continue work + - name: "Status/Blocked" + exclusive: true + color: 880e4f + description: Something is blocking this issue or pull request + - name: "Status/Abandoned" + exclusive: true + color: "222222" + description: Somebody has started to work on this but abandoned work + - name: "Priority/Critical" + exclusive: true + color: b71c1c + description: The priority is critical + priority: critical + - name: "Priority/High" + exclusive: true + color: d32f2f + description: The priority is high + priority: high + - name: "Priority/Medium" + exclusive: true + color: e64a19 + description: The priority is medium + priority: medium + - name: "Priority/Low" + exclusive: true + color: 4caf50 + description: The priority is low + priority: low diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 36f8188ff04bf..3695bd0384aa9 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1470,6 +1470,7 @@ issues.error_removing_due_date = "Failed to remove the due date." issues.push_commit_1 = "added %d commit %s" issues.push_commits_n = "added %d commits %s" issues.force_push_codes = `force-pushed %[1]s from %[2]s to %[4]s %[6]s` +issues.force_push_compare = Compare issues.due_date_form = "yyyy-mm-dd" issues.due_date_form_add = "Add due date" issues.due_date_form_edit = "Edit" diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index ddbedafa1e717..b06c8ecc71f91 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -57,6 +57,7 @@ new_mirror=新しいミラー new_fork=新しいフォーク new_org=新しい組織 new_project=新しいプロジェクト +new_project_column=新しい列 manage_org=組織を管理 admin_panel=サイト管理 account_settings=アカウント設定 @@ -90,6 +91,7 @@ disabled=無効 copy=コピー copy_url=URLをコピー +copy_content=内容をコピー copy_branch=ブランチ名をコピー copy_success=コピーされました! copy_error=コピーに失敗しました @@ -247,6 +249,8 @@ no_reply_address=メールを隠すときのドメイン no_reply_address_helper=メールアドレスを隠しているユーザーに使用するドメイン名。 例えば 'noreply.example.org' と設定した場合、ユーザー名 'joe' はGitに 'joe@noreply.example.org' としてログインすることになります。 password_algorithm=パスワードハッシュアルゴリズム password_algorithm_helper=パスワードハッシュアルゴリズムを設定します。 アルゴリズムにより動作要件と強度が異なります。 `argon2`は良い特性を備えていますが、多くのメモリを使用するため小さなシステムには適さない場合があります。 +enable_update_checker=アップデートチェッカーを有効にする +enable_update_checker_helper=gitea.ioに接続して定期的に新しいバージョンのリリースを確認します。 [home] uname_holder=ユーザー名またはメールアドレス @@ -324,6 +328,7 @@ email_not_associate=このメールアドレスは、どのアカウントにも send_reset_mail=アカウント回復メールを送信 reset_password=アカウントの回復 invalid_code=確認コードが無効か期限切れです。 +invalid_password=アカウントの作成に使用されたパスワードと一致しません。 reset_password_helper=アカウント回復 reset_password_wrong_user=アカウント回復のリンクは %[2]s のものですが、あなたは %[1]s でサインイン中です password_too_short=%d文字未満のパスワードは設定できません。 @@ -367,6 +372,7 @@ password_pwned_err=HaveIBeenPwnedへのリクエストを完了できません [mail] view_it_on=%s で見る +reply=またはこのメールに直接返信してください link_not_working_do_paste=開かないですか? コピーしてブラウザーに貼り付けてみてください。 hi_user_x=こんにちは、%s さん。 @@ -510,6 +516,7 @@ duplicate_invite_to_team=指定したユーザーはすでにチームメンバ 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 @@ -746,6 +753,8 @@ access_token_deletion_cancel_action=キャンセル access_token_deletion_confirm_action=削除 access_token_deletion_desc=トークンを削除すると、それを使用しているアプリケーションは、アカウントへのアクセスができなくなります。これは元に戻せません。続行しますか? delete_token_success=トークンを削除しました。 削除したトークンを使用しているアプリケーションは、今後あなたのアカウントにアクセスできません。 +select_scopes=スコープを選択 +scopes_list=スコープ: manage_oauth2_applications=OAuth2アプリケーションの管理 edit_oauth2_application=OAuth2アプリケーションの編集 @@ -1018,10 +1027,12 @@ unstar=スター取消 star=スター fork=フォーク download_archive=リポジトリをダウンロード +more_operations=その他の操作 no_desc=説明なし quick_guide=クイック ガイド clone_this_repo=このリポジトリのクローンを作成 +cite_this_repo=このリポジトリを引用 create_new_repo_command=コマンドラインから新しいリポジトリを作成 push_exist_repo=コマンドラインから既存のリポジトリをプッシュ empty_message=このリポジトリの中には何もありません。 @@ -1120,6 +1131,7 @@ editor.commit_directly_to_this_branch=ブランチ%s editor.create_new_branch=新しいブランチにコミットしてプルリクエストを作成する。 editor.create_new_branch_np=新しいブランチにコミットする。 editor.propose_file_change=ファイル修正を提案 +editor.new_branch_name=このコミットの新しいブランチに名前を付けます editor.new_branch_name_desc=新しいブランチ名… editor.cancel=キャンセル editor.filename_cannot_be_empty=ファイル名は空にできません。 @@ -1171,6 +1183,7 @@ commits.signed_by_untrusted_user_unmatched=コミッターと一致しない信 commits.gpg_key_id=GPGキーID commits.ssh_key_fingerprint=SSH鍵のフィンガープリント +commit.operations=操作 commit.revert=リバート commit.revert-header=リバート: %s commit.revert-content=リバートするブランチを選択: @@ -1195,19 +1208,30 @@ projects.deletion_desc=プロジェクトを削除し、関連するすべての projects.deletion_success=プロジェクトを削除しました。 projects.edit=プロジェクトの編集 projects.edit_subheader=プロジェクトはイシューをまとめ、進捗を管理します。 -projects.modify=プロジェクトを更新 +projects.modify=プロジェクトを編集 projects.edit_success=プロジェクト '%s' を更新しました。 projects.type.none=なし projects.type.basic_kanban=基本的なカンバン projects.type.bug_triage=バグ トリアージ -projects.template.desc=プロジェクト テンプレート +projects.template.desc=テンプレート projects.template.desc_helper=開始するプロジェクトテンプレートを選択 projects.type.uncategorized=未分類 +projects.column.edit=列を編集 projects.column.edit_title=名称 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.deletion_desc=プロジェクト列を削除すると、関連するすべてのイシューが '未分類' に移動します。 続行しますか? 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=テキストのみ issues.desc=バグ報告、タスク、マイルストーンの作成。 issues.filter_assignees=候補者の絞り込み @@ -1284,6 +1308,7 @@ issues.filter_label_no_select=すべてのラベル issues.filter_milestone=マイルストーン issues.filter_milestone_no_select=すべてのマイルストーン issues.filter_project=プロジェクト +issues.filter_project_all=すべてのプロジェクト issues.filter_project_none=プロジェクトなし issues.filter_assignee=担当者 issues.filter_assginee_no_select=すべての担当者 @@ -1295,6 +1320,7 @@ issues.filter_type.assigned_to_you=自分が担当 issues.filter_type.created_by_you=自分が作成 issues.filter_type.mentioning_you=自分が関係 issues.filter_type.review_requested=レビュー依頼あり +issues.filter_type.reviewed_by_you=自分がレビュー issues.filter_sort=並べ替え issues.filter_sort.latest=新しい順 issues.filter_sort.oldest=古い順 @@ -1316,6 +1342,8 @@ issues.action_milestone=マイルストーン issues.action_milestone_no_select=マイルストーンなし issues.action_assignee=担当者 issues.action_assignee_no_select=担当者なし +issues.action_check=チェックを設定/解除します +issues.action_check_all=すべての項目のチェックを設定/解除します issues.opened_by=%[3]sが%[1]sに作成 pulls.merged_by=%[3]sが作成、%[1]sにマージ pulls.merged_by_fake=%[2]sが作成、%[1]sにマージ @@ -1622,6 +1650,7 @@ pulls.reopened_at=`がプルリクエストを再オープン コマンドラインの手順も確認できます。` pulls.merge_instruction_step1_desc=あなたのプロジェクトリポジトリで新しいブランチをチェックアウトし、変更内容をテストします。 pulls.merge_instruction_step2_desc=変更内容をマージして、Giteaに反映します。 +pulls.clear_merge_message=マージメッセージをクリア pulls.auto_merge_button_when_succeed=(チェックがすべて成功した場合) pulls.auto_merge_when_succeed=すべてのチェックが成功したら自動マージ @@ -1813,6 +1842,7 @@ settings.mirror_sync_in_progress=ミラー同期を実行しています。 し settings.site=Webサイト settings.update_settings=設定を更新 settings.branches.update_default_branch=デフォルトブランチを更新 +settings.branches.add_new_rule=新しいルールを追加 settings.advanced_settings=拡張設定 settings.wiki_desc=Wikiを有効にする settings.use_internal_wiki=ビルトインのWikiを使用する @@ -1842,8 +1872,11 @@ settings.pulls.ignore_whitespace=空白文字のコンフリクトを無視す settings.pulls.enable_autodetect_manual_merge=手動マージの自動検出を有効にする (注意: 特殊なケースでは判定ミスが発生する場合があります) settings.pulls.allow_rebase_update=リベースでプルリクエストのブランチの更新を可能にする settings.pulls.default_delete_branch_after_merge=デフォルトでプルリクエストのブランチをマージ後に削除する +settings.pulls.default_allow_edits_from_maintainers=デフォルトでメンテナからの編集を許可する +settings.releases_desc=リリースを有効にする settings.packages_desc=リポジトリパッケージレジストリを有効にする settings.projects_desc=リポジトリプロジェクトを有効にする +settings.actions_desc=Actionsを有効にする settings.admin_settings=管理者用設定 settings.admin_enable_health_check=リポジトリのヘルスチェックを有効にする (git fsck) settings.admin_code_indexer=コードインデクサ @@ -2053,6 +2086,8 @@ 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=プッシュを許可する settings.protected_branch_can_push_yes=プッシュできます settings.protected_branch_can_push_no=プッシュできません @@ -2087,15 +2122,17 @@ settings.dismiss_stale_approvals=古くなった承認を取り消す settings.dismiss_stale_approvals_desc=プルリクエストの内容を変える新たなコミットがブランチにプッシュされた場合、以前の承認を取り消します。 settings.require_signed_commits=コミット署名必須 settings.require_signed_commits_desc=署名されていない場合、または署名が検証できなかった場合は、このブランチへのプッシュを拒否します。 +settings.protect_branch_name_pattern=保護ブランチ名のパターン settings.protect_protected_file_patterns=保護されるファイルのパターン (セミコロン'\;'で区切る): settings.protect_protected_file_patterns_desc=保護されたファイルは、このブランチにファイルを追加・編集・削除する権限を持つユーザーであっても、直接変更することができなくなります。 セミコロン('\;')で区切って複数のパターンを指定できます。 パターンの文法については github.com/gobwas/glob を参照してください。 例: .drone.yml, /docs/**/*.txt settings.protect_unprotected_file_patterns=保護しないファイルのパターン (セミコロン'\;'で区切る): settings.protect_unprotected_file_patterns_desc=保護しないファイルは、ユーザーに書き込み権限があればプッシュ制限をバイパスして直接変更できます。 セミコロン('\;')で区切って複数のパターンを指定できます。 パターンの文法については github.com/gobwas/glob を参照してください。 例: .drone.yml, /docs/**/*.txt settings.add_protected_branch=保護を有効にする settings.delete_protected_branch=保護を無効にする -settings.update_protect_branch_success=ブランチ '%s' の保護を更新しました。 -settings.remove_protected_branch_success=ブランチ '%s' の保護を無効にしました。 -settings.protected_branch_deletion=ブランチ保護の無効化 +settings.update_protect_branch_success=ルール '%s' に対するブランチ保護を更新しました。 +settings.remove_protected_branch_success=ルール '%s' に対するブランチ保護を削除しました。 +settings.remove_protected_branch_failed=ブランチ保護ルール '%s' を削除できませんでした。 +settings.protected_branch_deletion=ブランチ保護の削除 settings.protected_branch_deletion_desc=ブランチ保護を無効にすると、書き込み権限を持つユーザーにブランチへのプッシュを許可することになります。 続行しますか? settings.block_rejected_reviews=不承認レビューでマージをブロック settings.block_rejected_reviews_desc=公式レビューアが変更を要請しているときは、承認数を満たしていてもマージできないようにします。 @@ -2104,10 +2141,13 @@ settings.block_on_official_review_requests_desc=公式レビュー依頼があ settings.block_outdated_branch=遅れているプルリクエストのマージをブロック settings.block_outdated_branch_desc=baseブランチがheadブランチより進んでいる場合、マージできないようにします。 settings.default_branch_desc=プルリクエストやコミット表示のデフォルトのブランチを選択: -settings.default_merge_style_desc=プルリクエストのデフォルトのマージ方法: +settings.merge_style_desc=マージ スタイル +settings.default_merge_style_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=タグの保護 @@ -2530,6 +2570,10 @@ dashboard.delete_old_actions=データベースから古い操作履歴をすべ dashboard.delete_old_actions.started=データベースからの古い操作履歴の削除を開始しました。 dashboard.update_checker=更新チェック dashboard.delete_old_system_notices=データベースから古いシステム通知をすべて削除 +dashboard.gc_lfs=LFSメタオブジェクトのガベージコレクション +dashboard.stop_zombie_tasks=ゾンビタスクを停止 +dashboard.stop_endless_tasks=終わらないタスクを停止 +dashboard.cancel_abandoned_jobs=放置されたままのジョブをキャンセル users.user_manage_panel=ユーザーアカウント管理 users.new_account=ユーザーアカウントを作成 @@ -2973,6 +3017,7 @@ monitor.queue.pool.cancel_desc=キューをワーカーグループ無しのま notices.system_notice_list=システム通知 notices.view_detail_header=通知の詳細を表示 +notices.operations=操作 notices.select_all=すべて選択 notices.deselect_all=すべて選択解除 notices.inverse_selection=選択を反転 @@ -3097,6 +3142,8 @@ keywords=キーワード details=詳細 details.author=著作者 details.project_site=プロジェクトサイト +details.repository_site=リポジトリサイト +details.documentation_site=ドキュメンテーションサイト details.license=ライセンス assets=アセット versions=バージョン @@ -3104,6 +3151,8 @@ versions.on=on versions.view_all=すべて表示 dependency.id=ID dependency.version=バージョン +cargo.details.repository_site=リポジトリサイト +cargo.details.documentation_site=ドキュメンテーションサイト chef.install=パッケージをインストールするには、次のコマンドを実行します: composer.registry=あなたの ~/.composer/config.json ファイルに、このレジストリをセットアップします: composer.install=Composer を使用してパッケージをインストールするには、次のコマンドを実行します: @@ -3114,6 +3163,11 @@ conan.details.repository=リポジトリ conan.registry=このレジストリをコマンドラインからセットアップします: conan.install=Conan を使用してパッケージをインストールするには、次のコマンドを実行します: conan.documentation=Conan レジストリの詳細については、 ドキュメント を参照してください。 +conda.registry=あなたの .condarc ファイルに、このレジストリを Conda リポジトリとしてセットアップします: +conda.install=Conda を使用してパッケージをインストールするには、次のコマンドを実行します: +conda.documentation=Condaレジストリの詳細については、 ドキュメント を参照してください。 +conda.details.repository_site=リポジトリサイト +conda.details.documentation_site=ドキュメンテーションサイト container.details.type=イメージタイプ container.details.platform=プラットフォーム container.pull=コマンドラインでイメージを取得します: @@ -3172,26 +3226,91 @@ settings.delete.description=パッケージの削除は恒久的で元に戻す settings.delete.notice=%s (%s) を削除しようとしています。この操作は元に戻せません。よろしいですか? settings.delete.success=パッケージを削除しました。 settings.delete.error=パッケージの削除に失敗しました。 +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.success.update=クリーンアップルールが更新されました。 +owner.settings.cleanuprules.success.delete=クリーンアップルールが削除されました。 +owner.settings.chef.title=Chefレジストリ +owner.settings.chef.keypair=キーペアを生成 +owner.settings.chef.keypair.description=Chefレジストリの認証に使用するキーペアを生成します。 それ以降は、以前のキーは使用できなくなります。 [secrets] +secrets=シークレット +description=シークレットは特定のActionsに渡されます。 それ以外で読み出されることはありません。 +none=まだシークレットはありません。 value=値 name=名称 +creation=シークレットを追加 +creation.name_placeholder=大文字小文字の区別なし、英数字とアンダースコアのみ、GITEA_ や GITHUB_ で始まるものは不可 +creation.value_placeholder=内容を入力してください。前後の空白は除去されます。 +creation.success=シークレット '%s' を追加しました。 +creation.failed=シークレットの追加に失敗しました。 +deletion=シークレットの削除 +deletion.description=シークレットの削除は恒久的で元に戻すことはできません。 続行しますか? +deletion.success=シークレットを削除しました。 +deletion.failed=シークレットの削除に失敗しました。 [actions] - - - +actions=Actions + +unit.desc=Actionsの管理 + +status.unknown=不明 +status.waiting=待機中 +status.running=実行中 +status.success=成功 +status.failure=失敗 +status.cancelled=キャンセルされた +status.skipped=スキップ +status.blocked=ブロックされた + +runners=ランナー +runners.runner_manage_panel=ランナーの管理 +runners.new=新しいランナーを作成 +runners.new_notice=ランナーの開始方法 +runners.status=ステータス runners.id=ID runners.name=名称 runners.owner_type=タイプ runners.description=説明 runners.labels=ラベル +runners.last_online=最終オンライン時刻 +runners.agent_labels=エージェントのラベル +runners.custom_labels=カスタムラベル +runners.custom_labels_helper=カスタムラベルは管理者により手動で追加されたラベルです。 ラベルはカンマで区切り、各ラベルの前後の空白は無視されます。 +runners.runner_title=ランナー +runners.task_list=このランナーの最近のタスク runners.task_list.run=実行 +runners.task_list.status=ステータス runners.task_list.repository=リポジトリ runners.task_list.commit=コミット -runners.status.active=有効 - +runners.task_list.done_at=終了時刻 +runners.edit_runner=ランナーの編集 +runners.update_runner=変更を保存 +runners.update_runner_success=ランナーを更新しました +runners.update_runner_failed=ランナーの更新に失敗しました +runners.delete_runner=このランナーを削除 +runners.delete_runner_success=ランナーを削除しました +runners.delete_runner_failed=ランナーの削除に失敗しました +runners.delete_runner_header=ランナー削除の確認 +runners.delete_runner_notice=このランナーでタスクが実行されている場合、タスクは停止され失敗扱いとなります。 それによりビルドワークフローが途中で終了することになるかもしれません。 +runners.none=利用可能なランナーはありません +runners.status.unspecified=不明 +runners.status.idle=アイドル +runners.status.active=稼働中 +runners.status.offline=オフライン + +runs.all_workflows=すべてのワークフロー +runs.open_tab=%d オープン +runs.closed_tab=%d クローズ runs.commit=コミット +runs.pushed_by=Pushed by +need_approval_desc=フォークプルリクエストのワークフローを実行するには承認が必要です。 diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 38127391a2dbd..986a3d99efe94 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -112,7 +112,9 @@ never=Nunca rss_feed=Feed RSS [aria] +navbar=Barra de navegação footer=Rodapé +footer.software=Sobre o Software footer.links=Links [filter] @@ -248,6 +250,7 @@ no_reply_address_helper=Nome de domínio para usuários com um endereço de e-ma password_algorithm=Algoritmo Hash de Senha password_algorithm_helper=Escolha o algoritmo de hash para as senhas. Diferentes algoritmos têm requerimentos e forças diversos. O `Argon2` possui boa qualidade, porém usa muita memória e pode ser inapropriado para sistemas com menos recursos. enable_update_checker=Habilitar Verificador de Atualizações +enable_update_checker_helper=Procura por novas versões periodicamente conectando-se ao gitea.io. [home] uname_holder=Usuário ou e-mail @@ -283,7 +286,9 @@ organizations=Organizações search=Pesquisar code=Código search.fuzzy=Similar +search.fuzzy.tooltip=Incluir resultados que sejam próximos ao termo de busca search.match=Correspondência +search.match.tooltip=Incluir somente resultados que correspondam exatamente ao termo de busca code_search_unavailable=A pesquisa por código não está disponível no momento. Entre em contato com o administrador do site. repo_no_results=Nenhum repositório correspondente foi encontrado. user_no_results=Nenhum usuário correspondente foi encontrado. @@ -322,6 +327,7 @@ email_not_associate=O endereço de e-mail não está associado à nenhuma conta. send_reset_mail=Enviar e-mail de recuperação de conta reset_password=Recuperação de conta invalid_code=Seu código de confirmação é inválido ou expirou. +invalid_password=Sua senha não coincide com a senha que foi usada para criar a conta. reset_password_helper=Recuperar conta reset_password_wrong_user=Você está conectado como %s, mas o link de recuperação de conta é para %s password_too_short=A senha deve ter %d ou mais caracteres. @@ -365,6 +371,7 @@ password_pwned_err=Não foi possível concluir a requisição ao HaveIBeenPwned [mail] view_it_on=Veja em %s +reply=ou responda diretamente a este email link_not_working_do_paste=Não está funcionando? Tente copiá-lo e colá-lo no seu navegador. hi_user_x=Olá %s, @@ -423,6 +430,10 @@ repo.transfer.body=Para o aceitar ou rejeitar visite %s, ou simplesmente o ignor repo.collaborator.added.subject=%s adicionou você a %s repo.collaborator.added.text=Você foi adicionado como um colaborador do repositório: +team_invite.subject=%[1]s convidou você para participar da organização %[2]s +team_invite.text_1=%[1]s convidou você para participar da equipe %[2]s na organização %[3]s. +team_invite.text_2=Por favor, clique no seguinte link para se juntar à equipe: +team_invite.text_3=Nota: este convite foi destinado a %[1]s. Se você não estava esperando este convite, você pode ignorar este e-mail. [modal] yes=Sim @@ -464,6 +475,8 @@ url_error=`'%s' não é uma URL válida.` include_error=` deve conter '%s'.` glob_pattern_error=` padrão glob é inválido: %s.` regex_pattern_error=` o regex é inválido: %s.` +username_error=` só pode conter caracteres alfanuméricos ('0-9','a-z','A-Z'), traço ('-'), sublinhado ('_') e ponto ('.'). Não pode começar ou terminar com caracteres não alfanuméricos, e caracteres não-alfanuméricos consecutivos também são proibidos.` +invalid_group_team_map_error=` mapeamento é inválido: %s` unknown_error=Erro desconhecido: captcha_incorrect=O código CAPTCHA está incorreto. password_not_match=As senhas não coincidem. @@ -499,10 +512,13 @@ user_not_exist=O usuário não existe. team_not_exist=A equipe não existe. last_org_owner=Você não pode remover o último usuário do time 'proprietários'. Deve haver pelo menos um proprietário em uma organização. cannot_add_org_to_team=Uma organização não pode ser adicionada como membro de uma equipe. +duplicate_invite_to_team=O usuário já foi convidado para se juntar da equipe. +organization_leave_success=Você saiu da organização %s com sucesso. invalid_ssh_key=Não é possível verificar sua chave SSH: %s invalid_gpg_key=Não é possível verificar sua chave GPG: %s invalid_ssh_principal=Nome principal inválido: %s +must_use_public_key=A chave que você forneceu é uma chave privada. Por favor, não envie sua chave privada em nenhum lugar. Use sua chave pública em vez disso. unable_verify_ssh_key=Não é possível verificar sua chave SSH auth_failed=Autenticação falhou: %v @@ -739,6 +755,7 @@ access_token_deletion_cancel_action=Cancelar access_token_deletion_confirm_action=Excluir access_token_deletion_desc=A exclusão de um token revoga o acesso à sua conta para aplicativos que o usam. Continuar? delete_token_success=O token foi excluído. Os aplicativos que o utilizam já não têm acesso à sua conta. +select_scopes=Selecione os escopos scopes_list=Escopos: manage_oauth2_applications=Gerenciar aplicativos OAuth2 @@ -1011,10 +1028,12 @@ unstar=Retirar dos favoritos star=Juntar aos favoritos fork=Fork download_archive=Baixar repositório +more_operations=Mais Operações no_desc=Nenhuma descrição quick_guide=Guia Rápido clone_this_repo=Clonar este repositório +cite_this_repo=Citar este repositório create_new_repo_command=Criando um novo repositório por linha de comando push_exist_repo=Realizando push para um repositório existente por linha de comando empty_message=Este repositório está vazio. @@ -1113,6 +1132,7 @@ editor.commit_directly_to_this_branch=Commit diretamente no branch novo branch para este commit e crie um pull request. editor.create_new_branch_np=Crie um novo branch para este commit. editor.propose_file_change=Propor alteração de arquivo +editor.new_branch_name=Nome do novo branch para este commit editor.new_branch_name_desc=Novo nome do branch... editor.cancel=Cancelar editor.filename_cannot_be_empty=Nome do arquivo não pode ser em branco. @@ -1197,12 +1217,21 @@ projects.type.bug_triage=Triagem de Bugs projects.template.desc=Modelo de projeto projects.template.desc_helper=Selecione um modelo de projeto para começar projects.type.uncategorized=Sem categoria +projects.column.edit=Editar coluna projects.column.edit_title=Nome projects.column.new_title=Nome +projects.column.new_submit=Criar coluna projects.column.new=Nova Coluna +projects.column.set_default=Definir padrão +projects.column.set_default_desc=Definir esta coluna como padrão para pull e issues sem categoria +projects.column.delete=Excluir coluna +projects.column.deletion_desc=Excluir uma coluna do projeto move todas as issues relacionadas para 'Sem categoria'. Continuar? projects.column.color=Colorido projects.open=Abrir projects.close=Fechar +projects.column.assigned_to=Atribuído a +projects.card_type.images_and_text=Imagens e Texto +projects.card_type.text_only=Somente texto issues.desc=Organize relatórios de bugs, tarefas e marcos. issues.filter_assignees=Filtrar Atribuição @@ -1279,6 +1308,7 @@ issues.filter_label_no_select=Todas as etiquetas issues.filter_milestone=Marco issues.filter_milestone_no_select=Todos os marcos issues.filter_project=Projeto +issues.filter_project_all=Todos os projetos issues.filter_project_none=Sem projeto issues.filter_assignee=Atribuído issues.filter_assginee_no_select=Todos os responsáveis @@ -1290,6 +1320,7 @@ issues.filter_type.assigned_to_you=Atribuídos a você issues.filter_type.created_by_you=Criado por você issues.filter_type.mentioning_you=Mencionando você issues.filter_type.review_requested=Revisão solicitada +issues.filter_type.reviewed_by_you=Revisado por você issues.filter_sort=Ordenação issues.filter_sort.latest=Mais recentes issues.filter_sort.oldest=Mais antigos @@ -1364,6 +1395,7 @@ issues.save=Salvar issues.label_title=Nome da etiqueta issues.label_description=Descrição da etiqueta issues.label_color=Cor da etiqueta +issues.label_exclusive=Exclusivo issues.label_count=%d etiquetas issues.label_open_issues=%d issues abertas issues.label_edit=Editar @@ -1617,6 +1649,7 @@ pulls.reopened_at=`reabriu este pull request %[2]sinstruções para a linha de comandos.` pulls.merge_instruction_step1_desc=No repositório do seu projeto, crie um novo branch e teste as alterações. pulls.merge_instruction_step2_desc=Faça merge das alterações e atualize no Gitea. +pulls.clear_merge_message=Limpar mensagem do merge pulls.auto_merge_button_when_succeed=(Quando a verificação for bem-sucedida) pulls.auto_merge_when_succeed=Mesclar automaticamente quando todas as verificações forem bem sucedidas @@ -1773,7 +1806,9 @@ activity.git_stats_deletion_n=%d exclusões search=Pesquisar search.search_repo=Pesquisar no repositório... search.fuzzy=Aproximada +search.fuzzy.tooltip=Incluir resultados que sejam próximos ao termo de busca search.match=Corresponde +search.match.tooltip=Incluir somente resultados que correspondam exatamente ao termo de busca search.results=Resultados da pesquisa para "%s" em %s search.code_no_results=Nenhum código-fonte correspondente ao seu termo de pesquisa foi encontrado. search.code_search_unavailable=A pesquisa por código não está disponível no momento. Entre em contato com o administrador do site. @@ -1805,6 +1840,7 @@ settings.mirror_sync_in_progress=Sincronização do espelhamento está em andame settings.site=Site settings.update_settings=Atualizar configurações settings.branches.update_default_branch=Atualizar Branch Padrão +settings.branches.add_new_rule=Adicionar Nova Regra settings.advanced_settings=Configurações avançadas settings.wiki_desc=Habilitar a wiki do repositório settings.use_internal_wiki=Usar a wiki nativa @@ -1834,8 +1870,11 @@ settings.pulls.ignore_whitespace=Ignorar espaço em branco em conflitos settings.pulls.enable_autodetect_manual_merge=Habilitar a detecção automática de merge manual (Nota: Em alguns casos especiais, podem ocorrer julgamentos errados) settings.pulls.allow_rebase_update=Ativar atualização do branch do pull request por rebase settings.pulls.default_delete_branch_after_merge=Excluir o branch de pull request após o merge por padrão +settings.pulls.default_allow_edits_from_maintainers=Permitir edições de mantenedores por padrão +settings.releases_desc=Habilitar versões do Repositório settings.packages_desc=Habilitar Registro de Pacotes de Repositório settings.projects_desc=Habilitar Projetos do Repositório +settings.actions_desc=Habilitar ações do repositório settings.admin_settings=Configurações do administrador settings.admin_enable_health_check=Habilitar verificações de integridade (git fsck) no repositório settings.admin_code_indexer=Indexador de código @@ -2044,6 +2083,8 @@ settings.deploy_key_deletion_desc=A exclusão de uma chave de deploy irá revoga settings.deploy_key_deletion_success=A chave de deploy foi removida. settings.branches=Branches settings.protected_branch=Proteção de Branch +settings.protected_branch.save_rule=Salvar Regra +settings.protected_branch.delete_rule=Excluir Regra settings.protected_branch_can_push=Permitir push? settings.protected_branch_can_push_yes=Você pode fazer push settings.protected_branch_can_push_no=Você não pode fazer push @@ -2095,6 +2136,7 @@ settings.block_on_official_review_requests_desc=O merge não será possível qua settings.block_outdated_branch=Bloquear o merge se o pull request estiver desatualizado settings.block_outdated_branch_desc=O merge não será possível quando o branch de topo estiver atrás do branch base. settings.default_branch_desc=Selecione um branch padrão para pull requests e commits de código: +settings.merge_style_desc=Estilos de Merge settings.default_merge_style_desc=Estilo de merge padrão para pull requests: settings.choose_branch=Escolha um branch... settings.no_protected_branch=Não há branches protegidos. @@ -2661,6 +2703,7 @@ auths.admin_filter=Filtro de administrador auths.restricted_filter=Filtro de restrição auths.restricted_filter_helper=Deixe em branco para não definir nenhum usuário como restrito. Use um asterisco ('*') para definir todos os usuários que não correspondem ao Filtro de administrador como restritos. auths.verify_group_membership=Verificar associação ao grupo no LDAP (deixe o filtro vazio para ignorar) +auths.group_search_base=Grupo de Pesquisa DN Base auths.group_attribute_list_users=Atributo do Grupo que Contém a Lista de Usuários auths.user_attribute_in_group=Atributo do Usuário Listado em Grupo auths.map_group_to_team=Mapear grupos LDAP para Organizações (deixe o campo vazio para pular) @@ -3087,6 +3130,8 @@ keywords=Palavras-chave details=Detalhes details.author=Autor details.project_site=Site do Projeto +details.repository_site=Site do Repositório +details.documentation_site=Site da Documentação details.license=Licença assets=Recursos versions=Versões @@ -3094,6 +3139,11 @@ versions.on=em versions.view_all=Ver todas dependency.id=ID dependency.version=Versão +cargo.registry=Configurar este registro no arquivo de configuração de Cargo (por exemplo ~/.cargo/config.toml): +cargo.install=Para instalar o pacote usando Cargo, execute o seguinte comando: +cargo.details.repository_site=Site do Repositório +cargo.details.documentation_site=Site da Documentação +chef.registry=Configure este registro em seu arquivo ~/.chef/config.rb: chef.install=Para instalar o pacote, execute o seguinte comando: composer.registry=Configure este registro em seu arquivo ~/.composer/config.json: composer.install=Para instalar o pacote usando o Composer, execute o seguinte comando: @@ -3104,6 +3154,11 @@ conan.details.repository=Repositório conan.registry=Configure este registro pela linha de comando: conan.install=Para instalar o pacote usando o Conan, execute o seguinte comando: conan.documentation=Para obter mais informações sobre o registro Conan, consulte a documentação. +conda.registry=Configure este registro como um repositório Conda no arquivo .condarc: +conda.install=Para instalar o pacote usando o Conda, execute o seguinte comando: +conda.documentation=Para obter mais informações sobre o registro Conda, consulte a documentação. +conda.details.repository_site=Site do Repositório +conda.details.documentation_site=Site da Documentação container.details.type=Tipo de Imagem container.details.platform=Plataforma container.pull=Puxe a imagem pela linha de comando: @@ -3162,26 +3217,78 @@ settings.delete.description=A exclusão de um pacote é permanente e não pode s settings.delete.notice=Você está prestes a excluir %s (%s). Esta operação é irreversível, tem certeza? settings.delete.success=O pacote foi excluído. settings.delete.error=Falha ao excluir o pacote. +owner.settings.cleanuprules.title=Gerenciar Regras de Limpeza +owner.settings.cleanuprules.add=Adicionar Regra de Limpeza +owner.settings.cleanuprules.edit=Editar Regra de Limpeza +owner.settings.cleanuprules.none=Não há regras de limpeza disponíveis. Leia a documentação para saber mais. +owner.settings.cleanuprules.preview=Pré-visualizar Regra de Limpeza +owner.settings.cleanuprules.preview.overview=%d pacotes agendados para serem removidos. +owner.settings.cleanuprules.preview.none=A regra de limpeza não corresponde a nenhum pacote. owner.settings.cleanuprules.enabled=Habilitado +owner.settings.cleanuprules.keep.title=Versões que correspondem a estas regras são mantidas, mesmo se corresponderem a uma regra de remoção abaixo. +owner.settings.cleanuprules.keep.count=Manter o mais recente +owner.settings.cleanuprules.keep.count.1=1 versão por pacote +owner.settings.cleanuprules.keep.count.n=%d versões por pacote +owner.settings.cleanuprules.keep.pattern=Manter versões correspondentes +owner.settings.cleanuprules.keep.pattern.container=A versão latest é sempre mantida para pacotes de Container. +owner.settings.cleanuprules.remove.title=Versões que correspondem a essas regras são removidas, a menos que uma regra acima diga para mantê-las. +owner.settings.cleanuprules.remove.days=Remover versões mais antigas que +owner.settings.cleanuprules.remove.pattern=Remover versões correspondentes +owner.settings.cleanuprules.success.update=Regra de limpeza foi atualizada. +owner.settings.cleanuprules.success.delete=Regra de limpeza foi excluída. +owner.settings.chef.title=Registro Chef +owner.settings.chef.keypair=Gerar par de chaves [secrets] +secrets=Segredos +description=Os segredos serão passados a certas ações e não poderão ser lidos de outra forma. +none=Não há segredos ainda. value=Valor name=Nome +creation=Adicionar Segredo +creation.success=O segredo '%s' foi adicionado. +creation.failed=Falha ao adicionar segredo. +deletion=Excluir segredo +deletion.description=A exclusão de um segredo é permanente e não pode ser desfeita. Continuar? +deletion.success=O segredo foi excluído. +deletion.failed=Falha ao excluir segredo. [actions] +actions=Ações +unit.desc=Gerenciar ações +status.unknown=Desconhecido +status.waiting=Em espera +status.running=Rodando +status.success=Sucesso +status.failure=Falha +status.cancelled=Cancelado +status.skipped=Ignorado +status.blocked=Bloqueado +runners.status=Status runners.id=ID runners.name=Nome runners.owner_type=Tipo runners.description=Descrição runners.labels=Rótulos +runners.last_online=Última Vez Online +runners.custom_labels=Etiquetas Personalizadas +runners.custom_labels_helper=Etiquetas personalizadas são etiquetas que são adicionadas manualmente por um administrador. Separe as etiquetas com vírgula. Espaço em branco no começo ou no final de cada etiqueta é ignorado. runners.task_list.run=Executar +runners.task_list.status=Status runners.task_list.repository=Repositório runners.task_list.commit=Commit +runners.update_runner=Atualizar as Alterações +runners.status.unspecified=Desconhecido +runners.status.idle=Inativo runners.status.active=Ativo +runners.status.offline=Offiline +runs.open_tab=%d Aberto +runs.closed_tab=%d Fechado runs.commit=Commit +runs.pushed_by=Push realizado por diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index d7497224ad0fa..60b8f14db2f05 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -57,6 +57,7 @@ new_mirror=Nova réplica new_fork=Nova derivação do repositório new_org=Nova organização new_project=Novo planeamento +new_project_column=Nova coluna manage_org=Gerir organizações admin_panel=Administração do sítio account_settings=Configurações da conta @@ -90,9 +91,11 @@ disabled=Desabilitado copy=Copiar copy_url=Copiar URL +copy_content=Copiar conteúdo copy_branch=Copiar nome do ramo copy_success=Copiado! copy_error=Falha ao copiar +copy_type_unsupported=Este tipo de ficheiro não pode ser copiado write=Escrever preview=Pré-visualizar @@ -109,6 +112,10 @@ never=Nunca rss_feed=Fonte RSS [aria] +navbar=Barra de navegação +footer=Rodapé +footer.software=Sobre o Software +footer.links=Ligações [filter] string.asc=A - Z @@ -321,6 +328,7 @@ email_not_associate=O endereço de email não está associado a qualquer conta. send_reset_mail=Enviar email de recuperação da conta reset_password=Recuperação de conta invalid_code=O seu código de confirmação é inválido ou expirou. +invalid_password=A sua senha não corresponde à senha que foi usada para criar a conta. reset_password_helper=Recuperar conta reset_password_wrong_user=Tem conta iniciada como %s, mas a ligação de recuperação de conta é para %s password_too_short=O tamanho da senha não pode ser inferior a %d caracteres. @@ -364,6 +372,7 @@ password_pwned_err=Não foi possível completar o pedido ao HaveIBeenPwned [mail] view_it_on=Ver em %s +reply=ou responda a este email imediatamente link_not_working_do_paste=Não está a funcionar? Tente copiar e colar no seu navegador. hi_user_x=Olá %s, @@ -467,6 +476,8 @@ url_error=`'%s' não é um URL válido.` include_error=` tem que conter o texto '%s'.` glob_pattern_error=` o padrão glob é inválido: %s.` regex_pattern_error=` o padrão regex é inválido: %s.` +username_error=` só pode conter caracteres alfanuméricos ('0-9','a-z','A-Z'), hífen ('-'), sublinhado ('_') e ponto ('.') Não pode começar nem terminar com caracteres não alfanuméricos, e caracteres não alfanuméricos consecutivos também são proibidos.` +invalid_group_team_map_error=` o mapeamento é inválido: %s` unknown_error=Erro desconhecido: captcha_incorrect=O código CAPTCHA está errado. password_not_match=As senhas não coincidem. @@ -503,10 +514,12 @@ team_not_exist=A equipa não existe. last_org_owner=Não pode remover o último utilizador da equipa 'proprietários'. Tem que haver pelo menos um proprietário numa organização. cannot_add_org_to_team=Uma organização não pode ser adicionada como membro de uma equipa. duplicate_invite_to_team=O(A) utilizador(a) já tinha sido convidado(a) para ser membro da equipa. +organization_leave_success=Você deixou a organização %s com sucesso. invalid_ssh_key=Não é possível validar a sua chave SSH: %s invalid_gpg_key=Não é possível validar a sua chave GPG: %s invalid_ssh_principal=Protagonista inválido: %s +must_use_public_key=A chave que você forneceu é privada. Não carregue a sua chave em lugar nenhum, em vez disso use a sua chave pública. unable_verify_ssh_key=Não é possível validar a chave SSH auth_failed=Falhou a autenticação: %v @@ -743,6 +756,8 @@ access_token_deletion_cancel_action=Cancelar access_token_deletion_confirm_action=Eliminar access_token_deletion_desc=Eliminar um código revoga o acesso à sua conta nas aplicações que o usem. Esta operação não poderá ser revertida. Quer continuar? delete_token_success=O código foi eliminado. Aplicações que o usavam deixaram de ter acesso à sua conta. +select_scopes=Escolha os âmbitos +scopes_list=Âmbitos: manage_oauth2_applications=Gerir aplicações OAuth2 edit_oauth2_application=Editar aplicação OAuth2 @@ -1015,10 +1030,12 @@ unstar=Tirar dos favoritos star=Juntar aos favoritos fork=Derivar download_archive=Descarregar repositório +more_operations=Mais operações no_desc=Sem descrição quick_guide=Guia rápido clone_this_repo=Clonar este repositório +cite_this_repo=Citar este repositório create_new_repo_command=Criando um novo repositório na linha de comandos push_exist_repo=Enviando, pela linha de comandos, um repositório existente empty_message=Este repositório não contém qualquer conteúdo. @@ -1117,6 +1134,7 @@ editor.commit_directly_to_this_branch=Cometer imediatamente no ramo novo ramo para este cometimento e inicie um pedido de integração. editor.create_new_branch_np=Criar um novo ramo para este cometimento. editor.propose_file_change=Propor modificação do ficheiro +editor.new_branch_name=Dê um nome ao novo ramo para este cometimento editor.new_branch_name_desc=Nome do novo ramo… editor.cancel=Cancelar editor.filename_cannot_be_empty=O nome do ficheiro não pode estar em branco. @@ -1168,6 +1186,7 @@ commits.signed_by_untrusted_user_unmatched=Assinado por um utilizador não fiáv commits.gpg_key_id=ID da chave GPG commits.ssh_key_fingerprint=Identificação digital da chave SSH +commit.operations=Operações commit.revert=Reverter commit.revert-header=Reverter: %s commit.revert-content=Escolha o ramo para onde vai reverter: @@ -1200,11 +1219,22 @@ projects.type.bug_triage=Triagem de erros projects.template.desc=Modelo de planeamento projects.template.desc_helper=Escolha um modelo de planeamento para começar projects.type.uncategorized=Sem categoria +projects.column.edit=Editar coluna projects.column.edit_title=Nome projects.column.new_title=Nome +projects.column.new_submit=Criar coluna +projects.column.new=Nova coluna +projects.column.set_default=Definir como padrão +projects.column.set_default_desc=Defina esta coluna como padrão para questões e pedidos de integração não categorizados +projects.column.delete=Eliminar coluna +projects.column.deletion_desc=Eliminar uma coluna de um planeamento faz com que todas as questões que nela constam sejam movidas para a coluna 'Sem categoria'. Continuar? projects.column.color=Colorido projects.open=Abrir projects.close=Fechar +projects.column.assigned_to=Atribuída a +projects.card_type.desc=Previsão dos cartões +projects.card_type.images_and_text=Imagens e texto +projects.card_type.text_only=Apenas texto issues.desc=Organize relatórios de erros, tarefas e etapas. issues.filter_assignees=Filtrar encarregado @@ -1281,6 +1311,7 @@ issues.filter_label_no_select=Todos os rótulos issues.filter_milestone=Etapa issues.filter_milestone_no_select=Todas as etapas issues.filter_project=Planeamento +issues.filter_project_all=Todos os planeamentos issues.filter_project_none=Nenhum planeamento issues.filter_assignee=Encarregado issues.filter_assginee_no_select=Todos os encarregados @@ -1292,6 +1323,7 @@ issues.filter_type.assigned_to_you=Atribuídas a si issues.filter_type.created_by_you=Criadas por si issues.filter_type.mentioning_you=Mencionando a si issues.filter_type.review_requested=Revisão solicitada +issues.filter_type.reviewed_by_you=Revistos por si issues.filter_sort=Ordem issues.filter_sort.latest=Mais recentes issues.filter_sort.oldest=Mais antigas @@ -1313,6 +1345,8 @@ issues.action_milestone=Etapa issues.action_milestone_no_select=Sem etapa issues.action_assignee=Encarregado issues.action_assignee_no_select=Sem encarregado +issues.action_check=Marcar/desmarcar +issues.action_check_all=Marcar/desmarcar todos os itens issues.opened_by=aberta %[1]s por %[3]s pulls.merged_by=por %[3]s foi executado %[1]s pulls.merged_by_fake=por %[2]s foi executado %[1]s @@ -1366,6 +1400,9 @@ issues.save=Guardar issues.label_title=Nome do rótulo issues.label_description=Descrição do rótulo issues.label_color=Cor do rótulo +issues.label_exclusive=Exclusivo +issues.label_exclusive_desc=Nomeie o rótulo âmbito/item para torná-lo mutuamente exclusivo com outros rótulos do âmbito/. +issues.label_exclusive_warning=Quaisquer rótulos com âmbito que estejam em conflito irão ser removidos ao editar os rótulos de uma questão ou de um pedido de integração. issues.label_count=%d rótulos issues.label_open_issues=%d questões abertas issues.label_edit=Editar @@ -1619,6 +1656,8 @@ pulls.reopened_at=`reabriu este pedido de integração instruções para a linha de comandos.` pulls.merge_instruction_step1_desc=No seu repositório, crie um novo ramo e teste as modificações. pulls.merge_instruction_step2_desc=Integre as modificações e envie para o Gitea. +pulls.clear_merge_message=Apagar mensagem de integração +pulls.clear_merge_message_hint=Apagar a mensagem de integração apenas remove o conteúdo da mensagem de cometimento e mantém os rodapés do git, tais como "Co-Autorado-Por …". pulls.auto_merge_button_when_succeed=(quando as verificações forem bem-sucedidas) pulls.auto_merge_when_succeed=Integrar automaticamente quando todas as verificações forem bem-sucedidas @@ -1810,6 +1849,7 @@ settings.mirror_sync_in_progress=A sincronização da réplica está em andament settings.site=Sítio web settings.update_settings=Modificar configurações settings.branches.update_default_branch=Definir o ramo principal +settings.branches.add_new_rule=Adicionar nova regra settings.advanced_settings=Configurações avançadas settings.wiki_desc=Habilitar wiki do repositório settings.use_internal_wiki=Usar o wiki nativo @@ -1839,8 +1879,11 @@ settings.pulls.ignore_whitespace=Ignorar espaços em branco nos conflitos settings.pulls.enable_autodetect_manual_merge=Habilitar a identificação automática de integrações manuais (obs.: nalguns casos especiais a avaliação pode ser errada) settings.pulls.allow_rebase_update=Habilitar a modificação do ramo do pedido de integração através da mudança de base settings.pulls.default_delete_branch_after_merge=Eliminar o ramo do pedido de integração depois de finalizada a integração, como predefinição +settings.pulls.default_allow_edits_from_maintainers=Permitir, por padrão, que os responsáveis editem +settings.releases_desc=Habilitar lançamentos no repositório settings.packages_desc=Habilitar o registo de pacotes do repositório settings.projects_desc=Habilitar planeamentos no repositório +settings.actions_desc=Habilitar operações no repositório settings.admin_settings=Configurações do administrador settings.admin_enable_health_check=Habilitar verificações de integridade (git fsck) no repositório settings.admin_code_indexer=Indexador de código @@ -2050,6 +2093,8 @@ settings.deploy_key_deletion_desc=Remover uma chave de instalação irá revogar settings.deploy_key_deletion_success=A chave de instalação foi removida. settings.branches=Ramos settings.protected_branch=Salvaguarda do ramo +settings.protected_branch.save_rule=Guardar regra +settings.protected_branch.delete_rule=Eliminar regra settings.protected_branch_can_push=Permitir envios? settings.protected_branch_can_push_yes=Pode enviar settings.protected_branch_can_push_no=Não pode enviar @@ -2084,6 +2129,7 @@ settings.dismiss_stale_approvals=Descartar aprovações obsoletas settings.dismiss_stale_approvals_desc=Quando novos cometimentos que mudam o conteúdo do pedido de integração forem enviados para o ramo, as aprovações antigas serão descartadas. settings.require_signed_commits=Exigir cometimentos assinados settings.require_signed_commits_desc=Rejeitar envios para este ramo que não estejam assinados ou que não sejam validáveis. +settings.protect_branch_name_pattern=Padrão do nome do ramo protegido settings.protect_protected_file_patterns=Padrões de ficheiros protegidos (separados com ponto e vírgula '\;'): settings.protect_protected_file_patterns_desc=Ficheiros protegidos que não podem ser modificados, mesmo que o utilizador tenha direitos para adicionar, editar ou eliminar ficheiros neste ramo. Múltiplos padrões podem ser separados com ponto e vírgula ('\;'). Veja a documentação em github.com/gobwas/glob para ver a sintaxe. Exemplos: .drone.yml, /docs/**/*.txt. settings.protect_unprotected_file_patterns=Padrões de ficheiros desprotegidos (separados com ponto e vírgula '\;'): @@ -2092,6 +2138,7 @@ settings.add_protected_branch=Habilitar salvaguarda settings.delete_protected_branch=Desabilitar salvaguarda settings.update_protect_branch_success=A salvaguarda do ramo '%s' foi modificada. settings.remove_protected_branch_success=A salvaguarda do ramo '%s' foi desabilitada. +settings.remove_protected_branch_failed=A remoção da regra '%s' de salvaguarda do ramo falhou. settings.protected_branch_deletion=Desabilitar salvaguarda do ramo settings.protected_branch_deletion_desc=Desabilitar a salvaguarda do ramo irá permitir que os utilizadores que tenham permissão de escrita enviem para o ramo. Quer continuar? settings.block_rejected_reviews=Bloquear a integração quando há revisões rejeitadas @@ -2101,10 +2148,13 @@ settings.block_on_official_review_requests_desc=A integração não será possí settings.block_outdated_branch=Bloquear integração se o pedido de integração for obsoleto settings.block_outdated_branch_desc=A integração não será possível quando o ramo de topo estiver abaixo do ramo base. settings.default_branch_desc=Escolha um ramo do repositório como sendo o predefinido para pedidos de integração e cometimentos: +settings.merge_style_desc=Estilos de integração settings.default_merge_style_desc=Tipo de integração predefinido para pedidos de integração: settings.choose_branch=Escolha um ramo… settings.no_protected_branch=Não existem ramos protegidos. settings.edit_protected_branch=Editar +settings.protected_branch_required_rule_name=Nome de regra obrigatório +settings.protected_branch_duplicate_rule_name=Nome de regra duplicado settings.protected_branch_required_approvals_min=O número mínimo exigido de aprovações não pode ser negativo. settings.tags=Etiquetas settings.tags.protection=Proteger etiquetas @@ -2260,6 +2310,8 @@ release.downloads=Descargas release.download_count=Descargas: %s release.add_tag_msg=Usar o título e o conteúdo do lançamento como mensagem da etiqueta. release.add_tag=Criar apenas a etiqueta +release.releases_for=Lançamentos para %s +release.tags_for=Etiquetas para %s branch.name=Nome do ramo branch.search=Procurar ramos @@ -2527,6 +2579,10 @@ dashboard.delete_old_actions=Eliminar todas as operações antigas da base de da dashboard.delete_old_actions.started=Foi iniciado o processo de eliminação de todas as operações antigas da base de dados. dashboard.update_checker=Verificador de novas versões dashboard.delete_old_system_notices=Eliminar todas as notificações do sistema antigas da base de dados +dashboard.gc_lfs=Recolher lixo dos meta-elementos LFS +dashboard.stop_zombie_tasks=Parar tarefas zombies +dashboard.stop_endless_tasks=Parar tarefas intermináveis +dashboard.cancel_abandoned_jobs=Cancelar trabalhos abandonados users.user_manage_panel=Gestão das contas de utilizadores users.new_account=Criar conta de utilizador @@ -2615,6 +2671,7 @@ repos.size=Tamanho packages.package_manage_panel=Gestão de pacotes packages.total_size=Tamanho total: %s +packages.unreferenced_size=Tamanho não referenciado: %s packages.owner=Proprietário packages.creator=Criador packages.name=Nome @@ -2708,6 +2765,8 @@ auths.oauth2_required_claim_value_helper=Defina este valor para restringir o in auths.oauth2_group_claim_name=Reivindicar nome que fornece nomes de grupo para esta fonte. (Opcional) auths.oauth2_admin_group=Valor da Reivindicação de Grupo para utilizadores administradores. (Opcional - exige a reivindicação de nome acima) auths.oauth2_restricted_group=Valor da Reivindicação de Grupo para utilizadores restritos. (Opcional - exige a reivindicação de nome acima) +auths.oauth2_map_group_to_team=Mapear grupos reclamados em equipas da organização (opcional — requer nome de reclamação acima). +auths.oauth2_map_group_to_team_removal=Remover utilizadores das equipas sincronizadas se esses utilizadores não pertencerem ao grupo correspondente. auths.enable_auto_register=Habilitar o registo automático auths.sspi_auto_create_users=Criar utilizadores automaticamente auths.sspi_auto_create_users_helper=Permitir que o método de autenticação SSPI crie, automaticamente, novas contas para utilizadores que iniciam a sessão pela primeira vez @@ -2970,6 +3029,7 @@ monitor.queue.pool.cancel_desc=Deixar uma fila sem quaisquer grupos de trabalhad notices.system_notice_list=Notificações do sistema notices.view_detail_header=Ver os detalhes da notificação +notices.operations=Operações notices.select_all=Marcar todas notices.deselect_all=Desmarcar todas notices.inverse_selection=Inverter as marcações @@ -3094,6 +3154,8 @@ keywords=Palavras-chave details=Detalhes details.author=Autor(a) details.project_site=Página web do projecto +details.repository_site=Página web do repositório +details.documentation_site=Página web da documentação details.license=Licença assets=Recursos versions=Versões @@ -3101,7 +3163,14 @@ versions.on=ligado versions.view_all=Ver todas dependency.id=ID dependency.version=Versão +cargo.registry=Configurar este registo no ficheiro de configuração do Cargo (por exemplo: ~/.cargo/config.toml): +cargo.install=Para instalar o pacote usando o Cargo, execute o seguinte comando: +cargo.documentation=Para obter mais informações sobre o registo do Cargo, consulte a documentação. +cargo.details.repository_site=Página web do repositório +cargo.details.documentation_site=Página web da documentação +chef.registry=Configure este registo no seu ficheiro ~/.chef/config.rb: chef.install=Para instalar o pacote, execute o seguinte comando: +chef.documentation=Para obter mais informações sobre o registo do Chef, consulte a documentação. composer.registry=Configure este registo no seu ficheiro ~/.composer/config.json: composer.install=Para instalar o pacote usando o Composer, execute o seguinte comando: composer.documentation=Para obter mais informações sobre o registo do Composer, consulte a documentação. @@ -3111,6 +3180,11 @@ conan.details.repository=Repositório conan.registry=Configurar este registo usando a linha de comandos: conan.install=Para instalar o pacote usando o Conan, execute o seguinte comando: conan.documentation=Para obter mais informações sobre o registo do Conan, consulte a documentação. +conda.registry=Configure este registo como um repositório Conda no seu ficheiro .condarc: +conda.install=Para instalar o pacote usando o Conda, execute o seguinte comando: +conda.documentation=Para obter mais informações sobre o registo do Conda, consulte a documentação. +conda.details.repository_site=Página web do repositório +conda.details.documentation_site=Página web da documentação container.details.type=Tipo de imagem container.details.platform=Plataforma container.pull=Puxar a imagem usando a linha de comandos: @@ -3169,26 +3243,110 @@ settings.delete.description=Eliminar o pacote é permanente e não pode ser desf settings.delete.notice=Está prestes a eliminar %s (%s). Esta operação é irreversível. Tem a certeza? settings.delete.success=O pacote foi eliminado. settings.delete.error=Falhou a eliminação do pacote. +owner.settings.cargo.title=Índice do registo do Cargo +owner.settings.cargo.initialize=Inicializar índice +owner.settings.cargo.initialize.description=Para usar o registo Cargo, é necessário um repositório git de índice especial. Aqui pode (re)criá-lo com a configuração necessária. +owner.settings.cargo.initialize.error=Falhou ao inicializar o índice do Cargo: %v +owner.settings.cargo.initialize.success=O índice do Cargo foi criado com sucesso. +owner.settings.cargo.rebuild=Reconstruir índice +owner.settings.cargo.rebuild.description=Se o índice não estiver sincronizado com os pacotes Cargo armazenados, pode reconstruí-lo aqui. +owner.settings.cargo.rebuild.error=Falhou ao reconstruir o índice do Cargo: %v +owner.settings.cargo.rebuild.success=O índice do Cargo foi reconstruído com sucesso. +owner.settings.cleanuprules.title=Gerir regras de limpeza +owner.settings.cleanuprules.add=Adicionar regra de limpeza +owner.settings.cleanuprules.edit=Editar regra de limpeza +owner.settings.cleanuprules.none=Não há regras de limpeza disponíveis. Leia a documentação para obter mais informação. +owner.settings.cleanuprules.preview=Previsão da regra de limpeza +owner.settings.cleanuprules.preview.overview=%d pacotes estão agendados para serem removidos. +owner.settings.cleanuprules.preview.none=A regra de limpeza não corresponde a nenhum pacote. owner.settings.cleanuprules.enabled=Habilitado +owner.settings.cleanuprules.pattern_full_match=Aplicar o padrão ao nome completo do pacote +owner.settings.cleanuprules.keep.title=As versões que correspondem a estas regras serão mantidas, mesmo que correspondam à regra de remoção abaixo. +owner.settings.cleanuprules.keep.count=Manter a mais recente +owner.settings.cleanuprules.keep.count.1=1 versão por pacote +owner.settings.cleanuprules.keep.count.n=%d versões por pacote +owner.settings.cleanuprules.keep.pattern=Manter as versões correspondentes +owner.settings.cleanuprules.keep.pattern.container=A última versão será sempre mantida para pacotes de contentor. +owner.settings.cleanuprules.remove.title=Versões que correspondam a estas regras serão removidos, a não ser que a regra acima diga para os manter. +owner.settings.cleanuprules.remove.days=Remover versões mais antigas do que +owner.settings.cleanuprules.remove.pattern=Remover as versões correspondentes +owner.settings.cleanuprules.success.update=A regra de limpeza foi modificada. +owner.settings.cleanuprules.success.delete=A regra de limpeza foi eliminada. +owner.settings.chef.title=Registo do Chef +owner.settings.chef.keypair=Gerar par de chaves +owner.settings.chef.keypair.description=Gerar um par de chaves para se autenticar no registo do Chef. A chave anterior deixará de poder ser utilizada. [secrets] +secrets=Segredos +description=Os segredos serão transmitidos a certas operações e não poderão ser lidos de outra forma. +none=Ainda não há segredos. value=Valor name=Nome +creation=Adicionar segredo +creation.name_placeholder=apenas caracteres sem distinção de maiúsculas, alfanuméricos ou sublinhados, não podem começar com GITEA_ nem com GITHUB_ +creation.value_placeholder=Insira um conteúdo qualquer. Espaços em branco no início ou no fim serão omitidos. +creation.success=O segredo '%s' foi adicionado. +creation.failed=Falhou ao adicionar o segredo. +deletion=Remover segredo +deletion.description=Remover um segredo é permanente e não pode ser revertido. Continuar? +deletion.success=O segredo foi removido. +deletion.failed=Falhou ao remover o segredo. [actions] - - - +actions=Operações + +unit.desc=Gerir operações + +status.unknown=Desconhecido +status.waiting=Aguardando +status.running=Em execução +status.success=Sucesso +status.failure=Falha +status.cancelled=Cancelada +status.skipped=Ignorada +status.blocked=Bloqueada + +runners=Executores +runners.runner_manage_panel=Gestão de executores +runners.new=Criar um novo executor +runners.new_notice=Como iniciar um executor +runners.status=Estado runners.id=ID runners.name=Nome runners.owner_type=Tipo runners.description=Descrição runners.labels=Rótulos +runners.last_online=Última vez ligado +runners.agent_labels=Rótulos do agente +runners.custom_labels=Rótulos personalizados +runners.custom_labels_helper=Rótulos personalizados são rótulos que são adicionados manualmente por um administrador. São separados por vírgulas e espaços em branco antes e após cada rótulo são ignorados. +runners.runner_title=Executor +runners.task_list=Tarefas recentes deste executor runners.task_list.run=Executar +runners.task_list.status=Estado runners.task_list.repository=Repositório runners.task_list.commit=Cometimento +runners.task_list.done_at=Feito em +runners.edit_runner=Editar executor +runners.update_runner=Guardar alterações +runners.update_runner_success=O executor foi modificado com sucesso +runners.update_runner_failed=Falhou ao modificar o executor +runners.delete_runner=Eliminar o executor +runners.delete_runner_success=O executor foi eliminado com sucesso +runners.delete_runner_failed=Falhou ao eliminar o executor +runners.delete_runner_header=Confirme que quer eliminar este executor +runners.delete_runner_notice=Se uma tarefa estiver a correr sob este executor, será terminada e marcada como tendo falhado. Pode quebrar o fluxo de trabalho de construção. +runners.none=Não há executores disponíveis +runners.status.unspecified=Desconhecido +runners.status.idle=Parada runners.status.active=Em funcionamento +runners.status.offline=Desconectada +runs.all_workflows=Todos os fluxos de trabalho +runs.open_tab=%d abertas +runs.closed_tab=%d fechadas runs.commit=Cometimento +runs.pushed_by=Enviada por +need_approval_desc=É necessária aprovação para executar fluxos de trabalho para a derivação do pedido de integração. diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index a2a3154ad7ba9..7f5525e65245e 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -57,6 +57,7 @@ new_mirror=创建新的镜像 new_fork=新的仓库Fork new_org=创建组织 new_project=创建项目 +new_project_column=创建列 manage_org=管理我的组织 admin_panel=管理后台 account_settings=帐户设置 @@ -90,9 +91,11 @@ disabled=禁用 copy=复制 copy_url=复制网址 +copy_content=复制内容 copy_branch=复制分支名 copy_success=复制成功! copy_error=复制失败 +copy_type_unsupported=无法复制此类型的文件内容 write=撰写 preview=预览 @@ -109,6 +112,10 @@ never=从不 rss_feed=RSS 订阅源 [aria] +navbar=导航栏 +footer=页脚 +footer.software=关于软件 +footer.links=链接 [filter] string.asc=A - Z @@ -258,24 +265,24 @@ view_home=访问 %s search_repos=查找仓库… filter=其他过滤器 filter_by_team_repositories=按团队仓库筛选 -feed_of="%s\" 的源 +feed_of=`"%s"的源` -show_archived=已存档 -show_both_archived_unarchived=显示已存档和未存档的 +show_archived=已归档 +show_both_archived_unarchived=显示已归档和未归档的 show_only_archived=只显示已归档的 -show_only_unarchived=只显示未存档的 +show_only_unarchived=只显示未归档的 -show_private=私有 -show_both_private_public=显示公开的和私有的 +show_private=私有库 +show_both_private_public=同时显示公开的和私有的 show_only_private=只显示私有的 show_only_public=只显示公开的 -issues.in_your_repos=属于该用户仓库的 +issues.in_your_repos=在您的仓库中 [explore] -repos=仓库 +repos=仓库管理 users=用户 -organizations=组织 +organizations=组织管理 search=搜索 code=代码 search.type.tooltip=搜索类型 @@ -283,24 +290,15 @@ search.fuzzy=模糊 search.fuzzy.tooltip=包含近似匹配搜索词的结果 search.match=匹配 search.match.tooltip=仅包含精确匹配搜索词的结果 -code_search_unavailable=当前代码搜索不可用。请与网站管理员联系。 +code_search_unavailable=目前代码搜索不可用。请与网站管理员联系。 repo_no_results=未找到匹配的仓库。 user_no_results=未找到匹配的用户。 org_no_results=未找到匹配的组织。 code_no_results=未找到与搜索字词匹配的源代码。 -code_search_results=“%s” 的搜索结果是 +code_search_results=“%s” 的搜索结果 code_last_indexed_at=最后索引于 %s relevant_repositories_tooltip=派生的仓库,以及缺少主题、图标和描述的仓库将被隐藏。 -relevant_repositories=只显示相关的仓库, 显示未过滤结果。 [auth] @@ -330,6 +328,7 @@ email_not_associate=您输入的邮箱地址未被关联到任何帐号! send_reset_mail=发送账户恢复邮件 reset_password=账户恢复 invalid_code=此确认密钥无效或已过期。 +invalid_password=您的密码与用于创建账户的密码不匹配。 reset_password_helper=恢复账户 reset_password_wrong_user=您已作为 %s 登录,无法使用链接恢复 %s 的账户。 password_too_short=密码长度不能少于 %d 位。 @@ -373,6 +372,7 @@ password_pwned_err=无法完成对 HaveIBeenPwned 的请求 [mail] view_it_on=在 %s 上查看 +reply=或直接回复此邮件 link_not_working_do_paste=不起作用?尝试复制并粘贴到您的浏览器。 hi_user_x=%s 您好, @@ -476,6 +476,8 @@ url_error=`'%s' 不是一个有效的 URL。` include_error=`必须包含子字符串 '%s'。` glob_pattern_error=`匹配模式无效:%s.` regex_pattern_error=`正则表达式无效:%s.` +username_error=` 只能包含字母数字字符('0-9','a-z','A-Z'), 破折号 ('-'), 下划线 ('_') 和点 ('.'). 不能以非字母数字字符开头或结尾,并且不允许连续的非字母数字字符。` +invalid_group_team_map_error=`映射无效: %s` unknown_error=未知错误: captcha_incorrect=验证码不正确。 password_not_match=密码不匹配。 @@ -512,10 +514,12 @@ 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 @@ -752,6 +756,8 @@ access_token_deletion_cancel_action=取消 access_token_deletion_confirm_action=刪除 access_token_deletion_desc=删除令牌将撤销程序对您账户的访问权限。此操作无法撤消。是否继续? delete_token_success=令牌已经被删除。使用该令牌的应用将不再能够访问你的账号。 +select_scopes=选择范围 +scopes_list=范围: manage_oauth2_applications=管理 OAuth2 应用程序 edit_oauth2_application=编辑 OAuth2 应用程序 @@ -926,9 +932,9 @@ delete_preexisting_success=删除 %s 中未收录的文件 blame_prior=查看此更改前的 blame transfer.accept=接受转移 -transfer.accept_desc=转移到 "%s" +transfer.accept_desc=`转移到 "%s"` transfer.reject=拒绝转移 -transfer.reject_desc=取消转移到 "%s" +transfer.reject_desc=`取消转移到 "%s"` transfer.no_permission_to_accept=您没有接受的权限 transfer.no_permission_to_reject=您没有拒绝的权限 @@ -1024,10 +1030,12 @@ unstar=取消点赞 star=点赞 fork=派生 download_archive=下载此仓库 +more_operations=更多操作 no_desc=暂无描述 quick_guide=快速帮助 clone_this_repo=克隆当前仓库 +cite_this_repo=引用此仓库 create_new_repo_command=从命令行创建一个新的仓库 push_exist_repo=从命令行推送已经创建的仓库 empty_message=这个家伙很懒,什么都没有推送。 @@ -1126,6 +1134,7 @@ editor.commit_directly_to_this_branch=直接提交至 %[3]s 于 %[1]s创建 pulls.merged_by=由 %[3]s 创建,被合并于 %[1]s pulls.merged_by_fake=由 %[2]s 创建,被合并于 %[1]s @@ -1375,6 +1400,9 @@ issues.save=保存 issues.label_title=标签名称 issues.label_description=标签描述 issues.label_color=标签颜色 +issues.label_exclusive=独有 +issues.label_exclusive_desc=命名标签为 scope/item 以使其与其他以 scope/ 开头的标签互斥。 +issues.label_exclusive_warning=在编辑工单或合并请求的标签时,任何冲突的范围标签都将被删除。 issues.label_count=%d 个标签 issues.label_open_issues=%d 个开启的工单 issues.label_edit=编辑 @@ -1442,6 +1470,7 @@ issues.error_removing_due_date=删除到期时间失败。 issues.push_commit_1=于 %[2]s 推送了 %[1]d 个提交 issues.push_commits_n=于 %[2]s 推送了 %[1]d 个提交 issues.force_push_codes=`于 %[6]s 强制推送 %[1]s,从 %[2]s,至 %[4]s` +issues.force_push_compare=比较 issues.due_date_form=yyyy年mm月dd日 issues.due_date_form_add=设置到期时间 issues.due_date_form_edit=编辑 @@ -1628,6 +1657,8 @@ pulls.reopened_at=`重新打开此合并请求 %[2]s pulls.merge_instruction_hint=`你也可以查看 命令行指令` pulls.merge_instruction_step1_desc=从你的仓库中签出一个新的分支并测试变更。 pulls.merge_instruction_step2_desc=合并变更并更新到 Gitea 上 +pulls.clear_merge_message=清除合并信息 +pulls.clear_merge_message_hint=Clearing the merge message will only remove the commit message content and keep generated git trailers such as "Co-Authored-By …". pulls.auto_merge_button_when_succeed=(当检查成功时) pulls.auto_merge_when_succeed=在所有检查成功后自动合并 @@ -1819,6 +1850,7 @@ settings.mirror_sync_in_progress=镜像同步正在进行中,请稍后再试 settings.site=网站 settings.update_settings=更新仓库设置 settings.branches.update_default_branch=更新默认分支 +settings.branches.add_new_rule=添加新规则 settings.advanced_settings=高级设置 settings.wiki_desc=启用仓库百科 settings.use_internal_wiki=使用内置百科 @@ -1848,8 +1880,11 @@ settings.pulls.ignore_whitespace=忽略空白冲突 settings.pulls.enable_autodetect_manual_merge=启用自动检测手动合并 (注意:在某些特殊情况下可能发生错误判断) settings.pulls.allow_rebase_update=允许通过变基更新拉取请求分支 settings.pulls.default_delete_branch_after_merge=默认合并后删除合并请求分支 +settings.pulls.default_allow_edits_from_maintainers=默认开启允许维护者编辑 +settings.releases_desc=启用发布 settings.packages_desc=启用仓库软件包注册中心 settings.projects_desc=启用仓库项目 +settings.actions_desc=启用 Actions settings.admin_settings=管理员设置 settings.admin_enable_health_check=启用仓库健康检查 (git fsck) settings.admin_code_indexer=代码索引器 @@ -2059,6 +2094,8 @@ 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=允许推吗? settings.protected_branch_can_push_yes=你可以推 settings.protected_branch_can_push_no=你不能推 @@ -2093,6 +2130,7 @@ settings.dismiss_stale_approvals=取消过时的批准 settings.dismiss_stale_approvals_desc=当新的提交更改合并请求内容被推送到分支时,旧的批准将被撤销。 settings.require_signed_commits=需要签名提交 settings.require_signed_commits_desc=拒绝推送未签名或无法验证的提交到分支 +settings.protect_branch_name_pattern=受保护的分支名称模式 settings.protect_protected_file_patterns=受保护的文件模式(使用分号分隔) settings.protect_protected_file_patterns_desc=即使用户有权在此分支中添加、编辑或删除文件,也不允许直接更改受保护文件。 可以使用分号分隔多个模式 ('\;')。语法文档见 github.com/gobwas/glob。示例:.drone.yml/docs/**/*.txt。 settings.protect_unprotected_file_patterns=不受保护的文件模式 (使用分号 '\;' 分隔): @@ -2101,6 +2139,7 @@ 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=禁用分支保护 settings.protected_branch_deletion_desc=禁用分支保护允许具有写入权限的用户推送提交到此分支。继续? settings.block_rejected_reviews=拒绝审核阻止了此合并 @@ -2110,10 +2149,13 @@ settings.block_on_official_review_requests_desc=处于评审状态时,即使 settings.block_outdated_branch=如果拉取请求已经过时,阻止合并 settings.block_outdated_branch_desc=当头部分支落后基础分支时,不能合并。 settings.default_branch_desc=请选择一个默认的分支用于合并请求和提交: +settings.merge_style_desc=合并方式 settings.default_merge_style_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=Git标签保护 @@ -2269,6 +2311,8 @@ release.downloads=下载附件 release.download_count=下载:%s release.add_tag_msg=使用发布的标题和内容作为标签消息。 release.add_tag=仅创建标签 +release.releases_for=%s 的版本发布 +release.tags_for=%s 的标签 branch.name=分支名称 branch.search=搜索分支 @@ -2536,6 +2580,10 @@ dashboard.delete_old_actions=从数据库中删除所有旧操作记录 dashboard.delete_old_actions.started=已开始从数据库中删除所有旧操作记录。 dashboard.update_checker=更新检查器 dashboard.delete_old_system_notices=从数据库中删除所有旧系统通知 +dashboard.gc_lfs=垃圾回收 LFS 元数据 +dashboard.stop_zombie_tasks=停止僵尸任务 +dashboard.stop_endless_tasks=停止永不停止的任务 +dashboard.cancel_abandoned_jobs=取消丢弃的任务 users.user_manage_panel=用户帐户管理 users.new_account=创建新帐户 @@ -2624,6 +2672,7 @@ repos.size=大小 packages.package_manage_panel=软件包管理 packages.total_size=总大小:%s +packages.unreferenced_size=未引用大小: %s packages.owner=所有者 packages.creator=创建者 packages.name=名称 @@ -2717,6 +2766,8 @@ auths.oauth2_required_claim_value_helper=设置此值,只有拥有对应的声 auths.oauth2_group_claim_name=用于提供用户组名称的 Claim 声明名称。(可选) auths.oauth2_admin_group=管理员用户组的 Claim 声明值。(可选 - 需要上面的声明名称) auths.oauth2_restricted_group=受限用户组的 Claim 声明值。(可选 - 需要上面的声明名称) +auths.oauth2_map_group_to_team=Map claimed groups to Organization teams. (Optional - requires claim name above) +auths.oauth2_map_group_to_team_removal=如果用户不属于相应的组,从已同步团队中移除用户 auths.enable_auto_register=允许用户自动注册 auths.sspi_auto_create_users=自动创建用户 auths.sspi_auto_create_users_helper=允许 SSPI 认证在用户第一次登录时自动创建新账号 @@ -2732,10 +2783,10 @@ auths.tips=帮助提示 auths.tips.oauth2.general=OAuth2 认证 auths.tips.oauth2.general.tip=当注册一个新的 OAuth2 认证,回调/重定向 URL 应该是: /user/oauth2//callback auths.tip.oauth2_provider=OAuth2 提供程序 -auths.tip.bitbucket=在 https://bitbucket.org/account/user//oauth-consumers/new 注册新的 OAuth 消费者同时添加权限"帐户"-"读" +auths.tip.bitbucket=`在 https://bitbucket.org/account/user//oauth-consumers/new 注册新的 OAuth 消费者同时添加权限"帐户"-"读"` auths.tip.nextcloud=使用下面的菜单“设置(Settings) -> 安全(Security) -> OAuth 2.0 client”在您的实例上注册一个新的 OAuth 客户端。 auths.tip.dropbox=在 https://www.dropbox.com/developers/apps 上创建一个新的应用程序 -auths.tip.facebook=在 https://developers.facebook.com/apps 注册一个新的应用,并添加产品"Facebook 登录" +auths.tip.facebook=`在 https://developers.facebook.com/apps 注册一个新的应用,并添加产品"Facebook 登录"` auths.tip.github=在 https://github.com/settings/applications/new 注册一个 OAuth 应用程序 auths.tip.gitlab=在 https://gitlab.com/profile/applications 上注册新应用程序 auths.tip.google_plus=从谷歌 API 控制台 (https://console.developers.google.com/) 获得 OAuth2 客户端凭据 @@ -2979,6 +3030,7 @@ monitor.queue.pool.cancel_desc=没有工作者组的队列将会引起请求永 notices.system_notice_list=系统提示管理 notices.view_detail_header=查看提示详情 +notices.operations=操作 notices.select_all=选中全部 notices.deselect_all=取消全选 notices.inverse_selection=反向选中 @@ -3112,9 +3164,14 @@ versions.on=于 versions.view_all=查看全部 dependency.id=ID dependency.version=版本 +cargo.registry=在 Cargo 配置文件中设置此注册中心(例如:~/.cargo/config.toml): +cargo.install=要使用 Cargo 安装软件包,请运行以下命令: +cargo.documentation=关于 Cargo 注册中心的更多信息,请参阅 文档。 cargo.details.repository_site=仓库站点 cargo.details.documentation_site=文档站点 +chef.registry=在您的 ~/.chef/config.rb 文件中设置此注册中心: chef.install=要安装包,请运行以下命令: +chef.documentation=关于 Chef 注册中心的更多信息,请参阅 文档。 composer.registry=在您的 ~/.composer/config.json 文件中设置此注册中心: composer.install=要使用 Composer 安装软件包,请运行以下命令: composer.documentation=关于 Composer 注册中心的更多信息,请参阅 文档 。 @@ -3124,6 +3181,9 @@ conan.details.repository=仓库 conan.registry=从命令行设置此注册中心: conan.install=要使用 Conan 安装软件包,请运行以下命令: conan.documentation=关于 Conan 注册中心的更多信息,请参阅 文档。 +conda.registry=在您的 .condarc 文件中将此注册中心设置为 Conda 仓库: +conda.install=要使用 Conda 安装软件包,请运行以下命令: +conda.documentation=关于 Conda 注册中心的更多信息,请参阅 文档。 conda.details.repository_site=仓库站点 conda.details.documentation_site=文档站点 container.details.type=镜像类型 @@ -3184,26 +3244,110 @@ 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=To use the Cargo registry a special index git repository is needed. Here you can (re)create it with the required config. +owner.settings.cargo.initialize.error=初始化Cargo索引失败: %v +owner.settings.cargo.initialize.success=Cargo索引已经成功创建。 +owner.settings.cargo.rebuild=重建索引 +owner.settings.cargo.rebuild.description=If the index is out of sync with the cargo packages stored you can rebuild it here. +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=Apply pattern to full package name +owner.settings.cleanuprules.keep.title=Versions that match these rules are kept, even if they match a removal rule below. +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.keep.pattern.container=The latest version is always kept for Container packages. +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] +secrets=密钥 +description=Secrets will be passed to certain actions and cannot be read otherwise. +none=还没有密钥。 value=值 name=名称 +creation=添加密钥 +creation.name_placeholder=不区分大小写,字母数字或下划线不能以GITEA_ 或 GITHUB_ 开头。 +creation.value_placeholder=输入任何内容,开头和结尾的空白都会被省略 +creation.success=您的密钥 '%s' 添加成功。 +creation.failed=添加密钥失败。 +deletion=删除密钥 +deletion.description=删除密钥是永久性的,无法撤消。继续吗? +deletion.success=此Secret已被删除。 +deletion.failed=删除密钥失败。 [actions] - - - +actions=Actions + +unit.desc=管理Actions + +status.unknown=未知 +status.waiting=等待中 +status.running=正在运行 +status.success=成功 +status.failure=失败 +status.cancelled=已取消 +status.skipped=已忽略 +status.blocked=阻塞中 + +runners=Runners +runners.runner_manage_panel=Runners管理 +runners.new=创建 Runner +runners.new_notice=如何启动一个运行器 +runners.status=状态 runners.id=ID runners.name=名称 runners.owner_type=类型 runners.description=组织描述 runners.labels=标签 +runners.last_online=上次在线时间 +runners.agent_labels=代理标签 +runners.custom_labels=自定义标签 +runners.custom_labels_helper=自定义标签是由管理员手动添加的标签。标签之间用逗号分隔,每个标签的开头和结尾的空白被忽略。 +runners.runner_title=Runner +runners.task_list=最近在此runner上的任务 runners.task_list.run=执行 +runners.task_list.status=状态 runners.task_list.repository=仓库 runners.task_list.commit=提交 +runners.task_list.done_at=完成于 +runners.edit_runner=编辑运行器 +runners.update_runner=更新更改 +runners.update_runner_success=运行器更新成功 +runners.update_runner_failed=更新运行器失败 +runners.delete_runner=删除运行器 +runners.delete_runner_success=运行器删除成功 +runners.delete_runner_failed=删除运行器失败 +runners.delete_runner_header=确认要删除此运行器 +runners.delete_runner_notice=如果一个任务正在运行在此运行器上,它将被终止并标记为失败。它可能会打断正在构建的工作流。 +runners.none=无可用的 Runner +runners.status.unspecified=未知 +runners.status.idle=空闲 runners.status.active=激活 +runners.status.offline=离线 +runs.all_workflows=所有工作流 +runs.open_tab=%d 开启中 +runs.closed_tab=%d 已关闭 runs.commit=提交 +runs.pushed_by=推送者 +need_approval_desc=该工作流由派生仓库的合并请求所触发,需要批准方可运行。 diff --git a/routers/api/v1/admin/adopt.go b/routers/api/v1/admin/adopt.go index 0e4e498e98243..47fd0ef3c30cf 100644 --- a/routers/api/v1/admin/adopt.go +++ b/routers/api/v1/admin/adopt.go @@ -45,7 +45,7 @@ func ListUnadoptedRepositories(ctx *context.APIContext) { if listOptions.Page == 0 { listOptions.Page = 1 } - repoNames, count, err := repo_service.ListUnadoptedRepositories(ctx.FormString("query"), &listOptions) + repoNames, count, err := repo_service.ListUnadoptedRepositories(ctx, ctx.FormString("query"), &listOptions) if err != nil { ctx.InternalServerError(err) return @@ -109,7 +109,7 @@ func AdoptRepository(ctx *context.APIContext) { ctx.NotFound() return } - if _, err := repo_service.AdoptRepository(ctx.Doer, ctxUser, repo_module.CreateRepoOptions{ + if _, err := repo_service.AdoptRepository(ctx, ctx.Doer, ctxUser, repo_module.CreateRepoOptions{ Name: repoName, IsPrivate: true, }); err != nil { @@ -172,7 +172,7 @@ func DeleteUnadoptedRepository(ctx *context.APIContext) { return } - if err := repo_service.DeleteUnadoptedRepository(ctx.Doer, ctxUser, repoName); err != nil { + if err := repo_service.DeleteUnadoptedRepository(ctx, ctx.Doer, ctxUser, repoName); err != nil { ctx.InternalServerError(err) return } diff --git a/routers/api/v1/org/label.go b/routers/api/v1/org/label.go index 938fe79df64cc..183c1e6cc8a8c 100644 --- a/routers/api/v1/org/label.go +++ b/routers/api/v1/org/label.go @@ -4,13 +4,13 @@ package org import ( - "fmt" "net/http" "strconv" "strings" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/label" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/utils" @@ -84,13 +84,12 @@ func CreateLabel(ctx *context.APIContext) { // "$ref": "#/responses/validationError" form := web.GetForm(ctx).(*api.CreateLabelOption) form.Color = strings.Trim(form.Color, " ") - if len(form.Color) == 6 { - form.Color = "#" + form.Color - } - if !issues_model.LabelColorPattern.MatchString(form.Color) { - ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", form.Color)) + color, err := label.NormalizeColor(form.Color) + if err != nil { + ctx.Error(http.StatusUnprocessableEntity, "Color", err) return } + form.Color = color label := &issues_model.Label{ Name: form.Name, @@ -183,7 +182,7 @@ func EditLabel(ctx *context.APIContext) { // "422": // "$ref": "#/responses/validationError" form := web.GetForm(ctx).(*api.EditLabelOption) - label, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.ParamsInt64(":id")) + l, err := issues_model.GetLabelInOrgByID(ctx, ctx.Org.Organization.ID, ctx.ParamsInt64(":id")) if err != nil { if issues_model.IsErrOrgLabelNotExist(err) { ctx.NotFound() @@ -194,30 +193,28 @@ func EditLabel(ctx *context.APIContext) { } if form.Name != nil { - label.Name = *form.Name + l.Name = *form.Name } if form.Exclusive != nil { - label.Exclusive = *form.Exclusive + l.Exclusive = *form.Exclusive } if form.Color != nil { - label.Color = strings.Trim(*form.Color, " ") - if len(label.Color) == 6 { - label.Color = "#" + label.Color - } - if !issues_model.LabelColorPattern.MatchString(label.Color) { - ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", label.Color)) + color, err := label.NormalizeColor(*form.Color) + if err != nil { + ctx.Error(http.StatusUnprocessableEntity, "Color", err) return } + l.Color = color } if form.Description != nil { - label.Description = *form.Description + l.Description = *form.Description } - if err := issues_model.UpdateLabel(label); err != nil { + if err := issues_model.UpdateLabel(l); err != nil { ctx.Error(http.StatusInternalServerError, "UpdateLabel", err) return } - ctx.JSON(http.StatusOK, convert.ToLabel(label, nil, ctx.Org.Organization.AsUser())) + ctx.JSON(http.StatusOK, convert.ToLabel(l, nil, ctx.Org.Organization.AsUser())) } // DeleteLabel delete a label for an organization diff --git a/routers/api/v1/repo/branch.go b/routers/api/v1/repo/branch.go index 8acaeaffb4173..dff47fbcf15b7 100644 --- a/routers/api/v1/repo/branch.go +++ b/routers/api/v1/repo/branch.go @@ -118,7 +118,7 @@ func DeleteBranch(ctx *context.APIContext) { branchName := ctx.Params("*") - if err := repo_service.DeleteBranch(ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { + if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { switch { case git.IsErrBranchNotExist(err): ctx.NotFound(err) diff --git a/routers/api/v1/repo/label.go b/routers/api/v1/repo/label.go index a06d26e837335..6cb231f596c8b 100644 --- a/routers/api/v1/repo/label.go +++ b/routers/api/v1/repo/label.go @@ -5,13 +5,12 @@ package repo import ( - "fmt" "net/http" "strconv" - "strings" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/label" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/api/v1/utils" @@ -93,14 +92,14 @@ func GetLabel(ctx *context.APIContext) { // "$ref": "#/responses/Label" var ( - label *issues_model.Label - err error + l *issues_model.Label + err error ) strID := ctx.Params(":id") if intID, err2 := strconv.ParseInt(strID, 10, 64); err2 != nil { - label, err = issues_model.GetLabelInRepoByName(ctx, ctx.Repo.Repository.ID, strID) + l, err = issues_model.GetLabelInRepoByName(ctx, ctx.Repo.Repository.ID, strID) } else { - label, err = issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, intID) + l, err = issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, intID) } if err != nil { if issues_model.IsErrRepoLabelNotExist(err) { @@ -111,7 +110,7 @@ func GetLabel(ctx *context.APIContext) { return } - ctx.JSON(http.StatusOK, convert.ToLabel(label, ctx.Repo.Repository, nil)) + ctx.JSON(http.StatusOK, convert.ToLabel(l, ctx.Repo.Repository, nil)) } // CreateLabel create a label for a repository @@ -145,28 +144,27 @@ func CreateLabel(ctx *context.APIContext) { // "$ref": "#/responses/validationError" form := web.GetForm(ctx).(*api.CreateLabelOption) - form.Color = strings.Trim(form.Color, " ") - if len(form.Color) == 6 { - form.Color = "#" + form.Color - } - if !issues_model.LabelColorPattern.MatchString(form.Color) { - ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", form.Color)) + + color, err := label.NormalizeColor(form.Color) + if err != nil { + ctx.Error(http.StatusUnprocessableEntity, "StringToColor", err) return } + form.Color = color - label := &issues_model.Label{ + l := &issues_model.Label{ Name: form.Name, Exclusive: form.Exclusive, Color: form.Color, RepoID: ctx.Repo.Repository.ID, Description: form.Description, } - if err := issues_model.NewLabel(ctx, label); err != nil { + if err := issues_model.NewLabel(ctx, l); err != nil { ctx.Error(http.StatusInternalServerError, "NewLabel", err) return } - ctx.JSON(http.StatusCreated, convert.ToLabel(label, ctx.Repo.Repository, nil)) + ctx.JSON(http.StatusCreated, convert.ToLabel(l, ctx.Repo.Repository, nil)) } // EditLabel modify a label for a repository @@ -206,7 +204,7 @@ func EditLabel(ctx *context.APIContext) { // "$ref": "#/responses/validationError" form := web.GetForm(ctx).(*api.EditLabelOption) - label, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) + l, err := issues_model.GetLabelInRepoByID(ctx, ctx.Repo.Repository.ID, ctx.ParamsInt64(":id")) if err != nil { if issues_model.IsErrRepoLabelNotExist(err) { ctx.NotFound() @@ -217,30 +215,28 @@ func EditLabel(ctx *context.APIContext) { } if form.Name != nil { - label.Name = *form.Name + l.Name = *form.Name } if form.Exclusive != nil { - label.Exclusive = *form.Exclusive + l.Exclusive = *form.Exclusive } if form.Color != nil { - label.Color = strings.Trim(*form.Color, " ") - if len(label.Color) == 6 { - label.Color = "#" + label.Color - } - if !issues_model.LabelColorPattern.MatchString(label.Color) { - ctx.Error(http.StatusUnprocessableEntity, "ColorPattern", fmt.Errorf("bad color code: %s", label.Color)) + color, err := label.NormalizeColor(*form.Color) + if err != nil { + ctx.Error(http.StatusUnprocessableEntity, "StringToColor", err) return } + l.Color = color } if form.Description != nil { - label.Description = *form.Description + l.Description = *form.Description } - if err := issues_model.UpdateLabel(label); err != nil { + if err := issues_model.UpdateLabel(l); err != nil { ctx.Error(http.StatusInternalServerError, "UpdateLabel", err) return } - ctx.JSON(http.StatusOK, convert.ToLabel(label, ctx.Repo.Repository, nil)) + ctx.JSON(http.StatusOK, convert.ToLabel(l, ctx.Repo.Repository, nil)) } // DeleteLabel delete a label for a repository diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 84eebeb94dd7d..9b5ec0b3f8ed6 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -904,7 +904,7 @@ func MergePullRequest(ctx *context.APIContext) { } defer headRepo.Close() } - if err := repo_service.DeleteBranch(ctx.Doer, pr.HeadRepo, headRepo, pr.HeadBranch); err != nil { + if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, headRepo, pr.HeadBranch); err != nil { switch { case git.IsErrBranchNotExist(err): ctx.NotFound(err) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 0395198e209a3..397600dc50a97 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -19,6 +19,7 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/label" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -230,7 +231,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre if opt.AutoInit && opt.Readme == "" { opt.Readme = "Default" } - repo, err := repo_service.CreateRepository(ctx.Doer, owner, repo_module.CreateRepoOptions{ + repo, err := repo_service.CreateRepository(ctx, ctx.Doer, owner, repo_module.CreateRepoOptions{ Name: opt.Name, Description: opt.Description, IssueLabels: opt.IssueLabels, @@ -248,7 +249,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *user_model.User, opt api.Cre ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.") } else if db.IsErrNameReserved(err) || db.IsErrNamePatternNotAllowed(err) || - repo_module.IsErrIssueLabelTemplateLoad(err) { + label.IsErrTemplateLoad(err) { ctx.Error(http.StatusUnprocessableEntity, "", err) } else { ctx.Error(http.StatusInternalServerError, "CreateRepository", err) @@ -393,7 +394,7 @@ func Generate(ctx *context.APIContext) { } } - repo, err := repo_service.GenerateRepository(ctx.Doer, ctxUser, ctx.Repo.Repository, opts) + repo, err := repo_service.GenerateRepository(ctx, ctx.Doer, ctxUser, ctx.Repo.Repository, opts) if err != nil { if repo_model.IsErrRepoAlreadyExist(err) { ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.") @@ -637,7 +638,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err } // Check if repository name has been changed and not just a case change if repo.LowerName != strings.ToLower(newRepoName) { - if err := repo_service.ChangeRepositoryName(ctx.Doer, repo, newRepoName); err != nil { + if err := repo_service.ChangeRepositoryName(ctx, ctx.Doer, repo, newRepoName); err != nil { switch { case repo_model.IsErrRepoAlreadyExist(err): ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("repo name is already taken [name: %s]", newRepoName), err) @@ -714,7 +715,7 @@ func updateBasicProperties(ctx *context.APIContext, opts api.EditRepoOption) err repo.DefaultBranch = *opts.DefaultBranch } - if err := repo_service.UpdateRepository(repo, visibilityChanged); err != nil { + if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil { ctx.Error(http.StatusInternalServerError, "UpdateRepository", err) return err } diff --git a/routers/private/serv.go b/routers/private/serv.go index 17f966e3e40a7..23ac011cf5a69 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -368,7 +368,7 @@ func ServCommand(ctx *context.PrivateContext) { return } - repo, err = repo_service.PushCreateRepo(user, owner, results.RepoName) + repo, err = repo_service.PushCreateRepo(ctx, user, owner, results.RepoName) if err != nil { log.Error("pushCreateRepo: %v", err) ctx.JSON(http.StatusNotFound, private.ErrServCommand{ diff --git a/routers/web/admin/repos.go b/routers/web/admin/repos.go index 1c4754f6d8a3e..53b609af966ba 100644 --- a/routers/web/admin/repos.go +++ b/routers/web/admin/repos.go @@ -96,7 +96,7 @@ func UnadoptedRepos(ctx *context.Context) { } ctx.Data["Keyword"] = q - repoNames, count, err := repo_service.ListUnadoptedRepositories(q, &opts) + repoNames, count, err := repo_service.ListUnadoptedRepositories(ctx, q, &opts) if err != nil { ctx.ServerError("ListUnadoptedRepositories", err) } @@ -148,7 +148,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) { if has || !isDir { // Fallthrough to failure mode } else if action == "adopt" { - if _, err := repo_service.AdoptRepository(ctx.Doer, ctxUser, repo_module.CreateRepoOptions{ + if _, err := repo_service.AdoptRepository(ctx, ctx.Doer, ctxUser, repo_module.CreateRepoOptions{ Name: dirSplit[1], IsPrivate: true, }); err != nil { @@ -157,7 +157,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("repo.adopt_preexisting_success", dir)) } else if action == "delete" { - if err := repo_service.DeleteUnadoptedRepository(ctx.Doer, ctxUser, dirSplit[1]); err != nil { + if err := repo_service.DeleteUnadoptedRepository(ctx, ctx.Doer, ctxUser, dirSplit[1]); err != nil { ctx.ServerError("repository.AdoptRepository", err) return } diff --git a/routers/web/org/org_labels.go b/routers/web/org/org_labels.go index e96627762bd54..9ce05680d7bd2 100644 --- a/routers/web/org/org_labels.go +++ b/routers/web/org/org_labels.go @@ -9,6 +9,7 @@ import ( "code.gitea.io/gitea/models/db" issues_model "code.gitea.io/gitea/models/issues" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/label" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" @@ -103,8 +104,8 @@ func InitializeLabels(ctx *context.Context) { } if err := repo_module.InitializeLabels(ctx, ctx.Org.Organization.ID, form.TemplateName, true); err != nil { - if repo_module.IsErrIssueLabelTemplateLoad(err) { - originalErr := err.(repo_module.ErrIssueLabelTemplateLoad).OriginalError + if label.IsErrTemplateLoad(err) { + originalErr := err.(label.ErrTemplateLoad).OriginalError ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr)) ctx.Redirect(ctx.Org.OrgLink + "/settings/labels") return diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 5c9b7967c3dc2..f713d096639bc 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -137,7 +137,7 @@ func SettingsPost(ctx *context.Context) { } for _, repo := range repos { repo.OwnerName = org.Name - if err := repo_service.UpdateRepository(repo, true); err != nil { + if err := repo_service.UpdateRepository(ctx, repo, true); err != nil { ctx.ServerError("UpdateRepository", err) return } diff --git a/routers/web/repo/branch.go b/routers/web/repo/branch.go index b34ccf8538553..d23367e04790d 100644 --- a/routers/web/repo/branch.go +++ b/routers/web/repo/branch.go @@ -91,7 +91,7 @@ func DeleteBranchPost(ctx *context.Context) { defer redirect(ctx) branchName := ctx.FormString("name") - if err := repo_service.DeleteBranch(ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { + if err := repo_service.DeleteBranch(ctx, ctx.Doer, ctx.Repo.Repository, ctx.Repo.GitRepo, branchName); err != nil { switch { case git.IsErrBranchNotExist(err): log.Debug("DeleteBranch: Can't delete non existing branch '%s'", branchName) diff --git a/routers/web/repo/http.go b/routers/web/repo/http.go index 002843d103c9a..cd32d99533dc2 100644 --- a/routers/web/repo/http.go +++ b/routers/web/repo/http.go @@ -276,7 +276,7 @@ func httpBase(ctx *context.Context) (h *serviceHandler) { return } - repo, err = repo_service.PushCreateRepo(ctx.Doer, owner, reponame) + repo, err = repo_service.PushCreateRepo(ctx, ctx.Doer, owner, reponame) if err != nil { log.Error("pushCreateRepo: %v", err) ctx.Status(http.StatusNotFound) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index e4f94006155a2..3715320f10c84 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -589,7 +589,7 @@ func RetrieveRepoReviewers(ctx *context.Context, repo *repo_model.Repository, is return } - teamReviewers, err = repo_service.GetReviewerTeams(repo) + teamReviewers, err = repo_service.GetReviewerTeams(ctx, repo) if err != nil { ctx.ServerError("GetReviewerTeams", err) return @@ -1420,11 +1420,12 @@ func ViewIssue(ctx *context.Context) { } var ( - role issues_model.RoleDescriptor - ok bool - marked = make(map[int64]issues_model.RoleDescriptor) - comment *issues_model.Comment - participants = make([]*user_model.User, 1, 10) + role issues_model.RoleDescriptor + ok bool + marked = make(map[int64]issues_model.RoleDescriptor) + comment *issues_model.Comment + participants = make([]*user_model.User, 1, 10) + latestCloseCommentID int64 ) if ctx.Repo.Repository.IsTimetrackerEnabled(ctx) { if ctx.IsSigned { @@ -1622,9 +1623,15 @@ func ViewIssue(ctx *context.Context) { comment.Type == issues_model.CommentTypeStopTracking { // drop error since times could be pruned from DB.. _ = comment.LoadTime() + } else if comment.Type == issues_model.CommentTypeClose { + // record ID of latest closed comment. + // if PR is closed, the comments whose type is CommentTypePullRequestPush(29) after latestCloseCommentID won't be rendered. + latestCloseCommentID = comment.ID } } + ctx.Data["LatestCloseCommentID"] = latestCloseCommentID + // Combine multiple label assignments into a single comment combineLabelComments(issue) @@ -2952,7 +2959,7 @@ func ChangeIssueReaction(ctx *context.Context) { } html, err := ctx.RenderToString(tplReactions, map[string]interface{}{ - "ctx": ctx.Data, + "ctxData": ctx.Data, "ActionURL": fmt.Sprintf("%s/issues/%d/reactions", ctx.Repo.RepoLink, issue.Index), "Reactions": issue.Reactions.GroupByType(), }) @@ -3054,7 +3061,7 @@ func ChangeCommentReaction(ctx *context.Context) { } html, err := ctx.RenderToString(tplReactions, map[string]interface{}{ - "ctx": ctx.Data, + "ctxData": ctx.Data, "ActionURL": fmt.Sprintf("%s/comments/%d/reactions", ctx.Repo.RepoLink, comment.ID), "Reactions": comment.Reactions.GroupByType(), }) @@ -3176,7 +3183,7 @@ func updateAttachments(ctx *context.Context, item interface{}, files []string) e func attachmentsHTML(ctx *context.Context, attachments []*repo_model.Attachment, content string) string { attachHTML, err := ctx.RenderToString(tplAttachment, map[string]interface{}{ - "ctx": ctx.Data, + "ctxData": ctx.Data, "Attachments": attachments, "Content": content, }) diff --git a/routers/web/repo/issue_label.go b/routers/web/repo/issue_label.go index d4fece9f014b7..31bf85fedb29a 100644 --- a/routers/web/repo/issue_label.go +++ b/routers/web/repo/issue_label.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/label" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/web" @@ -41,8 +42,8 @@ func InitializeLabels(ctx *context.Context) { } if err := repo_module.InitializeLabels(ctx, ctx.Repo.Repository.ID, form.TemplateName, false); err != nil { - if repo_module.IsErrIssueLabelTemplateLoad(err) { - originalErr := err.(repo_module.ErrIssueLabelTemplateLoad).OriginalError + if label.IsErrTemplateLoad(err) { + originalErr := err.(label.ErrTemplateLoad).OriginalError ctx.Flash.Error(ctx.Tr("repo.issues.label_templates.fail_to_load_file", form.TemplateName, originalErr)) ctx.Redirect(ctx.Repo.RepoLink + "/labels") return diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 38b9f22cbf3dd..4f99687738247 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -587,7 +587,7 @@ func PrepareViewPullInfo(ctx *context.Context, issue *issues_model.Issue) *git.C ctx.Data["HeadBranchCommitID"] = headBranchSha ctx.Data["PullHeadCommitID"] = sha - if pull.HeadRepo == nil || !headBranchExist || headBranchSha != sha { + if pull.HeadRepo == nil || !headBranchExist || (!pull.Issue.IsClosed && (headBranchSha != sha)) { ctx.Data["IsPullRequestBroken"] = true if pull.IsSameRepo() { ctx.Data["HeadTarget"] = pull.HeadBranch @@ -1399,7 +1399,7 @@ func CleanUpPullRequest(ctx *context.Context) { func deleteBranch(ctx *context.Context, pr *issues_model.PullRequest, gitRepo *git.Repository) { fullBranchName := pr.HeadRepo.FullName() + ":" + pr.HeadBranch - if err := repo_service.DeleteBranch(ctx.Doer, pr.HeadRepo, gitRepo, pr.HeadBranch); err != nil { + if err := repo_service.DeleteBranch(ctx, ctx.Doer, pr.HeadRepo, gitRepo, pr.HeadBranch); err != nil { switch { case git.IsErrBranchNotExist(err): ctx.Flash.Error(ctx.Tr("repo.branch.deletion_failed", fullBranchName)) diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 9f2add1fe6de6..b4e7b5a46e2a7 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -248,14 +248,14 @@ func CreatePost(ctx *context.Context) { return } - repo, err = repo_service.GenerateRepository(ctx.Doer, ctxUser, templateRepo, opts) + repo, err = repo_service.GenerateRepository(ctx, ctx.Doer, ctxUser, templateRepo, opts) if err == nil { log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) ctx.Redirect(repo.Link()) return } } else { - repo, err = repo_service.CreateRepository(ctx.Doer, ctxUser, repo_module.CreateRepoOptions{ + repo, err = repo_service.CreateRepository(ctx, ctx.Doer, ctxUser, repo_module.CreateRepoOptions{ Name: form.RepoName, Description: form.Description, Gitignores: form.Gitignores, @@ -302,7 +302,7 @@ func Action(ctx *context.Context) { ctx.Repo.Repository.Description = ctx.FormString("desc") ctx.Repo.Repository.Website = ctx.FormString("site") - err = repo_service.UpdateRepository(ctx.Repo.Repository, false) + err = repo_service.UpdateRepository(ctx, ctx.Repo.Repository, false) } if err != nil { diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index 387a917412f1e..0c36503b3c4fc 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -134,7 +134,7 @@ func SettingsPost(ctx *context.Context) { ctx.Repo.GitRepo.Close() ctx.Repo.GitRepo = nil } - if err := repo_service.ChangeRepositoryName(ctx.Doer, repo, newRepoName); err != nil { + if err := repo_service.ChangeRepositoryName(ctx, ctx.Doer, repo, newRepoName); err != nil { ctx.Data["Err_RepoName"] = true switch { case repo_model.IsErrRepoAlreadyExist(err): @@ -183,7 +183,7 @@ func SettingsPost(ctx *context.Context) { } repo.IsPrivate = form.Private - if err := repo_service.UpdateRepository(repo, visibilityChanged); err != nil { + if err := repo_service.UpdateRepository(ctx, repo, visibilityChanged); err != nil { ctx.ServerError("UpdateRepository", err) return } @@ -541,7 +541,7 @@ func SettingsPost(ctx *context.Context) { return } if repoChanged { - if err := repo_service.UpdateRepository(repo, false); err != nil { + if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { ctx.ServerError("UpdateRepository", err) return } @@ -560,7 +560,7 @@ func SettingsPost(ctx *context.Context) { } if changed { - if err := repo_service.UpdateRepository(repo, false); err != nil { + if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { ctx.ServerError("UpdateRepository", err) return } @@ -580,7 +580,7 @@ func SettingsPost(ctx *context.Context) { repo.IsFsckEnabled = form.EnableHealthCheck } - if err := repo_service.UpdateRepository(repo, false); err != nil { + if err := repo_service.UpdateRepository(ctx, repo, false); err != nil { ctx.ServerError("UpdateRepository", err) return } @@ -672,7 +672,7 @@ func SettingsPost(ctx *context.Context) { return } - if err := repo_service.ConvertForkToNormalRepository(repo); err != nil { + if err := repo_service.ConvertForkToNormalRepository(ctx, repo); err != nil { log.Error("Unable to convert repository %-v from fork. Error: %v", repo, err) ctx.ServerError("Convert Fork", err) return @@ -1244,7 +1244,7 @@ func UpdateAvatarSetting(ctx *context.Context, form forms.AvatarForm) error { if !(st.IsImage() && !st.IsSvgImage()) { return errors.New(ctx.Tr("settings.uploaded_avatar_not_a_image")) } - if err = repo_service.UploadAvatar(ctxRepo, data); err != nil { + if err = repo_service.UploadAvatar(ctx, ctxRepo, data); err != nil { return fmt.Errorf("UploadAvatar: %w", err) } return nil @@ -1264,7 +1264,7 @@ func SettingsAvatar(ctx *context.Context) { // SettingsDeleteAvatar delete repository avatar func SettingsDeleteAvatar(ctx *context.Context) { - if err := repo_service.DeleteAvatar(ctx.Repo.Repository); err != nil { + if err := repo_service.DeleteAvatar(ctx, ctx.Repo.Repository); err != nil { ctx.Flash.Error(fmt.Sprintf("DeleteAvatar: %v", err)) } ctx.Redirect(ctx.Repo.RepoLink + "/settings") diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go index 0a8c39fef0735..34e84c4656827 100644 --- a/routers/web/repo/setting_protected_branch.go +++ b/routers/web/repo/setting_protected_branch.go @@ -356,7 +356,7 @@ func RenameBranchPost(ctx *context.Context) { return } - msg, err := repository.RenameBranch(ctx.Repo.Repository, ctx.Doer, ctx.Repo.GitRepo, form.From, form.To) + msg, err := repository.RenameBranch(ctx, ctx.Repo.Repository, ctx.Doer, ctx.Repo.GitRepo, form.From, form.To) if err != nil { ctx.ServerError("RenameBranch", err) return diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index e3c61fa408bdb..8663e11382085 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -186,7 +186,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { return } - renderReadmeFile(ctx, readmeFile, treeLink) + renderReadmeFile(ctx, readmeFile, fmt.Sprintf("%s/%s", treeLink, readmeFile.name)) } // localizedExtensions prepends the provided language code with and without a diff --git a/routers/web/user/setting/adopt.go b/routers/web/user/setting/adopt.go index 844d6fa166a1d..c9995e7278c55 100644 --- a/routers/web/user/setting/adopt.go +++ b/routers/web/user/setting/adopt.go @@ -45,7 +45,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) { if has || !isDir { // Fallthrough to failure mode } else if action == "adopt" && allowAdopt { - if _, err := repo_service.AdoptRepository(ctxUser, ctxUser, repo_module.CreateRepoOptions{ + if _, err := repo_service.AdoptRepository(ctx, ctxUser, ctxUser, repo_module.CreateRepoOptions{ Name: dir, IsPrivate: true, }); err != nil { @@ -54,7 +54,7 @@ func AdoptOrDeleteRepository(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("repo.adopt_preexisting_success", dir)) } else if action == "delete" && allowDelete { - if err := repo_service.DeleteUnadoptedRepository(ctxUser, ctxUser, dir); err != nil { + if err := repo_service.DeleteUnadoptedRepository(ctx, ctxUser, ctxUser, dir); err != nil { ctx.ServerError("repository.AdoptRepository", err) return } diff --git a/services/migrations/gitea_uploader.go b/services/migrations/gitea_uploader.go index 20370d99f9824..8b259a362b1eb 100644 --- a/services/migrations/gitea_uploader.go +++ b/services/migrations/gitea_uploader.go @@ -21,6 +21,7 @@ import ( repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/label" "code.gitea.io/gitea/modules/log" base "code.gitea.io/gitea/modules/migration" repo_module "code.gitea.io/gitea/modules/repository" @@ -217,18 +218,20 @@ func (g *GiteaLocalUploader) CreateMilestones(milestones ...*base.Milestone) err // CreateLabels creates labels func (g *GiteaLocalUploader) CreateLabels(labels ...*base.Label) error { lbs := make([]*issues_model.Label, 0, len(labels)) - for _, label := range labels { - // We must validate color here: - if !issues_model.LabelColorPattern.MatchString("#" + label.Color) { - log.Warn("Invalid label color: #%s for label: %s in migration to %s/%s", label.Color, label.Name, g.repoOwner, g.repoName) - label.Color = "ffffff" + for _, l := range labels { + if color, err := label.NormalizeColor(l.Color); err != nil { + log.Warn("Invalid label color: #%s for label: %s in migration to %s/%s", l.Color, l.Name, g.repoOwner, g.repoName) + l.Color = "#ffffff" + } else { + l.Color = color } lbs = append(lbs, &issues_model.Label{ RepoID: g.repo.ID, - Name: label.Name, - Description: label.Description, - Color: "#" + label.Color, + Name: l.Name, + Exclusive: l.Exclusive, + Description: l.Description, + Color: l.Color, }) } diff --git a/services/pull/pull.go b/services/pull/pull.go index 0d260c93b1ec3..a19e88b33b657 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -257,7 +257,7 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string, // If you don't let it run all the way then you will lose data // TODO: graceful: AddTestPullRequestTask needs to become a queue! - prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repoID, branch) + prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repoID, branch, true) if err != nil { log.Error("Find pull requests [head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err) return @@ -500,7 +500,7 @@ func (errs errlist) Error() string { // CloseBranchPulls close all the pull requests who's head branch is the branch func CloseBranchPulls(doer *user_model.User, repoID int64, branch string) error { - prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repoID, branch) + prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repoID, branch, false) if err != nil { return err } @@ -536,7 +536,7 @@ func CloseRepoBranchesPulls(ctx context.Context, doer *user_model.User, repo *re var errs errlist for _, branch := range branches { - prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repo.ID, branch.Name) + prs, err := issues_model.GetUnmergedPullRequestsByHeadInfo(repo.ID, branch.Name, false) if err != nil { return err } diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index e0d6b4a158f6b..2bef671555e00 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -67,6 +67,12 @@ func createTemporaryRepo(ctx context.Context, pr *issues_model.PullRequest) (str remoteRepoName := "head_repo" baseBranch := "base" + fetchArgs := git.TrustedCmdArgs{"--no-tags"} + if git.CheckGitVersionAtLeast("2.25.0") == nil { + // Writing the commit graph can be slow and is not needed here + fetchArgs = append(fetchArgs, "--no-write-commit-graph") + } + // Add head repo remote. addCacheRepo := func(staging, cache string) error { p := filepath.Join(staging, ".git", "objects", "info", "alternates") @@ -108,7 +114,7 @@ func createTemporaryRepo(ctx context.Context, pr *issues_model.PullRequest) (str outbuf.Reset() errbuf.Reset() - if err := git.NewCommand(ctx, "fetch", "origin", "--no-tags").AddDashesAndList(pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch). + if err := git.NewCommand(ctx, "fetch", "origin").AddArguments(fetchArgs...).AddDashesAndList(pr.BaseBranch+":"+baseBranch, pr.BaseBranch+":original_"+baseBranch). Run(&git.RunOpts{ Dir: tmpBasePath, Stdout: &outbuf, @@ -171,7 +177,7 @@ func createTemporaryRepo(ctx context.Context, pr *issues_model.PullRequest) (str } else { headBranch = pr.GetGitRefName() } - if err := git.NewCommand(ctx, "fetch", "--no-tags").AddDynamicArguments(remoteRepoName, headBranch+":"+trackingBranch). + if err := git.NewCommand(ctx, "fetch").AddArguments(fetchArgs...).AddDynamicArguments(remoteRepoName, headBranch+":"+trackingBranch). Run(&git.RunOpts{ Dir: tmpBasePath, Stdout: &outbuf, diff --git a/services/repository/adopt.go b/services/repository/adopt.go index 280c4cc035a8f..94b2c3f3d573f 100644 --- a/services/repository/adopt.go +++ b/services/repository/adopt.go @@ -26,7 +26,7 @@ import ( ) // AdoptRepository adopts pre-existing repository files for the user/organization. -func AdoptRepository(doer, u *user_model.User, opts repo_module.CreateRepoOptions) (*repo_model.Repository, error) { +func AdoptRepository(ctx context.Context, doer, u *user_model.User, opts repo_module.CreateRepoOptions) (*repo_model.Repository, error) { if !doer.IsAdmin && !u.CanCreateRepo() { return nil, repo_model.ErrReachLimitOfRepo{ Limit: u.MaxRepoCreation, @@ -53,7 +53,7 @@ func AdoptRepository(doer, u *user_model.User, opts repo_module.CreateRepoOption IsEmpty: !opts.AutoInit, } - if err := db.WithTx(db.DefaultContext, func(ctx context.Context) error { + if err := db.WithTx(ctx, func(ctx context.Context) error { repoPath := repo_model.RepoPath(u.Name, repo.Name) isExist, err := util.IsExist(repoPath) if err != nil { @@ -95,7 +95,7 @@ func AdoptRepository(doer, u *user_model.User, opts repo_module.CreateRepoOption return nil, err } - notification.NotifyCreateRepository(db.DefaultContext, doer, u, repo) + notification.NotifyCreateRepository(ctx, doer, u, repo) return repo, nil } @@ -188,7 +188,7 @@ func adoptRepository(ctx context.Context, repoPath string, u *user_model.User, r } // DeleteUnadoptedRepository deletes unadopted repository files from the filesystem -func DeleteUnadoptedRepository(doer, u *user_model.User, repoName string) error { +func DeleteUnadoptedRepository(ctx context.Context, doer, u *user_model.User, repoName string) error { if err := repo_model.IsUsableRepoName(repoName); err != nil { return err } @@ -206,7 +206,7 @@ func DeleteUnadoptedRepository(doer, u *user_model.User, repoName string) error } } - if exist, err := repo_model.IsRepositoryExist(db.DefaultContext, u, repoName); err != nil { + if exist, err := repo_model.IsRepositoryExist(ctx, u, repoName); err != nil { return err } else if exist { return repo_model.ErrRepoAlreadyExist{ @@ -232,11 +232,11 @@ func (unadopted *unadoptedRepositories) add(repository string) { unadopted.index++ } -func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unadopted *unadoptedRepositories) error { +func checkUnadoptedRepositories(ctx context.Context, userName string, repoNamesToCheck []string, unadopted *unadoptedRepositories) error { if len(repoNamesToCheck) == 0 { return nil } - ctxUser, err := user_model.GetUserByName(db.DefaultContext, userName) + ctxUser, err := user_model.GetUserByName(ctx, userName) if err != nil { if user_model.IsErrUserNotExist(err) { log.Debug("Missing user: %s", userName) @@ -271,7 +271,7 @@ func checkUnadoptedRepositories(userName string, repoNamesToCheck []string, unad } // ListUnadoptedRepositories lists all the unadopted repositories that match the provided query -func ListUnadoptedRepositories(query string, opts *db.ListOptions) ([]string, int, error) { +func ListUnadoptedRepositories(ctx context.Context, query string, opts *db.ListOptions) ([]string, int, error) { globUser, _ := glob.Compile("*") globRepo, _ := glob.Compile("*") @@ -315,7 +315,7 @@ func ListUnadoptedRepositories(query string, opts *db.ListOptions) ([]string, in if !strings.ContainsRune(path[len(root)+1:], filepath.Separator) { // Got a new user - if err = checkUnadoptedRepositories(userName, repoNamesToCheck, unadopted); err != nil { + if err = checkUnadoptedRepositories(ctx, userName, repoNamesToCheck, unadopted); err != nil { return err } repoNamesToCheck = repoNamesToCheck[:0] @@ -338,7 +338,7 @@ func ListUnadoptedRepositories(query string, opts *db.ListOptions) ([]string, in repoNamesToCheck = append(repoNamesToCheck, name) if len(repoNamesToCheck) >= setting.Database.IterateBufferSize { - if err = checkUnadoptedRepositories(userName, repoNamesToCheck, unadopted); err != nil { + if err = checkUnadoptedRepositories(ctx, userName, repoNamesToCheck, unadopted); err != nil { return err } repoNamesToCheck = repoNamesToCheck[:0] @@ -349,7 +349,7 @@ func ListUnadoptedRepositories(query string, opts *db.ListOptions) ([]string, in return nil, 0, err } - if err := checkUnadoptedRepositories(userName, repoNamesToCheck, unadopted); err != nil { + if err := checkUnadoptedRepositories(ctx, userName, repoNamesToCheck, unadopted); err != nil { return nil, 0, err } diff --git a/services/repository/adopt_test.go b/services/repository/adopt_test.go index be8897693eb8f..3f777c425fb15 100644 --- a/services/repository/adopt_test.go +++ b/services/repository/adopt_test.go @@ -39,7 +39,7 @@ func TestCheckUnadoptedRepositories(t *testing.T) { // Non existent user // unadopted := &unadoptedRepositories{start: 0, end: 100} - err := checkUnadoptedRepositories("notauser", []string{"repo"}, unadopted) + err := checkUnadoptedRepositories(db.DefaultContext, "notauser", []string{"repo"}, unadopted) assert.NoError(t, err) assert.Equal(t, 0, len(unadopted.repositories)) // @@ -50,14 +50,14 @@ func TestCheckUnadoptedRepositories(t *testing.T) { repoName := "repo2" unadoptedRepoName := "unadopted" unadopted = &unadoptedRepositories{start: 0, end: 100} - err = checkUnadoptedRepositories(userName, []string{repoName, unadoptedRepoName}, unadopted) + err = checkUnadoptedRepositories(db.DefaultContext, userName, []string{repoName, unadoptedRepoName}, unadopted) assert.NoError(t, err) assert.Equal(t, []string{path.Join(userName, unadoptedRepoName)}, unadopted.repositories) // // Existing (adopted) repository is not returned // unadopted = &unadoptedRepositories{start: 0, end: 100} - err = checkUnadoptedRepositories(userName, []string{repoName}, unadopted) + err = checkUnadoptedRepositories(db.DefaultContext, userName, []string{repoName}, unadopted) assert.NoError(t, err) assert.Equal(t, 0, len(unadopted.repositories)) assert.Equal(t, 0, unadopted.index) @@ -72,13 +72,13 @@ func TestListUnadoptedRepositories_ListOptions(t *testing.T) { } opts := db.ListOptions{Page: 1, PageSize: 1} - repoNames, count, err := ListUnadoptedRepositories("", &opts) + repoNames, count, err := ListUnadoptedRepositories(db.DefaultContext, "", &opts) assert.NoError(t, err) assert.Equal(t, 2, count) assert.Equal(t, unadoptedList[0], repoNames[0]) opts = db.ListOptions{Page: 2, PageSize: 1} - repoNames, count, err = ListUnadoptedRepositories("", &opts) + repoNames, count, err = ListUnadoptedRepositories(db.DefaultContext, "", &opts) assert.NoError(t, err) assert.Equal(t, 2, count) assert.Equal(t, unadoptedList[1], repoNames[0]) diff --git a/services/repository/avatar.go b/services/repository/avatar.go index 5fe8bd2c72f88..74e5de877e0ca 100644 --- a/services/repository/avatar.go +++ b/services/repository/avatar.go @@ -20,7 +20,7 @@ import ( // UploadAvatar saves custom avatar for repository. // FIXME: split uploads to different subdirs in case we have massive number of repos. -func UploadAvatar(repo *repo_model.Repository, data []byte) error { +func UploadAvatar(ctx context.Context, repo *repo_model.Repository, data []byte) error { m, err := avatar.Prepare(data) if err != nil { return err @@ -31,7 +31,7 @@ func UploadAvatar(repo *repo_model.Repository, data []byte) error { return nil } - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -65,7 +65,7 @@ func UploadAvatar(repo *repo_model.Repository, data []byte) error { } // DeleteAvatar deletes the repos's custom avatar. -func DeleteAvatar(repo *repo_model.Repository) error { +func DeleteAvatar(ctx context.Context, repo *repo_model.Repository) error { // Avatar not exists if len(repo.Avatar) == 0 { return nil @@ -74,7 +74,7 @@ func DeleteAvatar(repo *repo_model.Repository) error { avatarPath := repo.CustomAvatarRelativePath() log.Trace("DeleteAvatar[%d]: %s", repo.ID, avatarPath) - ctx, committer, err := db.TxContext(db.DefaultContext) + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } @@ -102,7 +102,7 @@ func RemoveRandomAvatars(ctx context.Context) error { } stringifiedID := strconv.FormatInt(repository.ID, 10) if repository.Avatar == stringifiedID { - return DeleteAvatar(repository) + return DeleteAvatar(ctx, repository) } return nil }) diff --git a/services/repository/avatar_test.go b/services/repository/avatar_test.go index 5ec899ec3f9a7..4a0ba61853895 100644 --- a/services/repository/avatar_test.go +++ b/services/repository/avatar_test.go @@ -9,6 +9,7 @@ import ( "image/png" "testing" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/avatar" @@ -25,7 +26,7 @@ func TestUploadAvatar(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) - err := UploadAvatar(repo, buff.Bytes()) + err := UploadAvatar(db.DefaultContext, repo, buff.Bytes()) assert.NoError(t, err) assert.Equal(t, avatar.HashAvatar(10, buff.Bytes()), repo.Avatar) } @@ -39,7 +40,7 @@ func TestUploadBigAvatar(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) - err := UploadAvatar(repo, buff.Bytes()) + err := UploadAvatar(db.DefaultContext, repo, buff.Bytes()) assert.Error(t, err) } @@ -52,10 +53,10 @@ func TestDeleteAvatar(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 10}) - err := UploadAvatar(repo, buff.Bytes()) + err := UploadAvatar(db.DefaultContext, repo, buff.Bytes()) assert.NoError(t, err) - err = DeleteAvatar(repo) + err = DeleteAvatar(db.DefaultContext, repo) assert.NoError(t, err) assert.Equal(t, "", repo.Avatar) diff --git a/services/repository/branch.go b/services/repository/branch.go index 291fb4a92b374..a085026ae1563 100644 --- a/services/repository/branch.go +++ b/services/repository/branch.go @@ -10,7 +10,6 @@ import ( "strings" "code.gitea.io/gitea/models" - "code.gitea.io/gitea/models/db" git_model "code.gitea.io/gitea/models/git" repo_model "code.gitea.io/gitea/models/repo" user_model "code.gitea.io/gitea/models/user" @@ -106,7 +105,7 @@ func CreateNewBranchFromCommit(ctx context.Context, doer *user_model.User, repo } // RenameBranch rename a branch -func RenameBranch(repo *repo_model.Repository, doer *user_model.User, gitRepo *git.Repository, from, to string) (string, error) { +func RenameBranch(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, gitRepo *git.Repository, from, to string) (string, error) { if from == to { return "target_exist", nil } @@ -119,7 +118,7 @@ func RenameBranch(repo *repo_model.Repository, doer *user_model.User, gitRepo *g return "from_not_exist", nil } - if err := git_model.RenameBranch(db.DefaultContext, repo, from, to, func(isDefault bool) error { + if err := git_model.RenameBranch(ctx, repo, from, to, func(isDefault bool) error { err2 := gitRepo.RenameBranch(from, to) if err2 != nil { return err2 @@ -141,8 +140,8 @@ func RenameBranch(repo *repo_model.Repository, doer *user_model.User, gitRepo *g return "", err } - notification.NotifyDeleteRef(db.DefaultContext, doer, repo, "branch", git.BranchPrefix+from) - notification.NotifyCreateRef(db.DefaultContext, doer, repo, "branch", git.BranchPrefix+to, refID) + notification.NotifyDeleteRef(ctx, doer, repo, "branch", git.BranchPrefix+from) + notification.NotifyCreateRef(ctx, doer, repo, "branch", git.BranchPrefix+to, refID) return "", nil } @@ -153,12 +152,12 @@ var ( ) // DeleteBranch delete branch -func DeleteBranch(doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string) error { +func DeleteBranch(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, gitRepo *git.Repository, branchName string) error { if branchName == repo.DefaultBranch { return ErrBranchIsDefault } - isProtected, err := git_model.IsBranchProtected(db.DefaultContext, repo.ID, branchName) + isProtected, err := git_model.IsBranchProtected(ctx, repo.ID, branchName) if err != nil { return err } @@ -195,7 +194,7 @@ func DeleteBranch(doer *user_model.User, repo *repo_model.Repository, gitRepo *g log.Error("Update: %v", err) } - if err := git_model.AddDeletedBranch(db.DefaultContext, repo.ID, branchName, commit.ID.String(), doer.ID); err != nil { + if err := git_model.AddDeletedBranch(ctx, repo.ID, branchName, commit.ID.String(), doer.ID); err != nil { log.Warn("AddDeletedBranch: %v", err) } diff --git a/services/repository/fork.go b/services/repository/fork.go index c3ca89e02e7f8..fb93b10f1c312 100644 --- a/services/repository/fork.go +++ b/services/repository/fork.go @@ -189,8 +189,8 @@ 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(repo *repo_model.Repository) error { - err := db.WithTx(db.DefaultContext, func(ctx context.Context) error { +func ConvertForkToNormalRepository(ctx context.Context, repo *repo_model.Repository) error { + err := db.WithTx(ctx, func(ctx context.Context) error { repo, err := repo_model.GetRepositoryByID(ctx, repo.ID) if err != nil { return err diff --git a/services/repository/push.go b/services/repository/push.go index 8aa8be6aa2fd6..355c2878113fd 100644 --- a/services/repository/push.go +++ b/services/repository/push.go @@ -80,6 +80,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { ctx, _, finished := process.GetManager().AddContext(graceful.GetManager().HammerContext(), fmt.Sprintf("PushUpdates: %s/%s", optsList[0].RepoUserName, optsList[0].RepoName)) defer finished() + ctx = cache.WithCacheContext(ctx) repo, err := repo_model.GetRepositoryByOwnerAndName(ctx, optsList[0].RepoUserName, optsList[0].RepoName) if err != nil { @@ -122,7 +123,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { tagName := opts.TagName() if opts.IsDelRef() { notification.NotifyPushCommits( - db.DefaultContext, pusher, repo, + ctx, pusher, repo, &repo_module.PushUpdateOptions{ RefFullName: git.TagPrefix + tagName, OldCommitID: opts.OldCommitID, @@ -130,7 +131,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { }, repo_module.NewPushCommits()) delTags = append(delTags, tagName) - notification.NotifyDeleteRef(db.DefaultContext, pusher, repo, "tag", opts.RefFullName) + notification.NotifyDeleteRef(ctx, pusher, repo, "tag", opts.RefFullName) } else { // is new tag newCommit, err := gitRepo.GetCommit(opts.NewCommitID) if err != nil { @@ -142,7 +143,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { commits.CompareURL = repo.ComposeCompareURL(git.EmptySHA, opts.NewCommitID) notification.NotifyPushCommits( - db.DefaultContext, pusher, repo, + ctx, pusher, repo, &repo_module.PushUpdateOptions{ RefFullName: git.TagPrefix + tagName, OldCommitID: git.EmptySHA, @@ -150,7 +151,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { }, commits) addTags = append(addTags, tagName) - notification.NotifyCreateRef(db.DefaultContext, pusher, repo, "tag", opts.RefFullName, opts.NewCommitID) + notification.NotifyCreateRef(ctx, pusher, repo, "tag", opts.RefFullName, opts.NewCommitID) } } else if opts.IsBranch() { // If is branch reference if pusher == nil || pusher.ID != opts.PusherID { @@ -190,7 +191,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { } } // Update the is empty and default_branch columns - if err := repo_model.UpdateRepositoryCols(db.DefaultContext, repo, "default_branch", "is_empty"); err != nil { + if err := repo_model.UpdateRepositoryCols(ctx, repo, "default_branch", "is_empty"); err != nil { return fmt.Errorf("UpdateRepositoryCols: %w", err) } } @@ -199,7 +200,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { if err != nil { return fmt.Errorf("newCommit.CommitsBeforeLimit: %w", err) } - notification.NotifyCreateRef(db.DefaultContext, pusher, repo, "branch", opts.RefFullName, opts.NewCommitID) + notification.NotifyCreateRef(ctx, pusher, repo, "branch", opts.RefFullName, opts.NewCommitID) } else { l, err = newCommit.CommitsBeforeUntil(opts.OldCommitID) if err != nil { @@ -259,7 +260,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { commits.Commits = commits.Commits[:setting.UI.FeedMaxCommitNum] } - notification.NotifyPushCommits(db.DefaultContext, pusher, repo, opts, commits) + notification.NotifyPushCommits(ctx, pusher, repo, opts, commits) if err = git_model.RemoveDeletedBranchByName(ctx, repo.ID, branch); err != nil { log.Error("models.RemoveDeletedBranch %s/%s failed: %v", repo.ID, branch, err) @@ -270,7 +271,7 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { log.Error("repo_module.CacheRef %s/%s failed: %v", repo.ID, branch, err) } } else { - notification.NotifyDeleteRef(db.DefaultContext, pusher, repo, "branch", opts.RefFullName) + notification.NotifyDeleteRef(ctx, pusher, repo, "branch", opts.RefFullName) if err = pull_service.CloseBranchPulls(pusher, repo.ID, branch); err != nil { // close all related pulls log.Error("close related pull request failed: %v", err) @@ -278,14 +279,14 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { } // Even if user delete a branch on a repository which he didn't watch, he will be watch that. - if err = repo_model.WatchIfAuto(db.DefaultContext, opts.PusherID, repo.ID, true); err != nil { + if err = repo_model.WatchIfAuto(ctx, opts.PusherID, repo.ID, true); err != nil { log.Warn("Fail to perform auto watch on user %v for repo %v: %v", opts.PusherID, repo.ID, err) } } else { log.Trace("Non-tag and non-branch commits pushed.") } } - if err := PushUpdateAddDeleteTags(repo, gitRepo, addTags, delTags); err != nil { + if err := PushUpdateAddDeleteTags(ctx, repo, gitRepo, addTags, delTags); err != nil { return fmt.Errorf("PushUpdateAddDeleteTags: %w", err) } @@ -298,8 +299,8 @@ func pushUpdates(optsList []*repo_module.PushUpdateOptions) error { } // PushUpdateAddDeleteTags updates a number of added and delete tags -func PushUpdateAddDeleteTags(repo *repo_model.Repository, gitRepo *git.Repository, addTags, delTags []string) error { - return db.WithTx(db.DefaultContext, func(ctx context.Context) error { +func PushUpdateAddDeleteTags(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, addTags, delTags []string) error { + return db.WithTx(ctx, func(ctx context.Context) error { if err := repo_model.PushUpdateDeleteTagsContext(ctx, repo, delTags); err != nil { return err } diff --git a/services/repository/repository.go b/services/repository/repository.go index 3c3e7e82c3f8f..000b1a3da6bce 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -24,14 +24,14 @@ import ( ) // CreateRepository creates a repository for the user/organization. -func CreateRepository(doer, owner *user_model.User, opts repo_module.CreateRepoOptions) (*repo_model.Repository, error) { +func CreateRepository(ctx context.Context, doer, owner *user_model.User, opts repo_module.CreateRepoOptions) (*repo_model.Repository, error) { repo, err := repo_module.CreateRepository(doer, owner, opts) if err != nil { // No need to rollback here we should do this in CreateRepository... return nil, err } - notification.NotifyCreateRepository(db.DefaultContext, doer, owner, repo) + notification.NotifyCreateRepository(ctx, doer, owner, repo) return repo, nil } @@ -55,10 +55,10 @@ func DeleteRepository(ctx context.Context, doer *user_model.User, repo *repo_mod } // PushCreateRepo creates a repository when a new repository is pushed to an appropriate namespace -func PushCreateRepo(authUser, owner *user_model.User, repoName string) (*repo_model.Repository, error) { +func PushCreateRepo(ctx context.Context, authUser, owner *user_model.User, repoName string) (*repo_model.Repository, error) { if !authUser.IsAdmin { if owner.IsOrganization() { - if ok, err := organization.CanCreateOrgRepo(db.DefaultContext, owner.ID, authUser.ID); err != nil { + if ok, err := organization.CanCreateOrgRepo(ctx, owner.ID, authUser.ID); err != nil { return nil, err } else if !ok { return nil, fmt.Errorf("cannot push-create repository for org") @@ -68,7 +68,7 @@ func PushCreateRepo(authUser, owner *user_model.User, repoName string) (*repo_mo } } - repo, err := CreateRepository(authUser, owner, repo_module.CreateRepoOptions{ + repo, err := CreateRepository(ctx, authUser, owner, repo_module.CreateRepoOptions{ Name: repoName, IsPrivate: setting.Repository.DefaultPushCreatePrivate, }) @@ -88,8 +88,8 @@ func Init() error { } // UpdateRepository updates a repository -func UpdateRepository(repo *repo_model.Repository, visibilityChanged bool) (err error) { - ctx, committer, err := db.TxContext(db.DefaultContext) +func UpdateRepository(ctx context.Context, repo *repo_model.Repository, visibilityChanged bool) (err error) { + ctx, committer, err := db.TxContext(ctx) if err != nil { return err } diff --git a/services/repository/review.go b/services/repository/review.go index 6b5f096053214..40513e6bc67ba 100644 --- a/services/repository/review.go +++ b/services/repository/review.go @@ -4,20 +4,21 @@ package repository import ( - "code.gitea.io/gitea/models/db" + "context" + "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" ) // GetReviewerTeams get all teams can be requested to review -func GetReviewerTeams(repo *repo_model.Repository) ([]*organization.Team, error) { - if err := repo.LoadOwner(db.DefaultContext); err != nil { +func GetReviewerTeams(ctx context.Context, repo *repo_model.Repository) ([]*organization.Team, error) { + if err := repo.LoadOwner(ctx); err != nil { return nil, err } if !repo.Owner.IsOrganization() { return nil, nil } - return organization.GetTeamsWithAccessToRepo(db.DefaultContext, repo.OwnerID, repo.ID, perm.AccessModeRead) + return organization.GetTeamsWithAccessToRepo(ctx, repo.OwnerID, repo.ID, perm.AccessModeRead) } diff --git a/services/repository/review_test.go b/services/repository/review_test.go index 2bf4cdbf5cdf2..2db56d4e8a9cc 100644 --- a/services/repository/review_test.go +++ b/services/repository/review_test.go @@ -6,6 +6,7 @@ package repository import ( "testing" + "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" @@ -16,12 +17,12 @@ func TestRepoGetReviewerTeams(t *testing.T) { assert.NoError(t, unittest.PrepareTestDatabase()) repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) - teams, err := GetReviewerTeams(repo2) + teams, err := GetReviewerTeams(db.DefaultContext, repo2) assert.NoError(t, err) assert.Empty(t, teams) repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) - teams, err = GetReviewerTeams(repo3) + teams, err = GetReviewerTeams(db.DefaultContext, repo3) assert.NoError(t, err) assert.Len(t, teams, 2) } diff --git a/services/repository/template.go b/services/repository/template.go index 8c75948c418b8..42174d095b035 100644 --- a/services/repository/template.go +++ b/services/repository/template.go @@ -40,7 +40,7 @@ func GenerateIssueLabels(ctx context.Context, templateRepo, generateRepo *repo_m } // GenerateRepository generates a repository from a template -func GenerateRepository(doer, owner *user_model.User, templateRepo *repo_model.Repository, opts repo_module.GenerateRepoOptions) (_ *repo_model.Repository, err error) { +func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templateRepo *repo_model.Repository, opts repo_module.GenerateRepoOptions) (_ *repo_model.Repository, err error) { if !doer.IsAdmin && !owner.CanCreateRepo() { return nil, repo_model.ErrReachLimitOfRepo{ Limit: owner.MaxRepoCreation, @@ -48,7 +48,7 @@ func GenerateRepository(doer, owner *user_model.User, templateRepo *repo_model.R } var generateRepo *repo_model.Repository - if err = db.WithTx(db.DefaultContext, func(ctx context.Context) error { + if err = db.WithTx(ctx, func(ctx context.Context) error { generateRepo, err = repo_module.GenerateRepository(ctx, doer, owner, templateRepo, opts) if err != nil { return err @@ -101,7 +101,7 @@ func GenerateRepository(doer, owner *user_model.User, templateRepo *repo_model.R return nil, err } - notification.NotifyCreateRepository(db.DefaultContext, doer, owner, generateRepo) + notification.NotifyCreateRepository(ctx, doer, owner, generateRepo) return generateRepo, nil } diff --git a/services/repository/transfer.go b/services/repository/transfer.go index 8c167552da06f..b9b26f314c2c3 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -8,7 +8,6 @@ import ( "fmt" "code.gitea.io/gitea/models" - "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" @@ -61,7 +60,7 @@ func TransferOwnership(ctx context.Context, doer, newOwner *user_model.User, rep } // ChangeRepositoryName changes all corresponding setting from old repository name to new one. -func ChangeRepositoryName(doer *user_model.User, repo *repo_model.Repository, newRepoName string) error { +func ChangeRepositoryName(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, newRepoName string) error { log.Trace("ChangeRepositoryName: %s/%s -> %s", doer.Name, repo.Name, newRepoName) oldRepoName := repo.Name @@ -78,7 +77,7 @@ func ChangeRepositoryName(doer *user_model.User, repo *repo_model.Repository, ne repoWorkingPool.CheckOut(fmt.Sprint(repo.ID)) repo.Name = newRepoName - notification.NotifyRenameRepository(db.DefaultContext, doer, repo, oldRepoName) + notification.NotifyRenameRepository(ctx, doer, repo, oldRepoName) return nil } diff --git a/templates/admin/user/new.tmpl b/templates/admin/user/new.tmpl index e5ca864cb77d7..2bf1d0ace582d 100644 --- a/templates/admin/user/new.tmpl +++ b/templates/admin/user/new.tmpl @@ -50,7 +50,7 @@ -
+
@@ -62,12 +62,12 @@
-
+
-
+
diff --git a/templates/repo/diff/comments.tmpl b/templates/repo/diff/comments.tmpl index 985ad06559c9e..f28a3c5b5dc25 100644 --- a/templates/repo/diff/comments.tmpl +++ b/templates/repo/diff/comments.tmpl @@ -42,8 +42,8 @@
{{end}} {{end}} - {{template "repo/issue/view_content/add_reaction" Dict "ctx" $.root "ActionURL" (Printf "%s/comments/%d/reactions" $.root.RepoLink .ID)}} - {{template "repo/issue/view_content/context_menu" Dict "ctx" $.root "item" . "delete" true "issue" false "diff" true "IsCommentPoster" (and $.root.IsSigned (eq $.root.SignedUserID .PosterID))}} + {{template "repo/issue/view_content/add_reaction" Dict "ctxData" $.root "ActionURL" (Printf "%s/comments/%d/reactions" $.root.RepoLink .ID)}} + {{template "repo/issue/view_content/context_menu" Dict "ctxData" $.root "item" . "delete" true "issue" false "diff" true "IsCommentPoster" (and $.root.IsSigned (eq $.root.SignedUserID .PosterID))}}
@@ -60,7 +60,7 @@ {{$reactions := .Reactions.GroupByType}} {{if $reactions}}
- {{template "repo/issue/view_content/reactions" Dict "ctx" $.root "ActionURL" (Printf "%s/comments/%d/reactions" $.root.RepoLink .ID) "Reactions" $reactions}} + {{template "repo/issue/view_content/reactions" Dict "ctxData" $.root "ActionURL" (Printf "%s/comments/%d/reactions" $.root.RepoLink .ID) "Reactions" $reactions}}
{{end}}
diff --git a/templates/repo/editor/commit_form.tmpl b/templates/repo/editor/commit_form.tmpl index c6c48c5a838c2..7ac0ed3df19fe 100644 --- a/templates/repo/editor/commit_form.tmpl +++ b/templates/repo/editor/commit_form.tmpl @@ -58,7 +58,7 @@
-
+
{{svg "octicon-git-branch"}} diff --git a/templates/repo/issue/labels/labels_sidebar.tmpl b/templates/repo/issue/labels/labels_sidebar.tmpl index bd878d6f5c975..89fe26b75537a 100644 --- a/templates/repo/issue/labels/labels_sidebar.tmpl +++ b/templates/repo/issue/labels/labels_sidebar.tmpl @@ -1,10 +1,10 @@
- {{.ctx.locale.Tr "repo.issues.new.no_label"}} + {{.root.locale.Tr "repo.issues.new.no_label"}} - {{range .ctx.Labels}} + {{range .root.Labels}} {{template "repo/issue/labels/label" dict "root" $.root "label" .}} {{end}} - {{range .ctx.OrgLabels}} + {{range .root.OrgLabels}} {{template "repo/issue/labels/label" dict "root" $.root "label" .}} {{end}} diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index 8346d07a13c66..1c95dfac3ab66 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -80,7 +80,7 @@ {{end}}
- {{template "repo/issue/labels/labels_sidebar" dict "root" $ "ctx" .}} + {{template "repo/issue/labels/labels_sidebar" dict "root" $}}
diff --git a/templates/repo/issue/view_content.tmpl b/templates/repo/issue/view_content.tmpl index 08ba509045879..6aec6e3157f43 100644 --- a/templates/repo/issue/view_content.tmpl +++ b/templates/repo/issue/view_content.tmpl @@ -64,8 +64,8 @@ {{end}} {{end}} {{if not $.Repository.IsArchived}} - {{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index)}} - {{template "repo/issue/view_content/context_menu" Dict "ctx" $ "item" .Issue "delete" false "issue" true "diff" false "IsCommentPoster" $.IsIssuePoster}} + {{template "repo/issue/view_content/add_reaction" Dict "ctxData" $ "ActionURL" (Printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index)}} + {{template "repo/issue/view_content/context_menu" Dict "ctxData" $ "item" .Issue "delete" false "issue" true "diff" false "IsCommentPoster" $.IsIssuePoster}} {{end}}
@@ -80,13 +80,13 @@
{{.Issue.Content}}
{{if .Issue.Attachments}} - {{template "repo/issue/view_content/attachments" Dict "ctx" $ "Attachments" .Issue.Attachments "Content" .Issue.RenderedContent}} + {{template "repo/issue/view_content/attachments" Dict "ctxData" $ "Attachments" .Issue.Attachments "Content" .Issue.RenderedContent}} {{end}} {{$reactions := .Issue.Reactions.GroupByType}} {{if $reactions}}
- {{template "repo/issue/view_content/reactions" Dict "ctx" $ "ActionURL" (Printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index) "Reactions" $reactions}} + {{template "repo/issue/view_content/reactions" Dict "ctxData" $ "ActionURL" (Printf "%s/issues/%d/reactions" $.RepoLink .Issue.Index) "Reactions" $reactions}}
{{end}} diff --git a/templates/repo/issue/view_content/add_reaction.tmpl b/templates/repo/issue/view_content/add_reaction.tmpl index bfa8a7e122349..692d09e67980b 100644 --- a/templates/repo/issue/view_content/add_reaction.tmpl +++ b/templates/repo/issue/view_content/add_reaction.tmpl @@ -1,10 +1,10 @@ -{{if .ctx.IsSigned}} +{{if .ctxData.IsSigned}} @@ -80,13 +80,13 @@
{{.Content}}
{{if .Attachments}} - {{template "repo/issue/view_content/attachments" Dict "ctx" $ "Attachments" .Attachments "Content" .RenderedContent}} + {{template "repo/issue/view_content/attachments" Dict "ctxData" $ "Attachments" .Attachments "Content" .RenderedContent}} {{end}} {{$reactions := .Reactions.GroupByType}} {{if $reactions}}
- {{template "repo/issue/view_content/reactions" Dict "ctx" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}} + {{template "repo/issue/view_content/reactions" Dict "ctxData" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}}
{{end}} @@ -260,7 +260,7 @@ {{template "shared/user/authorlink" .Poster}} {{$.locale.Tr "repo.issues.stop_tracking_history" $createdStr | Safe}} - {{template "repo/issue/view_content/comments_delete_time" Dict "ctx" $ "comment" .}} + {{template "repo/issue/view_content/comments_delete_time" Dict "ctxData" $ "comment" .}}
{{svg "octicon-clock"}} {{.Content}} @@ -274,7 +274,7 @@ {{template "shared/user/authorlink" .Poster}} {{$.locale.Tr "repo.issues.add_time_history" $createdStr | Safe}} - {{template "repo/issue/view_content/comments_delete_time" Dict "ctx" $ "comment" .}} + {{template "repo/issue/view_content/comments_delete_time" Dict "ctxData" $ "comment" .}}
{{svg "octicon-clock"}} {{.Content}} @@ -436,8 +436,8 @@
{{end}} {{if not $.Repository.IsArchived}} - {{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.RepoLink .ID)}} - {{template "repo/issue/view_content/context_menu" Dict "ctx" $ "item" . "delete" false "issue" true "diff" false "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}} + {{template "repo/issue/view_content/add_reaction" Dict "ctxData" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.RepoLink .ID)}} + {{template "repo/issue/view_content/context_menu" Dict "ctxData" $ "item" . "delete" false "issue" true "diff" false "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}} {{end}}
@@ -452,13 +452,13 @@
{{.Content}}
{{if .Attachments}} - {{template "repo/issue/view_content/attachments" Dict "ctx" $ "Attachments" .Attachments "Content" .RenderedContent}} + {{template "repo/issue/view_content/attachments" Dict "ctxData" $ "Attachments" .Attachments "Content" .RenderedContent}} {{end}} {{$reactions := .Reactions.GroupByType}} {{if $reactions}}
- {{template "repo/issue/view_content/reactions" Dict "ctx" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}} + {{template "repo/issue/view_content/reactions" Dict "ctxData" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}}
{{end}} @@ -563,8 +563,8 @@ {{end}} {{if not $.Repository.IsArchived}} - {{template "repo/issue/view_content/add_reaction" Dict "ctx" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.RepoLink .ID)}} - {{template "repo/issue/view_content/context_menu" Dict "ctx" $ "item" . "delete" true "issue" true "diff" true "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}} + {{template "repo/issue/view_content/add_reaction" Dict "ctxData" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.RepoLink .ID)}} + {{template "repo/issue/view_content/context_menu" Dict "ctxData" $ "item" . "delete" true "issue" true "diff" true "IsCommentPoster" (and $.IsSigned (eq $.SignedUserID .PosterID))}} {{end}} @@ -582,7 +582,7 @@ {{$reactions := .Reactions.GroupByType}} {{if $reactions}}
- {{template "repo/issue/view_content/reactions" Dict "ctx" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}} + {{template "repo/issue/view_content/reactions" Dict "ctxData" $ "ActionURL" (Printf "%s/comments/%d/reactions" $.RepoLink .ID) "Reactions" $reactions}}
{{end}} @@ -697,7 +697,11 @@ {{else if and (eq .Type 29) (or (gt .CommitsNum 0) .IsForcePush)}} -
+ + {{if and .Issue.IsClosed (gt .ID $.LatestCloseCommentID)}} + {{continue}} + {{end}}` +
` {{svg "octicon-repo-push"}} {{template "shared/user/authorlink" .Poster}} @@ -707,6 +711,11 @@ {{$.locale.TrN (len .Commits) "repo.issues.push_commit_1" "repo.issues.push_commits_n" (len .Commits) $createdStr | Safe}} {{end}} + {{if and .IsForcePush $.Issue.PullRequest.BaseRepo.Name}} + + {{$.locale.Tr "repo.issues.force_push_compare"}} + + {{end}}
{{if not .IsForcePush}} {{template "repo/commits_list_small" dict "comment" . "root" $}} diff --git a/templates/repo/issue/view_content/comments_delete_time.tmpl b/templates/repo/issue/view_content/comments_delete_time.tmpl index e01d2602f6d63..bc08d7fde78de 100644 --- a/templates/repo/issue/view_content/comments_delete_time.tmpl +++ b/templates/repo/issue/view_content/comments_delete_time.tmpl @@ -1,18 +1,18 @@ {{if .comment.Time}} {{/* compatibility with time comments made before v1.14 */}} {{if (not .comment.Time.Deleted)}} - {{if (or .ctx.IsAdmin (and .ctx.IsSigned (eq .ctx.SignedUserID .comment.PosterID)))}} + {{if (or .ctxData.IsAdmin (and .ctxData.IsSigned (eq .ctxData.SignedUserID .comment.PosterID)))}} - diff --git a/templates/repo/issue/view_content/context_menu.tmpl b/templates/repo/issue/view_content/context_menu.tmpl index b4b9403b2af74..f836271b65b56 100644 --- a/templates/repo/issue/view_content/context_menu.tmpl +++ b/templates/repo/issue/view_content/context_menu.tmpl @@ -1,4 +1,4 @@ -{{if .ctx.IsSigned}} +{{if .ctxData.IsSigned}}
- {{template "repo/issue/labels/labels_sidebar" dict "root" $ "ctx" .}} + {{template "repo/issue/labels/labels_sidebar" dict "root" $}}
diff --git a/templates/user/dashboard/repolist.tmpl b/templates/user/dashboard/repolist.tmpl index 22af578fc39c5..97234176bd210 100644 --- a/templates/user/dashboard/repolist.tmpl +++ b/templates/user/dashboard/repolist.tmpl @@ -46,56 +46,38 @@ diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/40/3d76c604cb569323864e06a07b85d466924802 b/tests/gitea-repositories-meta/user2/repo1.git/objects/40/3d76c604cb569323864e06a07b85d466924802 new file mode 100644 index 0000000000000..ea0bf76d0cc85 Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo1.git/objects/40/3d76c604cb569323864e06a07b85d466924802 differ diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/78/fb907e3a3309eae4fe8fef030874cebbf1cd5e b/tests/gitea-repositories-meta/user2/repo1.git/objects/78/fb907e3a3309eae4fe8fef030874cebbf1cd5e new file mode 100644 index 0000000000000..6a25f7409b410 Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo1.git/objects/78/fb907e3a3309eae4fe8fef030874cebbf1cd5e differ diff --git a/tests/gitea-repositories-meta/user2/repo1.git/objects/f3/fa0f5cc797fc4c02a1b8bec9de4b2072fcdbdf b/tests/gitea-repositories-meta/user2/repo1.git/objects/f3/fa0f5cc797fc4c02a1b8bec9de4b2072fcdbdf new file mode 100644 index 0000000000000..9b20f8af3f2d5 Binary files /dev/null and b/tests/gitea-repositories-meta/user2/repo1.git/objects/f3/fa0f5cc797fc4c02a1b8bec9de4b2072fcdbdf differ diff --git a/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/home-md-img-check b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/home-md-img-check new file mode 100644 index 0000000000000..a254e42921f41 --- /dev/null +++ b/tests/gitea-repositories-meta/user2/repo1.git/refs/heads/home-md-img-check @@ -0,0 +1 @@ +78fb907e3a3309eae4fe8fef030874cebbf1cd5e diff --git a/tests/integration/pull_merge_test.go b/tests/integration/pull_merge_test.go index 090a27c39efbc..55cf295257a17 100644 --- a/tests/integration/pull_merge_test.go +++ b/tests/integration/pull_merge_test.go @@ -356,7 +356,7 @@ func TestConflictChecking(t *testing.T) { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // Create new clean repo to test conflict checking. - baseRepo, err := repo_service.CreateRepository(user, user, repo_module.CreateRepoOptions{ + baseRepo, err := repo_service.CreateRepository(db.DefaultContext, user, user, repo_module.CreateRepoOptions{ Name: "conflict-checking", Description: "Tempo repo", AutoInit: true, diff --git a/tests/integration/pull_update_test.go b/tests/integration/pull_update_test.go index bd416e5bcfac3..1b66656518a30 100644 --- a/tests/integration/pull_update_test.go +++ b/tests/integration/pull_update_test.go @@ -80,7 +80,7 @@ func TestAPIPullUpdateByRebase(t *testing.T) { } func createOutdatedPR(t *testing.T, actor, forkOrg *user_model.User) *issues_model.PullRequest { - baseRepo, err := repo_service.CreateRepository(actor, actor, repo_module.CreateRepoOptions{ + baseRepo, err := repo_service.CreateRepository(db.DefaultContext, actor, actor, repo_module.CreateRepoOptions{ Name: "repo-pr-update", Description: "repo-tmp-pr-update description", AutoInit: true, diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index 3692b11ecabe5..7c3eac20cfac2 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -256,3 +256,23 @@ func TestViewRepoDirectory(t *testing.T) { assert.Zero(t, repoTopics.Length()) assert.Zero(t, repoSummary.Length()) } + +func TestMarkDownImage(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + session := loginUser(t, "user2") + + req := NewRequest(t, "GET", "/user2/repo1/src/branch/home-md-img-check") + resp := session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + _, exists := htmlDoc.doc.Find(`img[src="/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg"]`).Attr("src") + assert.True(t, exists, "Repo home page markdown image link check failed") + + req = NewRequest(t, "GET", "/user2/repo1/src/branch/home-md-img-check/README.md") + resp = session.MakeRequest(t, req, http.StatusOK) + + htmlDoc = NewHTMLParser(t, resp.Body) + _, exists = htmlDoc.doc.Find(`img[src="/user2/repo1/media/branch/home-md-img-check/test-fake-img.jpg"]`).Attr("src") + assert.True(t, exists, "Repo src page markdown image link check failed") +} diff --git a/web_src/js/components/DashboardRepoList.js b/web_src/js/components/DashboardRepoList.js index 0a009e78d1516..2328cc83a90c0 100644 --- a/web_src/js/components/DashboardRepoList.js +++ b/web_src/js/components/DashboardRepoList.js @@ -87,6 +87,7 @@ function initVueComponents(app) { } return { + hasMounted: false, // accessing $refs in computed() need to wait for mounted tab, repos: [], reposTotalCount: 0, @@ -134,7 +135,19 @@ function initVueComponents(app) { }, repoTypeCount() { return this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`]; - } + }, + checkboxArchivedFilterTitle() { + return this.hasMounted && this.$refs.checkboxArchivedFilter?.getAttribute(`data-title-${this.archivedFilter}`); + }, + checkboxArchivedFilterProps() { + return {checked: this.archivedFilter === 'archived', indeterminate: this.archivedFilter === 'both'}; + }, + checkboxPrivateFilterTitle() { + return this.hasMounted && this.$refs.checkboxPrivateFilter?.getAttribute(`data-title-${this.privateFilter}`); + }, + checkboxPrivateFilterProps() { + return {checked: this.privateFilter === 'private', indeterminate: this.privateFilter === 'both'}; + }, }, mounted() { @@ -144,10 +157,11 @@ function initVueComponents(app) { initTooltip(elTooltip); } $(el).find('.dropdown').dropdown(); - this.setCheckboxes(); nextTick(() => { this.$refs.search.focus(); }); + + this.hasMounted = true; }, methods: { @@ -156,39 +170,6 @@ function initVueComponents(app) { this.updateHistory(); }, - setCheckboxes() { - switch (this.archivedFilter) { - case 'unarchived': - $('#archivedFilterCheckbox').checkbox('set unchecked'); - break; - case 'archived': - $('#archivedFilterCheckbox').checkbox('set checked'); - break; - case 'both': - $('#archivedFilterCheckbox').checkbox('set indeterminate'); - break; - default: - this.archivedFilter = 'unarchived'; - $('#archivedFilterCheckbox').checkbox('set unchecked'); - break; - } - switch (this.privateFilter) { - case 'public': - $('#privateFilterCheckbox').checkbox('set unchecked'); - break; - case 'private': - $('#privateFilterCheckbox').checkbox('set checked'); - break; - case 'both': - $('#privateFilterCheckbox').checkbox('set indeterminate'); - break; - default: - this.privateFilter = 'both'; - $('#privateFilterCheckbox').checkbox('set indeterminate'); - break; - } - }, - changeReposFilter(filter) { this.reposFilter = filter; this.repos = []; @@ -245,45 +226,29 @@ function initVueComponents(app) { }, toggleArchivedFilter() { - switch (this.archivedFilter) { - case 'both': - this.archivedFilter = 'unarchived'; - break; - case 'unarchived': - this.archivedFilter = 'archived'; - break; - case 'archived': - this.archivedFilter = 'both'; - break; - default: - this.archivedFilter = 'unarchived'; - break; + if (this.archivedFilter === 'unarchived') { + this.archivedFilter = 'archived'; + } else if (this.archivedFilter === 'archived') { + this.archivedFilter = 'both'; + } else { // including both + this.archivedFilter = 'unarchived'; } this.page = 1; this.repos = []; - this.setCheckboxes(); this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0; this.searchRepos(); }, togglePrivateFilter() { - switch (this.privateFilter) { - case 'both': - this.privateFilter = 'public'; - break; - case 'public': - this.privateFilter = 'private'; - break; - case 'private': - this.privateFilter = 'both'; - break; - default: - this.privateFilter = 'both'; - break; + if (this.privateFilter === 'both') { + this.privateFilter = 'public'; + } else if (this.privateFilter === 'public') { + this.privateFilter = 'private'; + } else { // including private + this.privateFilter = 'both'; } this.page = 1; this.repos = []; - this.setCheckboxes(); this.counts[`${this.reposFilter}:${this.archivedFilter}:${this.privateFilter}`] = 0; this.searchRepos(); }, diff --git a/web_src/js/features/citation.js b/web_src/js/features/citation.js index 38288456248da..c40b1adddd148 100644 --- a/web_src/js/features/citation.js +++ b/web_src/js/features/citation.js @@ -2,7 +2,7 @@ import $ from 'jquery'; const {pageData} = window.config; -const initInputCitationValue = async ($citationCopyBibtex, $citationCopyApa) => { +const initInputCitationValue = async ($citationCopyApa, $citationCopyBibtex) => { const [{Cite, plugins}] = await Promise.all([ import(/* webpackChunkName: "citation-js-core" */'@citation-js/core'), import(/* webpackChunkName: "citation-js-formats" */'@citation-js/plugin-software-formats'), diff --git a/web_src/js/features/comp/EasyMDE.js b/web_src/js/features/comp/EasyMDE.js index a030db83a36c2..2979627b0096a 100644 --- a/web_src/js/features/comp/EasyMDE.js +++ b/web_src/js/features/comp/EasyMDE.js @@ -78,7 +78,7 @@ export async function createCommentEasyMDE(textarea, easyMDEOptions = {}) { const inputField = easyMDE.codemirror.getInputField(); easyMDE.codemirror.on('change', (...args) => { - easyMDEOptions?.onChange(...args); + easyMDEOptions?.onChange?.(...args); }); easyMDE.codemirror.setOption('extraKeys', { 'Cmd-Enter': codeMirrorQuickSubmit, diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 4fc8bb5e62d90..4163fb120e527 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -418,6 +418,22 @@ function assignMenuAttributes(menu) { return id; } +export async function handleReply($el) { + hideElem($el); + const form = $el.closest('.comment-code-cloud').find('.comment-form'); + form.removeClass('gt-hidden'); + const $textarea = form.find('textarea'); + let easyMDE = getAttachedEasyMDE($textarea); + if (!easyMDE) { + await attachTribute($textarea.get(), {mentions: true, emoji: true}); + easyMDE = await createCommentEasyMDE($textarea); + } + $textarea.focus(); + easyMDE.codemirror.focus(); + assignMenuAttributes(form.find('.menu')); + return easyMDE; +} + export function initRepoPullRequestReview() { if (window.location.hash && window.location.hash.startsWith('#issuecomment-')) { const commentDiv = $(window.location.hash); @@ -455,19 +471,7 @@ export function initRepoPullRequestReview() { $(document).on('click', 'button.comment-form-reply', async function (e) { e.preventDefault(); - - hideElem($(this)); - const form = $(this).closest('.comment-code-cloud').find('.comment-form'); - form.removeClass('gt-hidden'); - const $textarea = form.find('textarea'); - let easyMDE = getAttachedEasyMDE($textarea); - if (!easyMDE) { - await attachTribute($textarea.get(), {mentions: true, emoji: true}); - easyMDE = await createCommentEasyMDE($textarea); - } - $textarea.focus(); - easyMDE.codemirror.focus(); - assignMenuAttributes(form.find('.menu')); + await handleReply($(this)); }); const $reviewBox = $('.review-box-panel'); diff --git a/web_src/js/features/repo-legacy.js b/web_src/js/features/repo-legacy.js index 118475e32e2cf..22113af169e37 100644 --- a/web_src/js/features/repo-legacy.js +++ b/web_src/js/features/repo-legacy.js @@ -6,7 +6,7 @@ import { initRepoIssueBranchSelect, initRepoIssueCodeCommentCancel, initRepoIssueCommentDelete, initRepoIssueComments, initRepoIssueDependencyDelete, initRepoIssueReferenceIssue, initRepoIssueStatusButton, initRepoIssueTitleEdit, initRepoIssueWipToggle, - initRepoPullRequestUpdate, updateIssuesMeta, + initRepoPullRequestUpdate, updateIssuesMeta, handleReply } from './repo-issue.js'; import {initUnicodeEscapeButton} from './repo-unicode-escape.js'; import {svg} from '../svg.js'; @@ -232,7 +232,7 @@ export function initRepoCommentForm() { $(this).parent().find('.item').each(function () { $(this).removeClass('checked'); - $(this).find('.octicon').addClass('invisible'); + $(this).find('.octicon-check').addClass('invisible'); }); if (selector === 'select-reviewers-modify' || selector === 'select-assignees-modify') { @@ -613,15 +613,15 @@ function initRepoIssueCommentEdit() { $(document).on('click', '.edit-content', onEditContent); // Quote reply - $(document).on('click', '.quote-reply', function (event) { + $(document).on('click', '.quote-reply', async function (event) { + event.preventDefault(); const target = $(this).data('target'); const quote = $(`#${target}`).text().replace(/\n/g, '\n> '); const content = `> ${quote}\n\n`; let easyMDE; if ($(this).hasClass('quote-reply-diff')) { - const $parent = $(this).closest('.comment-code-cloud'); - $parent.find('button.comment-form-reply').trigger('click'); - easyMDE = getAttachedEasyMDE($parent.find('[name="content"]')); + const $replyBtn = $(this).closest('.comment-code-cloud').find('button.comment-form-reply'); + easyMDE = await handleReply($replyBtn); } else { // for normal issue/comment page easyMDE = getAttachedEasyMDE($('#comment-form .edit_area')); @@ -637,6 +637,5 @@ function initRepoIssueCommentEdit() { easyMDE.codemirror.setCursor(easyMDE.codemirror.lineCount(), 0); }); } - event.preventDefault(); }); } diff --git a/web_src/js/webcomponents/README.md b/web_src/js/webcomponents/README.md new file mode 100644 index 0000000000000..eabbc24ad1da1 --- /dev/null +++ b/web_src/js/webcomponents/README.md @@ -0,0 +1,19 @@ +# Web Components + +This `webcomponents` directory contains the source code for the web components used in the Gitea Web UI. + +https://developer.mozilla.org/en-US/docs/Web/Web_Components + +# Guidelines + +* These components are loaded in `` (before DOM body), + so they should have their own dependencies and should be very light, + then they won't affect the page loading time too much. +* If the component is not a public one, it's suggested to have its own `Gitea` or `gitea-` prefix to avoid conflicts. + +# TODO + +There are still some components that are not migrated to web components yet: + +* `` +* `