From d4d564c8e7d2cbc3e6742475a793ba0f630167e3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 1 Feb 2018 18:48:32 +0800 Subject: [PATCH 01/52] Try not to hold env and release the controller after the request. This way, we could release the project referred from the controller, which potentially referred a repository which potentially allocated a lot of memories. Before this change, we could hold the last request data and cannot release the memory. After this change, the largest request data should be able to be collected from GC. This might not impact the instances having heavy load, as the last request should be changing all the time, and GC won't kick in for each request anyway. However it could still potentially allow us to free more memories for each GC runs, because now we could free one more request anyway. --- config.ru | 1 + lib/gitlab/middleware/read_only.rb | 2 +- lib/gitlab/middleware/release_controller.rb | 9 +++++++++ .../middleware/release_controller_spec.rb | 20 +++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 lib/gitlab/middleware/release_controller.rb create mode 100644 spec/lib/gitlab/middleware/release_controller_spec.rb diff --git a/config.ru b/config.ru index de0400f4f67..c4bef72308e 100644 --- a/config.ru +++ b/config.ru @@ -23,5 +23,6 @@ warmup do |app| end map ENV['RAILS_RELATIVE_URL_ROOT'] || "/" do + use Gitlab::ReleaseController run Gitlab::Application end diff --git a/lib/gitlab/middleware/read_only.rb b/lib/gitlab/middleware/read_only.rb index c26656704d7..a68c6c3d15c 100644 --- a/lib/gitlab/middleware/read_only.rb +++ b/lib/gitlab/middleware/read_only.rb @@ -28,7 +28,7 @@ module Gitlab end end - @app.call(env) + @app.call(env).tap { @env = nil } end private diff --git a/lib/gitlab/middleware/release_controller.rb b/lib/gitlab/middleware/release_controller.rb new file mode 100644 index 00000000000..a21d718d51c --- /dev/null +++ b/lib/gitlab/middleware/release_controller.rb @@ -0,0 +1,9 @@ +module Gitlab + module Middleware + ReleaseController = Struct.new(:app) do + def call(env) + app.call(env).tap { env.delete('action_controller.instance') } + end + end + end +end diff --git a/spec/lib/gitlab/middleware/release_controller_spec.rb b/spec/lib/gitlab/middleware/release_controller_spec.rb new file mode 100644 index 00000000000..854bac6e751 --- /dev/null +++ b/spec/lib/gitlab/middleware/release_controller_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::Middleware::ReleaseController do + let(:inner_app) { double(:app) } + let(:app) { described_class.new(inner_app) } + let(:env) { { 'action_controller.instance' => 'something' } } + + before do + expect(inner_app).to receive(:call).with(env).and_return('yay') + end + + describe '#call' do + it 'calls the app and delete the controller' do + result = app.call(env) + + expect(result).to eq('yay') + expect(env).to be_empty + end + end +end From bbfce29ba8d75df5344dae34dc472dfb3b3acf4b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 2 Feb 2018 22:12:28 +0800 Subject: [PATCH 02/52] Use a controller to hold request values So that we don't need to hold env after the request. This makes it much harder to test, especially Rails session is acting weirdly, so we need `dig('flash', 'flashes', 'alert')` to dig the actual flash value. --- lib/gitlab/middleware/read_only.rb | 159 ++++++++++--------- spec/lib/gitlab/middleware/read_only_spec.rb | 23 ++- 2 files changed, 104 insertions(+), 78 deletions(-) diff --git a/lib/gitlab/middleware/read_only.rb b/lib/gitlab/middleware/read_only.rb index a68c6c3d15c..b7649ea01db 100644 --- a/lib/gitlab/middleware/read_only.rb +++ b/lib/gitlab/middleware/read_only.rb @@ -5,86 +5,95 @@ module Gitlab APPLICATION_JSON = 'application/json'.freeze API_VERSIONS = (3..4) + class Controller + def initialize(app, env) + @app = app + @env = env + end + + def call + if disallowed_request? && Gitlab::Database.read_only? + Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation') + error_message = 'You cannot do writing operations on a read-only GitLab instance' + + if json_request? + return [403, { 'Content-Type' => 'application/json' }, [{ 'message' => error_message }.to_json]] + else + rack_flash.alert = error_message + rack_session['flash'] = rack_flash.to_session_value + + return [301, { 'Location' => last_visited_url }, []] + end + end + + @app.call(@env) + end + + private + + def disallowed_request? + DISALLOWED_METHODS.include?(@env['REQUEST_METHOD']) && + !whitelisted_routes + end + + def json_request? + request.media_type == APPLICATION_JSON + end + + def rack_flash + @rack_flash ||= ActionDispatch::Flash::FlashHash.from_session_value(rack_session) + end + + def rack_session + @env['rack.session'] + end + + def request + @env['rack.request'] ||= Rack::Request.new(@env) + end + + def last_visited_url + @env['HTTP_REFERER'] || rack_session['user_return_to'] || Gitlab::Routing.url_helpers.root_url + end + + def route_hash + @route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {} + end + + def whitelisted_routes + grack_route || ReadOnly.internal_routes.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route + end + + def sidekiq_route + request.path.start_with?('/admin/sidekiq') + end + + def grack_route + # Calling route_hash may be expensive. Only do it if we think there's a possible match + return false unless request.path.end_with?('.git/git-upload-pack') + + route_hash[:controller] == 'projects/git_http' && route_hash[:action] == 'git_upload_pack' + end + + def lfs_route + # Calling route_hash may be expensive. Only do it if we think there's a possible match + return false unless request.path.end_with?('/info/lfs/objects/batch') + + route_hash[:controller] == 'projects/lfs_api' && route_hash[:action] == 'batch' + end + end + + def self.internal_routes + @internal_routes ||= + API_VERSIONS.map { |version| "api/v#{version}/internal" } + end + def initialize(app) @app = app - @whitelisted = internal_routes end def call(env) - @env = env - @route_hash = nil - - if disallowed_request? && Gitlab::Database.read_only? - Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation') - error_message = 'You cannot do writing operations on a read-only GitLab instance' - - if json_request? - return [403, { 'Content-Type' => 'application/json' }, [{ 'message' => error_message }.to_json]] - else - rack_flash.alert = error_message - rack_session['flash'] = rack_flash.to_session_value - - return [301, { 'Location' => last_visited_url }, []] - end - end - - @app.call(env).tap { @env = nil } - end - - private - - def internal_routes - API_VERSIONS.flat_map { |version| "api/v#{version}/internal" } - end - - def disallowed_request? - DISALLOWED_METHODS.include?(@env['REQUEST_METHOD']) && !whitelisted_routes - end - - def json_request? - request.media_type == APPLICATION_JSON - end - - def rack_flash - @rack_flash ||= ActionDispatch::Flash::FlashHash.from_session_value(rack_session) - end - - def rack_session - @env['rack.session'] - end - - def request - @env['rack.request'] ||= Rack::Request.new(@env) - end - - def last_visited_url - @env['HTTP_REFERER'] || rack_session['user_return_to'] || Gitlab::Routing.url_helpers.root_url - end - - def route_hash - @route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {} - end - - def whitelisted_routes - grack_route || @whitelisted.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route - end - - def sidekiq_route - request.path.start_with?('/admin/sidekiq') - end - - def grack_route - # Calling route_hash may be expensive. Only do it if we think there's a possible match - return false unless request.path.end_with?('.git/git-upload-pack') - - route_hash[:controller] == 'projects/git_http' && route_hash[:action] == 'git_upload_pack' - end - - def lfs_route - # Calling route_hash may be expensive. Only do it if we think there's a possible match - return false unless request.path.end_with?('/info/lfs/objects/batch') - - route_hash[:controller] == 'projects/lfs_api' && route_hash[:action] == 'batch' + Controller.new(@app, env).call end end end diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb index 07ba11b93a3..b3c85142b82 100644 --- a/spec/lib/gitlab/middleware/read_only_spec.rb +++ b/spec/lib/gitlab/middleware/read_only_spec.rb @@ -11,8 +11,10 @@ describe Gitlab::Middleware::ReadOnly do RSpec::Matchers.define :disallow_request do match do |middleware| - flash = middleware.send(:rack_flash) - flash['alert'] && flash['alert'].include?('You cannot do writing operations') + alert = middleware.env['rack.session'].to_hash + .dig('flash', 'flashes', 'alert') + + alert&.include?('You cannot do writing operations') end end @@ -34,7 +36,22 @@ describe Gitlab::Middleware::ReadOnly do rack.to_app end - subject { described_class.new(fake_app) } + let(:observe_env) do + Module.new do + attr_reader :env + + def call(env) + @env = env + super + end + end + end + + subject do + app = described_class.new(fake_app) + app.extend(observe_env) + app + end let(:request) { Rack::MockRequest.new(rack_stack) } From 31f1ec59a7cf7517cd5935ef3af540aceba07bb3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 7 Feb 2018 22:44:05 +0800 Subject: [PATCH 03/52] Release the entire env --- config.ru | 2 +- lib/gitlab/middleware/release_controller.rb | 9 --------- lib/gitlab/middleware/release_env.rb | 14 ++++++++++++++ ...ease_controller_spec.rb => release_env_spec.rb} | 2 +- 4 files changed, 16 insertions(+), 11 deletions(-) delete mode 100644 lib/gitlab/middleware/release_controller.rb create mode 100644 lib/gitlab/middleware/release_env.rb rename spec/lib/gitlab/middleware/{release_controller_spec.rb => release_env_spec.rb} (89%) diff --git a/config.ru b/config.ru index c4bef72308e..7b15939c6ff 100644 --- a/config.ru +++ b/config.ru @@ -23,6 +23,6 @@ warmup do |app| end map ENV['RAILS_RELATIVE_URL_ROOT'] || "/" do - use Gitlab::ReleaseController + use Gitlab::Middleware::ReleaseEnv run Gitlab::Application end diff --git a/lib/gitlab/middleware/release_controller.rb b/lib/gitlab/middleware/release_controller.rb deleted file mode 100644 index a21d718d51c..00000000000 --- a/lib/gitlab/middleware/release_controller.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Gitlab - module Middleware - ReleaseController = Struct.new(:app) do - def call(env) - app.call(env).tap { env.delete('action_controller.instance') } - end - end - end -end diff --git a/lib/gitlab/middleware/release_env.rb b/lib/gitlab/middleware/release_env.rb new file mode 100644 index 00000000000..f8d0a135965 --- /dev/null +++ b/lib/gitlab/middleware/release_env.rb @@ -0,0 +1,14 @@ +module Gitlab + module Middleware + # Some of middleware would hold env for no good reason even after the + # request had already been processed, and we could not garbage collect + # them due to this. Put this middleware as the first middleware so that + # it would clear the env after the request is done, allowing GC gets a + # chance to release memory for the last request. + ReleaseEnv = Struct.new(:app) do + def call(env) + app.call(env).tap { env.clear } + end + end + end +end diff --git a/spec/lib/gitlab/middleware/release_controller_spec.rb b/spec/lib/gitlab/middleware/release_env_spec.rb similarity index 89% rename from spec/lib/gitlab/middleware/release_controller_spec.rb rename to spec/lib/gitlab/middleware/release_env_spec.rb index 854bac6e751..657b705502a 100644 --- a/spec/lib/gitlab/middleware/release_controller_spec.rb +++ b/spec/lib/gitlab/middleware/release_env_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Middleware::ReleaseController do +describe Gitlab::Middleware::ReleaseEnv do let(:inner_app) { double(:app) } let(:app) { described_class.new(inner_app) } let(:env) { { 'action_controller.instance' => 'something' } } From 5309d4457aea74729a8c6be9ec76d535f922bf8a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 7 Feb 2018 22:44:18 +0800 Subject: [PATCH 04/52] Put controller in its separate file --- lib/gitlab/middleware/read_only.rb | 80 +----------------- lib/gitlab/middleware/read_only/controller.rb | 83 +++++++++++++++++++ 2 files changed, 84 insertions(+), 79 deletions(-) create mode 100644 lib/gitlab/middleware/read_only/controller.rb diff --git a/lib/gitlab/middleware/read_only.rb b/lib/gitlab/middleware/read_only.rb index b7649ea01db..19b74c0c122 100644 --- a/lib/gitlab/middleware/read_only.rb +++ b/lib/gitlab/middleware/read_only.rb @@ -5,84 +5,6 @@ module Gitlab APPLICATION_JSON = 'application/json'.freeze API_VERSIONS = (3..4) - class Controller - def initialize(app, env) - @app = app - @env = env - end - - def call - if disallowed_request? && Gitlab::Database.read_only? - Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation') - error_message = 'You cannot do writing operations on a read-only GitLab instance' - - if json_request? - return [403, { 'Content-Type' => 'application/json' }, [{ 'message' => error_message }.to_json]] - else - rack_flash.alert = error_message - rack_session['flash'] = rack_flash.to_session_value - - return [301, { 'Location' => last_visited_url }, []] - end - end - - @app.call(@env) - end - - private - - def disallowed_request? - DISALLOWED_METHODS.include?(@env['REQUEST_METHOD']) && - !whitelisted_routes - end - - def json_request? - request.media_type == APPLICATION_JSON - end - - def rack_flash - @rack_flash ||= ActionDispatch::Flash::FlashHash.from_session_value(rack_session) - end - - def rack_session - @env['rack.session'] - end - - def request - @env['rack.request'] ||= Rack::Request.new(@env) - end - - def last_visited_url - @env['HTTP_REFERER'] || rack_session['user_return_to'] || Gitlab::Routing.url_helpers.root_url - end - - def route_hash - @route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {} - end - - def whitelisted_routes - grack_route || ReadOnly.internal_routes.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route - end - - def sidekiq_route - request.path.start_with?('/admin/sidekiq') - end - - def grack_route - # Calling route_hash may be expensive. Only do it if we think there's a possible match - return false unless request.path.end_with?('.git/git-upload-pack') - - route_hash[:controller] == 'projects/git_http' && route_hash[:action] == 'git_upload_pack' - end - - def lfs_route - # Calling route_hash may be expensive. Only do it if we think there's a possible match - return false unless request.path.end_with?('/info/lfs/objects/batch') - - route_hash[:controller] == 'projects/lfs_api' && route_hash[:action] == 'batch' - end - end - def self.internal_routes @internal_routes ||= API_VERSIONS.map { |version| "api/v#{version}/internal" } @@ -93,7 +15,7 @@ module Gitlab end def call(env) - Controller.new(@app, env).call + ReadOnly::Controller.new(@app, env).call end end end diff --git a/lib/gitlab/middleware/read_only/controller.rb b/lib/gitlab/middleware/read_only/controller.rb new file mode 100644 index 00000000000..053cb6f0a9f --- /dev/null +++ b/lib/gitlab/middleware/read_only/controller.rb @@ -0,0 +1,83 @@ +module Gitlab + module Middleware + class ReadOnly + class Controller + def initialize(app, env) + @app = app + @env = env + end + + def call + if disallowed_request? && Gitlab::Database.read_only? + Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation') + error_message = 'You cannot do writing operations on a read-only GitLab instance' + + if json_request? + return [403, { 'Content-Type' => 'application/json' }, [{ 'message' => error_message }.to_json]] + else + rack_flash.alert = error_message + rack_session['flash'] = rack_flash.to_session_value + + return [301, { 'Location' => last_visited_url }, []] + end + end + + @app.call(@env) + end + + private + + def disallowed_request? + DISALLOWED_METHODS.include?(@env['REQUEST_METHOD']) && + !whitelisted_routes + end + + def json_request? + request.media_type == APPLICATION_JSON + end + + def rack_flash + @rack_flash ||= ActionDispatch::Flash::FlashHash.from_session_value(rack_session) + end + + def rack_session + @env['rack.session'] + end + + def request + @env['rack.request'] ||= Rack::Request.new(@env) + end + + def last_visited_url + @env['HTTP_REFERER'] || rack_session['user_return_to'] || Gitlab::Routing.url_helpers.root_url + end + + def route_hash + @route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {} + end + + def whitelisted_routes + grack_route || ReadOnly.internal_routes.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route + end + + def sidekiq_route + request.path.start_with?('/admin/sidekiq') + end + + def grack_route + # Calling route_hash may be expensive. Only do it if we think there's a possible match + return false unless request.path.end_with?('.git/git-upload-pack') + + route_hash[:controller] == 'projects/git_http' && route_hash[:action] == 'git_upload_pack' + end + + def lfs_route + # Calling route_hash may be expensive. Only do it if we think there's a possible match + return false unless request.path.end_with?('/info/lfs/objects/batch') + + route_hash[:controller] == 'projects/lfs_api' && route_hash[:action] == 'batch' + end + end + end + end +end From 461ecbcf07f0785b5ea50c62b114bf8217ac5199 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 7 Feb 2018 22:55:50 +0800 Subject: [PATCH 05/52] Update peek-performance_bar which doesn't hold env --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index e78c3c5f794..546ba2f2a18 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -601,7 +601,7 @@ GEM atomic (>= 1.0.0) mysql2 peek - peek-performance_bar (1.3.0) + peek-performance_bar (1.3.1) peek (>= 0.1.0) peek-pg (1.3.0) concurrent-ruby From 1a09d5cda8e9f6b90b85351a16fcddea351b869f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarka=20Kadlecov=C3=A1?= Date: Fri, 16 Feb 2018 14:33:50 +0100 Subject: [PATCH 06/52] Render htmlentities correctly for links not supported by Rinku --- changelogs/unreleased/41719-mr-title-fix.yml | 5 ++ lib/banzai/filter/autolink_filter.rb | 36 ++-------- lib/gitlab/string_range_marker.rb | 2 +- lib/gitlab/string_regex_marker.rb | 12 ++-- .../lib/banzai/filter/autolink_filter_spec.rb | 67 +++++++++++++------ spec/lib/gitlab/string_regex_marker_spec.rb | 35 +++++++--- 6 files changed, 90 insertions(+), 67 deletions(-) create mode 100644 changelogs/unreleased/41719-mr-title-fix.yml diff --git a/changelogs/unreleased/41719-mr-title-fix.yml b/changelogs/unreleased/41719-mr-title-fix.yml new file mode 100644 index 00000000000..92388f30cb2 --- /dev/null +++ b/changelogs/unreleased/41719-mr-title-fix.yml @@ -0,0 +1,5 @@ +--- +title: Render htmlentities correctly for links not supported by Rinku +merge_request: +author: +type: fixed diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb index b8d2673c1a6..c4990637971 100644 --- a/lib/banzai/filter/autolink_filter.rb +++ b/lib/banzai/filter/autolink_filter.rb @@ -26,7 +26,7 @@ module Banzai # in the generated link. # # Rubular: http://rubular.com/r/cxjPyZc7Sb - LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://\S+)(?]+)(?See #{link}" - expect(filter(act).to_html).to eq exp - end - end - - context 'when the input contains link' do - it 'does parse_html back the rinku returned value' do - act = HTML::Pipeline.parse("

See #{link}

") - - expect_any_instance_of(described_class).to receive(:parse_html).at_least(:once).and_call_original - - filter(act).to_html - end - end - end - - context 'other schemes' do - let(:link) { 'foo://bar.baz/' } - it 'autolinks smb' do link = 'smb:///Volumes/shared/foo.pdf' doc = filter("See #{link}") @@ -91,6 +85,21 @@ describe Banzai::Filter::AutolinkFilter do expect(doc.at_css('a')['href']).to eq link end + it 'autolinks multiple occurences of smb' do + link1 = 'smb:///Volumes/shared/foo.pdf' + link2 = 'smb:///Volumes/shared/bar.pdf' + + doc = filter("See #{link1} and #{link2}") + + found_links = doc.css('a') + + expect(found_links.size).to eq(2) + expect(found_links[0].text).to eq(link1) + expect(found_links[0]['href']).to eq(link1) + expect(found_links[1].text).to eq(link2) + expect(found_links[1]['href']).to eq(link2) + end + it 'autolinks irc' do link = 'irc://irc.freenode.net/git' doc = filter("See #{link}") @@ -151,4 +160,18 @@ describe Banzai::Filter::AutolinkFilter do end end end + + context 'when the link is inside a tag' do + it 'renders text after the link correctly for http' do + doc = filter(ERB::Util.html_escape_once("")) + + expect(doc.children.last.text).to include('') + end + + it 'renders text after the link correctly for not other protocol' do + doc = filter(ERB::Util.html_escape_once("")) + + expect(doc.children.last.text).to include('') + end + end end diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb index d715f9bd641..37b1298b962 100644 --- a/spec/lib/gitlab/string_regex_marker_spec.rb +++ b/spec/lib/gitlab/string_regex_marker_spec.rb @@ -2,17 +2,36 @@ require 'spec_helper' describe Gitlab::StringRegexMarker do describe '#mark' do - let(:raw) { %{"name": "AFNetworking"} } - let(:rich) { %{"name": "AFNetworking"}.html_safe } - subject do - described_class.new(raw, rich).mark(/"[^"]+":\s*"(?[^"]+)"/, group: :name) do |text, left:, right:| - %{#{text}} + context 'with a single occurrence' do + let(:raw) { %{"name": "AFNetworking"} } + let(:rich) { %{"name": "AFNetworking"}.html_safe } + + subject do + described_class.new(raw, rich).mark(/"[^"]+":\s*"(?[^"]+)"/, group: :name) do |text, left:, right:| + %{#{text}} + end + end + + it 'marks the match' do + expect(subject).to eq(%{"name": "AFNetworking"}) + expect(subject).to be_html_safe end end - it 'marks the inline diffs' do - expect(subject).to eq(%{"name": "AFNetworking"}) - expect(subject).to be_html_safe + context 'with multiple occurrences' do + let(:raw) { %{a d} } + let(:rich) { %{a <b> <c> d}.html_safe } + + subject do + described_class.new(raw, rich).mark(/<[a-z]>/) do |text, left:, right:| + %{#{text}} + end + end + + it 'marks the matches' do + expect(subject).to eq(%{a <b> <c> d}) + expect(subject).to be_html_safe + end end end end From 7b31095ef85982af2aa3bcc5861c5c22e284b15c Mon Sep 17 00:00:00 2001 From: Adam Pahlevi Date: Sun, 5 Feb 2017 14:25:06 +0700 Subject: [PATCH 07/52] /wip slash command on MR creation change to symbol add complete changelog add test for /wip unwip as sym test for work in progress separate from issuable --- app/services/issuable_base_service.rb | 15 + app/services/merge_requests/update_service.rb | 11 - .../slash_commands/interpret_service.rb | 336 +++++++++ changelogs/unreleased/wip-new-mr-cmd.yml | 4 + .../merge_requests/create_service_spec.rb | 35 + .../slash_commands/interpret_service_spec.rb | 689 ++++++++++++++++++ 6 files changed, 1079 insertions(+), 11 deletions(-) create mode 100644 app/services/slash_commands/interpret_service.rb create mode 100644 changelogs/unreleased/wip-new-mr-cmd.yml create mode 100644 spec/services/slash_commands/interpret_service_spec.rb diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index e87fd49d193..c552bf6ea41 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -132,6 +132,7 @@ class IssuableBaseService < BaseService def create(issuable) merge_quick_actions_into_params!(issuable) + handle_wip_event(issuable) filter_params(issuable) params.delete(:state_event) @@ -311,4 +312,18 @@ class IssuableBaseService < BaseService def parent project end + + def handle_wip_event(issuable) + if wip_event = params.delete(:wip_event) + case issuable + when MergeRequest + # We update the title that is provided in the params or we use the mr title + title = params[:title] || issuable.title + params[:title] = case wip_event + when :wip then MergeRequest.wip_title(title) + when :unwip then MergeRequest.wipless_title(title) + end + end + end + end end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index c153872c874..8a40ad88182 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -98,17 +98,6 @@ module MergeRequests private - def handle_wip_event(merge_request) - if wip_event = params.delete(:wip_event) - # We update the title that is provided in the params or we use the mr title - title = params[:title] || merge_request.title - params[:title] = case wip_event - when 'wip' then MergeRequest.wip_title(title) - when 'unwip' then MergeRequest.wipless_title(title) - end - end - end - def create_branch_change_note(issuable, branch_type, old_branch, new_branch) SystemNoteService.change_branch( issuable, issuable.project, current_user, branch_type, diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb new file mode 100644 index 00000000000..0beb173a13d --- /dev/null +++ b/app/services/slash_commands/interpret_service.rb @@ -0,0 +1,336 @@ +module SlashCommands + class InterpretService < BaseService + include Gitlab::SlashCommands::Dsl + + attr_reader :issuable, :options + + # Takes a text and interprets the commands that are extracted from it. + # Returns the content without commands, and hash of changes to be applied to a record. + def execute(content, issuable) + @issuable = issuable + @updates = {} + + opts = { + issuable: issuable, + current_user: current_user, + project: project, + params: params + } + + content, commands = extractor.extract_commands(content, opts) + + commands.each do |name, arg| + definition = self.class.command_definitions_by_name[name.to_sym] + next unless definition + + definition.execute(self, opts, arg) + end + + [content, @updates] + end + + private + + def extractor + Gitlab::SlashCommands::Extractor.new(self.class.command_definitions) + end + + desc do + "Close this #{issuable.to_ability_name.humanize(capitalize: false)}" + end + condition do + issuable.persisted? && + issuable.open? && + current_user.can?(:"update_#{issuable.to_ability_name}", issuable) + end + command :close do + @updates[:state_event] = 'close' + end + + desc do + "Reopen this #{issuable.to_ability_name.humanize(capitalize: false)}" + end + condition do + issuable.persisted? && + issuable.closed? && + current_user.can?(:"update_#{issuable.to_ability_name}", issuable) + end + command :reopen do + @updates[:state_event] = 'reopen' + end + + desc 'Merge (when build succeeds)' + condition do + last_diff_sha = params && params[:merge_request_diff_head_sha] + issuable.is_a?(MergeRequest) && + issuable.persisted? && + issuable.mergeable_with_slash_command?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha) + end + command :merge do + @updates[:merge] = params[:merge_request_diff_head_sha] + end + + desc 'Change title' + params '' + condition do + issuable.persisted? && + current_user.can?(:"update_#{issuable.to_ability_name}", issuable) + end + command :title do |title_param| + @updates[:title] = title_param + end + + desc 'Assign' + params '@user' + condition do + current_user.can?(:"admin_#{issuable.to_ability_name}", project) + end + command :assign do |assignee_param| + user = extract_references(assignee_param, :user).first + user ||= User.find_by(username: assignee_param) + + @updates[:assignee_id] = user.id if user + end + + desc 'Remove assignee' + condition do + issuable.persisted? && + issuable.assignee_id? && + current_user.can?(:"admin_#{issuable.to_ability_name}", project) + end + command :unassign do + @updates[:assignee_id] = nil + end + + desc 'Set milestone' + params '%"milestone"' + condition do + current_user.can?(:"admin_#{issuable.to_ability_name}", project) && + project.milestones.active.any? + end + command :milestone do |milestone_param| + milestone = extract_references(milestone_param, :milestone).first + milestone ||= project.milestones.find_by(title: milestone_param.strip) + + @updates[:milestone_id] = milestone.id if milestone + end + + desc 'Remove milestone' + condition do + issuable.persisted? && + issuable.milestone_id? && + current_user.can?(:"admin_#{issuable.to_ability_name}", project) + end + command :remove_milestone do + @updates[:milestone_id] = nil + end + + desc 'Add label(s)' + params '~label1 ~"label 2"' + condition do + available_labels = LabelsFinder.new(current_user, project_id: project.id).execute + + current_user.can?(:"admin_#{issuable.to_ability_name}", project) && + available_labels.any? + end + command :label do |labels_param| + label_ids = find_label_ids(labels_param) + + if label_ids.any? + @updates[:add_label_ids] ||= [] + @updates[:add_label_ids] += label_ids + + @updates[:add_label_ids].uniq! + end + end + + desc 'Remove all or specific label(s)' + params '~label1 ~"label 2"' + condition do + issuable.persisted? && + issuable.labels.any? && + current_user.can?(:"admin_#{issuable.to_ability_name}", project) + end + command :unlabel do |labels_param = nil| + if labels_param.present? + label_ids = find_label_ids(labels_param) + + if label_ids.any? + @updates[:remove_label_ids] ||= [] + @updates[:remove_label_ids] += label_ids + + @updates[:remove_label_ids].uniq! + end + else + @updates[:label_ids] = [] + end + end + + desc 'Replace all label(s)' + params '~label1 ~"label 2"' + condition do + issuable.persisted? && + issuable.labels.any? && + current_user.can?(:"admin_#{issuable.to_ability_name}", project) + end + command :relabel do |labels_param| + label_ids = find_label_ids(labels_param) + + if label_ids.any? + @updates[:label_ids] ||= [] + @updates[:label_ids] += label_ids + + @updates[:label_ids].uniq! + end + end + + desc 'Add a todo' + condition do + issuable.persisted? && + !TodoService.new.todo_exist?(issuable, current_user) + end + command :todo do + @updates[:todo_event] = 'add' + end + + desc 'Mark todo as done' + condition do + issuable.persisted? && + TodoService.new.todo_exist?(issuable, current_user) + end + command :done do + @updates[:todo_event] = 'done' + end + + desc 'Subscribe' + condition do + issuable.persisted? && + !issuable.subscribed?(current_user, project) + end + command :subscribe do + @updates[:subscription_event] = 'subscribe' + end + + desc 'Unsubscribe' + condition do + issuable.persisted? && + issuable.subscribed?(current_user, project) + end + command :unsubscribe do + @updates[:subscription_event] = 'unsubscribe' + end + + desc 'Set due date' + params '' + condition do + issuable.respond_to?(:due_date) && + current_user.can?(:"admin_#{issuable.to_ability_name}", project) + end + command :due do |due_date_param| + due_date = Chronic.parse(due_date_param).try(:to_date) + + @updates[:due_date] = due_date if due_date + end + + desc 'Remove due date' + condition do + issuable.persisted? && + issuable.respond_to?(:due_date) && + issuable.due_date? && + current_user.can?(:"admin_#{issuable.to_ability_name}", project) + end + command :remove_due_date do + @updates[:due_date] = nil + end + + desc do + "Toggle the Work In Progress status" + end + condition do + issuable.respond_to?(:work_in_progress?) && ( + # /wip on comment text on MR page + (issuable.persisted? && current_user.can?(:"update_#{issuable.to_ability_name}", issuable)) || + # /wip on create MR page + issuable.new_record? + ) + end + command :wip do + @updates[:wip_event] = issuable.work_in_progress? ? :unwip : :wip + end + + desc 'Set time estimate' + params '<1w 3d 2h 14m>' + condition do + current_user.can?(:"admin_#{issuable.to_ability_name}", project) + end + command :estimate do |raw_duration| + time_estimate = Gitlab::TimeTrackingFormatter.parse(raw_duration) + + if time_estimate + @updates[:time_estimate] = time_estimate + end + end + + desc 'Add or substract spent time' + params '<1h 30m | -1h 30m>' + condition do + current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) + end + command :spend do |raw_duration| + time_spent = Gitlab::TimeTrackingFormatter.parse(raw_duration) + + if time_spent + @updates[:spend_time] = { duration: time_spent, user: current_user } + end + end + + desc 'Remove time estimate' + condition do + issuable.persisted? && + current_user.can?(:"admin_#{issuable.to_ability_name}", project) + end + command :remove_estimate do + @updates[:time_estimate] = 0 + end + + desc 'Remove spent time' + condition do + issuable.persisted? && + current_user.can?(:"admin_#{issuable.to_ability_name}", project) + end + command :remove_time_spent do + @updates[:spend_time] = { duration: :reset, user: current_user } + end + + # This is a dummy command, so that it appears in the autocomplete commands + desc 'CC' + params '@user' + command :cc + + desc 'Defines target branch for MR' + params '' + condition do + issuable.respond_to?(:target_branch) && + (current_user.can?(:"update_#{issuable.to_ability_name}", issuable) || + issuable.new_record?) + end + command :target_branch do |target_branch_param| + branch_name = target_branch_param.strip + @updates[:target_branch] = branch_name if project.repository.branch_names.include?(branch_name) + end + + def find_label_ids(labels_param) + label_ids_by_reference = extract_references(labels_param, :label).map(&:id) + labels_ids_by_name = LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute.select(:id) + + label_ids_by_reference | labels_ids_by_name + end + + def extract_references(arg, type) + ext = Gitlab::ReferenceExtractor.new(project, current_user) + ext.analyze(arg, author: current_user) + + ext.references(type) + end + end +end diff --git a/changelogs/unreleased/wip-new-mr-cmd.yml b/changelogs/unreleased/wip-new-mr-cmd.yml new file mode 100644 index 00000000000..08cbe84ea05 --- /dev/null +++ b/changelogs/unreleased/wip-new-mr-cmd.yml @@ -0,0 +1,4 @@ +--- +title: wip slash command option on merge request creation +merge_request: 8982 +author: Adam Pahlevi diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index 5d226f34d2d..44a83c436cb 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -28,6 +28,7 @@ describe MergeRequests::CreateService do it 'creates an MR' do expect(merge_request).to be_valid + expect(merge_request.work_in_progress?).to be(false) expect(merge_request.title).to eq('Awesome merge_request') expect(merge_request.assignee).to be_nil expect(merge_request.merge_params['force_remove_source_branch']).to eq('1') @@ -62,6 +63,40 @@ describe MergeRequests::CreateService do expect(Event.where(attributes).count).to eq(1) end + describe 'when marked with /wip' do + context 'in title and in description' do + let(:opts) do + { + title: 'WIP: Awesome merge_request', + description: "well this is not done yet\n/wip", + source_branch: 'feature', + target_branch: 'master', + assignee: assignee + } + end + + it 'sets MR to WIP' do + expect(merge_request.work_in_progress?).to be(true) + end + end + + context 'in description only' do + let(:opts) do + { + title: 'Awesome merge_request', + description: "well this is not done yet\n/wip", + source_branch: 'feature', + target_branch: 'master', + assignee: assignee + } + end + + it 'sets MR to WIP' do + expect(merge_request.work_in_progress?).to be(true) + end + end + end + context 'when merge request is assigned to someone' do let(:opts) do { diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb new file mode 100644 index 00000000000..7b247397b15 --- /dev/null +++ b/spec/services/slash_commands/interpret_service_spec.rb @@ -0,0 +1,689 @@ +require 'spec_helper' + +describe SlashCommands::InterpretService, services: true do + let(:project) { create(:project, :public) } + let(:developer) { create(:user) } + let(:issue) { create(:issue, project: project) } + let(:milestone) { create(:milestone, project: project, title: '9.10') } + let(:inprogress) { create(:label, project: project, title: 'In Progress') } + let(:bug) { create(:label, project: project, title: 'Bug') } + let(:note) { build(:note, commit_id: merge_request.diff_head_sha) } + + before do + project.team << [developer, :developer] + end + + describe '#execute' do + let(:service) { described_class.new(project, developer) } + let(:merge_request) { create(:merge_request, source_project: project) } + + shared_examples 'reopen command' do + it 'returns state_event: "reopen" if content contains /reopen' do + issuable.close! + _, updates = service.execute(content, issuable) + + expect(updates).to eq(state_event: 'reopen') + end + end + + shared_examples 'close command' do + it 'returns state_event: "close" if content contains /close' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(state_event: 'close') + end + end + + shared_examples 'title command' do + it 'populates title: "A brand new title" if content contains /title A brand new title' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(title: 'A brand new title') + end + end + + shared_examples 'assign command' do + it 'fetches assignee and populates assignee_id if content contains /assign' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(assignee_id: developer.id) + end + end + + shared_examples 'unassign command' do + it 'populates assignee_id: nil if content contains /unassign' do + issuable.update(assignee_id: developer.id) + _, updates = service.execute(content, issuable) + + expect(updates).to eq(assignee_id: nil) + end + end + + shared_examples 'milestone command' do + it 'fetches milestone and populates milestone_id if content contains /milestone' do + milestone # populate the milestone + _, updates = service.execute(content, issuable) + + expect(updates).to eq(milestone_id: milestone.id) + end + end + + shared_examples 'remove_milestone command' do + it 'populates milestone_id: nil if content contains /remove_milestone' do + issuable.update(milestone_id: milestone.id) + _, updates = service.execute(content, issuable) + + expect(updates).to eq(milestone_id: nil) + end + end + + shared_examples 'label command' do + it 'fetches label ids and populates add_label_ids if content contains /label' do + bug # populate the label + inprogress # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(add_label_ids: [bug.id, inprogress.id]) + end + end + + shared_examples 'multiple label command' do + it 'fetches label ids and populates add_label_ids if content contains multiple /label' do + bug # populate the label + inprogress # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(add_label_ids: [inprogress.id, bug.id]) + end + end + + shared_examples 'multiple label with same argument' do + it 'prevents duplicate label ids and populates add_label_ids if content contains multiple /label' do + inprogress # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(add_label_ids: [inprogress.id]) + end + end + + shared_examples 'unlabel command' do + it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do + issuable.update(label_ids: [inprogress.id]) # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(remove_label_ids: [inprogress.id]) + end + end + + shared_examples 'multiple unlabel command' do + it 'fetches label ids and populates remove_label_ids if content contains mutiple /unlabel' do + issuable.update(label_ids: [inprogress.id, bug.id]) # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(remove_label_ids: [inprogress.id, bug.id]) + end + end + + shared_examples 'unlabel command with no argument' do + it 'populates label_ids: [] if content contains /unlabel with no arguments' do + issuable.update(label_ids: [inprogress.id]) # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(label_ids: []) + end + end + + shared_examples 'relabel command' do + it 'populates label_ids: [] if content contains /relabel' do + issuable.update(label_ids: [bug.id]) # populate the label + inprogress # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(label_ids: [inprogress.id]) + end + end + + shared_examples 'todo command' do + it 'populates todo_event: "add" if content contains /todo' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(todo_event: 'add') + end + end + + shared_examples 'done command' do + it 'populates todo_event: "done" if content contains /done' do + TodoService.new.mark_todo(issuable, developer) + _, updates = service.execute(content, issuable) + + expect(updates).to eq(todo_event: 'done') + end + end + + shared_examples 'subscribe command' do + it 'populates subscription_event: "subscribe" if content contains /subscribe' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(subscription_event: 'subscribe') + end + end + + shared_examples 'unsubscribe command' do + it 'populates subscription_event: "unsubscribe" if content contains /unsubscribe' do + issuable.subscribe(developer, project) + _, updates = service.execute(content, issuable) + + expect(updates).to eq(subscription_event: 'unsubscribe') + end + end + + shared_examples 'due command' do + it 'populates due_date: Date.new(2016, 8, 28) if content contains /due 2016-08-28' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(due_date: defined?(expected_date) ? expected_date : Date.new(2016, 8, 28)) + end + end + + shared_examples 'remove_due_date command' do + it 'populates due_date: nil if content contains /remove_due_date' do + issuable.update(due_date: Date.today) + _, updates = service.execute(content, issuable) + + expect(updates).to eq(due_date: nil) + end + end + + shared_examples 'wip command' do + it 'returns wip_event: "wip" if content contains /wip' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(wip_event: :wip) + end + end + + shared_examples 'unwip command' do + it 'returns wip_event: "unwip" if content contains /wip' do + issuable.update(title: issuable.wip_title) + _, updates = service.execute(content, issuable) + + expect(updates).to eq(wip_event: :unwip) + end + end + + shared_examples 'estimate command' do + it 'populates time_estimate: 3600 if content contains /estimate 1h' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(time_estimate: 3600) + end + end + + shared_examples 'spend command' do + it 'populates spend_time: 3600 if content contains /spend 1h' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(spend_time: { duration: 3600, user: developer }) + end + end + + shared_examples 'spend command with negative time' do + it 'populates spend_time: -1800 if content contains /spend -30m' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(spend_time: { duration: -1800, user: developer }) + end + end + + shared_examples 'remove_estimate command' do + it 'populates time_estimate: 0 if content contains /remove_estimate' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(time_estimate: 0) + end + end + + shared_examples 'remove_time_spent command' do + it 'populates spend_time: :reset if content contains /remove_time_spent' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(spend_time: { duration: :reset, user: developer }) + end + end + + shared_examples 'empty command' do + it 'populates {} if content contains an unsupported command' do + _, updates = service.execute(content, issuable) + + expect(updates).to be_empty + end + end + + shared_examples 'merge command' do + it 'runs merge command if content contains /merge' do + _, updates = service.execute(content, issuable) + + expect(updates).to eq(merge: merge_request.diff_head_sha) + end + end + + it_behaves_like 'reopen command' do + let(:content) { '/reopen' } + let(:issuable) { issue } + end + + it_behaves_like 'reopen command' do + let(:content) { '/reopen' } + let(:issuable) { merge_request } + end + + it_behaves_like 'close command' do + let(:content) { '/close' } + let(:issuable) { issue } + end + + it_behaves_like 'close command' do + let(:content) { '/close' } + let(:issuable) { merge_request } + end + + context 'merge command' do + let(:service) { described_class.new(project, developer, { merge_request_diff_head_sha: merge_request.diff_head_sha }) } + + it_behaves_like 'merge command' do + let(:content) { '/merge' } + let(:issuable) { merge_request } + end + + context 'can not be merged when logged user does not have permissions' do + let(:service) { described_class.new(project, create(:user)) } + + it_behaves_like 'empty command' do + let(:content) { "/merge" } + let(:issuable) { merge_request } + end + end + + context 'can not be merged when sha does not match' do + let(:service) { described_class.new(project, developer, { merge_request_diff_head_sha: 'othersha' }) } + + it_behaves_like 'empty command' do + let(:content) { "/merge" } + let(:issuable) { merge_request } + end + end + + context 'when sha is missing' do + let(:service) { described_class.new(project, developer, {}) } + + it 'precheck passes and returns merge command' do + _, updates = service.execute('/merge', merge_request) + + expect(updates).to eq(merge: nil) + end + end + + context 'issue can not be merged' do + it_behaves_like 'empty command' do + let(:content) { "/merge" } + let(:issuable) { issue } + end + end + + context 'non persisted merge request cant be merged' do + it_behaves_like 'empty command' do + let(:content) { "/merge" } + let(:issuable) { build(:merge_request) } + end + end + + context 'not persisted merge request can not be merged' do + it_behaves_like 'empty command' do + let(:content) { "/merge" } + let(:issuable) { build(:merge_request, source_project: project) } + end + end + end + + it_behaves_like 'title command' do + let(:content) { '/title A brand new title' } + let(:issuable) { issue } + end + + it_behaves_like 'title command' do + let(:content) { '/title A brand new title' } + let(:issuable) { merge_request } + end + + it_behaves_like 'empty command' do + let(:content) { '/title' } + let(:issuable) { issue } + end + + it_behaves_like 'assign command' do + let(:content) { "/assign @#{developer.username}" } + let(:issuable) { issue } + end + + it_behaves_like 'assign command' do + let(:content) { "/assign @#{developer.username}" } + let(:issuable) { merge_request } + end + + it_behaves_like 'empty command' do + let(:content) { '/assign @abcd1234' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/assign' } + let(:issuable) { issue } + end + + it_behaves_like 'unassign command' do + let(:content) { '/unassign' } + let(:issuable) { issue } + end + + it_behaves_like 'unassign command' do + let(:content) { '/unassign' } + let(:issuable) { merge_request } + end + + it_behaves_like 'milestone command' do + let(:content) { "/milestone %#{milestone.title}" } + let(:issuable) { issue } + end + + it_behaves_like 'milestone command' do + let(:content) { "/milestone %#{milestone.title}" } + let(:issuable) { merge_request } + end + + it_behaves_like 'remove_milestone command' do + let(:content) { '/remove_milestone' } + let(:issuable) { issue } + end + + it_behaves_like 'remove_milestone command' do + let(:content) { '/remove_milestone' } + let(:issuable) { merge_request } + end + + it_behaves_like 'label command' do + let(:content) { %(/label ~"#{inprogress.title}" ~#{bug.title} ~unknown) } + let(:issuable) { issue } + end + + it_behaves_like 'label command' do + let(:content) { %(/label ~"#{inprogress.title}" ~#{bug.title} ~unknown) } + let(:issuable) { merge_request } + end + + it_behaves_like 'multiple label command' do + let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{bug.title}) } + let(:issuable) { issue } + end + + it_behaves_like 'multiple label with same argument' do + let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{inprogress.title}) } + let(:issuable) { issue } + end + + it_behaves_like 'unlabel command' do + let(:content) { %(/unlabel ~"#{inprogress.title}") } + let(:issuable) { issue } + end + + it_behaves_like 'unlabel command' do + let(:content) { %(/unlabel ~"#{inprogress.title}") } + let(:issuable) { merge_request } + end + + it_behaves_like 'multiple unlabel command' do + let(:content) { %(/unlabel ~"#{inprogress.title}" \n/unlabel ~#{bug.title}) } + let(:issuable) { issue } + end + + it_behaves_like 'unlabel command with no argument' do + let(:content) { %(/unlabel) } + let(:issuable) { issue } + end + + it_behaves_like 'unlabel command with no argument' do + let(:content) { %(/unlabel) } + let(:issuable) { merge_request } + end + + it_behaves_like 'relabel command' do + let(:content) { %(/relabel ~"#{inprogress.title}") } + let(:issuable) { issue } + end + + it_behaves_like 'relabel command' do + let(:content) { %(/relabel ~"#{inprogress.title}") } + let(:issuable) { merge_request } + end + + it_behaves_like 'todo command' do + let(:content) { '/todo' } + let(:issuable) { issue } + end + + it_behaves_like 'todo command' do + let(:content) { '/todo' } + let(:issuable) { merge_request } + end + + it_behaves_like 'done command' do + let(:content) { '/done' } + let(:issuable) { issue } + end + + it_behaves_like 'done command' do + let(:content) { '/done' } + let(:issuable) { merge_request } + end + + it_behaves_like 'subscribe command' do + let(:content) { '/subscribe' } + let(:issuable) { issue } + end + + it_behaves_like 'subscribe command' do + let(:content) { '/subscribe' } + let(:issuable) { merge_request } + end + + it_behaves_like 'unsubscribe command' do + let(:content) { '/unsubscribe' } + let(:issuable) { issue } + end + + it_behaves_like 'unsubscribe command' do + let(:content) { '/unsubscribe' } + let(:issuable) { merge_request } + end + + it_behaves_like 'due command' do + let(:content) { '/due 2016-08-28' } + let(:issuable) { issue } + end + + it_behaves_like 'due command' do + let(:content) { '/due tomorrow' } + let(:issuable) { issue } + let(:expected_date) { Date.tomorrow } + end + + it_behaves_like 'due command' do + let(:content) { '/due 5 days from now' } + let(:issuable) { issue } + let(:expected_date) { 5.days.from_now.to_date } + end + + it_behaves_like 'due command' do + let(:content) { '/due in 2 days' } + let(:issuable) { issue } + let(:expected_date) { 2.days.from_now.to_date } + end + + it_behaves_like 'empty command' do + let(:content) { '/due foo bar' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/due 2016-08-28' } + let(:issuable) { merge_request } + end + + it_behaves_like 'remove_due_date command' do + let(:content) { '/remove_due_date' } + let(:issuable) { issue } + end + + it_behaves_like 'wip command' do + let(:content) { '/wip' } + let(:issuable) { merge_request } + end + + it_behaves_like 'unwip command' do + let(:content) { '/wip' } + let(:issuable) { merge_request } + end + + it_behaves_like 'empty command' do + let(:content) { '/remove_due_date' } + let(:issuable) { merge_request } + end + + it_behaves_like 'estimate command' do + let(:content) { '/estimate 1h' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/estimate' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/estimate abc' } + let(:issuable) { issue } + end + + it_behaves_like 'spend command' do + let(:content) { '/spend 1h' } + let(:issuable) { issue } + end + + it_behaves_like 'spend command with negative time' do + let(:content) { '/spend -30m' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/spend' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/spend abc' } + let(:issuable) { issue } + end + + it_behaves_like 'remove_estimate command' do + let(:content) { '/remove_estimate' } + let(:issuable) { issue } + end + + it_behaves_like 'remove_time_spent command' do + let(:content) { '/remove_time_spent' } + let(:issuable) { issue } + end + + context 'when current_user cannot :admin_issue' do + let(:visitor) { create(:user) } + let(:issue) { create(:issue, project: project, author: visitor) } + let(:service) { described_class.new(project, visitor) } + + it_behaves_like 'empty command' do + let(:content) { "/assign @#{developer.username}" } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/unassign' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { "/milestone %#{milestone.title}" } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/remove_milestone' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { %(/label ~"#{inprogress.title}" ~#{bug.title} ~unknown) } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { %(/unlabel ~"#{inprogress.title}") } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { %(/relabel ~"#{inprogress.title}") } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/due tomorrow' } + let(:issuable) { issue } + end + + it_behaves_like 'empty command' do + let(:content) { '/remove_due_date' } + let(:issuable) { issue } + end + end + + context '/target_branch command' do + let(:non_empty_project) { create(:project) } + let(:another_merge_request) { create(:merge_request, author: developer, source_project: non_empty_project) } + let(:service) { described_class.new(non_empty_project, developer)} + + it 'updates target_branch if /target_branch command is executed' do + _, updates = service.execute('/target_branch merge-test', merge_request) + + expect(updates).to eq(target_branch: 'merge-test') + end + + it 'handles blanks around param' do + _, updates = service.execute('/target_branch merge-test ', merge_request) + + expect(updates).to eq(target_branch: 'merge-test') + end + + context 'ignores command with no argument' do + it_behaves_like 'empty command' do + let(:content) { '/target_branch' } + let(:issuable) { another_merge_request } + end + end + + context 'ignores non-existing target branch' do + it_behaves_like 'empty command' do + let(:content) { '/target_branch totally_non_existing_branch' } + let(:issuable) { another_merge_request } + end + end + end + end +end From 6297446d1773c95d86ecd31f591e1829b431f378 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Thu, 1 Mar 2018 11:32:39 -0300 Subject: [PATCH 08/52] Move wip handling to MergeRequest::BaseService --- app/services/issuable_base_service.rb | 21 +- app/services/merge_requests/base_service.rb | 11 + app/services/merge_requests/create_service.rb | 6 + .../quick_actions/interpret_service.rb | 6 +- .../slash_commands/interpret_service.rb | 336 --------- .../slash_commands/interpret_service_spec.rb | 689 ------------------ 6 files changed, 25 insertions(+), 1044 deletions(-) delete mode 100644 app/services/slash_commands/interpret_service.rb delete mode 100644 spec/services/slash_commands/interpret_service_spec.rb diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index c552bf6ea41..5044a3651cf 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -109,6 +109,10 @@ class IssuableBaseService < BaseService @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute end + def handle_quick_actions(issuable) + merge_quick_actions_into_params!(issuable) + end + def merge_quick_actions_into_params!(issuable) original_description = params.fetch(:description, issuable.description) @@ -131,8 +135,7 @@ class IssuableBaseService < BaseService end def create(issuable) - merge_quick_actions_into_params!(issuable) - handle_wip_event(issuable) + handle_quick_actions(issuable) filter_params(issuable) params.delete(:state_event) @@ -312,18 +315,4 @@ class IssuableBaseService < BaseService def parent project end - - def handle_wip_event(issuable) - if wip_event = params.delete(:wip_event) - case issuable - when MergeRequest - # We update the title that is provided in the params or we use the mr title - title = params[:title] || issuable.title - params[:title] = case wip_event - when :wip then MergeRequest.wip_title(title) - when :unwip then MergeRequest.wipless_title(title) - end - end - end - end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 20a2b50d3de..23262b62615 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -24,6 +24,17 @@ module MergeRequests private + def handle_wip_event(merge_request) + if wip_event = params.delete(:wip_event) + # We update the title that is provided in the params or we use the mr title + title = params[:title] || merge_request.title + params[:title] = case wip_event + when 'wip' then MergeRequest.wip_title(title) + when 'unwip' then MergeRequest.wipless_title(title) + end + end + end + def merge_request_metrics_service(merge_request) MergeRequestMetricsService.new(merge_request.metrics) end diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index a18b1c90765..0ed7ee6c57a 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -34,6 +34,12 @@ module MergeRequests super end + # Override from IssuableBaseService + def handle_quick_actions(merge_request) + super + handle_wip_event(merge_request) + end + private def update_merge_requests_head_pipeline(merge_request) diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index 1e9bd84e749..cba49faac31 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -347,9 +347,9 @@ module QuickActions "#{verb} this #{noun} as Work In Progress." end condition do - issuable.persisted? && - issuable.respond_to?(:work_in_progress?) && - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) + issuable.respond_to?(:work_in_progress?) && + # Allow it to mark as WIP on MR creation page _or_ through MR notes. + (issuable.new_record? || current_user.can?(:"update_#{issuable.to_ability_name}", issuable)) end command :wip do @updates[:wip_event] = issuable.work_in_progress? ? 'unwip' : 'wip' diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb deleted file mode 100644 index 0beb173a13d..00000000000 --- a/app/services/slash_commands/interpret_service.rb +++ /dev/null @@ -1,336 +0,0 @@ -module SlashCommands - class InterpretService < BaseService - include Gitlab::SlashCommands::Dsl - - attr_reader :issuable, :options - - # Takes a text and interprets the commands that are extracted from it. - # Returns the content without commands, and hash of changes to be applied to a record. - def execute(content, issuable) - @issuable = issuable - @updates = {} - - opts = { - issuable: issuable, - current_user: current_user, - project: project, - params: params - } - - content, commands = extractor.extract_commands(content, opts) - - commands.each do |name, arg| - definition = self.class.command_definitions_by_name[name.to_sym] - next unless definition - - definition.execute(self, opts, arg) - end - - [content, @updates] - end - - private - - def extractor - Gitlab::SlashCommands::Extractor.new(self.class.command_definitions) - end - - desc do - "Close this #{issuable.to_ability_name.humanize(capitalize: false)}" - end - condition do - issuable.persisted? && - issuable.open? && - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) - end - command :close do - @updates[:state_event] = 'close' - end - - desc do - "Reopen this #{issuable.to_ability_name.humanize(capitalize: false)}" - end - condition do - issuable.persisted? && - issuable.closed? && - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) - end - command :reopen do - @updates[:state_event] = 'reopen' - end - - desc 'Merge (when build succeeds)' - condition do - last_diff_sha = params && params[:merge_request_diff_head_sha] - issuable.is_a?(MergeRequest) && - issuable.persisted? && - issuable.mergeable_with_slash_command?(current_user, autocomplete_precheck: !last_diff_sha, last_diff_sha: last_diff_sha) - end - command :merge do - @updates[:merge] = params[:merge_request_diff_head_sha] - end - - desc 'Change title' - params '' - condition do - issuable.persisted? && - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) - end - command :title do |title_param| - @updates[:title] = title_param - end - - desc 'Assign' - params '@user' - condition do - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :assign do |assignee_param| - user = extract_references(assignee_param, :user).first - user ||= User.find_by(username: assignee_param) - - @updates[:assignee_id] = user.id if user - end - - desc 'Remove assignee' - condition do - issuable.persisted? && - issuable.assignee_id? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :unassign do - @updates[:assignee_id] = nil - end - - desc 'Set milestone' - params '%"milestone"' - condition do - current_user.can?(:"admin_#{issuable.to_ability_name}", project) && - project.milestones.active.any? - end - command :milestone do |milestone_param| - milestone = extract_references(milestone_param, :milestone).first - milestone ||= project.milestones.find_by(title: milestone_param.strip) - - @updates[:milestone_id] = milestone.id if milestone - end - - desc 'Remove milestone' - condition do - issuable.persisted? && - issuable.milestone_id? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :remove_milestone do - @updates[:milestone_id] = nil - end - - desc 'Add label(s)' - params '~label1 ~"label 2"' - condition do - available_labels = LabelsFinder.new(current_user, project_id: project.id).execute - - current_user.can?(:"admin_#{issuable.to_ability_name}", project) && - available_labels.any? - end - command :label do |labels_param| - label_ids = find_label_ids(labels_param) - - if label_ids.any? - @updates[:add_label_ids] ||= [] - @updates[:add_label_ids] += label_ids - - @updates[:add_label_ids].uniq! - end - end - - desc 'Remove all or specific label(s)' - params '~label1 ~"label 2"' - condition do - issuable.persisted? && - issuable.labels.any? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :unlabel do |labels_param = nil| - if labels_param.present? - label_ids = find_label_ids(labels_param) - - if label_ids.any? - @updates[:remove_label_ids] ||= [] - @updates[:remove_label_ids] += label_ids - - @updates[:remove_label_ids].uniq! - end - else - @updates[:label_ids] = [] - end - end - - desc 'Replace all label(s)' - params '~label1 ~"label 2"' - condition do - issuable.persisted? && - issuable.labels.any? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :relabel do |labels_param| - label_ids = find_label_ids(labels_param) - - if label_ids.any? - @updates[:label_ids] ||= [] - @updates[:label_ids] += label_ids - - @updates[:label_ids].uniq! - end - end - - desc 'Add a todo' - condition do - issuable.persisted? && - !TodoService.new.todo_exist?(issuable, current_user) - end - command :todo do - @updates[:todo_event] = 'add' - end - - desc 'Mark todo as done' - condition do - issuable.persisted? && - TodoService.new.todo_exist?(issuable, current_user) - end - command :done do - @updates[:todo_event] = 'done' - end - - desc 'Subscribe' - condition do - issuable.persisted? && - !issuable.subscribed?(current_user, project) - end - command :subscribe do - @updates[:subscription_event] = 'subscribe' - end - - desc 'Unsubscribe' - condition do - issuable.persisted? && - issuable.subscribed?(current_user, project) - end - command :unsubscribe do - @updates[:subscription_event] = 'unsubscribe' - end - - desc 'Set due date' - params '' - condition do - issuable.respond_to?(:due_date) && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :due do |due_date_param| - due_date = Chronic.parse(due_date_param).try(:to_date) - - @updates[:due_date] = due_date if due_date - end - - desc 'Remove due date' - condition do - issuable.persisted? && - issuable.respond_to?(:due_date) && - issuable.due_date? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :remove_due_date do - @updates[:due_date] = nil - end - - desc do - "Toggle the Work In Progress status" - end - condition do - issuable.respond_to?(:work_in_progress?) && ( - # /wip on comment text on MR page - (issuable.persisted? && current_user.can?(:"update_#{issuable.to_ability_name}", issuable)) || - # /wip on create MR page - issuable.new_record? - ) - end - command :wip do - @updates[:wip_event] = issuable.work_in_progress? ? :unwip : :wip - end - - desc 'Set time estimate' - params '<1w 3d 2h 14m>' - condition do - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :estimate do |raw_duration| - time_estimate = Gitlab::TimeTrackingFormatter.parse(raw_duration) - - if time_estimate - @updates[:time_estimate] = time_estimate - end - end - - desc 'Add or substract spent time' - params '<1h 30m | -1h 30m>' - condition do - current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) - end - command :spend do |raw_duration| - time_spent = Gitlab::TimeTrackingFormatter.parse(raw_duration) - - if time_spent - @updates[:spend_time] = { duration: time_spent, user: current_user } - end - end - - desc 'Remove time estimate' - condition do - issuable.persisted? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :remove_estimate do - @updates[:time_estimate] = 0 - end - - desc 'Remove spent time' - condition do - issuable.persisted? && - current_user.can?(:"admin_#{issuable.to_ability_name}", project) - end - command :remove_time_spent do - @updates[:spend_time] = { duration: :reset, user: current_user } - end - - # This is a dummy command, so that it appears in the autocomplete commands - desc 'CC' - params '@user' - command :cc - - desc 'Defines target branch for MR' - params '' - condition do - issuable.respond_to?(:target_branch) && - (current_user.can?(:"update_#{issuable.to_ability_name}", issuable) || - issuable.new_record?) - end - command :target_branch do |target_branch_param| - branch_name = target_branch_param.strip - @updates[:target_branch] = branch_name if project.repository.branch_names.include?(branch_name) - end - - def find_label_ids(labels_param) - label_ids_by_reference = extract_references(labels_param, :label).map(&:id) - labels_ids_by_name = LabelsFinder.new(current_user, project_id: project.id, name: labels_param.split).execute.select(:id) - - label_ids_by_reference | labels_ids_by_name - end - - def extract_references(arg, type) - ext = Gitlab::ReferenceExtractor.new(project, current_user) - ext.analyze(arg, author: current_user) - - ext.references(type) - end - end -end diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb deleted file mode 100644 index 7b247397b15..00000000000 --- a/spec/services/slash_commands/interpret_service_spec.rb +++ /dev/null @@ -1,689 +0,0 @@ -require 'spec_helper' - -describe SlashCommands::InterpretService, services: true do - let(:project) { create(:project, :public) } - let(:developer) { create(:user) } - let(:issue) { create(:issue, project: project) } - let(:milestone) { create(:milestone, project: project, title: '9.10') } - let(:inprogress) { create(:label, project: project, title: 'In Progress') } - let(:bug) { create(:label, project: project, title: 'Bug') } - let(:note) { build(:note, commit_id: merge_request.diff_head_sha) } - - before do - project.team << [developer, :developer] - end - - describe '#execute' do - let(:service) { described_class.new(project, developer) } - let(:merge_request) { create(:merge_request, source_project: project) } - - shared_examples 'reopen command' do - it 'returns state_event: "reopen" if content contains /reopen' do - issuable.close! - _, updates = service.execute(content, issuable) - - expect(updates).to eq(state_event: 'reopen') - end - end - - shared_examples 'close command' do - it 'returns state_event: "close" if content contains /close' do - _, updates = service.execute(content, issuable) - - expect(updates).to eq(state_event: 'close') - end - end - - shared_examples 'title command' do - it 'populates title: "A brand new title" if content contains /title A brand new title' do - _, updates = service.execute(content, issuable) - - expect(updates).to eq(title: 'A brand new title') - end - end - - shared_examples 'assign command' do - it 'fetches assignee and populates assignee_id if content contains /assign' do - _, updates = service.execute(content, issuable) - - expect(updates).to eq(assignee_id: developer.id) - end - end - - shared_examples 'unassign command' do - it 'populates assignee_id: nil if content contains /unassign' do - issuable.update(assignee_id: developer.id) - _, updates = service.execute(content, issuable) - - expect(updates).to eq(assignee_id: nil) - end - end - - shared_examples 'milestone command' do - it 'fetches milestone and populates milestone_id if content contains /milestone' do - milestone # populate the milestone - _, updates = service.execute(content, issuable) - - expect(updates).to eq(milestone_id: milestone.id) - end - end - - shared_examples 'remove_milestone command' do - it 'populates milestone_id: nil if content contains /remove_milestone' do - issuable.update(milestone_id: milestone.id) - _, updates = service.execute(content, issuable) - - expect(updates).to eq(milestone_id: nil) - end - end - - shared_examples 'label command' do - it 'fetches label ids and populates add_label_ids if content contains /label' do - bug # populate the label - inprogress # populate the label - _, updates = service.execute(content, issuable) - - expect(updates).to eq(add_label_ids: [bug.id, inprogress.id]) - end - end - - shared_examples 'multiple label command' do - it 'fetches label ids and populates add_label_ids if content contains multiple /label' do - bug # populate the label - inprogress # populate the label - _, updates = service.execute(content, issuable) - - expect(updates).to eq(add_label_ids: [inprogress.id, bug.id]) - end - end - - shared_examples 'multiple label with same argument' do - it 'prevents duplicate label ids and populates add_label_ids if content contains multiple /label' do - inprogress # populate the label - _, updates = service.execute(content, issuable) - - expect(updates).to eq(add_label_ids: [inprogress.id]) - end - end - - shared_examples 'unlabel command' do - it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do - issuable.update(label_ids: [inprogress.id]) # populate the label - _, updates = service.execute(content, issuable) - - expect(updates).to eq(remove_label_ids: [inprogress.id]) - end - end - - shared_examples 'multiple unlabel command' do - it 'fetches label ids and populates remove_label_ids if content contains mutiple /unlabel' do - issuable.update(label_ids: [inprogress.id, bug.id]) # populate the label - _, updates = service.execute(content, issuable) - - expect(updates).to eq(remove_label_ids: [inprogress.id, bug.id]) - end - end - - shared_examples 'unlabel command with no argument' do - it 'populates label_ids: [] if content contains /unlabel with no arguments' do - issuable.update(label_ids: [inprogress.id]) # populate the label - _, updates = service.execute(content, issuable) - - expect(updates).to eq(label_ids: []) - end - end - - shared_examples 'relabel command' do - it 'populates label_ids: [] if content contains /relabel' do - issuable.update(label_ids: [bug.id]) # populate the label - inprogress # populate the label - _, updates = service.execute(content, issuable) - - expect(updates).to eq(label_ids: [inprogress.id]) - end - end - - shared_examples 'todo command' do - it 'populates todo_event: "add" if content contains /todo' do - _, updates = service.execute(content, issuable) - - expect(updates).to eq(todo_event: 'add') - end - end - - shared_examples 'done command' do - it 'populates todo_event: "done" if content contains /done' do - TodoService.new.mark_todo(issuable, developer) - _, updates = service.execute(content, issuable) - - expect(updates).to eq(todo_event: 'done') - end - end - - shared_examples 'subscribe command' do - it 'populates subscription_event: "subscribe" if content contains /subscribe' do - _, updates = service.execute(content, issuable) - - expect(updates).to eq(subscription_event: 'subscribe') - end - end - - shared_examples 'unsubscribe command' do - it 'populates subscription_event: "unsubscribe" if content contains /unsubscribe' do - issuable.subscribe(developer, project) - _, updates = service.execute(content, issuable) - - expect(updates).to eq(subscription_event: 'unsubscribe') - end - end - - shared_examples 'due command' do - it 'populates due_date: Date.new(2016, 8, 28) if content contains /due 2016-08-28' do - _, updates = service.execute(content, issuable) - - expect(updates).to eq(due_date: defined?(expected_date) ? expected_date : Date.new(2016, 8, 28)) - end - end - - shared_examples 'remove_due_date command' do - it 'populates due_date: nil if content contains /remove_due_date' do - issuable.update(due_date: Date.today) - _, updates = service.execute(content, issuable) - - expect(updates).to eq(due_date: nil) - end - end - - shared_examples 'wip command' do - it 'returns wip_event: "wip" if content contains /wip' do - _, updates = service.execute(content, issuable) - - expect(updates).to eq(wip_event: :wip) - end - end - - shared_examples 'unwip command' do - it 'returns wip_event: "unwip" if content contains /wip' do - issuable.update(title: issuable.wip_title) - _, updates = service.execute(content, issuable) - - expect(updates).to eq(wip_event: :unwip) - end - end - - shared_examples 'estimate command' do - it 'populates time_estimate: 3600 if content contains /estimate 1h' do - _, updates = service.execute(content, issuable) - - expect(updates).to eq(time_estimate: 3600) - end - end - - shared_examples 'spend command' do - it 'populates spend_time: 3600 if content contains /spend 1h' do - _, updates = service.execute(content, issuable) - - expect(updates).to eq(spend_time: { duration: 3600, user: developer }) - end - end - - shared_examples 'spend command with negative time' do - it 'populates spend_time: -1800 if content contains /spend -30m' do - _, updates = service.execute(content, issuable) - - expect(updates).to eq(spend_time: { duration: -1800, user: developer }) - end - end - - shared_examples 'remove_estimate command' do - it 'populates time_estimate: 0 if content contains /remove_estimate' do - _, updates = service.execute(content, issuable) - - expect(updates).to eq(time_estimate: 0) - end - end - - shared_examples 'remove_time_spent command' do - it 'populates spend_time: :reset if content contains /remove_time_spent' do - _, updates = service.execute(content, issuable) - - expect(updates).to eq(spend_time: { duration: :reset, user: developer }) - end - end - - shared_examples 'empty command' do - it 'populates {} if content contains an unsupported command' do - _, updates = service.execute(content, issuable) - - expect(updates).to be_empty - end - end - - shared_examples 'merge command' do - it 'runs merge command if content contains /merge' do - _, updates = service.execute(content, issuable) - - expect(updates).to eq(merge: merge_request.diff_head_sha) - end - end - - it_behaves_like 'reopen command' do - let(:content) { '/reopen' } - let(:issuable) { issue } - end - - it_behaves_like 'reopen command' do - let(:content) { '/reopen' } - let(:issuable) { merge_request } - end - - it_behaves_like 'close command' do - let(:content) { '/close' } - let(:issuable) { issue } - end - - it_behaves_like 'close command' do - let(:content) { '/close' } - let(:issuable) { merge_request } - end - - context 'merge command' do - let(:service) { described_class.new(project, developer, { merge_request_diff_head_sha: merge_request.diff_head_sha }) } - - it_behaves_like 'merge command' do - let(:content) { '/merge' } - let(:issuable) { merge_request } - end - - context 'can not be merged when logged user does not have permissions' do - let(:service) { described_class.new(project, create(:user)) } - - it_behaves_like 'empty command' do - let(:content) { "/merge" } - let(:issuable) { merge_request } - end - end - - context 'can not be merged when sha does not match' do - let(:service) { described_class.new(project, developer, { merge_request_diff_head_sha: 'othersha' }) } - - it_behaves_like 'empty command' do - let(:content) { "/merge" } - let(:issuable) { merge_request } - end - end - - context 'when sha is missing' do - let(:service) { described_class.new(project, developer, {}) } - - it 'precheck passes and returns merge command' do - _, updates = service.execute('/merge', merge_request) - - expect(updates).to eq(merge: nil) - end - end - - context 'issue can not be merged' do - it_behaves_like 'empty command' do - let(:content) { "/merge" } - let(:issuable) { issue } - end - end - - context 'non persisted merge request cant be merged' do - it_behaves_like 'empty command' do - let(:content) { "/merge" } - let(:issuable) { build(:merge_request) } - end - end - - context 'not persisted merge request can not be merged' do - it_behaves_like 'empty command' do - let(:content) { "/merge" } - let(:issuable) { build(:merge_request, source_project: project) } - end - end - end - - it_behaves_like 'title command' do - let(:content) { '/title A brand new title' } - let(:issuable) { issue } - end - - it_behaves_like 'title command' do - let(:content) { '/title A brand new title' } - let(:issuable) { merge_request } - end - - it_behaves_like 'empty command' do - let(:content) { '/title' } - let(:issuable) { issue } - end - - it_behaves_like 'assign command' do - let(:content) { "/assign @#{developer.username}" } - let(:issuable) { issue } - end - - it_behaves_like 'assign command' do - let(:content) { "/assign @#{developer.username}" } - let(:issuable) { merge_request } - end - - it_behaves_like 'empty command' do - let(:content) { '/assign @abcd1234' } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { '/assign' } - let(:issuable) { issue } - end - - it_behaves_like 'unassign command' do - let(:content) { '/unassign' } - let(:issuable) { issue } - end - - it_behaves_like 'unassign command' do - let(:content) { '/unassign' } - let(:issuable) { merge_request } - end - - it_behaves_like 'milestone command' do - let(:content) { "/milestone %#{milestone.title}" } - let(:issuable) { issue } - end - - it_behaves_like 'milestone command' do - let(:content) { "/milestone %#{milestone.title}" } - let(:issuable) { merge_request } - end - - it_behaves_like 'remove_milestone command' do - let(:content) { '/remove_milestone' } - let(:issuable) { issue } - end - - it_behaves_like 'remove_milestone command' do - let(:content) { '/remove_milestone' } - let(:issuable) { merge_request } - end - - it_behaves_like 'label command' do - let(:content) { %(/label ~"#{inprogress.title}" ~#{bug.title} ~unknown) } - let(:issuable) { issue } - end - - it_behaves_like 'label command' do - let(:content) { %(/label ~"#{inprogress.title}" ~#{bug.title} ~unknown) } - let(:issuable) { merge_request } - end - - it_behaves_like 'multiple label command' do - let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{bug.title}) } - let(:issuable) { issue } - end - - it_behaves_like 'multiple label with same argument' do - let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{inprogress.title}) } - let(:issuable) { issue } - end - - it_behaves_like 'unlabel command' do - let(:content) { %(/unlabel ~"#{inprogress.title}") } - let(:issuable) { issue } - end - - it_behaves_like 'unlabel command' do - let(:content) { %(/unlabel ~"#{inprogress.title}") } - let(:issuable) { merge_request } - end - - it_behaves_like 'multiple unlabel command' do - let(:content) { %(/unlabel ~"#{inprogress.title}" \n/unlabel ~#{bug.title}) } - let(:issuable) { issue } - end - - it_behaves_like 'unlabel command with no argument' do - let(:content) { %(/unlabel) } - let(:issuable) { issue } - end - - it_behaves_like 'unlabel command with no argument' do - let(:content) { %(/unlabel) } - let(:issuable) { merge_request } - end - - it_behaves_like 'relabel command' do - let(:content) { %(/relabel ~"#{inprogress.title}") } - let(:issuable) { issue } - end - - it_behaves_like 'relabel command' do - let(:content) { %(/relabel ~"#{inprogress.title}") } - let(:issuable) { merge_request } - end - - it_behaves_like 'todo command' do - let(:content) { '/todo' } - let(:issuable) { issue } - end - - it_behaves_like 'todo command' do - let(:content) { '/todo' } - let(:issuable) { merge_request } - end - - it_behaves_like 'done command' do - let(:content) { '/done' } - let(:issuable) { issue } - end - - it_behaves_like 'done command' do - let(:content) { '/done' } - let(:issuable) { merge_request } - end - - it_behaves_like 'subscribe command' do - let(:content) { '/subscribe' } - let(:issuable) { issue } - end - - it_behaves_like 'subscribe command' do - let(:content) { '/subscribe' } - let(:issuable) { merge_request } - end - - it_behaves_like 'unsubscribe command' do - let(:content) { '/unsubscribe' } - let(:issuable) { issue } - end - - it_behaves_like 'unsubscribe command' do - let(:content) { '/unsubscribe' } - let(:issuable) { merge_request } - end - - it_behaves_like 'due command' do - let(:content) { '/due 2016-08-28' } - let(:issuable) { issue } - end - - it_behaves_like 'due command' do - let(:content) { '/due tomorrow' } - let(:issuable) { issue } - let(:expected_date) { Date.tomorrow } - end - - it_behaves_like 'due command' do - let(:content) { '/due 5 days from now' } - let(:issuable) { issue } - let(:expected_date) { 5.days.from_now.to_date } - end - - it_behaves_like 'due command' do - let(:content) { '/due in 2 days' } - let(:issuable) { issue } - let(:expected_date) { 2.days.from_now.to_date } - end - - it_behaves_like 'empty command' do - let(:content) { '/due foo bar' } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { '/due 2016-08-28' } - let(:issuable) { merge_request } - end - - it_behaves_like 'remove_due_date command' do - let(:content) { '/remove_due_date' } - let(:issuable) { issue } - end - - it_behaves_like 'wip command' do - let(:content) { '/wip' } - let(:issuable) { merge_request } - end - - it_behaves_like 'unwip command' do - let(:content) { '/wip' } - let(:issuable) { merge_request } - end - - it_behaves_like 'empty command' do - let(:content) { '/remove_due_date' } - let(:issuable) { merge_request } - end - - it_behaves_like 'estimate command' do - let(:content) { '/estimate 1h' } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { '/estimate' } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { '/estimate abc' } - let(:issuable) { issue } - end - - it_behaves_like 'spend command' do - let(:content) { '/spend 1h' } - let(:issuable) { issue } - end - - it_behaves_like 'spend command with negative time' do - let(:content) { '/spend -30m' } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { '/spend' } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { '/spend abc' } - let(:issuable) { issue } - end - - it_behaves_like 'remove_estimate command' do - let(:content) { '/remove_estimate' } - let(:issuable) { issue } - end - - it_behaves_like 'remove_time_spent command' do - let(:content) { '/remove_time_spent' } - let(:issuable) { issue } - end - - context 'when current_user cannot :admin_issue' do - let(:visitor) { create(:user) } - let(:issue) { create(:issue, project: project, author: visitor) } - let(:service) { described_class.new(project, visitor) } - - it_behaves_like 'empty command' do - let(:content) { "/assign @#{developer.username}" } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { '/unassign' } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { "/milestone %#{milestone.title}" } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { '/remove_milestone' } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { %(/label ~"#{inprogress.title}" ~#{bug.title} ~unknown) } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { %(/unlabel ~"#{inprogress.title}") } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { %(/relabel ~"#{inprogress.title}") } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { '/due tomorrow' } - let(:issuable) { issue } - end - - it_behaves_like 'empty command' do - let(:content) { '/remove_due_date' } - let(:issuable) { issue } - end - end - - context '/target_branch command' do - let(:non_empty_project) { create(:project) } - let(:another_merge_request) { create(:merge_request, author: developer, source_project: non_empty_project) } - let(:service) { described_class.new(non_empty_project, developer)} - - it 'updates target_branch if /target_branch command is executed' do - _, updates = service.execute('/target_branch merge-test', merge_request) - - expect(updates).to eq(target_branch: 'merge-test') - end - - it 'handles blanks around param' do - _, updates = service.execute('/target_branch merge-test ', merge_request) - - expect(updates).to eq(target_branch: 'merge-test') - end - - context 'ignores command with no argument' do - it_behaves_like 'empty command' do - let(:content) { '/target_branch' } - let(:issuable) { another_merge_request } - end - end - - context 'ignores non-existing target branch' do - it_behaves_like 'empty command' do - let(:content) { '/target_branch totally_non_existing_branch' } - let(:issuable) { another_merge_request } - end - end - end - end -end From 30ef73afac3d57af87ee948bb7c90743f49e6da8 Mon Sep 17 00:00:00 2001 From: julien MILLAU Date: Fri, 2 Mar 2018 10:10:39 +0000 Subject: [PATCH 09/52] Remove impersonate token from flash scope --- app/controllers/admin/impersonation_tokens_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/admin/impersonation_tokens_controller.rb b/app/controllers/admin/impersonation_tokens_controller.rb index 7a2c7234a1e..a7b562b1d8e 100644 --- a/app/controllers/admin/impersonation_tokens_controller.rb +++ b/app/controllers/admin/impersonation_tokens_controller.rb @@ -9,7 +9,6 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController @impersonation_token = finder.build(impersonation_token_params) if @impersonation_token.save - flash[:impersonation_token] = @impersonation_token.token redirect_to admin_user_impersonation_tokens_path, notice: "A new impersonation token has been created." else set_index_vars From 960f981db7e5f3a1bd9ee2ad8682435e1bc71a46 Mon Sep 17 00:00:00 2001 From: Andrew Newdigate Date: Fri, 2 Mar 2018 10:25:11 +0000 Subject: [PATCH 10/52] Upgrade GitLab Workhorse to 3.8.0 for structured logging support --- GITLAB_WORKHORSE_VERSION | 2 +- changelogs/unreleased/an-workhorse-3-8-0.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/an-workhorse-3-8-0.yml diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 40c341bdcdb..19811903a7f 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -3.6.0 +3.8.0 diff --git a/changelogs/unreleased/an-workhorse-3-8-0.yml b/changelogs/unreleased/an-workhorse-3-8-0.yml new file mode 100644 index 00000000000..5e2a72e1eda --- /dev/null +++ b/changelogs/unreleased/an-workhorse-3-8-0.yml @@ -0,0 +1,5 @@ +--- +title: Upgrade Workhorse to version 3.8.0 to support structured logging +merge_request: +author: +type: other From cb55bc3c0770adf7122d7cf49b12cb45c43de7ec Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 22 Feb 2018 12:09:27 +0000 Subject: [PATCH 11/52] Match Rinku's behaviour for closing punctuation in links Rinku 2.0.0 (the version we use) will remove the last character of a link if it's a closing part of a punctuation pair (different types of parentheses and quotes), unless both of the below are true: 1. The matching pair has different start and end characters. 2. There are equal numbers of both in the matched string (they don't have to be balanced). --- lib/banzai/filter/autolink_filter.rb | 50 ++++++++---- .../lib/banzai/filter/autolink_filter_spec.rb | 79 ++++++++++++++----- 2 files changed, 95 insertions(+), 34 deletions(-) diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb index c4990637971..75b64ae9af2 100644 --- a/lib/banzai/filter/autolink_filter.rb +++ b/lib/banzai/filter/autolink_filter.rb @@ -25,7 +25,7 @@ module Banzai # period or comma for punctuation without those characters being included # in the generated link. # - # Rubular: http://rubular.com/r/cxjPyZc7Sb + # Rubular: http://rubular.com/r/JzPhi6DCZp LINK_PATTERN = %r{([a-z][a-z0-9\+\.-]+://[^\s>]+)(? "'", + '"' => '"', + ')' => '(', + ']' => '[', + '}' => '{' + }.freeze + def call return doc if context[:autolink] == false - text_parse - end - - private - - # Return true if any of the UNSAFE_PROTOCOLS strings are included in the URI scheme - def contains_unsafe?(scheme) - return false unless scheme - - scheme = scheme.strip.downcase - Banzai::Filter::SanitizationFilter::UNSAFE_PROTOCOLS.any? { |protocol| scheme.include?(protocol) } - end - - def text_parse doc.xpath(TEXT_QUERY).each do |node| content = node.to_html @@ -69,6 +63,16 @@ module Banzai doc end + private + + # Return true if any of the UNSAFE_PROTOCOLS strings are included in the URI scheme + def contains_unsafe?(scheme) + return false unless scheme + + scheme = scheme.strip.downcase + Banzai::Filter::SanitizationFilter::UNSAFE_PROTOCOLS.any? { |protocol| scheme.include?(protocol) } + end + def autolink_match(match) # start by stripping out dangerous links begin @@ -84,6 +88,22 @@ module Banzai match.gsub!(/((?:&[\w#]+;)+)\z/, '') dropped = ($1 || '').html_safe + # To match the behaviour of Rinku, if the matched link ends with a + # closing part of a matched pair of punctuation, we remove that trailing + # character unless there are an equal number of closing and opening + # characters in the link. + if match.end_with?(*PUNCTUATION_PAIRS.keys) + close_character = match[-1] + close_count = match.count(close_character) + open_character = PUNCTUATION_PAIRS[close_character] + open_count = match.count(open_character) + + if open_count != close_count || open_character == close_character + dropped += close_character + match = match[0..-2] + end + end + options = link_options.merge(href: match) content_tag(:a, match.html_safe, options) + dropped end diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb index 0498b99ccf3..b502daea418 100644 --- a/spec/lib/banzai/filter/autolink_filter_spec.rb +++ b/spec/lib/banzai/filter/autolink_filter_spec.rb @@ -4,6 +4,7 @@ describe Banzai::Filter::AutolinkFilter do include FilterSpecHelper let(:link) { 'http://about.gitlab.com/' } + let(:quotes) { ['"', "'"] } it 'does nothing when :autolink is false' do exp = act = link @@ -15,16 +16,6 @@ describe Banzai::Filter::AutolinkFilter do expect(filter(act).to_html).to eq exp end - context 'when the input contains no links' do - it 'does not parse_html back the rinku returned value' do - act = HTML::Pipeline.parse('

This text contains no links to autolink

') - - expect_any_instance_of(described_class).not_to receive(:parse_html) - - filter(act).to_html - end - end - context 'Various schemes' do it 'autolinks http' do doc = filter("See #{link}") @@ -141,6 +132,45 @@ describe Banzai::Filter::AutolinkFilter do expect(doc.at_css('a').text).to eq link end + it 'includes trailing punctuation when part of a balanced pair' do + described_class::PUNCTUATION_PAIRS.each do |close, open| + next if open.in?(quotes) + + balanced_link = "#{link}#{open}abc#{close}" + balanced_actual = filter("See #{balanced_link}...") + unbalanced_link = "#{link}#{close}" + unbalanced_actual = filter("See #{unbalanced_link}...") + + expect(balanced_actual.at_css('a').text).to eq(balanced_link) + expect(unescape(balanced_actual.to_html)).to eq(Rinku.auto_link("See #{balanced_link}...")) + expect(unbalanced_actual.at_css('a').text).to eq(link) + expect(unescape(unbalanced_actual.to_html)).to eq(Rinku.auto_link("See #{unbalanced_link}...")) + end + end + + it 'removes trailing quotes' do + quotes.each do |quote| + balanced_link = "#{link}#{quote}abc#{quote}" + balanced_actual = filter("See #{balanced_link}...") + unbalanced_link = "#{link}#{quote}" + unbalanced_actual = filter("See #{unbalanced_link}...") + + expect(balanced_actual.at_css('a').text).to eq(balanced_link[0...-1]) + expect(unescape(balanced_actual.to_html)).to eq(Rinku.auto_link("See #{balanced_link}...")) + expect(unbalanced_actual.at_css('a').text).to eq(link) + expect(unescape(unbalanced_actual.to_html)).to eq(Rinku.auto_link("See #{unbalanced_link}...")) + end + end + + it 'removes one closing punctuation mark when the punctuation in the link is unbalanced' do + complicated_link = "(#{link}(a'b[c'd]))'" + expected_complicated_link = %Q{(#{link}(a'b[c'd]))'} + actual = unescape(filter(complicated_link).to_html) + + expect(actual).to eq(Rinku.auto_link(complicated_link)) + expect(actual).to eq(expected_complicated_link) + end + it 'does not include trailing HTML entities' do doc = filter("See <<<#{link}>>>") @@ -162,16 +192,27 @@ describe Banzai::Filter::AutolinkFilter do end context 'when the link is inside a tag' do - it 'renders text after the link correctly for http' do - doc = filter(ERB::Util.html_escape_once("")) + %w[http rdar].each do |protocol| + it "renders text after the link correctly for #{protocol}" do + doc = filter(ERB::Util.html_escape_once("<#{protocol}://link>")) - expect(doc.children.last.text).to include('') - end - - it 'renders text after the link correctly for not other protocol' do - doc = filter(ERB::Util.html_escape_once("")) - - expect(doc.children.last.text).to include('') + expect(doc.children.last.text).to include('') + end end end + + # Rinku does not escape these characters in HTML attributes, but content_tag + # does. We don't care about that difference for these specs, though. + def unescape(html) + %w([ ] { }).each do |cgi_escape| + html.sub!(CGI.escape(cgi_escape), cgi_escape) + end + + quotes.each do |html_escape| + html.sub!(CGI.escape_html(html_escape), html_escape) + html.sub!(CGI.escape(html_escape), CGI.escape_html(html_escape)) + end + + html + end end From f09fe848da02189a3dafe2d532fefb1379eacac4 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 1 Mar 2018 17:16:39 +0100 Subject: [PATCH 12/52] Don't use ProjectsFinder in TodosFinder Using ProjectsFinder in TodosFinder to limit todos to the right projects leads to overly complicated and slow database queries. This commit removes the use of ProjectsFinder in favour of using Project.public_or_visible_to_current_user directly. Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/43767 --- app/finders/todos_finder.rb | 15 ++++++--------- .../remove-projects-finder-from-todos-finder.yml | 5 +++++ 2 files changed, 11 insertions(+), 9 deletions(-) create mode 100644 changelogs/unreleased/remove-projects-finder-from-todos-finder.yml diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index edb17843002..47c8b9b60ed 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -110,10 +110,6 @@ class TodosFinder ids end - def projects(items) - ProjectsFinder.new(current_user: current_user, project_ids_relation: project_ids(items)).execute - end - def type? type.present? && %w(Issue MergeRequest).include?(type) end @@ -152,13 +148,14 @@ class TodosFinder def by_project(items) if project? - items = items.where(project: project) + items.where(project: project) else - item_projects = projects(items) - items = items.merge(item_projects).joins(:project) - end + projects = Project + .public_or_visible_to_user(current_user) + .order_id_desc - items + items.joins(:project).merge(projects) + end end def by_state(items) diff --git a/changelogs/unreleased/remove-projects-finder-from-todos-finder.yml b/changelogs/unreleased/remove-projects-finder-from-todos-finder.yml new file mode 100644 index 00000000000..0a3fc751edb --- /dev/null +++ b/changelogs/unreleased/remove-projects-finder-from-todos-finder.yml @@ -0,0 +1,5 @@ +--- +title: Don't use ProjectsFinder in TodosFinder +merge_request: +author: +type: performance From cd770946c420b92535bd6000d0b8ea2fc89cbd0a Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Thu, 1 Mar 2018 17:50:07 +0100 Subject: [PATCH 13/52] Add support for :all option to {count,find}_commits --- GITALY_SERVER_VERSION | 2 +- Gemfile | 2 +- Gemfile.lock | 4 +- lib/gitlab/git/repository.rb | 6 +- lib/gitlab/gitaly_client/commit_service.rb | 4 +- spec/lib/gitlab/git/repository_spec.rb | 472 +++++++++++---------- 6 files changed, 249 insertions(+), 241 deletions(-) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 359ee08a7ce..fe6d01c1a45 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.87.0 +0.88.0 diff --git a/Gemfile b/Gemfile index d8cb5267d81..a3352b8923c 100644 --- a/Gemfile +++ b/Gemfile @@ -411,7 +411,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 0.87.0', require: 'gitaly' +gem 'gitaly-proto', '~> 0.88.0', require: 'gitaly' # Locked until https://github.com/google/protobuf/issues/4210 is closed gem 'google-protobuf', '= 3.5.1' diff --git a/Gemfile.lock b/Gemfile.lock index 6918f92aa84..70f86a45043 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -285,7 +285,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) gherkin-ruby (0.3.2) - gitaly-proto (0.87.0) + gitaly-proto (0.88.0) google-protobuf (~> 3.1) grpc (~> 1.0) github-linguist (5.3.3) @@ -1057,7 +1057,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.2.0) - gitaly-proto (~> 0.87.0) + gitaly-proto (~> 0.88.0) github-linguist (~> 5.3.3) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.6.2) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index d7c373ccd6f..21c79a7a550 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -479,9 +479,8 @@ module Gitlab raise ArgumentError.new("invalid Repository#log limit: #{limit.inspect}") end - # TODO support options[:all] in Gitaly https://gitlab.com/gitlab-org/gitaly/issues/1049 gitaly_migrate(:find_commits) do |is_enabled| - if is_enabled && !options[:all] + if is_enabled gitaly_commit_client.find_commits(options) else raw_log(options).map { |c| Commit.decorate(self, c) } @@ -508,9 +507,8 @@ module Gitlab def count_commits(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| - if is_enabled && !options[:all] + if is_enabled count_commits_by_gitaly(count_commits_options) else count_commits_by_shelling_out(count_commits_options) diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index 1ad0bf1d060..456a8a1a2d6 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -134,7 +134,8 @@ module Gitlab def commit_count(ref, options = {}) request = Gitaly::CountCommitsRequest.new( repository: @gitaly_repo, - revision: encode_binary(ref) + revision: encode_binary(ref), + all: !!options[:all] ) request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present? request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present? @@ -269,6 +270,7 @@ module Gitlab offset: options[:offset], follow: options[:follow], skip_merges: options[:skip_merges], + all: !!options[:all], disable_walk: true # This option is deprecated. The 'walk' implementation is being removed. ) request.after = GitalyClient.timestamp(options[:after]) if options[:after] diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 25defb98b7c..52c9876cbb6 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -751,255 +751,263 @@ describe Gitlab::Git::Repository, seed_helper: true do end describe "#log" do - let(:commit_with_old_name) do - Gitlab::Git::Commit.decorate(repository, @commit_with_old_name_id) - end - let(:commit_with_new_name) do - Gitlab::Git::Commit.decorate(repository, @commit_with_new_name_id) - end - let(:rename_commit) do - Gitlab::Git::Commit.decorate(repository, @rename_commit_id) - end + shared_examples 'repository log' do + let(:commit_with_old_name) do + Gitlab::Git::Commit.decorate(repository, @commit_with_old_name_id) + end + let(:commit_with_new_name) do + Gitlab::Git::Commit.decorate(repository, @commit_with_new_name_id) + end + let(:rename_commit) do + Gitlab::Git::Commit.decorate(repository, @rename_commit_id) + end - before(:context) do - # Add new commits so that there's a renamed file in the commit history - repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged - @commit_with_old_name_id = new_commit_edit_old_file(repo) - @rename_commit_id = new_commit_move_file(repo) - @commit_with_new_name_id = new_commit_edit_new_file(repo) - end + before(:context) do + # Add new commits so that there's a renamed file in the commit history + repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged + @commit_with_old_name_id = new_commit_edit_old_file(repo) + @rename_commit_id = new_commit_move_file(repo) + @commit_with_new_name_id = new_commit_edit_new_file(repo) + end - after(:context) do - # Erase our commits so other tests get the original repo - repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged - repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID) - end + after(:context) do + # Erase our commits so other tests get the original repo + repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged + repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID) + end - context "where 'follow' == true" do - let(:options) { { ref: "master", follow: true } } + context "where 'follow' == true" do + let(:options) { { ref: "master", follow: true } } - context "and 'path' is a directory" do - it "does not follow renames" do - log_commits = repository.log(options.merge(path: "encoding")) + context "and 'path' is a directory" do + it "does not follow renames" do + log_commits = repository.log(options.merge(path: "encoding")) - aggregate_failures do + aggregate_failures do + expect(log_commits).to include(commit_with_new_name) + expect(log_commits).to include(rename_commit) + expect(log_commits).not_to include(commit_with_old_name) + end + end + end + + context "and 'path' is a file that matches the new filename" do + context 'without offset' do + it "follows renames" do + log_commits = repository.log(options.merge(path: "encoding/CHANGELOG")) + + aggregate_failures do + expect(log_commits).to include(commit_with_new_name) + expect(log_commits).to include(rename_commit) + expect(log_commits).to include(commit_with_old_name) + end + end + end + + context 'with offset=1' do + it "follows renames and skip the latest commit" do + log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1)) + + aggregate_failures do + expect(log_commits).not_to include(commit_with_new_name) + expect(log_commits).to include(rename_commit) + expect(log_commits).to include(commit_with_old_name) + end + end + end + + context 'with offset=1', 'and limit=1' do + it "follows renames, skip the latest commit and return only one commit" do + log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1)) + + expect(log_commits).to contain_exactly(rename_commit) + end + end + + context 'with offset=1', 'and limit=2' do + it "follows renames, skip the latest commit and return only two commits" do + log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2)) + + aggregate_failures do + expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name) + end + end + end + + context 'with offset=2' do + it "follows renames and skip the latest commit" do + log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2)) + + aggregate_failures do + expect(log_commits).not_to include(commit_with_new_name) + expect(log_commits).not_to include(rename_commit) + expect(log_commits).to include(commit_with_old_name) + end + end + end + + context 'with offset=2', 'and limit=1' do + it "follows renames, skip the two latest commit and return only one commit" do + log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1)) + + expect(log_commits).to contain_exactly(commit_with_old_name) + end + end + + context 'with offset=2', 'and limit=2' do + it "follows renames, skip the two latest commit and return only one commit" do + log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2)) + + aggregate_failures do + expect(log_commits).not_to include(commit_with_new_name) + expect(log_commits).not_to include(rename_commit) + expect(log_commits).to include(commit_with_old_name) + end + end + end + end + + context "and 'path' is a file that matches the old filename" do + it "does not follow renames" do + log_commits = repository.log(options.merge(path: "CHANGELOG")) + + aggregate_failures do + expect(log_commits).not_to include(commit_with_new_name) + expect(log_commits).to include(rename_commit) + expect(log_commits).to include(commit_with_old_name) + end + end + end + + context "unknown ref" do + it "returns an empty array" do + log_commits = repository.log(options.merge(ref: 'unknown')) + + expect(log_commits).to eq([]) + end + end + end + + context "where 'follow' == false" do + options = { follow: false } + + context "and 'path' is a directory" do + let(:log_commits) do + repository.log(options.merge(path: "encoding")) + end + + it "does not follow renames" do expect(log_commits).to include(commit_with_new_name) expect(log_commits).to include(rename_commit) expect(log_commits).not_to include(commit_with_old_name) end end - end - context "and 'path' is a file that matches the new filename" do - context 'without offset' do - it "follows renames" do - log_commits = repository.log(options.merge(path: "encoding/CHANGELOG")) - - aggregate_failures do - expect(log_commits).to include(commit_with_new_name) - expect(log_commits).to include(rename_commit) - expect(log_commits).to include(commit_with_old_name) - end + context "and 'path' is a file that matches the new filename" do + let(:log_commits) do + repository.log(options.merge(path: "encoding/CHANGELOG")) end - end - context 'with offset=1' do - it "follows renames and skip the latest commit" do - log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1)) - - aggregate_failures do - expect(log_commits).not_to include(commit_with_new_name) - expect(log_commits).to include(rename_commit) - expect(log_commits).to include(commit_with_old_name) - end - end - end - - context 'with offset=1', 'and limit=1' do - it "follows renames, skip the latest commit and return only one commit" do - log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1)) - - expect(log_commits).to contain_exactly(rename_commit) - end - end - - context 'with offset=1', 'and limit=2' do - it "follows renames, skip the latest commit and return only two commits" do - log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2)) - - aggregate_failures do - expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name) - end - end - end - - context 'with offset=2' do - it "follows renames and skip the latest commit" do - log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2)) - - aggregate_failures do - expect(log_commits).not_to include(commit_with_new_name) - expect(log_commits).not_to include(rename_commit) - expect(log_commits).to include(commit_with_old_name) - end - end - end - - context 'with offset=2', 'and limit=1' do - it "follows renames, skip the two latest commit and return only one commit" do - log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1)) - - expect(log_commits).to contain_exactly(commit_with_old_name) - end - end - - context 'with offset=2', 'and limit=2' do - it "follows renames, skip the two latest commit and return only one commit" do - log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2)) - - aggregate_failures do - expect(log_commits).not_to include(commit_with_new_name) - expect(log_commits).not_to include(rename_commit) - expect(log_commits).to include(commit_with_old_name) - end - end - end - end - - context "and 'path' is a file that matches the old filename" do - it "does not follow renames" do - log_commits = repository.log(options.merge(path: "CHANGELOG")) - - aggregate_failures do - expect(log_commits).not_to include(commit_with_new_name) + it "does not follow renames" do + expect(log_commits).to include(commit_with_new_name) expect(log_commits).to include(rename_commit) + expect(log_commits).not_to include(commit_with_old_name) + end + end + + context "and 'path' is a file that matches the old filename" do + let(:log_commits) do + repository.log(options.merge(path: "CHANGELOG")) + end + + it "does not follow renames" do expect(log_commits).to include(commit_with_old_name) + expect(log_commits).to include(rename_commit) + expect(log_commits).not_to include(commit_with_new_name) + end + end + + context "and 'path' includes a directory that used to be a file" do + let(:log_commits) do + repository.log(options.merge(ref: "refs/heads/fix-blob-path", path: "files/testdir/file.txt")) + end + + it "returns a list of commits" do + expect(log_commits.size).to eq(1) end end end - context "unknown ref" do - it "returns an empty array" do - log_commits = repository.log(options.merge(ref: 'unknown')) + context "where provides 'after' timestamp" do + options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') } - expect(log_commits).to eq([]) + it "should returns commits on or after that timestamp" do + commits = repository.log(options) + + expect(commits.size).to be > 0 + expect(commits).to satisfy do |commits| + commits.all? { |commit| commit.committed_date >= options[:after] } + end + end + end + + context "where provides 'before' timestamp" do + options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') } + + it "should returns commits on or before that timestamp" do + commits = repository.log(options) + + expect(commits.size).to be > 0 + expect(commits).to satisfy do |commits| + commits.all? { |commit| commit.committed_date <= options[:before] } + end + end + end + + context 'when multiple paths are provided' do + let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } } + + def commit_files(commit) + commit.rugged_diff_from_parent.deltas.flat_map do |delta| + [delta.old_file[:path], delta.new_file[:path]].uniq.compact + end + end + + it 'only returns commits matching at least one path' do + commits = repository.log(options) + + expect(commits.size).to be > 0 + expect(commits).to satisfy do |commits| + commits.none? { |commit| (commit_files(commit) & options[:path]).empty? } + end + end + end + + context 'limit validation' do + where(:limit) do + [0, nil, '', 'foo'] + end + + with_them do + it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) } + end + end + + context 'with all' do + it 'returns a list of commits' do + commits = repository.log({ all: true, limit: 50 }) + + expect(commits.size).to eq(37) end end end - context "where 'follow' == false" do - options = { follow: false } - - context "and 'path' is a directory" do - let(:log_commits) do - repository.log(options.merge(path: "encoding")) - end - - it "does not follow renames" do - expect(log_commits).to include(commit_with_new_name) - expect(log_commits).to include(rename_commit) - expect(log_commits).not_to include(commit_with_old_name) - end - end - - context "and 'path' is a file that matches the new filename" do - let(:log_commits) do - repository.log(options.merge(path: "encoding/CHANGELOG")) - end - - it "does not follow renames" do - expect(log_commits).to include(commit_with_new_name) - expect(log_commits).to include(rename_commit) - expect(log_commits).not_to include(commit_with_old_name) - end - end - - context "and 'path' is a file that matches the old filename" do - let(:log_commits) do - repository.log(options.merge(path: "CHANGELOG")) - end - - it "does not follow renames" do - expect(log_commits).to include(commit_with_old_name) - expect(log_commits).to include(rename_commit) - expect(log_commits).not_to include(commit_with_new_name) - end - end - - context "and 'path' includes a directory that used to be a file" do - let(:log_commits) do - repository.log(options.merge(ref: "refs/heads/fix-blob-path", path: "files/testdir/file.txt")) - end - - it "returns a list of commits" do - expect(log_commits.size).to eq(1) - end - end + context 'when Gitaly find_commits feature is enabled' do + it_behaves_like 'repository log' end - context "where provides 'after' timestamp" do - options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') } - - it "should returns commits on or after that timestamp" do - commits = repository.log(options) - - expect(commits.size).to be > 0 - expect(commits).to satisfy do |commits| - commits.all? { |commit| commit.committed_date >= options[:after] } - end - end - end - - context "where provides 'before' timestamp" do - options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') } - - it "should returns commits on or before that timestamp" do - commits = repository.log(options) - - expect(commits.size).to be > 0 - expect(commits).to satisfy do |commits| - commits.all? { |commit| commit.committed_date <= options[:before] } - end - end - end - - context 'when multiple paths are provided' do - let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } } - - def commit_files(commit) - commit.rugged_diff_from_parent.deltas.flat_map do |delta| - [delta.old_file[:path], delta.new_file[:path]].uniq.compact - end - end - - it 'only returns commits matching at least one path' do - commits = repository.log(options) - - expect(commits.size).to be > 0 - expect(commits).to satisfy do |commits| - commits.none? { |commit| (commit_files(commit) & options[:path]).empty? } - end - end - end - - context 'limit validation' do - where(:limit) do - [0, nil, '', 'foo'] - end - - with_them do - it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) } - 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 + context 'when Gitaly find_commits feature is disabled', :disable_gitaly do + it_behaves_like 'repository log' end end @@ -1136,14 +1144,6 @@ describe Gitlab::Git::Repository, seed_helper: true do expect(repository.count_commits(options)).to eq(10) end end - end - - context 'when Gitaly count_commits feature is enabled' do - it_behaves_like 'extended commit counting' - end - - context 'when Gitaly count_commits feature is disabled', :skip_gitaly_mock do - it_behaves_like 'extended commit counting' context "with all" do it "returns the number of commits in the whole repository" do @@ -1155,10 +1155,18 @@ describe Gitlab::Git::Repository, seed_helper: true do 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") + expect { repository.count_commits({}) }.to raise_error(ArgumentError) end end end + + context 'when Gitaly count_commits feature is enabled' do + it_behaves_like 'extended commit counting' + end + + context 'when Gitaly count_commits feature is disabled', :disable_gitaly do + it_behaves_like 'extended commit counting' + end end describe '#autocrlf' do From 6f945f20b4c3683bc862ebc476bad9331d72784e Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 27 Feb 2018 19:15:25 +0000 Subject: [PATCH 14/52] Foreground verification of uploads and LFS objects --- app/models/lfs_object.rb | 4 ++ .../ee-4862-verify-file-checksums.yml | 5 ++ doc/administration/raketasks/check.md | 27 +++++--- lib/gitlab/verify/batch_verifier.rb | 64 +++++++++++++++++++ lib/gitlab/verify/lfs_objects.rb | 27 ++++++++ lib/gitlab/verify/rake_task.rb | 53 +++++++++++++++ lib/gitlab/verify/uploads.rb | 27 ++++++++ lib/tasks/gitlab/lfs/check.rake | 8 +++ lib/tasks/gitlab/uploads.rake | 44 ------------- lib/tasks/gitlab/uploads/check.rake | 8 +++ spec/factories/lfs_objects.rb | 6 ++ spec/lib/gitlab/verify/lfs_objects_spec.rb | 35 ++++++++++ spec/lib/gitlab/verify/uploads_spec.rb | 44 +++++++++++++ spec/support/gitlab_verify.rb | 45 +++++++++++++ spec/tasks/gitlab/lfs/check_rake_spec.rb | 28 ++++++++ .../check_rake_spec.rb} | 13 ++-- 16 files changed, 378 insertions(+), 60 deletions(-) create mode 100644 changelogs/unreleased/ee-4862-verify-file-checksums.yml create mode 100644 lib/gitlab/verify/batch_verifier.rb create mode 100644 lib/gitlab/verify/lfs_objects.rb create mode 100644 lib/gitlab/verify/rake_task.rb create mode 100644 lib/gitlab/verify/uploads.rb create mode 100644 lib/tasks/gitlab/lfs/check.rake delete mode 100644 lib/tasks/gitlab/uploads.rake create mode 100644 lib/tasks/gitlab/uploads/check.rake create mode 100644 spec/lib/gitlab/verify/lfs_objects_spec.rb create mode 100644 spec/lib/gitlab/verify/uploads_spec.rb create mode 100644 spec/support/gitlab_verify.rb create mode 100644 spec/tasks/gitlab/lfs/check_rake_spec.rb rename spec/tasks/gitlab/{uploads_rake_spec.rb => uploads/check_rake_spec.rb} (59%) diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index fc586fa216e..b444812a4cf 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -15,4 +15,8 @@ class LfsObject < ActiveRecord::Base .where(lfs_objects_projects: { id: nil }) .destroy_all end + + def self.calculate_oid(path) + Digest::SHA256.file(path).hexdigest + end end diff --git a/changelogs/unreleased/ee-4862-verify-file-checksums.yml b/changelogs/unreleased/ee-4862-verify-file-checksums.yml new file mode 100644 index 00000000000..392c766ab37 --- /dev/null +++ b/changelogs/unreleased/ee-4862-verify-file-checksums.yml @@ -0,0 +1,5 @@ +--- +title: Foreground verification of uploads and LFS objects +merge_request: 17402 +author: +type: added diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md index d1ed152b58c..d73d9422d2c 100644 --- a/doc/administration/raketasks/check.md +++ b/doc/administration/raketasks/check.md @@ -78,34 +78,41 @@ Example output: ## Uploaded Files Integrity -The uploads check Rake task will loop through all uploads in the database -and run two checks to determine the integrity of each file: +Various types of file can be uploaded to a GitLab installation by users. +Checksums are generated and stored in the database upon upload, and integrity +checks using those checksums can be run. These checks also detect missing files. -1. Check if the file exist on the file system. -1. Check if the checksum of the file on the file system matches the checksum in the database. +Currently, integrity checks are supported for the following types of file: + +* LFS objects +* User uploads **Omnibus Installation** ``` +sudo gitlab-rake gitlab:lfs:check sudo gitlab-rake gitlab:uploads:check ``` **Source Installation** ```bash +sudo -u git -H bundle exec rake gitlab:lfs:check RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:uploads:check RAILS_ENV=production ``` -This task also accepts some environment variables which you can use to override +These tasks also accept some environment variables which you can use to override certain values: -Variable | Type | Description --------- | ---- | ----------- -`BATCH` | integer | Specifies the size of the batch. Defaults to 200. -`ID_FROM` | integer | Specifies the ID to start from, inclusive of the value. -`ID_TO` | integer | Specifies the ID value to end at, inclusive of the value. +Variable | Type | Description +--------- | ------- | ----------- +`BATCH` | integer | Specifies the size of the batch. Defaults to 200. +`ID_FROM` | integer | Specifies the ID to start from, inclusive of the value. +`ID_TO` | integer | Specifies the ID value to end at, inclusive of the value. +`VERBOSE` | boolean | Causes failures to be listed individually, rather than being summarized. ```bash +sudo gitlab-rake gitlab:lfs:check BATCH=100 ID_FROM=50 ID_TO=250 sudo gitlab-rake gitlab:uploads:check BATCH=100 ID_FROM=50 ID_TO=250 ``` diff --git a/lib/gitlab/verify/batch_verifier.rb b/lib/gitlab/verify/batch_verifier.rb new file mode 100644 index 00000000000..1ef369a4b67 --- /dev/null +++ b/lib/gitlab/verify/batch_verifier.rb @@ -0,0 +1,64 @@ +module Gitlab + module Verify + class BatchVerifier + attr_reader :batch_size, :start, :finish + + def initialize(batch_size:, start: nil, finish: nil) + @batch_size = batch_size + @start = start + @finish = finish + end + + # Yields a Range of IDs and a Hash of failed verifications (object => error) + def run_batches(&blk) + relation.in_batches(of: batch_size, start: start, finish: finish) do |relation| # rubocop: disable Cop/InBatches + range = relation.first.id..relation.last.id + failures = run_batch(relation) + + yield(range, failures) + end + end + + def name + raise NotImplementedError.new + end + + def describe(_object) + raise NotImplementedError.new + end + + private + + def run_batch(relation) + relation.map { |upload| verify(upload) }.compact.to_h + end + + def verify(object) + expected = expected_checksum(object) + actual = actual_checksum(object) + + raise 'Checksum missing' unless expected.present? + raise 'Checksum mismatch' unless expected == actual + + nil + rescue => err + [object, err] + end + + # This should return an ActiveRecord::Relation suitable for calling #in_batches on + def relation + raise NotImplementedError.new + end + + # The checksum we expect the object to have + def expected_checksum(_object) + raise NotImplementedError.new + end + + # The freshly-recalculated checksum of the object + def actual_checksum(_object) + raise NotImplementedError.new + end + end + end +end diff --git a/lib/gitlab/verify/lfs_objects.rb b/lib/gitlab/verify/lfs_objects.rb new file mode 100644 index 00000000000..fe51edbdeeb --- /dev/null +++ b/lib/gitlab/verify/lfs_objects.rb @@ -0,0 +1,27 @@ +module Gitlab + module Verify + class LfsObjects < BatchVerifier + def name + 'LFS objects' + end + + def describe(object) + "LFS object: #{object.oid}" + end + + private + + def relation + LfsObject.all + end + + def expected_checksum(lfs_object) + lfs_object.oid + end + + def actual_checksum(lfs_object) + LfsObject.calculate_oid(lfs_object.file.path) + end + end + end +end diff --git a/lib/gitlab/verify/rake_task.rb b/lib/gitlab/verify/rake_task.rb new file mode 100644 index 00000000000..dd138e6b92b --- /dev/null +++ b/lib/gitlab/verify/rake_task.rb @@ -0,0 +1,53 @@ +module Gitlab + module Verify + class RakeTask + def self.run!(verify_kls) + verifier = verify_kls.new( + batch_size: ENV.fetch('BATCH', 200).to_i, + start: ENV['ID_FROM'], + finish: ENV['ID_TO'] + ) + + verbose = Gitlab::Utils.to_boolean(ENV['VERBOSE']) + + new(verifier, verbose).run! + end + + attr_reader :verifier, :output + + def initialize(verifier, verbose) + @verifier = verifier + @verbose = verbose + end + + def run! + say "Checking integrity of #{verifier.name}" + + verifier.run_batches { |*args| run_batch(*args) } + + say 'Done!' + end + + def verbose? + !!@verbose + end + + private + + def say(text) + puts(text) # rubocop:disable Rails/Output + end + + def run_batch(range, failures) + status_color = failures.empty? ? :green : :red + say "- #{range}: Failures: #{failures.count}".color(status_color) + + return unless verbose? + + failures.each do |object, error| + say " - #{verifier.describe(object)}: #{error.inspect}".color(:red) + end + end + end + end +end diff --git a/lib/gitlab/verify/uploads.rb b/lib/gitlab/verify/uploads.rb new file mode 100644 index 00000000000..6972e517ea5 --- /dev/null +++ b/lib/gitlab/verify/uploads.rb @@ -0,0 +1,27 @@ +module Gitlab + module Verify + class Uploads < BatchVerifier + def name + 'Uploads' + end + + def describe(object) + "Upload: #{object.id}" + end + + private + + def relation + Upload.all + end + + def expected_checksum(upload) + upload.checksum + end + + def actual_checksum(upload) + Upload.hexdigest(upload.absolute_path) + end + end + end +end diff --git a/lib/tasks/gitlab/lfs/check.rake b/lib/tasks/gitlab/lfs/check.rake new file mode 100644 index 00000000000..869463d4e5d --- /dev/null +++ b/lib/tasks/gitlab/lfs/check.rake @@ -0,0 +1,8 @@ +namespace :gitlab do + namespace :lfs do + desc 'GitLab | LFS | Check integrity of uploaded LFS objects' + task check: :environment do + Gitlab::Verify::RakeTask.run!(Gitlab::Verify::LfsObjects) + end + end +end diff --git a/lib/tasks/gitlab/uploads.rake b/lib/tasks/gitlab/uploads.rake deleted file mode 100644 index df31567ce64..00000000000 --- a/lib/tasks/gitlab/uploads.rake +++ /dev/null @@ -1,44 +0,0 @@ -namespace :gitlab do - namespace :uploads do - desc 'GitLab | Uploads | Check integrity of uploaded files' - task check: :environment do - puts 'Checking integrity of uploaded files' - - uploads_batches do |batch| - batch.each do |upload| - puts "- Checking file (#{upload.id}): #{upload.absolute_path}".color(:green) - - if upload.exist? - check_checksum(upload) - else - puts " * File does not exist on the file system".color(:red) - end - end - end - - puts 'Done!' - end - - def batch_size - ENV.fetch('BATCH', 200).to_i - end - - def calculate_checksum(absolute_path) - Digest::SHA256.file(absolute_path).hexdigest - end - - def check_checksum(upload) - checksum = calculate_checksum(upload.absolute_path) - - if checksum != upload.checksum - puts " * File checksum (#{checksum}) does not match the one in the database (#{upload.checksum})".color(:red) - end - end - - def uploads_batches(&block) - Upload.all.in_batches(of: batch_size, start: ENV['ID_FROM'], finish: ENV['ID_TO']) do |relation| # rubocop: disable Cop/InBatches - yield relation - end - end - end -end diff --git a/lib/tasks/gitlab/uploads/check.rake b/lib/tasks/gitlab/uploads/check.rake new file mode 100644 index 00000000000..2be2ec7f9c9 --- /dev/null +++ b/lib/tasks/gitlab/uploads/check.rake @@ -0,0 +1,8 @@ +namespace :gitlab do + namespace :uploads do + desc 'GitLab | Uploads | Check integrity of uploaded files' + task check: :environment do + Gitlab::Verify::RakeTask.run!(Gitlab::Verify::Uploads) + end + end +end diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb index 8eb709022ce..caaed4d5246 100644 --- a/spec/factories/lfs_objects.rb +++ b/spec/factories/lfs_objects.rb @@ -9,4 +9,10 @@ FactoryBot.define do trait :with_file do file { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") } end + + # The uniqueness constraint means we can't use the correct OID for all LFS + # objects, so the test needs to decide which (if any) object gets it + trait :correct_oid do + oid 'b804383982bb89b00e828e3f44c038cc991d3d1768009fc39ba8e2c081b9fb75' + end end diff --git a/spec/lib/gitlab/verify/lfs_objects_spec.rb b/spec/lib/gitlab/verify/lfs_objects_spec.rb new file mode 100644 index 00000000000..64f3a9660e0 --- /dev/null +++ b/spec/lib/gitlab/verify/lfs_objects_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe Gitlab::Verify::LfsObjects do + include GitlabVerifyHelpers + + it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do + let!(:objects) { create_list(:lfs_object, 3, :with_file) } + end + + describe '#run_batches' do + let(:failures) { collect_failures } + let(:failure) { failures[lfs_object] } + + let!(:lfs_object) { create(:lfs_object, :with_file, :correct_oid) } + + it 'passes LFS objects with the correct file' do + expect(failures).to eq({}) + end + + it 'fails LFS objects with a missing file' do + FileUtils.rm_f(lfs_object.file.path) + + expect(failures.keys).to contain_exactly(lfs_object) + expect(failure).to be_a(Errno::ENOENT) + expect(failure.to_s).to include(lfs_object.file.path) + end + + it 'fails LFS objects with a mismatched oid' do + File.truncate(lfs_object.file.path, 0) + + expect(failures.keys).to contain_exactly(lfs_object) + expect(failure.to_s).to include('Checksum mismatch') + end + end +end diff --git a/spec/lib/gitlab/verify/uploads_spec.rb b/spec/lib/gitlab/verify/uploads_spec.rb new file mode 100644 index 00000000000..6146ce61226 --- /dev/null +++ b/spec/lib/gitlab/verify/uploads_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe Gitlab::Verify::Uploads do + include GitlabVerifyHelpers + + it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do + let(:projects) { create_list(:project, 3, :with_avatar) } + let!(:objects) { projects.flat_map(&:uploads) } + end + + describe '#run_batches' do + let(:project) { create(:project, :with_avatar) } + let(:failures) { collect_failures } + let(:failure) { failures[upload] } + + let!(:upload) { project.uploads.first } + + it 'passes uploads with the correct file' do + expect(failures).to eq({}) + end + + it 'fails uploads with a missing file' do + FileUtils.rm_f(upload.absolute_path) + + expect(failures.keys).to contain_exactly(upload) + expect(failure).to be_a(Errno::ENOENT) + expect(failure.to_s).to include(upload.absolute_path) + end + + it 'fails uploads with a mismatched checksum' do + upload.update!(checksum: 'something incorrect') + + expect(failures.keys).to contain_exactly(upload) + expect(failure.to_s).to include('Checksum mismatch') + end + + it 'fails uploads with a missing precalculated checksum' do + upload.update!(checksum: '') + + expect(failures.keys).to contain_exactly(upload) + expect(failure.to_s).to include('Checksum missing') + end + end +end diff --git a/spec/support/gitlab_verify.rb b/spec/support/gitlab_verify.rb new file mode 100644 index 00000000000..13e2e37624d --- /dev/null +++ b/spec/support/gitlab_verify.rb @@ -0,0 +1,45 @@ +RSpec.shared_examples 'Gitlab::Verify::BatchVerifier subclass' do + describe 'batching' do + let(:first_batch) { objects[0].id..objects[0].id } + let(:second_batch) { objects[1].id..objects[1].id } + let(:third_batch) { objects[2].id..objects[2].id } + + it 'iterates through objects in batches' do + expect(collect_ranges).to eq([first_batch, second_batch, third_batch]) + end + + it 'allows the starting ID to be specified' do + expect(collect_ranges(start: second_batch.first)).to eq([second_batch, third_batch]) + end + + it 'allows the finishing ID to be specified' do + expect(collect_ranges(finish: second_batch.last)).to eq([first_batch, second_batch]) + end + end +end + +module GitlabVerifyHelpers + def collect_ranges(args = {}) + verifier = described_class.new(args.merge(batch_size: 1)) + + collect_results(verifier).map { |range, _| range } + end + + def collect_failures + verifier = described_class.new(batch_size: 1) + + out = {} + + collect_results(verifier).map { |_, failures| out.merge!(failures) } + + out + end + + def collect_results(verifier) + out = [] + + verifier.run_batches { |*args| out << args } + + out + end +end diff --git a/spec/tasks/gitlab/lfs/check_rake_spec.rb b/spec/tasks/gitlab/lfs/check_rake_spec.rb new file mode 100644 index 00000000000..2610edf8bac --- /dev/null +++ b/spec/tasks/gitlab/lfs/check_rake_spec.rb @@ -0,0 +1,28 @@ +require 'rake_helper' + +describe 'gitlab:lfs rake tasks' do + describe 'check' do + let!(:lfs_object) { create(:lfs_object, :with_file, :correct_oid) } + + before do + Rake.application.rake_require('tasks/gitlab/lfs/check') + stub_env('VERBOSE' => 'true') + end + + it 'outputs the integrity check for each batch' do + expect { run_rake_task('gitlab:lfs:check') }.to output(/Failures: 0/).to_stdout + end + + it 'errors out about missing files on the file system' do + FileUtils.rm_f(lfs_object.file.path) + + expect { run_rake_task('gitlab:lfs:check') }.to output(/No such file.*#{Regexp.quote(lfs_object.file.path)}/).to_stdout + end + + it 'errors out about invalid checksum' do + File.truncate(lfs_object.file.path, 0) + + expect { run_rake_task('gitlab:lfs:check') }.to output(/Checksum mismatch/).to_stdout + end + end +end diff --git a/spec/tasks/gitlab/uploads_rake_spec.rb b/spec/tasks/gitlab/uploads/check_rake_spec.rb similarity index 59% rename from spec/tasks/gitlab/uploads_rake_spec.rb rename to spec/tasks/gitlab/uploads/check_rake_spec.rb index ac0005e51e0..5d597c66133 100644 --- a/spec/tasks/gitlab/uploads_rake_spec.rb +++ b/spec/tasks/gitlab/uploads/check_rake_spec.rb @@ -5,23 +5,24 @@ describe 'gitlab:uploads rake tasks' do let!(:upload) { create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) } before do - Rake.application.rake_require 'tasks/gitlab/uploads' + Rake.application.rake_require('tasks/gitlab/uploads/check') + stub_env('VERBOSE' => 'true') end - it 'outputs the integrity check for each uploaded file' do - expect { run_rake_task('gitlab:uploads:check') }.to output(/Checking file \(#{upload.id}\): #{Regexp.quote(upload.absolute_path)}/).to_stdout + it 'outputs the integrity check for each batch' do + expect { run_rake_task('gitlab:uploads:check') }.to output(/Failures: 0/).to_stdout end it 'errors out about missing files on the file system' do - create(:upload) + missing_upload = create(:upload) - expect { run_rake_task('gitlab:uploads:check') }.to output(/File does not exist on the file system/).to_stdout + expect { run_rake_task('gitlab:uploads:check') }.to output(/No such file.*#{Regexp.quote(missing_upload.absolute_path)}/).to_stdout end it 'errors out about invalid checksum' do upload.update_column(:checksum, '01a3156db2cf4f67ec823680b40b7302f89ab39179124ad219f94919b8a1769e') - expect { run_rake_task('gitlab:uploads:check') }.to output(/File checksum \(9e697aa09fe196909813ee36103e34f721fe47a5fdc8aac0e4e4ac47b9b38282\) does not match the one in the database \(#{upload.checksum}\)/).to_stdout + expect { run_rake_task('gitlab:uploads:check') }.to output(/Checksum mismatch/).to_stdout end end end From daeeb7f8480d747d500ea3aeddb479a29e890562 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 2 Mar 2018 12:03:03 +0000 Subject: [PATCH 15/52] Fix quick actions for users who cannot update issues and MRs There are several quick actions now that don't need this access - /todo and /unsubscribe for instance - but when these were first added, there weren't. Quick actions are now responsible for checking their own permissions. --- app/helpers/notes_helper.rb | 2 +- app/services/notes/quick_actions_service.rb | 8 ++- ...d-not-pick-up-unsubscribe-quick-action.yml | 5 ++ .../issues/user_uses_slash_commands_spec.rb | 3 -- .../emails/update_commands_only_reply.eml | 38 ++++++++++++++ .../email/handler/create_note_handler_spec.rb | 27 ++++------ spec/services/notes/create_service_spec.rb | 51 ++++++++++++++----- .../notes/quick_actions_service_spec.rb | 30 +++-------- ...issuable_slash_commands_shared_examples.rb | 5 +- 9 files changed, 104 insertions(+), 65 deletions(-) create mode 100644 changelogs/unreleased/43334-reply-by-email-did-not-pick-up-unsubscribe-quick-action.yml create mode 100644 spec/fixtures/emails/update_commands_only_reply.eml diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index e86e43b5ebf..a70e73a6da9 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -11,7 +11,7 @@ module NotesHelper end def note_supports_quick_actions?(note) - Notes::QuickActionsService.supported?(note, current_user) + Notes::QuickActionsService.supported?(note) end def noteable_json(noteable) diff --git a/app/services/notes/quick_actions_service.rb b/app/services/notes/quick_actions_service.rb index a8d0cc15527..0a33d5f3f3d 100644 --- a/app/services/notes/quick_actions_service.rb +++ b/app/services/notes/quick_actions_service.rb @@ -9,14 +9,12 @@ module Notes UPDATE_SERVICES[note.noteable_type] end - def self.supported?(note, current_user) - noteable_update_service(note) && - current_user && - current_user.can?(:"update_#{note.to_ability_name}", note.noteable) + def self.supported?(note) + !!noteable_update_service(note) end def supported?(note) - self.class.supported?(note, current_user) + self.class.supported?(note) end def extract_commands(note, options = {}) diff --git a/changelogs/unreleased/43334-reply-by-email-did-not-pick-up-unsubscribe-quick-action.yml b/changelogs/unreleased/43334-reply-by-email-did-not-pick-up-unsubscribe-quick-action.yml new file mode 100644 index 00000000000..86be5ee1804 --- /dev/null +++ b/changelogs/unreleased/43334-reply-by-email-did-not-pick-up-unsubscribe-quick-action.yml @@ -0,0 +1,5 @@ +--- +title: Fix quick actions for users who cannot update issues and merge requests +merge_request: 17482 +author: +type: fixed diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb index e711a191db2..ea7a97d02a0 100644 --- a/spec/features/issues/user_uses_slash_commands_spec.rb +++ b/spec/features/issues/user_uses_slash_commands_spec.rb @@ -59,7 +59,6 @@ feature 'Issues > User uses quick actions', :js do it 'does not create a note, and sets the due date accordingly' do write_note("/due 2016-08-28") - expect(page).to have_content '/due 2016-08-28' expect(page).not_to have_content 'Commands applied' issue.reload @@ -99,7 +98,6 @@ feature 'Issues > User uses quick actions', :js do it 'does not create a note, and sets the due date accordingly' do write_note("/remove_due_date") - expect(page).to have_content '/remove_due_date' expect(page).not_to have_content 'Commands applied' issue.reload @@ -147,7 +145,6 @@ feature 'Issues > User uses quick actions', :js do it 'does not create a note, and does not mark the issue as a duplicate' do write_note("/duplicate ##{original_issue.to_reference}") - expect(page).to have_content "/duplicate ##{original_issue.to_reference}" expect(page).not_to have_content 'Commands applied' expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}" diff --git a/spec/fixtures/emails/update_commands_only_reply.eml b/spec/fixtures/emails/update_commands_only_reply.eml new file mode 100644 index 00000000000..bb0d2b0e03a --- /dev/null +++ b/spec/fixtures/emails/update_commands_only_reply.eml @@ -0,0 +1,38 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +In-Reply-To: +References: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +/close + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta + wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb index 031efcf1291..53899e00b53 100644 --- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -55,8 +55,8 @@ describe Gitlab::Email::Handler::CreateNoteHandler do expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError) end - context 'because the note was commands only' do - let!(:email_raw) { fixture_file("emails/commands_only_reply.eml") } + context 'because the note was update commands only' do + let!(:email_raw) { fixture_file("emails/update_commands_only_reply.eml") } context 'and current user cannot update noteable' do it 'raises a CommandsOnlyNoteError' do @@ -70,13 +70,10 @@ describe Gitlab::Email::Handler::CreateNoteHandler do end it 'does not raise an error' do - expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy - # One system note is created for the 'close' event expect { receiver.execute }.to change { noteable.notes.count }.by(1) expect(noteable.reload).to be_closed - expect(TodoService.new.todo_exist?(noteable, user)).to be_truthy end end end @@ -85,15 +82,13 @@ describe Gitlab::Email::Handler::CreateNoteHandler do context 'when the note contains quick actions' do let!(:email_raw) { fixture_file("emails/commands_in_reply.eml") } - context 'and current user cannot update noteable' do - it 'post a note and does not update the noteable' do - expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy - - # One system note is created for the new note - expect { receiver.execute }.to change { noteable.notes.count }.by(1) + context 'and current user cannot update the noteable' do + it 'only executes the commands that the user can perform' do + expect { receiver.execute } + .to change { noteable.notes.user.count }.by(1) + .and change { user.todos_pending_count }.from(0).to(1) expect(noteable.reload).to be_open - expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy end end @@ -102,14 +97,14 @@ describe Gitlab::Email::Handler::CreateNoteHandler do project.add_developer(user) end - it 'post a note and updates the noteable' do + it 'posts a note and updates the noteable' do expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy - # One system note is created for the new note, one for the 'close' event - expect { receiver.execute }.to change { noteable.notes.count }.by(2) + expect { receiver.execute } + .to change { noteable.notes.user.count }.by(1) + .and change { user.todos_pending_count }.from(0).to(1) expect(noteable.reload).to be_closed - expect(TodoService.new.todo_exist?(noteable, user)).to be_truthy end end end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 0ae26e87154..f5cff66de6d 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -57,32 +57,55 @@ describe Notes::CreateService do end end - describe 'note with commands' do - describe '/close, /label, /assign & /milestone' do - let(:note_text) { %(HELLO\n/close\n/assign @#{user.username}\nWORLD) } + context 'note with commands' do + context 'as a user who can update the target' do + context '/close, /label, /assign & /milestone' do + let(:note_text) { %(HELLO\n/close\n/assign @#{user.username}\nWORLD) } - it 'saves the note and does not alter the note text' do - expect_any_instance_of(Issues::UpdateService).to receive(:execute).and_call_original + it 'saves the note and does not alter the note text' do + expect_any_instance_of(Issues::UpdateService).to receive(:execute).and_call_original - note = described_class.new(project, user, opts.merge(note: note_text)).execute + note = described_class.new(project, user, opts.merge(note: note_text)).execute - expect(note.note).to eq "HELLO\nWORLD" + expect(note.note).to eq "HELLO\nWORLD" + end + end + + context '/merge with sha option' do + let(:note_text) { %(HELLO\n/merge\nWORLD) } + let(:params) { opts.merge(note: note_text, merge_request_diff_head_sha: 'sha') } + + it 'saves the note and exectues merge command' do + note = described_class.new(project, user, params).execute + + expect(note.note).to eq "HELLO\nWORLD" + end end end - describe '/merge with sha option' do - let(:note_text) { %(HELLO\n/merge\nWORLD) } - let(:params) { opts.merge(note: note_text, merge_request_diff_head_sha: 'sha') } + context 'as a user who cannot update the target' do + let(:note_text) { "HELLO\n/todo\n/assign #{user.to_reference}\nWORLD" } + let(:note) { described_class.new(project, user, opts.merge(note: note_text)).execute } - it 'saves the note and exectues merge command' do - note = described_class.new(project, user, params).execute + before do + project.team.find_member(user.id).update!(access_level: Gitlab::Access::GUEST) + end + it 'applies commands the user can execute' do + expect { note }.to change { user.todos_pending_count }.from(0).to(1) + end + + it 'does not apply commands the user cannot execute' do + expect { note }.not_to change { issue.assignees } + end + + it 'saves the note' do expect(note.note).to eq "HELLO\nWORLD" end end end - describe 'personal snippet note' do + context 'personal snippet note' do subject { described_class.new(nil, user, params).execute } let(:snippet) { create(:personal_snippet) } @@ -103,7 +126,7 @@ describe Notes::CreateService do end end - describe 'note with emoji only' do + context 'note with emoji only' do it 'creates regular note' do opts = { note: ':smile: ', diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb index 5eafe56c99d..b1e218821d2 100644 --- a/spec/services/notes/quick_actions_service_spec.rb +++ b/spec/services/notes/quick_actions_service_spec.rb @@ -165,31 +165,17 @@ describe Notes::QuickActionsService do let(:note) { create(:note_on_issue, project: project) } - context 'with no current_user' do - it 'returns false' do - expect(described_class.supported?(note, nil)).to be_falsy - end - end - - context 'when current_user cannot update the noteable' do - it 'returns false' do - user = create(:user) - - expect(described_class.supported?(note, user)).to be_falsy - end - end - - context 'when current_user can update the noteable' do + context 'with a note on an issue' do it 'returns true' do - expect(described_class.supported?(note, master)).to be_truthy + expect(described_class.supported?(note)).to be_truthy end + end - context 'with a note on a commit' do - let(:note) { create(:note_on_commit, project: project) } + context 'with a note on a commit' do + let(:note) { create(:note_on_commit, project: project) } - it 'returns false' do - expect(described_class.supported?(note, nil)).to be_falsy - end + it 'returns false' do + expect(described_class.supported?(note)).to be_falsy end end end @@ -201,7 +187,7 @@ describe Notes::QuickActionsService do service = described_class.new(project, master) note = create(:note_on_issue, project: project) - expect(described_class).to receive(:supported?).with(note, master) + expect(described_class).to receive(:supported?).with(note) service.supported?(note) end diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb index 2c20821ac3f..f61469f673d 100644 --- a/spec/support/features/issuable_slash_commands_shared_examples.rb +++ b/spec/support/features/issuable_slash_commands_shared_examples.rb @@ -127,7 +127,6 @@ shared_examples 'issuable record that supports quick actions in its description it "does not close the #{issuable_type}" do write_note("/close") - expect(page).to have_content '/close' expect(page).not_to have_content 'Commands applied' expect(issuable).to be_open @@ -165,7 +164,6 @@ shared_examples 'issuable record that supports quick actions in its description it "does not reopen the #{issuable_type}" do write_note("/reopen") - expect(page).to have_content '/reopen' expect(page).not_to have_content 'Commands applied' expect(issuable).to be_closed @@ -195,10 +193,9 @@ shared_examples 'issuable record that supports quick actions in its description visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable) end - it "does not reopen the #{issuable_type}" do + it "does not change the #{issuable_type} title" do write_note("/title Awesome new title") - expect(page).to have_content '/title' expect(page).not_to have_content 'Commands applied' expect(issuable.reload.title).not_to eq 'Awesome new title' From bb4fcb7809aa9d14a60e5c90f11f07fac8f584a8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 3 Mar 2018 00:39:42 +0800 Subject: [PATCH 16/52] Move constants and update for feedback --- lib/gitlab/middleware/read_only.rb | 2 -- lib/gitlab/middleware/read_only/controller.rb | 9 ++++++--- spec/lib/gitlab/middleware/read_only_spec.rb | 16 ++++++++-------- spec/lib/gitlab/middleware/release_env_spec.rb | 8 ++------ 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/lib/gitlab/middleware/read_only.rb b/lib/gitlab/middleware/read_only.rb index 19b74c0c122..d9d5f90596f 100644 --- a/lib/gitlab/middleware/read_only.rb +++ b/lib/gitlab/middleware/read_only.rb @@ -1,8 +1,6 @@ module Gitlab module Middleware class ReadOnly - DISALLOWED_METHODS = %w(POST PATCH PUT DELETE).freeze - APPLICATION_JSON = 'application/json'.freeze API_VERSIONS = (3..4) def self.internal_routes diff --git a/lib/gitlab/middleware/read_only/controller.rb b/lib/gitlab/middleware/read_only/controller.rb index 053cb6f0a9f..45b644e6510 100644 --- a/lib/gitlab/middleware/read_only/controller.rb +++ b/lib/gitlab/middleware/read_only/controller.rb @@ -2,6 +2,10 @@ module Gitlab module Middleware class ReadOnly class Controller + DISALLOWED_METHODS = %w(POST PATCH PUT DELETE).freeze + APPLICATION_JSON = 'application/json'.freeze + ERROR_MESSAGE = 'You cannot perform write operations on a read-only instance'.freeze + def initialize(app, env) @app = app @env = env @@ -10,12 +14,11 @@ module Gitlab def call if disallowed_request? && Gitlab::Database.read_only? Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation') - error_message = 'You cannot do writing operations on a read-only GitLab instance' if json_request? - return [403, { 'Content-Type' => 'application/json' }, [{ 'message' => error_message }.to_json]] + return [403, { 'Content-Type' => APPLICATION_JSON }, [{ 'message' => ERROR_MESSAGE }.to_json]] else - rack_flash.alert = error_message + rack_flash.alert = ERROR_MESSAGE rack_session['flash'] = rack_flash.to_session_value return [301, { 'Location' => last_visited_url }, []] diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb index b3c85142b82..39ec2f37a83 100644 --- a/spec/lib/gitlab/middleware/read_only_spec.rb +++ b/spec/lib/gitlab/middleware/read_only_spec.rb @@ -14,14 +14,14 @@ describe Gitlab::Middleware::ReadOnly do alert = middleware.env['rack.session'].to_hash .dig('flash', 'flashes', 'alert') - alert&.include?('You cannot do writing operations') + alert&.include?('You cannot perform write operations') end end RSpec::Matchers.define :disallow_request_in_json do match do |response| json_response = JSON.parse(response.body) - response.body.include?('You cannot do writing operations') && json_response.key?('message') + response.body.include?('You cannot perform write operations') && json_response.key?('message') end end @@ -47,14 +47,14 @@ describe Gitlab::Middleware::ReadOnly do end end - subject do - app = described_class.new(fake_app) - app.extend(observe_env) - app - end - let(:request) { Rack::MockRequest.new(rack_stack) } + subject do + described_class.new(fake_app).tap do |app| + app.extend(observe_env) + end + end + context 'normal requests to a read-only Gitlab instance' do let(:fake_app) { lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['OK']] } } diff --git a/spec/lib/gitlab/middleware/release_env_spec.rb b/spec/lib/gitlab/middleware/release_env_spec.rb index 657b705502a..5e3aa877409 100644 --- a/spec/lib/gitlab/middleware/release_env_spec.rb +++ b/spec/lib/gitlab/middleware/release_env_spec.rb @@ -1,16 +1,12 @@ require 'spec_helper' describe Gitlab::Middleware::ReleaseEnv do - let(:inner_app) { double(:app) } + let(:inner_app) { double(:app, call: 'yay') } let(:app) { described_class.new(inner_app) } let(:env) { { 'action_controller.instance' => 'something' } } - before do - expect(inner_app).to receive(:call).with(env).and_return('yay') - end - describe '#call' do - it 'calls the app and delete the controller' do + it 'calls the app and clears the env' do result = app.call(env) expect(result).to eq('yay') From 57ce7c655cc58ee793cf6514fae590fa53850884 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 2 Mar 2018 17:10:50 +0000 Subject: [PATCH 17/52] codequality: Install jq directly instead of pulling it via docker --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8a0c9802c15..8b489f1a07c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -619,9 +619,10 @@ codequality: cache: {} dependencies: [] script: + - apk update && apk add jq - ./scripts/codequality analyze -f json > raw_codeclimate.json || true # The following line keeps only the fields used in the MR widget, reducing the JSON artifact size - - cat raw_codeclimate.json | docker run -i stedolan/jq -c 'map({check_name,description,fingerprint,location})' > codeclimate.json + - jq -c 'map({check_name,description,fingerprint,location})' raw_codeclimate.json > codeclimate.json artifacts: paths: [codeclimate.json] expire_in: 1 week From a0a7b551ae117f481ec2d920d47ef55043119313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Tue, 6 Feb 2018 19:49:33 -0300 Subject: [PATCH 18/52] Incorporate Gitaly's RPCs for Gitlab::Git::LfsChanges --- lib/gitlab/git/lfs_changes.rb | 26 +++++++- lib/gitlab/gitaly_client/blob_service.rb | 55 +++++++++++++---- spec/lib/gitlab/checks/lfs_integrity_spec.rb | 22 +++---- spec/lib/gitlab/git/lfs_changes_spec.rb | 38 ++++++------ .../gitlab/gitaly_client/blob_service_spec.rb | 60 +++++++++++++++++++ spec/support/bare_repo_operations.rb | 14 +++-- 6 files changed, 170 insertions(+), 45 deletions(-) create mode 100644 spec/lib/gitlab/gitaly_client/blob_service_spec.rb diff --git a/lib/gitlab/git/lfs_changes.rb b/lib/gitlab/git/lfs_changes.rb index 48434047fce..b9e5cf258f4 100644 --- a/lib/gitlab/git/lfs_changes.rb +++ b/lib/gitlab/git/lfs_changes.rb @@ -7,6 +7,28 @@ module Gitlab end def new_pointers(object_limit: nil, not_in: nil) + @repository.gitaly_migrate(:blob_get_new_lfs_pointers) do |is_enabled| + if is_enabled + @repository.gitaly_blob_client.get_new_lfs_pointers(@newrev, object_limit, not_in) + else + git_new_pointers(object_limit, not_in) + end + end + end + + def all_pointers + @repository.gitaly_migrate(:blob_get_all_lfs_pointers) do |is_enabled| + if is_enabled + @repository.gitaly_blob_client.get_all_lfs_pointers(@newrev) + else + git_all_pointers + end + end + end + + private + + def git_new_pointers(object_limit, not_in) @new_pointers ||= begin rev_list.new_objects(not_in: not_in, require_path: true) do |object_ids| object_ids = object_ids.take(object_limit) if object_limit @@ -16,14 +38,12 @@ module Gitlab end end - def all_pointers + def git_all_pointers rev_list.all_objects(require_path: true) do |object_ids| Gitlab::Git::Blob.batch_lfs_pointers(@repository, object_ids) end end - private - def rev_list Gitlab::Git::RevList.new(@repository, newrev: @newrev) end diff --git a/lib/gitlab/gitaly_client/blob_service.rb b/lib/gitlab/gitaly_client/blob_service.rb index dfa0fa43b0f..28554208984 100644 --- a/lib/gitlab/gitaly_client/blob_service.rb +++ b/lib/gitlab/gitaly_client/blob_service.rb @@ -45,16 +45,7 @@ module Gitlab response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request) - response.flat_map do |message| - message.lfs_pointers.map do |lfs_pointer| - Gitlab::Git::Blob.new( - id: lfs_pointer.oid, - size: lfs_pointer.size, - data: lfs_pointer.data, - binary: Gitlab::Git::Blob.binary?(lfs_pointer.data) - ) - end - end + map_lfs_pointers(response) end def get_blobs(revision_paths, limit = -1) @@ -80,6 +71,50 @@ module Gitlab GitalyClient::BlobsStitcher.new(response) end + + def get_new_lfs_pointers(revision, limit, not_in) + request = Gitaly::GetNewLFSPointersRequest.new( + repository: @gitaly_repo, + revision: encode_binary(revision), + limit: limit || 0 + ) + + if not_in.nil? || not_in == :all + request.not_in_all = true + else + request.not_in_refs += not_in + end + + response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_new_lfs_pointers, request) + + map_lfs_pointers(response) + end + + def get_all_lfs_pointers(revision) + request = Gitaly::GetNewLFSPointersRequest.new( + repository: @gitaly_repo, + revision: encode_binary(revision) + ) + + response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_all_lfs_pointers, request) + + map_lfs_pointers(response) + end + + private + + def map_lfs_pointers(response) + response.flat_map do |message| + message.lfs_pointers.map do |lfs_pointer| + Gitlab::Git::Blob.new( + id: lfs_pointer.oid, + size: lfs_pointer.size, + data: lfs_pointer.data, + binary: Gitlab::Git::Blob.binary?(lfs_pointer.data) + ) + end + end + end end end end diff --git a/spec/lib/gitlab/checks/lfs_integrity_spec.rb b/spec/lib/gitlab/checks/lfs_integrity_spec.rb index 17756621221..7201e4f7bf6 100644 --- a/spec/lib/gitlab/checks/lfs_integrity_spec.rb +++ b/spec/lib/gitlab/checks/lfs_integrity_spec.rb @@ -2,23 +2,25 @@ require 'spec_helper' describe Gitlab::Checks::LfsIntegrity do include ProjectForksHelper + let(:project) { create(:project, :repository) } - let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' } + let(:repository) { project.repository } + let(:newrev) do + operations = BareRepoOperations.new(repository.path) + + # Create a commit not pointed at by any ref to emulate being in the + # pre-receive hook so that `--not --all` returns some objects + operations.commit_tree('8856a329dd38ca86dfb9ce5aa58a16d88cc119bd', "New LFS objects") + end subject { described_class.new(project, newrev) } describe '#objects_missing?' do - let(:blob_object) { project.repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') } - - before do - allow_any_instance_of(Gitlab::Git::RevList).to receive(:new_objects) do |&lazy_block| - lazy_block.call([blob_object.id]) - end - end + let(:blob_object) { repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') } context 'with LFS not enabled' do it 'skips integrity check' do - expect_any_instance_of(Gitlab::Git::RevList).not_to receive(:new_objects) + expect_any_instance_of(Gitlab::Git::LfsChanges).not_to receive(:new_pointers) subject.objects_missing? end @@ -33,7 +35,7 @@ describe Gitlab::Checks::LfsIntegrity do let(:newrev) { nil } it 'skips integrity check' do - expect_any_instance_of(Gitlab::Git::RevList).not_to receive(:new_objects) + expect_any_instance_of(Gitlab::Git::LfsChanges).not_to receive(:new_pointers) expect(subject.objects_missing?).to be_falsey end diff --git a/spec/lib/gitlab/git/lfs_changes_spec.rb b/spec/lib/gitlab/git/lfs_changes_spec.rb index c9007d7d456..d0dd8c6303f 100644 --- a/spec/lib/gitlab/git/lfs_changes_spec.rb +++ b/spec/lib/gitlab/git/lfs_changes_spec.rb @@ -7,34 +7,36 @@ describe Gitlab::Git::LfsChanges do subject { described_class.new(project.repository, newrev) } - describe 'new_pointers' do - before do - allow_any_instance_of(Gitlab::Git::RevList).to receive(:new_objects).and_yield([blob_object_id]) + describe '#new_pointers' do + shared_examples 'new pointers' do + it 'filters new objects to find lfs pointers' do + expect(subject.new_pointers(not_in: []).first.id).to eq(blob_object_id) + end + + it 'limits new_objects using object_limit' do + expect(subject.new_pointers(object_limit: 1)).to eq([]) + end end - it 'uses rev-list to find new objects' do - rev_list = double - allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list) - - expect(rev_list).to receive(:new_objects).and_return([]) - - subject.new_pointers + context 'with gitaly enabled' do + it_behaves_like 'new pointers' end - it 'filters new objects to find lfs pointers' do - expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).with(project.repository, [blob_object_id]) + context 'with gitaly disabled', :skip_gitaly_mock do + it_behaves_like 'new pointers' - subject.new_pointers(object_limit: 1) - end + it 'uses rev-list to find new objects' do + rev_list = double + allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list) - it 'limits new_objects using object_limit' do - expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).with(project.repository, []) + expect(rev_list).to receive(:new_objects).and_return([]) - subject.new_pointers(object_limit: 0) + subject.new_pointers + end end end - describe 'all_pointers' do + describe '#all_pointers', :skip_gitaly_mock do it 'uses rev-list to find all objects' do rev_list = double allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list) diff --git a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb new file mode 100644 index 00000000000..a2770ef2fe4 --- /dev/null +++ b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb @@ -0,0 +1,60 @@ +require 'spec_helper' + +describe Gitlab::GitalyClient::BlobService do + let(:project) { create(:project, :repository) } + let(:storage_name) { project.repository_storage } + let(:relative_path) { project.disk_path + '.git' } + let(:repository) { project.repository } + let(:client) { described_class.new(repository) } + + describe '#get_new_lfs_pointers' do + let(:revision) { 'master' } + let(:limit) { 5 } + let(:not_in) { ['branch-a', 'branch-b'] } + let(:expected_params) do + { revision: revision, limit: limit, not_in_refs: not_in, not_in_all: false } + end + + subject { client.get_new_lfs_pointers(revision, limit, not_in) } + + it 'sends a get_new_lfs_pointers message' do + expect_any_instance_of(Gitaly::BlobService::Stub) + .to receive(:get_new_lfs_pointers) + .with(gitaly_request_with_params(expected_params), kind_of(Hash)) + .and_return([]) + + subject + end + + context 'with not_in = :all' do + let(:not_in) { :all } + let(:expected_params) do + { revision: revision, limit: limit, not_in_refs: [], not_in_all: true } + end + + it 'sends the correct message' do + expect_any_instance_of(Gitaly::BlobService::Stub) + .to receive(:get_new_lfs_pointers) + .with(gitaly_request_with_params(expected_params), kind_of(Hash)) + .and_return([]) + + subject + end + end + end + + describe '#get_all_lfs_pointers' do + let(:revision) { 'master' } + + subject { client.get_all_lfs_pointers(revision) } + + it 'sends a get_all_lfs_pointers message' do + expect_any_instance_of(Gitaly::BlobService::Stub) + .to receive(:get_all_lfs_pointers) + .with(gitaly_request_with_params(revision: revision), kind_of(Hash)) + .and_return([]) + + subject + end + end +end diff --git a/spec/support/bare_repo_operations.rb b/spec/support/bare_repo_operations.rb index 38d11992dc2..8eeaa37d3c5 100644 --- a/spec/support/bare_repo_operations.rb +++ b/spec/support/bare_repo_operations.rb @@ -11,6 +11,14 @@ class BareRepoOperations @path_to_repo = path_to_repo end + def commit_tree(tree_id, msg, parent: EMPTY_TREE_ID) + commit_tree_args = ['commit-tree', tree_id, '-m', msg] + commit_tree_args += ['-p', parent] unless parent == EMPTY_TREE_ID + commit_id = execute(commit_tree_args) + + commit_id[0] + end + # Based on https://stackoverflow.com/a/25556917/1856239 def commit_file(file, dst_path, branch = 'master') head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || EMPTY_TREE_ID @@ -26,11 +34,9 @@ class BareRepoOperations tree_id = execute(['write-tree']) - commit_tree_args = ['commit-tree', tree_id[0], '-m', "Add #{dst_path}"] - commit_tree_args += ['-p', head_id] unless head_id == EMPTY_TREE_ID - commit_id = execute(commit_tree_args) + commit_id = commit_tree(tree_id[0], "Add #{dst_path}", parent: head_id) - execute(['update-ref', "refs/heads/#{branch}", commit_id[0]]) + execute(['update-ref', "refs/heads/#{branch}", commit_id]) end private From 94dc43f37ce20347b5ad67aee0cf90f5c86a814f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 2 Mar 2018 10:14:19 -0800 Subject: [PATCH 19/52] Clean up backup/restore temporary directory --- spec/lib/backup/repository_spec.rb | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb index f7b1a61f4f8..a9b5ed1112a 100644 --- a/spec/lib/backup/repository_spec.rb +++ b/spec/lib/backup/repository_spec.rb @@ -28,6 +28,23 @@ describe Backup::Repository do end describe '#restore' do + subject { described_class.new } + + let(:timestamp) { Time.utc(2017, 3, 22) } + let(:temp_dirs) do + Gitlab.config.repositories.storages.map do |name, storage| + File.join(storage['path'], '..', 'repositories.old.' + timestamp.to_i.to_s) + end + end + + around do |example| + Timecop.freeze(timestamp) { example.run } + end + + after do + temp_dirs.each { |path| FileUtils.rm_rf(path) } + end + describe 'command failure' do before do allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1]) @@ -35,7 +52,7 @@ describe Backup::Repository do context 'hashed storage' do it 'shows the appropriate error' do - described_class.new.restore + subject.restore expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} (#{project.disk_path}) - error") end @@ -45,7 +62,7 @@ describe Backup::Repository do let!(:project) { create(:project, :legacy_storage) } it 'shows the appropriate error' do - described_class.new.restore + subject.restore expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error") end From bb216e2b7cb15054381211844103d29c60f15424 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Sun, 4 Mar 2018 20:15:43 +0900 Subject: [PATCH 20/52] Stop loading spinner on error of milestone update on issue --- app/assets/javascripts/milestone_select.js | 3 +++ .../43837-error-handle-in-updating-milestone-on-issue.yml | 5 +++++ 2 files changed, 8 insertions(+) create mode 100644 changelogs/unreleased/43837-error-handle-in-updating-milestone-on-issue.yml diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 2841ecb558b..c259d5405bd 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -216,6 +216,9 @@ export default class MilestoneSelect { $value.html(milestoneLinkNoneTemplate); return $sidebarCollapsedValue.find('span').text('No'); } + }) + .catch(() => { + $loading.fadeOut(); }); } } diff --git a/changelogs/unreleased/43837-error-handle-in-updating-milestone-on-issue.yml b/changelogs/unreleased/43837-error-handle-in-updating-milestone-on-issue.yml new file mode 100644 index 00000000000..526523964c3 --- /dev/null +++ b/changelogs/unreleased/43837-error-handle-in-updating-milestone-on-issue.yml @@ -0,0 +1,5 @@ +--- +title: Stop loading spinner on error of milestone update on issue +merge_request: 17507 +author: Takuya Noguchi +type: fixed From 95cb9b229f8486a38c6a2e47db8fcc24e06c4ecd Mon Sep 17 00:00:00 2001 From: Victor Wu Date: Mon, 26 Feb 2018 15:54:52 +0000 Subject: [PATCH 21/52] Add severity labels to contributing.md --- CONTRIBUTING.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index dfe4bf65f9f..76ee6265c5c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -196,6 +196,17 @@ release. There are two levels of priority labels: milestone. If these issues are not done in the current release, they will strongly be considered for the next release. +### Severity labels (~S1, ~S2, etc.) + +Severity labels help us clearly communicate the impact of a ~bug on users. + +| Label | Meaning | Example | +|-------|------------------------------------------|---------| +| ~S1 | Feature broken, no workaround | Unable to create an issue | +| ~S2 | Feature broken, workaround unacceptable | Can push commits, but only via the command line | +| ~S3 | Feature broken, workaround acceptable | Can create merge requests only from the Merge Requests page, not through the Issue | +| ~S4 | Cosmetic issue | Label colors are incorrect / not being displayed | + ### Label for community contributors (~"Accepting Merge Requests") Issues that are beneficial to our users, 'nice to haves', that we currently do @@ -397,9 +408,9 @@ For issues related to the open source stewardship of GitLab, there is the ~"stewardship" label. This label is to be used for issues in which the stewardship of GitLab -is a topic of discussion. For instance if GitLab Inc. is planning to remove -features from GitLab CE to make exclusive in GitLab EE, related issues -would be labelled with ~"stewardship". +is a topic of discussion. For instance if GitLab Inc. is planning to add +features from GitLab EE to GitLab CE, related issues would be labelled with +~"stewardship". A recent example of this was the issue for [bringing the time tracking API to GitLab CE][time-tracking-issue]. From 477c83f6bbbc9d0a447826900d45207e145e7261 Mon Sep 17 00:00:00 2001 From: Tiago Botelho Date: Mon, 5 Mar 2018 11:31:23 +0000 Subject: [PATCH 22/52] Ports https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4747 to CE --- .../forbid_sidekiq_in_transactions.rb | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/config/initializers/forbid_sidekiq_in_transactions.rb b/config/initializers/forbid_sidekiq_in_transactions.rb index cb611aa21df..4cf1d455eb4 100644 --- a/config/initializers/forbid_sidekiq_in_transactions.rb +++ b/config/initializers/forbid_sidekiq_in_transactions.rb @@ -18,13 +18,26 @@ module Sidekiq %i(perform_async perform_at perform_in).each do |name| define_method(name) do |*args| if !Sidekiq::Worker.skip_transaction_check && AfterCommitQueue.inside_transaction? - raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG + begin + raise Sidekiq::Worker::EnqueueFromTransactionError, <<~MSG `#{self}.#{name}` cannot be called inside a transaction as this can lead to race conditions when the worker runs before the transaction is committed and tries to access a model that has not been saved yet. Use an `after_commit` hook, or include `AfterCommitQueue` and use a `run_after_commit` block instead. - MSG + MSG + rescue Sidekiq::Worker::EnqueueFromTransactionError => e + if Rails.env.production? + Rails.logger.error(e.message) + + if Gitlab::Sentry.enabled? + Gitlab::Sentry.context + Raven.capture_exception(e) + end + else + raise + end + end end super(*args) From 462f9e93875845eb46853ad47a60b37ccce2771c Mon Sep 17 00:00:00 2001 From: Marcia Ramos Date: Mon, 5 Mar 2018 11:44:42 +0000 Subject: [PATCH 23/52] Docs: update ssh doc --- doc/ssh/README.md | 51 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/doc/ssh/README.md b/doc/ssh/README.md index 33a2d7a88a7..aa14a39e4c9 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -35,8 +35,8 @@ to clipboard step. If you don't see the string or would like to generate a SSH key pair with a custom name continue onto the next step. -> -**Note:** Public SSH key may also be named as follows: +Note that Public SSH key may also be named as follows: + - `id_dsa.pub` - `id_ecdsa.pub` - `id_ed25519.pub` @@ -73,7 +73,7 @@ custom name continue onto the next step. key pair, but it is not required and you can skip creating a password by pressing enter. - >**Note:** + NOTE: **Note:** If you want to change the password of your SSH key pair, you can use `ssh-keygen -p `. @@ -162,11 +162,13 @@ That's why it needs to uniquely map to a single user. ## Deploy keys +### Per-repository deploy keys + Deploy keys allow read-only or read-write (if enabled) access to one or multiple projects with a single SSH key pair. This is really useful for cloning repositories to your Continuous -Integration (CI) server. By using deploy keys, you don't have to setup a +Integration (CI) server. By using deploy keys, you don't have to set up a dummy user account. If you are a project master or owner, you can add a deploy key in the @@ -185,6 +187,47 @@ a group. Deploy keys can be shared between projects, you just need to add them to each project. +### Global shared deploy keys + +Global Shared Deploy keys allow read-only or read-write (if enabled) access to +be configured on any repository in the entire GitLab installation. + +This is really useful for integrating repositories to secured, shared Continuous +Integration (CI) services or other shared services. +GitLab administrators can set up the Global Shared Deploy key in GitLab and +add the private key to any shared systems. Individual repositories opt into +exposing their repsitory using these keys when a project masters (or higher) +authorizes a Global Shared Deploy key to be used with their project. + +Global Shared Keys can provide greater security compared to Per-Project Deploy +Keys since an administrator of the target integrated system is the only one +who needs to know and configure the private key. + +GitLab administrators set up Global Deploy keys in the Admin area under the +section **Deploy Keys**. Ensure keys have a meaningful title as that will be +the primary way for project masters and owners to identify the correct Global +Deploy key to add. For instance, if the key gives access to a SaaS CI instance, +use the name of that service in the key name if that is all it is used for. +When creating Global Shared Deploy keys, give some thought to the granularity +of keys - they could be of very narrow usage such as just a specific service or +of broader usage for something like "Anywhere you need to give read access to +your repository". + +Once a GitLab administrator adds the Global Deployment key, project masters +and owners can add it in project's **Settings > Repository** section by expanding the +**Deploy Key** section and clicking **Enable** next to the appropriate key listed +under **Public deploy keys available to any project**. + +NOTE: **Note:** +The heading **Public deploy keys available to any project** only appears +if there is at least one Global Deploy Key configured. + +CAUTION: **Warning:** +Defining Global Deploy Keys does not expose any given repository via +the key until that respository adds the Global Deploy Key to their project. +In this way the Global Deploy Keys enable access by other systems, but do +not implicitly give any access just by setting them up. + ## Applications ### Eclipse From a83db3c9ea7b719f7839438709666ac8f745b8d4 Mon Sep 17 00:00:00 2001 From: Jakub Kadlubiec Date: Mon, 5 Mar 2018 11:56:17 +0000 Subject: [PATCH 24/52] Add a note about the "List MR pipelines" endpoint only being available from 10.5.0 --- doc/api/merge_requests.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index cb9b0618767..3e60e001bc1 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -494,6 +494,8 @@ Parameters: ## List MR pipelines +> [Introduced][ce-15454] in GitLab 10.5.0. + Get a list of merge request pipelines. ``` @@ -1449,3 +1451,4 @@ Example response: [ce-13060]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13060 [ce-14016]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14016 +[ce-15454]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15454 From 53de2737fd322332b67cbab1cb2df403ec7272a3 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:03:03 +0530 Subject: [PATCH 25/52] Allow exposing methods to `gon_helper` --- app/helpers/labels_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index c1c19062c91..b2c641a5dbd 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -1,4 +1,5 @@ module LabelsHelper + extend self include ActionView::Helpers::TagHelper def show_label_issuables_link?(label, issuables_type, current_user: nil, project: nil) From e7789ed91934360f7d7ffda4a15c6cfe6ee0101a Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:03:25 +0530 Subject: [PATCH 26/52] Add `suggest_colors` from LabelsHelper --- lib/gitlab/gon_helper.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index 92f0e0402a8..a7e055ac444 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -20,6 +20,7 @@ module Gitlab gon.sprite_icons = IconsHelper.sprite_icon_path gon.sprite_file_icons = IconsHelper.sprite_file_icons_path gon.test_env = Rails.env.test? + gon.suggested_label_colors = LabelsHelper.suggested_colors if current_user gon.current_user_id = current_user.id From 3005d6046484d6cf42fe02aa7577ad123e4d240d Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:04:59 +0530 Subject: [PATCH 27/52] ListLabel Shared Model --- app/assets/javascripts/{boards => vue_shared}/models/label.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) rename app/assets/javascripts/{boards => vue_shared}/models/label.js (77%) diff --git a/app/assets/javascripts/boards/models/label.js b/app/assets/javascripts/vue_shared/models/label.js similarity index 77% rename from app/assets/javascripts/boards/models/label.js rename to app/assets/javascripts/vue_shared/models/label.js index 98c1ec014c4..70b9efe0c68 100644 --- a/app/assets/javascripts/boards/models/label.js +++ b/app/assets/javascripts/vue_shared/models/label.js @@ -1,7 +1,5 @@ -/* eslint-disable no-unused-vars, space-before-function-paren */ - class ListLabel { - constructor (obj) { + constructor(obj) { this.id = obj.id; this.title = obj.title; this.type = obj.type; From 3fe4ea8018fa3f4fa8482f03d228b409be774571 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:05:58 +0530 Subject: [PATCH 28/52] Update path for ListLabel model --- app/assets/javascripts/boards/index.js | 2 +- spec/javascripts/boards/board_card_spec.js | 2 +- spec/javascripts/boards/boards_store_spec.js | 2 +- spec/javascripts/boards/issue_card_spec.js | 2 +- spec/javascripts/boards/issue_spec.js | 2 +- spec/javascripts/boards/list_spec.js | 2 +- spec/javascripts/boards/modal_store_spec.js | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js index 8e31f1865f0..8b34fe232c2 100644 --- a/app/assets/javascripts/boards/index.js +++ b/app/assets/javascripts/boards/index.js @@ -5,12 +5,12 @@ import Vue from 'vue'; import Flash from '~/flash'; import { __ } from '~/locale'; +import '~/vue_shared/models/label'; import FilteredSearchBoards from './filtered_search_boards'; import eventHub from './eventhub'; import sidebarEventHub from '~/sidebar/event_hub'; // eslint-disable-line import/first import './models/issue'; -import './models/label'; import './models/list'; import './models/milestone'; import './models/assignee'; diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js index 80a598e63bd..13d607a06d2 100644 --- a/spec/javascripts/boards/board_card_spec.js +++ b/spec/javascripts/boards/board_card_spec.js @@ -9,8 +9,8 @@ import axios from '~/lib/utils/axios_utils'; import '~/boards/models/assignee'; import eventHub from '~/boards/eventhub'; +import '~/vue_shared/models/label'; import '~/boards/models/list'; -import '~/boards/models/label'; import '~/boards/stores/boards_store'; import boardCard from '~/boards/components/board_card.vue'; import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data'; diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 8411f4dd8a6..0cf9e4c9ba1 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -7,8 +7,8 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import Cookies from 'js-cookie'; +import '~/vue_shared/models/label'; import '~/boards/models/issue'; -import '~/boards/models/label'; import '~/boards/models/list'; import '~/boards/models/assignee'; import '~/boards/services/board_service'; diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js index 278155c585e..37088a6421c 100644 --- a/spec/javascripts/boards/issue_card_spec.js +++ b/spec/javascripts/boards/issue_card_spec.js @@ -4,8 +4,8 @@ import Vue from 'vue'; +import '~/vue_shared/models/label'; import '~/boards/models/issue'; -import '~/boards/models/label'; import '~/boards/models/list'; import '~/boards/models/assignee'; import '~/boards/stores/boards_store'; diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index dbbe14fe3e0..4a11131b55c 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -3,8 +3,8 @@ /* global ListIssue */ import Vue from 'vue'; +import '~/vue_shared/models/label'; import '~/boards/models/issue'; -import '~/boards/models/label'; import '~/boards/models/list'; import '~/boards/models/assignee'; import '~/boards/services/board_service'; diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index 34964b20b05..d9a1d692949 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -6,8 +6,8 @@ import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; import _ from 'underscore'; +import '~/vue_shared/models/label'; import '~/boards/models/issue'; -import '~/boards/models/label'; import '~/boards/models/list'; import '~/boards/models/assignee'; import '~/boards/services/board_service'; diff --git a/spec/javascripts/boards/modal_store_spec.js b/spec/javascripts/boards/modal_store_spec.js index 7eecb58a4c3..e9d77f035e3 100644 --- a/spec/javascripts/boards/modal_store_spec.js +++ b/spec/javascripts/boards/modal_store_spec.js @@ -1,7 +1,7 @@ /* global ListIssue */ +import '~/vue_shared/models/label'; import '~/boards/models/issue'; -import '~/boards/models/label'; import '~/boards/models/list'; import '~/boards/models/assignee'; import '~/boards/stores/modal_store'; From 8c9094cbaf9fa1f5eda5039448f759f745123186 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:07:01 +0530 Subject: [PATCH 29/52] LabelsSelectComponent tests mock data --- .../sidebar/labels_select/mock_data.js | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js new file mode 100644 index 00000000000..e9008c29b22 --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js @@ -0,0 +1,49 @@ +export const mockLabels = [ + { + id: 26, + title: 'Foo Label', + description: 'Foobar', + color: '#BADA55', + text_color: '#FFFFFF', + }, +]; + +export const mockSuggestedColors = [ + '#0033CC', + '#428BCA', + '#44AD8E', + '#A8D695', + '#5CB85C', + '#69D100', + '#004E00', + '#34495E', + '#7F8C8D', + '#A295D6', + '#5843AD', + '#8E44AD', + '#FFECDB', + '#AD4363', + '#D10069', + '#CC0033', + '#FF0000', + '#D9534F', + '#D1D100', + '#F0AD4E', + '#AD8D43', +]; + +export const mockConfig = { + showCreate: true, + abilityName: 'issue', + context: { + labels: mockLabels, + }, + namespace: 'gitlab-org', + updatePath: '/gitlab-org/my-project/issue/1', + labelsPath: '/gitlab-org/my-project/labels.json', + labelsWebUrl: '/gitlab-org/my-project/labels', + labelFilterBasePath: '/gitlab-org/my-project/issues', + canEdit: true, + suggestedColors: mockSuggestedColors, + emptyValueText: 'None', +}; From 7d58f0e088431b04639639018c36434fb95c2cb9 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:07:33 +0530 Subject: [PATCH 30/52] LabelsSelect Base Component --- .../components/sidebar/labels_select/base.vue | 149 ++++++++++++++++++ .../sidebar/labels_select/base_spec.js | 81 ++++++++++ 2 files changed, 230 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue create mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue new file mode 100644 index 00000000000..3b17135f0e5 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/base.vue @@ -0,0 +1,149 @@ + + + diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js new file mode 100644 index 00000000000..67056793a20 --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js @@ -0,0 +1,81 @@ +import Vue from 'vue'; + +import LabelsSelect from '~/labels_select'; +import baseComponent from '~/vue_shared/components/sidebar/labels_select/base.vue'; + +import { mockConfig, mockLabels } from './mock_data'; + +import mountComponent from '../../../../helpers/vue_mount_component_helper'; + +const createComponent = (config = mockConfig) => { + const Component = Vue.extend(baseComponent); + + return mountComponent(Component, config); +}; + +describe('BaseComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('hiddenInputName', () => { + it('returns correct string when showCreate prop is `true`', () => { + expect(vm.hiddenInputName).toBe('issue[label_names][]'); + }); + + it('returns correct string when showCreate prop is `false`', () => { + const mockConfigNonEditable = Object.assign({}, mockConfig, { showCreate: false }); + const vmNonEditable = createComponent(mockConfigNonEditable); + expect(vmNonEditable.hiddenInputName).toBe('label_id[]'); + vmNonEditable.$destroy(); + }); + }); + }); + + describe('methods', () => { + describe('handleClick', () => { + it('emits onLabelClick event with label and list of labels as params', () => { + spyOn(vm, '$emit'); + vm.handleClick(mockLabels[0]); + expect(vm.$emit).toHaveBeenCalledWith('onLabelClick', mockLabels[0]); + }); + }); + }); + + describe('mounted', () => { + it('creates LabelsSelect object and assigns it to `labelsDropdon` as prop', () => { + expect(vm.labelsDropdown instanceof LabelsSelect).toBe(true); + }); + }); + + describe('template', () => { + it('renders component container element with classes `block labels`', () => { + expect(vm.$el.classList.contains('block')).toBe(true); + expect(vm.$el.classList.contains('labels')).toBe(true); + }); + + it('renders `.selectbox` element', () => { + expect(vm.$el.querySelector('.selectbox')).not.toBeNull(); + expect(vm.$el.querySelector('.selectbox').getAttribute('style')).toBe('display: none;'); + }); + + it('renders `.dropdown` element', () => { + expect(vm.$el.querySelector('.dropdown')).not.toBeNull(); + }); + + it('renders `.dropdown-menu` element', () => { + const dropdownMenuEl = vm.$el.querySelector('.dropdown-menu'); + expect(dropdownMenuEl).not.toBeNull(); + expect(dropdownMenuEl.querySelector('.dropdown-page-one')).not.toBeNull(); + expect(dropdownMenuEl.querySelector('.dropdown-content')).not.toBeNull(); + expect(dropdownMenuEl.querySelector('.dropdown-loading')).not.toBeNull(); + }); + }); +}); From 5c8854864ae4886c8a642536f6b4297a633144d2 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:07:52 +0530 Subject: [PATCH 31/52] LabelsSelect DropdownButton Component --- .../sidebar/labels_select/dropdown_button.vue | 78 ++++++++++++++++++ .../labels_select/dropdown_button_spec.js | 82 +++++++++++++++++++ 2 files changed, 160 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue create mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue new file mode 100644 index 00000000000..47497c1de98 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button.vue @@ -0,0 +1,78 @@ + + + diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js new file mode 100644 index 00000000000..ec63ac306d0 --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js @@ -0,0 +1,82 @@ +import Vue from 'vue'; + +import dropdownButtonComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_button.vue'; + +import { mockConfig, mockLabels } from './mock_data'; + +import mountComponent from '../../../../helpers/vue_mount_component_helper'; + +const componentConfig = Object.assign({}, mockConfig, { + fieldName: 'label_id[]', + labels: mockLabels, + showExtraOptions: false, +}); + +const createComponent = (config = componentConfig) => { + const Component = Vue.extend(dropdownButtonComponent); + + return mountComponent(Component, config); +}; + +describe('DropdownButtonComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('dropdownToggleText', () => { + it('returns text as `Label` when `labels` prop is empty array', () => { + const mockEmptyLabels = Object.assign({}, componentConfig, { labels: [] }); + const vmEmptyLabels = createComponent(mockEmptyLabels); + expect(vmEmptyLabels.dropdownToggleText).toBe('Label'); + vmEmptyLabels.$destroy(); + }); + + it('returns first label name with remaining label count when `labels` prop has more than one item', () => { + const mockMoreLabels = Object.assign({}, componentConfig, { + labels: mockLabels.concat(mockLabels), + }); + const vmMoreLabels = createComponent(mockMoreLabels); + expect(vmMoreLabels.dropdownToggleText).toBe('Foo Label +1 more'); + vmMoreLabels.$destroy(); + }); + + it('returns first label name when `labels` prop has only one item present', () => { + expect(vm.dropdownToggleText).toBe('Foo Label'); + }); + }); + }); + + describe('template', () => { + it('renders component container element of type `button`', () => { + expect(vm.$el.nodeName).toBe('BUTTON'); + }); + + it('renders component container element with required data attributes', () => { + expect(vm.$el.dataset.abilityName).toBe(vm.abilityName); + expect(vm.$el.dataset.fieldName).toBe(vm.fieldName); + expect(vm.$el.dataset.issueUpdate).toBe(vm.updatePath); + expect(vm.$el.dataset.labels).toBe(vm.labelsPath); + expect(vm.$el.dataset.namespacePath).toBe(vm.namespace); + expect(vm.$el.dataset.showAny).not.toBeDefined(); + }); + + it('renders dropdown toggle text element', () => { + const dropdownToggleTextEl = vm.$el.querySelector('.dropdown-toggle-text'); + expect(dropdownToggleTextEl).not.toBeNull(); + expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label'); + }); + + it('renders dropdown button icon', () => { + const dropdownIconEl = vm.$el.querySelector('i.fa'); + expect(dropdownIconEl).not.toBeNull(); + expect(dropdownIconEl.classList.contains('fa-chevron-down')).toBe(true); + }); + }); +}); From 523093220beab3c2768a748542829242f6a22c06 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:08:13 +0530 Subject: [PATCH 32/52] LabelsSelect DropdownCreateLabel Component --- .../labels_select/dropdown_create_label.vue | 84 +++++++++++++++++++ .../dropdown_create_label_spec.js | 84 +++++++++++++++++++ 2 files changed, 168 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue create mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue new file mode 100644 index 00000000000..4200d1e8473 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue @@ -0,0 +1,84 @@ + + + diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js new file mode 100644 index 00000000000..f07aefb2f87 --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js @@ -0,0 +1,84 @@ +import Vue from 'vue'; + +import dropdownCreateLabelComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue'; + +import { mockSuggestedColors } from './mock_data'; + +import mountComponent from '../../../../helpers/vue_mount_component_helper'; + +const createComponent = () => { + const Component = Vue.extend(dropdownCreateLabelComponent); + + return mountComponent(Component); +}; + +describe('DropdownCreateLabelComponent', () => { + let vm; + + beforeEach(() => { + gon.suggested_label_colors = mockSuggestedColors; + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('created', () => { + it('initializes `suggestedColors` prop on component from `gon.suggested_color_labels` object', () => { + expect(vm.suggestedColors.length).toBe(mockSuggestedColors.length); + }); + }); + + describe('template', () => { + it('renders component container element with classes `dropdown-page-two dropdown-new-label`', () => { + expect(vm.$el.classList.contains('dropdown-page-two', 'dropdown-new-label')).toBe(true); + }); + + it('renders `Go back` button on component header', () => { + const backButtonEl = vm.$el.querySelector('.dropdown-title button.dropdown-title-button.dropdown-menu-back'); + expect(backButtonEl).not.toBe(null); + expect(backButtonEl.querySelector('.fa-arrow-left')).not.toBe(null); + }); + + it('renders component header element', () => { + const headerEl = vm.$el.querySelector('.dropdown-title'); + expect(headerEl.innerText.trim()).toContain('Create new label'); + }); + + it('renders `Close` button on component header', () => { + const closeButtonEl = vm.$el.querySelector('.dropdown-title button.dropdown-title-button.dropdown-menu-close'); + expect(closeButtonEl).not.toBe(null); + expect(closeButtonEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBe(null); + }); + + it('renders `Name new label` input element', () => { + expect(vm.$el.querySelector('.dropdown-labels-error.js-label-error')).not.toBe(null); + expect(vm.$el.querySelector('input#new_label_name.default-dropdown-input')).not.toBe(null); + }); + + it('renders suggested colors list elements', () => { + const colorsListContainerEl = vm.$el.querySelector('.suggest-colors.suggest-colors-dropdown'); + expect(colorsListContainerEl).not.toBe(null); + expect(colorsListContainerEl.querySelectorAll('a').length).toBe(mockSuggestedColors.length); + + const colorItemEl = colorsListContainerEl.querySelectorAll('a')[0]; + expect(colorItemEl.dataset.color).toBe(vm.suggestedColors[0]); + expect(colorItemEl.getAttribute('style')).toBe('background-color: rgb(0, 51, 204);'); + }); + + it('renders color input element', () => { + expect(vm.$el.querySelector('.dropdown-label-color-input')).not.toBe(null); + expect(vm.$el.querySelector('.dropdown-label-color-preview.js-dropdown-label-color-preview')).not.toBe(null); + expect(vm.$el.querySelector('input#new_label_color.default-dropdown-input')).not.toBe(null); + }); + + it('renders component action buttons', () => { + const createBtnEl = vm.$el.querySelector('button.js-new-label-btn'); + const cancelBtnEl = vm.$el.querySelector('button.js-cancel-label-btn'); + expect(createBtnEl).not.toBe(null); + expect(createBtnEl.innerText.trim()).toBe('Create'); + expect(cancelBtnEl.innerText.trim()).toBe('Cancel'); + }); + }); +}); From ab1bc5c7f76bcb893004749918a2913c2c965a1b Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:08:44 +0530 Subject: [PATCH 33/52] LabelsSelect DropdownFooter Component --- .../sidebar/labels_select/dropdown_footer.vue | 34 +++++++++++++++ .../labels_select/dropdown_footer_spec.js | 42 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue create mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue new file mode 100644 index 00000000000..e951a863811 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer.vue @@ -0,0 +1,34 @@ + + + diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js new file mode 100644 index 00000000000..809e0327b1c --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js @@ -0,0 +1,42 @@ +import Vue from 'vue'; + +import dropdownFooterComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_footer.vue'; + +import { mockConfig } from './mock_data'; + +import mountComponent from '../../../../helpers/vue_mount_component_helper'; + +const createComponent = (labelsWebUrl = mockConfig.labelsWebUrl) => { + const Component = Vue.extend(dropdownFooterComponent); + + return mountComponent(Component, { + labelsWebUrl, + }); +}; + +describe('DropdownFooterComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('template', () => { + it('renders `Create new label` link element', () => { + const createLabelEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-toggle-page'); + expect(createLabelEl).not.toBeNull(); + expect(createLabelEl.innerText.trim()).toBe('Create new label'); + }); + + it('renders `Manage labels` link element', () => { + const manageLabelsEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-external-link'); + expect(manageLabelsEl).not.toBeNull(); + expect(manageLabelsEl.getAttribute('href')).toBe(vm.labelsWebUrl); + expect(manageLabelsEl.innerText.trim()).toBe('Manage labels'); + }); + }); +}); From 5989aa18f4ec49973e4d42015c1af8c93d3aed1f Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:08:56 +0530 Subject: [PATCH 34/52] LabelsSelect DropdownHeader Component --- .../sidebar/labels_select/dropdown_header.vue | 21 +++++++++++ .../labels_select/dropdown_header_spec.js | 36 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header.vue create mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header.vue new file mode 100644 index 00000000000..7664acdf19c --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header.vue @@ -0,0 +1,21 @@ + + + diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js new file mode 100644 index 00000000000..325fa47c957 --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js @@ -0,0 +1,36 @@ +import Vue from 'vue'; + +import dropdownHeaderComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_header.vue'; + +import mountComponent from '../../../../helpers/vue_mount_component_helper'; + +const createComponent = () => { + const Component = Vue.extend(dropdownHeaderComponent); + + return mountComponent(Component); +}; + +describe('DropdownHeaderComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('template', () => { + it('renders header text element', () => { + const headerEl = vm.$el.querySelector('.dropdown-title span'); + expect(headerEl.innerText.trim()).toBe('Assign labels'); + }); + + it('renders `Close` button element', () => { + const closeBtnEl = vm.$el.querySelector('.dropdown-title button.dropdown-title-button.dropdown-menu-close'); + expect(closeBtnEl).not.toBeNull(); + expect(closeBtnEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBeNull(); + }); + }); +}); From b4bd9777fb519a537c011d87651d29683587893e Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:09:12 +0530 Subject: [PATCH 35/52] LabelsSelect DropdownHiddenInput Component --- .../labels_select/dropdown_hidden_input.vue | 22 +++++++++++ .../dropdown_hidden_input_spec.js | 37 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue create mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue new file mode 100644 index 00000000000..1832c3c1757 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue @@ -0,0 +1,22 @@ + + + diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js new file mode 100644 index 00000000000..703b87498c7 --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js @@ -0,0 +1,37 @@ +import Vue from 'vue'; + +import dropdownHiddenInputComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue'; + +import { mockLabels } from './mock_data'; + +import mountComponent from '../../../../helpers/vue_mount_component_helper'; + +const createComponent = (name = 'label_id[]', label = mockLabels[0]) => { + const Component = Vue.extend(dropdownHiddenInputComponent); + + return mountComponent(Component, { + name, + label, + }); +}; + +describe('DropdownHiddenInputComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('template', () => { + it('renders input element of type `hidden`', () => { + expect(vm.$el.nodeName).toBe('INPUT'); + expect(vm.$el.getAttribute('type')).toBe('hidden'); + expect(vm.$el.getAttribute('name')).toBe(vm.name); + expect(vm.$el.getAttribute('value')).toBe(`${vm.label.id}`); + }); + }); +}); From 2e5497f0057021daa57d3e8958c840f6b8a82098 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:09:27 +0530 Subject: [PATCH 36/52] LabelsSelect DropdownSearchInput Component --- .../labels_select/dropdown_search_input.vue | 27 +++++++++++++ .../dropdown_search_input_spec.js | 39 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue create mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue new file mode 100644 index 00000000000..ae633460c95 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue @@ -0,0 +1,27 @@ + + + diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js new file mode 100644 index 00000000000..69e11d966c2 --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js @@ -0,0 +1,39 @@ +import Vue from 'vue'; + +import dropdownSearchInputComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue'; + +import mountComponent from '../../../../helpers/vue_mount_component_helper'; + +const createComponent = () => { + const Component = Vue.extend(dropdownSearchInputComponent); + + return mountComponent(Component); +}; + +describe('DropdownSearchInputComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('template', () => { + it('renders input element with type `search`', () => { + const inputEl = vm.$el.querySelector('input.dropdown-input-field'); + expect(inputEl).not.toBeNull(); + expect(inputEl.getAttribute('type')).toBe('search'); + }); + + it('renders search icon element', () => { + expect(vm.$el.querySelector('.fa-search.dropdown-input-search')).not.toBeNull(); + }); + + it('renders clear search icon element', () => { + expect(vm.$el.querySelector('.fa-times.dropdown-input-clear.js-dropdown-input-clear')).not.toBeNull(); + }); + }); +}); From 8b44ad6e6ab5408dca800b9cdb6a29335704109a Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:09:43 +0530 Subject: [PATCH 37/52] LabelsSelect DropdownTitle Component --- .../sidebar/labels_select/dropdown_title.vue | 30 +++++++++++++ .../labels_select/dropdown_title_spec.js | 42 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue create mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue new file mode 100644 index 00000000000..7da82e90e29 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title.vue @@ -0,0 +1,30 @@ + + + diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js new file mode 100644 index 00000000000..c3580933072 --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js @@ -0,0 +1,42 @@ +import Vue from 'vue'; + +import dropdownTitleComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_title.vue'; + +import mountComponent from '../../../../helpers/vue_mount_component_helper'; + +const createComponent = (canEdit = true) => { + const Component = Vue.extend(dropdownTitleComponent); + + return mountComponent(Component, { + canEdit, + }); +}; + +describe('DropdownTitleComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('template', () => { + it('renders title text', () => { + expect(vm.$el.classList.contains('title', 'hide-collapsed')).toBe(true); + expect(vm.$el.innerText.trim()).toContain('Labels'); + }); + + it('renders spinner icon element', () => { + expect(vm.$el.querySelector('.fa-spinner.fa-spin.block-loading')).not.toBeNull(); + }); + + it('renders `Edit` button element', () => { + const editBtnEl = vm.$el.querySelector('button.edit-link.js-sidebar-dropdown-toggle'); + expect(editBtnEl).not.toBeNull(); + expect(editBtnEl.innerText.trim()).toBe('Edit'); + }); + }); +}); From c1ed7f3f961348ca9897beadc75ea9cf2494f344 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:09:55 +0530 Subject: [PATCH 38/52] LabelsSelect DropdownValue Component --- .../sidebar/labels_select/dropdown_value.vue | 63 +++++++++++++ .../labels_select/dropdown_value_spec.js | 94 +++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue create mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue new file mode 100644 index 00000000000..ba4c8fba5ec --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value.vue @@ -0,0 +1,63 @@ + + + diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js new file mode 100644 index 00000000000..66e0957b431 --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js @@ -0,0 +1,94 @@ +import Vue from 'vue'; + +import dropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue'; + +import { mockConfig, mockLabels } from './mock_data'; + +import mountComponent from '../../../../helpers/vue_mount_component_helper'; + +const createComponent = ( + labels = mockLabels, + labelFilterBasePath = mockConfig.labelFilterBasePath, +) => { + const Component = Vue.extend(dropdownValueComponent); + + return mountComponent(Component, { + labels, + labelFilterBasePath, + }); +}; + +describe('DropdownValueComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('isEmpty', () => { + it('returns true if `labels` prop is empty', () => { + const vmEmptyLabels = createComponent([]); + expect(vmEmptyLabels.isEmpty).toBe(true); + vmEmptyLabels.$destroy(); + }); + + it('returns false if `labels` prop is empty', () => { + expect(vm.isEmpty).toBe(false); + }); + }); + }); + + describe('methods', () => { + describe('labelFilterUrl', () => { + it('returns URL string starting with labelFilterBasePath and encoded label.title', () => { + expect(vm.labelFilterUrl({ + title: 'Foo bar', + })).toBe('/gitlab-org/my-project/issues?label_name[]=Foo%20bar'); + }); + }); + + describe('labelStyle', () => { + it('returns object with `color` & `backgroundColor` properties from label.textColor & label.color', () => { + const label = { + textColor: '#FFFFFF', + color: '#BADA55', + }; + const styleObj = vm.labelStyle(label); + + expect(styleObj.color).toBe(label.textColor); + expect(styleObj.backgroundColor).toBe(label.color); + }); + }); + }); + + describe('template', () => { + it('renders component container element with classes `hide-collapsed value issuable-show-labels`', () => { + expect(vm.$el.classList.contains('hide-collapsed', 'value', 'issuable-show-labels')).toBe(true); + }); + + it('render slot content inside component when `labels` prop is empty', () => { + const vmEmptyLabels = createComponent([]); + expect(vmEmptyLabels.$el.querySelector('.text-secondary').innerText.trim()).toBe(mockConfig.emptyValueText); + vmEmptyLabels.$destroy(); + }); + + it('renders label element with filter URL', () => { + expect(vm.$el.querySelector('a').getAttribute('href')).toBe('/gitlab-org/my-project/issues?label_name[]=Foo%20Label'); + }); + + it('renders label element with tooltip and styles based on label details', () => { + const labelEl = vm.$el.querySelector('a span.label.color-label'); + expect(labelEl).not.toBeNull(); + expect(labelEl.dataset.placement).toBe('bottom'); + expect(labelEl.dataset.container).toBe('body'); + expect(labelEl.dataset.originalTitle).toBe(mockLabels[0].description); + expect(labelEl.getAttribute('style')).toBe('background-color: rgb(186, 218, 85);'); + expect(labelEl.innerText.trim()).toBe(mockLabels[0].title); + }); + }); +}); From 00db4cb733e5c1314d63972a280108a1d81c7301 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:10:13 +0530 Subject: [PATCH 39/52] LabelsSelect DropdownValueCollapsed Component --- .../dropdown_value_collapsed.vue | 48 ++++++++++++ .../dropdown_value_collapsed_spec.js | 74 +++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue create mode 100644 spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue new file mode 100644 index 00000000000..5cf728fe050 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue @@ -0,0 +1,48 @@ + + + diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js new file mode 100644 index 00000000000..93b42795bea --- /dev/null +++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js @@ -0,0 +1,74 @@ +import Vue from 'vue'; + +import dropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue'; + +import { mockLabels } from './mock_data'; + +import mountComponent from '../../../../helpers/vue_mount_component_helper'; + +const createComponent = (labels = mockLabels) => { + const Component = Vue.extend(dropdownValueCollapsedComponent); + + return mountComponent(Component, { + labels, + }); +}; + +describe('DropdownValueCollapsedComponent', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('computed', () => { + describe('labelsList', () => { + it('returns empty text when `labels` prop is empty array', () => { + const vmEmptyLabels = createComponent([]); + expect(vmEmptyLabels.labelsList).toBe(''); + vmEmptyLabels.$destroy(); + }); + + it('returns labels names separated by coma when `labels` prop has more than one item', () => { + const vmMoreLabels = createComponent(mockLabels.concat(mockLabels)); + expect(vmMoreLabels.labelsList).toBe('Foo Label, Foo Label'); + vmMoreLabels.$destroy(); + }); + + it('returns labels names separated by coma with remaining labels count and `and more` phrase when `labels` prop has more than five items', () => { + const mockMoreLabels = Object.assign([], mockLabels); + for (let i = 0; i < 6; i += 1) { + mockMoreLabels.unshift(mockLabels[0]); + } + + const vmMoreLabels = createComponent(mockMoreLabels); + expect(vmMoreLabels.labelsList).toBe('Foo Label, Foo Label, Foo Label, Foo Label, Foo Label, and 2 more'); + vmMoreLabels.$destroy(); + }); + + it('returns first label name when `labels` prop has only one item present', () => { + expect(vm.labelsList).toBe('Foo Label'); + }); + }); + }); + + describe('template', () => { + it('renders component container element with tooltip`', () => { + expect(vm.$el.dataset.placement).toBe('left'); + expect(vm.$el.dataset.container).toBe('body'); + expect(vm.$el.dataset.originalTitle).toBe(vm.labelsList); + }); + + it('renders tags icon element', () => { + expect(vm.$el.querySelector('.fa-tags')).not.toBeNull(); + }); + + it('renders labels count', () => { + expect(vm.$el.querySelector('span').innerText.trim()).toBe(`${vm.labels.length}`); + }); + }); +}); From 4051d01c9cbd867b2478743ffe1ad50dfb030f6d Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:10:23 +0530 Subject: [PATCH 40/52] Add changelog entry --- changelogs/unreleased/kp-label-select-vue.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/kp-label-select-vue.yml diff --git a/changelogs/unreleased/kp-label-select-vue.yml b/changelogs/unreleased/kp-label-select-vue.yml new file mode 100644 index 00000000000..1f5952f2554 --- /dev/null +++ b/changelogs/unreleased/kp-label-select-vue.yml @@ -0,0 +1,5 @@ +--- +title: Port Labels Select dropdown to Vue +merge_request: 17411 +author: +type: other From 47d4890d3ad8a1c2ecb2b9c497d537c044c76e25 Mon Sep 17 00:00:00 2001 From: bunufi Date: Mon, 5 Mar 2018 12:57:47 +0000 Subject: [PATCH 41/52] Update API: add search param to branches --- ...api_branches_add_search_param_20180207.yml | 5 +++++ doc/api/branches.md | 1 + lib/api/branches.rb | 16 ++++++++++++-- spec/requests/api/branches_spec.rb | 21 +++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/42712_api_branches_add_search_param_20180207.yml diff --git a/changelogs/unreleased/42712_api_branches_add_search_param_20180207.yml b/changelogs/unreleased/42712_api_branches_add_search_param_20180207.yml new file mode 100644 index 00000000000..609b5ce48ef --- /dev/null +++ b/changelogs/unreleased/42712_api_branches_add_search_param_20180207.yml @@ -0,0 +1,5 @@ +--- +title: Add search param to Branches API +merge_request: 17005 +author: bunufi +type: added diff --git a/doc/api/branches.md b/doc/api/branches.md index 80744258acb..01bb30c3859 100644 --- a/doc/api/branches.md +++ b/doc/api/branches.md @@ -13,6 +13,7 @@ GET /projects/:id/repository/branches | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `search` | string | no | Return list of branches matching the search criteria. | ```bash curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/repository/branches diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 1794207e29b..13cfba728fa 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -16,6 +16,10 @@ module API render_api_error!('The branch refname is invalid', 400) end end + + params :filter_params do + optional :search, type: String, desc: 'Return list of branches matching the search criteria' + end end params do @@ -27,15 +31,23 @@ module API end params do use :pagination + use :filter_params end get ':id/repository/branches' do Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42329') repository = user_project.repository - branches = ::Kaminari.paginate_array(repository.branches.sort_by(&:name)) + + branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute + merged_branch_names = repository.merged_branch_names(branches.map(&:name)) - present paginate(branches), with: Entities::Branch, project: user_project, merged_branch_names: merged_branch_names + present( + paginate(::Kaminari.paginate_array(branches)), + with: Entities::Branch, + project: user_project, + merged_branch_names: merged_branch_names + ) end resource ':id/repository/branches/:branch', requirements: BRANCH_ENDPOINT_REQUIREMENTS do diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index e433597f58b..64f51d9843d 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -39,6 +39,27 @@ describe API::Branches do end end + context 'when search parameter is passed' do + context 'and branch exists' do + it 'returns correct branches' do + get api(route, user), per_page: 100, search: branch_name + + searched_branch_names = json_response.map { |branch| branch['name'] } + project_branch_names = project.repository.branch_names.grep(/#{branch_name}/) + + expect(searched_branch_names).to match_array(project_branch_names) + end + end + + context 'and branch does not exist' do + it 'returns an empty array' do + get api(route, user), per_page: 100, search: 'no_such_branch_name_entropy_of_jabadabadu' + + expect(json_response).to eq [] + end + end + end + context 'when unauthenticated', 'and project is public' do before do project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC) From 168ff28506dfe66a13a6c1bc5e3b772445a5bd18 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Wed, 28 Feb 2018 12:16:29 +0100 Subject: [PATCH 42/52] Adds updated_at filter to issues and merge_requests API --- app/finders/issuable_finder.rb | 12 +++++ app/finders/issues_finder.rb | 4 ++ app/finders/merge_requests_finder.rb | 4 ++ app/models/concerns/issuable.rb | 1 + app/models/concerns/updated_at_filterable.rb | 12 +++++ .../41616-api-issues-between-date.yml | 5 ++ doc/api/issues.md | 14 ++++- doc/api/merge_requests.md | 12 +++-- lib/api/issues.rb | 2 + lib/api/merge_requests.rb | 2 + spec/finders/issues_finder_spec.rb | 42 +++++++++++++-- spec/finders/merge_requests_finder_spec.rb | 51 +++++++++++++++++-- spec/requests/api/issues_spec.rb | 36 +++++++++++++ spec/requests/api/merge_requests_spec.rb | 36 +++++++++++++ 14 files changed, 219 insertions(+), 14 deletions(-) create mode 100644 app/models/concerns/updated_at_filterable.rb create mode 100644 changelogs/unreleased/41616-api-issues-between-date.yml diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 9dd6634b38f..b2d4f9938ff 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -19,6 +19,10 @@ # non_archived: boolean # iids: integer[] # my_reaction_emoji: string +# created_after: datetime +# created_before: datetime +# updated_after: datetime +# updated_before: datetime # class IssuableFinder prepend FinderWithCrossProjectAccess @@ -79,6 +83,7 @@ class IssuableFinder def filter_items(items) items = by_scope(items) items = by_created_at(items) + items = by_updated_at(items) items = by_state(items) items = by_group(items) items = by_search(items) @@ -283,6 +288,13 @@ class IssuableFinder end end + def by_updated_at(items) + items = items.updated_after(params[:updated_after]) if params[:updated_after].present? + items = items.updated_before(params[:updated_before]) if params[:updated_before].present? + + items + end + def by_state(items) case params[:state].to_s when 'closed' diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index d65c620e75a..2a27ff0e386 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -17,6 +17,10 @@ # my_reaction_emoji: string # public_only: boolean # due_date: date or '0', '', 'overdue', 'week', or 'month' +# created_after: datetime +# created_before: datetime +# updated_after: datetime +# updated_before: datetime # class IssuesFinder < IssuableFinder CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index 068ae7f8c89..64dc1e6af0f 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -19,6 +19,10 @@ # my_reaction_emoji: string # source_branch: string # target_branch: string +# created_after: datetime +# created_before: datetime +# updated_after: datetime +# updated_before: datetime # class MergeRequestsFinder < IssuableFinder def klass diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 7049f340c9d..4560bc23193 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -19,6 +19,7 @@ module Issuable include AfterCommitQueue include Sortable include CreatedAtFilterable + include UpdatedAtFilterable # This object is used to gather issuable meta data for displaying # upvotes, downvotes, notes and closing merge requests count for issues and merge requests diff --git a/app/models/concerns/updated_at_filterable.rb b/app/models/concerns/updated_at_filterable.rb new file mode 100644 index 00000000000..edb423b7828 --- /dev/null +++ b/app/models/concerns/updated_at_filterable.rb @@ -0,0 +1,12 @@ +module UpdatedAtFilterable + extend ActiveSupport::Concern + + included do + scope :updated_before, ->(date) { where(scoped_table[:updated_at].lteq(date)) } + scope :updated_after, ->(date) { where(scoped_table[:updated_at].gteq(date)) } + + def self.scoped_table + arel_table.alias(table_name) + end + end +end diff --git a/changelogs/unreleased/41616-api-issues-between-date.yml b/changelogs/unreleased/41616-api-issues-between-date.yml new file mode 100644 index 00000000000..d8a23f48699 --- /dev/null +++ b/changelogs/unreleased/41616-api-issues-between-date.yml @@ -0,0 +1,5 @@ +--- +title: Adds updated_at filter to issues and merge_requests API +merge_request: 17417 +author: Jacopo Beschi @jacopo-beschi +type: added diff --git a/doc/api/issues.md b/doc/api/issues.md index da89db17cd9..a4a51101297 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -46,6 +46,10 @@ GET /issues?my_reaction_emoji=star | `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Search issues against their `title` and `description` | +| `created_after` | datetime | no | Return issues created on or after the given time | +| `created_before` | datetime | no | Return issues created on or before the given time | +| `updated_after` | datetime | no | Return issues updated on or after the given time | +| `updated_before` | datetime | no | Return issues updated on or before the given time | ```bash curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/issues @@ -152,6 +156,10 @@ GET /groups/:id/issues?my_reaction_emoji=star | `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Search group issues against their `title` and `description` | +| `created_after` | datetime | no | Return issues created on or after the given time | +| `created_before` | datetime | no | Return issues created on or before the given time | +| `updated_after` | datetime | no | Return issues updated on or after the given time | +| `updated_before` | datetime | no | Return issues updated on or before the given time | ```bash @@ -259,8 +267,10 @@ GET /projects/:id/issues?my_reaction_emoji=star | `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` | | `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` | | `search` | string | no | Search project issues against their `title` and `description` | -| `created_after` | datetime | no | Return issues created after the given time (inclusive) | -| `created_before` | datetime | no | Return issues created before the given time (inclusive) | +| `created_after` | datetime | no | Return issues created on or after the given time | +| `created_before` | datetime | no | Return issues created on or before the given time | +| `updated_after` | datetime | no | Return issues updated on or after the given time | +| `updated_before` | datetime | no | Return issues updated on or before the given time | ```bash curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/4/issues diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 6ce021cb4bf..b7f095da50a 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -41,8 +41,10 @@ Parameters: | `milestone` | string | no | Return merge requests for a specific milestone | | `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request | | `labels` | string | no | Return merge requests matching a comma separated list of labels | -| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) | -| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) | +| `created_after` | datetime | no | Return merge requests created on or after the given time | +| `created_before` | datetime | no | Return merge requests created on or before the given time | +| `updated_after` | datetime | no | Return merge requests updated on or after the given time | +| `updated_before` | datetime | no | Return merge requests updated on or before the given time | | `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all`. Defaults to `created-by-me` | | `author_id` | integer | no | Returns merge requests created by the given user `id`. Combine with `scope=all` or `scope=assigned-to-me` | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` | @@ -158,8 +160,10 @@ Parameters: | `milestone` | string | no | Return merge requests for a specific milestone | | `view` | string | no | If `simple`, returns the `iid`, URL, title, description, and basic state of merge request | | `labels` | string | no | Return merge requests matching a comma separated list of labels | -| `created_after` | datetime | no | Return merge requests created after the given time (inclusive) | -| `created_before` | datetime | no | Return merge requests created before the given time (inclusive) | +| `created_after` | datetime | no | Return merge requests created on or after the given time | +| `created_before` | datetime | no | Return merge requests created on or before the given time | +| `updated_after` | datetime | no | Return merge requests updated on or after the given time | +| `updated_before` | datetime | no | Return merge requests updated on or before the given time | | `scope` | string | no | Return merge requests for the given scope: `created-by-me`, `assigned-to-me` or `all` _([Introduced][ce-13060] in GitLab 9.5)_ | | `author_id` | integer | no | Returns merge requests created by the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | | `assignee_id` | integer | no | Returns merge requests assigned to the given user `id` _([Introduced][ce-13060] in GitLab 9.5)_ | diff --git a/lib/api/issues.rb b/lib/api/issues.rb index b6c278c89d0..f74b3b26802 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -32,6 +32,8 @@ module API optional :search, type: String, desc: 'Search issues for text present in the title or description' optional :created_after, type: DateTime, desc: 'Return issues created after the specified time' optional :created_before, type: DateTime, desc: 'Return issues created before the specified time' + optional :updated_after, type: DateTime, desc: 'Return issues updated after the specified time' + optional :updated_before, type: DateTime, desc: 'Return issues updated before the specified time' optional :author_id, type: Integer, desc: 'Return issues which are authored by the user with the given ID' optional :assignee_id, type: Integer, desc: 'Return issues which are assigned to the user with the given ID' optional :scope, type: String, values: %w[created-by-me assigned-to-me all], diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 4ffd4895c7e..0692cfa09e9 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -42,6 +42,8 @@ module API optional :labels, type: String, desc: 'Comma-separated list of label names' optional :created_after, type: DateTime, desc: 'Return merge requests created after the specified time' optional :created_before, type: DateTime, desc: 'Return merge requests created before the specified time' + optional :updated_after, type: DateTime, desc: 'Return merge requests updated after the specified time' + optional :updated_before, type: DateTime, desc: 'Return merge requests updated before the specified time' optional :view, type: String, values: %w[simple], desc: 'If simple, returns the `iid`, URL, title, description, and basic state of merge request' optional :author_id, type: Integer, desc: 'Return merge requests which are authored by the user with the given ID' optional :assignee_id, type: Integer, desc: 'Return merge requests which are assigned to the user with the given ID' diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index abb7631d7d7..45439640ea3 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -10,9 +10,9 @@ describe IssuesFinder do set(:project3) { create(:project, group: subgroup) } set(:milestone) { create(:milestone, project: project1) } set(:label) { create(:label, project: project2) } - set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago) } - set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab') } - set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 1.week.from_now) } + set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago, updated_at: 1.week.ago) } + set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab', created_at: 1.week.from_now, updated_at: 1.week.from_now) } + set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 2.weeks.from_now, updated_at: 2.weeks.from_now) } set(:issue4) { create(:issue, project: project3) } set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) } set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) } @@ -275,12 +275,46 @@ describe IssuesFinder do end context 'through created_before' do - let(:params) { { created_before: issue1.created_at + 1.second } } + let(:params) { { created_before: issue1.created_at } } it 'returns issues created on or before the given date' do expect(issues).to contain_exactly(issue1) end end + + context 'through created_after and created_before' do + let(:params) { { created_after: issue2.created_at, created_before: issue3.created_at } } + + it 'returns issues created between the given dates' do + expect(issues).to contain_exactly(issue2, issue3) + end + end + end + + context 'filtering by updated_at' do + context 'through updated_after' do + let(:params) { { updated_after: issue3.updated_at } } + + it 'returns issues updated on or after the given date' do + expect(issues).to contain_exactly(issue3) + end + end + + context 'through updated_before' do + let(:params) { { updated_before: issue1.updated_at } } + + it 'returns issues updated on or before the given date' do + expect(issues).to contain_exactly(issue1) + end + end + + context 'through updated_after and updated_before' do + let(:params) { { updated_after: issue2.updated_at, updated_before: issue3.updated_at } } + + it 'returns issues updated between the given dates' do + expect(issues).to contain_exactly(issue2, issue3) + end + end end context 'filtering by reaction name' do diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 7917a00fc50..c8a43ddf410 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -109,7 +109,7 @@ describe MergeRequestsFinder do end end - context 'with created_after and created_before params' do + context 'filtering by created_at/updated_at' do let(:new_project) { create(:project, forked_from_project: project1) } let!(:new_merge_request) do @@ -117,15 +117,18 @@ describe MergeRequestsFinder do :simple, author: user, created_at: 1.week.from_now, + updated_at: 1.week.from_now, source_project: new_project, - target_project: project1) + target_project: new_project) end let!(:old_merge_request) do create(:merge_request, :simple, author: user, + source_branch: 'feature_1', created_at: 1.week.ago, + updated_at: 1.week.ago, source_project: new_project, target_project: new_project) end @@ -135,7 +138,7 @@ describe MergeRequestsFinder do end it 'filters by created_after' do - params = { project_id: project1.id, created_after: new_merge_request.created_at } + params = { project_id: new_project.id, created_after: new_merge_request.created_at } merge_requests = described_class.new(user, params).execute @@ -143,12 +146,52 @@ describe MergeRequestsFinder do end it 'filters by created_before' do - params = { project_id: new_project.id, created_before: old_merge_request.created_at + 1.second } + params = { project_id: new_project.id, created_before: old_merge_request.created_at } merge_requests = described_class.new(user, params).execute expect(merge_requests).to contain_exactly(old_merge_request) end + + it 'filters by created_after and created_before' do + params = { + project_id: new_project.id, + created_after: old_merge_request.created_at, + created_before: new_merge_request.created_at + } + + merge_requests = described_class.new(user, params).execute + + expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request) + end + + it 'filters by updated_after' do + params = { project_id: new_project.id, updated_after: new_merge_request.updated_at } + + merge_requests = described_class.new(user, params).execute + + expect(merge_requests).to contain_exactly(new_merge_request) + end + + it 'filters by updated_before' do + params = { project_id: new_project.id, updated_before: old_merge_request.updated_at } + + merge_requests = described_class.new(user, params).execute + + expect(merge_requests).to contain_exactly(old_merge_request) + end + + it 'filters by updated_after and updated_before' do + params = { + project_id: new_project.id, + updated_after: old_merge_request.updated_at, + updated_before: new_merge_request.updated_at + } + + merge_requests = described_class.new(user, params).execute + + expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request) + end end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index d1569e5d650..6614e8cea43 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -163,6 +163,42 @@ describe API::Issues do expect(first_issue['id']).to eq(issue.id) end + context 'filtering before a specific date' do + let!(:issue2) { create(:issue, project: project, author: user, created_at: Date.new(2000, 1, 1), updated_at: Date.new(2000, 1, 1)) } + + it 'returns issues created before a specific date' do + get api('/issues?created_before=2000-01-02T00:00:00.060Z', user) + + expect(json_response.size).to eq(1) + expect(first_issue['id']).to eq(issue2.id) + end + + it 'returns issues updated before a specific date' do + get api('/issues?updated_before=2000-01-02T00:00:00.060Z', user) + + expect(json_response.size).to eq(1) + expect(first_issue['id']).to eq(issue2.id) + end + end + + context 'filtering after a specific date' do + let!(:issue2) { create(:issue, project: project, author: user, created_at: 1.week.from_now, updated_at: 1.week.from_now) } + + it 'returns issues created after a specific date' do + get api("/issues?created_after=#{issue2.created_at}", user) + + expect(json_response.size).to eq(1) + expect(first_issue['id']).to eq(issue2.id) + end + + it 'returns issues updated after a specific date' do + get api("/issues?updated_after=#{issue2.updated_at}", user) + + expect(json_response.size).to eq(1) + expect(first_issue['id']).to eq(issue2.id) + end + end + it 'returns an array of labeled issues' do get api("/issues", user), labels: label.title diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 658cedd6b5f..5c980853318 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -171,6 +171,42 @@ describe API::MergeRequests do end end + it 'returns merge requests created before a specific date' do + merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', created_at: Date.new(2000, 1, 1)) + + get api('/merge_requests?created_before=2000-01-02T00:00:00.060Z', user) + + expect(json_response.size).to eq(1) + expect(json_response.first['id']).to eq(merge_request2.id) + end + + it 'returns merge requests created after a specific date' do + merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', created_at: 1.week.from_now) + + get api("/merge_requests?created_after=#{merge_request2.created_at}", user) + + expect(json_response.size).to eq(1) + expect(json_response.first['id']).to eq(merge_request2.id) + end + + it 'returns merge requests updated before a specific date' do + merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', updated_at: Date.new(2000, 1, 1)) + + get api('/merge_requests?updated_before=2000-01-02T00:00:00.060Z', user) + + expect(json_response.size).to eq(1) + expect(json_response.first['id']).to eq(merge_request2.id) + end + + it 'returns merge requests updated after a specific date' do + merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', updated_at: 1.week.from_now) + + get api("/merge_requests?updated_after=#{merge_request2.updated_at}", user) + + expect(json_response.size).to eq(1) + expect(json_response.first['id']).to eq(merge_request2.id) + end + context 'search params' do before do merge_request.update(title: 'Search title', description: 'Search description') From 4ed79b4d4898221dee1b075ddfd17f8561699172 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 5 Mar 2018 10:24:18 -0300 Subject: [PATCH 43/52] Rename quick actions handler --- app/services/issuable_base_service.rb | 4 ++-- app/services/merge_requests/create_service.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 5044a3651cf..02fb48108fb 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -109,7 +109,7 @@ class IssuableBaseService < BaseService @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute end - def handle_quick_actions(issuable) + def handle_quick_actions_on_create(issuable) merge_quick_actions_into_params!(issuable) end @@ -135,7 +135,7 @@ class IssuableBaseService < BaseService end def create(issuable) - handle_quick_actions(issuable) + handle_quick_actions_on_create(issuable) filter_params(issuable) params.delete(:state_event) diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb index 0ed7ee6c57a..c57a2445341 100644 --- a/app/services/merge_requests/create_service.rb +++ b/app/services/merge_requests/create_service.rb @@ -35,7 +35,7 @@ module MergeRequests end # Override from IssuableBaseService - def handle_quick_actions(merge_request) + def handle_quick_actions_on_create(merge_request) super handle_wip_event(merge_request) end From 8403d35b2cf2d3c3a6c3c20571e286d9619bb938 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 5 Mar 2018 10:25:44 -0300 Subject: [PATCH 44/52] Update changelog --- changelogs/unreleased/wip-new-mr-cmd.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/changelogs/unreleased/wip-new-mr-cmd.yml b/changelogs/unreleased/wip-new-mr-cmd.yml index 08cbe84ea05..2192ed1b43a 100644 --- a/changelogs/unreleased/wip-new-mr-cmd.yml +++ b/changelogs/unreleased/wip-new-mr-cmd.yml @@ -1,4 +1,3 @@ ---- -title: wip slash command option on merge request creation -merge_request: 8982 +title: Port /wip quick action command to Merge Request creation (on description) +merge_request: 17463 author: Adam Pahlevi From 741caf93e14758c223b6ef819390f5889bdd108b Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 5 Mar 2018 13:25:56 +0000 Subject: [PATCH 45/52] Use limited count queries also for scoped searches --- app/finders/notes_finder.rb | 12 ++++++ app/views/search/_category.html.haml | 8 ++-- .../unreleased/jprovazn-scoped-limit.yml | 6 +++ lib/gitlab/project_search_results.rb | 29 +++++++++---- lib/gitlab/search_results.rb | 16 -------- spec/finders/notes_finder_spec.rb | 12 ++++++ .../lib/gitlab/project_search_results_spec.rb | 41 ++++++++++++++++--- spec/lib/gitlab/search_results_spec.rb | 36 +++------------- 8 files changed, 97 insertions(+), 63 deletions(-) create mode 100644 changelogs/unreleased/jprovazn-scoped-limit.yml diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index 33ee1e975b9..35f4ff2f62f 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -48,11 +48,23 @@ class NotesFinder def init_collection if target notes_on_target + elsif target_type + notes_of_target_type else notes_of_any_type end end + def notes_of_target_type + notes = notes_for_type(target_type) + + search(notes) + end + + def target_type + @params[:target_type] + end + def notes_of_any_type types = %w(commit issue merge_request snippet) note_relations = types.map { |t| notes_for_type(t) } diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml index 915e648a5d3..7d43fd61081 100644 --- a/app/views/search/_category.html.haml +++ b/app/views/search/_category.html.haml @@ -14,25 +14,25 @@ = link_to search_filter_path(scope: 'issues') do Issues %span.badge - = @search_results.issues_count + = limited_count(@search_results.limited_issues_count) - if project_search_tabs?(:merge_requests) %li{ class: active_when(@scope == 'merge_requests') } = link_to search_filter_path(scope: 'merge_requests') do Merge requests %span.badge - = @search_results.merge_requests_count + = limited_count(@search_results.limited_merge_requests_count) - if project_search_tabs?(:milestones) %li{ class: active_when(@scope == 'milestones') } = link_to search_filter_path(scope: 'milestones') do Milestones %span.badge - = @search_results.milestones_count + = limited_count(@search_results.limited_milestones_count) - if project_search_tabs?(:notes) %li{ class: active_when(@scope == 'notes') } = link_to search_filter_path(scope: 'notes') do Comments %span.badge - = @search_results.notes_count + = limited_count(@search_results.limited_notes_count) - if project_search_tabs?(:wiki) %li{ class: active_when(@scope == 'wiki_blobs') } = link_to search_filter_path(scope: 'wiki_blobs') do diff --git a/changelogs/unreleased/jprovazn-scoped-limit.yml b/changelogs/unreleased/jprovazn-scoped-limit.yml new file mode 100644 index 00000000000..45724bb3479 --- /dev/null +++ b/changelogs/unreleased/jprovazn-scoped-limit.yml @@ -0,0 +1,6 @@ +--- +title: Optimize search queries on the search page by setting a limit for matching + records in project scope +merge_request: +author: +type: performance diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index cf0935dbd9a..29277ec6481 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -29,8 +29,18 @@ module Gitlab @blobs_count ||= blobs.count end - def notes_count - @notes_count ||= notes.count + def limited_notes_count + return @limited_notes_count if defined?(@limited_notes_count) + + types = %w(issue merge_request commit snippet) + @limited_notes_count = 0 + + types.each do |type| + @limited_notes_count += notes_finder(type).limit(count_limit).count + break if @limited_notes_count >= count_limit + end + + @limited_notes_count end def wiki_blobs_count @@ -72,11 +82,12 @@ module Gitlab end def single_commit_result? - commits_count == 1 && total_result_count == 1 - end + return false if commits_count != 1 - def total_result_count - issues_count + merge_requests_count + milestones_count + notes_count + blobs_count + wiki_blobs_count + commits_count + counts = %i(limited_milestones_count limited_notes_count + limited_merge_requests_count limited_issues_count + blobs_count wiki_blobs_count) + counts.all? { |count_method| public_send(count_method).zero? } # rubocop:disable GitlabSecurity/PublicSend end private @@ -106,7 +117,11 @@ module Gitlab end def notes - @notes ||= NotesFinder.new(project, @current_user, search: query).execute.user.order('updated_at DESC') + @notes ||= notes_finder(nil) + end + + def notes_finder(type) + NotesFinder.new(project, @current_user, search: query, target_type: type).execute.user.order('updated_at DESC') end def commits diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index 781783f4d97..757ef71b95a 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -62,22 +62,6 @@ module Gitlab without_count ? collection.without_count : collection end - def projects_count - @projects_count ||= projects.count - end - - def issues_count - @issues_count ||= issues.count - end - - def merge_requests_count - @merge_requests_count ||= merge_requests.count - end - - def milestones_count - @milestones_count ||= milestones.count - end - def limited_projects_count @limited_projects_count ||= projects.limit(count_limit).count end diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb index 7b43494eea2..f1ae2c7ab65 100644 --- a/spec/finders/notes_finder_spec.rb +++ b/spec/finders/notes_finder_spec.rb @@ -75,6 +75,18 @@ describe NotesFinder do end end + context 'for target type' do + let(:project) { create(:project, :repository) } + let!(:note1) { create :note_on_issue, project: project } + let!(:note2) { create :note_on_commit, project: project } + + it 'finds only notes for the selected type' do + notes = described_class.new(project, user, target_type: 'issue').execute + + expect(notes).to eq([note1]) + end + end + context 'for target' do let(:project) { create(:project, :repository) } let(:note1) { create :note_on_commit, project: project } diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index d8250e4b4c6..c46bb8edebf 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -217,7 +217,7 @@ describe Gitlab::ProjectSearchResults do expect(issues).to include issue expect(issues).not_to include security_issue_1 expect(issues).not_to include security_issue_2 - expect(results.issues_count).to eq 1 + expect(results.limited_issues_count).to eq 1 end it 'does not list project confidential issues for project members with guest role' do @@ -229,7 +229,7 @@ describe Gitlab::ProjectSearchResults do expect(issues).to include issue expect(issues).not_to include security_issue_1 expect(issues).not_to include security_issue_2 - expect(results.issues_count).to eq 1 + expect(results.limited_issues_count).to eq 1 end it 'lists project confidential issues for author' do @@ -239,7 +239,7 @@ describe Gitlab::ProjectSearchResults do expect(issues).to include issue expect(issues).to include security_issue_1 expect(issues).not_to include security_issue_2 - expect(results.issues_count).to eq 2 + expect(results.limited_issues_count).to eq 2 end it 'lists project confidential issues for assignee' do @@ -249,7 +249,7 @@ describe Gitlab::ProjectSearchResults do expect(issues).to include issue expect(issues).not_to include security_issue_1 expect(issues).to include security_issue_2 - expect(results.issues_count).to eq 2 + expect(results.limited_issues_count).to eq 2 end it 'lists project confidential issues for project members' do @@ -261,7 +261,7 @@ describe Gitlab::ProjectSearchResults do expect(issues).to include issue expect(issues).to include security_issue_1 expect(issues).to include security_issue_2 - expect(results.issues_count).to eq 3 + expect(results.limited_issues_count).to eq 3 end it 'lists all project issues for admin' do @@ -271,7 +271,7 @@ describe Gitlab::ProjectSearchResults do expect(issues).to include issue expect(issues).to include security_issue_1 expect(issues).to include security_issue_2 - expect(results.issues_count).to eq 3 + expect(results.limited_issues_count).to eq 3 end end @@ -304,6 +304,35 @@ describe Gitlab::ProjectSearchResults do end end + describe '#limited_notes_count' do + let(:project) { create(:project, :public) } + let(:note) { create(:note_on_issue, project: project) } + let(:results) { described_class.new(user, project, note.note) } + + context 'when count_limit is lower than total amount' do + before do + allow(results).to receive(:count_limit).and_return(1) + end + + it 'calls note finder once to get the limited amount of notes' do + expect(results).to receive(:notes_finder).once.and_call_original + expect(results.limited_notes_count).to eq(1) + end + end + + context 'when count_limit is higher than total amount' do + it 'calls note finder multiple times to get the limited amount of notes' do + project = create(:project, :public) + note = create(:note_on_issue, project: project) + + results = described_class.new(user, project, note.note) + + expect(results).to receive(:notes_finder).exactly(4).times.and_call_original + expect(results.limited_notes_count).to eq(1) + end + end + end + # Examples for commit access level test # # params: diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb index 9dbab95f70e..87288baedb0 100644 --- a/spec/lib/gitlab/search_results_spec.rb +++ b/spec/lib/gitlab/search_results_spec.rb @@ -29,30 +29,6 @@ describe Gitlab::SearchResults do end end - describe '#projects_count' do - it 'returns the total amount of projects' do - expect(results.projects_count).to eq(1) - end - end - - describe '#issues_count' do - it 'returns the total amount of issues' do - expect(results.issues_count).to eq(1) - end - end - - describe '#merge_requests_count' do - it 'returns the total amount of merge requests' do - expect(results.merge_requests_count).to eq(1) - end - end - - describe '#milestones_count' do - it 'returns the total amount of milestones' do - expect(results.milestones_count).to eq(1) - end - end - context "when count_limit is lower than total amount" do before do allow(results).to receive(:count_limit).and_return(1) @@ -183,7 +159,7 @@ describe Gitlab::SearchResults do expect(issues).not_to include security_issue_3 expect(issues).not_to include security_issue_4 expect(issues).not_to include security_issue_5 - expect(results.issues_count).to eq 1 + expect(results.limited_issues_count).to eq 1 end it 'does not list confidential issues for project members with guest role' do @@ -199,7 +175,7 @@ describe Gitlab::SearchResults do expect(issues).not_to include security_issue_3 expect(issues).not_to include security_issue_4 expect(issues).not_to include security_issue_5 - expect(results.issues_count).to eq 1 + expect(results.limited_issues_count).to eq 1 end it 'lists confidential issues for author' do @@ -212,7 +188,7 @@ describe Gitlab::SearchResults do expect(issues).to include security_issue_3 expect(issues).not_to include security_issue_4 expect(issues).not_to include security_issue_5 - expect(results.issues_count).to eq 3 + expect(results.limited_issues_count).to eq 3 end it 'lists confidential issues for assignee' do @@ -225,7 +201,7 @@ describe Gitlab::SearchResults do expect(issues).not_to include security_issue_3 expect(issues).to include security_issue_4 expect(issues).not_to include security_issue_5 - expect(results.issues_count).to eq 3 + expect(results.limited_issues_count).to eq 3 end it 'lists confidential issues for project members' do @@ -241,7 +217,7 @@ describe Gitlab::SearchResults do expect(issues).to include security_issue_3 expect(issues).not_to include security_issue_4 expect(issues).not_to include security_issue_5 - expect(results.issues_count).to eq 4 + expect(results.limited_issues_count).to eq 4 end it 'lists all issues for admin' do @@ -254,7 +230,7 @@ describe Gitlab::SearchResults do expect(issues).to include security_issue_3 expect(issues).to include security_issue_4 expect(issues).not_to include security_issue_5 - expect(results.issues_count).to eq 5 + expect(results.limited_issues_count).to eq 5 end end From 25ed4a7e324f3d08f121436296efd41365e01258 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 5 Mar 2018 10:26:54 -0300 Subject: [PATCH 46/52] Add "added" type on changelog --- changelogs/unreleased/wip-new-mr-cmd.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/changelogs/unreleased/wip-new-mr-cmd.yml b/changelogs/unreleased/wip-new-mr-cmd.yml index 2192ed1b43a..ce7072631dd 100644 --- a/changelogs/unreleased/wip-new-mr-cmd.yml +++ b/changelogs/unreleased/wip-new-mr-cmd.yml @@ -1,3 +1,4 @@ title: Port /wip quick action command to Merge Request creation (on description) merge_request: 17463 author: Adam Pahlevi +type: added From 0a4ee10eda01e23ddea0e6b4f80f61df3ffaabde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 28 Feb 2018 19:22:44 -0300 Subject: [PATCH 47/52] Fix n+1 issue by not reloading fully loaded blobs --- .../projects/compare_controller.rb | 6 ++-- lib/gitlab/git/blob.rb | 14 +++++---- spec/lib/gitlab/git/blob_spec.rb | 29 +++++++++++++++++++ 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 3cb4eb23981..2b0c2ca97c0 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -17,10 +17,8 @@ class Projects::CompareController < Projects::ApplicationController def show apply_diff_view_cookie! - # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37430 - Gitlab::GitalyClient.allow_n_plus_1_calls do - render - end + + render end def diff_for_path diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index b2fca2c16de..eabcf46cf58 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -238,9 +238,9 @@ module Gitlab self.__send__("#{key}=", options[key.to_sym]) # rubocop:disable GitlabSecurity/PublicSend end - @loaded_all_data = false # Retain the actual size before it is encoded @loaded_size = @data.bytesize if @data + @loaded_all_data = @loaded_size == size end def binary? @@ -255,10 +255,15 @@ module Gitlab # memory as a Ruby string. def load_all_data!(repository) return if @data == '' # don't mess with submodule blobs - return @data if @loaded_all_data - Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled| - @data = begin + # Even if we return early, recalculate wether this blob is binary in + # case a blob was initialized as text but the full data isn't + @binary = nil + + return if @loaded_all_data + + @data = Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled| + begin if is_enabled repository.gitaly_blob_client.get_blob(oid: id, limit: -1).data else @@ -269,7 +274,6 @@ module Gitlab @loaded_all_data = true @loaded_size = @data.bytesize - @binary = nil end def name diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index a6341cd509b..67d898e787e 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -500,4 +500,33 @@ describe Gitlab::Git::Blob, seed_helper: true do end end end + + describe '#load_all_data!' do + let(:full_data) { 'abcd' } + let(:blob) { Gitlab::Git::Blob.new(name: 'test', size: 4, data: 'abc') } + + subject { blob.load_all_data!(repository) } + + it 'loads missing data' do + expect(Gitlab::GitalyClient).to receive(:migrate) + .with(:git_blob_load_all_data).and_return(full_data) + + subject + + expect(blob.data).to eq(full_data) + end + + context 'with a fully loaded blob' do + let(:blob) { Gitlab::Git::Blob.new(name: 'test', size: 4, data: full_data) } + + it "doesn't perform any loading" do + expect(Gitlab::GitalyClient).not_to receive(:migrate) + .with(:git_blob_load_all_data) + + subject + + expect(blob.data).to eq(full_data) + end + end + end end From 641b058b9e8c0faeb6d0de9e0c97b97e7a423c97 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 5 Mar 2018 14:15:58 +0000 Subject: [PATCH 48/52] Fix MR merge commit cross-references to the MR itself --- app/workers/process_commit_worker.rb | 7 +- spec/workers/process_commit_worker_spec.rb | 74 +++++++++++++--------- 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb index 5b25d980bdb..201e7f332b4 100644 --- a/app/workers/process_commit_worker.rb +++ b/app/workers/process_commit_worker.rb @@ -30,10 +30,9 @@ class ProcessCommitWorker end def process_commit_message(project, commit, user, author, default = false) - # this is a GitLab generated commit message, ignore it. - return if commit.merged_merge_request?(user) - - closed_issues = default ? commit.closes_issues(user) : [] + # Ignore closing references from GitLab-generated commit messages. + find_closing_issues = default && !commit.merged_merge_request?(user) + closed_issues = find_closing_issues ? commit.closes_issues(user) : [] close_issues(project, user, author, commit, closed_issues) if closed_issues.any? commit.create_cross_references!(author, closed_issues) diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb index 76ef57b6b1e..ac79d9c0ac1 100644 --- a/spec/workers/process_commit_worker_spec.rb +++ b/spec/workers/process_commit_worker_spec.rb @@ -20,32 +20,6 @@ describe ProcessCommitWorker do worker.perform(project.id, -1, commit.to_hash) end - context 'when commit is a merge request merge commit' do - let(:merge_request) do - create(:merge_request, - description: "Closes #{issue.to_reference}", - source_branch: 'feature-merged', - target_branch: 'master', - source_project: project) - end - - let(:commit) do - project.repository.create_branch('feature-merged', 'feature') - - sha = project.repository.merge(user, - merge_request.diff_head_sha, - merge_request, - "Closes #{issue.to_reference}") - project.repository.commit(sha) - end - - it 'it does not close any issues from the commit message' do - expect(worker).not_to receive(:close_issues) - - worker.perform(project.id, user.id, commit.to_hash) - end - end - it 'processes the commit message' do expect(worker).to receive(:process_commit_message).and_call_original @@ -73,13 +47,21 @@ describe ProcessCommitWorker do describe '#process_commit_message' do context 'when pushing to the default branch' do - it 'closes issues that should be closed per the commit message' do + before do allow(commit).to receive(:safe_message).and_return("Closes #{issue.to_reference}") + end + it 'closes issues that should be closed per the commit message' do expect(worker).to receive(:close_issues).with(project, user, user, commit, [issue]) worker.process_commit_message(project, commit, user, user, true) end + + it 'creates cross references' do + expect(commit).to receive(:create_cross_references!).with(user, [issue]) + + worker.process_commit_message(project, commit, user, user, true) + end end context 'when pushing to a non-default branch' do @@ -90,12 +72,44 @@ describe ProcessCommitWorker do worker.process_commit_message(project, commit, user, user, false) end + + it 'does not create cross references' do + expect(commit).to receive(:create_cross_references!).with(user, []) + + worker.process_commit_message(project, commit, user, user, false) + end end - it 'creates cross references' do - expect(commit).to receive(:create_cross_references!) + context 'when commit is a merge request merge commit to the default branch' do + let(:merge_request) do + create(:merge_request, + description: "Closes #{issue.to_reference}", + source_branch: 'feature-merged', + target_branch: 'master', + source_project: project) + end - worker.process_commit_message(project, commit, user, user) + let(:commit) do + project.repository.create_branch('feature-merged', 'feature') + + MergeRequests::MergeService + .new(project, merge_request.author) + .execute(merge_request) + + merge_request.reload.merge_commit + end + + it 'does not close any issues from the commit message' do + expect(worker).not_to receive(:close_issues) + + worker.process_commit_message(project, commit, user, user, true) + end + + it 'still creates cross references' do + expect(commit).to receive(:create_cross_references!).with(user, []) + + worker.process_commit_message(project, commit, user, user, true) + end end end From 0b7d10851456018328da137beeca931767b4fd0a Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 5 Mar 2018 08:25:27 +0100 Subject: [PATCH 49/52] Cleanup after adding MR diff's commit_count * processes any pending records which are not migrated yet * bumps import_export version because of new commits_count attribute * removes commits_count fallback method --- app/models/merge_request_diff.rb | 4 ---- ...304204842_clean_commits_count_migration.rb | 14 ++++++++++++++ db/schema.rb | 2 +- doc/user/project/settings/import_export.md | 3 ++- lib/gitlab/import_export.rb | 2 +- .../import_export/test_project_export.tar.gz | Bin 343092 -> 343087 bytes vendor/project_templates/express.tar.gz | Bin 5614 -> 5608 bytes vendor/project_templates/rails.tar.gz | Bin 25007 -> 25004 bytes vendor/project_templates/spring.tar.gz | Bin 50945 -> 50938 bytes 9 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20180304204842_clean_commits_count_migration.rb diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index c1c27ccf3e5..06aa67c600f 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -197,10 +197,6 @@ class MergeRequestDiff < ActiveRecord::Base CompareService.new(project, head_commit_sha).execute(project, sha, straight: true) end - def commits_count - super || merge_request_diff_commits.size - end - private def create_merge_request_diff_files(diffs) diff --git a/db/migrate/20180304204842_clean_commits_count_migration.rb b/db/migrate/20180304204842_clean_commits_count_migration.rb new file mode 100644 index 00000000000..ace4c6aa1cf --- /dev/null +++ b/db/migrate/20180304204842_clean_commits_count_migration.rb @@ -0,0 +1,14 @@ +class CleanCommitsCountMigration < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + Gitlab::BackgroundMigration.steal('AddMergeRequestDiffCommitsCount') + end + + def down + end +end diff --git a/db/schema.rb b/db/schema.rb index db8bafe9677..4937fbd3df1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180301084653) do +ActiveRecord::Schema.define(version: 20180304204842) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md index dedf102fc37..5ddeb014b30 100644 --- a/doc/user/project/settings/import_export.md +++ b/doc/user/project/settings/import_export.md @@ -31,7 +31,8 @@ with all their related data and be moved into a new GitLab instance. | GitLab version | Import/Export version | | ---------------- | --------------------- | -| 10.4 to current | 0.2.2 | +| 10.6 to current | 0.2.3 | +| 10.4 | 0.2.2 | | 10.3 | 0.2.1 | | 10.0 | 0.2.0 | | 9.4.0 | 0.1.8 | diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index af203ff711d..b713fa7e1cd 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -3,7 +3,7 @@ module Gitlab extend self # For every version update, the version history in import_export.md has to be kept up to date. - VERSION = '0.2.2'.freeze + VERSION = '0.2.3'.freeze FILENAME_LIMIT = 50 def export_path(relative_path:) diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz index 0cc68aff4947757cbddeaee30bc82076cafec2b4..12bfcc177c755256f86282debb322bbdf437e161 100644 GIT binary patch delta 143848 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∓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 diff --git a/vendor/project_templates/express.tar.gz b/vendor/project_templates/express.tar.gz index dcf5e4a041641ea11df88af466eab29103881443..06093deb4591a50f3dc7461cc0ad822965367b9b 100644 GIT binary patch literal 5608 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 C*d;&! literal 5614 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 I!2l=#058uQuK)l5 diff --git a/vendor/project_templates/rails.tar.gz b/vendor/project_templates/rails.tar.gz index d4856090ed93e3f68ee7644ebe271d5574da6c72..85cc1b6bb783a43c2e2295c5d06b6ab0c5960229 100644 GIT binary patch literal 25004 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 z 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 G7#IKsk$yP< delta 49602 zcmV(uK1AiZj2mt+wP+9;3)VEWtFih08w{6?DZQHhO8_%|F+qP}nwr#8L zmp^UVgQh*{L9fi@UNdVZSqCfkWE`CAtxSzw=!{(LOl(X6{y%}?KiF7U{zn2 zz{F(AWXxbh;ACp%OmAUoXyQz7Yv}A^>h%Bqs)VSZ2o#ipps@6R1pxgw@iRm{vP+Vt zVynbZM9=<-&VSTk=c@X<*rVh#f@PHr*rf+l0boa_4YYrJx*(aRYQ{E~6tS)s^_)f^ z5J*)zB|(Taig*A{7D|B>2oN25s2p|&ca(>VP^A;*Nw5P)9HHdiGs*AAAfQ4)NJ$_` z_l}n|A~>NMN+J;&KAP5kL7Eg3Ey0uD^~m*{nL-j~|I^xGv=h5zFxZ`?sp|V~(F~NGc=XRJ z7*u{ZGA@7cbl>N2y;5mpd03G6clp`{kdd&&`I=cUDF`z^D4sL)gJP$W` zFJ2b)O5h5ej^lIRNtI&M9DZRv5`8yI!mQR(j zFd0^kGIyCMs59H=Sy1g}j$Q4rl~tF#3jv1G^@$l+#`|YI%P1o!spmu|5@a<@PE^(g z?fioewxcz=duK8v+?f8d_=8$PC(3;%97A7o zZGZHOOJ?xY)|e92dT+lj8m{@IXfu0X(XMmh zyRkUSWZbT~zQar;bR#dyL3p*Oz3>ucfq4OQe(-9cYITWePqJle+vzpMf+NleNS$IC zeZ}cqRay7u2xWts?5^hUP0$O+`?|U+gMW!&8=psl@t;)~wN!T6yCrIO-SLuxolYWc zg8rc-#xY4mi9!T;A3t2>z-~hw=95zo^Zwk=F-vQEEl zcPgAmc1{#G+#mx2sCS-`x5#lHJCcyV0Ppn+77Zx|XxaWNh$FBbdy%J)A(%Rfk<_R) zcJ&l7CzXZ?*fRmBD zZ2Yh^jkFb~;)oDZdod1WZV3$`>zKWFG;AebiPar``*!o2$^GOAwM>aDH0|PUo;=J@6&4kR{6up$`{HO0Nds>gq zO&R|sclPnea4QLqHG@d`q<^H$qU`K))%1)Zgl=cA8RU={=iBt0scgc?^lgu5o-B;N zfFOMvP29o1t3l(IHcl%zee0#lD`;+G{Dw!W<$MBladdt4o^2E`@(@Tcda6<$4;AnI zqVuEGcGN0gUvMfWc2-q@OvPD*AIf`yj;mwk-+@wVzJG7^+N7mx@PDizBz0*pEJ2R1 zu6vE$J$%!pSb>pv?2AH0#;1ZSD92})ssK4utz4w+pEFJD9dudd>AG4hRVDX-RN=}| zQHhu&x^%qb7mgWBSJ-}+Fb#DzvGSX8fB-eoj=df59@(^V|I;8aJ3f(xb`r9)0D%QC zG^XNq#!?EIVzHJZH-C~@GXbMo=xNU-3nw0d>Xga}uV=#R3Fmq9QJYpyH2om7kz zFpoGB$VKshtQ^sd`Q3s&4)Ga&8z8y-ED+@qM5}R(75$RgkUrBHHRw*WyOpm_yeBT! z5C`SgSnS!`p#>`KN!MdvrFT={IZA2EqQZYIv*#?7gXq#|U?<9uZCw zDx15Ad-HE*HsdNE3RD;`?uy!HRNI?YXphX^sZ%{{KI_OrIH=gcQ;F455{ir}#^pyu zik0T; z*f<3;Nmlbg%BXh&8{n6p$`(}S;Ctab4jjo`#C@bA0U2Ckw)28-|5;{V{W~x8% zPwMxhL4OG%;G~_x6I7`qBx4^j5yG^+6JnkxRMeRCIX_i^-(fAOq4*#YR`#BUG7FU= zMkW`2!L1CQXFJle4!kAx`{eZ)Dw-*A7%Q&QE!9+28D&GMHU2vAC<01k^CGarUYWST zpclsiS-VBJ;Sed{aZh{zZ*%qobzC3!vuoPl_J4Lud7FVE1q$C28ZA&5he!eGjnXO8 z%CQb#;#@_1FxMlWHA#0)6!y)6>%0$LtUGKnmv2Uo4z_u8y*8WTNu=ROfyX&+^%Jch z+UhQ?q{3Sq49%pS)$Q@|^Y8vXeAjpP^|WWEuzQ+-r*txPi3IL?vmJS7oekc|X(|sX zf`4MNV8Mhklr~sSLYk1I*6{cu=Yu`fa)ukjV?LR$LM;xVI1Ew7x;qe^Hr^lsrtS7F zIE?24HqfprYeqSiV(Eu0RC2(3zGdTzsvf2fgH#vB0_Sq8Ufx`%|H3$kJ^bC{<>?(n zLQSwpl}%vX=_H~oOFZQrM?viYj;j!G6axKchKv9}A&-T_fq~0Jp_{hweVi2x^nX$| z?tY5#4~;&5QY9<^+sANT1z`V&Z;s2-NY5uX83N8!8B0PQA~m>k!Dnkz=VlvPG?BJ^y(yk_%7*4-0oJhpH63|zxJyCs@=<0Csi?3Q57*2 z69OV-L@^N*R0t#_BnSuyi1Ppb3H+=a537Wn7axyc3izX#68!ptLA>j?q5k9i?%HILigK=p7Y6nyNM$G&5yWGkFfP*WS`9;xe`Q=py_0aYNCH?5@qDw2AV_|Y# zW>jpDwQ{C{rV?9*LV|T_Z-ib{sA5%6dTvG#TWVc8YGP`B;?IvASAUR>nO9(#oR^QB z$MXLgJ49lAwr~6OWM1KegMX?wxikH>VFZ4l!}S9i*cg7=7ZCmUDgN#8z5a>r_dTil zexnP~{E0}c@9vIloX`uv8CaGb0y#J_x)!px0&;MdmB)1zNaVi;2N$B7`3K@-CSv?Z z1v|LcV<6o!C5p9R9hP^?(%6Qc&RrWE&x}Kb(I{=Y6X2bTkMSt0eD_|J;=F$R=X25V0 zg@u{;#~CD*Xu?sf#f^${T zXvADj-64anRX9d6BkcxZoX`J+Fd@-Z9t@#jZO!mT8V37QeCzVg89&BIN~4z+fptf{ z7JA}pp{+*@gnt$axhI(m;@om*w5mHz%-;wwgwp_P{bCS^$)=y859Yz9GJ+b37u2cY zIMF@kccJG*uLyWAhAI5FkgJ~`%L`*yWZk$7X^U$%Nq(JYeQ{TKC=NYH(i^srQ+Wy_ zGsQJZ6Kw6*TGFNrM|f6GpQUz@ohpolI4Nf~3L?=sntw_tn=dU^%{+O>4sK*Y-@&TM z@pglGVSi*<{_?dFA0eF^ZH866@;RlGY%^I4B!qiV-+!qkd-<(YF@ZUD6@-)_ZLK3fQv->W$ub;pnwUYsPY=?F_F8^0XM*`gf5DT2DZ((d4l26vNccRJO$ zf;7ou&xMLTD%KjQ63i*Itm?79L7(9G8a0E=eSet?#kF36V=id>{v2s#QX!3ib?6!B z7!rBDOhE}~3mgpQie0BFXOn667hZ!;(-_}i9^$^qIE>xurr0Kpj9p7>-WW3alzkIU z{w{DXS$}Q=sbE;m_*kK9PcCfn9#SiP*;lOr#BtVrc&XB`v)8-5nk^U|#h5K;el~Hw zkbk_46-bCRVn|A|lEKUyZKX`*!VLv)L;Wpmrjv|`hu|)?Ij7ChCh_$e=lg<#I$6~7 z_*AtErTJ&UU%^Z8kdVT%qFgPBQ!7&1t$J{wZ^zFDs;FHMvapghVu$!r*rgyq*DFh98d<@rWbqA z!TotgFLLPmC8c-hhanuZ1*|X{34bx(O=nVLW4I-HrB==(B75_YbHPh!K#?#9L5?vc zFA-T`0sk)js$i~=%p9LYc{y&`vT}>I{M20(t33u9{%rmob#ug?De1|8{frgiSIyTypkih}y3x^2&?R5+J9;OS7~9t;J<$b$s~d$soJu>+gL_gudD*8X^b~5L4(c zDMIao9GTY23e2I!5wXR+t*HS#J)J9|(u1ojeUs|f{_R73Zb*52Wn^%9c4QG_-{_AX zSud&DJdP$9s}p*WW~sHbT7TbiVdI)(%QpKYnK*$%gN z=p-;7+XcLWL5%LuV1FBDf<$Bv@g5tmEYOKOeE$im4npZ>i5FME++;+qvwi!MIsC=2 zd;aw~B4wCykEU>5D1{64%Qskl&Arpb&k1cqZl$yh)p@DK2o#>a;M9?MXKOAL3ezzk z6m2zl>+P|DT>5ju9KjvbT-BgYgE|}X$J2@yh>$pC1lJXbY=2sh9lIseE>1)Mb-v-1 z^^W2+$wFXX0<*LWsn8YFaI=J>#9tZC0o3yXw_xC%R2or%QP*@$;QsR>MHVw^zkGM( zR&&28?)i?U_P(IbH{5Fm-7i1N@u|3i2yV-T^Fz5!BXdKxhtfF`qgSF}-2@Ws-C{dv zEMG?bt-qQug@53OQUt#dD7gH;;;`tyRJ`9hj)6sNbIcigmXsC?on}H)3bb{$c#6WV zMgW*VYEKF_a3pu$629?qAm!0@?)Vo6s!m5b5>*XhtkU9*5`)-UkWiU>2-LB{yK!e@ z_ugCNQMWYmhc{g#O;IhdImuf0OX;_XS?}z=Kib8)fq%7X3m1xBwbX9V=(3>x;7Mf| z4y?5YPZ$^usI`FS5vrvugFVMf**Zt?B!k7%Y}L@GNU^R9CZ{G%97a&bg@BvZRZv|o zqSt7eS(xmLq>c3JX-HqA0>}wgU*eoirkotJAFXRF!M0w5EkRV{qe8f|whGr0iFdRL zRzmxwQh%pACGDj$2_m9jJ(mnzb4}~M`+z*`nXel@rGs`Gm#Djy0ivU&)oZ34QmZ^0az|-1rQfh8daEq)Ls_rj}-|Z7%xs z`8}Hufb$_G<)liK8bSFLo=*2l)&{&?tlUL)Zhwh{oPOpDo9EL%sBEwplMJ z-H_P%Lg0VfCgQ?9AKtJ#8&1%(21){S;C1aTi*Q&dBG?KU?n%WSI`>A{17vyDSlI#> zy*u;anh&p(P-juo7#r?7Z9fFws-TYJ1kC)J1N+wy&TmNUc-vD0M2(l1v}`@6L>z8v zjem9MrV$)8kIWcW8OmTDxis)unpD{VXnJvU)LaWGdqh)?N+x?R5p z2nD)gL%FQD!;He=1tL&d5g?Rowyy%1tQd()Xj>1Y) zzZ@{sZ-^UbE*0Y58dytQq|XQK9j!UaC7r0<959V!c$MzxYBC&aoSpR>Xv2)~&%^kZ zWV~~f(&#kTe;UlJ?ukQ2O`fkafq(d^_LzxfiOW>L&+Q$arjCpc=aOD?aAr7?5vsm` zt;h9c{y344_;2Al+Q&I(eg?XxI-}pYlOu!t><4X)IN-c3v8HG~En5wJ|bm&iATo`3udsKcwMfv~emZMaj!f_i_Xa&=_dB4n}guie9Pi^g5I zdCuf$*)GEF7Rd<6$q5~~lD|)B+_cf(^Y!4m%zy()I>`SE0Is#vm;RUS z4!mOvi`at;c6)yBwhwzWF}fY?*$UWJ1wPO(L$|RO)f*f}ueA z4+mFz3OpJ_+l%=+H5l9x=igelYmj}Oai?UzOK61Oeh2z)B_b6|k9|kmdVg7^?`Z)9 ztshqhb87a9V+a(-5PxzKVC+N;0+CG3J=sCQG57^DhkHD3Uf*4V{bP>goh3!wU>PCp zt$lvDf$`)MUO}^Yafb33jVT4>R%@csj*E;Na=)WpZ>Iou+-Nwlueu)U<;2XCYlZvZ zG~i0(hbu(7%gJj#NhzE2Ah~uUnAtK02BsFD(pP`NrZ>YU8-E<+F!C!p?v0r9Gd2-3 z`fpO|o)t#ho$J`nf#~?M=CN&iom|`f5XbLs2~j(D^16pxOEja-<~qlf3wlH&W>Jm5 zX^YSDaGvjoC#@cykMW7%?3v3JFB04)QJiQP*2Y)-oZ<}{RB=b!ETr-q^!z=Z2lvIn6(#+=D+x?B`l2!2?Y{)TF5=RTs zkA{gWFL`n2yGUK%z5mGThnP^4Bk1uos8sLhRlaZpaKE?Cpi62k4U`z|LK{!;(n>E( zNjn(c2aQSMpEUfp?OGkoQCXBb7(6E9UPTjKvObUde1Fj|%qL_W$PiP5&sGWF0remY zR;@S#C^w(TMm#wKWMMJb1BG1AM_iww4f6N<7!u!pC;oAk!;8+4j*D#lgcdf?y8$gk zEbbAkJ{Ue=l1<#%3!!_qnlF|ci8PZhR*h^R4*p{nQQ{u3)B$!-KFwjCA87!c%%F7B zBbU2~+JBFsZ#&4>=z$;pr_Q>BTd#$~$}ib3K=cyXaR9Tz4(Fn=-|y0H|!ST~Kz&ooGYs{e5q$@Sjt z6W+vP{@6Vt`bIV#;EP`HT3!h_(H@EB3W})S{o~J}%obBzz~Mfn+ovEB?GmMYd1Y`f z-DIoEk=l`qWz*}w3VIj7*EWnmT>bc~KcmNcd_O;h-z1FU>ug;BHnu-$D9%g;yvnLt zeSgPlXb#4AO)`EgLqhC{a0!VPf*^uY#d=L9PAA?V1a0^=F|2!v zTPAMQt@7eQlxNgBu7CyvVb1bWm*x7KZYF1f}Y z<-VppcfQ+2w}8u)55SMW7v%;IwfI5;!GEC3phWKji_~;RgAgeeYUguA9zZzj1dX=% zu`Y`2akO}ZVZ(JhkDa1*x_n9(R-TMqZ2ESzRCI1x-Bzxqjv6mcgq#fewDJjB@-kh{ z%w)L}rRY;#)^3u=*Witpo#d~|!_DaOTk!r@=Xb^d^}xu;DPOIrP3XeQM%+UJW`DTZ zZa2pEk(hw9BzAOUQ*mnWCD26UtKl-g^2XbsmvXsfFMsnL51L(+p$x_Blyl0emo!)Z z5TSgCV%@B*75O84fx^;d2@F9-UL7xt2CxEQ(XU z^)A@346DtX0B9dC@=R2rC5_<7Vzit%2;qDO`7`wieF9dKuv5FONkw!U7)5$chJwwZ zv4A+7`I<3wk!}U)6A_ODQxn7-QHoO7$0n>*kb2NUOC0ISjpOSbueO`wZGTK1cPAhD zhgQuO%P({!GvD4*j~M0a*nCX%4SCNquXQCOtaTOc+q#^T6~PYMczfH6ku{1hk( z$*hqCbK`&XJPz%t;a#OmX@A&>wqAvZ)Z|MY)H_}orm9C1Q;alwgDI#BoE<*S5PuJD zvsTzn_1kr-aV0K<(=TtzVqKAgAy+3#hNXtMtjLDm0qQ3i9g)i$u+BqtS$jCma@AS} zX-C=lh9xKCf;3qPh(xa~+6G~IgLk-$%pSBi{vb|Z&}6A3Hp{@Uy?^y(k3iCqw^mf< zP`ef+@je@+%Ttp5_ynQJkyamN6#@5M`xq&#L&M0+(9n?qEmWK@6 zUH}c_Rj9BUyF;F727leDAx~&Z=o~ZZqTfmO9!4b~*eXT{r|jFapy9>+1e`VP6R0|J z(!xk?(}8|M+viMCowCHcc!@4(&uEhg2t?A&*s#(+nds{k%xXv~s5c-B+24Xf5XSm{6}ggiKD3BIE}U1V8v$O1i}s;#U-QrtTmW=vb=j< zsh3ndtiQ9Ln&4CMZQ1VzUZel$reTm>>D;-H6YH^&veUPZ))BsS>GxcLnnE9MR}Tf{ zzT`jt?O5lA7(n7gLw`mEua$Y&PUj5e6Kqx{JG#XT7=O}pl_zwX2WD}SH<8iDIG`38 z=sP7>FQFSq;|h@(aLr@-c($60jIhYOZxb;X!E|)`X*W7Wm9&c*O1?#hAo-qwlDp5$ z`_%+vjeI)<*DY0duaT76z>?9&9jr+s#pv=Ge9NyAO7ApMFw_8n_;#vfL$e zzxi53;ku^(fkxkRmd_qa@Pk^UZ8kzSwSK$*K7Xu+E#&R@mJ0Aq4bIy%a46s(LuaPv zV1t{_^rw5dD~kS_%42snm)Uw`PNLuZb*0@v)>#}w!_v%!Ydf{76#mj$r#|DvBN_&%aG^?>@ap^mO{Ot71*VKg#<0l{3&iUNhRV-$p%}45!LYYl+=MpqHe9pc*PuA_6NK} ziG1u5t0;#jwL?!k^xd7zrfFZIA?LS;99yQ&9hI}sl~U7;Wm~57DTz@rp|r9swtuzZ z$J`4@rCUA;2@X7O!`PO8k8BxS=T7TZeOZyG0 zce>c?TLJmsJpWLtWJ~+5&bcXZ(NaRfGidAq+bMQAL08YVj+cX`mYtVWSgHa3s?N?@ zx`hp?+JkXLKA*0NCZqO7vSI59yJDuM?)-}wjczTL$rTo@)B_s-2@=E?(d6Y`_Aw(m17j5#*mg6$*PLFTTdrtwk>wo!l- z^lZ|PtSO6ZSpRr*UU#OZu)GJ~iZ(Ek`XSU3OQ!MD8F2`5NUkV!>x1za8Sqi||mS7FebsJXp08SaX}l1xOQ{JX=H| z&4CEz+=2$o9JvObN61K|4;{!^HOG~Uq>c_M=}Nlj9QEMQD&OMlmV>%RkDc7s5_`bYjR!~oNl-zUTY>k6;CL?KH$Vc0ogRPc*Pr@%4f z5Q8FdIKo&Ufa&7Arh=v^4Bq1LlHPb|mo<(6DOk$|Bgm&v0=zd?-mWjdOHcRy@fUhs z>$~QDr|7ylCbwLd95S@$P5pWoGoSu)qip6n%*TDQW#?+1zCH=HEzakkYQeS{eUsW^&k%b`xP$U`$H$&- zbq%%!pRx-S<@^HBgemq&Frt!@_f-SBB zKd_l1Jp3C0ui6sdedM1}Pd5g_nt}kP0u-JBSJmw~mVC$u4cYegDbLQS&urEbTEsmz zRe#oT@oujfkb5@Ds+4M;%Cu~xrk%E7&!pcGf+zl?g4Q4&XpPvz<@$cnAE?nXT^K2T3Aqtt2ycy zC;geG2%o(2HOs7PFAKnD4$#$SnJ>|NnY^xmIk~UNRzZbx6Vq#qO%%F{Wv76ZM}H4* z5Y9cl`{1q%dOD1xmiRGd#~_u=%Un4hyih}R1!FN*e1iWC++LyY_oVRv8tHv(_u?ud zEh6jp#A;91-gD9Alt7Ep04gcWK_N%(m1ihubUZ{(0+)wRMZgUj5zi9ZadHruo?x4z zil-7YpQ6r@zw~>O`}gXj*$;2Kn|~aZr5=K79Cm3{MRQFe9j%W^=zy;uB8j@CEg@~V z#7V6a8_H;q)aV)GVy#KurB*(1!{s2R&%RfATQPLwEvYJKVS!}|F@l`1LLrmnar}e% z)U$m24S7}pIr11baC40$RRCtr)te0ftUhx4vN@Nmf-i4DUhXu3LPqi;RDX?51|cyO zYo>HeqN=>f|IE$!mWMONe77*BS%&JMB(GELQlqBn?J&guYp5-WKLfzR1B3|2`pa{& z3H6(7IQb1_`6a)9qYCm%fn_25=ipo9^Cf;vQX)gfb*R)&YzSKtDrpqzV01`?>3^*GFRSEk(t9Uk_`5>Ld)R_BGYGBL(1906!lQ~+q<^(Lgo*k>#AP}}%fC%p zqYmu4nUQhpd%zrB&TC`^uP8_aor%wZ7aHjgaP@d|)E1iz0wE18+&x#yoW z(TvN;Zl<*aQ`p*hWrj_^Cs@2!ak2IkXV41pG0m7JCs?}==;t`B*BIW;{Czf843kvb zg0`d|fl3Y>VSkELsn&?w;s8%kyCaeO3s+&MS`O68kCz-fwwtW=vXp;bwB3q|qV08| z$;soR-KdB+8``yfM5`H8A(%N5D886m5+QE9j$lsZkaT_=aB04DnH7eGv!m?oj zCz_9l6!uZ_Kmbr7MvhQ~3V@YlW^B&n{H>IFhqh>syizl4$^(puS>XwdWY&5)ZIR?Z z3rg<9nAxN~4=FCL+DhH3=Y9Dn@%9}Cyl>I>;Bv$#Qr*K_hoW$W=f;lYVSKhbIi~NG z!h+vnC4UO)og+p&$S#-0F)vg?&c(w$^qqKsmmHilPTsosXe2hSPLn6wG4o0*-3$pJ z;#WiM-v&7`=qnX2{a*x3rMK-(;ObX|TBcmEqOE}g5n^fQQ!w0zMHdK9ipvihJuR>z zkbV?U#9%~>JGN56MisSWu=UB+&bWVtoHLNZTYnCJwoU_FD#@BUuD!6ebjvnoU}QlH zowq=NJHCJZE8Qs`ie$)&*hbVi(&V$#rNSZxSR~Xws4*S7__i{w;%kb1HR9sMqk56G z9~jgh`I9j0LB19p8qn%fE!u6b^)EH8tDuaDO-EMJH@E(Sy}AO5R%p<9OZ)Y0fv=Q)UV9*`C= zK5)qUnUXlP6|@zU9Y6=KtWR-#1etD_1D2d{!8sT3W!0Qnu=3*9Stv`=`unxZxJqVA zB^1Fu=K{eYNKAZIJIDfO;mQXAJE;)eM}M_~Z3C<)vDXVHUK_ZGSOHlsN2GLBFk!H$ zFLn)=%kB1rH$fiPav2G}G_1EPP6g+=c{`xs)W23QY`(3Q+GxQP*nSrS2h`=pOGTv= z)0lYvV!wKAZ3(q1$V1rL5rBUAFfr~_44^Yaqk{>!+;w@uxeOlu|2T99T3w#brGM6l zuJg5&ePZxq*gE#-uK8eC{;E4;%1|-~6cBrMelUtaJz0sQYSe{>OW;r4Mp`HL3Zzf9)$Bi**s=nKz}l7l(lR*MYw(!JeTW~dek2+fVe`Fg3Vbl-5K1x zB=0p-+*vGqh+h*e@(KqPg&FSO84N4X|J88@&Z?TFm#BL>W9~?ec>)y^afB7Do$`!9 zkq>yf)ci)YrT`Vz5!2qRb?Kt?*h*#G2_^u6LscW$pB(mhNe#RfCE!Iaz<-gyP(Ayl z&wsx>dfT2$DsETk5BauSA_Y<#v~)6pE6zNUjU(0OMn^)7GTXbAm|o+&d<88I@vsv& z*LU)Cp~^$mZLqYNcoK1)9OTP~%ZcLu?V=*mu}dvB2M((Gx(O}A5*Yk(bgT1PdqE7KIH+V%SQ!k5N!U8Vb zo+wR~3P8|PLX2shFdGRPR53z7cedG}R@=SS@J{Jr-wUdA!AP0TUw`*wrCSM+FDeVc z!cG{wWV#?n?c|Hzrwbh@?#N#iRfElUVugiHv}iVeT}IvHwNT}}1#P%k`~NGC$dv19Zt})XS5^<{;wgN$6x} z-aK-9c?n5$!6MjoPJc!zvo6AhyaG~32Pt5jp0dIj;5S!DTkuV37O!J^5-O?47QQRq zk^75Jr}I*&W^M2fJMZLha}Yh>y619Chm)w5`3~92@UY6^JHMX@%V5R;Ce$T`eFVT{ zCKkFUVV6y&wF#8(%_82Qhi4d~M^=BR7g9@qb|S3=G_+pLxPJyRs*K+sTwR9$=Rz9q z;W__o$KhhzDKH+xa5m}V#;5dGge*0eb)q-2fqJ|b%5$^aJ$9m~y$r_B{XSoyJBD52 z1c&w&JfKTjRc^O@7Q4r`7jVGmeO_u`Ur?C9&hl~D!L#=vj?f`@N&t3{KbpUYsT8`Ugpdn9*CJgY75t?)yQmlWQ6ueMt%Ot!qANICZYr`F zgQwcY`3*bLui3XBP2@GFY!iU#rcYAp3pZKH1`@17_0O3yTaWA`-0ddfH%b8A7t0qG z{RQf+hD8g5x#cxbS*kh^*yC`PXq

Kd?e=~%fiUR}BOG>H}cg@L3%+RC-+gh=-zx1K3C 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 Date: Mon, 5 Mar 2018 14:51:20 +0000 Subject: [PATCH 50/52] #43691: DiffNotes not counted by ContributionsCalendar --- ...re--43691-count-diff-note-calendar-activity.yml | 5 +++++ lib/gitlab/contributions_calendar.rb | 2 +- spec/lib/gitlab/contributions_calendar_spec.rb | 14 ++++++++++---- 3 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/feature--43691-count-diff-note-calendar-activity.yml diff --git a/changelogs/unreleased/feature--43691-count-diff-note-calendar-activity.yml b/changelogs/unreleased/feature--43691-count-diff-note-calendar-activity.yml new file mode 100644 index 00000000000..768686aeda8 --- /dev/null +++ b/changelogs/unreleased/feature--43691-count-diff-note-calendar-activity.yml @@ -0,0 +1,5 @@ +--- +title: Count comments on diffs as contributions for the contributions calendar +merge_request: 17418 +author: Riccardo Padovani +type: fixed diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb index 9576d5a3fd8..02d3763514e 100644 --- a/lib/gitlab/contributions_calendar.rb +++ b/lib/gitlab/contributions_calendar.rb @@ -23,7 +23,7 @@ module Gitlab mr_events = event_counts(date_from, :merge_requests) .having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest") note_events = event_counts(date_from, :merge_requests) - .having(action: [Event::COMMENTED], target_type: "Note") + .having(action: [Event::COMMENTED], target_type: %w(Note DiffNote)) union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events, note_events]) events = Event.find_by_sql(union.to_sql).map(&:attributes) diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb index 49a179ba875..167876ca158 100644 --- a/spec/lib/gitlab/contributions_calendar_spec.rb +++ b/spec/lib/gitlab/contributions_calendar_spec.rb @@ -11,7 +11,7 @@ describe Gitlab::ContributionsCalendar do end let(:public_project) do - create(:project, :public) do |project| + create(:project, :public, :repository) do |project| create(:project_member, user: contributor, project: project) end end @@ -40,13 +40,13 @@ describe Gitlab::ContributionsCalendar do described_class.new(contributor, current_user) end - def create_event(project, day, hour = 0) + def create_event(project, day, hour = 0, action = Event::CREATED, target_symbol = :issue) @targets ||= {} - @targets[project] ||= create(:issue, project: project, author: contributor) + @targets[project] ||= create(target_symbol, project: project, author: contributor) Event.create!( project: project, - action: Event::CREATED, + action: action, target: @targets[project], author: contributor, created_at: DateTime.new(day.year, day.month, day.day, hour) @@ -71,6 +71,12 @@ describe Gitlab::ContributionsCalendar do expect(calendar(contributor).activity_dates[today]).to eq(2) end + it "counts the diff notes on merge request" do + create_event(public_project, today, 0, Event::COMMENTED, :diff_note_on_merge_request) + + expect(calendar(contributor).activity_dates[today]).to eq(1) + end + context "when events fall under different dates depending on the time zone" do before do create_event(public_project, today, 1) From 2cc43aaaf3162db8c584df3bb9d1a42d92084fae Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Fri, 2 Mar 2018 21:04:32 +0100 Subject: [PATCH 51/52] Keep a commit around if its sha is present Closes gitaly#1054 --- app/models/repository.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 242d9d5f125..1a14afb951a 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -253,7 +253,7 @@ class Repository # branches or tags, but we want to keep some of these commits around, for # example if they have comments or CI builds. def keep_around(sha) - return unless sha && commit_by(oid: sha) + return unless sha.present? && commit_by(oid: sha) return if kept_around?(sha) From 8fe880dc064e0e6cd10f7176ade7c312cfb37b90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Javier=20L=C3=B3pez?= Date: Mon, 5 Mar 2018 17:51:40 +0000 Subject: [PATCH 52/52] Projects and groups badges API --- app/models/badge.rb | 51 ++ app/models/badges/group_badge.rb | 5 + app/models/badges/project_badge.rb | 15 + app/models/group.rb | 2 + app/models/project.rb | 13 + app/services/badges/base_service.rb | 11 + app/services/badges/build_service.rb | 12 + app/services/badges/create_service.rb | 10 + app/services/badges/update_service.rb | 12 + app/validators/url_placeholder_validator.rb | 32 + app/views/projects/_home_panel.html.haml | 6 + .../fj-41174-projects-groups-badges-api.yml | 5 + config/application.rb | 1 + db/migrate/20180214093516_create_badges.rb | 17 + db/schema.rb | 15 + doc/api/README.md | 2 + doc/api/group_badges.md | 191 +++++ doc/api/groups.md | 4 + doc/api/project_badges.md | 188 +++++ doc/api/projects.md | 4 + lib/api/api.rb | 1 + lib/api/badges.rb | 134 +++ lib/api/entities.rb | 18 + lib/api/helpers/badges_helpers.rb | 28 + lib/gitlab/import_export/import_export.yml | 5 + lib/gitlab/import_export/relation_factory.rb | 3 +- lib/gitlab/string_placeholder_replacer.rb | 27 + spec/factories/badge.rb | 14 + spec/lib/gitlab/import_export/all_models.yml | 3 + spec/lib/gitlab/import_export/project.json | 767 +++++------------- .../project_tree_restorer_spec.rb | 4 + .../import_export/project_tree_saver_spec.rb | 7 + .../import_export/safe_model_attributes.yml | 9 + .../string_placeholder_replacer_spec.rb | 38 + spec/models/badge_spec.rb | 94 +++ spec/models/badges/group_badge_spec.rb | 11 + spec/models/badges/project_badge_spec.rb | 43 + spec/models/group_spec.rb | 1 + spec/models/project_spec.rb | 33 + spec/requests/api/badges_spec.rb | 367 +++++++++ .../url_placeholder_validator_spec.rb | 39 + spec/validators/url_validator_spec.rb | 46 ++ .../projects/_home_panel.html.haml_spec.rb | 54 +- 43 files changed, 1793 insertions(+), 549 deletions(-) create mode 100644 app/models/badge.rb create mode 100644 app/models/badges/group_badge.rb create mode 100644 app/models/badges/project_badge.rb create mode 100644 app/services/badges/base_service.rb create mode 100644 app/services/badges/build_service.rb create mode 100644 app/services/badges/create_service.rb create mode 100644 app/services/badges/update_service.rb create mode 100644 app/validators/url_placeholder_validator.rb create mode 100644 changelogs/unreleased/fj-41174-projects-groups-badges-api.yml create mode 100644 db/migrate/20180214093516_create_badges.rb create mode 100644 doc/api/group_badges.md create mode 100644 doc/api/project_badges.md create mode 100644 lib/api/badges.rb create mode 100644 lib/api/helpers/badges_helpers.rb create mode 100644 lib/gitlab/string_placeholder_replacer.rb create mode 100644 spec/factories/badge.rb create mode 100644 spec/lib/gitlab/string_placeholder_replacer_spec.rb create mode 100644 spec/models/badge_spec.rb create mode 100644 spec/models/badges/group_badge_spec.rb create mode 100644 spec/models/badges/project_badge_spec.rb create mode 100644 spec/requests/api/badges_spec.rb create mode 100644 spec/validators/url_placeholder_validator_spec.rb create mode 100644 spec/validators/url_validator_spec.rb diff --git a/app/models/badge.rb b/app/models/badge.rb new file mode 100644 index 00000000000..f7e10c2ebfc --- /dev/null +++ b/app/models/badge.rb @@ -0,0 +1,51 @@ +class Badge < ActiveRecord::Base + # This structure sets the placeholders that the urls + # can have. This hash also sets which action to ask when + # the placeholder is found. + PLACEHOLDERS = { + 'project_path' => :full_path, + 'project_id' => :id, + 'default_branch' => :default_branch, + 'commit_sha' => ->(project) { project.commit&.sha } + }.freeze + + # This regex is built dynamically using the keys from the PLACEHOLDER struct. + # So, we can easily add new placeholder just by modifying the PLACEHOLDER hash. + # This regex will build the new PLACEHOLDER_REGEX with the new information + PLACEHOLDERS_REGEX = /(#{PLACEHOLDERS.keys.join('|')})/.freeze + + default_scope { order_created_at_asc } + + scope :order_created_at_asc, -> { reorder(created_at: :asc) } + + validates :link_url, :image_url, url_placeholder: { protocols: %w(http https), placeholder_regex: PLACEHOLDERS_REGEX } + validates :type, presence: true + + def rendered_link_url(project = nil) + build_rendered_url(link_url, project) + end + + def rendered_image_url(project = nil) + build_rendered_url(image_url, project) + end + + private + + def build_rendered_url(url, project = nil) + return url unless valid? && project + + Gitlab::StringPlaceholderReplacer.replace_string_placeholders(url, PLACEHOLDERS_REGEX) do |arg| + replace_placeholder_action(PLACEHOLDERS[arg], project) + end + end + + # The action param represents the :symbol or Proc to call in order + # to retrieve the return value from the project. + # This method checks if it is a Proc and use the call method, and if it is + # a symbol just send the action + def replace_placeholder_action(action, project) + return unless project + + action.is_a?(Proc) ? action.call(project) : project.public_send(action) # rubocop:disable GitlabSecurity/PublicSend + end +end diff --git a/app/models/badges/group_badge.rb b/app/models/badges/group_badge.rb new file mode 100644 index 00000000000..f4b2bdecdcc --- /dev/null +++ b/app/models/badges/group_badge.rb @@ -0,0 +1,5 @@ +class GroupBadge < Badge + belongs_to :group + + validates :group, presence: true +end diff --git a/app/models/badges/project_badge.rb b/app/models/badges/project_badge.rb new file mode 100644 index 00000000000..3945b376052 --- /dev/null +++ b/app/models/badges/project_badge.rb @@ -0,0 +1,15 @@ +class ProjectBadge < Badge + belongs_to :project + + validates :project, presence: true + + def rendered_link_url(project = nil) + project ||= self.project + super + end + + def rendered_image_url(project = nil) + project ||= self.project + super + end +end diff --git a/app/models/group.rb b/app/models/group.rb index 75bf013ecd2..201505c3d3c 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -31,6 +31,8 @@ class Group < Namespace has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :badges, class_name: 'GroupBadge' + accepts_nested_attributes_for :variables, allow_destroy: true validate :visibility_level_allowed_by_projects diff --git a/app/models/project.rb b/app/models/project.rb index 5b1f8b2658b..a11b1e4f554 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -221,6 +221,8 @@ class Project < ActiveRecord::Base has_one :auto_devops, class_name: 'ProjectAutoDevops' has_many :custom_attributes, class_name: 'ProjectCustomAttribute' + has_many :project_badges, class_name: 'ProjectBadge' + accepts_nested_attributes_for :variables, allow_destroy: true accepts_nested_attributes_for :project_feature, update_only: true accepts_nested_attributes_for :import_data @@ -1766,6 +1768,17 @@ class Project < ActiveRecord::Base .set(import_jid, StuckImportJobsWorker::IMPORT_JOBS_EXPIRATION) end + def badges + return project_badges unless group + + group_badges_rel = GroupBadge.where(group: group.self_and_ancestors) + + union = Gitlab::SQL::Union.new([project_badges.select(:id), + group_badges_rel.select(:id)]) + + Badge.where("id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection + end + private def storage diff --git a/app/services/badges/base_service.rb b/app/services/badges/base_service.rb new file mode 100644 index 00000000000..4f87426bd38 --- /dev/null +++ b/app/services/badges/base_service.rb @@ -0,0 +1,11 @@ +module Badges + class BaseService + protected + + attr_accessor :params + + def initialize(params = {}) + @params = params.dup + end + end +end diff --git a/app/services/badges/build_service.rb b/app/services/badges/build_service.rb new file mode 100644 index 00000000000..6267e571838 --- /dev/null +++ b/app/services/badges/build_service.rb @@ -0,0 +1,12 @@ +module Badges + class BuildService < Badges::BaseService + # returns the created badge + def execute(source) + if source.is_a?(Group) + GroupBadge.new(params.merge(group: source)) + else + ProjectBadge.new(params.merge(project: source)) + end + end + end +end diff --git a/app/services/badges/create_service.rb b/app/services/badges/create_service.rb new file mode 100644 index 00000000000..aafb87f7dcd --- /dev/null +++ b/app/services/badges/create_service.rb @@ -0,0 +1,10 @@ +module Badges + class CreateService < Badges::BaseService + # returns the created badge + def execute(source) + badge = Badges::BuildService.new(params).execute(source) + + badge.tap { |b| b.save } + end + end +end diff --git a/app/services/badges/update_service.rb b/app/services/badges/update_service.rb new file mode 100644 index 00000000000..7ca84b5df31 --- /dev/null +++ b/app/services/badges/update_service.rb @@ -0,0 +1,12 @@ +module Badges + class UpdateService < Badges::BaseService + # returns the updated badge + def execute(badge) + if params.present? + badge.update_attributes(params) + end + + badge + end + end +end diff --git a/app/validators/url_placeholder_validator.rb b/app/validators/url_placeholder_validator.rb new file mode 100644 index 00000000000..dd681218b6b --- /dev/null +++ b/app/validators/url_placeholder_validator.rb @@ -0,0 +1,32 @@ +# UrlValidator +# +# Custom validator for URLs. +# +# By default, only URLs for the HTTP(S) protocols will be considered valid. +# Provide a `:protocols` option to configure accepted protocols. +# +# Also, this validator can help you validate urls with placeholders inside. +# Usually, if you have a url like 'http://www.example.com/%{project_path}' the +# URI parser will reject that URL format. Provide a `:placeholder_regex` option +# to configure accepted placeholders. +# +# Example: +# +# class User < ActiveRecord::Base +# validates :personal_url, url: true +# +# validates :ftp_url, url: { protocols: %w(ftp) } +# +# validates :git_url, url: { protocols: %w(http https ssh git) } +# +# validates :placeholder_url, url: { placeholder_regex: /(project_path|project_id|default_branch)/ } +# end +# +class UrlPlaceholderValidator < UrlValidator + def validate_each(record, attribute, value) + placeholder_regex = self.options[:placeholder_regex] + value = value.gsub(/%{#{placeholder_regex}}/, 'foo') if placeholder_regex && value + + super(record, attribute, value) + end +end diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index b565f14747a..a2ecfddb163 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -23,6 +23,12 @@ - deleted_message = s_('ForkedFromProjectPath|Forked from %{project_name} (deleted)') = deleted_message % { project_name: fork_source_name(@project) } + .project-badges + - @project.badges.each do |badge| + - badge_link_url = badge.rendered_link_url(@project) + %a{ href: badge_link_url, target: '_blank', rel: 'noopener noreferrer' } + %img{ src: badge.rendered_image_url(@project), alt: badge_link_url } + .project-repo-buttons .count-buttons = render 'projects/buttons/star' diff --git a/changelogs/unreleased/fj-41174-projects-groups-badges-api.yml b/changelogs/unreleased/fj-41174-projects-groups-badges-api.yml new file mode 100644 index 00000000000..7cb12e26332 --- /dev/null +++ b/changelogs/unreleased/fj-41174-projects-groups-badges-api.yml @@ -0,0 +1,5 @@ +--- +title: Implemented badge API endpoints +merge_request: 17082 +author: +type: added diff --git a/config/application.rb b/config/application.rb index 918bd4d57cf..74fe3e439ed 100644 --- a/config/application.rb +++ b/config/application.rb @@ -26,6 +26,7 @@ module Gitlab # This is a nice reference article on autoloading/eager loading: # http://blog.arkency.com/2014/11/dont-forget-about-eager-load-when-extending-autoload config.eager_load_paths.push(*%W[#{config.root}/lib + #{config.root}/app/models/badges #{config.root}/app/models/hooks #{config.root}/app/models/members #{config.root}/app/models/project_services diff --git a/db/migrate/20180214093516_create_badges.rb b/db/migrate/20180214093516_create_badges.rb new file mode 100644 index 00000000000..6559f834484 --- /dev/null +++ b/db/migrate/20180214093516_create_badges.rb @@ -0,0 +1,17 @@ +class CreateBadges < ActiveRecord::Migration + DOWNTIME = false + + def change + create_table :badges do |t| + t.string :link_url, null: false + t.string :image_url, null: false + t.references :project, index: true, foreign_key: { on_delete: :cascade }, null: true + t.integer :group_id, index: true, null: true + t.string :type, null: false + + t.timestamps_with_timezone null: false + end + + add_foreign_key :badges, :namespaces, column: :group_id, on_delete: :cascade + end +end diff --git a/db/schema.rb b/db/schema.rb index 4937fbd3df1..9e117440ed2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -183,6 +183,19 @@ ActiveRecord::Schema.define(version: 20180304204842) do add_index "award_emoji", ["awardable_type", "awardable_id"], name: "index_award_emoji_on_awardable_type_and_awardable_id", using: :btree add_index "award_emoji", ["user_id", "name"], name: "index_award_emoji_on_user_id_and_name", using: :btree + create_table "badges", force: :cascade do |t| + t.string "link_url", null: false + t.string "image_url", null: false + t.integer "project_id" + t.integer "group_id" + t.string "type", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + end + + add_index "badges", ["group_id"], name: "index_badges_on_group_id", using: :btree + add_index "badges", ["project_id"], name: "index_badges_on_project_id", using: :btree + create_table "boards", force: :cascade do |t| t.integer "project_id", null: false t.datetime "created_at", null: false @@ -1969,6 +1982,8 @@ ActiveRecord::Schema.define(version: 20180304204842) do add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree add_index "web_hooks", ["type"], name: "index_web_hooks_on_type", using: :btree + add_foreign_key "badges", "namespaces", column: "group_id", on_delete: :cascade + add_foreign_key "badges", "projects", on_delete: :cascade add_foreign_key "boards", "projects", name: "fk_f15266b5f9", on_delete: :cascade add_foreign_key "chat_teams", "namespaces", on_delete: :cascade add_foreign_key "ci_build_trace_section_names", "projects", on_delete: :cascade diff --git a/doc/api/README.md b/doc/api/README.md index b193ef4ab7f..53f1a70c1aa 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -24,6 +24,7 @@ following locations: - [GitLab CI Config templates](templates/gitlab_ci_ymls.md) - [Groups](groups.md) - [Group Access Requests](access_requests.md) +- [Group Badges](group_badges.md) - [Group Members](members.md) - [Issues](issues.md) - [Issue Boards](boards.md) @@ -43,6 +44,7 @@ following locations: - [Pipeline Schedules](pipeline_schedules.md) - [Projects](projects.md) including setting Webhooks - [Project Access Requests](access_requests.md) +- [Project Badges](project_badges.md) - [Project import/export](project_import_export.md) - [Project Members](members.md) - [Project Snippets](project_snippets.md) diff --git a/doc/api/group_badges.md b/doc/api/group_badges.md new file mode 100644 index 00000000000..3e0683f378d --- /dev/null +++ b/doc/api/group_badges.md @@ -0,0 +1,191 @@ +# Group badges API + +## Placeholder tokens + +Badges support placeholders that will be replaced in real time in both the link and image URL. The allowed placeholders are: + +- **%{project_path}**: will be replaced by the project path. +- **%{project_id}**: will be replaced by the project id. +- **%{default_branch}**: will be replaced by the project default branch. +- **%{commit_sha}**: will be replaced by the last project's commit sha. + +Because these enpoints aren't inside a project's context, the information used to replace the placeholders will be +from the first group's project by creation date. If the group hasn't got any project the original URL with the placeholders will be returned. + +## List all badges of a group + +Gets a list of a group's badges. + +``` +GET /groups/:id/badges +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges +``` + +Example response: + +```json +[ + { + "id": 1, + "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}", + "image_url": "https://shields.io/my/badge", + "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master", + "rendered_image_url": "https://shields.io/my/badge", + "kind": "group" + }, + { + "id": 2, + "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}", + "image_url": "https://shields.io/my/badge", + "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master", + "rendered_image_url": "https://shields.io/my/badge", + "kind": "group" + }, +] +``` + +## Get a badge of a group + +Gets a badge of a group. + +``` +GET /groups/:id/badges/:badge_id +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `badge_id` | integer | yes | The badge ID | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id +``` + +Example response: + +```json +{ + "id": 1, + "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}", + "image_url": "https://shields.io/my/badge", + "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master", + "rendered_image_url": "https://shields.io/my/badge", + "kind": "group" +} +``` + +## Add a badge to a group + +Adds a badge to a group. + +``` +POST /groups/:id/badges +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `link_url` | string | yes | URL of the badge link | +| `image_url` | string | yes | URL of the badge image | + +```bash +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "link_url=https://gitlab.com/gitlab-org/gitlab-ce/commits/master&image_url=https://shields.io/my/badge1&position=0" https://gitlab.example.com/api/v4/groups/:id/badges +``` + +Example response: + +```json +{ + "id": 1, + "link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master", + "image_url": "https://shields.io/my/badge1", + "rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master", + "rendered_image_url": "https://shields.io/my/badge1", + "kind": "group" +} +``` + +## Edit a badge of a group + +Updates a badge of a group. + +``` +PUT /groups/:id/badges/:badge_id +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `badge_id` | integer | yes | The badge ID | +| `link_url` | string | no | URL of the badge link | +| `image_url` | string | no | URL of the badge image | + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id +``` + +Example response: + +```json +{ + "id": 1, + "link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master", + "image_url": "https://shields.io/my/badge", + "rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master", + "rendered_image_url": "https://shields.io/my/badge", + "kind": "group" +} +``` + +## Remove a badge from a group + +Removes a badge from a group. + +``` +DELETE /groups/:id/badges/:badge_id +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `badge_id` | integer | yes | The badge ID | + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/:badge_id +``` + +## Preview a badge from a group + +Returns how the `link_url` and `image_url` final URLs would be after resolving the placeholder interpolation. + +``` +GET /groups/:id/badges/render +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | +| `link_url` | string | yes | URL of the badge link| +| `image_url` | string | yes | URL of the badge image | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/groups/:id/badges/render?link_url=http%3A%2F%2Fexample.com%2Fci_status.svg%3Fproject%3D%25%7Bproject_path%7D%26ref%3D%25%7Bdefault_branch%7D&image_url=https%3A%2F%2Fshields.io%2Fmy%2Fbadge +``` + +Example response: + +```json +{ + "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}", + "image_url": "https://shields.io/my/badge", + "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master", + "rendered_image_url": "https://shields.io/my/badge", +} +``` diff --git a/doc/api/groups.md b/doc/api/groups.md index f50558b58a6..1aed8aac64e 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -525,3 +525,7 @@ And to switch pages add: ``` [ce-15142]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15142 + +## Group badges + +Read more in the [Group Badges](group_badges.md) documentation. diff --git a/doc/api/project_badges.md b/doc/api/project_badges.md new file mode 100644 index 00000000000..3f6e348b5b4 --- /dev/null +++ b/doc/api/project_badges.md @@ -0,0 +1,188 @@ +# Project badges API + +## Placeholder tokens + +Badges support placeholders that will be replaced in real time in both the link and image URL. The allowed placeholders are: + +- **%{project_path}**: will be replaced by the project path. +- **%{project_id}**: will be replaced by the project id. +- **%{default_branch}**: will be replaced by the project default branch. +- **%{commit_sha}**: will be replaced by the last project's commit sha. + +## List all badges of a project + +Gets a list of a project's badges and its group badges. + +``` +GET /projects/:id/badges +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges +``` + +Example response: + +```json +[ + { + "id": 1, + "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}", + "image_url": "https://shields.io/my/badge", + "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master", + "rendered_image_url": "https://shields.io/my/badge", + "kind": "project" + }, + { + "id": 2, + "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}", + "image_url": "https://shields.io/my/badge", + "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master", + "rendered_image_url": "https://shields.io/my/badge", + "kind": "group" + }, +] +``` + +## Get a badge of a project + +Gets a badge of a project. + +``` +GET /projects/:id/badges/:badge_id +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `badge_id` | integer | yes | The badge ID | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id +``` + +Example response: + +```json +{ + "id": 1, + "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}", + "image_url": "https://shields.io/my/badge", + "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master", + "rendered_image_url": "https://shields.io/my/badge", + "kind": "project" +} +``` + +## Add a badge to a project + +Adds a badge to a project. + +``` +POST /projects/:id/badges +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project ](README.md#namespaced-path-encoding) owned by the authenticated user | +| `link_url` | string | yes | URL of the badge link | +| `image_url` | string | yes | URL of the badge image | + +```bash +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "link_url=https://gitlab.com/gitlab-org/gitlab-ce/commits/master&image_url=https://shields.io/my/badge1&position=0" https://gitlab.example.com/api/v4/projects/:id/badges +``` + +Example response: + +```json +{ + "id": 1, + "link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master", + "image_url": "https://shields.io/my/badge1", + "rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master", + "rendered_image_url": "https://shields.io/my/badge1", + "kind": "project" +} +``` + +## Edit a badge of a project + +Updates a badge of a project. + +``` +PUT /projects/:id/badges/:badge_id +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `badge_id` | integer | yes | The badge ID | +| `link_url` | string | no | URL of the badge link | +| `image_url` | string | no | URL of the badge image | + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id +``` + +Example response: + +```json +{ + "id": 1, + "link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master", + "image_url": "https://shields.io/my/badge", + "rendered_link_url": "https://gitlab.com/gitlab-org/gitlab-ce/commits/master", + "rendered_image_url": "https://shields.io/my/badge", + "kind": "project" +} +``` + +## Remove a badge from a project + +Removes a badge from a project. Only project's badges will be removed by using this endpoint. + +``` +DELETE /projects/:id/badges/:badge_id +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `badge_id` | integer | yes | The badge ID | + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/:badge_id +``` + +## Preview a badge from a project + +Returns how the `link_url` and `image_url` final URLs would be after resolving the placeholder interpolation. + +``` +GET /projects/:id/badges/render +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `link_url` | string | yes | URL of the badge link| +| `image_url` | string | yes | URL of the badge image | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/badges/render?link_url=http%3A%2F%2Fexample.com%2Fci_status.svg%3Fproject%3D%25%7Bproject_path%7D%26ref%3D%25%7Bdefault_branch%7D&image_url=https%3A%2F%2Fshields.io%2Fmy%2Fbadge +``` + +Example response: + +```json +{ + "link_url": "http://example.com/ci_status.svg?project=%{project_path}&ref=%{default_branch}", + "image_url": "https://shields.io/my/badge", + "rendered_link_url": "http://example.com/ci_status.svg?project=example-org/example-project&ref=master", + "rendered_image_url": "https://shields.io/my/badge", +} +``` diff --git a/doc/api/projects.md b/doc/api/projects.md index b6442cfac22..271ee91dc72 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1340,3 +1340,7 @@ Read more in the [Project import/export](project_import_export.md) documentation ## Project members Read more in the [Project members](members.md) documentation. + +## Project badges + +Read more in the [Project Badges](project_badges.md) documentation. diff --git a/lib/api/api.rb b/lib/api/api.rb index 754549f72f0..b1b247b70b9 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -108,6 +108,7 @@ module API mount ::API::AccessRequests mount ::API::Applications mount ::API::AwardEmoji + mount ::API::Badges mount ::API::Boards mount ::API::Branches mount ::API::BroadcastMessages diff --git a/lib/api/badges.rb b/lib/api/badges.rb new file mode 100644 index 00000000000..334948b2995 --- /dev/null +++ b/lib/api/badges.rb @@ -0,0 +1,134 @@ +module API + class Badges < Grape::API + include PaginationParams + + before { authenticate_non_get! } + + helpers ::API::Helpers::BadgesHelpers + + helpers do + def find_source_if_admin(source_type) + source = find_source(source_type, params[:id]) + + authorize_admin_source!(source_type, source) + + source + end + end + + %w[group project].each do |source_type| + params do + requires :id, type: String, desc: "The ID of a #{source_type}" + end + resource source_type.pluralize, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do + desc "Gets a list of #{source_type} badges viewable by the authenticated user." do + detail 'This feature was introduced in GitLab 10.6.' + success Entities::Badge + end + params do + use :pagination + end + get ":id/badges" do + source = find_source(source_type, params[:id]) + + present_badges(source, paginate(source.badges)) + end + + desc "Preview a badge from a #{source_type}." do + detail 'This feature was introduced in GitLab 10.6.' + success Entities::BasicBadgeDetails + end + params do + requires :link_url, type: String, desc: 'URL of the badge link' + requires :image_url, type: String, desc: 'URL of the badge image' + end + get ":id/badges/render" do + authenticate! + + source = find_source_if_admin(source_type) + + badge = ::Badges::BuildService.new(declared_params(include_missing: false)) + .execute(source) + + if badge.valid? + present_badges(source, badge, with: Entities::BasicBadgeDetails) + else + render_validation_error!(badge) + end + end + + desc "Gets a badge of a #{source_type}." do + detail 'This feature was introduced in GitLab 10.6.' + success Entities::Badge + end + params do + requires :badge_id, type: Integer, desc: 'The badge ID' + end + get ":id/badges/:badge_id" do + source = find_source(source_type, params[:id]) + badge = find_badge(source) + + present_badges(source, badge) + end + + desc "Adds a badge to a #{source_type}." do + detail 'This feature was introduced in GitLab 10.6.' + success Entities::Badge + end + params do + requires :link_url, type: String, desc: 'URL of the badge link' + requires :image_url, type: String, desc: 'URL of the badge image' + end + post ":id/badges" do + source = find_source_if_admin(source_type) + + badge = ::Badges::CreateService.new(declared_params(include_missing: false)).execute(source) + + if badge.persisted? + present_badges(source, badge) + else + render_validation_error!(badge) + end + end + + desc "Updates a badge of a #{source_type}." do + detail 'This feature was introduced in GitLab 10.6.' + success Entities::Badge + end + params do + optional :link_url, type: String, desc: 'URL of the badge link' + optional :image_url, type: String, desc: 'URL of the badge image' + end + put ":id/badges/:badge_id" do + source = find_source_if_admin(source_type) + + badge = ::Badges::UpdateService.new(declared_params(include_missing: false)) + .execute(find_badge(source)) + + if badge.valid? + present_badges(source, badge) + else + render_validation_error!(badge) + end + end + + desc 'Removes a badge from a project or group.' do + detail 'This feature was introduced in GitLab 10.6.' + end + params do + requires :badge_id, type: Integer, desc: 'The badge ID' + end + delete ":id/badges/:badge_id" do + source = find_source_if_admin(source_type) + badge = find_badge(source) + + if badge.is_a?(GroupBadge) && source.is_a?(Project) + error!('To delete a Group badge please use the Group endpoint', 403) + end + + destroy_conditionally!(badge) + end + end + end + end +end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 0c8ec7dd5f5..e5bcbface6b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1235,5 +1235,23 @@ module API expose :startline expose :project_id end + + class BasicBadgeDetails < Grape::Entity + expose :link_url + expose :image_url + expose :rendered_link_url do |badge, options| + badge.rendered_link_url(options.fetch(:project, nil)) + end + expose :rendered_image_url do |badge, options| + badge.rendered_image_url(options.fetch(:project, nil)) + end + end + + class Badge < BasicBadgeDetails + expose :id + expose :kind do |badge| + badge.type == 'ProjectBadge' ? 'project' : 'group' + end + end end end diff --git a/lib/api/helpers/badges_helpers.rb b/lib/api/helpers/badges_helpers.rb new file mode 100644 index 00000000000..1f8afbf3c90 --- /dev/null +++ b/lib/api/helpers/badges_helpers.rb @@ -0,0 +1,28 @@ +module API + module Helpers + module BadgesHelpers + include ::API::Helpers::MembersHelpers + + def find_badge(source) + source.badges.find(params[:badge_id]) + end + + def present_badges(source, records, options = {}) + entity_type = options[:with] || Entities::Badge + badge_params = badge_source_params(source).merge(with: entity_type) + + present records, badge_params + end + + def badge_source_params(source) + project = if source.is_a?(Project) + source + else + GroupProjectsFinder.new(group: source, current_user: current_user).execute.first + end + + { project: project } + end + end + end +end diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 9f404003125..4bdd01f5e94 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -65,6 +65,7 @@ project_tree: - :create_access_levels - :project_feature - :custom_attributes + - :project_badges # Only include the following attributes for the models specified. included_attributes: @@ -125,6 +126,8 @@ excluded_attributes: - :when push_event_payload: - :event_id + project_badges: + - :group_id methods: labels: @@ -147,3 +150,5 @@ methods: - :action push_event_payload: - :action + project_badges: + - :type diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 759833a5ee5..cf6b7e306dd 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -16,7 +16,8 @@ module Gitlab priorities: :label_priorities, auto_devops: :project_auto_devops, label: :project_label, - custom_attributes: 'ProjectCustomAttribute' }.freeze + custom_attributes: 'ProjectCustomAttribute', + project_badges: 'Badge' }.freeze USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id].freeze diff --git a/lib/gitlab/string_placeholder_replacer.rb b/lib/gitlab/string_placeholder_replacer.rb new file mode 100644 index 00000000000..9a2219b7d77 --- /dev/null +++ b/lib/gitlab/string_placeholder_replacer.rb @@ -0,0 +1,27 @@ +module Gitlab + class StringPlaceholderReplacer + # This method accepts the following paras + # - string: the string to be analyzed + # - placeholder_regex: i.e. /%{project_path|project_id|default_branch|commit_sha}/ + # - block: this block will be called with each placeholder found in the string using + # the placeholder regex. If the result of the block is nil, the original + # placeholder will be returned. + + def self.replace_string_placeholders(string, placeholder_regex = nil, &block) + return string if string.blank? || placeholder_regex.blank? || !block_given? + + replace_placeholders(string, placeholder_regex, &block) + end + + class << self + private + + # If the result of the block is nil, then the placeholder is returned + def replace_placeholders(string, placeholder_regex, &block) + string.gsub(/%{(#{placeholder_regex})}/) do |arg| + yield($~[1]) || arg + end + end + end + end +end diff --git a/spec/factories/badge.rb b/spec/factories/badge.rb new file mode 100644 index 00000000000..b87ece946cb --- /dev/null +++ b/spec/factories/badge.rb @@ -0,0 +1,14 @@ +FactoryBot.define do + trait :base_badge do + link_url { generate(:url) } + image_url { generate(:url) } + end + + factory :project_badge, traits: [:base_badge], class: ProjectBadge do + project + end + + factory :group_badge, aliases: [:badge], traits: [:base_badge], class: GroupBadge do + group + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 41a55027f4d..b20cc34dd5c 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -277,6 +277,7 @@ project: - fork_network - custom_attributes - lfs_file_locks +- project_badges award_emoji: - awardable - user @@ -293,3 +294,5 @@ issue_assignees: - assignee lfs_file_locks: - user +project_badges: +- project diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index b6c1f0c81cb..62ef93f847a 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -14,8 +14,7 @@ "template": false, "description": "", "type": "ProjectLabel", - "priorities": [ - ] + "priorities": [] }, { "id": 3, @@ -160,9 +159,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 352, @@ -184,9 +181,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 353, @@ -208,9 +203,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 354, @@ -232,9 +225,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 355, @@ -256,9 +247,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 356, @@ -280,9 +269,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 357, @@ -304,9 +291,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 358, @@ -328,9 +313,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ] }, @@ -395,9 +378,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 360, @@ -419,9 +400,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 361, @@ -443,9 +422,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 362, @@ -467,9 +444,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 363, @@ -491,9 +466,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 364, @@ -515,9 +488,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 365, @@ -539,9 +510,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 366, @@ -563,9 +532,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ] }, @@ -628,9 +595,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 368, @@ -652,9 +617,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 369, @@ -676,9 +639,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 370, @@ -700,9 +661,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 371, @@ -724,9 +683,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 372, @@ -748,9 +705,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 373, @@ -772,9 +727,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 374, @@ -796,9 +749,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ] }, @@ -840,9 +791,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 376, @@ -864,9 +813,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 377, @@ -888,9 +835,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 378, @@ -912,9 +857,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 379, @@ -936,9 +879,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 380, @@ -960,9 +901,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 381, @@ -984,9 +923,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 382, @@ -1008,9 +945,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ] }, @@ -1052,9 +987,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 384, @@ -1076,9 +1009,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 385, @@ -1100,9 +1031,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 386, @@ -1124,9 +1053,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 387, @@ -1148,9 +1075,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 388, @@ -1172,9 +1097,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 389, @@ -1196,9 +1119,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 390, @@ -1220,9 +1141,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ] }, @@ -1264,9 +1183,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 392, @@ -1288,9 +1205,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 393, @@ -1312,9 +1227,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 394, @@ -1336,9 +1249,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 395, @@ -1360,9 +1271,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 396, @@ -1384,9 +1293,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 397, @@ -1408,9 +1315,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 398, @@ -1432,9 +1337,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ] }, @@ -1476,9 +1379,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 400, @@ -1500,9 +1401,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 401, @@ -1524,9 +1423,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 402, @@ -1548,9 +1445,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 403, @@ -1572,9 +1467,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 404, @@ -1596,9 +1489,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 405, @@ -1620,9 +1511,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 406, @@ -1644,9 +1533,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ] }, @@ -1688,9 +1575,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 408, @@ -1712,9 +1597,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 409, @@ -1736,9 +1619,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 410, @@ -1760,9 +1641,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 411, @@ -1784,9 +1663,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 412, @@ -1808,9 +1685,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 413, @@ -1832,9 +1707,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 414, @@ -1856,9 +1729,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ] }, @@ -1900,9 +1771,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 416, @@ -1924,9 +1793,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 417, @@ -1948,9 +1815,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 418, @@ -1972,9 +1837,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 419, @@ -1996,9 +1859,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 420, @@ -2020,9 +1881,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 421, @@ -2044,9 +1903,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 422, @@ -2068,9 +1925,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ] }, @@ -2112,9 +1967,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 424, @@ -2136,9 +1989,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 425, @@ -2160,9 +2011,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 426, @@ -2184,9 +2033,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 427, @@ -2208,9 +2055,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 428, @@ -2232,9 +2077,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 429, @@ -2256,9 +2099,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 430, @@ -2280,9 +2121,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ] } @@ -2378,12 +2217,8 @@ ] } ], - "snippets": [ - - ], - "releases": [ - - ], + "snippets": [], + "releases": [], "project_members": [ { "id": 36, @@ -2515,9 +2350,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 672, @@ -2539,9 +2372,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 673, @@ -2563,9 +2394,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 674, @@ -2587,9 +2416,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 675, @@ -2611,9 +2438,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 676, @@ -2635,9 +2460,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 677, @@ -2659,9 +2482,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 678, @@ -2683,9 +2504,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ], "merge_request_diff": { @@ -2696,7 +2515,7 @@ "merge_request_diff_id": 27, "relative_order": 0, "sha": "bb5206fee213d983da88c47f9cf4cc6caf9c66dc", - "message": "Feature conflcit added\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Feature conflcit added\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-08-06T08:35:52.000+02:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -2708,7 +2527,7 @@ "merge_request_diff_id": 27, "relative_order": 1, "sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e", - "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T10:01:38.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -2720,7 +2539,7 @@ "merge_request_diff_id": 27, "relative_order": 2, "sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", - "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T09:57:31.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -2732,7 +2551,7 @@ "merge_request_diff_id": 27, "relative_order": 3, "sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9", - "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T09:54:21.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -2744,7 +2563,7 @@ "merge_request_diff_id": 27, "relative_order": 4, "sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08", - "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T09:49:50.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -2756,7 +2575,7 @@ "merge_request_diff_id": 27, "relative_order": 5, "sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8", - "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T09:48:32.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -2834,7 +2653,7 @@ { "merge_request_diff_id": 27, "relative_order": 5, - "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" =\u003e path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" =\u003e path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output \u003c\u003c stdout.read\n @cmd_output \u003c\u003c stderr.read\n", + "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" => path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" => path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output << stdout.read\n @cmd_output << stderr.read\n", "new_path": "files/ruby/popen.rb", "old_path": "files/ruby/popen.rb", "a_mode": "100644", @@ -2958,9 +2777,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 680, @@ -2982,9 +2799,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 681, @@ -3006,9 +2821,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 682, @@ -3030,9 +2843,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 683, @@ -3054,9 +2865,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 684, @@ -3078,9 +2887,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 685, @@ -3102,9 +2909,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 686, @@ -3126,9 +2931,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ], "merge_request_diff": { @@ -3139,7 +2942,7 @@ "merge_request_diff_id": 26, "sha": "0b4bc9a49b562e85de7cc9e834518ea6828729b9", "relative_order": 0, - "message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T09:26:01.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -3237,9 +3040,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 778, @@ -3261,9 +3062,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 779, @@ -3285,9 +3084,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 780, @@ -3309,9 +3106,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 781, @@ -3333,9 +3128,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 782, @@ -3357,9 +3150,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 783, @@ -3381,9 +3172,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 784, @@ -3405,9 +3194,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ], "merge_request_diff": { @@ -3516,9 +3303,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 786, @@ -3540,9 +3325,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 787, @@ -3564,9 +3347,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 788, @@ -3588,9 +3369,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 789, @@ -3612,9 +3391,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 790, @@ -3636,9 +3413,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 791, @@ -3660,9 +3435,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 792, @@ -3684,9 +3457,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ], "merge_request_diff": { @@ -3877,7 +3648,7 @@ "merge_request_diff_id": 14, "relative_order": 15, "sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e", - "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T10:01:38.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -3889,7 +3660,7 @@ "merge_request_diff_id": 14, "relative_order": 16, "sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", - "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T09:57:31.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -3901,7 +3672,7 @@ "merge_request_diff_id": 14, "relative_order": 17, "sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9", - "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T09:54:21.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -3913,7 +3684,7 @@ "merge_request_diff_id": 14, "relative_order": 18, "sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08", - "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T09:49:50.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -3925,7 +3696,7 @@ "merge_request_diff_id": 14, "relative_order": 19, "sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8", - "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T09:48:32.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -4016,7 +3787,7 @@ { "merge_request_diff_id": 14, "relative_order": 6, - "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+ \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+ \u003ctitle\u003ewm\u003c/title\u003e\n+ \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+ \u003cdefs\u003e\n+ \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+ \u003c/defs\u003e\n+ \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+ \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+ \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+ \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+ \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g16\"\u003e\n+ \u003cg id=\"g18-Clipped\"\u003e\n+ \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+ \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+ \u003c/mask\u003e\n+ \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+ \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+ \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+ \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+ \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+ \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+ \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+ \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+ \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+ \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+ \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+ \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+ \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+ \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+ \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n", + "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\n+\n+ \n+ wm\n+ Created with Sketch.\n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+\n\\ No newline at end of file\n", "new_path": "files/images/wm.svg", "old_path": "files/images/wm.svg", "a_mode": "0", @@ -4042,7 +3813,7 @@ { "merge_request_diff_id": 14, "relative_order": 8, - "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" =\u003e path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" =\u003e path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output \u003c\u003c stdout.read\n @cmd_output \u003c\u003c stderr.read\n", + "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" => path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" => path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output << stdout.read\n @cmd_output << stderr.read\n", "new_path": "files/ruby/popen.rb", "old_path": "files/ruby/popen.rb", "a_mode": "100644", @@ -4207,7 +3978,7 @@ }, "events": [ { - "merge_request_diff_id": 14, + "merge_request_diff_id": 14, "id": 529, "target_type": "Note", "target_id": 793, @@ -4239,9 +4010,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 795, @@ -4263,9 +4032,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 796, @@ -4287,9 +4054,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 797, @@ -4311,9 +4076,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 798, @@ -4335,9 +4098,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 799, @@ -4359,9 +4120,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 800, @@ -4383,9 +4142,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ], "merge_request_diff": { @@ -4603,7 +4360,7 @@ { "merge_request_diff_id": 13, "relative_order": 2, - "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+ \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+ \u003ctitle\u003ewm\u003c/title\u003e\n+ \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+ \u003cdefs\u003e\n+ \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+ \u003c/defs\u003e\n+ \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+ \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+ \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+ \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+ \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g16\"\u003e\n+ \u003cg id=\"g18-Clipped\"\u003e\n+ \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+ \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+ \u003c/mask\u003e\n+ \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+ \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+ \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+ \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+ \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+ \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+ \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+ \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+ \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+ \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+ \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+ \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+ \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+ \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+ \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n", + "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\n+\n+ \n+ wm\n+ Created with Sketch.\n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+\n\\ No newline at end of file\n", "new_path": "files/images/wm.svg", "old_path": "files/images/wm.svg", "a_mode": "0", @@ -4740,9 +4497,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 802, @@ -4764,9 +4519,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 803, @@ -4788,9 +4541,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 804, @@ -4812,9 +4563,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 805, @@ -4836,9 +4585,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 806, @@ -4860,9 +4607,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 807, @@ -4884,9 +4629,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 808, @@ -4908,9 +4651,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ], "merge_request_diff": { @@ -5104,7 +4845,7 @@ { "merge_request_diff_id": 12, "relative_order": 2, - "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+ \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+ \u003ctitle\u003ewm\u003c/title\u003e\n+ \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+ \u003cdefs\u003e\n+ \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+ \u003c/defs\u003e\n+ \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+ \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+ \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+ \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+ \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g16\"\u003e\n+ \u003cg id=\"g18-Clipped\"\u003e\n+ \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+ \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+ \u003c/mask\u003e\n+ \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+ \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+ \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+ \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+ \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+ \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+ \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+ \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+ \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+ \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+ \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+ \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+ \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+ \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+ \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n", + "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\n+\n+ \n+ wm\n+ Created with Sketch.\n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+\n\\ No newline at end of file\n", "new_path": "files/images/wm.svg", "old_path": "files/images/wm.svg", "a_mode": "0", @@ -5228,9 +4969,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 810, @@ -5252,9 +4991,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 811, @@ -5276,9 +5013,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 812, @@ -5300,9 +5035,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 813, @@ -5324,9 +5057,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 814, @@ -5348,9 +5079,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 815, @@ -5372,9 +5101,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 816, @@ -5396,18 +5123,14 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ], "merge_request_diff": { "id": 11, "state": "empty", - "merge_request_diff_commits": [ - ], - "merge_request_diff_files": [ - ], + "merge_request_diff_commits": [], + "merge_request_diff_files": [], "merge_request_id": 11, "created_at": "2016-06-14T15:02:23.772Z", "updated_at": "2016-06-14T15:02:23.833Z", @@ -5482,9 +5205,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 818, @@ -5506,9 +5227,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 819, @@ -5530,9 +5249,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 820, @@ -5554,9 +5271,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 821, @@ -5578,9 +5293,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 822, @@ -5602,9 +5315,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 823, @@ -5626,9 +5337,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 824, @@ -5650,9 +5359,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ], "merge_request_diff": { @@ -5843,7 +5550,7 @@ "merge_request_diff_id": 10, "relative_order": 16, "sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e", - "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T10:01:38.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -5855,7 +5562,7 @@ "merge_request_diff_id": 10, "relative_order": 17, "sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d", - "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T09:57:31.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -5867,7 +5574,7 @@ "merge_request_diff_id": 10, "relative_order": 18, "sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9", - "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T09:54:21.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -5879,7 +5586,7 @@ "merge_request_diff_id": 10, "relative_order": 19, "sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08", - "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T09:49:50.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -5891,7 +5598,7 @@ "merge_request_diff_id": 10, "relative_order": 20, "sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8", - "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \n", "authored_date": "2014-02-27T09:48:32.000+01:00", "author_name": "Dmitriy Zaporozhets", "author_email": "dmitriy.zaporozhets@gmail.com", @@ -5982,7 +5689,7 @@ { "merge_request_diff_id": 10, "relative_order": 6, - "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+ \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+ \u003ctitle\u003ewm\u003c/title\u003e\n+ \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+ \u003cdefs\u003e\n+ \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+ \u003c/defs\u003e\n+ \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+ \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+ \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+ \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+ \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g16\"\u003e\n+ \u003cg id=\"g18-Clipped\"\u003e\n+ \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+ \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+ \u003c/mask\u003e\n+ \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+ \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+ \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+ \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+ \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+ \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+ \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+ \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+ \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+ \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+ \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+ \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+ \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+ \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+ \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n", + "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\n+\n+ \n+ wm\n+ Created with Sketch.\n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+ \n+\n\\ No newline at end of file\n", "new_path": "files/images/wm.svg", "old_path": "files/images/wm.svg", "a_mode": "0", @@ -6008,7 +5715,7 @@ { "merge_request_diff_id": 10, "relative_order": 8, - "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" =\u003e path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" =\u003e path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output \u003c\u003c stdout.read\n @cmd_output \u003c\u003c stderr.read\n", + "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" => path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" => path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output << stdout.read\n @cmd_output << stderr.read\n", "new_path": "files/ruby/popen.rb", "old_path": "files/ruby/popen.rb", "a_mode": "100644", @@ -6171,9 +5878,7 @@ "author": { "name": "User 4" }, - "events": [ - - ] + "events": [] }, { "id": 826, @@ -6195,9 +5900,7 @@ "author": { "name": "User 3" }, - "events": [ - - ] + "events": [] }, { "id": 827, @@ -6219,9 +5922,7 @@ "author": { "name": "User 0" }, - "events": [ - - ] + "events": [] }, { "id": 828, @@ -6243,9 +5944,7 @@ "author": { "name": "Ottis Schuster II" }, - "events": [ - - ] + "events": [] }, { "id": 829, @@ -6267,9 +5966,7 @@ "author": { "name": "Rhett Emmerich IV" }, - "events": [ - - ] + "events": [] }, { "id": 830, @@ -6291,9 +5988,7 @@ "author": { "name": "Burdette Bernier" }, - "events": [ - - ] + "events": [] }, { "id": 831, @@ -6315,9 +6010,7 @@ "author": { "name": "Ari Wintheiser" }, - "events": [ - - ] + "events": [] }, { "id": 832, @@ -6339,9 +6032,7 @@ "author": { "name": "Administrator" }, - "events": [ - - ] + "events": [] } ], "merge_request_diff": { @@ -6953,9 +6644,7 @@ "updated_at": "2017-01-16T15:25:28.637Z" } ], - "deploy_keys": [ - - ], + "deploy_keys": [], "services": [ { "id": 100, @@ -6964,9 +6653,7 @@ "created_at": "2016-06-14T15:01:51.315Z", "updated_at": "2016-06-14T15:01:51.315Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7008,9 +6695,7 @@ "created_at": "2016-06-14T15:01:51.289Z", "updated_at": "2016-06-14T15:01:51.289Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7030,9 +6715,7 @@ "created_at": "2016-06-14T15:01:51.277Z", "updated_at": "2016-06-14T15:01:51.277Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7052,9 +6735,7 @@ "created_at": "2016-06-14T15:01:51.267Z", "updated_at": "2016-06-14T15:01:51.267Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7097,9 +6778,7 @@ "created_at": "2016-06-14T15:01:51.232Z", "updated_at": "2016-06-14T15:01:51.232Z", "active": true, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7141,9 +6820,7 @@ "created_at": "2016-06-14T15:01:51.202Z", "updated_at": "2016-06-14T15:01:51.202Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7163,9 +6840,7 @@ "created_at": "2016-06-14T15:01:51.182Z", "updated_at": "2016-06-14T15:01:51.182Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7185,9 +6860,7 @@ "created_at": "2016-06-14T15:01:51.166Z", "updated_at": "2016-06-14T15:01:51.166Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7207,9 +6880,7 @@ "created_at": "2016-06-14T15:01:51.153Z", "updated_at": "2016-06-14T15:01:51.153Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7229,9 +6900,7 @@ "created_at": "2016-06-14T15:01:51.139Z", "updated_at": "2016-06-14T15:01:51.139Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7251,9 +6920,7 @@ "created_at": "2016-06-14T15:01:51.125Z", "updated_at": "2016-06-14T15:01:51.125Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7273,9 +6940,7 @@ "created_at": "2016-06-14T15:01:51.113Z", "updated_at": "2016-06-14T15:01:51.113Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7295,9 +6960,7 @@ "created_at": "2016-06-14T15:01:51.080Z", "updated_at": "2016-06-14T15:01:51.080Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7317,9 +6980,7 @@ "created_at": "2016-06-14T15:01:51.067Z", "updated_at": "2016-06-14T15:01:51.067Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7339,9 +7000,7 @@ "created_at": "2016-06-14T15:01:51.047Z", "updated_at": "2016-06-14T15:01:51.047Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7361,9 +7020,7 @@ "created_at": "2016-06-14T15:01:51.031Z", "updated_at": "2016-06-14T15:01:51.031Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7383,9 +7040,7 @@ "created_at": "2016-06-14T15:01:51.031Z", "updated_at": "2016-06-14T15:01:51.031Z", "active": false, - "properties": { - - }, + "properties": {}, "template": false, "push_events": true, "issues_events": true, @@ -7399,9 +7054,7 @@ "type": "JenkinsDeprecatedService" } ], - "hooks": [ - - ], + "hooks": [], "protected_branches": [ { "id": 1, @@ -7475,5 +7128,25 @@ "key": "bar", "value": "bar" } + ], + "project_badges": [ + { + "id": 1, + "created_at": "2017-10-19T15:36:23.466Z", + "updated_at": "2017-10-19T15:36:23.466Z", + "project_id": 5, + "type": "ProjectBadge", + "link_url": "http://www.example.com", + "image_url": "http://www.example.com" + }, + { + "id": 2, + "created_at": "2017-10-19T15:36:23.466Z", + "updated_at": "2017-10-19T15:36:23.466Z", + "project_id": 5, + "type": "ProjectBadge", + "link_url": "http://www.example.com", + "image_url": "http://www.example.com" + } ] } diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index d076007e4bc..1a4d09724fc 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -129,6 +129,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do expect(@project.custom_attributes.count).to eq(2) end + it 'has badges' do + expect(@project.project_badges.count).to eq(2) + end + it 'restores the correct service' do expect(CustomIssueTrackerService.first).not_to be_nil end diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index 5804c45871e..d6bd5f5c81d 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -180,6 +180,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do expect(saved_project_json['custom_attributes'].count).to eq(2) end + it 'has badges' do + expect(saved_project_json['project_badges'].count).to eq(2) + end + it 'does not complain about non UTF-8 characters in MR diff files' do ActiveRecord::Base.connection.execute("UPDATE merge_request_diff_files SET diff = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'") @@ -288,6 +292,9 @@ describe Gitlab::ImportExport::ProjectTreeSaver do create(:project_custom_attribute, project: project) create(:project_custom_attribute, project: project) + create(:project_badge, project: project) + create(:project_badge, project: project) + project end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index feaab6673cd..ddcbb7a0033 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -536,3 +536,12 @@ LfsFileLock: - user_id - project_id - created_at +Badge: +- id +- link_url +- image_url +- project_id +- group_id +- created_at +- updated_at +- type diff --git a/spec/lib/gitlab/string_placeholder_replacer_spec.rb b/spec/lib/gitlab/string_placeholder_replacer_spec.rb new file mode 100644 index 00000000000..7a03ea4154c --- /dev/null +++ b/spec/lib/gitlab/string_placeholder_replacer_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe Gitlab::StringPlaceholderReplacer do + describe '.render_url' do + it 'returns the nil if the string is blank' do + expect(described_class.replace_string_placeholders(nil, /whatever/)).to be_blank + end + + it 'returns the string if the placeholder regex' do + expect(described_class.replace_string_placeholders('whatever')).to eq 'whatever' + end + + it 'returns the string if no block given' do + expect(described_class.replace_string_placeholders('whatever', /whatever/)).to eq 'whatever' + end + + context 'when all params are valid' do + let(:string) { '%{path}/%{id}/%{branch}' } + let(:regex) { /(path|id)/ } + + it 'replaces each placeholders with the block result' do + result = described_class.replace_string_placeholders(string, regex) do |arg| + 'WHATEVER' + end + + expect(result).to eq 'WHATEVER/WHATEVER/%{branch}' + end + + it 'does not replace the placeholder if the block result is nil' do + result = described_class.replace_string_placeholders(string, regex) do |arg| + arg == 'path' ? nil : 'WHATEVER' + end + + expect(result).to eq '%{path}/WHATEVER/%{branch}' + end + end + end +end diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb new file mode 100644 index 00000000000..33dc19e3432 --- /dev/null +++ b/spec/models/badge_spec.rb @@ -0,0 +1,94 @@ +require 'spec_helper' + +describe Badge do + let(:placeholder_url) { 'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}' } + + describe 'validations' do + # Requires the let variable url_sym + shared_examples 'placeholder url' do + let(:badge) { build(:badge) } + + it 'allows url with http protocol' do + badge[url_sym] = 'http://www.example.com' + + expect(badge).to be_valid + end + + it 'allows url with https protocol' do + badge[url_sym] = 'https://www.example.com' + + expect(badge).to be_valid + end + + it 'cannot be empty' do + badge[url_sym] = '' + + expect(badge).not_to be_valid + end + + it 'cannot be nil' do + badge[url_sym] = nil + + expect(badge).not_to be_valid + end + + it 'accept badges placeholders' do + badge[url_sym] = placeholder_url + + expect(badge).to be_valid + end + + it 'sanitize url' do + badge[url_sym] = 'javascript:alert(1)' + + expect(badge).not_to be_valid + end + end + + context 'link_url format' do + let(:url_sym) { :link_url } + + it_behaves_like 'placeholder url' + end + + context 'image_url format' do + let(:url_sym) { :image_url } + + it_behaves_like 'placeholder url' + end + end + + shared_examples 'rendered_links' do + it 'should use the project information to populate the url placeholders' do + stub_project_commit_info(project) + + expect(badge.public_send("rendered_#{method}", project)).to eq "http://www.example.com/#{project.full_path}/#{project.id}/master/whatever" + end + + it 'returns the url if the project used is nil' do + expect(badge.public_send("rendered_#{method}", nil)).to eq placeholder_url + end + + def stub_project_commit_info(project) + allow(project).to receive(:commit).and_return(double('Commit', sha: 'whatever')) + allow(project).to receive(:default_branch).and_return('master') + end + end + + context 'methods' do + let(:badge) { build(:badge, link_url: placeholder_url, image_url: placeholder_url) } + let!(:project) { create(:project) } + + context '#rendered_link_url' do + let(:method) { :link_url } + + it_behaves_like 'rendered_links' + end + + context '#rendered_image_url' do + let(:method) { :image_url } + + it_behaves_like 'rendered_links' + end + end +end diff --git a/spec/models/badges/group_badge_spec.rb b/spec/models/badges/group_badge_spec.rb new file mode 100644 index 00000000000..ed7f83d0489 --- /dev/null +++ b/spec/models/badges/group_badge_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe GroupBadge do + describe 'associations' do + it { is_expected.to belong_to(:group) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:group) } + end +end diff --git a/spec/models/badges/project_badge_spec.rb b/spec/models/badges/project_badge_spec.rb new file mode 100644 index 00000000000..0e1a8159cb6 --- /dev/null +++ b/spec/models/badges/project_badge_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe ProjectBadge do + let(:placeholder_url) { 'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}' } + + describe 'associations' do + it { is_expected.to belong_to(:project) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:project) } + end + + shared_examples 'rendered_links' do + it 'should use the badge project information to populate the url placeholders' do + stub_project_commit_info(project) + + expect(badge.public_send("rendered_#{method}")).to eq "http://www.example.com/#{project.full_path}/#{project.id}/master/whatever" + end + + def stub_project_commit_info(project) + allow(project).to receive(:commit).and_return(double('Commit', sha: 'whatever')) + allow(project).to receive(:default_branch).and_return('master') + end + end + + context 'methods' do + let(:badge) { build(:project_badge, link_url: placeholder_url, image_url: placeholder_url) } + let!(:project) { badge.project } + + context '#rendered_link_url' do + let(:method) { :link_url } + + it_behaves_like 'rendered_links' + end + + context '#rendered_image_url' do + let(:method) { :image_url } + + it_behaves_like 'rendered_links' + end + end +end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 4f16b73ef38..abfc0896a41 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -18,6 +18,7 @@ describe Group do it { is_expected.to have_many(:uploads).dependent(:destroy) } it { is_expected.to have_one(:chat_team) } it { is_expected.to have_many(:custom_attributes).class_name('GroupCustomAttribute') } + it { is_expected.to have_many(:badges).class_name('GroupBadge') } describe '#members & #requesters' do let(:requester) { create(:user) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f4faec9e52a..92ea8841123 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -80,6 +80,7 @@ describe Project do it { is_expected.to have_many(:members_and_requesters) } it { is_expected.to have_many(:clusters) } it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') } + it { is_expected.to have_many(:project_badges).class_name('ProjectBadge') } it { is_expected.to have_many(:lfs_file_locks) } context 'after initialized' do @@ -3331,4 +3332,36 @@ describe Project do end.not_to raise_error # Sidekiq::Worker::EnqueueFromTransactionError end end + + describe '#badges' do + let(:project_group) { create(:group) } + let(:project) { create(:project, path: 'avatar', namespace: project_group) } + + before do + create_list(:project_badge, 2, project: project) + create(:group_badge, group: project_group) + end + + it 'returns the project and the project group badges' do + create(:group_badge, group: create(:group)) + + expect(Badge.count).to eq 4 + expect(project.badges.count).to eq 3 + end + + if Group.supports_nested_groups? + context 'with nested_groups' do + let(:parent_group) { create(:group) } + + before do + create_list(:group_badge, 2, group: project_group) + project_group.update(parent: parent_group) + end + + it 'returns the project and the project nested groups badges' do + expect(project.badges.count).to eq 5 + end + end + end + end end diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb new file mode 100644 index 00000000000..ae64a9ca162 --- /dev/null +++ b/spec/requests/api/badges_spec.rb @@ -0,0 +1,367 @@ +require 'spec_helper' + +describe API::Badges do + let(:master) { create(:user, username: 'master_user') } + let(:developer) { create(:user) } + let(:access_requester) { create(:user) } + let(:stranger) { create(:user) } + let(:project_group) { create(:group) } + let(:project) { setup_project } + let!(:group) { setup_group } + + shared_context 'source helpers' do + def get_source(source_type) + source_type == 'project' ? project : group + end + end + + shared_examples 'GET /:sources/:id/badges' do |source_type| + include_context 'source helpers' + + let(:source) { get_source(source_type) } + + context "with :sources == #{source_type.pluralize}" do + it_behaves_like 'a 404 response when source is private' do + let(:route) { get api("/#{source_type.pluralize}/#{source.id}/badges", stranger) } + end + + %i[master developer access_requester stranger].each do |type| + context "when authenticated as a #{type}" do + it 'returns 200' do + user = public_send(type) + badges_count = source_type == 'project' ? 3 : 2 + + get api("/#{source_type.pluralize}/#{source.id}/badges", user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.size).to eq(badges_count) + end + end + end + + it 'avoids N+1 queries' do + # Establish baseline + get api("/#{source_type.pluralize}/#{source.id}/badges", master) + + control = ActiveRecord::QueryRecorder.new do + get api("/#{source_type.pluralize}/#{source.id}/badges", master) + end + + project.add_developer(create(:user)) + + expect do + get api("/#{source_type.pluralize}/#{source.id}/badges", master) + end.not_to exceed_query_limit(control) + end + end + end + + shared_examples 'GET /:sources/:id/badges/:badge_id' do |source_type| + include_context 'source helpers' + + let(:source) { get_source(source_type) } + + context "with :sources == #{source_type.pluralize}" do + it_behaves_like 'a 404 response when source is private' do + let(:route) { get api("/#{source_type.pluralize}/#{source.id}/badges/#{developer.id}", stranger) } + end + + context 'when authenticated as a non-member' do + %i[master developer access_requester stranger].each do |type| + let(:badge) { source.badges.first } + + context "as a #{type}" do + it 'returns 200' do + user = public_send(type) + + get api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['id']).to eq(badge.id) + expect(json_response['link_url']).to eq(badge.link_url) + expect(json_response['rendered_link_url']).to eq(badge.rendered_link_url) + expect(json_response['image_url']).to eq(badge.image_url) + expect(json_response['rendered_image_url']).to eq(badge.rendered_image_url) + expect(json_response['kind']).to eq source_type + end + end + end + end + end + end + + shared_examples 'POST /:sources/:id/badges' do |source_type| + include_context 'source helpers' + + let(:source) { get_source(source_type) } + let(:example_url) { 'http://www.example.com' } + let(:example_url2) { 'http://www.example1.com' } + + context "with :sources == #{source_type.pluralize}" do + it_behaves_like 'a 404 response when source is private' do + let(:route) do + post api("/#{source_type.pluralize}/#{source.id}/badges", stranger), + link_url: example_url, image_url: example_url2 + end + end + + context 'when authenticated as a non-member or member with insufficient rights' do + %i[access_requester stranger developer].each do |type| + context "as a #{type}" do + it 'returns 403' do + user = public_send(type) + + post api("/#{source_type.pluralize}/#{source.id}/badges", user), + link_url: example_url, image_url: example_url2 + + expect(response).to have_gitlab_http_status(403) + end + end + end + end + + context 'when authenticated as a master/owner' do + it 'creates a new badge' do + expect do + post api("/#{source_type.pluralize}/#{source.id}/badges", master), + link_url: example_url, image_url: example_url2 + + expect(response).to have_gitlab_http_status(201) + end.to change { source.badges.count }.by(1) + + expect(json_response['link_url']).to eq(example_url) + expect(json_response['image_url']).to eq(example_url2) + expect(json_response['kind']).to eq source_type + end + end + + it 'returns 400 when link_url is not given' do + post api("/#{source_type.pluralize}/#{source.id}/badges", master), + link_url: example_url + + expect(response).to have_gitlab_http_status(400) + end + + it 'returns 400 when image_url is not given' do + post api("/#{source_type.pluralize}/#{source.id}/badges", master), + image_url: example_url2 + + expect(response).to have_gitlab_http_status(400) + end + + it 'returns 400 when link_url or image_url is not valid' do + post api("/#{source_type.pluralize}/#{source.id}/badges", master), + link_url: 'whatever', image_url: 'whatever' + + expect(response).to have_gitlab_http_status(400) + end + end + end + + shared_examples 'PUT /:sources/:id/badges/:badge_id' do |source_type| + include_context 'source helpers' + + let(:source) { get_source(source_type) } + + context "with :sources == #{source_type.pluralize}" do + let(:badge) { source.badges.first } + let(:example_url) { 'http://www.example.com' } + let(:example_url2) { 'http://www.example1.com' } + + it_behaves_like 'a 404 response when source is private' do + let(:route) do + put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", stranger), + link_url: example_url + end + end + + context 'when authenticated as a non-member or member with insufficient rights' do + %i[access_requester stranger developer].each do |type| + context "as a #{type}" do + it 'returns 403' do + user = public_send(type) + + put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", user), + link_url: example_url + + expect(response).to have_gitlab_http_status(403) + end + end + end + end + + context 'when authenticated as a master/owner' do + it 'updates the member' do + put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", master), + link_url: example_url, image_url: example_url2 + + expect(response).to have_gitlab_http_status(200) + expect(json_response['link_url']).to eq(example_url) + expect(json_response['image_url']).to eq(example_url2) + expect(json_response['kind']).to eq source_type + end + end + + it 'returns 400 when link_url or image_url is not valid' do + put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", master), + link_url: 'whatever', image_url: 'whatever' + + expect(response).to have_gitlab_http_status(400) + end + end + end + + shared_examples 'DELETE /:sources/:id/badges/:badge_id' do |source_type| + include_context 'source helpers' + + let(:source) { get_source(source_type) } + + context "with :sources == #{source_type.pluralize}" do + let(:badge) { source.badges.first } + + it_behaves_like 'a 404 response when source is private' do + let(:route) { delete api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", stranger) } + end + + context 'when authenticated as a non-member or member with insufficient rights' do + %i[access_requester developer stranger].each do |type| + context "as a #{type}" do + it 'returns 403' do + user = public_send(type) + + delete api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", user) + + expect(response).to have_gitlab_http_status(403) + end + end + end + end + + context 'when authenticated as a master/owner' do + it 'deletes the badge' do + expect do + delete api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", master) + + expect(response).to have_gitlab_http_status(204) + end.to change { source.badges.count }.by(-1) + end + + it_behaves_like '412 response' do + let(:request) { api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", master) } + end + end + + it 'returns 404 if badge does not exist' do + delete api("/#{source_type.pluralize}/#{source.id}/badges/123", master) + + expect(response).to have_gitlab_http_status(404) + end + end + end + + shared_examples 'GET /:sources/:id/badges/render' do |source_type| + include_context 'source helpers' + + let(:source) { get_source(source_type) } + let(:example_url) { 'http://www.example.com' } + let(:example_url2) { 'http://www.example1.com' } + + context "with :sources == #{source_type.pluralize}" do + it_behaves_like 'a 404 response when source is private' do + let(:route) do + get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=#{example_url}&image_url=#{example_url2}", stranger) + end + end + + context 'when authenticated as a non-member or member with insufficient rights' do + %i[access_requester stranger developer].each do |type| + context "as a #{type}" do + it 'returns 403' do + user = public_send(type) + + get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=#{example_url}&image_url=#{example_url2}", user) + + expect(response).to have_gitlab_http_status(403) + end + end + end + end + + context 'when authenticated as a master/owner' do + it 'gets the rendered badge values' do + get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=#{example_url}&image_url=#{example_url2}", master) + + expect(response).to have_gitlab_http_status(200) + + expect(json_response.keys).to contain_exactly('link_url', 'rendered_link_url', 'image_url', 'rendered_image_url') + expect(json_response['link_url']).to eq(example_url) + expect(json_response['image_url']).to eq(example_url2) + expect(json_response['rendered_link_url']).to eq(example_url) + expect(json_response['rendered_image_url']).to eq(example_url2) + end + end + + it 'returns 400 when link_url is not given' do + get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=#{example_url}", master) + + expect(response).to have_gitlab_http_status(400) + end + + it 'returns 400 when image_url is not given' do + get api("/#{source_type.pluralize}/#{source.id}/badges/render?image_url=#{example_url}", master) + + expect(response).to have_gitlab_http_status(400) + end + + it 'returns 400 when link_url or image_url is not valid' do + get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=whatever&image_url=whatever", master) + + expect(response).to have_gitlab_http_status(400) + end + end + end + + context 'when deleting a badge' do + context 'and the source is a project' do + it 'cannot delete badges owned by the project group' do + delete api("/projects/#{project.id}/badges/#{project_group.badges.first.id}", master) + + expect(response).to have_gitlab_http_status(403) + end + end + end + + describe 'Endpoints' do + %w(project group).each do |source_type| + it_behaves_like 'GET /:sources/:id/badges', source_type + it_behaves_like 'GET /:sources/:id/badges/:badge_id', source_type + it_behaves_like 'GET /:sources/:id/badges/render', source_type + it_behaves_like 'POST /:sources/:id/badges', source_type + it_behaves_like 'PUT /:sources/:id/badges/:badge_id', source_type + it_behaves_like 'DELETE /:sources/:id/badges/:badge_id', source_type + end + end + + def setup_project + create(:project, :public, :access_requestable, creator_id: master.id, namespace: project_group) do |project| + project.add_developer(developer) + project.add_master(master) + project.request_access(access_requester) + project.project_badges << build(:project_badge, project: project) + project.project_badges << build(:project_badge, project: project) + project_group.badges << build(:group_badge, group: group) + end + end + + def setup_group + create(:group, :public, :access_requestable) do |group| + group.add_developer(developer) + group.add_owner(master) + group.request_access(access_requester) + group.badges << build(:group_badge, group: group) + group.badges << build(:group_badge, group: group) + end + end +end diff --git a/spec/validators/url_placeholder_validator_spec.rb b/spec/validators/url_placeholder_validator_spec.rb new file mode 100644 index 00000000000..b76d8acdf88 --- /dev/null +++ b/spec/validators/url_placeholder_validator_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe UrlPlaceholderValidator do + let(:validator) { described_class.new(attributes: [:link_url], **options) } + let!(:badge) { build(:badge) } + let(:placeholder_url) { 'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}' } + + subject { validator.validate_each(badge, :link_url, badge.link_url) } + + describe '#validates_each' do + context 'with no options' do + let(:options) { {} } + + it 'allows http and https protocols by default' do + expect(validator.send(:default_options)[:protocols]).to eq %w(http https) + end + + it 'checks that the url structure is valid' do + badge.link_url = placeholder_url + + subject + + expect(badge.errors.empty?).to be false + end + end + + context 'with placeholder regex' do + let(:options) { { placeholder_regex: /(project_path|project_id|commit_sha|default_branch)/ } } + + it 'checks that the url is valid and obviate placeholders that match regex' do + badge.link_url = placeholder_url + + subject + + expect(badge.errors.empty?).to be true + end + end + end +end diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb new file mode 100644 index 00000000000..763dff181d2 --- /dev/null +++ b/spec/validators/url_validator_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe UrlValidator do + let(:validator) { described_class.new(attributes: [:link_url], **options) } + let!(:badge) { build(:badge) } + + subject { validator.validate_each(badge, :link_url, badge.link_url) } + + describe '#validates_each' do + context 'with no options' do + let(:options) { {} } + + it 'allows http and https protocols by default' do + expect(validator.send(:default_options)[:protocols]).to eq %w(http https) + end + + it 'checks that the url structure is valid' do + badge.link_url = 'http://www.google.es/%{whatever}' + + subject + + expect(badge.errors.empty?).to be false + end + end + + context 'with protocols' do + let(:options) { { protocols: %w(http) } } + + it 'allows urls with the defined protocols' do + badge.link_url = 'http://www.example.com' + + subject + + expect(badge.errors.empty?).to be true + end + + it 'add error if the url protocol does not match the selected ones' do + badge.link_url = 'https://www.example.com' + + subject + + expect(badge.errors.empty?).to be false + end + end + end +end diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb index 62af946dcab..15fce65979b 100644 --- a/spec/views/projects/_home_panel.html.haml_spec.rb +++ b/spec/views/projects/_home_panel.html.haml_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' describe 'projects/_home_panel' do - let(:project) { create(:project, :public) } + let(:group) { create(:group) } + let(:project) { create(:project, :public, namespace: group) } let(:notification_settings) do user&.notification_settings_for(project) @@ -35,4 +36,55 @@ describe 'projects/_home_panel' do expect(rendered).not_to have_selector('.notification_dropdown') end end + + context 'when project' do + let!(:user) { create(:user) } + let(:badges) { project.badges } + + context 'has no badges' do + it 'should not render any badge' do + render + + expect(rendered).to have_selector('.project-badges') + expect(rendered).not_to have_selector('.project-badges > a') + end + end + + shared_examples 'show badges' do + it 'should render the all badges' do + render + + expect(rendered).to have_selector('.project-badges a') + + badges.each do |badge| + expect(rendered).to have_link(href: badge.rendered_link_url) + end + end + end + + context 'only has group badges' do + before do + create(:group_badge, group: project.group) + end + + it_behaves_like 'show badges' + end + + context 'only has project badges' do + before do + create(:project_badge, project: project) + end + + it_behaves_like 'show badges' + end + + context 'has both group and project badges' do + before do + create(:project_badge, project: project) + create(:group_badge, group: project.group) + end + + it_behaves_like 'show badges' + end + end end

V{5w20#fD!Ui4)WpQkbA*fV1F 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 Ob^Raycuooc_y+(iBB6!= delta 143841 zcmV(zK<2-%_!PAG6bB!R2msoKP>}~IFV}s}%y(wKnYnj&@sSD;o_|`U1aNdBhh}9lU^ZZ6C$I(O-{*4E|w+4VsmKiHU=i!-$c| z5MaP$Xw1US#=^vIV93D2zy{!8G-6?6U^6xVu>VIJ$_}>9c4oB74(@hluC`xxSU4G> z*#Rboj2x`S#!O5m#;iuHUtTe=u&{Ho8nZDOvU7ZYoiQ*NF|+(9X8_iAPVTgZ4hA+x zX3z|VEQUs$1}vP0tZYnxFG~P+BO^`#2Qv#RBL~2Mjf07Uor%-%KdrC{z`*G%E6`uK z_=?vA0AONdHs<7DHa6hkFk)dh;WRQ~F*0H^GWa@UV>34TZ*=vHY;8=e%#EC&SvX7> z3;`^EhQ^ExMvTS)BLINS!~np`WWs3xVEg)|2|K$X8-oeUe==h3_J0eSCRPScP5>KP zV{->bXjV?Oe{9Br)u&2s`R?q-o#K8z)HRLpAHvt$iGcx{XWk)A~!xxkd zIgDBV0)@kvWZt?|dMi!$ltN_@5nONA2nc3J3S(uFgi~!EB4av;RVaWL3=(0j{ zvKufMn{cvy1M=man*3zI^5|VPawaFS<<7#>U1hU$HW>8E`T) zvT}TZnUfV@$YR3!wckxRSXfxTu)@a4#PMHLnW0TMIXMiBIKBYJ$;859!pI69;51@n zWn|zq__{2B?F%3*hV1N||2503&}^)%UrUqG1Sv(8Q6s?L{cLOmFyUnR;y0!*-@mT* z6$raA$5%|u|MjZuUp!y}Fg9Z1G-hUC;$Zk%5RSjx%J>CMR+hi&Un>UqTKfMQlRpJn ze>`%0A%gh}H-Fa{z`Q;GWb$twI~kZd(z`IyG0-tUa~Log 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