From 0d4f80593e5c466e77eeb6a73c09b991a1c68f72 Mon Sep 17 00:00:00 2001 From: Sachin Kumar Date: Wed, 15 Apr 2026 10:25:59 +0530 Subject: [PATCH] feat(engine): add commit iteration support for pull request evaluation --- internal/engine/ingester/diff/diff.go | 74 +++++++++++++++++++++++++++ internal/providers/github/common.go | 43 ++++++++++++++++ 2 files changed, 117 insertions(+) diff --git a/internal/engine/ingester/diff/diff.go b/internal/engine/ingester/diff/diff.go index 4412e58a7f..f049d05fc3 100644 --- a/internal/engine/ingester/diff/diff.go +++ b/internal/engine/ingester/diff/diff.go @@ -18,6 +18,7 @@ import ( "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/helper/iofs" + "github.com/google/go-github/v63/github" scalibr "github.com/google/osv-scalibr" "github.com/google/osv-scalibr/extractor" scalibr_fs "github.com/google/osv-scalibr/fs" @@ -92,6 +93,13 @@ func (di *Diff) Ingest( } prNumber := int(pr.Number) + // Best-effort experiment: if the underlying provider supports listing PR commits, + // iterate over them here so we can observe commit-level data during evaluation. This + // is intentionally non-blocking for providers that do not implement the method. + if err := di.logPullRequestCommits(ctx, pr, prNumber); err != nil { + zerolog.Ctx(ctx).Debug().Err(err).Msg("failed to list pull request commits") + } + switch di.cfg.GetType() { case "", pb.DiffTypeDep: return di.getDepTypeDiff(ctx, prNumber, pr) @@ -108,6 +116,72 @@ func (di *Diff) Ingest( } } +// commitLister is an internal, optional trait that providers can implement to +// expose pull request commits for a given PR. +type commitLister interface { + ListPullRequestCommits( + ctx context.Context, + owner string, + repo string, + prNumber int, + perPage int, + pageNumber int, + ) ([]*github.RepositoryCommit, *github.Response, error) +} + +// logPullRequestCommits iterates over all commits in the given pull request, +// logging their SHAs and messages when the underlying provider implements the +// commitLister trait. This is a no-op for providers that do not support it. +func (di *Diff) logPullRequestCommits( + ctx context.Context, + pr *pbinternal.PullRequest, + prNumber int, +) error { + cli, ok := di.cli.(commitLister) + if !ok { + return nil + } + + logger := zerolog.Ctx(ctx) + page := 0 + const prCommitsPerPage = 50 + + for { + commits, resp, err := cli.ListPullRequestCommits( + ctx, + pr.RepoOwner, + pr.RepoName, + prNumber, + prCommitsPerPage, + page, + ) + if err != nil { + return fmt.Errorf("error listing pull request commits: %w", err) + } + + for _, c := range commits { + sha := c.GetSHA() + msg := c.GetCommit().GetMessage() + firstLine := strings.Split(msg, "\n")[0] + + logger.Debug(). + Str("pr_repo", fmt.Sprintf("%s/%s", pr.RepoOwner, pr.RepoName)). + Int64("pr_number", pr.Number). + Str("commit_sha", sha). + Str("commit_msg", firstLine). + Msg("pull request commit") + } + + if resp == nil || resp.NextPage == 0 { + break + } + + page = resp.NextPage + } + + return nil +} + func (di *Diff) getDepTypeDiff(ctx context.Context, prNumber int, pr *pbinternal.PullRequest) (*interfaces.Ingested, error) { deps := pbinternal.PrDependencies{Pr: pr} page := 0 diff --git a/internal/providers/github/common.go b/internal/providers/github/common.go index 21a9008cb3..fe5778ec98 100644 --- a/internal/providers/github/common.go +++ b/internal/providers/github/common.go @@ -422,6 +422,49 @@ func (c *GitHub) ListFiles( return resp.files, resp.resp, err } +// ListPullRequestCommits is a wrapper for the GitHub API to list commits in a pull request. +// It is currently intended for internal engine experimentation, such as iterating over all +// commits in a PR during evaluation. +func (c *GitHub) ListPullRequestCommits( + ctx context.Context, + owner string, + repo string, + prNumber int, + perPage int, + pageNumber int, +) ([]*github.RepositoryCommit, *github.Response, error) { + type listCommitsRespWrapper struct { + commits []*github.RepositoryCommit + resp *github.Response + } + + op := func() (listCommitsRespWrapper, error) { + opt := &github.ListOptions{ + Page: pageNumber, + PerPage: perPage, + } + commits, resp, err := c.client.PullRequests.ListCommits(ctx, owner, repo, prNumber, opt) + + wrapped := listCommitsRespWrapper{ + commits: commits, + resp: resp, + } + + if isRateLimitError(err) { + waitErr := c.waitForRateLimitReset(ctx, err) + if waitErr == nil { + return wrapped, err + } + return wrapped, backoffv4.Permanent(err) + } + + return wrapped, backoffv4.Permanent(err) + } + + resp, err := performWithRetry(ctx, op) + return resp.commits, resp.resp, err +} + // CreateReview is a wrapper for the GitHub API to create a review func (c *GitHub) CreateReview( ctx context.Context, owner, repo string, number int, reviewRequest *github.PullRequestReviewRequest,