Skip to content

Commit 1c4b085

Browse files
committed
Cleanup go commands
1 parent 97d3d29 commit 1c4b085

File tree

8 files changed

+407
-61
lines changed

8 files changed

+407
-61
lines changed

tools/flakeguard/cmd/find.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,11 +94,6 @@ func init() {
9494
}
9595

9696
func findAffectedPackages(baseRef, projectPath string, excludes []string, levels int) []string {
97-
goList, err := golang.GoList()
98-
if err != nil {
99-
log.Error().Err(err).Msg("Error getting go list")
100-
os.Exit(ErrorExitCode)
101-
}
10297
gitDiff, err := git.Diff(baseRef)
10398
if err != nil {
10499
log.Error().Err(err).Msg("Error getting the git diff")
@@ -110,7 +105,7 @@ func findAffectedPackages(baseRef, projectPath string, excludes []string, levels
110105
os.Exit(ErrorExitCode)
111106
}
112107

113-
packages, err := golang.ParsePackages(goList.Stdout)
108+
packages, err := golang.Packages(projectPath)
114109
if err != nil {
115110
log.Error().Err(err).Msg("Error parsing packages")
116111
os.Exit(ErrorExitCode)

tools/flakeguard/cmd/make_pr.go

Lines changed: 139 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,22 @@ package cmd
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
7+
"io"
68
"os"
9+
"strings"
710
"time"
811

912
"github.com/go-git/go-git/v5"
1013
"github.com/go-git/go-git/v5/plumbing"
1114
"github.com/google/go-github/v72/github"
12-
"github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard/golang"
13-
"github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard/localdb"
1415
"github.com/spf13/cobra"
1516
"golang.org/x/oauth2"
17+
18+
flake_git "github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard/git"
19+
"github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard/golang"
20+
"github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard/localdb"
1621
)
1722

1823
var (
@@ -29,55 +34,118 @@ var MakePRCmd = &cobra.Command{
2934
func makePR(cmd *cobra.Command, args []string) error {
3035
repo, err := git.PlainOpen(repoPath)
3136
if err != nil {
32-
return err
37+
return fmt.Errorf("failed to open repo: %w", err)
3338
}
3439

3540
db, err := localdb.LoadDBWithPath(localDBPath)
3641
if err != nil {
37-
return err
42+
return fmt.Errorf("failed to load local db: %w", err)
3843
}
3944

4045
currentlyFlakyEntries := db.GetAllCurrentlyFlakyEntries()
4146

42-
branchName := fmt.Sprintf("flakeguard-skip-%s", time.Now().Format("20060102150405"))
47+
owner, repoName, defaultBranch, err := flake_git.GetOwnerRepoDefaultBranchFromLocalRepo(repoPath)
48+
if err != nil {
49+
return fmt.Errorf("failed to get repo info: %w", err)
50+
}
51+
4352
targetRepoWorktree, err := repo.Worktree()
4453
if err != nil {
45-
return err
54+
return fmt.Errorf("failed to open repo's worktree: %w", err)
4655
}
56+
57+
// First checkout default branch and pull latest
58+
err = targetRepoWorktree.Checkout(&git.CheckoutOptions{
59+
Branch: plumbing.NewBranchReferenceName(defaultBranch),
60+
})
61+
if err != nil {
62+
if errors.Is(err, git.ErrUnstagedChanges) {
63+
fmt.Println("Local repo has unstaged changes, please commit or stash them before running this command")
64+
}
65+
return fmt.Errorf("failed to checkout default branch %s: %w", defaultBranch, err)
66+
}
67+
68+
fmt.Print("Fetching latest changes from default branch, tap your yubikey if it's blinking...")
69+
err = repo.Fetch(&git.FetchOptions{})
70+
if err != nil && err != git.NoErrAlreadyUpToDate {
71+
return fmt.Errorf("failed to fetch latest: %w", err)
72+
}
73+
fmt.Println(" ✅")
74+
75+
fmt.Print("Pulling latest changes from default branch, tap your yubikey if it's blinking...")
76+
err = targetRepoWorktree.Pull(&git.PullOptions{})
77+
if err != nil && err != git.NoErrAlreadyUpToDate {
78+
return fmt.Errorf("failed to pull latest changes: %w", err)
79+
}
80+
fmt.Println(" ✅")
81+
82+
// Create and checkout new branch
83+
branchName := fmt.Sprintf("flakeguard-skip-%s", time.Now().Format("20060102150405"))
4784
err = targetRepoWorktree.Checkout(&git.CheckoutOptions{
4885
Branch: plumbing.NewBranchReferenceName(branchName),
4986
Create: true,
5087
})
5188
if err != nil {
52-
return err
89+
return fmt.Errorf("failed to checkout new branch: %w", err)
90+
}
91+
92+
cleanUpBranch := true
93+
defer func() {
94+
if cleanUpBranch {
95+
fmt.Printf("Cleaning up branch %s...", branchName)
96+
err = targetRepoWorktree.Checkout(&git.CheckoutOptions{
97+
Branch: plumbing.NewBranchReferenceName(defaultBranch),
98+
})
99+
if err != nil {
100+
fmt.Printf("Failed to clean up branch: %v\n", err)
101+
}
102+
err = repo.Storer.RemoveReference(plumbing.NewBranchReferenceName(branchName))
103+
if err != nil {
104+
fmt.Printf("Failed to remove branch: %v\n", err)
105+
}
106+
fmt.Println(" ✅")
107+
}
108+
}()
109+
110+
if len(currentlyFlakyEntries) == 0 {
111+
fmt.Println("No flaky tests found!")
112+
return nil
53113
}
54114

55-
testsToSkip := []golang.SkipTest{}
115+
jiraTickets := []string{}
116+
testsToSkip := []*golang.SkipTest{}
56117
for _, entry := range currentlyFlakyEntries {
57-
testsToSkip = append(testsToSkip, golang.SkipTest{
58-
Package: entry.TestPackage,
59-
Name: entry.TestName,
118+
testsToSkip = append(testsToSkip, &golang.SkipTest{
119+
Package: entry.TestPackage,
120+
Name: entry.TestName,
121+
JiraTicket: entry.JiraTicket,
60122
})
123+
jiraTickets = append(jiraTickets, entry.JiraTicket)
61124
}
62125

63126
err = golang.SkipTests(repoPath, testsToSkip)
64127
if err != nil {
65-
return err
128+
return fmt.Errorf("failed to modify code to skip tests: %w", err)
66129
}
67130

68131
_, err = targetRepoWorktree.Add(".")
69132
if err != nil {
70-
return err
133+
return fmt.Errorf("failed to add changes: %w", err)
71134
}
72-
_, err = targetRepoWorktree.Commit("Skips flaky tests", &git.CommitOptions{})
135+
136+
fmt.Print("Committing changes, tap your yubikey if it's blinking...")
137+
commitHash, err := targetRepoWorktree.Commit(fmt.Sprintf("Skips flaky %d tests", len(testsToSkip)), &git.CommitOptions{})
73138
if err != nil {
74-
return err
139+
return fmt.Errorf("failed to commit changes: %w", err)
75140
}
141+
fmt.Println(" ✅")
76142

143+
fmt.Print("Pushing changes to remote, tap your yubikey if it's blinking...")
77144
err = repo.Push(&git.PushOptions{})
78145
if err != nil {
79-
return err
146+
return fmt.Errorf("failed to push changes: %w", err)
80147
}
148+
fmt.Println(" ✅")
81149

82150
ctx := context.Background()
83151
ts := oauth2.StaticTokenSource(
@@ -86,24 +154,71 @@ func makePR(cmd *cobra.Command, args []string) error {
86154
tc := oauth2.NewClient(ctx, ts)
87155
client := github.NewClient(tc)
88156

89-
owner := "your-org"
90-
repoName := "your-repo"
157+
var (
158+
skippedTestsPRBody strings.Builder
159+
alreadySkippedTestsPRBody strings.Builder
160+
)
161+
162+
for _, test := range testsToSkip {
163+
if test.Skipped {
164+
skippedTestsPRBody.WriteString(fmt.Sprintf("- Package: `%s`\n", test.Package))
165+
skippedTestsPRBody.WriteString(fmt.Sprintf(" Test: `%s`\n", test.Name))
166+
skippedTestsPRBody.WriteString(fmt.Sprintf(" Ticket: [%s](https://%s/browse/%s)\n", test.JiraTicket, os.Getenv("JIRA_DOMAIN"), test.JiraTicket))
167+
skippedTestsPRBody.WriteString(fmt.Sprintf(" [View skip in PR](https://github.com/%s/%s/pull/%s/files#diff-%sL%d)\n\n", owner, repoName, branchName, commitHash, test.Line))
168+
} else {
169+
alreadySkippedTestsPRBody.WriteString(fmt.Sprintf("- Package: `%s`\n", test.Package))
170+
alreadySkippedTestsPRBody.WriteString(fmt.Sprintf(" Test: `%s`\n", test.Name))
171+
alreadySkippedTestsPRBody.WriteString(fmt.Sprintf(" Ticket: [%s](https://%s/browse/%s)\n", test.JiraTicket, os.Getenv("JIRA_DOMAIN"), test.JiraTicket))
172+
}
173+
}
174+
91175
pr := &github.NewPullRequest{
92-
Title: github.Ptr("Skip flaky tests"),
176+
Title: github.Ptr(fmt.Sprintf("[%s] Flakeguard: Skip flaky tests", strings.Join(jiraTickets, "] ["))),
93177
Head: github.Ptr(branchName),
94-
Base: github.Ptr("main"),
95-
Body: github.Ptr("This PR skips flaky tests."),
178+
Base: github.Ptr(defaultBranch),
179+
Body: github.Ptr(fmt.Sprintf("## Tests Skipped\n\n%s\n\n## Tests Already Skipped\n\n%s", skippedTestsPRBody.String(), alreadySkippedTestsPRBody.String())),
96180
MaintainerCanModify: github.Ptr(true),
97181
}
98-
_, _, err = client.PullRequests.Create(ctx, owner, repoName, pr)
182+
183+
fmt.Println("PR Preview:")
184+
fmt.Println("================================================")
185+
fmt.Println(pr.Title)
186+
fmt.Println("--------------------------------")
187+
fmt.Printf("Merging '%s' into '%s'\n", branchName, defaultBranch)
188+
fmt.Println(pr.Body)
189+
fmt.Println("================================================")
190+
191+
fmt.Printf("To preview the code changes in the GitHub UI, visit: https://github.com/%s/%s/compare/%s...%s\n", owner, repoName, defaultBranch, branchName)
192+
fmt.Print("Would you like to create the PR automatically from the CLI? (y/N): ")
193+
194+
var confirm string
195+
_, err = fmt.Scanln(&confirm)
99196
if err != nil {
100197
return err
101198
}
102199

103-
fmt.Println("PR created!")
200+
if strings.ToLower(confirm) != "y" {
201+
fmt.Println("Exiting. Please use the GitHub UI to create the PR.")
202+
return nil
203+
}
204+
205+
createdPR, resp, err := client.PullRequests.Create(ctx, owner, repoName, pr)
206+
if err != nil {
207+
return err
208+
}
209+
if resp.StatusCode != 201 {
210+
body, err := io.ReadAll(resp.Body)
211+
if err != nil {
212+
return fmt.Errorf("failed to read github response body while trying to create PR: %s\n%w", resp.Status, err)
213+
}
214+
return fmt.Errorf("failed to create PR, got bad status: %s\n%s", resp.Status, string(body))
215+
}
216+
217+
cleanUpBranch = false
218+
fmt.Printf("PR created! https://github.com/%s/%s/pull/%d\n", owner, repoName, createdPR.GetNumber())
104219
return nil
105220
}
106221

107222
func init() {
108-
MakePRCmd.Flags().StringVarP(&repoPath, "repo", "r", ".", "Path to the repository to make the PR in")
223+
MakePRCmd.Flags().StringVarP(&repoPath, "repoPath", "r", ".", "Local path to the repository to make the PR for")
109224
}

tools/flakeguard/git/git.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"strings"
1010
"syscall"
1111

12+
"github.com/go-git/go-git/v5"
13+
"github.com/go-git/go-git/v5/plumbing"
1214
"github.com/smartcontractkit/chainlink-testing-framework/tools/flakeguard/utils"
1315
)
1416

@@ -186,3 +188,76 @@ func shouldExclude(excludes []string, item string) bool {
186188
}
187189
return false
188190
}
191+
192+
// GetOwnerRepoDefaultBranchFromLocalRepo returns the owner, repo name, and default branch of a local git repository.
193+
// It uses the origin remote URL to determine the owner and repo name, and the default branch is determined from the
194+
// refs/remotes/origin/HEAD reference.
195+
func GetOwnerRepoDefaultBranchFromLocalRepo(repoPath string) (owner, repoName, defaultBranch string, err error) {
196+
repo, err := git.PlainOpen(repoPath)
197+
if err != nil {
198+
return "", "", "", err
199+
}
200+
201+
// Get remote URL (origin)
202+
remotes, err := repo.Remotes()
203+
if err != nil {
204+
return "", "", "", err
205+
}
206+
var originURL string
207+
for _, remote := range remotes {
208+
if remote.Config().Name == "origin" && len(remote.Config().URLs) > 0 {
209+
originURL = remote.Config().URLs[0]
210+
break
211+
}
212+
}
213+
if originURL == "" {
214+
return "", "", "", fmt.Errorf("origin remote not found")
215+
}
216+
217+
// Parse owner and repo from URL
218+
originURL = strings.TrimSuffix(originURL, ".git")
219+
var path string
220+
if strings.Contains(originURL, "@github.com:") {
221+
parts := strings.SplitN(originURL, ":", 2)
222+
if len(parts) == 2 {
223+
path = parts[1]
224+
}
225+
} else if strings.HasPrefix(originURL, "https://") {
226+
parts := strings.SplitN(originURL, "github.com/", 2)
227+
if len(parts) == 2 {
228+
path = parts[1]
229+
}
230+
}
231+
if path == "" {
232+
return "", "", "", fmt.Errorf("could not parse remote URL: %s", originURL)
233+
}
234+
segments := strings.Split(path, "/")
235+
if len(segments) != 2 {
236+
return "", "", "", fmt.Errorf("unexpected path format: %s", path)
237+
}
238+
owner, repoName = segments[0], segments[1]
239+
240+
// Find default branch from refs/remotes/origin/HEAD
241+
refs, err := repo.References()
242+
if err != nil {
243+
return "", "", "", err
244+
}
245+
err = refs.ForEach(func(ref *plumbing.Reference) error {
246+
if ref.Name().IsRemote() && ref.Name().String() == "refs/remotes/origin/HEAD" {
247+
target := ref.Target().String()
248+
parts := strings.Split(target, "/")
249+
if len(parts) > 0 {
250+
defaultBranch = parts[len(parts)-1]
251+
}
252+
}
253+
return nil
254+
})
255+
if err != nil {
256+
return "", "", "", err
257+
}
258+
if defaultBranch == "" {
259+
return "", "", "", fmt.Errorf("could not determine default branch")
260+
}
261+
262+
return owner, repoName, defaultBranch, nil
263+
}

0 commit comments

Comments
 (0)