github.com/grafana/pyroscope@v1.18.0/pkg/frontend/vcs/commit.go (about) 1 package vcs 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/opentracing/opentracing-go" 8 "golang.org/x/sync/errgroup" 9 10 vcsv1 "github.com/grafana/pyroscope/api/gen/proto/go/vcs/v1" 11 ) 12 13 const maxConcurrentRequests = 10 14 15 type gitHubCommitGetter interface { 16 GetCommit(context.Context, string, string, string) (*vcsv1.CommitInfo, error) 17 } 18 19 // getCommits fetches multiple commits in parallel for a given repository. 20 // It attempts to retrieve commits for all provided refs and returns: 21 // 1. Successfully fetched commits 22 // 2. Errors for failed fetches 23 // 3. An overall error if no commits were successfully fetched 24 // This function provides partial success behavior, returning any commits 25 // that were successfully fetched along with errors for those that failed. 26 func getCommits(ctx context.Context, client gitHubCommitGetter, owner, repo string, refs []string) ([]*vcsv1.CommitInfo, []error, error) { 27 sp, ctx := opentracing.StartSpanFromContext(ctx, "getCommits") 28 defer sp.Finish() 29 sp.SetTag("owner", owner) 30 sp.SetTag("repo", repo) 31 sp.SetTag("ref_count", len(refs)) 32 33 type result struct { 34 commit *vcsv1.CommitInfo 35 err error 36 } 37 38 resultsCh := make(chan result, maxConcurrentRequests) 39 g, ctx := errgroup.WithContext(ctx) 40 g.SetLimit(maxConcurrentRequests) 41 42 for _, ref := range refs { 43 ref := ref 44 g.Go(func() error { 45 commit, err := tryGetCommit(ctx, client, owner, repo, ref) 46 select { 47 case resultsCh <- result{commit, err}: 48 case <-ctx.Done(): 49 return ctx.Err() 50 } 51 return nil 52 }) 53 } 54 55 go func() { 56 _ = g.Wait() // ignore errors since they're handled in the `resultsCh`. 57 close(resultsCh) 58 }() 59 60 var validCommits []*vcsv1.CommitInfo 61 var failedFetches []error 62 for r := range resultsCh { 63 if r.err != nil { 64 failedFetches = append(failedFetches, r.err) 65 } 66 if r.commit != nil { 67 validCommits = append(validCommits, r.commit) 68 } 69 } 70 71 if len(validCommits) == 0 && len(failedFetches) > 0 { 72 return nil, failedFetches, fmt.Errorf("failed to fetch any commits") 73 } 74 75 return validCommits, failedFetches, nil 76 } 77 78 // tryGetCommit attempts to retrieve a commit using different ref formats (commit hash, branch, tag). 79 // It tries each format in order and returns the first successful result. 80 func tryGetCommit(ctx context.Context, client gitHubCommitGetter, owner, repo, ref string) (*vcsv1.CommitInfo, error) { 81 sp, ctx := opentracing.StartSpanFromContext(ctx, "tryGetCommit") 82 defer sp.Finish() 83 sp.SetTag("owner", owner) 84 sp.SetTag("repo", repo) 85 sp.SetTag("ref", ref) 86 87 refFormats := []string{ 88 ref, // Try as a commit hash 89 "heads/" + ref, // Try as a branch 90 "tags/" + ref, // Try as a tag 91 } 92 93 var lastErr error 94 for _, format := range refFormats { 95 commit, err := client.GetCommit(ctx, owner, repo, format) 96 if err == nil { 97 return commit, nil 98 } 99 100 lastErr = err 101 } 102 103 return nil, lastErr 104 }