From 33cf8edae170890ccd4510490f1dcd2f345c81fa Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 22 Apr 2019 18:07:19 +0200 Subject: Port changes for design management to CE This ports the changes from https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/10462/ to CE --- Gemfile | 1 + Gemfile.lock | 4 + app/graphql/resolvers/issues_resolver.rb | 6 + app/models/repository.rb | 13 +++ app/workers/post_receive.rb | 4 +- lib/gitlab/git/repository.rb | 6 + lib/gitlab/git_access.rb | 6 +- spec/graphql/resolvers/issues_resolver_spec.rb | 155 ++++++++++++++----------- spec/lib/gitlab/git/repository_spec.rb | 6 + spec/models/repository_spec.rb | 65 +++++++++++ spec/support/helpers/graphql_helpers.rb | 13 ++- 11 files changed, 206 insertions(+), 73 deletions(-) diff --git a/Gemfile b/Gemfile index a350f194f62..6654b285a72 100644 --- a/Gemfile +++ b/Gemfile @@ -79,6 +79,7 @@ gem 'rack-cors', '~> 1.0.0', require: 'rack/cors' # GraphQL API gem 'graphql', '~> 1.8.0' gem 'graphiql-rails', '~> 1.4.10' +gem 'apollo_upload_server', '~> 2.0.0.beta3' # Disable strong_params so that Mash does not respond to :permitted? gem 'hashie-forbidden_attributes' diff --git a/Gemfile.lock b/Gemfile.lock index 64f2f78a4f8..ceece1da8d7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -52,6 +52,9 @@ GEM public_suffix (>= 2.0.2, < 4.0) aes_key_wrap (1.0.1) akismet (2.0.0) + apollo_upload_server (2.0.0.beta.3) + graphql (>= 1.8) + rails (>= 4.2) arel (8.0.0) asana (0.8.1) faraday (~> 0.9) @@ -988,6 +991,7 @@ DEPENDENCIES acts-as-taggable-on (~> 6.0) addressable (~> 2.5.2) akismet (~> 2.0) + apollo_upload_server (~> 2.0.0.beta3) asana (~> 0.8.1) asciidoctor (~> 1.5.8) asciidoctor-plantuml (= 0.0.8) diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index b98d8bd1fff..54d32a688bf 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -44,6 +44,12 @@ module Resolvers alias_method :project, :object def resolve(**args) + # The project could have been loaded in batch by `BatchLoader`. + # At this point we need the `id` of the project to query for issues, so + # make sure it's loaded and not `nil` before continueing. + project.sync if project.respond_to?(:sync) + return Issue.none if project.nil? + # Will need to be be made group & namespace aware with # https://gitlab.com/gitlab-org/gitlab-ce/issues/54520 args[:project_id] = project.id diff --git a/app/models/repository.rb b/app/models/repository.rb index 51ab2247a03..8b728c4f6b2 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1065,6 +1065,19 @@ class Repository blob.data end + def create_if_not_exists + return if exists? + + raw.create_repository + after_create + end + + def blobs_metadata(paths, ref = 'HEAD') + references = Array.wrap(paths).map { |path| [ref, path] } + + Gitlab::Git::Blob.batch_metadata(raw, references).map { |raw_blob| Blob.decorate(raw_blob) } + end + private # TODO Generice finder, later split this on finders by Ref or Oid diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 337efa7919b..9a9c0c9d803 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -21,8 +21,10 @@ class PostReceive if repo_type.wiki? process_wiki_changes(post_received) - else + elsif repo_type.project? process_project_changes(post_received) + else + # Other repos don't have hooks for now end end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index c12cb6a6434..55bd77f6c4a 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -118,6 +118,12 @@ module Gitlab gitaly_repository_client.exists? end + def create_repository + wrapped_gitaly_errors do + gitaly_repository_client.create_repository + end + end + # Returns an Array of branch names # sorted by name ASC def branch_names diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index cb80ed64eff..4b626509008 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -85,7 +85,7 @@ module Gitlab check_push_access! end - ::Gitlab::GitAccessResult::Success.new(console_messages: check_for_console_messages(cmd)) + success_result(cmd) end def guest_can_download_code? @@ -365,6 +365,10 @@ module Gitlab protected + def success_result(cmd) + ::Gitlab::GitAccessResult::Success.new(console_messages: check_for_console_messages(cmd)) + end + def changes_list @changes_list ||= Gitlab::ChangesList.new(changes == ANY ? [] : changes) end diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index 5f9c180cbb7..399a33dae75 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -4,104 +4,119 @@ describe Resolvers::IssuesResolver do include GraphqlHelpers let(:current_user) { create(:user) } - set(:project) { create(:project) } - set(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) } - set(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) } - set(:label1) { create(:label, project: project) } - set(:label2) { create(:label, project: project) } - - before do - project.add_developer(current_user) - create(:label_link, label: label1, target: issue1) - create(:label_link, label: label1, target: issue2) - create(:label_link, label: label2, target: issue2) - end - - describe '#resolve' do - it 'finds all issues' do - expect(resolve_issues).to contain_exactly(issue1, issue2) - end - it 'filters by state' do - expect(resolve_issues(state: 'opened')).to contain_exactly(issue1) - expect(resolve_issues(state: 'closed')).to contain_exactly(issue2) + context "with a project" do + set(:project) { create(:project) } + set(:issue1) { create(:issue, project: project, state: :opened, created_at: 3.hours.ago, updated_at: 3.hours.ago) } + set(:issue2) { create(:issue, project: project, state: :closed, title: 'foo', created_at: 1.hour.ago, updated_at: 1.hour.ago, closed_at: 1.hour.ago) } + set(:label1) { create(:label, project: project) } + set(:label2) { create(:label, project: project) } + + before do + project.add_developer(current_user) + create(:label_link, label: label1, target: issue1) + create(:label_link, label: label1, target: issue2) + create(:label_link, label: label2, target: issue2) end - it 'filters by labels' do - expect(resolve_issues(label_name: [label1.title])).to contain_exactly(issue1, issue2) - expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2) - end + describe '#resolve' do + it 'finds all issues' do + expect(resolve_issues).to contain_exactly(issue1, issue2) + end - describe 'filters by created_at' do - it 'filters by created_before' do - expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1) + it 'filters by state' do + expect(resolve_issues(state: 'opened')).to contain_exactly(issue1) + expect(resolve_issues(state: 'closed')).to contain_exactly(issue2) end - it 'filters by created_after' do - expect(resolve_issues(created_after: 2.hours.ago)).to contain_exactly(issue2) + it 'filters by labels' do + expect(resolve_issues(label_name: [label1.title])).to contain_exactly(issue1, issue2) + expect(resolve_issues(label_name: [label1.title, label2.title])).to contain_exactly(issue2) end - end - describe 'filters by updated_at' do - it 'filters by updated_before' do - expect(resolve_issues(updated_before: 2.hours.ago)).to contain_exactly(issue1) + describe 'filters by created_at' do + it 'filters by created_before' do + expect(resolve_issues(created_before: 2.hours.ago)).to contain_exactly(issue1) + end + + it 'filters by created_after' do + expect(resolve_issues(created_after: 2.hours.ago)).to contain_exactly(issue2) + end end - it 'filters by updated_after' do - expect(resolve_issues(updated_after: 2.hours.ago)).to contain_exactly(issue2) + describe 'filters by updated_at' do + it 'filters by updated_before' do + expect(resolve_issues(updated_before: 2.hours.ago)).to contain_exactly(issue1) + end + + it 'filters by updated_after' do + expect(resolve_issues(updated_after: 2.hours.ago)).to contain_exactly(issue2) + end end - end - describe 'filters by closed_at' do - let!(:issue3) { create(:issue, project: project, state: :closed, closed_at: 3.hours.ago) } + describe 'filters by closed_at' do + let!(:issue3) { create(:issue, project: project, state: :closed, closed_at: 3.hours.ago) } - it 'filters by closed_before' do - expect(resolve_issues(closed_before: 2.hours.ago)).to contain_exactly(issue3) + it 'filters by closed_before' do + expect(resolve_issues(closed_before: 2.hours.ago)).to contain_exactly(issue3) + end + + it 'filters by closed_after' do + expect(resolve_issues(closed_after: 2.hours.ago)).to contain_exactly(issue2) + end end - it 'filters by closed_after' do - expect(resolve_issues(closed_after: 2.hours.ago)).to contain_exactly(issue2) + it 'searches issues' do + expect(resolve_issues(search: 'foo')).to contain_exactly(issue2) end - end - it 'searches issues' do - expect(resolve_issues(search: 'foo')).to contain_exactly(issue2) - end + it 'sort issues' do + expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue1] + end - it 'sort issues' do - expect(resolve_issues(sort: 'created_desc')).to eq [issue2, issue1] - end + it 'returns issues user can see' do + project.add_guest(current_user) - it 'returns issues user can see' do - project.add_guest(current_user) + create(:issue, confidential: true) - create(:issue, confidential: true) + expect(resolve_issues).to contain_exactly(issue1, issue2) + end - expect(resolve_issues).to contain_exactly(issue1, issue2) - end + it 'finds a specific issue with iid' do + expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1) + end - it 'finds a specific issue with iid' do - expect(resolve_issues(iid: issue1.iid)).to contain_exactly(issue1) - end + it 'finds a specific issue with iids' do + expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1) + end - it 'finds a specific issue with iids' do - expect(resolve_issues(iids: issue1.iid)).to contain_exactly(issue1) - end + it 'finds multiple issues with iids' do + expect(resolve_issues(iids: [issue1.iid, issue2.iid])) + .to contain_exactly(issue1, issue2) + end - it 'finds multiple issues with iids' do - expect(resolve_issues(iids: [issue1.iid, issue2.iid])) - .to contain_exactly(issue1, issue2) - end + it 'finds only the issues within the project we are looking at' do + another_project = create(:project) + iids = [issue1, issue2].map(&:iid) + + iids.each do |iid| + create(:issue, project: another_project, iid: iid) + end - it 'finds only the issues within the project we are looking at' do - another_project = create(:project) - iids = [issue1, issue2].map(&:iid) + expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2) + end + end + end - iids.each do |iid| - create(:issue, project: another_project, iid: iid) + context "when passing a non existent, batch loaded project" do + let(:project) do + BatchLoader.for("non-existent-path").batch do |_fake_paths, loader, _| + loader.call("non-existent-path", nil) end + end - expect(resolve_issues(iids: iids)).to contain_exactly(issue1, issue2) + it "returns nil without breaking" do + expect(resolve_issues(iids: ["don't", "break"])).to be_empty end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 45fe5d72937..5f8a2848944 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -95,6 +95,12 @@ describe Gitlab::Git::Repository, :seed_helper do end end + describe '#create_repository' do + it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RepositoryService, :create_repository do + subject { repository.create_repository } + end + end + describe '#branch_names' do subject { repository.branch_names } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 4c354593b57..43ec1125087 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -2487,4 +2487,69 @@ describe Repository do repository.merge_base('master', 'fix') end end + + describe '#create_if_not_exists' do + let(:project) { create(:project) } + let(:repository) { project.repository } + + it 'creates the repository if it did not exist' do + expect { repository.create_if_not_exists }.to change { repository.exists? }.from(false).to(true) + end + + it 'calls out to the repository client to create a repo' do + expect(repository.raw.gitaly_repository_client).to receive(:create_repository) + + repository.create_if_not_exists + end + + context 'it does nothing if the repository already existed' do + let(:project) { create(:project, :repository) } + + it 'does nothing if the repository already existed' do + expect(repository.raw.gitaly_repository_client).not_to receive(:create_repository) + + repository.create_if_not_exists + end + end + + context 'when the repository exists but the cache is not up to date' do + let(:project) { create(:project, :repository) } + + it 'does not raise errors' do + allow(repository).to receive(:exists?).and_return(false) + expect(repository.raw).to receive(:create_repository).and_call_original + + expect { repository.create_if_not_exists }.not_to raise_error + end + end + end + + describe "#blobs_metadata" do + set(:project) { create(:project, :repository) } + let(:repository) { project.repository } + + def expect_metadata_blob(thing) + expect(thing).to be_a(Blob) + expect(thing.data).to be_empty + end + + it "returns blob metadata in batch for HEAD" do + result = repository.blobs_metadata(["bar/branch-test.txt", "README.md", "does/not/exist"]) + + expect_metadata_blob(result.first) + expect_metadata_blob(result.second) + expect(result.size).to eq(2) + end + + it "returns blob metadata for a specified ref" do + result = repository.blobs_metadata(["files/ruby/feature.rb"], "feature") + + expect_metadata_blob(result.first) + end + + it "performs a single gitaly call", :request_store do + expect { repository.blobs_metadata(["bar/branch-test.txt", "readme.txt", "does/not/exist"]) } + .to change { Gitlab::GitalyClient.get_request_count }.by(1) + end + end end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index 2f4e6e4c934..b49d743fb9a 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -61,7 +61,14 @@ module GraphqlHelpers def variables_for_mutation(name, input) graphql_input = input.map { |name, value| [GraphqlHelpers.fieldnamerize(name), value] }.to_h - { input_variable_name_for_mutation(name) => graphql_input }.to_json + result = { input_variable_name_for_mutation(name) => graphql_input } + + # Avoid trying to serialize multipart data into JSON + if graphql_input.values.none? { |value| io_value?(value) } + result.to_json + else + result + end end def input_variable_name_for_mutation(mutation_name) @@ -162,6 +169,10 @@ module GraphqlHelpers field.arguments.values.any? { |argument| argument.type.non_null? } end + def io_value?(value) + Array.wrap(value).any? { |v| v.respond_to?(:to_io) } + end + def field_type(field) field_type = field.type -- cgit v1.2.1