package main import ( "fmt" "net/url" "regexp" "time" ) var reGitLabMR = regexp.MustCompile(`^https://([^/]+)/([^?#]+)/-/merge_requests/([0-9]+)(?:\?[^#]*)?(?:#.*)?$`) type GitLab struct{} var _ Forge = GitLab{} func (GitLab) FetchStatus(urls []string) (string, error) { for _, u := range urls { m := reGitLabMR.FindStringSubmatch(u) if m == nil { continue } authority := m[1] projectID := m[2] mrnum := m[3] urlStr := "https://" + authority + "/api/v4/projects/" + url.QueryEscape(projectID) + "/merge_requests/" + mrnum var obj struct { // State values are "opened", "closed", "locked", and "merged". State string `json:"state"` MergeCommitSha string `json:"merge_commit_sha"` SquashCommitSha string `json:"squash_commit_sha"` } if err := httpGetJSON(urlStr, nil, &obj); err != nil { return "", err } ret := obj.State if ret == "opened" { ret = statusOpen } if ret == "merged" { ret = statusMerged var mergeCommit string if obj.MergeCommitSha != "" { mergeCommit = obj.MergeCommitSha } if obj.SquashCommitSha != "" { mergeCommit = obj.SquashCommitSha } if mergeCommit != "" { tag, err := getGitTagThatContainsAll("https://"+authority+"/"+projectID+".git", mergeCommit) if err != nil { return "", err } if tag != "" { ret = fmt.Sprintf(statusReleasedFmt, tag) } } } return ret, nil } return "", nil } func (GitLab) FetchSubmittedAt(urls []string) (time.Time, error) { for _, u := range urls { m := reGitLabMR.FindStringSubmatch(u) if m == nil { continue } authority := m[1] projectID := m[2] mrnum := m[3] urlStr := "https://" + authority + "/api/v4/projects/" + url.QueryEscape(projectID) + "/merge_requests/" + mrnum 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 } return time.Time{}, nil } func (GitLab) FetchLastUpdated(urls []string) (time.Time, User, error) { for _, u := range urls { m := reGitLabMR.FindStringSubmatch(u) if m == nil { continue } authority := m[1] projectID := m[2] mrnum := m[3] urlStr := "https://" + authority + "/api/v4/projects/" + url.QueryEscape(projectID) + "/merge_requests/" + mrnum var obj struct { ID int `json:"id"` UpdatedAt time.Time `json:"updated_at"` CreatedAt time.Time `json:"created_at"` CreatedBy struct { Username string `json:"username"` WebURL string `json:"web_url"` } `json:"author"` MergedAt time.Time `json:"merged_at"` MergedBy struct { Username string `json:"username"` WebURL string `json:"web_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.Username retUser.URL = obj.CreatedBy.WebURL } if retUser == (User{}) && withinOneSecond(obj.MergedAt, retUpdatedAt) { retUser.Name = obj.MergedBy.Username retUser.URL = obj.MergedBy.WebURL } if retUser == (User{}) { var notes struct { Notes []struct { UpdatedAt time.Time `json:"updated_at"` Author struct { Username string `json:"username"` WebURL string `json:"web_url"` } `json:"author"` } `json:"notes"` } if err := httpGetJSON(fmt.Sprintf("https://%s/%s/noteable/merge_request/%d/notes", authority, projectID, obj.ID), map[string]string{"X-Last-Fetched-At": "0"}, ¬es); err != nil { return time.Time{}, User{}, err } for _, note := range notes.Notes { if withinOneSecond(note.UpdatedAt, retUpdatedAt) { retUser.Name = note.Author.Username retUser.URL = note.Author.WebURL break } } } return retUpdatedAt, retUser, nil } return time.Time{}, User{}, nil }