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/models/issues/issue.go b/models/issues/issue.go index dc9e5c5acd1..50c9b771199 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -1572,6 +1572,7 @@ type IssueStatsOptions struct { RepoID int64 Labels string MilestoneID int64 + ProjectID int64 AssigneeID int64 MentionedID int64 PosterID int64 @@ -1650,6 +1651,11 @@ func getIssueStatsChunk(opts *IssueStatsOptions, issueIDs []int64) (*IssueStats, sess.And("issue.milestone_id = ?", opts.MilestoneID) } + if opts.ProjectID > 0 { + sess.Join("INNER", "project_issue", "issue.id = project_issue.issue_id"). + And("project_issue.project_id=?", opts.ProjectID) + } + if opts.AssigneeID > 0 { applyAssigneeCondition(sess, opts.AssigneeID) } 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/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 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 } 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, }) } 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/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 3a8adb87ffa..905495984ac 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 @@ -322,6 +328,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. @@ -1299,6 +1306,8 @@ issues.filter_label_exclude = `Use alt + click/enter t issues.filter_label_no_select = All labels issues.filter_milestone = Milestone issues.filter_milestone_no_select = All milestones +issues.filter_project = Project +issues.filter_project_no_select = All projects issues.filter_assignee = Assignee issues.filter_assginee_no_select = All assignees issues.filter_poster = Author 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 cd08aae4145..eef2a642440 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). @@ -1221,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/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/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/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/routers/web/repo/issue.go b/routers/web/repo/issue.go index edd9821ac78..ed7461bbc66 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -203,6 +203,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti RepoID: repo.ID, Labels: selectLabels, MilestoneID: milestoneID, + ProjectID: projectID, AssigneeID: assigneeID, MentionedID: mentionedID, PosterID: posterID, @@ -362,18 +363,16 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti return 0 } - if ctx.Repo.CanWriteIssuesOrPulls(ctx.Params(":type") == "pulls") { - projects, _, err := project_model.FindProjects(ctx, project_model.SearchOptions{ - RepoID: repo.ID, - Type: project_model.TypeRepository, - IsClosed: util.OptionalBoolOf(isShowClosed), - }) - if err != nil { - ctx.ServerError("GetProjects", err) - return - } - ctx.Data["Projects"] = projects + projects, _, err := project_model.FindProjects(ctx, project_model.SearchOptions{ + RepoID: repo.ID, + Type: project_model.TypeRepository, + IsClosed: util.OptionalBoolOf(isShowClosed), + }) + if err != nil { + ctx.ServerError("FindProjects", err) + return } + ctx.Data["Projects"] = projects ctx.Data["IssueStats"] = issueStats ctx.Data["SelLabelIDs"] = labelIDs @@ -381,6 +380,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti ctx.Data["ViewType"] = viewType ctx.Data["SortType"] = sortType ctx.Data["MilestoneID"] = milestoneID + ctx.Data["ProjectID"] = projectID ctx.Data["AssigneeID"] = assigneeID ctx.Data["PosterID"] = posterID ctx.Data["IsShowClosed"] = isShowClosed @@ -397,6 +397,7 @@ func issues(ctx *context.Context, milestoneID, projectID int64, isPullOption uti pager.AddParam(ctx, "state", "State") pager.AddParam(ctx, "labels", "SelectLabels") pager.AddParam(ctx, "milestone", "MilestoneID") + pager.AddParam(ctx, "project", "ProjectID") pager.AddParam(ctx, "assignee", "AssigneeID") pager.AddParam(ctx, "poster", "PosterID") ctx.Data["Page"] = pager @@ -2352,6 +2353,8 @@ func SearchIssues(ctx *context.Context) { includedMilestones = strings.Split(milestones, ",") } + projectID := ctx.FormInt64("project") + // this api is also used in UI, // so the default limit is set to fit UI needs limit := ctx.FormInt("limit") @@ -2374,6 +2377,7 @@ func SearchIssues(ctx *context.Context) { IssueIDs: issueIDs, IncludedLabelNames: includedLabelNames, IncludeMilestones: includedMilestones, + ProjectID: projectID, SortType: "priorityrepo", PriorityRepoID: ctx.FormInt64("priority_repo_id"), IsPull: isPull, @@ -2511,6 +2515,8 @@ func ListIssues(ctx *context.Context) { } } + projectID := ctx.FormInt64("project") + listOptions := db.ListOptions{ Page: ctx.FormInt("page"), PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), @@ -2550,6 +2556,7 @@ func ListIssues(ctx *context.Context) { IssueIDs: issueIDs, LabelIDs: labelIDs, MilestoneIDs: mileIDs, + ProjectID: projectID, IsPull: isPull, UpdatedBeforeUnix: before, UpdatedAfterUnix: since, 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/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)) 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 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 @@ -