Allow multiple projects per issue and pull requests (#36784)

Add ability to add and remove multiple projects per issue
and pull request.

Resolve #12974

---------

Signed-off-by: Icy Avocado <avocado@ovacoda.com>
Co-authored-by: Tyrone Yeh <siryeh@gmail.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: OpenCode (gpt-5.2-codex) <opencode@openai.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com>
This commit is contained in:
Icy Avocado
2026-04-30 08:38:05 -06:00
committed by GitHub
parent 52d6baf5a8
commit 81692ceafa
58 changed files with 1597 additions and 430 deletions
+12 -8
View File
@@ -17,6 +17,7 @@ import (
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/optional"
"code.gitea.io/gitea/modules/setting"
@@ -447,13 +448,12 @@ func UpdateIssueProject(ctx *context.Context) {
return
}
projectID := ctx.FormInt64("id")
projectIDs := ctx.FormStringInt64s("id")
var failedIssues []int64
for _, issue := range issues {
if issue.Project != nil && issue.Project.ID == projectID {
continue
}
if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, projectID, 0); err != nil {
if err := issues_model.IssueAssignOrRemoveProject(ctx, issue, ctx.Doer, projectIDs); err != nil {
if errors.Is(err, util.ErrPermissionDenied) {
failedIssues = append(failedIssues, issue.ID)
continue
}
ctx.ServerError("IssueAssignOrRemoveProject", err)
@@ -461,6 +461,10 @@ func UpdateIssueProject(ctx *context.Context) {
}
}
if len(failedIssues) > 0 {
log.Warn("Failed to assign projects to %d issues due to permission denied: %v", len(failedIssues), failedIssues)
}
ctx.JSONOK()
}
@@ -477,12 +481,12 @@ func UpdateIssueProjectColumn(ctx *context.Context) {
return
}
if err := issue.LoadProject(ctx); err != nil {
ctx.ServerError("LoadProject", err)
if err := issue.LoadProjects(ctx); err != nil {
ctx.ServerError("LoadProjects", err)
return
}
issueProjects := []*project_model.Project{issue.Project} // TODO: this is for the multiple project support in the future
issueProjects := issue.Projects
// it must make sure the requested column is in this issue's projects
var columnProject *project_model.Project