Commit de454de9 authored by Douwe Maan's avatar Douwe Maan

Merge branch '42434-allow-commits-endpoint-to-work-over-all-commits' into 'master'

Resolve "Allow API method /projects/:id/repository/commits  to work over all commits"

Closes #42434

See merge request gitlab-org/gitlab-ce!17182
parents 46196026 cd9daf64
...@@ -139,7 +139,7 @@ class Repository ...@@ -139,7 +139,7 @@ class Repository
end end
end end
def commits(ref, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil) def commits(ref = nil, path: nil, limit: nil, offset: nil, skip_merges: false, after: nil, before: nil, all: nil)
options = { options = {
repo: raw_repository, repo: raw_repository,
ref: ref, ref: ref,
...@@ -149,7 +149,8 @@ class Repository ...@@ -149,7 +149,8 @@ class Repository
after: after, after: after,
before: before, before: before,
follow: Array(path).length == 1, follow: Array(path).length == 1,
skip_merges: skip_merges skip_merges: skip_merges,
all: all
} }
commits = Gitlab::Git::Commit.where(options) commits = Gitlab::Git::Commit.where(options)
......
---
title: Allow commits endpoint to work over all commits of a repository
merge_request: 17182
author:
type: added
...@@ -14,6 +14,9 @@ GET /projects/:id/repository/commits ...@@ -14,6 +14,9 @@ GET /projects/:id/repository/commits
| `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch | | `ref_name` | string | no | The name of a repository branch or tag or if not given the default branch |
| `since` | string | no | Only commits after or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | | `since` | string | no | Only commits after or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
| `until` | string | no | Only commits before or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ | | `until` | string | no | Only commits before or on this date will be returned in ISO 8601 format YYYY-MM-DDTHH:MM:SSZ |
| `path` | string | no | The file path |
| `all` | boolean | no | Retrieve every commit from the repository |
```bash ```bash
curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits" curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/projects/5/repository/commits"
......
...@@ -18,25 +18,28 @@ module API ...@@ -18,25 +18,28 @@ module API
optional :since, type: DateTime, desc: 'Only commits after or on this date will be returned' optional :since, type: DateTime, desc: 'Only commits after or on this date will be returned'
optional :until, type: DateTime, desc: 'Only commits before or on this date will be returned' optional :until, type: DateTime, desc: 'Only commits before or on this date will be returned'
optional :path, type: String, desc: 'The file path' optional :path, type: String, desc: 'The file path'
optional :all, type: Boolean, desc: 'Every commit will be returned'
use :pagination use :pagination
end end
get ':id/repository/commits' do get ':id/repository/commits' do
path = params[:path] path = params[:path]
before = params[:until] before = params[:until]
after = params[:since] after = params[:since]
ref = params[:ref_name] || user_project.try(:default_branch) || 'master' ref = params[:ref_name] || user_project.try(:default_branch) || 'master' unless params[:all]
offset = (params[:page] - 1) * params[:per_page] offset = (params[:page] - 1) * params[:per_page]
all = params[:all]
commits = user_project.repository.commits(ref, commits = user_project.repository.commits(ref,
path: path, path: path,
limit: params[:per_page], limit: params[:per_page],
offset: offset, offset: offset,
before: before, before: before,
after: after) after: after,
all: all)
commit_count = commit_count =
if path || before || after if all || path || before || after
user_project.repository.count_commits(ref: ref, path: path, before: before, after: after) user_project.repository.count_commits(ref: ref, path: path, before: before, after: after, all: all)
else else
# Cacheable commit count. # Cacheable commit count.
user_project.repository.commit_count_for_ref(ref) user_project.repository.commit_count_for_ref(ref)
......
...@@ -467,7 +467,8 @@ module Gitlab ...@@ -467,7 +467,8 @@ module Gitlab
follow: false, follow: false,
skip_merges: false, skip_merges: false,
after: nil, after: nil,
before: nil before: nil,
all: false
} }
options = default_options.merge(options) options = default_options.merge(options)
...@@ -478,8 +479,9 @@ module Gitlab ...@@ -478,8 +479,9 @@ module Gitlab
raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}") raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}")
end end
# TODO support options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1049
gitaly_migrate(:find_commits) do |is_enabled| gitaly_migrate(:find_commits) do |is_enabled|
if is_enabled if is_enabled && !options[:all]
gitaly_commit_client.find_commits(options) gitaly_commit_client.find_commits(options)
else else
raw_log(options).map { |c| Commit.decorate(self, c) } raw_log(options).map { |c| Commit.decorate(self, c) }
...@@ -489,13 +491,16 @@ module Gitlab ...@@ -489,13 +491,16 @@ module Gitlab
# Used in gitaly-ruby # Used in gitaly-ruby
def raw_log(options) def raw_log(options)
sha =
unless options[:all]
actual_ref = options[:ref] || root_ref actual_ref = options[:ref] || root_ref
begin begin
sha = sha_from_ref(actual_ref) sha_from_ref(actual_ref)
rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
# Return an empty array if the ref wasn't found # Return an empty array if the ref wasn't found
return [] return []
end end
end
log_by_shell(sha, options) log_by_shell(sha, options)
end end
...@@ -503,8 +508,9 @@ module Gitlab ...@@ -503,8 +508,9 @@ module Gitlab
def count_commits(options) def count_commits(options)
count_commits_options = process_count_commits_options(options) count_commits_options = process_count_commits_options(options)
# TODO add support for options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1050
gitaly_migrate(:count_commits) do |is_enabled| gitaly_migrate(:count_commits) do |is_enabled|
if is_enabled if is_enabled && !options[:all]
count_commits_by_gitaly(count_commits_options) count_commits_by_gitaly(count_commits_options)
else else
count_commits_by_shelling_out(count_commits_options) count_commits_by_shelling_out(count_commits_options)
...@@ -1701,7 +1707,12 @@ module Gitlab ...@@ -1701,7 +1707,12 @@ module Gitlab
cmd << '--no-merges' if options[:skip_merges] cmd << '--no-merges' if options[:skip_merges]
cmd << "--after=#{options[:after].iso8601}" if options[:after] cmd << "--after=#{options[:after].iso8601}" if options[:after]
cmd << "--before=#{options[:before].iso8601}" if options[:before] cmd << "--before=#{options[:before].iso8601}" if options[:before]
if options[:all]
cmd += %w[--all --reverse]
else
cmd << sha cmd << sha
end
# :path can be a string or an array of strings # :path can be a string or an array of strings
if options[:path].present? if options[:path].present?
...@@ -1918,7 +1929,16 @@ module Gitlab ...@@ -1918,7 +1929,16 @@ module Gitlab
cmd << "--before=#{options[:before].iso8601}" if options[:before] cmd << "--before=#{options[:before].iso8601}" if options[:before]
cmd << "--max-count=#{options[:max_count]}" if options[:max_count] cmd << "--max-count=#{options[:max_count]}" if options[:max_count]
cmd << "--left-right" if options[:left_right] cmd << "--left-right" if options[:left_right]
cmd += %W[--count #{options[:ref]}] cmd << '--count'
cmd << if options[:all]
'--all'
elsif options[:ref]
options[:ref]
else
raise ArgumentError, "Please specify a valid ref or set the 'all' attribute to true"
end
cmd += %W[-- #{options[:path]}] if options[:path].present? cmd += %W[-- #{options[:path]}] if options[:path].present?
cmd cmd
end end
......
...@@ -895,7 +895,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -895,7 +895,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository.log(options.merge(path: "encoding")) repository.log(options.merge(path: "encoding"))
end end
it "should not follow renames" do it "does not follow renames" do
expect(log_commits).to include(commit_with_new_name) expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit) expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_old_name) expect(log_commits).not_to include(commit_with_old_name)
...@@ -907,7 +907,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -907,7 +907,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository.log(options.merge(path: "encoding/CHANGELOG")) repository.log(options.merge(path: "encoding/CHANGELOG"))
end end
it "should not follow renames" do it "does not follow renames" do
expect(log_commits).to include(commit_with_new_name) expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit) expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_old_name) expect(log_commits).not_to include(commit_with_old_name)
...@@ -919,7 +919,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -919,7 +919,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository.log(options.merge(path: "CHANGELOG")) repository.log(options.merge(path: "CHANGELOG"))
end end
it "should not follow renames" do it "does not follow renames" do
expect(log_commits).to include(commit_with_old_name) expect(log_commits).to include(commit_with_old_name)
expect(log_commits).to include(rename_commit) expect(log_commits).to include(rename_commit)
expect(log_commits).not_to include(commit_with_new_name) expect(log_commits).not_to include(commit_with_new_name)
...@@ -931,7 +931,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -931,7 +931,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
repository.log(options.merge(ref: "refs/heads/fix-blob-path", path: "files/testdir/file.txt")) repository.log(options.merge(ref: "refs/heads/fix-blob-path", path: "files/testdir/file.txt"))
end end
it "should return a list of commits" do it "returns a list of commits" do
expect(log_commits.size).to eq(1) expect(log_commits.size).to eq(1)
end end
end end
...@@ -991,6 +991,16 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -991,6 +991,16 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) } it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) }
end end
end end
context 'with all' do
let(:options) { { all: true, limit: 50 } }
it 'returns a list of commits' do
commits = repository.log(options)
expect(commits.size).to eq(37)
end
end
end end
describe "#rugged_commits_between" do describe "#rugged_commits_between" do
...@@ -1134,6 +1144,20 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1134,6 +1144,20 @@ describe Gitlab::Git::Repository, seed_helper: true do
context 'when Gitaly count_commits feature is disabled', :skip_gitaly_mock do context 'when Gitaly count_commits feature is disabled', :skip_gitaly_mock do
it_behaves_like 'extended commit counting' it_behaves_like 'extended commit counting'
context "with all" do
it "returns the number of commits in the whole repository" do
options = { all: true }
expect(repository.count_commits(options)).to eq(34)
end
end
context 'without all or ref being specified' do
it "raises an ArgumentError" do
expect { repository.count_commits({}) }.to raise_error(ArgumentError, "Please specify a valid ref or set the 'all' attribute to true")
end
end
end end
end end
......
...@@ -242,25 +242,53 @@ describe Repository do ...@@ -242,25 +242,53 @@ describe Repository do
end end
describe '#commits' do describe '#commits' do
it 'sets follow when path is a single path' do context 'when neither the all flag nor a ref are specified' do
it 'returns every commit from default branch' do
expect(repository.commits(limit: 60).size).to eq(37)
end
end
context 'when ref is passed' do
it 'returns every commit from the specified ref' do
expect(repository.commits('master', limit: 60).size).to eq(37)
end
context 'when all' do
it 'returns every commit from the repository' do
expect(repository.commits('master', limit: 60, all: true).size).to eq(60)
end
end
context 'with path' do
it 'sets follow when it is a single path' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: true)).and_call_original.twice expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: true)).and_call_original.twice
repository.commits('master', limit: 1, path: 'README.md') repository.commits('master', limit: 1, path: 'README.md')
repository.commits('master', limit: 1, path: ['README.md']) repository.commits('master', limit: 1, path: ['README.md'])
end end
it 'does not set follow when path is multiple paths' do it 'does not set follow when it is multiple paths' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original
repository.commits('master', limit: 1, path: ['README.md', 'CHANGELOG']) repository.commits('master', limit: 1, path: ['README.md', 'CHANGELOG'])
end end
end
it 'does not set follow when there are no paths' do context 'without path' do
it 'does not set follow' do
expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original expect(Gitlab::Git::Commit).to receive(:where).with(a_hash_including(follow: false)).and_call_original
repository.commits('master', limit: 1) repository.commits('master', limit: 1)
end end
end end
end
context "when 'all' flag is set" do
it 'returns every commit from the repository' do
expect(repository.commits(all: true, limit: 60).size).to eq(60)
end
end
end
describe '#new_commits' do describe '#new_commits' do
let(:new_refs) do let(:new_refs) do
......
...@@ -149,6 +149,18 @@ describe API::Commits do ...@@ -149,6 +149,18 @@ describe API::Commits do
end end
end end
context 'all optional parameter' do
it 'returns all project commits' do
commit_count = project.repository.count_commits(all: true)
get api("/projects/#{project_id}/repository/commits?all=true", user)
expect(response).to include_pagination_headers
expect(response.headers['X-Total']).to eq(commit_count.to_s)
expect(response.headers['X-Page']).to eql('1')
end
end
context 'with pagination params' do context 'with pagination params' do
let(:page) { 1 } let(:page) { 1 }
let(:per_page) { 5 } let(:per_page) { 5 }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment