@@ -17,7 +17,6 @@ import (
1717 "io"
1818 "net/http"
1919 "net/url"
20- "path"
2120 "strings"
2221)
2322
@@ -98,6 +97,54 @@ func (r *RepositoryContent) GetContent() (string, error) {
9897 }
9998}
10099
100+ // DownloadContent downloads the content of a file using the information from
101+ // the RepositoryContent. This method supports files of any size and works with
102+ // RepositoryContent entries obtained from GetContents calls on both individual
103+ // files and directories.
104+ //
105+ // It returns an io.ReadCloser for the file content and an *http.Response for
106+ // the download request (which may be nil if content was obtained without an
107+ // HTTP request). It is the caller's responsibility to close the ReadCloser.
108+ func (r * RepositoryContent ) DownloadContent (ctx context.Context , httpClient * http.Client ) (io.ReadCloser , * http.Response , error ) {
109+ // Check if this is a file.
110+ if r .Type == nil || * r .Type != "file" {
111+ return nil , nil , fmt .Errorf ("cannot download content for type %q (only files are supported)" , r .GetType ())
112+ }
113+
114+ // Handle empty files.
115+ if r .Size != nil && * r .Size == 0 {
116+ return io .NopCloser (strings .NewReader ("" )), nil , nil
117+ }
118+
119+ // Try to use the inline content if available.
120+ if r .Content != nil && * r .Content != "" {
121+ content , err := r .GetContent ()
122+ if err == nil {
123+ return io .NopCloser (strings .NewReader (content )), nil , nil
124+ }
125+ // If GetContent fails (e.g., encoding "none"), fall through to use DownloadURL.
126+ }
127+
128+ // Use the download URL.
129+ if r .DownloadURL == nil || * r .DownloadURL == "" {
130+ return nil , nil , errors .New ("no download URL available for this file" )
131+ }
132+
133+ req , err := http .NewRequestWithContext (ctx , "GET" , * r .DownloadURL , nil )
134+ if err != nil {
135+ return nil , nil , err
136+ }
137+
138+ resp , err := httpClient .Do (req )
139+ if err != nil {
140+ return nil , nil , err
141+ }
142+
143+ // Return the response body even for non-2xx status codes.
144+ // Callers should check resp.StatusCode to verify success.
145+ return resp .Body , resp , nil
146+ }
147+
101148// GetReadme gets the Readme file for the repository.
102149//
103150// GitHub API docs: https://docs.github.com/rest/repos/contents#get-a-repository-readme
@@ -137,40 +184,8 @@ func (s *RepositoriesService) GetReadme(ctx context.Context, owner, repo string,
137184//
138185//meta:operation GET /repos/{owner}/{repo}/contents/{path}
139186func (s * RepositoriesService ) DownloadContents (ctx context.Context , owner , repo , filepath string , opts * RepositoryContentGetOptions ) (io.ReadCloser , * Response , error ) {
140- dir := path .Dir (filepath )
141- filename := path .Base (filepath )
142- fileContent , _ , resp , err := s .GetContents (ctx , owner , repo , filepath , opts )
143- if err == nil && fileContent != nil {
144- content , err := fileContent .GetContent ()
145- if err == nil && content != "" {
146- return io .NopCloser (strings .NewReader (content )), resp , nil
147- }
148- }
149-
150- _ , dirContents , resp , err := s .GetContents (ctx , owner , repo , dir , opts )
151- if err != nil {
152- return nil , resp , err
153- }
154-
155- for _ , contents := range dirContents {
156- if contents .GetName () == filename {
157- if contents .GetDownloadURL () == "" {
158- return nil , resp , fmt .Errorf ("no download link found for %v" , filepath )
159- }
160- dlReq , err := http .NewRequestWithContext (ctx , "GET" , * contents .DownloadURL , nil )
161- if err != nil {
162- return nil , resp , err
163- }
164- dlResp , err := s .client .client .Do (dlReq )
165- if err != nil {
166- return nil , & Response {Response : dlResp }, err
167- }
168-
169- return dlResp .Body , & Response {Response : dlResp }, nil
170- }
171- }
172-
173- return nil , resp , fmt .Errorf ("no file named %v found in %v" , filename , dir )
187+ reader , _ , resp , err := s .DownloadContentsWithMeta (ctx , owner , repo , filepath , opts )
188+ return reader , resp , err
174189}
175190
176191// DownloadContentsWithMeta is identical to DownloadContents but additionally
@@ -186,40 +201,25 @@ func (s *RepositoriesService) DownloadContents(ctx context.Context, owner, repo,
186201//
187202//meta:operation GET /repos/{owner}/{repo}/contents/{path}
188203func (s * RepositoriesService ) DownloadContentsWithMeta (ctx context.Context , owner , repo , filepath string , opts * RepositoryContentGetOptions ) (io.ReadCloser , * RepositoryContent , * Response , error ) {
189- dir := path .Dir (filepath )
190- filename := path .Base (filepath )
191204 fileContent , _ , resp , err := s .GetContents (ctx , owner , repo , filepath , opts )
192- if err == nil && fileContent != nil {
193- content , err := fileContent . GetContent ()
194- if err == nil && content != "" {
195- return io . NopCloser ( strings . NewReader ( content )), fileContent , resp , nil
196- }
205+ if err != nil {
206+ return nil , nil , resp , err
207+ }
208+ if fileContent == nil {
209+ return nil , nil , resp , fmt . Errorf ( "no file found at %v" , filepath )
197210 }
198211
199- _ , dirContents , resp , err := s . GetContents (ctx , owner , repo , dir , opts )
212+ reader , httpResp , err := fileContent . DownloadContent (ctx , s . client . client )
200213 if err != nil {
201- return nil , nil , resp , err
214+ return nil , fileContent , resp , err
202215 }
203216
204- for _ , contents := range dirContents {
205- if contents .GetName () == filename {
206- if contents .GetDownloadURL () == "" {
207- return nil , contents , resp , fmt .Errorf ("no download link found for %v" , filepath )
208- }
209- dlReq , err := http .NewRequestWithContext (ctx , "GET" , * contents .DownloadURL , nil )
210- if err != nil {
211- return nil , contents , resp , err
212- }
213- dlResp , err := s .client .client .Do (dlReq )
214- if err != nil {
215- return nil , contents , & Response {Response : dlResp }, err
216- }
217-
218- return dlResp .Body , contents , & Response {Response : dlResp }, nil
219- }
217+ // If we got an HTTP response from the download, wrap it in a Response.
218+ if httpResp != nil {
219+ return reader , fileContent , & Response {Response : httpResp }, nil
220220 }
221221
222- return nil , nil , resp , fmt . Errorf ( "no file named %v found in %v" , filename , dir )
222+ return reader , fileContent , resp , nil
223223}
224224
225225// GetContents can return either the metadata and content of a single file
0 commit comments