feat: vendor gitea 1.16.2
This commit is contained in:
@@ -24,6 +24,7 @@ import (
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web"
|
||||
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||
"code.gitea.io/gitea/routers/common"
|
||||
@@ -32,6 +33,61 @@ import (
|
||||
issue_service "code.gitea.io/gitea/services/issue"
|
||||
)
|
||||
|
||||
// buildSearchIssuesRepoIDs builds the list of repository IDs for issue search based on query parameters.
|
||||
// It returns repoIDs, allPublic flag, and any error that occurred.
|
||||
func buildSearchIssuesRepoIDs(ctx *context.APIContext) (repoIDs []int64, allPublic bool, err error) {
|
||||
opts := repo_model.SearchRepoOptions{
|
||||
Private: false,
|
||||
AllPublic: true,
|
||||
TopicOnly: false,
|
||||
Collaborate: optional.None[bool](),
|
||||
// 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
|
||||
}
|
||||
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||
if ctx.FormString("owner") != "" {
|
||||
owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner"))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
opts.OwnerID = owner.ID
|
||||
opts.AllLimited = false
|
||||
opts.AllPublic = false
|
||||
opts.Collaborate = optional.Some(false)
|
||||
}
|
||||
if ctx.FormString("team") != "" {
|
||||
if ctx.FormString("owner") == "" {
|
||||
return nil, false, util.NewInvalidArgumentErrorf("owner organisation is required for filtering on team")
|
||||
}
|
||||
team, err := organization.GetTeam(ctx, opts.OwnerID, ctx.FormString("team"))
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
opts.TeamID = team.ID
|
||||
}
|
||||
|
||||
if opts.AllPublic {
|
||||
allPublic = true
|
||||
opts.AllPublic = false // set it false to avoid returning too many repos, we could filter by indexer
|
||||
}
|
||||
repoIDs, _, err = repo_model.SearchRepositoryIDs(ctx, opts)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if len(repoIDs) == 0 {
|
||||
// no repos found, don't let the indexer return all repos
|
||||
repoIDs = []int64{0}
|
||||
}
|
||||
|
||||
return repoIDs, allPublic, nil
|
||||
}
|
||||
|
||||
// SearchIssues searches for issues across the repositories that the user has access to
|
||||
func SearchIssues(ctx *context.APIContext) {
|
||||
// swagger:operation GET /repos/issues/search issue issueSearchIssues
|
||||
@@ -58,11 +114,6 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
// in: query
|
||||
// description: Search string
|
||||
// type: string
|
||||
// - name: priority_repo_id
|
||||
// in: query
|
||||
// description: Repository ID to prioritize in the results
|
||||
// type: integer
|
||||
// format: int64
|
||||
// - name: type
|
||||
// in: query
|
||||
// description: Filter by issue type
|
||||
@@ -107,6 +158,10 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
// in: query
|
||||
// description: Filter by repository owner
|
||||
// type: string
|
||||
// - name: created_by
|
||||
// in: query
|
||||
// description: Only show items which were created by the given user
|
||||
// type: string
|
||||
// - name: team
|
||||
// in: query
|
||||
// description: Filter by team (requires organization owner parameter)
|
||||
@@ -136,81 +191,16 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
var isClosed optional.Option[bool]
|
||||
switch ctx.FormString("state") {
|
||||
case "closed":
|
||||
isClosed = optional.Some(true)
|
||||
case "all":
|
||||
isClosed = optional.None[bool]()
|
||||
default:
|
||||
isClosed = optional.Some(false)
|
||||
}
|
||||
isClosed := common.ParseIssueFilterStateIsClosed(ctx.FormString("state"))
|
||||
|
||||
var (
|
||||
repoIDs []int64
|
||||
allPublic bool
|
||||
)
|
||||
{
|
||||
// find repos user can access (for issue search)
|
||||
opts := repo_model.SearchRepoOptions{
|
||||
Private: false,
|
||||
AllPublic: true,
|
||||
TopicOnly: false,
|
||||
Collaborate: optional.None[bool](),
|
||||
// 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 = !ctx.PublicOnly
|
||||
opts.AllLimited = true
|
||||
}
|
||||
if ctx.FormString("owner") != "" {
|
||||
owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner"))
|
||||
if err != nil {
|
||||
if user_model.IsErrUserNotExist(err) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
opts.OwnerID = owner.ID
|
||||
opts.AllLimited = false
|
||||
opts.AllPublic = false
|
||||
opts.Collaborate = optional.Some(false)
|
||||
}
|
||||
if ctx.FormString("team") != "" {
|
||||
if ctx.FormString("owner") == "" {
|
||||
ctx.APIError(http.StatusBadRequest, "Owner organisation is required for filtering on team")
|
||||
return
|
||||
}
|
||||
team, err := organization.GetTeam(ctx, opts.OwnerID, ctx.FormString("team"))
|
||||
if err != nil {
|
||||
if organization.IsErrTeamNotExist(err) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
opts.TeamID = team.ID
|
||||
}
|
||||
|
||||
if opts.AllPublic {
|
||||
allPublic = true
|
||||
opts.AllPublic = false // set it false to avoid returning too many repos, we could filter by indexer
|
||||
}
|
||||
repoIDs, _, err = repo_model.SearchRepositoryIDs(ctx, opts)
|
||||
if err != nil {
|
||||
repoIDs, allPublic, err := buildSearchIssuesRepoIDs(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, util.ErrNotExist) || errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
} else {
|
||||
ctx.APIErrorInternal(err)
|
||||
return
|
||||
}
|
||||
if len(repoIDs) == 0 {
|
||||
// no repos found, don't let the indexer return all repos
|
||||
repoIDs = []int64{0}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
keyword := ctx.FormTrim("q")
|
||||
@@ -218,15 +208,7 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
keyword = ""
|
||||
}
|
||||
|
||||
var isPull optional.Option[bool]
|
||||
switch ctx.FormString("type") {
|
||||
case "pulls":
|
||||
isPull = optional.Some(true)
|
||||
case "issues":
|
||||
isPull = optional.Some(false)
|
||||
default:
|
||||
isPull = optional.None[bool]()
|
||||
}
|
||||
isPull := common.ParseIssueFilterTypeIsPull(ctx.FormString("type"))
|
||||
|
||||
var includedAnyLabels []int64
|
||||
{
|
||||
@@ -256,14 +238,7 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
limit := util.IfZero(ctx.FormInt("limit"), setting.API.DefaultPagingNum)
|
||||
|
||||
searchOpt := &issue_indexer.SearchOptions{
|
||||
Paginator: &db.ListOptions{
|
||||
@@ -287,6 +262,14 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
searchOpt.UpdatedBeforeUnix = optional.Some(before)
|
||||
}
|
||||
|
||||
createdByID := getUserIDForFilter(ctx, "created_by")
|
||||
if ctx.Written() {
|
||||
return
|
||||
}
|
||||
if createdByID > 0 {
|
||||
searchOpt.PosterID = strconv.FormatInt(createdByID, 10)
|
||||
}
|
||||
|
||||
if ctx.IsSigned {
|
||||
ctxUserID := ctx.Doer.ID
|
||||
if ctx.FormBool("created") {
|
||||
@@ -306,10 +289,6 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: It's unsupported to sort by priority repo when searching by indexer,
|
||||
// it's indeed an regression, but I think it is worth to support filtering by indexer first.
|
||||
_ = ctx.FormInt64("priority_repo_id")
|
||||
|
||||
ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt)
|
||||
if err != nil {
|
||||
ctx.APIErrorInternal(err)
|
||||
@@ -321,7 +300,7 @@ func SearchIssues(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(total), limit)
|
||||
ctx.SetLinkHeader(total, limit)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, issues))
|
||||
}
|
||||
@@ -351,7 +330,7 @@ func ListIssues(ctx *context.APIContext) {
|
||||
// enum: [closed, open, all]
|
||||
// - name: labels
|
||||
// in: query
|
||||
// description: comma separated list of labels. Fetch only issues that have any of this labels. Non existent labels are discarded
|
||||
// description: comma separated list of label names. Fetch only issues that have any of this label names. Non existent labels are discarded.
|
||||
// type: string
|
||||
// - name: q
|
||||
// in: query
|
||||
@@ -409,16 +388,7 @@ func ListIssues(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
var isClosed optional.Option[bool]
|
||||
switch ctx.FormString("state") {
|
||||
case "closed":
|
||||
isClosed = optional.Some(true)
|
||||
case "all":
|
||||
isClosed = optional.None[bool]()
|
||||
default:
|
||||
isClosed = optional.Some(false)
|
||||
}
|
||||
|
||||
isClosed := common.ParseIssueFilterStateIsClosed(ctx.FormString("state"))
|
||||
keyword := ctx.FormTrim("q")
|
||||
if strings.IndexByte(keyword, 0) >= 0 {
|
||||
keyword = ""
|
||||
@@ -558,7 +528,7 @@ func ListIssues(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetLinkHeader(int(total), listOptions.PageSize)
|
||||
ctx.SetLinkHeader(total, listOptions.PageSize)
|
||||
ctx.SetTotalCountHeader(total)
|
||||
ctx.JSON(http.StatusOK, convert.ToAPIIssueList(ctx, ctx.Doer, issues))
|
||||
}
|
||||
@@ -757,6 +727,9 @@ func EditIssue(ctx *context.APIContext) {
|
||||
// swagger:operation PATCH /repos/{owner}/{repo}/issues/{index} issue issueEditIssue
|
||||
// ---
|
||||
// summary: Edit an issue. If using deadline only the date will be taken into account, and time of day ignored.
|
||||
// description: |
|
||||
// Pass `content_version` to enable optimistic locking on body edits.
|
||||
// If the version doesn't match the current value, the request fails with 409 Conflict.
|
||||
// consumes:
|
||||
// - application/json
|
||||
// produces:
|
||||
@@ -816,6 +789,15 @@ func EditIssue(ctx *context.APIContext) {
|
||||
return
|
||||
}
|
||||
|
||||
// Fail fast: if content_version is provided and already stale, reject
|
||||
// before any mutations. The DB-level check in ChangeContent still
|
||||
// handles concurrent requests.
|
||||
// TODO: wrap all mutations in a transaction to fully prevent partial writes.
|
||||
if form.ContentVersion != nil && *form.ContentVersion != issue.ContentVersion {
|
||||
ctx.APIError(http.StatusConflict, issues_model.ErrIssueAlreadyChanged)
|
||||
return
|
||||
}
|
||||
|
||||
if len(form.Title) > 0 {
|
||||
err = issue_service.ChangeTitle(ctx, issue, ctx.Doer, form.Title)
|
||||
if err != nil {
|
||||
@@ -824,10 +806,14 @@ func EditIssue(ctx *context.APIContext) {
|
||||
}
|
||||
}
|
||||
if form.Body != nil {
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, issue.ContentVersion)
|
||||
contentVersion := issue.ContentVersion
|
||||
if form.ContentVersion != nil {
|
||||
contentVersion = *form.ContentVersion
|
||||
}
|
||||
err = issue_service.ChangeContent(ctx, issue, ctx.Doer, *form.Body, contentVersion)
|
||||
if err != nil {
|
||||
if errors.Is(err, issues_model.ErrIssueAlreadyChanged) {
|
||||
ctx.APIError(http.StatusBadRequest, err)
|
||||
ctx.APIError(http.StatusConflict, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user