Skip to content

Commit

Permalink
Add "GetCommitsWithQueryOptions" new API. (#135)
Browse files Browse the repository at this point in the history
  • Loading branch information
gailazar300 authored Jun 30, 2024
1 parent 8b10260 commit 4523262
Show file tree
Hide file tree
Showing 11 changed files with 422 additions and 15 deletions.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Currently supported providers are: [GitHub](#github), [Bitbucket Server](#bitbuc
- [Delete Pull Request Comment](#delete-pull-request-comment)
- [Delete Pull Request Review Comments](#delete-pull-request-review-comments)
- [Get Commits](#get-commits)
- [Get Commits With Options](#get-commits-with-options)
- [Get Latest Commit](#get-latest-commit)
- [Get Commit By SHA](#get-commit-by-sha)
- [Get List of Modified Files](#get-list-of-modified-files)
Expand Down Expand Up @@ -544,6 +545,29 @@ branch := "dev"
commitInfo, err := client.GetCommits(ctx, owner, repository, branch)
```

#### Get Commits With Options

```go
// Go context
ctx := context.Background()
// Organization or username
owner := "jfrog"
// VCS repository
repository := "jfrog-cli"

// Commits query options
options := GitCommitsQueryOptions{
Since: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
Until: time.Now(),
ListOptions: ListOptions{
Page: 1,
PerPage: 30,
},
}

result, err := client.GetCommitsWithQueryOptions(ctx, owner, repository, options)
```

#### Get Latest Commit

```go
Expand Down
7 changes: 7 additions & 0 deletions vcsclient/azurerepos.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ import (
)

const (
notSupportedOnAzure = "currently not supported on Azure"
defaultAzureBaseUrl = "https://dev.azure.com/"
azurePullRequestDetailsSizeLimit = 4000
azurePullRequestCommentSizeLimit = 150000
)

var errAzureGetCommitsWithOptionsNotSupported = fmt.Errorf("get commits with options is %s", notSupportedOnAzure)

// Azure Devops API version 6
type AzureReposClient struct {
vcsInfo VcsInfo
Expand Down Expand Up @@ -425,6 +428,10 @@ func (client *AzureReposClient) GetCommits(ctx context.Context, _, repository, b
return commitsInfo, nil
}

func (client *AzureReposClient) GetCommitsWithQueryOptions(ctx context.Context, _, repository string, listOptions GitCommitsQueryOptions) ([]CommitInfo, error) {
return nil, errAzureGetCommitsWithOptionsNotSupported
}

func mapAzureReposCommitsToCommitInfo(commit git.GitCommitRef) CommitInfo {
var authorName, authorEmail string
if commit.Author != nil {
Expand Down
4 changes: 4 additions & 0 deletions vcsclient/bitbucketcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,10 @@ func (client *BitbucketCloudClient) GetCommits(_ context.Context, _, _, _ string
return nil, errBitbucketGetCommitsNotSupported
}

func (client *BitbucketCloudClient) GetCommitsWithQueryOptions(ctx context.Context, owner, repository string, listOptions GitCommitsQueryOptions) ([]CommitInfo, error) {
return nil, errBitbucketGetCommitsWithOptionsNotSupported
}

// GetRepositoryInfo on Bitbucket cloud
func (client *BitbucketCloudClient) GetRepositoryInfo(ctx context.Context, owner, repository string) (RepositoryInfo, error) {
if err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository}); err != nil {
Expand Down
1 change: 1 addition & 0 deletions vcsclient/bitbucketcommon.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var (
errBitbucketCodeScanningNotSupported = fmt.Errorf("code scanning is %s", notSupportedOnBitbucket)
errBitbucketDownloadFileFromRepoNotSupported = fmt.Errorf("download file from repo is %s", notSupportedOnBitbucket)
errBitbucketGetCommitsNotSupported = fmt.Errorf("get commits is %s", notSupportedOnBitbucket)
errBitbucketGetCommitsWithOptionsNotSupported = fmt.Errorf("get commits with options is %s", notSupportedOnBitbucket)
errBitbucketGetRepoEnvironmentInfoNotSupported = fmt.Errorf("get repository environment info is %s", notSupportedOnBitbucket)
errBitbucketListPullRequestReviewCommentsNotSupported = fmt.Errorf("list pull request review comments is %s", notSupportedOnBitbucket)
errBitbucketAddPullRequestReviewCommentsNotSupported = fmt.Errorf("add pull request review comment is %s", notSupportedOnBitbucket)
Expand Down
62 changes: 58 additions & 4 deletions vcsclient/bitbucketserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,52 @@ func (client *BitbucketServerClient) GetCommits(ctx context.Context, owner, repo
"limit": vcsutils.NumberOfCommitsToFetch,
"until": branch,
}
return client.getCommitsWithQueryOptions(ctx, owner, repository, options)
}

func (client *BitbucketServerClient) GetCommitsWithQueryOptions(ctx context.Context, owner, repository string, listOptions GitCommitsQueryOptions) ([]CommitInfo, error) {
err := validateParametersNotBlank(map[string]string{
"owner": owner,
"repository": repository,
})
if err != nil {
return nil, err
}
commits, err := client.getCommitsWithQueryOptions(ctx, owner, repository, convertToBitbucketOptionsMap(listOptions))
if err != nil {
return nil, err
}
return getCommitsInDateRate(commits, listOptions), nil
}

// Bitbucket doesn't support filtering by date, so we need to filter the commits by date ourselves.
func getCommitsInDateRate(commits []CommitInfo, options GitCommitsQueryOptions) []CommitInfo {
commitsNumber := len(commits)
if commitsNumber == 0 {
return commits
}

firstCommit := time.Unix(commits[0].Timestamp, 0).UTC()
lastCommit := time.Unix(commits[commitsNumber-1].Timestamp, 0).UTC()

// If all commits are in the range return all.
if lastCommit.After(options.Since) || lastCommit.Equal(options.Since) {
return commits
}
// If the first commit is older than the "since" timestamp, all commits are out of range, return an empty list.
if firstCommit.Before(options.Since) {
return []CommitInfo{}
}
// Find the first commit that is older than the "since" timestamp.
for i, commit := range commits {
if time.Unix(commit.Timestamp, 0).UTC().Before(options.Since) {
return commits[:i]
}
}
return []CommitInfo{}
}

func (client *BitbucketServerClient) getCommitsWithQueryOptions(ctx context.Context, owner, repository string, options map[string]interface{}) ([]CommitInfo, error) {
bitbucketClient := client.buildBitbucketClient(ctx)

apiResponse, err := bitbucketClient.GetCommits(owner, repository, options)
Expand All @@ -571,6 +617,13 @@ func (client *BitbucketServerClient) GetCommits(ctx context.Context, owner, repo
return commitsInfo, nil
}

func convertToBitbucketOptionsMap(listOptions GitCommitsQueryOptions) map[string]interface{} {
return map[string]interface{}{
"limit": listOptions.PerPage,
"start": (listOptions.Page - 1) * listOptions.PerPage,
}
}

// GetRepositoryInfo on Bitbucket server
func (client *BitbucketServerClient) GetRepositoryInfo(ctx context.Context, owner, repository string) (RepositoryInfo, error) {
if err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository}); err != nil {
Expand Down Expand Up @@ -767,10 +820,11 @@ func (client *BitbucketServerClient) mapBitbucketServerCommitToCommitInfo(commit
AuthorName: commit.Author.Name,
CommitterName: commit.Committer.Name,
Url: url,
Timestamp: commit.CommitterTimestamp,
Message: commit.Message,
ParentHashes: parents,
AuthorEmail: commit.Author.EmailAddress,
// Convert from bitbucket millisecond timestamp to CommitInfo seconds timestamp.
Timestamp: commit.CommitterTimestamp / 1000,
Message: commit.Message,
ParentHashes: parents,
AuthorEmail: commit.Author.EmailAddress,
}
}

Expand Down
131 changes: 127 additions & 4 deletions vcsclient/bitbucketserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ func TestBitbucketServer_GetLatestCommit(t *testing.T) {
AuthorName: "charlie",
CommitterName: "mark",
Url: expectedUrl,
Timestamp: 1548720847610,
Timestamp: 1548720847,
Message: "More work on feature 1",
ParentHashes: []string{"abcdef0123abcdef4567abcdef8987abcdef6543", "qwerty0123abcdef4567abcdef8987abcdef6543"},
AuthorEmail: "charlie@example.com",
Expand Down Expand Up @@ -371,7 +371,7 @@ func TestBitbucketServer_GetCommits(t *testing.T) {
AuthorName: "charlie",
CommitterName: "mark",
Url: expectedUrl,
Timestamp: 1548720847610,
Timestamp: 1548720847,
Message: "More work on feature 1",
ParentHashes: []string{"abcdef0123abcdef4567abcdef8987abcdef6543", "qwerty0123abcdef4567abcdef8987abcdef6543"},
AuthorEmail: "charlie@example.com",
Expand All @@ -381,7 +381,7 @@ func TestBitbucketServer_GetCommits(t *testing.T) {
AuthorName: "marly",
CommitterName: "marly",
Url: expectedUrl,
Timestamp: 1548720847610,
Timestamp: 1548720847,
Message: "More work on feature 2",
ParentHashes: []string{"abcdef0123abcdef4567abcdef8987abcdef6543", "qwerty0123abcdef4567abcdef8987abcdef6543"},
AuthorEmail: "marly@example.com",
Expand All @@ -391,6 +391,54 @@ func TestBitbucketServer_GetCommits(t *testing.T) {
assert.Error(t, err)
}

func TestBitbucketServer_GetCommitsWithQueryOptions(t *testing.T) {
ctx := context.Background()
response, err := os.ReadFile(filepath.Join("testdata", "bitbucketserver", "commit_list_response.json"))
assert.NoError(t, err)
client, serverUrl, cleanUp := createServerWithUrlAndClientReturningStatus(t, vcsutils.BitbucketServer, false,
response,
fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s/commits?limit=30&limit=30&start=0", owner, repo1),
http.StatusOK, createBitbucketServerHandler)
defer cleanUp()

options := GitCommitsQueryOptions{
Since: time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC),
ListOptions: ListOptions{
Page: 1,
PerPage: 30,
},
}

result, err := client.GetCommitsWithQueryOptions(ctx, owner, repo1, options)

assert.NoError(t, err)
expectedUrl := fmt.Sprintf("%s/projects/jfrog/repos/repo-1"+
"/commits/def0123abcdef4567abcdef8987abcdef6543abc", serverUrl)
assert.Equal(t, CommitInfo{
Hash: "def0123abcdef4567abcdef8987abcdef6543abc",
AuthorName: "charlie",
CommitterName: "mark",
Url: expectedUrl,
Timestamp: 1548720847,
Message: "More work on feature 1",
ParentHashes: []string{"abcdef0123abcdef4567abcdef8987abcdef6543", "qwerty0123abcdef4567abcdef8987abcdef6543"},
AuthorEmail: "charlie@example.com",
}, result[0])
assert.Equal(t, CommitInfo{
Hash: "def0123abcdef4567abcdef8987abcdef6543abc",
AuthorName: "marly",
CommitterName: "marly",
Url: expectedUrl,
Timestamp: 1548720847,
Message: "More work on feature 2",
ParentHashes: []string{"abcdef0123abcdef4567abcdef8987abcdef6543", "qwerty0123abcdef4567abcdef8987abcdef6543"},
AuthorEmail: "marly@example.com",
}, result[1])

_, err = createBadBitbucketServerClient(t).GetCommitsWithQueryOptions(ctx, owner, repo1, options)
assert.Error(t, err)
}

func TestBitbucketServer_GetLatestCommitNotFound(t *testing.T) {
ctx := context.Background()
response := []byte(`{
Expand Down Expand Up @@ -603,7 +651,7 @@ func TestBitbucketServer_GetCommitBySha(t *testing.T) {
AuthorName: "charlie",
CommitterName: "mark",
Url: expectedUrl,
Timestamp: 1636089306104,
Timestamp: 1636089306,
Message: "WIP on feature 1",
ParentHashes: []string{"bbcdef0123abcdef4567abcdef8987abcdef6543"},
AuthorEmail: "charlie@example.com",
Expand Down Expand Up @@ -882,3 +930,78 @@ func createBadBitbucketServerClient(t *testing.T) VcsClient {
assert.NoError(t, err)
return client
}

func TestGetCommitsInDateRate(t *testing.T) {
tests := []struct {
name string
commits []CommitInfo
options GitCommitsQueryOptions
expected []CommitInfo
}{
{
name: "All commits within range",
commits: []CommitInfo{
{Timestamp: 1717396600}, // Mon, 03 Jun 2024 09:56:40 GMT (Within range)
{Timestamp: 1717396500}, // Mon, 03 Jun 2024 09:55:00 GMT (Within range)
{Timestamp: 1717396400}, // Mon, 03 Jun 2024 09:53:20 GMT (Within range)
},
options: GitCommitsQueryOptions{
Since: time.Unix(1717396300, 0), // Mon, 03 Jun 2024 09:51:40 GMT (Set since timestamp in seconds)
},
expected: []CommitInfo{
{Timestamp: 1717396600},
{Timestamp: 1717396500},
{Timestamp: 1717396400},
},
},
{
name: "All commits within range or equal",
commits: []CommitInfo{
{Timestamp: 1717396600}, // Mon, 03 Jun 2024 09:56:40 GMT (Within range)
{Timestamp: 1717396500}, // Mon, 03 Jun 2024 09:55:00 GMT (Within range)
{Timestamp: 1717396400}, // Mon, 03 Jun 2024 09:53:20 GMT (Within range)
},
options: GitCommitsQueryOptions{
Since: time.Unix(1717396400, 0), // Mon, 03 Jun 2024 09:53:20 GMT (Set since timestamp in seconds)
},
expected: []CommitInfo{
{Timestamp: 1717396600},
{Timestamp: 1717396500},
{Timestamp: 1717396400},
},
},
{
name: "No commits within range",
commits: []CommitInfo{
{Timestamp: 1717396500}, // Mon, 03 Jun 2024 09:55:00 GMT (Older than range)
{Timestamp: 1717396400}, // Mon, 03 Jun 2024 09:53:20 GMT (Older than range)
},
options: GitCommitsQueryOptions{
Since: time.Unix(1717396600, 0), // Mon, 03 Jun 2024 09:56:40 GMT (Set since timestamp in seconds)
},
expected: []CommitInfo{},
},
{
name: "Partial commits within range",
commits: []CommitInfo{
{Timestamp: 1717396600}, // Mon, 03 Jun 2024 09:56:40 GMT (Within range)
{Timestamp: 1717396500}, // Mon, 03 Jun 2024 09:55:00 GMT (Within range)
{Timestamp: 1717396400}, // Mon, 03 Jun 2024 09:53:20 GMT (Older than range)
},
options: GitCommitsQueryOptions{
Since: time.Unix(1717396500, 0), // Mon, 03 Jun 2024 09:55:00 GMT (Set since timestamp in seconds)
},
expected: []CommitInfo{
{Timestamp: 1717396600},
{Timestamp: 1717396500},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := getCommitsInDateRate(tt.commits, tt.options)
assert.ElementsMatch(t, result, tt.expected)
})
}
}
41 changes: 35 additions & 6 deletions vcsclient/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"sort"
"strconv"
"strings"
"time"
)

const (
Expand Down Expand Up @@ -690,21 +691,49 @@ func (client *GitHubClient) GetCommits(ctx context.Context, owner, repository, b
var commitsInfo []CommitInfo
err = client.runWithRateLimitRetries(func() (*github.Response, error) {
var ghResponse *github.Response
commitsInfo, ghResponse, err = client.executeGetCommits(ctx, owner, repository, branch)
listOptions := &github.CommitsListOptions{
SHA: branch,
ListOptions: github.ListOptions{
Page: 1,
PerPage: vcsutils.NumberOfCommitsToFetch,
},
}
commitsInfo, ghResponse, err = client.executeGetCommits(ctx, owner, repository, listOptions)
return ghResponse, err
})
return commitsInfo, err
}

func (client *GitHubClient) executeGetCommits(ctx context.Context, owner, repository, branch string) ([]CommitInfo, *github.Response, error) {
listOptions := &github.CommitsListOptions{
SHA: branch,
// GetCommitsWithQueryOptions on GitHub
func (client *GitHubClient) GetCommitsWithQueryOptions(ctx context.Context, owner, repository string, listOptions GitCommitsQueryOptions) ([]CommitInfo, error) {
err := validateParametersNotBlank(map[string]string{
"owner": owner,
"repository": repository,
})
if err != nil {
return nil, err
}
var commitsInfo []CommitInfo
err = client.runWithRateLimitRetries(func() (*github.Response, error) {
var ghResponse *github.Response
commitsInfo, ghResponse, err = client.executeGetCommits(ctx, owner, repository, convertToGitHubCommitsListOptions(listOptions))
return ghResponse, err
})
return commitsInfo, err
}

func convertToGitHubCommitsListOptions(listOptions GitCommitsQueryOptions) *github.CommitsListOptions {
return &github.CommitsListOptions{
Since: listOptions.Since,
Until: time.Now(),
ListOptions: github.ListOptions{
Page: 1,
PerPage: vcsutils.NumberOfCommitsToFetch,
Page: listOptions.Page,
PerPage: listOptions.PerPage,
},
}
}

func (client *GitHubClient) executeGetCommits(ctx context.Context, owner, repository string, listOptions *github.CommitsListOptions) ([]CommitInfo, *github.Response, error) {
commits, ghResponse, err := client.ghClient.Repositories.ListCommits(ctx, owner, repository, listOptions)
if err != nil {
return nil, ghResponse, err
Expand Down
Loading

0 comments on commit 4523262

Please sign in to comment.