Skip to content
Open
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
775f0f6
Add support for projects V2
JoannaaKL Sep 17, 2025
74712bd
Add test
JoannaaKL Sep 17, 2025
3a8cf0c
Address feedback
JoannaaKL Sep 22, 2025
b754eeb
Merge branch 'master' into add-projects
JoannaaKL Sep 22, 2025
e93ed36
Remove header and rename functions
JoannaaKL Sep 22, 2025
99852cf
Update comments
JoannaaKL Sep 22, 2025
7664215
Copyright update
JoannaaKL Sep 22, 2025
89016d9
Merge branch 'master' into add-projects
JoannaaKL Sep 23, 2025
2b94765
Update comments
JoannaaKL Sep 23, 2025
a4b0a56
Generate docs
JoannaaKL Sep 23, 2025
696102b
Add list projects option
JoannaaKL Sep 23, 2025
469e03a
Generate openapi docs
JoannaaKL Sep 23, 2025
2dbe5b4
Results of generate
JoannaaKL Sep 23, 2025
478ef5a
Merge branch 'master' into add-projects
JoannaaKL Sep 23, 2025
c34802d
Merge branch 'master' into add-projects
JoannaaKL Sep 23, 2025
053a9e1
Update github/projects.go
JoannaaKL Sep 23, 2025
680a0d1
Rename url in test
JoannaaKL Sep 23, 2025
d8696b4
Generate again
JoannaaKL Sep 23, 2025
93dd34f
Merge branch 'master' into add-projects
JoannaaKL Sep 23, 2025
060aaae
Shorten functions for orgs
JoannaaKL Sep 24, 2025
65e64f7
Change ID type from string to int
JoannaaKL Sep 24, 2025
d300550
Extract pagination options for projects
JoannaaKL Sep 24, 2025
fc5d3cb
Remove comments
JoannaaKL Sep 24, 2025
9d6bf4b
Merge branch 'master' into add-projects
JoannaaKL Sep 24, 2025
9f4c89d
Merge branch 'master' into add-projects
JoannaaKL Sep 24, 2025
3dd69be
Merge branch 'master' into add-projects
JoannaaKL Sep 25, 2025
275f930
Update github/projects.go
JoannaaKL Sep 25, 2025
aca15c0
Sync openapi_operations.yaml
JoannaaKL Sep 26, 2025
8ee5bc4
Merge branch 'master' into add-projects
JoannaaKL Sep 26, 2025
0ca0521
Address pr comments
JoannaaKL Sep 26, 2025
fb6e7a8
Add integration test and minor tweaks
JoannaaKL Sep 26, 2025
6d0637f
Merge branch 'master' into add-projects
JoannaaKL Oct 1, 2025
5cc797e
.
JoannaaKL Oct 1, 2025
c6cfe31
Update test/integration/projects_test.go
JoannaaKL Oct 3, 2025
c9feeb2
Update test/integration/projects_test.go
JoannaaKL Oct 3, 2025
8c985a9
Update test/integration/projects_test.go
JoannaaKL Oct 3, 2025
1625c61
Update test/integration/projects_test.go
JoannaaKL Oct 3, 2025
a11a2fa
Merge branch 'master' into add-projects
JoannaaKL Oct 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions github/github-accessors.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions github/github-accessors_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions github/github-stringify_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ type Client struct {
Meta *MetaService
Migrations *MigrationService
Organizations *OrganizationsService
Projects *ProjectsService
PullRequests *PullRequestsService
RateLimit *RateLimitService
Reactions *ReactionsService
Expand Down Expand Up @@ -456,6 +457,7 @@ func (c *Client) initialize() {
c.Meta = (*MetaService)(&c.common)
c.Migrations = (*MigrationService)(&c.common)
c.Organizations = (*OrganizationsService)(&c.common)
c.Projects = (*ProjectsService)(&c.common)
c.PullRequests = (*PullRequestsService)(&c.common)
c.RateLimit = (*RateLimitService)(&c.common)
c.Reactions = (*ReactionsService)(&c.common)
Expand Down
2 changes: 1 addition & 1 deletion github/orgs_attestations.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// with a given subject digest that are associated with repositories
// owned by an organization.
//
// GitHub API docs: https://docs.github.com/rest/orgs/orgs#list-attestations
// GitHub API docs: https://docs.github.com/rest/orgs/attestations#list-attestations
//
//meta:operation GET /orgs/{org}/attestations/{subject_digest}
func (s *OrganizationsService) ListAttestations(ctx context.Context, org, subjectDigest string, opts *ListOptions) (*AttestationsResponse, *Response, error) {
Expand Down
190 changes: 190 additions & 0 deletions github/projects.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// Copyright 2025 The go-github AUTHORS. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package github

import (
"context"
"fmt"
)

// ProjectsService handles communication with the project V2
// methods of the GitHub API.
//
// GitHub API docs: https://docs.github.com/rest/projects/projects
type ProjectsService service

func (p ProjectV2) String() string { return Stringify(p) }

// ListProjectsPaginationOptions specifies optional parameters to list projects for user / organization.
//
// Note: Pagination is powered by before/after cursor-style pagination. After the initial call,
// inspect the returned *Response. Use resp.After as the opts.After value to request
// the next page, and resp.Before as the opts.Before value to request the previous
// page. Set either Before or After for a request; if both are
// supplied GitHub API will return an error. PerPage controls the number of items
// per page (max 100 per GitHub API docs).
type ListProjectsPaginationOptions struct {
// A cursor, as given in the Link header. If specified, the query only searches for events before this cursor.
Before string `url:"before,omitempty"`

// A cursor, as given in the Link header. If specified, the query only searches for events after this cursor.
After string `url:"after,omitempty"`

// For paginated result sets, the number of results to include per page.
PerPage int `url:"per_page,omitempty"`
}

// ListProjectsOptions specifies optional parameters to list projects for user / organization.
type ListProjectsOptions struct {
ListProjectsPaginationOptions

// Q is an optional query string to limit results to projects of the specified type.
Query string `url:"q,omitempty"`
}

// ProjectV2FieldOption represents an option for a project field of type single_select or multi_select.
// It defines the available choices that can be selected for dropdown-style fields.
//
// GitHub API docs: https://docs.github.com/rest/projects/fields
type ProjectV2FieldOption struct {
// The unique identifier for this option.
ID string `json:"id,omitempty"`
// The display name of the option.
Name string `json:"name,omitempty"`
// The color associated with this option (e.g., "blue", "red").
Color string `json:"color,omitempty"`
// An optional description for this option.
Description string `json:"description,omitempty"`
}

// ProjectV2Field represents a field in a GitHub Projects V2 project.
// Fields define the structure and data types for project items.
//
// GitHub API docs: https://docs.github.com/rest/projects/fields
type ProjectV2Field struct {
ID *int64 `json:"id,omitempty"` // The unique identifier for this field.
NodeID string `json:"node_id,omitempty"` // The GraphQL node ID for this field.
Name string `json:"name,omitempty"` // The display name of the field.
DataType string `json:"dataType,omitempty"` // The data type of the field (e.g., "text", "number", "date", "single_select", "multi_select").
URL string `json:"url,omitempty"` // The API URL for this field.
Options []*ProjectV2FieldOption `json:"options,omitempty"` // Available options for single_select and multi_select fields.
CreatedAt *Timestamp `json:"created_at,omitempty"` // The time when this field was created.
UpdatedAt *Timestamp `json:"updated_at,omitempty"` // The time when this field was last updated.
}

// ListProjectsForOrg lists Projects V2 for an organization.
//
// GitHub API docs: https://docs.github.com/rest/projects/projects#list-projects-for-organization
//
//meta:operation GET /orgs/{org}/projectsV2
func (s *ProjectsService) ListProjectsForOrg(ctx context.Context, org string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) {
u := fmt.Sprintf("orgs/%v/projectsV2", org)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}

req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

var projects []*ProjectV2
resp, err := s.client.Do(ctx, req, &projects)
if err != nil {
return nil, resp, err
}
return projects, resp, nil
}

// GetProjectForOrg gets a Projects V2 project for an organization by ID.
//
// GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-organization
//
//meta:operation GET /orgs/{org}/projectsV2/{project_number}
func (s *ProjectsService) GetProjectForOrg(ctx context.Context, org string, projectNumber int64) (*ProjectV2, *Response, error) {
Copy link
Contributor

@alexandear alexandear Sep 26, 2025

Choose a reason for hiding this comment

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

Perhaps the type of projectNumber should be the same as the type of Number in the ProjectV2 structure:

Suggested change
func (s *ProjectsService) GetProjectForOrg(ctx context.Context, org string, projectNumber int64) (*ProjectV2, *Response, error) {
func (s *ProjectsService) GetProjectForOrg(ctx context.Context, org string, projectNumber int) (*ProjectV2, *Response, error) {

Copy link
Collaborator

Choose a reason for hiding this comment

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

Great catch, @alexandear! This made me curious if the Number property ever might be as huge as an "id" and as far as I can tell, that is not the case, so I do NOT think both should be int64, but instead both should be int as you have suggested.

Thoughts, @JoannaaKL?

Copy link
Contributor Author

@JoannaaKL JoannaaKL Sep 26, 2025

Choose a reason for hiding this comment

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

In the GH API project's ID is a number type and project_number is an integer type. It can hold int64 actually. number type can hold any number so yes, I think it should be equal to int 64 as it is now. So I don't think they should be set to int.

Copy link
Collaborator

Choose a reason for hiding this comment

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

In the GH API project's ID is a number type and project_number is an integer type. It can hold int64 actually. number type can hold any number so yes, I think it should be equal to int 64 as it is now. So I don't think they should be set to int.

I think we may have a disconnect here.

The ProjectV2 struct has these fields (among others):

	ID               *int64     `json:"id,omitempty"`
	Number           *int       `json:"number,omitempty"`

IDs in this repo are (now) always of type int64 because we empirically discovered IDs from GitHub many years ago that did not fit within an int, so that is our policy moving forward.

Other integers in this repo that we never expect to overflow an int remain an int because that is the natural and familiar integer type for Go programmers.

What we are saying here is that we believe the ProjectV2.Number field should remain an int and on line 136 above you currently have projectNumber int64 which does not match its corresponding field in the struct.

Therefore, we would like you to change projectNumber int64 to projectNumber int to make the two of them match.

Does this make sense, @JoannaaKL?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I was talking about types described in the doc https://docs.github.com/en/rest/projects/fields?apiVersion=2022-11-28
projectNumber there is described as integer and that type can hold int64 link (correct me if I am wrong).

u := fmt.Sprintf("orgs/%v/projectsV2/%v", org, projectNumber)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

project := new(ProjectV2)
resp, err := s.client.Do(ctx, req, project)
if err != nil {
return nil, resp, err
}
return project, resp, nil
}

// ListProjectsForUser lists Projects V2 for a user.
//
// GitHub API docs: https://docs.github.com/rest/projects/projects#list-projects-for-user
//
//meta:operation GET /users/{username}/projectsV2
func (s *ProjectsService) ListProjectsForUser(ctx context.Context, username string, opts *ListProjectsOptions) ([]*ProjectV2, *Response, error) {
u := fmt.Sprintf("users/%v/projectsV2", username)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

var projects []*ProjectV2
resp, err := s.client.Do(ctx, req, &projects)
if err != nil {
return nil, resp, err
}
return projects, resp, nil
}

// GetProjectForUser gets a Projects V2 project for a user by ID.
//
// GitHub API docs: https://docs.github.com/rest/projects/projects#get-project-for-user
//
//meta:operation GET /users/{username}/projectsV2/{project_number}
func (s *ProjectsService) GetProjectForUser(ctx context.Context, username string, projectNumber int64) (*ProjectV2, *Response, error) {
u := fmt.Sprintf("users/%v/projectsV2/%v", username, projectNumber)
req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

project := new(ProjectV2)
resp, err := s.client.Do(ctx, req, project)
if err != nil {
return nil, resp, err
}
return project, resp, nil
}

// ListProjectFieldsForOrg lists Projects V2 for an organization.
//
// GitHub API docs: https://docs.github.com/rest/projects/fields#list-project-fields-for-organization
//
//meta:operation GET /orgs/{org}/projectsV2/{project_number}/fields
func (s *ProjectsService) ListProjectFieldsForOrg(ctx context.Context, org string, projectNumber int64, opts *ListProjectsOptions) ([]*ProjectV2Field, *Response, error) {
u := fmt.Sprintf("orgs/%v/projectsV2/%v/fields", org, projectNumber)
u, err := addOptions(u, opts)
if err != nil {
return nil, nil, err
}

req, err := s.client.NewRequest("GET", u, nil)
if err != nil {
return nil, nil, err
}

var fields []*ProjectV2Field
resp, err := s.client.Do(ctx, req, &fields)
if err != nil {
return nil, resp, err
}
return fields, resp, nil
}
Loading