Add support for commit-based lookup to GitHub APIs (#1612)

Co-authored-by: Azeem Shaikh <azeems@google.com>
This commit is contained in:
Azeem Shaikh 2022-02-07 14:06:05 -08:00 committed by GitHub
parent 68bf172e59
commit eac2aecce6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 71 deletions

View File

@ -52,6 +52,7 @@ type Client struct {
// InitRepo sets up the GitHub repo in local storage for improving performance and GitHub token usage efficiency. // InitRepo sets up the GitHub repo in local storage for improving performance and GitHub token usage efficiency.
func (client *Client) InitRepo(inputRepo clients.Repo) error { func (client *Client) InitRepo(inputRepo clients.Repo) error {
commitSHA := "HEAD"
ghRepo, ok := inputRepo.(*repoURL) ghRepo, ok := inputRepo.(*repoURL)
if !ok { if !ok {
return fmt.Errorf("%w: %v", errInputRepoType, inputRepo) return fmt.Errorf("%w: %v", errInputRepoType, inputRepo)
@ -67,12 +68,13 @@ func (client *Client) InitRepo(inputRepo clients.Repo) error {
client.repoName = repo.GetName() client.repoName = repo.GetName()
// Init tarballHandler. // Init tarballHandler.
if err := client.tarball.init(client.ctx, client.repo); err != nil { if err := client.tarball.init(client.ctx, client.repo, commitSHA); err != nil {
return fmt.Errorf("error during tarballHandler.init: %w", err) return fmt.Errorf("error during tarballHandler.init: %w", err)
} }
// Setup GraphQL. // Setup GraphQL.
client.graphClient.init(client.ctx, client.owner, client.repoName) client.graphClient.init(client.ctx, client.owner, client.repoName,
client.repo.GetDefaultBranch(), commitSHA)
// Setup contributorsHandler. // Setup contributorsHandler.
client.contributors.init(client.ctx, client.owner, client.repoName) client.contributors.init(client.ctx, client.owner, client.repoName)

View File

@ -17,6 +17,7 @@ package githubrepo
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"sync" "sync"
"time" "time"
@ -38,65 +39,63 @@ const (
// nolint: govet // nolint: govet
type graphqlData struct { type graphqlData struct {
Repository struct { Repository struct {
IsArchived githubv4.Boolean IsArchived githubv4.Boolean
DefaultBranchRef struct { Object struct {
Target struct { Commit struct {
Commit struct { History struct {
History struct { Nodes []struct {
Nodes []struct { CommittedDate githubv4.DateTime
CommittedDate githubv4.DateTime Message githubv4.String
Message githubv4.String Oid githubv4.GitObjectID
Oid githubv4.GitObjectID Author struct {
Author struct { User struct {
User struct { Login githubv4.String
}
}
Committer struct {
Name *string
User struct {
Login *string
}
}
AssociatedPullRequests struct {
Nodes []struct {
Repository struct {
Name githubv4.String
Owner struct {
Login githubv4.String
}
}
Author struct {
Login githubv4.String Login githubv4.String
} }
} Number githubv4.Int
Committer struct { HeadRefOid githubv4.String
Name *string MergedAt githubv4.DateTime
User struct { MergeCommit struct {
Login *string // NOTE: only used for sanity check.
// Use original commit oid instead.
Oid githubv4.GitObjectID
} }
} Labels struct {
AssociatedPullRequests struct { Nodes []struct {
Nodes []struct { Name githubv4.String
Repository struct { }
Name githubv4.String } `graphql:"labels(last: $labelsToAnalyze)"`
Owner struct { Reviews struct {
Nodes []struct {
State githubv4.String
Author struct {
Login githubv4.String Login githubv4.String
} }
} }
Author struct { } `graphql:"reviews(last: $reviewsToAnalyze)"`
Login githubv4.String }
} } `graphql:"associatedPullRequests(first: $pullRequestsToAnalyze)"`
Number githubv4.Int }
HeadRefOid githubv4.String } `graphql:"history(first: $commitsToAnalyze)"`
MergedAt githubv4.DateTime } `graphql:"... on Commit"`
MergeCommit struct { } `graphql:"object(expression: $commitExpression)"`
// NOTE: only used for sanity check.
// Use original commit oid instead.
Oid githubv4.GitObjectID
}
Labels struct {
Nodes []struct {
Name githubv4.String
}
} `graphql:"labels(last: $labelsToAnalyze)"`
Reviews struct {
Nodes []struct {
State githubv4.String
Author struct {
Login githubv4.String
}
}
} `graphql:"reviews(last: $reviewsToAnalyze)"`
}
} `graphql:"associatedPullRequests(first: $pullRequestsToAnalyze)"`
}
} `graphql:"history(first: $commitsToAnalyze)"`
} `graphql:"... on Commit"`
}
}
Issues struct { Issues struct {
Nodes []struct { Nodes []struct {
// nolint: revive,stylecheck // naming according to githubv4 convention. // nolint: revive,stylecheck // naming according to githubv4 convention.
@ -115,22 +114,26 @@ type graphqlData struct {
} }
type graphqlHandler struct { type graphqlHandler struct {
client *githubv4.Client client *githubv4.Client
data *graphqlData data *graphqlData
once *sync.Once once *sync.Once
ctx context.Context ctx context.Context
errSetup error errSetup error
owner string owner string
repo string repo string
commits []clients.Commit defaultBranch string
issues []clients.Issue commitSHA string
archived bool commits []clients.Commit
issues []clients.Issue
archived bool
} }
func (handler *graphqlHandler) init(ctx context.Context, owner, repo string) { func (handler *graphqlHandler) init(ctx context.Context, owner, repo, defaultBranch, commitSHA string) {
handler.ctx = ctx handler.ctx = ctx
handler.owner = owner handler.owner = owner
handler.repo = repo handler.repo = repo
handler.defaultBranch = defaultBranch
handler.commitSHA = commitSHA
handler.data = new(graphqlData) handler.data = new(graphqlData)
handler.errSetup = nil handler.errSetup = nil
handler.once = new(sync.Once) handler.once = new(sync.Once)
@ -138,6 +141,12 @@ func (handler *graphqlHandler) init(ctx context.Context, owner, repo string) {
func (handler *graphqlHandler) setup() error { func (handler *graphqlHandler) setup() error {
handler.once.Do(func() { handler.once.Do(func() {
commitExpression := handler.commitSHA
if strings.EqualFold(handler.commitSHA, "HEAD") {
// TODO(#575): Confirm that this works as expected.
commitExpression = fmt.Sprintf("heads/%s", handler.defaultBranch)
}
vars := map[string]interface{}{ vars := map[string]interface{}{
"owner": githubv4.String(handler.owner), "owner": githubv4.String(handler.owner),
"name": githubv4.String(handler.repo), "name": githubv4.String(handler.repo),
@ -147,6 +156,7 @@ func (handler *graphqlHandler) setup() error {
"reviewsToAnalyze": githubv4.Int(reviewsToAnalyze), "reviewsToAnalyze": githubv4.Int(reviewsToAnalyze),
"labelsToAnalyze": githubv4.Int(labelsToAnalyze), "labelsToAnalyze": githubv4.Int(labelsToAnalyze),
"commitsToAnalyze": githubv4.Int(commitsToAnalyze), "commitsToAnalyze": githubv4.Int(commitsToAnalyze),
"commitExpression": githubv4.String(commitExpression),
} }
if err := handler.client.Query(handler.ctx, handler.data, vars); err != nil { if err := handler.client.Query(handler.ctx, handler.data, vars); err != nil {
handler.errSetup = sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("githubv4.Query: %v", err)) handler.errSetup = sce.WithMessage(sce.ErrScorecardInternal, fmt.Sprintf("githubv4.Query: %v", err))
@ -186,7 +196,7 @@ func (handler *graphqlHandler) isArchived() (bool, error) {
// nolint: unparam // nolint: unparam
func commitsFrom(data *graphqlData, repoOwner, repoName string) ([]clients.Commit, error) { func commitsFrom(data *graphqlData, repoOwner, repoName string) ([]clients.Commit, error) {
ret := make([]clients.Commit, 0) ret := make([]clients.Commit, 0)
for _, commit := range data.Repository.DefaultBranchRef.Target.Commit.History.Nodes { for _, commit := range data.Repository.Object.Commit.History.Nodes {
var committer string var committer string
if commit.Committer.User.Login != nil { if commit.Committer.User.Login != nil {
committer = *commit.Committer.User.Login committer = *commit.Committer.User.Login

View File

@ -68,14 +68,14 @@ type tarballHandler struct {
files []string files []string
} }
func (handler *tarballHandler) init(ctx context.Context, repo *github.Repository) error { func (handler *tarballHandler) init(ctx context.Context, repo *github.Repository, commitSHA string) error {
// Cleanup any previous state. // Cleanup any previous state.
if err := handler.cleanup(); err != nil { if err := handler.cleanup(); err != nil {
return sce.WithMessage(sce.ErrScorecardInternal, err.Error()) return sce.WithMessage(sce.ErrScorecardInternal, err.Error())
} }
// Setup temp dir/files and download repo tarball. // Setup temp dir/files and download repo tarball.
if err := handler.getTarball(ctx, repo); errors.Is(err, errTarballNotFound) { if err := handler.getTarball(ctx, repo, commitSHA); errors.Is(err, errTarballNotFound) {
log.Printf("unable to get tarball %v. Skipping...", err) log.Printf("unable to get tarball %v. Skipping...", err)
return nil return nil
} else if err != nil { } else if err != nil {
@ -93,10 +93,14 @@ func (handler *tarballHandler) init(ctx context.Context, repo *github.Repository
return nil return nil
} }
func (handler *tarballHandler) getTarball(ctx context.Context, repo *github.Repository) error { func (handler *tarballHandler) getTarball(ctx context.Context, repo *github.Repository, commitSHA string) error {
url := repo.GetArchiveURL() url := repo.GetArchiveURL()
url = strings.Replace(url, "{archive_format}", "tarball/", 1) url = strings.Replace(url, "{archive_format}", "tarball/", 1)
url = strings.Replace(url, "{/ref}", "", 1) if strings.EqualFold(commitSHA, "HEAD") {
url = strings.Replace(url, "{/ref}", "", 1)
} else {
url = strings.Replace(url, "{/ref}", commitSHA, 1)
}
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { if err != nil {
return fmt.Errorf("http.NewRequestWithContext: %w", err) return fmt.Errorf("http.NewRequestWithContext: %w", err)