Repository: google/go-github
Pull request #1303 (2019-10-08 00:22:39)

Test for marshalling Organizations and make JSON comparison non sensitive to key order
This pull request contains two items:

1. Modifies the JSON comparison utility for checking marshalling, making it non sensible to key order.This makes it easier to write tests for marshalling, since there is no need to worry about keeping the right order of the JSON keys in the wanted string.  It also removes a non working part of the test. *I made sure old tests using this utility would still break if given the wrong input*.
2. Adds a new test for marshalling organizations (issue #55 )

codecov[bot]: # [Codecov](https://codecov.io/gh/google/go-github/pull/1303?src=pr&el=h1) Report
> Merging [#1303](https://codecov.io/gh/google/go-github/pull/1303?src=pr&el=desc) into [master](https://codecov.io/gh/google/go-github/commit/740806ae9dd43b6f68f872c299eedee17bcfa717?src=pr&el=desc) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/google/go-github/pull/1303/graphs/tree.svg?width=650&token=EgYjGjZlI0&height=150&src=pr)](https://codecov.io/gh/google/go-github/pull/1303?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master    #1303   +/-   ##
=======================================
  Coverage   73.42%   73.42%           
=======================================
  Files          86       86           
  Lines        6047     6047           
=======================================
  Hits         4440     4440           
  Misses        837      837           
  Partials      770      770
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/google/go-github/pull/1303?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/google/go-github/pull/1303?src=pr&el=footer). Last update [740806a...7d22998](https://codecov.io/gh/google/go-github/pull/1303?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.codecov.yml b/.codecov.yml
index 746db536..0f63adb3 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -1,3 +1,5 @@
 ignore:
   # ignore auto-generated code
   - "github/github-accessors.go"
+  # ignore experimental scrape package
+  - "scrape"
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index b1eff617..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-language: go
-
-go:
-  - "1.x"
-  - "1.12.x"
-  - master
-
-matrix:
-  allow_failures:
-    - go: master
-  fast_finish: true
-
-cache:
-  directories:
-    - $HOME/.cache/go-build
-    - $HOME/gopath/pkg/mod
-
-env:
-  global:
-    - GO111MODULE=on
-
-script:
-  - diff -u <(echo -n) <(gofmt -d -s .)
-  - go generate -x ./... && git diff --exit-code; code=$?; git checkout -- .; (exit $code) # Check that go generate ./... produces a zero diff; clean up any changes afterwards.
-  - go vet ./...
-  - go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
-  - go test -v -tags=integration -run=^$ ./test/integration # Check that integration test builds successfully, but don't run any of the tests (they hit live GitHub API).
-
-after_success:
-  - bash <(curl -s https://codecov.io/bash)
diff --git a/README.md b/README.md
index 66503b24..c6f57924 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,10 @@
 # go-github #
 
-[![GoDoc](https://godoc.org/github.com/google/go-github/github?status.svg)](https://godoc.org/github.com/google/go-github/github) [![Build Status](https://travis-ci.org/google/go-github.svg?branch=master)](https://travis-ci.org/google/go-github) [![Test Coverage](https://codecov.io/gh/google/go-github/branch/master/graph/badge.svg)](https://codecov.io/gh/google/go-github) [![Discuss at go-github@googlegroups.com](https://img.shields.io/badge/discuss-go--github%40googlegroups.com-blue.svg)](https://groups.google.com/group/go-github) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/796/badge)](https://bestpractices.coreinfrastructure.org/projects/796)
+[![GoDoc](https://godoc.org/github.com/google/go-github/github?status.svg)](https://godoc.org/github.com/google/go-github/github)
+[![Test Status](https://github.com/google/go-github/workflows/tests/badge.svg)](https://github.com/google/go-github/actions?query=workflow%3Atests)
+[![Test Coverage](https://codecov.io/gh/google/go-github/branch/master/graph/badge.svg)](https://codecov.io/gh/google/go-github)
+[![Discuss at go-github@googlegroups.com](https://img.shields.io/badge/discuss-go--github%40googlegroups.com-blue.svg)](https://groups.google.com/group/go-github)
+[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/796/badge)](https://bestpractices.coreinfrastructure.org/projects/796)
 
 go-github is a Go client library for accessing the [GitHub API v3][].
 
diff --git a/example/simple/main.go b/example/simple/main.go
index b10be06b..8bab9603 100644
--- a/example/simple/main.go
+++ b/example/simple/main.go
@@ -17,7 +17,7 @@ import (
 
 // Fetch all the public organizations' membership of a user.
 //
-func FetchOrganizations(username string) ([]*github.Organization, error) {
+func fetchOrganizations(username string) ([]*github.Organization, error) {
 	client := github.NewClient(nil)
 	orgs, _, err := client.Organizations.List(context.Background(), username, nil)
 	return orgs, err
@@ -28,7 +28,7 @@ func main() {
 	fmt.Print("Enter GitHub username: ")
 	fmt.Scanf("%s", &username)
 
-	organizations, err := FetchOrganizations(username)
+	organizations, err := fetchOrganizations(username)
 	if err != nil {
 		fmt.Printf("Error: %v\n", err)
 		return
diff --git a/example/topics/main.go b/example/topics/main.go
new file mode 100644
index 00000000..96098efe
--- /dev/null
+++ b/example/topics/main.go
@@ -0,0 +1,40 @@
+// Copyright 2019 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.
+
+// The simple command demonstrates a simple functionality which
+// prompts the user for a GitHub topic and lists all the public
+// organization memberships of the specified topic.
+package main
+
+import (
+	"context"
+	"fmt"
+
+	"github.com/google/go-github/v28/github"
+)
+
+// Fetch all the public organizations' membership of a user.
+//
+func FetchTopics(topic string) (*github.TopicsSearchResult, error) {
+	client := github.NewClient(nil)
+	topics, _, err := client.Search.Topics(context.Background(), topic, nil)
+	return topics, err
+}
+
+func main() {
+	var topic string
+	fmt.Print("Enter GitHub topic: ")
+	fmt.Scanf("%s", &topic)
+
+	topics, err := FetchTopics(topic)
+	if err != nil {
+		fmt.Printf("Error: %v\n", err)
+		return
+	}
+
+	for _, topic := range topics.Topics {
+		fmt.Println(*topic.Name)
+	}
+}
diff --git a/github/admin.go b/github/admin.go
index 2d96733a..31cd1fa2 100644
--- a/github/admin.go
+++ b/github/admin.go
@@ -62,6 +62,24 @@ func (m UserLDAPMapping) String() string {
 	return Stringify(m)
 }
 
+// Enterprise represents the GitHub enterprise profile.
+type Enterprise struct {
+	ID          *int       `json:"id,omitempty"`
+	Slug        *string    `json:"slug,omitempty"`
+	Name        *string    `json:"name,omitempty"`
+	NodeID      *string    `json:"node_id,omitempty"`
+	AvatarURL   *string    `json:"avatar_url,omitempty"`
+	Description *string    `json:"description,omitempty"`
+	WebsiteURL  *string    `json:"website_url,omitempty"`
+	HTMLURL     *string    `json:"html_url,omitempty"`
+	CreatedAt   *Timestamp `json:"created_at,omitempty"`
+	UpdatedAt   *Timestamp `json:"updated_at,omitempty"`
+}
+
+func (m Enterprise) String() string {
+	return Stringify(m)
+}
+
 // UpdateUserLDAPMapping updates the mapping between a GitHub user and an LDAP user.
 //
 // GitHub API docs: https://developer.github.com/v3/enterprise/ldap/#update-ldap-mapping-for-a-user
diff --git a/github/admin_users.go b/github/admin_users.go
index ea7a47d3..74285550 100644
--- a/github/admin_users.go
+++ b/github/admin_users.go
@@ -7,6 +7,7 @@ package github
 
 import (
 	"context"
+	"fmt"
 )
 
 // createUserRequest is a subset of User and is used internally
@@ -59,3 +60,74 @@ func (s *AdminService) DeleteUser(ctx context.Context, username string) (*Respon
 
 	return resp, nil
 }
+
+// ImpersonateUserOptions represents the scoping for the OAuth token.
+type ImpersonateUserOptions struct {
+	Scopes []string `json:"scopes,omitempty"`
+}
+
+// OAuthAPP represents the GitHub Site Administrator OAuth app.
+type OAuthAPP struct {
+	URL      *string `json:"url,omitempty"`
+	Name     *string `json:"name,omitempty"`
+	ClientID *string `json:"client_id,omitempty"`
+}
+
+func (s OAuthAPP) String() string {
+	return Stringify(s)
+}
+
+// UserAuthorization represents the impersonation response.
+type UserAuthorization struct {
+	ID             *int64     `json:"id,omitempty"`
+	URL            *string    `json:"url,omitempty"`
+	Scopes         []string   `json:"scopes,omitempty"`
+	Token          *string    `json:"token,omitempty"`
+	TokenLastEight *string    `json:"token_last_eight,omitempty"`
+	HashedToken    *string    `json:"hashed_token,omitempty"`
+	App            *OAuthAPP  `json:"app,omitempty"`
+	Note           *string    `json:"note,omitempty"`
+	NoteURL        *string    `json:"note_url,omitempty"`
+	UpdatedAt      *Timestamp `json:"updated_at,omitempty"`
+	CreatedAt      *Timestamp `json:"created_at,omitempty"`
+	Fingerprint    *string    `json:"fingerprint,omitempty"`
+}
+
+// CreateUserImpersonation creates an impersonation OAuth token.
+//
+// GitHub Enterprise API docs: https://developer.github.com/enterprise/v3/enterprise-admin/users/#create-an-impersonation-oauth-token
+func (s *AdminService) CreateUserImpersonation(ctx context.Context, username string, opt *ImpersonateUserOptions) (*UserAuthorization, *Response, error) {
+	u := fmt.Sprintf("admin/users/%s/authorizations", username)
+
+	req, err := s.client.NewRequest("POST", u, opt)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	a := new(UserAuthorization)
+	resp, err := s.client.Do(ctx, req, a)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return a, resp, nil
+}
+
+// DeleteUserImpersonation deletes an impersonation OAuth token.
+//
+// GitHub Enterprise API docs: https://developer.github.com/enterprise/v3/enterprise-admin/users/#delete-an-impersonation-oauth-token
+func (s *AdminService) DeleteUserImpersonation(ctx context.Context, username string) (*Response, error) {
+	u := fmt.Sprintf("admin/users/%s/authorizations", username)
+
+	req, err := s.client.NewRequest("DELETE", u, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := s.client.Do(ctx, req, nil)
+	if err != nil {
+		return resp, err
+	}
+
+	return resp, nil
+}
diff --git a/github/apps.go b/github/apps.go
index 5041e801..251ac631 100644
--- a/github/apps.go
+++ b/github/apps.go
@@ -19,15 +19,18 @@ type AppsService service
 
 // App represents a GitHub App.
 type App struct {
-	ID          *int64     `json:"id,omitempty"`
-	NodeID      *string    `json:"node_id,omitempty"`
-	Owner       *User      `json:"owner,omitempty"`
-	Name        *string    `json:"name,omitempty"`
-	Description *string    `json:"description,omitempty"`
-	ExternalURL *string    `json:"external_url,omitempty"`
-	HTMLURL     *string    `json:"html_url,omitempty"`
-	CreatedAt   *Timestamp `json:"created_at,omitempty"`
-	UpdatedAt   *Timestamp `json:"updated_at,omitempty"`
+	ID          *int64                   `json:"id,omitempty"`
+	Slug        *string                  `json:"slug,omitempty"`
+	NodeID      *string                  `json:"node_id,omitempty"`
+	Owner       *User                    `json:"owner,omitempty"`
+	Name        *string                  `json:"name,omitempty"`
+	Description *string                  `json:"description,omitempty"`
+	ExternalURL *string                  `json:"external_url,omitempty"`
+	HTMLURL     *string                  `json:"html_url,omitempty"`
+	CreatedAt   *Timestamp               `json:"created_at,omitempty"`
+	UpdatedAt   *Timestamp               `json:"updated_at,omitempty"`
+	Permissions *InstallationPermissions `json:"permissions,omitempty"`
+	Events      []*Event                 `json:"events,omitempty"`
 }
 
 // InstallationToken represents an installation token.
@@ -56,10 +59,13 @@ type InstallationTokenOptions struct {
 //   https://developer.github.com/enterprise/v3/apps/permissions/
 type InstallationPermissions struct {
 	Administration              *string `json:"administration,omitempty"`
+	Blocking                    *string `json:"blocking,omitempty"`
 	Checks                      *string `json:"checks,omitempty"`
 	Contents                    *string `json:"contents,omitempty"`
 	ContentReferences           *string `json:"content_references,omitempty"`
 	Deployments                 *string `json:"deployments,omitempty"`
+	Emails                      *string `json:"emails,omitempty"`
+	Followers                   *string `json:"followers,omitempty"`
 	Issues                      *string `json:"issues,omitempty"`
 	Metadata                    *string `json:"metadata,omitempty"`
 	Members                     *string `json:"members,omitempty"`
@@ -145,7 +151,7 @@ func (s *AppsService) Get(ctx context.Context, appSlug string) (*App, *Response,
 
 // ListInstallations lists the installations that the current GitHub App has.
 //
-// GitHub API docs: https://developer.github.com/v3/apps/#find-installations
+// GitHub API docs: https://developer.github.com/v3/apps/#list-installations
 func (s *AppsService) ListInstallations(ctx context.Context, opt *ListOptions) ([]*Installation, *Response, error) {
 	u, err := addOptions("app/installations", opt)
 	if err != nil {
@@ -227,7 +233,7 @@ func (s *AppsService) CreateInstallationToken(ctx context.Context, id int64, opt
 	return t, resp, nil
 }
 
-// Create a new attachment on user comment containing a url.
+// CreateAttachment creates a new attachment on user comment containing a url.
 //
 // GitHub API docs: https://developer.github.com/v3/apps/#create-a-content-attachment
 func (s *AppsService) CreateAttachment(ctx context.Context, contentReferenceID int64, title, body string) (*Attachment, *Response, error) {
@@ -252,14 +258,14 @@ func (s *AppsService) CreateAttachment(ctx context.Context, contentReferenceID i
 
 // FindOrganizationInstallation finds the organization's installation information.
 //
-// GitHub API docs: https://developer.github.com/v3/apps/#find-organization-installation
+// GitHub API docs: https://developer.github.com/v3/apps/#get-an-organization-installation
 func (s *AppsService) FindOrganizationInstallation(ctx context.Context, org string) (*Installation, *Response, error) {
 	return s.getInstallation(ctx, fmt.Sprintf("orgs/%v/installation", org))
 }
 
 // FindRepositoryInstallation finds the repository's installation information.
 //
-// GitHub API docs: https://developer.github.com/v3/apps/#find-repository-installation
+// GitHub API docs: https://developer.github.com/v3/apps/#get-a-repository-installation
 func (s *AppsService) FindRepositoryInstallation(ctx context.Context, owner, repo string) (*Installation, *Response, error) {
 	return s.getInstallation(ctx, fmt.Sprintf("repos/%v/%v/installation", owner, repo))
 }
@@ -273,7 +279,7 @@ func (s *AppsService) FindRepositoryInstallationByID(ctx context.Context, id int
 
 // FindUserInstallation finds the user's installation information.
 //
-// GitHub API docs: https://developer.github.com/v3/apps/#find-repository-installation
+// GitHub API docs: https://developer.github.com/v3/apps/#get-a-user-installation
 func (s *AppsService) FindUserInstallation(ctx context.Context, user string) (*Installation, *Response, error) {
 	return s.getInstallation(ctx, fmt.Sprintf("users/%v/installation", user))
 }
diff --git a/github/apps_manifest.go b/github/apps_manifest.go
new file mode 100644
index 00000000..8d6ab3c8
--- /dev/null
+++ b/github/apps_manifest.go
@@ -0,0 +1,53 @@
+// Copyright 2019 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"
+)
+
+const (
+	mediaTypeAppManifestPreview = "application/vnd.github.fury-preview+json"
+)
+
+// AppConfig describes the configuration of a GitHub App.
+type AppConfig struct {
+	ID            *int64     `json:"id,omitempty"`
+	NodeID        *string    `json:"node_id,omitempty"`
+	Owner         *User      `json:"owner,omitempty"`
+	Name          *string    `json:"name,omitempty"`
+	Description   *string    `json:"description,omitempty"`
+	ExternalURL   *string    `json:"external_url,omitempty"`
+	HTMLURL       *string    `json:"html_url,omitempty"`
+	CreatedAt     *Timestamp `json:"created_at,omitempty"`
+	UpdatedAt     *Timestamp `json:"updated_at,omitempty"`
+	ClientID      *string    `json:"client_id,omitempty"`
+	ClientSecret  *string    `json:"client_secret,omitempty"`
+	WebhookSecret *string    `json:"webhook_secret,omitempty"`
+	PEM           *string    `json:"pem,omitempty"`
+}
+
+// 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
+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)
+	if err != nil {
+		return nil, nil, err
+	}
+	req.Header.Set("Accept", mediaTypeAppManifestPreview)
+
+	cfg := new(AppConfig)
+	resp, err := s.client.Do(ctx, req, cfg)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return cfg, resp, nil
+}
diff --git a/github/checks.go b/github/checks.go
index 84f84034..1acc26e9 100644
--- a/github/checks.go
+++ b/github/checks.go
@@ -140,7 +140,6 @@ func (s *ChecksService) GetCheckSuite(ctx context.Context, owner, repo string, c
 // CreateCheckRunOptions sets up parameters needed to create a CheckRun.
 type CreateCheckRunOptions struct {
 	Name        string            `json:"name"`                   // The name of the check (e.g., "code-coverage"). (Required.)
-	HeadBranch  string            `json:"head_branch"`            // The name of the branch to perform a check against. (Required.)
 	HeadSHA     string            `json:"head_sha"`               // The SHA of the commit. (Required.)
 	DetailsURL  *string           `json:"details_url,omitempty"`  // The URL of the integrator's site that has the full details of the check. (Optional.)
 	ExternalID  *string           `json:"external_id,omitempty"`  // A reference for the run on the integrator's system. (Optional.)
@@ -183,7 +182,6 @@ func (s *ChecksService) CreateCheckRun(ctx context.Context, owner, repo string,
 // UpdateCheckRunOptions sets up parameters needed to update a CheckRun.
 type UpdateCheckRunOptions struct {
 	Name        string            `json:"name"`                   // The name of the check (e.g., "code-coverage"). (Required.)
-	HeadBranch  *string           `json:"head_branch,omitempty"`  // The name of the branch to perform a check against. (Optional.)
 	HeadSHA     *string           `json:"head_sha,omitempty"`     // The SHA of the commit. (Optional.)
 	DetailsURL  *string           `json:"details_url,omitempty"`  // The URL of the integrator's site that has the full details of the check. (Optional.)
 	ExternalID  *string           `json:"external_id,omitempty"`  // A reference for the run on the integrator's system. (Optional.)
diff --git a/github/event.go b/github/event.go
index 0ba8f46a..de20f584 100644
--- a/github/event.go
+++ b/github/event.go
@@ -110,6 +110,8 @@ func (e *Event) ParsePayload() (payload interface{}, err error) {
 		payload = &TeamEvent{}
 	case "TeamAddEvent":
 		payload = &TeamAddEvent{}
+	case "UserEvent":
+		payload = &UserEvent{}
 	case "WatchEvent":
 		payload = &WatchEvent{}
 	}
diff --git a/github/event_types.go b/github/event_types.go
index d3e21235..a04c7f62 100644
--- a/github/event_types.go
+++ b/github/event_types.go
@@ -738,7 +738,7 @@ type PushEventRepoOwner struct {
 }
 
 // ReleaseEvent is triggered when a release is published, unpublished, created,
-// edited, deleted, or prerelased.
+// edited, deleted, or prereleased.
 // The Webhook event name is "release".
 //
 // GitHub API docs: https://developer.github.com/v3/activity/events/types/#releaseevent
@@ -872,6 +872,20 @@ type TeamAddEvent struct {
 	Installation *Installation `json:"installation,omitempty"`
 }
 
+// UserEvent is triggered when a user is created or deleted.
+// The Webhook event name is "user".
+//
+// Only global webhooks can subscribe to this event type.
+//
+// GitHub API docs: https://developer.github.com/enterprise/v3/activity/events/types/#userevent-enterprise
+type UserEvent struct {
+	User *User `json:"user,omitempty"`
+	// The action performed. Possible values are: "created" or "deleted".
+	Action     *string     `json:"action,omitempty"`
+	Enterprise *Enterprise `json:"enterprise,omitempty"`
+	Sender     *User       `json:"sender,omitempty"`
+}
+
 // WatchEvent is related to starring a repository, not watching. See this API
 // blog post for an explanation: https://developer.github.com/changes/2012-09-05-watcher-api/
 //
diff --git a/github/git_trees.go b/github/git_trees.go
index 4bc29135..df843f4d 100644
--- a/github/git_trees.go
+++ b/github/git_trees.go
@@ -7,6 +7,7 @@ package github
 
 import (
 	"context"
+	"encoding/json"
 	"fmt"
 )
 
@@ -43,6 +44,39 @@ func (t TreeEntry) String() string {
 	return Stringify(t)
 }
 
+func (t *TreeEntry) MarshalJSON() ([]byte, error) {
+	if t.SHA == nil && t.Content == nil {
+		return json.Marshal(struct {
+			SHA  *string `json:"sha"`
+			Path *string `json:"path,omitempty"`
+			Mode *string `json:"mode,omitempty"`
+			Type *string `json:"type,omitempty"`
+		}{
+			nil,
+			t.Path,
+			t.Mode,
+			t.Type,
+		})
+	}
+	return json.Marshal(struct {
+		SHA     *string `json:"sha,omitempty"`
+		Path    *string `json:"path,omitempty"`
+		Mode    *string `json:"mode,omitempty"`
+		Type    *string `json:"type,omitempty"`
+		Size    *int    `json:"size,omitempty"`
+		Content *string `json:"content,omitempty"`
+		URL     *string `json:"url,omitempty"`
+	}{
+		SHA:     t.SHA,
+		Path:    t.Path,
+		Mode:    t.Mode,
+		Type:    t.Type,
+		Size:    t.Size,
+		Content: t.Content,
+		URL:     t.URL,
+	})
+}
+
 // GetTree fetches the Tree object for a given sha hash from a repository.
 //
 // GitHub API docs: https://developer.github.com/v3/git/trees/#get-a-tree
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 9e5a3a6b..3805905e 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -180,6 +180,22 @@ func (a *App) GetOwner() *User {
 	return a.Owner
 }
 
+// GetPermissions returns the Permissions field.
+func (a *App) GetPermissions() *InstallationPermissions {
+	if a == nil {
+		return nil
+	}
+	return a.Permissions
+}
+
+// GetSlug returns the Slug field if it's non-nil, zero value otherwise.
+func (a *App) GetSlug() string {
+	if a == nil || a.Slug == nil {
+		return ""
+	}
+	return *a.Slug
+}
+
 // GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise.
 func (a *App) GetUpdatedAt() Timestamp {
 	if a == nil || a.UpdatedAt == nil {
@@ -188,6 +204,110 @@ func (a *App) GetUpdatedAt() Timestamp {
 	return *a.UpdatedAt
 }
 
+// GetClientID returns the ClientID field if it's non-nil, zero value otherwise.
+func (a *AppConfig) GetClientID() string {
+	if a == nil || a.ClientID == nil {
+		return ""
+	}
+	return *a.ClientID
+}
+
+// GetClientSecret returns the ClientSecret field if it's non-nil, zero value otherwise.
+func (a *AppConfig) GetClientSecret() string {
+	if a == nil || a.ClientSecret == nil {
+		return ""
+	}
+	return *a.ClientSecret
+}
+
+// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
+func (a *AppConfig) GetCreatedAt() Timestamp {
+	if a == nil || a.CreatedAt == nil {
+		return Timestamp{}
+	}
+	return *a.CreatedAt
+}
+
+// GetDescription returns the Description field if it's non-nil, zero value otherwise.
+func (a *AppConfig) GetDescription() string {
+	if a == nil || a.Description == nil {
+		return ""
+	}
+	return *a.Description
+}
+
+// GetExternalURL returns the ExternalURL field if it's non-nil, zero value otherwise.
+func (a *AppConfig) GetExternalURL() string {
+	if a == nil || a.ExternalURL == nil {
+		return ""
+	}
+	return *a.ExternalURL
+}
+
+// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise.
+func (a *AppConfig) GetHTMLURL() string {
+	if a == nil || a.HTMLURL == nil {
+		return ""
+	}
+	return *a.HTMLURL
+}
+
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (a *AppConfig) GetID() int64 {
+	if a == nil || a.ID == nil {
+		return 0
+	}
+	return *a.ID
+}
+
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (a *AppConfig) GetName() string {
+	if a == nil || a.Name == nil {
+		return ""
+	}
+	return *a.Name
+}
+
+// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise.
+func (a *AppConfig) GetNodeID() string {
+	if a == nil || a.NodeID == nil {
+		return ""
+	}
+	return *a.NodeID
+}
+
+// GetOwner returns the Owner field.
+func (a *AppConfig) GetOwner() *User {
+	if a == nil {
+		return nil
+	}
+	return a.Owner
+}
+
+// GetPEM returns the PEM field if it's non-nil, zero value otherwise.
+func (a *AppConfig) GetPEM() string {
+	if a == nil || a.PEM == nil {
+		return ""
+	}
+	return *a.PEM
+}
+
+// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise.
+func (a *AppConfig) GetUpdatedAt() Timestamp {
+	if a == nil || a.UpdatedAt == nil {
+		return Timestamp{}
+	}
+	return *a.UpdatedAt
+}
+
+// GetWebhookSecret returns the WebhookSecret field if it's non-nil, zero value otherwise.
+func (a *AppConfig) GetWebhookSecret() string {
+	if a == nil || a.WebhookSecret == nil {
+		return ""
+	}
+	return *a.WebhookSecret
+}
+
 // GetBody returns the Body field if it's non-nil, zero value otherwise.
 func (a *Attachment) GetBody() string {
 	if a == nil || a.Body == nil {
@@ -1988,6 +2108,14 @@ func (c *CreateOrgInvitationOptions) GetRole() string {
 	return *c.Role
 }
 
+// GetBody returns the Body field if it's non-nil, zero value otherwise.
+func (c *CreateUserProjectOptions) GetBody() string {
+	if c == nil || c.Body == nil {
+		return ""
+	}
+	return *c.Body
+}
+
 // GetInstallation returns the Installation field.
 func (d *DeleteEvent) GetInstallation() *Installation {
 	if d == nil {
@@ -2604,6 +2732,86 @@ func (d *DraftReviewComment) GetPosition() int {
 	return *d.Position
 }
 
+// GetAvatarURL returns the AvatarURL field if it's non-nil, zero value otherwise.
+func (e *Enterprise) GetAvatarURL() string {
+	if e == nil || e.AvatarURL == nil {
+		return ""
+	}
+	return *e.AvatarURL
+}
+
+// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
+func (e *Enterprise) GetCreatedAt() Timestamp {
+	if e == nil || e.CreatedAt == nil {
+		return Timestamp{}
+	}
+	return *e.CreatedAt
+}
+
+// GetDescription returns the Description field if it's non-nil, zero value otherwise.
+func (e *Enterprise) GetDescription() string {
+	if e == nil || e.Description == nil {
+		return ""
+	}
+	return *e.Description
+}
+
+// GetHTMLURL returns the HTMLURL field if it's non-nil, zero value otherwise.
+func (e *Enterprise) GetHTMLURL() string {
+	if e == nil || e.HTMLURL == nil {
+		return ""
+	}
+	return *e.HTMLURL
+}
+
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (e *Enterprise) GetID() int {
+	if e == nil || e.ID == nil {
+		return 0
+	}
+	return *e.ID
+}
+
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (e *Enterprise) GetName() string {
+	if e == nil || e.Name == nil {
+		return ""
+	}
+	return *e.Name
+}
+
+// GetNodeID returns the NodeID field if it's non-nil, zero value otherwise.
+func (e *Enterprise) GetNodeID() string {
+	if e == nil || e.NodeID == nil {
+		return ""
+	}
+	return *e.NodeID
+}
+
+// GetSlug returns the Slug field if it's non-nil, zero value otherwise.
+func (e *Enterprise) GetSlug() string {
+	if e == nil || e.Slug == nil {
+		return ""
+	}
+	return *e.Slug
+}
+
+// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise.
+func (e *Enterprise) GetUpdatedAt() Timestamp {
+	if e == nil || e.UpdatedAt == nil {
+		return Timestamp{}
+	}
+	return *e.UpdatedAt
+}
+
+// GetWebsiteURL returns the WebsiteURL field if it's non-nil, zero value otherwise.
+func (e *Enterprise) GetWebsiteURL() string {
+	if e == nil || e.WebsiteURL == nil {
+		return ""
+	}
+	return *e.WebsiteURL
+}
+
 // GetActor returns the Actor field.
 func (e *Event) GetActor() *User {
 	if e == nil {
@@ -3676,6 +3884,14 @@ func (i *InstallationPermissions) GetAdministration() string {
 	return *i.Administration
 }
 
+// GetBlocking returns the Blocking field if it's non-nil, zero value otherwise.
+func (i *InstallationPermissions) GetBlocking() string {
+	if i == nil || i.Blocking == nil {
+		return ""
+	}
+	return *i.Blocking
+}
+
 // GetChecks returns the Checks field if it's non-nil, zero value otherwise.
 func (i *InstallationPermissions) GetChecks() string {
 	if i == nil || i.Checks == nil {
@@ -3708,6 +3924,22 @@ func (i *InstallationPermissions) GetDeployments() string {
 	return *i.Deployments
 }
 
+// GetEmails returns the Emails field if it's non-nil, zero value otherwise.
+func (i *InstallationPermissions) GetEmails() string {
+	if i == nil || i.Emails == nil {
+		return ""
+	}
+	return *i.Emails
+}
+
+// GetFollowers returns the Followers field if it's non-nil, zero value otherwise.
+func (i *InstallationPermissions) GetFollowers() string {
+	if i == nil || i.Followers == nil {
+		return ""
+	}
+	return *i.Followers
+}
+
 // GetIssues returns the Issues field if it's non-nil, zero value otherwise.
 func (i *InstallationPermissions) GetIssues() string {
 	if i == nil || i.Issues == nil {
@@ -4484,6 +4716,30 @@ func (i *IssueEvent) GetURL() string {
 	return *i.URL
 }
 
+// GetDirection returns the Direction field if it's non-nil, zero value otherwise.
+func (i *IssueListCommentsOptions) GetDirection() string {
+	if i == nil || i.Direction == nil {
+		return ""
+	}
+	return *i.Direction
+}
+
+// GetSince returns the Since field if it's non-nil, zero value otherwise.
+func (i *IssueListCommentsOptions) GetSince() time.Time {
+	if i == nil || i.Since == nil {
+		return time.Time{}
+	}
+	return *i.Since
+}
+
+// GetSort returns the Sort field if it's non-nil, zero value otherwise.
+func (i *IssueListCommentsOptions) GetSort() string {
+	if i == nil || i.Sort == nil {
+		return ""
+	}
+	return *i.Sort
+}
+
 // GetAssignee returns the Assignee field if it's non-nil, zero value otherwise.
 func (i *IssueRequest) GetAssignee() string {
 	if i == nil || i.Assignee == nil {
@@ -6036,6 +6292,30 @@ func (n *NotificationSubject) GetURL() string {
 	return *n.URL
 }
 
+// GetClientID returns the ClientID field if it's non-nil, zero value otherwise.
+func (o *OAuthAPP) GetClientID() string {
+	if o == nil || o.ClientID == nil {
+		return ""
+	}
+	return *o.ClientID
+}
+
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (o *OAuthAPP) GetName() string {
+	if o == nil || o.Name == nil {
+		return ""
+	}
+	return *o.Name
+}
+
+// GetURL returns the URL field if it's non-nil, zero value otherwise.
+func (o *OAuthAPP) GetURL() string {
+	if o == nil || o.URL == nil {
+		return ""
+	}
+	return *o.URL
+}
+
 // GetAvatarURL returns the AvatarURL field if it's non-nil, zero value otherwise.
 func (o *Organization) GetAvatarURL() string {
 	if o == nil || o.AvatarURL == nil {
@@ -6380,6 +6660,14 @@ func (o *OrganizationEvent) GetSender() *User {
 	return o.Sender
 }
 
+// GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise.
+func (o *OrganizationInstallations) GetTotalCount() int {
+	if o == nil || o.TotalCount == nil {
+		return 0
+	}
+	return *o.TotalCount
+}
+
 // GetAction returns the Action field if it's non-nil, zero value otherwise.
 func (o *OrgBlockEvent) GetAction() string {
 	if o == nil || o.Action == nil {
@@ -6724,6 +7012,14 @@ func (p *Plan) GetCollaborators() int {
 	return *p.Collaborators
 }
 
+// GetFilledSeats returns the FilledSeats field if it's non-nil, zero value otherwise.
+func (p *Plan) GetFilledSeats() int {
+	if p == nil || p.FilledSeats == nil {
+		return 0
+	}
+	return *p.FilledSeats
+}
+
 // GetName returns the Name field if it's non-nil, zero value otherwise.
 func (p *Plan) GetName() string {
 	if p == nil || p.Name == nil {
@@ -6740,6 +7036,14 @@ func (p *Plan) GetPrivateRepos() int {
 	return *p.PrivateRepos
 }
 
+// GetSeats returns the Seats field if it's non-nil, zero value otherwise.
+func (p *Plan) GetSeats() int {
+	if p == nil || p.Seats == nil {
+		return 0
+	}
+	return *p.Seats
+}
+
 // GetSpace returns the Space field if it's non-nil, zero value otherwise.
 func (p *Plan) GetSpace() int {
 	if p == nil || p.Space == nil {
@@ -8420,6 +8724,14 @@ func (p *PullRequestReviewRequest) GetNodeID() string {
 	return *p.NodeID
 }
 
+// GetDismissalRestrictions returns the DismissalRestrictions field.
+func (p *PullRequestReviewsEnforcement) GetDismissalRestrictions() *DismissalRestrictions {
+	if p == nil {
+		return nil
+	}
+	return p.DismissalRestrictions
+}
+
 // GetDismissalRestrictionsRequest returns the DismissalRestrictionsRequest field.
 func (p *PullRequestReviewsEnforcementRequest) GetDismissalRestrictionsRequest() *DismissalRestrictionsRequest {
 	if p == nil {
@@ -11916,6 +12228,102 @@ func (t *Timeline) GetURL() string {
 	return *t.URL
 }
 
+// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
+func (t *TopicResult) GetCreatedAt() Timestamp {
+	if t == nil || t.CreatedAt == nil {
+		return Timestamp{}
+	}
+	return *t.CreatedAt
+}
+
+// GetCreatedBy returns the CreatedBy field if it's non-nil, zero value otherwise.
+func (t *TopicResult) GetCreatedBy() string {
+	if t == nil || t.CreatedBy == nil {
+		return ""
+	}
+	return *t.CreatedBy
+}
+
+// GetCurated returns the Curated field if it's non-nil, zero value otherwise.
+func (t *TopicResult) GetCurated() bool {
+	if t == nil || t.Curated == nil {
+		return false
+	}
+	return *t.Curated
+}
+
+// GetDescription returns the Description field if it's non-nil, zero value otherwise.
+func (t *TopicResult) GetDescription() string {
+	if t == nil || t.Description == nil {
+		return ""
+	}
+	return *t.Description
+}
+
+// GetDisplayName returns the DisplayName field if it's non-nil, zero value otherwise.
+func (t *TopicResult) GetDisplayName() string {
+	if t == nil || t.DisplayName == nil {
+		return ""
+	}
+	return *t.DisplayName
+}
+
+// GetFeatured returns the Featured field if it's non-nil, zero value otherwise.
+func (t *TopicResult) GetFeatured() bool {
+	if t == nil || t.Featured == nil {
+		return false
+	}
+	return *t.Featured
+}
+
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (t *TopicResult) GetName() string {
+	if t == nil || t.Name == nil {
+		return ""
+	}
+	return *t.Name
+}
+
+// GetScore returns the Score field.
+func (t *TopicResult) GetScore() *float64 {
+	if t == nil {
+		return nil
+	}
+	return t.Score
+}
+
+// GetShortDescription returns the ShortDescription field if it's non-nil, zero value otherwise.
+func (t *TopicResult) GetShortDescription() string {
+	if t == nil || t.ShortDescription == nil {
+		return ""
+	}
+	return *t.ShortDescription
+}
+
+// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise.
+func (t *TopicResult) GetUpdatedAt() string {
+	if t == nil || t.UpdatedAt == nil {
+		return ""
+	}
+	return *t.UpdatedAt
+}
+
+// GetIncompleteResults returns the IncompleteResults field if it's non-nil, zero value otherwise.
+func (t *TopicsSearchResult) GetIncompleteResults() bool {
+	if t == nil || t.IncompleteResults == nil {
+		return false
+	}
+	return *t.IncompleteResults
+}
+
+// GetTotal returns the Total field if it's non-nil, zero value otherwise.
+func (t *TopicsSearchResult) GetTotal() int {
+	if t == nil || t.Total == nil {
+		return 0
+	}
+	return *t.Total
+}
+
 // GetCount returns the Count field if it's non-nil, zero value otherwise.
 func (t *TrafficClones) GetCount() int {
 	if t == nil || t.Count == nil {
@@ -12132,14 +12540,6 @@ func (u *UpdateCheckRunOptions) GetExternalID() string {
 	return *u.ExternalID
 }
 
-// GetHeadBranch returns the HeadBranch field if it's non-nil, zero value otherwise.
-func (u *UpdateCheckRunOptions) GetHeadBranch() string {
-	if u == nil || u.HeadBranch == nil {
-		return ""
-	}
-	return *u.HeadBranch
-}
-
 // GetHeadSHA returns the HeadSHA field if it's non-nil, zero value otherwise.
 func (u *UpdateCheckRunOptions) GetHeadSHA() string {
 	if u == nil || u.HeadSHA == nil {
@@ -12492,6 +12892,94 @@ func (u *User) GetURL() string {
 	return *u.URL
 }
 
+// GetApp returns the App field.
+func (u *UserAuthorization) GetApp() *OAuthAPP {
+	if u == nil {
+		return nil
+	}
+	return u.App
+}
+
+// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
+func (u *UserAuthorization) GetCreatedAt() Timestamp {
+	if u == nil || u.CreatedAt == nil {
+		return Timestamp{}
+	}
+	return *u.CreatedAt
+}
+
+// GetFingerprint returns the Fingerprint field if it's non-nil, zero value otherwise.
+func (u *UserAuthorization) GetFingerprint() string {
+	if u == nil || u.Fingerprint == nil {
+		return ""
+	}
+	return *u.Fingerprint
+}
+
+// GetHashedToken returns the HashedToken field if it's non-nil, zero value otherwise.
+func (u *UserAuthorization) GetHashedToken() string {
+	if u == nil || u.HashedToken == nil {
+		return ""
+	}
+	return *u.HashedToken
+}
+
+// GetID returns the ID field if it's non-nil, zero value otherwise.
+func (u *UserAuthorization) GetID() int64 {
+	if u == nil || u.ID == nil {
+		return 0
+	}
+	return *u.ID
+}
+
+// GetNote returns the Note field if it's non-nil, zero value otherwise.
+func (u *UserAuthorization) GetNote() string {
+	if u == nil || u.Note == nil {
+		return ""
+	}
+	return *u.Note
+}
+
+// GetNoteURL returns the NoteURL field if it's non-nil, zero value otherwise.
+func (u *UserAuthorization) GetNoteURL() string {
+	if u == nil || u.NoteURL == nil {
+		return ""
+	}
+	return *u.NoteURL
+}
+
+// GetToken returns the Token field if it's non-nil, zero value otherwise.
+func (u *UserAuthorization) GetToken() string {
+	if u == nil || u.Token == nil {
+		return ""
+	}
+	return *u.Token
+}
+
+// GetTokenLastEight returns the TokenLastEight field if it's non-nil, zero value otherwise.
+func (u *UserAuthorization) GetTokenLastEight() string {
+	if u == nil || u.TokenLastEight == nil {
+		return ""
+	}
+	return *u.TokenLastEight
+}
+
+// GetUpdatedAt returns the UpdatedAt field if it's non-nil, zero value otherwise.
+func (u *UserAuthorization) GetUpdatedAt() Timestamp {
+	if u == nil || u.UpdatedAt == nil {
+		return Timestamp{}
+	}
+	return *u.UpdatedAt
+}
+
+// GetURL returns the URL field if it's non-nil, zero value otherwise.
+func (u *UserAuthorization) GetURL() string {
+	if u == nil || u.URL == nil {
+		return ""
+	}
+	return *u.URL
+}
+
 // GetMessage returns the Message field if it's non-nil, zero value otherwise.
 func (u *UserContext) GetMessage() string {
 	if u == nil || u.Message == nil {
@@ -12540,6 +13028,38 @@ func (u *UserEmail) GetVisibility() string {
 	return *u.Visibility
 }
 
+// GetAction returns the Action field if it's non-nil, zero value otherwise.
+func (u *UserEvent) GetAction() string {
+	if u == nil || u.Action == nil {
+		return ""
+	}
+	return *u.Action
+}
+
+// GetEnterprise returns the Enterprise field.
+func (u *UserEvent) GetEnterprise() *Enterprise {
+	if u == nil {
+		return nil
+	}
+	return u.Enterprise
+}
+
+// GetSender returns the Sender field.
+func (u *UserEvent) GetSender() *User {
+	if u == nil {
+		return nil
+	}
+	return u.Sender
+}
+
+// GetUser returns the User field.
+func (u *UserEvent) GetUser() *User {
+	if u == nil {
+		return nil
+	}
+	return u.User
+}
+
 // GetAvatarURL returns the AvatarURL field if it's non-nil, zero value otherwise.
 func (u *UserLDAPMapping) GetAvatarURL() string {
 	if u == nil || u.AvatarURL == nil {
diff --git a/github/github.go b/github/github.go
index 0580271c..3f4edd4a 100644
--- a/github/github.go
+++ b/github/github.go
@@ -67,9 +67,6 @@ const (
 	// https://developer.github.com/changes/2016-05-23-timeline-preview-api/
 	mediaTypeTimelinePreview = "application/vnd.github.mockingbird-preview+json"
 
-	// https://developer.github.com/changes/2016-07-06-github-pages-preiew-api/
-	mediaTypePagesPreview = "application/vnd.github.mister-fantastic-preview+json"
-
 	// https://developer.github.com/changes/2016-09-14-projects-api/
 	mediaTypeProjectsPreview = "application/vnd.github.inertia-preview+json"
 
@@ -148,9 +145,6 @@ const (
 	// https://developer.github.com/changes/2019-04-11-pulls-branches-for-commit/
 	mediaTypeListPullsOrBranchesForCommitPreview = "application/vnd.github.groot-preview+json"
 
-	// https://developer.github.com/changes/2019-06-12-team-sync/
-	mediaTypeTeamSyncPreview = "application/vnd.github.team-sync-preview+json"
-
 	// https://developer.github.com/v3/previews/#repository-creation-permissions
 	mediaTypeMemberAllowedRepoCreationTypePreview = "application/vnd.github.surtur-preview+json"
 
@@ -648,7 +642,7 @@ type TwoFactorAuthError ErrorResponse
 func (r *TwoFactorAuthError) Error() string { return (*ErrorResponse)(r).Error() }
 
 // RateLimitError occurs when GitHub returns 403 Forbidden response with a rate limit
-// remaining value of 0, and error message starts with "API rate limit exceeded for ".
+// remaining value of 0.
 type RateLimitError struct {
 	Rate     Rate           // Rate specifies last known rate limit for the client
 	Response *http.Response // HTTP response that caused this error
@@ -763,7 +757,7 @@ func CheckResponse(r *http.Response) error {
 	switch {
 	case r.StatusCode == http.StatusUnauthorized && strings.HasPrefix(r.Header.Get(headerOTP), "required"):
 		return (*TwoFactorAuthError)(errorResponse)
-	case r.StatusCode == http.StatusForbidden && r.Header.Get(headerRateRemaining) == "0" && strings.HasPrefix(errorResponse.Message, "API rate limit exceeded for "):
+	case r.StatusCode == http.StatusForbidden && r.Header.Get(headerRateRemaining) == "0":
 		return &RateLimitError{
 			Rate:     parseRate(r),
 			Response: errorResponse.Response,
diff --git a/github/issues_comments.go b/github/issues_comments.go
index ab68afe2..42d6bc78 100644
--- a/github/issues_comments.go
+++ b/github/issues_comments.go
@@ -36,13 +36,13 @@ func (i IssueComment) String() string {
 // IssuesService.ListComments method.
 type IssueListCommentsOptions struct {
 	// Sort specifies how to sort comments. Possible values are: created, updated.
-	Sort string `url:"sort,omitempty"`
+	Sort *string `url:"sort,omitempty"`
 
 	// Direction in which to sort comments. Possible values are: asc, desc.
-	Direction string `url:"direction,omitempty"`
+	Direction *string `url:"direction,omitempty"`
 
 	// Since filters comments by time.
-	Since time.Time `url:"since,omitempty"`
+	Since *time.Time `url:"since,omitempty"`
 
 	ListOptions
 }
diff --git a/github/messages.go b/github/messages.go
index ac7c17f3..a87daa3e 100644
--- a/github/messages.go
+++ b/github/messages.go
@@ -80,6 +80,7 @@ var (
 		"status":                         "StatusEvent",
 		"team":                           "TeamEvent",
 		"team_add":                       "TeamAddEvent",
+		"user":                           "UserEvent",
 		"watch":                          "WatchEvent",
 	}
 )
diff --git a/github/orgs.go b/github/orgs.go
index e1aa20db..5012c6c9 100644
--- a/github/orgs.go
+++ b/github/orgs.go
@@ -70,6 +70,12 @@ type Organization struct {
 	ReposURL         *string `json:"repos_url,omitempty"`
 }
 
+// OrganizationInstallations represents GitHub app installations for an organization.
+type OrganizationInstallations struct {
+	TotalCount    *int            `json:"total_count,omitempty"`
+	Installations []*Installation `json:"installations,omitempty"`
+}
+
 func (o Organization) String() string {
 	return Stringify(o)
 }
@@ -80,6 +86,8 @@ type Plan struct {
 	Space         *int    `json:"space,omitempty"`
 	Collaborators *int    `json:"collaborators,omitempty"`
 	PrivateRepos  *int    `json:"private_repos,omitempty"`
+	FilledSeats   *int    `json:"filled_seats,omitempty"`
+	Seats         *int    `json:"seats,omitempty"`
 }
 
 func (p Plan) String() string {
@@ -213,3 +221,31 @@ func (s *OrganizationsService) Edit(ctx context.Context, name string, org *Organ
 
 	return o, resp, nil
 }
+
+// ListInstallations lists installations for an organization.
+//
+// GitHub API docs: https://developer.github.com/v3/orgs/#list-installations-for-an-organization
+func (s *OrganizationsService) ListInstallations(ctx context.Context, org string, opt *ListOptions) (*OrganizationInstallations, *Response, error) {
+	u := fmt.Sprintf("orgs/%v/installations", org)
+
+	u, err := addOptions(u, opt)
+	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 header when this API fully launches.
+	req.Header.Set("Accept", mediaTypeIntegrationPreview)
+
+	result := new(OrganizationInstallations)
+	resp, err := s.client.Do(ctx, req, result)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return result, resp, nil
+}
diff --git a/github/reactions.go b/github/reactions.go
index 0865f8cd..1935ce11 100644
--- a/github/reactions.go
+++ b/github/reactions.go
@@ -44,10 +44,21 @@ 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, opt *ListOptions) ([]*Reaction, *Response, error) {
+func (s *ReactionsService) ListCommentReactions(ctx context.Context, owner, repo string, id int64, opt *ListCommentReactionOptions) ([]*Reaction, *Response, error) {
 	u := fmt.Sprintf("repos/%v/%v/comments/%v/reactions", owner, repo, id)
 	u, err := addOptions(u, opt)
 	if err != nil {
diff --git a/github/repos.go b/github/repos.go
index 37a09d5b..34f22ebc 100644
--- a/github/repos.go
+++ b/github/repos.go
@@ -519,6 +519,26 @@ type ListContributorsOptions struct {
 	ListOptions
 }
 
+// GetVulnerabilityAlerts checks if vulnerability alerts are enabled for a repository.
+//
+// GitHub API docs: https://developer.github.com/v3/repos/#check-if-vulnerability-alerts-are-enabled-for-a-repository
+func (s *RepositoriesService) GetVulnerabilityAlerts(ctx context.Context, owner, repository string) (bool, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/vulnerability-alerts", owner, repository)
+
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return false, nil, err
+	}
+
+	// TODO: remove custom Accept header when this API fully launches
+	req.Header.Set("Accept", mediaTypeRequiredVulnerabilityAlertsPreview)
+
+	resp, err := s.client.Do(ctx, req, nil)
+	vulnerabilityAlertsEnabled, err := parseBoolResponse(err)
+
+	return vulnerabilityAlertsEnabled, resp, err
+}
+
 // EnableVulnerabilityAlerts enables vulnerability alerts and the dependency graph for a repository.
 //
 // GitHub API docs: https://developer.github.com/v3/repos/#enable-vulnerability-alerts
@@ -736,7 +756,7 @@ type RequiredStatusChecksRequest struct {
 // PullRequestReviewsEnforcement represents the pull request reviews enforcement of a protected branch.
 type PullRequestReviewsEnforcement struct {
 	// Specifies which users and teams can dismiss pull request reviews.
-	DismissalRestrictions DismissalRestrictions `json:"dismissal_restrictions"`
+	DismissalRestrictions *DismissalRestrictions `json:"dismissal_restrictions,omitempty"`
 	// Specifies if approved reviews are dismissed automatically, when a new commit is pushed.
 	DismissStaleReviews bool `json:"dismiss_stale_reviews"`
 	// RequireCodeOwnerReviews specifies if an approved review is required in pull requests including files with a designated code owner.
@@ -791,6 +811,8 @@ type BranchRestrictions struct {
 	Users []*User `json:"users"`
 	// The list of team slugs with push access.
 	Teams []*Team `json:"teams"`
+	// The list of app slugs with push access.
+	Apps []*App `json:"apps"`
 }
 
 // BranchRestrictionsRequest represents the request to create/edit the
@@ -802,6 +824,8 @@ type BranchRestrictionsRequest struct {
 	Users []string `json:"users"`
 	// The list of team slugs with push access. (Required; use []string{} instead of nil for empty list.)
 	Teams []string `json:"teams"`
+	// The list of app slugs with push access.
+	Apps []string `json:"apps,omitempty"`
 }
 
 // DismissalRestrictions specifies which users and teams can dismiss pull request reviews.
@@ -1134,9 +1158,9 @@ func (s *RepositoriesService) UpdatePullRequestReviewEnforcement(ctx context.Con
 func (s *RepositoriesService) DisableDismissalRestrictions(ctx context.Context, owner, repo, branch string) (*PullRequestReviewsEnforcement, *Response, error) {
 	u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/required_pull_request_reviews", owner, repo, branch)
 
-	data := struct {
-		R []interface{} `json:"dismissal_restrictions"`
-	}{[]interface{}{}}
+	data := new(struct {
+		DismissalRestrictionsRequest `json:"dismissal_restrictions"`
+	})
 
 	req, err := s.client.NewRequest("PATCH", u, data)
 	if err != nil {
@@ -1287,6 +1311,93 @@ func (s *RepositoriesService) ReplaceAllTopics(ctx context.Context, owner, repo
 	return t.Names, resp, nil
 }
 
+// ListApps lists the Github apps that have push access to a given protected branch.
+// It requires the Github apps to have `write` access to the `content` permission.
+//
+// GitHub API docs: https://developer.github.com/v3/repos/branches/#list-apps-with-access-to-protected-branch
+func (s *RepositoriesService) ListApps(ctx context.Context, owner, repo, branch string) ([]*App, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/restrictions/apps", owner, repo, branch)
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var apps []*App
+	resp, err := s.client.Do(ctx, req, &apps)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return apps, resp, nil
+}
+
+// ReplaceAppRestrictions replaces the apps that have push access to a given protected branch.
+// It removes all apps that previously had push access and grants push access to the new list of apps.
+// It requires the Github apps to have `write` access to the `content` permission.
+//
+// Note: The list of users, apps, and teams in total is limited to 100 items.
+//
+// GitHub API docs: https://developer.github.com/v3/repos/branches/#replace-app-restrictions-of-protected-branch
+func (s *RepositoriesService) ReplaceAppRestrictions(ctx context.Context, owner, repo, branch string, slug []string) ([]*App, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/restrictions/apps", owner, repo, branch)
+	req, err := s.client.NewRequest("PUT", u, slug)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var apps []*App
+	resp, err := s.client.Do(ctx, req, &apps)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return apps, resp, nil
+}
+
+// AddAppRestrictions grants the specified apps push access to a given protected branch.
+// It requires the Github apps to have `write` access to the `content` permission.
+//
+// Note: The list of users, apps, and teams in total is limited to 100 items.
+//
+// GitHub API docs: https://developer.github.com/v3/repos/branches/#add-app-restrictions-of-protected-branch
+func (s *RepositoriesService) AddAppRestrictions(ctx context.Context, owner, repo, branch string, slug []string) ([]*App, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/restrictions/apps", owner, repo, branch)
+	req, err := s.client.NewRequest("POST", u, slug)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var apps []*App
+	resp, err := s.client.Do(ctx, req, &apps)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return apps, resp, nil
+}
+
+// RemoveAppRestrictions removes the ability of an app to push to this branch.
+// It requires the Github apps to have `write` access to the `content` permission.
+//
+// Note: The list of users, apps, and teams in total is limited to 100 items.
+//
+// GitHub API docs: https://developer.github.com/v3/repos/branches/#remove-app-restrictions-of-protected-branch
+func (s *RepositoriesService) RemoveAppRestrictions(ctx context.Context, owner, repo, branch string, slug []string) ([]*App, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/branches/%v/protection/restrictions/apps", owner, repo, branch)
+	req, err := s.client.NewRequest("DELETE", u, slug)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	var apps []*App
+	resp, err := s.client.Do(ctx, req, &apps)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	return apps, resp, nil
+}
+
 // TransferRequest represents a request to transfer a repository.
 type TransferRequest struct {
 	NewOwner string  `json:"new_owner"`
diff --git a/github/repos_contents.go b/github/repos_contents.go
index bf6cabc5..eed0420a 100644
--- a/github/repos_contents.go
+++ b/github/repos_contents.go
@@ -240,15 +240,28 @@ const (
 // or github.Zipball constant.
 //
 // GitHub API docs: https://developer.github.com/v3/repos/contents/#get-archive-link
-func (s *RepositoriesService) GetArchiveLink(ctx context.Context, owner, repo string, archiveformat archiveFormat, opt *RepositoryContentGetOptions) (*url.URL, *Response, error) {
+func (s *RepositoriesService) GetArchiveLink(ctx context.Context, owner, repo string, archiveformat archiveFormat, opt *RepositoryContentGetOptions, followRedirects bool) (*url.URL, *Response, error) {
 	u := fmt.Sprintf("repos/%s/%s/%s", owner, repo, archiveformat)
 	if opt != nil && opt.Ref != "" {
 		u += fmt.Sprintf("/%s", opt.Ref)
 	}
-	req, err := s.client.NewRequest("GET", u, nil)
+	resp, err := s.getArchiveLinkFromURL(ctx, u, followRedirects)
 	if err != nil {
 		return nil, nil, err
 	}
+	if resp.StatusCode != http.StatusFound {
+		return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status)
+	}
+	parsedURL, err := url.Parse(resp.Header.Get("Location"))
+	return parsedURL, newResponse(resp), err
+}
+
+func (s *RepositoriesService) getArchiveLinkFromURL(ctx context.Context, u string, followRedirects bool) (*http.Response, error) {
+	req, err := s.client.NewRequest("GET", u, nil)
+	if err != nil {
+		return nil, err
+	}
+
 	var resp *http.Response
 	// Use http.DefaultTransport if no custom Transport is configured
 	req = withContext(ctx, req)
@@ -258,12 +271,14 @@ func (s *RepositoriesService) GetArchiveLink(ctx context.Context, owner, repo st
 		resp, err = s.client.client.Transport.RoundTrip(req)
 	}
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 	resp.Body.Close()
-	if resp.StatusCode != http.StatusFound {
-		return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status)
+
+	// If redirect response is returned, follow it
+	if followRedirects && resp.StatusCode == http.StatusMovedPermanently {
+		u = resp.Header.Get("Location")
+		resp, err = s.getArchiveLinkFromURL(ctx, u, false)
 	}
-	parsedURL, err := url.Parse(resp.Header.Get("Location"))
-	return parsedURL, newResponse(resp), err
+	return resp, err
 }
diff --git a/github/repos_pages.go b/github/repos_pages.go
index 28674130..7eb32a9d 100644
--- a/github/repos_pages.go
+++ b/github/repos_pages.go
@@ -8,7 +8,6 @@ package github
 import (
 	"context"
 	"fmt"
-	"strings"
 )
 
 // Pages represents a GitHub Pages site configuration.
@@ -54,9 +53,7 @@ func (s *RepositoriesService) EnablePages(ctx context.Context, owner, repo strin
 		return nil, nil, err
 	}
 
-	// TODO: remove custom Accept header when this API fully launches.
-	acceptHeaders := []string{mediaTypeEnablePagesAPIPreview, mediaTypePagesPreview}
-	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
+	req.Header.Set("Accept", mediaTypeEnablePagesAPIPreview)
 
 	enable := new(Pages)
 	resp, err := s.client.Do(ctx, req, enable)
@@ -93,9 +90,6 @@ func (s *RepositoriesService) GetPagesInfo(ctx context.Context, owner, repo stri
 		return nil, nil, err
 	}
 
-	// TODO: remove custom Accept header when this API fully launches.
-	req.Header.Set("Accept", mediaTypePagesPreview)
-
 	site := new(Pages)
 	resp, err := s.client.Do(ctx, req, site)
 	if err != nil {
@@ -177,9 +171,6 @@ func (s *RepositoriesService) RequestPageBuild(ctx context.Context, owner, repo
 		return nil, nil, err
 	}
 
-	// TODO: remove custom Accept header when this API fully launches.
-	req.Header.Set("Accept", mediaTypePagesPreview)
-
 	build := new(PagesBuild)
 	resp, err := s.client.Do(ctx, req, build)
 	if err != nil {
diff --git a/github/search.go b/github/search.go
index 13cc4c84..4e66ba1c 100644
--- a/github/search.go
+++ b/github/search.go
@@ -76,6 +76,37 @@ func (s *SearchService) Repositories(ctx context.Context, query string, opt *Sea
 	return result, resp, err
 }
 
+// TopicsSearchResult represents the result of a topics search.
+type TopicsSearchResult struct {
+	Total             *int           `json:"total_count,omitempty"`
+	IncompleteResults *bool          `json:"incomplete_results,omitempty"`
+	Topics            []*TopicResult `json:"items,omitempty"`
+}
+
+type TopicResult struct {
+	Name             *string    `json:"name,omitempty"`
+	DisplayName      *string    `json:"display_name,omitempty"`
+	ShortDescription *string    `json:"short_description,omitempty"`
+	Description      *string    `json:"description,omitempty"`
+	CreatedBy        *string    `json:"created_by,omitempty"`
+	CreatedAt        *Timestamp `json:"created_at,omitempty"`
+	UpdatedAt        *string    `json:"updated_at,omitempty"`
+	Featured         *bool      `json:"featured,omitempty"`
+	Curated          *bool      `json:"curated,omitempty"`
+	Score            *float64   `json:"score,omitempty"`
+}
+
+// Topics finds topics via various criteria. Results are sorted by best match.
+// Please see https://help.github.com/en/articles/searching-topics for more
+// information about search qualifiers.
+//
+// GitHub API docs: https://developer.github.com/v3/search/#search-topics
+func (s *SearchService) Topics(ctx context.Context, query string, opt *SearchOptions) (*TopicsSearchResult, *Response, error) {
+	result := new(TopicsSearchResult)
+	resp, err := s.search(ctx, "topics", &searchParameters{Query: query}, opt, result)
+	return result, resp, err
+}
+
 // CommitsSearchResult represents the result of a commits search.
 type CommitsSearchResult struct {
 	Total             *int            `json:"total_count,omitempty"`
@@ -245,6 +276,10 @@ func (s *SearchService) search(ctx context.Context, searchType string, parameter
 		// Accept header for search commits preview endpoint
 		// TODO: remove custom Accept header when this API fully launches.
 		req.Header.Set("Accept", mediaTypeCommitSearchPreview)
+	case searchType == "topics":
+		// Accept header for search repositories based on topics preview endpoint
+		// TODO: remove custom Accept header when this API fully launches.
+		req.Header.Set("Accept", mediaTypeTopicsPreview)
 	case searchType == "repositories":
 		// Accept header for search repositories based on topics preview endpoint
 		// TODO: remove custom Accept header when this API fully launches.
diff --git a/github/teams.go b/github/teams.go
index b4953e63..7f00b986 100644
--- a/github/teams.go
+++ b/github/teams.go
@@ -504,9 +504,6 @@ func (s *TeamsService) ListIDPGroupsInOrganization(ctx context.Context, org stri
 		return nil, nil, err
 	}
 
-	// TODO: remove custom Accept header when this API fully launches.
-	req.Header.Set("Accept", mediaTypeTeamSyncPreview)
-
 	groups := new(IDPGroupList)
 	resp, err := s.client.Do(ctx, req, groups)
 	if err != nil {
@@ -526,9 +523,6 @@ func (s *TeamsService) ListIDPGroupsForTeam(ctx context.Context, teamID string)
 		return nil, nil, err
 	}
 
-	// TODO: remove custom Accept header when this API fully launches.
-	req.Header.Set("Accept", mediaTypeTeamSyncPreview)
-
 	groups := new(IDPGroupList)
 	resp, err := s.client.Do(ctx, req, groups)
 	if err != nil {
@@ -549,9 +543,6 @@ func (s *TeamsService) CreateOrUpdateIDPGroupConnections(ctx context.Context, te
 		return nil, nil, err
 	}
 
-	// TODO: remove custom Accept header when this API fully launches.
-	req.Header.Set("Accept", mediaTypeTeamSyncPreview)
-
 	groups := new(IDPGroupList)
 	resp, err := s.client.Do(ctx, req, groups)
 	if err != nil {
diff --git a/github/users_projects.go b/github/users_projects.go
new file mode 100644
index 00000000..bd2ca75e
--- /dev/null
+++ b/github/users_projects.go
@@ -0,0 +1,68 @@
+// Copyright 2019 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"
+)
+
+// ListProjects lists the projects for the specified user.
+//
+// GitHub API docs: https://developer.github.com/v3/projects/#list-user-projects
+func (s *UsersService) ListProjects(ctx context.Context, user string, opt *ProjectListOptions) ([]*Project, *Response, error) {
+	u := fmt.Sprintf("users/%v/projects", user)
+	u, err := addOptions(u, opt)
+	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 header when this API fully launches.
+	req.Header.Set("Accept", mediaTypeProjectsPreview)
+
+	var projects []*Project
+	resp, err := s.client.Do(ctx, req, &projects)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return projects, resp, nil
+}
+
+// CreateUserProjectOptions specifies the parameters to the UsersService.CreateProject method.
+type CreateUserProjectOptions struct {
+	// The name of the project. (Required.)
+	Name string `json:"name"`
+	// The description of the project. (Optional.)
+	Body *string `json:"body,omitempty"`
+}
+
+// CreateProject creates a GitHub Project for the current user.
+//
+// GitHub API docs: https://developer.github.com/v3/projects/#create-a-user-project
+func (s *UsersService) CreateProject(ctx context.Context, opt *CreateUserProjectOptions) (*Project, *Response, error) {
+	u := "users/projects"
+	req, err := s.client.NewRequest("POST", u, opt)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// TODO: remove custom Accept header when this API fully launches.
+	req.Header.Set("Accept", mediaTypeProjectsPreview)
+
+	project := &Project{}
+	resp, err := s.client.Do(ctx, req, project)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return project, resp, nil
+}
diff --git a/go.mod b/go.mod
index f6198d3c..23ce41ca 100644
--- a/go.mod
+++ b/go.mod
@@ -1,12 +1,11 @@
 module github.com/google/go-github/v28
 
 require (
-	github.com/golang/protobuf v1.2.0 // indirect
+	github.com/golang/protobuf v1.3.2 // indirect
 	github.com/google/go-querystring v1.0.0
 	golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
 	golang.org/x/net v0.0.0-20190311183353-d8887717615a // indirect
 	golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be
-	golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 // indirect
 	google.golang.org/appengine v1.1.0
 )
 
diff --git a/go.sum b/go.sum
index 927256d3..dbab13e8 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,5 @@
-github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
-github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
 github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
@@ -8,8 +8,6 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJV
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI=
-golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/scrape/README.md b/scrape/README.md
new file mode 100644
index 00000000..5d6180da
--- /dev/null
+++ b/scrape/README.md
@@ -0,0 +1,26 @@
+[![GoDoc](https://godoc.org/github.com/google/go-github/scrape?status.svg)](https://godoc.org/github.com/google/go-github/scrape)
+
+The scrape package provides an experimental client for accessing additional
+GitHub data via screen scraping.  It is designed to be a client of last resort
+for data that cannot be retrieved via the REST or GraphQL APIs.
+
+# What should be added here
+
+**Add only what you need.**  Whereas the main go-github library attempts to
+implement the entire GitHub REST API, there is little point in trying to do that
+here.  Certainly, feel free to contribution patches to get data you actually
+need, but I'd rather not try and provide exhaustive coverage of all GitHub data
+here.
+
+**Add only what can't be accessed elsewhere.**  If the data can be retrieved
+through the REST or GraphQL API, use the appropriate libraries for that.
+
+**Prefer read-only access.**  For now, I'm only focusing on reading data. It
+might be that writing works fine as well, but it is of course much riskier.
+
+# How to add methods
+
+See [apps.go](apps.go) for examples of methods that access data.  Basically,
+fetch the contents of the page using `client.get`, and then use goquery to dig
+into the markup on the page.  Prefer selectors that grab semantic ID or class
+names, as they are more likely to be stable.
diff --git a/scrape/apps.go b/scrape/apps.go
new file mode 100644
index 00000000..eb2f9005
--- /dev/null
+++ b/scrape/apps.go
@@ -0,0 +1,146 @@
+// 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.
+
+// apps.go contains functions for accessing data about applications installed
+// on a GitHub organization.
+
+package scrape
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/PuerkitoBio/goquery"
+	"github.com/google/go-github/v28/github"
+)
+
+// AppRestrictionsEnabled returns whether the specified organization has
+// restricted third-party application access.
+func (c *Client) AppRestrictionsEnabled(org string) (bool, error) {
+	doc, err := c.get("/organizations/%s/settings/oauth_application_policy", org)
+	if err != nil {
+		return false, err
+	}
+
+	s := doc.Find(".oauth-application-whitelist svg").First()
+	if s.Length() == 0 {
+		return false, errors.New("unable to find expected markup")
+	}
+
+	if s.HasClass("octicon-check") {
+		return true, nil
+	}
+	if s.HasClass("octicon-alert") {
+		return false, nil
+	}
+
+	return false, errors.New("unable to find expected markup")
+}
+
+// ListOAuthApps lists the reviewed OAuth Applications for the
+// specified organization (whether approved or denied).
+func (c *Client) ListOAuthApps(org string) ([]OAuthApp, error) {
+	doc, err := c.get("/organizations/%s/settings/oauth_application_policy", org)
+	if err != nil {
+		return nil, err
+	}
+
+	var apps []OAuthApp
+	doc.Find(".oauth-application-whitelist ul > li").Each(func(i int, s *goquery.Selection) {
+		var app OAuthApp
+		app.Name = s.Find(".request-info strong").First().Text()
+		app.Description = s.Find(".request-info .application-description").Text()
+
+		if editURL, ok := s.Find(".request-indicator a.edit-link").Attr("href"); ok {
+			app.ID = intFromLastPathSegment(editURL)
+		}
+
+		if r := s.Find(".request-indicator .requestor"); r.Length() > 0 {
+			app.State = OAuthAppRequested
+			app.RequestedBy = r.Text()
+			if editURL, ok := s.Find(".request-indicator a").Last().Attr("href"); ok {
+				app.ID = intFromLastPathSegment(editURL)
+			}
+		} else if r := s.Find(".request-indicator .approved-request"); r.Length() > 0 {
+			app.State = OAuthAppApproved
+		} else if r := s.Find(".request-indicator .denied-request"); r.Length() > 0 {
+			app.State = OAuthAppDenied
+		}
+		apps = append(apps, app)
+	})
+
+	return apps, nil
+}
+
+func intFromLastPathSegment(s string) int {
+	seg := strings.Split(s, "/")
+	if len(seg) > 0 {
+		i, _ := strconv.Atoi(seg[len(seg)-1])
+		return i
+	}
+	return 0
+}
+
+// OAuthAppReviewState indicates the current state of a requested OAuth Application.
+type OAuthAppReviewState int
+
+const (
+	// OAuthAppRequested indicates access has been requested, but not reviewed
+	OAuthAppRequested OAuthAppReviewState = iota + 1
+	// OAuthAppApproved indicates access has been approved
+	OAuthAppApproved
+	// OAuthAppDenied indicates access has been denied
+	OAuthAppDenied
+)
+
+// OAuthApp represents an OAuth application that has been reviewed for access to organization data.
+type OAuthApp struct {
+	ID          int
+	Name        string
+	Description string
+	State       OAuthAppReviewState
+	RequestedBy string
+}
+
+// AppManifest represents a GitHub App manifest, used for preconfiguring
+// GitHub App configuration.
+type AppManifest struct {
+	// The name of the GitHub App.
+	Name *string `json:"name,omitempty"`
+	//Required. The homepage of your GitHub App.
+	URL *string `json:"url,omitempty"`
+	// Required. The configuration of the GitHub App's webhook.
+	HookAttributes map[string]string `json:"hook_attributes,omitempty"`
+	// The full URL to redirect to after the person installs the GitHub App.
+	RedirectURL *string `json:"redirect_url,omitempty"`
+	// A description of the GitHub App.
+	Description *string `json:"description,omitempty"`
+	// Set to true when your GitHub App is available to the public or false when
+	// it is only accessible to the owner of the app.
+	Public *bool `json:"public,omitempty"`
+	// The list of events the GitHub App subscribes to.
+	DefaultEvents []string `json:"default_events,omitempty"`
+	// The set of permissions needed by the GitHub App.
+	DefaultPermissions *github.InstallationPermissions `json:"default_permissions,omitempty"`
+}
+
+// CreateApp creates a new GitHub App with the given manifest configuration.
+func (c *Client) CreateApp(m *AppManifest) (*http.Response, error) {
+	u, err := c.baseURL.Parse("/settings/apps/new")
+	if err != nil {
+		return nil, err
+	}
+
+	body, err := json.Marshal(map[string]*AppManifest{"manifest": m})
+	if err != nil {
+		return nil, err
+	}
+
+	return c.Client.Post(u.String(), "json", bytes.NewReader(body))
+}
diff --git a/scrape/example/scrape/main.go b/scrape/example/scrape/main.go
new file mode 100644
index 00000000..a55b88eb
--- /dev/null
+++ b/scrape/example/scrape/main.go
@@ -0,0 +1,61 @@
+// The scrape tool demonstrates use of the github.com/google/go-github/scrape
+// package to fetch data from GitHub.  The tool lists whether third-party app
+// restrictions are enabled for an organization, and lists information about
+// OAuth apps requested for the org.
+package main
+
+import (
+	"bufio"
+	"flag"
+	"fmt"
+	"log"
+	"os"
+	"strings"
+
+	"github.com/google/go-github/scrape"
+)
+
+var (
+	username = flag.String("username", "", "github auth: username")
+	password = flag.String("password", "", "github auth: password")
+	otpseed  = flag.String("otpseed", "", "github auth: otp seed")
+	org      = flag.String("org", "", "github org to get data for")
+)
+
+func main() {
+	flag.Parse()
+
+	// prompt for password and otpseed in case the user didn't want to specify as flags
+	reader := bufio.NewReader(os.Stdin)
+	if *password == "" {
+		fmt.Print("password: ")
+		*password, _ = reader.ReadString('\n')
+		*password = strings.TrimSpace(*password)
+	}
+	if *otpseed == "" {
+		fmt.Print("OTP seed: ")
+		*otpseed, _ = reader.ReadString('\n')
+		*otpseed = strings.TrimSpace(*otpseed)
+	}
+
+	client := scrape.NewClient(nil)
+
+	if err := client.Authenticate(*username, *password, *otpseed); err != nil {
+		log.Fatal(err)
+	}
+
+	enabled, err := client.AppRestrictionsEnabled(*org)
+	if err != nil {
+		log.Fatal(err)
+	}
+	fmt.Printf("App restrictions enabled for %q: %t\n", *org, enabled)
+
+	apps, err := client.ListOAuthApps(*org)
+	if err != nil {
+		log.Fatal(err)
+	}
+	fmt.Printf("OAuth apps for %q: \n", *org)
+	for _, app := range apps {
+		fmt.Printf("\t%+v\n", app)
+	}
+}
diff --git a/scrape/forms.go b/scrape/forms.go
new file mode 100644
index 00000000..ee559395
--- /dev/null
+++ b/scrape/forms.go
@@ -0,0 +1,98 @@
+// 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.
+
+// forms.go contains logic for parsing and submitting HTML forms.  None of this
+// is specific to go-github in any way, and could easily be pulled out into a
+// general purpose scraping library in the future.
+
+package scrape
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+
+	"github.com/PuerkitoBio/goquery"
+	"golang.org/x/net/html"
+)
+
+// htmlForm represents the basic elements of an HTML Form.
+type htmlForm struct {
+	// Action is the URL where the form will be submitted
+	Action string
+	// Method is the HTTP method to use when submitting the form
+	Method string
+	// Values contains form values to be submitted
+	Values url.Values
+}
+
+// parseForms parses and returns all form elements beneath node.  Form values
+// include all nested input elements within the form (textarea is not currently
+// supported).
+//
+// In the future, we might want to allow a custom selector to be passed in to
+// further restrict what forms will be returned.
+func parseForms(node *html.Node) (forms []htmlForm) {
+	if node == nil {
+		return nil
+	}
+
+	doc := goquery.NewDocumentFromNode(node)
+	doc.Find("form").Each(func(_ int, s *goquery.Selection) {
+		form := htmlForm{Values: url.Values{}}
+		form.Action, _ = s.Attr("action")
+		form.Method, _ = s.Attr("method")
+
+		s.Find("input").Each(func(_ int, s *goquery.Selection) {
+			name, _ := s.Attr("name")
+			value, _ := s.Attr("value")
+			if name != "" {
+				form.Values.Add(name, value)
+			}
+		})
+		forms = append(forms, form)
+	})
+
+	return forms
+}
+
+// fetchAndSubmitForm will fetch the page at urlStr, then parse and submit the first form found.
+// setValues will be called with the parsed form values, allowing the caller to set any custom
+// form values. Form submission will always use the POST method, regardless of the value of the
+// method attribute in the form.  The response from submitting the parsed form is returned.
+func fetchAndSubmitForm(client *http.Client, urlStr string, setValues func(url.Values)) (*http.Response, error) {
+	resp, err := client.Get(urlStr)
+	if err != nil {
+		return nil, fmt.Errorf("error fetching url %q: %v", urlStr, err)
+	}
+
+	defer resp.Body.Close()
+	root, err := html.Parse(resp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("error parsing response: %v", err)
+	}
+
+	forms := parseForms(root)
+	if len(forms) == 0 {
+		return nil, fmt.Errorf("no forms found at %q", urlStr)
+	}
+	form := forms[0]
+
+	actionURL, err := url.Parse(form.Action)
+	if err != nil {
+		return nil, fmt.Errorf("error parsing form action URL %q: %v", form.Action, err)
+	}
+	actionURL = resp.Request.URL.ResolveReference(actionURL)
+
+	// allow caller to fill out the form
+	setValues(form.Values)
+
+	resp, err = client.PostForm(actionURL.String(), form.Values)
+	if err != nil {
+		return nil, fmt.Errorf("error posting form: %v", err)
+	}
+
+	return resp, nil
+}
diff --git a/scrape/go.mod b/scrape/go.mod
new file mode 100644
index 00000000..f936c07d
--- /dev/null
+++ b/scrape/go.mod
@@ -0,0 +1,11 @@
+module github.com/google/go-github/scrape
+
+go 1.13
+
+require (
+	github.com/PuerkitoBio/goquery v1.5.0
+	github.com/google/go-cmp v0.3.1
+	github.com/google/go-github/v28 v28.1.1
+	github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119
+	golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582
+)
diff --git a/scrape/go.sum b/scrape/go.sum
new file mode 100644
index 00000000..7f226791
--- /dev/null
+++ b/scrape/go.sum
@@ -0,0 +1,25 @@
+github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk=
+github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg=
+github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
+github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
+github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
+github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
+github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119 h1:YyPWX3jLOtYKulBR6AScGIs74lLrJcgeKRwcbAuQOG4=
+github.com/xlzd/gotp v0.0.0-20181030022105-c8557ba2c119/go.mod h1:/nuTSlK+okRfR/vnIPqR89fFKonnWPiZymN5ydRJkX8=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA=
+golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
diff --git a/scrape/payment.go b/scrape/payment.go
new file mode 100644
index 00000000..fff9a753
--- /dev/null
+++ b/scrape/payment.go
@@ -0,0 +1,51 @@
+// 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.
+
+// apps.go contains functions for accessing data about applications installed
+// on a GitHub organization.
+
+package scrape
+
+import (
+	"strings"
+
+	"github.com/PuerkitoBio/goquery"
+)
+
+// OrgPaymentInformation returns payment information for the specified org.
+func (c *Client) OrgPaymentInformation(org string) (PaymentInformation, error) {
+	var info PaymentInformation
+
+	doc, err := c.get("/organizations/%s/settings/billing/payment_information", org)
+	if err != nil {
+		return info, err
+	}
+
+	doc.Find("main h4.mb-1").Each(func(i int, s *goquery.Selection) {
+		name := strings.TrimSpace(strings.ToLower(s.Text()))
+		value := strings.Join(strings.Fields(strings.TrimSpace(s.NextFiltered("p").Text())), " ")
+
+		switch name {
+		case "payment method":
+			info.PaymentMethod = value
+		case "last payment":
+			info.LastPayment = value
+		case "coupon":
+			info.Coupon = value
+		case "extra information":
+			info.ExtraInformation = value
+		}
+	})
+
+	return info, nil
+}
+
+// PaymentInformation for an organization on a paid plan.
+type PaymentInformation struct {
+	PaymentMethod    string
+	LastPayment      string
+	Coupon           string
+	ExtraInformation string
+}
diff --git a/scrape/scrape.go b/scrape/scrape.go
new file mode 100644
index 00000000..c3be9af1
--- /dev/null
+++ b/scrape/scrape.go
@@ -0,0 +1,153 @@
+// 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 scrape provides a client for interacting with GitHub using screen
+// scraping.
+//
+// This is intended to be used as a supplement to the standard go-github
+// library to access data that is not currently exposed by either the official
+// REST or GraphQL APIs.
+//
+// Because of the nature of screen scraping, this package should be treated as
+// HIGHLY EXPERIMENTAL, and potentially unstable. We make no promises relating
+// to compatibility or stability of the exported API. Even though this package
+// is distributed as part of the go-github library, it is explicitly exempt
+// from any stability promises that my be implied by the library version
+// number.
+package scrape
+
+import (
+	"bytes"
+	"encoding/gob"
+	"fmt"
+	"log"
+	"net/http"
+	"net/http/cookiejar"
+	"net/url"
+	"strings"
+
+	"github.com/PuerkitoBio/goquery"
+	"github.com/xlzd/gotp"
+	"golang.org/x/net/publicsuffix"
+)
+
+var defaultBaseURL = "https://github.com/"
+
+// Client is a GitHub scraping client.
+type Client struct {
+	*http.Client
+
+	// base URL for github.com pages.  Exposed primarily for testing.  Also
+	// used for saving and restoring cookies on the Client.
+	baseURL *url.URL
+}
+
+// NewClient constructs a new Client.  If transport is nil, a default transport is used.
+func NewClient(transport http.RoundTripper) *Client {
+	jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
+	if err != nil {
+		log.Fatalf("error creating cookiejar: %v", err)
+	}
+	baseURL, _ := url.Parse(defaultBaseURL)
+
+	return &Client{
+		Client: &http.Client{
+			Transport: transport,
+			Jar:       jar,
+		},
+		baseURL: baseURL,
+	}
+}
+
+// SaveCookies returns an encoded form of the github.com cookies set on this
+// client.  If Authenticate() has been called, this should include the
+// github.com session cookie.  These cookies can be loaded onto a new client by
+// calling LoadCookies.
+//
+// GitHub session cookies are bearer tokens that are not tied to any particular
+// client, so should be treated with the same sensitivity as the account
+// credentials.
+func (c *Client) SaveCookies() ([]byte, error) {
+	cookies := c.Client.Jar.Cookies(c.baseURL)
+
+	var b bytes.Buffer
+	err := gob.NewEncoder(&b).Encode(cookies)
+	return b.Bytes(), err
+}
+
+// LoadCookies loads the provided cookies for github.com.
+func (c *Client) LoadCookies(v []byte) error {
+	var cookies []*http.Cookie
+	r := bytes.NewReader(v)
+	err := gob.NewDecoder(r).Decode(&cookies)
+	if err != nil {
+		return err
+	}
+
+	c.Client.Jar.SetCookies(c.baseURL, cookies)
+	return nil
+}
+
+// get fetches a urlStr (a GitHub URL relative to the client's baseURL), and
+// returns the parsed response document.
+func (c *Client) get(urlStr string, a ...interface{}) (*goquery.Document, error) {
+	u, err := c.baseURL.Parse(fmt.Sprintf(urlStr, a...))
+	if err != nil {
+		return nil, fmt.Errorf("error parsing URL: %q: %v", urlStr, err)
+	}
+	resp, err := c.Client.Get(u.String())
+	if err != nil {
+		return nil, fmt.Errorf("error fetching url %q: %v", u, err)
+	}
+	if resp.StatusCode == http.StatusNotFound {
+		return nil, fmt.Errorf("received %v response fetching URL %q", resp.StatusCode, u)
+	}
+
+	defer resp.Body.Close()
+	doc, err := goquery.NewDocumentFromReader(resp.Body)
+	if err != nil {
+		return nil, fmt.Errorf("error parsing response: %v", err)
+	}
+
+	return doc, nil
+}
+
+// Authenticate client to GitHub with the provided username, password, and if
+// two-factor auth is enabled for the account, otpseed.
+//
+// otpseed is the OTP Secret provided from GitHub as part of two-factor
+// application enrollment.  When registering the application, click the "enter
+// this text code" link on the QR Code page to see the raw OTP Secret.
+func (c *Client) Authenticate(username, password, otpseed string) error {
+	setPassword := func(values url.Values) {
+		values.Set("login", username)
+		values.Set("password", password)
+	}
+	resp, err := fetchAndSubmitForm(c.Client, "https://github.com/login", setPassword)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("received %v response submitting login form", resp.StatusCode)
+	}
+
+	if otpseed == "" {
+		return nil
+	}
+
+	setOTP := func(values url.Values) {
+		otp := gotp.NewDefaultTOTP(strings.ToUpper(otpseed)).Now()
+		values.Set("otp", otp)
+	}
+	resp, err = fetchAndSubmitForm(c.Client, "https://github.com/sessions/two-factor", setOTP)
+	if err != nil {
+		return err
+	}
+	if resp.StatusCode != http.StatusOK {
+		return fmt.Errorf("received %v response submitting otp form", resp.StatusCode)
+	}
+
+	return nil
+}


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 00000000..d89f367e
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,69 @@
+on: [push, pull_request]
+name: tests
+env:
+  GO111MODULE: on
+
+jobs:
+  linux:
+    strategy:
+      matrix:
+        go-version: [1.x, 1.12.x]
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/setup-go@v1
+      with:
+        go-version: ${{ matrix.go-version }}
+    - uses: actions/checkout@v1
+
+    - name: Cache go modules
+      uses: actions/cache@preview
+      with:
+        path: ~/go/pkg/mod
+        key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
+        restore-keys: ${{ runner.os }}-go-
+
+    - name: Run go fmt
+      run: diff -u <(echo -n) <(gofmt -d -s .)
+
+    - name: Ensure go generate produces a zero diff
+      run:  go generate -x ./... && git diff --exit-code; code=$?; git checkout -- .; (exit $code)
+
+    - name: Run go vet
+      run: go vet ./...
+
+    - name: Run go test
+      run: go test -v -race -coverprofile=coverage.txt -covermode=atomic ./...
+
+    - name: Ensure integration tests build
+      # don't actually run tests since they hit live GitHub API
+      run: go test -v -tags=integration -run=^$ ./test/integration
+
+    - name: Run scrape tests
+      run: cd scrape && go test ./...
+
+    - name: Upload coverage to Codecov
+      env:
+        CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
+      run: bash <(curl -s https://codecov.io/bash)
+
+  # On Windows, just run the local tests.  Don't bother with checking gofmt, go
+  # vet, or uploading results to Codecov
+  windows:
+    strategy:
+      matrix:
+        go-version: [1.x, 1.12.x]
+    runs-on: windows-latest
+
+    steps:
+    - uses: actions/setup-go@v1
+      with:
+        go-version: ${{ matrix.go-version }}
+    - uses: actions/checkout@v1
+    - name: Cache go modules
+      uses: actions/cache@preview
+      with:
+        path: ~/go/pkg/mod
+        key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }}
+        restore-keys: ${{ runner.os }}-go-
+    - run: go test ./...
diff --git a/github/admin_users_test.go b/github/admin_users_test.go
index ad5379e2..2c3b6d7a 100644
--- a/github/admin_users_test.go
+++ b/github/admin_users_test.go
@@ -12,6 +12,7 @@ import (
 	"net/http"
 	"reflect"
 	"testing"
+	"time"
 )
 
 func TestAdminUsers_Create(t *testing.T) {
@@ -55,3 +56,74 @@ func TestAdminUsers_Delete(t *testing.T) {
 		t.Errorf("Admin.DeleteUser returned error: %v", err)
 	}
 }
+
+func TestUserImpersonation_Create(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/admin/users/github/authorizations", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		testBody(t, r, `{"scopes":["repo"]}`+"\n")
+		fmt.Fprint(w, `{"id": 1234,
+		"url": "https://git.company.com/api/v3/authorizations/1234",
+		"app": {
+		  "name": "GitHub Site Administrator",
+		  "url": "https://developer.github.com/v3/enterprise/users/",
+		  "client_id": "1234"
+		},
+		"token": "1234",
+		"hashed_token": "1234",
+		"token_last_eight": "1234",
+		"note": null,
+		"note_url": null,
+		"created_at": "2018-01-01T00:00:00Z",
+		"updated_at": "2018-01-01T00:00:00Z",
+		"scopes": [
+		  "repo"
+		],
+		"fingerprint": null}`)
+	})
+
+	opt := &ImpersonateUserOptions{Scopes: []string{"repo"}}
+	auth, _, err := client.Admin.CreateUserImpersonation(context.Background(), "github", opt)
+	if err != nil {
+		t.Errorf("Admin.CreateUserImpersonation returned error: %v", err)
+	}
+
+	date := Timestamp{Time: time.Date(2018, time.January, 1, 0, 0, 0, 0, time.UTC)}
+	want := &UserAuthorization{
+		ID:  Int64(1234),
+		URL: String("https://git.company.com/api/v3/authorizations/1234"),
+		App: &OAuthAPP{
+			Name:     String("GitHub Site Administrator"),
+			URL:      String("https://developer.github.com/v3/enterprise/users/"),
+			ClientID: String("1234"),
+		},
+		Token:          String("1234"),
+		HashedToken:    String("1234"),
+		TokenLastEight: String("1234"),
+		Note:           nil,
+		NoteURL:        nil,
+		CreatedAt:      &date,
+		UpdatedAt:      &date,
+		Scopes:         []string{"repo"},
+		Fingerprint:    nil,
+	}
+	if !reflect.DeepEqual(auth, want) {
+		t.Errorf("Admin.CreateUserImpersonation returned %+v, want %+v", auth, want)
+	}
+}
+
+func TestUserImpersonation_Delete(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/admin/users/github/authorizations", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+	})
+
+	_, err := client.Admin.DeleteUserImpersonation(context.Background(), "github")
+	if err != nil {
+		t.Errorf("Admin.DeleteUserImpersonation returned error: %v", err)
+	}
+}
diff --git a/github/apps_manifest_test.go b/github/apps_manifest_test.go
new file mode 100644
index 00000000..92a2be9d
--- /dev/null
+++ b/github/apps_manifest_test.go
@@ -0,0 +1,53 @@
+// Copyright 2019 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"
+	"reflect"
+	"testing"
+)
+
+const (
+	manifestJSON = `{
+	"id": 1,
+  "client_id": "a" ,
+  "client_secret": "b",
+  "webhook_secret": "c",
+  "pem": "key"
+}
+`
+)
+
+func TestGetConfig(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/app-manifests/code/conversions", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		testHeader(t, r, "Accept", mediaTypeAppManifestPreview)
+		fmt.Fprint(w, manifestJSON)
+	})
+
+	cfg, _, err := client.Apps.CompleteAppManifest(context.Background(), "code")
+	if err != nil {
+		t.Errorf("AppManifest.GetConfig returned error: %v", err)
+	}
+
+	want := &AppConfig{
+		ID:            Int64(1),
+		ClientID:      String("a"),
+		ClientSecret:  String("b"),
+		WebhookSecret: String("c"),
+		PEM:           String("key"),
+	}
+
+	if !reflect.DeepEqual(cfg, want) {
+		t.Errorf("GetConfig returned %+v, want %+v", cfg, want)
+	}
+}
diff --git a/github/checks_test.go b/github/checks_test.go
index 848c7e7d..e510e32f 100644
--- a/github/checks_test.go
+++ b/github/checks_test.go
@@ -102,11 +102,10 @@ func TestChecksService_CreateCheckRun(t *testing.T) {
 	})
 	startedAt, _ := time.Parse(time.RFC3339, "2018-05-04T01:14:52Z")
 	checkRunOpt := CreateCheckRunOptions{
-		HeadBranch: "master",
-		Name:       "testCreateCheckRun",
-		HeadSHA:    "deadbeef",
-		Status:     String("in_progress"),
-		StartedAt:  &Timestamp{startedAt},
+		Name:      "testCreateCheckRun",
+		HeadSHA:   "deadbeef",
+		Status:    String("in_progress"),
+		StartedAt: &Timestamp{startedAt},
 		Output: &CheckRunOutput{
 			Title:   String("Mighty test report"),
 			Summary: String(""),
@@ -200,7 +199,6 @@ func TestChecksService_UpdateCheckRun(t *testing.T) {
 	})
 	startedAt, _ := time.Parse(time.RFC3339, "2018-05-04T01:14:52Z")
 	updateCheckRunOpt := UpdateCheckRunOptions{
-		HeadBranch:  String("master"),
 		Name:        "testUpdateCheckRun",
 		HeadSHA:     String("deadbeef"),
 		Status:      String("completed"),
diff --git a/github/git_commits_test.go b/github/git_commits_test.go
index f316f01e..6e858baf 100644
--- a/github/git_commits_test.go
+++ b/github/git_commits_test.go
@@ -18,6 +18,106 @@ import (
 	"golang.org/x/crypto/openpgp"
 )
 
+func TestCommit_Marshal(t *testing.T) {
+	testJSONMarshal(t, &Commit{}, "{}")
+
+	u := &Commit{
+		SHA: String("s"),
+		Author: &CommitAuthor{
+			Date:  &referenceTime,
+			Name:  String("n"),
+			Email: String("e"),
+			Login: String("u"),
+		},
+		Committer: &CommitAuthor{
+			Date:  &referenceTime,
+			Name:  String("n"),
+			Email: String("e"),
+			Login: String("u"),
+		},
+		Message: String("m"),
+		Tree: &Tree{
+			SHA: String("s"),
+			Entries: []TreeEntry{{
+				SHA:     String("s"),
+				Path:    String("p"),
+				Mode:    String("m"),
+				Type:    String("t"),
+				Size:    Int(1),
+				Content: String("c"),
+				URL:     String("u"),
+			}},
+			Truncated: Bool(false),
+		},
+		Parents: nil,
+		Stats: &CommitStats{
+			Additions: Int(1),
+			Deletions: Int(1),
+			Total:     Int(1),
+		},
+		HTMLURL: String("h"),
+		URL:     String("u"),
+		Verification: &SignatureVerification{
+			Verified:  Bool(false),
+			Reason:    String("r"),
+			Signature: String("s"),
+			Payload:   String("p"),
+		},
+		NodeID:       String("n"),
+		CommentCount: Int(1),
+		SigningKey:   &openpgp.Entity{},
+	}
+
+	want := `{
+		"sha": "s",
+		"author": {
+			"date": ` + referenceTimeStr + `,
+			"name": "n",
+			"email": "e",
+			"username": "u"
+		},
+		"committer": {
+			"date": ` + referenceTimeStr + `,
+			"name": "n",
+			"email": "e",
+			"username": "u"
+		},
+		"message": "m",
+		"tree": {
+			"sha": "s",
+			"tree": [
+				{
+					"sha": "s",
+					"path": "p",
+					"mode": "m",
+					"type": "t",
+					"size": 1,
+					"content": "c",
+					"url": "u"
+				}
+			],
+			"truncated": false
+		},
+		"stats": {
+			"additions": 1,
+			"deletions": 1,
+			"total": 1
+		},
+		"html_url": "h",
+		"url": "u",
+		"verification": {
+			"verified": false,
+			"reason": "r",
+			"signature": "s",
+			"payload": "p"
+		},
+		"node_id": "n",
+		"comment_count": 1
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
 func TestGitService_GetCommit(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/git_trees_test.go b/github/git_trees_test.go
index 1f7024fc..1d1b789c 100644
--- a/github/git_trees_test.go
+++ b/github/git_trees_test.go
@@ -186,6 +186,72 @@ func TestGitService_CreateTree_Content(t *testing.T) {
 	}
 }
 
+func TestGitService_CreateTree_Delete(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	input := []TreeEntry{
+		{
+			Path: String("content.md"),
+			Mode: String("100644"),
+		},
+	}
+
+	mux.HandleFunc("/repos/o/r/git/trees", func(w http.ResponseWriter, r *http.Request) {
+		v := new(createTree)
+		json.NewDecoder(r.Body).Decode(v)
+
+		testMethod(t, r, "POST")
+
+		want := &createTree{
+			BaseTree: "b",
+			Entries:  input,
+		}
+		if !reflect.DeepEqual(v, want) {
+			t.Errorf("Git.CreateTree request body: %+v, want %+v", v, want)
+		}
+
+		fmt.Fprint(w, `{
+		  "sha": "5c6780ad2c68743383b740fd1dab6f6a33202b11",
+		  "url": "https://api.github.com/repos/o/r/git/trees/5c6780ad2c68743383b740fd1dab6f6a33202b11",
+		  "tree": [
+		    {
+			  "mode": "100644",
+			  "type": "blob",
+			  "sha":  null,
+			  "path": "content.md",
+			  "size": 12,
+			  "url": "https://api.github.com/repos/o/r/git/blobs/aad8feacf6f8063150476a7b2bd9770f2794c08b"
+		    }
+		  ]
+		}`)
+	})
+
+	tree, _, err := client.Git.CreateTree(context.Background(), "o", "r", "b", input)
+	if err != nil {
+		t.Errorf("Git.CreateTree returned error: %v", err)
+	}
+
+	want := Tree{
+		String("5c6780ad2c68743383b740fd1dab6f6a33202b11"),
+		[]TreeEntry{
+			{
+				Path: String("content.md"),
+				Mode: String("100644"),
+				Type: String("blob"),
+				Size: Int(12),
+				SHA:  nil,
+				URL:  String("https://api.github.com/repos/o/r/git/blobs/aad8feacf6f8063150476a7b2bd9770f2794c08b"),
+			},
+		},
+		nil,
+	}
+
+	if !reflect.DeepEqual(*tree, want) {
+		t.Errorf("Git.CreateTree returned %+v, want %+v", *tree, want)
+	}
+}
+
 func TestGitService_CreateTree_invalidOwner(t *testing.T) {
 	client, _, _, teardown := setup()
 	defer teardown()
diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go
index 22e1aafb..04cd771c 100644
--- a/github/github-stringify_test.go
+++ b/github/github-stringify_test.go
@@ -307,6 +307,25 @@ func TestDraftReviewComment_String(t *testing.T) {
 	}
 }
 
+func TestEnterprise_String(t *testing.T) {
+	v := Enterprise{
+		ID:          Int(0),
+		Slug:        String(""),
+		Name:        String(""),
+		NodeID:      String(""),
+		AvatarURL:   String(""),
+		Description: String(""),
+		WebsiteURL:  String(""),
+		HTMLURL:     String(""),
+		CreatedAt:   &Timestamp{},
+		UpdatedAt:   &Timestamp{},
+	}
+	want := `github.Enterprise{ID:0, Slug:"", Name:"", NodeID:"", AvatarURL:"", Description:"", WebsiteURL:"", HTMLURL:"", CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}`
+	if got := v.String(); got != want {
+		t.Errorf("Enterprise.String = %v, want %v", got, want)
+	}
+}
+
 func TestEvent_String(t *testing.T) {
 	v := Event{
 		Type:   String(""),
@@ -779,6 +798,18 @@ func TestNewTeam_String(t *testing.T) {
 	}
 }
 
+func TestOAuthAPP_String(t *testing.T) {
+	v := OAuthAPP{
+		URL:      String(""),
+		Name:     String(""),
+		ClientID: String(""),
+	}
+	want := `github.OAuthAPP{URL:"", Name:"", ClientID:""}`
+	if got := v.String(); got != want {
+		t.Errorf("OAuthAPP.String = %v, want %v", got, want)
+	}
+}
+
 func TestOrgStats_String(t *testing.T) {
 	v := OrgStats{
 		TotalOrgs:        Int(0),
@@ -852,8 +883,10 @@ func TestPlan_String(t *testing.T) {
 		Space:         Int(0),
 		Collaborators: Int(0),
 		PrivateRepos:  Int(0),
+		FilledSeats:   Int(0),
+		Seats:         Int(0),
 	}
-	want := `github.Plan{Name:"", Space:0, Collaborators:0, PrivateRepos:0}`
+	want := `github.Plan{Name:"", Space:0, Collaborators:0, PrivateRepos:0, FilledSeats:0, Seats:0}`
 	if got := v.String(); got != want {
 		t.Errorf("Plan.String = %v, want %v", got, want)
 	}
diff --git a/github/github_test.go b/github/github_test.go
index 9dfac059..759d76c5 100644
--- a/github/github_test.go
+++ b/github/github_test.go
@@ -6,7 +6,6 @@
 package github
 
 import (
-	"bytes"
 	"context"
 	"encoding/json"
 	"fmt"
@@ -135,32 +134,29 @@ func testBody(t *testing.T, r *http.Request, want string) {
 	}
 }
 
-// Helper function to test that a value is marshalled to JSON as expected.
+// Test whether the marshaling of v produces JSON that corresponds
+// to the want string.
 func testJSONMarshal(t *testing.T, v interface{}, want string) {
-	j, err := json.Marshal(v)
+	// Unmarshal the wanted JSON, to verify its correctness, and marshal it back
+	// to sort the keys.
+	u := reflect.New(reflect.TypeOf(v)).Interface()
+	if err := json.Unmarshal([]byte(want), &u); err != nil {
+		t.Errorf("Unable to unmarshal JSON for %v: %v", want, err)
+	}
+	w, err := json.Marshal(u)
 	if err != nil {
-		t.Errorf("Unable to marshal JSON for %v", v)
+		t.Errorf("Unable to marshal JSON for %#v", u)
 	}
 
-	w := new(bytes.Buffer)
-	err = json.Compact(w, []byte(want))
+	// Marshal the target value.
+	j, err := json.Marshal(v)
 	if err != nil {
-		t.Errorf("String is not valid json: %s", want)
+		t.Errorf("Unable to marshal JSON for %#v", v)
 	}
 
-	if w.String() != string(j) {
+	if string(w) != string(j) {
 		t.Errorf("json.Marshal(%q) returned %s, want %s", v, j, w)
 	}
-
-	// now go the other direction and make sure things unmarshal as expected
-	u := reflect.ValueOf(v).Interface()
-	if err := json.Unmarshal([]byte(want), u); err != nil {
-		t.Errorf("Unable to unmarshal JSON for %v: %v", want, err)
-	}
-
-	if !reflect.DeepEqual(v, u) {
-		t.Errorf("json.Unmarshal(%q) returned %s, want %s", want, u, v)
-	}
 }
 
 func TestNewClient(t *testing.T) {
@@ -862,6 +858,56 @@ func TestCheckResponse(t *testing.T) {
 	}
 }
 
+func TestCheckResponse_RateLimit(t *testing.T) {
+	res := &http.Response{
+		Request:    &http.Request{},
+		StatusCode: http.StatusForbidden,
+		Header:     http.Header{},
+		Body: ioutil.NopCloser(strings.NewReader(`{"message":"m",
+			"documentation_url": "url"}`)),
+	}
+	res.Header.Set(headerRateLimit, "60")
+	res.Header.Set(headerRateRemaining, "0")
+	res.Header.Set(headerRateReset, "243424")
+
+	err := CheckResponse(res).(*RateLimitError)
+
+	if err == nil {
+		t.Errorf("Expected error response.")
+	}
+
+	want := &RateLimitError{
+		Rate:     parseRate(res),
+		Response: res,
+		Message:  "m",
+	}
+	if !reflect.DeepEqual(err, want) {
+		t.Errorf("Error = %#v, want %#v", err, want)
+	}
+}
+
+func TestCheckResponse_AbuseRateLimit(t *testing.T) {
+	res := &http.Response{
+		Request:    &http.Request{},
+		StatusCode: http.StatusForbidden,
+		Body: ioutil.NopCloser(strings.NewReader(`{"message":"m",
+			"documentation_url": "developer.github.com/v3/#abuse-rate-limits"}`)),
+	}
+	err := CheckResponse(res).(*AbuseRateLimitError)
+
+	if err == nil {
+		t.Errorf("Expected error response.")
+	}
+
+	want := &AbuseRateLimitError{
+		Response: res,
+		Message:  "m",
+	}
+	if !reflect.DeepEqual(err, want) {
+		t.Errorf("Error = %#v, want %#v", err, want)
+	}
+}
+
 // ensure that we properly handle API errors that do not contain a response body
 func TestCheckResponse_noBody(t *testing.T) {
 	res := &http.Response{
diff --git a/github/issues_comments_test.go b/github/issues_comments_test.go
index 6911a798..69acb242 100644
--- a/github/issues_comments_test.go
+++ b/github/issues_comments_test.go
@@ -31,10 +31,11 @@ func TestIssuesService_ListComments_allIssues(t *testing.T) {
 		fmt.Fprint(w, `[{"id":1}]`)
 	})
 
+	since := time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC)
 	opt := &IssueListCommentsOptions{
-		Sort:        "updated",
-		Direction:   "desc",
-		Since:       time.Date(2002, time.February, 10, 15, 30, 0, 0, time.UTC),
+		Sort:        String("updated"),
+		Direction:   String("desc"),
+		Since:       &since,
 		ListOptions: ListOptions{Page: 2},
 	}
 	comments, _, err := client.Issues.ListComments(context.Background(), "o", "r", 0, opt)
diff --git a/github/messages_test.go b/github/messages_test.go
index 105904c6..30f7d95f 100644
--- a/github/messages_test.go
+++ b/github/messages_test.go
@@ -339,6 +339,10 @@ func TestParseWebHook(t *testing.T) {
 			payload:     &TeamAddEvent{},
 			messageType: "team_add",
 		},
+		{
+			payload:     &UserEvent{},
+			messageType: "user",
+		},
 		{
 			payload:     &WatchEvent{},
 			messageType: "watch",
diff --git a/github/orgs_test.go b/github/orgs_test.go
index 3b938fc9..c39f6097 100644
--- a/github/orgs_test.go
+++ b/github/orgs_test.go
@@ -14,6 +14,38 @@ import (
 	"testing"
 )
 
+func TestOrganization_marshal(t *testing.T) {
+	testJSONMarshal(t, &Organization{}, "{}")
+
+	o := &Organization{
+		BillingEmail:                         String("support@github.com"),
+		Blog:                                 String("https://github.com/blog"),
+		Company:                              String("GitHub"),
+		Email:                                String("support@github.com"),
+		Location:                             String("San Francisco"),
+		Name:                                 String("github"),
+		Description:                          String("GitHub, the company."),
+		DefaultRepoPermission:                String("read"),
+		MembersCanCreateRepos:                Bool(true),
+		MembersAllowedRepositoryCreationType: String("all"),
+	}
+	want := `
+		{
+			"billing_email": "support@github.com",
+			"blog": "https://github.com/blog",
+			"company": "GitHub",
+			"email": "support@github.com",
+			"location": "San Francisco",
+			"name": "github",
+			"description": "GitHub, the company.",
+			"default_repository_permission": "read",
+			"members_can_create_repositories": true,
+			"members_allowed_repository_creation_type": "all"
+		}
+	`
+	testJSONMarshal(t, o, want)
+}
+
 func TestOrganizationsService_ListAll(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
@@ -172,3 +204,61 @@ func TestOrganizationsService_Edit_invalidOrg(t *testing.T) {
 	_, _, err := client.Organizations.Edit(context.Background(), "%", nil)
 	testURLParseError(t, err)
 }
+
+func TestOrganizationsService_ListInstallations(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/installations", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", mediaTypeIntegrationPreview)
+		fmt.Fprint(w, `{"total_count": 1, "installations": [{ "id": 1, "app_id": 5}]}`)
+	})
+
+	apps, _, err := client.Organizations.ListInstallations(context.Background(), "o", nil)
+	if err != nil {
+		t.Errorf("Organizations.ListInstallations returned error: %v", err)
+	}
+
+	want := &OrganizationInstallations{TotalCount: Int(1), Installations: []*Installation{{ID: Int64(1), AppID: Int64(5)}}}
+	if !reflect.DeepEqual(apps, want) {
+		t.Errorf("Organizations.ListInstallations returned %+v, want %+v", apps, want)
+	}
+}
+
+func TestOrganizationsService_ListInstallations_invalidOrg(t *testing.T) {
+	client, _, _, teardown := setup()
+	defer teardown()
+
+	_, _, err := client.Organizations.ListInstallations(context.Background(), "%", nil)
+	testURLParseError(t, err)
+
+}
+
+func TestOrganizationsService_ListInstallations_withListOptions(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/orgs/o/installations", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", mediaTypeIntegrationPreview)
+		testFormValues(t, r, values{"page": "2"})
+		fmt.Fprint(w, `{"total_count": 2, "installations": [{ "id": 2, "app_id": 10}]}`)
+	})
+
+	apps, _, err := client.Organizations.ListInstallations(context.Background(), "o", &ListOptions{Page: 2})
+	if err != nil {
+		t.Errorf("Organizations.ListInstallations returned error: %v", err)
+	}
+
+	want := &OrganizationInstallations{TotalCount: Int(2), Installations: []*Installation{{ID: Int64(2), AppID: Int64(10)}}}
+	if !reflect.DeepEqual(apps, want) {
+		t.Errorf("Organizations.ListInstallations returned %+v, want %+v", apps, want)
+	}
+
+	// Test ListOptions failure
+	_, _, err = client.Organizations.ListInstallations(context.Background(), "%", &ListOptions{})
+	if err == nil {
+		t.Error("Organizations.ListInstallations returned error: nil")
+	}
+}
diff --git a/github/pulls_reviews_test.go b/github/pulls_reviews_test.go
index 3b78bc9e..25aaca1c 100644
--- a/github/pulls_reviews_test.go
+++ b/github/pulls_reviews_test.go
@@ -14,6 +14,88 @@ import (
 	"testing"
 )
 
+func TestReviewers_marshall(t *testing.T) {
+	testJSONMarshal(t, &Reviewers{}, "{}")
+
+	u := &Reviewers{
+		Users: []*User{{
+			Login:       String("l"),
+			ID:          Int64(1),
+			AvatarURL:   String("a"),
+			GravatarID:  String("g"),
+			Name:        String("n"),
+			Company:     String("c"),
+			Blog:        String("b"),
+			Location:    String("l"),
+			Email:       String("e"),
+			Hireable:    Bool(true),
+			PublicRepos: Int(1),
+			Followers:   Int(1),
+			Following:   Int(1),
+			CreatedAt:   &Timestamp{referenceTime},
+			URL:         String("u"),
+		}},
+		Teams: []*Team{{
+			ID:              Int64(1),
+			NodeID:          String("node"),
+			Name:            String("n"),
+			Description:     String("d"),
+			URL:             String("u"),
+			Slug:            String("s"),
+			Permission:      String("p"),
+			Privacy:         String("priv"),
+			MembersCount:    Int(1),
+			ReposCount:      Int(1),
+			Organization:    nil,
+			MembersURL:      String("m"),
+			RepositoriesURL: String("r"),
+			Parent:          nil,
+			LDAPDN:          String("l"),
+		}},
+	}
+
+	want := `{
+		"users" : [
+			{
+				"login": "l",
+				"id": 1,
+				"avatar_url": "a",
+				"gravatar_id": "g",
+				"name": "n",
+				"company": "c",
+				"blog": "b",
+				"location": "l",
+				"email": "e",
+				"hireable": true,
+				"public_repos": 1,
+				"followers": 1,
+				"following": 1,
+				"created_at": ` + referenceTimeStr + `,
+				"url": "u"
+			}
+		], 
+		"teams" : [
+			{
+				"id": 1,
+				"node_id": "node",
+				"name": "n",
+				"description": "d",
+				"url": "u",
+				"slug": "s",
+				"permission": "p",
+				"privacy": "priv",
+				"members_count": 1,
+				"repos_count": 1,
+				"members_url": "m",
+				"repositories_url": "r",
+				"ldap_dn": "l"
+			}
+		]
+	}`
+
+	testJSONMarshal(t, u, want)
+}
+
 func TestPullRequestsService_ListReviews(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/reactions_test.go b/github/reactions_test.go
index e1baec3f..c32274de 100644
--- a/github/reactions_test.go
+++ b/github/reactions_test.go
@@ -7,11 +7,59 @@ package github
 
 import (
 	"context"
+	"fmt"
 	"net/http"
 	"reflect"
 	"testing"
 )
 
+func TestReaction_Marshal(t *testing.T) {
+	testJSONMarshal(t, &Reaction{}, "{}")
+
+	r := &Reaction{
+		ID:      Int64(1),
+		User:    nil,
+		NodeID:  String("n"),
+		Content: String("+1"),
+	}
+
+	want := `{
+		"id": 1,
+		"node_id": "n",
+		"content": "+1"
+	}`
+
+	testJSONMarshal(t, r, want)
+}
+
+func TestReactions_Marshal(t *testing.T) {
+	testJSONMarshal(t, &Reactions{}, "{}")
+
+	r := &Reactions{
+		TotalCount: Int(1),
+		PlusOne:    Int(1),
+		MinusOne:   Int(1),
+		Laugh:      Int(1),
+		Confused:   Int(1),
+		Heart:      Int(1),
+		Hooray:     Int(1),
+		URL:        String("u"),
+	}
+
+	want := `{
+		"total_count": 1,
+		"+1": 1,
+		"-1": 1,
+		"laugh": 1,
+		"confused": 1,
+		"heart": 1,
+		"hooray": 1,
+		"url": "u"
+	}`
+
+	testJSONMarshal(t, r, want)
+}
+
 func TestReactionsService_ListCommentReactions(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
@@ -20,17 +68,18 @@ func TestReactionsService_ListCommentReactions(t *testing.T) {
 		testMethod(t, r, "GET")
 		testHeader(t, r, "Accept", mediaTypeReactionsPreview)
 
-		w.WriteHeader(http.StatusOK)
-		w.Write([]byte(`[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`))
+		testFormValues(t, r, values{"content": "+1"})
+		fmt.Fprint(w, `[{"id":1,"user":{"login":"l","id":2},"content":"+1"}]`)
 	})
 
-	got, _, err := client.Reactions.ListCommentReactions(context.Background(), "o", "r", 1, nil)
+	opt := &ListCommentReactionOptions{Content: "+1"}
+	reactions, _, err := client.Reactions.ListCommentReactions(context.Background(), "o", "r", 1, opt)
 	if err != nil {
 		t.Errorf("ListCommentReactions returned error: %v", err)
 	}
 	want := []*Reaction{{ID: Int64(1), User: &User{Login: String("l"), ID: Int64(2)}, Content: String("+1")}}
-	if !reflect.DeepEqual(got, want) {
-		t.Errorf("ListCommentReactions = %+v, want %+v", got, want)
+	if !reflect.DeepEqual(reactions, want) {
+		t.Errorf("ListCommentReactions = %+v, want %+v", reactions, want)
 	}
 }
 
diff --git a/github/repos_contents_test.go b/github/repos_contents_test.go
index 29bab8d2..f1225258 100644
--- a/github/repos_contents_test.go
+++ b/github/repos_contents_test.go
@@ -10,6 +10,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"net/http"
+	"net/url"
 	"reflect"
 	"testing"
 )
@@ -375,7 +376,46 @@ func TestRepositoriesService_GetArchiveLink(t *testing.T) {
 		testMethod(t, r, "GET")
 		http.Redirect(w, r, "http://github.com/a", http.StatusFound)
 	})
-	url, resp, err := client.Repositories.GetArchiveLink(context.Background(), "o", "r", Tarball, &RepositoryContentGetOptions{})
+	url, resp, err := client.Repositories.GetArchiveLink(context.Background(), "o", "r", Tarball, &RepositoryContentGetOptions{}, true)
+	if err != nil {
+		t.Errorf("Repositories.GetArchiveLink returned error: %v", err)
+	}
+	if resp.StatusCode != http.StatusFound {
+		t.Errorf("Repositories.GetArchiveLink returned status: %d, want %d", resp.StatusCode, http.StatusFound)
+	}
+	want := "http://github.com/a"
+	if url.String() != want {
+		t.Errorf("Repositories.GetArchiveLink returned %+v, want %+v", url.String(), want)
+	}
+}
+
+func TestRepositoriesService_GetArchiveLink_StatusMovedPermanently_dontFollowRedirects(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+	mux.HandleFunc("/repos/o/r/tarball", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		http.Redirect(w, r, "http://github.com/a", http.StatusMovedPermanently)
+	})
+	_, resp, _ := client.Repositories.GetArchiveLink(context.Background(), "o", "r", Tarball, &RepositoryContentGetOptions{}, false)
+	if resp.StatusCode != http.StatusMovedPermanently {
+		t.Errorf("Repositories.GetArchiveLink returned status: %d, want %d", resp.StatusCode, http.StatusMovedPermanently)
+	}
+}
+
+func TestRepositoriesService_GetArchiveLink_StatusMovedPermanently_followRedirects(t *testing.T) {
+	client, mux, serverURL, teardown := setup()
+	defer teardown()
+	// Mock a redirect link, which leads to an archive link
+	mux.HandleFunc("/repos/o/r/tarball", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		redirectURL, _ := url.Parse(serverURL + baseURLPath + "/redirect")
+		http.Redirect(w, r, redirectURL.String(), http.StatusMovedPermanently)
+	})
+	mux.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		http.Redirect(w, r, "http://github.com/a", http.StatusFound)
+	})
+	url, resp, err := client.Repositories.GetArchiveLink(context.Background(), "o", "r", Tarball, &RepositoryContentGetOptions{}, true)
 	if err != nil {
 		t.Errorf("Repositories.GetArchiveLink returned error: %v", err)
 	}
diff --git a/github/repos_pages_test.go b/github/repos_pages_test.go
index 62d42470..ec77f717 100644
--- a/github/repos_pages_test.go
+++ b/github/repos_pages_test.go
@@ -10,7 +10,6 @@ import (
 	"fmt"
 	"net/http"
 	"reflect"
-	"strings"
 	"testing"
 )
 
@@ -20,8 +19,7 @@ func TestRepositoriesService_EnablePages(t *testing.T) {
 
 	mux.HandleFunc("/repos/o/r/pages", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "POST")
-		wantAcceptHeaders := []string{mediaTypeEnablePagesAPIPreview, mediaTypePagesPreview}
-		testHeader(t, r, "Accept", strings.Join(wantAcceptHeaders, ", "))
+		testHeader(t, r, "Accept", mediaTypeEnablePagesAPIPreview)
 		fmt.Fprint(w, `{"url":"u","status":"s","cname":"c","custom_404":false,"html_url":"h", "source": {"branch":"master", "path":"/"}}`)
 	})
 
@@ -58,7 +56,6 @@ func TestRepositoriesService_GetPagesInfo(t *testing.T) {
 
 	mux.HandleFunc("/repos/o/r/pages", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
-		testHeader(t, r, "Accept", mediaTypePagesPreview)
 		fmt.Fprint(w, `{"url":"u","status":"s","cname":"c","custom_404":false,"html_url":"h"}`)
 	})
 
@@ -157,7 +154,6 @@ func TestRepositoriesService_RequestPageBuild(t *testing.T) {
 
 	mux.HandleFunc("/repos/o/r/pages/builds", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "POST")
-		testHeader(t, r, "Accept", mediaTypePagesPreview)
 		fmt.Fprint(w, `{"url":"u","status":"s"}`)
 	})
 
diff --git a/github/repos_test.go b/github/repos_test.go
index 5ed6682c..3b2893bc 100644
--- a/github/repos_test.go
+++ b/github/repos_test.go
@@ -630,6 +630,27 @@ func TestRepositoriesService_Edit_invalidOwner(t *testing.T) {
 	testURLParseError(t, err)
 }
 
+func TestRepositoriesService_GetVulnerabilityAlerts(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/vulnerability-alerts", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", mediaTypeRequiredVulnerabilityAlertsPreview)
+
+		w.WriteHeader(http.StatusNoContent)
+	})
+
+	vulnerabilityAlertsEnabled, _, err := client.Repositories.GetVulnerabilityAlerts(context.Background(), "o", "r")
+	if err != nil {
+		t.Errorf("Repositories.GetVulnerabilityAlerts returned error: %v", err)
+	}
+
+	if want := true; vulnerabilityAlertsEnabled != want {
+		t.Errorf("Repositories.GetVulnerabilityAlerts returned %+v, want %+v", vulnerabilityAlertsEnabled, want)
+	}
+}
+
 func TestRepositoriesService_EnableVulnerabilityAlerts(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
@@ -907,7 +928,7 @@ func TestRepositoriesService_GetBranchProtection(t *testing.T) {
 		},
 		RequiredPullRequestReviews: &PullRequestReviewsEnforcement{
 			DismissStaleReviews: true,
-			DismissalRestrictions: DismissalRestrictions{
+			DismissalRestrictions: &DismissalRestrictions{
 				Users: []*User{
 					{Login: String("u"), ID: Int64(3)},
 				},
@@ -936,6 +957,70 @@ func TestRepositoriesService_GetBranchProtection(t *testing.T) {
 	}
 }
 
+func TestRepositoriesService_GetBranchProtection_noDismissalRestrictions(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/branches/b/protection", func(w http.ResponseWriter, r *http.Request) {
+
+		testMethod(t, r, "GET")
+		// TODO: remove custom Accept header when this API fully launches
+		testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview)
+		fmt.Fprintf(w, `{
+				"required_status_checks":{
+					"strict":true,
+					"contexts":["continuous-integration"]
+				},
+				"required_pull_request_reviews":{
+					"dismiss_stale_reviews":true,
+					"require_code_owner_reviews":true,
+					"required_approving_review_count":1
+					},
+					"enforce_admins":{
+						"url":"/repos/o/r/branches/b/protection/enforce_admins",
+						"enabled":true
+					},
+					"restrictions":{
+						"users":[{"id":1,"login":"u"}],
+						"teams":[{"id":2,"slug":"t"}]
+					}
+				}`)
+	})
+
+	protection, _, err := client.Repositories.GetBranchProtection(context.Background(), "o", "r", "b")
+	if err != nil {
+		t.Errorf("Repositories.GetBranchProtection returned error: %v", err)
+	}
+
+	want := &Protection{
+		RequiredStatusChecks: &RequiredStatusChecks{
+			Strict:   true,
+			Contexts: []string{"continuous-integration"},
+		},
+		RequiredPullRequestReviews: &PullRequestReviewsEnforcement{
+			DismissStaleReviews:          true,
+			DismissalRestrictions:        nil,
+			RequireCodeOwnerReviews:      true,
+			RequiredApprovingReviewCount: 1,
+		},
+		EnforceAdmins: &AdminEnforcement{
+			URL:     String("/repos/o/r/branches/b/protection/enforce_admins"),
+			Enabled: true,
+		},
+		Restrictions: &BranchRestrictions{
+			Users: []*User{
+				{Login: String("u"), ID: Int64(1)},
+			},
+			Teams: []*Team{
+				{Slug: String("t"), ID: Int64(2)},
+			},
+		},
+	}
+	if !reflect.DeepEqual(protection, want) {
+		t.Errorf("Repositories.GetBranchProtection returned %+v, want %+v", protection, want)
+	}
+}
+
 func TestRepositoriesService_UpdateBranchProtection(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
@@ -955,6 +1040,7 @@ func TestRepositoriesService_UpdateBranchProtection(t *testing.T) {
 		Restrictions: &BranchRestrictionsRequest{
 			Users: []string{"u"},
 			Teams: []string{"t"},
+			Apps:  []string{"a"},
 		},
 	}
 
@@ -990,7 +1076,8 @@ func TestRepositoriesService_UpdateBranchProtection(t *testing.T) {
 			},
 			"restrictions":{
 				"users":[{"id":1,"login":"u"}],
-				"teams":[{"id":2,"slug":"t"}]
+				"teams":[{"id":2,"slug":"t"}],
+				"apps":[{"id":3,"slug":"a"}]
 			}
 		}`)
 	})
@@ -1007,7 +1094,7 @@ func TestRepositoriesService_UpdateBranchProtection(t *testing.T) {
 		},
 		RequiredPullRequestReviews: &PullRequestReviewsEnforcement{
 			DismissStaleReviews: true,
-			DismissalRestrictions: DismissalRestrictions{
+			DismissalRestrictions: &DismissalRestrictions{
 				Users: []*User{
 					{Login: String("uu"), ID: Int64(3)},
 				},
@@ -1024,6 +1111,9 @@ func TestRepositoriesService_UpdateBranchProtection(t *testing.T) {
 			Teams: []*Team{
 				{Slug: String("t"), ID: Int64(2)},
 			},
+			Apps: []*App{
+				{Slug: String("a"), ID: Int64(3)},
+			},
 		},
 	}
 	if !reflect.DeepEqual(protection, want) {
@@ -1201,7 +1291,7 @@ func TestRepositoriesService_GetPullRequestReviewEnforcement(t *testing.T) {
 
 	want := &PullRequestReviewsEnforcement{
 		DismissStaleReviews: true,
-		DismissalRestrictions: DismissalRestrictions{
+		DismissalRestrictions: &DismissalRestrictions{
 			Users: []*User{
 				{Login: String("u"), ID: Int64(1)},
 			},
@@ -1257,7 +1347,7 @@ func TestRepositoriesService_UpdatePullRequestReviewEnforcement(t *testing.T) {
 
 	want := &PullRequestReviewsEnforcement{
 		DismissStaleReviews: true,
-		DismissalRestrictions: DismissalRestrictions{
+		DismissalRestrictions: &DismissalRestrictions{
 			Users: []*User{
 				{Login: String("u"), ID: Int64(1)},
 			},
@@ -1281,8 +1371,8 @@ func TestRepositoriesService_DisableDismissalRestrictions(t *testing.T) {
 		testMethod(t, r, "PATCH")
 		// TODO: remove custom Accept header when this API fully launches
 		testHeader(t, r, "Accept", mediaTypeRequiredApprovingReviewsPreview)
-		testBody(t, r, `{"dismissal_restrictions":[]}`+"\n")
-		fmt.Fprintf(w, `{"dismissal_restrictions":{"users":[],"teams":[]},"dismiss_stale_reviews":true,"require_code_owner_reviews":true,"required_approving_review_count":1}`)
+		testBody(t, r, `{"dismissal_restrictions":{}}`+"\n")
+		fmt.Fprintf(w, `{"dismiss_stale_reviews":true,"require_code_owner_reviews":true,"required_approving_review_count":1}`)
 	})
 
 	enforcement, _, err := client.Repositories.DisableDismissalRestrictions(context.Background(), "o", "r", "b")
@@ -1291,11 +1381,8 @@ func TestRepositoriesService_DisableDismissalRestrictions(t *testing.T) {
 	}
 
 	want := &PullRequestReviewsEnforcement{
-		DismissStaleReviews: true,
-		DismissalRestrictions: DismissalRestrictions{
-			Users: []*User{},
-			Teams: []*Team{},
-		},
+		DismissStaleReviews:          true,
+		DismissalRestrictions:        nil,
 		RequireCodeOwnerReviews:      true,
 		RequiredApprovingReviewCount: 1,
 	}
@@ -1603,6 +1690,85 @@ func TestRepositoriesService_ReplaceAllTopics_emptySlice(t *testing.T) {
 	}
 }
 
+func TestRepositoriesService_ListApps(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/branches/b/protection/restrictions/apps", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+	})
+
+	_, _, err := client.Repositories.ListApps(context.Background(), "o", "r", "b")
+	if err != nil {
+		t.Errorf("Repositories.ListApps returned error: %v", err)
+	}
+}
+
+func TestRepositoriesService_ReplaceAppRestrictions(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/branches/b/protection/restrictions/apps", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "PUT")
+		fmt.Fprint(w, `[{
+				"name": "octocat"
+			}]`)
+	})
+	input := []string{"octocat"}
+	got, _, err := client.Repositories.ReplaceAppRestrictions(context.Background(), "o", "r", "b", input)
+	if err != nil {
+		t.Errorf("Repositories.ReplaceAppRestrictions returned error: %v", err)
+	}
+	want := []*App{
+		{Name: String("octocat")},
+	}
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("Repositories.ReplaceAppRestrictions returned %+v, want %+v", got, want)
+	}
+}
+
+func TestRepositoriesService_AddAppRestrictions(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/branches/b/protection/restrictions/apps", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		fmt.Fprint(w, `[{
+				"name": "octocat"
+			}]`)
+	})
+	input := []string{"octocat"}
+	got, _, err := client.Repositories.AddAppRestrictions(context.Background(), "o", "r", "b", input)
+	if err != nil {
+		t.Errorf("Repositories.AddAppRestrictions returned error: %v", err)
+	}
+	want := []*App{
+		{Name: String("octocat")},
+	}
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("Repositories.AddAppRestrictions returned %+v, want %+v", got, want)
+	}
+}
+
+func TestRepositoriesService_RemoveAppRestrictions(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/repos/o/r/branches/b/protection/restrictions/apps", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "DELETE")
+		fmt.Fprint(w, `[]`)
+	})
+	input := []string{"octocat"}
+	got, _, err := client.Repositories.RemoveAppRestrictions(context.Background(), "o", "r", "b", input)
+	if err != nil {
+		t.Errorf("Repositories.RemoveAppRestrictions returned error: %v", err)
+	}
+	want := []*App{}
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("Repositories.RemoveAppRestrictions returned %+v, want %+v", got, want)
+	}
+}
+
 func TestRepositoriesService_Transfer(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/search_test.go b/github/search_test.go
index 4b686884..7b607569 100644
--- a/github/search_test.go
+++ b/github/search_test.go
@@ -47,6 +47,37 @@ func TestSearchService_Repositories(t *testing.T) {
 	}
 }
 
+func TestSearchService_Topics(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/search/topics", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testFormValues(t, r, values{
+			"q":        "blah",
+			"page":     "2",
+			"per_page": "2",
+		})
+
+		fmt.Fprint(w, `{"total_count": 4, "incomplete_results": false, "items": [{"name":"blah"},{"name":"blahblah"}]}`)
+	})
+
+	opts := &SearchOptions{ListOptions: ListOptions{Page: 2, PerPage: 2}}
+	result, _, err := client.Search.Topics(context.Background(), "blah", opts)
+	if err != nil {
+		t.Errorf("Search.Topics returned error: %v", err)
+	}
+
+	want := &TopicsSearchResult{
+		Total:             Int(4),
+		IncompleteResults: Bool(false),
+		Topics:            []*TopicResult{{Name: String("blah")}, {Name: String("blahblah")}},
+	}
+	if !reflect.DeepEqual(result, want) {
+		t.Errorf("Search.Topics returned %+v, want %+v", result, want)
+	}
+}
+
 func TestSearchService_Commits(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
diff --git a/github/teams_test.go b/github/teams_test.go
index 6210bb59..97dc325d 100644
--- a/github/teams_test.go
+++ b/github/teams_test.go
@@ -608,7 +608,6 @@ func TestTeamsService_ListIDPGroupsInOrganization(t *testing.T) {
 
 	mux.HandleFunc("/orgs/o/team-sync/groups", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
-		testHeader(t, r, "Accept", mediaTypeTeamSyncPreview)
 		testFormValues(t, r, values{
 			"page": "2",
 		})
@@ -641,7 +640,6 @@ func TestTeamsService_ListIDPGroupsForTeam(t *testing.T) {
 
 	mux.HandleFunc("/teams/1/team-sync/group-mappings", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
-		testHeader(t, r, "Accept", mediaTypeTeamSyncPreview)
 		fmt.Fprint(w, `{"groups": [{"group_id": "1",  "group_name": "n", "group_description": "d"}]}`)
 	})
 
@@ -670,7 +668,6 @@ func TestTeamsService_CreateOrUpdateIDPGroupConnections(t *testing.T) {
 
 	mux.HandleFunc("/teams/1/team-sync/group-mappings", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "PATCH")
-		testHeader(t, r, "Accept", mediaTypeTeamSyncPreview)
 		fmt.Fprint(w, `{"groups": [{"group_id": "1",  "group_name": "n", "group_description": "d"}]}`)
 	})
 
diff --git a/github/users_projects_test.go b/github/users_projects_test.go
new file mode 100644
index 00000000..b186fbf0
--- /dev/null
+++ b/github/users_projects_test.go
@@ -0,0 +1,68 @@
+// Copyright 2019 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"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"reflect"
+	"testing"
+)
+
+func TestUsersService_ListProjects(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	mux.HandleFunc("/users/u/projects", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "GET")
+		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
+		testFormValues(t, r, values{"state": "open", "page": "2"})
+		fmt.Fprint(w, `[{"id":1}]`)
+	})
+
+	opt := &ProjectListOptions{State: "open", ListOptions: ListOptions{Page: 2}}
+	projects, _, err := client.Users.ListProjects(context.Background(), "u", opt)
+	if err != nil {
+		t.Errorf("Users.ListProjects returned error: %v", err)
+	}
+
+	want := []*Project{{ID: Int64(1)}}
+	if !reflect.DeepEqual(projects, want) {
+		t.Errorf("Users.ListProjects returned %+v, want %+v", projects, want)
+	}
+}
+
+func TestUsersService_CreateProject(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	input := &CreateUserProjectOptions{Name: "Project Name", Body: String("Project body.")}
+
+	mux.HandleFunc("/users/projects", func(w http.ResponseWriter, r *http.Request) {
+		testMethod(t, r, "POST")
+		testHeader(t, r, "Accept", mediaTypeProjectsPreview)
+
+		v := &CreateUserProjectOptions{}
+		json.NewDecoder(r.Body).Decode(v)
+		if !reflect.DeepEqual(v, input) {
+			t.Errorf("Request body = %+v, want %+v", v, input)
+		}
+
+		fmt.Fprint(w, `{"id":1}`)
+	})
+
+	project, _, err := client.Users.CreateProject(context.Background(), input)
+	if err != nil {
+		t.Errorf("Users.CreateProject returned error: %v", err)
+	}
+
+	want := &Project{ID: Int64(1)}
+	if !reflect.DeepEqual(project, want) {
+		t.Errorf("Users.CreateProject returned %+v, want %+v", project, want)
+	}
+}
diff --git a/scrape/apps_test.go b/scrape/apps_test.go
new file mode 100644
index 00000000..b895b667
--- /dev/null
+++ b/scrape/apps_test.go
@@ -0,0 +1,105 @@
+package scrape
+
+import (
+	"net/http"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-github/v28/github"
+)
+
+func Test_AppRestrictionsEnabled(t *testing.T) {
+	tests := []struct {
+		description string
+		testFile    string
+		org         string
+		want        bool
+	}{
+		{
+			description: "return true for enabled orgs",
+			testFile:    "access-restrictions-enabled.html",
+			want:        true,
+		},
+		{
+			description: "return false for disabled orgs",
+			testFile:    "access-restrictions-disabled.html",
+			want:        false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.description, func(t *testing.T) {
+			client, mux, cleanup := setup()
+			defer cleanup()
+
+			mux.HandleFunc("/organizations/o/settings/oauth_application_policy", func(w http.ResponseWriter, r *http.Request) {
+				copyTestFile(w, tt.testFile)
+			})
+
+			got, err := client.AppRestrictionsEnabled("o")
+			if err != nil {
+				t.Errorf("AppRestrictionsEnabled returned err: %v", err)
+			}
+			if want := tt.want; got != want {
+				t.Errorf("AppRestrictionsEnabled returned %t, want %t", got, want)
+			}
+
+		})
+	}
+}
+
+func Test_ListOAuthApps(t *testing.T) {
+	client, mux, cleanup := setup()
+	defer cleanup()
+
+	mux.HandleFunc("/organizations/e/settings/oauth_application_policy", func(w http.ResponseWriter, r *http.Request) {
+		copyTestFile(w, "access-restrictions-enabled.html")
+	})
+
+	got, err := client.ListOAuthApps("e")
+	if err != nil {
+		t.Errorf("ListOAuthApps(e) returned err: %v", err)
+	}
+	want := []OAuthApp{
+		{
+			ID:          22222,
+			Name:        "Coveralls",
+			Description: "Test coverage history and statistics.",
+			State:       OAuthAppRequested,
+			RequestedBy: "willnorris",
+		},
+		{
+			ID:    530107,
+			Name:  "Google Cloud Platform",
+			State: OAuthAppApproved,
+		},
+		{
+			ID:          231424,
+			Name:        "GitKraken",
+			Description: "An intuitive, cross-platform Git client that doesn't suck, built by @axosoft and made with @nodegit & @ElectronJS.",
+			State:       OAuthAppDenied,
+		},
+	}
+	if !cmp.Equal(got, want) {
+		t.Errorf("ListOAuthApps(o) returned %v, want %v", got, want)
+	}
+
+}
+
+func Test_CreateApp(t *testing.T) {
+	client, mux, cleanup := setup()
+	defer cleanup()
+
+	mux.HandleFunc("/apps/settings/new", func(w http.ResponseWriter, r *http.Request) {
+		w.WriteHeader(http.StatusCreated)
+	})
+
+	if _, err := client.CreateApp(&AppManifest{
+		URL: github.String("https://example.com"),
+		HookAttributes: map[string]string{
+			"url": "https://example.com/hook",
+		},
+	}); err != nil {
+		t.Fatalf("CreateApp: %v", err)
+	}
+}
diff --git a/scrape/forms_test.go b/scrape/forms_test.go
new file mode 100644
index 00000000..f45010cf
--- /dev/null
+++ b/scrape/forms_test.go
@@ -0,0 +1,78 @@
+package scrape
+
+import (
+	"fmt"
+	"net/http"
+	"net/url"
+	"strings"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"golang.org/x/net/html"
+)
+
+func Test_ParseForms(t *testing.T) {
+	tests := []struct {
+		description string
+		html        string
+		forms       []htmlForm
+	}{
+		{"no forms", `<html></html>`, nil},
+		{"empty form", `<html><form></form></html>`, []htmlForm{{Values: url.Values{}}}},
+		{
+			"single form with one value",
+			`<html><form action="a" method="m"><input name="n1" value="v1"></form></html>`,
+			[]htmlForm{{Action: "a", Method: "m", Values: url.Values{"n1": {"v1"}}}},
+		},
+		{
+			"two forms",
+			`<html>
+			  <form action="a1" method="m1"><input name="n1" value="v1"></form>
+			  <form action="a2" method="m2"><input name="n2" value="v2"></form>
+			</html>`,
+			[]htmlForm{
+				{Action: "a1", Method: "m1", Values: url.Values{"n1": {"v1"}}},
+				{Action: "a2", Method: "m2", Values: url.Values{"n2": {"v2"}}},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.description, func(t *testing.T) {
+			node, err := html.Parse(strings.NewReader(tt.html))
+			if err != nil {
+				t.Errorf("error parsing html: %v", err)
+			}
+			if got, want := parseForms(node), tt.forms; !cmp.Equal(got, want) {
+				t.Errorf("parseForms(%q) returned %+v, want %+v", tt.html, got, want)
+			}
+		})
+	}
+}
+
+func Test_FetchAndSumbitForm(t *testing.T) {
+	client, mux, cleanup := setup()
+	defer cleanup()
+	var submitted bool
+
+	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		fmt.Fprint(w, `<html><form action="/submit">
+		  <input type="hidden" name="hidden" value="h">
+		  <input type="text" name="name">
+		</form></html>`)
+	})
+	mux.HandleFunc("/submit", func(w http.ResponseWriter, r *http.Request) {
+		r.ParseForm()
+		want := url.Values{"hidden": {"h"}, "name": {"n"}}
+		if got := r.Form; !cmp.Equal(got, want) {
+			t.Errorf("submitted form contained values %v, want %v", got, want)
+		}
+		submitted = true
+	})
+
+	setValues := func(values url.Values) { values.Set("name", "n") }
+	fetchAndSubmitForm(client.Client, client.baseURL.String()+"/", setValues)
+	if !submitted {
+		t.Error("form was never submitted")
+	}
+}
diff --git a/scrape/scrape_test.go b/scrape/scrape_test.go
new file mode 100644
index 00000000..74e867b6
--- /dev/null
+++ b/scrape/scrape_test.go
@@ -0,0 +1,33 @@
+package scrape
+
+import (
+	"io"
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"os"
+)
+
+// setup a test HTTP server along with a scrape.Client that is configured to
+// talk to that test server. Tests should register handlers on the mux which
+// provide mock responses for the GitHub pages being tested.
+func setup() (client *Client, mux *http.ServeMux, cleanup func()) {
+	mux = http.NewServeMux()
+	server := httptest.NewServer(mux)
+
+	client = NewClient(nil)
+	client.baseURL, _ = url.Parse(server.URL + "/")
+
+	return client, mux, server.Close
+}
+
+func copyTestFile(w io.Writer, filename string) error {
+	f, err := os.Open("testdata/" + filename)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	_, err = io.Copy(w, f)
+	return err
+}
diff --git a/scrape/testdata/access-restrictions-disabled.html b/scrape/testdata/access-restrictions-disabled.html
new file mode 100644
index 00000000..97e81339
--- /dev/null
+++ b/scrape/testdata/access-restrictions-disabled.html
@@ -0,0 +1,39 @@
+<!-- fetched from https://github.com/organizations/google-test3/settings/oauth_application_policy on 2019-10-15
+     Some extraneous markup removed to keep the file size smaller -->
+<html lang="en">
+  <body class="logged-in env-production min-width-lg">
+    
+  <div class="application-main " data-commit-hovercards-enabled>
+      <main id="js-pjax-container" data-pjax-container>
+        
+<div itemscope itemtype="http://schema.org/Organization">
+
+  <div class="container-lg px-3 d-flex">
+
+    <div class="col-9">
+      
+  <div class="boxed-group oauth-application-whitelist ">
+    <h3>Third-party application access policy</h3>
+    <div class="boxed-group-inner">
+        <p>
+          Policy: <strong>No restrictions</strong> <svg class="octicon octicon-alert" viewBox="0 0 16 16" version="1.1" width="16" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"/></svg>
+        </p>
+        <p>
+          All applications authorized by organization members have access to <strong>google-test</strong>’s data.
+        </p>
+        <a class="btn" href="/organizations/google-test/settings/oauth_application_policy/confirm">Setup application access restrictions</a>
+      <p class="help">
+        <svg class="octicon octicon-question" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M6 10h2v2H6v-2zm4-3.5C10 8.64 8 9 8 9H6c0-.55.45-1 1-1h.5c.28 0 .5-.22.5-.5v-1c0-.28-.22-.5-.5-.5h-1c-.28 0-.5.22-.5.5V7H4c0-1.5 1.5-3 3-3s3 1 3 2.5zM7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7z"/></svg>
+        When authorized, applications can act on behalf of organization members. Your access policy determines which applications can access data in your organization. <a href="https://help.github.com/articles/about-third-party-application-restrictions/">Read more about third-party access and organizations.</a>
+      </p>
+    </div>
+  </div>
+
+    </div>
+  </div>
+</div>
+
+      </main>
+  </div>
+  </body>
+</html>
diff --git a/scrape/testdata/access-restrictions-enabled.html b/scrape/testdata/access-restrictions-enabled.html
new file mode 100644
index 00000000..39ef4b4c
--- /dev/null
+++ b/scrape/testdata/access-restrictions-enabled.html
@@ -0,0 +1,117 @@
+<!-- fetched from https://github.com/organizations/google-test3/settings/oauth_application_policy on 2019-10-15
+     Some extraneous markup removed to keep the file size smaller -->
+<html lang="en">
+  <body class="logged-in env-production min-width-lg">
+    
+  <div class="application-main " data-commit-hovercards-enabled>
+      <main id="js-pjax-container" data-pjax-container>
+        
+
+
+<div itemscope itemtype="http://schema.org/Organization">
+
+  <div class="container-lg px-3 d-flex">
+
+    <div class="col-9">
+      
+  <div class="boxed-group oauth-application-whitelist is-selectable">
+    <h3>Third-party application access policy</h3>
+    <div class="boxed-group-inner">
+        <p>
+          Policy: <strong>Access restricted</strong> <svg class="octicon octicon-check" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5L12 5z"/></svg>
+        </p>
+        <p>
+          Only approved applications can access data in this organization. Applications owned by <strong>google-test</strong> always have access.
+        </p>
+        
+<details
+  class="details-reset details-overlay details-overlay-dark lh-default text-gray-dark d-inline-block text-left"
+  >
+  <summary
+      class="btn btn-danger"
+     
+      
+      
+      
+      >
+    
+    Remove restrictions
+  </summary>
+  <details-dialog
+    aria-label="Are you sure?"
+    class="Box Box--overlay d-flex flex-column anim-fade-in fast "
+    
+    >
+    <div class="Box-header">
+      <button class="Box-btn-octicon btn-octicon float-right" type="button" aria-label="Close dialog" data-close-dialog>
+        <svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/></svg>
+      </button>
+      <h3 class="Box-title ">Are you sure?</h3>
+    </div>
+    
+          <div class="flash flash-full flash-warn">
+            You’re about to remove all third-party application restrictions. Please read this carefully.
+          </div>
+          <div class="Box-body">
+            <p>Removing third-party application restrictions will immediately give member authorized applications access to private data in the <strong>google-test</strong> organization.</p>
+            <p class="mb-0">Please be sure you want to do this.</p>
+          </div>
+          <div class="Box-footer">
+            <!-- '"` --><!-- </textarea></xmp> --></option></form><form action="/orgs/google-test/application_access" accept-charset="UTF-8" method="post"><input name="utf8" type="hidden" value="&#x2713;" /><input type="hidden" name="_method" value="put" /><input type="hidden" name="authenticity_token" value="S2KgMWzbT4gdPkJs7vxVqfwsObkhjk+bto0ulPixFALoIG2N9rlX7UhmoSm/H6uk9VIccrTHnr7WTBOFtlTogQ==" />
+              <input type="hidden" name="restrict_access" id="restrict_access" value="off" class="form-control" />
+              <button type="submit" class="btn btn-block btn-danger">
+                Yes, remove application restrictions
+              </button>
+</form>          </div>
+
+  </details-dialog>
+</details>
+          <ul class="boxed-group-list">
+            <li>
+              <div class="float-right request-indicator">
+                Approval requested by <a class="requestor" href="/willnorris">willnorris</a> &#8212; 
+                <a href="/orgs/google-test/policies/applications/22222">Review</a>
+              </div>
+              <img class="avatar float-left" src="https://avatars3.githubusercontent.com/oa/22222?s=40&amp;u=90a5890234f8061be4c6c0b29b4fa7594f2ba2d7&amp;v=4" height="20" width="20" alt="" />
+              <span class="request-info js-details-container Details">
+                <strong title="Coveralls">Coveralls</strong> <span class="hidden-text-expander inline"><button type="button" class="ellipsis-expander js-details-target" aria-expanded="false">&hellip;</button></span> <span class="application-description">Test coverage history and statistics.</span>
+              </span>
+            </li>
+            <li>
+              <span class="float-right request-indicator">
+                <span class="approved-request">
+                  <svg class="octicon octicon-check" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M12 5l-8 8-4-4 1.5-1.5L4 10l6.5-6.5L12 5z"/></svg> Approved</span> &#8212; <a class="edit-link" href="/orgs/google-test/policies/applications/530107">
+                    <svg aria-label="Update policy" class="octicon octicon-pencil" viewBox="0 0 14 16" version="1.1" width="14" height="16" role="img"><path fill-rule="evenodd" d="M0 12v3h3l8-8-3-3-8 8zm3 2H1v-2h1v1h1v1zm10.3-9.3L12 6 9 3l1.3-1.3a.996.996 0 0 1 1.41 0l1.59 1.59c.39.39.39 1.02 0 1.41z"/></svg>
+</a>              </span>
+              <img class="avatar float-left" src="https://avatars0.githubusercontent.com/oa/530107?s=40&amp;u=c2e8a853e3ef18a65cc0d3628cb3d1c12363ea90&amp;v=4" height="20" width="20" alt="" />
+              <span class="request-info js-details-container Details">
+                <strong title="Google Cloud Platform">Google Cloud Platform</strong>
+              </span>
+            </li>
+            <li>
+              <span class="float-right request-indicator">
+                <span class="denied-request">
+                  <svg class="octicon octicon-x" viewBox="0 0 12 16" version="1.1" width="12" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48L7.48 8z"/></svg> Denied</span> &#8212; <a class="edit-link" href="/orgs/google-test/policies/applications/231424">
+                    <svg aria-label="Update policy" class="octicon octicon-pencil" viewBox="0 0 14 16" version="1.1" width="14" height="16" role="img"><path fill-rule="evenodd" d="M0 12v3h3l8-8-3-3-8 8zm3 2H1v-2h1v1h1v1zm10.3-9.3L12 6 9 3l1.3-1.3a.996.996 0 0 1 1.41 0l1.59 1.59c.39.39.39 1.02 0 1.41z"/></svg>
+</a>              </span>
+              <img class="avatar float-left" src="https://avatars2.githubusercontent.com/oa/231424?s=40&amp;u=136599829eeacb9107cf422a684d11c5757c0eee&amp;v=4" height="20" width="20" alt="" />
+              <span class="request-info js-details-container Details">
+                <strong title="GitKraken">GitKraken</strong> <span class="hidden-text-expander inline"><button type="button" class="ellipsis-expander js-details-target" aria-expanded="false">&hellip;</button></span> <span class="application-description">An intuitive, cross-platform Git client that doesn&#39;t suck, built by @axosoft and made with @nodegit &amp; @ElectronJS.</span>
+              </span>
+            </li>
+          </ul>
+      <p class="help">
+        <svg class="octicon octicon-question" viewBox="0 0 14 16" version="1.1" width="14" height="16" aria-hidden="true"><path fill-rule="evenodd" d="M6 10h2v2H6v-2zm4-3.5C10 8.64 8 9 8 9H6c0-.55.45-1 1-1h.5c.28 0 .5-.22.5-.5v-1c0-.28-.22-.5-.5-.5h-1c-.28 0-.5.22-.5.5V7H4c0-1.5 1.5-3 3-3s3 1 3 2.5zM7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7z"/></svg>
+        When authorized, applications can act on behalf of organization members. Your access policy determines which applications can access data in your organization. <a href="https://help.github.com/articles/about-third-party-application-restrictions/">Read more about third-party access and organizations.</a>
+      </p>
+    </div>
+  </div>
+
+    </div>
+  </div>
+</div>
+
+      </main>
+  </div>
+  </body>
+</html>