Repository: google/go-github
Pull request #1238 (2019-07-23 15:54:38)

Update apps.go
Add installation token parameters, allowing creation of permission-scoped github tokens.

https://developer.github.com/v3/apps/#create-a-new-installation-token

googlebot: 
Thanks for your pull request. It looks like this may be your first contribution to a Google open source project (if not, look below for help). Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please visit <https://cla.developers.google.com/> to sign.**

Once you've signed (or fixed any issues), please reply here (e.g. `I signed it!`) and we'll verify it.

----

#### What to do if you already signed the CLA

##### Individual signers

*   It's possible we don't have your GitHub username or you're using a different email address on your commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).

##### Corporate signers

*   Your company has a Point of Contact who decides which employees are authorized to participate. Ask your POC to be added to the group of authorized contributors. If you don't know who your Point of Contact is, direct the Google project maintainer to [go/cla#troubleshoot](http://go/cla#troubleshoot) ([Public version](https://opensource.google.com/docs/cla/#troubleshoot)).
*   The email used to register you as an authorized contributor must be the email used for the Git commit. Check [your existing CLA data](https://cla.developers.google.com/clas) and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
*   The email used to register you as an authorized contributor must also be [attached to your GitHub account](https://github.com/settings/emails).
		

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogle%2Fgo-github%2Fpull%2F1238) for more info**.

<!-- need_sender_cla -->
googlebot: CLAs look good, thanks!

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2Fgoogle%2Fgo-github%2Fpull%2F1238) for more info**.

<!-- ok -->
shanempope: FYI This PR has related / same feature: https://github.com/google/go-github/pull/1182
gmlewis: Ah, thank you for the pointer, @shanempope!

It appears that #1182 never got completed... so when this PR is merged we can close that other PR, unless that one gets finished first, then we can close this one. :smiley:


Patch:
diff --git a/AUTHORS b/AUTHORS
index 8a41ba02..d38236ed 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -59,6 +59,7 @@ Chris King <chriskingnet@gmail.com>
 Chris Roche <chris@vsco.co>
 Chris Schaefer <chris@dtzq.com>
 chrisforrette <chris@chrisforrette.com>
+Christian Muehlhaeuser <muesli@gmail.com>
 Christoph Sassenberg <defsprite@gmail.com>
 Colin Misare <github.com/cmisare>
 Craig Peterson <cpeterson@stackoverflow.com>
@@ -157,6 +158,7 @@ Maksim Zhylinski <uzzable@gmail.com>
 Mark Tareshawty <tarebyte@github.com>
 Martin-Louis Bright <mlbright@gmail.com>
 Marwan Sulaiman <marwan.sameer@gmail.com>
+Masayuki Izumi <m@izum.in>
 Mat Geist <matgeist@gmail.com>
 Matt <alpmatthew@gmail.com>
 Matt Brender <mjbrender@gmail.com>
@@ -187,6 +189,7 @@ Pete Wagner <thepwagner@github.com>
 Petr Shevtsov <petr.shevtsov@gmail.com>
 Pierre Carrier <pierre@meteor.com>
 Piotr Zurek <p.zurek@gmail.com>
+Quentin Leffray <fiahil@gmail.com>
 Quinn Slack <qslack@qslack.com>
 Rackspace US, Inc.
 Radek Simko <radek.simko@gmail.com>
@@ -223,6 +226,7 @@ Shrikrishna Singh <krishnasingh.ss30@gmail.com>
 sona-tar <sona.zip@gmail.com>
 SoundCloud, Ltd.
 Sridhar Mocherla <srmocher@microsoft.com>
+SriVignessh Pss <sriknowledge@gmail.com>
 Stian Eikeland <stian@eikeland.se>
 Tasya Aditya Rukmana <tadityar@gmail.com>
 Thomas Bruyelle <thomas.bruyelle@gmail.com>
@@ -237,6 +241,7 @@ Vlad Ungureanu <vladu@palantir.com>
 Wasim Thabraze <wasim@thabraze.me>
 Will Maier <wcmaier@gmail.com>
 William Bailey <mail@williambailey.org.uk>
+William Cooke <pipeston@gmail.com>
 xibz <impactbchang@gmail.com>
 Yann Malet <yann.malet@gmail.com>
 Yannick Utard <yannickutard@gmail.com>
diff --git a/github/apps.go b/github/apps.go
index f675a9ab..5041e801 100644
--- a/github/apps.go
+++ b/github/apps.go
@@ -32,8 +32,21 @@ type App struct {
 
 // InstallationToken represents an installation token.
 type InstallationToken struct {
-	Token     *string    `json:"token,omitempty"`
-	ExpiresAt *time.Time `json:"expires_at,omitempty"`
+	Token        *string                  `json:"token,omitempty"`
+	ExpiresAt    *time.Time               `json:"expires_at,omitempty"`
+	Permissions  *InstallationPermissions `json:"permissions,omitempty"`
+	Repositories []*Repository            `json:"repositories,omitempty"`
+}
+
+// InstallationTokenOptions allow restricting a token's access to specific repositories.
+type InstallationTokenOptions struct {
+	// The IDs of the repositories that the installation token can access.
+	// Providing repository IDs restricts the access of an installation token to specific repositories.
+	RepositoryIDs []int64 `json:"repository_ids,omitempty"`
+
+	// The permissions granted to the access token.
+	// The permissions object includes the permission names and their access type.
+	Permissions *InstallationPermissions `json:"permissions,omitempty"`
 }
 
 // InstallationPermissions lists the repository and organization permissions for an installation.
@@ -194,10 +207,10 @@ func (s *AppsService) ListUserInstallations(ctx context.Context, opt *ListOption
 // CreateInstallationToken creates a new installation token.
 //
 // GitHub API docs: https://developer.github.com/v3/apps/#create-a-new-installation-token
-func (s *AppsService) CreateInstallationToken(ctx context.Context, id int64) (*InstallationToken, *Response, error) {
+func (s *AppsService) CreateInstallationToken(ctx context.Context, id int64, opt *InstallationTokenOptions) (*InstallationToken, *Response, error) {
 	u := fmt.Sprintf("app/installations/%v/access_tokens", id)
 
-	req, err := s.client.NewRequest("POST", u, nil)
+	req, err := s.client.NewRequest("POST", u, opt)
 	if err != nil {
 		return nil, nil, err
 	}
diff --git a/github/event_types.go b/github/event_types.go
index 8ba922a2..d3e21235 100644
--- a/github/event_types.go
+++ b/github/event_types.go
@@ -707,6 +707,7 @@ type PushEventRepository struct {
 	PushedAt        *Timestamp `json:"pushed_at,omitempty"`
 	UpdatedAt       *Timestamp `json:"updated_at,omitempty"`
 	Homepage        *string    `json:"homepage,omitempty"`
+	PullsURL        *string    `json:"pulls_url,omitempty"`
 	Size            *int       `json:"size,omitempty"`
 	StargazersCount *int       `json:"stargazers_count,omitempty"`
 	WatchersCount   *int       `json:"watchers_count,omitempty"`
diff --git a/github/github-accessors.go b/github/github-accessors.go
index b4ca033d..03fd93f9 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -3884,6 +3884,14 @@ func (i *InstallationToken) GetExpiresAt() time.Time {
 	return *i.ExpiresAt
 }
 
+// GetPermissions returns the Permissions field.
+func (i *InstallationToken) GetPermissions() *InstallationPermissions {
+	if i == nil {
+		return nil
+	}
+	return i.Permissions
+}
+
 // GetToken returns the Token field if it's non-nil, zero value otherwise.
 func (i *InstallationToken) GetToken() string {
 	if i == nil || i.Token == nil {
@@ -3892,6 +3900,14 @@ func (i *InstallationToken) GetToken() string {
 	return *i.Token
 }
 
+// GetPermissions returns the Permissions field.
+func (i *InstallationTokenOptions) GetPermissions() *InstallationPermissions {
+	if i == nil {
+		return nil
+	}
+	return i.Permissions
+}
+
 // GetExpiresAt returns the ExpiresAt field if it's non-nil, zero value otherwise.
 func (i *InteractionRestriction) GetExpiresAt() Timestamp {
 	if i == nil || i.ExpiresAt == nil {
@@ -4612,6 +4628,14 @@ func (i *IssueStats) GetTotalIssues() int {
 	return *i.TotalIssues
 }
 
+// GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
+func (k *Key) GetCreatedAt() Timestamp {
+	if k == nil || k.CreatedAt == nil {
+		return Timestamp{}
+	}
+	return *k.CreatedAt
+}
+
 // GetID returns the ID field if it's non-nil, zero value otherwise.
 func (k *Key) GetID() int64 {
 	if k == nil || k.ID == nil {
@@ -8860,6 +8884,14 @@ func (p *PushEventRepository) GetPrivate() bool {
 	return *p.Private
 }
 
+// GetPullsURL returns the PullsURL field if it's non-nil, zero value otherwise.
+func (p *PushEventRepository) GetPullsURL() string {
+	if p == nil || p.PullsURL == nil {
+		return ""
+	}
+	return *p.PullsURL
+}
+
 // GetPushedAt returns the PushedAt field if it's non-nil, zero value otherwise.
 func (p *PushEventRepository) GetPushedAt() Timestamp {
 	if p == nil || p.PushedAt == nil {
@@ -9612,6 +9644,14 @@ func (r *Repository) GetIssuesURL() string {
 	return *r.IssuesURL
 }
 
+// GetIsTemplate returns the IsTemplate field if it's non-nil, zero value otherwise.
+func (r *Repository) GetIsTemplate() bool {
+	if r == nil || r.IsTemplate == nil {
+		return false
+	}
+	return *r.IsTemplate
+}
+
 // GetKeysURL returns the KeysURL field if it's non-nil, zero value otherwise.
 func (r *Repository) GetKeysURL() string {
 	if r == nil || r.KeysURL == nil {
@@ -9900,6 +9940,14 @@ func (r *Repository) GetTeamsURL() string {
 	return *r.TeamsURL
 }
 
+// GetTemplateRepository returns the TemplateRepository field.
+func (r *Repository) GetTemplateRepository() *Repository {
+	if r == nil {
+		return nil
+	}
+	return r.TemplateRepository
+}
+
 // GetTreesURL returns the TreesURL field if it's non-nil, zero value otherwise.
 func (r *Repository) GetTreesURL() string {
 	if r == nil || r.TreesURL == nil {
@@ -11668,6 +11716,38 @@ func (t *TeamProjectOptions) GetPermission() string {
 	return *t.Permission
 }
 
+// GetDescription returns the Description field if it's non-nil, zero value otherwise.
+func (t *TemplateRepoRequest) GetDescription() string {
+	if t == nil || t.Description == nil {
+		return ""
+	}
+	return *t.Description
+}
+
+// GetName returns the Name field if it's non-nil, zero value otherwise.
+func (t *TemplateRepoRequest) GetName() string {
+	if t == nil || t.Name == nil {
+		return ""
+	}
+	return *t.Name
+}
+
+// GetOwner returns the Owner field if it's non-nil, zero value otherwise.
+func (t *TemplateRepoRequest) GetOwner() string {
+	if t == nil || t.Owner == nil {
+		return ""
+	}
+	return *t.Owner
+}
+
+// GetPrivate returns the Private field if it's non-nil, zero value otherwise.
+func (t *TemplateRepoRequest) GetPrivate() bool {
+	if t == nil || t.Private == nil {
+		return false
+	}
+	return *t.Private
+}
+
 // GetFragment returns the Fragment field if it's non-nil, zero value otherwise.
 func (t *TextMatch) GetFragment() string {
 	if t == nil || t.Fragment == nil {
diff --git a/github/github.go b/github/github.go
index 7a82677c..6ab801f2 100644
--- a/github/github.go
+++ b/github/github.go
@@ -153,6 +153,9 @@ const (
 
 	// https://developer.github.com/v3/previews/#repository-creation-permissions
 	mediaTypeMemberAllowedRepoCreationTypePreview = "application/vnd.github.surtur-preview+json"
+
+	// https://developer.github.com/v3/previews/#create-and-use-repository-templates
+	mediaTypeRepositoryTemplatePreview = "application/vnd.github.baptiste-preview+json"
 )
 
 // A Client manages communication with the GitHub API.
@@ -646,7 +649,7 @@ type RateLimitError struct {
 func (r *RateLimitError) Error() string {
 	return fmt.Sprintf("%v %v: %d %v %v",
 		r.Response.Request.Method, sanitizeURL(r.Response.Request.URL),
-		r.Response.StatusCode, r.Message, formatRateReset(r.Rate.Reset.Time.Sub(time.Now())))
+		r.Response.StatusCode, r.Message, formatRateReset(time.Until(r.Rate.Reset.Time)))
 }
 
 // AcceptedError occurs when GitHub returns 202 Accepted response with an
diff --git a/github/projects.go b/github/projects.go
index c7a68f53..1e9620df 100644
--- a/github/projects.go
+++ b/github/projects.go
@@ -344,8 +344,8 @@ func (s *ProjectsService) ListProjectCards(ctx context.Context, columnID int64,
 // GetProjectCard gets a card in a column of a GitHub Project.
 //
 // GitHub API docs: https://developer.github.com/v3/projects/cards/#get-a-project-card
-func (s *ProjectsService) GetProjectCard(ctx context.Context, columnID int64) (*ProjectCard, *Response, error) {
-	u := fmt.Sprintf("projects/columns/cards/%v", columnID)
+func (s *ProjectsService) GetProjectCard(ctx context.Context, cardID int64) (*ProjectCard, *Response, error) {
+	u := fmt.Sprintf("projects/columns/cards/%v", cardID)
 	req, err := s.client.NewRequest("GET", u, nil)
 	if err != nil {
 		return nil, nil, err
diff --git a/github/repos.go b/github/repos.go
index 5f44c6d8..a380f302 100644
--- a/github/repos.go
+++ b/github/repos.go
@@ -19,45 +19,46 @@ type RepositoriesService service
 
 // Repository represents a GitHub repository.
 type Repository struct {
-	ID               *int64           `json:"id,omitempty"`
-	NodeID           *string          `json:"node_id,omitempty"`
-	Owner            *User            `json:"owner,omitempty"`
-	Name             *string          `json:"name,omitempty"`
-	FullName         *string          `json:"full_name,omitempty"`
-	Description      *string          `json:"description,omitempty"`
-	Homepage         *string          `json:"homepage,omitempty"`
-	CodeOfConduct    *CodeOfConduct   `json:"code_of_conduct,omitempty"`
-	DefaultBranch    *string          `json:"default_branch,omitempty"`
-	MasterBranch     *string          `json:"master_branch,omitempty"`
-	CreatedAt        *Timestamp       `json:"created_at,omitempty"`
-	PushedAt         *Timestamp       `json:"pushed_at,omitempty"`
-	UpdatedAt        *Timestamp       `json:"updated_at,omitempty"`
-	HTMLURL          *string          `json:"html_url,omitempty"`
-	CloneURL         *string          `json:"clone_url,omitempty"`
-	GitURL           *string          `json:"git_url,omitempty"`
-	MirrorURL        *string          `json:"mirror_url,omitempty"`
-	SSHURL           *string          `json:"ssh_url,omitempty"`
-	SVNURL           *string          `json:"svn_url,omitempty"`
-	Language         *string          `json:"language,omitempty"`
-	Fork             *bool            `json:"fork,omitempty"`
-	ForksCount       *int             `json:"forks_count,omitempty"`
-	NetworkCount     *int             `json:"network_count,omitempty"`
-	OpenIssuesCount  *int             `json:"open_issues_count,omitempty"`
-	StargazersCount  *int             `json:"stargazers_count,omitempty"`
-	SubscribersCount *int             `json:"subscribers_count,omitempty"`
-	WatchersCount    *int             `json:"watchers_count,omitempty"`
-	Size             *int             `json:"size,omitempty"`
-	AutoInit         *bool            `json:"auto_init,omitempty"`
-	Parent           *Repository      `json:"parent,omitempty"`
-	Source           *Repository      `json:"source,omitempty"`
-	Organization     *Organization    `json:"organization,omitempty"`
-	Permissions      *map[string]bool `json:"permissions,omitempty"`
-	AllowRebaseMerge *bool            `json:"allow_rebase_merge,omitempty"`
-	AllowSquashMerge *bool            `json:"allow_squash_merge,omitempty"`
-	AllowMergeCommit *bool            `json:"allow_merge_commit,omitempty"`
-	Topics           []string         `json:"topics,omitempty"`
-	Archived         *bool            `json:"archived,omitempty"`
-	Disabled         *bool            `json:"disabled,omitempty"`
+	ID                 *int64           `json:"id,omitempty"`
+	NodeID             *string          `json:"node_id,omitempty"`
+	Owner              *User            `json:"owner,omitempty"`
+	Name               *string          `json:"name,omitempty"`
+	FullName           *string          `json:"full_name,omitempty"`
+	Description        *string          `json:"description,omitempty"`
+	Homepage           *string          `json:"homepage,omitempty"`
+	CodeOfConduct      *CodeOfConduct   `json:"code_of_conduct,omitempty"`
+	DefaultBranch      *string          `json:"default_branch,omitempty"`
+	MasterBranch       *string          `json:"master_branch,omitempty"`
+	CreatedAt          *Timestamp       `json:"created_at,omitempty"`
+	PushedAt           *Timestamp       `json:"pushed_at,omitempty"`
+	UpdatedAt          *Timestamp       `json:"updated_at,omitempty"`
+	HTMLURL            *string          `json:"html_url,omitempty"`
+	CloneURL           *string          `json:"clone_url,omitempty"`
+	GitURL             *string          `json:"git_url,omitempty"`
+	MirrorURL          *string          `json:"mirror_url,omitempty"`
+	SSHURL             *string          `json:"ssh_url,omitempty"`
+	SVNURL             *string          `json:"svn_url,omitempty"`
+	Language           *string          `json:"language,omitempty"`
+	Fork               *bool            `json:"fork,omitempty"`
+	ForksCount         *int             `json:"forks_count,omitempty"`
+	NetworkCount       *int             `json:"network_count,omitempty"`
+	OpenIssuesCount    *int             `json:"open_issues_count,omitempty"`
+	StargazersCount    *int             `json:"stargazers_count,omitempty"`
+	SubscribersCount   *int             `json:"subscribers_count,omitempty"`
+	WatchersCount      *int             `json:"watchers_count,omitempty"`
+	Size               *int             `json:"size,omitempty"`
+	AutoInit           *bool            `json:"auto_init,omitempty"`
+	Parent             *Repository      `json:"parent,omitempty"`
+	Source             *Repository      `json:"source,omitempty"`
+	TemplateRepository *Repository      `json:"template_repository,omitempty"`
+	Organization       *Organization    `json:"organization,omitempty"`
+	Permissions        *map[string]bool `json:"permissions,omitempty"`
+	AllowRebaseMerge   *bool            `json:"allow_rebase_merge,omitempty"`
+	AllowSquashMerge   *bool            `json:"allow_squash_merge,omitempty"`
+	AllowMergeCommit   *bool            `json:"allow_merge_commit,omitempty"`
+	Topics             []string         `json:"topics,omitempty"`
+	Archived           *bool            `json:"archived,omitempty"`
+	Disabled           *bool            `json:"disabled,omitempty"`
 
 	// Only provided when using RepositoriesService.Get while in preview
 	License *License `json:"license,omitempty"`
@@ -69,6 +70,7 @@ type Repository struct {
 	HasPages          *bool   `json:"has_pages,omitempty"`
 	HasProjects       *bool   `json:"has_projects,omitempty"`
 	HasDownloads      *bool   `json:"has_downloads,omitempty"`
+	IsTemplate        *bool   `json:"is_template,omitempty"`
 	LicenseTemplate   *string `json:"license_template,omitempty"`
 	GitignoreTemplate *string `json:"gitignore_template,omitempty"`
 
@@ -275,6 +277,7 @@ type createRepoRequest struct {
 	HasIssues   *bool `json:"has_issues,omitempty"`
 	HasProjects *bool `json:"has_projects,omitempty"`
 	HasWiki     *bool `json:"has_wiki,omitempty"`
+	IsTemplate  *bool `json:"is_template,omitempty"`
 
 	// Creating an organization repository. Required for non-owners.
 	TeamID *int64 `json:"team_id,omitempty"`
@@ -311,6 +314,7 @@ func (s *RepositoriesService) Create(ctx context.Context, org string, repo *Repo
 		HasIssues:         repo.HasIssues,
 		HasProjects:       repo.HasProjects,
 		HasWiki:           repo.HasWiki,
+		IsTemplate:        repo.IsTemplate,
 		TeamID:            repo.TeamID,
 		AutoInit:          repo.AutoInit,
 		GitignoreTemplate: repo.GitignoreTemplate,
@@ -325,6 +329,38 @@ func (s *RepositoriesService) Create(ctx context.Context, org string, repo *Repo
 		return nil, nil, err
 	}
 
+	req.Header.Set("Accept", mediaTypeRepositoryTemplatePreview)
+	r := new(Repository)
+	resp, err := s.client.Do(ctx, req, r)
+	if err != nil {
+		return nil, resp, err
+	}
+
+	return r, resp, nil
+}
+
+// TemplateRepoRequest represents a request to create a repository from a template.
+type TemplateRepoRequest struct {
+	// Name is required when creating a repo.
+	Name        *string `json:"name,omitempty"`
+	Owner       *string `json:"owner,omitempty"`
+	Description *string `json:"description,omitempty"`
+
+	Private *bool `json:"private,omitempty"`
+}
+
+// CreateFromTemplate generates a repository from a template.
+//
+// GitHub API docs: https://developer.github.com/v3/repos/#create-repository-using-a-repository-template
+func (s *RepositoriesService) CreateFromTemplate(ctx context.Context, templateOwner, templateRepo string, templateRepoReq *TemplateRepoRequest) (*Repository, *Response, error) {
+	u := fmt.Sprintf("repos/%v/%v/generate", templateOwner, templateRepo)
+
+	req, err := s.client.NewRequest("POST", u, templateRepoReq)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req.Header.Set("Accept", mediaTypeRepositoryTemplatePreview)
 	r := new(Repository)
 	resp, err := s.client.Do(ctx, req, r)
 	if err != nil {
@@ -346,7 +382,7 @@ func (s *RepositoriesService) Get(ctx context.Context, owner, repo string) (*Rep
 
 	// TODO: remove custom Accept header when the license support fully launches
 	// https://developer.github.com/v3/licenses/#get-a-repositorys-license
-	acceptHeaders := []string{mediaTypeCodesOfConductPreview, mediaTypeTopicsPreview}
+	acceptHeaders := []string{mediaTypeCodesOfConductPreview, mediaTypeTopicsPreview, mediaTypeRepositoryTemplatePreview}
 	req.Header.Set("Accept", strings.Join(acceptHeaders, ", "))
 
 	repository := new(Repository)
@@ -409,6 +445,7 @@ func (s *RepositoriesService) Edit(ctx context.Context, owner, repo string, repo
 		return nil, nil, err
 	}
 
+	req.Header.Set("Accept", mediaTypeRepositoryTemplatePreview)
 	r := new(Repository)
 	resp, err := s.client.Do(ctx, req, r)
 	if err != nil {
diff --git a/github/users_keys.go b/github/users_keys.go
index ddc832a1..39a4f54d 100644
--- a/github/users_keys.go
+++ b/github/users_keys.go
@@ -12,11 +12,12 @@ import (
 
 // Key represents a public SSH key used to authenticate a user or deploy script.
 type Key struct {
-	ID       *int64  `json:"id,omitempty"`
-	Key      *string `json:"key,omitempty"`
-	URL      *string `json:"url,omitempty"`
-	Title    *string `json:"title,omitempty"`
-	ReadOnly *bool   `json:"read_only,omitempty"`
+	ID        *int64     `json:"id,omitempty"`
+	Key       *string    `json:"key,omitempty"`
+	URL       *string    `json:"url,omitempty"`
+	Title     *string    `json:"title,omitempty"`
+	ReadOnly  *bool      `json:"read_only,omitempty"`
+	CreatedAt *Timestamp `json:"created_at,omitempty"`
 }
 
 func (k Key) String() string {


Test patch:
diff --git a/github/apps_test.go b/github/apps_test.go
index 8d5b27d3..55ef2a09 100644
--- a/github/apps_test.go
+++ b/github/apps_test.go
@@ -6,8 +6,12 @@
 package github
 
 import (
+	"bytes"
 	"context"
+	"encoding/json"
 	"fmt"
+	"io"
+	"io/ioutil"
 	"net/http"
 	"reflect"
 	"testing"
@@ -214,7 +218,54 @@ func TestAppsService_CreateInstallationToken(t *testing.T) {
 		fmt.Fprint(w, `{"token":"t"}`)
 	})
 
-	token, _, err := client.Apps.CreateInstallationToken(context.Background(), 1)
+	token, _, err := client.Apps.CreateInstallationToken(context.Background(), 1, nil)
+	if err != nil {
+		t.Errorf("Apps.CreateInstallationToken returned error: %v", err)
+	}
+
+	want := &InstallationToken{Token: String("t")}
+	if !reflect.DeepEqual(token, want) {
+		t.Errorf("Apps.CreateInstallationToken returned %+v, want %+v", token, want)
+	}
+}
+
+func TestAppsService_CreateInstallationTokenWithOptions(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	installationTokenOptions := &InstallationTokenOptions{
+		RepositoryIDs: []int64{1234},
+		Permissions: &InstallationPermissions{
+			Contents: String("write"),
+			Issues:   String("read"),
+		},
+	}
+
+	// Convert InstallationTokenOptions into an io.ReadCloser object for comparison.
+	wantBody, err := GetReadCloser(installationTokenOptions)
+	if err != nil {
+		t.Errorf("GetReadCloser returned error: %v", err)
+	}
+
+	mux.HandleFunc("/app/installations/1/access_tokens", func(w http.ResponseWriter, r *http.Request) {
+		// Read request body contents.
+		var gotBodyBytes []byte
+		gotBodyBytes, err = ioutil.ReadAll(r.Body)
+		if err != nil {
+			t.Errorf("ReadAll returned error: %v", err)
+		}
+		r.Body = ioutil.NopCloser(bytes.NewBuffer(gotBodyBytes))
+
+		if !reflect.DeepEqual(r.Body, wantBody) {
+			t.Errorf("request sent %+v, want %+v", r.Body, wantBody)
+		}
+
+		testMethod(t, r, "POST")
+		testHeader(t, r, "Accept", mediaTypeIntegrationPreview)
+		fmt.Fprint(w, `{"token":"t"}`)
+	})
+
+	token, _, err := client.Apps.CreateInstallationToken(context.Background(), 1, installationTokenOptions)
 	if err != nil {
 		t.Errorf("Apps.CreateInstallationToken returned error: %v", err)
 	}
@@ -247,6 +298,7 @@ func TestAppsService_CreateAttachement(t *testing.T) {
 		t.Errorf("CreateAttachment = %+v, want %+v", got, want)
 	}
 }
+
 func TestAppsService_FindOrganizationInstallation(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
@@ -330,3 +382,31 @@ func TestAppsService_FindUserInstallation(t *testing.T) {
 		t.Errorf("Apps.FindUserInstallation returned %+v, want %+v", installation, want)
 	}
 }
+
+// GetReadWriter converts a body interface into an io.ReadWriter object.
+func GetReadWriter(body interface{}) (io.ReadWriter, error) {
+	var buf io.ReadWriter
+	if body != nil {
+		buf = new(bytes.Buffer)
+		enc := json.NewEncoder(buf)
+		err := enc.Encode(body)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return buf, nil
+}
+
+// GetReadCloser converts a body interface into an io.ReadCloser object.
+func GetReadCloser(body interface{}) (io.ReadCloser, error) {
+	buf, err := GetReadWriter(body)
+	if err != nil {
+		return nil, err
+	}
+
+	all, err := ioutil.ReadAll(buf)
+	if err != nil {
+		return nil, err
+	}
+	return ioutil.NopCloser(bytes.NewBuffer(all)), nil
+}
diff --git a/github/github-stringify_test.go b/github/github-stringify_test.go
index f424531e..2e7b285e 100644
--- a/github/github-stringify_test.go
+++ b/github/github-stringify_test.go
@@ -623,13 +623,14 @@ func TestIssueStats_String(t *testing.T) {
 
 func TestKey_String(t *testing.T) {
 	v := Key{
-		ID:       Int64(0),
-		Key:      String(""),
-		URL:      String(""),
-		Title:    String(""),
-		ReadOnly: Bool(false),
+		ID:        Int64(0),
+		Key:       String(""),
+		URL:       String(""),
+		Title:     String(""),
+		ReadOnly:  Bool(false),
+		CreatedAt: &Timestamp{},
 	}
-	want := `github.Key{ID:0, Key:"", URL:"", Title:"", ReadOnly:false}`
+	want := `github.Key{ID:0, Key:"", URL:"", Title:"", ReadOnly:false, CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}}`
 	if got := v.String(); got != want {
 		t.Errorf("Key.String = %v, want %v", got, want)
 	}
@@ -1167,92 +1168,94 @@ func TestRepoStatus_String(t *testing.T) {
 
 func TestRepository_String(t *testing.T) {
 	v := Repository{
-		ID:                Int64(0),
-		NodeID:            String(""),
-		Owner:             &User{},
-		Name:              String(""),
-		FullName:          String(""),
-		Description:       String(""),
-		Homepage:          String(""),
-		CodeOfConduct:     &CodeOfConduct{},
-		DefaultBranch:     String(""),
-		MasterBranch:      String(""),
-		CreatedAt:         &Timestamp{},
-		PushedAt:          &Timestamp{},
-		UpdatedAt:         &Timestamp{},
-		HTMLURL:           String(""),
-		CloneURL:          String(""),
-		GitURL:            String(""),
-		MirrorURL:         String(""),
-		SSHURL:            String(""),
-		SVNURL:            String(""),
-		Language:          String(""),
-		Fork:              Bool(false),
-		ForksCount:        Int(0),
-		NetworkCount:      Int(0),
-		OpenIssuesCount:   Int(0),
-		StargazersCount:   Int(0),
-		SubscribersCount:  Int(0),
-		WatchersCount:     Int(0),
-		Size:              Int(0),
-		AutoInit:          Bool(false),
-		Parent:            &Repository{},
-		Source:            &Repository{},
-		Organization:      &Organization{},
-		AllowRebaseMerge:  Bool(false),
-		AllowSquashMerge:  Bool(false),
-		AllowMergeCommit:  Bool(false),
-		Archived:          Bool(false),
-		Disabled:          Bool(false),
-		License:           &License{},
-		Private:           Bool(false),
-		HasIssues:         Bool(false),
-		HasWiki:           Bool(false),
-		HasPages:          Bool(false),
-		HasProjects:       Bool(false),
-		HasDownloads:      Bool(false),
-		LicenseTemplate:   String(""),
-		GitignoreTemplate: String(""),
-		TeamID:            Int64(0),
-		URL:               String(""),
-		ArchiveURL:        String(""),
-		AssigneesURL:      String(""),
-		BlobsURL:          String(""),
-		BranchesURL:       String(""),
-		CollaboratorsURL:  String(""),
-		CommentsURL:       String(""),
-		CommitsURL:        String(""),
-		CompareURL:        String(""),
-		ContentsURL:       String(""),
-		ContributorsURL:   String(""),
-		DeploymentsURL:    String(""),
-		DownloadsURL:      String(""),
-		EventsURL:         String(""),
-		ForksURL:          String(""),
-		GitCommitsURL:     String(""),
-		GitRefsURL:        String(""),
-		GitTagsURL:        String(""),
-		HooksURL:          String(""),
-		IssueCommentURL:   String(""),
-		IssueEventsURL:    String(""),
-		IssuesURL:         String(""),
-		KeysURL:           String(""),
-		LabelsURL:         String(""),
-		LanguagesURL:      String(""),
-		MergesURL:         String(""),
-		MilestonesURL:     String(""),
-		NotificationsURL:  String(""),
-		PullsURL:          String(""),
-		ReleasesURL:       String(""),
-		StargazersURL:     String(""),
-		StatusesURL:       String(""),
-		SubscribersURL:    String(""),
-		SubscriptionURL:   String(""),
-		TagsURL:           String(""),
-		TreesURL:          String(""),
-		TeamsURL:          String(""),
-	}
-	want := `github.Repository{ID:0, NodeID:"", Owner:github.User{}, Name:"", FullName:"", Description:"", Homepage:"", CodeOfConduct:github.CodeOfConduct{}, DefaultBranch:"", MasterBranch:"", CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, PushedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, HTMLURL:"", CloneURL:"", GitURL:"", MirrorURL:"", SSHURL:"", SVNURL:"", Language:"", Fork:false, ForksCount:0, NetworkCount:0, OpenIssuesCount:0, StargazersCount:0, SubscribersCount:0, WatchersCount:0, Size:0, AutoInit:false, Parent:github.Repository{}, Source:github.Repository{}, Organization:github.Organization{}, AllowRebaseMerge:false, AllowSquashMerge:false, AllowMergeCommit:false, Archived:false, Disabled:false, License:github.License{}, Private:false, HasIssues:false, HasWiki:false, HasPages:false, HasProjects:false, HasDownloads:false, LicenseTemplate:"", GitignoreTemplate:"", TeamID:0, URL:"", ArchiveURL:"", AssigneesURL:"", BlobsURL:"", BranchesURL:"", CollaboratorsURL:"", CommentsURL:"", CommitsURL:"", CompareURL:"", ContentsURL:"", ContributorsURL:"", DeploymentsURL:"", DownloadsURL:"", EventsURL:"", ForksURL:"", GitCommitsURL:"", GitRefsURL:"", GitTagsURL:"", HooksURL:"", IssueCommentURL:"", IssueEventsURL:"", IssuesURL:"", KeysURL:"", LabelsURL:"", LanguagesURL:"", MergesURL:"", MilestonesURL:"", NotificationsURL:"", PullsURL:"", ReleasesURL:"", StargazersURL:"", StatusesURL:"", SubscribersURL:"", SubscriptionURL:"", TagsURL:"", TreesURL:"", TeamsURL:""}`
+		ID:                 Int64(0),
+		NodeID:             String(""),
+		Owner:              &User{},
+		Name:               String(""),
+		FullName:           String(""),
+		Description:        String(""),
+		Homepage:           String(""),
+		CodeOfConduct:      &CodeOfConduct{},
+		DefaultBranch:      String(""),
+		MasterBranch:       String(""),
+		CreatedAt:          &Timestamp{},
+		PushedAt:           &Timestamp{},
+		UpdatedAt:          &Timestamp{},
+		HTMLURL:            String(""),
+		CloneURL:           String(""),
+		GitURL:             String(""),
+		MirrorURL:          String(""),
+		SSHURL:             String(""),
+		SVNURL:             String(""),
+		Language:           String(""),
+		Fork:               Bool(false),
+		ForksCount:         Int(0),
+		NetworkCount:       Int(0),
+		OpenIssuesCount:    Int(0),
+		StargazersCount:    Int(0),
+		SubscribersCount:   Int(0),
+		WatchersCount:      Int(0),
+		Size:               Int(0),
+		AutoInit:           Bool(false),
+		Parent:             &Repository{},
+		Source:             &Repository{},
+		TemplateRepository: &Repository{},
+		Organization:       &Organization{},
+		AllowRebaseMerge:   Bool(false),
+		AllowSquashMerge:   Bool(false),
+		AllowMergeCommit:   Bool(false),
+		Archived:           Bool(false),
+		Disabled:           Bool(false),
+		License:            &License{},
+		Private:            Bool(false),
+		HasIssues:          Bool(false),
+		HasWiki:            Bool(false),
+		HasPages:           Bool(false),
+		HasProjects:        Bool(false),
+		HasDownloads:       Bool(false),
+		IsTemplate:         Bool(false),
+		LicenseTemplate:    String(""),
+		GitignoreTemplate:  String(""),
+		TeamID:             Int64(0),
+		URL:                String(""),
+		ArchiveURL:         String(""),
+		AssigneesURL:       String(""),
+		BlobsURL:           String(""),
+		BranchesURL:        String(""),
+		CollaboratorsURL:   String(""),
+		CommentsURL:        String(""),
+		CommitsURL:         String(""),
+		CompareURL:         String(""),
+		ContentsURL:        String(""),
+		ContributorsURL:    String(""),
+		DeploymentsURL:     String(""),
+		DownloadsURL:       String(""),
+		EventsURL:          String(""),
+		ForksURL:           String(""),
+		GitCommitsURL:      String(""),
+		GitRefsURL:         String(""),
+		GitTagsURL:         String(""),
+		HooksURL:           String(""),
+		IssueCommentURL:    String(""),
+		IssueEventsURL:     String(""),
+		IssuesURL:          String(""),
+		KeysURL:            String(""),
+		LabelsURL:          String(""),
+		LanguagesURL:       String(""),
+		MergesURL:          String(""),
+		MilestonesURL:      String(""),
+		NotificationsURL:   String(""),
+		PullsURL:           String(""),
+		ReleasesURL:        String(""),
+		StargazersURL:      String(""),
+		StatusesURL:        String(""),
+		SubscribersURL:     String(""),
+		SubscriptionURL:    String(""),
+		TagsURL:            String(""),
+		TreesURL:           String(""),
+		TeamsURL:           String(""),
+	}
+	want := `github.Repository{ID:0, NodeID:"", Owner:github.User{}, Name:"", FullName:"", Description:"", Homepage:"", CodeOfConduct:github.CodeOfConduct{}, DefaultBranch:"", MasterBranch:"", CreatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, PushedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, UpdatedAt:github.Timestamp{0001-01-01 00:00:00 +0000 UTC}, HTMLURL:"", CloneURL:"", GitURL:"", MirrorURL:"", SSHURL:"", SVNURL:"", Language:"", Fork:false, ForksCount:0, NetworkCount:0, OpenIssuesCount:0, StargazersCount:0, SubscribersCount:0, WatchersCount:0, Size:0, AutoInit:false, Parent:github.Repository{}, Source:github.Repository{}, TemplateRepository:github.Repository{}, Organization:github.Organization{}, AllowRebaseMerge:false, AllowSquashMerge:false, AllowMergeCommit:false, Archived:false, Disabled:false, License:github.License{}, Private:false, HasIssues:false, HasWiki:false, HasPages:false, HasProjects:false, HasDownloads:false, IsTemplate:false, LicenseTemplate:"", GitignoreTemplate:"", TeamID:0, URL:"", ArchiveURL:"", AssigneesURL:"", BlobsURL:"", BranchesURL:"", CollaboratorsURL:"", CommentsURL:"", CommitsURL:"", CompareURL:"", ContentsURL:"", ContributorsURL:"", DeploymentsURL:"", DownloadsURL:"", EventsURL:"", ForksURL:"", GitCommitsURL:"", GitRefsURL:"", GitTagsURL:"", HooksURL:"", IssueCommentURL:"", IssueEventsURL:"", IssuesURL:"", KeysURL:"", LabelsURL:"", LanguagesURL:"", MergesURL:"", MilestonesURL:"", NotificationsURL:"", PullsURL:"", ReleasesURL:"", StargazersURL:"", StatusesURL:"", SubscribersURL:"", SubscriptionURL:"", TagsURL:"", TreesURL:"", TeamsURL:""}`
 	if got := v.String(); got != want {
 		t.Errorf("Repository.String = %v, want %v", got, want)
 	}
diff --git a/github/repos_test.go b/github/repos_test.go
index 75e09a83..75ccbb0d 100644
--- a/github/repos_test.go
+++ b/github/repos_test.go
@@ -253,6 +253,7 @@ func TestRepositoriesService_Create_user(t *testing.T) {
 		json.NewDecoder(r.Body).Decode(v)
 
 		testMethod(t, r, "POST")
+		testHeader(t, r, "Accept", mediaTypeRepositoryTemplatePreview)
 		want := &createRepoRequest{Name: String("n")}
 		if !reflect.DeepEqual(v, want) {
 			t.Errorf("Request body = %+v, want %+v", v, want)
@@ -314,6 +315,7 @@ func TestRepositoriesService_Create_org(t *testing.T) {
 		json.NewDecoder(r.Body).Decode(v)
 
 		testMethod(t, r, "POST")
+		testHeader(t, r, "Accept", mediaTypeRepositoryTemplatePreview)
 		want := &createRepoRequest{Name: String("n")}
 		if !reflect.DeepEqual(v, want) {
 			t.Errorf("Request body = %+v, want %+v", v, want)
@@ -333,11 +335,72 @@ func TestRepositoriesService_Create_org(t *testing.T) {
 	}
 }
 
+func TestRepositoriesService_CreateFromTemplate(t *testing.T) {
+	client, mux, _, teardown := setup()
+	defer teardown()
+
+	templateRepoReq := &TemplateRepoRequest{
+		Name: String("n"),
+	}
+
+	mux.HandleFunc("/repos/to/tr/generate", func(w http.ResponseWriter, r *http.Request) {
+		v := new(TemplateRepoRequest)
+		json.NewDecoder(r.Body).Decode(v)
+
+		testMethod(t, r, "POST")
+		testHeader(t, r, "Accept", mediaTypeRepositoryTemplatePreview)
+		want := &TemplateRepoRequest{Name: String("n")}
+		if !reflect.DeepEqual(v, want) {
+			t.Errorf("Request body = %+v, want %+v", v, want)
+		}
+
+		fmt.Fprint(w, `{"id":1,"name":"n"}`)
+	})
+
+	ctx := context.Background()
+	got, _, err := client.Repositories.CreateFromTemplate(ctx, "to", "tr", templateRepoReq)
+	if err != nil {
+		t.Errorf("Repositories.CreateFromTemplate returned error: %v", err)
+	}
+
+	want := &Repository{ID: Int64(1), Name: String("n")}
+	if !reflect.DeepEqual(got, want) {
+		t.Errorf("Repositories.CreateFromTemplate returned %+v, want %+v", got, want)
+	}
+
+	// Test s.client.NewRequest failure
+	client.BaseURL.Path = ""
+	got, resp, err := client.Repositories.CreateFromTemplate(ctx, "to", "tr", templateRepoReq)
+	if got != nil {
+		t.Errorf("client.BaseURL.Path='' CreateFromTemplate = %#v, want nil", got)
+	}
+	if resp != nil {
+		t.Errorf("client.BaseURL.Path='' CreateFromTemplate resp = %#v, want nil", resp)
+	}
+	if err == nil {
+		t.Error("client.BaseURL.Path='' CreateFromTemplate err = nil, want error")
+	}
+
+	// Test s.client.Do failure
+	client.BaseURL.Path = "/api-v3/"
+	client.rateLimits[0].Reset.Time = time.Now().Add(10 * time.Minute)
+	got, resp, err = client.Repositories.CreateFromTemplate(ctx, "to", "tr", templateRepoReq)
+	if got != nil {
+		t.Errorf("rate.Reset.Time > now CreateFromTemplate = %#v, want nil", got)
+	}
+	if want := http.StatusForbidden; resp == nil || resp.Response.StatusCode != want {
+		t.Errorf("rate.Reset.Time > now CreateFromTemplate resp = %#v, want StatusCode=%v", resp.Response, want)
+	}
+	if err == nil {
+		t.Error("rate.Reset.Time > now CreateFromTemplate err = nil, want error")
+	}
+}
+
 func TestRepositoriesService_Get(t *testing.T) {
 	client, mux, _, teardown := setup()
 	defer teardown()
 
-	wantAcceptHeaders := []string{mediaTypeCodesOfConductPreview, mediaTypeTopicsPreview}
+	wantAcceptHeaders := []string{mediaTypeCodesOfConductPreview, mediaTypeTopicsPreview, mediaTypeRepositoryTemplatePreview}
 	mux.HandleFunc("/repos/o/r", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
 		testHeader(t, r, "Accept", strings.Join(wantAcceptHeaders, ", "))
@@ -490,6 +553,7 @@ func TestRepositoriesService_Edit(t *testing.T) {
 		json.NewDecoder(r.Body).Decode(v)
 
 		testMethod(t, r, "PATCH")
+		testHeader(t, r, "Accept", mediaTypeRepositoryTemplatePreview)
 		if !reflect.DeepEqual(v, input) {
 			t.Errorf("Request body = %+v, want %+v", v, input)
 		}