@@ -107,31 +107,37 @@ func FetchTags(githubRepoUrl string, githubToken string, instance GitHubInstance
107107 return tagsString , wrapError (err )
108108 }
109109
110- url := createGitHubRepoUrlForPath (repo , "tags" )
111- resp , err := callGitHubApi (repo , url , map [string ]string {})
112- if err != nil {
113- return tagsString , err
114- }
110+ // Set per_page to 100, which is the max, to reduce network calls
111+ tagsUrl := formatUrl (repo , createGitHubRepoUrlForPath (repo , "tags?per_page=100" ))
112+ for tagsUrl != "" {
113+ resp , err := callGitHubApiRaw (tagsUrl , "GET" , repo .Token , map [string ]string {})
114+ if err != nil {
115+ return tagsString , err
116+ }
115117
116- // Convert the response body to a byte array
117- buf := new (bytes.Buffer )
118- _ , goErr := buf .ReadFrom (resp .Body )
119- if goErr != nil {
120- return tagsString , wrapError (goErr )
121- }
122- jsonResp := buf .Bytes ()
118+ // Convert the response body to a byte array
119+ buf := new (bytes.Buffer )
120+ _ , goErr := buf .ReadFrom (resp .Body )
121+ if goErr != nil {
122+ return tagsString , wrapError (goErr )
123+ }
124+ jsonResp := buf .Bytes ()
123125
124- // Extract the JSON into our array of gitHubTagsCommitApiResponse's
125- var tags []GitHubTagsApiResponse
126- if err := json .Unmarshal (jsonResp , & tags ); err != nil {
127- return tagsString , wrapError (err )
128- }
126+ // Extract the JSON into our array of gitHubTagsCommitApiResponse's
127+ var tags []GitHubTagsApiResponse
128+ if err := json .Unmarshal (jsonResp , & tags ); err != nil {
129+ return tagsString , wrapError (err )
130+ }
129131
130- for _ , tag := range tags {
131- // Skip tags that are not semantically versioned so that they don't cause errors. (issue #75)
132- if _ , err := version .NewVersion (tag .Name ); err == nil {
133- tagsString = append (tagsString , tag .Name )
132+ for _ , tag := range tags {
133+ // Skip tags that are not semantically versioned so that they don't cause errors. (issue #75)
134+ if _ , err := version .NewVersion (tag .Name ); err == nil {
135+ tagsString = append (tagsString , tag .Name )
136+ }
134137 }
138+
139+ // Get paginated tags (issue #26 and #46)
140+ tagsUrl = getNextUrl (resp .Header .Get ("link" ))
135141 }
136142
137143 return tagsString , nil
@@ -203,17 +209,50 @@ func createGitHubRepoUrlForPath(repo GitHubRepo, path string) string {
203209 return fmt .Sprintf ("repos/%s/%s/%s" , repo .Owner , repo .Name , path )
204210}
205211
212+ var nextLinkRegex = regexp .MustCompile (`<(.+?)>;\s*rel="next"` )
213+
214+ // Get the next page URL from the given link header returned by the GitHub API. If there is no next page, return an
215+ // empty string. The link header is expected to be of the form:
216+ //
217+ // <url>; rel="next", <url>; rel="last"
218+ //
219+ func getNextUrl (links string ) string {
220+ if len (links ) == 0 {
221+ return ""
222+ }
223+
224+ for _ , link := range strings .Split (links , "," ) {
225+ urlMatches := nextLinkRegex .FindStringSubmatch (link )
226+ if len (urlMatches ) == 2 {
227+ return strings .TrimSpace (urlMatches [1 ])
228+ }
229+ }
230+
231+ return ""
232+ }
233+
234+ // Format a URL for calling the GitHub API for the given repo and path
235+ func formatUrl (repo GitHubRepo , path string ) string {
236+ return fmt .Sprintf ("https://" + repo .ApiUrl + "/%s" , path )
237+ }
238+
206239// Call the GitHub API at the given path and return the HTTP response
207240func callGitHubApi (repo GitHubRepo , path string , customHeaders map [string ]string ) (* http.Response , * FetchError ) {
241+ return callGitHubApiRaw (formatUrl (repo , path ), "GET" , repo .Token , customHeaders )
242+ }
243+
244+ // Call the GitHub API at the given URL, using the given HTTP method, and passing the given token and headers, and
245+ // return the response
246+ func callGitHubApiRaw (url string , method string , token string , customHeaders map [string ]string ) (* http.Response , * FetchError ) {
208247 httpClient := & http.Client {}
209248
210- request , err := http .NewRequest ("GET" , fmt . Sprintf ( "https://" + repo . ApiUrl + "/%s" , path ) , nil )
249+ request , err := http .NewRequest (method , url , nil )
211250 if err != nil {
212251 return nil , wrapError (err )
213252 }
214253
215- if repo . Token != "" {
216- request .Header .Set ("Authorization" , fmt .Sprintf ("token %s" , repo . Token ))
254+ if token != "" {
255+ request .Header .Set ("Authorization" , fmt .Sprintf ("token %s" , token ))
217256 }
218257
219258 for headerName , headerValue := range customHeaders {
@@ -236,7 +275,7 @@ func callGitHubApi(repo GitHubRepo, path string, customHeaders map[string]string
236275 respBody := buf .String ()
237276
238277 // We leverage the HTTP Response Code as our ErrorCode here.
239- return nil , newError (resp .StatusCode , fmt .Sprintf ("Received HTTP Response %d while fetching releases for GitHub URL %s. Full HTTP response: %s" , resp .StatusCode , repo . Url , respBody ))
278+ return nil , newError (resp .StatusCode , fmt .Sprintf ("Received HTTP Response %d while fetching releases for GitHub URL %s. Full HTTP response: %s" , resp .StatusCode , url , respBody ))
240279 }
241280
242281 return resp , nil
0 commit comments