From cc5a2cce6d0cbb90d3eabff6a30083348189984a Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Wed, 20 Nov 2024 14:40:05 +0100 Subject: [PATCH 1/8] Reimplement in terms of github v4 API --- go.mod | 2 + go.sum | 4 + pkg/cli/list/list.go | 186 ++++++++++++++------------ pkg/cli/list/list_test.go | 41 ++---- pkg/gitclient/client.go | 273 ++++++++++++++++++++++++++++++++++---- 5 files changed, 362 insertions(+), 144 deletions(-) diff --git a/go.mod b/go.mod index 855f494..633ee5f 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,8 @@ require ( github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect + github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 // indirect + github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect diff --git a/go.sum b/go.sum index cbcef3c..fcd55b4 100644 --- a/go.sum +++ b/go.sum @@ -184,6 +184,10 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 h1:cYCy18SHPKRkvclm+pWm1Lk4YrREb4IOIb/YdFO0p2M= +github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7/go.mod h1:zqMwyHmnN/eDOZOdiTohqIUKUrTFX62PNlu7IJdu0q8= +github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 h1:17JxqqJY66GmZVHkmAsGEkcIu0oCe3AM420QDgGwZx0= +github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466/go.mod h1:9dIRpgIY7hVhoqfe0/FcYp0bpInZaT7dc3BYOprrIUE= github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= diff --git a/pkg/cli/list/list.go b/pkg/cli/list/list.go index 95e1953..67b1d55 100644 --- a/pkg/cli/list/list.go +++ b/pkg/cli/list/list.go @@ -5,7 +5,6 @@ import ( "fmt" "log" "os" - "strconv" "strings" "time" @@ -13,8 +12,8 @@ import ( "github.com/cian911/go-merge/pkg/gitclient" "github.com/cian911/go-merge/pkg/printer" "github.com/cian911/go-merge/pkg/utils" - "github.com/google/go-github/v45/github" "github.com/olekukonko/tablewriter" + "github.com/shurcooL/githubv4" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -24,13 +23,28 @@ var ( repo = "" approveOnly = false configPresent = false - mergeMethod = "merge" ) const ( TokenEnvVar = "GITHUB_TOKEN" ) +func GetMergeMethod() githubv4.PullRequestMergeMethod { + method := viper.GetString("merge-method") + switch method { + case "merge": + return githubv4.PullRequestMergeMethodMerge + case "rebase": + return githubv4.PullRequestMergeMethodRebase + case "squash": + return githubv4.PullRequestMergeMethodSquash + } + if method != "" { + log.Fatalf("Unknown merge method %s. Please use one of the following: merge, rebase, squash", method) + } + return githubv4.PullRequestMergeMethodMerge +} + // TODO: Refactor NewCommnd func NewCommand() (c *cobra.Command) { c = &cobra.Command{ @@ -41,7 +55,7 @@ func NewCommand() (c *cobra.Command) { orgRepo := viper.GetString("repo") configFile := viper.GetString("config") approveOnly = viper.GetBool("approve") - mergeMethod := viper.GetString("merge-method") + mergeMethod := GetMergeMethod() flagToken := viper.GetString("token") skip := viper.GetBool("skip") closePr := viper.GetBool("close") @@ -68,72 +82,62 @@ func NewCommand() (c *cobra.Command) { isEnterprise = true } - ghClient := gitclient.Client(token, ctx, isEnterprise) - pullRequestsArray := []*github.PullRequest{} + ghClient := gitclient.ClientV4(token, ctx, isEnterprise) + + pullRequestsArray := []*gitclient.PullRequest{} table := initTable() - ctx = commitMsg(ctx, viper.GetString("commit-msg")) + ctx = commitMsg(ctx, viper.GetString("commit-msg")) + + var org string + var repositories []string = nil - // If user has passed a config file if configPresent { org = viper.GetString("organization") - - for _, v := range viper.GetStringSlice("repositories") { - pullRequests, _, err := ghClient.PullRequests.List(ctx, org, v, nil) - if err != nil { - log.Fatal(err) - } - - // Use variadic notation to append to array here... - pullRequestsArray = append(pullRequestsArray, pullRequests...) + if len(viper.GetStringSlice("repositories")) > 0 { + repositories = viper.GetStringSlice("repositories") + } else { + repositories = append(repositories, "") } - - if len(pullRequestsArray) == 0 { - fmt.Println("No open pull requests found for configured repositories.") - os.Exit(0) + } else { + parts := strings.Split(orgRepo, "/") + + if len(parts) == 1 { + org = parts[0] + repositories = append(repositories, "") + } else if len(parts) == 2 { + org = parts[0] + repositories = append(repositories, parts[1]) + } else { + log.Fatal("You must pass your repo name like so: organization/repository to continue.") } + } - selectedIds := promptAndFormat(pullRequestsArray, table) - for x, id := range selectedIds { - p := parsePrId(id) - prId, _ := strconv.Atoi(p[0]) - if approveOnly { - gitclient.ApprovePullRequest(ghClient, ctx, org, p[1], prId, skip) - } else if closePr { - gitclient.ClosePullRequest(ghClient, ctx, org, p[1], prId, pullRequestsArray[x]) - } else { - gitclient.MergePullRequest(ghClient, ctx, org, p[1], prId, mergeMethod, skip) - - // delay between merges to allow other active PRs to get synced - time.Sleep(time.Duration(delay) * time.Second) - } - } - } else { - org, repo = parseOrgRepo(orgRepo, configPresent) - // if user has NOT passed a config file - pullRequests, _, err := ghClient.PullRequests.List(ctx, org, repo, nil) + for _, v := range repositories { + pullRequests, err := gitclient.GetPullRequests(ghClient, ctx, org, v) if err != nil { log.Fatal(err) } - if len(pullRequests) == 0 { - fmt.Println("No open pull requests found for given repository.") - os.Exit(0) - } + // Use variadic notation to append to array here... + pullRequestsArray = append(pullRequestsArray, pullRequests...) + } - selectedIds := promptAndFormat(pullRequests, table) - for x, id := range selectedIds { - p := parsePrId(id) - prId, _ := strconv.Atoi(p[0]) - if approveOnly { - gitclient.ApprovePullRequest(ghClient, ctx, org, repo, prId, skip) - } else if closePr { - gitclient.ClosePullRequest(ghClient, ctx, org, repo, prId, pullRequests[x]) - } else { - gitclient.MergePullRequest(ghClient, ctx, org, repo, prId, mergeMethod, skip) - - // delay between merges to allow other active PRs to get synced - time.Sleep(time.Duration(delay) * time.Second) - } + if len(pullRequestsArray) == 0 { + fmt.Println("No open pull requests found for configured repositories.") + os.Exit(0) + } + + selectedIds := promptAndFormat(pullRequestsArray, table) + for _, id := range selectedIds { + + if approveOnly { + gitclient.ApprovePullRequest(ghClient, ctx, id, skip) + } else if closePr { + gitclient.ClosePullRequest(ghClient, ctx, id, skip) + } else { + gitclient.MergePullRequest(ghClient, ctx, id, &mergeMethod, skip) + // delay between merges to allow other active PRs to get synced + time.Sleep(time.Duration(delay) * time.Second) } } }, @@ -142,19 +146,13 @@ func NewCommand() (c *cobra.Command) { return } -func promptAndFormat(pullRequests []*github.PullRequest, table *tablewriter.Table) []string { +func promptAndFormat(pullRequests []*gitclient.PullRequest, table *tablewriter.Table) []githubv4.ID { prIds := []string{} - data := []string{} - repoName := "" for _, pr := range pullRequests { - if pr.Head.Repo == nil { - repoName = "Forked Likely Repository Removed." - } else { - repoName = *pr.Head.Repo.Name - } - prIds = append(prIds, fmt.Sprintf("%d | %s", *pr.Number, repoName)) - data = formatTable(pr, org, repoName) + prIds = append(prIds, fmt.Sprintf("%d | %s/%s", pr.Number, pr.RepositoryOwner, pr.RepositoryName)) + + data := formatTable(pr) if len(data) == 0 { // If there is an issue with the pr, skip continue @@ -165,7 +163,16 @@ func promptAndFormat(pullRequests []*github.PullRequest, table *tablewriter.Tabl prompt, selectedIds := selectPrIds(prIds) survey.AskOne(prompt, &selectedIds) - return selectedIds + indices := []githubv4.ID{} + for _, id := range selectedIds { + for i, prId := range prIds { + if id == prId { + indices = append(indices, pullRequests[i].ID) + break + } + } + } + return indices } func initTable() (table *tablewriter.Table) { @@ -182,16 +189,28 @@ func initTable() (table *tablewriter.Table) { return } -func formatTable(pr *github.PullRequest, org, repo string) (data []string) { - if (pr.Number == nil) || (pr.State == nil) || (pr.Title == nil) || (pr.CreatedAt == nil) { - return +func statusIcon(state string) (icon string) { + switch state { + case "SUCCESS": + icon = "✅" + case "IN_PROGRESS": + icon = "🟠" + case "FAILURE": + icon = "❌" + default: + icon = "" } + + return +} + +func formatTable(pr *gitclient.PullRequest) (data []string) { data = []string{ - fmt.Sprintf("#%s", printer.FormatID(pr.Number)), - printer.FormatString(pr.State), - printer.FormatString(pr.Title), - fmt.Sprintf("%s/%s", org, repo), - printer.FormatTime(pr.CreatedAt), + fmt.Sprintf("#%d", pr.Number), + fmt.Sprintf("%s %s", pr.State, statusIcon(pr.CheckConclusion)), + pr.Title, + fmt.Sprintf("%s/%s", pr.RepositoryOwner, pr.RepositoryName), + printer.FormatTime(&pr.CreatedAt), } return @@ -210,11 +229,6 @@ func parseOrgRepo(repo string, configPresent bool) (org, repository string) { return } -func parsePrId(prId string) []string { - str := strings.Split(strings.ReplaceAll(prId, " ", ""), "|") - return str -} - func getToken(flag, config string) (str string, err error) { if flag != str { return flag, nil @@ -248,9 +262,9 @@ func selectPrIds(prIds []string) (*survey.MultiSelect, []string) { } func commitMsg(ctx context.Context, msg string) context.Context { - if len(msg) != 0 { - return context.WithValue(ctx, "message", msg) - } + if len(msg) != 0 { + return context.WithValue(ctx, "message", msg) + } - return context.WithValue(ctx, "message", gitclient.DefaultApproveMsg()) + return context.WithValue(ctx, "message", gitclient.DefaultApproveMsg()) } diff --git a/pkg/cli/list/list_test.go b/pkg/cli/list/list_test.go index db47b95..78bde09 100644 --- a/pkg/cli/list/list_test.go +++ b/pkg/cli/list/list_test.go @@ -5,8 +5,8 @@ import ( "testing" "time" + "github.com/cian911/go-merge/pkg/gitclient" "github.com/cian911/go-merge/pkg/printer" - "github.com/google/go-github/v45/github" "github.com/olekukonko/tablewriter" "github.com/stretchr/testify/assert" ) @@ -37,48 +37,31 @@ func TestInitTable(t *testing.T) { } func TestFormatTable(t *testing.T) { - var ( - org = "Cian911" - repo = "syncwave" - ) - t.Run("It returns a string array", func(t *testing.T) { number := 1 state := "#open" title := "My Pr" createdAt := time.Now() - pr := &github.PullRequest{ - Number: &number, - State: &state, - Title: &title, - CreatedAt: &createdAt, + pr := &gitclient.PullRequest{ + RepositoryOwner: "Cian911", + RepositoryName: "syncwave", + Number: number, + State: state, + Title: title, + CheckConclusion: "SUCCESS", + CreatedAt: createdAt, } - got := formatTable(pr, org, repo) + got := formatTable(pr) want := []string{ "#1", - "#open", + "#open ✅", "My Pr", "Cian911/syncwave", - printer.FormatTime(pr.CreatedAt), - } - - assert.Equal(t, got, want) - }) - - t.Run("It returns an empty string array when attrs are not present in pr struct", func(t *testing.T) { - state := "#open" - title := "My Pr" - - pr := &github.PullRequest{ - State: &state, - Title: &title, + printer.FormatTime(&pr.CreatedAt), } - got := formatTable(pr, org, repo) - want := []string(nil) - assert.Equal(t, got, want) }) } diff --git a/pkg/gitclient/client.go b/pkg/gitclient/client.go index 02305fd..5cee34d 100644 --- a/pkg/gitclient/client.go +++ b/pkg/gitclient/client.go @@ -4,12 +4,61 @@ import ( "context" "fmt" "log" + "time" "github.com/google/go-github/v45/github" + "github.com/shurcooL/githubv4" + "github.com/spf13/viper" "golang.org/x/oauth2" ) +type PullRequest struct { + RepositoryOwner string + RepositoryName string + Number int + Title string + State string + CreatedAt time.Time + ID githubv4.ID + CheckConclusion string +} + +type Commits struct { + Nodes []struct { + Commit struct { + CheckSuites struct { + Nodes []struct { + App struct { + Name githubv4.String + } + Status githubv4.String + Conclusion githubv4.String + } + } `graphql:"checkSuites(first: $maxCheckSuites)"` + } + } +} + +type Repository struct { + NameWithOwner githubv4.String + Owner struct { + Login githubv4.String + } + Name githubv4.String + PullRequests struct { + Nodes []struct { + Number githubv4.Int + Title githubv4.String + State githubv4.String + Url githubv4.URI + CreatedAt githubv4.DateTime + ID githubv4.ID + Commits Commits `graphql:"commits(last:1)"` + } + } `graphql:"pullRequests(states: [OPEN], first: $maxPullRequests, orderBy: {field: CREATED_AT, direction: DESC})"` +} + func Client(githubToken string, ctx context.Context, isEnterprise bool) (client *github.Client) { tokenSource := oauth2.StaticTokenSource( &oauth2.Token{ @@ -35,46 +84,212 @@ func Client(githubToken string, ctx context.Context, isEnterprise bool) (client return } -func ApprovePullRequest(ghClient *github.Client, ctx context.Context, org, repo string, prId int, skip bool) { - // Create review - commitMsg := ctx.Value("message").(string) - e := "APPROVE" - reviewRequest := &github.PullRequestReviewRequest{ - Body: &commitMsg, - Event: &e, +func ClientV4(githubToken string, ctx context.Context, isEnterprise bool) (client *githubv4.Client) { + tokenSource := oauth2.StaticTokenSource( + &oauth2.Token{ + AccessToken: githubToken, + }, + ) + + httpClient := oauth2.NewClient(context.Background(), tokenSource) + + if isEnterprise { + baseUrl := viper.GetString("enterprise-base-url") + client = githubv4.NewEnterpriseClient(baseUrl, httpClient) + } else { + client = githubv4.NewClient(httpClient) + + } + + return +} + +func ReduceCheckConclusions(commits *Commits) string { + // Check if there are no commits + if len(commits.Nodes) == 0 { + return "" + } + + commit := commits.Nodes[0] + + status := "" + for _, checkSuite := range commit.Commit.CheckSuites.Nodes { + if checkSuite.Status == "QUEUED" { + continue + } + if checkSuite.Status == "IN_PROGRESS" || checkSuite.Status == "PENDING" || checkSuite.Status == "WAITING" { + status = "IN_PROGRESS" + break + } + if checkSuite.Conclusion == "FAILURE" || checkSuite.Status == "CANCELLED" || checkSuite.Status == "ACTION_REQUIRED" { + status = "FAILURE" + break + } + if checkSuite.Conclusion == "SUCCESS" { + status = "SUCCESS" + } + + } + + return status +} + +func GetPullRequests(client *githubv4.Client, ctx context.Context, owner string, repo string) ([]*PullRequest, error) { + vars := map[string]interface{}{ + "maxPullRequests": githubv4.Int(10), + "maxCheckSuites": githubv4.Int(10), + "owner": githubv4.String(owner), + } + + repos := []Repository{} + + if repo == "" { + var q struct { + RepositoryOwner struct { + Repositories struct { + Nodes []Repository + } `graphql:"repositories(first: $maxRepositories, isFork: false, isArchived: false, isLocked: false, orderBy: {field: UPDATED_AT, direction: DESC})"` + } `graphql:"repositoryOwner(login: $owner)"` + } + vars["maxRepositories"] = githubv4.Int(100) + err := client.Query(ctx, &q, vars) + if err != nil { + return nil, err + } + repos = append(repos, q.RepositoryOwner.Repositories.Nodes...) + for _, repo := range repos { + fmt.Printf("Found repo: %v\n", repo.NameWithOwner) + } + } else { + var q struct { + Repository Repository `graphql:"repository(owner: $owner, name: $name)"` + } + vars["name"] = githubv4.String(repo) + err := client.Query(ctx, &q, vars) + if err != nil { + return nil, err + } + repos = append(repos, q.Repository) + } + + pullRequests := []*PullRequest{} + for _, repo := range repos { + for _, pr := range repo.PullRequests.Nodes { + pullRequest := &PullRequest{ + RepositoryOwner: string(repo.Owner.Login), + RepositoryName: string(repo.Name), + Number: int(pr.Number), + Title: string(pr.Title), + State: string(pr.State), + CreatedAt: pr.CreatedAt.Time, + ID: pr.ID, + CheckConclusion: ReduceCheckConclusions(&pr.Commits), + } + + pullRequests = append(pullRequests, pullRequest) + } + } + + return pullRequests, nil +} + +func ApprovePullRequest(ghClient *githubv4.Client, ctx context.Context, prId githubv4.ID, skip bool) { + + commitMessage := ctx.Value("message").(githubv4.String) + event := githubv4.PullRequestReviewEventApprove + + input := githubv4.AddPullRequestReviewInput{ + PullRequestID: prId, + Body: &commitMessage, + Event: &event, } - review, _, err := ghClient.PullRequests.CreateReview(ctx, org, repo, prId, reviewRequest) + + var m struct { + AddPullRequestReview struct { + PullRequest struct { + Number githubv4.Int + Repository struct { + NameWithOwner githubv4.String + } + } + PullRequestReview struct { + State githubv4.String + } + } `graphql:"addPullRequestReview(input: $input)"` + } + + err := ghClient.Mutate(ctx, &m, input, nil) if err != nil && !skip { - log.Fatalf("Could not approve pull request, did you try to approve your on pull request? - %v", err) - } - - if err != nil && skip { - fmt.Printf("Could not approve pull request, skipping.") - } else { - fmt.Printf("PR #%d: %v\n", prId, *review.State) - } + log.Printf("Could not approve pull request %v, did you try to approve your on pull request? - %v\n", prId, err) + } + + review := m.AddPullRequestReview.PullRequestReview + + if err != nil && skip { + fmt.Printf("Could not approve pull request, skipping.") + } else { + fmt.Printf("PR %v: %v\n", prId, review.State) + } } +func MergePullRequest(ghClient *githubv4.Client, ctx context.Context, prId githubv4.ID, mergeMethod *githubv4.PullRequestMergeMethod, skip bool) { -func MergePullRequest(ghClient *github.Client, ctx context.Context, org, repo string, prId int, mergeMethod string, skip bool) { - result, _, err := ghClient.PullRequests.Merge(ctx, org, repo, prId, defaultCommitMsg(), &github.PullRequestOptions{MergeMethod: mergeMethod}) - if err != nil { - log.Printf("Could not merge PR #%d, skipping: %v\n", prId, err) + commitMessage := githubv4.String(defaultCommitMsg()) - return + input := githubv4.MergePullRequestInput{ + PullRequestID: prId, + CommitHeadline: &commitMessage, + MergeMethod: mergeMethod, + } + + var m struct { + MergePullRequest struct { + PullRequest struct { + Merged bool + Number githubv4.Int + Repository struct { + NameWithOwner githubv4.String + } + } + } `graphql:"mergePullRequest(input: $input)"` + } + + err := ghClient.Mutate(ctx, &m, input, nil) + if err != nil { + log.Printf("Could not merge PR %s, skipping: %v\n", prId, err) } - fmt.Sprintf("PR #%d: %v.\n", prId, *result.Message) + pr := m.MergePullRequest.PullRequest + + fmt.Printf("PR %s#%d merged: %v\n", pr.Repository.NameWithOwner, pr.Number, pr.Merged) } -func ClosePullRequest(ghClient *github.Client, ctx context.Context, org, repo string, prId int, prRef *github.PullRequest) { - // Set Closed state for PR - *prRef.State = "closed" - result, _, err := ghClient.PullRequests.Edit(ctx, org, repo, prId, prRef) +func ClosePullRequest(ghClient *githubv4.Client, ctx context.Context, prId githubv4.ID, skip bool) { + + input := githubv4.ClosePullRequestInput{ + PullRequestID: prId, + } + + var m struct { + ClosePullRequest struct { + PullRequest struct { + Closed bool + Number githubv4.Int + Repository struct { + NameWithOwner githubv4.String + } + } + } `graphql:"closePullRequest(input: $input)"` + } + + err := ghClient.Mutate(ctx, &m, input, nil) if err != nil { - log.Printf("Could not close PR #%d - %v", prId, err) - } else { - fmt.Sprintf("PR #%d: %v.\n", prId, *result.State) + log.Printf("Could not close PR %s, skipping: %v\n", prId, err) } + + pr := m.ClosePullRequest.PullRequest + + fmt.Printf("PR %s#%d closed: %v\n", pr.Repository.NameWithOwner, pr.Number, pr.Closed) + } func defaultCommitMsg() string { From 54ada458c1d8e948175f564595ff6562817bf479 Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Wed, 20 Nov 2024 15:33:38 +0100 Subject: [PATCH 2/8] Get status from commit status rollup Vastly simpler than reducing the result of check runs, and also includes statuses set without a run (e.g. from Renovate) --- pkg/cli/list/list.go | 2 +- pkg/cli/list/list_test.go | 2 +- pkg/gitclient/client.go | 49 +++++---------------------------------- 3 files changed, 8 insertions(+), 45 deletions(-) diff --git a/pkg/cli/list/list.go b/pkg/cli/list/list.go index 67b1d55..9ff60a2 100644 --- a/pkg/cli/list/list.go +++ b/pkg/cli/list/list.go @@ -207,7 +207,7 @@ func statusIcon(state string) (icon string) { func formatTable(pr *gitclient.PullRequest) (data []string) { data = []string{ fmt.Sprintf("#%d", pr.Number), - fmt.Sprintf("%s %s", pr.State, statusIcon(pr.CheckConclusion)), + fmt.Sprintf("%s %s", pr.State, statusIcon(pr.StatusRollup)), pr.Title, fmt.Sprintf("%s/%s", pr.RepositoryOwner, pr.RepositoryName), printer.FormatTime(&pr.CreatedAt), diff --git a/pkg/cli/list/list_test.go b/pkg/cli/list/list_test.go index 78bde09..12d740a 100644 --- a/pkg/cli/list/list_test.go +++ b/pkg/cli/list/list_test.go @@ -49,7 +49,7 @@ func TestFormatTable(t *testing.T) { Number: number, State: state, Title: title, - CheckConclusion: "SUCCESS", + StatusRollup: "SUCCESS", CreatedAt: createdAt, } diff --git a/pkg/gitclient/client.go b/pkg/gitclient/client.go index 5cee34d..258d5cf 100644 --- a/pkg/gitclient/client.go +++ b/pkg/gitclient/client.go @@ -21,21 +21,15 @@ type PullRequest struct { State string CreatedAt time.Time ID githubv4.ID - CheckConclusion string + StatusRollup string } type Commits struct { Nodes []struct { Commit struct { - CheckSuites struct { - Nodes []struct { - App struct { - Name githubv4.String - } - Status githubv4.String - Conclusion githubv4.String - } - } `graphql:"checkSuites(first: $maxCheckSuites)"` + StatusCheckRollup struct { + State githubv4.String + } } } } @@ -104,40 +98,9 @@ func ClientV4(githubToken string, ctx context.Context, isEnterprise bool) (clien return } -func ReduceCheckConclusions(commits *Commits) string { - // Check if there are no commits - if len(commits.Nodes) == 0 { - return "" - } - - commit := commits.Nodes[0] - - status := "" - for _, checkSuite := range commit.Commit.CheckSuites.Nodes { - if checkSuite.Status == "QUEUED" { - continue - } - if checkSuite.Status == "IN_PROGRESS" || checkSuite.Status == "PENDING" || checkSuite.Status == "WAITING" { - status = "IN_PROGRESS" - break - } - if checkSuite.Conclusion == "FAILURE" || checkSuite.Status == "CANCELLED" || checkSuite.Status == "ACTION_REQUIRED" { - status = "FAILURE" - break - } - if checkSuite.Conclusion == "SUCCESS" { - status = "SUCCESS" - } - - } - - return status -} - func GetPullRequests(client *githubv4.Client, ctx context.Context, owner string, repo string) ([]*PullRequest, error) { vars := map[string]interface{}{ - "maxPullRequests": githubv4.Int(10), - "maxCheckSuites": githubv4.Int(10), + "maxPullRequests": githubv4.Int(100), "owner": githubv4.String(owner), } @@ -183,7 +146,7 @@ func GetPullRequests(client *githubv4.Client, ctx context.Context, owner string, State: string(pr.State), CreatedAt: pr.CreatedAt.Time, ID: pr.ID, - CheckConclusion: ReduceCheckConclusions(&pr.Commits), + StatusRollup: string(pr.Commits.Nodes[0].Commit.StatusCheckRollup.State), } pullRequests = append(pullRequests, pullRequest) From 5e75d5745b3945571054481644a37c2c48bc2d4b Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Wed, 20 Nov 2024 17:44:16 +0100 Subject: [PATCH 3/8] Do not set commit message on merge Let GitHub generate the commit message for the merge type, which does the right thing ~all of the time. --- README.md | 1 - pkg/cli/gomerge/gomerge.go | 2 -- pkg/cli/list/list.go | 1 - pkg/gitclient/client.go | 12 +++--------- pkg/gitclient/client_test.go | 11 ----------- 5 files changed, 3 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 88b892c..5af4599 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,6 @@ Available Commands: Flags: -a, --approve Pass an optional approve flag as an argument which will only approve and not merge selected repos. --close Pass an optional argument to close a pull request. - --commit-msg string Add a custom message when approving a pull request. -c, --config string Pass an optional config file as an argument with list of repositories. -d, --delay int Set the value of delay, which will determine how long to wait between mergeing pull requests. Default is (6) seconds. (default 6) -e, --enterprise-base-url string For Github Enterprise users, you can pass your enterprise base. Format: http(s)://[hostname]/ diff --git a/pkg/cli/gomerge/gomerge.go b/pkg/cli/gomerge/gomerge.go index 4dfe229..3804df1 100644 --- a/pkg/cli/gomerge/gomerge.go +++ b/pkg/cli/gomerge/gomerge.go @@ -22,7 +22,6 @@ func New() (c *cobra.Command) { c.PersistentFlags().BoolP("close", "", false, "Pass an optional argument to close a pull request.") c.PersistentFlags().IntP("delay", "d", 6, "Set the value of delay, which will determine how long to wait between mergeing pull requests. Default is (6) seconds.") c.PersistentFlags().StringP("enterprise-base-url", "e", "", "For Github Enterprise users, you can pass your enterprise base. Format: http(s)://[hostname]/") - c.PersistentFlags().StringP("commit-msg", "", "", "Add a custom message when approving a pull request.") c.MarkFlagRequired("token") @@ -35,7 +34,6 @@ func New() (c *cobra.Command) { viper.BindPFlag("delay", c.PersistentFlags().Lookup("delay")) viper.BindPFlag("close", c.PersistentFlags().Lookup("close")) viper.BindPFlag("enterprise-base-url", c.PersistentFlags().Lookup("enterprise-base-url")) - viper.BindPFlag("commit-msg", c.PersistentFlags().Lookup("commit-msg")) c.AddCommand(list.NewCommand()) c.AddCommand(version.NewCommand()) diff --git a/pkg/cli/list/list.go b/pkg/cli/list/list.go index 9ff60a2..9622084 100644 --- a/pkg/cli/list/list.go +++ b/pkg/cli/list/list.go @@ -86,7 +86,6 @@ func NewCommand() (c *cobra.Command) { pullRequestsArray := []*gitclient.PullRequest{} table := initTable() - ctx = commitMsg(ctx, viper.GetString("commit-msg")) var org string var repositories []string = nil diff --git a/pkg/gitclient/client.go b/pkg/gitclient/client.go index 258d5cf..a0014c7 100644 --- a/pkg/gitclient/client.go +++ b/pkg/gitclient/client.go @@ -194,14 +194,12 @@ func ApprovePullRequest(ghClient *githubv4.Client, ctx context.Context, prId git fmt.Printf("PR %v: %v\n", prId, review.State) } } -func MergePullRequest(ghClient *githubv4.Client, ctx context.Context, prId githubv4.ID, mergeMethod *githubv4.PullRequestMergeMethod, skip bool) { - commitMessage := githubv4.String(defaultCommitMsg()) +func MergePullRequest(ghClient *githubv4.Client, ctx context.Context, prId githubv4.ID, mergeMethod *githubv4.PullRequestMergeMethod, skip bool) { input := githubv4.MergePullRequestInput{ - PullRequestID: prId, - CommitHeadline: &commitMessage, - MergeMethod: mergeMethod, + PullRequestID: prId, + MergeMethod: mergeMethod, } var m struct { @@ -255,10 +253,6 @@ func ClosePullRequest(ghClient *githubv4.Client, ctx context.Context, prId githu } -func defaultCommitMsg() string { - return "Merged by gomerge CLI." -} - func DefaultApproveMsg() string { return `PR has been approved by [GoMerge](https://github.com/Cian911/gomerge) tool. :rocket:` } diff --git a/pkg/gitclient/client_test.go b/pkg/gitclient/client_test.go index e7ed18c..1c1469c 100644 --- a/pkg/gitclient/client_test.go +++ b/pkg/gitclient/client_test.go @@ -4,17 +4,6 @@ import ( "testing" ) -func TestDefaultCommitMsg(t *testing.T) { - t.Run("It returns a default commit message", func(t *testing.T) { - got := defaultCommitMsg() - want := "Merged by gomerge CLI." - - if got != want { - t.Errorf("got %s, want %s", got, want) - } - }) -} - func TestDefaultApproveMsg(t *testing.T) { t.Run("It returns a default approve message.", func(t *testing.T) { got := DefaultApproveMsg() From 32c092319a73b4cdef225ba56c47287b4737332c Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Wed, 20 Nov 2024 17:49:13 +0100 Subject: [PATCH 4/8] Move sync delay between merges Rather than after each merge. This avoids a confusing delay after the last merge. --- pkg/cli/list/list.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pkg/cli/list/list.go b/pkg/cli/list/list.go index 9622084..c35c534 100644 --- a/pkg/cli/list/list.go +++ b/pkg/cli/list/list.go @@ -127,16 +127,19 @@ func NewCommand() (c *cobra.Command) { } selectedIds := promptAndFormat(pullRequestsArray, table) - for _, id := range selectedIds { + for i, id := range selectedIds { if approveOnly { gitclient.ApprovePullRequest(ghClient, ctx, id, skip) } else if closePr { gitclient.ClosePullRequest(ghClient, ctx, id, skip) } else { - gitclient.MergePullRequest(ghClient, ctx, id, &mergeMethod, skip) // delay between merges to allow other active PRs to get synced - time.Sleep(time.Duration(delay) * time.Second) + if i > 0 { + time.Sleep(time.Duration(delay) * time.Second) + } + gitclient.MergePullRequest(ghClient, ctx, id, &mergeMethod, skip) + } } }, From e95cc0f7d6d377a9c3b4d45346eb90dbbcb07bc1 Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Thu, 21 Nov 2024 11:50:46 +0100 Subject: [PATCH 5/8] Optionally filter PRs by label --- pkg/cli/gomerge/gomerge.go | 2 + pkg/cli/list/list.go | 16 +++- pkg/gitclient/client.go | 149 ++++++++++++++++++++++++------------- 3 files changed, 112 insertions(+), 55 deletions(-) diff --git a/pkg/cli/gomerge/gomerge.go b/pkg/cli/gomerge/gomerge.go index 3804df1..1b8e123 100644 --- a/pkg/cli/gomerge/gomerge.go +++ b/pkg/cli/gomerge/gomerge.go @@ -14,6 +14,7 @@ func New() (c *cobra.Command) { } c.PersistentFlags().StringP("repo", "r", "", "Pass name of repository as argument (organization/repo).") + c.PersistentFlags().StringArrayP("label", "l", []string{}, "Pass an optional list of labels to filter pull requests.") c.PersistentFlags().StringP("token", "t", "", "Pass your github personal access token (PAT).") c.PersistentFlags().StringP("config", "c", "", "Pass an optional config file as an argument with list of repositories.") c.PersistentFlags().BoolP("approve", "a", false, "Pass an optional approve flag as an argument which will only approve and not merge selected repos.") @@ -26,6 +27,7 @@ func New() (c *cobra.Command) { c.MarkFlagRequired("token") viper.BindPFlag("repo", c.PersistentFlags().Lookup("repo")) + viper.BindPFlag("label", c.PersistentFlags().Lookup("label")) viper.BindPFlag("token", c.PersistentFlags().Lookup("token")) viper.BindPFlag("config", c.PersistentFlags().Lookup("config")) viper.BindPFlag("approve", c.PersistentFlags().Lookup("approve")) diff --git a/pkg/cli/list/list.go b/pkg/cli/list/list.go index c35c534..6278875 100644 --- a/pkg/cli/list/list.go +++ b/pkg/cli/list/list.go @@ -29,7 +29,7 @@ const ( TokenEnvVar = "GITHUB_TOKEN" ) -func GetMergeMethod() githubv4.PullRequestMergeMethod { +func getMergeMethod() githubv4.PullRequestMergeMethod { method := viper.GetString("merge-method") switch method { case "merge": @@ -45,6 +45,15 @@ func GetMergeMethod() githubv4.PullRequestMergeMethod { return githubv4.PullRequestMergeMethodMerge } +func getLabels() (labels []githubv4.String) { + raw_labels := viper.GetStringSlice("label") + labels = make([]githubv4.String, len(raw_labels)) + for i, label := range raw_labels { + labels[i] = githubv4.String(label) + } + return +} + // TODO: Refactor NewCommnd func NewCommand() (c *cobra.Command) { c = &cobra.Command{ @@ -53,9 +62,10 @@ func NewCommand() (c *cobra.Command) { Run: func(cmd *cobra.Command, args []string) { ctx := cmd.Context() orgRepo := viper.GetString("repo") + labels := getLabels() configFile := viper.GetString("config") approveOnly = viper.GetBool("approve") - mergeMethod := GetMergeMethod() + mergeMethod := getMergeMethod() flagToken := viper.GetString("token") skip := viper.GetBool("skip") closePr := viper.GetBool("close") @@ -112,7 +122,7 @@ func NewCommand() (c *cobra.Command) { } for _, v := range repositories { - pullRequests, err := gitclient.GetPullRequests(ghClient, ctx, org, v) + pullRequests, err := gitclient.GetPullRequests(ghClient, ctx, org, v, &labels) if err != nil { log.Fatal(err) } diff --git a/pkg/gitclient/client.go b/pkg/gitclient/client.go index a0014c7..74530de 100644 --- a/pkg/gitclient/client.go +++ b/pkg/gitclient/client.go @@ -24,35 +24,6 @@ type PullRequest struct { StatusRollup string } -type Commits struct { - Nodes []struct { - Commit struct { - StatusCheckRollup struct { - State githubv4.String - } - } - } -} - -type Repository struct { - NameWithOwner githubv4.String - Owner struct { - Login githubv4.String - } - Name githubv4.String - PullRequests struct { - Nodes []struct { - Number githubv4.Int - Title githubv4.String - State githubv4.String - Url githubv4.URI - CreatedAt githubv4.DateTime - ID githubv4.ID - Commits Commits `graphql:"commits(last:1)"` - } - } `graphql:"pullRequests(states: [OPEN], first: $maxPullRequests, orderBy: {field: CREATED_AT, direction: DESC})"` -} - func Client(githubToken string, ctx context.Context, isEnterprise bool) (client *github.Client) { tokenSource := oauth2.StaticTokenSource( &oauth2.Token{ @@ -98,41 +69,115 @@ func ClientV4(githubToken string, ctx context.Context, isEnterprise bool) (clien return } -func GetPullRequests(client *githubv4.Client, ctx context.Context, owner string, repo string) ([]*PullRequest, error) { +type commits struct { + Nodes []struct { + Commit struct { + StatusCheckRollup struct { + State githubv4.String + } + } + } +} + +type pullRequests struct { + Nodes []struct { + Number githubv4.Int + Title githubv4.String + State githubv4.String + Url githubv4.URI + CreatedAt githubv4.DateTime + ID githubv4.ID + Commits commits `graphql:"commits(last:1)"` + } +} + +// NB: githubv4 uses struct tags to define the GraphQL query. To omit the labels +// argument to pullRequests(), we need to define a compeltely new type. Luckily +// these are convertible to each other as of go 1.8. +type repository struct { + NameWithOwner githubv4.String + Owner struct { + Login githubv4.String + } + Name githubv4.String + PullRequests pullRequests `graphql:"pullRequests(states: [OPEN], first: $maxPullRequests, orderBy: {field: CREATED_AT, direction: DESC})"` +} + +type repositoryWithPRLabels struct { + NameWithOwner githubv4.String + Owner struct { + Login githubv4.String + } + Name githubv4.String + PullRequests pullRequests `graphql:"pullRequests(states: [OPEN], labels: $labels, first: $maxPullRequests, orderBy: {field: CREATED_AT, direction: DESC})"` +} + +func GetPullRequests(client *githubv4.Client, ctx context.Context, owner string, repo string, labels *[]githubv4.String) ([]*PullRequest, error) { + vars := map[string]interface{}{ "maxPullRequests": githubv4.Int(100), "owner": githubv4.String(owner), } + if len(*labels) > 0 { + vars["labels"] = labels + } - repos := []Repository{} + repos := []repository{} if repo == "" { - var q struct { - RepositoryOwner struct { - Repositories struct { - Nodes []Repository - } `graphql:"repositories(first: $maxRepositories, isFork: false, isArchived: false, isLocked: false, orderBy: {field: UPDATED_AT, direction: DESC})"` - } `graphql:"repositoryOwner(login: $owner)"` - } vars["maxRepositories"] = githubv4.Int(100) - err := client.Query(ctx, &q, vars) - if err != nil { - return nil, err - } - repos = append(repos, q.RepositoryOwner.Repositories.Nodes...) - for _, repo := range repos { - fmt.Printf("Found repo: %v\n", repo.NameWithOwner) + if len(*labels) > 0 { + var q struct { + RepositoryOwner struct { + Repositories struct { + Nodes []repositoryWithPRLabels + } `graphql:"repositories(first: $maxRepositories, isFork: false, isArchived: false, isLocked: false, orderBy: {field: UPDATED_AT, direction: DESC})"` + } `graphql:"repositoryOwner(login: $owner)"` + } + err := client.Query(ctx, &q, vars) + if err != nil { + return nil, err + } + for _, repo := range q.RepositoryOwner.Repositories.Nodes { + repos = append(repos, repository(repo)) + } + } else { + var q struct { + RepositoryOwner struct { + Repositories struct { + Nodes []repository + } `graphql:"repositories(first: $maxRepositories, isFork: false, isArchived: false, isLocked: false, orderBy: {field: UPDATED_AT, direction: DESC})"` + } `graphql:"repositoryOwner(login: $owner)"` + } + err := client.Query(ctx, &q, vars) + if err != nil { + return nil, err + } + for _, repo := range q.RepositoryOwner.Repositories.Nodes { + repos = append(repos, repository(repo)) + } } } else { - var q struct { - Repository Repository `graphql:"repository(owner: $owner, name: $name)"` - } vars["name"] = githubv4.String(repo) - err := client.Query(ctx, &q, vars) - if err != nil { - return nil, err + if len(*labels) > 0 { + var q struct { + Repository repositoryWithPRLabels `graphql:"repository(owner: $owner, name: $name)"` + } + err := client.Query(ctx, &q, vars) + if err != nil { + return nil, err + } + repos = append(repos, repository(q.Repository)) + } else { + var q struct { + Repository repository `graphql:"repository(owner: $owner, name: $name)"` + } + err := client.Query(ctx, &q, vars) + if err != nil { + return nil, err + } + repos = append(repos, q.Repository) } - repos = append(repos, q.Repository) } pullRequests := []*PullRequest{} From 6aa22b76cd6ea89e07159303f94fc856c49e6858 Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Thu, 21 Nov 2024 14:24:21 +0100 Subject: [PATCH 6/8] Attempt to add a review before merging --- pkg/cli/list/list.go | 23 +++++++++------- pkg/gitclient/client.go | 60 ++++++++++++++++++++++++++--------------- 2 files changed, 51 insertions(+), 32 deletions(-) diff --git a/pkg/cli/list/list.go b/pkg/cli/list/list.go index 6278875..8aa38e7 100644 --- a/pkg/cli/list/list.go +++ b/pkg/cli/list/list.go @@ -137,18 +137,21 @@ func NewCommand() (c *cobra.Command) { } selectedIds := promptAndFormat(pullRequestsArray, table) - for i, id := range selectedIds { + for i, pr := range selectedIds { if approveOnly { - gitclient.ApprovePullRequest(ghClient, ctx, id, skip) + gitclient.ApprovePullRequest(ghClient, ctx, pr, skip) } else if closePr { - gitclient.ClosePullRequest(ghClient, ctx, id, skip) + gitclient.ClosePullRequest(ghClient, ctx, pr, skip) } else { // delay between merges to allow other active PRs to get synced if i > 0 { time.Sleep(time.Duration(delay) * time.Second) } - gitclient.MergePullRequest(ghClient, ctx, id, &mergeMethod, skip) + if pr.NeedsReview { + gitclient.ApprovePullRequest(ghClient, ctx, pr, skip) + } + gitclient.MergePullRequest(ghClient, ctx, pr, &mergeMethod, skip) } } @@ -158,7 +161,7 @@ func NewCommand() (c *cobra.Command) { return } -func promptAndFormat(pullRequests []*gitclient.PullRequest, table *tablewriter.Table) []githubv4.ID { +func promptAndFormat(pullRequests []*gitclient.PullRequest, table *tablewriter.Table) (selectedPullRequests []*gitclient.PullRequest) { prIds := []string{} for _, pr := range pullRequests { @@ -175,16 +178,16 @@ func promptAndFormat(pullRequests []*gitclient.PullRequest, table *tablewriter.T prompt, selectedIds := selectPrIds(prIds) survey.AskOne(prompt, &selectedIds) - indices := []githubv4.ID{} - for _, id := range selectedIds { - for i, prId := range prIds { + selectedPullRequests = make([]*gitclient.PullRequest, len(selectedIds)) + for idIndex, id := range selectedIds { + for prIndex, prId := range prIds { if id == prId { - indices = append(indices, pullRequests[i].ID) + selectedPullRequests[idIndex] = pullRequests[prIndex] break } } } - return indices + return } func initTable() (table *tablewriter.Table) { diff --git a/pkg/gitclient/client.go b/pkg/gitclient/client.go index 74530de..ac87d62 100644 --- a/pkg/gitclient/client.go +++ b/pkg/gitclient/client.go @@ -22,6 +22,7 @@ type PullRequest struct { CreatedAt time.Time ID githubv4.ID StatusRollup string + NeedsReview bool } func Client(githubToken string, ctx context.Context, isEnterprise bool) (client *github.Client) { @@ -88,6 +89,18 @@ type pullRequests struct { CreatedAt githubv4.DateTime ID githubv4.ID Commits commits `graphql:"commits(last:1)"` + // NB: this only works for classic branch protection rules. Repository + // rulesets don't appear to be visible in the API + BaseRef struct { + BranchProtectionRule struct { + RequiredApprovingReviewCount githubv4.Int + } + } + Reviews struct { + Nodes []struct { + AuthorCanPushToRepository githubv4.Boolean + } + } `graphql:"reviews(first: 100, states: [APPROVED])"` } } @@ -183,6 +196,12 @@ func GetPullRequests(client *githubv4.Client, ctx context.Context, owner string, pullRequests := []*PullRequest{} for _, repo := range repos { for _, pr := range repo.PullRequests.Nodes { + reviews := 0 + for _, review := range pr.Reviews.Nodes { + if review.AuthorCanPushToRepository { + reviews++ + } + } pullRequest := &PullRequest{ RepositoryOwner: string(repo.Owner.Login), RepositoryName: string(repo.Name), @@ -192,6 +211,7 @@ func GetPullRequests(client *githubv4.Client, ctx context.Context, owner string, CreatedAt: pr.CreatedAt.Time, ID: pr.ID, StatusRollup: string(pr.Commits.Nodes[0].Commit.StatusCheckRollup.State), + NeedsReview: reviews < int(pr.BaseRef.BranchProtectionRule.RequiredApprovingReviewCount), } pullRequests = append(pullRequests, pullRequest) @@ -201,34 +221,28 @@ func GetPullRequests(client *githubv4.Client, ctx context.Context, owner string, return pullRequests, nil } -func ApprovePullRequest(ghClient *githubv4.Client, ctx context.Context, prId githubv4.ID, skip bool) { +func ApprovePullRequest(ghClient *githubv4.Client, ctx context.Context, pr *PullRequest, skip bool) { - commitMessage := ctx.Value("message").(githubv4.String) + commitMessage := githubv4.String(DefaultApproveMsg()) event := githubv4.PullRequestReviewEventApprove input := githubv4.AddPullRequestReviewInput{ - PullRequestID: prId, + PullRequestID: pr.ID, Body: &commitMessage, Event: &event, } var m struct { AddPullRequestReview struct { - PullRequest struct { - Number githubv4.Int - Repository struct { - NameWithOwner githubv4.String - } - } PullRequestReview struct { - State githubv4.String + State githubv4.PullRequestReviewState } } `graphql:"addPullRequestReview(input: $input)"` } err := ghClient.Mutate(ctx, &m, input, nil) if err != nil && !skip { - log.Printf("Could not approve pull request %v, did you try to approve your on pull request? - %v\n", prId, err) + log.Printf("Could not approve pull request %s/%s#%d - %v\n", pr.RepositoryOwner, pr.RepositoryName, pr.Number, err) } review := m.AddPullRequestReview.PullRequestReview @@ -236,14 +250,14 @@ func ApprovePullRequest(ghClient *githubv4.Client, ctx context.Context, prId git if err != nil && skip { fmt.Printf("Could not approve pull request, skipping.") } else { - fmt.Printf("PR %v: %v\n", prId, review.State) + fmt.Printf("%s/%s#%d: %v\n", pr.RepositoryOwner, pr.RepositoryName, pr.Number, review.State) } } -func MergePullRequest(ghClient *githubv4.Client, ctx context.Context, prId githubv4.ID, mergeMethod *githubv4.PullRequestMergeMethod, skip bool) { +func MergePullRequest(ghClient *githubv4.Client, ctx context.Context, pr *PullRequest, mergeMethod *githubv4.PullRequestMergeMethod, skip bool) { input := githubv4.MergePullRequestInput{ - PullRequestID: prId, + PullRequestID: pr.ID, MergeMethod: mergeMethod, } @@ -251,6 +265,7 @@ func MergePullRequest(ghClient *githubv4.Client, ctx context.Context, prId githu MergePullRequest struct { PullRequest struct { Merged bool + State githubv4.PullRequestState Number githubv4.Int Repository struct { NameWithOwner githubv4.String @@ -261,24 +276,25 @@ func MergePullRequest(ghClient *githubv4.Client, ctx context.Context, prId githu err := ghClient.Mutate(ctx, &m, input, nil) if err != nil { - log.Printf("Could not merge PR %s, skipping: %v\n", prId, err) + log.Printf("Could not merge %s/%s#%d - %v\n", pr.RepositoryOwner, pr.RepositoryName, pr.Number, err) } - pr := m.MergePullRequest.PullRequest + prOut := m.MergePullRequest.PullRequest + fmt.Printf("%s/%s#%d merged: %v\n", pr.RepositoryOwner, pr.RepositoryName, pr.Number, prOut.State) - fmt.Printf("PR %s#%d merged: %v\n", pr.Repository.NameWithOwner, pr.Number, pr.Merged) } -func ClosePullRequest(ghClient *githubv4.Client, ctx context.Context, prId githubv4.ID, skip bool) { +func ClosePullRequest(ghClient *githubv4.Client, ctx context.Context, pr *PullRequest, skip bool) { input := githubv4.ClosePullRequestInput{ - PullRequestID: prId, + PullRequestID: pr.ID, } var m struct { ClosePullRequest struct { PullRequest struct { Closed bool + State githubv4.PullRequestState Number githubv4.Int Repository struct { NameWithOwner githubv4.String @@ -289,12 +305,12 @@ func ClosePullRequest(ghClient *githubv4.Client, ctx context.Context, prId githu err := ghClient.Mutate(ctx, &m, input, nil) if err != nil { - log.Printf("Could not close PR %s, skipping: %v\n", prId, err) + log.Printf("Could not close %s/%s#%d - %v\n", pr.RepositoryOwner, pr.RepositoryName, pr.Number, err) } - pr := m.ClosePullRequest.PullRequest + prOut := m.ClosePullRequest.PullRequest - fmt.Printf("PR %s#%d closed: %v\n", pr.Repository.NameWithOwner, pr.Number, pr.Closed) + fmt.Printf("%s/%s#%d merged: %v\n", pr.RepositoryOwner, pr.RepositoryName, pr.Number, prOut.State) } From cd5fce3faa23336703796241f68fe7bb97c402a5 Mon Sep 17 00:00:00 2001 From: Jakob van Santen Date: Thu, 21 Nov 2024 16:36:46 +0100 Subject: [PATCH 7/8] Remove last vestiges of v3 client --- go.mod | 5 +---- go.sum | 6 ------ pkg/gitclient/client.go | 26 -------------------------- 3 files changed, 1 insertion(+), 36 deletions(-) diff --git a/go.mod b/go.mod index 633ee5f..6714def 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.19 require ( github.com/AlecAivazis/survey/v2 v2.3.6 - github.com/google/go-github/v45 v45.2.0 github.com/olekukonko/tablewriter v0.0.5 + github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 github.com/spf13/cobra v1.6.1 github.com/spf13/viper v1.14.0 github.com/stretchr/testify v1.9.0 @@ -16,7 +16,6 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/golang/protobuf v1.5.3 // indirect - github.com/google/go-querystring v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect @@ -30,14 +29,12 @@ require ( github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/shurcooL/githubv4 v0.0.0-20240727222349-48295856cce7 // indirect github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.4.1 // indirect - golang.org/x/crypto v0.13.0 // indirect golang.org/x/net v0.15.0 // indirect golang.org/x/sys v0.12.0 // indirect golang.org/x/term v0.12.0 // indirect diff --git a/go.sum b/go.sum index fcd55b4..4f73b9a 100644 --- a/go.sum +++ b/go.sum @@ -109,10 +109,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI= -github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -230,8 +226,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= -golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= diff --git a/pkg/gitclient/client.go b/pkg/gitclient/client.go index ac87d62..1377088 100644 --- a/pkg/gitclient/client.go +++ b/pkg/gitclient/client.go @@ -6,7 +6,6 @@ import ( "log" "time" - "github.com/google/go-github/v45/github" "github.com/shurcooL/githubv4" "github.com/spf13/viper" @@ -25,31 +24,6 @@ type PullRequest struct { NeedsReview bool } -func Client(githubToken string, ctx context.Context, isEnterprise bool) (client *github.Client) { - tokenSource := oauth2.StaticTokenSource( - &oauth2.Token{ - AccessToken: githubToken, - }, - ) - - tokenContext := oauth2.NewClient(ctx, tokenSource) - - if isEnterprise { - baseUrl := viper.GetString("enterprise-base-url") - c, err := github.NewEnterpriseClient(baseUrl, baseUrl, tokenContext) - - if err != nil { - log.Fatalf("Could not auth enterprise client: %v", err) - } - - client = c - } else { - client = github.NewClient(tokenContext) - } - - return -} - func ClientV4(githubToken string, ctx context.Context, isEnterprise bool) (client *githubv4.Client) { tokenSource := oauth2.StaticTokenSource( &oauth2.Token{ From e83ab641fbbb15ffdd6dcffd3f62b95ad1d87043 Mon Sep 17 00:00:00 2001 From: Cian Gallagher Date: Sun, 29 Dec 2024 17:41:29 +0000 Subject: [PATCH 8/8] chore: add foreground color diff between ci state. fix formatting + other fixes --- .gitignore | 1 + pkg/cli/gomerge/gomerge.go | 32 +++++--- pkg/cli/list/list.go | 122 ++++++++++++++++--------------- pkg/cli/list/list_test.go | 146 +++++++++++++++++++------------------ pkg/printer/printer.go | 29 ++++++-- pkg/utils/utils_test.go | 8 +- 6 files changed, 190 insertions(+), 148 deletions(-) diff --git a/.gitignore b/.gitignore index 2b267ab..d94d6cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.swp +debug.log bin/* dist/ diff --git a/pkg/cli/gomerge/gomerge.go b/pkg/cli/gomerge/gomerge.go index 1b8e123..b6b3251 100644 --- a/pkg/cli/gomerge/gomerge.go +++ b/pkg/cli/gomerge/gomerge.go @@ -1,10 +1,11 @@ package gomerge import ( - "github.com/cian911/go-merge/pkg/cli/list" - "github.com/cian911/go-merge/pkg/cli/version" "github.com/spf13/cobra" "github.com/spf13/viper" + + "github.com/cian911/go-merge/pkg/cli/list" + "github.com/cian911/go-merge/pkg/cli/version" ) func New() (c *cobra.Command) { @@ -13,16 +14,25 @@ func New() (c *cobra.Command) { Short: "Gomerge makes it simple to merge an open pull request from your terminal.", } - c.PersistentFlags().StringP("repo", "r", "", "Pass name of repository as argument (organization/repo).") - c.PersistentFlags().StringArrayP("label", "l", []string{}, "Pass an optional list of labels to filter pull requests.") + c.PersistentFlags(). + StringP("repo", "r", "", "Pass name of repository as argument (organization/repo).") + c.PersistentFlags(). + StringArrayP("label", "l", []string{}, "Pass an optional list of labels to filter pull requests. (label1,label2,label3)") c.PersistentFlags().StringP("token", "t", "", "Pass your github personal access token (PAT).") - c.PersistentFlags().StringP("config", "c", "", "Pass an optional config file as an argument with list of repositories.") - c.PersistentFlags().BoolP("approve", "a", false, "Pass an optional approve flag as an argument which will only approve and not merge selected repos.") - c.PersistentFlags().StringP("merge-method", "m", "", "Pass an optional merge method for the pull request (merge [default], squash, rebase).") - c.PersistentFlags().BoolP("skip", "s", false, "Pass an optional flag to skip a pull request and continue if one or more are not mergable.") - c.PersistentFlags().BoolP("close", "", false, "Pass an optional argument to close a pull request.") - c.PersistentFlags().IntP("delay", "d", 6, "Set the value of delay, which will determine how long to wait between mergeing pull requests. Default is (6) seconds.") - c.PersistentFlags().StringP("enterprise-base-url", "e", "", "For Github Enterprise users, you can pass your enterprise base. Format: http(s)://[hostname]/") + c.PersistentFlags(). + StringP("config", "c", "", "Pass an optional config file as an argument with list of repositories.") + c.PersistentFlags(). + BoolP("approve", "a", false, "Pass an optional approve flag as an argument which will only approve and not merge selected repos.") + c.PersistentFlags(). + StringP("merge-method", "m", "", "Pass an optional merge method for the pull request (merge [default], squash, rebase).") + c.PersistentFlags(). + BoolP("skip", "s", false, "Pass an optional flag to skip a pull request and continue if one or more are not mergable.") + c.PersistentFlags(). + BoolP("close", "", false, "Pass an optional argument to close a pull request.") + c.PersistentFlags(). + IntP("delay", "d", 6, "Set the value of delay, which will determine how long to wait between mergeing pull requests. Default is (6) seconds.") + c.PersistentFlags(). + StringP("enterprise-base-url", "e", "", "For Github Enterprise users, you can pass your enterprise base. Format: http(s)://[hostname]/") c.MarkFlagRequired("token") diff --git a/pkg/cli/list/list.go b/pkg/cli/list/list.go index 8aa38e7..1300412 100644 --- a/pkg/cli/list/list.go +++ b/pkg/cli/list/list.go @@ -1,7 +1,6 @@ package list import ( - "context" "fmt" "log" "os" @@ -9,13 +8,14 @@ import ( "time" "github.com/AlecAivazis/survey/v2" - "github.com/cian911/go-merge/pkg/gitclient" - "github.com/cian911/go-merge/pkg/printer" - "github.com/cian911/go-merge/pkg/utils" "github.com/olekukonko/tablewriter" "github.com/shurcooL/githubv4" "github.com/spf13/cobra" "github.com/spf13/viper" + + "github.com/cian911/go-merge/pkg/gitclient" + "github.com/cian911/go-merge/pkg/printer" + "github.com/cian911/go-merge/pkg/utils" ) var ( @@ -26,35 +26,12 @@ var ( ) const ( - TokenEnvVar = "GITHUB_TOKEN" + TokenEnvVar = "GITHUB_TOKEN" + STATUS_SUCCESS = 0 + STATUS_WAITING = 1 + STATUS_FAILED = 2 ) -func getMergeMethod() githubv4.PullRequestMergeMethod { - method := viper.GetString("merge-method") - switch method { - case "merge": - return githubv4.PullRequestMergeMethodMerge - case "rebase": - return githubv4.PullRequestMergeMethodRebase - case "squash": - return githubv4.PullRequestMergeMethodSquash - } - if method != "" { - log.Fatalf("Unknown merge method %s. Please use one of the following: merge, rebase, squash", method) - } - return githubv4.PullRequestMergeMethodMerge -} - -func getLabels() (labels []githubv4.String) { - raw_labels := viper.GetStringSlice("label") - labels = make([]githubv4.String, len(raw_labels)) - for i, label := range raw_labels { - labels[i] = githubv4.String(label) - } - return -} - -// TODO: Refactor NewCommnd func NewCommand() (c *cobra.Command) { c = &cobra.Command{ Use: "list", @@ -78,7 +55,9 @@ func NewCommand() (c *cobra.Command) { } if !configPresent && len(orgRepo) <= 0 { - log.Fatal("You must pass either a config file or repository as argument to continue.") + log.Fatal( + "You must pass either a config file or repository as argument to continue.", + ) } configToken := viper.GetString("token") @@ -138,7 +117,6 @@ func NewCommand() (c *cobra.Command) { selectedIds := promptAndFormat(pullRequestsArray, table) for i, pr := range selectedIds { - if approveOnly { gitclient.ApprovePullRequest(ghClient, ctx, pr, skip) } else if closePr { @@ -161,18 +139,31 @@ func NewCommand() (c *cobra.Command) { return } -func promptAndFormat(pullRequests []*gitclient.PullRequest, table *tablewriter.Table) (selectedPullRequests []*gitclient.PullRequest) { +func promptAndFormat( + pullRequests []*gitclient.PullRequest, + table *tablewriter.Table, +) (selectedPullRequests []*gitclient.PullRequest) { prIds := []string{} for _, pr := range pullRequests { - prIds = append(prIds, fmt.Sprintf("%d | %s/%s", pr.Number, pr.RepositoryOwner, pr.RepositoryName)) + prIds = append( + prIds, + fmt.Sprintf("%d | %s/%s", pr.Number, pr.RepositoryOwner, pr.RepositoryName), + ) - data := formatTable(pr) + data, status := formatTable(pr) if len(data) == 0 { // If there is an issue with the pr, skip continue } - table = printer.SuccessStyle(table, data) + switch status { + case STATUS_SUCCESS: + table = printer.SuccessStyle(table, data) + case STATUS_WAITING: + table = printer.WaitingStyle(table, data) + case STATUS_FAILED: + table = printer.ErrorStyle(table, data) + } } table.Render() @@ -204,14 +195,17 @@ func initTable() (table *tablewriter.Table) { return } -func statusIcon(state string) (icon string) { +func statusIcon(state string) (icon string, status int) { switch state { case "SUCCESS": - icon = "✅" + icon = "" + status = STATUS_SUCCESS case "IN_PROGRESS": - icon = "🟠" + icon = "" + status = STATUS_WAITING case "FAILURE": - icon = "❌" + icon = "󰅙" + status = STATUS_FAILED default: icon = "" } @@ -219,10 +213,11 @@ func statusIcon(state string) (icon string) { return } -func formatTable(pr *gitclient.PullRequest) (data []string) { +func formatTable(pr *gitclient.PullRequest) (data []string, status int) { + icon, status := statusIcon(pr.StatusRollup) data = []string{ fmt.Sprintf("#%d", pr.Number), - fmt.Sprintf("%s %s", pr.State, statusIcon(pr.StatusRollup)), + fmt.Sprintf("%s %s", pr.State, icon), pr.Title, fmt.Sprintf("%s/%s", pr.RepositoryOwner, pr.RepositoryName), printer.FormatTime(&pr.CreatedAt), @@ -231,19 +226,6 @@ func formatTable(pr *gitclient.PullRequest) (data []string) { return } -func parseOrgRepo(repo string, configPresent bool) (org, repository string) { - str := strings.Split(repo, "/") - - if len(str) <= 1 { - log.Fatal("You must pass your repo name like so: organization/repository to continue.") - } - - org = str[0] - repository = str[1] - - return -} - func getToken(flag, config string) (str string, err error) { if flag != str { return flag, nil @@ -276,10 +258,30 @@ func selectPrIds(prIds []string) (*survey.MultiSelect, []string) { return prompt, selectedIds } -func commitMsg(ctx context.Context, msg string) context.Context { - if len(msg) != 0 { - return context.WithValue(ctx, "message", msg) +func getMergeMethod() githubv4.PullRequestMergeMethod { + method := viper.GetString("merge-method") + switch method { + case "merge": + return githubv4.PullRequestMergeMethodMerge + case "rebase": + return githubv4.PullRequestMergeMethodRebase + case "squash": + return githubv4.PullRequestMergeMethodSquash } + if method != "" { + log.Fatalf( + "Unknown merge method %s. Please use one of the following: merge, rebase, squash", + method, + ) + } + return githubv4.PullRequestMergeMethodMerge +} - return context.WithValue(ctx, "message", gitclient.DefaultApproveMsg()) +func getLabels() (labels []githubv4.String) { + raw_labels := viper.GetStringSlice("label") + labels = make([]githubv4.String, len(raw_labels)) + for i, label := range raw_labels { + labels[i] = githubv4.String(label) + } + return } diff --git a/pkg/cli/list/list_test.go b/pkg/cli/list/list_test.go index 12d740a..b8d1bf6 100644 --- a/pkg/cli/list/list_test.go +++ b/pkg/cli/list/list_test.go @@ -5,27 +5,12 @@ import ( "testing" "time" - "github.com/cian911/go-merge/pkg/gitclient" - "github.com/cian911/go-merge/pkg/printer" "github.com/olekukonko/tablewriter" "github.com/stretchr/testify/assert" -) - -func TestParseOrgRepo(t *testing.T) { - t.Run("It returns a valid tuple when no config is present", func(t *testing.T) { - repo := "Cian911/syncwave" - configPresent := false - want1 := "Cian911" - want2 := "syncwave" - - got1, got2 := parseOrgRepo(repo, configPresent) - - if got1 != want1 || got2 != want2 { - t.Errorf("got1: %s, got2: %s, want1: %s, want2: %s", got1, got2, want1, want2) - } - }) -} + "github.com/cian911/go-merge/pkg/gitclient" + "github.com/cian911/go-merge/pkg/printer" +) func TestInitTable(t *testing.T) { t.Run("It returns a tablewriter pointer", func(t *testing.T) { @@ -53,10 +38,10 @@ func TestFormatTable(t *testing.T) { CreatedAt: createdAt, } - got := formatTable(pr) + got, _ := formatTable(pr) want := []string{ "#1", - "#open ✅", + "#open ", "My Pr", "Cian911/syncwave", printer.FormatTime(&pr.CreatedAt), @@ -73,62 +58,83 @@ func TestListGetToken(t *testing.T) { envVar = "env@token" ) - t.Run("When a given token is set by flag, it should return token as the flag value", func(t *testing.T) { - got, err := getToken(flag, "") - want := flag - assert.Nil(t, err) - assert.Equal(t, want, got) - }) + t.Run( + "When a given token is set by flag, it should return token as the flag value", + func(t *testing.T) { + got, err := getToken(flag, "") + want := flag + assert.Nil(t, err) + assert.Equal(t, want, got) + }, + ) - t.Run("When a given token is set by config, it should return token as defined on the configuration file", func(t *testing.T) { - got, err := getToken("", config) - want := config - assert.Nil(t, err) - assert.Equal(t, want, got) - }) + t.Run( + "When a given token is set by config, it should return token as defined on the configuration file", + func(t *testing.T) { + got, err := getToken("", config) + want := config + assert.Nil(t, err) + assert.Equal(t, want, got) + }, + ) - t.Run("When a given token is set by environment variable, it should return token as defined on the environment", func(t *testing.T) { - os.Setenv(TokenEnvVar, envVar) - got, err := getToken("", "") - want := envVar - assert.Nil(t, err) - assert.Equal(t, want, got) - os.Unsetenv(TokenEnvVar) - }) + t.Run( + "When a given token is set by environment variable, it should return token as defined on the environment", + func(t *testing.T) { + os.Setenv(TokenEnvVar, envVar) + got, err := getToken("", "") + want := envVar + assert.Nil(t, err) + assert.Equal(t, want, got) + os.Unsetenv(TokenEnvVar) + }, + ) - t.Run("When a given token is set on flag and config file, it should return the value set on flag", func(t *testing.T) { - got, err := getToken(flag, config) - want := flag - assert.Nil(t, err) - assert.Equal(t, want, got) - }) + t.Run( + "When a given token is set on flag and config file, it should return the value set on flag", + func(t *testing.T) { + got, err := getToken(flag, config) + want := flag + assert.Nil(t, err) + assert.Equal(t, want, got) + }, + ) - t.Run("When a given token is set on flag and environment, it should return the value set on the flag", func(t *testing.T) { - os.Setenv(TokenEnvVar, envVar) - got, err := getToken(flag, "") - want := flag - assert.Nil(t, err) - assert.Equal(t, want, got) - os.Unsetenv(TokenEnvVar) - }) + t.Run( + "When a given token is set on flag and environment, it should return the value set on the flag", + func(t *testing.T) { + os.Setenv(TokenEnvVar, envVar) + got, err := getToken(flag, "") + want := flag + assert.Nil(t, err) + assert.Equal(t, want, got) + os.Unsetenv(TokenEnvVar) + }, + ) - t.Run("When a given token is set on config file and environment, it should return the value set on the config file", func(t *testing.T) { - os.Setenv(TokenEnvVar, envVar) - got, err := getToken("", config) - want := config - assert.Nil(t, err) - assert.Equal(t, want, got) - os.Unsetenv(TokenEnvVar) - }) + t.Run( + "When a given token is set on config file and environment, it should return the value set on the config file", + func(t *testing.T) { + os.Setenv(TokenEnvVar, envVar) + got, err := getToken("", config) + want := config + assert.Nil(t, err) + assert.Equal(t, want, got) + os.Unsetenv(TokenEnvVar) + }, + ) - t.Run("When a given token is set on flag, config file, and environment, it should return the value set on flag", func(t *testing.T) { - os.Setenv(TokenEnvVar, envVar) - got, err := getToken(flag, config) - want := flag - assert.Nil(t, err) - assert.Equal(t, want, got) - os.Unsetenv(TokenEnvVar) - }) + t.Run( + "When a given token is set on flag, config file, and environment, it should return the value set on flag", + func(t *testing.T) { + os.Setenv(TokenEnvVar, envVar) + got, err := getToken(flag, config) + want := flag + assert.Nil(t, err) + assert.Equal(t, want, got) + os.Unsetenv(TokenEnvVar) + }, + ) t.Run("When no token is passed should return error", func(t *testing.T) { got, err := getToken("", "") diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go index 493ae43..5926fd8 100644 --- a/pkg/printer/printer.go +++ b/pkg/printer/printer.go @@ -24,16 +24,33 @@ func HeaderStyle(t *tablewriter.Table) *tablewriter.Table { func SuccessStyle(t *tablewriter.Table, data []string) *tablewriter.Table { t.Rich(data, []tablewriter.Colors{ - tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiCyanColor}, - tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiGreenColor}, - tablewriter.Colors{tablewriter.Bold}, - tablewriter.Colors{tablewriter.Bold}, - tablewriter.Colors{tablewriter.Bold}, + {tablewriter.Bold, tablewriter.FgHiCyanColor}, + {tablewriter.Bold, tablewriter.FgHiGreenColor}, + {tablewriter.Bold}, + {tablewriter.Bold}, + {tablewriter.Bold}, }) return t } func ErrorStyle(t *tablewriter.Table, data []string) *tablewriter.Table { - t.Rich(data, []tablewriter.Colors{tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiRedColor}, tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiRedColor}}) + t.Rich( + data, + []tablewriter.Colors{ + {tablewriter.Bold, tablewriter.FgHiCyanColor}, + {tablewriter.Bold, tablewriter.FgHiRedColor}, + }, + ) + return t +} + +func WaitingStyle(t *tablewriter.Table, data []string) *tablewriter.Table { + t.Rich( + data, + []tablewriter.Colors{ + {tablewriter.Bold, tablewriter.FgHiCyanColor}, + {tablewriter.Bold, tablewriter.FgHiYellowColor}, + }, + ) return t } diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 141566c..477fc14 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -35,7 +35,13 @@ func TestReadConfigFile(t *testing.T) { wantExt := "yaml" if gotFilename != wantFilename || gotExt != wantExt { - t.Errorf("got: %s, want: %s, got: %s, want: %s", gotFilename, wantFilename, gotExt, wantExt) + t.Errorf( + "got: %s, want: %s, got: %s, want: %s", + gotFilename, + wantFilename, + gotExt, + wantExt, + ) } }) }