本站源代码
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

270 lines
9.6KB

  1. // Copyright 2013 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. // Repository contents API methods.
  6. // GitHub API docs: https://developer.github.com/v3/repos/contents/
  7. package github
  8. import (
  9. "context"
  10. "encoding/base64"
  11. "encoding/json"
  12. "fmt"
  13. "io"
  14. "net/http"
  15. "net/url"
  16. "path"
  17. )
  18. // RepositoryContent represents a file or directory in a github repository.
  19. type RepositoryContent struct {
  20. Type *string `json:"type,omitempty"`
  21. // Target is only set if the type is "symlink" and the target is not a normal file.
  22. // If Target is set, Path will be the symlink path.
  23. Target *string `json:"target,omitempty"`
  24. Encoding *string `json:"encoding,omitempty"`
  25. Size *int `json:"size,omitempty"`
  26. Name *string `json:"name,omitempty"`
  27. Path *string `json:"path,omitempty"`
  28. // Content contains the actual file content, which may be encoded.
  29. // Callers should call GetContent which will decode the content if
  30. // necessary.
  31. Content *string `json:"content,omitempty"`
  32. SHA *string `json:"sha,omitempty"`
  33. URL *string `json:"url,omitempty"`
  34. GitURL *string `json:"git_url,omitempty"`
  35. HTMLURL *string `json:"html_url,omitempty"`
  36. DownloadURL *string `json:"download_url,omitempty"`
  37. }
  38. // RepositoryContentResponse holds the parsed response from CreateFile, UpdateFile, and DeleteFile.
  39. type RepositoryContentResponse struct {
  40. Content *RepositoryContent `json:"content,omitempty"`
  41. Commit `json:"commit,omitempty"`
  42. }
  43. // RepositoryContentFileOptions specifies optional parameters for CreateFile, UpdateFile, and DeleteFile.
  44. type RepositoryContentFileOptions struct {
  45. Message *string `json:"message,omitempty"`
  46. Content []byte `json:"content,omitempty"` // unencoded
  47. SHA *string `json:"sha,omitempty"`
  48. Branch *string `json:"branch,omitempty"`
  49. Author *CommitAuthor `json:"author,omitempty"`
  50. Committer *CommitAuthor `json:"committer,omitempty"`
  51. }
  52. // RepositoryContentGetOptions represents an optional ref parameter, which can be a SHA,
  53. // branch, or tag
  54. type RepositoryContentGetOptions struct {
  55. Ref string `url:"ref,omitempty"`
  56. }
  57. // String converts RepositoryContent to a string. It's primarily for testing.
  58. func (r RepositoryContent) String() string {
  59. return Stringify(r)
  60. }
  61. // GetContent returns the content of r, decoding it if necessary.
  62. func (r *RepositoryContent) GetContent() (string, error) {
  63. var encoding string
  64. if r.Encoding != nil {
  65. encoding = *r.Encoding
  66. }
  67. switch encoding {
  68. case "base64":
  69. c, err := base64.StdEncoding.DecodeString(*r.Content)
  70. return string(c), err
  71. case "":
  72. if r.Content == nil {
  73. return "", nil
  74. }
  75. return *r.Content, nil
  76. default:
  77. return "", fmt.Errorf("unsupported content encoding: %v", encoding)
  78. }
  79. }
  80. // GetReadme gets the Readme file for the repository.
  81. //
  82. // GitHub API docs: https://developer.github.com/v3/repos/contents/#get-the-readme
  83. func (s *RepositoriesService) GetReadme(ctx context.Context, owner, repo string, opt *RepositoryContentGetOptions) (*RepositoryContent, *Response, error) {
  84. u := fmt.Sprintf("repos/%v/%v/readme", owner, repo)
  85. u, err := addOptions(u, opt)
  86. if err != nil {
  87. return nil, nil, err
  88. }
  89. req, err := s.client.NewRequest("GET", u, nil)
  90. if err != nil {
  91. return nil, nil, err
  92. }
  93. readme := new(RepositoryContent)
  94. resp, err := s.client.Do(ctx, req, readme)
  95. if err != nil {
  96. return nil, resp, err
  97. }
  98. return readme, resp, nil
  99. }
  100. // DownloadContents returns an io.ReadCloser that reads the contents of the
  101. // specified file. This function will work with files of any size, as opposed
  102. // to GetContents which is limited to 1 Mb files. It is the caller's
  103. // responsibility to close the ReadCloser.
  104. func (s *RepositoriesService) DownloadContents(ctx context.Context, owner, repo, filepath string, opt *RepositoryContentGetOptions) (io.ReadCloser, error) {
  105. dir := path.Dir(filepath)
  106. filename := path.Base(filepath)
  107. _, dirContents, _, err := s.GetContents(ctx, owner, repo, dir, opt)
  108. if err != nil {
  109. return nil, err
  110. }
  111. for _, contents := range dirContents {
  112. if *contents.Name == filename {
  113. if contents.DownloadURL == nil || *contents.DownloadURL == "" {
  114. return nil, fmt.Errorf("No download link found for %s", filepath)
  115. }
  116. resp, err := s.client.client.Get(*contents.DownloadURL)
  117. if err != nil {
  118. return nil, err
  119. }
  120. return resp.Body, nil
  121. }
  122. }
  123. return nil, fmt.Errorf("No file named %s found in %s", filename, dir)
  124. }
  125. // GetContents can return either the metadata and content of a single file
  126. // (when path references a file) or the metadata of all the files and/or
  127. // subdirectories of a directory (when path references a directory). To make it
  128. // easy to distinguish between both result types and to mimic the API as much
  129. // as possible, both result types will be returned but only one will contain a
  130. // value and the other will be nil.
  131. //
  132. // GitHub API docs: https://developer.github.com/v3/repos/contents/#get-contents
  133. func (s *RepositoriesService) GetContents(ctx context.Context, owner, repo, path string, opt *RepositoryContentGetOptions) (fileContent *RepositoryContent, directoryContent []*RepositoryContent, resp *Response, err error) {
  134. escapedPath := (&url.URL{Path: path}).String()
  135. u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, escapedPath)
  136. u, err = addOptions(u, opt)
  137. if err != nil {
  138. return nil, nil, nil, err
  139. }
  140. req, err := s.client.NewRequest("GET", u, nil)
  141. if err != nil {
  142. return nil, nil, nil, err
  143. }
  144. var rawJSON json.RawMessage
  145. resp, err = s.client.Do(ctx, req, &rawJSON)
  146. if err != nil {
  147. return nil, nil, resp, err
  148. }
  149. fileUnmarshalError := json.Unmarshal(rawJSON, &fileContent)
  150. if fileUnmarshalError == nil {
  151. return fileContent, nil, resp, nil
  152. }
  153. directoryUnmarshalError := json.Unmarshal(rawJSON, &directoryContent)
  154. if directoryUnmarshalError == nil {
  155. return nil, directoryContent, resp, nil
  156. }
  157. return nil, nil, resp, fmt.Errorf("unmarshalling failed for both file and directory content: %s and %s", fileUnmarshalError, directoryUnmarshalError)
  158. }
  159. // CreateFile creates a new file in a repository at the given path and returns
  160. // the commit and file metadata.
  161. //
  162. // GitHub API docs: https://developer.github.com/v3/repos/contents/#create-a-file
  163. func (s *RepositoriesService) CreateFile(ctx context.Context, owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
  164. u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
  165. req, err := s.client.NewRequest("PUT", u, opt)
  166. if err != nil {
  167. return nil, nil, err
  168. }
  169. createResponse := new(RepositoryContentResponse)
  170. resp, err := s.client.Do(ctx, req, createResponse)
  171. if err != nil {
  172. return nil, resp, err
  173. }
  174. return createResponse, resp, nil
  175. }
  176. // UpdateFile updates a file in a repository at the given path and returns the
  177. // commit and file metadata. Requires the blob SHA of the file being updated.
  178. //
  179. // GitHub API docs: https://developer.github.com/v3/repos/contents/#update-a-file
  180. func (s *RepositoriesService) UpdateFile(ctx context.Context, owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
  181. u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
  182. req, err := s.client.NewRequest("PUT", u, opt)
  183. if err != nil {
  184. return nil, nil, err
  185. }
  186. updateResponse := new(RepositoryContentResponse)
  187. resp, err := s.client.Do(ctx, req, updateResponse)
  188. if err != nil {
  189. return nil, resp, err
  190. }
  191. return updateResponse, resp, nil
  192. }
  193. // DeleteFile deletes a file from a repository and returns the commit.
  194. // Requires the blob SHA of the file to be deleted.
  195. //
  196. // GitHub API docs: https://developer.github.com/v3/repos/contents/#delete-a-file
  197. func (s *RepositoriesService) DeleteFile(ctx context.Context, owner, repo, path string, opt *RepositoryContentFileOptions) (*RepositoryContentResponse, *Response, error) {
  198. u := fmt.Sprintf("repos/%s/%s/contents/%s", owner, repo, path)
  199. req, err := s.client.NewRequest("DELETE", u, opt)
  200. if err != nil {
  201. return nil, nil, err
  202. }
  203. deleteResponse := new(RepositoryContentResponse)
  204. resp, err := s.client.Do(ctx, req, deleteResponse)
  205. if err != nil {
  206. return nil, resp, err
  207. }
  208. return deleteResponse, resp, nil
  209. }
  210. // archiveFormat is used to define the archive type when calling GetArchiveLink.
  211. type archiveFormat string
  212. const (
  213. // Tarball specifies an archive in gzipped tar format.
  214. Tarball archiveFormat = "tarball"
  215. // Zipball specifies an archive in zip format.
  216. Zipball archiveFormat = "zipball"
  217. )
  218. // GetArchiveLink returns an URL to download a tarball or zipball archive for a
  219. // repository. The archiveFormat can be specified by either the github.Tarball
  220. // or github.Zipball constant.
  221. //
  222. // GitHub API docs: https://developer.github.com/v3/repos/contents/#get-archive-link
  223. func (s *RepositoriesService) GetArchiveLink(ctx context.Context, owner, repo string, archiveformat archiveFormat, opt *RepositoryContentGetOptions) (*url.URL, *Response, error) {
  224. u := fmt.Sprintf("repos/%s/%s/%s", owner, repo, archiveformat)
  225. if opt != nil && opt.Ref != "" {
  226. u += fmt.Sprintf("/%s", opt.Ref)
  227. }
  228. req, err := s.client.NewRequest("GET", u, nil)
  229. if err != nil {
  230. return nil, nil, err
  231. }
  232. var resp *http.Response
  233. // Use http.DefaultTransport if no custom Transport is configured
  234. req = withContext(ctx, req)
  235. if s.client.client.Transport == nil {
  236. resp, err = http.DefaultTransport.RoundTrip(req)
  237. } else {
  238. resp, err = s.client.client.Transport.RoundTrip(req)
  239. }
  240. if err != nil {
  241. return nil, nil, err
  242. }
  243. resp.Body.Close()
  244. if resp.StatusCode != http.StatusFound {
  245. return nil, newResponse(resp), fmt.Errorf("unexpected status code: %s", resp.Status)
  246. }
  247. parsedURL, err := url.Parse(resp.Header.Get("Location"))
  248. return parsedURL, newResponse(resp), err
  249. }
上海开阖软件有限公司 沪ICP备12045867号-1