From a30c4d0fa713c47e7c605861d1318be929ab7ef6 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Tue, 14 Dec 2021 18:03:38 +0100 Subject: [PATCH 01/78] Add basic logifc for allow edits by maintainers feature and allow updates to PRs --- models/migrations/migrations.go | 2 ++ models/migrations/v204.go | 18 ++++++++++++++++++ models/pull.go | 19 ++++++++++--------- models/repo_permission.go | 19 +++++++++++++++++++ modules/indexer/code/indexer_test.go | 3 ++- modules/indexer/issues/indexer_test.go | 3 ++- modules/indexer/stats/indexer_test.go | 3 ++- options/locale/locale_en-US.ini | 2 ++ routers/web/repo/pull.go | 17 +++++++++-------- services/asymkey/ssh_key_test.go | 1 + services/forms/repo_form.go | 19 ++++++++++--------- services/mailer/mailer_test.go | 1 + services/pull/update.go | 14 ++++++++++++++ services/webhook/main_test.go | 3 ++- templates/repo/issue/new_form.tmpl | 8 ++++++++ .../repo/issue/view_content/sidebar.tmpl | 12 ++++++++++++ 16 files changed, 114 insertions(+), 30 deletions(-) create mode 100644 models/migrations/v204.go diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index a5bacd0d92bd2..3ca97fa1362b9 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -361,6 +361,8 @@ var migrations = []Migration{ NewMigration("Create key/value table for user settings", createUserSettingsTable), // v203 -> v204 NewMigration("Add Sorting to ProjectIssue table", addProjectIssueSorting), + // v204 -> v205 + NewMigration("ADd allow edits by maintainers to PullRequest table", addAllowEditsByMaintainers), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v204.go b/models/migrations/v204.go new file mode 100644 index 0000000000000..08b2c43ddbfd2 --- /dev/null +++ b/models/migrations/v204.go @@ -0,0 +1,18 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package migrations + +import ( + "xorm.io/xorm" +) + +func addAllowEditsByMaintainers(x *xorm.Engine) error { + // PullRequest represents relation between pull request and repositories. + type PullRequest struct { + AllowEditsByMaintainers bool + } + + return x.Sync2(new(PullRequest)) +} diff --git a/models/pull.go b/models/pull.go index 243d40b1fa874..8b9b5057fb0e3 100644 --- a/models/pull.go +++ b/models/pull.go @@ -69,15 +69,16 @@ type PullRequest struct { Issue *Issue `xorm:"-"` Index int64 - HeadRepoID int64 `xorm:"INDEX"` - HeadRepo *repo_model.Repository `xorm:"-"` - BaseRepoID int64 `xorm:"INDEX"` - BaseRepo *repo_model.Repository `xorm:"-"` - HeadBranch string - HeadCommitID string `xorm:"-"` - BaseBranch string - ProtectedBranch *ProtectedBranch `xorm:"-"` - MergeBase string `xorm:"VARCHAR(40)"` + HeadRepoID int64 `xorm:"INDEX"` + HeadRepo *repo_model.Repository `xorm:"-"` + BaseRepoID int64 `xorm:"INDEX"` + BaseRepo *repo_model.Repository `xorm:"-"` + HeadBranch string + HeadCommitID string `xorm:"-"` + BaseBranch string + ProtectedBranch *ProtectedBranch `xorm:"-"` + MergeBase string `xorm:"VARCHAR(40)"` + AllowEditsByMaintainers bool HasMerged bool `xorm:"INDEX"` MergedCommitID string `xorm:"VARCHAR(40)"` diff --git a/models/repo_permission.go b/models/repo_permission.go index 45878c8ba4e08..d363dcc4b060b 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -266,6 +266,25 @@ func getUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use } } + var pr *PullRequest + pr, err = GetUnmergedPullRequest(ci.HeadRepo.ID, ctx.Repo.Repository.ID, ci.HeadBranch, ci.BaseBranch, PullRequestFlowGithub) // TODO fix this + if err != nil { + if !IsErrPullRequestNotExist(err) { + return + } + } else { + if pr.AllowEditsByMaintainers { + prPerm, prPermErr := getUserRepoPermission(db.DefaultContext, pr.HeadRepo, user) + if err != nil { + err = prPermErr + return + } + if prPerm.CanWrite(unit.TypeCode) { + perm.UnitsMode[unit.TypeCode] = perm_model.AccessModeWrite + } + } + } + // remove no permission units perm.Units = make([]*repo_model.RepoUnit, 0, len(repo.Units)) for t := range perm.UnitsMode { diff --git a/modules/indexer/code/indexer_test.go b/modules/indexer/code/indexer_test.go index 98494afceb24c..42f21ed0df2da 100644 --- a/modules/indexer/code/indexer_test.go +++ b/modules/indexer/code/indexer_test.go @@ -8,9 +8,10 @@ import ( "path/filepath" "testing" - _ "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/unittest" + _ "code.gitea.io/gitea/models" + "github.com/stretchr/testify/assert" ) diff --git a/modules/indexer/issues/indexer_test.go b/modules/indexer/issues/indexer_test.go index 0855165556dab..866e3cc7c920a 100644 --- a/modules/indexer/issues/indexer_test.go +++ b/modules/indexer/issues/indexer_test.go @@ -11,11 +11,12 @@ import ( "testing" "time" - _ "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" + _ "code.gitea.io/gitea/models" + "github.com/stretchr/testify/assert" "gopkg.in/ini.v1" ) diff --git a/modules/indexer/stats/indexer_test.go b/modules/indexer/stats/indexer_test.go index b32100b458298..50c6cc38e9bbe 100644 --- a/modules/indexer/stats/indexer_test.go +++ b/modules/indexer/stats/indexer_test.go @@ -9,11 +9,12 @@ import ( "testing" "time" - _ "code.gitea.io/gitea/models" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/setting" + _ "code.gitea.io/gitea/models" + "github.com/stretchr/testify/assert" "gopkg.in/ini.v1" ) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index c70002278cef5..1a4ef32c61bba 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1417,6 +1417,8 @@ compare.compare_head = compare pulls.desc = Enable pull requests and code reviews. pulls.new = New Pull Request pulls.compare_changes = New Pull Request +pulls.allow_edits_by_maintainers = Allow edits by maintainers +pulls.allow_edits_by_maintainers_desc = Users with write access to the base branch can also push to this branch pulls.compare_changes_desc = Select the branch to merge into and the branch to pull from. pulls.compare_base = merge into pulls.compare_compare = pull from diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 2fcee33a23a6a..ca7377f704daf 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1121,14 +1121,15 @@ func CompareAndPullRequestPost(ctx *context.Context) { Content: form.Content, } pullRequest := &models.PullRequest{ - HeadRepoID: ci.HeadRepo.ID, - BaseRepoID: repo.ID, - HeadBranch: ci.HeadBranch, - BaseBranch: ci.BaseBranch, - HeadRepo: ci.HeadRepo, - BaseRepo: repo, - MergeBase: ci.CompareInfo.MergeBase, - Type: models.PullRequestGitea, + HeadRepoID: ci.HeadRepo.ID, + BaseRepoID: repo.ID, + HeadBranch: ci.HeadBranch, + BaseBranch: ci.BaseBranch, + HeadRepo: ci.HeadRepo, + BaseRepo: repo, + MergeBase: ci.CompareInfo.MergeBase, + Type: models.PullRequestGitea, + AllowEditsByMaintainers: form.AllowEditsByMaintainers, } // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt // instead of 500. diff --git a/services/asymkey/ssh_key_test.go b/services/asymkey/ssh_key_test.go index 0ce235f7f65cf..9de6a4c11bbdf 100644 --- a/services/asymkey/ssh_key_test.go +++ b/services/asymkey/ssh_key_test.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models/login" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "github.com/stretchr/testify/assert" ) diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 7c61be5e2221d..f9d5234b62c28 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -404,15 +404,16 @@ func (f *NewWechatWorkHookForm) Validate(req *http.Request, errs binding.Errors) // CreateIssueForm form for creating issue type CreateIssueForm struct { - Title string `binding:"Required;MaxSize(255)"` - LabelIDs string `form:"label_ids"` - AssigneeIDs string `form:"assignee_ids"` - Ref string `form:"ref"` - MilestoneID int64 - ProjectID int64 - AssigneeID int64 - Content string - Files []string + Title string `binding:"Required;MaxSize(255)"` + LabelIDs string `form:"label_ids"` + AssigneeIDs string `form:"assignee_ids"` + Ref string `form:"ref"` + MilestoneID int64 + ProjectID int64 + AssigneeID int64 + Content string + Files []string + AllowEditsByMaintainers bool } // Validate validates the fields diff --git a/services/mailer/mailer_test.go b/services/mailer/mailer_test.go index 1739a68a64414..56f2eb52b0bcd 100644 --- a/services/mailer/mailer_test.go +++ b/services/mailer/mailer_test.go @@ -9,6 +9,7 @@ import ( "time" "code.gitea.io/gitea/modules/setting" + "github.com/stretchr/testify/assert" ) diff --git a/services/pull/update.go b/services/pull/update.go index 25c6d36308274..194324513f860 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -104,11 +104,25 @@ func IsUserAllowedToUpdate(pull *models.PullRequest, user *user_model.User) (mer return false, false, nil } + baseRepoPerm, err := models.GetUserRepoPermission(pull.BaseRepo, user) + if err != nil { + return false, false, err + } + mergeAllowed, err = IsUserAllowedToMerge(pr, headRepoPerm, user) if err != nil { return false, false, err } + if pull.AllowEditsByMaintainers { + mergeAllowedMaintainer, err := IsUserAllowedToMerge(pr, baseRepoPerm, user) + if err != nil { + return false, false, err + } + + mergeAllowed = mergeAllowed || mergeAllowedMaintainer + } + return mergeAllowed, rebaseAllowed, nil } diff --git a/services/webhook/main_test.go b/services/webhook/main_test.go index e64acf3b61122..a87b74e89d743 100644 --- a/services/webhook/main_test.go +++ b/services/webhook/main_test.go @@ -8,8 +8,9 @@ import ( "path/filepath" "testing" - _ "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/unittest" + + _ "code.gitea.io/gitea/models" ) func TestMain(m *testing.M) { diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index 1089c82415ab8..bafb72f4d3a07 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -236,6 +236,14 @@ {{end}} + {{if and .PageIsComparePull .HeadRepo.IsFork}} +
+
+ + +
+
+ {{end}} diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 6198b6a6210b4..e5aabe92e5e9b 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -647,4 +647,16 @@ {{end}} + + {{if and .Issue.IsPull .IsIssuePoster}} + {{if .Issue.PullRequest.HeadRepo.IsFork}} +
+
+ + +
+
+ {{end}} + {{end}} + From fe065469a3b4fafd271e6eb3d5e2619543872c61 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Thu, 16 Dec 2021 19:50:37 +0100 Subject: [PATCH 02/78] Add "Allow edits from maintainer" feature Adds a feature [like GitHub has](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) if you create a new PR from a forked repo. You can select (and change later, but only if you are the PR creater/poster) the "Allow edits from maintainers" option. Then users with write access to the base branch get more permissions on this branch: * use the update pull request button * push directly from the command line (`git push`) * edit/delete/upload files via web UI You can't merge PRs to this branch. This feature has a pretty big impact on the permission system. I might forgot changing some things or didn't find security vulnerabilities. In this case, please leave a review or comment on this PR. Closes #17728 --- models/migrations/migrations.go | 2 +- models/migrations/v204.go | 4 +- models/pull.go | 15 ++++- models/repo_permission.go | 55 ++++++++++++------- modules/context/permission.go | 14 +++++ modules/context/repo.go | 2 +- modules/structs/repo_file.go | 20 +++++++ options/locale/locale_en-US.ini | 5 +- routers/api/v1/api.go | 17 ++++-- routers/api/v1/repo/file.go | 8 +-- routers/private/hook_pre_receive.go | 8 ++- routers/web/repo/compare.go | 1 + routers/web/repo/pull.go | 51 ++++++++++++++++- routers/web/repo/view.go | 6 +- routers/web/web.go | 7 ++- services/forms/repo_form.go | 2 +- services/pull/update.go | 2 +- templates/repo/issue/new_form.tmpl | 8 +-- .../repo/issue/view_content/sidebar.tmpl | 20 +++---- web_src/js/features/repo-issue.js | 14 +++++ web_src/js/index.js | 2 + 21 files changed, 205 insertions(+), 58 deletions(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 3ca97fa1362b9..0d926c1e5bbb0 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -362,7 +362,7 @@ var migrations = []Migration{ // v203 -> v204 NewMigration("Add Sorting to ProjectIssue table", addProjectIssueSorting), // v204 -> v205 - NewMigration("ADd allow edits by maintainers to PullRequest table", addAllowEditsByMaintainers), + NewMigration("ADd allow edits from maintainers to PullRequest table", addAllowEditsFromMaintainers), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v204.go b/models/migrations/v204.go index 08b2c43ddbfd2..1f3999120a299 100644 --- a/models/migrations/v204.go +++ b/models/migrations/v204.go @@ -8,10 +8,10 @@ import ( "xorm.io/xorm" ) -func addAllowEditsByMaintainers(x *xorm.Engine) error { +func addAllowEditsFromMaintainers(x *xorm.Engine) error { // PullRequest represents relation between pull request and repositories. type PullRequest struct { - AllowEditsByMaintainers bool + AllowEditsFromMaintainers bool } return x.Sync2(new(PullRequest)) diff --git a/models/pull.go b/models/pull.go index 8b9b5057fb0e3..f4d2552ca20b1 100644 --- a/models/pull.go +++ b/models/pull.go @@ -78,7 +78,7 @@ type PullRequest struct { BaseBranch string ProtectedBranch *ProtectedBranch `xorm:"-"` MergeBase string `xorm:"VARCHAR(40)"` - AllowEditsByMaintainers bool + AllowEditsFromMaintainers bool HasMerged bool `xorm:"INDEX"` MergedCommitID string `xorm:"VARCHAR(40)"` @@ -703,3 +703,16 @@ func (pr *PullRequest) GetHeadBranchHTMLURL() string { } return pr.HeadRepo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(pr.HeadBranch) } + +// UpdateAllowEdits update if PR can be edited from maintainers +func (pr *PullRequest) UpdateAllowEdits(allow bool) error { + return pr.updateAllowEdits(db.GetEngine(db.DefaultContext), allow) +} + +func (pr *PullRequest) updateAllowEdits(e db.Engine, allow bool) error { + pr.AllowEditsFromMaintainers = allow + if _, err := e.ID(pr.ID).Cols("allow_edits_from_maintainers").Update(pr); err != nil { + return err + } + return nil +} diff --git a/models/repo_permission.go b/models/repo_permission.go index d363dcc4b060b..1c91fb5a136aa 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -21,6 +21,9 @@ type Permission struct { AccessMode perm_model.AccessMode Units []*repo_model.RepoUnit UnitsMode map[unit.Type]perm_model.AccessMode + + User *user_model.User + Repo *repo_model.Repository // TODO try to move to Permission.Units } // IsOwner returns true if current user is the owner of repository. @@ -102,6 +105,35 @@ func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool { return p.CanWrite(unit.TypeIssues) } +// CanWriteToBranch checks if the branch is writable by the user +func (p *Permission) CanWriteToBranch(branch string) bool { + if p.CanWrite(unit.TypeCode) || p.User == nil { + return p.CanWrite(unit.TypeCode) + } + + prs, err := GetUnmergedPullRequestsByHeadInfo(p.Repo.ID, branch) + if err != nil { + return false + } + + for _, pr := range prs { + err = pr.LoadBaseRepo() + if err != nil { + continue + } + if pr.AllowEditsFromMaintainers { + prPerm, err := getUserRepoPermission(db.DefaultContext, pr.BaseRepo, p.User) + if err != nil { + continue + } + if prPerm.CanWrite(unit.TypeCode) { + return prPerm.CanWrite(unit.TypeCode) + } + } + } + return false +} + // ColorFormat writes a colored string for these Permissions func (p *Permission) ColorFormat(s fmt.State) { noColor := log.ColorBytes(log.Reset) @@ -163,6 +195,10 @@ func getUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use perm) }() } + + perm.Repo = repo + perm.User = user + // anonymous user visit private repo. // TODO: anonymous user visit public unit of private repo??? if user == nil && repo.IsPrivate { @@ -266,25 +302,6 @@ func getUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use } } - var pr *PullRequest - pr, err = GetUnmergedPullRequest(ci.HeadRepo.ID, ctx.Repo.Repository.ID, ci.HeadBranch, ci.BaseBranch, PullRequestFlowGithub) // TODO fix this - if err != nil { - if !IsErrPullRequestNotExist(err) { - return - } - } else { - if pr.AllowEditsByMaintainers { - prPerm, prPermErr := getUserRepoPermission(db.DefaultContext, pr.HeadRepo, user) - if err != nil { - err = prPermErr - return - } - if prPerm.CanWrite(unit.TypeCode) { - perm.UnitsMode[unit.TypeCode] = perm_model.AccessModeWrite - } - } - } - // remove no permission units perm.Units = make([]*repo_model.RepoUnit, 0, len(repo.Units)) for t := range perm.UnitsMode { diff --git a/modules/context/permission.go b/modules/context/permission.go index 2b87aa4591a5e..6d91dd682ebf3 100644 --- a/modules/context/permission.go +++ b/modules/context/permission.go @@ -5,6 +5,8 @@ package context import ( + "fmt" + "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/log" ) @@ -29,6 +31,18 @@ func RequireRepoWriter(unitType unit.Type) func(ctx *Context) { } } +// CanEnableEditor returns a middleware for requiring repository write to the specific branch +func CanEnableEditor() func(ctx *Context) { + return func(ctx *Context) { + //RepoRefByType(RepoRefBranch)(ctx) + fmt.Println(ctx.Repo.BranchName) + if !ctx.Repo.Permission.CanWriteToBranch(ctx.Repo.BranchName) { + ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + return + } + } +} + // RequireRepoWriterOr returns a middleware for requiring repository write to one of the unit permission func RequireRepoWriterOr(unitTypes ...unit.Type) func(ctx *Context) { return func(ctx *Context) { diff --git a/modules/context/repo.go b/modules/context/repo.go index 010d3b7f81814..b18b33717008a 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -78,7 +78,7 @@ type Repository struct { // CanEnableEditor returns true if repository is editable and user has proper access level. func (r *Repository) CanEnableEditor() bool { - return r.Permission.CanWrite(unit_model.TypeCode) && r.Repository.CanEnableEditor() && r.IsViewBranch && !r.Repository.IsArchived + return r.IsViewBranch && r.Permission.CanWriteToBranch(r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived } // CanCreateBranch returns true if repository is editable and user has proper access level. diff --git a/modules/structs/repo_file.go b/modules/structs/repo_file.go index 71733c90e7045..7b18d42f964bd 100644 --- a/modules/structs/repo_file.go +++ b/modules/structs/repo_file.go @@ -30,6 +30,11 @@ type CreateFileOptions struct { Content string `json:"content"` } +// Branch returns branch name +func (o *CreateFileOptions) Branch() string { + return o.FileOptions.BranchName +} + // DeleteFileOptions options for deleting files (used for other File structs below) // Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) type DeleteFileOptions struct { @@ -39,6 +44,11 @@ type DeleteFileOptions struct { SHA string `json:"sha" binding:"Required"` } +// Branch returns branch name +func (o *DeleteFileOptions) Branch() string { + return o.FileOptions.BranchName +} + // UpdateFileOptions options for updating files // Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used) type UpdateFileOptions struct { @@ -50,6 +60,16 @@ type UpdateFileOptions struct { FromPath string `json:"from_path" binding:"MaxSize(500)"` } +// Branch returns branch name +func (o *UpdateFileOptions) Branch() string { + return o.FileOptions.BranchName +} + +// FileOptionInterface provides a unified interface for the different file options +type FileOptionInterface interface { + Branch() string +} + // FileLinksResponse contains the links for a repo's file type FileLinksResponse struct { Self *string `json:"self"` diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 1a4ef32c61bba..9465b6f65f6cb 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1417,8 +1417,9 @@ compare.compare_head = compare pulls.desc = Enable pull requests and code reviews. pulls.new = New Pull Request pulls.compare_changes = New Pull Request -pulls.allow_edits_by_maintainers = Allow edits by maintainers -pulls.allow_edits_by_maintainers_desc = Users with write access to the base branch can also push to this branch +pulls.allow_edits_from_maintainers = Allow edits from maintainers +pulls.allow_edits_from_maintainers_desc = Users with write access to the base branch can also push to this branch +pulls.allow_edits_from_maintainers_err = Updating failed pulls.compare_changes_desc = Select the branch to merge into and the branch to pull from. pulls.compare_base = merge into pulls.compare_compare = pull from diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index af5ab96d05315..f1683f5c06402 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -270,6 +270,15 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { } } +// reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin +func reqRepoBranchWriter(ctx *context.APIContext) { + options := web.GetForm(ctx).(api.FileOptionInterface) + if !ctx.Repo.CanWriteToBranch(options.Branch()) && !ctx.IsUserSiteAdmin() { + ctx.Error(http.StatusForbidden, "reqRepoBranchWriter", "user should have a permission to write to this branch") + return + } +} + // reqRepoReader user should have specific read permission or be a repo admin or a site admin func reqRepoReader(unitType unit.Type) func(ctx *context.APIContext) { return func(ctx *context.APIContext) { @@ -976,10 +985,10 @@ func Routes(sessioner func(http.Handler) http.Handler) *web.Route { m.Get("", repo.GetContentsList) m.Get("/*", repo.GetContents) m.Group("/*", func() { - m.Post("", bind(api.CreateFileOptions{}), repo.CreateFile) - m.Put("", bind(api.UpdateFileOptions{}), repo.UpdateFile) - m.Delete("", bind(api.DeleteFileOptions{}), repo.DeleteFile) - }, reqRepoWriter(unit.TypeCode), reqToken()) + m.Post("", bind(api.CreateFileOptions{}), reqRepoBranchWriter, repo.CreateFile) + m.Put("", bind(api.UpdateFileOptions{}), reqRepoBranchWriter, repo.UpdateFile) + m.Delete("", bind(api.DeleteFileOptions{}), reqRepoBranchWriter, repo.DeleteFile) + }, reqToken()) }, reqRepoReader(unit.TypeCode)) m.Get("/signing-key.gpg", misc.SigningKey) m.Group("/topics", func() { diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 98decfcb6f286..c05e6e01edfc8 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -183,8 +183,8 @@ func GetEditorconfig(ctx *context.APIContext) { } // canWriteFiles returns true if repository is editable and user has proper access level. -func canWriteFiles(r *context.Repository) bool { - return r.Permission.CanWrite(unit.TypeCode) && !r.Repository.IsMirror && !r.Repository.IsArchived +func canWriteFiles(r *context.Repository, branch string) bool { + return r.Permission.CanWriteToBranch(branch) && !r.Repository.IsMirror && !r.Repository.IsArchived } // canReadFiles returns true if repository is readable and user has proper access level. @@ -389,7 +389,7 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) { // Called from both CreateFile or UpdateFile to handle both func createOrUpdateFile(ctx *context.APIContext, opts *files_service.UpdateRepoFileOptions) (*api.FileResponse, error) { - if !canWriteFiles(ctx.Repo) { + if !canWriteFiles(ctx.Repo, opts.OldBranch) { return nil, models.ErrUserDoesNotHaveAccessToRepo{ UserID: ctx.User.ID, RepoName: ctx.Repo.Repository.LowerName, @@ -446,7 +446,7 @@ func DeleteFile(ctx *context.APIContext) { // "$ref": "#/responses/error" apiOpts := web.GetForm(ctx).(*api.DeleteFileOptions) - if !canWriteFiles(ctx.Repo) { + if !canWriteFiles(ctx.Repo, apiOpts.BranchName) { ctx.Error(http.StatusForbidden, "DeleteFile", models.ErrUserDoesNotHaveAccessToRepo{ UserID: ctx.User.ID, RepoName: ctx.Repo.Repository.LowerName, diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 17ccf010aefc6..7662309223f53 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -39,6 +39,8 @@ type preReceiveContext struct { env []string opts *private.HookOptions + + branchName string } // User gets or loads User @@ -60,7 +62,7 @@ func (ctx *preReceiveContext) Perm() *models.Permission { // CanWriteCode returns true if can write code func (ctx *preReceiveContext) CanWriteCode() bool { if !ctx.checkedCanWriteCode { - ctx.canWriteCode = ctx.Perm().CanWrite(unit.TypeCode) + ctx.canWriteCode = ctx.Perm().CanWriteToBranch(ctx.branchName) ctx.checkedCanWriteCode = true } return ctx.canWriteCode @@ -138,13 +140,15 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { } func preReceiveBranch(ctx *preReceiveContext, oldCommitID, newCommitID, refFullName string) { + branchName := strings.TrimPrefix(refFullName, git.BranchPrefix) + ctx.branchName = branchName + if !ctx.AssertCanWriteCode() { return } repo := ctx.Repo.Repository gitRepo := ctx.Repo.GitRepo - branchName := strings.TrimPrefix(refFullName, git.BranchPrefix) if branchName == repo.DefaultBranch && newCommitID == git.EmptySHA { log.Warn("Forbidden: Branch: %s is the default branch in %-v and cannot be deleted", branchName, repo) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 8d08fec8fdb84..d89702f9c4bd3 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -393,6 +393,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { } ctx.Data["HeadRepo"] = ci.HeadRepo + ctx.Data["BaseCompareRepo"] = ctx.Repo.Repository // Now we need to assert that the ctx.User has permission to read // the baseRepo's code and pulls diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index ca7377f704daf..f9b6b658c673b 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1129,7 +1129,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { BaseRepo: repo, MergeBase: ci.CompareInfo.MergeBase, Type: models.PullRequestGitea, - AllowEditsByMaintainers: form.AllowEditsByMaintainers, + AllowEditsFromMaintainers: form.AllowEditsFromMaintainers, } // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt // instead of 500. @@ -1428,3 +1428,52 @@ func UpdatePullRequestTarget(ctx *context.Context) { "base_branch": pr.BaseBranch, }) } + +// AllowEdits allow edits from maintainers to PRs +func AllowEdits(ctx *context.Context) { + updateAllowEditsStatus(ctx, true) +} + +// DisallowEdits disallow edits from maintainers to PRs +func DisallowEdits(ctx *context.Context) { + updateAllowEditsStatus(ctx, false) +} + +func updateAllowEditsStatus(ctx *context.Context, allow bool) { + pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if models.IsErrPullRequestNotExist(err) { + ctx.NotFound("GetPullRequestByIndex", err) + } else { + ctx.ServerError("GetPullRequestByIndex", err) + } + return + } + + err = pr.LoadIssue() + if err != nil { + ctx.ServerError("LoadIssue", err) + return + } + + issue := pr.Issue + if !issue.IsPull { + ctx.Error(http.StatusNotFound) + return + } + + if !ctx.IsSigned || !issue.IsPoster(ctx.User.ID) { + ctx.Error(http.StatusForbidden) + return + } + + err = pr.UpdateAllowEdits(allow) + if err != nil { + ctx.ServerError("UpdateAllowEdits", err) + return + } + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "allow_edits": pr.AllowEditsFromMaintainers, + }) +} diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 5e9003b16baf4..196af4b72c29c 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -357,7 +357,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { } // Check permission to add or upload new file. - if ctx.Repo.CanWrite(unit_model.TypeCode) && ctx.Repo.IsViewBranch { + if ctx.Repo.CanWriteToBranch(ctx.Repo.BranchName) && ctx.Repo.IsViewBranch { ctx.Data["CanAddFile"] = !ctx.Repo.Repository.IsArchived ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled && !ctx.Repo.Repository.IsArchived } @@ -549,7 +549,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } } else if !ctx.Repo.IsViewBranch { ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") - } else if !ctx.Repo.CanWrite(unit_model.TypeCode) { + } else if !ctx.Repo.CanWriteToBranch(ctx.Repo.BranchName) { ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit") } } @@ -598,7 +598,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } } else if !ctx.Repo.IsViewBranch { ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") - } else if !ctx.Repo.CanWrite(unit_model.TypeCode) { + } else if !ctx.Repo.CanWriteToBranch(ctx.Repo.BranchName) { ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access") } } diff --git a/routers/web/web.go b/routers/web/web.go index 0d4d3bd90f4f9..d3fc6ed4fd7b0 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -494,6 +494,7 @@ func RegisterRoutes(m *web.Route) { reqRepoAdmin := context.RequireRepoAdmin() reqRepoCodeWriter := context.RequireRepoWriter(unit.TypeCode) + canEnableEditor := context.CanEnableEditor() reqRepoCodeReader := context.RequireRepoReader(unit.TypeCode) reqRepoReleaseWriter := context.RequireRepoWriter(unit.TypeReleases) reqRepoReleaseReader := context.RequireRepoReader(unit.TypeReleases) @@ -798,12 +799,12 @@ func RegisterRoutes(m *web.Route) { m.Combo("/_upload/*", repo.MustBeAbleToUpload). Get(repo.UploadFile). Post(bindIgnErr(forms.UploadRepoFileForm{}), repo.UploadFilePost) - }, context.RepoRefByType(context.RepoRefBranch), repo.MustBeEditable) + }, context.RepoRefByType(context.RepoRefBranch), canEnableEditor, repo.MustBeEditable) m.Group("", func() { m.Post("/upload-file", repo.UploadFileToServer) m.Post("/upload-remove", bindIgnErr(forms.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer) }, context.RepoRef(), repo.MustBeEditable, repo.MustBeAbleToUpload) - }, context.RepoMustNotBeArchived(), reqRepoCodeWriter, repo.MustBeNotEmpty) + }, context.RepoMustNotBeArchived(), repo.MustBeNotEmpty) m.Group("/branches", func() { m.Group("/_new", func() { @@ -957,6 +958,8 @@ func RegisterRoutes(m *web.Route) { m.Get("/commits", context.RepoRef(), repo.ViewPullCommits) m.Post("/merge", context.RepoMustNotBeArchived(), bindIgnErr(forms.MergePullRequestForm{}), repo.MergePullRequest) m.Post("/update", repo.UpdatePullRequest) + m.Post("/allow_edits", repo.AllowEdits) + m.Post("/disallow_edits", repo.DisallowEdits) m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest) m.Group("/files", func() { m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.ViewPullFiles) diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index f9d5234b62c28..fcc93f47c9e94 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -413,7 +413,7 @@ type CreateIssueForm struct { AssigneeID int64 Content string Files []string - AllowEditsByMaintainers bool + AllowEditsFromMaintainers bool } // Validate validates the fields diff --git a/services/pull/update.go b/services/pull/update.go index 194324513f860..5c3490c4b15b6 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -114,7 +114,7 @@ func IsUserAllowedToUpdate(pull *models.PullRequest, user *user_model.User) (mer return false, false, err } - if pull.AllowEditsByMaintainers { + if pull.AllowEditsFromMaintainers { mergeAllowedMaintainer, err := IsUserAllowedToMerge(pr, baseRepoPerm, user) if err != nil { return false, false, err diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index bafb72f4d3a07..ced70d813c86d 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -236,11 +236,11 @@ {{end}} - {{if and .PageIsComparePull .HeadRepo.IsFork}} + {{if and .PageIsComparePull (not (eq .HeadRepo.FullName .BaseCompareRepo.FullName))}}
-
- - +
+ +
{{end}} diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index e5aabe92e5e9b..82490d03bb696 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -646,17 +646,17 @@
{{end}} - - {{if and .Issue.IsPull .IsIssuePoster}} - {{if .Issue.PullRequest.HeadRepo.IsFork}} -
-
- - + {{if and .Issue.IsPull .IsIssuePoster}} + {{if not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)}} +
+
+
+ + +
-
+ {{end}} {{end}} - {{end}} - +
diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 545f59a4d88fc..f40899ce300da 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -287,6 +287,20 @@ export function initRepoPullRequestMergeInstruction() { }); } +export function initRepoPullRequestAllowEditsFromMaintainers() { + $('#allow-edits-from-maintainers input').on('change', function () { + let url = ''; + if ($(this).is(':checked')) { // allow edits + url = `${window.location}/allow_edits`; + } else { // disallow edits + url = `${window.location}/disallow_edits`; + } + $.post(url, { + _csrf: csrfToken + }); + }); +} + export function initRepoIssueReferenceRepositorySearch() { $('.issue_reference_repository_search') .dropdown({ diff --git a/web_src/js/index.js b/web_src/js/index.js index c9bf197a35518..2167449e07993 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -34,6 +34,7 @@ import { initRepoIssueTimeTracking, initRepoIssueWipTitle, initRepoPullRequestMergeInstruction, + initRepoPullRequestAllowEditsFromMaintainers, initRepoPullRequestReview, } from './features/repo-issue.js'; import {initRepoEllipsisButton, initRepoCommitLastCommitLoader} from './features/repo-commit.js'; @@ -150,6 +151,7 @@ $(document).ready(() => { initRepoMigrationStatusChecker(); initRepoProject(); initRepoPullRequestMergeInstruction(); + initRepoPullRequestAllowEditsFromMaintainers(); initRepoPullRequestReview(); initRepoRelease(); initRepoReleaseEditor(); From d9ae6cf28d944b270b90c391692c9420e6867f4b Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Thu, 16 Dec 2021 20:18:31 +0100 Subject: [PATCH 03/78] Fix security issue --- routers/web/repo/compare.go | 1 + routers/web/repo/issue.go | 1 + templates/repo/issue/new_form.tmpl | 2 +- templates/repo/issue/view_content/sidebar.tmpl | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index d89702f9c4bd3..14bfeb3970680 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -432,6 +432,7 @@ func ParseCompareInfo(ctx *context.Context) *CompareInfo { ctx.NotFound("ParseCompareInfo", nil) return nil } + ctx.Data["CanWriteToHeadRepo"] = permHead.CanWrite(unit.TypeCode) } // If we have a rootRepo and it's different from: diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 464646a8688b9..27828afaa1431 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -1510,6 +1510,7 @@ func ViewIssue(ctx *context.Context) { canDelete = true ctx.Data["DeleteBranchLink"] = issue.Link() + "/cleanup" } + ctx.Data["CanWriteToHeadRepo"] = true } } diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index ced70d813c86d..8d4bd9f085df4 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -236,7 +236,7 @@ {{end}} - {{if and .PageIsComparePull (not (eq .HeadRepo.FullName .BaseCompareRepo.FullName))}} + {{if and .PageIsComparePull (not (eq .HeadRepo.FullName .BaseCompareRepo.FullName)) .CanWriteToHeadRepo}}
diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 82490d03bb696..953f147940def 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -648,7 +648,7 @@ {{end}} {{if and .Issue.IsPull .IsIssuePoster}} - {{if not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)}} + {{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
From bc5f4d0920a3db095acb83b70389db4615e9076a Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Thu, 16 Dec 2021 20:22:34 +0100 Subject: [PATCH 04/78] fmt --- models/pull.go | 18 +++++++++--------- routers/web/repo/pull.go | 16 ++++++++-------- services/forms/repo_form.go | 18 +++++++++--------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/models/pull.go b/models/pull.go index f4d2552ca20b1..f66db06380078 100644 --- a/models/pull.go +++ b/models/pull.go @@ -69,15 +69,15 @@ type PullRequest struct { Issue *Issue `xorm:"-"` Index int64 - HeadRepoID int64 `xorm:"INDEX"` - HeadRepo *repo_model.Repository `xorm:"-"` - BaseRepoID int64 `xorm:"INDEX"` - BaseRepo *repo_model.Repository `xorm:"-"` - HeadBranch string - HeadCommitID string `xorm:"-"` - BaseBranch string - ProtectedBranch *ProtectedBranch `xorm:"-"` - MergeBase string `xorm:"VARCHAR(40)"` + HeadRepoID int64 `xorm:"INDEX"` + HeadRepo *repo_model.Repository `xorm:"-"` + BaseRepoID int64 `xorm:"INDEX"` + BaseRepo *repo_model.Repository `xorm:"-"` + HeadBranch string + HeadCommitID string `xorm:"-"` + BaseBranch string + ProtectedBranch *ProtectedBranch `xorm:"-"` + MergeBase string `xorm:"VARCHAR(40)"` AllowEditsFromMaintainers bool HasMerged bool `xorm:"INDEX"` diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 4d46bdafd4316..fc9c36531f3bf 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1121,14 +1121,14 @@ func CompareAndPullRequestPost(ctx *context.Context) { Content: form.Content, } pullRequest := &models.PullRequest{ - HeadRepoID: ci.HeadRepo.ID, - BaseRepoID: repo.ID, - HeadBranch: ci.HeadBranch, - BaseBranch: ci.BaseBranch, - HeadRepo: ci.HeadRepo, - BaseRepo: repo, - MergeBase: ci.CompareInfo.MergeBase, - Type: models.PullRequestGitea, + HeadRepoID: ci.HeadRepo.ID, + BaseRepoID: repo.ID, + HeadBranch: ci.HeadBranch, + BaseBranch: ci.BaseBranch, + HeadRepo: ci.HeadRepo, + BaseRepo: repo, + MergeBase: ci.CompareInfo.MergeBase, + Type: models.PullRequestGitea, AllowEditsFromMaintainers: form.AllowEditsFromMaintainers, } // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 41784a5b9f294..0fda4ffab6785 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -405,15 +405,15 @@ func (f *NewWechatWorkHookForm) Validate(req *http.Request, errs binding.Errors) // CreateIssueForm form for creating issue type CreateIssueForm struct { - Title string `binding:"Required;MaxSize(255)"` - LabelIDs string `form:"label_ids"` - AssigneeIDs string `form:"assignee_ids"` - Ref string `form:"ref"` - MilestoneID int64 - ProjectID int64 - AssigneeID int64 - Content string - Files []string + Title string `binding:"Required;MaxSize(255)"` + LabelIDs string `form:"label_ids"` + AssigneeIDs string `form:"assignee_ids"` + Ref string `form:"ref"` + MilestoneID int64 + ProjectID int64 + AssigneeID int64 + Content string + Files []string AllowEditsFromMaintainers bool } From 44d7b25c097fad2cbb493f8cc9449a38a6e82b85 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Thu, 16 Dec 2021 20:25:06 +0100 Subject: [PATCH 05/78] Fix typo --- models/migrations/migrations.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 0d926c1e5bbb0..6f4a100ec7fd8 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -362,7 +362,7 @@ var migrations = []Migration{ // v203 -> v204 NewMigration("Add Sorting to ProjectIssue table", addProjectIssueSorting), // v204 -> v205 - NewMigration("ADd allow edits from maintainers to PullRequest table", addAllowEditsFromMaintainers), + NewMigration("Add allow edits from maintainers to PullRequest table", addAllowEditsFromMaintainers), } // GetCurrentDBVersion returns the current db version From bf2244a0723243db64f542e325fb46d637027f0d Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Thu, 16 Dec 2021 20:32:32 +0100 Subject: [PATCH 06/78] Resolve TODO --- models/repo_permission.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/models/repo_permission.go b/models/repo_permission.go index 1c91fb5a136aa..d59d0cbe4365a 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -23,7 +23,6 @@ type Permission struct { UnitsMode map[unit.Type]perm_model.AccessMode User *user_model.User - Repo *repo_model.Repository // TODO try to move to Permission.Units } // IsOwner returns true if current user is the owner of repository. @@ -111,7 +110,7 @@ func (p *Permission) CanWriteToBranch(branch string) bool { return p.CanWrite(unit.TypeCode) } - prs, err := GetUnmergedPullRequestsByHeadInfo(p.Repo.ID, branch) + prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch) if err != nil { return false } @@ -196,7 +195,6 @@ func getUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use }() } - perm.Repo = repo perm.User = user // anonymous user visit private repo. From 298f91255200cdfd92e6c3b14760b7b770973ab1 Mon Sep 17 00:00:00 2001 From: qwerty287 <80460567+qwerty287@users.noreply.github.com> Date: Fri, 17 Dec 2021 16:16:04 +0100 Subject: [PATCH 07/78] Update web_src/js/features/repo-issue.js Co-authored-by: silverwind --- web_src/js/features/repo-issue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index f40899ce300da..5101e60f168e5 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -290,7 +290,7 @@ export function initRepoPullRequestMergeInstruction() { export function initRepoPullRequestAllowEditsFromMaintainers() { $('#allow-edits-from-maintainers input').on('change', function () { let url = ''; - if ($(this).is(':checked')) { // allow edits + if (this.checked) { // allow edits url = `${window.location}/allow_edits`; } else { // disallow edits url = `${window.location}/disallow_edits`; From b4dbc8d4433b27b9d56eb68d80e83ae4d81a8ab3 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 17 Dec 2021 16:22:31 +0100 Subject: [PATCH 08/78] Rm debug code --- modules/context/permission.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/context/permission.go b/modules/context/permission.go index 6d91dd682ebf3..0a883fb80c0e4 100644 --- a/modules/context/permission.go +++ b/modules/context/permission.go @@ -5,8 +5,6 @@ package context import ( - "fmt" - "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/modules/log" ) @@ -34,8 +32,6 @@ func RequireRepoWriter(unitType unit.Type) func(ctx *Context) { // CanEnableEditor returns a middleware for requiring repository write to the specific branch func CanEnableEditor() func(ctx *Context) { return func(ctx *Context) { - //RepoRefByType(RepoRefBranch)(ctx) - fmt.Println(ctx.Repo.BranchName) if !ctx.Repo.Permission.CanWriteToBranch(ctx.Repo.BranchName) { ctx.NotFound(ctx.Req.URL.RequestURI(), nil) return From 111896ce1abfd53e9f47572efe4da5ed196134ba Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 17 Dec 2021 16:23:19 +0100 Subject: [PATCH 09/78] Update desc --- modules/context/permission.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/context/permission.go b/modules/context/permission.go index 0a883fb80c0e4..99969683e7cf1 100644 --- a/modules/context/permission.go +++ b/modules/context/permission.go @@ -29,7 +29,7 @@ func RequireRepoWriter(unitType unit.Type) func(ctx *Context) { } } -// CanEnableEditor returns a middleware for requiring repository write to the specific branch +// CanEnableEditor checks if the user is allowed to write to the branch of the repo func CanEnableEditor() func(ctx *Context) { return func(ctx *Context) { if !ctx.Repo.Permission.CanWriteToBranch(ctx.Repo.BranchName) { From 594d2774fc7a450f91479b923a879d288efbe725 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 17 Dec 2021 16:24:39 +0100 Subject: [PATCH 10/78] Rm var --- routers/web/repo/pull.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index fc9c36531f3bf..78aaf089b5920 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1456,13 +1456,12 @@ func updateAllowEditsStatus(ctx *context.Context, allow bool) { return } - issue := pr.Issue - if !issue.IsPull { + if !pr.Issue.IsPull { ctx.Error(http.StatusNotFound) return } - if !ctx.IsSigned || !issue.IsPoster(ctx.User.ID) { + if !ctx.IsSigned || !pr.Issue.IsPoster(ctx.User.ID) { ctx.Error(http.StatusForbidden) return } From c30bdc36f092ba9fb09ded3d27171bed77e6de96 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 17 Dec 2021 16:28:44 +0100 Subject: [PATCH 11/78] Disable JS on new PR --- templates/repo/issue/new_form.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index 8d4bd9f085df4..4787b82645901 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -238,7 +238,7 @@
{{if and .PageIsComparePull (not (eq .HeadRepo.FullName .BaseCompareRepo.FullName)) .CanWriteToHeadRepo}}
-
+
From 4c7f2ba70b1e0dfc14e20401d06e57eab7c4f665 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 17 Dec 2021 16:49:13 +0100 Subject: [PATCH 12/78] Use var to save url --- templates/repo/issue/view_content/sidebar.tmpl | 2 +- web_src/js/features/repo-issue.js | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 953f147940def..fcac16f8a8fc9 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -652,7 +652,7 @@
- +
diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 5101e60f168e5..07247b7f32fe5 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -289,11 +289,13 @@ export function initRepoPullRequestMergeInstruction() { export function initRepoPullRequestAllowEditsFromMaintainers() { $('#allow-edits-from-maintainers input').on('change', function () { - let url = ''; + const $label = $('#allow-edits-from-maintainers label'); + let url = $label.data('url'); + console.log(url) if (this.checked) { // allow edits - url = `${window.location}/allow_edits`; + url = `${url}/allow_edits`; } else { // disallow edits - url = `${window.location}/disallow_edits`; + url = `${url}/disallow_edits`; } $.post(url, { _csrf: csrfToken From f086784e9b97dcd0e851a796a7466dd5bbc93ab0 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 17 Dec 2021 17:06:48 +0100 Subject: [PATCH 13/78] Show error popup if failed --- templates/repo/issue/view_content/sidebar.tmpl | 4 ++-- web_src/js/features/repo-issue.js | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index fcac16f8a8fc9..004096c17acff 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -651,8 +651,8 @@ {{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
-
- +
+
diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 07247b7f32fe5..0c62b6838fad3 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -291,14 +291,25 @@ export function initRepoPullRequestAllowEditsFromMaintainers() { $('#allow-edits-from-maintainers input').on('change', function () { const $label = $('#allow-edits-from-maintainers label'); let url = $label.data('url'); - console.log(url) if (this.checked) { // allow edits url = `${url}/allow_edits`; } else { // disallow edits url = `${url}/disallow_edits`; } - $.post(url, { - _csrf: csrfToken + $.ajax({ + url: url, + type: "POST", + data: { + _csrf: csrfToken + }, + error: function () { + let labelElem = document.getElementById('allow-edits-from-maintainers-label'); + $label.popup('destroy'); + const oldContent = labelElem.getAttribute('data-content'); + labelElem.setAttribute('data-content', $label.data('failed')); + $label.popup('show'); + labelElem.setAttribute('data-content', oldContent || ''); + }, }); }); } From 17f0729f81c73828a68b4ba68bd5beafc72d573d Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 17 Dec 2021 17:17:47 +0100 Subject: [PATCH 14/78] Fix lint --- web_src/js/features/repo-issue.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 0c62b6838fad3..d0cbb9be84dc7 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -297,16 +297,16 @@ export function initRepoPullRequestAllowEditsFromMaintainers() { url = `${url}/disallow_edits`; } $.ajax({ - url: url, - type: "POST", + url, + type: 'POST', data: { _csrf: csrfToken }, - error: function () { - let labelElem = document.getElementById('allow-edits-from-maintainers-label'); + error: () => { + const $labelElem = document.getElementById('allow-edits-from-maintainers-label'); $label.popup('destroy'); - const oldContent = labelElem.getAttribute('data-content'); - labelElem.setAttribute('data-content', $label.data('failed')); + const oldContent = $labelElem.getAttribute('data-content'); + $labelElem.setAttribute('data-content', $label.data('failed')); $label.popup('show'); labelElem.setAttribute('data-content', oldContent || ''); }, From 31ff67877eaccc716e81895a31c4e52df6ee9575 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 17 Dec 2021 17:30:39 +0100 Subject: [PATCH 15/78] Fix js --- web_src/js/features/repo-issue.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index d0cbb9be84dc7..ccdcce6088722 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -308,7 +308,7 @@ export function initRepoPullRequestAllowEditsFromMaintainers() { const oldContent = $labelElem.getAttribute('data-content'); $labelElem.setAttribute('data-content', $label.data('failed')); $label.popup('show'); - labelElem.setAttribute('data-content', oldContent || ''); + $labelElem.setAttribute('data-content', oldContent || ''); }, }); }); From f5f208e49cac4851139d2383c89dd7b19e45c8fa Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Tue, 21 Dec 2021 10:34:51 +0100 Subject: [PATCH 16/78] Hide optin if pr is closed --- templates/repo/issue/view_content/sidebar.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 004096c17acff..cd0957f70ae0b 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -647,7 +647,7 @@
{{end}} - {{if and .Issue.IsPull .IsIssuePoster}} + {{if and .Issue.IsPull .IsIssuePoster (not .Issue.IsClosed)}} {{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
From 2f912a229de573f2fdf68e04518b2c7248c2eb4d Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Tue, 21 Dec 2021 10:50:22 +0100 Subject: [PATCH 17/78] Remove `nil` check --- models/repo_permission.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/repo_permission.go b/models/repo_permission.go index d59d0cbe4365a..c814c37d9d720 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -106,7 +106,7 @@ func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool { // CanWriteToBranch checks if the branch is writable by the user func (p *Permission) CanWriteToBranch(branch string) bool { - if p.CanWrite(unit.TypeCode) || p.User == nil { + if p.CanWrite(unit.TypeCode) { return p.CanWrite(unit.TypeCode) } From b0109619647b07d91f54f1b69084e370c623f673 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Tue, 21 Dec 2021 14:55:31 +0100 Subject: [PATCH 18/78] Move checkbox into segment --- templates/repo/issue/new_form.tmpl | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/templates/repo/issue/new_form.tmpl b/templates/repo/issue/new_form.tmpl index 4787b82645901..e20fbf1f28a0d 100644 --- a/templates/repo/issue/new_form.tmpl +++ b/templates/repo/issue/new_form.tmpl @@ -235,15 +235,16 @@ {{end}}
-
- {{if and .PageIsComparePull (not (eq .HeadRepo.FullName .BaseCompareRepo.FullName)) .CanWriteToHeadRepo}} -
-
- - + {{if and .PageIsComparePull (not (eq .HeadRepo.FullName .BaseCompareRepo.FullName)) .CanWriteToHeadRepo}} +
+
+
+ + +
-
- {{end}} + {{end}} +
From ad83a25907289e91ee80a5ce04799bdfeb94cc0b Mon Sep 17 00:00:00 2001 From: qwerty287 <80460567+qwerty287@users.noreply.github.com> Date: Wed, 22 Dec 2021 07:42:26 +0100 Subject: [PATCH 19/78] Apply suggestions from code review Co-authored-by: Gusted --- models/repo_permission.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/repo_permission.go b/models/repo_permission.go index c814c37d9d720..a492ecc71e6af 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -107,7 +107,7 @@ func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool { // CanWriteToBranch checks if the branch is writable by the user func (p *Permission) CanWriteToBranch(branch string) bool { if p.CanWrite(unit.TypeCode) { - return p.CanWrite(unit.TypeCode) + return true } prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch) @@ -126,7 +126,7 @@ func (p *Permission) CanWriteToBranch(branch string) bool { continue } if prPerm.CanWrite(unit.TypeCode) { - return prPerm.CanWrite(unit.TypeCode) + return true } } } From 2feff0b8699a47f474fd688379a66f953e114e5f Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Wed, 22 Dec 2021 15:06:27 +0100 Subject: [PATCH 20/78] len() check --- models/repo_permission.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/models/repo_permission.go b/models/repo_permission.go index c814c37d9d720..f837d0a7b3281 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -110,6 +110,10 @@ func (p *Permission) CanWriteToBranch(branch string) bool { return p.CanWrite(unit.TypeCode) } + if len(p.Units) < 1 { + return false + } + prs, err := GetUnmergedPullRequestsByHeadInfo(p.Units[0].RepoID, branch) if err != nil { return false From 819d679f68f40c2a143a001ded259fc513fff56a Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 7 Jan 2022 17:03:18 +0100 Subject: [PATCH 21/78] fmt --- models/migrations/v207.go | 2 +- modules/context/permission.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/models/migrations/v207.go b/models/migrations/v207.go index d93d206039a38..1f3999120a299 100644 --- a/models/migrations/v207.go +++ b/models/migrations/v207.go @@ -15,4 +15,4 @@ func addAllowEditsFromMaintainers(x *xorm.Engine) error { } return x.Sync2(new(PullRequest)) -} \ No newline at end of file +} diff --git a/modules/context/permission.go b/modules/context/permission.go index 99969683e7cf1..5faeeee1ee5fb 100644 --- a/modules/context/permission.go +++ b/modules/context/permission.go @@ -33,7 +33,7 @@ func RequireRepoWriter(unitType unit.Type) func(ctx *Context) { func CanEnableEditor() func(ctx *Context) { return func(ctx *Context) { if !ctx.Repo.Permission.CanWriteToBranch(ctx.Repo.BranchName) { - ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + ctx.NotFound("CanWriteToBranch denies permission", nil) return } } From c6e2a02f2fc77c1b541203392908d1885eb78dcf Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 7 Jan 2022 17:09:17 +0100 Subject: [PATCH 22/78] Fix permissions --- routers/web/web.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/routers/web/web.go b/routers/web/web.go index 59869f3e199d6..49f605278636c 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -807,12 +807,12 @@ func RegisterRoutes(m *web.Route) { m.Combo("/_upload/*", repo.MustBeAbleToUpload). Get(repo.UploadFile). Post(bindIgnErr(forms.UploadRepoFileForm{}), repo.UploadFilePost) - }, context.RepoRefByType(context.RepoRefBranch), canEnableEditor, repo.MustBeEditable) + }, repo.MustBeEditable) m.Group("", func() { m.Post("/upload-file", repo.UploadFileToServer) m.Post("/upload-remove", bindIgnErr(forms.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer) - }, context.RepoRef(), repo.MustBeEditable, repo.MustBeAbleToUpload) - }, context.RepoMustNotBeArchived(), repo.MustBeNotEmpty) + }, repo.MustBeEditable, repo.MustBeAbleToUpload) + }, context.RepoRef(), canEnableEditor, context.RepoMustNotBeArchived(), repo.MustBeNotEmpty) m.Group("/branches", func() { m.Group("/_new", func() { From f4a0983fb6d4cfeb129263bfaed11cc84a2f66ca Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 7 Jan 2022 17:10:43 +0100 Subject: [PATCH 23/78] Use not null default true --- models/migrations/v207.go | 2 +- models/pull.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/models/migrations/v207.go b/models/migrations/v207.go index 1f3999120a299..e2dd6dfc1f77a 100644 --- a/models/migrations/v207.go +++ b/models/migrations/v207.go @@ -11,7 +11,7 @@ import ( func addAllowEditsFromMaintainers(x *xorm.Engine) error { // PullRequest represents relation between pull request and repositories. type PullRequest struct { - AllowEditsFromMaintainers bool + AllowEditsFromMaintainers bool `xorm:"NOT NULL DEFAULT true"` } return x.Sync2(new(PullRequest)) diff --git a/models/pull.go b/models/pull.go index f66db06380078..749135af7a20d 100644 --- a/models/pull.go +++ b/models/pull.go @@ -78,7 +78,7 @@ type PullRequest struct { BaseBranch string ProtectedBranch *ProtectedBranch `xorm:"-"` MergeBase string `xorm:"VARCHAR(40)"` - AllowEditsFromMaintainers bool + AllowEditsFromMaintainers bool `xorm:"NOT NULL DEFAULT true"` HasMerged bool `xorm:"INDEX"` MergedCommitID string `xorm:"VARCHAR(40)"` From e55c639c6bd77a51609316aeb00aa8a4d493cfb4 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 7 Jan 2022 17:21:24 +0100 Subject: [PATCH 24/78] methods -> funcs --- models/pull.go | 6 +++--- routers/web/repo/pull.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/models/pull.go b/models/pull.go index 749135af7a20d..c5fc8cdab53a1 100644 --- a/models/pull.go +++ b/models/pull.go @@ -705,11 +705,11 @@ func (pr *PullRequest) GetHeadBranchHTMLURL() string { } // UpdateAllowEdits update if PR can be edited from maintainers -func (pr *PullRequest) UpdateAllowEdits(allow bool) error { - return pr.updateAllowEdits(db.GetEngine(db.DefaultContext), allow) +func UpdateAllowEdits(pr *PullRequest, allow bool) error { + return updateAllowEdits(db.GetEngine(db.DefaultContext), pr, allow) } -func (pr *PullRequest) updateAllowEdits(e db.Engine, allow bool) error { +func updateAllowEdits(e db.Engine, pr *PullRequest, allow bool) error { pr.AllowEditsFromMaintainers = allow if _, err := e.ID(pr.ID).Cols("allow_edits_from_maintainers").Update(pr); err != nil { return err diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index b770131a5fc9c..aabd643577805 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1490,7 +1490,7 @@ func updateAllowEditsStatus(ctx *context.Context, allow bool) { return } - err = pr.UpdateAllowEdits(allow) + err = models.UpdateAllowEdits(pr, allow) if err != nil { ctx.ServerError("UpdateAllowEdits", err) return From 4d5b07387ba71ccc2b16c63ec55c430d6cd0f0a4 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 14 Jan 2022 19:59:54 +0100 Subject: [PATCH 25/78] Rename --- models/migrations/migrations.go | 2 +- models/migrations/v207.go | 4 ++-- models/pull.go | 4 ++-- models/repo_permission.go | 2 +- routers/web/repo/pull.go | 4 ++-- services/pull/update.go | 2 +- web_src/js/features/repo-issue.js | 2 +- web_src/js/index.js | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 4b88e8981dbdb..ee5dea9015e87 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -367,7 +367,7 @@ var migrations = []Migration{ // v206 -> v207 NewMigration("Add authorize column to team_unit table", addAuthorizeColForTeamUnit), // v207 -> v208 - NewMigration("Add allow edits from maintainers to PullRequest table", addAllowEditsFromMaintainers), + NewMigration("Add allow edits from maintainers to PullRequest table", addAllowMaintainerEdit), } // GetCurrentDBVersion returns the current db version diff --git a/models/migrations/v207.go b/models/migrations/v207.go index e2dd6dfc1f77a..4895f62d721b5 100644 --- a/models/migrations/v207.go +++ b/models/migrations/v207.go @@ -8,10 +8,10 @@ import ( "xorm.io/xorm" ) -func addAllowEditsFromMaintainers(x *xorm.Engine) error { +func addAllowMaintainerEdit(x *xorm.Engine) error { // PullRequest represents relation between pull request and repositories. type PullRequest struct { - AllowEditsFromMaintainers bool `xorm:"NOT NULL DEFAULT true"` + AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT true"` } return x.Sync2(new(PullRequest)) diff --git a/models/pull.go b/models/pull.go index c5fc8cdab53a1..3fe03afdb360d 100644 --- a/models/pull.go +++ b/models/pull.go @@ -78,7 +78,7 @@ type PullRequest struct { BaseBranch string ProtectedBranch *ProtectedBranch `xorm:"-"` MergeBase string `xorm:"VARCHAR(40)"` - AllowEditsFromMaintainers bool `xorm:"NOT NULL DEFAULT true"` + AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT true"` HasMerged bool `xorm:"INDEX"` MergedCommitID string `xorm:"VARCHAR(40)"` @@ -710,7 +710,7 @@ func UpdateAllowEdits(pr *PullRequest, allow bool) error { } func updateAllowEdits(e db.Engine, pr *PullRequest, allow bool) error { - pr.AllowEditsFromMaintainers = allow + pr.AllowMaintainerEdit = allow if _, err := e.ID(pr.ID).Cols("allow_edits_from_maintainers").Update(pr); err != nil { return err } diff --git a/models/repo_permission.go b/models/repo_permission.go index e260dd76dcedf..28a08accd2f9e 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -124,7 +124,7 @@ func (p *Permission) CanWriteToBranch(branch string) bool { if err != nil { continue } - if pr.AllowEditsFromMaintainers { + if pr.AllowMaintainerEdit { prPerm, err := getUserRepoPermission(db.DefaultContext, pr.BaseRepo, p.User) if err != nil { continue diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index aabd643577805..32826ac8288f9 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1180,7 +1180,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { BaseRepo: repo, MergeBase: ci.CompareInfo.MergeBase, Type: models.PullRequestGitea, - AllowEditsFromMaintainers: form.AllowEditsFromMaintainers, + AllowMaintainerEdit: form.AllowEditsFromMaintainers, } // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt // instead of 500. @@ -1497,6 +1497,6 @@ func updateAllowEditsStatus(ctx *context.Context, allow bool) { } ctx.JSON(http.StatusOK, map[string]interface{}{ - "allow_edits": pr.AllowEditsFromMaintainers, + "allow_edits": pr.AllowMaintainerEdit, }) } diff --git a/services/pull/update.go b/services/pull/update.go index 18c5d5dbd5bed..2a12a4b249f43 100644 --- a/services/pull/update.go +++ b/services/pull/update.go @@ -114,7 +114,7 @@ func IsUserAllowedToUpdate(pull *models.PullRequest, user *user_model.User) (mer return false, false, err } - if pull.AllowEditsFromMaintainers { + if pull.AllowMaintainerEdit { mergeAllowedMaintainer, err := IsUserAllowedToMerge(pr, baseRepoPerm, user) if err != nil { return false, false, err diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 560a38e5dcd74..9e2af5cd87699 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -287,7 +287,7 @@ export function initRepoPullRequestMergeInstruction() { }); } -export function initRepoPullRequestAllowEditsFromMaintainers() { +export function initRepoPullRequestAllowMaintainerEdit() { $('#allow-edits-from-maintainers input').on('change', function () { const $label = $('#allow-edits-from-maintainers label'); let url = $label.data('url'); diff --git a/web_src/js/index.js b/web_src/js/index.js index 2167449e07993..0f5958d3c0801 100644 --- a/web_src/js/index.js +++ b/web_src/js/index.js @@ -34,7 +34,7 @@ import { initRepoIssueTimeTracking, initRepoIssueWipTitle, initRepoPullRequestMergeInstruction, - initRepoPullRequestAllowEditsFromMaintainers, + initRepoPullRequestAllowMaintainerEdit, initRepoPullRequestReview, } from './features/repo-issue.js'; import {initRepoEllipsisButton, initRepoCommitLastCommitLoader} from './features/repo-commit.js'; @@ -151,7 +151,7 @@ $(document).ready(() => { initRepoMigrationStatusChecker(); initRepoProject(); initRepoPullRequestMergeInstruction(); - initRepoPullRequestAllowEditsFromMaintainers(); + initRepoPullRequestAllowMaintainerEdit(); initRepoPullRequestReview(); initRepoRelease(); initRepoReleaseEditor(); From d21701f262583900134787112cceb2f185e40d00 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Tue, 18 Jan 2022 19:35:56 +0100 Subject: [PATCH 26/78] fmt --- models/auth/webauthn.go | 2 +- models/pull.go | 18 +++++++++--------- routers/web/repo/pull.go | 16 ++++++++-------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/models/auth/webauthn.go b/models/auth/webauthn.go index 75776f1e0ea4f..443913d77314b 100644 --- a/models/auth/webauthn.go +++ b/models/auth/webauthn.go @@ -12,9 +12,9 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" - "xorm.io/xorm" "github.com/duo-labs/webauthn/webauthn" + "xorm.io/xorm" ) // ErrWebAuthnCredentialNotExist represents a "ErrWebAuthnCRedentialNotExist" kind of error. diff --git a/models/pull.go b/models/pull.go index 3fe03afdb360d..bb552046891b7 100644 --- a/models/pull.go +++ b/models/pull.go @@ -69,15 +69,15 @@ type PullRequest struct { Issue *Issue `xorm:"-"` Index int64 - HeadRepoID int64 `xorm:"INDEX"` - HeadRepo *repo_model.Repository `xorm:"-"` - BaseRepoID int64 `xorm:"INDEX"` - BaseRepo *repo_model.Repository `xorm:"-"` - HeadBranch string - HeadCommitID string `xorm:"-"` - BaseBranch string - ProtectedBranch *ProtectedBranch `xorm:"-"` - MergeBase string `xorm:"VARCHAR(40)"` + HeadRepoID int64 `xorm:"INDEX"` + HeadRepo *repo_model.Repository `xorm:"-"` + BaseRepoID int64 `xorm:"INDEX"` + BaseRepo *repo_model.Repository `xorm:"-"` + HeadBranch string + HeadCommitID string `xorm:"-"` + BaseBranch string + ProtectedBranch *ProtectedBranch `xorm:"-"` + MergeBase string `xorm:"VARCHAR(40)"` AllowMaintainerEdit bool `xorm:"NOT NULL DEFAULT true"` HasMerged bool `xorm:"INDEX"` diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 32826ac8288f9..2e8d33bc9453b 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1172,14 +1172,14 @@ func CompareAndPullRequestPost(ctx *context.Context) { Content: form.Content, } pullRequest := &models.PullRequest{ - HeadRepoID: ci.HeadRepo.ID, - BaseRepoID: repo.ID, - HeadBranch: ci.HeadBranch, - BaseBranch: ci.BaseBranch, - HeadRepo: ci.HeadRepo, - BaseRepo: repo, - MergeBase: ci.CompareInfo.MergeBase, - Type: models.PullRequestGitea, + HeadRepoID: ci.HeadRepo.ID, + BaseRepoID: repo.ID, + HeadBranch: ci.HeadBranch, + BaseBranch: ci.BaseBranch, + HeadRepo: ci.HeadRepo, + BaseRepo: repo, + MergeBase: ci.CompareInfo.MergeBase, + Type: models.PullRequestGitea, AllowMaintainerEdit: form.AllowEditsFromMaintainers, } // FIXME: check error in the case two people send pull request at almost same time, give nice error prompt From f0b688f90c9c04d6a4fdba049fdc80ee9b139765 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Wed, 19 Jan 2022 17:09:58 +0100 Subject: [PATCH 27/78] fmt --- models/migrations/v209.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/migrations/v209.go b/models/migrations/v209.go index 37b7bb97b474f..857a1c9056cf9 100644 --- a/models/migrations/v209.go +++ b/models/migrations/v209.go @@ -15,4 +15,4 @@ func addAllowMaintainerEdit(x *xorm.Engine) error { } return x.Sync2(new(PullRequest)) -} \ No newline at end of file +} From 59bda5061412a92eed1652fd68bac2652e1cbee7 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Wed, 26 Jan 2022 17:20:20 +0100 Subject: [PATCH 28/78] Update col name --- models/pull.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/pull.go b/models/pull.go index 0aa7d81019fe5..2c2f0f93a5624 100644 --- a/models/pull.go +++ b/models/pull.go @@ -712,7 +712,7 @@ func UpdateAllowEdits(pr *PullRequest, allow bool) error { func updateAllowEdits(e db.Engine, pr *PullRequest, allow bool) error { pr.AllowMaintainerEdit = allow - if _, err := e.ID(pr.ID).Cols("allow_edits_from_maintainers").Update(pr); err != nil { + if _, err := e.ID(pr.ID).Cols("allow_maintainers_edits").Update(pr); err != nil { return err } return nil From 7add0453977778e12f8cdda194dc9a5eb7c89d72 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Mon, 7 Feb 2022 15:25:27 +0100 Subject: [PATCH 29/78] Fix 500 + disable checkbox --- templates/repo/issue/view_content/sidebar.tmpl | 2 +- web_src/js/features/repo-issue.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index cd0957f70ae0b..2cefb3580c637 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -653,7 +653,7 @@
- +
{{end}} diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 7dc1f6385c6e8..76a8c81281e24 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -290,6 +290,7 @@ export function initRepoPullRequestMergeInstruction() { export function initRepoPullRequestAllowMaintainerEdit() { $('#allow-edits-from-maintainers input').on('change', function () { + $('#allow-edits-from-maintainers input').attr("disabled", true); const $label = $('#allow-edits-from-maintainers label'); let url = $label.data('url'); if (this.checked) { // allow edits @@ -310,6 +311,10 @@ export function initRepoPullRequestAllowMaintainerEdit() { $labelElem.setAttribute('data-content', $label.data('failed')); $label.popup('show'); $labelElem.setAttribute('data-content', oldContent || ''); + $('#allow-edits-from-maintainers input').removeAttr("disabled"); + }, + success: () => { + $('#allow-edits-from-maintainers input').removeAttr("disabled"); }, }); }); From 9cd6a2c9a1229d7d200086cc3f0f858ae5638b20 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Mon, 7 Feb 2022 15:37:44 +0100 Subject: [PATCH 30/78] Fix lint --- web_src/js/features/repo-issue.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index 76a8c81281e24..e173df4c64f55 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -290,7 +290,7 @@ export function initRepoPullRequestMergeInstruction() { export function initRepoPullRequestAllowMaintainerEdit() { $('#allow-edits-from-maintainers input').on('change', function () { - $('#allow-edits-from-maintainers input').attr("disabled", true); + $('#allow-edits-from-maintainers input').attr('disabled', true); const $label = $('#allow-edits-from-maintainers label'); let url = $label.data('url'); if (this.checked) { // allow edits @@ -311,10 +311,10 @@ export function initRepoPullRequestAllowMaintainerEdit() { $labelElem.setAttribute('data-content', $label.data('failed')); $label.popup('show'); $labelElem.setAttribute('data-content', oldContent || ''); - $('#allow-edits-from-maintainers input').removeAttr("disabled"); + $('#allow-edits-from-maintainers input').removeAttr('disabled'); }, success: () => { - $('#allow-edits-from-maintainers input').removeAttr("disabled"); + $('#allow-edits-from-maintainers input').removeAttr('disabled'); }, }); }); From 4e13dfc8efba4837fa82e9c33cd37f632bcb5842 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Mon, 14 Feb 2022 17:03:50 +0100 Subject: [PATCH 31/78] Fix branch perms values --- modules/convert/convert.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/convert/convert.go b/modules/convert/convert.go index 41a044c6d74e2..39f33fffe0642 100644 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -39,12 +39,19 @@ func ToEmail(email *user_model.EmailAddress) *api.Email { func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *models.ProtectedBranch, user *user_model.User, isRepoAdmin bool) (*api.Branch, error) { if bp == nil { var hasPerm bool + var canPush bool var err error if user != nil { hasPerm, err = models.HasAccessUnit(user, repo, unit.TypeCode, perm.AccessModeWrite) if err != nil { return nil, err } + + perms, err := models.GetUserRepoPermission(repo, user) + if err != nil { + return nil, err + } + canPush = perms.CanWriteToBranch(b.Name) } return &api.Branch{ @@ -54,7 +61,7 @@ func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *mod RequiredApprovals: 0, EnableStatusCheck: false, StatusCheckContexts: []string{}, - UserCanPush: hasPerm, + UserCanPush: canPush, UserCanMerge: hasPerm, }, nil } From af05be8459c46559d950feeca7d38928bbde1bf9 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Mon, 14 Feb 2022 18:32:44 +0100 Subject: [PATCH 32/78] Handle casting error --- routers/api/v1/api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index c9bc27a473345..564f6ec245abc 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -272,8 +272,8 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { // reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin func reqRepoBranchWriter(ctx *context.APIContext) { - options := web.GetForm(ctx).(api.FileOptionInterface) - if !ctx.Repo.CanWriteToBranch(options.Branch()) && !ctx.IsUserSiteAdmin() { + options, ok := web.GetForm(ctx).(api.FileOptionInterface) + if !ok || (!ctx.Repo.CanWriteToBranch(options.Branch()) && !ctx.IsUserSiteAdmin()) { ctx.Error(http.StatusForbidden, "reqRepoBranchWriter", "user should have a permission to write to this branch") return } From ae5b39e9e82c68ed09c61e86eaca4b2cafc6a320 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Tue, 15 Feb 2022 08:16:29 +0100 Subject: [PATCH 33/78] Use single endpoint and update popup --- routers/web/repo/pull.go | 16 ++---- routers/web/web.go | 3 +- services/forms/repo_form.go | 5 ++ .../repo/issue/view_content/sidebar.tmpl | 8 ++- web_src/js/features/repo-issue.js | 57 ++++++++++--------- 5 files changed, 45 insertions(+), 44 deletions(-) diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index d6c6b5591067b..e98255209c2a5 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1456,17 +1456,8 @@ func UpdatePullRequestTarget(ctx *context.Context) { }) } -// AllowEdits allow edits from maintainers to PRs -func AllowEdits(ctx *context.Context) { - updateAllowEditsStatus(ctx, true) -} - -// DisallowEdits disallow edits from maintainers to PRs -func DisallowEdits(ctx *context.Context) { - updateAllowEditsStatus(ctx, false) -} - -func updateAllowEditsStatus(ctx *context.Context, allow bool) { +// SetAllowEdits allow edits from maintainers to PRs +func SetAllowEdits(ctx *context.Context) { pr, err := models.GetPullRequestByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) if err != nil { if models.IsErrPullRequestNotExist(err) { @@ -1493,7 +1484,8 @@ func updateAllowEditsStatus(ctx *context.Context, allow bool) { return } - err = models.UpdateAllowEdits(pr, allow) + form := web.GetForm(ctx).(*forms.UpdateAllowEditsForm) + err = models.UpdateAllowEdits(pr, form.AllowMaintainerEdit) if err != nil { ctx.ServerError("UpdateAllowEdits", err) return diff --git a/routers/web/web.go b/routers/web/web.go index b3a82df52817f..741d8724fdaf8 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -995,8 +995,7 @@ func RegisterRoutes(m *web.Route) { m.Get("/commits", context.RepoRef(), repo.ViewPullCommits) m.Post("/merge", context.RepoMustNotBeArchived(), bindIgnErr(forms.MergePullRequestForm{}), repo.MergePullRequest) m.Post("/update", repo.UpdatePullRequest) - m.Post("/allow_edits", repo.AllowEdits) - m.Post("/disallow_edits", repo.DisallowEdits) + m.Post("/set_allow_maintainer_edit", bindIgnErr(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits) m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest) m.Group("/files", func() { m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.ViewPullFiles) diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 77fc019ed0b3a..9bdc888234b7e 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -657,6 +657,11 @@ type DismissReviewForm struct { Message string } +// UpdateAllowEditsForm form for changing if PR allows edits from maintainers +type UpdateAllowEditsForm struct { + AllowMaintainerEdit bool +} + // __________ .__ // \______ \ ____ | | ____ _____ ______ ____ // | _// __ \| | _/ __ \\__ \ / ___// __ \ diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index 2cefb3580c637..1b18039d1c6f1 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -651,8 +651,12 @@ {{if and (not (eq .Issue.PullRequest.HeadRepo.FullName .Issue.PullRequest.BaseRepo.FullName)) .CanWriteToHeadRepo}}
-
- +
+
diff --git a/web_src/js/features/repo-issue.js b/web_src/js/features/repo-issue.js index e173df4c64f55..7f653ee3d62b5 100644 --- a/web_src/js/features/repo-issue.js +++ b/web_src/js/features/repo-issue.js @@ -289,34 +289,35 @@ export function initRepoPullRequestMergeInstruction() { } export function initRepoPullRequestAllowMaintainerEdit() { - $('#allow-edits-from-maintainers input').on('change', function () { - $('#allow-edits-from-maintainers input').attr('disabled', true); - const $label = $('#allow-edits-from-maintainers label'); - let url = $label.data('url'); - if (this.checked) { // allow edits - url = `${url}/allow_edits`; - } else { // disallow edits - url = `${url}/disallow_edits`; - } - $.ajax({ - url, - type: 'POST', - data: { - _csrf: csrfToken - }, - error: () => { - const $labelElem = document.getElementById('allow-edits-from-maintainers-label'); - $label.popup('destroy'); - const oldContent = $labelElem.getAttribute('data-content'); - $labelElem.setAttribute('data-content', $label.data('failed')); - $label.popup('show'); - $labelElem.setAttribute('data-content', oldContent || ''); - $('#allow-edits-from-maintainers input').removeAttr('disabled'); - }, - success: () => { - $('#allow-edits-from-maintainers input').removeAttr('disabled'); - }, - }); + const $checkbox = $('#allow-edits-from-maintainers'); + if (!$checkbox.length) return; + + const promptTip = $checkbox.attr('data-prompt-tip'); + const promptError = $checkbox.attr('data-prompt-error'); + $checkbox.popup({content: promptTip}); + $checkbox.checkbox({ + 'onChange': () => { + const checked = $checkbox.checkbox('is checked'); + let url = $checkbox.attr('data-url'); + url += '/set_allow_maintainer_edit'; + $checkbox.checkbox('set disabled'); + $.ajax({url, type: 'POST', + data: {_csrf: csrfToken, allowMaintainerEdit: checked}, + error: () => { + $checkbox.popup({ + content: promptError, + onHidden: () => { + // the error popup should be shown only once, then we restore the popup to the default message + $checkbox.popup({content: promptTip}); + }, + }); + $checkbox.popup('show'); + }, + complete: () => { + $checkbox.checkbox('set enabled'); + }, + }); + }, }); } From 9a31150450fb1530d483e96908a1a1c7bf117968 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Tue, 15 Feb 2022 08:27:56 +0100 Subject: [PATCH 34/78] Update API endpoints --- integrations/api_repo_test.go | 1 - models/migrations/migrations.go | 1 - models/migrations/v209_test.go | 1 + modules/convert/pull.go | 43 +++++++++++++++++---------------- modules/structs/pull.go | 12 +++++---- routers/api/v1/repo/pull.go | 9 +++++++ services/forms/repo_form.go | 2 +- templates/swagger/v1_json.tmpl | 8 ++++++ 8 files changed, 48 insertions(+), 29 deletions(-) diff --git a/integrations/api_repo_test.go b/integrations/api_repo_test.go index 52764a3a6909e..ce1ecb1d43d8a 100644 --- a/integrations/api_repo_test.go +++ b/integrations/api_repo_test.go @@ -468,7 +468,6 @@ func TestAPIRepoTransfer(t *testing.T) { expectedStatus int }{ // Disclaimer for test story: "user1" is an admin, "user2" is normal user and part of in owner team of org "user3" - // Transfer to a user with teams in another org should fail {ctxUserID: 1, newOwner: "user3", teams: &[]int64{5}, expectedStatus: http.StatusForbidden}, // Transfer to a user with non-existent team IDs should fail diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index cdb4af8ecd214..fb1c80995d591 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -61,7 +61,6 @@ type Version struct { // update minDBVersion accordingly var migrations = []Migration{ // Gitea 1.5.0 ends at v69 - // v70 -> v71 NewMigration("add issue_dependencies", addIssueDependencies), // v71 -> v72 diff --git a/models/migrations/v209_test.go b/models/migrations/v209_test.go index a929f95adc9c1..0ee14e37fae80 100644 --- a/models/migrations/v209_test.go +++ b/models/migrations/v209_test.go @@ -8,6 +8,7 @@ import ( "testing" "code.gitea.io/gitea/modules/timeutil" + "github.com/stretchr/testify/assert" "xorm.io/xorm/schemas" ) diff --git a/modules/convert/pull.go b/modules/convert/pull.go index 1551645a51979..43197a12fbbb2 100644 --- a/modules/convert/pull.go +++ b/modules/convert/pull.go @@ -50,27 +50,28 @@ func ToAPIPullRequest(ctx context.Context, pr *models.PullRequest, doer *user_mo } apiPullRequest := &api.PullRequest{ - ID: pr.ID, - URL: pr.Issue.HTMLURL(), - Index: pr.Index, - Poster: apiIssue.Poster, - Title: apiIssue.Title, - Body: apiIssue.Body, - Labels: apiIssue.Labels, - Milestone: apiIssue.Milestone, - Assignee: apiIssue.Assignee, - Assignees: apiIssue.Assignees, - State: apiIssue.State, - IsLocked: apiIssue.IsLocked, - Comments: apiIssue.Comments, - HTMLURL: pr.Issue.HTMLURL(), - DiffURL: pr.Issue.DiffURL(), - PatchURL: pr.Issue.PatchURL(), - HasMerged: pr.HasMerged, - MergeBase: pr.MergeBase, - Deadline: apiIssue.Deadline, - Created: pr.Issue.CreatedUnix.AsTimePtr(), - Updated: pr.Issue.UpdatedUnix.AsTimePtr(), + ID: pr.ID, + URL: pr.Issue.HTMLURL(), + Index: pr.Index, + Poster: apiIssue.Poster, + Title: apiIssue.Title, + Body: apiIssue.Body, + Labels: apiIssue.Labels, + Milestone: apiIssue.Milestone, + Assignee: apiIssue.Assignee, + Assignees: apiIssue.Assignees, + State: apiIssue.State, + IsLocked: apiIssue.IsLocked, + Comments: apiIssue.Comments, + HTMLURL: pr.Issue.HTMLURL(), + DiffURL: pr.Issue.DiffURL(), + PatchURL: pr.Issue.PatchURL(), + HasMerged: pr.HasMerged, + MergeBase: pr.MergeBase, + Deadline: apiIssue.Deadline, + Created: pr.Issue.CreatedUnix.AsTimePtr(), + Updated: pr.Issue.UpdatedUnix.AsTimePtr(), + AllowMaintainerEdit: pr.AllowMaintainerEdit, Base: &api.PRBranchInfo{ Name: pr.BaseBranch, diff --git a/modules/structs/pull.go b/modules/structs/pull.go index 653091b2f4321..b63b3edfd30a4 100644 --- a/modules/structs/pull.go +++ b/modules/structs/pull.go @@ -31,9 +31,10 @@ type PullRequest struct { Mergeable bool `json:"mergeable"` HasMerged bool `json:"merged"` // swagger:strfmt date-time - Merged *time.Time `json:"merged_at"` - MergedCommitID *string `json:"merge_commit_sha"` - MergedBy *User `json:"merged_by"` + Merged *time.Time `json:"merged_at"` + MergedCommitID *string `json:"merge_commit_sha"` + MergedBy *User `json:"merged_by"` + AllowMaintainerEdit bool `json:"allow_maintainer_edit"` Base *PRBranchInfo `json:"base"` Head *PRBranchInfo `json:"head"` @@ -90,6 +91,7 @@ type EditPullRequestOption struct { Labels []int64 `json:"labels"` State *string `json:"state"` // swagger:strfmt date-time - Deadline *time.Time `json:"due_date"` - RemoveDeadline *bool `json:"unset_due_date"` + Deadline *time.Time `json:"due_date"` + RemoveDeadline *bool `json:"unset_due_date"` + AllowMaintainerEdit *bool `json:"allow_maintainer_edit"` } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index a494cb06cc75a..5617a6813f2b0 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -615,6 +615,15 @@ func EditPullRequest(ctx *context.APIContext) { notification.NotifyPullRequestChangeTargetBranch(ctx.User, pr, form.Base) } + // update allow edits + if pr.Issue.IsPoster(ctx.User.ID) && form.AllowMaintainerEdit != nil { + err = models.UpdateAllowEdits(pr, *form.AllowMaintainerEdit) + if err != nil { + ctx.ServerError("UpdateAllowEdits", err) + return + } + } + // Refetch from database pr, err = models.GetPullRequestByIndex(ctx.Repo.Repository.ID, pr.Index) if err != nil { diff --git a/services/forms/repo_form.go b/services/forms/repo_form.go index 9bdc888234b7e..b3e9fb089a515 100644 --- a/services/forms/repo_form.go +++ b/services/forms/repo_form.go @@ -659,7 +659,7 @@ type DismissReviewForm struct { // UpdateAllowEditsForm form for changing if PR allows edits from maintainers type UpdateAllowEditsForm struct { - AllowMaintainerEdit bool + AllowMaintainerEdit bool } // __________ .__ diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 0b0b83ebbc16f..05a260805186a 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -14649,6 +14649,10 @@ "description": "EditPullRequestOption options when modify pull request", "type": "object", "properties": { + "allow_maintainer_edit": { + "type": "boolean", + "x-go-name": "AllowMaintainerEdit" + }, "assignee": { "type": "string", "x-go-name": "Assignee" @@ -16667,6 +16671,10 @@ "description": "PullRequest represents a pull request", "type": "object", "properties": { + "allow_maintainer_edit": { + "type": "boolean", + "x-go-name": "AllowMaintainerEdit" + }, "assignee": { "$ref": "#/definitions/User" }, From ec88ad911fd10536929347fd75388c3451a3bad5 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sat, 19 Feb 2022 19:16:13 +0100 Subject: [PATCH 35/78] Rm dupl loading --- routers/web/repo/pull.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index e98255209c2a5..25dcb797f2093 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1468,17 +1468,6 @@ func SetAllowEdits(ctx *context.Context) { return } - err = pr.LoadIssue() - if err != nil { - ctx.ServerError("LoadIssue", err) - return - } - - if !pr.Issue.IsPull { - ctx.Error(http.StatusNotFound) - return - } - if !ctx.IsSigned || !pr.Issue.IsPoster(ctx.User.ID) { ctx.Error(http.StatusForbidden) return From 12f0efc9f11e67c3c9a159a3895625aa92c3d8c5 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 25 Feb 2022 14:39:28 +0100 Subject: [PATCH 36/78] add right formations & text to migrations.go --- models/migrations/migrations.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 231b37e907838..af0dc326b4448 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -61,6 +61,7 @@ type Version struct { // update minDBVersion accordingly var migrations = []Migration{ // Gitea 1.5.0 ends at v69 + // v70 -> v71 NewMigration("add issue_dependencies", addIssueDependencies), // v71 -> v72 @@ -373,6 +374,9 @@ var migrations = []Migration{ NewMigration("Increase WebAuthentication CredentialID size to 410 - NO-OPED", increaseCredentialIDTo410), // v210 -> v211 NewMigration("v208 was completely broken - remigrate", remigrateU2FCredentials), + + // Gitea 1.16.2 ends at v211 + // v211 -> v212 NewMigration("Add allow edits from maintainers to PullRequest table", addAllowMaintainerEdit), } From 463618fad8ed67acf3da01e281cd62c46809cc59 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Fri, 25 Feb 2022 14:56:03 +0100 Subject: [PATCH 37/78] refactor --- models/repo_permission.go | 8 ++------ modules/context/permission.go | 2 +- modules/context/repo.go | 8 ++++---- modules/convert/convert.go | 2 +- routers/api/v1/api.go | 2 +- routers/api/v1/repo/file.go | 10 ++++++---- routers/api/v1/repo/patch.go | 2 +- routers/private/hook_pre_receive.go | 2 +- routers/web/repo/view.go | 10 +++++----- 9 files changed, 22 insertions(+), 24 deletions(-) diff --git a/models/repo_permission.go b/models/repo_permission.go index 28a08accd2f9e..366b4af31dd66 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -21,8 +21,6 @@ type Permission struct { AccessMode perm_model.AccessMode Units []*repo_model.RepoUnit UnitsMode map[unit.Type]perm_model.AccessMode - - User *user_model.User } // IsOwner returns true if current user is the owner of repository. @@ -105,7 +103,7 @@ func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool { } // CanWriteToBranch checks if the branch is writable by the user -func (p *Permission) CanWriteToBranch(branch string) bool { +func (p *Permission) CanWriteToBranch(user *user_model.User, branch string) bool { if p.CanWrite(unit.TypeCode) { return true } @@ -125,7 +123,7 @@ func (p *Permission) CanWriteToBranch(branch string) bool { continue } if pr.AllowMaintainerEdit { - prPerm, err := getUserRepoPermission(db.DefaultContext, pr.BaseRepo, p.User) + prPerm, err := getUserRepoPermission(db.DefaultContext, pr.BaseRepo, user) if err != nil { continue } @@ -199,8 +197,6 @@ func getUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use }() } - perm.User = user - // anonymous user visit private repo. // TODO: anonymous user visit public unit of private repo??? if user == nil && repo.IsPrivate { diff --git a/modules/context/permission.go b/modules/context/permission.go index 5faeeee1ee5fb..fc1bca87c05d3 100644 --- a/modules/context/permission.go +++ b/modules/context/permission.go @@ -32,7 +32,7 @@ func RequireRepoWriter(unitType unit.Type) func(ctx *Context) { // CanEnableEditor checks if the user is allowed to write to the branch of the repo func CanEnableEditor() func(ctx *Context) { return func(ctx *Context) { - if !ctx.Repo.Permission.CanWriteToBranch(ctx.Repo.BranchName) { + if !ctx.Repo.Permission.CanWriteToBranch(ctx.User, ctx.Repo.BranchName) { ctx.NotFound("CanWriteToBranch denies permission", nil) return } diff --git a/modules/context/repo.go b/modules/context/repo.go index 5a92e8345a33e..bc8aaa0d548bd 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -79,8 +79,8 @@ type Repository struct { } // CanEnableEditor returns true if repository is editable and user has proper access level. -func (r *Repository) CanEnableEditor() bool { - return r.IsViewBranch && r.Permission.CanWriteToBranch(r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived +func (r *Repository) CanEnableEditor(user *user_model.User) bool { + return r.IsViewBranch && r.Permission.CanWriteToBranch(user, r.BranchName) && r.Repository.CanEnableEditor() && !r.Repository.IsArchived } // CanCreateBranch returns true if repository is editable and user has proper access level. @@ -124,7 +124,7 @@ func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.Use sign, keyID, _, err := asymkey_service.SignCRUDAction(ctx, r.Repository.RepoPath(), doer, r.Repository.RepoPath(), git.BranchPrefix+r.BranchName) - canCommit := r.CanEnableEditor() && userCanPush + canCommit := r.CanEnableEditor(doer) && userCanPush if requireSigned { canCommit = canCommit && sign } @@ -140,7 +140,7 @@ func (r *Repository) CanCommitToBranch(ctx context.Context, doer *user_model.Use return CanCommitToBranchResults{ CanCommitToBranch: canCommit, - EditorEnabled: r.CanEnableEditor(), + EditorEnabled: r.CanEnableEditor(doer), UserCanPush: userCanPush, RequireSigned: requireSigned, WillSign: sign, diff --git a/modules/convert/convert.go b/modules/convert/convert.go index 39f33fffe0642..ad76c9f3a8b48 100644 --- a/modules/convert/convert.go +++ b/modules/convert/convert.go @@ -51,7 +51,7 @@ func ToBranch(repo *repo_model.Repository, b *git.Branch, c *git.Commit, bp *mod if err != nil { return nil, err } - canPush = perms.CanWriteToBranch(b.Name) + canPush = perms.CanWriteToBranch(user, b.Name) } return &api.Branch{ diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 564f6ec245abc..a0b65484643cc 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -273,7 +273,7 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { // reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin func reqRepoBranchWriter(ctx *context.APIContext) { options, ok := web.GetForm(ctx).(api.FileOptionInterface) - if !ok || (!ctx.Repo.CanWriteToBranch(options.Branch()) && !ctx.IsUserSiteAdmin()) { + if !ok || (!ctx.Repo.CanWriteToBranch(ctx.User, options.Branch()) && !ctx.IsUserSiteAdmin()) { ctx.Error(http.StatusForbidden, "reqRepoBranchWriter", "user should have a permission to write to this branch") return } diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 566bc0faae591..a7b711ab6a816 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -183,8 +183,10 @@ func GetEditorconfig(ctx *context.APIContext) { } // canWriteFiles returns true if repository is editable and user has proper access level. -func canWriteFiles(r *context.Repository, branch string) bool { - return r.Permission.CanWriteToBranch(branch) && !r.Repository.IsMirror && !r.Repository.IsArchived +func canWriteFiles(ctx *context.APIContext, branch string) bool { + return ctx.Repo.Permission.CanWriteToBranch(ctx.User, branch) && + !ctx.Repo.Repository.IsMirror && + !ctx.Repo.Repository.IsArchived } // canReadFiles returns true if repository is readable and user has proper access level. @@ -389,7 +391,7 @@ func handleCreateOrUpdateFileError(ctx *context.APIContext, err error) { // Called from both CreateFile or UpdateFile to handle both func createOrUpdateFile(ctx *context.APIContext, opts *files_service.UpdateRepoFileOptions) (*api.FileResponse, error) { - if !canWriteFiles(ctx.Repo, opts.OldBranch) { + if !canWriteFiles(ctx, opts.OldBranch) { return nil, models.ErrUserDoesNotHaveAccessToRepo{ UserID: ctx.User.ID, RepoName: ctx.Repo.Repository.LowerName, @@ -446,7 +448,7 @@ func DeleteFile(ctx *context.APIContext) { // "$ref": "#/responses/error" apiOpts := web.GetForm(ctx).(*api.DeleteFileOptions) - if !canWriteFiles(ctx.Repo, apiOpts.BranchName) { + if !canWriteFiles(ctx, apiOpts.BranchName) { ctx.Error(http.StatusForbidden, "DeleteFile", models.ErrUserDoesNotHaveAccessToRepo{ UserID: ctx.User.ID, RepoName: ctx.Repo.Repository.LowerName, diff --git a/routers/api/v1/repo/patch.go b/routers/api/v1/repo/patch.go index 5dd65d900f4dc..3b407ffec5fee 100644 --- a/routers/api/v1/repo/patch.go +++ b/routers/api/v1/repo/patch.go @@ -77,7 +77,7 @@ func ApplyDiffPatch(ctx *context.APIContext) { opts.Message = "apply-patch" } - if !canWriteFiles(ctx.Repo, apiOpts.BranchName) { + if !canWriteFiles(ctx, apiOpts.BranchName) { ctx.Error(http.StatusInternalServerError, "ApplyPatch", models.ErrUserDoesNotHaveAccessToRepo{ UserID: ctx.User.ID, RepoName: ctx.Repo.Repository.LowerName, diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 5c96aeac8fe52..e7d0abad65061 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -62,7 +62,7 @@ func (ctx *preReceiveContext) Perm() *models.Permission { // CanWriteCode returns true if can write code func (ctx *preReceiveContext) CanWriteCode() bool { if !ctx.checkedCanWriteCode { - ctx.canWriteCode = ctx.Perm().CanWriteToBranch(ctx.branchName) + ctx.canWriteCode = ctx.Perm().CanWriteToBranch(ctx.user, ctx.branchName) ctx.checkedCanWriteCode = true } return ctx.canWriteCode diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 79266afc0f49a..f1b8bf453f8ae 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -364,7 +364,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { } // Check permission to add or upload new file. - if ctx.Repo.CanWriteToBranch(ctx.Repo.BranchName) && ctx.Repo.IsViewBranch { + if ctx.Repo.CanWriteToBranch(ctx.User, ctx.Repo.BranchName) && ctx.Repo.IsViewBranch { ctx.Data["CanAddFile"] = !ctx.Repo.Repository.IsArchived ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled && !ctx.Repo.Repository.IsArchived } @@ -557,7 +557,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st ctx.Data["LineEscapeStatus"] = statuses } if !isLFSFile { - if ctx.Repo.CanEnableEditor() { + if ctx.Repo.CanEnableEditor(ctx.User) { if lfsLock != nil && lfsLock.OwnerID != ctx.User.ID { ctx.Data["CanEditFile"] = false ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked") @@ -567,7 +567,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } } else if !ctx.Repo.IsViewBranch { ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") - } else if !ctx.Repo.CanWriteToBranch(ctx.Repo.BranchName) { + } else if !ctx.Repo.CanWriteToBranch(ctx.User, ctx.Repo.BranchName) { ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit") } } @@ -607,7 +607,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } } - if ctx.Repo.CanEnableEditor() { + if ctx.Repo.CanEnableEditor(ctx.User) { if lfsLock != nil && lfsLock.OwnerID != ctx.User.ID { ctx.Data["CanDeleteFile"] = false ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked") @@ -617,7 +617,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } } else if !ctx.Repo.IsViewBranch { ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") - } else if !ctx.Repo.CanWriteToBranch(ctx.Repo.BranchName) { + } else if !ctx.Repo.CanWriteToBranch(ctx.User, ctx.Repo.BranchName) { ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access") } } From 723658af4a714bd6af11e96669bc19e483fc3340 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 11 Mar 2022 17:03:55 +0100 Subject: [PATCH 38/78] Move base repo loading --- models/repo_permission.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/models/repo_permission.go b/models/repo_permission.go index 366b4af31dd66..a91360984cc85 100644 --- a/models/repo_permission.go +++ b/models/repo_permission.go @@ -118,11 +118,11 @@ func (p *Permission) CanWriteToBranch(user *user_model.User, branch string) bool } for _, pr := range prs { - err = pr.LoadBaseRepo() - if err != nil { - continue - } if pr.AllowMaintainerEdit { + err = pr.LoadBaseRepo() + if err != nil { + continue + } prPerm, err := getUserRepoPermission(db.DefaultContext, pr.BaseRepo, user) if err != nil { continue From 024d44f10bcab8fc66f135081cf0e766d0d5c30b Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Fri, 11 Mar 2022 17:12:14 +0100 Subject: [PATCH 39/78] Fix checkbox visibility --- routers/web/repo/issue.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 7f33aed3851c9..62e0d02cf1328 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -1495,7 +1495,7 @@ func ViewIssue(ctx *context.Context) { if ctx.IsSigned { if err := pull.LoadHeadRepo(); err != nil { log.Error("LoadHeadRepo: %v", err) - } else if pull.HeadRepo != nil && pull.HeadBranch != pull.HeadRepo.DefaultBranch { + } else if pull.HeadRepo != nil { perm, err := models.GetUserRepoPermission(pull.HeadRepo, ctx.User) if err != nil { ctx.ServerError("GetUserRepoPermission", err) @@ -1503,11 +1503,13 @@ func ViewIssue(ctx *context.Context) { } if perm.CanWrite(unit.TypeCode) { // Check if branch is not protected - if protected, err := models.IsProtectedBranch(pull.HeadRepo.ID, pull.HeadBranch); err != nil { - log.Error("IsProtectedBranch: %v", err) - } else if !protected { - canDelete = true - ctx.Data["DeleteBranchLink"] = issue.Link() + "/cleanup" + if pull.HeadBranch != pull.HeadRepo.DefaultBranch { + if protected, err := models.IsProtectedBranch(pull.HeadRepo.ID, pull.HeadBranch); err != nil { + log.Error("IsProtectedBranch: %v", err) + } else if !protected { + canDelete = true + ctx.Data["DeleteBranchLink"] = issue.Link() + "/cleanup" + } } ctx.Data["CanWriteToHeadRepo"] = true } From 4140480be6fe41e2aa9ab06b4ca986f76856ed89 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Wed, 23 Mar 2022 17:05:30 +0100 Subject: [PATCH 40/78] Fix merge --- routers/web/repo/view.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index a3791a8ffeb5f..13a6dbdb14de4 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -558,7 +558,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st ctx.Data["LineEscapeStatus"] = statuses } if !isLFSFile { - if ctx.Repo.CanEnableEditor(ctx.User) { + if ctx.Repo.CanEnableEditor(ctx.Doer) { if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID { ctx.Data["CanEditFile"] = false ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.this_file_locked") @@ -608,13 +608,8 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } } -<<<<<<< HEAD - if ctx.Repo.CanEnableEditor(ctx.User) { - if lfsLock != nil && lfsLock.OwnerID != ctx.User.ID { -======= - if ctx.Repo.CanEnableEditor() { + if ctx.Repo.CanEnableEditor(ctx.Doer) { if lfsLock != nil && lfsLock.OwnerID != ctx.Doer.ID { ->>>>>>> main ctx.Data["CanDeleteFile"] = false ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.this_file_locked") } else { From 4753b2f09a8a27851d80dca97cd1ada92ac753a0 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Wed, 23 Mar 2022 17:13:26 +0100 Subject: [PATCH 41/78] Fix refactored parts --- modules/context/permission.go | 2 +- routers/api/v1/api.go | 2 +- routers/api/v1/repo/file.go | 2 +- routers/api/v1/repo/pull.go | 2 +- routers/private/hook_pre_receive.go | 2 +- routers/web/repo/pull.go | 2 +- routers/web/repo/view.go | 6 +++--- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/context/permission.go b/modules/context/permission.go index 577bf9f8313c0..8dc3b3cd46ec1 100644 --- a/modules/context/permission.go +++ b/modules/context/permission.go @@ -32,7 +32,7 @@ func RequireRepoWriter(unitType unit.Type) func(ctx *Context) { // CanEnableEditor checks if the user is allowed to write to the branch of the repo func CanEnableEditor() func(ctx *Context) { return func(ctx *Context) { - if !ctx.Repo.Permission.CanWriteToBranch(ctx.User, ctx.Repo.BranchName) { + if !ctx.Repo.Permission.CanWriteToBranch(ctx.Doer, ctx.Repo.BranchName) { ctx.NotFound("CanWriteToBranch denies permission", nil) return } diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index fb17a5176c7e2..dd3d553284ffa 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -273,7 +273,7 @@ func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) { // reqRepoBranchWriter user should have a permission to write to a branch, or be a site admin func reqRepoBranchWriter(ctx *context.APIContext) { options, ok := web.GetForm(ctx).(api.FileOptionInterface) - if !ok || (!ctx.Repo.CanWriteToBranch(ctx.User, options.Branch()) && !ctx.IsUserSiteAdmin()) { + if !ok || (!ctx.Repo.CanWriteToBranch(ctx.Doer, options.Branch()) && !ctx.IsUserSiteAdmin()) { ctx.Error(http.StatusForbidden, "reqRepoBranchWriter", "user should have a permission to write to this branch") return } diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index f2d68c31933e8..22f7d4622e1bc 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -184,7 +184,7 @@ func GetEditorconfig(ctx *context.APIContext) { // canWriteFiles returns true if repository is editable and user has proper access level. func canWriteFiles(ctx *context.APIContext, branch string) bool { - return ctx.Repo.Permission.CanWriteToBranch(ctx.User, branch) && + return ctx.Repo.Permission.CanWriteToBranch(ctx.Doer, branch) && !ctx.Repo.Repository.IsMirror && !ctx.Repo.Repository.IsArchived } diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index 3732c72277d5a..7d6dbbd07d080 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -616,7 +616,7 @@ func EditPullRequest(ctx *context.APIContext) { } // update allow edits - if pr.Issue.IsPoster(ctx.User.ID) && form.AllowMaintainerEdit != nil { + if pr.Issue.IsPoster(ctx.Doer.ID) && form.AllowMaintainerEdit != nil { err = models.UpdateAllowEdits(pr, *form.AllowMaintainerEdit) if err != nil { ctx.ServerError("UpdateAllowEdits", err) diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 1ef40a6043fa5..e8ae3daeb53f4 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -55,7 +55,7 @@ func (ctx *preReceiveContext) CanWriteCode() bool { if !ctx.loadPusherAndPermission() { return false } - ctx.canWriteCode = ctx.Perm().CanWriteToBranch(ctx.user, ctx.branchName) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite + ctx.canWriteCode = ctx.userPerm.CanWriteToBranch(ctx.user, ctx.branchName) || ctx.deployKeyAccessMode >= perm_model.AccessModeWrite ctx.checkedCanWriteCode = true } return ctx.canWriteCode diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 1b1f1bb79a642..a2c1b93b4af36 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1468,7 +1468,7 @@ func SetAllowEdits(ctx *context.Context) { return } - if !ctx.IsSigned || !pr.Issue.IsPoster(ctx.User.ID) { + if !ctx.IsSigned || !pr.Issue.IsPoster(ctx.Doer.ID) { ctx.Error(http.StatusForbidden) return } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 13a6dbdb14de4..c72bc38561a47 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -365,7 +365,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { } // Check permission to add or upload new file. - if ctx.Repo.CanWriteToBranch(ctx.User, ctx.Repo.BranchName) && ctx.Repo.IsViewBranch { + if ctx.Repo.CanWriteToBranch(ctx.Doer, ctx.Repo.BranchName) && ctx.Repo.IsViewBranch { ctx.Data["CanAddFile"] = !ctx.Repo.Repository.IsArchived ctx.Data["CanUploadFile"] = setting.Repository.Upload.Enabled && !ctx.Repo.Repository.IsArchived } @@ -568,7 +568,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } } else if !ctx.Repo.IsViewBranch { ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") - } else if !ctx.Repo.CanWriteToBranch(ctx.User, ctx.Repo.BranchName) { + } else if !ctx.Repo.CanWriteToBranch(ctx.Doer, ctx.Repo.BranchName) { ctx.Data["EditFileTooltip"] = ctx.Tr("repo.editor.fork_before_edit") } } @@ -618,7 +618,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } } else if !ctx.Repo.IsViewBranch { ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_be_on_a_branch") - } else if !ctx.Repo.CanWriteToBranch(ctx.User, ctx.Repo.BranchName) { + } else if !ctx.Repo.CanWriteToBranch(ctx.Doer, ctx.Repo.BranchName) { ctx.Data["DeleteFileTooltip"] = ctx.Tr("repo.editor.must_have_write_access") } } From e62e9883c7ed6406abc26e6b7f25853d5a9accd7 Mon Sep 17 00:00:00 2001 From: qwerty287 <80460567+qwerty287@users.noreply.github.com> Date: Thu, 24 Mar 2022 07:25:01 +0100 Subject: [PATCH 42/78] Update models/migrations/migrations.go Co-authored-by: 6543 <6543@obermui.de> --- models/migrations/migrations.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go index 4e7a69683961a..d368b166f4b99 100644 --- a/models/migrations/migrations.go +++ b/models/migrations/migrations.go @@ -379,7 +379,7 @@ var migrations = []Migration{ // v211 -> v212 NewMigration("Create ForeignReference table", createForeignReferenceTable), - // v122 -> v212 + // v212 -> v213 NewMigration("Add allow edits from maintainers to PullRequest table", addAllowMaintainerEdit), } From 0feca993e0a6f9a0b08fd3a6893de57788bf006a Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 5 Apr 2022 22:28:10 +0200 Subject: [PATCH 43/78] Warn on SSH connection for incorrect configuration (#19317) * Warn on SSH connection for incorrect configuration - When `setting.RepoRootPath` cannot be found(most likely due to incorrect configuration) show "Gitea: Incorrect configuration" on the client-side to help easier with debugging the problem. * Update cmd/serv.go Co-authored-by: delvh * Don't leak configuration * Update cmd/serv.go Co-authored-by: delvh Co-authored-by: wxiaoguang Co-authored-by: techknowlogick --- cmd/serv.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/serv.go b/cmd/serv.go index c834ca298acff..b106d40d28d8f 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -296,6 +296,15 @@ func runServ(c *cli.Context) error { gitcmd = exec.CommandContext(ctx, verb, repoPath) } + // Check if setting.RepoRootPath exists. It could be the case that it doesn't exist, this can happen when + // `[repository]` `ROOT` is a relative path and $GITEA_WORK_DIR isn't passed to the SSH connection. + if _, err := os.Stat(setting.RepoRootPath); err != nil { + if os.IsNotExist(err) { + return fail("Incorrect configuration.", + "Directory `[repository]` `ROOT` was not found, please check if $GITEA_WORK_DIR is passed to the SSH connection or make `[repository]` `ROOT` an absolute value.") + } + } + gitcmd.Dir = setting.RepoRootPath gitcmd.Stdout = os.Stdout gitcmd.Stdin = os.Stdin From 29904e7c176205c396314125d19b0b7c6b81cfc0 Mon Sep 17 00:00:00 2001 From: Gusted Date: Tue, 5 Apr 2022 22:41:56 +0200 Subject: [PATCH 44/78] Add `ENABLE_SSH_LOG` to debugging problems (#19316) - Add this option to the debugging problems section. So users that are trying to debug SSH-related problems will get the errors logged from `cmd/serv.go` --- docs/content/doc/advanced/logging-documentation.en-us.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/content/doc/advanced/logging-documentation.en-us.md b/docs/content/doc/advanced/logging-documentation.en-us.md index dee1dbb6d664a..bdde5bd8c4d74 100644 --- a/docs/content/doc/advanced/logging-documentation.en-us.md +++ b/docs/content/doc/advanced/logging-documentation.en-us.md @@ -287,6 +287,7 @@ MODE = console LEVEL = debug ; please set the level to debug when we are debugging a problem ROUTER = console COLORIZE = false ; this can be true if you can strip out the ansi coloring +ENABLE_SSH_LOG = true ; shows logs related to git over SSH. ``` Sometimes it will be helpful get some specific `TRACE` level logging restricted @@ -445,7 +446,7 @@ Gitea includes built-in log rotation, which should be enough for most deployment - Disable built-in log rotation by setting `LOG_ROTATE` to `false` in your `app.ini`. - Install `logrotate`. - Configure `logrotate` to match your deployment requirements, see `man 8 logrotate` for configuration syntax details. In the `postrotate/endscript` block send Gitea a `USR1` signal via `kill -USR1` or `kill -10` to the `gitea` process itself, or run `gitea manager logging release-and-reopen` (with the appropriate environment). Ensure that your configurations apply to all files emitted by Gitea loggers as described in the above sections. -- Always do `logrotate /etc/logrotate.conf --debug` to test your configurations. +- Always do `logrotate /etc/logrotate.conf --debug` to test your configurations. - If you are using docker and are running from outside of the container you can use `docker exec -u $OS_USER $CONTAINER_NAME sh -c 'gitea manager logging release-and-reopen'` or `docker exec $CONTAINER_NAME sh -c '/bin/s6-svc -1 /etc/s6/gitea/'` or send `USR1` directly to the Gitea process itself. The next `logrotate` jobs will include your configurations, so no restart is needed. You can also immediately reload `logrotate` with `logrotate /etc/logrotate.conf --force`. From eb579d46595b44bfe6127bd5e9851f56950ef94f Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Wed, 6 Apr 2022 00:17:36 +0000 Subject: [PATCH 45/78] [skip ci] Updated translations via Crowdin --- options/locale/locale_ja-JP.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 400af5a7f47e1..248e11f97fecb 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -2985,4 +2985,12 @@ error.no_unit_allowed_repo=このリポジトリのどのセクションにも error.unit_not_allowed=このセクションへのアクセスが許可されていません。 [packages] +settings.link.button=リポジトリのリンクを更新 +settings.link.success=リポジトリのリンクが正常に更新されました。 +settings.link.error=リポジトリのリンクの更新に失敗しました。 +settings.delete=パッケージ削除 +settings.delete.description=パッケージの削除は恒久的で元に戻すことはできません。 +settings.delete.notice=%s (%s) を削除しようとしています。この操作は元に戻せません。よろしいですか? +settings.delete.success=パッケージを削除しました。 +settings.delete.error=パッケージの削除に失敗しました。 From 84f66d93de2605b59dd82ddd0be096e62632e5b0 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Wed, 6 Apr 2022 03:32:09 +0200 Subject: [PATCH 46/78] Package registry changes (#19305) * removed debug logs * fixed SELECT * removed unneeded error type * use common SearchVersions method * remove empty container upload versions * return err --- integrations/api_packages_test.go | 33 ++++- models/packages/conan/references.go | 6 +- models/packages/conan/search.go | 4 +- models/packages/package.go | 8 +- models/packages/package_blob.go | 2 +- models/packages/package_file.go | 2 +- models/packages/package_version.go | 173 ++++++++++------------ routers/api/packages/composer/composer.go | 4 +- routers/api/packages/npm/npm.go | 7 +- routers/api/packages/nuget/nuget.go | 6 +- routers/api/packages/rubygems/rubygems.go | 15 +- routers/api/v1/packages/package.go | 4 +- routers/web/admin/packages.go | 6 +- routers/web/repo/packages.go | 8 +- routers/web/user/package.go | 15 +- services/packages/container/cleanup.go | 31 ++-- services/packages/packages.go | 11 +- 17 files changed, 185 insertions(+), 150 deletions(-) diff --git a/integrations/api_packages_test.go b/integrations/api_packages_test.go index 263e7cea53fac..b3f6e88d9f20a 100644 --- a/integrations/api_packages_test.go +++ b/integrations/api_packages_test.go @@ -9,11 +9,15 @@ import ( "fmt" "net/http" "testing" + "time" - "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/models/db" + packages_model "code.gitea.io/gitea/models/packages" + container_model "code.gitea.io/gitea/models/packages/container" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" api "code.gitea.io/gitea/modules/structs" + packages_service "code.gitea.io/gitea/services/packages" "github.com/stretchr/testify/assert" ) @@ -43,7 +47,7 @@ func TestPackageAPI(t *testing.T) { DecodeJSON(t, resp, &apiPackages) assert.Len(t, apiPackages, 1) - assert.Equal(t, string(packages.TypeGeneric), apiPackages[0].Type) + assert.Equal(t, string(packages_model.TypeGeneric), apiPackages[0].Type) assert.Equal(t, packageName, apiPackages[0].Name) assert.Equal(t, packageVersion, apiPackages[0].Version) assert.NotNil(t, apiPackages[0].Creator) @@ -62,7 +66,7 @@ func TestPackageAPI(t *testing.T) { var p *api.Package DecodeJSON(t, resp, &p) - assert.Equal(t, string(packages.TypeGeneric), p.Type) + assert.Equal(t, string(packages_model.TypeGeneric), p.Type) assert.Equal(t, packageName, p.Name) assert.Equal(t, packageVersion, p.Version) assert.NotNil(t, p.Creator) @@ -100,3 +104,26 @@ func TestPackageAPI(t *testing.T) { MakeRequest(t, req, http.StatusNoContent) }) } + +func TestPackageCleanup(t *testing.T) { + defer prepareTestEnv(t)() + + time.Sleep(time.Second) + + pbs, err := packages_model.FindExpiredUnreferencedBlobs(db.DefaultContext, time.Duration(0)) + assert.NoError(t, err) + assert.NotEmpty(t, pbs) + + _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, 2, packages_model.TypeContainer, "test", container_model.UploadVersion) + assert.NoError(t, err) + + err = packages_service.Cleanup(nil, time.Duration(0)) + assert.NoError(t, err) + + pbs, err = packages_model.FindExpiredUnreferencedBlobs(db.DefaultContext, time.Duration(0)) + assert.NoError(t, err) + assert.Empty(t, pbs) + + _, err = packages_model.GetInternalVersionByNameAndVersion(db.DefaultContext, 2, packages_model.TypeContainer, "test", container_model.UploadVersion) + assert.ErrorIs(t, err, packages_model.ErrPackageNotExist) +} diff --git a/models/packages/conan/references.go b/models/packages/conan/references.go index 4b7b2014304ff..e47e689af72fc 100644 --- a/models/packages/conan/references.go +++ b/models/packages/conan/references.go @@ -65,14 +65,14 @@ func findPropertyValues(ctx context.Context, propertyName string, ownerID int64, in2 := builder. Select("package_file.id"). From("package_file"). - Join("INNER", "package_version", "package_version.id = package_file.version_id"). - Join("INNER", "package", "package.id = package_version.package_id"). + InnerJoin("package_version", "package_version.id = package_file.version_id"). + InnerJoin("package", "package.id = package_version.package_id"). Where(cond) query := builder. Select("package_property.value, MAX(package_file.created_unix) AS created_unix"). From("package_property"). - Join("INNER", "package_file", "package_file.id = package_property.ref_id"). + InnerJoin("package_file", "package_file.id = package_property.ref_id"). Where(builder.Eq{"package_property.name": propertyName}.And(builder.In("package_property.ref_id", in2))). GroupBy("package_property.value"). OrderBy("created_unix DESC") diff --git a/models/packages/conan/search.go b/models/packages/conan/search.go index c274a7ce02504..6a2cfa38f5958 100644 --- a/models/packages/conan/search.go +++ b/models/packages/conan/search.go @@ -74,8 +74,8 @@ func SearchRecipes(ctx context.Context, opts *RecipeSearchOptions) ([]string, er query := builder. Select("package.name, package_version.version, package_file.id"). From("package_file"). - Join("INNER", "package_version", "package_version.id = package_file.version_id"). - Join("INNER", "package", "package.id = package_version.package_id"). + InnerJoin("package_version", "package_version.id = package_file.version_id"). + InnerJoin("package", "package.id = package_version.package_id"). Where(cond) results := make([]struct { diff --git a/models/packages/package.go b/models/packages/package.go index 05170ab3f430c..373bd86d9f7db 100644 --- a/models/packages/package.go +++ b/models/packages/package.go @@ -190,13 +190,15 @@ func GetPackagesByType(ctx context.Context, ownerID int64, packageType Type) ([] // DeletePackagesIfUnreferenced deletes a package if there are no associated versions func DeletePackagesIfUnreferenced(ctx context.Context) error { in := builder. - Select("package_version.package_id"). + Select("package.id"). From("package"). - Join("LEFT", "package_version", "package_version.package_id = package.id"). + LeftJoin("package_version", "package_version.package_id = package.id"). Where(builder.Expr("package_version.id IS NULL")) _, err := db.GetEngine(ctx). - Where(builder.In("package.id", in)). + // double select workaround for MySQL + // https://stackoverflow.com/questions/4471277/mysql-delete-from-with-subquery-as-condition + Where(builder.In("package.id", builder.Select("id").From(in, "temp"))). Delete(&Package{}) return err diff --git a/models/packages/package_blob.go b/models/packages/package_blob.go index d9a8314c8801b..8c701d4285d09 100644 --- a/models/packages/package_blob.go +++ b/models/packages/package_blob.go @@ -67,7 +67,7 @@ func FindExpiredUnreferencedBlobs(ctx context.Context, olderThan time.Duration) pbs := make([]*PackageBlob, 0, 10) return pbs, db.GetEngine(ctx). Table("package_blob"). - Join("LEFT OUTER", "package_file", "package_file.blob_id = package_blob.id"). + Join("LEFT", "package_file", "package_file.blob_id = package_blob.id"). Where("package_file.id IS NULL AND package_blob.created_unix < ?", time.Now().Add(-olderThan).Unix()). Find(&pbs) } diff --git a/models/packages/package_file.go b/models/packages/package_file.go index df36467548c55..8f304ce8ac42a 100644 --- a/models/packages/package_file.go +++ b/models/packages/package_file.go @@ -147,7 +147,7 @@ func (opts *PackageFileSearchOptions) toConds() builder.Cond { in := builder. Select("package_version.id"). From("package_version"). - Join("INNER", "package", "package.id = package_version.package_id"). + InnerJoin("package", "package.id = package_version.package_id"). Where(versionCond) cond = cond.And(builder.In("package_file.version_id", in)) diff --git a/models/packages/package_version.go b/models/packages/package_version.go index f7c6d4dc586e7..78e76c50545ee 100644 --- a/models/packages/package_version.go +++ b/models/packages/package_version.go @@ -12,16 +12,13 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" "xorm.io/builder" ) -var ( - // ErrDuplicatePackageVersion indicates a duplicated package version error - ErrDuplicatePackageVersion = errors.New("Package version does exist already") - // ErrPackageVersionNotExist indicates a package version not exist error - ErrPackageVersionNotExist = errors.New("Package version does not exist") -) +// ErrDuplicatePackageVersion indicates a duplicated package version error +var ErrDuplicatePackageVersion = errors.New("Package version already exists") func init() { db.RegisterModel(new(PackageVersion)) @@ -99,75 +96,49 @@ func GetInternalVersionByNameAndVersion(ctx context.Context, ownerID int64, pack } func getVersionByNameAndVersion(ctx context.Context, ownerID int64, packageType Type, name, version string, isInternal bool) (*PackageVersion, error) { - var cond builder.Cond = builder.Eq{ - "package.owner_id": ownerID, - "package.type": packageType, - "package.lower_name": strings.ToLower(name), - "package_version.is_internal": isInternal, - } - pv := &PackageVersion{ - LowerVersion: strings.ToLower(version), - } - has, err := db.GetEngine(ctx). - Join("INNER", "package", "package.id = package_version.package_id"). - Where(cond). - Get(pv) + pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{ + OwnerID: ownerID, + Type: packageType, + Name: SearchValue{ + ExactMatch: true, + Value: name, + }, + Version: SearchValue{ + ExactMatch: true, + Value: version, + }, + IsInternal: isInternal, + Paginator: db.NewAbsoluteListOptions(0, 1), + }) if err != nil { return nil, err } - if !has { + if len(pvs) == 0 { return nil, ErrPackageNotExist } - - return pv, nil + return pvs[0], nil } // GetVersionsByPackageType gets all versions of a specific type func GetVersionsByPackageType(ctx context.Context, ownerID int64, packageType Type) ([]*PackageVersion, error) { - var cond builder.Cond = builder.Eq{ - "package.owner_id": ownerID, - "package.type": packageType, - "package_version.is_internal": false, - } - - pvs := make([]*PackageVersion, 0, 10) - return pvs, db.GetEngine(ctx). - Where(cond). - Join("INNER", "package", "package.id = package_version.package_id"). - Find(&pvs) + pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{ + OwnerID: ownerID, + Type: packageType, + }) + return pvs, err } // GetVersionsByPackageName gets all versions of a specific package func GetVersionsByPackageName(ctx context.Context, ownerID int64, packageType Type, name string) ([]*PackageVersion, error) { - var cond builder.Cond = builder.Eq{ - "package.owner_id": ownerID, - "package.type": packageType, - "package.lower_name": strings.ToLower(name), - "package_version.is_internal": false, - } - - pvs := make([]*PackageVersion, 0, 10) - return pvs, db.GetEngine(ctx). - Where(cond). - Join("INNER", "package", "package.id = package_version.package_id"). - Find(&pvs) -} - -// GetVersionsByFilename gets all versions which are linked to a filename -func GetVersionsByFilename(ctx context.Context, ownerID int64, packageType Type, filename string) ([]*PackageVersion, error) { - var cond builder.Cond = builder.Eq{ - "package.owner_id": ownerID, - "package.type": packageType, - "package_file.lower_name": strings.ToLower(filename), - "package_version.is_internal": false, - } - - pvs := make([]*PackageVersion, 0, 10) - return pvs, db.GetEngine(ctx). - Where(cond). - Join("INNER", "package_file", "package_file.version_id = package_version.id"). - Join("INNER", "package", "package.id = package_version.package_id"). - Find(&pvs) + pvs, _, err := SearchVersions(ctx, &PackageSearchOptions{ + OwnerID: ownerID, + Type: packageType, + Name: SearchValue{ + ExactMatch: true, + Value: name, + }, + }) + return pvs, err } // DeleteVersionByID deletes a version by id @@ -183,21 +154,32 @@ func HasVersionFileReferences(ctx context.Context, versionID int64) (bool, error }) } +// SearchValue describes a value to search +// If ExactMatch is true, the field must match the value otherwise a LIKE search is performed. +type SearchValue struct { + Value string + ExactMatch bool +} + // PackageSearchOptions are options for SearchXXX methods +// Besides IsInternal are all fields optional and are not used if they have their default value (nil, "", 0) type PackageSearchOptions struct { - OwnerID int64 - RepoID int64 - Type string - PackageID int64 - QueryName string - QueryVersion string - Properties map[string]string - Sort string + OwnerID int64 + RepoID int64 + Type Type + PackageID int64 + Name SearchValue // only results with the specific name are found + Version SearchValue // only results with the specific version are found + Properties map[string]string // only results are found which contain all listed version properties with the specific value + IsInternal bool + HasFileWithName string // only results are found which are associated with a file with the specific name + HasFiles util.OptionalBool // only results are found which have associated files + Sort string db.Paginator } func (opts *PackageSearchOptions) toConds() builder.Cond { - var cond builder.Cond = builder.Eq{"package_version.is_internal": false} + var cond builder.Cond = builder.Eq{"package_version.is_internal": opts.IsInternal} if opts.OwnerID != 0 { cond = cond.And(builder.Eq{"package.owner_id": opts.OwnerID}) @@ -211,11 +193,19 @@ func (opts *PackageSearchOptions) toConds() builder.Cond { if opts.PackageID != 0 { cond = cond.And(builder.Eq{"package.id": opts.PackageID}) } - if opts.QueryName != "" { - cond = cond.And(builder.Like{"package.lower_name", strings.ToLower(opts.QueryName)}) + if opts.Name.Value != "" { + if opts.Name.ExactMatch { + cond = cond.And(builder.Eq{"package.lower_name": strings.ToLower(opts.Name.Value)}) + } else { + cond = cond.And(builder.Like{"package.lower_name", strings.ToLower(opts.Name.Value)}) + } } - if opts.QueryVersion != "" { - cond = cond.And(builder.Like{"package_version.lower_version", strings.ToLower(opts.QueryVersion)}) + if opts.Version.Value != "" { + if opts.Version.ExactMatch { + cond = cond.And(builder.Eq{"package_version.lower_version": strings.ToLower(opts.Version.Value)}) + } else { + cond = cond.And(builder.Like{"package_version.lower_version", strings.ToLower(opts.Version.Value)}) + } } if len(opts.Properties) != 0 { @@ -238,6 +228,22 @@ func (opts *PackageSearchOptions) toConds() builder.Cond { }) } + if opts.HasFileWithName != "" { + fileCond := builder.Expr("package_file.version_id = package_version.id").And(builder.Eq{"package_file.lower_name": strings.ToLower(opts.HasFileWithName)}) + + cond = cond.And(builder.Exists(builder.Select("package_file.id").From("package_file").Where(fileCond))) + } + + if !opts.HasFiles.IsNone() { + var filesCond builder.Cond = builder.Exists(builder.Select("package_file.id").From("package_file").Where(builder.Expr("package_file.version_id = package_version.id"))) + + if opts.HasFiles.IsFalse() { + filesCond = builder.Not{filesCond} + } + + cond = cond.And(filesCond) + } + return cond } @@ -297,20 +303,3 @@ func SearchLatestVersions(ctx context.Context, opts *PackageSearchOptions) ([]*P count, err := sess.FindAndCount(&pvs) return pvs, count, err } - -// FindVersionsByPropertyNameAndValue gets all package versions which are associated with a specific property + value -func FindVersionsByPropertyNameAndValue(ctx context.Context, packageID int64, name, value string) ([]*PackageVersion, error) { - var cond builder.Cond = builder.Eq{ - "package_property.ref_type": PropertyTypeVersion, - "package_property.name": name, - "package_property.value": value, - "package_version.package_id": packageID, - "package_version.is_internal": false, - } - - pvs := make([]*PackageVersion, 0, 5) - return pvs, db.GetEngine(ctx). - Where(cond). - Join("INNER", "package_property", "package_property.ref_id = package_version.id"). - Find(&pvs) -} diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go index 22a452325ef25..23de28c7f9ed2 100644 --- a/routers/api/packages/composer/composer.go +++ b/routers/api/packages/composer/composer.go @@ -63,8 +63,8 @@ func SearchPackages(ctx *context.Context) { opts := &packages_model.PackageSearchOptions{ OwnerID: ctx.Package.Owner.ID, - Type: string(packages_model.TypeComposer), - QueryName: ctx.FormTrim("q"), + Type: packages_model.TypeComposer, + Name: packages_model.SearchValue{Value: ctx.FormTrim("q")}, Paginator: &paginator, } if ctx.FormTrim("type") != "" { diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go index 50151ee5ea6f8..d127134d44558 100644 --- a/routers/api/packages/npm/npm.go +++ b/routers/api/packages/npm/npm.go @@ -256,7 +256,12 @@ func setPackageTag(tag string, pv *packages_model.PackageVersion, deleteOnly boo } defer committer.Close() - pvs, err := packages_model.FindVersionsByPropertyNameAndValue(ctx, pv.PackageID, npm_module.TagProperty, tag) + pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ + PackageID: pv.PackageID, + Properties: map[string]string{ + npm_module.TagProperty: tag, + }, + }) if err != nil { return err } diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go index 3af7155fae6d2..013c0c1e33543 100644 --- a/routers/api/packages/nuget/nuget.go +++ b/routers/api/packages/nuget/nuget.go @@ -39,9 +39,9 @@ func ServiceIndex(ctx *context.Context) { // SearchService https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages func SearchService(ctx *context.Context) { pvs, count, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ - OwnerID: ctx.Package.Owner.ID, - Type: string(packages_model.TypeNuGet), - QueryName: ctx.FormTrim("q"), + OwnerID: ctx.Package.Owner.ID, + Type: packages_model.TypeNuGet, + Name: packages_model.SearchValue{Value: ctx.FormTrim("q")}, Paginator: db.NewAbsoluteListOptions( ctx.FormInt("skip"), ctx.FormInt("take"), diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go index a5a9b779abb24..6fdd03e8ea704 100644 --- a/routers/api/packages/rubygems/rubygems.go +++ b/routers/api/packages/rubygems/rubygems.go @@ -41,7 +41,7 @@ func EnumeratePackages(ctx *context.Context) { func EnumeratePackagesLatest(ctx *context.Context) { pvs, _, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{ OwnerID: ctx.Package.Owner.ID, - Type: string(packages_model.TypeRubyGems), + Type: packages_model.TypeRubyGems, }) if err != nil { apiError(ctx, http.StatusInternalServerError, err) @@ -96,7 +96,7 @@ func ServePackageSpecification(ctx *context.Context) { return } - pvs, err := packages_model.GetVersionsByFilename(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, filename[:len(filename)-10]+"gem") + pvs, err := getVersionsByFilename(ctx, filename[:len(filename)-10]+"gem") if err != nil { apiError(ctx, http.StatusInternalServerError, err) return @@ -158,7 +158,7 @@ func ServePackageSpecification(ctx *context.Context) { func DownloadPackageFile(ctx *context.Context) { filename := ctx.Params("filename") - pvs, err := packages_model.GetVersionsByFilename(ctx, ctx.Package.Owner.ID, packages_model.TypeRubyGems, filename) + pvs, err := getVersionsByFilename(ctx, filename) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return @@ -283,3 +283,12 @@ func DeletePackage(ctx *context.Context) { apiError(ctx, http.StatusInternalServerError, err) } } + +func getVersionsByFilename(ctx *context.Context, filename string) ([]*packages_model.PackageVersion, error) { + pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ + OwnerID: ctx.Package.Owner.ID, + Type: packages_model.TypeRubyGems, + HasFileWithName: filename, + }) + return pvs, err +} diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go index 8952241222b5e..b445e8e2f830e 100644 --- a/routers/api/v1/packages/package.go +++ b/routers/api/v1/packages/package.go @@ -56,8 +56,8 @@ func ListPackages(ctx *context.APIContext) { pvs, count, err := packages.SearchVersions(ctx, &packages.PackageSearchOptions{ OwnerID: ctx.Package.Owner.ID, - Type: packageType, - QueryName: query, + Type: packages.Type(packageType), + Name: packages.SearchValue{Value: query}, Paginator: &listOptions, }) if err != nil { diff --git a/routers/web/admin/packages.go b/routers/web/admin/packages.go index 22be37526fecd..79bf025dd28c3 100644 --- a/routers/web/admin/packages.go +++ b/routers/web/admin/packages.go @@ -31,9 +31,9 @@ func Packages(ctx *context.Context) { sort := ctx.FormTrim("sort") pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ - QueryName: query, - Type: packageType, - Sort: sort, + Type: packages_model.Type(packageType), + Name: packages_model.SearchValue{Value: query}, + Sort: sort, Paginator: &db.ListOptions{ PageSize: setting.UI.PackagesPagingNum, Page: page, diff --git a/routers/web/repo/packages.go b/routers/web/repo/packages.go index f796bb0de5c70..b4db2d5787bf9 100644 --- a/routers/web/repo/packages.go +++ b/routers/web/repo/packages.go @@ -32,10 +32,10 @@ func Packages(ctx *context.Context) { PageSize: setting.UI.PackagesPagingNum, Page: page, }, - OwnerID: ctx.ContextUser.ID, - RepoID: ctx.Repo.Repository.ID, - QueryName: query, - Type: packageType, + OwnerID: ctx.ContextUser.ID, + RepoID: ctx.Repo.Repository.ID, + Type: packages.Type(packageType), + Name: packages.SearchValue{Value: query}, }) if err != nil { ctx.ServerError("SearchLatestVersions", err) diff --git a/routers/web/user/package.go b/routers/web/user/package.go index edbb4aadf6fdf..7fecf9e76880c 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -43,9 +43,9 @@ func ListPackages(ctx *context.Context) { PageSize: setting.UI.PackagesPagingNum, Page: page, }, - OwnerID: ctx.ContextUser.ID, - Type: packageType, - QueryName: query, + OwnerID: ctx.ContextUser.ID, + Type: packages_model.Type(packageType), + Name: packages_model.SearchValue{Value: query}, }) if err != nil { ctx.ServerError("SearchLatestVersions", err) @@ -219,9 +219,12 @@ func ListPackageVersions(ctx *context.Context) { } default: pvs, total, err = packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ - Paginator: pagination, - PackageID: p.ID, - QueryVersion: query, + Paginator: pagination, + PackageID: p.ID, + Version: packages_model.SearchValue{ + ExactMatch: false, + Value: query, + }, }) if err != nil { ctx.ServerError("SearchVersions", err) diff --git a/services/packages/container/cleanup.go b/services/packages/container/cleanup.go index 91992a4d7f674..390a0b7b052d5 100644 --- a/services/packages/container/cleanup.go +++ b/services/packages/container/cleanup.go @@ -10,6 +10,7 @@ import ( packages_model "code.gitea.io/gitea/models/packages" container_model "code.gitea.io/gitea/models/packages/container" + "code.gitea.io/gitea/modules/util" ) // Cleanup removes expired container data @@ -43,10 +44,7 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e return err } - versions := make(map[int64]struct{}) for _, pf := range pfs { - versions[pf.VersionID] = struct{}{} - if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil { return err } @@ -55,19 +53,26 @@ func cleanupExpiredUploadedBlobs(ctx context.Context, olderThan time.Duration) e } } - for versionID := range versions { - has, err := packages_model.HasVersionFileReferences(ctx, versionID) - if err != nil { + pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ + Type: packages_model.TypeContainer, + Version: packages_model.SearchValue{ + ExactMatch: true, + Value: container_model.UploadVersion, + }, + IsInternal: true, + HasFiles: util.OptionalBoolFalse, + }) + if err != nil { + return err + } + + for _, pv := range pvs { + if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeVersion, pv.ID); err != nil { return err } - if !has { - if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeVersion, versionID); err != nil { - return err - } - if err := packages_model.DeleteVersionByID(ctx, versionID); err != nil { - return err - } + if err := packages_model.DeleteVersionByID(ctx, pv.ID); err != nil { + return err } } diff --git a/services/packages/packages.go b/services/packages/packages.go index 7f90f80bafc28..7f25fce5b85cc 100644 --- a/services/packages/packages.go +++ b/services/packages/packages.go @@ -336,7 +336,7 @@ func DeletePackageFile(ctx context.Context, pf *packages_model.PackageFile) erro return packages_model.DeleteFileByID(ctx, pf.ID) } -// Cleanup removes old unreferenced package blobs +// Cleanup removes expired package data func Cleanup(unused context.Context, olderThan time.Duration) error { ctx, committer, err := db.TxContext() if err != nil { @@ -345,24 +345,20 @@ func Cleanup(unused context.Context, olderThan time.Duration) error { defer committer.Close() if err := container_service.Cleanup(ctx, olderThan); err != nil { - log.Error("hier") return err } if err := packages_model.DeletePackagesIfUnreferenced(ctx); err != nil { - log.Error("hier2") return err } pbs, err := packages_model.FindExpiredUnreferencedBlobs(ctx, olderThan) if err != nil { - log.Error("hier3") return err } for _, pb := range pbs { if err := packages_model.DeleteBlobByID(ctx, pb.ID); err != nil { - log.Error("hier4") return err } } @@ -403,10 +399,9 @@ func GetFileStreamByPackageVersionAndFileID(ctx context.Context, owner *user_mod pv, err := packages_model.GetVersionByID(ctx, versionID) if err != nil { - if err == packages_model.ErrPackageVersionNotExist { - return nil, nil, packages_model.ErrPackageNotExist + if err != packages_model.ErrPackageNotExist { + log.Error("Error getting package version: %v", err) } - log.Error("Error getting package version: %v", err) return nil, nil, err } From b70b908f010b2a318c2002ce907e11e6bc1e3945 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8E=9F=E4=BF=8A=E6=9D=B0?= Date: Wed, 6 Apr 2022 19:35:04 +0800 Subject: [PATCH 47/78] Show ssh command directly in template instead of i18n translation (#19335) * add missing space for generate ssh token command Signed-off-by: Junjie Yuan * Do not use i18n for ssh command * Remove unnecessary settings.ssh_token_code * Revert locale_zh-CN.ini Co-authored-by: wxiaoguang --- options/locale/locale_en-US.ini | 1 - templates/user/settings/keys_ssh.tmpl | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 609dfdbe980ab..706afdba9a589 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -664,7 +664,6 @@ ssh_invalid_token_signature = The provided SSH key, signature or token do not ma ssh_token_required = You must provide a signature for the below token ssh_token = Token ssh_token_help = You can generate a signature using: -ssh_token_code = echo -n "%s" | ssh-keygen -Y sign -n gitea -f /path_to_your_pubkey ssh_token_signature = Armored SSH signature key_signature_ssh_placeholder = Begins with '-----BEGIN SSH SIGNATURE-----' verify_ssh_key_success = SSH key '%s' has been verified. diff --git a/templates/user/settings/keys_ssh.tmpl b/templates/user/settings/keys_ssh.tmpl index 85f11c6e4ad73..5051780efe55b 100644 --- a/templates/user/settings/keys_ssh.tmpl +++ b/templates/user/settings/keys_ssh.tmpl @@ -75,7 +75,7 @@

{{$.i18n.Tr "settings.ssh_token_help"}}

-

{{$.i18n.Tr "settings.ssh_token_code" $.TokenToSign}}

+

{{printf "echo -n '%s' | ssh-keygen -Y sign -n gitea -f /path_to_your_pubkey" $.TokenToSign}}


From 1815192a1cd14046283901773371b7f946dfaa05 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Thu, 7 Apr 2022 00:17:35 +0000 Subject: [PATCH 48/78] [skip ci] Updated translations via Crowdin --- options/locale/locale_pt-BR.ini | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index 206e5afa0ff55..f3a81dd852391 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -844,6 +844,7 @@ auto_init=Inicializar o repositório (adicionando .gitignore, licença e LEIA-ME trust_model_helper=Selecione o modelo de confiança para verificação de assinatura. As opções possíveis são: trust_model_helper_collaborator=Colaborador: Confiar em assinaturas de colaboradores trust_model_helper_committer=Committer: Confiar em assinaturas que correspondem aos committers +trust_model_helper_collaborator_committer=Colaborador+Committer: Confiar em assinaturas dos colaboradores que correspondem ao committer trust_model_helper_default=Padrão: Usar o modelo de confiança padrão para esta instalação create_repo=Criar repositório default_branch=Branch Padrão @@ -1302,10 +1303,14 @@ issues.context.edit=Editar issues.context.delete=Excluir issues.no_content=Ainda não há conteúdo. issues.close_issue=Fechar +issues.pull_merged_at=`aplicou o merge do commit %[2]s em %[3]s %[4]s` +issues.manually_pull_merged_at=`aplicou o merge do commit %[2]s em %[3]s manualmente %[4]s` issues.close_comment_issue=Comentar e fechar issues.reopen_issue=Reabrir issues.reopen_comment_issue=Comentar e reabrir issues.create_comment=Comentar +issues.closed_at=`fechou esta issue %[2]s` +issues.reopened_at=`reabriu esta issue %[2]s` issues.commit_ref_at=`citou esta issue em um commit %[2]s` issues.ref_issue_from=`referenciado esta issue %[4]s %[2]s` issues.ref_pull_from=`referenciado este pull request %[4]s %[2]s` @@ -1416,6 +1421,8 @@ issues.dependency.remove=Remover issues.dependency.remove_info=Remover esta dependência issues.dependency.added_dependency=`adicionou uma nova dependência %s` issues.dependency.removed_dependency=`removeu uma dependência %s` +issues.dependency.pr_closing_blockedby=Fechamento deste pull request está bloqueado pelas seguintes issues +issues.dependency.issue_closing_blockedby=Fechamento desta issue está bloqueado pelas seguintes issues issues.dependency.issue_close_blocks=Esta issue bloqueia o fechamento das seguintes issues issues.dependency.pr_close_blocks=Este pull request bloqueia o fechamento das seguintes issues issues.dependency.issue_close_blocked=Você precisa fechar todas as issues que bloqueiam esta issue antes de poder fechá-la. @@ -1493,6 +1500,7 @@ pulls.cant_reopen_deleted_branch=Este pull request não pode ser reaberto porque pulls.merged=Merge aplicado pulls.merged_as=O pull request teve merge aplicado como %[2]s. pulls.manually_merged=Merge aplicado manualmente +pulls.manually_merged_as=O pull request foi aplicado manualmente como %[2]s. pulls.is_closed=O pull request foi fechado. pulls.has_merged=O merge deste pull request foi aplicado. pulls.title_wip_desc=`Inicie o título com o prefixo %s para prevenir o merge do pull request até que o mesmo esteja pronto.` @@ -1532,6 +1540,7 @@ pulls.no_merge_wip=O merge deste pull request não pode ser aplicado porque est pulls.no_merge_not_ready=Este pull request não está pronto para ser realizado o merge, verifique o status da revisão e as verificações de status. pulls.no_merge_access=Você não está autorizado para realizar o merge deste pull request. pulls.merge_pull_request=Criar commit de merge +pulls.rebase_merge_commit_pull_request=Rebase e criar commit de merge pulls.squash_merge_pull_request=Criar commit de squash pulls.merge_manually=Merge feito manualmente pulls.merge_commit_id=A ID de merge commit @@ -1565,6 +1574,7 @@ pulls.closed_at=`fechou este pull request %[2]s` pulls.reopened_at=`reabriu este pull request %[2]s` pulls.merge_instruction_hint=`Você também pode ver as instruçõ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. milestones.new=Novo marco @@ -1818,6 +1828,10 @@ settings.trust_model=Modelo de Confiança na Assinatura settings.trust_model.default=Modelo Padrão de Confiança settings.trust_model.collaborator=Colaborador settings.trust_model.collaborator.long=Colaborador: Confiar em assinaturas feitas por colaboradores +settings.trust_model.committer=Committer +settings.trust_model.committer.long=Committer: Confiar nas assinaturas que correspondam aos committers (isso corresponde ao GitHub e forçará commits assinados pelo Gitea a ter o Gitea como o committer) +settings.trust_model.collaboratorcommitter=Colaborador+Commiter +settings.trust_model.collaboratorcommitter.long=Colaborador+Committer: Confiar na assinatura dos colaboradores que correspondem ao autor do commit settings.wiki_delete=Excluir dados da wiki settings.wiki_delete_desc=A exclusão de dados da wiki é permanente e não pode ser desfeita. settings.wiki_delete_notices_1=- Isso excluirá e desabilitará permanentemente a wiki do repositório %s. @@ -1865,6 +1879,8 @@ settings.webhook.headers=Cabeçalhos settings.webhook.payload=Conteúdo settings.webhook.body=Corpo settings.webhook.replay.description=Executar novamente esse webhook. +settings.webhook.delivery.success=Um evento foi adicionado à fila de envio. Pode levar alguns segundos até que ele apareça no histórico de envio. +settings.githooks_desc=Hooks do Git são executados pelo próprio Git. Você pode editar arquivos de hook abaixo para configurar operações personalizadas. settings.githook_edit_desc=Se o hook não estiver ativo, o conteúdo de exemplo será apresentado. Deixar o conteúdo em branco irá desabilitar esse hook. settings.githook_name=Nome do Hook settings.githook_content=Conteúdo do Hook @@ -1919,6 +1935,7 @@ settings.event_pull_request_milestone_desc=Marco atribuído ou desatribuído ao settings.event_pull_request_comment=Comentário no Pull Request settings.event_pull_request_comment_desc=Comentário criado, editado ou excluído no pull request. settings.event_pull_request_review=Pull Request Revisado +settings.event_pull_request_review_desc=Pull request aprovado, rejeitado ou revisão comentada. settings.event_pull_request_sync=Pull Request Sincronizado settings.event_pull_request_sync_desc=Pull request sincronizado. settings.event_package=Pacote @@ -2141,6 +2158,7 @@ release.stable=Estável release.compare=Comparar release.edit=editar release.ahead.commits=%d commits +release.ahead.target=para %s desde esta versão release.source_code=Código fonte release.new_subheader=Lançamentos organizam versões do projeto. release.edit_subheader=Lançamentos organizam versões do projeto. @@ -2492,6 +2510,7 @@ orgs.members=Membros orgs.new_orga=Nova organização repos.repo_manage_panel=Gerenciamento do repositório +repos.unadopted=Repositórios Não Adotados repos.owner=Proprietário repos.name=Nome repos.private=Privado @@ -2757,6 +2776,7 @@ monitor.next=Próxima vez monitor.previous=Vez anterior monitor.execute_times=Execuções monitor.process=Processos em execução +monitor.stacktrace=Stacktraces monitor.goroutines=%d Goroutines monitor.desc=Descrição monitor.start=Hora de início From 28185316abe9ab01fd8f99e7089c5364a1279903 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Fri, 8 Apr 2022 02:59:56 +0800 Subject: [PATCH 49/78] Never use /api/v1 from Gitea UI Pages (#19318) Reusing `/api/v1` from Gitea UI Pages have pros and cons. Pros: 1) Less code copy Cons: 1) API/v1 have to support shared session with page requests. 2) You need to consider for each other when you want to change something about api/v1 or page. This PR moves all dependencies to API/v1 from UI Pages. Partially replace #16052 --- integrations/issue_test.go | 208 ++++++++++ integrations/org_test.go | 29 ++ integrations/repo_topic_test.go | 46 +++ integrations/user_test.go | 26 ++ modules/context/api.go | 16 - modules/context/context.go | 36 ++ .../api/v1/utils => modules/context}/utils.go | 18 +- routers/api/v1/notify/notifications.go | 2 +- routers/api/v1/repo/issue.go | 4 +- routers/api/v1/repo/issue_comment.go | 6 +- routers/api/v1/repo/issue_tracked_time.go | 6 +- routers/api/v1/repo/repo.go | 19 +- routers/api/v1/utils/page.go | 19 + routers/web/explore/topic.go | 42 ++ routers/web/org/teams.go | 48 +++ routers/web/repo/issue.go | 382 ++++++++++++++++++ routers/web/repo/repo.go | 112 +++++ routers/web/user/notification.go | 6 + routers/web/user/search.go | 44 ++ routers/web/user/stop_watch.go | 41 ++ routers/web/web.go | 16 +- .../repo/issue/view_content/sidebar.tmpl | 2 +- web_src/js/components/ContextPopup.vue | 2 +- web_src/js/components/DashboardRepoList.js | 4 +- web_src/js/features/comp/SearchUserBox.js | 2 +- web_src/js/features/notification.js | 2 +- web_src/js/features/org-team.js | 2 +- web_src/js/features/repo-home.js | 2 +- web_src/js/features/repo-issue.js | 8 +- web_src/js/features/repo-settings.js | 2 +- web_src/js/features/repo-template.js | 2 +- web_src/js/features/stopwatch.js | 2 +- 32 files changed, 1082 insertions(+), 74 deletions(-) create mode 100644 integrations/repo_topic_test.go rename {routers/api/v1/utils => modules/context}/utils.go (66%) create mode 100644 routers/api/v1/utils/page.go create mode 100644 routers/web/explore/topic.go create mode 100644 routers/web/user/search.go create mode 100644 routers/web/user/stop_watch.go diff --git a/integrations/issue_test.go b/integrations/issue_test.go index 6a9b48e5a41d6..8a58f59baa249 100644 --- a/integrations/issue_test.go +++ b/integrations/issue_test.go @@ -7,6 +7,7 @@ package integrations import ( "fmt" "net/http" + "net/url" "path" "strconv" "strings" @@ -20,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/indexer/issues" "code.gitea.io/gitea/modules/references" "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" "github.com/PuerkitoBio/goquery" @@ -347,3 +349,209 @@ func TestIssueRedirect(t *testing.T) { resp = session.MakeRequest(t, req, http.StatusSeeOther) assert.Equal(t, "/"+path.Join("org26", "repo_external_tracker_alpha", "pulls", "1"), test.RedirectURL(resp)) } + +func TestSearchIssues(t *testing.T) { + defer prepareTestEnv(t)() + + session := loginUser(t, "user2") + + link, _ := url.Parse("/issues/search") + req := NewRequest(t, "GET", link.String()) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiIssues []*api.Issue + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 10) + + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 10) + + since := "2000-01-01T00%3A50%3A01%2B00%3A00" // 946687801 + before := time.Unix(999307200, 0).Format(time.RFC3339) + query := url.Values{} + query.Add("since", since) + query.Add("before", before) + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 8) + query.Del("since") + query.Del("before") + + query.Add("state", "closed") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + query.Set("state", "all") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.EqualValues(t, "15", resp.Header().Get("X-Total-Count")) + assert.Len(t, apiIssues, 10) // there are more but 10 is page item limit + + query.Add("limit", "20") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 15) + + query = url.Values{"assigned": {"true"}, "state": {"all"}} + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 1) + + query = url.Values{"milestones": {"milestone1"}, "state": {"all"}} + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 1) + + query = url.Values{"milestones": {"milestone1,milestone3"}, "state": {"all"}} + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + query = url.Values{"owner": {"user2"}} // user + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 6) + + query = url.Values{"owner": {"user3"}} // organization + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 3) + + query = url.Values{"owner": {"user3"}, "team": {"team1"}} // organization + team + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) +} + +func TestSearchIssuesWithLabels(t *testing.T) { + defer prepareTestEnv(t)() + + session := loginUser(t, "user1") + + link, _ := url.Parse("/api/v1/repos/issues/search") + req := NewRequest(t, "GET", link.String()) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiIssues []*api.Issue + DecodeJSON(t, resp, &apiIssues) + + assert.Len(t, apiIssues, 10) + + query := url.Values{} + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 10) + + query.Add("labels", "label1") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + // multiple labels + query.Set("labels", "label1,label2") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + // an org label + query.Set("labels", "orglabel4") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 1) + + // org and repo label + query.Set("labels", "label2,orglabel4") + query.Add("state", "all") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) + + // org and repo label which share the same issue + query.Set("labels", "label1,orglabel4") + link.RawQuery = query.Encode() + req = NewRequest(t, "GET", link.String()) + resp = session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &apiIssues) + assert.Len(t, apiIssues, 2) +} + +func TestGetIssueInfo(t *testing.T) { + defer prepareTestEnv(t)() + + issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 10}).(*models.Issue) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issue.RepoID}).(*repo_model.Repository) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User) + assert.NoError(t, issue.LoadAttributes()) + assert.Equal(t, int64(1019307200), int64(issue.DeadlineUnix)) + assert.Equal(t, api.StateOpen, issue.State()) + + session := loginUser(t, owner.Name) + + urlStr := fmt.Sprintf("/%s/%s/issues/%d/info", owner.Name, repo.Name, issue.Index) + req := NewRequest(t, "GET", urlStr) + resp := session.MakeRequest(t, req, http.StatusOK) + var apiIssue api.Issue + DecodeJSON(t, resp, &apiIssue) + + assert.EqualValues(t, issue.ID, apiIssue.ID) +} + +func TestUpdateIssueDeadline(t *testing.T) { + defer prepareTestEnv(t)() + + issueBefore := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: 10}).(*models.Issue) + repoBefore := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: issueBefore.RepoID}).(*repo_model.Repository) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repoBefore.OwnerID}).(*user_model.User) + assert.NoError(t, issueBefore.LoadAttributes()) + assert.Equal(t, int64(1019307200), int64(issueBefore.DeadlineUnix)) + assert.Equal(t, api.StateOpen, issueBefore.State()) + + session := loginUser(t, owner.Name) + + issueURL := fmt.Sprintf("%s/%s/issues/%d", owner.Name, repoBefore.Name, issueBefore.Index) + req := NewRequest(t, "GET", issueURL) + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + urlStr := issueURL + "/deadline?_csrf=" + htmlDoc.GetCSRF() + req = NewRequestWithJSON(t, "POST", urlStr, map[string]string{ + "due_date": "2022-04-06T00:00:00.000Z", + }) + + resp = session.MakeRequest(t, req, http.StatusCreated) + var apiIssue api.IssueDeadline + DecodeJSON(t, resp, &apiIssue) + + assert.EqualValues(t, "2022-04-06", apiIssue.Deadline.Format("2006-01-02")) +} diff --git a/integrations/org_test.go b/integrations/org_test.go index 794475a9245d8..227a1b8d40376 100644 --- a/integrations/org_test.go +++ b/integrations/org_test.go @@ -10,6 +10,8 @@ import ( "strings" "testing" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" api "code.gitea.io/gitea/modules/structs" "github.com/stretchr/testify/assert" @@ -173,3 +175,30 @@ func TestOrgRestrictedUser(t *testing.T) { req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName)) restrictedSession.MakeRequest(t, req, http.StatusOK) } + +func TestTeamSearch(t *testing.T) { + defer prepareTestEnv(t)() + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User) + org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3}).(*user_model.User) + + var results TeamSearchResults + + session := loginUser(t, user.Name) + csrf := GetCSRF(t, session, "/"+org.Name) + req := NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "_team") + req.Header.Add("X-Csrf-Token", csrf) + resp := session.MakeRequest(t, req, http.StatusOK) + DecodeJSON(t, resp, &results) + assert.NotEmpty(t, results.Data) + assert.Len(t, results.Data, 1) + assert.Equal(t, "test_team", results.Data[0].Name) + + // no access if not organization member + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User) + session = loginUser(t, user5.Name) + csrf = GetCSRF(t, session, "/"+org.Name) + req = NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "team") + req.Header.Add("X-Csrf-Token", csrf) + session.MakeRequest(t, req, http.StatusNotFound) +} diff --git a/integrations/repo_topic_test.go b/integrations/repo_topic_test.go new file mode 100644 index 0000000000000..146f90e710f21 --- /dev/null +++ b/integrations/repo_topic_test.go @@ -0,0 +1,46 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integrations + +import ( + "net/http" + "net/url" + "testing" + + api "code.gitea.io/gitea/modules/structs" + "github.com/stretchr/testify/assert" +) + +func TestTopicSearch(t *testing.T) { + defer prepareTestEnv(t)() + searchURL, _ := url.Parse("/explore/topics/search") + var topics struct { + TopicNames []*api.TopicResponse `json:"topics"` + } + + query := url.Values{"page": []string{"1"}, "limit": []string{"4"}} + + searchURL.RawQuery = query.Encode() + res := MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + DecodeJSON(t, res, &topics) + assert.Len(t, topics.TopicNames, 4) + assert.EqualValues(t, "6", res.Header().Get("x-total-count")) + + query.Add("q", "topic") + searchURL.RawQuery = query.Encode() + res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + DecodeJSON(t, res, &topics) + assert.Len(t, topics.TopicNames, 2) + + query.Set("q", "database") + searchURL.RawQuery = query.Encode() + res = MakeRequest(t, NewRequest(t, "GET", searchURL.String()), http.StatusOK) + DecodeJSON(t, res, &topics) + if assert.Len(t, topics.TopicNames, 1) { + assert.EqualValues(t, 2, topics.TopicNames[0].ID) + assert.EqualValues(t, "database", topics.TopicNames[0].Name) + assert.EqualValues(t, 1, topics.TopicNames[0].RepoCount) + } +} diff --git a/integrations/user_test.go b/integrations/user_test.go index e8fbccd51e8a0..d0523d8b3a986 100644 --- a/integrations/user_test.go +++ b/integrations/user_test.go @@ -8,8 +8,11 @@ import ( "net/http" "testing" + "code.gitea.io/gitea/models" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/translation/i18n" @@ -222,3 +225,26 @@ func testExportUserGPGKeys(t *testing.T, user, expected string) { // t.Log(resp.Body.String()) assert.Equal(t, expected, resp.Body.String()) } + +func TestListStopWatches(t *testing.T) { + defer prepareTestEnv(t)() + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository) + owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID}).(*user_model.User) + + session := loginUser(t, owner.Name) + req := NewRequestf(t, "GET", "/user/stopwatches") + resp := session.MakeRequest(t, req, http.StatusOK) + var apiWatches []*api.StopWatch + DecodeJSON(t, resp, &apiWatches) + stopwatch := unittest.AssertExistsAndLoadBean(t, &models.Stopwatch{UserID: owner.ID}).(*models.Stopwatch) + issue := unittest.AssertExistsAndLoadBean(t, &models.Issue{ID: stopwatch.IssueID}).(*models.Issue) + if assert.Len(t, apiWatches, 1) { + assert.EqualValues(t, stopwatch.CreatedUnix.AsTime().Unix(), apiWatches[0].Created.Unix()) + assert.EqualValues(t, issue.Index, apiWatches[0].IssueIndex) + assert.EqualValues(t, issue.Title, apiWatches[0].IssueTitle) + assert.EqualValues(t, repo.Name, apiWatches[0].RepoName) + assert.EqualValues(t, repo.OwnerName, apiWatches[0].RepoOwnerName) + assert.Greater(t, int64(apiWatches[0].Seconds), int64(0)) + } +} diff --git a/modules/context/api.go b/modules/context/api.go index ae516503e4a23..da08f990bbfad 100644 --- a/modules/context/api.go +++ b/modules/context/api.go @@ -191,22 +191,6 @@ func (ctx *APIContext) SetLinkHeader(total, pageSize int) { } } -// SetTotalCountHeader set "X-Total-Count" header -func (ctx *APIContext) SetTotalCountHeader(total int64) { - ctx.RespHeader().Set("X-Total-Count", fmt.Sprint(total)) - ctx.AppendAccessControlExposeHeaders("X-Total-Count") -} - -// AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header -func (ctx *APIContext) AppendAccessControlExposeHeaders(names ...string) { - val := ctx.RespHeader().Get("Access-Control-Expose-Headers") - if len(val) != 0 { - ctx.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", "))) - } else { - ctx.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", ")) - } -} - // RequireCSRF requires a validated a CSRF token func (ctx *APIContext) RequireCSRF() { headerToken := ctx.Req.Header.Get(ctx.csrf.GetHeaderName()) diff --git a/modules/context/context.go b/modules/context/context.go index f73b5f19c0c67..8ede3646a4468 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -10,6 +10,7 @@ import ( "crypto/sha256" "encoding/hex" "errors" + "fmt" "html" "html/template" "io" @@ -21,6 +22,7 @@ import ( "strings" "time" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" @@ -577,6 +579,22 @@ func (ctx *Context) Value(key interface{}) interface{} { return ctx.Req.Context().Value(key) } +// SetTotalCountHeader set "X-Total-Count" header +func (ctx *Context) SetTotalCountHeader(total int64) { + ctx.RespHeader().Set("X-Total-Count", fmt.Sprint(total)) + ctx.AppendAccessControlExposeHeaders("X-Total-Count") +} + +// AppendAccessControlExposeHeaders append headers by name to "Access-Control-Expose-Headers" header +func (ctx *Context) AppendAccessControlExposeHeaders(names ...string) { + val := ctx.RespHeader().Get("Access-Control-Expose-Headers") + if len(val) != 0 { + ctx.RespHeader().Set("Access-Control-Expose-Headers", fmt.Sprintf("%s, %s", val, strings.Join(names, ", "))) + } else { + ctx.RespHeader().Set("Access-Control-Expose-Headers", strings.Join(names, ", ")) + } +} + // Handler represents a custom handler type Handler func(*Context) @@ -780,3 +798,21 @@ func Contexter() func(next http.Handler) http.Handler { }) } } + +// SearchOrderByMap represents all possible search order +var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{ + "asc": { + "alpha": db.SearchOrderByAlphabetically, + "created": db.SearchOrderByOldest, + "updated": db.SearchOrderByLeastUpdated, + "size": db.SearchOrderBySize, + "id": db.SearchOrderByID, + }, + "desc": { + "alpha": db.SearchOrderByAlphabeticallyReverse, + "created": db.SearchOrderByNewest, + "updated": db.SearchOrderByRecentUpdated, + "size": db.SearchOrderBySizeReverse, + "id": db.SearchOrderByIDReverse, + }, +} diff --git a/routers/api/v1/utils/utils.go b/modules/context/utils.go similarity index 66% rename from routers/api/v1/utils/utils.go rename to modules/context/utils.go index 7564857115715..aea51cc5d6708 100644 --- a/routers/api/v1/utils/utils.go +++ b/modules/context/utils.go @@ -2,20 +2,16 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package utils +package context import ( "net/url" "strings" "time" - - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/context" - "code.gitea.io/gitea/modules/convert" ) // GetQueryBeforeSince return parsed time (unix format) from URL query's before and since -func GetQueryBeforeSince(ctx *context.APIContext) (before, since int64, err error) { +func GetQueryBeforeSince(ctx *Context) (before, since int64, err error) { qCreatedBefore, err := prepareQueryArg(ctx, "before") if err != nil { return 0, 0, err @@ -53,16 +49,8 @@ func parseTime(value string) (int64, error) { } // prepareQueryArg unescape and trim a query arg -func prepareQueryArg(ctx *context.APIContext, name string) (value string, err error) { +func prepareQueryArg(ctx *Context, name string) (value string, err error) { value, err = url.PathUnescape(ctx.FormString(name)) value = strings.TrimSpace(value) return } - -// GetListOptions returns list options using the page and limit parameters -func GetListOptions(ctx *context.APIContext) db.ListOptions { - return db.ListOptions{ - Page: ctx.FormInt("page"), - PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), - } -} diff --git a/routers/api/v1/notify/notifications.go b/routers/api/v1/notify/notifications.go index 1821c30377fa3..c707cf4524756 100644 --- a/routers/api/v1/notify/notifications.go +++ b/routers/api/v1/notify/notifications.go @@ -26,7 +26,7 @@ func NewAvailable(ctx *context.APIContext) { } func getFindNotificationOptions(ctx *context.APIContext) *models.FindNotificationOptions { - before, since, err := utils.GetQueryBeforeSince(ctx) + before, since, err := context.GetQueryBeforeSince(ctx.Context) if err != nil { ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) return nil diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 05c9c27144b03..cd05ce12ca0b1 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -111,7 +111,7 @@ func SearchIssues(ctx *context.APIContext) { // "200": // "$ref": "#/responses/IssueList" - before, since, err := utils.GetQueryBeforeSince(ctx) + before, since, err := context.GetQueryBeforeSince(ctx.Context) if err != nil { ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) return @@ -359,7 +359,7 @@ func ListIssues(ctx *context.APIContext) { // responses: // "200": // "$ref": "#/responses/IssueList" - before, since, err := utils.GetQueryBeforeSince(ctx) + before, since, err := context.GetQueryBeforeSince(ctx.Context) if err != nil { ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) return diff --git a/routers/api/v1/repo/issue_comment.go b/routers/api/v1/repo/issue_comment.go index d63a71afc2e45..ef91a2481c273 100644 --- a/routers/api/v1/repo/issue_comment.go +++ b/routers/api/v1/repo/issue_comment.go @@ -58,7 +58,7 @@ func ListIssueComments(ctx *context.APIContext) { // "200": // "$ref": "#/responses/CommentList" - before, since, err := utils.GetQueryBeforeSince(ctx) + before, since, err := context.GetQueryBeforeSince(ctx.Context) if err != nil { ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) return @@ -150,7 +150,7 @@ func ListIssueCommentsAndTimeline(ctx *context.APIContext) { // "200": // "$ref": "#/responses/TimelineList" - before, since, err := utils.GetQueryBeforeSince(ctx) + before, since, err := context.GetQueryBeforeSince(ctx.Context) if err != nil { ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) return @@ -253,7 +253,7 @@ func ListRepoIssueComments(ctx *context.APIContext) { // "200": // "$ref": "#/responses/CommentList" - before, since, err := utils.GetQueryBeforeSince(ctx) + before, since, err := context.GetQueryBeforeSince(ctx.Context) if err != nil { ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) return diff --git a/routers/api/v1/repo/issue_tracked_time.go b/routers/api/v1/repo/issue_tracked_time.go index 19732c101f8c8..e42dc60a94c81 100644 --- a/routers/api/v1/repo/issue_tracked_time.go +++ b/routers/api/v1/repo/issue_tracked_time.go @@ -103,7 +103,7 @@ func ListTrackedTimes(ctx *context.APIContext) { opts.UserID = user.ID } - if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil { + if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Context); err != nil { ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) return } @@ -522,7 +522,7 @@ func ListTrackedTimesByRepository(ctx *context.APIContext) { } var err error - if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil { + if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Context); err != nil { ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) return } @@ -597,7 +597,7 @@ func ListMyTrackedTimes(ctx *context.APIContext) { } var err error - if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = utils.GetQueryBeforeSince(ctx); err != nil { + if opts.CreatedBeforeUnix, opts.CreatedAfterUnix, err = context.GetQueryBeforeSince(ctx.Context); err != nil { ctx.Error(http.StatusUnprocessableEntity, "GetQueryBeforeSince", err) return } diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index aca1338a27c7f..f645502590e73 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -31,23 +31,6 @@ import ( repo_service "code.gitea.io/gitea/services/repository" ) -var searchOrderByMap = map[string]map[string]db.SearchOrderBy{ - "asc": { - "alpha": db.SearchOrderByAlphabetically, - "created": db.SearchOrderByOldest, - "updated": db.SearchOrderByLeastUpdated, - "size": db.SearchOrderBySize, - "id": db.SearchOrderByID, - }, - "desc": { - "alpha": db.SearchOrderByAlphabeticallyReverse, - "created": db.SearchOrderByNewest, - "updated": db.SearchOrderByRecentUpdated, - "size": db.SearchOrderBySizeReverse, - "id": db.SearchOrderByIDReverse, - }, -} - // Search repositories via options func Search(ctx *context.APIContext) { // swagger:operation GET /repos/search repository repoSearch @@ -193,7 +176,7 @@ func Search(ctx *context.APIContext) { if len(sortOrder) == 0 { sortOrder = "asc" } - if searchModeMap, ok := searchOrderByMap[sortOrder]; ok { + if searchModeMap, ok := context.SearchOrderByMap[sortOrder]; ok { if orderBy, ok := searchModeMap[sortMode]; ok { opts.OrderBy = orderBy } else { diff --git a/routers/api/v1/utils/page.go b/routers/api/v1/utils/page.go new file mode 100644 index 0000000000000..608bec739576a --- /dev/null +++ b/routers/api/v1/utils/page.go @@ -0,0 +1,19 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package utils + +import ( + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" +) + +// GetListOptions returns list options using the page and limit parameters +func GetListOptions(ctx *context.APIContext) db.ListOptions { + return db.ListOptions{ + Page: ctx.FormInt("page"), + PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), + } +} diff --git a/routers/web/explore/topic.go b/routers/web/explore/topic.go new file mode 100644 index 0000000000000..39b87f2498091 --- /dev/null +++ b/routers/web/explore/topic.go @@ -0,0 +1,42 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package explore + +import ( + "net/http" + + "code.gitea.io/gitea/models/db" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" + api "code.gitea.io/gitea/modules/structs" +) + +// TopicSearch search for creating topic +func TopicSearch(ctx *context.Context) { + opts := &repo_model.FindTopicOptions{ + Keyword: ctx.FormString("q"), + ListOptions: db.ListOptions{ + Page: ctx.FormInt("page"), + PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), + }, + } + + topics, total, err := repo_model.FindTopics(opts) + if err != nil { + ctx.Error(http.StatusInternalServerError) + return + } + + topicResponses := make([]*api.TopicResponse, len(topics)) + for i, topic := range topics { + topicResponses[i] = convert.ToTopicResponse(topic) + } + + ctx.SetTotalCountHeader(total) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "topics": topicResponses, + }) +} diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index 034a8ce978105..31bfaea92f310 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -13,6 +13,7 @@ import ( "strings" "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/perm" repo_model "code.gitea.io/gitea/models/repo" @@ -20,7 +21,9 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/log" + api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/routers/utils" "code.gitea.io/gitea/services/forms" @@ -329,6 +332,51 @@ func TeamRepositories(ctx *context.Context) { ctx.HTML(http.StatusOK, tplTeamRepositories) } +// SearchTeam api for searching teams +func SearchTeam(ctx *context.Context) { + listOptions := db.ListOptions{ + Page: ctx.FormInt("page"), + PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), + } + + opts := &organization.SearchTeamOptions{ + UserID: ctx.Doer.ID, + Keyword: ctx.FormTrim("q"), + OrgID: ctx.Org.Organization.ID, + IncludeDesc: ctx.FormString("include_desc") == "" || ctx.FormBool("include_desc"), + ListOptions: listOptions, + } + + teams, maxResults, err := organization.SearchTeam(opts) + if err != nil { + log.Error("SearchTeam failed: %v", err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "ok": false, + "error": "SearchTeam internal failure", + }) + return + } + + apiTeams := make([]*api.Team, len(teams)) + for i := range teams { + if err := teams[i].GetUnits(); err != nil { + log.Error("Team GetUnits failed: %v", err) + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "ok": false, + "error": "SearchTeam failed to get units", + }) + return + } + apiTeams[i] = convert.ToTeam(teams[i]) + } + + ctx.SetTotalCountHeader(maxResults) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "ok": true, + "data": apiTeams, + }) +} + // EditTeam render team edit page func EditTeam(ctx *context.Context) { ctx.Data["Title"] = ctx.Org.Organization.FullName diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index eefa329a544e0..72ad393909029 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -16,6 +16,7 @@ import ( "path" "strconv" "strings" + "time" "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/db" @@ -36,6 +37,7 @@ import ( "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/templates/vars" + "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/upload" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" @@ -1765,6 +1767,20 @@ func getActionIssues(ctx *context.Context) []*models.Issue { return issues } +// GetIssueInfo get an issue of a repository +func GetIssueInfo(ctx *context.Context) { + issue, err := models.GetIssueWithAttrsByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if models.IsErrIssueNotExist(err) { + ctx.Error(http.StatusNotFound) + } else { + ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err.Error()) + } + return + } + ctx.JSON(http.StatusOK, convert.ToAPIIssue(issue)) +} + // UpdateIssueTitle change issue's title func UpdateIssueTitle(ctx *context.Context) { issue := GetActionIssue(ctx) @@ -1859,6 +1875,40 @@ func UpdateIssueContent(ctx *context.Context) { }) } +// UpdateIssueDeadline updates an issue deadline +func UpdateIssueDeadline(ctx *context.Context) { + form := web.GetForm(ctx).(*api.EditDeadlineOption) + issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, ctx.ParamsInt64(":index")) + if err != nil { + if models.IsErrIssueNotExist(err) { + ctx.NotFound("GetIssueByIndex", err) + } else { + ctx.Error(http.StatusInternalServerError, "GetIssueByIndex", err.Error()) + } + return + } + + if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) { + ctx.Error(http.StatusForbidden, "", "Not repo writer") + return + } + + var deadlineUnix timeutil.TimeStamp + var deadline time.Time + if form.Deadline != nil && !form.Deadline.IsZero() { + deadline = time.Date(form.Deadline.Year(), form.Deadline.Month(), form.Deadline.Day(), + 23, 59, 59, 0, time.Local) + deadlineUnix = timeutil.TimeStamp(deadline.Unix()) + } + + if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.Doer); err != nil { + ctx.Error(http.StatusInternalServerError, "UpdateIssueDeadline", err.Error()) + return + } + + ctx.JSON(http.StatusCreated, api.IssueDeadline{Deadline: &deadline}) +} + // UpdateIssueMilestone change issue's milestone func UpdateIssueMilestone(ctx *context.Context) { issues := getActionIssues(ctx) @@ -2055,6 +2105,338 @@ func UpdatePullReviewRequest(ctx *context.Context) { }) } +// SearchIssues searches for issues across the repositories that the user has access to +func SearchIssues(ctx *context.Context) { + before, since, err := context.GetQueryBeforeSince(ctx) + if err != nil { + ctx.Error(http.StatusUnprocessableEntity, err.Error()) + return + } + + var isClosed util.OptionalBool + switch ctx.FormString("state") { + case "closed": + isClosed = util.OptionalBoolTrue + case "all": + isClosed = util.OptionalBoolNone + default: + isClosed = util.OptionalBoolFalse + } + + // find repos user can access (for issue search) + opts := &models.SearchRepoOptions{ + Private: false, + AllPublic: true, + TopicOnly: false, + Collaborate: util.OptionalBoolNone, + // This needs to be a column that is not nil in fixtures or + // MySQL will return different results when sorting by null in some cases + OrderBy: db.SearchOrderByAlphabetically, + Actor: ctx.Doer, + } + if ctx.IsSigned { + opts.Private = true + opts.AllLimited = true + } + if ctx.FormString("owner") != "" { + owner, err := user_model.GetUserByName(ctx.FormString("owner")) + if err != nil { + if user_model.IsErrUserNotExist(err) { + ctx.Error(http.StatusBadRequest, "Owner not found", err.Error()) + } else { + ctx.Error(http.StatusInternalServerError, "GetUserByName", err.Error()) + } + return + } + opts.OwnerID = owner.ID + opts.AllLimited = false + opts.AllPublic = false + opts.Collaborate = util.OptionalBoolFalse + } + if ctx.FormString("team") != "" { + if ctx.FormString("owner") == "" { + ctx.Error(http.StatusBadRequest, "", "Owner organisation is required for filtering on team") + return + } + team, err := organization.GetTeam(opts.OwnerID, ctx.FormString("team")) + if err != nil { + if organization.IsErrTeamNotExist(err) { + ctx.Error(http.StatusBadRequest, "Team not found", err.Error()) + } else { + ctx.Error(http.StatusInternalServerError, "GetUserByName", err.Error()) + } + return + } + opts.TeamID = team.ID + } + + repoIDs, _, err := models.SearchRepositoryIDs(opts) + if err != nil { + ctx.Error(http.StatusInternalServerError, "SearchRepositoryByName", err.Error()) + return + } + + var issues []*models.Issue + var filteredCount int64 + + keyword := ctx.FormTrim("q") + if strings.IndexByte(keyword, 0) >= 0 { + keyword = "" + } + var issueIDs []int64 + if len(keyword) > 0 && len(repoIDs) > 0 { + if issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, repoIDs, keyword); err != nil { + ctx.Error(http.StatusInternalServerError, "SearchIssuesByKeyword", err.Error()) + return + } + } + + var isPull util.OptionalBool + switch ctx.FormString("type") { + case "pulls": + isPull = util.OptionalBoolTrue + case "issues": + isPull = util.OptionalBoolFalse + default: + isPull = util.OptionalBoolNone + } + + labels := ctx.FormTrim("labels") + var includedLabelNames []string + if len(labels) > 0 { + includedLabelNames = strings.Split(labels, ",") + } + + milestones := ctx.FormTrim("milestones") + var includedMilestones []string + if len(milestones) > 0 { + includedMilestones = strings.Split(milestones, ",") + } + + // this api is also used in UI, + // so the default limit is set to fit UI needs + limit := ctx.FormInt("limit") + if limit == 0 { + limit = setting.UI.IssuePagingNum + } else if limit > setting.API.MaxResponseItems { + limit = setting.API.MaxResponseItems + } + + // Only fetch the issues if we either don't have a keyword or the search returned issues + // This would otherwise return all issues if no issues were found by the search. + if len(keyword) == 0 || len(issueIDs) > 0 || len(includedLabelNames) > 0 || len(includedMilestones) > 0 { + issuesOpt := &models.IssuesOptions{ + ListOptions: db.ListOptions{ + Page: ctx.FormInt("page"), + PageSize: limit, + }, + RepoIDs: repoIDs, + IsClosed: isClosed, + IssueIDs: issueIDs, + IncludedLabelNames: includedLabelNames, + IncludeMilestones: includedMilestones, + SortType: "priorityrepo", + PriorityRepoID: ctx.FormInt64("priority_repo_id"), + IsPull: isPull, + UpdatedBeforeUnix: before, + UpdatedAfterUnix: since, + } + + ctxUserID := int64(0) + if ctx.IsSigned { + ctxUserID = ctx.Doer.ID + } + + // Filter for: Created by User, Assigned to User, Mentioning User, Review of User Requested + if ctx.FormBool("created") { + issuesOpt.PosterID = ctxUserID + } + if ctx.FormBool("assigned") { + issuesOpt.AssigneeID = ctxUserID + } + if ctx.FormBool("mentioned") { + issuesOpt.MentionedID = ctxUserID + } + if ctx.FormBool("review_requested") { + issuesOpt.ReviewRequestedID = ctxUserID + } + + if issues, err = models.Issues(issuesOpt); err != nil { + ctx.Error(http.StatusInternalServerError, "Issues", err.Error()) + return + } + + issuesOpt.ListOptions = db.ListOptions{ + Page: -1, + } + if filteredCount, err = models.CountIssues(issuesOpt); err != nil { + ctx.Error(http.StatusInternalServerError, "CountIssues", err.Error()) + return + } + } + + ctx.SetTotalCountHeader(filteredCount) + ctx.JSON(http.StatusOK, convert.ToAPIIssueList(issues)) +} + +func getUserIDForFilter(ctx *context.Context, queryName string) int64 { + userName := ctx.FormString(queryName) + if len(userName) == 0 { + return 0 + } + + user, err := user_model.GetUserByName(userName) + if user_model.IsErrUserNotExist(err) { + ctx.NotFound("", err) + return 0 + } + + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return 0 + } + + return user.ID +} + +// ListIssues list the issues of a repository +func ListIssues(ctx *context.Context) { + before, since, err := context.GetQueryBeforeSince(ctx) + if err != nil { + ctx.Error(http.StatusUnprocessableEntity, err.Error()) + return + } + + var isClosed util.OptionalBool + switch ctx.FormString("state") { + case "closed": + isClosed = util.OptionalBoolTrue + case "all": + isClosed = util.OptionalBoolNone + default: + isClosed = util.OptionalBoolFalse + } + + var issues []*models.Issue + var filteredCount int64 + + keyword := ctx.FormTrim("q") + if strings.IndexByte(keyword, 0) >= 0 { + keyword = "" + } + var issueIDs []int64 + var labelIDs []int64 + if len(keyword) > 0 { + issueIDs, err = issue_indexer.SearchIssuesByKeyword(ctx, []int64{ctx.Repo.Repository.ID}, keyword) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + } + + if splitted := strings.Split(ctx.FormString("labels"), ","); len(splitted) > 0 { + labelIDs, err = models.GetLabelIDsInRepoByNames(ctx.Repo.Repository.ID, splitted) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + } + + var mileIDs []int64 + if part := strings.Split(ctx.FormString("milestones"), ","); len(part) > 0 { + for i := range part { + // uses names and fall back to ids + // non existent milestones are discarded + mile, err := models.GetMilestoneByRepoIDANDName(ctx.Repo.Repository.ID, part[i]) + if err == nil { + mileIDs = append(mileIDs, mile.ID) + continue + } + if !models.IsErrMilestoneNotExist(err) { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + id, err := strconv.ParseInt(part[i], 10, 64) + if err != nil { + continue + } + mile, err = models.GetMilestoneByRepoID(ctx.Repo.Repository.ID, id) + if err == nil { + mileIDs = append(mileIDs, mile.ID) + continue + } + if models.IsErrMilestoneNotExist(err) { + continue + } + ctx.Error(http.StatusInternalServerError, err.Error()) + } + } + + listOptions := db.ListOptions{ + Page: ctx.FormInt("page"), + PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), + } + + var isPull util.OptionalBool + switch ctx.FormString("type") { + case "pulls": + isPull = util.OptionalBoolTrue + case "issues": + isPull = util.OptionalBoolFalse + default: + isPull = util.OptionalBoolNone + } + + // FIXME: we should be more efficient here + createdByID := getUserIDForFilter(ctx, "created_by") + if ctx.Written() { + return + } + assignedByID := getUserIDForFilter(ctx, "assigned_by") + if ctx.Written() { + return + } + mentionedByID := getUserIDForFilter(ctx, "mentioned_by") + if ctx.Written() { + return + } + + // Only fetch the issues if we either don't have a keyword or the search returned issues + // This would otherwise return all issues if no issues were found by the search. + if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 { + issuesOpt := &models.IssuesOptions{ + ListOptions: listOptions, + RepoIDs: []int64{ctx.Repo.Repository.ID}, + IsClosed: isClosed, + IssueIDs: issueIDs, + LabelIDs: labelIDs, + MilestoneIDs: mileIDs, + IsPull: isPull, + UpdatedBeforeUnix: before, + UpdatedAfterUnix: since, + PosterID: createdByID, + AssigneeID: assignedByID, + MentionedID: mentionedByID, + } + + if issues, err = models.Issues(issuesOpt); err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + + issuesOpt.ListOptions = db.ListOptions{ + Page: -1, + } + if filteredCount, err = models.CountIssues(issuesOpt); err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + } + + ctx.SetTotalCountHeader(filteredCount) + ctx.JSON(http.StatusOK, convert.ToAPIIssueList(issues)) +} + // UpdateIssueStatus change issue's status func UpdateIssueStatus(ctx *context.Context) { issues := getActionIssues(ctx) diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 989c1a565e56d..60298121dfe4b 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -20,11 +20,14 @@ import ( user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" + api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" repo_service "code.gitea.io/gitea/services/repository" @@ -503,3 +506,112 @@ func InitiateDownload(ctx *context.Context) { "complete": completed, }) } + +// SearchRepo repositories via options +func SearchRepo(ctx *context.Context) { + opts := &models.SearchRepoOptions{ + ListOptions: db.ListOptions{ + Page: ctx.FormInt("page"), + PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), + }, + Actor: ctx.Doer, + Keyword: ctx.FormTrim("q"), + OwnerID: ctx.FormInt64("uid"), + PriorityOwnerID: ctx.FormInt64("priority_owner_id"), + TeamID: ctx.FormInt64("team_id"), + TopicOnly: ctx.FormBool("topic"), + Collaborate: util.OptionalBoolNone, + Private: ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")), + Template: util.OptionalBoolNone, + StarredByID: ctx.FormInt64("starredBy"), + IncludeDescription: ctx.FormBool("includeDesc"), + } + + if ctx.FormString("template") != "" { + opts.Template = util.OptionalBoolOf(ctx.FormBool("template")) + } + + if ctx.FormBool("exclusive") { + opts.Collaborate = util.OptionalBoolFalse + } + + mode := ctx.FormString("mode") + switch mode { + case "source": + opts.Fork = util.OptionalBoolFalse + opts.Mirror = util.OptionalBoolFalse + case "fork": + opts.Fork = util.OptionalBoolTrue + case "mirror": + opts.Mirror = util.OptionalBoolTrue + case "collaborative": + opts.Mirror = util.OptionalBoolFalse + opts.Collaborate = util.OptionalBoolTrue + case "": + default: + ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid search mode: \"%s\"", mode)) + return + } + + if ctx.FormString("archived") != "" { + opts.Archived = util.OptionalBoolOf(ctx.FormBool("archived")) + } + + if ctx.FormString("is_private") != "" { + opts.IsPrivate = util.OptionalBoolOf(ctx.FormBool("is_private")) + } + + sortMode := ctx.FormString("sort") + if len(sortMode) > 0 { + sortOrder := ctx.FormString("order") + if len(sortOrder) == 0 { + sortOrder = "asc" + } + if searchModeMap, ok := context.SearchOrderByMap[sortOrder]; ok { + if orderBy, ok := searchModeMap[sortMode]; ok { + opts.OrderBy = orderBy + } else { + ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort mode: \"%s\"", sortMode)) + return + } + } else { + ctx.Error(http.StatusUnprocessableEntity, fmt.Sprintf("Invalid sort order: \"%s\"", sortOrder)) + return + } + } + + var err error + repos, count, err := models.SearchRepository(opts) + if err != nil { + ctx.JSON(http.StatusInternalServerError, api.SearchError{ + OK: false, + Error: err.Error(), + }) + return + } + + results := make([]*api.Repository, len(repos)) + for i, repo := range repos { + if err = repo.GetOwner(ctx); err != nil { + ctx.JSON(http.StatusInternalServerError, api.SearchError{ + OK: false, + Error: err.Error(), + }) + return + } + accessMode, err := models.AccessLevel(ctx.Doer, repo) + if err != nil { + ctx.JSON(http.StatusInternalServerError, api.SearchError{ + OK: false, + Error: err.Error(), + }) + } + results[i] = convert.ToRepo(repo, accessMode) + } + + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, api.SearchResults{ + OK: true, + Data: results, + }) +} diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go index 04e987924d44c..f7848de90a164 100644 --- a/routers/web/user/notification.go +++ b/routers/web/user/notification.go @@ -15,6 +15,7 @@ import ( "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" + api "code.gitea.io/gitea/modules/structs" ) const ( @@ -191,3 +192,8 @@ func NotificationPurgePost(c *context.Context) { c.Redirect(setting.AppSubURL+"/notifications", http.StatusSeeOther) } + +// NewAvailable returns the notification counts +func NewAvailable(ctx *context.APIContext) { + ctx.JSON(http.StatusOK, api.NotificationCount{New: models.CountUnread(ctx.Doer)}) +} diff --git a/routers/web/user/search.go b/routers/web/user/search.go new file mode 100644 index 0000000000000..328c7bade4f8b --- /dev/null +++ b/routers/web/user/search.go @@ -0,0 +1,44 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + "net/http" + + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" +) + +// Search search users +func Search(ctx *context.Context) { + listOptions := db.ListOptions{ + Page: ctx.FormInt("page"), + PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), + } + + users, maxResults, err := user_model.SearchUsers(&user_model.SearchUserOptions{ + Actor: ctx.Doer, + Keyword: ctx.FormTrim("q"), + UID: ctx.FormInt64("uid"), + Type: user_model.UserTypeIndividual, + ListOptions: listOptions, + }) + if err != nil { + ctx.JSON(http.StatusInternalServerError, map[string]interface{}{ + "ok": false, + "error": err.Error(), + }) + return + } + + ctx.SetTotalCountHeader(maxResults) + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "ok": true, + "data": convert.ToUsers(ctx.Doer, users), + }) +} diff --git a/routers/web/user/stop_watch.go b/routers/web/user/stop_watch.go new file mode 100644 index 0000000000000..4b16c9aeda704 --- /dev/null +++ b/routers/web/user/stop_watch.go @@ -0,0 +1,41 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package user + +import ( + "net/http" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/convert" +) + +// GetStopwatches get all stopwatches +func GetStopwatches(ctx *context.Context) { + sws, err := models.GetUserStopwatches(ctx.Doer.ID, db.ListOptions{ + Page: ctx.FormInt("page"), + PageSize: convert.ToCorrectPageSize(ctx.FormInt("limit")), + }) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + + count, err := models.CountUserStopwatches(ctx.Doer.ID) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + + apiSWs, err := convert.ToStopWatches(sws) + if err != nil { + ctx.Error(http.StatusInternalServerError, err.Error()) + return + } + + ctx.SetTotalCountHeader(count) + ctx.JSON(http.StatusOK, apiSWs) +} diff --git a/routers/web/web.go b/routers/web/web.go index a55763aedc492..7094c55a7eceb 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -20,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/public" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" + "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/validation" "code.gitea.io/gitea/modules/web" @@ -289,8 +290,13 @@ func RegisterRoutes(m *web.Route) { m.Get("/users", explore.Users) m.Get("/organizations", explore.Organizations) m.Get("/code", explore.Code) + m.Get("/topics/search", explore.TopicSearch) }, ignExploreSignIn) - m.Get("/issues", reqSignIn, user.Issues) + m.Group("/issues", func() { + m.Get("", user.Issues) + m.Get("/search", repo.SearchIssues) + }, reqSignIn) + m.Get("/pulls", reqSignIn, user.Pulls) m.Get("/milestones", reqSignIn, reqMilestonesDashboardPageEnabled, user.Milestones) @@ -421,6 +427,8 @@ func RegisterRoutes(m *web.Route) { m.Post("/forgot_password", auth.ForgotPasswdPost) m.Post("/logout", auth.SignOut) m.Get("/task/{task}", user.TaskStatus) + m.Get("/stopwatches", user.GetStopwatches, reqSignIn) + m.Get("/search", user.Search, ignExploreSignIn) }) // ***** END: User ***** @@ -606,6 +614,7 @@ func RegisterRoutes(m *web.Route) { m.Group("/{org}", func() { m.Get("/teams/new", org.NewTeam) m.Post("/teams/new", bindIgnErr(forms.CreateTeamForm{}), org.NewTeamPost) + m.Get("/teams/-/search", org.SearchTeam) m.Get("/teams/{team}/edit", org.EditTeam) m.Post("/teams/{team}/edit", bindIgnErr(forms.CreateTeamForm{}), org.EditTeamPost) m.Post("/teams/{team}/delete", org.DeleteTeam) @@ -670,6 +679,7 @@ func RegisterRoutes(m *web.Route) { m.Combo("/{repoid}").Get(repo.Fork). Post(bindIgnErr(forms.CreateRepoForm{}), repo.ForkPost) }, context.RepoIDAssignment(), context.UnitTypes(), reqRepoCodeReader) + m.Get("/search", repo.SearchRepo) }, reqSignIn) m.Group("/{username}/-", func() { @@ -812,13 +822,16 @@ func RegisterRoutes(m *web.Route) { Post(bindIgnErr(forms.CreateIssueForm{}), repo.NewIssuePost) m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate) }) + m.Get("/search", repo.ListIssues) }, context.RepoMustNotBeArchived(), reqRepoIssueReader) // FIXME: should use different URLs but mostly same logic for comments of issue and pull request. // So they can apply their own enable/disable logic on routers. m.Group("/{type:issues|pulls}", func() { m.Group("/{index}", func() { + m.Get("/info", repo.GetIssueInfo) m.Post("/title", repo.UpdateIssueTitle) m.Post("/content", repo.UpdateIssueContent) + m.Post("/deadline", bindIgnErr(structs.EditDeadlineOption{}), repo.UpdateIssueDeadline) m.Post("/watch", repo.IssueWatch) m.Post("/ref", repo.UpdateIssueRef) m.Group("/dependency", func() { @@ -1197,6 +1210,7 @@ func RegisterRoutes(m *web.Route) { m.Get("", user.Notifications) m.Post("/status", user.NotificationStatusPost) m.Post("/purge", user.NotificationPurgePost) + m.Get("/new", user.NewAvailable) }, reqSignIn) if setting.API.EnableSwagger { diff --git a/templates/repo/issue/view_content/sidebar.tmpl b/templates/repo/issue/view_content/sidebar.tmpl index a16471e0d39a2..39c67eefcb907 100644 --- a/templates/repo/issue/view_content/sidebar.tmpl +++ b/templates/repo/issue/view_content/sidebar.tmpl @@ -429,7 +429,7 @@ {{if and .HasIssuesOrPullsWritePermission (not .Repository.IsArchived)}}