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 req, err := s.client.NewRequest("GET", u, nil)
106 if err != nil {
107 return nil, nil, err
108 }
109 readme := new(RepositoryContent)
110 resp, err := s.client.Do(ctx, req, readme)
111 if err != nil {
112 return nil, resp, err
113 }
114 return readme, resp, nil
115 }
116
117
118
119
120
121
122
123
124
125 func (s *RepositoriesService) DownloadContents(ctx context.Context, owner, repo, filepath string, opts *RepositoryContentGetOptions) (io.ReadCloser, *Response, error) {
126 dir := path.Dir(filepath)
127 filename := path.Base(filepath)
128 _, dirContents, resp, err := s.GetContents(ctx, owner, repo, dir, opts)
129 if err != nil {
130 return nil, resp, err
131 }
132 for _, contents := range dirContents {
133 if *contents.Name == filename {
134 if contents.DownloadURL == nil || *contents.DownloadURL == "" {
135 return nil, resp, fmt.Errorf("No download link found for %s", filepath)
136 }
137 dlResp, err := s.client.client.Get(*contents.DownloadURL)
138 if err != nil {
139 return nil, &Response{Response: dlResp}, err
140 }
141 return dlResp.Body, &Response{Response: dlResp}, nil
142 }
143 }
144 return nil, resp, fmt.Errorf("No file named %s found in %s", filename, dir)
145 }
146
147
148
149
150
151
152
153
154
155 func (s *RepositoriesService) GetContents(ctx context.Context, owner, repo, path string, opts *RepositoryContentGetOptions) (fileContent *RepositoryContent, directoryContent []*RepositoryContent, resp *Response, err error) {
156 escapedPath := (&url.URL{Path: strings.TrimSuffix(path, "/")}).String()
157 u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, escapedPath)
158 u, err = addOptions(u, opts)
159 if err != nil {
160 return nil, nil, nil, err
161 }
162 req, err := s.client.NewRequest("GET", u, nil)
163 if err != nil {
164 return nil, nil, nil, err
165 }
166 var rawJSON json.RawMessage
167 resp, err = s.client.Do(ctx, req, &rawJSON)
168 if err != nil {
169 return nil, nil, resp, err
170 }
171 fileUnmarshalError := json.Unmarshal(rawJSON, &fileContent)
172 if fileUnmarshalError == nil {
173 return fileContent, nil, resp, nil
174 }
175 directoryUnmarshalError := json.Unmarshal(rawJSON, &directoryContent)
176 if directoryUnmarshalError == nil {
177 return nil, directoryContent, resp, nil
178 }
179 return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s", fileUnmarshalError, directoryUnmarshalError)
180 }
181
182
183
184
185
186 func (s *RepositoriesService) CreateFile(ctx context.Context, owner, repo, path string, opts *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
187 u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
188 req, err := s.client.NewRequest("PUT", u, opts)
189 if err != nil {
190 return nil, nil, err
191 }
192 createResponse := new(RepositoryContentResponse)
193 resp, err := s.client.Do(ctx, req, createResponse)
194 if err != nil {
195 return nil, resp, err
196 }
197 return createResponse, resp, nil
198 }
199
200
201
202
203
204 func (s *RepositoriesService) UpdateFile(ctx context.Context, owner, repo, path string, opts *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
205 u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
206 req, err := s.client.NewRequest("PUT", u, opts)
207 if err != nil {
208 return nil, nil, err
209 }
210 updateResponse := new(RepositoryContentResponse)
211 resp, err := s.client.Do(ctx, req, updateResponse)
212 if err != nil {
213 return nil, resp, err
214 }
215 return updateResponse, resp, nil
216 }
217
218
219
220
221
222 func (s *RepositoriesService) DeleteFile(ctx context.Context, owner, repo, path string, opts *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
223 u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
224 req, err := s.client.NewRequest("DELETE", u, opts)
225 if err != nil {
226 return nil, nil, err
227 }
228 deleteResponse := new(RepositoryContentResponse)
229 resp, err := s.client.Do(ctx, req, deleteResponse)
230 if err != nil {
231 return nil, resp, err
232 }
233 return deleteResponse, resp, nil
234 }
235
236
237 type ArchiveFormat string
238
239 const (
240
241 Tarball ArchiveFormat = "tarball"
242
243
244 Zipball ArchiveFormat = "zipball"
245 )
246
247
248
249
250
251
252 func (s *RepositoriesService) GetArchiveLink(ctx context.Context, owner, repo string, archiveformat ArchiveFormat, opts *RepositoryContentGetOptions, followRedirects bool) (*url.URL, *Response, error) {
253 u := fmt.Sprintf("repos/%s/%s/%s", owner, repo, archiveformat)
254 if opts != nil && opts.Ref != "" {
255 u += fmt.Sprintf("/%s", opts.Ref)
256 }
257 resp, err := s.getArchiveLinkFromURL(ctx, u, followRedirects)
258 if err != nil {
259 return nil, nil, err
260 }
261 if resp.StatusCode != http.StatusFound {
262 return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status)
263 }
264 parsedURL, err := url.Parse(resp.Header.Get("Location"))
265 return parsedURL, newResponse(resp), err
266 }
267
268 func (s *RepositoriesService) getArchiveLinkFromURL(ctx context.Context, u string, followRedirects bool) (*http.Response, error) {
269 req, err := s.client.NewRequest("GET", u, nil)
270 if err != nil {
271 return nil, err
272 }
273
274 var resp *http.Response
275
276 req = withContext(ctx, req)
277 if s.client.client.Transport == nil {
278 resp, err = http.DefaultTransport.RoundTrip(req)
279 } else {
280 resp, err = s.client.client.Transport.RoundTrip(req)
281 }
282 if err != nil {
283 return nil, err
284 }
285 resp.Body.Close()
286
287
288 if followRedirects && resp.StatusCode == http.StatusMovedPermanently {
289 u = resp.Header.Get("Location")
290 resp, err = s.getArchiveLinkFromURL(ctx, u, false)
291 }
292 return resp, err
293 }
294
View as plain text