Merge pull request '[gitea] week 2024-42 cherry pick (gitea/main -> forgejo)' (#5543) from earl-warren/wcp/2024-42 into forgejo
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/5543 Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
commit
e2354703ed
|
@ -26,6 +26,7 @@ type PullRequestsOptions struct {
|
||||||
SortType string
|
SortType string
|
||||||
Labels []int64
|
Labels []int64
|
||||||
MilestoneID int64
|
MilestoneID int64
|
||||||
|
PosterID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) *xorm.Session {
|
func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullRequestsOptions) *xorm.Session {
|
||||||
|
@ -46,6 +47,10 @@ func listPullRequestStatement(ctx context.Context, baseRepoID int64, opts *PullR
|
||||||
sess.And("issue.milestone_id=?", opts.MilestoneID)
|
sess.And("issue.milestone_id=?", opts.MilestoneID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if opts.PosterID > 0 {
|
||||||
|
sess.And("issue.poster_id=?", opts.PosterID)
|
||||||
|
}
|
||||||
|
|
||||||
return sess
|
return sess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -586,44 +586,46 @@ var (
|
||||||
".",
|
".",
|
||||||
"..",
|
"..",
|
||||||
".well-known",
|
".well-known",
|
||||||
"admin",
|
|
||||||
"api",
|
"api", // gitea api
|
||||||
"assets",
|
"metrics", // prometheus metrics api
|
||||||
"attachments",
|
"v2", // container registry api
|
||||||
"avatar",
|
|
||||||
"avatars",
|
"assets", // static asset files
|
||||||
"captcha",
|
"attachments", // issue attachments
|
||||||
"commits",
|
|
||||||
"debug",
|
"avatar", // avatar by email hash
|
||||||
"devtest",
|
"avatars", // user avatars by file name
|
||||||
"error",
|
|
||||||
"explore",
|
|
||||||
"favicon.ico",
|
|
||||||
"ghost",
|
|
||||||
"issues",
|
|
||||||
"login",
|
|
||||||
"manifest.json",
|
|
||||||
"metrics",
|
|
||||||
"milestones",
|
|
||||||
"new",
|
|
||||||
"notifications",
|
|
||||||
"org",
|
|
||||||
"pulls",
|
|
||||||
"raw",
|
|
||||||
"repo",
|
|
||||||
"repo-avatars",
|
"repo-avatars",
|
||||||
"robots.txt",
|
|
||||||
"search",
|
"captcha",
|
||||||
"serviceworker.js",
|
"login", // oauth2 login
|
||||||
"ssh_info",
|
"org", // org create/manage, or "/org/{org}", BUT if an org is named as "invite" then it goes wrong
|
||||||
|
"repo", // repo create/migrate, etc
|
||||||
|
"user", // user login/activate/settings, etc
|
||||||
|
|
||||||
|
"admin",
|
||||||
|
"devtest",
|
||||||
|
"explore",
|
||||||
|
"issues",
|
||||||
|
"pulls",
|
||||||
|
"milestones",
|
||||||
|
"notifications",
|
||||||
|
|
||||||
|
"favicon.ico",
|
||||||
|
"manifest.json", // web app manifests
|
||||||
|
"robots.txt", // search engine robots
|
||||||
|
"sitemap.xml", // search engine sitemap
|
||||||
|
"ssh_info", // agit info
|
||||||
"swagger.v1.json",
|
"swagger.v1.json",
|
||||||
"user",
|
|
||||||
"v2",
|
"ghost", // reserved name for deleted users (id: -1)
|
||||||
"gitea-actions",
|
"gitea-actions", // gitea builtin user (id: -2)
|
||||||
"forgejo-actions",
|
"forgejo-actions", // forgejo builtin user (id: -2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DON'T ADD ANY NEW STUFF, WE SOLVE THIS WITH `/user/{obj}` PATHS!
|
// These names are reserved for user accounts: user's keys, user's rss feed, user's avatar, etc.
|
||||||
|
// DO NOT add any new stuff! The paths with these names are processed by `/{username}` handler (UsernameSubRoute) manually.
|
||||||
reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom", "*.png"}
|
reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom", "*.png"}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
feat: [commit](https://codeberg.org/forgejo/forgejo/commit/d0af8fe4dc7b294fe5409b2271468494267d5a7d) Allow filtering pull requests by poster in the API.
|
|
@ -52,56 +52,79 @@ func ListPullRequests(ctx *context.APIContext) {
|
||||||
// parameters:
|
// parameters:
|
||||||
// - name: owner
|
// - name: owner
|
||||||
// in: path
|
// in: path
|
||||||
// description: owner of the repo
|
// description: Owner of the repo
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
// - name: repo
|
// - name: repo
|
||||||
// in: path
|
// in: path
|
||||||
// description: name of the repo
|
// description: Name of the repo
|
||||||
// type: string
|
// type: string
|
||||||
// required: true
|
// required: true
|
||||||
// - name: state
|
// - name: state
|
||||||
// in: query
|
// in: query
|
||||||
// description: "State of pull request: open or closed (optional)"
|
// description: State of pull request
|
||||||
// type: string
|
// type: string
|
||||||
// enum: [closed, open, all]
|
// enum: [open, closed, all]
|
||||||
|
// default: open
|
||||||
// - name: sort
|
// - name: sort
|
||||||
// in: query
|
// in: query
|
||||||
// description: "Type of sort"
|
// description: Type of sort
|
||||||
// type: string
|
// type: string
|
||||||
// enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority]
|
// enum: [oldest, recentupdate, leastupdate, mostcomment, leastcomment, priority]
|
||||||
// - name: milestone
|
// - name: milestone
|
||||||
// in: query
|
// in: query
|
||||||
// description: "ID of the milestone"
|
// description: ID of the milestone
|
||||||
// type: integer
|
// type: integer
|
||||||
// format: int64
|
// format: int64
|
||||||
// - name: labels
|
// - name: labels
|
||||||
// in: query
|
// in: query
|
||||||
// description: "Label IDs"
|
// description: Label IDs
|
||||||
// type: array
|
// type: array
|
||||||
// collectionFormat: multi
|
// collectionFormat: multi
|
||||||
// items:
|
// items:
|
||||||
// type: integer
|
// type: integer
|
||||||
// format: int64
|
// format: int64
|
||||||
|
// - name: poster
|
||||||
|
// in: query
|
||||||
|
// description: Filter by pull request author
|
||||||
|
// type: string
|
||||||
// - name: page
|
// - name: page
|
||||||
// in: query
|
// in: query
|
||||||
// description: page number of results to return (1-based)
|
// description: Page number of results to return (1-based)
|
||||||
// type: integer
|
// type: integer
|
||||||
|
// minimum: 1
|
||||||
|
// default: 1
|
||||||
// - name: limit
|
// - name: limit
|
||||||
// in: query
|
// in: query
|
||||||
// description: page size of results
|
// description: Page size of results
|
||||||
// type: integer
|
// type: integer
|
||||||
|
// minimum: 0
|
||||||
// responses:
|
// responses:
|
||||||
// "200":
|
// "200":
|
||||||
// "$ref": "#/responses/PullRequestList"
|
// "$ref": "#/responses/PullRequestList"
|
||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "500":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
|
||||||
labelIDs, err := base.StringsToInt64s(ctx.FormStrings("labels"))
|
labelIDs, err := base.StringsToInt64s(ctx.FormStrings("labels"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "PullRequests", err)
|
ctx.Error(http.StatusInternalServerError, "PullRequests", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var posterID int64
|
||||||
|
if posterStr := ctx.FormString("poster"); posterStr != "" {
|
||||||
|
poster, err := user_model.GetUserByName(ctx, posterStr)
|
||||||
|
if err != nil {
|
||||||
|
if user_model.IsErrUserNotExist(err) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "Poster not found", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
posterID = poster.ID
|
||||||
|
}
|
||||||
listOptions := utils.GetListOptions(ctx)
|
listOptions := utils.GetListOptions(ctx)
|
||||||
prs, maxResults, err := issues_model.PullRequests(ctx, ctx.Repo.Repository.ID, &issues_model.PullRequestsOptions{
|
prs, maxResults, err := issues_model.PullRequests(ctx, ctx.Repo.Repository.ID, &issues_model.PullRequestsOptions{
|
||||||
ListOptions: listOptions,
|
ListOptions: listOptions,
|
||||||
|
@ -109,6 +132,7 @@ func ListPullRequests(ctx *context.APIContext) {
|
||||||
SortType: ctx.FormTrim("sort"),
|
SortType: ctx.FormTrim("sort"),
|
||||||
Labels: labelIDs,
|
Labels: labelIDs,
|
||||||
MilestoneID: ctx.FormInt64("milestone"),
|
MilestoneID: ctx.FormInt64("milestone"),
|
||||||
|
PosterID: posterID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(http.StatusInternalServerError, "PullRequests", err)
|
ctx.Error(http.StatusInternalServerError, "PullRequests", err)
|
||||||
|
|
|
@ -33,6 +33,10 @@ import (
|
||||||
|
|
||||||
// RenameUser renames a user
|
// RenameUser renames a user
|
||||||
func RenameUser(ctx context.Context, u *user_model.User, newUserName string) error {
|
func RenameUser(ctx context.Context, u *user_model.User, newUserName string) error {
|
||||||
|
if newUserName == u.Name {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Non-local users are not allowed to change their username.
|
// Non-local users are not allowed to change their username.
|
||||||
if !u.IsOrganization() && !u.IsLocal() {
|
if !u.IsOrganization() && !u.IsLocal() {
|
||||||
return user_model.ErrUserIsNotLocal{
|
return user_model.ErrUserIsNotLocal{
|
||||||
|
@ -41,10 +45,6 @@ func RenameUser(ctx context.Context, u *user_model.User, newUserName string) err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if newUserName == u.Name {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := user_model.IsUsableUsername(newUserName); err != nil {
|
if err := user_model.IsUsableUsername(newUserName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,12 +114,10 @@ func TestRenameUser(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Non usable username", func(t *testing.T) {
|
t.Run("Non usable username", func(t *testing.T) {
|
||||||
usernames := []string{"--diff", "aa.png", ".well-known", "search", "aaa.atom"}
|
usernames := []string{"--diff", ".well-known", "gitea-actions", "aaa.atom", "aa.png"}
|
||||||
for _, username := range usernames {
|
for _, username := range usernames {
|
||||||
t.Run(username, func(t *testing.T) {
|
require.Error(t, user_model.IsUsableUsername(username), "non-usable username: %s", username)
|
||||||
require.Error(t, user_model.IsUsableUsername(username))
|
require.Error(t, RenameUser(db.DefaultContext, user, username), "non-usable username: %s", username)
|
||||||
require.Error(t, RenameUser(db.DefaultContext, user, username))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -12295,26 +12295,27 @@
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "owner of the repo",
|
"description": "Owner of the repo",
|
||||||
"name": "owner",
|
"name": "owner",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "name of the repo",
|
"description": "Name of the repo",
|
||||||
"name": "repo",
|
"name": "repo",
|
||||||
"in": "path",
|
"in": "path",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"enum": [
|
"enum": [
|
||||||
"closed",
|
|
||||||
"open",
|
"open",
|
||||||
|
"closed",
|
||||||
"all"
|
"all"
|
||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "State of pull request: open or closed (optional)",
|
"default": "open",
|
||||||
|
"description": "State of pull request",
|
||||||
"name": "state",
|
"name": "state",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
@ -12351,14 +12352,23 @@
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Filter by pull request author",
|
||||||
|
"name": "poster",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"minimum": 1,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "page number of results to return (1-based)",
|
"default": 1,
|
||||||
|
"description": "Page number of results to return (1-based)",
|
||||||
"name": "page",
|
"name": "page",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"minimum": 0,
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "page size of results",
|
"description": "Page size of results",
|
||||||
"name": "limit",
|
"name": "limit",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
}
|
}
|
||||||
|
@ -12369,6 +12379,9 @@
|
||||||
},
|
},
|
||||||
"404": {
|
"404": {
|
||||||
"$ref": "#/responses/notFound"
|
"$ref": "#/responses/notFound"
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -114,10 +114,7 @@ func TestRenameReservedUsername(t *testing.T) {
|
||||||
"avatar",
|
"avatar",
|
||||||
"avatars",
|
"avatars",
|
||||||
"captcha",
|
"captcha",
|
||||||
"commits",
|
|
||||||
"debug",
|
|
||||||
"devtest",
|
"devtest",
|
||||||
"error",
|
|
||||||
"explore",
|
"explore",
|
||||||
"favicon.ico",
|
"favicon.ico",
|
||||||
"ghost",
|
"ghost",
|
||||||
|
@ -126,16 +123,12 @@ func TestRenameReservedUsername(t *testing.T) {
|
||||||
"manifest.json",
|
"manifest.json",
|
||||||
"metrics",
|
"metrics",
|
||||||
"milestones",
|
"milestones",
|
||||||
"new",
|
|
||||||
"notifications",
|
"notifications",
|
||||||
"org",
|
"org",
|
||||||
"pulls",
|
"pulls",
|
||||||
"raw",
|
|
||||||
"repo",
|
"repo",
|
||||||
"repo-avatars",
|
"repo-avatars",
|
||||||
"robots.txt",
|
"robots.txt",
|
||||||
"search",
|
|
||||||
"serviceworker.js",
|
|
||||||
"ssh_info",
|
"ssh_info",
|
||||||
"swagger.v1.json",
|
"swagger.v1.json",
|
||||||
"user",
|
"user",
|
||||||
|
|
|
@ -358,9 +358,9 @@ export default sfc; // activate the IDE's Vue plugin
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<a class="item" @click="toggleArchivedFilter()">
|
<a class="item" @click="toggleArchivedFilter()">
|
||||||
<div class="ui checkbox" ref="checkboxArchivedFilter" :title="checkboxArchivedFilterTitle">
|
<div class="ui checkbox" ref="checkboxArchivedFilter" :title="checkboxArchivedFilterTitle">
|
||||||
<!--the "hidden" is necessary to make the checkbox work without Fomantic UI js,
|
<!--the "tw-pointer-events-none" is necessary to prevent the checkbox from handling user's input,
|
||||||
otherwise if the "input" handles click event for intermediate status, it breaks the internal state-->
|
otherwise if the "input" handles click event for intermediate status, it breaks the internal state-->
|
||||||
<input type="checkbox" class="hidden" v-bind.prop="checkboxArchivedFilterProps">
|
<input type="checkbox" class="tw-pointer-events-none" v-bind.prop="checkboxArchivedFilterProps">
|
||||||
<label>
|
<label>
|
||||||
<svg-icon name="octicon-archive" :size="16" class-name="tw-mr-1"/>
|
<svg-icon name="octicon-archive" :size="16" class-name="tw-mr-1"/>
|
||||||
{{ textShowArchived }}
|
{{ textShowArchived }}
|
||||||
|
@ -369,7 +369,7 @@ export default sfc; // activate the IDE's Vue plugin
|
||||||
</a>
|
</a>
|
||||||
<a class="item" @click="togglePrivateFilter()">
|
<a class="item" @click="togglePrivateFilter()">
|
||||||
<div class="ui checkbox" ref="checkboxPrivateFilter" :title="checkboxPrivateFilterTitle">
|
<div class="ui checkbox" ref="checkboxPrivateFilter" :title="checkboxPrivateFilterTitle">
|
||||||
<input type="checkbox" class="hidden" v-bind.prop="checkboxPrivateFilterProps">
|
<input type="checkbox" class="tw-pointer-events-none" v-bind.prop="checkboxPrivateFilterProps">
|
||||||
<label>
|
<label>
|
||||||
<svg-icon name="octicon-lock" :size="16" class-name="tw-mr-1"/>
|
<svg-icon name="octicon-lock" :size="16" class-name="tw-mr-1"/>
|
||||||
{{ textShowPrivate }}
|
{{ textShowPrivate }}
|
||||||
|
|
|
@ -60,7 +60,7 @@ export function initRepoTopicBar() {
|
||||||
// how to test: input topic like " invalid topic " (with spaces), and select it from the list, then "Save"
|
// how to test: input topic like " invalid topic " (with spaces), and select it from the list, then "Save"
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
lastErrorToast = showErrorToast(responseData.message, {duration: 5000});
|
lastErrorToast = showErrorToast(responseData.message, {duration: 5000});
|
||||||
if (responseData.invalidTopics.length > 0) {
|
if (responseData.invalidTopics && responseData.invalidTopics.length > 0) {
|
||||||
const {invalidTopics} = responseData;
|
const {invalidTopics} = responseData;
|
||||||
const topicLabels = queryElemChildren(topicDropdown, 'a.ui.label');
|
const topicLabels = queryElemChildren(topicDropdown, 'a.ui.label');
|
||||||
for (const [index, value] of topics.split(',').entries()) {
|
for (const [index, value] of topics.split(',').entries()) {
|
||||||
|
|
Loading…
Reference in New Issue