package main import ( "fmt" "net/url" "regexp" "time" ) var reGitHubPR = regexp.MustCompile(`^https://github\.com/([^/?#]+)/([^/?#]+)/pull/([0-9]+)(?:\?[^#]*)?(?:#.*)?$`) func githubPagination(i int) url.Values { params := make(url.Values) params.Set("page", fmt.Sprintf("%v", i+1)) return params } type GitHub struct{} var _ Forge = GitHub{} func (GitHub) FetchStatus(urls []string) (string, error) { for _, u := range urls { if reGoogleGerritCL.MatchString(u) { return "", nil } } return fetchPerURLStatus(urls, func(u string) (string, error) { m := reGitHubPR.FindStringSubmatch(u) if m == nil { return "", nil } user := m[1] repo := m[2] prnum := m[3] urlStr := "https://api.github.com/repos/" + user + "/" + repo + "/pulls/" + prnum var obj struct { // State values are "open" and "closed". State string `json:"state"` Merged bool `json:"merged"` MergeCommitSha string `json:"merge_commit_sha"` } if err := httpGetJSON(urlStr, nil, &obj); err != nil { return "", err } ret := obj.State if obj.Merged { ret = statusMerged tag, err := getGitTagThatContainsAll("https://github.com/"+user+"/"+repo, obj.MergeCommitSha) if err != nil { return "", err } if tag != "" { ret = fmt.Sprintf(statusReleasedFmt, tag) } } return ret, nil }) } func (GitHub) FetchSubmittedAt(urls []string) (time.Time, error) { return fetchPerURLSubmittedAt(urls, func(u string) (time.Time, error) { m := reGitHubPR.FindStringSubmatch(u) if m == nil { return time.Time{}, nil } user := m[1] repo := m[2] prnum := m[3] urlStr := "https://api.github.com/repos/" + user + "/" + repo + "/pulls/" + prnum var obj struct { CreatedAt time.Time `json:"created_at"` } if err := httpGetJSON(urlStr, nil, &obj); err != nil { return time.Time{}, err } return obj.CreatedAt, nil }) } func (GitHub) FetchLastUpdated(urls []string) (time.Time, User, error) { for _, u := range urls { if reGoogleGerritCL.MatchString(u) { return time.Time{}, User{}, nil } } return fetchPerURLLastUpdated(urls, func(u string) (time.Time, User, error) { m := reGitHubPR.FindStringSubmatch(u) if m == nil { return time.Time{}, User{}, nil } user := m[1] repo := m[2] prnum := m[3] urlStr := "https://api.github.com/repos/" + user + "/" + repo + "/pulls/" + prnum var obj struct { UpdatedAt time.Time `json:"updated_at"` CreatedAt time.Time `json:"created_at"` CreatedBy struct { Login string `json:"login"` HTMLURL string `json:"html_url"` } `json:"user"` MergedAt time.Time `json:"merged_at"` MergedBy struct { Login string `json:"login"` HTMLURL string `json:"html_url"` } `json:"merged_by"` } if err := httpGetJSON(urlStr, nil, &obj); err != nil { return time.Time{}, User{}, err } retUpdatedAt := obj.UpdatedAt var retUser User if retUser == (User{}) && withinOneSecond(obj.CreatedAt, retUpdatedAt) { retUser.Name = obj.CreatedBy.Login retUser.URL = obj.CreatedBy.HTMLURL } if retUser == (User{}) && withinOneSecond(obj.MergedAt, retUpdatedAt) { retUser.Name = obj.MergedBy.Login retUser.URL = obj.MergedBy.HTMLURL } if retUser == (User{}) { // "normal" comments var comments []struct { UpdatedAt time.Time `json:"updated_at"` User struct { Login string `json:"login"` HTMLURL string `json:"html_url"` } `json:"user"` } if err := httpGetPaginatedJSON("https://api.github.com/repos/"+user+"/"+repo+"/issues/"+prnum+"/comments", nil, &comments, githubPagination); err != nil { return time.Time{}, User{}, err } for _, comment := range comments { if withinOneSecond(comment.UpdatedAt, retUpdatedAt) { retUser.Name = comment.User.Login retUser.URL = comment.User.HTMLURL break } } } if retUser == (User{}) { // comments on a specific part of the diff var reviewComments []struct { UpdatedAt time.Time `json:"updated_at"` User struct { Login string `json:"login"` HTMLURL string `json:"html_url"` } `json:"user"` } if err := httpGetPaginatedJSON("https://api.github.com/repos/"+user+"/"+repo+"/pulls/"+prnum+"/comments", nil, &reviewComments, githubPagination); err != nil { return time.Time{}, User{}, err } for _, comment := range reviewComments { if withinOneSecond(comment.UpdatedAt, retUpdatedAt) { retUser.Name = comment.User.Login retUser.URL = comment.User.HTMLURL break } } } if retUser == (User{}) { var events []struct { CreatedAt time.Time `json:"created_at"` Actor struct { Login string `json:"login"` HTMLURL string `json:"html_url"` } `json:"actor"` } if err := httpGetJSON("https://api.github.com/repos/"+user+"/"+repo+"/issues/"+prnum+"/events", nil, &events); err != nil { return time.Time{}, User{}, err } for _, event := range events { if withinOneSecond(event.CreatedAt, retUpdatedAt) { retUser.Name = event.Actor.Login retUser.URL = event.Actor.HTMLURL break } } } return retUpdatedAt, retUser, nil }) }