diff --git a/README.md b/README.md index 424d8e3..7c1ada5 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 diff --git a/vcsclient/azurerepos.go b/vcsclient/azurerepos.go index d9e36de..d65dd9f 100644 --- a/vcsclient/azurerepos.go +++ b/vcsclient/azurerepos.go @@ -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 @@ -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 { diff --git a/vcsclient/bitbucketcloud.go b/vcsclient/bitbucketcloud.go index a76efd1..8ad185e 100644 --- a/vcsclient/bitbucketcloud.go +++ b/vcsclient/bitbucketcloud.go @@ -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 { diff --git a/vcsclient/bitbucketcommon.go b/vcsclient/bitbucketcommon.go index 4897cc3..db15f6d 100644 --- a/vcsclient/bitbucketcommon.go +++ b/vcsclient/bitbucketcommon.go @@ -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) diff --git a/vcsclient/bitbucketserver.go b/vcsclient/bitbucketserver.go index 34239fd..47a11c4 100644 --- a/vcsclient/bitbucketserver.go +++ b/vcsclient/bitbucketserver.go @@ -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) @@ -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 { @@ -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, } } diff --git a/vcsclient/bitbucketserver_test.go b/vcsclient/bitbucketserver_test.go index 2bd2399..281543f 100644 --- a/vcsclient/bitbucketserver_test.go +++ b/vcsclient/bitbucketserver_test.go @@ -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", @@ -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", @@ -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", @@ -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(`{ @@ -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", @@ -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) + }) + } +} diff --git a/vcsclient/github.go b/vcsclient/github.go index f02cf3d..9c13c3e 100644 --- a/vcsclient/github.go +++ b/vcsclient/github.go @@ -19,6 +19,7 @@ import ( "sort" "strconv" "strings" + "time" ) const ( @@ -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 diff --git a/vcsclient/github_test.go b/vcsclient/github_test.go index 6112ab1..8f5749e 100644 --- a/vcsclient/github_test.go +++ b/vcsclient/github_test.go @@ -393,6 +393,49 @@ func TestGitHubClient_GetCommits(t *testing.T) { assert.Error(t, err) } +func TestGitHubClient_GetCommitsWithQueryOptions(t *testing.T) { + ctx := context.Background() + response, err := os.ReadFile(filepath.Join("testdata", "github", "commit_list_response.json")) + assert.NoError(t, err) + client, cleanUp := createServerAndClient(t, vcsutils.GitHub, false, response, + fmt.Sprintf("/repos/%s/%s/commits?page=1&per_page=30&since=2021-01-01T00%%3A00%%3A00Z&until=", owner, repo1), createGitHubHandlerForUnknownUrl) + defer cleanUp() + + options := GitCommitsQueryOptions{ + Since: time.Date(2021, 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) + assert.Equal(t, CommitInfo{ + Hash: "6dcb09b5b57875f334f61aebed695e2e4193db5e", + AuthorName: "Monalisa Octocat", + CommitterName: "Joconde Octocat", + Url: "https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e", + Timestamp: 1302796850, + Message: "Fix all the bugs", + ParentHashes: []string{"6dcb09b5b57875f334f61aebed695e2e4193db5e"}, + AuthorEmail: "support@github.com", + }, result[0]) + assert.Equal(t, CommitInfo{ + Hash: "6dcb09b5b57875f334f61aebed695e2e4193db5e", + AuthorName: "Leonardo De Vinci", + CommitterName: "Leonardo De Vinci", + Url: "https://api.github.com/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e", + Timestamp: 1302796850, + Message: "Fix all the bugs", + ParentHashes: []string{"6dcb09b5b57875f334f61aebed695e2e4193db5e"}, + AuthorEmail: "vinci@github.com", + }, result[1]) + + _, err = createBadGitHubClient(t).GetCommitsWithQueryOptions(ctx, owner, repo1, options) + assert.Error(t, err) +} + func TestGitHubClient_GetLatestCommitNotFound(t *testing.T) { ctx := context.Background() response := []byte(`{ @@ -1009,6 +1052,22 @@ func createGitHubHandler(t *testing.T, expectedURI string, response []byte, expe } } +// Similar to createGitHubHandler but without checking if the expectedURI is equal to the request URI, only if it contained in the request URI. +func createGitHubHandlerForUnknownUrl(t *testing.T, expectedURI string, response []byte, expectedStatusCode int) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + assert.Contains(t, r.RequestURI, expectedURI) + assert.Equal(t, "Bearer "+token, r.Header.Get("Authorization")) + if strings.Contains(r.RequestURI, "tarball") { + w.Header().Add("Location", string(response)) + w.WriteHeader(expectedStatusCode) + return + } + w.WriteHeader(expectedStatusCode) + _, err := w.Write(response) + assert.NoError(t, err) + } +} + func createGitHubHandlerWithoutExpectedURI(t *testing.T, _ string, response []byte, expectedStatusCode int) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "Bearer "+token, r.Header.Get("Authorization")) diff --git a/vcsclient/gitlab.go b/vcsclient/gitlab.go index 48b5a76..9be4703 100644 --- a/vcsclient/gitlab.go +++ b/vcsclient/gitlab.go @@ -13,6 +13,7 @@ import ( "sort" "strconv" "strings" + "time" ) // GitLabClient API version 4 @@ -532,8 +533,35 @@ func (client *GitLabClient) GetCommits(ctx context.Context, owner, repository, b PerPage: vcsutils.NumberOfCommitsToFetch, }, } + return client.getCommitsWithQueryOptions(ctx, owner, repository, listOptions) +} + +func (client *GitLabClient) 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 + } + + return client.getCommitsWithQueryOptions(ctx, owner, repository, convertToListCommitsOptions(listOptions)) +} + +func convertToListCommitsOptions(options GitCommitsQueryOptions) *gitlab.ListCommitsOptions { + t := time.Now() + return &gitlab.ListCommitsOptions{ + ListOptions: gitlab.ListOptions{ + Page: options.Page, + PerPage: options.PerPage, + }, + Since: &options.Since, + Until: &t, + } +} - commits, _, err := client.glClient.Commits.ListCommits(getProjectID(owner, repository), listOptions, gitlab.WithContext(ctx)) +func (client *GitLabClient) getCommitsWithQueryOptions(ctx context.Context, owner, repository string, options *gitlab.ListCommitsOptions) ([]CommitInfo, error) { + commits, _, err := client.glClient.Commits.ListCommits(getProjectID(owner, repository), options, gitlab.WithContext(ctx)) if err != nil { return nil, err } diff --git a/vcsclient/gitlab_test.go b/vcsclient/gitlab_test.go index 16764a8..2084f4d 100644 --- a/vcsclient/gitlab_test.go +++ b/vcsclient/gitlab_test.go @@ -394,6 +394,48 @@ func TestGitLabClient_GetCommits(t *testing.T) { }, result[1]) } +func TestGitLabClient_GetCommitsWithQueryOptions(t *testing.T) { + ctx := context.Background() + response, err := os.ReadFile(filepath.Join("testdata", "gitlab", "commit_list_response.json")) + assert.NoError(t, err) + client, cleanUp := createServerAndClient(t, vcsutils.GitLab, false, response, + fmt.Sprintf("/api/v4/projects/%s/repository/commits?page=1&per_page=30&since=2021-01-01T00%%3A00%%3A00Z&until=", + url.PathEscape(owner+"/"+repo1)), createGitLabHandlerForUnknownUrl) + defer cleanUp() + + options := GitCommitsQueryOptions{ + Since: time.Date(2021, 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) + assert.Equal(t, CommitInfo{ + Hash: "ed899a2f4b50b4370feeea94676502b42383c746", + AuthorName: "Example User", + CommitterName: "Administrator", + Url: "https://gitlab.example.com/thedude/gitlab-foss/-/commit/ed899a2f4b50b4370feeea94676502b42383c746", + Timestamp: 1348131022, + Message: "Replace sanitize with escape once", + ParentHashes: []string{"6104942438c14ec7bd21c6cd5bd995272b3faff6"}, + AuthorEmail: "user@example.com", + }, result[0]) + assert.Equal(t, CommitInfo{ + Hash: "6104942438c14ec7bd21c6cd5bd995272b3faff6", + AuthorName: "randx", + CommitterName: "ExampleName", + Url: "https://gitlab.example.com/thedude/gitlab-foss/-/commit/ed899a2f4b50b4370feeea94676502b42383c746", + Timestamp: 1348131022, + Message: "Sanitize for network graph", + ParentHashes: []string{"ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba"}, + AuthorEmail: "user@example.com", + }, result[1]) +} + func TestGitLabClient_GetLatestCommitNotFound(t *testing.T) { ctx := context.Background() response := []byte(`{ @@ -712,6 +754,21 @@ func createGitLabHandler(t *testing.T, expectedURI string, response []byte, expe } } +// Similar to createGitLabHandler but without checking if the expectedURI is equal to the request URI, only if it contained in the request URI. +func createGitLabHandlerForUnknownUrl(t *testing.T, expectedURI string, response []byte, expectedStatusCode int) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/api/v4/" { + w.WriteHeader(http.StatusOK) + return + } + w.WriteHeader(expectedStatusCode) + _, err := w.Write(response) + assert.NoError(t, err) + assert.Contains(t, r.RequestURI, expectedURI) + assert.Equal(t, token, r.Header.Get("Private-Token")) + } +} + func createGitLabHandlerWithoutExpectedURI(t *testing.T, _ string, response []byte, expectedStatusCode int) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.RequestURI == "/api/v4/" { diff --git a/vcsclient/vcsclient.go b/vcsclient/vcsclient.go index 4b6b9a6..7830d50 100644 --- a/vcsclient/vcsclient.go +++ b/vcsclient/vcsclient.go @@ -225,6 +225,12 @@ type VcsClient interface { // branch - The name of the branch GetCommits(ctx context.Context, owner, repository, branch string) ([]CommitInfo, error) + // GetCommitsWithQueryOptions Gets repository commits considering GitCommitsQueryOptions provided by the user. + // owner - User or organization + // repository - VCS repository name + // listOptions - Optional parameters for the 'ListCommits' method + GetCommitsWithQueryOptions(ctx context.Context, owner, repository string, options GitCommitsQueryOptions) ([]CommitInfo, error) + // AddSshKeyToRepository Adds a public ssh key to a repository // owner - User or organization // repository - VCS repository name @@ -399,6 +405,21 @@ type LabelInfo struct { Color string } +// GitCommitsQueryOptions specifies the optional parameters fot the commit list. +type GitCommitsQueryOptions struct { + // Since when should Commits be included in the response. + Since time.Time + ListOptions +} + +// ListOptions specifies the optional parameters to various List methods that support offset pagination. +type ListOptions struct { + // For paginated result sets, page of results to retrieve. + Page int + // For paginated result sets, the number of results to include per page. + PerPage int +} + func validateParametersNotBlank(paramNameValueMap map[string]string) error { var errorMessages []string for k, v := range paramNameValueMap {