Skip to content

Allow multiple projects per issue and pull requests#36784

Merged
wxiaoguang merged 162 commits intogo-gitea:mainfrom
icyavocado:issue-12974-multiple-projects-per-issue
Apr 30, 2026
Merged

Allow multiple projects per issue and pull requests#36784
wxiaoguang merged 162 commits intogo-gitea:mainfrom
icyavocado:issue-12974-multiple-projects-per-issue

Conversation

@icyavocado
Copy link
Copy Markdown
Contributor

@icyavocado icyavocado commented Feb 28, 2026

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

E2E test video: https://github.com/user-attachments/assets/5807944d-f524-4280-b61f-0cadcb4aa904 stiched together from 740bfce

No breaking change expected.

Copilot was used to review changes, syntax and help with commit messages.

Resolve #12974


Updated progress:

Add support for column picker

image

Styling reverted to use class .milestone-list

image

@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Feb 28, 2026
@icyavocado icyavocado force-pushed the issue-12974-multiple-projects-per-issue branch from b180da7 to a5dbb63 Compare February 28, 2026 20:55
Copy link
Copy Markdown
Contributor Author

@icyavocado icyavocado left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressing previous concerns in #34386

Comment thread models/issues/issue_project.go Outdated
Comment thread models/issues/issue_project.go Outdated
Comment thread modules/indexer/issues/internal/model.go
@icyavocado icyavocado marked this pull request as draft February 28, 2026 21:12
@icyavocado icyavocado marked this pull request as ready for review February 28, 2026 22:58
@silverwind silverwind requested a review from Copilot March 10, 2026 06:04
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements the ability to assign multiple projects to an issue or pull request, resolving issue #12974. It upgrades the data model throughout the stack — from the database layer (Issue.Projects [] replacing Issue.Project), the search/indexer models (ProjectIDs []int64 replacing ProjectID int64), through the service layer, web routers, templates, and tests.

Changes:

  • Core model changes: Issue.Project (singular) replaced with Issue.Projects (slice) across models, list loading, indexers, and search options
  • Service/router changes: IssueAssignOrRemoveProject now accepts []int64 and is diff-based (adds new, removes old), form and validation logic updated for multi-select projects
  • Frontend/template changes: data-selection-mode="multiple" on project sidebar, project_ids field, project-list CSS class rename in list.tmpl

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
models/issues/issue.go Replace Project field with Projects []*Project + loaded flag
models/issues/issue_project.go Replace LoadProject/single-project logic with LoadProjects, diff-based IssueAssignOrRemoveProject
models/issues/issue_list.go Update LoadProjects for multiple projects per issue
models/issues/issue_search.go ProjectID int64ProjectIDs []int64; INNER JOIN for project filter
models/issues/issue_project_multi_test.go New multi-project integration tests
modules/util/util.go New DiffSlice generic utility function
modules/indexer/issues/internal/model.go ProjectIDProjectIDs []int64 in indexer data model
modules/indexer/issues/bleve/bleve.go Multi-project search in Bleve
modules/indexer/issues/elasticsearch/elasticsearch.go Multi-project search in Elasticsearch
modules/indexer/issues/meilisearch/meilisearch.go Multi-project search in Meilisearch
modules/indexer/issues/dboptions.go ProjectIDProjectIDs conversion
routers/web/repo/projects.go UpdateIssueProject updated for multi-project
routers/web/repo/issue_new.go Form validation for ProjectIDs string
routers/web/repo/pull.go PR creation project assignment updated
routers/web/repo/issue_list.go Project filter params updated
services/issue/issue.go NewIssue signature change to projectIDs []int64
services/pull/pull.go PR options updated for ProjectIDs []int64
services/forms/repo_form.go ProjectID int64ProjectIDs string
templates/shared/issuelist.tmpl Render multiple projects per issue
templates/repo/issue/sidebar/project_list.tmpl Multi-select sidebar
templates/projects/list.tmpl CSS class rename milestone-listproject-list

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread routers/web/repo/pull.go Outdated
Comment thread models/issues/issue_search.go Outdated
Comment thread routers/web/repo/issue_new.go
Comment thread modules/util/util.go
Comment thread templates/projects/list.tmpl Outdated
Comment thread models/issues/issue_list_test.go Outdated
Comment thread models/issues/issue_test.go Outdated
Comment thread routers/web/repo/issue_list.go Outdated
@icyavocado icyavocado force-pushed the issue-12974-multiple-projects-per-issue branch from 69f8e06 to b244390 Compare March 10, 2026 19:19
@silverwind
Copy link
Copy Markdown
Member

This comment was written by Claude.

Some issues I found during review:

  1. DiffSlice can produce duplicate entries. If newSlice contains [4, 4, 5] and oldSlice is [1], added returns [4, 4, 5] because it iterates the input slice but only checks membership in the other set. In IssueAssignOrRemoveProject, this could insert duplicate project_issue rows. The output should be deduplicated.

  2. newColumnID shared across all added projects is architecturally wrong. In IssueAssignOrRemoveProject, a single newColumnID is used for all projects being added. A column belongs to a specific project — using project A's column for project B would create invalid data. Current callers all pass 0 (defaulting to each project's default column) so this isn't triggered today, but the function signature invites misuse.

  3. "No project" indexer test case removed without replacement. The old "no ProjectID" test (searching for issues with no project assigned) was deleted. This removes test coverage for an important filter.

  4. SearchIssues returns HTTP 500 for bad user input. Malformed projects query parameter triggers ctx.HTTPError(http.StatusInternalServerError, ...) — should be 400 Bad Request.

  5. SelectedProjectID changed from int64 to comma-separated string. This is fragile — an []int64 field with template-level serialization would be type-safe.

  6. Possible double-filtering for ProjectColumnID. The new applyProjectCondition handles ProjectColumnID inline for single-project and multi-project cases, but the standalone applyProjectColumnCondition function still exists and still applies independently. When both a project and column are specified, conditions could conflict.

  7. API support is incomplete. The REST API only changes 0 to nil — there's no API support for creating/editing issues with multiple projects, and Swagger definitions aren't updated.

  8. isProjectsLoaded not reset after mutation. IssueAssignOrRemoveProject modifies project associations but doesn't reset isProjectsLoaded on the issue, so subsequent LoadProjects calls on the same instance would serve stale data.

@silverwind
Copy link
Copy Markdown
Member

E2E test video: video.webm

If you like, you can add a actual playwright e2e test, just ensure that it's absolutely stable and not flaky please.

@icyavocado
Copy link
Copy Markdown
Contributor Author

icyavocado commented Mar 14, 2026

  1. DiffSlice can produce duplicate entries. If newSlice contains [4, 4, 5] and oldSlice is [1], added returns [4, 4, 5] because it iterates the input slice but only checks membership in the other set. In IssueAssignOrRemoveProject, this could insert duplicate project_issue rows. The output should be deduplicated.

In commit 1e9b8ac, tests was written for this behaviour, and used Set to fix the duplication.

  1. newColumnID shared across all added projects is architecturally wrong. In IssueAssignOrRemoveProject, a single newColumnID is used for all projects being added. A column belongs to a specific project — using project A's column for project B would create invalid data. Current callers all pass 0 (defaulting to each project's default column) so this isn't triggered today, but the function signature invites misuse.

In commit 5972dc2, test was written to confirm the behaviour of assigning default column automatically, and then remove the newColumnID or 0 value, so there is no way to misuse the function. Because of this change, the default column will be created automatically, so column wasn't needed to be created before assigning, therefor tests services/projects/issue_test.go was altered to accommodate the new behavior.

  1. "No project" indexer test case removed without replacement. The old "no ProjectID" test (searching for issues with no project assigned) was deleted. This removes test coverage for an important filter.

In commit a5cd0f8, reintroduce no ProjectID test, mimic the original test functionality.

  1. SearchIssues returns HTTP 500 for bad user input. Malformed projects query parameter triggers ctx.HTTPError(http.StatusInternalServerError, ...) — should be 400 Bad Request.

In commit 20279aa, return 400 since it was user input error.

  1. SelectedProjectID changed from int64 to comma-separated string. This is fragile — an []int64 field with template-level serialization would be type-safe.

In commit 437ef65, using a new util to split string to present data on the template level instead, this should provide []int64 data straight from template level down.

  1. Possible double-filtering for ProjectColumnID. The new applyProjectCondition handles ProjectColumnID inline for single-project and multi-project cases, but the standalone applyProjectColumnCondition function still exists and still applies independently. When both a project and column are specified, conditions could conflict.

In commit 935154d, a test was added to recereate the double filtering issue, and a small patch was added to applyProjectColumnCondition, return early if projectIDs is already defined.

  1. API support is incomplete. The REST API only changes 0 to nil — there's no API support for creating/editing issues with multiple projects, and Swagger definitions aren't updated.

In commit eb2308d, test was added to go through the API end points setup, and also recreate the swagger template data so actions can pass properly.

  1. isProjectsLoaded not reset after mutation. IssueAssignOrRemoveProject modifies project associations but doesn't reset isProjectsLoaded on the issue, so subsequent LoadProjects calls on the same instance would serve stale data.

In commit 3154a0e, added test to recreate the LoadProjects issue when projectIDs lingered. The fix is to reset the flag and .projectIDs of issues after IssueAssignOrRemoveProject update or remove projects.

If you like, you can add a actual playwright e2e test, just ensure that it's absolutely stable and not flaky please.

In commit 740bfce, added e2e tests for projects. The test was using a mixed of api calls, and clicking around the interface.

When writing the e2e tests, a commit 87644e8 was introduced so user can filter multiple projects. using ?projects=1,2

Copy link
Copy Markdown
Member

@silverwind silverwind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was written by Claude.

Nice work on this — the approach is clean, no DB migration needed, and the test coverage is solid. A few concerns:

ProjectColumnID() is still singular

issue.ProjectColumnID() uses .Get(&ip) which returns an arbitrary project_issue row. With multiple projects per issue, this is nondeterministic. getIssueIndexerData() in modules/indexer/issues/util.go calls it to index the ProjectColumnID field — that indexed value is now meaningless when an issue belongs to multiple projects with different columns.

SearchIssues parameter precedence

In routers/web/repo/issue_list.go, if both project and projects query params are provided, the single project value gets appended to includedProjectIDs first (line ~164), then the projects param completely replaces includedProjectIDs (line ~173), silently dropping the project value. Should either skip appending project when projects is present, or document the precedence.

Silent project validation change

In ValidateRepoMetasForNewIssue, the old code returned 404 when a project ID wasn't in candidateProjects. The new code silently drops invalid IDs. A user submitting project_ids=999 would succeed with no project assigned instead of getting an error.

N+1 queries in IssueAssignOrRemoveProject

For each project to add: GetProjectByID + MustDefaultColumn + SELECT max(sorting) = 3 queries per project inside the transaction. Could batch-load the projects upfront.

Removed "no project" indexer test case

The internal test suite (modules/indexer/issues/internal/tests/tests.go) had a dedicated "no ProjectID" test case validating ProjectID = optional.Some(int64(0)) across all indexer engines (bleve/elasticsearch/meilisearch). It was removed without a per-engine replacement — the new test in indexer_test.go covers it at a higher level but doesn't exercise each engine's implementation.

@silverwind
Copy link
Copy Markdown
Member

Some more minor concerns, and please fix the merge conflict. Once done, I will look at the UI.

@bircni
Copy link
Copy Markdown
Member

bircni commented Mar 28, 2026

This comment was written by Claude.

Good progress addressing the previous round of feedback. A few remaining concerns:

API validation logic is duplicated

CreateIssue and EditIssue in routers/api/v1/repo/issue.go both contain an identical 3-step project validation loop: GetProjectByIDLoadRepoCanBeAccessedByOwnerRepo. This should be extracted into a shared helper, both to avoid drift and because the duplication is not obvious to future contributors.

redirect_after_creation with multiple projects is arbitrary

In routers/web/repo/issue_new.go, after creating an issue with multiple projects:

if ctx.FormString("redirect_after_creation") == "project" && len(projectIDs) > 0 {
    project, err := project_model.GetProjectByID(ctx, projectIDs[0])

projectIDs[0] is used, silently redirecting to the first project in the submitted list. The selection order is form-submission order, which is non-obvious. This is probably acceptable short-term, but it should at least be a comment explaining why [0] is chosen.

ProjectColumnID() is still singular (re: silverwind's comment)

To add to the existing note: getIssueIndexerData in modules/indexer/issues/util.go indexes a single ProjectColumnID. With multiple projects, this value is nondeterministic and effectively meaningless. The indexed project_board_id field should either be removed from the index, or changed to a project_board_ids []int64 like project_ids. Leaving it as a single value makes ProjectColumnID-based searches silently incorrect for multi-project issues.

MoveIssuesOnProjectColumn fix deserves a call-out

The change from raw SQL UPDATE project_issue SET project_board_id=? WHERE issue_id=? to scoping by project_id too is a real bug fix — without it, moving an issue in one project would also change its column in every other project it belongs to. Worth noting explicitly in the PR description.

@okdas
Copy link
Copy Markdown

okdas commented Apr 4, 2026

Just wanted to say we're following this with interest. We're evaluating a move from Forgejo to Gitea and multi-project support plus a projects API would be a deciding factor.

The work here looks solid. Happy to help test when it's ready.

@okdas okdas mentioned this pull request Apr 4, 2026
6 tasks
@bircni
Copy link
Copy Markdown
Member

bircni commented Apr 4, 2026

@icyavocado updates? please lets try to move this into 1.26

@icyavocado
Copy link
Copy Markdown
Contributor Author

@icyavocado updates? please lets try to move this into 1.26

Im actively working on this issue. Will have a better update in a day.

@icyavocado icyavocado force-pushed the issue-12974-multiple-projects-per-issue branch from 437ef65 to 5979d49 Compare April 6, 2026 08:00
@icyavocado
Copy link
Copy Markdown
Contributor Author

icyavocado commented Apr 6, 2026

Much appreciated for reviewing my PR!

I addressed the merge conflict and then worked on the updates below.

Update to comment by @silverwind

This review was written by Claude.

Nice work on this — the approach is clean, no DB migration needed, and the test coverage is solid. A few concerns:

ProjectColumnID() is still singular

issue.ProjectColumnID() uses .Get(&ip) which returns an arbitrary project_issue row. With multiple projects per issue, this is nondeterministic. getIssueIndexerData() in modules/indexer/issues/util.go calls it to index the ProjectColumnID field — that indexed value is now meaningless when an issue belongs to multiple projects with different columns.

In commit 1c0d99a, introduced a new ProjectColumnMap to handle columns for multiple projects and kept ProjectColumnID for backward compatibility.

SearchIssues parameter precedence

In routers/web/repo/issue_list.go, if both project and projects query params are provided, the single project value gets appended to includedProjectIDs first (line ~164), then the projects param completely replaces includedProjectIDs (line ~173), silently dropping the project value. Should either skip appending project when projects is present, or document the precedence.

In commit f064b5b, in an API call, projects now takes precedence over project. I also added tests to reinforce the behavior.

Silent project validation change

In ValidateRepoMetasForNewIssue, the old code returned 404 when a project ID wasn't in candidateProjects. The new code silently drops invalid IDs. A user submitting project_ids=999 would succeed with no project assigned instead of getting an error.

In commit 9aea92b, we now return a 400 Bad Request for invalid IDs. Added tests to check the error responses.

N+1 queries in IssueAssignOrRemoveProject

For each project to add: GetProjectByID + MustDefaultColumn + SELECT max(sorting) = 3 queries per project inside the transaction. Could batch-load the projects upfront.

In commit 0f76785, I introduced two helper functions, GetProjectsByIDs to load projects and GetDefaultColumnsByProjectIDs to load default columns into a map, reducing database calls.

Removed "no project" indexer test case

The internal test suite (modules/indexer/issues/internal/tests/tests.go) had a dedicated "no ProjectID" test case validating ProjectID = optional.Some(int64(0)) across all indexer engines (bleve/elasticsearch/meilisearch). It was removed without a per-engine replacement — the new test in indexer_test.go covers it at a higher level but doesn't exercise each engine's implementation.

In commit a7589dc, I added no_project to indexers following the no_label convention and added a test case to cover the new behavior.


Update to comment by @bircni

This comment was written by Claude.

Good progress addressing the previous round of feedback. A few remaining concerns:

API validation logic is duplicated

CreateIssue and EditIssue in routers/api/v1/repo/issue.go both contain an identical 3-step project validation loop: GetProjectByIDLoadRepoCanBeAccessedByOwnerRepo. This should be extracted into a shared helper, both to avoid drift and because the duplication is not obvious to future contributors.

In commit 65f57ee, I created the helper function validateProjectAccess and used it in CreateIssue and EditIssue.

redirect_after_creation with multiple projects is arbitrary

In routers/web/repo/issue_new.go, after creating an issue with multiple projects:

if ctx.FormString("redirect_after_creation") == "project" && len(projectIDs) > 0 {
    project, err := project_model.GetProjectByID(ctx, projectIDs[0])

projectIDs[0] is used, silently redirecting to the first project in the submitted list. The selection order is form-submission order, which is non-obvious. This is probably acceptable short-term, but it should at least be a comment explaining why [0] is chosen.

In commit a036265, I added the comment before the redirect: // When issue is in multiple projects, redirect to first project from form order. I know it is not much of a justification, let me know if you want a stronger reason here.

ProjectColumnID() is still singular (re: silverwind's comment)

To add to the existing note: getIssueIndexerData in modules/indexer/issues/util.go indexes a single ProjectColumnID. With multiple projects, this value is nondeterministic and effectively meaningless. The indexed project_board_id field should either be removed from the index, or changed to a project_board_ids []int64 like project_ids. Leaving it as a single value makes ProjectColumnID-based searches silently incorrect for multi-project issues.

In commit 1c0d99a, I introduced a new ProjectColumnMap to handle columns for multiple projects and kept ProjectColumnID for backward compatibility.

MoveIssuesOnProjectColumn fix deserves a call-out

The change from raw SQL UPDATE project_issue SET project_board_id=? WHERE issue_id=? to scoping by project_id too is a real bug fix — without it, moving an issue in one project would also change its column in every other project it belongs to. Worth noting explicitly in the PR description.

In commit a036265, I added the note above the bug fix. Sorry in advance if this is not what you wanted.

@bircni
Copy link
Copy Markdown
Member

bircni commented Apr 6, 2026

You need to solve some conflicts

@icyavocado icyavocado force-pushed the issue-12974-multiple-projects-per-issue branch from 5979d49 to e8e4d95 Compare April 6, 2026 09:11
myers added a commit to myers/gitea that referenced this pull request Apr 11, 2026
Change the issue/PR API response from `project` (singular object)
to `projects` (array of objects). Today this array has 0 or 1
entries. When multi-project support lands (go-gitea#36784), the API contract
won't need to change.

Co-Authored-By: Claude Opus 4 (claude-opus-4-6)
@bircni bircni requested a review from wxiaoguang April 12, 2026 14:36
@wxiaoguang wxiaoguang force-pushed the issue-12974-multiple-projects-per-issue branch from f4c03fc to 1d6b6aa Compare April 30, 2026 08:48
@wxiaoguang wxiaoguang force-pushed the issue-12974-multiple-projects-per-issue branch from 1d6b6aa to 4a0c9a1 Compare April 30, 2026 08:52
@wxiaoguang
Copy link
Copy Markdown
Contributor

wxiaoguang commented Apr 30, 2026

Cleaned up a lot for AI slop, still WIP.

TBH, I don't understand why such UI/UX can look good to you @silverwind since you always keep focusing on and arguing about many trivial details. But this one obviously isn't trivial.

UI is wrong, UX is wrong, search logic is wrong.

Details image

Comment thread tests/e2e/issue-project.test.ts Outdated
Comment on lines +505 to +507
} finally {
await apiDeleteRepo(page.request, user, repoName);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@silverwind I think you have refactored the tests and removed all "try-finally-apiDeleteRepo", what do you think?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, these should be removed for the sake of performance.

@wxiaoguang wxiaoguang dismissed their stale review April 30, 2026 12:07

dismiss change request

@GiteaBot GiteaBot added lgtm/need 1 This PR needs approval from one additional maintainer to be merged. and removed lgtm/blocked A maintainer has reservations with the PR and thus it cannot be merged labels Apr 30, 2026
@wxiaoguang wxiaoguang force-pushed the issue-12974-multiple-projects-per-issue branch from 57b19ed to 4d2ad28 Compare April 30, 2026 12:50
@wxiaoguang wxiaoguang force-pushed the issue-12974-multiple-projects-per-issue branch from 4d2ad28 to 8ef4591 Compare April 30, 2026 13:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 57 out of 58 changed files in this pull request and generated 7 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread routers/web/repo/projects.go
Comment thread tests/e2e/utils.ts
Comment thread tests/e2e/issue-project.test.ts Outdated
Comment thread tests/e2e/issue-project.test.ts Outdated
Comment thread modules/indexer/issues/db/options.go
Comment thread routers/web/repo/issue_list.go
Comment thread services/context/base_form.go
Copy link
Copy Markdown
Contributor

@wxiaoguang wxiaoguang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleaned up 1000 lines of AI slop

@GiteaBot GiteaBot added lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. and removed lgtm/need 1 This PR needs approval from one additional maintainer to be merged. labels Apr 30, 2026
@wxiaoguang wxiaoguang merged commit 81692ce into go-gitea:main Apr 30, 2026
28 checks passed
await apiCreateRepo(request, {name: repoName});
await Promise.all([
apiCreateIssue(request, owner, repoName, {title: 'Reaction test'}),
apiCreateIssue(request, {owner, repo: repoName, title: 'Reaction test'}),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The design of these helpers is that required args are positional and optional ones are in the object. This violates it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need a complete fix for the e2e code (including #36784 (comment))

Just not worth to introduce more AI slop in this one.

silverwind added a commit to silverwind/gitea that referenced this pull request Apr 30, 2026
* origin/main:
  Refactor CI workflows (go-gitea#37487)
  Allow multiple projects per issue and pull requests (go-gitea#36784)
  [skip ci] Updated translations via Crowdin
  Refactor compare diff/pull page (1) (go-gitea#37481)
  Fix review submission from single-commit PR view (go-gitea#37475)
  Refactor integration tests infrastructure (go-gitea#37462)
  Fix allow maintainer edit permission check (go-gitea#37479)
  Serve OpenAPI 3.0 spec at /openapi.v1.json (go-gitea#37038)
  Batch-load related data in actions run, job, and task API endpoints (go-gitea#37032)
  Add DEFAULT_TITLE_SOURCE setting for pull request title default behavior (go-gitea#37465)
  Fix compare dropdown for branches without common history (go-gitea#37470)
  FIX: URL sanitization to handle schemeless credentials (go-gitea#37440)
  Refactor pull request view (4) (go-gitea#37451)

# Conflicts:
#	modules/indexer/issues/elasticsearch/elasticsearch.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/done This PR has enough approvals to get merged. There are no important open reservations anymore. type/feature Completely new functionality. Can only be merged if feature freeze is not active.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow multiple projects per issue

10 participants