// Copyright 2020 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package service import ( "context" "errors" "fmt" "net/http" "os" "strconv" "strings" "time" "github.com/google/go-github/v50/github" "golang.org/x/oauth2" ) // GitHub defines a GitHub service type GitHub struct { Milestone string GitTag string Token string Repo string Issues bool client *github.Client } // OwnerRepo splits owner/repo func (gh *GitHub) OwnerRepo() (string, string) { parts := strings.Split(gh.Repo, "/") if len(parts) < 2 { return parts[0], "" } return parts[0], parts[1] } // Generate returns a GitHub changelog func (gh *GitHub) Generate() (string, []Entry, error) { owner, repo := gh.OwnerRepo() ctx := context.Background() gh.initClient(ctx) tagURL := fmt.Sprintf("## [%s](https://github.com/%s/releases/tag/%s) - %s", gh.Milestone, gh.Repo, gh.GitTag, time.Now().Format("2006-01-02")) prs := make([]Entry, 0) milestoneNum, err := gh.milestoneNum(ctx) if err != nil { return "", nil, err } p := 1 perPage := 100 for { result, _, err := gh.client.Issues.ListByRepo(ctx, owner, repo, &github.IssueListByRepoOptions{ Milestone: strconv.Itoa(milestoneNum), State: "closed", ListOptions: github.ListOptions{ Page: p, PerPage: perPage, }, }) if err != nil { return "", nil, err } p++ isPull := !(gh.Issues) for _, pr := range result { if pr.IsPullRequest() == isPull { p := Entry{ Title: CleanTitle(pr.GetTitle()), Index: int64(pr.GetNumber()), } labels := make([]Label, len(pr.Labels)) for idx, lbl := range pr.Labels { labels[idx] = Label{ Name: lbl.GetName(), } } p.Labels = labels prs = append(prs, p) } } if len(result) != perPage { break } } return tagURL, prs, nil } // Contributors returns a list of contributors from GitHub func (gh *GitHub) Contributors() (ContributorList, error) { ctx := context.Background() owner, repo := gh.OwnerRepo() gh.initClient(ctx) contributorsMap := make(map[string]bool) milestoneNum, err := gh.milestoneNum(ctx) if err != nil { return nil, err } p := 1 perPage := 100 for { result, _, err := gh.client.Issues.ListByRepo(ctx, owner, repo, &github.IssueListByRepoOptions{ Milestone: strconv.Itoa(milestoneNum), State: "closed", ListOptions: github.ListOptions{ Page: p, PerPage: perPage, }, }) if err != nil { return nil, err } p++ for _, pr := range result { contributorsMap[pr.GetUser().GetLogin()] = true } if len(result) != perPage { break } } contributors := make(ContributorList, 0, len(contributorsMap)) for contributor := range contributorsMap { contributors = append(contributors, Contributor{ Name: contributor, Profile: fmt.Sprintf("https://github.com/%s", contributor), }) } return contributors, nil } func (gh *GitHub) initClient(ctx context.Context) { token := gh.Token if envToken, ok := os.LookupEnv("CHANGELOG_GITHUB_TOKEN"); ok && token == "" { token = envToken } cl := http.DefaultClient if token != "" { ts := oauth2.StaticTokenSource( &oauth2.Token{AccessToken: token}, ) cl = oauth2.NewClient(ctx, ts) } gh.client = github.NewClient(cl) } func (gh *GitHub) milestoneNum(ctx context.Context) (int, error) { owner, repo := gh.OwnerRepo() p := 1 perPage := 100 for { milestones, _, err := gh.client.Issues.ListMilestones(ctx, owner, repo, &github.MilestoneListOptions{ State: "all", ListOptions: github.ListOptions{ Page: p, PerPage: perPage, }, Sort: "due_on", Direction: "desc", }) if err != nil { return 0, err } p++ for _, milestone := range milestones { if strings.EqualFold(milestone.GetTitle(), gh.Milestone) { return milestone.GetNumber(), nil } } if len(milestones) != perPage { break } } return 0, errors.New("no milestone found") }