diff --git a/.gitignore b/.gitignore index 3515c4b9740..d99e2ea721c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +*.sh *.test coverage.out diff --git a/github/actions_workflow_jobs.go b/github/actions_workflow_jobs.go index 8cb7c6aa876..0838ce48d74 100644 --- a/github/actions_workflow_jobs.go +++ b/github/actions_workflow_jobs.go @@ -84,7 +84,7 @@ func (s *ActionsService) ListWorkflowJobs(ctx context.Context, owner, repo strin // GetWorkflowJobByID gets a specific job in a workflow run by ID. // -// GitHub API docs: https://developer.github.com/v3/actions/workflow_jobs/#list-jobs-for-a-workflow-run +// GitHub API docs: https://developer.github.com/v3/actions/workflow_jobs/#get-a-workflow-job func (s *ActionsService) GetWorkflowJobByID(ctx context.Context, owner, repo string, jobID int64) (*WorkflowJob, *Response, error) { u := fmt.Sprintf("repos/%v/%v/actions/jobs/%v", owner, repo, jobID) diff --git a/github/activity_events.go b/github/activity_events.go index 115bcc97d3a..d45b26ddcd8 100644 --- a/github/activity_events.go +++ b/github/activity_events.go @@ -59,7 +59,7 @@ func (s *ActivityService) ListRepositoryEvents(ctx context.Context, owner, repo // ListIssueEventsForRepository lists issue events for a repository. // -// GitHub API docs: https://developer.github.com/v3/activity/events/#list-issue-events-for-a-repository +// GitHub API docs: https://developer.github.com/v3/issues/events/#list-events-for-a-repository func (s *ActivityService) ListIssueEventsForRepository(ctx context.Context, owner, repo string, opts *ListOptions) ([]*IssueEvent, *Response, error) { u := fmt.Sprintf("repos/%v/%v/issues/events", owner, repo) u, err := addOptions(u, opts) @@ -107,7 +107,7 @@ func (s *ActivityService) ListEventsForRepoNetwork(ctx context.Context, owner, r // ListEventsForOrganization lists public events for an organization. // -// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events-for-an-organization +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-organization-events func (s *ActivityService) ListEventsForOrganization(ctx context.Context, org string, opts *ListOptions) ([]*Event, *Response, error) { u := fmt.Sprintf("orgs/%v/events", org) u, err := addOptions(u, opts) @@ -132,7 +132,8 @@ func (s *ActivityService) ListEventsForOrganization(ctx context.Context, org str // ListEventsPerformedByUser lists the events performed by a user. If publicOnly is // true, only public events will be returned. // -// GitHub API docs: https://developer.github.com/v3/activity/events/#list-events-performed-by-a-user +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-events-for-the-authenticated-user +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events-for-a-user func (s *ActivityService) ListEventsPerformedByUser(ctx context.Context, user string, publicOnly bool, opts *ListOptions) ([]*Event, *Response, error) { var u string if publicOnly { @@ -162,7 +163,8 @@ func (s *ActivityService) ListEventsPerformedByUser(ctx context.Context, user st // ListEventsReceivedByUser lists the events received by a user. If publicOnly is // true, only public events will be returned. // -// GitHub API docs: https://developer.github.com/v3/activity/events/#list-events-that-a-user-has-received +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-events-received-by-the-authenticated-user +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events-received-by-a-user func (s *ActivityService) ListEventsReceivedByUser(ctx context.Context, user string, publicOnly bool, opts *ListOptions) ([]*Event, *Response, error) { var u string if publicOnly { @@ -192,7 +194,7 @@ func (s *ActivityService) ListEventsReceivedByUser(ctx context.Context, user str // ListUserEventsForOrganization provides the user’s organization dashboard. You // must be authenticated as the user to view this. // -// GitHub API docs: https://developer.github.com/v3/activity/events/#list-events-for-an-organization +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-organization-events-for-the-authenticated-user func (s *ActivityService) ListUserEventsForOrganization(ctx context.Context, org, user string, opts *ListOptions) ([]*Event, *Response, error) { u := fmt.Sprintf("users/%v/events/orgs/%v", user, org) u, err := addOptions(u, opts) diff --git a/github/activity_notifications.go b/github/activity_notifications.go index 983da6f82f1..9c19922bb30 100644 --- a/github/activity_notifications.go +++ b/github/activity_notifications.go @@ -49,7 +49,7 @@ type NotificationListOptions struct { // ListNotifications lists all notifications for the authenticated user. // -// GitHub API docs: https://developer.github.com/v3/activity/notifications/#list-your-notifications +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#list-notifications-for-the-authenticated-user func (s *ActivityService) ListNotifications(ctx context.Context, opts *NotificationListOptions) ([]*Notification, *Response, error) { u := fmt.Sprintf("notifications") u, err := addOptions(u, opts) @@ -74,7 +74,7 @@ func (s *ActivityService) ListNotifications(ctx context.Context, opts *Notificat // ListRepositoryNotifications lists all notifications in a given repository // for the authenticated user. // -// GitHub API docs: https://developer.github.com/v3/activity/notifications/#list-your-notifications-in-a-repository +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#list-repository-notifications-for-the-authenticated-user func (s *ActivityService) ListRepositoryNotifications(ctx context.Context, owner, repo string, opts *NotificationListOptions) ([]*Notification, *Response, error) { u := fmt.Sprintf("repos/%v/%v/notifications", owner, repo) u, err := addOptions(u, opts) @@ -118,7 +118,7 @@ func (s *ActivityService) MarkNotificationsRead(ctx context.Context, lastRead ti // MarkRepositoryNotificationsRead marks all notifications up to lastRead in // the specified repository as read. // -// GitHub API docs: https://developer.github.com/v3/activity/notifications/#mark-notifications-as-read-in-a-repository +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#mark-repository-notifications-as-read func (s *ActivityService) MarkRepositoryNotificationsRead(ctx context.Context, owner, repo string, lastRead time.Time) (*Response, error) { opts := &markReadOptions{ LastReadAt: lastRead, @@ -134,7 +134,7 @@ func (s *ActivityService) MarkRepositoryNotificationsRead(ctx context.Context, o // GetThread gets the specified notification thread. // -// GitHub API docs: https://developer.github.com/v3/activity/notifications/#view-a-single-thread +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#get-a-thread func (s *ActivityService) GetThread(ctx context.Context, id string) (*Notification, *Response, error) { u := fmt.Sprintf("notifications/threads/%v", id) @@ -169,7 +169,7 @@ func (s *ActivityService) MarkThreadRead(ctx context.Context, id string) (*Respo // GetThreadSubscription checks to see if the authenticated user is subscribed // to a thread. // -// GitHub API docs: https://developer.github.com/v3/activity/notifications/#get-a-thread-subscription +// GitHub API docs: https://developer.github.com/v3/activity/notifications/#get-a-thread-subscription-for-the-authenticated-user func (s *ActivityService) GetThreadSubscription(ctx context.Context, id string) (*Subscription, *Response, error) { u := fmt.Sprintf("notifications/threads/%v/subscription", id) diff --git a/github/activity_star.go b/github/activity_star.go index 1e9850f9e13..158f395210a 100644 --- a/github/activity_star.go +++ b/github/activity_star.go @@ -67,7 +67,8 @@ type ActivityListStarredOptions struct { // ListStarred lists all the repos starred by a user. Passing the empty string // will list the starred repositories for the authenticated user. // -// GitHub API docs: https://developer.github.com/v3/activity/starring/#list-repositories-being-starred +// GitHub API docs: https://developer.github.com/v3/activity/starring/#list-repositories-starred-by-a-user +// GitHub API docs: https://developer.github.com/v3/activity/starring/#list-repositories-starred-by-the-authenticated-user func (s *ActivityService) ListStarred(ctx context.Context, user string, opts *ActivityListStarredOptions) ([]*StarredRepository, *Response, error) { var u string if user != "" { @@ -100,7 +101,7 @@ func (s *ActivityService) ListStarred(ctx context.Context, user string, opts *Ac // IsStarred checks if a repository is starred by authenticated user. // -// GitHub API docs: https://developer.github.com/v3/activity/starring/#check-if-you-are-starring-a-repository +// GitHub API docs: https://developer.github.com/v3/activity/starring/#check-if-a-repository-is-starred-by-the-authenticated-user func (s *ActivityService) IsStarred(ctx context.Context, owner, repo string) (bool, *Response, error) { u := fmt.Sprintf("user/starred/%v/%v", owner, repo) req, err := s.client.NewRequest("GET", u, nil) @@ -114,7 +115,7 @@ func (s *ActivityService) IsStarred(ctx context.Context, owner, repo string) (bo // Star a repository as the authenticated user. // -// GitHub API docs: https://developer.github.com/v3/activity/starring/#star-a-repository +// GitHub API docs: https://developer.github.com/v3/activity/starring/#star-a-repository-for-the-authenticated-user func (s *ActivityService) Star(ctx context.Context, owner, repo string) (*Response, error) { u := fmt.Sprintf("user/starred/%v/%v", owner, repo) req, err := s.client.NewRequest("PUT", u, nil) @@ -126,7 +127,7 @@ func (s *ActivityService) Star(ctx context.Context, owner, repo string) (*Respon // Unstar a repository as the authenticated user. // -// GitHub API docs: https://developer.github.com/v3/activity/starring/#unstar-a-repository +// GitHub API docs: https://developer.github.com/v3/activity/starring/#unstar-a-repository-for-the-authenticated-user func (s *ActivityService) Unstar(ctx context.Context, owner, repo string) (*Response, error) { u := fmt.Sprintf("user/starred/%v/%v", owner, repo) req, err := s.client.NewRequest("DELETE", u, nil) diff --git a/github/activity_watching.go b/github/activity_watching.go index 608647a6ce5..0265a08d0d9 100644 --- a/github/activity_watching.go +++ b/github/activity_watching.go @@ -52,7 +52,8 @@ func (s *ActivityService) ListWatchers(ctx context.Context, owner, repo string, // ListWatched lists the repositories the specified user is watching. Passing // the empty string will fetch watched repos for the authenticated user. // -// GitHub API docs: https://developer.github.com/v3/activity/watching/#list-repositories-being-watched +// GitHub API docs: https://developer.github.com/v3/activity/watching/#list-repositories-watched-by-a-user +// GitHub API docs: https://developer.github.com/v3/activity/watching/#list-repositories-watched-by-the-authenticated-user func (s *ActivityService) ListWatched(ctx context.Context, user string, opts *ListOptions) ([]*Repository, *Response, error) { var u string if user != "" { diff --git a/github/apps.go b/github/apps.go index 75d9177fa9a..c0e8c9a0bfe 100644 --- a/github/apps.go +++ b/github/apps.go @@ -124,6 +124,7 @@ func (i Installation) String() string { // (e.g., https://github.com/settings/apps/:app_slug). // // GitHub API docs: https://developer.github.com/v3/apps/#get-a-single-github-app +// GitHub API docs: https://developer.github.com/v3/apps/#get-the-authenticated-github-app func (s *AppsService) Get(ctx context.Context, appSlug string) (*App, *Response, error) { var u string if appSlug != "" { @@ -177,14 +178,14 @@ func (s *AppsService) ListInstallations(ctx context.Context, opts *ListOptions) // GetInstallation returns the specified installation. // -// GitHub API docs: https://developer.github.com/v3/apps/#get-a-single-installation +// GitHub API docs: https://developer.github.com/v3/apps/#get-an-installation func (s *AppsService) GetInstallation(ctx context.Context, id int64) (*Installation, *Response, error) { return s.getInstallation(ctx, fmt.Sprintf("app/installations/%v", id)) } // ListUserInstallations lists installations that are accessible to the authenticated user. // -// GitHub API docs: https://developer.github.com/v3/apps/#list-installations-for-user +// GitHub API docs: https://developer.github.com/v3/apps/installations/#list-installations-for-a-user func (s *AppsService) ListUserInstallations(ctx context.Context, opts *ListOptions) ([]*Installation, *Response, error) { u, err := addOptions("user/installations", opts) if err != nil { @@ -235,7 +236,7 @@ func (s *AppsService) CreateInstallationToken(ctx context.Context, id int64, opt // CreateAttachment creates a new attachment on user comment containing a url. // -// GitHub API docs: https://developer.github.com/v3/apps/#create-a-content-attachment +// GitHub API docs: https://developer.github.com/v3/apps/installations/#create-a-content-attachment func (s *AppsService) CreateAttachment(ctx context.Context, contentReferenceID int64, title, body string) (*Attachment, *Response, error) { u := fmt.Sprintf("content_references/%v/attachments", contentReferenceID) payload := &Attachment{Title: String(title), Body: String(body)} diff --git a/github/apps_installation.go b/github/apps_installation.go index 59a90c15a99..2f7664cfbac 100644 --- a/github/apps_installation.go +++ b/github/apps_installation.go @@ -90,7 +90,7 @@ func (s *AppsService) AddRepository(ctx context.Context, instID, repoID int64) ( // RemoveRepository removes a single repository from an installation. // -// GitHub docs: https://developer.github.com/v3/apps/installations/#remove-repository-from-installation +// GitHub API docs: https://developer.github.com/v3/apps/installations/#remove-repository-from-installation func (s *AppsService) RemoveRepository(ctx context.Context, instID, repoID int64) (*Response, error) { u := fmt.Sprintf("user/installations/%v/repositories/%v", instID, repoID) req, err := s.client.NewRequest("DELETE", u, nil) @@ -104,7 +104,7 @@ func (s *AppsService) RemoveRepository(ctx context.Context, instID, repoID int64 // RevokeInstallationToken revokes an installation token. // -// GitHub docs: https://developer.github.com/v3/apps/installations/#revoke-an-installation-token +// GitHub API docs: https://developer.github.com/v3/apps/installations/#revoke-an-installation-token func (s *AppsService) RevokeInstallationToken(ctx context.Context) (*Response, error) { u := "installation/token" req, err := s.client.NewRequest("DELETE", u, nil) diff --git a/github/apps_manifest.go b/github/apps_manifest.go index 8d6ab3c8c45..9db76a30457 100644 --- a/github/apps_manifest.go +++ b/github/apps_manifest.go @@ -34,7 +34,7 @@ type AppConfig struct { // CompleteAppManifest completes the App manifest handshake flow for the given // code. // -// GitHub API docs: https://developer.github.com/apps/building-github-apps/creating-github-apps-from-a-manifest/#3-you-exchange-the-temporary-code-to-retrieve-the-app-configuration +// GitHub API docs: https://developer.github.com/v3/apps/#create-a-github-app-from-a-manifest func (s *AppsService) CompleteAppManifest(ctx context.Context, code string) (*AppConfig, *Response, error) { u := fmt.Sprintf("app-manifests/%s/conversions", code) req, err := s.client.NewRequest("POST", u, nil) diff --git a/github/apps_marketplace.go b/github/apps_marketplace.go index 053d451dc60..9026633e7aa 100644 --- a/github/apps_marketplace.go +++ b/github/apps_marketplace.go @@ -150,7 +150,8 @@ func (s *MarketplaceService) ListPlanAccountsForAccount(ctx context.Context, acc // ListMarketplacePurchasesForUser lists all GitHub marketplace purchases made by a user. // -// GitHub API docs: https://developer.github.com/v3/apps/marketplace/#get-a-users-marketplace-purchases +// GitHub API docs: https://developer.github.com/v3/apps/marketplace/#list-subscriptions-for-the-authenticated-user +// GitHub API docs: https://developer.github.com/v3/apps/marketplace/#list-subscriptions-for-the-authenticated-user-stubbed func (s *MarketplaceService) ListMarketplacePurchasesForUser(ctx context.Context, opts *ListOptions) ([]*MarketplacePurchase, *Response, error) { uri := "user/marketplace_purchases" if s.Stubbed { diff --git a/github/checks.go b/github/checks.go index 67b7e770217..8d08cb39fbc 100644 --- a/github/checks.go +++ b/github/checks.go @@ -96,7 +96,7 @@ func (c CheckSuite) String() string { // GetCheckRun gets a check-run for a repository. // -// GitHub API docs: https://developer.github.com/v3/checks/runs/#get-a-single-check-run +// GitHub API docs: https://developer.github.com/v3/checks/runs/#get-a-check-run func (s *ChecksService) GetCheckRun(ctx context.Context, owner, repo string, checkRunID int64) (*CheckRun, *Response, error) { u := fmt.Sprintf("repos/%v/%v/check-runs/%v", owner, repo, checkRunID) req, err := s.client.NewRequest("GET", u, nil) @@ -117,7 +117,7 @@ func (s *ChecksService) GetCheckRun(ctx context.Context, owner, repo string, che // GetCheckSuite gets a single check suite. // -// GitHub API docs: https://developer.github.com/v3/checks/suites/#get-a-single-check-suite +// GitHub API docs: https://developer.github.com/v3/checks/suites/#get-a-check-suite func (s *ChecksService) GetCheckSuite(ctx context.Context, owner, repo string, checkSuiteID int64) (*CheckSuite, *Response, error) { u := fmt.Sprintf("repos/%v/%v/check-suites/%v", owner, repo, checkSuiteID) req, err := s.client.NewRequest("GET", u, nil) @@ -214,7 +214,7 @@ func (s *ChecksService) UpdateCheckRun(ctx context.Context, owner, repo string, // ListCheckRunAnnotations lists the annotations for a check run. // -// GitHub API docs: https://developer.github.com/v3/checks/runs/#list-annotations-for-a-check-run +// GitHub API docs: https://developer.github.com/v3/checks/runs/#list-check-run-annotations func (s *ChecksService) ListCheckRunAnnotations(ctx context.Context, owner, repo string, checkRunID int64, opts *ListOptions) ([]*CheckRunAnnotation, *Response, error) { u := fmt.Sprintf("repos/%v/%v/check-runs/%v/annotations", owner, repo, checkRunID) u, err := addOptions(u, opts) @@ -255,7 +255,7 @@ type ListCheckRunsResults struct { // ListCheckRunsForRef lists check runs for a specific ref. // -// GitHub API docs: https://developer.github.com/v3/checks/runs/#list-check-runs-for-a-specific-ref +// GitHub API docs: https://developer.github.com/v3/checks/runs/#list-check-runs-for-a-git-reference func (s *ChecksService) ListCheckRunsForRef(ctx context.Context, owner, repo, ref string, opts *ListCheckRunsOptions) (*ListCheckRunsResults, *Response, error) { u := fmt.Sprintf("repos/%v/%v/commits/%v/check-runs", owner, repo, refURLEscape(ref)) u, err := addOptions(u, opts) @@ -321,7 +321,7 @@ type ListCheckSuiteResults struct { // ListCheckSuitesForRef lists check suite for a specific ref. // -// GitHub API docs: https://developer.github.com/v3/checks/suites/#list-check-suites-for-a-specific-ref +// GitHub API docs: https://developer.github.com/v3/checks/suites/#list-check-suites-for-a-git-reference func (s *ChecksService) ListCheckSuitesForRef(ctx context.Context, owner, repo, ref string, opts *ListCheckSuiteOptions) (*ListCheckSuiteResults, *Response, error) { u := fmt.Sprintf("repos/%v/%v/commits/%v/check-suites", owner, repo, refURLEscape(ref)) u, err := addOptions(u, opts) @@ -369,7 +369,7 @@ type PreferenceList struct { // SetCheckSuitePreferences changes the default automatic flow when creating check suites. // -// GitHub API docs: https://developer.github.com/v3/checks/suites/#set-preferences-for-check-suites-on-a-repository +// GitHub API docs: https://developer.github.com/v3/checks/suites/#update-repository-preferences-for-check-suites func (s *ChecksService) SetCheckSuitePreferences(ctx context.Context, owner, repo string, opts CheckSuitePreferenceOptions) (*CheckSuitePreferenceResults, *Response, error) { u := fmt.Sprintf("repos/%v/%v/check-suites/preferences", owner, repo) req, err := s.client.NewRequest("PATCH", u, opts) @@ -417,7 +417,7 @@ func (s *ChecksService) CreateCheckSuite(ctx context.Context, owner, repo string // ReRequestCheckSuite triggers GitHub to rerequest an existing check suite, without pushing new code to a repository. // -// GitHub API docs: https://developer.github.com/v3/checks/suites/#rerequest-check-suite +// GitHub API docs: https://developer.github.com/v3/checks/suites/#rerequest-a-check-suite func (s *ChecksService) ReRequestCheckSuite(ctx context.Context, owner, repo string, checkSuiteID int64) (*Response, error) { u := fmt.Sprintf("repos/%v/%v/check-suites/%v/rerequest", owner, repo, checkSuiteID) diff --git a/github/gists.go b/github/gists.go index 0f326055421..026b5d15ee1 100644 --- a/github/gists.go +++ b/github/gists.go @@ -96,7 +96,8 @@ type GistListOptions struct { // is authenticated, it will returns all gists for the authenticated // user. // -// GitHub API docs: https://developer.github.com/v3/gists/#list-gists +// GitHub API docs: https://developer.github.com/v3/gists/#list-gists-for-a-user +// GitHub API docs: https://developer.github.com/v3/gists/#list-gists-for-the-authenticated-user func (s *GistsService) List(ctx context.Context, user string, opts *GistListOptions) ([]*Gist, *Response, error) { var u string if user != "" { @@ -125,7 +126,7 @@ func (s *GistsService) List(ctx context.Context, user string, opts *GistListOpti // ListAll lists all public gists. // -// GitHub API docs: https://developer.github.com/v3/gists/#list-gists +// GitHub API docs: https://developer.github.com/v3/gists/#list-public-gists func (s *GistsService) ListAll(ctx context.Context, opts *GistListOptions) ([]*Gist, *Response, error) { u, err := addOptions("gists/public", opts) if err != nil { @@ -148,7 +149,7 @@ func (s *GistsService) ListAll(ctx context.Context, opts *GistListOptions) ([]*G // ListStarred lists starred gists of authenticated user. // -// GitHub API docs: https://developer.github.com/v3/gists/#list-gists +// GitHub API docs: https://developer.github.com/v3/gists/#list-starred-gists func (s *GistsService) ListStarred(ctx context.Context, opts *GistListOptions) ([]*Gist, *Response, error) { u, err := addOptions("gists/starred", opts) if err != nil { @@ -171,7 +172,7 @@ func (s *GistsService) ListStarred(ctx context.Context, opts *GistListOptions) ( // Get a single gist. // -// GitHub API docs: https://developer.github.com/v3/gists/#get-a-single-gist +// GitHub API docs: https://developer.github.com/v3/gists/#get-a-gist func (s *GistsService) Get(ctx context.Context, id string) (*Gist, *Response, error) { u := fmt.Sprintf("gists/%v", id) req, err := s.client.NewRequest("GET", u, nil) @@ -228,7 +229,7 @@ func (s *GistsService) Create(ctx context.Context, gist *Gist) (*Gist, *Response // Edit a gist. // -// GitHub API docs: https://developer.github.com/v3/gists/#edit-a-gist +// GitHub API docs: https://developer.github.com/v3/gists/#update-a-gist func (s *GistsService) Edit(ctx context.Context, id string, gist *Gist) (*Gist, *Response, error) { u := fmt.Sprintf("gists/%v", id) req, err := s.client.NewRequest("PATCH", u, gist) diff --git a/github/issues.go b/github/issues.go index bc3b83e732b..ccce80abc79 100644 --- a/github/issues.go +++ b/github/issues.go @@ -129,7 +129,8 @@ type PullRequestLinks struct { // organization repositories; if false, list only owned and member // repositories. // -// GitHub API docs: https://developer.github.com/v3/issues/#list-issues +// GitHub API docs: https://developer.github.com/v3/issues/#list-issues-assigned-to-the-authenticated-user +// GitHub API docs: https://developer.github.com/v3/issues/#list-user-account-issues-assigned-to-the-authenticated-user func (s *IssuesService) List(ctx context.Context, all bool, opts *IssueListOptions) ([]*Issue, *Response, error) { var u string if all { @@ -143,7 +144,7 @@ func (s *IssuesService) List(ctx context.Context, all bool, opts *IssueListOptio // ListByOrg fetches the issues in the specified organization for the // authenticated user. // -// GitHub API docs: https://developer.github.com/v3/issues/#list-issues +// GitHub API docs: https://developer.github.com/v3/issues/#list-organization-issues-assigned-to-the-authenticated-user func (s *IssuesService) ListByOrg(ctx context.Context, org string, opts *IssueListOptions) ([]*Issue, *Response, error) { u := fmt.Sprintf("orgs/%v/issues", org) return s.listIssues(ctx, u, opts) @@ -243,7 +244,7 @@ func (s *IssuesService) ListByRepo(ctx context.Context, owner string, repo strin // Get a single issue. // -// GitHub API docs: https://developer.github.com/v3/issues/#get-a-single-issue +// GitHub API docs: https://developer.github.com/v3/issues/#get-an-issue func (s *IssuesService) Get(ctx context.Context, owner string, repo string, number int) (*Issue, *Response, error) { u := fmt.Sprintf("repos/%v/%v/issues/%d", owner, repo, number) req, err := s.client.NewRequest("GET", u, nil) @@ -285,7 +286,7 @@ func (s *IssuesService) Create(ctx context.Context, owner string, repo string, i // Edit an issue. // -// GitHub API docs: https://developer.github.com/v3/issues/#edit-an-issue +// GitHub API docs: https://developer.github.com/v3/issues/#update-an-issue func (s *IssuesService) Edit(ctx context.Context, owner string, repo string, number int, issue *IssueRequest) (*Issue, *Response, error) { u := fmt.Sprintf("repos/%v/%v/issues/%d", owner, repo, number) req, err := s.client.NewRequest("PATCH", u, issue) diff --git a/github/issues_comments.go b/github/issues_comments.go index 56b24c19179..5e5a754744e 100644 --- a/github/issues_comments.go +++ b/github/issues_comments.go @@ -50,6 +50,7 @@ type IssueListCommentsOptions struct { // ListComments lists all comments on the specified issue. Specifying an issue // number of 0 will return all comments on all issues for the repository. // +// GitHub API docs: https://developer.github.com/v3/issues/comments/#list-comments-in-a-repository // GitHub API docs: https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue func (s *IssuesService) ListComments(ctx context.Context, owner string, repo string, number int, opts *IssueListCommentsOptions) ([]*IssueComment, *Response, error) { var u string diff --git a/github/migrations.go b/github/migrations.go index f4260bb77cc..86b7c2789e0 100644 --- a/github/migrations.go +++ b/github/migrations.go @@ -74,7 +74,7 @@ type startMigration struct { // StartMigration starts the generation of a migration archive. // repos is a slice of repository names to migrate. // -// GitHub API docs: https://developer.github.com/v3/migration/migrations/#start-a-migration +// GitHub API docs: https://developer.github.com/v3/migrations/orgs/#start-an-organization-migration func (s *MigrationService) StartMigration(ctx context.Context, org string, repos []string, opts *MigrationOptions) (*Migration, *Response, error) { u := fmt.Sprintf("orgs/%v/migrations", org) @@ -103,7 +103,7 @@ func (s *MigrationService) StartMigration(ctx context.Context, org string, repos // ListMigrations lists the most recent migrations. // -// GitHub API docs: https://developer.github.com/v3/migrations/orgs/#get-a-list-of-organization-migrations +// GitHub API docs: https://developer.github.com/v3/migrations/orgs/#list-organization-migrations func (s *MigrationService) ListMigrations(ctx context.Context, org string, opts *ListOptions) ([]*Migration, *Response, error) { u := fmt.Sprintf("orgs/%v/migrations", org) u, err := addOptions(u, opts) @@ -131,7 +131,7 @@ func (s *MigrationService) ListMigrations(ctx context.Context, org string, opts // MigrationStatus gets the status of a specific migration archive. // id is the migration ID. // -// GitHub API docs: https://developer.github.com/v3/migration/migrations/#get-the-status-of-a-migration +// GitHub API docs: https://developer.github.com/v3/migrations/orgs/#get-the-status-of-an-organization-migration func (s *MigrationService) MigrationStatus(ctx context.Context, org string, id int64) (*Migration, *Response, error) { u := fmt.Sprintf("orgs/%v/migrations/%v", org, id) @@ -155,7 +155,7 @@ func (s *MigrationService) MigrationStatus(ctx context.Context, org string, id i // MigrationArchiveURL fetches a migration archive URL. // id is the migration ID. // -// GitHub API docs: https://developer.github.com/v3/migration/migrations/#download-a-migration-archive +// GitHub API docs: https://developer.github.com/v3/migrations/orgs/#download-an-organization-migration-archive func (s *MigrationService) MigrationArchiveURL(ctx context.Context, org string, id int64) (url string, err error) { u := fmt.Sprintf("orgs/%v/migrations/%v/archive", org, id) @@ -192,7 +192,7 @@ func (s *MigrationService) MigrationArchiveURL(ctx context.Context, org string, // DeleteMigration deletes a previous migration archive. // id is the migration ID. // -// GitHub API docs: https://developer.github.com/v3/migration/migrations/#delete-a-migration-archive +// GitHub API docs: https://developer.github.com/v3/migrations/orgs/#delete-an-organization-migration-archive func (s *MigrationService) DeleteMigration(ctx context.Context, org string, id int64) (*Response, error) { u := fmt.Sprintf("orgs/%v/migrations/%v/archive", org, id) @@ -212,7 +212,7 @@ func (s *MigrationService) DeleteMigration(ctx context.Context, org string, id i // You should unlock each migrated repository and delete them when the migration // is complete and you no longer need the source data. // -// GitHub API docs: https://developer.github.com/v3/migration/migrations/#unlock-a-repository +// GitHub API docs: https://developer.github.com/v3/migrations/orgs/#unlock-an-organization-repository func (s *MigrationService) UnlockRepo(ctx context.Context, org string, id int64, repo string) (*Response, error) { u := fmt.Sprintf("orgs/%v/migrations/%v/repos/%v/lock", org, id, repo) diff --git a/github/migrations_source_import.go b/github/migrations_source_import.go index cbdf1ea8a13..3980dc902c1 100644 --- a/github/migrations_source_import.go +++ b/github/migrations_source_import.go @@ -146,7 +146,7 @@ func (f LargeFile) String() string { // StartImport initiates a repository import. // -// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#start-an-import +// GitHub API docs: https://developer.github.com/v3/migrations/source_imports/#start-an-import func (s *MigrationService) StartImport(ctx context.Context, owner, repo string, in *Import) (*Import, *Response, error) { u := fmt.Sprintf("repos/%v/%v/import", owner, repo) req, err := s.client.NewRequest("PUT", u, in) @@ -165,7 +165,7 @@ func (s *MigrationService) StartImport(ctx context.Context, owner, repo string, // ImportProgress queries for the status and progress of an ongoing repository import. // -// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#get-import-progress +// GitHub API docs: https://developer.github.com/v3/migrations/source_imports/#get-import-progress func (s *MigrationService) ImportProgress(ctx context.Context, owner, repo string) (*Import, *Response, error) { u := fmt.Sprintf("repos/%v/%v/import", owner, repo) req, err := s.client.NewRequest("GET", u, nil) @@ -184,7 +184,7 @@ func (s *MigrationService) ImportProgress(ctx context.Context, owner, repo strin // UpdateImport initiates a repository import. // -// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#update-existing-import +// GitHub API docs: https://developer.github.com/v3/migrations/source_imports/#update-existing-import func (s *MigrationService) UpdateImport(ctx context.Context, owner, repo string, in *Import) (*Import, *Response, error) { u := fmt.Sprintf("repos/%v/%v/import", owner, repo) req, err := s.client.NewRequest("PATCH", u, in) @@ -213,7 +213,7 @@ func (s *MigrationService) UpdateImport(ctx context.Context, owner, repo string, // This method and MapCommitAuthor allow you to provide correct Git author // information. // -// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#get-commit-authors +// GitHub API docs: https://developer.github.com/v3/migrations/source_imports/#get-commit-authors func (s *MigrationService) CommitAuthors(ctx context.Context, owner, repo string) ([]*SourceImportAuthor, *Response, error) { u := fmt.Sprintf("repos/%v/%v/import/authors", owner, repo) req, err := s.client.NewRequest("GET", u, nil) @@ -234,7 +234,7 @@ func (s *MigrationService) CommitAuthors(ctx context.Context, owner, repo string // application can continue updating authors any time before you push new // commits to the repository. // -// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#map-a-commit-author +// GitHub API docs: https://developer.github.com/v3/migrations/source_imports/#map-a-commit-author func (s *MigrationService) MapCommitAuthor(ctx context.Context, owner, repo string, id int64, author *SourceImportAuthor) (*SourceImportAuthor, *Response, error) { u := fmt.Sprintf("repos/%v/%v/import/authors/%v", owner, repo, id) req, err := s.client.NewRequest("PATCH", u, author) @@ -255,7 +255,7 @@ func (s *MigrationService) MapCommitAuthor(ctx context.Context, owner, repo stri // files larger than 100MB. Only the UseLFS field on the provided Import is // used. // -// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#set-git-lfs-preference +// GitHub API docs: https://developer.github.com/v3/migrations/source_imports/#set-git-lfs-preference func (s *MigrationService) SetLFSPreference(ctx context.Context, owner, repo string, in *Import) (*Import, *Response, error) { u := fmt.Sprintf("repos/%v/%v/import/lfs", owner, repo) req, err := s.client.NewRequest("PATCH", u, in) @@ -274,7 +274,7 @@ func (s *MigrationService) SetLFSPreference(ctx context.Context, owner, repo str // LargeFiles lists files larger than 100MB found during the import. // -// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#get-large-files +// GitHub API docs: https://developer.github.com/v3/migrations/source_imports/#get-large-files func (s *MigrationService) LargeFiles(ctx context.Context, owner, repo string) ([]*LargeFile, *Response, error) { u := fmt.Sprintf("repos/%v/%v/import/large_files", owner, repo) req, err := s.client.NewRequest("GET", u, nil) @@ -293,7 +293,7 @@ func (s *MigrationService) LargeFiles(ctx context.Context, owner, repo string) ( // CancelImport stops an import for a repository. // -// GitHub API docs: https://developer.github.com/v3/migration/source_imports/#cancel-an-import +// GitHub API docs: https://developer.github.com/v3/migrations/source_imports/#cancel-an-import func (s *MigrationService) CancelImport(ctx context.Context, owner, repo string) (*Response, error) { u := fmt.Sprintf("repos/%v/%v/import", owner, repo) req, err := s.client.NewRequest("DELETE", u, nil) diff --git a/github/migrations_user.go b/github/migrations_user.go index 5b58d293544..5224d7b844c 100644 --- a/github/migrations_user.go +++ b/github/migrations_user.go @@ -96,7 +96,7 @@ func (s *MigrationService) StartUserMigration(ctx context.Context, repos []strin // ListUserMigrations lists the most recent migrations. // -// GitHub API docs: https://developer.github.com/v3/migrations/users/#get-a-list-of-user-migrations +// GitHub API docs: https://developer.github.com/v3/migrations/users/#list-user-migrations func (s *MigrationService) ListUserMigrations(ctx context.Context) ([]*UserMigration, *Response, error) { u := "user/migrations" diff --git a/github/orgs.go b/github/orgs.go index 3c87ec84d4f..564231bc418 100644 --- a/github/orgs.go +++ b/github/orgs.go @@ -145,6 +145,7 @@ func (s *OrganizationsService) ListAll(ctx context.Context, opts *OrganizationsL // organizations for the authenticated user. // // GitHub API docs: https://developer.github.com/v3/orgs/#list-user-organizations +// GitHub API docs: https://developer.github.com/v3/orgs/#oauth-scope-requirements func (s *OrganizationsService) List(ctx context.Context, user string, opts *ListOptions) ([]*Organization, *Response, error) { var u string if user != "" { @@ -214,7 +215,7 @@ func (s *OrganizationsService) GetByID(ctx context.Context, id int64) (*Organiza // Edit an organization. // -// GitHub API docs: https://developer.github.com/v3/orgs/#edit-an-organization +// GitHub API docs: https://developer.github.com/v3/orgs/#members_can_create_repositories func (s *OrganizationsService) Edit(ctx context.Context, name string, org *Organization) (*Organization, *Response, error) { u := fmt.Sprintf("orgs/%v", name) req, err := s.client.NewRequest("PATCH", u, org) diff --git a/github/orgs_members.go b/github/orgs_members.go index 6b96d05f67c..0dfa92070d5 100644 --- a/github/orgs_members.go +++ b/github/orgs_members.go @@ -72,6 +72,7 @@ type ListMembersOptions struct { // public members, otherwise it will only return public members. // // GitHub API docs: https://developer.github.com/v3/orgs/members/#members-list +// GitHub API docs: https://developer.github.com/v3/orgs/members/#public-members-list func (s *OrganizationsService) ListMembers(ctx context.Context, org string, opts *ListMembersOptions) ([]*User, *Response, error) { var u string if opts != nil && opts.PublicOnly { @@ -206,9 +207,8 @@ func (s *OrganizationsService) ListOrgMemberships(ctx context.Context, opts *Lis // Passing an empty string for user will get the membership for the // authenticated user. // -// GitHub API docs: -// https://developer.github.com/v3/orgs/members/#get-organization-membership -// https://developer.github.com/v3/orgs/members/#get-your-organization-membership +// GitHub API docs: https://developer.github.com/v3/orgs/members/#get-organization-membership +// GitHub API docs: https://developer.github.com/v3/orgs/members/#get-your-organization-membership func (s *OrganizationsService) GetOrgMembership(ctx context.Context, user, org string) (*Membership, *Response, error) { var u string if user != "" { @@ -322,7 +322,7 @@ type CreateOrgInvitationOptions struct { // In order to create invitations in an organization, // the authenticated user must be an organization owner. // -// https://developer.github.com/v3/orgs/members/#create-organization-invitation +// GitHub API docs: https://developer.github.com/v3/orgs/members/#create-organization-invitation func (s *OrganizationsService) CreateOrgInvitation(ctx context.Context, org string, opts *CreateOrgInvitationOptions) (*Invitation, *Response, error) { u := fmt.Sprintf("orgs/%v/invitations", org) diff --git a/github/pulls.go b/github/pulls.go index 00692088647..019432659a2 100644 --- a/github/pulls.go +++ b/github/pulls.go @@ -214,6 +214,8 @@ func (s *PullRequestsService) Get(ctx context.Context, owner string, repo string } // GetRaw gets a single pull request in raw (diff or patch) format. +// +// GitHub API docs: https://developer.github.com/v3/pulls/#get-a-single-pull-request func (s *PullRequestsService) GetRaw(ctx context.Context, owner string, repo string, number int, opts RawOptions) (string, *Response, error) { u := fmt.Sprintf("repos/%v/%v/pulls/%d", owner, repo, number) req, err := s.client.NewRequest("GET", u, nil) @@ -458,7 +460,7 @@ type pullRequestMergeRequest struct { // Merge a pull request (Merge Button™). // commitMessage is the title for the automatic commit message. // -// GitHub API docs: https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-buttontrade +// GitHub API docs: https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button func (s *PullRequestsService) Merge(ctx context.Context, owner string, repo string, number int, commitMessage string, options *PullRequestOptions) (*PullRequestMergeResult, *Response, error) { u := fmt.Sprintf("repos/%v/%v/pulls/%d/merge", owner, repo, number) diff --git a/github/pulls_comments.go b/github/pulls_comments.go index 88fb209b6fa..741454aeefb 100644 --- a/github/pulls_comments.go +++ b/github/pulls_comments.go @@ -66,6 +66,7 @@ type PullRequestListCommentsOptions struct { // pull request number of 0 will return all comments on all pull requests for // the repository. // +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#list-comments-in-a-repository // GitHub API docs: https://developer.github.com/v3/pulls/comments/#list-comments-on-a-pull-request func (s *PullRequestsService) ListComments(ctx context.Context, owner string, repo string, number int, opts *PullRequestListCommentsOptions) ([]*PullRequestComment, *Response, error) { var u string @@ -144,7 +145,7 @@ func (s *PullRequestsService) CreateComment(ctx context.Context, owner string, r // CreateCommentInReplyTo creates a new comment as a reply to an existing pull request comment. // -// GitHub API docs: https://developer.github.com/v3/pulls/comments/#alternative-input +// GitHub API docs: https://developer.github.com/v3/pulls/comments/#create-a-comment func (s *PullRequestsService) CreateCommentInReplyTo(ctx context.Context, owner string, repo string, number int, body string, commentID int64) (*PullRequestComment, *Response, error) { comment := &struct { Body string `json:"body,omitempty"` diff --git a/github/reactions.go b/github/reactions.go index 7dd8b7c9db3..ce0a7518295 100644 --- a/github/reactions.go +++ b/github/reactions.go @@ -274,7 +274,7 @@ func (s *ReactionsService) DeleteIssueCommentReactionByID(ctx context.Context, r // ListPullRequestCommentReactions lists the reactions for a pull request review comment. // -// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-an-issue-comment +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-pull-request-review-comment func (s *ReactionsService) ListPullRequestCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListOptions) ([]*Reaction, *Response, error) { u := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions", owner, repo, id) u, err := addOptions(u, opts) @@ -304,7 +304,7 @@ func (s *ReactionsService) ListPullRequestCommentReactions(ctx context.Context, // previously created reaction will be returned with Status: 200 OK. // The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". // -// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-an-issue-comment +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-pull-request-review-comment func (s *ReactionsService) CreatePullRequestCommentReaction(ctx context.Context, owner, repo string, id int64, content string) (*Reaction, *Response, error) { u := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions", owner, repo, id) @@ -346,7 +346,7 @@ func (s *ReactionsService) DeletePullRequestCommentReactionByID(ctx context.Cont // ListTeamDiscussionReactions lists the reactions for a team discussion. // -// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-team-discussion +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-team-discussion-legacy func (s *ReactionsService) ListTeamDiscussionReactions(ctx context.Context, teamID int64, discussionNumber int, opts *ListOptions) ([]*Reaction, *Response, error) { u := fmt.Sprintf("teams/%v/discussions/%v/reactions", teamID, discussionNumber) u, err := addOptions(u, opts) @@ -373,7 +373,7 @@ func (s *ReactionsService) ListTeamDiscussionReactions(ctx context.Context, team // CreateTeamDiscussionReaction creates a reaction for a team discussion. // The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". // -// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-team-discussion +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-team-discussion-legacy func (s *ReactionsService) CreateTeamDiscussionReaction(ctx context.Context, teamID int64, discussionNumber int, content string) (*Reaction, *Response, error) { u := fmt.Sprintf("teams/%v/discussions/%v/reactions", teamID, discussionNumber) @@ -414,7 +414,7 @@ func (s *ReactionsService) DeleteTeamDiscussionReactionByOrgIDAndTeamID(ctx cont // ListTeamDiscussionCommentReactions lists the reactions for a team discussion comment. // -// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-team-discussion-comment +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-team-discussion-comment-legacy func (s *ReactionsService) ListTeamDiscussionCommentReactions(ctx context.Context, teamID int64, discussionNumber, commentNumber int, opts *ListOptions) ([]*Reaction, *Response, error) { u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v/reactions", teamID, discussionNumber, commentNumber) u, err := addOptions(u, opts) @@ -440,7 +440,7 @@ func (s *ReactionsService) ListTeamDiscussionCommentReactions(ctx context.Contex // CreateTeamDiscussionCommentReaction creates a reaction for a team discussion comment. // The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". // -// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-team-discussion-comment +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-team-discussion-comment-legacy func (s *ReactionsService) CreateTeamDiscussionCommentReaction(ctx context.Context, teamID int64, discussionNumber, commentNumber int, content string) (*Reaction, *Response, error) { u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v/reactions", teamID, discussionNumber, commentNumber) diff --git a/github/repos.go b/github/repos.go index c30d5955ee3..1014d91f44b 100644 --- a/github/repos.go +++ b/github/repos.go @@ -182,7 +182,8 @@ type RepositoryListOptions struct { // List the repositories for a user. Passing the empty string will list // repositories for the authenticated user. // -// GitHub API docs: https://developer.github.com/v3/repos/#list-user-repositories +// GitHub API docs: https://developer.github.com/v3/repos/#list-repositories-for-a-user +// GitHub API docs: https://developer.github.com/v3/repos/#list-repositories-for-the-authenticated-user func (s *RepositoriesService) List(ctx context.Context, user string, opts *RepositoryListOptions) ([]*Repository, *Response, error) { var u string if user != "" { @@ -268,7 +269,7 @@ type RepositoryListAllOptions struct { // ListAll lists all GitHub repositories in the order that they were created. // -// GitHub API docs: https://developer.github.com/v3/repos/#list-all-public-repositories +// GitHub API docs: https://developer.github.com/v3/repos/#list-public-repositories func (s *RepositoriesService) ListAll(ctx context.Context, opts *RepositoryListAllOptions) ([]*Repository, *Response, error) { u, err := addOptions("repositories", opts) if err != nil { @@ -326,7 +327,8 @@ type createRepoRequest struct { // Note that only a subset of the repo fields are used and repo must // not be nil. // -// GitHub API docs: https://developer.github.com/v3/repos/#create +// GitHub API docs: https://developer.github.com/v3/repos/#create-a-repository-for-the-authenticated-user +// GitHub API docs: https://developer.github.com/v3/repos/#create-an-organization-repository func (s *RepositoriesService) Create(ctx context.Context, org string, repo *Repository) (*Repository, *Response, error) { var u string if org != "" { @@ -383,7 +385,7 @@ type TemplateRepoRequest struct { // CreateFromTemplate generates a repository from a template. // -// GitHub API docs: https://developer.github.com/v3/repos/#create-repository-using-a-repository-template +// GitHub API docs: https://developer.github.com/v3/repos/#create-a-repository-using-a-template func (s *RepositoriesService) CreateFromTemplate(ctx context.Context, templateOwner, templateRepo string, templateRepoReq *TemplateRepoRequest) (*Repository, *Response, error) { u := fmt.Sprintf("repos/%v/%v/generate", templateOwner, templateRepo) @@ -404,7 +406,7 @@ func (s *RepositoriesService) CreateFromTemplate(ctx context.Context, templateOw // Get fetches a repository. // -// GitHub API docs: https://developer.github.com/v3/repos/#get +// GitHub API docs: https://developer.github.com/v3/repos/#get-a-repository func (s *RepositoriesService) Get(ctx context.Context, owner, repo string) (*Repository, *Response, error) { u := fmt.Sprintf("repos/%v/%v", owner, repo) req, err := s.client.NewRequest("GET", u, nil) @@ -474,7 +476,7 @@ func (s *RepositoriesService) GetByID(ctx context.Context, id int64) (*Repositor // Edit updates a repository. // -// GitHub API docs: https://developer.github.com/v3/repos/#edit +// GitHub API docs: https://developer.github.com/v3/repos/#update-a-repository func (s *RepositoriesService) Edit(ctx context.Context, owner, repo string, repository *Repository) (*Repository, *Response, error) { u := fmt.Sprintf("repos/%v/%v", owner, repo) req, err := s.client.NewRequest("PATCH", u, repository) @@ -898,7 +900,7 @@ type SignaturesProtectedBranch struct { // ListBranches lists branches for the specified repository. // -// GitHub API docs: https://developer.github.com/v3/repos/#list-branches +// GitHub API docs: https://developer.github.com/v3/repos/branches/#list-branches func (s *RepositoriesService) ListBranches(ctx context.Context, owner string, repo string, opts *BranchListOptions) ([]*Branch, *Response, error) { u := fmt.Sprintf("repos/%v/%v/branches", owner, repo) u, err := addOptions(u, opts) @@ -925,7 +927,7 @@ func (s *RepositoriesService) ListBranches(ctx context.Context, owner string, re // GetBranch gets the specified branch for a repository. // -// GitHub API docs: https://developer.github.com/v3/repos/#get-branch +// GitHub API docs: https://developer.github.com/v3/repos/branches/#get-branch func (s *RepositoriesService) GetBranch(ctx context.Context, owner, repo, branch string) (*Branch, *Response, error) { u := fmt.Sprintf("repos/%v/%v/branches/%v", owner, repo, branch) req, err := s.client.NewRequest("GET", u, nil) @@ -1304,7 +1306,7 @@ type repositoryTopics struct { // ListAllTopics lists topics for a repository. // -// GitHub API docs: https://developer.github.com/v3/repos/#list-all-topics-for-a-repository +// GitHub API docs: https://developer.github.com/v3/repos/#get-all-repository-topics func (s *RepositoriesService) ListAllTopics(ctx context.Context, owner, repo string) ([]string, *Response, error) { u := fmt.Sprintf("repos/%v/%v/topics", owner, repo) req, err := s.client.NewRequest("GET", u, nil) @@ -1326,7 +1328,7 @@ func (s *RepositoriesService) ListAllTopics(ctx context.Context, owner, repo str // ReplaceAllTopics replaces topics for a repository. // -// GitHub API docs: https://developer.github.com/v3/repos/#replace-all-topics-for-a-repository +// GitHub API docs: https://developer.github.com/v3/repos/#replace-all-repository-topics func (s *RepositoriesService) ReplaceAllTopics(ctx context.Context, owner, repo string, topics []string) ([]string, *Response, error) { u := fmt.Sprintf("repos/%v/%v/topics", owner, repo) t := &repositoryTopics{ diff --git a/github/repos_collaborators.go b/github/repos_collaborators.go index bdfe04f13f3..e461516cae0 100644 --- a/github/repos_collaborators.go +++ b/github/repos_collaborators.go @@ -68,7 +68,7 @@ func (s *RepositoriesService) ListCollaborators(ctx context.Context, owner, repo // Note: This will return false if the user is not a collaborator OR the user // is not a GitHub user. // -// GitHub API docs: https://developer.github.com/v3/repos/collaborators/#get +// GitHub API docs: https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-collaborator func (s *RepositoriesService) IsCollaborator(ctx context.Context, owner, repo, user string) (bool, *Response, error) { u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user) req, err := s.client.NewRequest("GET", u, nil) @@ -141,7 +141,7 @@ func (s *RepositoriesService) AddCollaborator(ctx context.Context, owner, repo, // RemoveCollaborator removes the specified GitHub user as collaborator from the given repo. // Note: Does not return error if a valid user that is not a collaborator is removed. // -// GitHub API docs: https://developer.github.com/v3/repos/collaborators/#remove-collaborator +// GitHub API docs: https://developer.github.com/v3/repos/collaborators/#remove-user-as-a-collaborator func (s *RepositoriesService) RemoveCollaborator(ctx context.Context, owner, repo, user string) (*Response, error) { u := fmt.Sprintf("repos/%v/%v/collaborators/%v", owner, repo, user) req, err := s.client.NewRequest("DELETE", u, nil) diff --git a/github/repos_commits.go b/github/repos_commits.go index 77bd748187f..4db577085ad 100644 --- a/github/repos_commits.go +++ b/github/repos_commits.go @@ -123,7 +123,7 @@ type BranchCommit struct { // ListCommits lists the commits of a repository. // -// GitHub API docs: https://developer.github.com/v3/repos/commits/#list +// GitHub API docs: https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository func (s *RepositoriesService) ListCommits(ctx context.Context, owner, repo string, opts *CommitsListOptions) ([]*RepositoryCommit, *Response, error) { u := fmt.Sprintf("repos/%v/%v/commits", owner, repo) u, err := addOptions(u, opts) @@ -167,6 +167,8 @@ func (s *RepositoriesService) GetCommit(ctx context.Context, owner, repo, sha st } // GetCommitRaw fetches the specified commit in raw (diff or patch) format. +// +// GitHub API docs: https://developer.github.com/v3/repos/commits/#get-a-single-commit func (s *RepositoriesService) GetCommitRaw(ctx context.Context, owner string, repo string, sha string, opts RawOptions) (string, *Response, error) { u := fmt.Sprintf("repos/%v/%v/commits/%v", owner, repo, sha) req, err := s.client.NewRequest("GET", u, nil) @@ -195,7 +197,7 @@ func (s *RepositoriesService) GetCommitRaw(ctx context.Context, owner string, re // GetCommitSHA1 gets the SHA-1 of a commit reference. If a last-known SHA1 is // supplied and no new commits have occurred, a 304 Unmodified response is returned. // -// GitHub API docs: https://developer.github.com/v3/repos/commits/#get-the-sha-1-of-a-commit-reference +// GitHub API docs: https://developer.github.com/v3/repos/commits/#get-a-single-commit func (s *RepositoriesService) GetCommitSHA1(ctx context.Context, owner, repo, ref, lastSHA string) (string, *Response, error) { u := fmt.Sprintf("repos/%v/%v/commits/%v", owner, repo, refURLEscape(ref)) diff --git a/github/repos_community_health.go b/github/repos_community_health.go index 73d1d573bff..e8775a6c15e 100644 --- a/github/repos_community_health.go +++ b/github/repos_community_health.go @@ -38,7 +38,7 @@ type CommunityHealthMetrics struct { // GetCommunityHealthMetrics retrieves all the community health metrics for a repository. // -// GitHub API docs: https://developer.github.com/v3/repos/community/#retrieve-community-health-metrics +// GitHub API docs: https://developer.github.com/v3/repos/community/#retrieve-community-profile-metrics func (s *RepositoriesService) GetCommunityHealthMetrics(ctx context.Context, owner, repo string) (*CommunityHealthMetrics, *Response, error) { u := fmt.Sprintf("repos/%v/%v/community/profile", owner, repo) req, err := s.client.NewRequest("GET", u, nil) diff --git a/github/repos_contents.go b/github/repos_contents.go index ede93e4f4b5..015d9471a5e 100644 --- a/github/repos_contents.go +++ b/github/repos_contents.go @@ -177,7 +177,7 @@ func (s *RepositoriesService) GetContents(ctx context.Context, owner, repo, path // CreateFile creates a new file in a repository at the given path and returns // the commit and file metadata. // -// GitHub API docs: https://developer.github.com/v3/repos/contents/#create-a-file +// GitHub API docs: https://developer.github.com/v3/repos/contents/#create-or-update-a-file func (s *RepositoriesService) CreateFile(ctx context.Context, owner, repo, path string, opts *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) { u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path) req, err := s.client.NewRequest("PUT", u, opts) @@ -195,7 +195,7 @@ func (s *RepositoriesService) CreateFile(ctx context.Context, owner, repo, path // UpdateFile updates a file in a repository at the given path and returns the // commit and file metadata. Requires the blob SHA of the file being updated. // -// GitHub API docs: https://developer.github.com/v3/repos/contents/#update-a-file +// GitHub API docs: https://developer.github.com/v3/repos/contents/#create-or-update-a-file func (s *RepositoriesService) UpdateFile(ctx context.Context, owner, repo, path string, opts *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) { u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path) req, err := s.client.NewRequest("PUT", u, opts) diff --git a/github/repos_hooks.go b/github/repos_hooks.go index 5af71dfd11f..d81e74c9cf9 100644 --- a/github/repos_hooks.go +++ b/github/repos_hooks.go @@ -131,7 +131,7 @@ func (s *RepositoriesService) CreateHook(ctx context.Context, owner, repo string // ListHooks lists all Hooks for the specified repository. // -// GitHub API docs: https://developer.github.com/v3/repos/hooks/#list +// GitHub API docs: https://developer.github.com/v3/repos/hooks/#list-hooks func (s *RepositoriesService) ListHooks(ctx context.Context, owner, repo string, opts *ListOptions) ([]*Hook, *Response, error) { u := fmt.Sprintf("repos/%v/%v/hooks", owner, repo) u, err := addOptions(u, opts) diff --git a/github/repos_keys.go b/github/repos_keys.go index 11b3b6f6840..0eefd3dd96f 100644 --- a/github/repos_keys.go +++ b/github/repos_keys.go @@ -14,7 +14,7 @@ import ( // ListKeys lists the deploy keys for a repository. // -// GitHub API docs: https://developer.github.com/v3/repos/keys/#list +// GitHub API docs: https://developer.github.com/v3/repos/keys/#list-deploy-keys func (s *RepositoriesService) ListKeys(ctx context.Context, owner string, repo string, opts *ListOptions) ([]*Key, *Response, error) { u := fmt.Sprintf("repos/%v/%v/keys", owner, repo) u, err := addOptions(u, opts) @@ -38,7 +38,7 @@ func (s *RepositoriesService) ListKeys(ctx context.Context, owner string, repo s // GetKey fetches a single deploy key. // -// GitHub API docs: https://developer.github.com/v3/repos/keys/#get +// GitHub API docs: https://developer.github.com/v3/repos/keys/#get-a-deploy-key func (s *RepositoriesService) GetKey(ctx context.Context, owner string, repo string, id int64) (*Key, *Response, error) { u := fmt.Sprintf("repos/%v/%v/keys/%v", owner, repo, id) @@ -58,7 +58,7 @@ func (s *RepositoriesService) GetKey(ctx context.Context, owner string, repo str // CreateKey adds a deploy key for a repository. // -// GitHub API docs: https://developer.github.com/v3/repos/keys/#create +// GitHub API docs: https://developer.github.com/v3/repos/keys/#add-a-new-deploy-key func (s *RepositoriesService) CreateKey(ctx context.Context, owner string, repo string, key *Key) (*Key, *Response, error) { u := fmt.Sprintf("repos/%v/%v/keys", owner, repo) @@ -78,7 +78,7 @@ func (s *RepositoriesService) CreateKey(ctx context.Context, owner string, repo // DeleteKey deletes a deploy key. // -// GitHub API docs: https://developer.github.com/v3/repos/keys/#delete +// GitHub API docs: https://developer.github.com/v3/repos/keys/#remove-a-deploy-key func (s *RepositoriesService) DeleteKey(ctx context.Context, owner string, repo string, id int64) (*Response, error) { u := fmt.Sprintf("repos/%v/%v/keys/%v", owner, repo, id) diff --git a/github/repos_pages.go b/github/repos_pages.go index 58826527d9d..bdbd84b6c04 100644 --- a/github/repos_pages.go +++ b/github/repos_pages.go @@ -165,7 +165,7 @@ func (s *RepositoriesService) ListPagesBuilds(ctx context.Context, owner, repo s // GetLatestPagesBuild fetches the latest build information for a GitHub pages site. // -// GitHub API docs: https://developer.github.com/v3/repos/pages/#list-latest-pages-build +// GitHub API docs: https://developer.github.com/v3/repos/pages/#get-latest-pages-build func (s *RepositoriesService) GetLatestPagesBuild(ctx context.Context, owner, repo string) (*PagesBuild, *Response, error) { u := fmt.Sprintf("repos/%v/%v/pages/builds/latest", owner, repo) req, err := s.client.NewRequest("GET", u, nil) @@ -184,7 +184,7 @@ func (s *RepositoriesService) GetLatestPagesBuild(ctx context.Context, owner, re // GetPageBuild fetches the specific build information for a GitHub pages site. // -// GitHub API docs: https://developer.github.com/v3/repos/pages/#list-a-specific-pages-build +// GitHub API docs: https://developer.github.com/v3/repos/pages/#get-a-specific-pages-build func (s *RepositoriesService) GetPageBuild(ctx context.Context, owner, repo string, id int64) (*PagesBuild, *Response, error) { u := fmt.Sprintf("repos/%v/%v/pages/builds/%v", owner, repo, id) req, err := s.client.NewRequest("GET", u, nil) diff --git a/github/repos_stats.go b/github/repos_stats.go index e9c6d3fff4d..0e257bad39d 100644 --- a/github/repos_stats.go +++ b/github/repos_stats.go @@ -45,7 +45,7 @@ func (w WeeklyStats) String() string { // it is now computing the requested statistics. A follow up request, after a // delay of a second or so, should result in a successful request. // -// GitHub API docs: https://developer.github.com/v3/repos/statistics/#contributors +// GitHub API docs: https://developer.github.com/v3/repos/statistics/#get-contributors-list-with-additions-deletions-and-commit-counts func (s *RepositoriesService) ListContributorsStats(ctx context.Context, owner, repo string) ([]*ContributorStats, *Response, error) { u := fmt.Sprintf("repos/%v/%v/stats/contributors", owner, repo) req, err := s.client.NewRequest("GET", u, nil) @@ -84,7 +84,7 @@ func (w WeeklyCommitActivity) String() string { // it is now computing the requested statistics. A follow up request, after a // delay of a second or so, should result in a successful request. // -// GitHub API docs: https://developer.github.com/v3/repos/statistics/#commit-activity +// GitHub API docs: https://developer.github.com/v3/repos/statistics/#get-the-last-year-of-commit-activity-data func (s *RepositoriesService) ListCommitActivity(ctx context.Context, owner, repo string) ([]*WeeklyCommitActivity, *Response, error) { u := fmt.Sprintf("repos/%v/%v/stats/commit_activity", owner, repo) req, err := s.client.NewRequest("GET", u, nil) @@ -111,7 +111,7 @@ func (s *RepositoriesService) ListCommitActivity(ctx context.Context, owner, rep // it is now computing the requested statistics. A follow up request, after a // delay of a second or so, should result in a successful request. // -// GitHub API docs: https://developer.github.com/v3/repos/statistics/#code-frequency +// GitHub API docs: https://developer.github.com/v3/repos/statistics/#get-the-number-of-additions-and-deletions-per-week func (s *RepositoriesService) ListCodeFrequency(ctx context.Context, owner, repo string) ([]*WeeklyStats, *Response, error) { u := fmt.Sprintf("repos/%v/%v/stats/code_frequency", owner, repo) req, err := s.client.NewRequest("GET", u, nil) @@ -164,7 +164,7 @@ func (r RepositoryParticipation) String() string { // it is now computing the requested statistics. A follow up request, after a // delay of a second or so, should result in a successful request. // -// GitHub API docs: https://developer.github.com/v3/repos/statistics/#participation +// GitHub API docs: https://developer.github.com/v3/repos/statistics/#get-the-weekly-commit-count-for-the-repository-owner-and-everyone-else func (s *RepositoriesService) ListParticipation(ctx context.Context, owner, repo string) (*RepositoryParticipation, *Response, error) { u := fmt.Sprintf("repos/%v/%v/stats/participation", owner, repo) req, err := s.client.NewRequest("GET", u, nil) @@ -197,7 +197,7 @@ type PunchCard struct { // it is now computing the requested statistics. A follow up request, after a // delay of a second or so, should result in a successful request. // -// GitHub API docs: https://developer.github.com/v3/repos/statistics/#punch-card +// GitHub API docs: https://developer.github.com/v3/repos/statistics/#get-the-number-of-commits-per-hour-in-each-day func (s *RepositoriesService) ListPunchCard(ctx context.Context, owner, repo string) ([]*PunchCard, *Response, error) { u := fmt.Sprintf("repos/%v/%v/stats/punch_card", owner, repo) req, err := s.client.NewRequest("GET", u, nil) diff --git a/github/repos_traffic.go b/github/repos_traffic.go index e7ee18849a5..91d85706286 100644 --- a/github/repos_traffic.go +++ b/github/repos_traffic.go @@ -118,7 +118,7 @@ func (s *RepositoriesService) ListTrafficViews(ctx context.Context, owner, repo // ListTrafficClones get total number of clones for the last 14 days and breaks it down either per day or week for the last 14 days. // -// GitHub API docs: https://developer.github.com/v3/repos/traffic/#views +// GitHub API docs: https://developer.github.com/v3/repos/traffic/#clones func (s *RepositoriesService) ListTrafficClones(ctx context.Context, owner, repo string, opts *TrafficBreakdownOptions) (*TrafficClones, *Response, error) { u := fmt.Sprintf("repos/%v/%v/traffic/clones", owner, repo) u, err := addOptions(u, opts) diff --git a/github/search.go b/github/search.go index f08b3eb080a..c15c1f2b272 100644 --- a/github/search.go +++ b/github/search.go @@ -147,7 +147,7 @@ type IssuesSearchResult struct { // Issues searches issues via various criteria. // -// GitHub API docs: https://developer.github.com/v3/search/#search-issues +// GitHub API docs: https://developer.github.com/v3/search/#search-issues-and-pull-requests func (s *SearchService) Issues(ctx context.Context, query string, opts *SearchOptions) (*IssuesSearchResult, *Response, error) { result := new(IssuesSearchResult) resp, err := s.search(ctx, "issues", &searchParameters{Query: query}, opts, result) diff --git a/github/teams.go b/github/teams.go index 9ca449cdba1..6eab37fd545 100644 --- a/github/teams.go +++ b/github/teams.go @@ -461,7 +461,7 @@ type TeamAddTeamRepoOptions struct { // The specified repository must be owned by the organization to which the team // belongs, or a direct fork of a repository owned by the organization. // -// GitHub API docs: https://developer.github.com/v3/teams/#add-team-repo +// GitHub API docs: https://developer.github.com/v3/teams/#add-or-update-team-repository func (s *TeamsService) AddTeamRepoByID(ctx context.Context, orgID, teamID int64, owner, repo string, opts *TeamAddTeamRepoOptions) (*Response, error) { u := fmt.Sprintf("organizations/%v/team/%v/repos/%v/%v", orgID, teamID, owner, repo) req, err := s.client.NewRequest("PUT", u, opts) @@ -476,7 +476,7 @@ func (s *TeamsService) AddTeamRepoByID(ctx context.Context, orgID, teamID int64, // The specified repository must be owned by the organization to which the team // belongs, or a direct fork of a repository owned by the organization. // -// GitHub API docs: https://developer.github.com/v3/teams/#add-team-repo +// GitHub API docs: https://developer.github.com/v3/teams/#add-or-update-team-repository func (s *TeamsService) AddTeamRepoBySlug(ctx context.Context, org, slug, owner, repo string, opts *TeamAddTeamRepoOptions) (*Response, error) { u := fmt.Sprintf("orgs/%v/teams/%v/repos/%v/%v", org, slug, owner, repo) req, err := s.client.NewRequest("PUT", u, opts) @@ -491,7 +491,7 @@ func (s *TeamsService) AddTeamRepoBySlug(ctx context.Context, org, slug, owner, // team given the team ID. Note that this does not delete the repository, it // just removes it from the team. // -// GitHub API docs: https://developer.github.com/v3/teams/#remove-team-repo +// GitHub API docs: https://developer.github.com/v3/teams/#remove-team-repository func (s *TeamsService) RemoveTeamRepoByID(ctx context.Context, orgID, teamID int64, owner, repo string) (*Response, error) { u := fmt.Sprintf("organizations/%v/team/%v/repos/%v/%v", orgID, teamID, owner, repo) req, err := s.client.NewRequest("DELETE", u, nil) @@ -506,7 +506,7 @@ func (s *TeamsService) RemoveTeamRepoByID(ctx context.Context, orgID, teamID int // team given the team slug. Note that this does not delete the repository, it // just removes it from the team. // -// GitHub API docs: https://developer.github.com/v3/teams/#remove-team-repo +// GitHub API docs: https://developer.github.com/v3/teams/#remove-team-repository func (s *TeamsService) RemoveTeamRepoBySlug(ctx context.Context, org, slug, owner, repo string) (*Response, error) { u := fmt.Sprintf("orgs/%v/teams/%v/repos/%v/%v", org, slug, owner, repo) req, err := s.client.NewRequest("DELETE", u, nil) diff --git a/github/users.go b/github/users.go index 8b6ce5d87b9..97747f713c6 100644 --- a/github/users.go +++ b/github/users.go @@ -78,7 +78,7 @@ func (u User) String() string { // user. // // GitHub API docs: https://developer.github.com/v3/users/#get-a-single-user -// and: https://developer.github.com/v3/users/#get-the-authenticated-user +// GitHub API docs: https://developer.github.com/v3/users/#get-the-authenticated-user func (s *UsersService) Get(ctx context.Context, user string) (*User, *Response, error) { var u string if user != "" { diff --git a/github/users_followers.go b/github/users_followers.go index afa7d4b6b71..fa841c6c5de 100644 --- a/github/users_followers.go +++ b/github/users_followers.go @@ -14,6 +14,7 @@ import ( // fetch followers for the authenticated user. // // GitHub API docs: https://developer.github.com/v3/users/followers/#list-followers-of-a-user +// GitHub API docs: https://developer.github.com/v3/users/followers/#list-followers-of-the-authenticated-user func (s *UsersService) ListFollowers(ctx context.Context, user string, opts *ListOptions) ([]*User, *Response, error) { var u string if user != "" { @@ -44,6 +45,7 @@ func (s *UsersService) ListFollowers(ctx context.Context, user string, opts *Lis // string will list people the authenticated user is following. // // GitHub API docs: https://developer.github.com/v3/users/followers/#list-users-followed-by-another-user +// GitHub API docs: https://developer.github.com/v3/users/followers/#list-users-followed-by-the-authenticated-user func (s *UsersService) ListFollowing(ctx context.Context, user string, opts *ListOptions) ([]*User, *Response, error) { var u string if user != "" { @@ -73,6 +75,7 @@ func (s *UsersService) ListFollowing(ctx context.Context, user string, opts *Lis // IsFollowing checks if "user" is following "target". Passing the empty // string for "user" will check if the authenticated user is following "target". // +// GitHub API docs: https://developer.github.com/v3/users/followers/#check-if-one-user-follows-another // GitHub API docs: https://developer.github.com/v3/users/followers/#check-if-you-are-following-a-user func (s *UsersService) IsFollowing(ctx context.Context, user, target string) (bool, *Response, error) { var u string diff --git a/github/users_gpg_keys.go b/github/users_gpg_keys.go index 1d4dab045af..20b6edfd5b2 100644 --- a/github/users_gpg_keys.go +++ b/github/users_gpg_keys.go @@ -45,6 +45,7 @@ type GPGEmail struct { // via Basic Auth or via OAuth with at least read:gpg_key scope. // // GitHub API docs: https://developer.github.com/v3/users/gpg_keys/#list-gpg-keys-for-a-user +// GitHub API docs: https://developer.github.com/v3/users/gpg_keys/#list-your-gpg-keys func (s *UsersService) ListGPGKeys(ctx context.Context, user string, opts *ListOptions) ([]*GPGKey, *Response, error) { var u string if user != "" { diff --git a/github/users_keys.go b/github/users_keys.go index f12c01b9b07..1091606a9f2 100644 --- a/github/users_keys.go +++ b/github/users_keys.go @@ -28,6 +28,7 @@ func (k Key) String() string { // string will fetch keys for the authenticated user. // // GitHub API docs: https://developer.github.com/v3/users/keys/#list-public-keys-for-a-user +// GitHub API docs: https://developer.github.com/v3/users/keys/#list-your-public-keys func (s *UsersService) ListKeys(ctx context.Context, user string, opts *ListOptions) ([]*Key, *Response, error) { var u string if user != "" { diff --git a/update-urls/activity-events_test.go b/update-urls/activity-events_test.go new file mode 100644 index 00000000000..6f4dbeb4b13 --- /dev/null +++ b/update-urls/activity-events_test.go @@ -0,0 +1,1441 @@ +// Copyright 2020 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 main + +import ( + "testing" +) + +func newActivitiesEventsPipeline() *pipelineSetup { + return &pipelineSetup{ + baseURL: "https://developer.github.com/v3/activity/events/", + endpointsFromWebsite: activityEventsWant, + filename: "activity_events.go", + serviceName: "ActivityService", + originalGoSource: activityEventsGoFileOriginal, + wantGoSource: activityEventsGoFileWant, + wantNumEndpoints: 7, + } +} + +func TestPipeline_ActivityEvents(t *testing.T) { + ps := newActivitiesEventsPipeline() + ps.setup(t, false, false) + ps.validate(t) +} + +func TestPipeline_ActivityEvents_FirstStripAllURLs(t *testing.T) { + ps := newActivitiesEventsPipeline() + ps.setup(t, true, false) + ps.validate(t) +} + +func TestPipeline_ActivityEvents_FirstDestroyReceivers(t *testing.T) { + ps := newActivitiesEventsPipeline() + ps.setup(t, false, true) + ps.validate(t) +} + +func TestPipeline_ActivityEvents_FirstStripAllURLsAndDestroyReceivers(t *testing.T) { + ps := newActivitiesEventsPipeline() + ps.setup(t, true, true) + ps.validate(t) +} + +func TestParseWebPageEndpoints_ActivityEvents(t *testing.T) { + got, want := parseWebPageEndpoints(activityEventsTestWebPage), activityEventsWant + testWebPageHelper(t, got, want) +} + +var activityEventsWant = endpointsByFragmentID{ + "list-public-events": []*Endpoint{ + {urlFormats: []string{"events"}, httpMethod: "GET"}, + }, + + "list-repository-events": []*Endpoint{ + {urlFormats: []string{"repos/%v/%v/events"}, httpMethod: "GET"}, + }, + + "list-public-events-for-a-network-of-repositories": []*Endpoint{ + {urlFormats: []string{"networks/%v/%v/events"}, httpMethod: "GET"}, + }, + + "list-events-received-by-the-authenticated-user": []*Endpoint{ + {urlFormats: []string{"users/%v/received_events"}, httpMethod: "GET"}, + }, + + "list-events-for-the-authenticated-user": []*Endpoint{ + {urlFormats: []string{"users/%v/events"}, httpMethod: "GET"}, + }, + + "list-public-events-for-a-user": []*Endpoint{ + {urlFormats: []string{"users/%v/events/public"}, httpMethod: "GET"}, + }, + + "list-organization-events-for-the-authenticated-user": []*Endpoint{ + {urlFormats: []string{"users/%v/events/orgs/%v"}, httpMethod: "GET"}, + }, + + "list-public-organization-events": []*Endpoint{ + {urlFormats: []string{"orgs/%v/events"}, httpMethod: "GET"}, + }, + + "list-public-events-received-by-a-user": []*Endpoint{ + {urlFormats: []string{"users/%v/received_events/public"}, httpMethod: "GET"}, + }, +} + +var activityEventsTestWebPage = ` + + + + + + + + Events | GitHub Developer Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+

+Events

+ +

This is a read-only API to the GitHub events. These events power the various activity streams on the site. An events API for repository issues is also available. For more information, see the "Issue Events API."

+ + + +

Events are optimized for polling with the "ETag" header. If no new events have been triggered, you will see a "304 Not Modified" response, and your current rate limit will be untouched. There is also an "X-Poll-Interval" header that specifies how often (in seconds) you are allowed to poll. In times of high +server load, the time may increase. Please obey the header.

+ +
+curl -I https://api.github.com/users/tater/events
+HTTP/1.1 200 OK
+X-Poll-Interval: 60
+ETag: "a18c3bded88eb5dbb5c849a489412bf3"
+# The quotes around the ETag value are important
+curl -I https://api.github.com/users/tater/events \
+   -H 'If-None-Match: "a18c3bded88eb5dbb5c849a489412bf3"'
+HTTP/1.1 304 Not Modified
+X-Poll-Interval: 60
+
+ +

Events support pagination, however the per_page option is unsupported. The fixed page size is 30 items. Fetching up to ten pages is supported, for a total of 300 events.

+ +

Only events created within the past 90 days will be included in timelines. Events older than 90 days will not be included (even if the total number of events in the timeline is less than 300).

+ +

All Events have the same response format:

+ +
Status: 200 OK
+Link: <https://api.github.com/resource?page=2>; rel="next",
+      <https://api.github.com/resource?page=5>; rel="last"
+
+ + +
[
+  {
+    "type": "Event",
+    "public": true,
+    "payload": {
+    },
+    "repo": {
+      "id": 3,
+      "name": "octocat/Hello-World",
+      "url": "https://api.github.com/repos/octocat/Hello-World"
+    },
+    "actor": {
+      "id": 1,
+      "login": "octocat",
+      "gravatar_id": "",
+      "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+      "url": "https://api.github.com/users/octocat"
+    },
+    "org": {
+      "id": 1,
+      "login": "github",
+      "gravatar_id": "",
+      "url": "https://api.github.com/orgs/github",
+      "avatar_url": "https://github.com/images/error/octocat_happy.gif"
+    },
+    "created_at": "2011-09-06T17:26:27Z",
+    "id": "12345"
+  }
+]
+
+ + +

+List public events +

+ +

We delay the public events feed by five minutes, which means the most recent event returned by the public events API actually occurred at least five minutes ago.

+ +
GET /events
+
+ +

+List repository events +

+ +
GET /repos/:owner/:repo/events
+
+ +

+List public events for a network of repositories +

+ +
GET /networks/:owner/:repo/events
+
+ +

+List public organization events +

+ +
GET /orgs/:org/events
+
+ +

+List events received by the authenticated user +

+ +

These are events that you've received by watching repos and following users. If you are authenticated as the given user, you will see private events. Otherwise, you'll only see public events.

+ +
GET /users/:username/received_events
+
+ +

+List public events received by a user +

+ +
GET /users/:username/received_events/public
+
+ +

+List events for the authenticated user +

+ +

If you are authenticated as the given user, you will see your private events. Otherwise, you'll only see public events.

+ +
GET /users/:username/events
+
+ +

+List public events for a user +

+ +
GET /users/:username/events/public
+
+ +

+List organization events for the authenticated user

+ +

This is the user's organization dashboard. You must be authenticated as the user to view this.

+ +
GET /users/:username/events/orgs/:org
+
+
+ + + + + +
+
+ + + + + + + +` + +var activityEventsGoFileOriginal = `// Copyright 2013 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" +) + +// ListEvents drinks from the firehose of all public events across GitHub. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events +func (s *ActivityService) ListEvents(ctx context.Context, opts *ListOptions) ([]*Event, *Response, error) { + u, err := addOptions("events", opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListRepositoryEvents lists events for a repository. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-repository-events +func (s *ActivityService) ListRepositoryEvents(ctx context.Context, owner, repo string, opts *ListOptions) ([]*Event, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/events", owner, repo) + 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 events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// Note that ActivityService.ListIssueEventsForRepository was moved to: +// IssuesService.ListRepositoryEvents. + +// ListEventsForRepoNetwork lists public events for a network of repositories. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events-for-a-network-of-repositories +func (s *ActivityService) ListEventsForRepoNetwork(ctx context.Context, owner, repo string, opts *ListOptions) ([]*Event, *Response, error) { + u := fmt.Sprintf("networks/%v/%v/events", owner, repo) + 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 events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListEventsForOrganization lists public events for an organization. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events-for-an-organization +func (s *ActivityService) ListEventsForOrganization(ctx context.Context, org string, opts *ListOptions) ([]*Event, *Response, error) { + u := fmt.Sprintf("orgs/%v/events", 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 events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListEventsPerformedByUser lists the events performed by a user. If publicOnly is +// true, only public events will be returned. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-events-for-the-authenticated-user +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events-for-a-user +func (s *ActivityService) ListEventsPerformedByUser(ctx context.Context, user string, publicOnly bool, opts *ListOptions) ([]*Event, *Response, error) { + var u string + if publicOnly { + u = fmt.Sprintf("users/%v/events/public", user) + } else { + u = fmt.Sprintf("users/%v/events", user) + } + 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 events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListEventsReceivedByUser lists the events received by a user. If publicOnly is +// true, only public events will be returned. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-events-received-by-the-authenticated-user +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events-received-by-a-user +func (s *ActivityService) ListEventsReceivedByUser(ctx context.Context, user string, publicOnly bool, opts *ListOptions) ([]*Event, *Response, error) { + var u string + if publicOnly { + u = fmt.Sprintf("users/%v/received_events/public", user) + } else { + u = fmt.Sprintf("users/%v/received_events", user) + } + 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 events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListUserEventsForOrganization provides the user’s organization dashboard. You +// must be authenticated as the user to view this. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-events-for-an-organization +func (s *ActivityService) ListUserEventsForOrganization(ctx context.Context, org, user string, opts *ListOptions) ([]*Event, *Response, error) { + u := fmt.Sprintf("users/%v/events/orgs/%v", user, 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 events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} +` + +var activityEventsGoFileWant = `// Copyright 2013 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" +) + +// ListEvents drinks from the firehose of all public events across GitHub. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events +func (s *ActivityService) ListEvents(ctx context.Context, opts *ListOptions) ([]*Event, *Response, error) { + u, err := addOptions("events", opts) + if err != nil { + return nil, nil, err + } + + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, nil, err + } + + var events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListRepositoryEvents lists events for a repository. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-repository-events +func (s *ActivityService) ListRepositoryEvents(ctx context.Context, owner, repo string, opts *ListOptions) ([]*Event, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/events", owner, repo) + 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 events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// Note that ActivityService.ListIssueEventsForRepository was moved to: +// IssuesService.ListRepositoryEvents. + +// ListEventsForRepoNetwork lists public events for a network of repositories. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events-for-a-network-of-repositories +func (s *ActivityService) ListEventsForRepoNetwork(ctx context.Context, owner, repo string, opts *ListOptions) ([]*Event, *Response, error) { + u := fmt.Sprintf("networks/%v/%v/events", owner, repo) + 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 events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListEventsForOrganization lists public events for an organization. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-organization-events +func (s *ActivityService) ListEventsForOrganization(ctx context.Context, org string, opts *ListOptions) ([]*Event, *Response, error) { + u := fmt.Sprintf("orgs/%v/events", 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 events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListEventsPerformedByUser lists the events performed by a user. If publicOnly is +// true, only public events will be returned. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-events-for-the-authenticated-user +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events-for-a-user +func (s *ActivityService) ListEventsPerformedByUser(ctx context.Context, user string, publicOnly bool, opts *ListOptions) ([]*Event, *Response, error) { + var u string + if publicOnly { + u = fmt.Sprintf("users/%v/events/public", user) + } else { + u = fmt.Sprintf("users/%v/events", user) + } + 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 events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListEventsReceivedByUser lists the events received by a user. If publicOnly is +// true, only public events will be returned. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-events-received-by-the-authenticated-user +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-public-events-received-by-a-user +func (s *ActivityService) ListEventsReceivedByUser(ctx context.Context, user string, publicOnly bool, opts *ListOptions) ([]*Event, *Response, error) { + var u string + if publicOnly { + u = fmt.Sprintf("users/%v/received_events/public", user) + } else { + u = fmt.Sprintf("users/%v/received_events", user) + } + 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 events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} + +// ListUserEventsForOrganization provides the user’s organization dashboard. You +// must be authenticated as the user to view this. +// +// GitHub API docs: https://developer.github.com/v3/activity/events/#list-organization-events-for-the-authenticated-user +func (s *ActivityService) ListUserEventsForOrganization(ctx context.Context, org, user string, opts *ListOptions) ([]*Event, *Response, error) { + u := fmt.Sprintf("users/%v/events/orgs/%v", user, 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 events []*Event + resp, err := s.client.Do(ctx, req, &events) + if err != nil { + return nil, resp, err + } + + return events, resp, nil +} +` diff --git a/update-urls/go.mod b/update-urls/go.mod new file mode 100644 index 00000000000..3e168745cfc --- /dev/null +++ b/update-urls/go.mod @@ -0,0 +1,5 @@ +module github.com/google/go-github/update-urls + +go 1.14 + +require github.com/pmezard/go-difflib v1.0.0 diff --git a/update-urls/go.sum b/update-urls/go.sum new file mode 100644 index 00000000000..5d60ca74bbf --- /dev/null +++ b/update-urls/go.sum @@ -0,0 +1,2 @@ +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/update-urls/main.go b/update-urls/main.go new file mode 100644 index 00000000000..73c2e9ca3aa --- /dev/null +++ b/update-urls/main.go @@ -0,0 +1,1165 @@ +// Copyright 2020 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. + +// update-urls updates GitHub URL docs for each service endpoint. +// +// It is meant to be used periodically by go-github repo maintainers +// to update stale GitHub Developer v3 API documenation URLs. +// +// Usage (from go-github directory): +// go run ./update-urls/main.go +// go generate ./... +// go test ./... +// go vet ./... +package main + +import ( + "errors" + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "log" + "net/http" + "os" + "regexp" + "sort" + "strings" +) + +const ( + skipPrefix = "gen-" + + enterpriseURL = "developer.github.com/enterprise" + stdURL = "developer.github.com" + + enterpriseRefFmt = "// GitHub Enterprise API docs: %v" + stdRefFmt = "// GitHub API docs: %v" +) + +var ( + verbose = flag.Bool("v", false, "Print verbose log messages") + debugFile = flag.String("d", "", "Debug named file only") + + // methodBlacklist holds methods that do not have GitHub v3 API URLs + // or are otherwise problematic in parsing, discovering, and/or fixing. + methodBlacklist = map[string]bool{ + "ActionsService.DownloadArtifact": true, + "AdminService.CreateOrg": true, + "AdminService.CreateUser": true, + "AdminService.CreateUserImpersonation": true, + "AdminService.DeleteUserImpersonation": true, + "AdminService.GetAdminStats": true, + "AdminService.RenameOrg": true, + "AdminService.RenameOrgByName": true, + "AdminService.UpdateTeamLDAPMapping": true, + "AdminService.UpdateUserLDAPMapping": true, + "AppsService.FindRepositoryInstallationByID": true, + "AuthorizationsService.CreateImpersonation": true, + "AuthorizationsService.DeleteImpersonation": true, + "GitService.GetRef": true, + "GitService.GetRefs": true, + "GitService.ListRefs": true, + "MarketplaceService.marketplaceURI": true, + "OrganizationsService.GetByID": true, + "RepositoriesService.DeletePreReceiveHook": true, + "RepositoriesService.DownloadContents": true, + "RepositoriesService.GetArchiveLink": true, + "RepositoriesService.GetByID": true, + "RepositoriesService.GetPreReceiveHook": true, + "RepositoriesService.ListPreReceiveHooks": true, + "RepositoriesService.UpdatePreReceiveHook": true, + "SearchService.search": true, + "UsersService.DemoteSiteAdmin": true, + "UsersService.GetByID": true, + "UsersService.PromoteSiteAdmin": true, + "UsersService.Suspend": true, + "UsersService.Unsuspend": true, + } + + helperOverrides = map[string]overrideFunc{ + "s.search": func(arg string) (httpMethod, url string) { + return "GET", fmt.Sprintf("search/%v", arg) + }, + } + + // methodOverrides contains overrides for troublesome endpoints. + methodOverrides = map[string]string{ + "OrganizationsService.EditOrgMembership: method orgs/%v/memberships/%v": "PUT", + "OrganizationsService.EditOrgMembership: PUT user/memberships/orgs/%v": "PATCH", + } + + paramRE = regexp.MustCompile(`:[a-z_]+`) +) + +type overrideFunc func(arg string) (httpMethod, url string) + +func logf(fmt string, args ...interface{}) { + if *verbose { + log.Printf(fmt, args...) + } +} + +type servicesMap map[string]*Service +type endpointsMap map[string]*Endpoint + +func main() { + flag.Parse() + fset := token.NewFileSet() + + sourceFilter := func(fi os.FileInfo) bool { + return !strings.HasSuffix(fi.Name(), "_test.go") && !strings.HasPrefix(fi.Name(), skipPrefix) + } + + if err := os.Chdir("./github"); err != nil { + log.Fatalf("Please run this from the go-github directory.") + } + + pkgs, err := parser.ParseDir(fset, ".", sourceFilter, parser.ParseComments) + if err != nil { + log.Fatal(err) + } + + // Step 1 - get a map of all services. + services := findAllServices(pkgs) + + // Step 2 - find all the API service endpoints. + iter := &realAstFileIterator{fset: fset, pkgs: pkgs} + endpoints, err := findAllServiceEndpoints(iter, services) + if err != nil { + log.Fatalf("\n%v", err) + } + + // Step 3 - resolve all missing httpMethods from helperMethods. + // Additionally, use existing URLs as hints to pre-cache all apiDocs. + docCache := &documentCache{} + usedHelpers, endpointsByFilename := resolveHelpersAndCacheDocs(endpoints, docCache) + + // Step 4 - validate and rewrite all URLs, skipping used helper methods. + frw := &liveFileRewriter{fset: fset} + validateRewriteURLs(usedHelpers, endpointsByFilename, docCache, frw) + + logf("Done.") +} + +type usedHelpersMap map[string]bool +type endpointsByFilenameMap map[string][]*Endpoint + +// FileRewriter read/writes files and converts AST token positions. +type FileRewriter interface { + Position(token.Pos) token.Position + ReadFile(filename string) ([]byte, error) + WriteFile(filename string, buf []byte, mode os.FileMode) error +} + +// liveFileRewriter implements FileRewriter. +type liveFileRewriter struct { + fset *token.FileSet +} + +func (lfr *liveFileRewriter) Position(pos token.Pos) token.Position { return lfr.fset.Position(pos) } +func (lfr *liveFileRewriter) ReadFile(filename string) ([]byte, error) { + return ioutil.ReadFile(filename) +} +func (lfr *liveFileRewriter) WriteFile(filename string, buf []byte, mode os.FileMode) error { + return ioutil.WriteFile(filename, buf, mode) +} + +func validateRewriteURLs(usedHelpers usedHelpersMap, endpointsByFilename endpointsByFilenameMap, docCache documentCacheReader, fileRewriter FileRewriter) { + for filename, slc := range endpointsByFilename { + logf("Step 4 - Processing %v methods in %v ...", len(slc), filename) + + var fileEdits []*FileEdit + for _, endpoint := range slc { + fullName := fmt.Sprintf("%v.%v", endpoint.serviceName, endpoint.endpointName) + if usedHelpers[fullName] { + logf("Step 4 - skipping used helper method %q", fullName) + continue + } + + // First, find the correct GitHub v3 API URL by httpMethod and urlFormat. + for _, path := range endpoint.urlFormats { + path = strings.ReplaceAll(path, "%d", "%v") + path = strings.ReplaceAll(path, "%s", "%v") + + // Check the overrides. + endpoint.checkHttpMethodOverride(path) + + methodAndPath := fmt.Sprintf("%v %v", endpoint.httpMethod, path) + url, ok := docCache.UrlByMethodAndPath(methodAndPath) + if !ok { + if i := len(endpoint.endpointComments); i > 0 { + pos := fileRewriter.Position(endpoint.endpointComments[i-1].Pos()) + fmt.Printf("%v:%v:%v: WARNING: unable to find online docs for %q: (%v)\nPLEASE CHECK MANUALLY AND FIX.\n", pos.Filename, pos.Line, pos.Column, fullName, methodAndPath) + } else { + fmt.Printf("%v: WARNING: unable to find online docs for %q: (%v)\nPLEASE CHECK MANUALLY AND FIX.\n", filename, fullName, methodAndPath) + } + continue + } + logf("found %q for: %q (%v)", url, fullName, methodAndPath) + + // Make sure URL is up-to-date. + switch { + case len(endpoint.enterpriseRefLines) > 1: + log.Printf("WARNING: multiple Enterprise GitHub URLs found - skipping: %#v", endpoint.enterpriseRefLines) + case len(endpoint.enterpriseRefLines) > 0: + line := fmt.Sprintf(enterpriseRefFmt, url) + cmt := endpoint.enterpriseRefLines[0] + if cmt.Text != line { + pos := fileRewriter.Position(cmt.Pos()) + logf("At byte offset %v:\nFOUND %q\nWANT: %q", pos.Offset, cmt.Text, line) + fileEdits = append(fileEdits, &FileEdit{ + pos: pos, + fromText: cmt.Text, + toText: line, + }) + } + case len(endpoint.stdRefLines) > 1: + var foundMatch bool + line := fmt.Sprintf(stdRefFmt, url) + for i, stdRefLine := range endpoint.stdRefLines { + if stdRefLine.Text == line { + foundMatch = true + logf("found match with %v, not editing and removing from list", line) + // Remove matching line + endpoint.stdRefLines = append(endpoint.stdRefLines[:i], endpoint.stdRefLines[i+1:]...) + break + } + } + if !foundMatch { // Edit last stdRefLine, then remove it. + cmt := endpoint.stdRefLines[len(endpoint.stdRefLines)-1] + pos := fileRewriter.Position(cmt.Pos()) + logf("stdRefLines=%v: At byte offset %v:\nFOUND %q\nWANT: %q", len(endpoint.stdRefLines), pos.Offset, cmt.Text, line) + fileEdits = append(fileEdits, &FileEdit{ + pos: pos, + fromText: cmt.Text, + toText: line, + }) + endpoint.stdRefLines = endpoint.stdRefLines[:len(endpoint.stdRefLines)-1] + } + case len(endpoint.stdRefLines) > 0: + line := fmt.Sprintf(stdRefFmt, url) + cmt := endpoint.stdRefLines[0] + if cmt.Text != line { + pos := fileRewriter.Position(cmt.Pos()) + logf("stdRefLines=1: At byte offset %v:\nFOUND %q\nWANT: %q", pos.Offset, cmt.Text, line) + fileEdits = append(fileEdits, &FileEdit{ + pos: pos, + fromText: cmt.Text, + toText: line, + }) + } + endpoint.stdRefLines = nil + case len(endpoint.endpointComments) > 0: + lastCmt := endpoint.endpointComments[len(endpoint.endpointComments)-1] + // logf("lastCmt.Text=%q (len=%v)", lastCmt.Text, len(lastCmt.Text)) + pos := fileRewriter.Position(lastCmt.Pos()) + pos.Offset += len(lastCmt.Text) + line := "\n" + fmt.Sprintf(stdRefFmt, url) + if lastCmt.Text != "//" { + line = "\n//" + line // Add blank comment line before URL. + } + // logf("line=%q (len=%v)", line, len(line)) + // logf("At byte offset %v: adding missing documentation:\n%q", pos.Offset, line) + fileEdits = append(fileEdits, &FileEdit{ + pos: pos, + fromText: "", + toText: line, + }) + default: // Missing documentation - add it. + log.Printf("WARNING: file %v has no godoc comment string for method %v", fullName, methodAndPath) + } + } + } + + if len(fileEdits) > 0 { + b, err := fileRewriter.ReadFile(filename) + if err != nil { + log.Fatalf("ReadFile: %v", err) + } + + log.Printf("Performing %v edits on file %v", len(fileEdits), filename) + b = performBufferEdits(b, fileEdits) + + if err := fileRewriter.WriteFile(filename, b, 0644); err != nil { + log.Fatalf("WriteFile: %v", err) + } + } + } +} + +func performBufferEdits(b []byte, fileEdits []*FileEdit) []byte { + fileEdits = sortAndMergeFileEdits(fileEdits) + + for _, edit := range fileEdits { + prelude := b[0:edit.pos.Offset] + postlude := b[edit.pos.Offset+len(edit.fromText):] + logf("At byte offset %v, replacing %v bytes with %v bytes\nBEFORE: %v\nAFTER : %v", edit.pos.Offset, len(edit.fromText), len(edit.toText), edit.fromText, edit.toText) + b = []byte(fmt.Sprintf("%s%v%s", prelude, edit.toText, postlude)) + } + + return b +} + +func sortAndMergeFileEdits(fileEdits []*FileEdit) []*FileEdit { + // Sort edits from last to first in the file. + // If the offsets are identical, sort the comment "toText" strings, ascending. + var foundDups bool + sort.Slice(fileEdits, func(a, b int) bool { + if fileEdits[a].pos.Offset == fileEdits[b].pos.Offset { + foundDups = true + return fileEdits[a].toText < fileEdits[b].toText + } + return fileEdits[a].pos.Offset > fileEdits[b].pos.Offset + }) + + if !foundDups { + return fileEdits + } + + // Merge the duplicate edits. + var mergedEdits []*FileEdit + var dupOffsets []*FileEdit + + mergeFunc := func() { + if len(dupOffsets) > 1 { + isInsert := dupOffsets[0].fromText == "" + var hasBlankCommentLine bool + + // Merge dups + var lines []string + for _, dup := range dupOffsets { + if isInsert && strings.HasPrefix(dup.toText, "\n//\n//") { + lines = append(lines, strings.TrimPrefix(dup.toText, "\n//")) + hasBlankCommentLine = true + } else { + lines = append(lines, dup.toText) + } + } + sort.Strings(lines) + + var joinStr string + // if insert, no extra newlines + if !isInsert { // if replacement - add newlines + joinStr = "\n" + } + toText := strings.Join(lines, joinStr) + if hasBlankCommentLine { // Add back in + toText = "\n//" + toText + } + mergedEdits = append(mergedEdits, &FileEdit{ + pos: dupOffsets[0].pos, + fromText: dupOffsets[0].fromText, + toText: toText, + }) + } else if len(dupOffsets) > 0 { + // Move non-dup to final output + mergedEdits = append(mergedEdits, dupOffsets[0]) + } + dupOffsets = nil + } + + lastOffset := -1 + for _, fileEdit := range fileEdits { + if fileEdit.pos.Offset != lastOffset { + mergeFunc() + } + dupOffsets = append(dupOffsets, fileEdit) + lastOffset = fileEdit.pos.Offset + } + mergeFunc() + return mergedEdits +} + +// astFileIterator iterates over all files in an ast.Package. +type astFileIterator interface { + // Finds the position of a token. + Position(token.Pos) token.Position + // Reset resets the iterator. + Reset() + // Next returns the next filenameAstFilePair pair or nil if done. + Next() *filenameAstFilePair +} + +type filenameAstFilePair struct { + filename string + astFile *ast.File +} + +// realAstFileIterator implements astFileIterator. +type realAstFileIterator struct { + fset *token.FileSet + pkgs map[string]*ast.Package + ch chan *filenameAstFilePair + closed bool +} + +func (rafi *realAstFileIterator) Position(pos token.Pos) token.Position { + return rafi.fset.Position(pos) +} + +func (rafi *realAstFileIterator) Reset() { + if !rafi.closed && rafi.ch != nil { + logf("Closing old channel on Reset") + close(rafi.ch) + } + rafi.ch = make(chan *filenameAstFilePair, 10) + rafi.closed = false + + go func() { + var count int + for _, pkg := range rafi.pkgs { + for filename, f := range pkg.Files { + logf("Sending file #%v: %v to channel", count, filename) + rafi.ch <- &filenameAstFilePair{filename: filename, astFile: f} + count++ + } + } + rafi.closed = true + close(rafi.ch) + logf("Closed channel after sending %v files", count) + if count == 0 { + log.Fatalf("Processed no files. Did you run this from the go-github directory?") + } + }() +} + +func (rafi *realAstFileIterator) Next() *filenameAstFilePair { + for pair := range rafi.ch { + logf("Next: returning file %v", pair.filename) + return pair + } + return nil +} + +func findAllServices(pkgs map[string]*ast.Package) servicesMap { + services := servicesMap{} + for _, pkg := range pkgs { + for filename, f := range pkg.Files { + if filename != "github.go" { + continue + } + + logf("Step 1 - Processing %v ...", filename) + if err := findClientServices(filename, f, services); err != nil { + log.Fatal(err) + } + } + } + return services +} + +func findAllServiceEndpoints(iter astFileIterator, services servicesMap) (endpointsMap, error) { + endpoints := endpointsMap{} + iter.Reset() + var errs []string // Collect all the errors and return in a big batch. + for next := iter.Next(); next != nil; next = iter.Next() { + filename, f := next.filename, next.astFile + if filename == "github.go" { + continue + } + + if *debugFile != "" && !strings.Contains(filename, *debugFile) { + continue + } + + logf("Step 2 - Processing %v ...", filename) + if err := processAST(filename, f, services, endpoints, iter); err != nil { + errs = append(errs, err.Error()) + } + } + + if len(errs) > 0 { + return nil, errors.New(strings.Join(errs, "\n")) + } + + return endpoints, nil +} + +func resolveHelpersAndCacheDocs(endpoints endpointsMap, docCache documentCacheWriter) (usedHelpers usedHelpersMap, endpointsByFilename endpointsByFilenameMap) { + usedHelpers = usedHelpersMap{} + endpointsByFilename = endpointsByFilenameMap{} + for k, v := range endpoints { + if _, ok := endpointsByFilename[v.filename]; !ok { + endpointsByFilename[v.filename] = []*Endpoint{} + } + endpointsByFilename[v.filename] = append(endpointsByFilename[v.filename], v) + + for _, cmt := range v.enterpriseRefLines { + docCache.CacheDocFromInternet(cmt.Text) + } + for _, cmt := range v.stdRefLines { + docCache.CacheDocFromInternet(cmt.Text) + } + + if v.httpMethod == "" && v.helperMethod != "" { + fullName := fmt.Sprintf("%v.%v", v.serviceName, v.helperMethod) + hm, ok := endpoints[fullName] + if !ok { + log.Fatalf("Unable to find helper method %q for %q", fullName, k) + } + if hm.httpMethod == "" { + log.Fatalf("Helper method %q for %q has empty httpMethod: %#v", fullName, k, hm) + } + v.httpMethod = hm.httpMethod + usedHelpers[fullName] = true + } + } + + return usedHelpers, endpointsByFilename +} + +type documentCacheReader interface { + UrlByMethodAndPath(string) (string, bool) +} + +type documentCacheWriter interface { + CacheDocFromInternet(urlWithFragmentID string) +} + +// documentCache implements documentCacheReader and documentCachWriter. +type documentCache struct { + apiDocs map[string]map[string][]*Endpoint // cached by URL, then mapped by web fragment identifier. + urlByMethodAndPath map[string]string +} + +func (dc *documentCache) UrlByMethodAndPath(methodAndPath string) (string, bool) { + url, ok := dc.urlByMethodAndPath[methodAndPath] + return url, ok +} + +func (dc *documentCache) CacheDocFromInternet(urlWithID string) { + if dc.apiDocs == nil { + dc.apiDocs = map[string]map[string][]*Endpoint{} // cached by URL, then mapped by web fragment identifier. + dc.urlByMethodAndPath = map[string]string{} + } + + url := getURL(urlWithID) + if _, ok := dc.apiDocs[url]; ok { + return // already cached + } + + // TODO: Enterprise URLs are currently causing problems - for example: + // GET https://developer.github.com/enterprise/v3/enterprise-admin/users/ + // returns StatusCode=404 + if strings.Contains(url, "enterprise") { + logf("Skipping troublesome Enterprise URL: %v", url) + return + } + + logf("GET %q ...", url) + resp, err := http.Get(url) + check("Unable to get URL: %v: %v", url, err) + if resp.StatusCode != http.StatusOK { + log.Fatalf("url %v - StatusCode=%v", url, resp.StatusCode) + } + + b, err := ioutil.ReadAll(resp.Body) + check("Unable to read body of URL: %v, %v", url, err) + check("Unable to close body of URL: %v, %v", url, resp.Body.Close()) + dc.apiDocs[url] = parseWebPageEndpoints(string(b)) + + // Now reverse-map the methods+paths to URLs. + for fragID, v := range dc.apiDocs[url] { + for _, endpoint := range v { + for _, path := range endpoint.urlFormats { + methodAndPath := fmt.Sprintf("%v %v", endpoint.httpMethod, path) + dc.urlByMethodAndPath[methodAndPath] = fmt.Sprintf("%v#%v", url, fragID) + logf("urlByMethodAndPath[%q] = %q", methodAndPath, dc.urlByMethodAndPath[methodAndPath]) + } + } + } +} + +// FileEdit represents an edit that needs to be performed on a file. +type FileEdit struct { + pos token.Position + fromText string + toText string +} + +func getURL(s string) string { + i := strings.Index(s, "http") + if i < 0 { + return "" + } + j := strings.Index(s, "#") + if j < i { + s = s[i:] + } else { + s = s[i:j] + } + if !strings.HasSuffix(s, "/") { // Prevent unnecessary redirects if possible. + s += "/" + } + return s +} + +// Service represents a go-github service. +type Service struct { + serviceName string +} + +// Endpoint represents an API endpoint in this repo. +type Endpoint struct { + endpointName string + filename string + serviceName string + urlFormats []string + httpMethod string + helperMethod string // If populated, httpMethod lives in helperMethod. + + enterpriseRefLines []*ast.Comment + stdRefLines []*ast.Comment + endpointComments []*ast.Comment +} + +// String helps with debugging by providing an easy-to-read summary of the endpoint. +func (e *Endpoint) String() string { + var b strings.Builder + b.WriteString(fmt.Sprintf(" filename: %v\n", e.filename)) + b.WriteString(fmt.Sprintf(" serviceName: %v\n", e.serviceName)) + b.WriteString(fmt.Sprintf(" endpointName: %v\n", e.endpointName)) + b.WriteString(fmt.Sprintf(" httpMethod: %v\n", e.httpMethod)) + if e.helperMethod != "" { + b.WriteString(fmt.Sprintf(" helperMethod: %v\n", e.helperMethod)) + } + for i := 0; i < len(e.urlFormats); i++ { + b.WriteString(fmt.Sprintf(" urlFormats[%v]: %v\n", i, e.urlFormats[i])) + } + for i := 0; i < len(e.enterpriseRefLines); i++ { + b.WriteString(fmt.Sprintf(" enterpriseRefLines[%v]: comment: %v\n", i, e.enterpriseRefLines[i].Text)) + } + for i := 0; i < len(e.stdRefLines); i++ { + b.WriteString(fmt.Sprintf(" stdRefLines[%v]: comment: %v\n", i, e.stdRefLines[i].Text)) + } + return b.String() +} + +func (ep *Endpoint) checkHttpMethodOverride(path string) { + lookupOverride := fmt.Sprintf("%v.%v: %v %v", ep.serviceName, ep.endpointName, ep.httpMethod, path) + logf("Looking up override for %q", lookupOverride) + if v, ok := methodOverrides[lookupOverride]; ok { + logf("overriding method for %v to %q", lookupOverride, v) + ep.httpMethod = v + return + } +} + +func processAST(filename string, f *ast.File, services servicesMap, endpoints endpointsMap, iter astFileIterator) error { + var errs []string + + for _, decl := range f.Decls { + switch decl := decl.(type) { + case *ast.FuncDecl: // Doc, Recv, Name, Type, Body + if decl.Recv == nil || len(decl.Recv.List) != 1 || decl.Name == nil || decl.Body == nil { + continue + } + + recv := decl.Recv.List[0] + se, ok := recv.Type.(*ast.StarExpr) // Star, X + if !ok || se.X == nil || len(recv.Names) != 1 { + if decl.Name.Name != "String" && decl.Name.Name != "Equal" && decl.Name.Name != "IsPullRequest" { + pos := iter.Position(recv.Pos()) + if id, ok := recv.Type.(*ast.Ident); ok { + pos = iter.Position(id.Pos()) + } + errs = append(errs, fmt.Sprintf("%v:%v:%v: method %v does not use a pointer receiver and needs fixing!", pos.Filename, pos.Line, pos.Column, decl.Name)) + } + continue + } + recvType, ok := se.X.(*ast.Ident) // NamePos, Name, Obj + if !ok { + return fmt.Errorf("unhandled se.X = %T", se.X) + } + serviceName := recvType.Name + if _, ok := services[serviceName]; !ok { + continue + } + endpointName := decl.Name.Name + fullName := fmt.Sprintf("%v.%v", serviceName, endpointName) + if methodBlacklist[fullName] { + logf("skipping %v", fullName) + continue + } + + receiverName := recv.Names[0].Name + + logf("ast.FuncDecl: %#v", *decl) // Doc, Recv, Name, Type, Body + logf("ast.FuncDecl.Name: %#v", *decl.Name) // NamePos, Name, Obj(nil) + // logf("ast.FuncDecl.Recv: %#v", *decl.Recv) // Opening, List, Closing + logf("ast.FuncDecl.Recv.List[0]: %#v", *recv) // Doc, Names, Type, Tag, Comment + // for i, name := range decl.Recv.List[0].Names { + // logf("recv.name[%v] = %v", i, name.Name) + // } + logf("recvType = %#v", recvType) + var enterpriseRefLines []*ast.Comment + var stdRefLines []*ast.Comment + var endpointComments []*ast.Comment + if decl.Doc != nil { + endpointComments = decl.Doc.List + for i, comment := range decl.Doc.List { + logf("doc.comment[%v] = %#v", i, *comment) + if strings.Contains(comment.Text, enterpriseURL) { + enterpriseRefLines = append(enterpriseRefLines, comment) + } else if strings.Contains(comment.Text, stdURL) { + stdRefLines = append(stdRefLines, comment) + } + } + logf("%v comment lines, %v enterprise URLs, %v standard URLs", len(decl.Doc.List), len(enterpriseRefLines), len(stdRefLines)) + } + + bd := &bodyData{receiverName: receiverName} + if err := bd.parseBody(decl.Body); err != nil { // Lbrace, List, Rbrace + return fmt.Errorf("parseBody: %v", err) + } + + ep := &Endpoint{ + endpointName: endpointName, + filename: filename, + serviceName: serviceName, + urlFormats: bd.urlFormats, + httpMethod: bd.httpMethod, + helperMethod: bd.helperMethod, + enterpriseRefLines: enterpriseRefLines, + stdRefLines: stdRefLines, + endpointComments: endpointComments, + } + // ep.checkHttpMethodOverride("") + endpoints[fullName] = ep + logf("endpoints[%q] = %#v", fullName, endpoints[fullName]) + if ep.httpMethod == "" && (ep.helperMethod == "" || len(ep.urlFormats) == 0) { + return fmt.Errorf("could not find body info: %#v", *ep) + } + case *ast.GenDecl: + default: + return fmt.Errorf("unhandled decl type: %T", decl) + } + } + + if len(errs) > 0 { + return errors.New(strings.Join(errs, "\n")) + } + + return nil +} + +// bodyData contains information found in a BlockStmt. +type bodyData struct { + receiverName string // receiver name of method to help identify helper methods. + httpMethod string + urlVarName string + urlFormats []string + assignments []lhsrhs + helperMethod string // If populated, httpMethod lives in helperMethod. +} + +func (b *bodyData) parseBody(body *ast.BlockStmt) error { + logf("body=%#v", *body) + + // Find the variable used for the format string, its one-or-more values, + // and the httpMethod used for the NewRequest. + for _, stmt := range body.List { + switch stmt := stmt.(type) { + case *ast.AssignStmt: + hm, uvn, hlp, asgn := processAssignStmt(b.receiverName, stmt) + if b.httpMethod != "" && hm != "" && b.httpMethod != hm { + return fmt.Errorf("found two httpMethod values: %q and %q", b.httpMethod, hm) + } + if hm != "" { + b.httpMethod = hm + // logf("parseBody: httpMethod=%v", b.httpMethod) + } + if hlp != "" { + b.helperMethod = hlp + } + b.assignments = append(b.assignments, asgn...) + // logf("assignments=%#v", b.assignments) + if b.urlVarName == "" && uvn != "" { + b.urlVarName = uvn + // logf("parseBody: urlVarName=%v", b.urlVarName) + // By the time the urlVarName is found, all assignments should + // have already taken place so that we can find the correct + // ones and determine the urlFormats. + for _, lr := range b.assignments { + if lr.lhs == b.urlVarName { + b.urlFormats = append(b.urlFormats, lr.rhs) + logf("found urlFormat: %v", lr.rhs) + } + } + } + case *ast.DeclStmt: + logf("*ast.DeclStmt: %#v", *stmt) + case *ast.DeferStmt: + logf("*ast.DeferStmt: %#v", *stmt) + case *ast.ExprStmt: + logf("*ast.ExprStmt: %#v", *stmt) + case *ast.IfStmt: + if err := b.parseIf(stmt); err != nil { + return err + } + case *ast.RangeStmt: + logf("*ast.RangeStmt: %#v", *stmt) + case *ast.ReturnStmt: // Return Results + logf("*ast.ReturnStmt: %#v", *stmt) + if len(stmt.Results) > 0 { + ce, ok := stmt.Results[0].(*ast.CallExpr) + if ok { + recv, funcName, args := processCallExpr(ce) + logf("return CallExpr: recv=%q, funcName=%q, args=%#v", recv, funcName, args) + // If the httpMethod has not been found at this point, but + // this method is calling a helper function, then see if + // any of its arguments match a previous assignment, then + // record the urlFormat and remember the helper method. + if b.httpMethod == "" && len(args) > 1 && recv == b.receiverName { + if args[0] != "ctx" { + return fmt.Errorf("expected helper function to get ctx as first arg: %#v, %#v", args, *b) + } + if len(b.assignments) == 0 && len(b.urlFormats) == 0 { + b.urlFormats = append(b.urlFormats, strings.Trim(args[1], `"`)) + b.helperMethod = funcName + logf("found urlFormat: %v and helper method: %v", b.urlFormats[0], b.helperMethod) + } else { + for _, lr := range b.assignments { + if lr.lhs == args[1] { // Multiple matches are possible. Loop over all assignments. + b.urlVarName = args[1] + b.urlFormats = append(b.urlFormats, lr.rhs) + b.helperMethod = funcName + logf("found urlFormat: %v and helper method: %v", lr.rhs, b.helperMethod) + } + } + } + } + } + } + case *ast.SwitchStmt: + logf("*ast.SwitchStmt: %#v", *stmt) + default: + return fmt.Errorf("unhandled stmt type: %T", stmt) + } + } + logf("parseBody: assignments=%#v", b.assignments) + + return nil +} + +func (b *bodyData) parseIf(stmt *ast.IfStmt) error { + logf("*ast.IfStmt: %#v", *stmt) + if err := b.parseBody(stmt.Body); err != nil { + return err + } + logf("if body: b=%#v", *b) + if stmt.Else != nil { + switch els := stmt.Else.(type) { + case *ast.BlockStmt: + if err := b.parseBody(els); err != nil { + return err + } + logf("if else: b=%#v", *b) + case *ast.IfStmt: + if err := b.parseIf(els); err != nil { + return err + } + default: + return fmt.Errorf("unhandled else stmt type %T", els) + } + } + + return nil +} + +// lhsrhs represents an assignment with a variable name on the left +// and a string on the right - used to find the URL format string. +type lhsrhs struct { + lhs string + rhs string +} + +func processAssignStmt(receiverName string, stmt *ast.AssignStmt) (httpMethod, urlVarName, helperMethod string, assignments []lhsrhs) { + logf("*ast.AssignStmt: %#v", *stmt) // Lhs, TokPos, Tok, Rhs + var lhs []string + for _, expr := range stmt.Lhs { + switch expr := expr.(type) { + case *ast.Ident: // NamePos, Name, Obj + logf("processAssignStmt: *ast.Ident: %#v", expr) + lhs = append(lhs, expr.Name) + case *ast.SelectorExpr: // X, Sel + logf("processAssignStmt: *ast.SelectorExpr: %#v", expr) + default: + log.Fatalf("unhandled AssignStmt Lhs type: %T", expr) + } + } + + for i, expr := range stmt.Rhs { + switch expr := expr.(type) { + case *ast.BasicLit: // ValuePos, Kind, Value + v := strings.Trim(expr.Value, `"`) + if !strings.HasPrefix(v, "?") { // Hack to remove "?recursive=1" + assignments = append(assignments, lhsrhs{lhs: lhs[i], rhs: v}) + } + case *ast.BinaryExpr: + logf("processAssignStmt: *ast.BinaryExpr: %#v", *expr) + case *ast.CallExpr: // Fun, Lparen, Args, Ellipsis, Rparen + recv, funcName, args := processCallExpr(expr) + logf("processAssignStmt: CallExpr: recv=%q, funcName=%q, args=%#v", recv, funcName, args) + switch funcName { + case "addOptions": + if v := strings.Trim(args[0], `"`); v != args[0] { + assignments = append(assignments, lhsrhs{lhs: lhs[i], rhs: v}) + urlVarName = lhs[i] + } else { + urlVarName = args[0] + } + case "Sprintf": + assignments = append(assignments, lhsrhs{lhs: lhs[i], rhs: strings.Trim(args[0], `"`)}) + case "NewRequest": + httpMethod = strings.Trim(args[0], `"`) + urlVarName = args[1] + case "NewUploadRequest": + httpMethod = "POST" + urlVarName = args[0] + } + if recv == receiverName && len(args) > 1 && args[0] == "ctx" { // This might be a helper method. + fullName := fmt.Sprintf("%v.%v", recv, funcName) + logf("checking for override: fullName=%v", fullName) + if fn, ok := helperOverrides[fullName]; ok { + logf("found helperOverride for %v", fullName) + hm, url := fn(strings.Trim(args[1], `"`)) + httpMethod = hm + urlVarName = "u" // arbitrary + assignments = []lhsrhs{{lhs: urlVarName, rhs: url}} + } else { + urlVarName = args[1] // For this to work correctly, the URL must be the second arg to the helper method! + helperMethod = funcName + logf("found possible helper method: funcName=%v, urlVarName=%v", funcName, urlVarName) + } + } + case *ast.CompositeLit: // Type, Lbrace, Elts, Rbrace, Incomplete + logf("processAssignStmt: *ast.CompositeLit: %#v", *expr) + case *ast.FuncLit: + logf("processAssignStmt: *ast.FuncLit: %#v", *expr) + case *ast.SelectorExpr: + logf("processAssignStmt: *ast.SelectorExpr: %#v", *expr) + case *ast.UnaryExpr: // OpPos, Op, X + logf("processAssignStmt: *ast.UnaryExpr: %#v", *expr) + default: + log.Fatalf("unhandled AssignStmt Rhs type: %T", expr) + } + } + logf("urlVarName=%v, assignments=%#v", urlVarName, assignments) + + return httpMethod, urlVarName, helperMethod, assignments +} + +func processCallExpr(expr *ast.CallExpr) (recv, funcName string, args []string) { + logf("*ast.CallExpr: %#v", *expr) + + for _, arg := range expr.Args { + switch arg := arg.(type) { + case *ast.ArrayType: + logf("processCallExpr: *ast.ArrayType: %#v", arg) + case *ast.BasicLit: // ValuePos, Kind, Value + args = append(args, arg.Value) // Do not trim quotes here so as to identify it later as a string literal. + case *ast.CallExpr: // Fun, Lparen, Args, Ellipsis, Rparen + logf("processCallExpr: *ast.CallExpr: %#v", arg) + r, fn, as := processCallExpr(arg) + if r == "fmt" && fn == "Sprintf" && len(as) > 0 { // Special case - return format string. + args = append(args, as[0]) + } + case *ast.Ident: // NamePos, Name, Obj + args = append(args, arg.Name) + case *ast.MapType: + logf("processCallExpr: *ast.MapType: %#v", arg) + case *ast.SelectorExpr: // X, Sel + logf("processCallExpr: *ast.SelectorExpr: %#v", arg) + x, ok := arg.X.(*ast.Ident) + if ok { // special case + switch name := fmt.Sprintf("%v.%v", x.Name, arg.Sel.Name); name { + case "http.MethodGet": + args = append(args, http.MethodGet) + case "http.MethodHead": + args = append(args, http.MethodHead) + case "http.MethodPost": + args = append(args, http.MethodPost) + case "http.MethodPut": + args = append(args, http.MethodPut) + case "http.MethodPatch": + args = append(args, http.MethodPatch) + case "http.MethodDelete": + args = append(args, http.MethodDelete) + case "http.MethodConnect": + args = append(args, http.MethodConnect) + case "http.MethodOptions": + args = append(args, http.MethodOptions) + case "http.MethodTrace": + args = append(args, http.MethodTrace) + default: + args = append(args, name) + } + } + case *ast.StarExpr: + logf("processCallExpr: *ast.StarExpr: %#v", arg) + case *ast.StructType: + logf("processCallExpr: *ast.StructType: %#v", arg) + case *ast.UnaryExpr: // OpPos, Op, X + switch x := arg.X.(type) { + case *ast.Ident: + args = append(args, x.Name) + case *ast.CompositeLit: // Type, Lbrace, Elts, Rbrace, Incomplete + logf("processCallExpr: *ast.CompositeLit: %#v", x) + default: + log.Fatalf("processCallExpr: unhandled UnaryExpr.X arg type: %T", arg.X) + } + default: + log.Fatalf("processCallExpr: unhandled arg type: %T", arg) + } + } + + switch fun := expr.Fun.(type) { + case *ast.Ident: // NamePos, Name, Obj + funcName = fun.Name + case *ast.SelectorExpr: // X, Sel + funcName = fun.Sel.Name + switch x := fun.X.(type) { + case *ast.Ident: // NamePos, Name, Obj + logf("processCallExpr: X recv *ast.Ident=%#v", x) + recv = x.Name + case *ast.ParenExpr: + logf("processCallExpr: X recv *ast.ParenExpr: %#v", x) + case *ast.SelectorExpr: // X, Sel + logf("processCallExpr: X recv *ast.SelectorExpr: %#v", x.Sel) + recv = x.Sel.Name + default: + log.Fatalf("processCallExpr: unhandled X receiver type: %T", x) + } + default: + log.Fatalf("processCallExpr: unhandled Fun: %T", expr.Fun) + } + + return recv, funcName, args +} + +// findClientServices finds all go-github services from the Client struct. +func findClientServices(filename string, f *ast.File, services servicesMap) error { + for _, decl := range f.Decls { + switch decl := decl.(type) { + case *ast.GenDecl: + if decl.Tok != token.TYPE || len(decl.Specs) != 1 { + continue + } + ts, ok := decl.Specs[0].(*ast.TypeSpec) + if !ok || decl.Doc == nil || ts.Name == nil || ts.Type == nil || ts.Name.Name != "Client" { + continue + } + st, ok := ts.Type.(*ast.StructType) + if !ok || st.Fields == nil || len(st.Fields.List) == 0 { + continue + } + + for _, field := range st.Fields.List { + se, ok := field.Type.(*ast.StarExpr) + if !ok || se.X == nil || len(field.Names) != 1 { + continue + } + id, ok := se.X.(*ast.Ident) + if !ok { + continue + } + name := id.Name + if !strings.HasSuffix(name, "Service") { + continue + } + + services[name] = &Service{serviceName: name} + } + + return nil // Found all services in Client struct. + } + } + + return fmt.Errorf("unable to find Client struct in github.go") +} + +func check(fmtStr string, args ...interface{}) { + if err := args[len(args)-1]; err != nil { + log.Fatalf(fmtStr, args...) + } +} + +// parseWebPageEndpoints returns endpoint information, mapped by +// web page fragment identifier. +func parseWebPageEndpoints(buf string) map[string][]*Endpoint { + result := map[string][]*Endpoint{} + + // The GitHub v3 API web pages do not appear to be auto-generated + // and therefore, the XML decoder is too strict to reliably parse them. + // Here is a tiny example where the XML decoder completely fails + // due to mal-formed HTML: + // + // + // + // ... + // + + parts := strings.Split(buf, "") + var lastFragmentID string + for _, part := range parts { + for _, method := range httpMethods { + if strings.HasPrefix(part, method) { + eol := strings.Index(part, "\n") + if eol < 0 { + eol = len(part) + } + if v := strings.Index(part, "<"); v > len(method) && v < eol { + eol = v + } + if v := strings.Index(part, "{"); v > len(method) && v < eol { + eol = v + } + path := strings.TrimSpace(part[len(method):eol]) + if strings.HasPrefix(path, ":server") { // Hack to remove :server + path = strings.TrimPrefix(path, ":server") + } + path = paramRE.ReplaceAllString(path, "%v") + // strip leading garbage + if i := strings.Index(path, "/"); i >= 0 { + path = path[i+1:] + } + path = strings.TrimSuffix(path, ".") + logf("Found %v %v", method, path) + result[lastFragmentID] = append(result[lastFragmentID], &Endpoint{ + urlFormats: []string{path}, + httpMethod: method, + }) + } + } + + if i := strings.LastIndex(part, "= 0 { + lastFragmentID = b[:i] + logf("Found lastFragmentID: %v", lastFragmentID) + } + } + } + + return result +} + +var httpMethods = []string{ + "GET", + "HEAD", + "POST", + "PUT", + "PATCH", + "DELETE", + "CONNECT", + "OPTIONS", + "TRACE", +} diff --git a/update-urls/main_test.go b/update-urls/main_test.go new file mode 100644 index 00000000000..e77411a022c --- /dev/null +++ b/update-urls/main_test.go @@ -0,0 +1,563 @@ +// Copyright 2020 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 main + +import ( + "fmt" + "go/parser" + "go/token" + "os" + "reflect" + "regexp" + "strings" + "testing" + + "github.com/pmezard/go-difflib/difflib" +) + +type pipelineSetup struct { + // Fields filled in by the unit test: + baseURL string + endpointsFromWebsite endpointsByFragmentID + filename string + serviceName string + originalGoSource string + wantGoSource string + wantNumEndpoints int + + // Fields filled in by setup: + docCache *fakeDocCache + fileRewriter *fakeFileRewriter + iter *fakeAstFileIterator + services servicesMap + wantFailure bool +} + +func (ps *pipelineSetup) setup(t *testing.T, stripURLs, destroyReceiverPointers bool) *pipelineSetup { + t.Helper() + + if stripURLs { + // For every GitHub API doc URL, remove it from the original source, + // and alternate between stripping the previous blank comment line and not. + for removeBlank := false; true; removeBlank = !removeBlank { + var changes bool + if removeBlank { + ps.originalGoSource, changes = removeNextURLAndOptionalBlank(ps.originalGoSource) + } else { + ps.originalGoSource, changes = removeNextURLLineOnly(ps.originalGoSource) + } + if !changes { + break + } + } + // log.Printf("Modified Go Source:\n%v", ps.originalGoSource) + } + + if destroyReceiverPointers { + from := fmt.Sprintf(" *%v) ", ps.serviceName) + to := fmt.Sprintf(" %v) ", ps.serviceName) + ps.originalGoSource = strings.ReplaceAll(ps.originalGoSource, from, to) + ps.wantFailure = true // receiver pointers must be fixed before running. + } + + ps.docCache = &fakeDocCache{ + t: t, + baseURL: ps.baseURL, + endpoints: ps.endpointsFromWebsite, + } + fset := token.NewFileSet() + ps.fileRewriter = &fakeFileRewriter{fset: fset, in: ps.originalGoSource} + ps.services = servicesMap{ps.serviceName: &Service{serviceName: ps.serviceName}} + astFile, err := parser.ParseFile(fset, ps.filename, ps.originalGoSource, parser.ParseComments) + if err != nil { + t.Fatalf("ParseFile: %v", err) + } + ps.iter = &fakeAstFileIterator{ + fset: fset, + orig: &filenameAstFilePair{ + filename: ps.filename, + astFile: astFile, + }, + } + + return ps +} + +func (ps *pipelineSetup) validate(t *testing.T) { + t.Helper() + + // Call pipeline + endpoints, err := findAllServiceEndpoints(ps.iter, ps.services) + if ps.wantFailure { + if err != nil { + // test successful - receivers must be pointers first + return + } + t.Fatalf("Expected non-pointer receivers to fail parsing, but no error was raised") + } + if err != nil { + t.Fatalf("Fail detected but not expected: %v", err) + } + + // log.Printf("endpoints=%#v (%v)", endpoints, len(endpoints)) + if len(endpoints) != ps.wantNumEndpoints { + t.Errorf("got %v endpoints, want %v", len(endpoints), ps.wantNumEndpoints) + } + usedHelpers, endpointsByFilename := resolveHelpersAndCacheDocs(endpoints, ps.docCache) + // log.Printf("endpointsByFilename=%#v (%v)", endpointsByFilename, len(endpointsByFilename[ps.filename])) + if len(endpointsByFilename[ps.filename]) != ps.wantNumEndpoints { + t.Errorf("got %v endpointsByFilename, want %v", len(endpointsByFilename[ps.filename]), ps.wantNumEndpoints) + } + validateRewriteURLs(usedHelpers, endpointsByFilename, ps.docCache, ps.fileRewriter) + + if ps.fileRewriter.out == "" { + t.Fatalf("No modifications were made to the file") + } + + if ps.fileRewriter.out != ps.wantGoSource { + diff := difflib.ContextDiff{ + A: difflib.SplitLines(ps.fileRewriter.out), + B: difflib.SplitLines(ps.wantGoSource), + FromFile: "got", + ToFile: "want", + Context: 1, + Eol: "\n", + } + result, _ := difflib.GetContextDiffString(diff) + t.Errorf(strings.Replace(result, "\t", " ", -1)) + } +} + +var ( + urlWithBlankCommentRE = regexp.MustCompile(`(//\n)?// GitHub API docs: [^\n]+\n`) + urlLineOnlyRE = regexp.MustCompile(`// GitHub API docs: [^\n]+\n`) +) + +func removeNextURLAndOptionalBlank(s string) (string, bool) { + parts := urlWithBlankCommentRE.Split(s, 2) + if len(parts) == 1 { + return parts[0], false + } + return parts[0] + parts[1], true +} + +func TestRemoveNextURLAndOptionalBlank(t *testing.T) { + tests := []struct { + name string + s string + want string + changes bool + }{ + {name: "empty string"}, + {name: "no URLs", s: "// line 1\n//\n// line 3", want: "// line 1\n//\n// line 3"}, + { + name: "URL without prior blank comment", + s: "// line 1\n// GitHub API docs: yeah\nfunc MyFunc() {\n", + want: "// line 1\nfunc MyFunc() {\n", + changes: true, + }, + { + name: "URL with prior blank comment", + s: "// line 1\n//\n// GitHub API docs: yeah\nfunc MyFunc() {\n", + want: "// line 1\nfunc MyFunc() {\n", + changes: true, + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("test #%v: %v", i, tt.name), func(t *testing.T) { + got, changes := removeNextURLAndOptionalBlank(tt.s) + if got != tt.want { + t.Errorf("got = %v, want %v", got, tt.want) + } + if changes != tt.changes { + t.Errorf("got changes = %v, want %v", changes, tt.changes) + } + }) + } +} + +func removeNextURLLineOnly(s string) (string, bool) { + parts := urlLineOnlyRE.Split(s, 2) + if len(parts) == 1 { + return parts[0], false + } + return parts[0] + parts[1], true +} + +func TestRemoveNextURLLineOnly(t *testing.T) { + tests := []struct { + name string + s string + want string + changes bool + }{ + {name: "empty string"}, + {name: "no URLs", s: "// line 1\n//\n// line 3", want: "// line 1\n//\n// line 3"}, + { + name: "URL without prior blank comment", + s: "// line 1\n// GitHub API docs: yeah\nfunc MyFunc() {\n", + want: "// line 1\nfunc MyFunc() {\n", + changes: true, + }, + { + name: "URL with prior blank comment", + s: "// line 1\n//\n// GitHub API docs: yeah\nfunc MyFunc() {\n", + want: "// line 1\n//\nfunc MyFunc() {\n", + changes: true, + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("test #%v: %v", i, tt.name), func(t *testing.T) { + got, changes := removeNextURLLineOnly(tt.s) + if got != tt.want { + t.Errorf("got = %v, want %v", got, tt.want) + } + if changes != tt.changes { + t.Errorf("got changes = %v, want %v", changes, tt.changes) + } + }) + } +} + +type endpointsByFragmentID map[string][]*Endpoint + +// fakeDocCache implements documentCacheReader and documentCacheWriter. +type fakeDocCache struct { + t *testing.T + baseURL string + endpoints endpointsByFragmentID +} + +func (f *fakeDocCache) UrlByMethodAndPath(methodAndPath string) (string, bool) { + for fragmentID, endpoints := range f.endpoints { + for _, endpoint := range endpoints { + for _, urlFormat := range endpoint.urlFormats { + key := fmt.Sprintf("%v %v", endpoint.httpMethod, urlFormat) + if key == methodAndPath { + url := fmt.Sprintf("%v#%v", f.baseURL, fragmentID) + // log.Printf("UrlByMethodAndPath(%q) = (%q, true)", methodAndPath, url) + return url, true + } + } + } + } + f.t.Fatalf("fakeDocCache.UrlByMethodAndPath: unable to find method %v", methodAndPath) + return "", false +} + +func (f *fakeDocCache) CacheDocFromInternet(url string) {} // no-op + +// fakeFileRewriter implements FileRewriter. +type fakeFileRewriter struct { + fset *token.FileSet + in string + out string +} + +func (f *fakeFileRewriter) Position(pos token.Pos) token.Position { + return f.fset.Position(pos) +} + +func (f *fakeFileRewriter) ReadFile(filename string) ([]byte, error) { + return []byte(f.in), nil +} + +func (f *fakeFileRewriter) WriteFile(filename string, buf []byte, mode os.FileMode) error { + f.out = string(buf) + return nil +} + +// fakeAstFileIterator implements astFileIterator. +type fakeAstFileIterator struct { + orig, next *filenameAstFilePair + fset *token.FileSet +} + +func (f *fakeAstFileIterator) Position(pos token.Pos) token.Position { return f.fset.Position(pos) } +func (f *fakeAstFileIterator) Reset() { f.next = f.orig } +func (f *fakeAstFileIterator) Next() *filenameAstFilePair { + v := f.next + f.next = nil + return v +} + +func TestSortAndMergeFileEdits(t *testing.T) { + tests := []struct { + name string + fileEdits []*FileEdit + want []*FileEdit + }{ + {name: "no edits"}, + { + name: "one edit", + fileEdits: []*FileEdit{ + {toText: "one edit"}, + }, + want: []*FileEdit{ + {toText: "one edit"}, + }, + }, + { + name: "two inserts at same offset - no extra blank comment", + fileEdits: []*FileEdit{ + {pos: token.Position{Offset: 2}, fromText: "", toText: "\n// one insert"}, + {pos: token.Position{Offset: 2}, fromText: "", toText: "\n// second insert"}, + }, + want: []*FileEdit{ + {pos: token.Position{Offset: 2}, toText: "\n// one insert\n// second insert"}, + }, + }, + { + name: "two inserts at same offset - strip extra blank comment", + fileEdits: []*FileEdit{ + {pos: token.Position{Offset: 2}, fromText: "", toText: "\n//\n// one insert"}, + {pos: token.Position{Offset: 2}, fromText: "", toText: "\n//\n// second insert"}, + }, + want: []*FileEdit{ + {pos: token.Position{Offset: 2}, toText: "\n//\n// one insert\n// second insert"}, + }, + }, + { + name: "two non-overlapping edits, low offset to high", + fileEdits: []*FileEdit{ + {fromText: ".", pos: token.Position{Offset: 0}, toText: "edit one"}, + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit two"}, + }, + want: []*FileEdit{ + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit two"}, + {fromText: ".", pos: token.Position{Offset: 0}, toText: "edit one"}, + }, + }, + { + name: "two non-overlapping edits, high offset to low", + fileEdits: []*FileEdit{ + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit two"}, + {fromText: ".", pos: token.Position{Offset: 0}, toText: "edit one"}, + }, + want: []*FileEdit{ + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit two"}, + {fromText: ".", pos: token.Position{Offset: 0}, toText: "edit one"}, + }, + }, + { + name: "two overlapping edits, text low to high", + fileEdits: []*FileEdit{ + {fromText: ".", toText: "edit 0"}, + {fromText: ".", toText: "edit 1"}, + }, + want: []*FileEdit{ + {fromText: ".", toText: "edit 0\nedit 1"}, + }, + }, + { + name: "two overlapping edits, text high to low", + fileEdits: []*FileEdit{ + {fromText: ".", toText: "edit 1"}, + {fromText: ".", toText: "edit 0"}, + }, + want: []*FileEdit{ + {fromText: ".", toText: "edit 0\nedit 1"}, + }, + }, + { + name: "dup, non-dup", + fileEdits: []*FileEdit{ + {fromText: ".", toText: "edit 1"}, + {fromText: ".", toText: "edit 0"}, + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit 2"}, + }, + want: []*FileEdit{ + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit 2"}, + {fromText: ".", toText: "edit 0\nedit 1"}, + }, + }, + { + name: "non-dup, dup", + fileEdits: []*FileEdit{ + {fromText: ".", toText: "edit 2"}, + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit 1"}, + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit 0"}, + }, + want: []*FileEdit{ + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit 0\nedit 1"}, + {fromText: ".", toText: "edit 2"}, + }, + }, + { + name: "dup, non-dup, dup", + fileEdits: []*FileEdit{ + {fromText: ".", toText: "edit 1"}, + {fromText: ".", toText: "edit 0"}, + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit 2"}, + {fromText: ".", pos: token.Position{Offset: 200}, toText: "edit 4"}, + {fromText: ".", pos: token.Position{Offset: 200}, toText: "edit 3"}, + }, + want: []*FileEdit{ + {fromText: ".", pos: token.Position{Offset: 200}, toText: "edit 3\nedit 4"}, + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit 2"}, + {fromText: ".", toText: "edit 0\nedit 1"}, + }, + }, + { + name: "non-dup, dup, non-dup", + fileEdits: []*FileEdit{ + {fromText: ".", toText: "edit 2"}, + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit 1"}, + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit 0"}, + {fromText: ".", pos: token.Position{Offset: 200}, toText: "edit 3"}, + }, + want: []*FileEdit{ + {fromText: ".", pos: token.Position{Offset: 200}, toText: "edit 3"}, + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit 0\nedit 1"}, + {fromText: ".", toText: "edit 2"}, + }, + }, + { + name: "triplet, non-dup", + fileEdits: []*FileEdit{ + {fromText: ".", toText: "edit 1"}, + {fromText: ".", toText: "edit 0"}, + {fromText: ".", toText: "edit 2"}, + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit 3"}, + }, + want: []*FileEdit{ + {fromText: ".", pos: token.Position{Offset: 100}, toText: "edit 3"}, + {fromText: ".", toText: "edit 0\nedit 1\nedit 2"}, + }, + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("test #%v: %v", i, tt.name), func(t *testing.T) { + got := sortAndMergeFileEdits(tt.fileEdits) + + if len(got) != len(tt.want) { + t.Errorf("len(got) = %v, len(want) = %v", len(got), len(tt.want)) + } + for i := 0; i < len(got); i++ { + var wantFileEdit *FileEdit + if i < len(tt.want) { + wantFileEdit = tt.want[i] + } + if !reflect.DeepEqual(got[i], wantFileEdit) { + t.Errorf("got[%v] =\n%#v\nwant[%v]:\n%#v", i, got[i], i, wantFileEdit) + } + } + }) + } +} + +func TestPerformBufferEdits(t *testing.T) { + tests := []struct { + name string + fileEdits []*FileEdit + s string + want string + }{ + {name: "no edits", s: "my\nshort\nfile\n", want: "my\nshort\nfile\n"}, + { + name: "one edit", + fileEdits: []*FileEdit{ + {pos: token.Position{Offset: 3}, fromText: "short", toText: "one edit"}, + }, + s: "my\nshort\nfile\n", + want: "my\none edit\nfile\n", + }, + { + name: "one insert", + fileEdits: []*FileEdit{ + {pos: token.Position{Offset: 2}, fromText: "", toText: "\none insert"}, + }, + s: "my\nshort\nfile\n", + want: "my\none insert\nshort\nfile\n", + }, + { + name: "two inserts at same offset", + fileEdits: []*FileEdit{ + {pos: token.Position{Offset: 2}, fromText: "", toText: "\none insert"}, + {pos: token.Position{Offset: 2}, fromText: "", toText: "\nsecond insert"}, + }, + s: "my\nshort\nfile\n", + want: "my\none insert\nsecond insert\nshort\nfile\n", + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("test #%v: %v", i, tt.name), func(t *testing.T) { + b := performBufferEdits([]byte(tt.s), tt.fileEdits) + got := string(b) + + if len(got) != len(tt.want) { + t.Errorf("len(got) = %v, len(want) = %v", len(got), len(tt.want)) + } + if got != tt.want { + t.Errorf("got = %v, want = %v", got, tt.want) + } + }) + } +} + +func TestGitURL(t *testing.T) { + tests := []struct { + name string + s string + want string + }{ + {name: "empty string"}, + {name: "non-http", s: "howdy"}, + { + name: "normal URL, no slash", + s: "https://developer.github.com/v3/activity/events", + want: "https://developer.github.com/v3/activity/events/", + }, + { + name: "normal URL, with slash", + s: "https://developer.github.com/v3/activity/events/", + want: "https://developer.github.com/v3/activity/events/", + }, + { + name: "normal URL, with fragment identifier", + s: "https://developer.github.com/v3/activity/events/#list-public-events", + want: "https://developer.github.com/v3/activity/events/", + }, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("test #%v: %v", i, tt.name), func(t *testing.T) { + got := getURL(tt.s) + if got != tt.want { + t.Errorf("getURL = %v ; want %v", got, tt.want) + } + }) + } +} + +func testWebPageHelper(t *testing.T, got, want map[string][]*Endpoint) { + t.Helper() + + for k := range got { + w, ok := want[k] + if len(got[k]) != len(w) { + t.Errorf("len(got[%q]) = %v, len(want[%q]) = %v", k, len(got[k]), k, len(w)) + } + for i := 0; i < len(got[k]); i++ { + var wantEndpoint *Endpoint + if ok && i < len(w) { + wantEndpoint = w[i] + } + if !reflect.DeepEqual(got[k][i], wantEndpoint) { + t.Errorf("got[%q][%v] =\n%#v\nwant[%q][%v]:\n%#v", k, i, got[k][i], k, i, wantEndpoint) + } + } + } + for k := range want { + if _, ok := got[k]; !ok { + t.Errorf("got[%q] = nil\nwant[%q]:\n%#v", k, k, want[k]) + } + } +} diff --git a/update-urls/reactions_test.go b/update-urls/reactions_test.go new file mode 100644 index 00000000000..1ef696f7799 --- /dev/null +++ b/update-urls/reactions_test.go @@ -0,0 +1,4143 @@ +// Copyright 2020 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 main + +import ( + "testing" +) + +func newReactionsPipeline() *pipelineSetup { + return &pipelineSetup{ + baseURL: "https://developer.github.com/v3/reactions/", + endpointsFromWebsite: reactionsWant, + filename: "reactions.go", + serviceName: "ReactionsService", + originalGoSource: reactionsGoFileOriginal, + wantGoSource: reactionsGoFileWant, + wantNumEndpoints: 25, + } +} + +func TestPipeline_Reactions(t *testing.T) { + ps := newReactionsPipeline() + ps.setup(t, false, false) + ps.validate(t) +} + +func TestPipeline_Reactions_FirstStripAllURLs(t *testing.T) { + ps := newReactionsPipeline() + ps.setup(t, true, false) + ps.validate(t) +} + +func TestPipeline_Reactions_FirstDestroyReceivers(t *testing.T) { + ps := newReactionsPipeline() + ps.setup(t, false, true) + ps.validate(t) +} + +func TestPipeline_Reactions_FirstStripAllURLsAndDestroyReceivers(t *testing.T) { + ps := newReactionsPipeline() + ps.setup(t, true, true) + ps.validate(t) +} + +func TestParseWebPageEndpoints_Reactions(t *testing.T) { + got, want := parseWebPageEndpoints(reactionsTestWebPage), reactionsWant + testWebPageHelper(t, got, want) +} + +var reactionsWant = endpointsByFragmentID{ + "list-reactions-for-a-commit-comment": []*Endpoint{{urlFormats: []string{"repos/%v/%v/comments/%v/reactions"}, httpMethod: "GET"}}, + + "delete-a-commit-comment-reaction": []*Endpoint{ + {urlFormats: []string{"repositories/%v/comments/%v/reactions/%v"}, httpMethod: "DELETE"}, + {urlFormats: []string{"repos/%v/%v/comments/%v/reactions/%v"}, httpMethod: "DELETE"}, + }, + + "create-reaction-for-an-issue": []*Endpoint{{urlFormats: []string{"repos/%v/%v/issues/%v/reactions"}, httpMethod: "POST"}}, + + "delete-an-issue-reaction": []*Endpoint{ + {urlFormats: []string{"repositories/%v/issues/%v/reactions/%v"}, httpMethod: "DELETE"}, + {urlFormats: []string{"repos/%v/%v/issues/%v/reactions/%v"}, httpMethod: "DELETE"}, + }, + + "create-reaction-for-a-pull-request-review-comment": []*Endpoint{{urlFormats: []string{"repos/%v/%v/pulls/comments/%v/reactions"}, httpMethod: "POST"}}, + + "list-reactions-for-a-team-discussion": []*Endpoint{ + {urlFormats: []string{"organizations/%v/team/%v/discussions/%v/reactions"}, httpMethod: "GET"}, + {urlFormats: []string{"orgs/%v/teams/%v/discussions/%v/reactions"}, httpMethod: "GET"}, + }, + + "delete-a-reaction-legacy": []*Endpoint{{urlFormats: []string{"reactions/%v"}, httpMethod: "DELETE"}}, + + "list-reactions-for-a-team-discussion-comment-legacy": []*Endpoint{{urlFormats: []string{"teams/%v/discussions/%v/comments/%v/reactions"}, httpMethod: "GET"}}, + + "delete-an-issue-comment-reaction": []*Endpoint{ + {urlFormats: []string{"repositories/%v/issues/comments/%v/reactions/%v"}, httpMethod: "DELETE"}, + {urlFormats: []string{"repos/%v/%v/issues/comments/%v/reactions/%v"}, httpMethod: "DELETE"}, + }, + + "list-reactions-for-a-pull-request-review-comment": []*Endpoint{{urlFormats: []string{"repos/%v/%v/pulls/comments/%v/reactions"}, httpMethod: "GET"}}, + + "create-reaction-for-a-team-discussion-legacy": []*Endpoint{{urlFormats: []string{"teams/%v/discussions/%v/reactions"}, httpMethod: "POST"}}, + + "create-reaction-for-a-team-discussion-comment-legacy": []*Endpoint{{urlFormats: []string{"teams/%v/discussions/%v/comments/%v/reactions"}, httpMethod: "POST"}}, + + "create-reaction-for-a-commit-comment": []*Endpoint{{urlFormats: []string{"repos/%v/%v/comments/%v/reactions"}, httpMethod: "POST"}}, + + "list-reactions-for-an-issue": []*Endpoint{{urlFormats: []string{"repos/%v/%v/issues/%v/reactions"}, httpMethod: "GET"}}, + + "create-reaction-for-an-issue-comment": []*Endpoint{{urlFormats: []string{"repos/%v/%v/issues/comments/%v/reactions"}, httpMethod: "POST"}}, + + "create-reaction-for-a-team-discussion": []*Endpoint{ + {urlFormats: []string{"organizations/%v/team/%v/discussions/%v/reactions"}, httpMethod: "POST"}, + {urlFormats: []string{"orgs/%v/teams/%v/discussions/%v/reactions"}, httpMethod: "POST"}, + }, + + "delete-team-discussion-reaction": []*Endpoint{ + {urlFormats: []string{"organizations/%v/team/%v/discussions/%v/reactions/%v"}, httpMethod: "DELETE"}, + {urlFormats: []string{"orgs/%v/teams/%v/discussions/%v/reactions/%v"}, httpMethod: "DELETE"}, + }, + + "create-reaction-for-a-team-discussion-comment": []*Endpoint{ + {urlFormats: []string{"organizations/%v/team/%v/discussions/%v/comments/%v/reactions"}, httpMethod: "POST"}, + {urlFormats: []string{"orgs/%v/teams/%v/discussions/%v/comments/%v/reactions"}, httpMethod: "POST"}, + }, + + "list-reactions-for-an-issue-comment": []*Endpoint{{urlFormats: []string{"repos/%v/%v/issues/comments/%v/reactions"}, httpMethod: "GET"}}, + + "delete-a-pull-request-comment-reaction": []*Endpoint{ + {urlFormats: []string{"repositories/%v/pulls/comments/%v/reactions/%v"}, httpMethod: "DELETE"}, + {urlFormats: []string{"repos/%v/%v/pulls/comments/%v/reactions/%v"}, httpMethod: "DELETE"}, + }, + + "list-reactions-for-a-team-discussion-comment": []*Endpoint{ + {urlFormats: []string{"organizations/%v/team/%v/discussions/%v/comments/%v/reactions"}, httpMethod: "GET"}, + {urlFormats: []string{"orgs/%v/teams/%v/discussions/%v/comments/%v/reactions"}, httpMethod: "GET"}, + }, + + "delete-team-discussion-comment-reaction": []*Endpoint{ + {urlFormats: []string{"organizations/%v/team/%v/discussions/%v/comments/%v/reactions/%v"}, httpMethod: "DELETE"}, + {urlFormats: []string{"orgs/%v/teams/%v/discussions/%v/comments/%v/reactions/%v"}, httpMethod: "DELETE"}, + }, + + "list-reactions-for-a-team-discussion-legacy": []*Endpoint{{urlFormats: []string{"teams/%v/discussions/%v/reactions"}, httpMethod: "GET"}}, +} + +var reactionsTestWebPage = ` + + + + + + + + + Reactions | GitHub Developer Guide + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+
+

+ Reactions

+ + + +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

+ Reaction types

+ +

When creating a reaction, the allowed values for the content parameter are as follows (with the + corresponding emoji for reference):

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
contentemoji
+1:+1:
-1:-1:
laugh:smile:
confused:confused:
heart:heart:
hooray:tada:
rocket:rocket:
eyes:eyes:
+ +

+ List reactions for a + commit comment +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

List the reactions to a commit comment.

+ +
GET /repos/:owner/:repo/comments/:comment_id/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstringReturns a single reaction type. Omit this parameter to + list all reactions to a commit comment.
+ +

+ Response

+ +
Status: 200 OK
+Link: <https://api.github.com/resource?page=2>; rel="next",
+      <https://api.github.com/resource?page=5>; rel="last"
+
+ + +
[
+  {
+    "id": 1,
+    "node_id": "MDg6UmVhY3Rpb24x",
+    "user": {
+      "login": "octocat",
+      "id": 1,
+      "node_id": "MDQ6VXNlcjE=",
+      "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/octocat",
+      "html_url": "https://github.com/octocat",
+      "followers_url": "https://api.github.com/users/octocat/followers",
+      "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+      "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+      "organizations_url": "https://api.github.com/users/octocat/orgs",
+      "repos_url": "https://api.github.com/users/octocat/repos",
+      "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/octocat/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "content": "heart",
+    "created_at": "2016-05-20T20:09:31Z"
+  }
+]
+
+ + +

+ Create reaction for a + commit comment +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

Create a reaction to a commit comment. A response with a + Status: 200 OK means that you already added the reaction type to this commit comment.

+ +
POST /repos/:owner/:repo/comments/:comment_id/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstring + Required. The reaction type to add to the + commit comment.
+ +

+ Example

+ +
{
+  "content": "heart"
+}
+
+ + +

+ Response

+ +
Status: 201 Created
+
+ + +
{
+  "id": 1,
+  "node_id": "MDg6UmVhY3Rpb24x",
+  "user": {
+    "login": "octocat",
+    "id": 1,
+    "node_id": "MDQ6VXNlcjE=",
+    "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+    "gravatar_id": "",
+    "url": "https://api.github.com/users/octocat",
+    "html_url": "https://github.com/octocat",
+    "followers_url": "https://api.github.com/users/octocat/followers",
+    "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+    "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+    "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+    "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+    "organizations_url": "https://api.github.com/users/octocat/orgs",
+    "repos_url": "https://api.github.com/users/octocat/repos",
+    "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+    "received_events_url": "https://api.github.com/users/octocat/received_events",
+    "type": "User",
+    "site_admin": false
+  },
+  "content": "heart",
+  "created_at": "2016-05-20T20:09:31Z"
+}
+
+ + +

+ Delete a commit comment + reaction +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +
+ +

Note: You can also specify a repository by repository_id using the route + DELETE /repositories/:repository_id/comments/:comment_id/reactions/:reaction_id.

+ +
+ +

Delete a reaction to a commit comment.

+ +
DELETE /repos/:owner/:repo/comments/:comment_id/reactions/:reaction_id
+
+ +

+ Response

+ +
Status: 204 No Content
+
+ + +

+ List reactions for an + issue +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

List the reactions to an issue.

+ +
GET /repos/:owner/:repo/issues/:issue_number/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstringReturns a single reaction type. Omit this parameter to + list all reactions to an issue.
+ +

+ Response

+ +
Status: 200 OK
+Link: <https://api.github.com/resource?page=2>; rel="next",
+      <https://api.github.com/resource?page=5>; rel="last"
+
+ + +
[
+  {
+    "id": 1,
+    "node_id": "MDg6UmVhY3Rpb24x",
+    "user": {
+      "login": "octocat",
+      "id": 1,
+      "node_id": "MDQ6VXNlcjE=",
+      "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/octocat",
+      "html_url": "https://github.com/octocat",
+      "followers_url": "https://api.github.com/users/octocat/followers",
+      "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+      "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+      "organizations_url": "https://api.github.com/users/octocat/orgs",
+      "repos_url": "https://api.github.com/users/octocat/repos",
+      "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/octocat/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "content": "heart",
+    "created_at": "2016-05-20T20:09:31Z"
+  }
+]
+
+ + +

+ Create reaction for an + issue

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

Create a reaction to an issue. A response with a Status: 200 OK means + that you already added the reaction type to this issue.

+ +
POST /repos/:owner/:repo/issues/:issue_number/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstring + Required. The reaction type to add to the + issue.
+ +

+ Example

+ +
{
+  "content": "heart"
+}
+
+ + +

+ Response

+ +
Status: 201 Created
+
+ + +
{
+  "id": 1,
+  "node_id": "MDg6UmVhY3Rpb24x",
+  "user": {
+    "login": "octocat",
+    "id": 1,
+    "node_id": "MDQ6VXNlcjE=",
+    "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+    "gravatar_id": "",
+    "url": "https://api.github.com/users/octocat",
+    "html_url": "https://github.com/octocat",
+    "followers_url": "https://api.github.com/users/octocat/followers",
+    "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+    "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+    "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+    "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+    "organizations_url": "https://api.github.com/users/octocat/orgs",
+    "repos_url": "https://api.github.com/users/octocat/repos",
+    "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+    "received_events_url": "https://api.github.com/users/octocat/received_events",
+    "type": "User",
+    "site_admin": false
+  },
+  "content": "heart",
+  "created_at": "2016-05-20T20:09:31Z"
+}
+
+ + +

+ Delete an issue reaction +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +
+ +

Note: You can also specify a repository by repository_id using the route + DELETE /repositories/:repository_id/issues/:issue_number/reactions/:reaction_id.

+ +
+ +

Delete a reaction to an issue.

+ +
DELETE /repos/:owner/:repo/issues/:issue_number/reactions/:reaction_id
+
+ +

+ Response

+ +
Status: 204 No Content
+
+ + +

+ List reactions for an + issue comment +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

List the reactions to an issue comment.

+ +
GET /repos/:owner/:repo/issues/comments/:comment_id/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstringReturns a single reaction type. Omit this parameter to + list all reactions to an issue comment.
+ +

+ Response

+ +
Status: 200 OK
+Link: <https://api.github.com/resource?page=2>; rel="next",
+      <https://api.github.com/resource?page=5>; rel="last"
+
+ + +
[
+  {
+    "id": 1,
+    "node_id": "MDg6UmVhY3Rpb24x",
+    "user": {
+      "login": "octocat",
+      "id": 1,
+      "node_id": "MDQ6VXNlcjE=",
+      "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/octocat",
+      "html_url": "https://github.com/octocat",
+      "followers_url": "https://api.github.com/users/octocat/followers",
+      "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+      "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+      "organizations_url": "https://api.github.com/users/octocat/orgs",
+      "repos_url": "https://api.github.com/users/octocat/repos",
+      "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/octocat/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "content": "heart",
+    "created_at": "2016-05-20T20:09:31Z"
+  }
+]
+
+ + +

+ Create reaction for an + issue comment +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

Create a reaction to an issue comment. A response with a + Status: 200 OK means that you already added the reaction type to this issue comment.

+ +
POST /repos/:owner/:repo/issues/comments/:comment_id/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstring + Required. The reaction type to add to the + issue comment.
+ +

+ Example

+ +
{
+  "content": "heart"
+}
+
+ + +

+ Response

+ +
Status: 201 Created
+
+ + +
{
+  "id": 1,
+  "node_id": "MDg6UmVhY3Rpb24x",
+  "user": {
+    "login": "octocat",
+    "id": 1,
+    "node_id": "MDQ6VXNlcjE=",
+    "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+    "gravatar_id": "",
+    "url": "https://api.github.com/users/octocat",
+    "html_url": "https://github.com/octocat",
+    "followers_url": "https://api.github.com/users/octocat/followers",
+    "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+    "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+    "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+    "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+    "organizations_url": "https://api.github.com/users/octocat/orgs",
+    "repos_url": "https://api.github.com/users/octocat/repos",
+    "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+    "received_events_url": "https://api.github.com/users/octocat/received_events",
+    "type": "User",
+    "site_admin": false
+  },
+  "content": "heart",
+  "created_at": "2016-05-20T20:09:31Z"
+}
+
+ + +

+ Delete an issue comment + reaction +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +
+ +

Note: You can also specify a repository by repository_id using the route + DELETE delete /repositories/:repository_id/issues/comments/:comment_id/reactions/:reaction_id. +

+ +
+ +

Delete a reaction to an issue comment.

+ +
DELETE /repos/:owner/:repo/issues/comments/:comment_id/reactions/:reaction_id
+
+ +

+ Response

+ +
Status: 204 No Content
+
+ + +

+ List reactions for a pull request review comment +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

List the reactions to a pull request review comment.

+ +
GET /repos/:owner/:repo/pulls/comments/:comment_id/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstringReturns a single reaction type. Omit this parameter to + list all reactions to a pull request review comment.
+ +

+ Response

+ +
Status: 200 OK
+Link: <https://api.github.com/resource?page=2>; rel="next",
+      <https://api.github.com/resource?page=5>; rel="last"
+
+ + +
[
+  {
+    "id": 1,
+    "node_id": "MDg6UmVhY3Rpb24x",
+    "user": {
+      "login": "octocat",
+      "id": 1,
+      "node_id": "MDQ6VXNlcjE=",
+      "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/octocat",
+      "html_url": "https://github.com/octocat",
+      "followers_url": "https://api.github.com/users/octocat/followers",
+      "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+      "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+      "organizations_url": "https://api.github.com/users/octocat/orgs",
+      "repos_url": "https://api.github.com/users/octocat/repos",
+      "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/octocat/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "content": "heart",
+    "created_at": "2016-05-20T20:09:31Z"
+  }
+]
+
+ + +

+ Create reaction for a pull request review comment +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

Create a reaction to a pull request review comment. A response with a + Status: 200 OK means that you already added the reaction type to this pull request review + comment.

+ +
POST /repos/:owner/:repo/pulls/comments/:comment_id/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstring + Required. The reaction type to add to the + pull request review comment.
+ +

+ Example

+ +
{
+  "content": "heart"
+}
+
+ + +

+ Response

+ +
Status: 201 Created
+
+ + +
{
+  "id": 1,
+  "node_id": "MDg6UmVhY3Rpb24x",
+  "user": {
+    "login": "octocat",
+    "id": 1,
+    "node_id": "MDQ6VXNlcjE=",
+    "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+    "gravatar_id": "",
+    "url": "https://api.github.com/users/octocat",
+    "html_url": "https://github.com/octocat",
+    "followers_url": "https://api.github.com/users/octocat/followers",
+    "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+    "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+    "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+    "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+    "organizations_url": "https://api.github.com/users/octocat/orgs",
+    "repos_url": "https://api.github.com/users/octocat/repos",
+    "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+    "received_events_url": "https://api.github.com/users/octocat/received_events",
+    "type": "User",
+    "site_admin": false
+  },
+  "content": "heart",
+  "created_at": "2016-05-20T20:09:31Z"
+}
+
+ + +

+ Delete a pull request + comment reaction +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +
+ +

Note: You can also specify a repository by repository_id using the route + DELETE /repositories/:repository_id/pulls/comments/:comment_id/reactions/:reaction_id.

+ +
+ +

Delete a reaction to a pull request review comment.

+ +
DELETE /repos/:owner/:repo/pulls/comments/:comment_id/reactions/:reaction_id
+
+ +

+ Response

+ +
Status: 204 No Content
+
+ + +

+ List reactions for a + team discussion +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

List the reactions to a team discussion. OAuth access tokens require the + read:discussion scope.

+ +
+ +

Note: You can also specify a team by org_id and team_id using + the route GET /organizations/:org_id/team/:team_id/discussions/:discussion_number/reactions. +

+ +
+ +
GET /orgs/:org/teams/:team_slug/discussions/:discussion_number/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstringReturns a single reaction type. Omit this parameter to + list all reactions to a team discussion.
+ +

+ Response

+ +
Status: 200 OK
+Link: <https://api.github.com/resource?page=2>; rel="next",
+      <https://api.github.com/resource?page=5>; rel="last"
+
+ + +
[
+  {
+    "id": 1,
+    "node_id": "MDg6UmVhY3Rpb24x",
+    "user": {
+      "login": "octocat",
+      "id": 1,
+      "node_id": "MDQ6VXNlcjE=",
+      "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/octocat",
+      "html_url": "https://github.com/octocat",
+      "followers_url": "https://api.github.com/users/octocat/followers",
+      "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+      "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+      "organizations_url": "https://api.github.com/users/octocat/orgs",
+      "repos_url": "https://api.github.com/users/octocat/repos",
+      "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/octocat/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "content": "heart",
+    "created_at": "2016-05-20T20:09:31Z"
+  }
+]
+
+ + +

+ Create reaction for a + team discussion

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

Create a reaction to a team discussion. OAuth access tokens require the + write:discussion scope. A response with a + Status: 200 OK means that you already added the reaction type to this team discussion.

+ +
+ +

Note: You can also specify a team by org_id and team_id using + the route POST /organizations/:org_id/team/:team_id/discussions/:discussion_number/reactions. +

+ +
+ +
POST /orgs/:org/teams/:team_slug/discussions/:discussion_number/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstring + Required. The reaction type to add to the + team discussion.
+ +

+ Example

+ +
{
+  "content": "heart"
+}
+
+ + +

+ Response

+ +
Status: 201 Created
+
+ + +
{
+  "id": 1,
+  "node_id": "MDg6UmVhY3Rpb24x",
+  "user": {
+    "login": "octocat",
+    "id": 1,
+    "node_id": "MDQ6VXNlcjE=",
+    "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+    "gravatar_id": "",
+    "url": "https://api.github.com/users/octocat",
+    "html_url": "https://github.com/octocat",
+    "followers_url": "https://api.github.com/users/octocat/followers",
+    "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+    "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+    "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+    "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+    "organizations_url": "https://api.github.com/users/octocat/orgs",
+    "repos_url": "https://api.github.com/users/octocat/repos",
+    "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+    "received_events_url": "https://api.github.com/users/octocat/received_events",
+    "type": "User",
+    "site_admin": false
+  },
+  "content": "heart",
+  "created_at": "2016-05-20T20:09:31Z"
+}
+
+ + +

+ Delete team discussion + reaction +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +
+ +

Note: You can also specify a team or organization with team_id and + org_id using the route + DELETE /organizations/:org_id/team/:team_id/discussions/:discussion_number/reactions/:reaction_id. +

+ +
+ +

Delete a reaction to a team discussion. OAuth access tokens require the + write:discussion scope.

+ +
DELETE /orgs/:org/teams/:team_slug/discussions/:discussion_number/reactions/:reaction_id
+
+ +

+ Response

+ +
Status: 204 No Content
+
+ + +

+ List reactions for a team discussion comment +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

List the reactions to a team discussion comment. OAuth access + tokens require the read:discussion scope.

+ +
+ +

Note: You can also specify a team by org_id and team_id using + the route + GET /organizations/:org_id/team/:team_id/discussions/:discussion_number/comments/:comment_number/reactions. +

+ +
+ +
GET /orgs/:org/teams/:team_slug/discussions/:discussion_number/comments/:comment_number/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstringReturns a single reaction type. Omit this parameter to + list all reactions to a team discussion comment.
+ +

+ Response

+ +
Status: 200 OK
+Link: <https://api.github.com/resource?page=2>; rel="next",
+      <https://api.github.com/resource?page=5>; rel="last"
+
+ + +
[
+  {
+    "id": 1,
+    "node_id": "MDg6UmVhY3Rpb24x",
+    "user": {
+      "login": "octocat",
+      "id": 1,
+      "node_id": "MDQ6VXNlcjE=",
+      "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/octocat",
+      "html_url": "https://github.com/octocat",
+      "followers_url": "https://api.github.com/users/octocat/followers",
+      "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+      "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+      "organizations_url": "https://api.github.com/users/octocat/orgs",
+      "repos_url": "https://api.github.com/users/octocat/repos",
+      "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/octocat/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "content": "heart",
+    "created_at": "2016-05-20T20:09:31Z"
+  }
+]
+
+ + +

+ Create reaction for a team discussion comment +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

Create a reaction to a team discussion comment. OAuth access + tokens require the write:discussion scope. A response with a + Status: 200 OK means that you already added the reaction type to this team discussion comment. +

+ +
+ +

Note: You can also specify a team by org_id and team_id using + the route + POST /organizations/:org_id/team/:team_id/discussions/:discussion_number/comments/:comment_number/reactions. +

+ +
+ +
POST /orgs/:org/teams/:team_slug/discussions/:discussion_number/comments/:comment_number/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstring + Required. The reaction type to add to the + team discussion comment.
+ +

+ Example

+ +
{
+  "content": "heart"
+}
+
+ + +

+ Response

+ +
Status: 201 Created
+
+ + +
{
+  "id": 1,
+  "node_id": "MDg6UmVhY3Rpb24x",
+  "user": {
+    "login": "octocat",
+    "id": 1,
+    "node_id": "MDQ6VXNlcjE=",
+    "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+    "gravatar_id": "",
+    "url": "https://api.github.com/users/octocat",
+    "html_url": "https://github.com/octocat",
+    "followers_url": "https://api.github.com/users/octocat/followers",
+    "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+    "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+    "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+    "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+    "organizations_url": "https://api.github.com/users/octocat/orgs",
+    "repos_url": "https://api.github.com/users/octocat/repos",
+    "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+    "received_events_url": "https://api.github.com/users/octocat/received_events",
+    "type": "User",
+    "site_admin": false
+  },
+  "content": "heart",
+  "created_at": "2016-05-20T20:09:31Z"
+}
+
+ + +

+ Delete team discussion + comment reaction +

+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +
+ +

Note: You can also specify a team or organization with team_id and + org_id using the route + DELETE /organizations/:org_id/team/:team_id/discussions/:discussion_number/comments/:comment_number/reactions/:reaction_id. +

+ +
+ +

Delete a reaction to a team discussion comment. OAuth access + tokens require the write:discussion scope.

+ +
DELETE /orgs/:org/teams/:team_slug/discussions/:discussion_number/comments/:comment_number/reactions/:reaction_id
+
+ +

+ Response

+ +
Status: 204 No Content
+
+ + +

+ Delete a reaction (Legacy) +

+ +
+ +

Deprecation Notice: This endpoint route is deprecated and will be removed from the + Reactions API. We recommend migrating your existing code to use the new delete reactions endpoints. For more + information, see this blog post.

+ +
+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

OAuth access tokens require the write:discussion scope, when deleting a team discussion or team + discussion comment.

+ +
DELETE /reactions/:reaction_id
+
+ +

+ Response

+ +
Status: 204 No Content
+
+ + +

+ List reactions for a team discussion (Legacy) +

+ +
+ +

Deprecation Notice: This endpoint route is deprecated and will be removed from the Teams + API. We recommend migrating your existing code to use the new List reactions for a team discussion + endpoint.

+ +
+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

List the reactions to a team discussion. OAuth access tokens require the + read:discussion scope.

+ +
GET /teams/:team_id/discussions/:discussion_number/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstringReturns a single reaction type. Omit this parameter to + list all reactions to a team discussion.
+ +

+ Response

+ +
Status: 200 OK
+Link: <https://api.github.com/resource?page=2>; rel="next",
+      <https://api.github.com/resource?page=5>; rel="last"
+
+ + +
[
+  {
+    "id": 1,
+    "node_id": "MDg6UmVhY3Rpb24x",
+    "user": {
+      "login": "octocat",
+      "id": 1,
+      "node_id": "MDQ6VXNlcjE=",
+      "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/octocat",
+      "html_url": "https://github.com/octocat",
+      "followers_url": "https://api.github.com/users/octocat/followers",
+      "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+      "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+      "organizations_url": "https://api.github.com/users/octocat/orgs",
+      "repos_url": "https://api.github.com/users/octocat/repos",
+      "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/octocat/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "content": "heart",
+    "created_at": "2016-05-20T20:09:31Z"
+  }
+]
+
+ + +

+ Create reaction for a team discussion (Legacy)

+ +
+ +

Deprecation Notice: This endpoint route is deprecated and will be removed from the Teams + API. We recommend migrating your existing code to use the new Create reaction for a team discussion + endpoint.

+ +
+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

Create a reaction to a team discussion. OAuth access tokens require the + write:discussion scope. A response with a + Status: 200 OK means that you already added the reaction type to this team discussion.

+ +
POST /teams/:team_id/discussions/:discussion_number/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstring + Required. The reaction type to add to the + team discussion.
+ +

+ Example

+ +
{
+  "content": "heart"
+}
+
+ + +

+ Response

+ +
Status: 201 Created
+
+ + +
{
+  "id": 1,
+  "node_id": "MDg6UmVhY3Rpb24x",
+  "user": {
+    "login": "octocat",
+    "id": 1,
+    "node_id": "MDQ6VXNlcjE=",
+    "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+    "gravatar_id": "",
+    "url": "https://api.github.com/users/octocat",
+    "html_url": "https://github.com/octocat",
+    "followers_url": "https://api.github.com/users/octocat/followers",
+    "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+    "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+    "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+    "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+    "organizations_url": "https://api.github.com/users/octocat/orgs",
+    "repos_url": "https://api.github.com/users/octocat/repos",
+    "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+    "received_events_url": "https://api.github.com/users/octocat/received_events",
+    "type": "User",
+    "site_admin": false
+  },
+  "content": "heart",
+  "created_at": "2016-05-20T20:09:31Z"
+}
+
+ + +

+ List reactions for a team discussion comment (Legacy) +

+ +
+ +

Deprecation Notice: This endpoint route is deprecated and will be removed from the Teams + API. We recommend migrating your existing code to use the new List reactions for a team discussion comment + endpoint.

+ +
+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

List the reactions to a team discussion comment. OAuth access + tokens require the read:discussion scope.

+ +
GET /teams/:team_id/discussions/:discussion_number/comments/:comment_number/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstringReturns a single reaction type. Omit this parameter to + list all reactions to a team discussion comment.
+ +

+ Response

+ +
Status: 200 OK
+Link: <https://api.github.com/resource?page=2>; rel="next",
+      <https://api.github.com/resource?page=5>; rel="last"
+
+ + +
[
+  {
+    "id": 1,
+    "node_id": "MDg6UmVhY3Rpb24x",
+    "user": {
+      "login": "octocat",
+      "id": 1,
+      "node_id": "MDQ6VXNlcjE=",
+      "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+      "gravatar_id": "",
+      "url": "https://api.github.com/users/octocat",
+      "html_url": "https://github.com/octocat",
+      "followers_url": "https://api.github.com/users/octocat/followers",
+      "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+      "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+      "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+      "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+      "organizations_url": "https://api.github.com/users/octocat/orgs",
+      "repos_url": "https://api.github.com/users/octocat/repos",
+      "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+      "received_events_url": "https://api.github.com/users/octocat/received_events",
+      "type": "User",
+      "site_admin": false
+    },
+    "content": "heart",
+    "created_at": "2016-05-20T20:09:31Z"
+  }
+]
+
+ + +

+ Create reaction for a team discussion comment (Legacy) +

+ +
+ +

Deprecation Notice: This endpoint route is deprecated and will be removed from the Teams + API. We recommend migrating your existing code to use the new Create reaction for a team discussion comment + endpoint.

+ +
+ +
+ +

Note: APIs for managing reactions are currently available for developers to preview. See + the blog post for full details. To access the API + during the preview period, you must provide a custom media type in the + Accept header:

+ +
  application/vnd.github.squirrel-girl-preview+json
+
+ +
+ +
+ +

Warning: The API may change without advance notice during the preview period. Preview + features are not supported for production use. If you experience any issues, contact GitHub Support or GitHub + Premium Support.

+ +
+ +

Create a reaction to a team discussion comment. OAuth access + tokens require the write:discussion scope. A response with a + Status: 200 OK means that you already added the reaction type to this team discussion comment. +

+ +
POST /teams/:team_id/discussions/:discussion_number/comments/:comment_number/reactions
+
+ +

+ Parameters

+ + + + + + + + + + + + + + + + +
NameTypeDescription
contentstring + Required. The reaction type to add to the + team discussion comment.
+ +

+ Example

+ +
{
+  "content": "heart"
+}
+
+ + +

+ Response

+ +
Status: 201 Created
+
+ + +
{
+  "id": 1,
+  "node_id": "MDg6UmVhY3Rpb24x",
+  "user": {
+    "login": "octocat",
+    "id": 1,
+    "node_id": "MDQ6VXNlcjE=",
+    "avatar_url": "https://github.com/images/error/octocat_happy.gif",
+    "gravatar_id": "",
+    "url": "https://api.github.com/users/octocat",
+    "html_url": "https://github.com/octocat",
+    "followers_url": "https://api.github.com/users/octocat/followers",
+    "following_url": "https://api.github.com/users/octocat/following{/other_user}",
+    "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
+    "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
+    "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
+    "organizations_url": "https://api.github.com/users/octocat/orgs",
+    "repos_url": "https://api.github.com/users/octocat/repos",
+    "events_url": "https://api.github.com/users/octocat/events{/privacy}",
+    "received_events_url": "https://api.github.com/users/octocat/received_events",
+    "type": "User",
+    "site_admin": false
+  },
+  "content": "heart",
+  "created_at": "2016-05-20T20:09:31Z"
+}
+
+ +
+ + + + + +
+
+ + + + + + + + +` + +var reactionsGoFileOriginal = `// Copyright 2016 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" + "net/http" +) + +// ReactionsService provides access to the reactions-related functions in the +// GitHub API. +type ReactionsService service + +// Reaction represents a GitHub reaction. +type Reaction struct { + // ID is the Reaction ID. + ID *int64 ` + "`" + `json:"id,omitempty"` + "`" + ` + User *User ` + "`" + `json:"user,omitempty"` + "`" + ` + NodeID *string ` + "`" + `json:"node_id,omitempty"` + "`" + ` + // Content is the type of reaction. + // Possible values are: + // "+1", "-1", "laugh", "confused", "heart", "hooray". + Content *string ` + "`" + `json:"content,omitempty"` + "`" + ` +} + +// Reactions represents a summary of GitHub reactions. +type Reactions struct { + TotalCount *int ` + "`" + `json:"total_count,omitempty"` + "`" + ` + PlusOne *int ` + "`" + `json:"+1,omitempty"` + "`" + ` + MinusOne *int ` + "`" + `json:"-1,omitempty"` + "`" + ` + Laugh *int ` + "`" + `json:"laugh,omitempty"` + "`" + ` + Confused *int ` + "`" + `json:"confused,omitempty"` + "`" + ` + Heart *int ` + "`" + `json:"heart,omitempty"` + "`" + ` + Hooray *int ` + "`" + `json:"hooray,omitempty"` + "`" + ` + URL *string ` + "`" + `json:"url,omitempty"` + "`" + ` +} + +func (r Reaction) String() string { + return Stringify(r) +} + +// ListCommentReactionOptions specifies the optional parameters to the +// ReactionsService.ListCommentReactions method. +type ListCommentReactionOptions struct { + // Content restricts the returned comment reactions to only those with the given type. + // Omit this parameter to list all reactions to a commit comment. + // Possible values are: "+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", or "eyes". + Content string ` + "`" + `url:"content,omitempty"` + "`" + ` + + ListOptions +} + +// ListCommentReactions lists the reactions for a commit comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-commit-comment +func (s *ReactionsService) ListCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListCommentReactionOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v/reactions", owner, repo, id) + 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 + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreateCommentReaction creates a reaction for a commit comment. +// Note that if you have already created a reaction of type content, the +// previously created reaction will be returned with Status: 200 OK. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-commit-comment +func (s *ReactionsService) CreateCommentReaction(ctx context.Context, owner, repo string, id int64, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v/reactions", owner, repo, id) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeleteCommentReaction deletes the reaction for a commit comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-a-commit-comment-reaction +func (s *ReactionsService) DeleteCommentReaction(ctx context.Context, owner, repo string, commentID, reactionID int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v/reactions/%v", owner, repo, commentID, reactionID) + + return s.deleteReaction(ctx, u) +} + +// DeleteCommentReactionByID deletes the reaction for a commit comment by repository ID. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-a-commit-comment-reaction +func (s *ReactionsService) DeleteCommentReactionByID(ctx context.Context, repoID, commentID, reactionID int64) (*Response, error) { + u := fmt.Sprintf("repositories/%v/comments/%v/reactions/%v", repoID, commentID, reactionID) + + return s.deleteReaction(ctx, u) +} + +// ListIssueReactions lists the reactions for an issue. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-an-issue +func (s *ReactionsService) ListIssueReactions(ctx context.Context, owner, repo string, number int, opts *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/reactions", owner, repo, number) + 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 + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreateIssueReaction creates a reaction for an issue. +// Note that if you have already created a reaction of type content, the +// previously created reaction will be returned with Status: 200 OK. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-an-issue +func (s *ReactionsService) CreateIssueReaction(ctx context.Context, owner, repo string, number int, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/reactions", owner, repo, number) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeleteIssueReaction deletes the reaction to an issue. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-an-issue-reaction +func (s *ReactionsService) DeleteIssueReaction(ctx context.Context, owner, repo string, issueNumber int, reactionID int64) (*Response, error) { + url := fmt.Sprintf("repos/%v/%v/issues/%v/reactions/%v", owner, repo, issueNumber, reactionID) + + return s.deleteReaction(ctx, url) +} + +// DeleteIssueReactionByID deletes the reaction to an issue by repository ID. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-an-issue-reaction +func (s *ReactionsService) DeleteIssueReactionByID(ctx context.Context, repoID, issueNumber int, reactionID int64) (*Response, error) { + url := fmt.Sprintf("repositories/%v/issues/%v/reactions/%v", repoID, issueNumber, reactionID) + + return s.deleteReaction(ctx, url) +} + +// ListIssueCommentReactions lists the reactions for an issue comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-an-issue-comment +func (s *ReactionsService) ListIssueCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%v/reactions", owner, repo, id) + 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 + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreateIssueCommentReaction creates a reaction for an issue comment. +// Note that if you have already created a reaction of type content, the +// previously created reaction will be returned with Status: 200 OK. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-an-issue-comment +func (s *ReactionsService) CreateIssueCommentReaction(ctx context.Context, owner, repo string, id int64, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%v/reactions", owner, repo, id) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeleteIssueCommentReaction deletes the reaction to an issue comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-an-issue-comment-reaction +func (s *ReactionsService) DeleteIssueCommentReaction(ctx context.Context, owner, repo string, commentID, reactionID int64) (*Response, error) { + url := fmt.Sprintf("repos/%v/%v/issues/comments/%v/reactions/%v", owner, repo, commentID, reactionID) + + return s.deleteReaction(ctx, url) +} + +// DeleteIssueCommentReactionByID deletes the reaction to an issue comment by repository ID. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-an-issue-comment-reaction +func (s *ReactionsService) DeleteIssueCommentReactionByID(ctx context.Context, repoID, commentID, reactionID int64) (*Response, error) { + url := fmt.Sprintf("repositories/%v/issues/comments/%v/reactions/%v", repoID, commentID, reactionID) + + return s.deleteReaction(ctx, url) +} + +// ListPullRequestCommentReactions lists the reactions for a pull request review comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-an-issue-comment +func (s *ReactionsService) ListPullRequestCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions", owner, repo, id) + 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 + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreatePullRequestCommentReaction creates a reaction for a pull request review comment. +// Note that if you have already created a reaction of type content, the +// previously created reaction will be returned with Status: 200 OK. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-an-issue-comment +func (s *ReactionsService) CreatePullRequestCommentReaction(ctx context.Context, owner, repo string, id int64, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions", owner, repo, id) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeletePullRequestCommentReaction deletes the reaction to a pull request review comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-a-pull-request-comment-reaction +func (s *ReactionsService) DeletePullRequestCommentReaction(ctx context.Context, owner, repo string, commentID, reactionID int64) (*Response, error) { + url := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions/%v", owner, repo, commentID, reactionID) + + return s.deleteReaction(ctx, url) +} + +// DeletePullRequestCommentReactionByID deletes the reaction to a pull request review comment by repository ID. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-a-pull-request-comment-reaction +func (s *ReactionsService) DeletePullRequestCommentReactionByID(ctx context.Context, repoID, commentID, reactionID int64) (*Response, error) { + url := fmt.Sprintf("repositories/%v/pulls/comments/%v/reactions/%v", repoID, commentID, reactionID) + + return s.deleteReaction(ctx, url) +} + +// ListTeamDiscussionReactions lists the reactions for a team discussion. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-team-discussion +func (s *ReactionsService) ListTeamDiscussionReactions(ctx context.Context, teamID int64, discussionNumber int, opts *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/reactions", teamID, discussionNumber) + 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 + } + + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreateTeamDiscussionReaction creates a reaction for a team discussion. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-team-discussion +func (s *ReactionsService) CreateTeamDiscussionReaction(ctx context.Context, teamID int64, discussionNumber int, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/reactions", teamID, discussionNumber) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeleteTeamDiscussionReaction deletes the reaction to a team discussion. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-team-discussion-reaction +func (s *ReactionsService) DeleteTeamDiscussionReaction(ctx context.Context, org, teamSlug string, discussionNumber int, reactionID int64) (*Response, error) { + url := fmt.Sprintf("orgs/%v/teams/%v/discussions/%v/reactions/%v", org, teamSlug, discussionNumber, reactionID) + + return s.deleteReaction(ctx, url) +} + +// DeleteTeamDiscussionReactionByOrgIDAndTeamID deletes the reaction to a team discussion by organization ID and team ID. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-team-discussion-reaction +func (s *ReactionsService) DeleteTeamDiscussionReactionByOrgIDAndTeamID(ctx context.Context, orgID, teamID, discussionNumber int, reactionID int64) (*Response, error) { + url := fmt.Sprintf("organizations/%v/team/%v/discussions/%v/reactions/%v", orgID, teamID, discussionNumber, reactionID) + + return s.deleteReaction(ctx, url) +} + +// ListTeamDiscussionCommentReactions lists the reactions for a team discussion comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-team-discussion-comment +func (s *ReactionsService) ListTeamDiscussionCommentReactions(ctx context.Context, teamID int64, discussionNumber, commentNumber int, opts *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v/reactions", teamID, discussionNumber, commentNumber) + 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 + } + + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, nil, err + } + return m, resp, nil +} + +// CreateTeamDiscussionCommentReaction creates a reaction for a team discussion comment. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-team-discussion-comment +func (s *ReactionsService) CreateTeamDiscussionCommentReaction(ctx context.Context, teamID int64, discussionNumber, commentNumber int, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v/reactions", teamID, discussionNumber, commentNumber) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeleteTeamDiscussionCommentReaction deletes the reaction to a team discussion comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-team-discussion-comment-reaction +func (s *ReactionsService) DeleteTeamDiscussionCommentReaction(ctx context.Context, org, teamSlug string, discussionNumber, commentNumber int, reactionID int64) (*Response, error) { + url := fmt.Sprintf("orgs/%v/teams/%v/discussions/%v/comments/%v/reactions/%v", org, teamSlug, discussionNumber, commentNumber, reactionID) + + return s.deleteReaction(ctx, url) +} + +// DeleteTeamDiscussionCommentReactionByOrgIDAndTeamID deletes the reaction to a team discussion comment by organization ID and team ID. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-team-discussion-comment-reaction +func (s *ReactionsService) DeleteTeamDiscussionCommentReactionByOrgIDAndTeamID(ctx context.Context, orgID, teamID, discussionNumber, commentNumber int, reactionID int64) (*Response, error) { + url := fmt.Sprintf("organizations/%v/team/%v/discussions/%v/comments/%v/reactions/%v", orgID, teamID, discussionNumber, commentNumber, reactionID) + + return s.deleteReaction(ctx, url) +} + +func (s *ReactionsService) deleteReaction(ctx context.Context, url string) (*Response, error) { + req, err := s.client.NewRequest(http.MethodDelete, url, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + return s.client.Do(ctx, req, nil) +} +` + +var reactionsGoFileWant = `// Copyright 2016 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" + "net/http" +) + +// ReactionsService provides access to the reactions-related functions in the +// GitHub API. +type ReactionsService service + +// Reaction represents a GitHub reaction. +type Reaction struct { + // ID is the Reaction ID. + ID *int64 ` + "`" + `json:"id,omitempty"` + "`" + ` + User *User ` + "`" + `json:"user,omitempty"` + "`" + ` + NodeID *string ` + "`" + `json:"node_id,omitempty"` + "`" + ` + // Content is the type of reaction. + // Possible values are: + // "+1", "-1", "laugh", "confused", "heart", "hooray". + Content *string ` + "`" + `json:"content,omitempty"` + "`" + ` +} + +// Reactions represents a summary of GitHub reactions. +type Reactions struct { + TotalCount *int ` + "`" + `json:"total_count,omitempty"` + "`" + ` + PlusOne *int ` + "`" + `json:"+1,omitempty"` + "`" + ` + MinusOne *int ` + "`" + `json:"-1,omitempty"` + "`" + ` + Laugh *int ` + "`" + `json:"laugh,omitempty"` + "`" + ` + Confused *int ` + "`" + `json:"confused,omitempty"` + "`" + ` + Heart *int ` + "`" + `json:"heart,omitempty"` + "`" + ` + Hooray *int ` + "`" + `json:"hooray,omitempty"` + "`" + ` + URL *string ` + "`" + `json:"url,omitempty"` + "`" + ` +} + +func (r Reaction) String() string { + return Stringify(r) +} + +// ListCommentReactionOptions specifies the optional parameters to the +// ReactionsService.ListCommentReactions method. +type ListCommentReactionOptions struct { + // Content restricts the returned comment reactions to only those with the given type. + // Omit this parameter to list all reactions to a commit comment. + // Possible values are: "+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", or "eyes". + Content string ` + "`" + `url:"content,omitempty"` + "`" + ` + + ListOptions +} + +// ListCommentReactions lists the reactions for a commit comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-commit-comment +func (s *ReactionsService) ListCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListCommentReactionOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v/reactions", owner, repo, id) + 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 + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreateCommentReaction creates a reaction for a commit comment. +// Note that if you have already created a reaction of type content, the +// previously created reaction will be returned with Status: 200 OK. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-commit-comment +func (s *ReactionsService) CreateCommentReaction(ctx context.Context, owner, repo string, id int64, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v/reactions", owner, repo, id) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeleteCommentReaction deletes the reaction for a commit comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-a-commit-comment-reaction +func (s *ReactionsService) DeleteCommentReaction(ctx context.Context, owner, repo string, commentID, reactionID int64) (*Response, error) { + u := fmt.Sprintf("repos/%v/%v/comments/%v/reactions/%v", owner, repo, commentID, reactionID) + + return s.deleteReaction(ctx, u) +} + +// DeleteCommentReactionByID deletes the reaction for a commit comment by repository ID. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-a-commit-comment-reaction +func (s *ReactionsService) DeleteCommentReactionByID(ctx context.Context, repoID, commentID, reactionID int64) (*Response, error) { + u := fmt.Sprintf("repositories/%v/comments/%v/reactions/%v", repoID, commentID, reactionID) + + return s.deleteReaction(ctx, u) +} + +// ListIssueReactions lists the reactions for an issue. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-an-issue +func (s *ReactionsService) ListIssueReactions(ctx context.Context, owner, repo string, number int, opts *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/reactions", owner, repo, number) + 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 + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreateIssueReaction creates a reaction for an issue. +// Note that if you have already created a reaction of type content, the +// previously created reaction will be returned with Status: 200 OK. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-an-issue +func (s *ReactionsService) CreateIssueReaction(ctx context.Context, owner, repo string, number int, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/%v/reactions", owner, repo, number) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeleteIssueReaction deletes the reaction to an issue. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-an-issue-reaction +func (s *ReactionsService) DeleteIssueReaction(ctx context.Context, owner, repo string, issueNumber int, reactionID int64) (*Response, error) { + url := fmt.Sprintf("repos/%v/%v/issues/%v/reactions/%v", owner, repo, issueNumber, reactionID) + + return s.deleteReaction(ctx, url) +} + +// DeleteIssueReactionByID deletes the reaction to an issue by repository ID. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-an-issue-reaction +func (s *ReactionsService) DeleteIssueReactionByID(ctx context.Context, repoID, issueNumber int, reactionID int64) (*Response, error) { + url := fmt.Sprintf("repositories/%v/issues/%v/reactions/%v", repoID, issueNumber, reactionID) + + return s.deleteReaction(ctx, url) +} + +// ListIssueCommentReactions lists the reactions for an issue comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-an-issue-comment +func (s *ReactionsService) ListIssueCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%v/reactions", owner, repo, id) + 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 + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreateIssueCommentReaction creates a reaction for an issue comment. +// Note that if you have already created a reaction of type content, the +// previously created reaction will be returned with Status: 200 OK. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-an-issue-comment +func (s *ReactionsService) CreateIssueCommentReaction(ctx context.Context, owner, repo string, id int64, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/issues/comments/%v/reactions", owner, repo, id) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeleteIssueCommentReaction deletes the reaction to an issue comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-an-issue-comment-reaction +func (s *ReactionsService) DeleteIssueCommentReaction(ctx context.Context, owner, repo string, commentID, reactionID int64) (*Response, error) { + url := fmt.Sprintf("repos/%v/%v/issues/comments/%v/reactions/%v", owner, repo, commentID, reactionID) + + return s.deleteReaction(ctx, url) +} + +// DeleteIssueCommentReactionByID deletes the reaction to an issue comment by repository ID. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-an-issue-comment-reaction +func (s *ReactionsService) DeleteIssueCommentReactionByID(ctx context.Context, repoID, commentID, reactionID int64) (*Response, error) { + url := fmt.Sprintf("repositories/%v/issues/comments/%v/reactions/%v", repoID, commentID, reactionID) + + return s.deleteReaction(ctx, url) +} + +// ListPullRequestCommentReactions lists the reactions for a pull request review comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-pull-request-review-comment +func (s *ReactionsService) ListPullRequestCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions", owner, repo, id) + 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 + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreatePullRequestCommentReaction creates a reaction for a pull request review comment. +// Note that if you have already created a reaction of type content, the +// previously created reaction will be returned with Status: 200 OK. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-pull-request-review-comment +func (s *ReactionsService) CreatePullRequestCommentReaction(ctx context.Context, owner, repo string, id int64, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions", owner, repo, id) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeletePullRequestCommentReaction deletes the reaction to a pull request review comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-a-pull-request-comment-reaction +func (s *ReactionsService) DeletePullRequestCommentReaction(ctx context.Context, owner, repo string, commentID, reactionID int64) (*Response, error) { + url := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions/%v", owner, repo, commentID, reactionID) + + return s.deleteReaction(ctx, url) +} + +// DeletePullRequestCommentReactionByID deletes the reaction to a pull request review comment by repository ID. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-a-pull-request-comment-reaction +func (s *ReactionsService) DeletePullRequestCommentReactionByID(ctx context.Context, repoID, commentID, reactionID int64) (*Response, error) { + url := fmt.Sprintf("repositories/%v/pulls/comments/%v/reactions/%v", repoID, commentID, reactionID) + + return s.deleteReaction(ctx, url) +} + +// ListTeamDiscussionReactions lists the reactions for a team discussion. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-team-discussion-legacy +func (s *ReactionsService) ListTeamDiscussionReactions(ctx context.Context, teamID int64, discussionNumber int, opts *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/reactions", teamID, discussionNumber) + 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 + } + + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// CreateTeamDiscussionReaction creates a reaction for a team discussion. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-team-discussion-legacy +func (s *ReactionsService) CreateTeamDiscussionReaction(ctx context.Context, teamID int64, discussionNumber int, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/reactions", teamID, discussionNumber) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeleteTeamDiscussionReaction deletes the reaction to a team discussion. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-team-discussion-reaction +func (s *ReactionsService) DeleteTeamDiscussionReaction(ctx context.Context, org, teamSlug string, discussionNumber int, reactionID int64) (*Response, error) { + url := fmt.Sprintf("orgs/%v/teams/%v/discussions/%v/reactions/%v", org, teamSlug, discussionNumber, reactionID) + + return s.deleteReaction(ctx, url) +} + +// DeleteTeamDiscussionReactionByOrgIDAndTeamID deletes the reaction to a team discussion by organization ID and team ID. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-team-discussion-reaction +func (s *ReactionsService) DeleteTeamDiscussionReactionByOrgIDAndTeamID(ctx context.Context, orgID, teamID, discussionNumber int, reactionID int64) (*Response, error) { + url := fmt.Sprintf("organizations/%v/team/%v/discussions/%v/reactions/%v", orgID, teamID, discussionNumber, reactionID) + + return s.deleteReaction(ctx, url) +} + +// ListTeamDiscussionCommentReactions lists the reactions for a team discussion comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#list-reactions-for-a-team-discussion-comment-legacy +func (s *ReactionsService) ListTeamDiscussionCommentReactions(ctx context.Context, teamID int64, discussionNumber, commentNumber int, opts *ListOptions) ([]*Reaction, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v/reactions", teamID, discussionNumber, commentNumber) + 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 + } + + req.Header.Set("Accept", mediaTypeReactionsPreview) + + var m []*Reaction + resp, err := s.client.Do(ctx, req, &m) + if err != nil { + return nil, nil, err + } + return m, resp, nil +} + +// CreateTeamDiscussionCommentReaction creates a reaction for a team discussion comment. +// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray". +// +// GitHub API docs: https://developer.github.com/v3/reactions/#create-reaction-for-a-team-discussion-comment-legacy +func (s *ReactionsService) CreateTeamDiscussionCommentReaction(ctx context.Context, teamID int64, discussionNumber, commentNumber int, content string) (*Reaction, *Response, error) { + u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v/reactions", teamID, discussionNumber, commentNumber) + + body := &Reaction{Content: String(content)} + req, err := s.client.NewRequest("POST", u, body) + if err != nil { + return nil, nil, err + } + + req.Header.Set("Accept", mediaTypeReactionsPreview) + + m := &Reaction{} + resp, err := s.client.Do(ctx, req, m) + if err != nil { + return nil, resp, err + } + + return m, resp, nil +} + +// DeleteTeamDiscussionCommentReaction deletes the reaction to a team discussion comment. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-team-discussion-comment-reaction +func (s *ReactionsService) DeleteTeamDiscussionCommentReaction(ctx context.Context, org, teamSlug string, discussionNumber, commentNumber int, reactionID int64) (*Response, error) { + url := fmt.Sprintf("orgs/%v/teams/%v/discussions/%v/comments/%v/reactions/%v", org, teamSlug, discussionNumber, commentNumber, reactionID) + + return s.deleteReaction(ctx, url) +} + +// DeleteTeamDiscussionCommentReactionByOrgIDAndTeamID deletes the reaction to a team discussion comment by organization ID and team ID. +// +// GitHub API docs: https://developer.github.com/v3/reactions/#delete-team-discussion-comment-reaction +func (s *ReactionsService) DeleteTeamDiscussionCommentReactionByOrgIDAndTeamID(ctx context.Context, orgID, teamID, discussionNumber, commentNumber int, reactionID int64) (*Response, error) { + url := fmt.Sprintf("organizations/%v/team/%v/discussions/%v/comments/%v/reactions/%v", orgID, teamID, discussionNumber, commentNumber, reactionID) + + return s.deleteReaction(ctx, url) +} + +func (s *ReactionsService) deleteReaction(ctx context.Context, url string) (*Response, error) { + req, err := s.client.NewRequest(http.MethodDelete, url, nil) + if err != nil { + return nil, err + } + + // TODO: remove custom Accept headers when APIs fully launch. + req.Header.Set("Accept", mediaTypeReactionsPreview) + + return s.client.Do(ctx, req, nil) +} +`