From 4d072a4c4e47140550df18edf35d7243ae5edcbd Mon Sep 17 00:00:00 2001 From: JakobDev Date: Thu, 26 Jan 2023 17:33:47 +0100 Subject: [PATCH 01/14] Add API endpoint to get latest release (#21267) This PR adds a new API endpoint to get the latest stable release of a repo, similar to [GitHub API](https://docs.github.com/en/rest/releases/releases#get-the-latest-release). --- routers/api/v1/api.go | 1 + routers/api/v1/repo/release.go | 41 ++++++++++++++++++++++++++ templates/swagger/v1_json.tmpl | 36 ++++++++++++++++++++++ tests/integration/api_releases_test.go | 18 +++++++++++ 4 files changed, 96 insertions(+) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index cd08aae4145..21bc2e2de4d 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1011,6 +1011,7 @@ func Routes(ctx gocontext.Context) *web.Route { m.Group("/releases", func() { m.Combo("").Get(repo.ListReleases). Post(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.CreateReleaseOption{}), repo.CreateRelease) + m.Combo("/latest").Get(repo.GetLatestRelease) m.Group("/{id}", func() { m.Combo("").Get(repo.GetRelease). Patch(reqToken(auth_model.AccessTokenScopeRepo), reqRepoWriter(unit.TypeReleases), context.ReferencesGitRepo(), bind(api.EditReleaseOption{}), repo.EditRelease). diff --git a/routers/api/v1/repo/release.go b/routers/api/v1/repo/release.go index d0b20102f7a..c01e66150fe 100644 --- a/routers/api/v1/repo/release.go +++ b/routers/api/v1/repo/release.go @@ -67,6 +67,47 @@ func GetRelease(ctx *context.APIContext) { ctx.JSON(http.StatusOK, convert.ToRelease(release)) } +// GetLatestRelease gets the most recent non-prerelease, non-draft release of a repository, sorted by created_at +func GetLatestRelease(ctx *context.APIContext) { + // swagger:operation GET /repos/{owner}/{repo}/releases/latest repository repoGetLatestRelease + // --- + // summary: Gets the most recent non-prerelease, non-draft release of a repository, sorted by created_at + // produces: + // - application/json + // parameters: + // - name: owner + // in: path + // description: owner of the repo + // type: string + // required: true + // - name: repo + // in: path + // description: name of the repo + // type: string + // required: true + // responses: + // "200": + // "$ref": "#/responses/Release" + // "404": + // "$ref": "#/responses/notFound" + release, err := repo_model.GetLatestReleaseByRepoID(ctx.Repo.Repository.ID) + if err != nil && !repo_model.IsErrReleaseNotExist(err) { + ctx.Error(http.StatusInternalServerError, "GetLatestRelease", err) + return + } + if err != nil && repo_model.IsErrReleaseNotExist(err) || + release.IsTag || release.RepoID != ctx.Repo.Repository.ID { + ctx.NotFound() + return + } + + if err := release.LoadAttributes(ctx); err != nil { + ctx.Error(http.StatusInternalServerError, "LoadAttributes", err) + return + } + ctx.JSON(http.StatusOK, convert.ToRelease(release)) +} + // ListReleases list a repository's releases func ListReleases(ctx *context.APIContext) { // swagger:operation GET /repos/{owner}/{repo}/releases repository repoListReleases diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 76d02d825fd..cd64b7070ff 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -9776,6 +9776,42 @@ } } }, + "/repos/{owner}/{repo}/releases/latest": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "repository" + ], + "summary": "Gets the most recent non-prerelease, non-draft release of a repository, sorted by created_at", + "operationId": "repoGetLatestRelease", + "parameters": [ + { + "type": "string", + "description": "owner of the repo", + "name": "owner", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of the repo", + "name": "repo", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/Release" + }, + "404": { + "$ref": "#/responses/notFound" + } + } + } + }, "/repos/{owner}/{repo}/releases/tags/{tag}": { "get": { "produces": [ diff --git a/tests/integration/api_releases_test.go b/tests/integration/api_releases_test.go index d7f2a1b8b1b..aa5816ad029 100644 --- a/tests/integration/api_releases_test.go +++ b/tests/integration/api_releases_test.go @@ -176,6 +176,24 @@ func TestAPICreateReleaseToDefaultBranchOnExistingTag(t *testing.T) { createNewReleaseUsingAPI(t, session, token, owner, repo, "v0.0.1", "", "v0.0.1", "test") } +func TestAPIGetLatestRelease(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}) + + urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/releases/latest", + owner.Name, repo.Name) + + req := NewRequestf(t, "GET", urlStr) + resp := MakeRequest(t, req, http.StatusOK) + + var release *api.Release + DecodeJSON(t, resp, &release) + + assert.Equal(t, "testing-release", release.Title) +} + func TestAPIGetReleaseByTag(t *testing.T) { defer tests.PrepareTestEnv(t)() From 4f8c0eba9a084c43654e50a87b86936fa29fceb4 Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Fri, 27 Jan 2023 03:44:34 +0900 Subject: [PATCH 02/14] set org visibility class to basic in header (#22605) Fixes https://github.com/go-gitea/gitea/issues/22601 At people and team page, we have red private tag or orange limited tag, but at repo page, it is gray (basic). I think it is better to set them into same color (basic). --- templates/org/header.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/org/header.tmpl b/templates/org/header.tmpl index 582a0c8fb39..1102610e9ec 100644 --- a/templates/org/header.tmpl +++ b/templates/org/header.tmpl @@ -6,8 +6,8 @@ {{avatar . 100}} {{.DisplayName}} - {{if .Visibility.IsLimited}}
{{$.locale.Tr "org.settings.visibility.limited_shortname"}}
{{end}} - {{if .Visibility.IsPrivate}}
{{$.locale.Tr "org.settings.visibility.private_shortname"}}
{{end}} + {{if .Visibility.IsLimited}}
{{$.locale.Tr "org.settings.visibility.limited_shortname"}}
{{end}} + {{if .Visibility.IsPrivate}}
{{$.locale.Tr "org.settings.visibility.private_shortname"}}
{{end}}
From 642db3c8f7d2faf9ff02867cf1e1287fa0e1d593 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Thu, 26 Jan 2023 14:36:15 -0600 Subject: [PATCH 03/14] Fix `delete_repo` in template (#22606) Currently the value doesn't match the model, so selecting it results in a 500. https://github.com/go-gitea/gitea/blob/e8ac6a9aeacf0adf21982abc51baa8938e5dd6bb/models/auth/token_scope.go#L42 Signed-off-by: jolheiser --- templates/user/settings/applications.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/user/settings/applications.tmpl b/templates/user/settings/applications.tmpl index c1503bb535b..5cea142238f 100644 --- a/templates/user/settings/applications.tmpl +++ b/templates/user/settings/applications.tmpl @@ -165,7 +165,7 @@
- +
From 2903afb78f77ed94c0515a6e58e27c23a13f2671 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Thu, 26 Jan 2023 23:45:49 -0500 Subject: [PATCH 04/14] Allow issue templates to not render title (#22589) This adds a yaml attribute that will allow the option for when markdown is rendered that the title will be not included in the output Based on work from @brechtvl --- modules/issue/template/template.go | 11 ++++++++++- modules/issue/template/template_test.go | 3 +-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/issue/template/template.go b/modules/issue/template/template.go index f8bce3f4651..0f19d87e8d5 100644 --- a/modules/issue/template/template.go +++ b/modules/issue/template/template.go @@ -259,7 +259,9 @@ func (f *valuedField) WriteTo(builder *strings.Builder) { } // write label - _, _ = fmt.Fprintf(builder, "### %s\n\n", f.Label()) + if !f.HideLabel() { + _, _ = fmt.Fprintf(builder, "### %s\n\n", f.Label()) + } blankPlaceholder := "_No response_\n" @@ -311,6 +313,13 @@ func (f *valuedField) Label() string { return "" } +func (f *valuedField) HideLabel() bool { + if label, ok := f.Attributes["hide_label"].(bool); ok { + return label + } + return false +} + func (f *valuedField) Render() string { if render, ok := f.Attributes["render"].(string); ok { return render diff --git a/modules/issue/template/template_test.go b/modules/issue/template/template_test.go index 0845642cd39..0cdddd0c85f 100644 --- a/modules/issue/template/template_test.go +++ b/modules/issue/template/template_test.go @@ -640,6 +640,7 @@ body: description: Description of input placeholder: Placeholder of input value: Value of input + hide_label: true validations: required: true is_number: true @@ -681,8 +682,6 @@ body: ` + "```bash\nValue of id2\n```" + ` -### Label of input - Value of id3 ### Label of dropdown From 5ff037ef51090b9ac8f521466d99456b236926be Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 27 Jan 2023 13:56:00 +0100 Subject: [PATCH 05/14] Show migration validation error (#22619) Discord request: https://discord.com/channels/322538954119184384/322910365237248000/1067083214096703488 If there is a json schema validation error the full file content gets dumped into the log. That does not help and may be a lot of data. This PR prints the schema validation error message instead. --- modules/migration/file_format.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/migration/file_format.go b/modules/migration/file_format.go index f319f02ef17..04e5d769813 100644 --- a/modules/migration/file_format.go +++ b/modules/migration/file_format.go @@ -76,7 +76,7 @@ func validate(bs []byte, datatype interface{}, isJSON bool) error { } err = sch.Validate(v) if err != nil { - log.Error("migration validation with %s failed for\n%s", schemaFilename, string(bs)) + log.Error("migration validation with %s failed:\n%#v", schemaFilename, err) } return err } From 51a92cb8218b6702a5a0c8f921eda02456332748 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 27 Jan 2023 15:12:18 +0100 Subject: [PATCH 06/14] Use `--index-url` in PyPi description (#22620) Fixes #22616 Co-authored-by: zeripath --- docs/content/doc/packages/pypi.en-us.md | 2 ++ templates/package/content/pypi.tmpl | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/content/doc/packages/pypi.en-us.md b/docs/content/doc/packages/pypi.en-us.md index 588df71d60c..ec2475aea34 100644 --- a/docs/content/doc/packages/pypi.en-us.md +++ b/docs/content/doc/packages/pypi.en-us.md @@ -77,6 +77,8 @@ For example: pip install --index-url https://testuser:password123@gitea.example.com/api/packages/testuser/pypi/simple --no-deps test_package ``` +You can use `--extra-index-url` instead of `--index-url` but that makes you vulnerable to dependency confusion attacks because `pip` checks the official PyPi repository for the package before it checks the specified custom repository. Read the `pip` docs for more information. + ## Supported commands ``` diff --git a/templates/package/content/pypi.tmpl b/templates/package/content/pypi.tmpl index 1cce31f537b..1ae243813de 100644 --- a/templates/package/content/pypi.tmpl +++ b/templates/package/content/pypi.tmpl @@ -4,7 +4,7 @@
-
pip install --extra-index-url {{AppUrl}}api/packages/{{.PackageDescriptor.Owner.Name}}/pypi/simple {{.PackageDescriptor.Package.Name}}
+
pip install --index-url {{AppUrl}}api/packages/{{.PackageDescriptor.Owner.Name}}/pypi/simple {{.PackageDescriptor.Package.Name}}
From 74466eb1334386148caa50ccfd18b0f218e413d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Leopoldo=20Sologuren=20Guti=C3=A9rrez?= Date: Fri, 27 Jan 2023 19:40:17 -0300 Subject: [PATCH 07/14] Fixes accessibility of empty repository commit status (#22632) Avoid empty labelled anchor in repo without commits. Contributed by @forgejo. --- templates/repo/commit_statuses.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/commit_statuses.tmpl b/templates/repo/commit_statuses.tmpl index d6827090824..4fe644ff0a4 100644 --- a/templates/repo/commit_statuses.tmpl +++ b/templates/repo/commit_statuses.tmpl @@ -1,4 +1,4 @@ -{{template "repo/commit_status" .Status}} +{{if eq (len .Statuses) 1}}{{$status := index .Statuses 0}}{{if $status.TargetURL}}{{template "repo/commit_status" .Status}}{{end}}{{end}}
{{range .Statuses}} From 95d9fbdcf39db7595a23a69ca48bfb49b845874a Mon Sep 17 00:00:00 2001 From: "Otto Richter (fnetX)" Date: Sat, 28 Jan 2023 08:59:46 +0100 Subject: [PATCH 08/14] Fix error on account activation with wrong passwd (#22609) On activating local accounts, the error message didn't differentiate between using a wrong or expired token, or a wrong password. The result could already be obtained from the behaviour (different screens were presented), but the error message was misleading and lead to confusion for new users on Codeberg with Forgejo. Now, entering a wrong password for a valid token prints a different error message. The problem was introduced in 0f14f69e6070c9aca09f57c419e7d6007d0e520b. Co-authored-by: Lunny Xiao --- options/locale/locale_en-US.ini | 1 + routers/web/auth/auth.go | 6 +++--- templates/user/auth/activate.tmpl | 4 +++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 43a8aeb08eb..6ccbbc1c013 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -322,6 +322,7 @@ email_not_associate = The email address is not associated with any account. send_reset_mail = Send Account Recovery Email reset_password = Account Recovery invalid_code = Your confirmation code is invalid or has expired. +invalid_password = Your password does not match the password that was used to create the account. reset_password_helper = Recover Account reset_password_wrong_user = You are signed in as %s, but the account recovery link is for %s password_too_short = Password length cannot be less than %d characters. diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 71a62bce654..48b7dc6862a 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -633,7 +633,7 @@ func Activate(ctx *context.Context) { user := user_model.VerifyUserActiveCode(code) // if code is wrong if user == nil { - ctx.Data["IsActivateFailed"] = true + ctx.Data["IsCodeInvalid"] = true ctx.HTML(http.StatusOK, TplActivate) return } @@ -660,7 +660,7 @@ func ActivatePost(ctx *context.Context) { user := user_model.VerifyUserActiveCode(code) // if code is wrong if user == nil { - ctx.Data["IsActivateFailed"] = true + ctx.Data["IsCodeInvalid"] = true ctx.HTML(http.StatusOK, TplActivate) return } @@ -675,7 +675,7 @@ func ActivatePost(ctx *context.Context) { return } if !user.ValidatePassword(password) { - ctx.Data["IsActivateFailed"] = true + ctx.Data["IsPasswordInvalid"] = true ctx.HTML(http.StatusOK, TplActivate) return } diff --git a/templates/user/auth/activate.tmpl b/templates/user/auth/activate.tmpl index eba9e3229b2..ef72ef1e545 100644 --- a/templates/user/auth/activate.tmpl +++ b/templates/user/auth/activate.tmpl @@ -30,8 +30,10 @@ {{else if .IsSendRegisterMail}}

{{.locale.Tr "auth.confirmation_mail_sent_prompt" (.Email|Escape) .ActiveCodeLives | Str2html}}

- {{else if .IsActivateFailed}} + {{else if .IsCodeInvalid}}

{{.locale.Tr "auth.invalid_code"}}

+ {{else if .IsPasswordInvalid}} +

{{.locale.Tr "auth.invalid_password"}}

{{else if .ManualActivationOnly}}

{{.locale.Tr "auth.manual_activation_only"}}

{{else}} From 48f5d519088c2b33b48eb35f6ef3261e3ec677a1 Mon Sep 17 00:00:00 2001 From: a1012112796 <1012112796@qq.com> Date: Sat, 28 Jan 2023 17:28:55 +0800 Subject: [PATCH 09/14] fix permission check for creating comment while mail (#22524) only creating comment on locked issue request write permission, for others, read permission is enough. related to https://github.com/go-gitea/gitea/pull/22056 /cc @KN4CK3R --------- Signed-off-by: a1012112796 <1012112796@qq.com> Co-authored-by: delvh Co-authored-by: Lunny Xiao --- services/mailer/incoming/incoming_handler.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/services/mailer/incoming/incoming_handler.go b/services/mailer/incoming/incoming_handler.go index 173b362a550..d89a5eab3d1 100644 --- a/services/mailer/incoming/incoming_handler.go +++ b/services/mailer/incoming/incoming_handler.go @@ -71,11 +71,17 @@ func (h *ReplyHandler) Handle(ctx context.Context, content *MailContent, doer *u return err } - if !perm.CanWriteIssuesOrPulls(issue.IsPull) || issue.IsLocked && !doer.IsAdmin { + // Locked issues require write permissions + if issue.IsLocked && !perm.CanWriteIssuesOrPulls(issue.IsPull) && !doer.IsAdmin { log.Debug("can't write issue or pull") return nil } + if !perm.CanReadIssuesOrPulls(issue.IsPull) { + log.Debug("can't read issue or pull") + return nil + } + switch r := ref.(type) { case *issues_model.Issue: attachmentIDs := make([]string, 0, len(content.Attachments)) From e9cd18b5578cdf84264f3d69a5f166a33d74b4e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Leopoldo=20Sologuren=20Guti=C3=A9rrez?= Date: Sat, 28 Jan 2023 08:16:46 -0300 Subject: [PATCH 10/14] Link issue and pull requests status change in UI notifications directly to their event in the timelined view. (#22627) Adding the related comment to the issue and pull request status change in the UI notifications allows to navigate directly to the specific event in its dedicated view, easing the reading of last comments and to the editor for additional comments if desired. --- modules/notification/ui/ui.go | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/notification/ui/ui.go b/modules/notification/ui/ui.go index 4b85f17b6c4..73ea9227482 100644 --- a/modules/notification/ui/ui.go +++ b/modules/notification/ui/ui.go @@ -97,6 +97,7 @@ func (ns *notificationService) NotifyIssueChangeStatus(ctx context.Context, doer _ = ns.issueQueue.Push(issueNotificationOpts{ IssueID: issue.ID, NotificationAuthorID: doer.ID, + CommentID: actionComment.ID, }) } From 78e6b21c1a4c9867dd3054d6c167cc80407b020d Mon Sep 17 00:00:00 2001 From: zeripath Date: Sat, 28 Jan 2023 15:54:40 +0000 Subject: [PATCH 11/14] Improve checkIfPRContentChanged (#22611) The code for checking if a commit has caused a change in a PR is extremely inefficient and affects the head repository instead of using a temporary repository. This PR therefore makes several significant improvements: * A temporary repo like that used in merging. * The diff code is then significant improved to use a three-way diff instead of comparing diffs (possibly binary) line-by-line - in memory... Ref #22578 Signed-off-by: Andrew Thornton --- modules/util/io.go | 22 +++++++++++ services/pull/pull.go | 88 ++++++++++++++++++------------------------- 2 files changed, 59 insertions(+), 51 deletions(-) diff --git a/modules/util/io.go b/modules/util/io.go index e5d7561beff..69b1d63145a 100644 --- a/modules/util/io.go +++ b/modules/util/io.go @@ -4,6 +4,7 @@ package util import ( + "errors" "io" ) @@ -17,3 +18,24 @@ func ReadAtMost(r io.Reader, buf []byte) (n int, err error) { } return n, err } + +// ErrNotEmpty is an error reported when there is a non-empty reader +var ErrNotEmpty = errors.New("not-empty") + +// IsEmptyReader reads a reader and ensures it is empty +func IsEmptyReader(r io.Reader) (err error) { + var buf [1]byte + + for { + n, err := r.Read(buf[:]) + if err != nil { + if err == io.EOF { + return nil + } + return err + } + if n > 0 { + return ErrNotEmpty + } + } +} diff --git a/services/pull/pull.go b/services/pull/pull.go index 7f81def6d66..c983c4f3e78 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -4,14 +4,12 @@ package pull import ( - "bufio" - "bytes" "context" "fmt" "io" + "os" "regexp" "strings" - "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" @@ -29,6 +27,7 @@ import ( repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/sync" + "code.gitea.io/gitea/modules/util" issue_service "code.gitea.io/gitea/services/issue" ) @@ -351,69 +350,56 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string, // checkIfPRContentChanged checks if diff to target branch has changed by push // A commit can be considered to leave the PR untouched if the patch/diff with its merge base is unchanged func checkIfPRContentChanged(ctx context.Context, pr *issues_model.PullRequest, oldCommitID, newCommitID string) (hasChanged bool, err error) { - if err = pr.LoadHeadRepo(ctx); err != nil { - return false, fmt.Errorf("LoadHeadRepo: %w", err) - } else if pr.HeadRepo == nil { - // corrupt data assumed changed - return true, nil + tmpBasePath, err := createTemporaryRepo(ctx, pr) + if err != nil { + log.Error("CreateTemporaryRepo: %v", err) + return false, err } + defer func() { + if err := repo_module.RemoveTemporaryPath(tmpBasePath); err != nil { + log.Error("checkIfPRContentChanged: RemoveTemporaryPath: %s", err) + } + }() - if err = pr.LoadBaseRepo(ctx); err != nil { - return false, fmt.Errorf("LoadBaseRepo: %w", err) - } - - headGitRepo, err := git.OpenRepository(ctx, pr.HeadRepo.RepoPath()) + tmpRepo, err := git.OpenRepository(ctx, tmpBasePath) if err != nil { return false, fmt.Errorf("OpenRepository: %w", err) } - defer headGitRepo.Close() + defer tmpRepo.Close() - // Add a temporary remote. - tmpRemote := "checkIfPRContentChanged-" + fmt.Sprint(time.Now().UnixNano()) - if err = headGitRepo.AddRemote(tmpRemote, pr.BaseRepo.RepoPath(), true); err != nil { - return false, fmt.Errorf("AddRemote: %s/%s-%s: %w", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err) - } - defer func() { - if err := headGitRepo.RemoveRemote(tmpRemote); err != nil { - log.Error("checkIfPRContentChanged: RemoveRemote: %s/%s-%s: %v", pr.HeadRepo.OwnerName, pr.HeadRepo.Name, tmpRemote, err) - } - }() - // To synchronize repo and get a base ref - _, base, err := headGitRepo.GetMergeBase(tmpRemote, pr.BaseBranch, pr.HeadBranch) + // Find the merge-base + _, base, err := tmpRepo.GetMergeBase("", "base", "tracking") if err != nil { return false, fmt.Errorf("GetMergeBase: %w", err) } - diffBefore := &bytes.Buffer{} - diffAfter := &bytes.Buffer{} - if err := headGitRepo.GetDiffFromMergeBase(base, oldCommitID, diffBefore); err != nil { - // If old commit not found, assume changed. - log.Debug("GetDiffFromMergeBase: %v", err) - return true, nil - } - if err := headGitRepo.GetDiffFromMergeBase(base, newCommitID, diffAfter); err != nil { - // New commit should be found - return false, fmt.Errorf("GetDiffFromMergeBase: %w", err) + cmd := git.NewCommand(ctx, "diff", "--name-only", "-z").AddDynamicArguments(newCommitID, oldCommitID, base) + stdoutReader, stdoutWriter, err := os.Pipe() + if err != nil { + return false, fmt.Errorf("unable to open pipe for to run diff: %w", err) } - diffBeforeLines := bufio.NewScanner(diffBefore) - diffAfterLines := bufio.NewScanner(diffAfter) - - for diffBeforeLines.Scan() && diffAfterLines.Scan() { - if strings.HasPrefix(diffBeforeLines.Text(), "index") && strings.HasPrefix(diffAfterLines.Text(), "index") { - // file hashes can change without the diff changing - continue - } else if strings.HasPrefix(diffBeforeLines.Text(), "@@") && strings.HasPrefix(diffAfterLines.Text(), "@@") { - // the location of the difference may change - continue - } else if !bytes.Equal(diffBeforeLines.Bytes(), diffAfterLines.Bytes()) { + if err := cmd.Run(&git.RunOpts{ + Dir: tmpBasePath, + Stdout: stdoutWriter, + PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { + _ = stdoutWriter.Close() + defer func() { + _ = stdoutReader.Close() + }() + return util.IsEmptyReader(stdoutReader) + }, + }); err != nil { + if err == util.ErrNotEmpty { return true, nil } - } - if diffBeforeLines.Scan() || diffAfterLines.Scan() { - // Diffs not of equal length - return true, nil + log.Error("Unable to run diff on %s %s %s in tempRepo for PR[%d]%s/%s...%s/%s: Error: %v", + newCommitID, oldCommitID, base, + pr.ID, pr.BaseRepo.FullName(), pr.BaseBranch, pr.HeadRepo.FullName(), pr.HeadBranch, + err) + + return false, fmt.Errorf("Unable to run git diff --name-only -z %s %s %s: %w", newCommitID, oldCommitID, base, err) } return false, nil From c0015979a692b795bcf7416196bec01c375d7aa2 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Sun, 29 Jan 2023 02:12:10 +0800 Subject: [PATCH 12/14] Support system hook API (#14537) This add system hook API --- models/webhook/webhook.go | 76 -------------- models/webhook/webhook_system.go | 81 ++++++++++++++ routers/api/v1/admin/hooks.go | 174 +++++++++++++++++++++++++++++++ routers/api/v1/api.go | 7 ++ routers/api/v1/utils/hook.go | 38 +++++++ routers/web/admin/hooks.go | 2 +- routers/web/repo/webhook.go | 2 +- templates/swagger/v1_json.tmpl | 148 ++++++++++++++++++++++++++ 8 files changed, 450 insertions(+), 78 deletions(-) create mode 100644 models/webhook/webhook_system.go create mode 100644 routers/api/v1/admin/hooks.go diff --git a/models/webhook/webhook.go b/models/webhook/webhook.go index c24404c42cf..64119f14949 100644 --- a/models/webhook/webhook.go +++ b/models/webhook/webhook.go @@ -463,41 +463,6 @@ func CountWebhooksByOpts(opts *ListWebhookOptions) (int64, error) { return db.GetEngine(db.DefaultContext).Where(opts.toCond()).Count(&Webhook{}) } -// GetDefaultWebhooks returns all admin-default webhooks. -func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) { - webhooks := make([]*Webhook, 0, 5) - return webhooks, db.GetEngine(ctx). - Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false). - Find(&webhooks) -} - -// GetSystemOrDefaultWebhook returns admin system or default webhook by given ID. -func GetSystemOrDefaultWebhook(id int64) (*Webhook, error) { - webhook := &Webhook{ID: id} - has, err := db.GetEngine(db.DefaultContext). - Where("repo_id=? AND org_id=?", 0, 0). - Get(webhook) - if err != nil { - return nil, err - } else if !has { - return nil, ErrWebhookNotExist{ID: id} - } - return webhook, nil -} - -// GetSystemWebhooks returns all admin system webhooks. -func GetSystemWebhooks(ctx context.Context, isActive util.OptionalBool) ([]*Webhook, error) { - webhooks := make([]*Webhook, 0, 5) - if isActive.IsNone() { - return webhooks, db.GetEngine(ctx). - Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true). - Find(&webhooks) - } - return webhooks, db.GetEngine(ctx). - Where("repo_id=? AND org_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.IsTrue()). - Find(&webhooks) -} - // UpdateWebhook updates information of webhook. func UpdateWebhook(w *Webhook) error { _, err := db.GetEngine(db.DefaultContext).ID(w.ID).AllCols().Update(w) @@ -545,44 +510,3 @@ func DeleteWebhookByOrgID(orgID, id int64) error { OrgID: orgID, }) } - -// DeleteDefaultSystemWebhook deletes an admin-configured default or system webhook (where Org and Repo ID both 0) -func DeleteDefaultSystemWebhook(id int64) error { - ctx, committer, err := db.TxContext(db.DefaultContext) - if err != nil { - return err - } - defer committer.Close() - - count, err := db.GetEngine(ctx). - Where("repo_id=? AND org_id=?", 0, 0). - Delete(&Webhook{ID: id}) - if err != nil { - return err - } else if count == 0 { - return ErrWebhookNotExist{ID: id} - } - - if _, err := db.DeleteByBean(ctx, &HookTask{HookID: id}); err != nil { - return err - } - - return committer.Commit() -} - -// CopyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo -func CopyDefaultWebhooksToRepo(ctx context.Context, repoID int64) error { - ws, err := GetDefaultWebhooks(ctx) - if err != nil { - return fmt.Errorf("GetDefaultWebhooks: %w", err) - } - - for _, w := range ws { - w.ID = 0 - w.RepoID = repoID - if err := CreateWebhook(ctx, w); err != nil { - return fmt.Errorf("CreateWebhook: %w", err) - } - } - return nil -} diff --git a/models/webhook/webhook_system.go b/models/webhook/webhook_system.go new file mode 100644 index 00000000000..21dc0406a0d --- /dev/null +++ b/models/webhook/webhook_system.go @@ -0,0 +1,81 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package webhook + +import ( + "context" + "fmt" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/util" +) + +// GetDefaultWebhooks returns all admin-default webhooks. +func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) { + webhooks := make([]*Webhook, 0, 5) + return webhooks, db.GetEngine(ctx). + Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false). + Find(&webhooks) +} + +// GetSystemOrDefaultWebhook returns admin system or default webhook by given ID. +func GetSystemOrDefaultWebhook(ctx context.Context, id int64) (*Webhook, error) { + webhook := &Webhook{ID: id} + has, err := db.GetEngine(ctx). + Where("repo_id=? AND org_id=?", 0, 0). + Get(webhook) + if err != nil { + return nil, err + } else if !has { + return nil, ErrWebhookNotExist{ID: id} + } + return webhook, nil +} + +// GetSystemWebhooks returns all admin system webhooks. +func GetSystemWebhooks(ctx context.Context, isActive util.OptionalBool) ([]*Webhook, error) { + webhooks := make([]*Webhook, 0, 5) + if isActive.IsNone() { + return webhooks, db.GetEngine(ctx). + Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, true). + Find(&webhooks) + } + return webhooks, db.GetEngine(ctx). + Where("repo_id=? AND org_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.IsTrue()). + Find(&webhooks) +} + +// DeleteDefaultSystemWebhook deletes an admin-configured default or system webhook (where Org and Repo ID both 0) +func DeleteDefaultSystemWebhook(ctx context.Context, id int64) error { + return db.WithTx(ctx, func(ctx context.Context) error { + count, err := db.GetEngine(ctx). + Where("repo_id=? AND org_id=?", 0, 0). + Delete(&Webhook{ID: id}) + if err != nil { + return err + } else if count == 0 { + return ErrWebhookNotExist{ID: id} + } + + _, err = db.DeleteByBean(ctx, &HookTask{HookID: id}) + return err + }) +} + +// CopyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo +func CopyDefaultWebhooksToRepo(ctx context.Context, repoID int64) error { + ws, err := GetDefaultWebhooks(ctx) + if err != nil { + return fmt.Errorf("GetDefaultWebhooks: %v", err) + } + + for _, w := range ws { + w.ID = 0 + w.RepoID = repoID + if err := CreateWebhook(ctx, w); err != nil { + return fmt.Errorf("CreateWebhook: %v", err) + } + } + return nil +} diff --git a/routers/api/v1/admin/hooks.go b/routers/api/v1/admin/hooks.go new file mode 100644 index 00000000000..2aed4139f3c --- /dev/null +++ b/routers/api/v1/admin/hooks.go @@ -0,0 +1,174 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package admin + +import ( + "net/http" + + "code.gitea.io/gitea/models/webhook" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/modules/web" + "code.gitea.io/gitea/routers/api/v1/utils" + webhook_service "code.gitea.io/gitea/services/webhook" +) + +// ListHooks list system's webhooks +func ListHooks(ctx *context.APIContext) { + // swagger:operation GET /admin/hooks admin adminListHooks + // --- + // summary: List system's webhooks + // produces: + // - application/json + // parameters: + // - name: page + // in: query + // description: page number of results to return (1-based) + // type: integer + // - name: limit + // in: query + // description: page size of results + // type: integer + // responses: + // "200": + // "$ref": "#/responses/HookList" + + sysHooks, err := webhook.GetSystemWebhooks(ctx, util.OptionalBoolNone) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetSystemWebhooks", err) + return + } + hooks := make([]*api.Hook, len(sysHooks)) + for i, hook := range sysHooks { + h, err := webhook_service.ToHook(setting.AppURL+"/admin", hook) + if err != nil { + ctx.Error(http.StatusInternalServerError, "convert.ToHook", err) + return + } + hooks[i] = h + } + ctx.JSON(http.StatusOK, hooks) +} + +// GetHook get an organization's hook by id +func GetHook(ctx *context.APIContext) { + // swagger:operation GET /admin/hooks/{id} admin adminGetHook + // --- + // summary: Get a hook + // produces: + // - application/json + // parameters: + // - name: id + // in: path + // description: id of the hook to get + // type: integer + // format: int64 + // required: true + // responses: + // "200": + // "$ref": "#/responses/Hook" + + hookID := ctx.ParamsInt64(":id") + hook, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetSystemOrDefaultWebhook", err) + return + } + h, err := webhook_service.ToHook("/admin/", hook) + if err != nil { + ctx.Error(http.StatusInternalServerError, "convert.ToHook", err) + return + } + ctx.JSON(http.StatusOK, h) +} + +// CreateHook create a hook for an organization +func CreateHook(ctx *context.APIContext) { + // swagger:operation POST /admin/hooks admin adminCreateHook + // --- + // summary: Create a hook + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: body + // in: body + // required: true + // schema: + // "$ref": "#/definitions/CreateHookOption" + // responses: + // "201": + // "$ref": "#/responses/Hook" + + form := web.GetForm(ctx).(*api.CreateHookOption) + // TODO in body params + if !utils.CheckCreateHookOption(ctx, form) { + return + } + utils.AddSystemHook(ctx, form) +} + +// EditHook modify a hook of a repository +func EditHook(ctx *context.APIContext) { + // swagger:operation PATCH /admin/hooks/{id} admin adminEditHook + // --- + // summary: Update a hook + // consumes: + // - application/json + // produces: + // - application/json + // parameters: + // - name: id + // in: path + // description: id of the hook to update + // type: integer + // format: int64 + // required: true + // - name: body + // in: body + // schema: + // "$ref": "#/definitions/EditHookOption" + // responses: + // "200": + // "$ref": "#/responses/Hook" + + form := web.GetForm(ctx).(*api.EditHookOption) + + // TODO in body params + hookID := ctx.ParamsInt64(":id") + utils.EditSystemHook(ctx, form, hookID) +} + +// DeleteHook delete a system hook +func DeleteHook(ctx *context.APIContext) { + // swagger:operation DELETE /amdin/hooks/{id} admin adminDeleteHook + // --- + // summary: Delete a hook + // produces: + // - application/json + // parameters: + // - name: id + // in: path + // description: id of the hook to delete + // type: integer + // format: int64 + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + + hookID := ctx.ParamsInt64(":id") + if err := webhook.DeleteDefaultSystemWebhook(ctx, hookID); err != nil { + if webhook.IsErrWebhookNotExist(err) { + ctx.NotFound() + } else { + ctx.Error(http.StatusInternalServerError, "DeleteDefaultSystemWebhook", err) + } + return + } + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 21bc2e2de4d..eef2a642440 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -1222,6 +1222,13 @@ func Routes(ctx gocontext.Context) *web.Route { m.Post("/{username}/{reponame}", admin.AdoptRepository) m.Delete("/{username}/{reponame}", admin.DeleteUnadoptedRepository) }) + m.Group("/hooks", func() { + m.Combo("").Get(admin.ListHooks). + Post(bind(api.CreateHookOption{}), admin.CreateHook) + m.Combo("/{id}").Get(admin.GetHook). + Patch(bind(api.EditHookOption{}), admin.EditHook). + Delete(admin.DeleteHook) + }) }, reqToken(auth_model.AccessTokenScopeSudo), reqSiteAdmin()) m.Group("/topics", func() { diff --git a/routers/api/v1/utils/hook.go b/routers/api/v1/utils/hook.go index 44fba22b5af..f6aaf74aff1 100644 --- a/routers/api/v1/utils/hook.go +++ b/routers/api/v1/utils/hook.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/util" webhook_module "code.gitea.io/gitea/modules/webhook" @@ -67,6 +68,19 @@ func CheckCreateHookOption(ctx *context.APIContext, form *api.CreateHookOption) return true } +// AddSystemHook add a system hook +func AddSystemHook(ctx *context.APIContext, form *api.CreateHookOption) { + hook, ok := addHook(ctx, form, 0, 0) + if ok { + h, err := webhook_service.ToHook(setting.AppSubURL+"/admin", hook) + if err != nil { + ctx.Error(http.StatusInternalServerError, "convert.ToHook", err) + return + } + ctx.JSON(http.StatusCreated, h) + } +} + // AddOrgHook add a hook to an organization. Writes to `ctx` accordingly func AddOrgHook(ctx *context.APIContext, form *api.CreateHookOption) { org := ctx.Org.Organization @@ -196,6 +210,30 @@ func addHook(ctx *context.APIContext, form *api.CreateHookOption, orgID, repoID return w, true } +// EditSystemHook edit system webhook `w` according to `form`. Writes to `ctx` accordingly +func EditSystemHook(ctx *context.APIContext, form *api.EditHookOption, hookID int64) { + hook, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetSystemOrDefaultWebhook", err) + return + } + if !editHook(ctx, form, hook) { + ctx.Error(http.StatusInternalServerError, "editHook", err) + return + } + updated, err := webhook.GetSystemOrDefaultWebhook(ctx, hookID) + if err != nil { + ctx.Error(http.StatusInternalServerError, "GetSystemOrDefaultWebhook", err) + return + } + h, err := webhook_service.ToHook(setting.AppURL+"/admin", updated) + if err != nil { + ctx.Error(http.StatusInternalServerError, "convert.ToHook", err) + return + } + ctx.JSON(http.StatusOK, h) +} + // EditOrgHook edit webhook `w` according to `form`. Writes to `ctx` accordingly func EditOrgHook(ctx *context.APIContext, form *api.EditHookOption, hookID int64) { org := ctx.Org.Organization diff --git a/routers/web/admin/hooks.go b/routers/web/admin/hooks.go index e8db9a3ded7..57cf5f49e5b 100644 --- a/routers/web/admin/hooks.go +++ b/routers/web/admin/hooks.go @@ -60,7 +60,7 @@ func DefaultOrSystemWebhooks(ctx *context.Context) { // DeleteDefaultOrSystemWebhook handler to delete an admin-defined system or default webhook func DeleteDefaultOrSystemWebhook(ctx *context.Context) { - if err := webhook.DeleteDefaultSystemWebhook(ctx.FormInt64("id")); err != nil { + if err := webhook.DeleteDefaultSystemWebhook(ctx, ctx.FormInt64("id")); err != nil { ctx.Flash.Error("DeleteDefaultWebhook: " + err.Error()) } else { ctx.Flash.Success(ctx.Tr("repo.settings.webhook_deletion_success")) diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index 96261af6744..d3826c3f3de 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -591,7 +591,7 @@ func checkWebhook(ctx *context.Context) (*orgRepoCtx, *webhook.Webhook) { } else if orCtx.OrgID > 0 { w, err = webhook.GetWebhookByOrgID(ctx.Org.Organization.ID, ctx.ParamsInt64(":id")) } else if orCtx.IsAdmin { - w, err = webhook.GetSystemOrDefaultWebhook(ctx.ParamsInt64(":id")) + w, err = webhook.GetSystemOrDefaultWebhook(ctx, ctx.ParamsInt64(":id")) } if err != nil || w == nil { if webhook.IsErrWebhookNotExist(err) { diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index cd64b7070ff..726b771cfc7 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -138,6 +138,127 @@ } } }, + "/admin/hooks": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "List system's webhooks", + "operationId": "adminListHooks", + "parameters": [ + { + "type": "integer", + "description": "page number of results to return (1-based)", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "page size of results", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "$ref": "#/responses/HookList" + } + } + }, + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Create a hook", + "operationId": "adminCreateHook", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/CreateHookOption" + } + } + ], + "responses": { + "201": { + "$ref": "#/responses/Hook" + } + } + } + }, + "/admin/hooks/{id}": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Get a hook", + "operationId": "adminGetHook", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the hook to get", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "$ref": "#/responses/Hook" + } + } + }, + "patch": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Update a hook", + "operationId": "adminEditHook", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the hook to update", + "name": "id", + "in": "path", + "required": true + }, + { + "name": "body", + "in": "body", + "schema": { + "$ref": "#/definitions/EditHookOption" + } + } + ], + "responses": { + "200": { + "$ref": "#/responses/Hook" + } + } + } + }, "/admin/orgs": { "get": { "produces": [ @@ -601,6 +722,33 @@ } } }, + "/amdin/hooks/{id}": { + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "admin" + ], + "summary": "Delete a hook", + "operationId": "adminDeleteHook", + "parameters": [ + { + "type": "integer", + "format": "int64", + "description": "id of the hook to delete", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "$ref": "#/responses/empty" + } + } + } + }, "/markdown": { "post": { "consumes": [ From 2b1e47e2a2a5a31a0fc5039ed7dbb192a4a51dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Leopoldo=20Sologuren=20Guti=C3=A9rrez?= Date: Sat, 28 Jan 2023 22:29:10 -0300 Subject: [PATCH 13/14] Improve accessibility of navigation bar and footer (#22635) Added ARIA navigation landmark to navigation bar and aria label for both nav bar and footer. Contributed by @forgejo. --------- Co-authored-by: Lunny Xiao --- options/locale/locale_en-US.ini | 6 ++++++ templates/base/footer_content.tmpl | 6 +++--- templates/base/head_navbar.tmpl | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 6ccbbc1c013..98922db808e 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -111,6 +111,12 @@ never = Never rss_feed = RSS Feed +[aria] +navbar = Navigation Bar +footer = Footer +footer.software = About Software +footer.links = Links + [filter] string.asc = A - Z string.desc = Z - A diff --git a/templates/base/footer_content.tmpl b/templates/base/footer_content.tmpl index 89be6092258..c3b96a0245a 100644 --- a/templates/base/footer_content.tmpl +++ b/templates/base/footer_content.tmpl @@ -1,6 +1,6 @@ -
+
-
+ - {{.locale.Tr "repo.issues.filter_milestone_no_select"}} {{range .Milestones}} - {{.Name}} + {{.Name}} + {{end}} +
+
+ + + @@ -86,9 +104,9 @@ {{svg "octicon-search" 16}}
- {{.locale.Tr "repo.issues.filter_poster_no_select"}} + {{.locale.Tr "repo.issues.filter_poster_no_select"}} {{range .Posters}} - + {{avatar .}} {{.GetDisplayName}} {{end}} @@ -106,9 +124,9 @@ {{svg "octicon-search" 16}}
- {{.locale.Tr "repo.issues.filter_assginee_no_select"}} + {{.locale.Tr "repo.issues.filter_assginee_no_select"}} {{range .Assignees}} - + {{avatar .}} {{.GetDisplayName}} {{end}} @@ -123,12 +141,12 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}}
@@ -141,14 +159,14 @@ {{svg "octicon-triangle-down" 14 "dropdown icon"}}
diff --git a/templates/repo/issue/openclose.tmpl b/templates/repo/issue/openclose.tmpl index 758fc447ce6..9299fe1cb1f 100644 --- a/templates/repo/issue/openclose.tmpl +++ b/templates/repo/issue/openclose.tmpl @@ -1,5 +1,5 @@