1
2
3
4
5
6
7
8
9 package github
10
11 import (
12 "context"
13 "encoding/base64"
14 "encoding/json"
15 "errors"
16 "fmt"
17 "io"
18 "net/http"
19 "net/url"
20 "path"
21 "strings"
22 )
23
24
25 type RepositoryContent struct {
26 Type *string `json:"type,omitempty"`
27
28
29 Target *string `json:"target,omitempty"`
30 Encoding *string `json:"encoding,omitempty"`
31 Size *int `json:"size,omitempty"`
32 Name *string `json:"name,omitempty"`
33 Path *string `json:"path,omitempty"`
34
35
36
37 Content *string `json:"content,omitempty"`
38 SHA *string `json:"sha,omitempty"`
39 URL *string `json:"url,omitempty"`
40 GitURL *string `json:"git_url,omitempty"`
41 HTMLURL *string `json:"html_url,omitempty"`
42 DownloadURL *string `json:"download_url,omitempty"`
43 }
44
45
46 type RepositoryContentResponse struct {
47 Content *RepositoryContent `json:"content,omitempty"`
48 Commit `json:"commit,omitempty"`
49 }
50
51
52 type RepositoryContentFileOptions struct {
53 Message *string `json:"message,omitempty"`
54 Content []byte `json:"content,omitempty"`
55 SHA *string `json:"sha,omitempty"`
56 Branch *string `json:"branch,omitempty"`
57 Author *CommitAuthor `json:"author,omitempty"`
58 Committer *CommitAuthor `json:"committer,omitempty"`
59 }
60
61
62
63 type RepositoryContentGetOptions struct {
64 Ref string `url:"ref,omitempty"`
65 }
66
67
68 func (r RepositoryContent) String() string {
69 return Stringify(r)
70 }
71
72
73 func (r *RepositoryContent) GetContent() (string, error) {
74 var encoding string
75 if r.Encoding != nil {
76 encoding = *r.Encoding
77 }
78
79 switch encoding {
80 case "base64":
81 if r.Content == nil {
82 return "", errors.New("malformed response: base64 encoding of null content")
83 }
84 c, err := base64.StdEncoding.DecodeString(*r.Content)
85 return string(c), err
86 case "":
87 if r.Content == nil {
88 return "", nil
89 }
90 return *r.Content, nil
91 default:
92 return "", fmt.Errorf("unsupported content encoding: %v", encoding)
93 }
94 }
95
96
97
98
99 func (s *RepositoriesService) GetReadme(ctx context.Context, owner, repo string, opts *RepositoryContentGetOptions) (*RepositoryContent, *Response, error) {
100 u := fmt.Sprintf("repos/%v/%v/readme", owner, repo)
101 u, err := addOptions(u, opts)
102 if err != nil {
103 return nil, nil, err
104 }
105
106 req, err := s.client.NewRequest("GET", u, nil)
107 if err != nil {
108 return nil, nil, err
109 }
110
111 readme := new(RepositoryContent)
112 resp, err := s.client.Do(ctx, req, readme)
113 if err != nil {
114 return nil, resp, err
115 }
116
117 return readme, resp, nil
118 }
119
120
121
122
123
124
125
126
127
128 func (s *RepositoriesService) DownloadContents(ctx context.Context, owner, repo, filepath string, opts *RepositoryContentGetOptions) (io.ReadCloser, *Response, error) {
129 dir := path.Dir(filepath)
130 filename := path.Base(filepath)
131 _, dirContents, resp, err := s.GetContents(ctx, owner, repo, dir, opts)
132 if err != nil {
133 return nil, resp, err
134 }
135
136 for _, contents := range dirContents {
137 if *contents.Name == filename {
138 if contents.DownloadURL == nil || *contents.DownloadURL == "" {
139 return nil, resp, fmt.Errorf("no download link found for %s", filepath)
140 }
141
142 dlResp, err := s.client.client.Get(*contents.DownloadURL)
143 if err != nil {
144 return nil, &Response{Response: dlResp}, err
145 }
146
147 return dlResp.Body, &Response{Response: dlResp}, nil
148 }
149 }
150
151 return nil, resp, fmt.Errorf("no file named %s found in %s", filename, dir)
152 }
153
154
155
156
157
158
159
160
161
162 func (s *RepositoriesService) DownloadContentsWithMeta(ctx context.Context, owner, repo, filepath string, opts *RepositoryContentGetOptions) (io.ReadCloser, *RepositoryContent, *Response, error) {
163 dir := path.Dir(filepath)
164 filename := path.Base(filepath)
165 _, dirContents, resp, err := s.GetContents(ctx, owner, repo, dir, opts)
166 if err != nil {
167 return nil, nil, resp, err
168 }
169
170 for _, contents := range dirContents {
171 if *contents.Name == filename {
172 if contents.DownloadURL == nil || *contents.DownloadURL == "" {
173 return nil, contents, resp, fmt.Errorf("no download link found for %s", filepath)
174 }
175
176 dlResp, err := s.client.client.Get(*contents.DownloadURL)
177 if err != nil {
178 return nil, contents, &Response{Response: dlResp}, err
179 }
180
181 return dlResp.Body, contents, &Response{Response: dlResp}, nil
182 }
183 }
184
185 return nil, nil, resp, fmt.Errorf("no file named %s found in %s", filename, dir)
186 }
187
188
189
190
191
192
193
194
195
196 func (s *RepositoriesService) GetContents(ctx context.Context, owner, repo, path string, opts *RepositoryContentGetOptions) (fileContent *RepositoryContent, directoryContent []*RepositoryContent, resp *Response, err error) {
197 escapedPath := (&url.URL{Path: strings.TrimSuffix(path, "/")}).String()
198 u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, escapedPath)
199 u, err = addOptions(u, opts)
200 if err != nil {
201 return nil, nil, nil, err
202 }
203
204 req, err := s.client.NewRequest("GET", u, nil)
205 if err != nil {
206 return nil, nil, nil, err
207 }
208
209 var rawJSON json.RawMessage
210 resp, err = s.client.Do(ctx, req, &rawJSON)
211 if err != nil {
212 return nil, nil, resp, err
213 }
214
215 fileUnmarshalError := json.Unmarshal(rawJSON, &fileContent)
216 if fileUnmarshalError == nil {
217 return fileContent, nil, resp, nil
218 }
219
220 directoryUnmarshalError := json.Unmarshal(rawJSON, &directoryContent)
221 if directoryUnmarshalError == nil {
222 return nil, directoryContent, resp, nil
223 }
224
225 return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s", fileUnmarshalError, directoryUnmarshalError)
226 }
227
228
229
230
231
232 func (s *RepositoriesService) CreateFile(ctx context.Context, owner, repo, path string, opts *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
233 u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
234 req, err := s.client.NewRequest("PUT", u, opts)
235 if err != nil {
236 return nil, nil, err
237 }
238
239 createResponse := new(RepositoryContentResponse)
240 resp, err := s.client.Do(ctx, req, createResponse)
241 if err != nil {
242 return nil, resp, err
243 }
244
245 return createResponse, resp, nil
246 }
247
248
249
250
251
252 func (s *RepositoriesService) UpdateFile(ctx context.Context, owner, repo, path string, opts *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
253 u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
254 req, err := s.client.NewRequest("PUT", u, opts)
255 if err != nil {
256 return nil, nil, err
257 }
258
259 updateResponse := new(RepositoryContentResponse)
260 resp, err := s.client.Do(ctx, req, updateResponse)
261 if err != nil {
262 return nil, resp, err
263 }
264
265 return updateResponse, resp, nil
266 }
267
268
269
270
271
272 func (s *RepositoriesService) DeleteFile(ctx context.Context, owner, repo, path string, opts *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
273 u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
274 req, err := s.client.NewRequest("DELETE", u, opts)
275 if err != nil {
276 return nil, nil, err
277 }
278
279 deleteResponse := new(RepositoryContentResponse)
280 resp, err := s.client.Do(ctx, req, deleteResponse)
281 if err != nil {
282 return nil, resp, err
283 }
284
285 return deleteResponse, resp, nil
286 }
287
288
289 type ArchiveFormat string
290
291 const (
292
293 Tarball ArchiveFormat = "tarball"
294
295
296 Zipball ArchiveFormat = "zipball"
297 )
298
299
300
301
302
303
304 func (s *RepositoriesService) GetArchiveLink(ctx context.Context, owner, repo string, archiveformat ArchiveFormat, opts *RepositoryContentGetOptions, followRedirects bool) (*url.URL, *Response, error) {
305 u := fmt.Sprintf("repos/%s/%s/%s", owner, repo, archiveformat)
306 if opts != nil && opts.Ref != "" {
307 u += fmt.Sprintf("/%s", opts.Ref)
308 }
309 resp, err := s.client.roundTripWithOptionalFollowRedirect(ctx, u, followRedirects)
310 if err != nil {
311 return nil, nil, err
312 }
313 defer resp.Body.Close()
314
315 if resp.StatusCode != http.StatusFound {
316 return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status)
317 }
318
319 parsedURL, err := url.Parse(resp.Header.Get("Location"))
320 return parsedURL, newResponse(resp), err
321 }
322
View as plain text