Skip to content

Commit 4523262

Browse files
authored
Add "GetCommitsWithQueryOptions" new API. (#135)
1 parent 8b10260 commit 4523262

11 files changed

+422
-15
lines changed

README.md

+24
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ Currently supported providers are: [GitHub](#github), [Bitbucket Server](#bitbuc
5151
- [Delete Pull Request Comment](#delete-pull-request-comment)
5252
- [Delete Pull Request Review Comments](#delete-pull-request-review-comments)
5353
- [Get Commits](#get-commits)
54+
- [Get Commits With Options](#get-commits-with-options)
5455
- [Get Latest Commit](#get-latest-commit)
5556
- [Get Commit By SHA](#get-commit-by-sha)
5657
- [Get List of Modified Files](#get-list-of-modified-files)
@@ -544,6 +545,29 @@ branch := "dev"
544545
commitInfo, err := client.GetCommits(ctx, owner, repository, branch)
545546
```
546547

548+
#### Get Commits With Options
549+
550+
```go
551+
// Go context
552+
ctx := context.Background()
553+
// Organization or username
554+
owner := "jfrog"
555+
// VCS repository
556+
repository := "jfrog-cli"
557+
558+
// Commits query options
559+
options := GitCommitsQueryOptions{
560+
Since: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
561+
Until: time.Now(),
562+
ListOptions: ListOptions{
563+
Page: 1,
564+
PerPage: 30,
565+
},
566+
}
567+
568+
result, err := client.GetCommitsWithQueryOptions(ctx, owner, repository, options)
569+
```
570+
547571
#### Get Latest Commit
548572

549573
```go

vcsclient/azurerepos.go

+7
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ import (
2020
)
2121

2222
const (
23+
notSupportedOnAzure = "currently not supported on Azure"
2324
defaultAzureBaseUrl = "https://dev.azure.com/"
2425
azurePullRequestDetailsSizeLimit = 4000
2526
azurePullRequestCommentSizeLimit = 150000
2627
)
2728

29+
var errAzureGetCommitsWithOptionsNotSupported = fmt.Errorf("get commits with options is %s", notSupportedOnAzure)
30+
2831
// Azure Devops API version 6
2932
type AzureReposClient struct {
3033
vcsInfo VcsInfo
@@ -425,6 +428,10 @@ func (client *AzureReposClient) GetCommits(ctx context.Context, _, repository, b
425428
return commitsInfo, nil
426429
}
427430

431+
func (client *AzureReposClient) GetCommitsWithQueryOptions(ctx context.Context, _, repository string, listOptions GitCommitsQueryOptions) ([]CommitInfo, error) {
432+
return nil, errAzureGetCommitsWithOptionsNotSupported
433+
}
434+
428435
func mapAzureReposCommitsToCommitInfo(commit git.GitCommitRef) CommitInfo {
429436
var authorName, authorEmail string
430437
if commit.Author != nil {

vcsclient/bitbucketcloud.go

+4
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,10 @@ func (client *BitbucketCloudClient) GetCommits(_ context.Context, _, _, _ string
497497
return nil, errBitbucketGetCommitsNotSupported
498498
}
499499

500+
func (client *BitbucketCloudClient) GetCommitsWithQueryOptions(ctx context.Context, owner, repository string, listOptions GitCommitsQueryOptions) ([]CommitInfo, error) {
501+
return nil, errBitbucketGetCommitsWithOptionsNotSupported
502+
}
503+
500504
// GetRepositoryInfo on Bitbucket cloud
501505
func (client *BitbucketCloudClient) GetRepositoryInfo(ctx context.Context, owner, repository string) (RepositoryInfo, error) {
502506
if err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository}); err != nil {

vcsclient/bitbucketcommon.go

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ var (
1717
errBitbucketCodeScanningNotSupported = fmt.Errorf("code scanning is %s", notSupportedOnBitbucket)
1818
errBitbucketDownloadFileFromRepoNotSupported = fmt.Errorf("download file from repo is %s", notSupportedOnBitbucket)
1919
errBitbucketGetCommitsNotSupported = fmt.Errorf("get commits is %s", notSupportedOnBitbucket)
20+
errBitbucketGetCommitsWithOptionsNotSupported = fmt.Errorf("get commits with options is %s", notSupportedOnBitbucket)
2021
errBitbucketGetRepoEnvironmentInfoNotSupported = fmt.Errorf("get repository environment info is %s", notSupportedOnBitbucket)
2122
errBitbucketListPullRequestReviewCommentsNotSupported = fmt.Errorf("list pull request review comments is %s", notSupportedOnBitbucket)
2223
errBitbucketAddPullRequestReviewCommentsNotSupported = fmt.Errorf("add pull request review comment is %s", notSupportedOnBitbucket)

vcsclient/bitbucketserver.go

+58-4
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,52 @@ func (client *BitbucketServerClient) GetCommits(ctx context.Context, owner, repo
553553
"limit": vcsutils.NumberOfCommitsToFetch,
554554
"until": branch,
555555
}
556+
return client.getCommitsWithQueryOptions(ctx, owner, repository, options)
557+
}
558+
559+
func (client *BitbucketServerClient) GetCommitsWithQueryOptions(ctx context.Context, owner, repository string, listOptions GitCommitsQueryOptions) ([]CommitInfo, error) {
560+
err := validateParametersNotBlank(map[string]string{
561+
"owner": owner,
562+
"repository": repository,
563+
})
564+
if err != nil {
565+
return nil, err
566+
}
567+
commits, err := client.getCommitsWithQueryOptions(ctx, owner, repository, convertToBitbucketOptionsMap(listOptions))
568+
if err != nil {
569+
return nil, err
570+
}
571+
return getCommitsInDateRate(commits, listOptions), nil
572+
}
573+
574+
// Bitbucket doesn't support filtering by date, so we need to filter the commits by date ourselves.
575+
func getCommitsInDateRate(commits []CommitInfo, options GitCommitsQueryOptions) []CommitInfo {
576+
commitsNumber := len(commits)
577+
if commitsNumber == 0 {
578+
return commits
579+
}
580+
581+
firstCommit := time.Unix(commits[0].Timestamp, 0).UTC()
582+
lastCommit := time.Unix(commits[commitsNumber-1].Timestamp, 0).UTC()
583+
584+
// If all commits are in the range return all.
585+
if lastCommit.After(options.Since) || lastCommit.Equal(options.Since) {
586+
return commits
587+
}
588+
// If the first commit is older than the "since" timestamp, all commits are out of range, return an empty list.
589+
if firstCommit.Before(options.Since) {
590+
return []CommitInfo{}
591+
}
592+
// Find the first commit that is older than the "since" timestamp.
593+
for i, commit := range commits {
594+
if time.Unix(commit.Timestamp, 0).UTC().Before(options.Since) {
595+
return commits[:i]
596+
}
597+
}
598+
return []CommitInfo{}
599+
}
600+
601+
func (client *BitbucketServerClient) getCommitsWithQueryOptions(ctx context.Context, owner, repository string, options map[string]interface{}) ([]CommitInfo, error) {
556602
bitbucketClient := client.buildBitbucketClient(ctx)
557603

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

620+
func convertToBitbucketOptionsMap(listOptions GitCommitsQueryOptions) map[string]interface{} {
621+
return map[string]interface{}{
622+
"limit": listOptions.PerPage,
623+
"start": (listOptions.Page - 1) * listOptions.PerPage,
624+
}
625+
}
626+
574627
// GetRepositoryInfo on Bitbucket server
575628
func (client *BitbucketServerClient) GetRepositoryInfo(ctx context.Context, owner, repository string) (RepositoryInfo, error) {
576629
if err := validateParametersNotBlank(map[string]string{"owner": owner, "repository": repository}); err != nil {
@@ -767,10 +820,11 @@ func (client *BitbucketServerClient) mapBitbucketServerCommitToCommitInfo(commit
767820
AuthorName: commit.Author.Name,
768821
CommitterName: commit.Committer.Name,
769822
Url: url,
770-
Timestamp: commit.CommitterTimestamp,
771-
Message: commit.Message,
772-
ParentHashes: parents,
773-
AuthorEmail: commit.Author.EmailAddress,
823+
// Convert from bitbucket millisecond timestamp to CommitInfo seconds timestamp.
824+
Timestamp: commit.CommitterTimestamp / 1000,
825+
Message: commit.Message,
826+
ParentHashes: parents,
827+
AuthorEmail: commit.Author.EmailAddress,
774828
}
775829
}
776830

vcsclient/bitbucketserver_test.go

+127-4
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ func TestBitbucketServer_GetLatestCommit(t *testing.T) {
340340
AuthorName: "charlie",
341341
CommitterName: "mark",
342342
Url: expectedUrl,
343-
Timestamp: 1548720847610,
343+
Timestamp: 1548720847,
344344
Message: "More work on feature 1",
345345
ParentHashes: []string{"abcdef0123abcdef4567abcdef8987abcdef6543", "qwerty0123abcdef4567abcdef8987abcdef6543"},
346346
AuthorEmail: "[email protected]",
@@ -371,7 +371,7 @@ func TestBitbucketServer_GetCommits(t *testing.T) {
371371
AuthorName: "charlie",
372372
CommitterName: "mark",
373373
Url: expectedUrl,
374-
Timestamp: 1548720847610,
374+
Timestamp: 1548720847,
375375
Message: "More work on feature 1",
376376
ParentHashes: []string{"abcdef0123abcdef4567abcdef8987abcdef6543", "qwerty0123abcdef4567abcdef8987abcdef6543"},
377377
AuthorEmail: "[email protected]",
@@ -381,7 +381,7 @@ func TestBitbucketServer_GetCommits(t *testing.T) {
381381
AuthorName: "marly",
382382
CommitterName: "marly",
383383
Url: expectedUrl,
384-
Timestamp: 1548720847610,
384+
Timestamp: 1548720847,
385385
Message: "More work on feature 2",
386386
ParentHashes: []string{"abcdef0123abcdef4567abcdef8987abcdef6543", "qwerty0123abcdef4567abcdef8987abcdef6543"},
387387
AuthorEmail: "[email protected]",
@@ -391,6 +391,54 @@ func TestBitbucketServer_GetCommits(t *testing.T) {
391391
assert.Error(t, err)
392392
}
393393

394+
func TestBitbucketServer_GetCommitsWithQueryOptions(t *testing.T) {
395+
ctx := context.Background()
396+
response, err := os.ReadFile(filepath.Join("testdata", "bitbucketserver", "commit_list_response.json"))
397+
assert.NoError(t, err)
398+
client, serverUrl, cleanUp := createServerWithUrlAndClientReturningStatus(t, vcsutils.BitbucketServer, false,
399+
response,
400+
fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s/commits?limit=30&limit=30&start=0", owner, repo1),
401+
http.StatusOK, createBitbucketServerHandler)
402+
defer cleanUp()
403+
404+
options := GitCommitsQueryOptions{
405+
Since: time.Date(2017, 1, 1, 0, 0, 0, 0, time.UTC),
406+
ListOptions: ListOptions{
407+
Page: 1,
408+
PerPage: 30,
409+
},
410+
}
411+
412+
result, err := client.GetCommitsWithQueryOptions(ctx, owner, repo1, options)
413+
414+
assert.NoError(t, err)
415+
expectedUrl := fmt.Sprintf("%s/projects/jfrog/repos/repo-1"+
416+
"/commits/def0123abcdef4567abcdef8987abcdef6543abc", serverUrl)
417+
assert.Equal(t, CommitInfo{
418+
Hash: "def0123abcdef4567abcdef8987abcdef6543abc",
419+
AuthorName: "charlie",
420+
CommitterName: "mark",
421+
Url: expectedUrl,
422+
Timestamp: 1548720847,
423+
Message: "More work on feature 1",
424+
ParentHashes: []string{"abcdef0123abcdef4567abcdef8987abcdef6543", "qwerty0123abcdef4567abcdef8987abcdef6543"},
425+
AuthorEmail: "[email protected]",
426+
}, result[0])
427+
assert.Equal(t, CommitInfo{
428+
Hash: "def0123abcdef4567abcdef8987abcdef6543abc",
429+
AuthorName: "marly",
430+
CommitterName: "marly",
431+
Url: expectedUrl,
432+
Timestamp: 1548720847,
433+
Message: "More work on feature 2",
434+
ParentHashes: []string{"abcdef0123abcdef4567abcdef8987abcdef6543", "qwerty0123abcdef4567abcdef8987abcdef6543"},
435+
AuthorEmail: "[email protected]",
436+
}, result[1])
437+
438+
_, err = createBadBitbucketServerClient(t).GetCommitsWithQueryOptions(ctx, owner, repo1, options)
439+
assert.Error(t, err)
440+
}
441+
394442
func TestBitbucketServer_GetLatestCommitNotFound(t *testing.T) {
395443
ctx := context.Background()
396444
response := []byte(`{
@@ -603,7 +651,7 @@ func TestBitbucketServer_GetCommitBySha(t *testing.T) {
603651
AuthorName: "charlie",
604652
CommitterName: "mark",
605653
Url: expectedUrl,
606-
Timestamp: 1636089306104,
654+
Timestamp: 1636089306,
607655
Message: "WIP on feature 1",
608656
ParentHashes: []string{"bbcdef0123abcdef4567abcdef8987abcdef6543"},
609657
AuthorEmail: "[email protected]",
@@ -882,3 +930,78 @@ func createBadBitbucketServerClient(t *testing.T) VcsClient {
882930
assert.NoError(t, err)
883931
return client
884932
}
933+
934+
func TestGetCommitsInDateRate(t *testing.T) {
935+
tests := []struct {
936+
name string
937+
commits []CommitInfo
938+
options GitCommitsQueryOptions
939+
expected []CommitInfo
940+
}{
941+
{
942+
name: "All commits within range",
943+
commits: []CommitInfo{
944+
{Timestamp: 1717396600}, // Mon, 03 Jun 2024 09:56:40 GMT (Within range)
945+
{Timestamp: 1717396500}, // Mon, 03 Jun 2024 09:55:00 GMT (Within range)
946+
{Timestamp: 1717396400}, // Mon, 03 Jun 2024 09:53:20 GMT (Within range)
947+
},
948+
options: GitCommitsQueryOptions{
949+
Since: time.Unix(1717396300, 0), // Mon, 03 Jun 2024 09:51:40 GMT (Set since timestamp in seconds)
950+
},
951+
expected: []CommitInfo{
952+
{Timestamp: 1717396600},
953+
{Timestamp: 1717396500},
954+
{Timestamp: 1717396400},
955+
},
956+
},
957+
{
958+
name: "All commits within range or equal",
959+
commits: []CommitInfo{
960+
{Timestamp: 1717396600}, // Mon, 03 Jun 2024 09:56:40 GMT (Within range)
961+
{Timestamp: 1717396500}, // Mon, 03 Jun 2024 09:55:00 GMT (Within range)
962+
{Timestamp: 1717396400}, // Mon, 03 Jun 2024 09:53:20 GMT (Within range)
963+
},
964+
options: GitCommitsQueryOptions{
965+
Since: time.Unix(1717396400, 0), // Mon, 03 Jun 2024 09:53:20 GMT (Set since timestamp in seconds)
966+
},
967+
expected: []CommitInfo{
968+
{Timestamp: 1717396600},
969+
{Timestamp: 1717396500},
970+
{Timestamp: 1717396400},
971+
},
972+
},
973+
{
974+
name: "No commits within range",
975+
commits: []CommitInfo{
976+
{Timestamp: 1717396500}, // Mon, 03 Jun 2024 09:55:00 GMT (Older than range)
977+
{Timestamp: 1717396400}, // Mon, 03 Jun 2024 09:53:20 GMT (Older than range)
978+
},
979+
options: GitCommitsQueryOptions{
980+
Since: time.Unix(1717396600, 0), // Mon, 03 Jun 2024 09:56:40 GMT (Set since timestamp in seconds)
981+
},
982+
expected: []CommitInfo{},
983+
},
984+
{
985+
name: "Partial commits within range",
986+
commits: []CommitInfo{
987+
{Timestamp: 1717396600}, // Mon, 03 Jun 2024 09:56:40 GMT (Within range)
988+
{Timestamp: 1717396500}, // Mon, 03 Jun 2024 09:55:00 GMT (Within range)
989+
{Timestamp: 1717396400}, // Mon, 03 Jun 2024 09:53:20 GMT (Older than range)
990+
},
991+
options: GitCommitsQueryOptions{
992+
Since: time.Unix(1717396500, 0), // Mon, 03 Jun 2024 09:55:00 GMT (Set since timestamp in seconds)
993+
},
994+
expected: []CommitInfo{
995+
{Timestamp: 1717396600},
996+
{Timestamp: 1717396500},
997+
},
998+
},
999+
}
1000+
1001+
for _, tt := range tests {
1002+
t.Run(tt.name, func(t *testing.T) {
1003+
result := getCommitsInDateRate(tt.commits, tt.options)
1004+
assert.ElementsMatch(t, result, tt.expected)
1005+
})
1006+
}
1007+
}

vcsclient/github.go

+35-6
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"sort"
2020
"strconv"
2121
"strings"
22+
"time"
2223
)
2324

2425
const (
@@ -690,21 +691,49 @@ func (client *GitHubClient) GetCommits(ctx context.Context, owner, repository, b
690691
var commitsInfo []CommitInfo
691692
err = client.runWithRateLimitRetries(func() (*github.Response, error) {
692693
var ghResponse *github.Response
693-
commitsInfo, ghResponse, err = client.executeGetCommits(ctx, owner, repository, branch)
694+
listOptions := &github.CommitsListOptions{
695+
SHA: branch,
696+
ListOptions: github.ListOptions{
697+
Page: 1,
698+
PerPage: vcsutils.NumberOfCommitsToFetch,
699+
},
700+
}
701+
commitsInfo, ghResponse, err = client.executeGetCommits(ctx, owner, repository, listOptions)
694702
return ghResponse, err
695703
})
696704
return commitsInfo, err
697705
}
698706

699-
func (client *GitHubClient) executeGetCommits(ctx context.Context, owner, repository, branch string) ([]CommitInfo, *github.Response, error) {
700-
listOptions := &github.CommitsListOptions{
701-
SHA: branch,
707+
// GetCommitsWithQueryOptions on GitHub
708+
func (client *GitHubClient) GetCommitsWithQueryOptions(ctx context.Context, owner, repository string, listOptions GitCommitsQueryOptions) ([]CommitInfo, error) {
709+
err := validateParametersNotBlank(map[string]string{
710+
"owner": owner,
711+
"repository": repository,
712+
})
713+
if err != nil {
714+
return nil, err
715+
}
716+
var commitsInfo []CommitInfo
717+
err = client.runWithRateLimitRetries(func() (*github.Response, error) {
718+
var ghResponse *github.Response
719+
commitsInfo, ghResponse, err = client.executeGetCommits(ctx, owner, repository, convertToGitHubCommitsListOptions(listOptions))
720+
return ghResponse, err
721+
})
722+
return commitsInfo, err
723+
}
724+
725+
func convertToGitHubCommitsListOptions(listOptions GitCommitsQueryOptions) *github.CommitsListOptions {
726+
return &github.CommitsListOptions{
727+
Since: listOptions.Since,
728+
Until: time.Now(),
702729
ListOptions: github.ListOptions{
703-
Page: 1,
704-
PerPage: vcsutils.NumberOfCommitsToFetch,
730+
Page: listOptions.Page,
731+
PerPage: listOptions.PerPage,
705732
},
706733
}
734+
}
707735

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

0 commit comments

Comments
 (0)