1// Copyright 2018 The go-github AUTHORS. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6package github
7
8import (
9	"context"
10	"errors"
11	"fmt"
12	"net/http"
13)
14
15// UserMigration represents a GitHub migration (archival).
16type UserMigration struct {
17	ID   *int64  `json:"id,omitempty"`
18	GUID *string `json:"guid,omitempty"`
19	// State is the current state of a migration.
20	// Possible values are:
21	//     "pending" which means the migration hasn't started yet,
22	//     "exporting" which means the migration is in progress,
23	//     "exported" which means the migration finished successfully, or
24	//     "failed" which means the migration failed.
25	State *string `json:"state,omitempty"`
26	// LockRepositories indicates whether repositories are locked (to prevent
27	// manipulation) while migrating data.
28	LockRepositories *bool `json:"lock_repositories,omitempty"`
29	// ExcludeAttachments indicates whether attachments should be excluded from
30	// the migration (to reduce migration archive file size).
31	ExcludeAttachments *bool         `json:"exclude_attachments,omitempty"`
32	URL                *string       `json:"url,omitempty"`
33	CreatedAt          *string       `json:"created_at,omitempty"`
34	UpdatedAt          *string       `json:"updated_at,omitempty"`
35	Repositories       []*Repository `json:"repositories,omitempty"`
36}
37
38func (m UserMigration) String() string {
39	return Stringify(m)
40}
41
42// UserMigrationOptions specifies the optional parameters to Migration methods.
43type UserMigrationOptions struct {
44	// LockRepositories indicates whether repositories should be locked (to prevent
45	// manipulation) while migrating data.
46	LockRepositories bool
47
48	// ExcludeAttachments indicates whether attachments should be excluded from
49	// the migration (to reduce migration archive file size).
50	ExcludeAttachments bool
51}
52
53// startUserMigration represents the body of a StartMigration request.
54type startUserMigration struct {
55	// Repositories is a slice of repository names to migrate.
56	Repositories []string `json:"repositories,omitempty"`
57
58	// LockRepositories indicates whether repositories should be locked (to prevent
59	// manipulation) while migrating data.
60	LockRepositories *bool `json:"lock_repositories,omitempty"`
61
62	// ExcludeAttachments indicates whether attachments should be excluded from
63	// the migration (to reduce migration archive file size).
64	ExcludeAttachments *bool `json:"exclude_attachments,omitempty"`
65}
66
67// StartUserMigration starts the generation of a migration archive.
68// repos is a slice of repository names to migrate.
69//
70// GitHub API docs: https://developer.github.com/v3/migrations/users/#start-a-user-migration
71func (s *MigrationService) StartUserMigration(ctx context.Context, repos []string, opt *UserMigrationOptions) (*UserMigration, *Response, error) {
72	u := "user/migrations"
73
74	body := &startUserMigration{Repositories: repos}
75	if opt != nil {
76		body.LockRepositories = Bool(opt.LockRepositories)
77		body.ExcludeAttachments = Bool(opt.ExcludeAttachments)
78	}
79
80	req, err := s.client.NewRequest("POST", u, body)
81	if err != nil {
82		return nil, nil, err
83	}
84
85	// TODO: remove custom Accept header when this API fully launches.
86	req.Header.Set("Accept", mediaTypeMigrationsPreview)
87
88	m := &UserMigration{}
89	resp, err := s.client.Do(ctx, req, m)
90	if err != nil {
91		return nil, resp, err
92	}
93
94	return m, resp, nil
95}
96
97// ListUserMigrations lists the most recent migrations.
98//
99// GitHub API docs: https://developer.github.com/v3/migrations/users/#get-a-list-of-user-migrations
100func (s *MigrationService) ListUserMigrations(ctx context.Context) ([]*UserMigration, *Response, error) {
101	u := "user/migrations"
102
103	req, err := s.client.NewRequest("GET", u, nil)
104	if err != nil {
105		return nil, nil, err
106	}
107
108	// TODO: remove custom Accept header when this API fully launches.
109	req.Header.Set("Accept", mediaTypeMigrationsPreview)
110
111	var m []*UserMigration
112	resp, err := s.client.Do(ctx, req, &m)
113	if err != nil {
114		return nil, resp, err
115	}
116
117	return m, resp, nil
118}
119
120// UserMigrationStatus gets the status of a specific migration archive.
121// id is the migration ID.
122//
123// GitHub API docs: https://developer.github.com/v3/migrations/users/#get-the-status-of-a-user-migration
124func (s *MigrationService) UserMigrationStatus(ctx context.Context, id int64) (*UserMigration, *Response, error) {
125	u := fmt.Sprintf("user/migrations/%v", id)
126
127	req, err := s.client.NewRequest("GET", u, nil)
128	if err != nil {
129		return nil, nil, err
130	}
131
132	// TODO: remove custom Accept header when this API fully launches.
133	req.Header.Set("Accept", mediaTypeMigrationsPreview)
134
135	m := &UserMigration{}
136	resp, err := s.client.Do(ctx, req, m)
137	if err != nil {
138		return nil, resp, err
139	}
140
141	return m, resp, nil
142}
143
144// UserMigrationArchiveURL gets the URL for a specific migration archive.
145// id is the migration ID.
146//
147// GitHub API docs: https://developer.github.com/v3/migrations/users/#download-a-user-migration-archive
148func (s *MigrationService) UserMigrationArchiveURL(ctx context.Context, id int64) (string, error) {
149	url := fmt.Sprintf("user/migrations/%v/archive", id)
150
151	req, err := s.client.NewRequest("GET", url, nil)
152	if err != nil {
153		return "", err
154	}
155
156	// TODO: remove custom Accept header when this API fully launches.
157	req.Header.Set("Accept", mediaTypeMigrationsPreview)
158
159	m := &UserMigration{}
160
161	var loc string
162	originalRedirect := s.client.client.CheckRedirect
163	s.client.client.CheckRedirect = func(req *http.Request, via []*http.Request) error {
164		loc = req.URL.String()
165		return http.ErrUseLastResponse
166	}
167	defer func() {
168		s.client.client.CheckRedirect = originalRedirect
169	}()
170	resp, err := s.client.Do(ctx, req, m)
171	if err == nil {
172		return "", errors.New("expected redirect, none provided")
173	}
174	loc = resp.Header.Get("Location")
175	return loc, nil
176}
177
178// DeleteUserMigration will delete a previous migration archive.
179// id is the migration ID.
180//
181// GitHub API docs: https://developer.github.com/v3/migrations/users/#delete-a-user-migration-archive
182func (s *MigrationService) DeleteUserMigration(ctx context.Context, id int64) (*Response, error) {
183	url := fmt.Sprintf("user/migrations/%v/archive", id)
184
185	req, err := s.client.NewRequest("DELETE", url, nil)
186	if err != nil {
187		return nil, err
188	}
189
190	// TODO: remove custom Accept header when this API fully launches.
191	req.Header.Set("Accept", mediaTypeMigrationsPreview)
192
193	return s.client.Do(ctx, req, nil)
194}
195
196// UnlockUserRepository will unlock a repo that was locked for migration.
197// id is migration ID.
198// You should unlock each migrated repository and delete them when the migration
199// is complete and you no longer need the source data.
200//
201// GitHub API docs: https://developer.github.com/v3/migrations/users/#unlock-a-user-repository
202func (s *MigrationService) UnlockUserRepo(ctx context.Context, id int64, repo string) (*Response, error) {
203	url := fmt.Sprintf("user/migrations/%v/repos/%v/lock", id, repo)
204
205	req, err := s.client.NewRequest("DELETE", url, nil)
206	if err != nil {
207		return nil, err
208	}
209
210	// TODO: remove custom Accept header when this API fully launches.
211	req.Header.Set("Accept", mediaTypeMigrationsPreview)
212
213	return s.client.Do(ctx, req, nil)
214}
215