diff --git a/app/models/repository.rb b/app/models/repository.rb index 0c6adaa1f60dd12762fe848cb0f5bd698c055956..16d270b57113d67277889c06b73f722d56032d7c 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -767,8 +767,8 @@ def tags_sorted_by(value, pagination_params = nil) # # order_by: name|email|commits # sort: asc|desc default: 'asc' - def contributors(order_by: nil, sort: 'asc') - commits = self.commits(nil, limit: 2000, offset: 0, skip_merges: true) + def contributors(ref: nil, order_by: nil, sort: 'asc') + commits = self.commits(ref, limit: 2000, offset: 0, skip_merges: true) commits = commits.group_by(&:author_email).map do |email, commits| contributor = Gitlab::Contributor.new diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 2ff8fae99ae9c18fc4054558b4ea11d61b29d880..fc7f9e903237f377c5b5c93bc89f74436c12dca7 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -232,6 +232,8 @@ Example response: ## Contributors +> - `ref` [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/156852) in GitLab 17.4. + Get repository contributors list. This endpoint can be accessed without authentication if the repository is publicly accessible. @@ -246,9 +248,17 @@ Supported attributes: | Attribute | Type | Required | Description | | :--------- | :------------- | :------- | :---------- | | `id` | integer or string | yes | The ID or [URL-encoded path of the project](rest/index.md#namespaced-path-encoding) owned by the authenticated user. | +| `ref` | string | no | The name of a repository branch or tag. If not given, the default branch. | | `order_by` | string | no | Return contributors ordered by `name`, `email`, or `commits` (orders by commit date) fields. Default is `commits`. | | `sort` | string | no | Return contributors sorted in `asc` or `desc` order. Default is `asc`. | +Example request: + +```shell +curl --header "PRIVATE-TOKEN: " \ + --url "https://gitlab.example.com/api/v4/projects/7/repository/contributors" +``` + Example response: ```json diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 359477578a817c980a482ac061cad55128edac01..2d3e13edcdffc08d87f6b95bcb9f799d895c6a53 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -234,11 +234,14 @@ def rescue_not_found? end params do use :pagination + optional :ref, type: String, + desc: 'The name of a repository branch or tag, if not given the default branch is used', + documentation: { example: 'main' } optional :order_by, type: String, values: %w[email name commits], default: 'commits', desc: 'Return contributors ordered by `name` or `email` or `commits`' optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)' end get ':id/repository/contributors' do - contributors = ::Kaminari.paginate_array(user_project.repository.contributors(order_by: params[:order_by], sort: params[:sort])) + contributors = ::Kaminari.paginate_array(user_project.repository.contributors(ref: params[:ref], order_by: params[:order_by], sort: params[:sort])) present paginate(contributors), with: Entities::Contributor rescue StandardError not_found! diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 3c82a7d5ef9c5f9156d91f2f252bfd227bef4158..c9d56ffd13f97e89105aec4a4e9d48fffd692b9c 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -3386,12 +3386,13 @@ def create_remote_branch(remote_name, branch_name, target) let(:order_by) { nil } let(:sort) { nil } + let(:ref) { nil } before do - allow(repository).to receive(:commits).with(nil, limit: 2000, offset: 0, skip_merges: true).and_return(stubbed_commits) + allow(repository).to receive(:commits).with(ref, limit: 2000, offset: 0, skip_merges: true).and_return(stubbed_commits) end - subject { repository.contributors(order_by: order_by, sort: sort) } + subject { repository.contributors(ref: ref, order_by: order_by, sort: sort) } def expect_contributors(*contributors) expect(subject.map(&:email)).to eq(contributors.map(&:email)) @@ -3477,6 +3478,24 @@ def expect_contributors(*contributors) expect_contributors(author_a, author_b, author_c) end end + + context 'when passing a ref param' do + let(:ref) { 'ref' } + let(:author_d) { build(:author, email: 'johndoe@gitlab.com', name: 'John Doe') } + let(:stubbed_commits) do + [build(:commit, author: author_a), + build(:commit, author: author_a), + build(:commit, author: author_b), + build(:commit, author: author_c), + build(:commit, author: author_c), + build(:commit, author: author_c), + build(:commit, author: author_d)] + end + + it 'returns the contributors for ref' do + expect_contributors(author_a, author_b, author_c, author_d) + end + end end describe '#merge_base' do diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index f38a120cc740e0820b1127cb674abd79acf58061..70313d834704483fcb06bde4e3bc8f019971e88f 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -675,9 +675,39 @@ def commit_messages(response) expect(first_contributor['deletions']).to eq(0) end + context 'using ref' do + new_branch_name = 'feature-test' + let(:user) { create(:user, name: "johndoe", email: "johndoe@example.com") } + + before do + project.repository.add_branch(user, new_branch_name, 'master') + project.repository.commit_files( + user, + branch_name: new_branch_name, + message: 'Message', + actions: [{ action: :create, file_path: 'a/new.file', content: 'This is a new file' }] + ) + end + + it 'returns valid data for the ref' do + get api(route, current_user), params: { ref: new_branch_name } + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + + first_contributor = json_response.first + expect(first_contributor['email']).to eq('johndoe@example.com') + expect(first_contributor['name']).to eq('johndoe') + expect(first_contributor['commits']).to eq(1) + expect(first_contributor['additions']).to eq(0) + expect(first_contributor['deletions']).to eq(0) + end + end + context 'using sorting' do context 'by commits desc' do - it 'returns the repository contribuors sorted by commits desc' do + it 'returns the repository contributors sorted by commits desc' do get api(route, current_user), params: { order_by: 'commits', sort: 'desc' } expect(response).to have_gitlab_http_status(:ok) @@ -687,7 +717,7 @@ def commit_messages(response) end context 'by name desc' do - it 'returns the repository contribuors sorted by name asc case insensitive' do + it 'returns the repository contributors sorted by name asc case insensitive' do get api(route, current_user), params: { order_by: 'name', sort: 'asc' } expect(response).to have_gitlab_http_status(:ok)