Commit 6319ca27 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'gitaly-local-branches' into 'master'

Incorporate Gitaly's local_branches operation into repo code

Closes #27379

See merge request !10059
parents 4d2d2b11 925945f0
...@@ -649,22 +649,8 @@ class Repository ...@@ -649,22 +649,8 @@ class Repository
"#{name}-#{highest_branch_id + 1}" "#{name}-#{highest_branch_id + 1}"
end end
# Remove archives older than 2 hours
def branches_sorted_by(value) def branches_sorted_by(value)
case value raw_repository.local_branches(sort_by: value)
when 'name'
branches.sort_by(&:name)
when 'updated_desc'
branches.sort do |a, b|
commit(b.dereferenced_target).committed_date <=> commit(a.dereferenced_target).committed_date
end
when 'updated_asc'
branches.sort do |a, b|
commit(a.dereferenced_target).committed_date <=> commit(b.dereferenced_target).committed_date
end
else
branches
end
end end
def tags_sorted_by(value) def tags_sorted_by(value)
......
---
title: Add suport for find_local_branches GRPC from Gitaly
merge_request: 10059
author:
module Gitlab module Gitlab
module Git module Git
class Branch < Ref class Branch < Ref
def initialize(repository, name, target)
if target.is_a?(Gitaly::FindLocalBranchResponse)
target = target_from_gitaly_local_branches_response(target)
end
super(repository, name, target)
end
def target_from_gitaly_local_branches_response(response)
# Git messages have no encoding enforcements. However, in the UI we only
# handle UTF-8, so basically we cross our fingers that the message force
# encoded to UTF-8 is readable.
message = response.commit_subject.dup.force_encoding('UTF-8')
# NOTE: For ease of parsing in Gitaly, we have only the subject of
# the commit and not the full message. This is ok, since all the
# code that uses `local_branches` only cares at most about the
# commit message.
# TODO: Once gitaly "takes over" Rugged consider separating the
# subject from the message to make it clearer when there's one
# available but not the other.
hash = {
id: response.commit_id,
message: message,
authored_date: Time.at(response.commit_author.date.seconds),
author_name: response.commit_author.name,
author_email: response.commit_author.email,
committed_date: Time.at(response.commit_committer.date.seconds),
committer_name: response.commit_committer.name,
committer_email: response.commit_committer.email
}
Gitlab::Git::Commit.decorate(hash)
end
end end
end end
end end
...@@ -19,13 +19,7 @@ module Gitlab ...@@ -19,13 +19,7 @@ module Gitlab
def ==(other) def ==(other)
return false unless other.is_a?(Gitlab::Git::Commit) return false unless other.is_a?(Gitlab::Git::Commit)
methods = [:message, :parent_ids, :authored_date, :author_name, id && id == other.id
:author_email, :committed_date, :committer_name,
:committer_email]
methods.all? do |method|
send(method) == other.send(method)
end
end end
class << self class << self
...@@ -55,6 +49,7 @@ module Gitlab ...@@ -55,6 +49,7 @@ module Gitlab
# Commit.find(repo, 'master') # Commit.find(repo, 'master')
# #
def find(repo, commit_id = "HEAD") def find(repo, commit_id = "HEAD")
return commit_id if commit_id.is_a?(Gitlab::Git::Commit)
return decorate(commit_id) if commit_id.is_a?(Rugged::Commit) return decorate(commit_id) if commit_id.is_a?(Rugged::Commit)
obj = if commit_id.is_a?(String) obj = if commit_id.is_a?(String)
......
...@@ -80,14 +80,16 @@ module Gitlab ...@@ -80,14 +80,16 @@ module Gitlab
end end
# Returns an Array of Branches # Returns an Array of Branches
def branches def branches(filter: nil, sort_by: nil)
rugged.branches.map do |rugged_ref| branches = rugged.branches.each(filter).map do |rugged_ref|
begin begin
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target) Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target)
rescue Rugged::ReferenceError rescue Rugged::ReferenceError
# Omit invalid branch # Omit invalid branch
end end
end.compact.sort_by(&:name) end.compact
sort_branches(branches, sort_by)
end end
def reload_rugged def reload_rugged
...@@ -108,9 +110,15 @@ module Gitlab ...@@ -108,9 +110,15 @@ module Gitlab
Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target) if rugged_ref Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target) if rugged_ref
end end
def local_branches def local_branches(sort_by: nil)
rugged.branches.each(:local).map do |branch| gitaly_migrate(:local_branches) do |is_enabled|
Gitlab::Git::Branch.new(self, branch.name, branch.target) if is_enabled
gitaly_ref_client.local_branches(sort_by: sort_by).map do |gitaly_branch|
Gitlab::Git::Branch.new(self, gitaly_branch.name, gitaly_branch)
end
else
branches(filter: :local, sort_by: sort_by)
end
end end
end end
...@@ -1202,6 +1210,23 @@ module Gitlab ...@@ -1202,6 +1210,23 @@ module Gitlab
diff.each_patch diff.each_patch
end end
def sort_branches(branches, sort_by)
case sort_by
when 'name'
branches.sort_by(&:name)
when 'updated_desc'
branches.sort do |a, b|
b.dereferenced_target.committed_date <=> a.dereferenced_target.committed_date
end
when 'updated_asc'
branches.sort do |a, b|
a.dereferenced_target.committed_date <=> b.dereferenced_target.committed_date
end
else
branches
end
end
def gitaly_ref_client def gitaly_ref_client
@gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(self) @gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(self)
end end
......
...@@ -44,6 +44,12 @@ module Gitlab ...@@ -44,6 +44,12 @@ module Gitlab
branch_names.count branch_names.count
end end
def local_branches(sort_by: nil)
request = Gitaly::FindLocalBranchesRequest.new(repository: @gitaly_repo)
request.sort_by = sort_by_param(sort_by) if sort_by
consume_branches_response(stub.find_local_branches(request))
end
private private
def consume_refs_response(response, prefix:) def consume_refs_response(response, prefix:)
...@@ -51,6 +57,16 @@ module Gitlab ...@@ -51,6 +57,16 @@ module Gitlab
r.names.map { |name| name.sub(/\A#{Regexp.escape(prefix)}/, '') } r.names.map { |name| name.sub(/\A#{Regexp.escape(prefix)}/, '') }
end end
end end
def sort_by_param(sort_by)
enum_value = Gitaly::FindLocalBranchesRequest::SortBy.resolve(sort_by.upcase.to_sym)
raise ArgumentError, "Invalid sort_by key `#{sort_by}`" unless enum_value
enum_value
end
def consume_branches_response(response)
response.flat_map { |r| r.branches }
end
end end
end end
end end
require 'spec_helper' require 'spec_helper'
Dir["./spec/features/protected_branches/*.rb"].sort.each { |f| require f }
feature 'Projected Branches', feature: true, js: true do feature 'Projected Branches', feature: true, js: true do
let(:user) { create(:user, :admin) } let(:user) { create(:user, :admin) }
......
require 'spec_helper' require 'spec_helper'
Dir["./spec/features/protected_tags/*.rb"].sort.each { |f| require f }
feature 'Projected Tags', feature: true, js: true do feature 'Projected Tags', feature: true, js: true do
let(:user) { create(:user, :admin) } let(:user) { create(:user, :admin) }
......
...@@ -7,6 +7,51 @@ describe Gitlab::Git::Branch, seed_helper: true do ...@@ -7,6 +7,51 @@ describe Gitlab::Git::Branch, seed_helper: true do
it { is_expected.to be_kind_of Array } it { is_expected.to be_kind_of Array }
describe 'initialize' do
let(:commit_id) { 'f00' }
let(:commit_subject) { "My commit".force_encoding('ASCII-8BIT') }
let(:committer) do
Gitaly::FindLocalBranchCommitAuthor.new(
name: generate(:name),
email: generate(:email),
date: Google::Protobuf::Timestamp.new(seconds: 123)
)
end
let(:author) do
Gitaly::FindLocalBranchCommitAuthor.new(
name: generate(:name),
email: generate(:email),
date: Google::Protobuf::Timestamp.new(seconds: 456)
)
end
let(:gitaly_branch) do
Gitaly::FindLocalBranchResponse.new(
name: 'foo', commit_id: commit_id, commit_subject: commit_subject,
commit_author: author, commit_committer: committer
)
end
let(:attributes) do
{
id: commit_id,
message: commit_subject,
authored_date: Time.at(author.date.seconds),
author_name: author.name,
author_email: author.email,
committed_date: Time.at(committer.date.seconds),
committer_name: committer.name,
committer_email: committer.email
}
end
let(:branch) { described_class.new(repository, 'foo', gitaly_branch) }
it 'parses Gitaly::FindLocalBranchResponse correctly' do
expect(Gitlab::Git::Commit).to receive(:decorate).
with(hash_including(attributes)).and_call_original
expect(branch.dereferenced_target.message.encoding).to be(Encoding::UTF_8)
end
end
describe '#size' do describe '#size' do
subject { super().size } subject { super().size }
it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) } it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) }
......
...@@ -1105,7 +1105,9 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1105,7 +1105,9 @@ describe Gitlab::Git::Repository, seed_helper: true do
ref = double() ref = double()
allow(ref).to receive(:name) { 'bad-branch' } allow(ref).to receive(:name) { 'bad-branch' }
allow(ref).to receive(:target) { raise Rugged::ReferenceError } allow(ref).to receive(:target) { raise Rugged::ReferenceError }
allow(repository.rugged).to receive(:branches) { [ref] } branches = double()
allow(branches).to receive(:each) { [ref].each }
allow(repository.rugged).to receive(:branches) { branches }
end end
it 'should return empty branches' do it 'should return empty branches' do
...@@ -1289,7 +1291,7 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1289,7 +1291,7 @@ describe Gitlab::Git::Repository, seed_helper: true do
describe '#local_branches' do describe '#local_branches' do
before(:all) do before(:all) do
@repo = Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH) @repo = Gitlab::Git::Repository.new('default', File.join(TEST_MUTABLE_REPO_PATH, '.git'))
end end
after(:all) do after(:all) do
...@@ -1304,6 +1306,29 @@ describe Gitlab::Git::Repository, seed_helper: true do ...@@ -1304,6 +1306,29 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(@repo.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false) expect(@repo.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false)
expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true) expect(@repo.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true)
end end
context 'with gitaly enabled' do
before { stub_gitaly }
after { Gitlab::GitalyClient.clear_stubs! }
it 'gets the branches from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches).
and_return([])
@repo.local_branches
end
it 'wraps GRPC not found' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches).
and_raise(GRPC::NotFound)
expect { @repo.local_branches }.to raise_error(Gitlab::Git::Repository::NoRepository)
end
it 'wraps GRPC exceptions' do
expect_any_instance_of(Gitlab::GitalyClient::Ref).to receive(:local_branches).
and_raise(GRPC::Unknown)
expect { @repo.local_branches }.to raise_error(Gitlab::Git::CommandError)
end
end
end end
def create_remote_branch(remote_name, branch_name, source_branch_name) def create_remote_branch(remote_name, branch_name, source_branch_name)
......
...@@ -9,6 +9,13 @@ describe Gitlab::GitalyClient::Ref do ...@@ -9,6 +9,13 @@ describe Gitlab::GitalyClient::Ref do
allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true) allow(Gitlab.config.gitaly).to receive(:enabled).and_return(true)
end end
after do
# When we say `expect_any_instance_of(Gitaly::Ref::Stub)` a double is created,
# and because GitalyClient shares stubs these will get passed from example to
# example, which will cause an error, so we clean the stubs after each example.
Gitlab::GitalyClient.clear_stubs!
end
describe '#branch_names' do describe '#branch_names' do
it 'sends a find_all_branch_names message' do it 'sends a find_all_branch_names message' do
expect_any_instance_of(Gitaly::Ref::Stub). expect_any_instance_of(Gitaly::Ref::Stub).
...@@ -38,4 +45,27 @@ describe Gitlab::GitalyClient::Ref do ...@@ -38,4 +45,27 @@ describe Gitlab::GitalyClient::Ref do
client.default_branch_name client.default_branch_name
end end
end end
describe '#local_branches' do
it 'sends a find_local_branches message' do
expect_any_instance_of(Gitaly::Ref::Stub).
to receive(:find_local_branches).with(gitaly_request_with_repo_path(repo_path)).
and_return([])
client.local_branches
end
it 'parses and sends the sort parameter' do
expect_any_instance_of(Gitaly::Ref::Stub).
to receive(:find_local_branches).
with(gitaly_request_with_params(sort_by: :UPDATED_DESC)).
and_return([])
client.local_branches(sort_by: 'updated_desc')
end
it 'raises an argument error if an invalid sort_by parameter is passed' do
expect { client.local_branches(sort_by: 'invalid_sort') }.to raise_error(ArgumentError)
end
end
end end
RSpec::Matchers.define :gitaly_request_with_repo_path do |path| RSpec::Matchers.define :gitaly_request_with_repo_path do |path|
match { |actual| actual.repository.path == path } match { |actual| actual.repository.path == path }
end end
RSpec::Matchers.define :gitaly_request_with_params do |params|
match do |actual|
params.reduce(true) { |r, (key, val)| r && actual.send(key) == val }
end
end
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