From d4d564c8e7d2cbc3e6742475a793ba0f630167e3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 1 Feb 2018 18:48:32 +0800 Subject: 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 +++++++++ .../lib/gitlab/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 -- cgit v1.2.3 From bbfce29ba8d75df5344dae34dc472dfb3b3acf4b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 2 Feb 2018 22:12:28 +0800 Subject: 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 | 125 ++++++++++++++------------- spec/lib/gitlab/middleware/read_only_spec.rb | 23 ++++- 2 files changed, 87 insertions(+), 61 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) - def initialize(app) - @app = app - @whitelisted = internal_routes - end - - def call(env) - @env = env - @route_hash = nil + class Controller + def initialize(app, env) + @app = app + @env = env + end - 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' + 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 + 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 }, []] + return [301, { 'Location' => last_visited_url }, []] + end end + + @app.call(@env) end - @app.call(env).tap { @env = nil } - end + private - private + def disallowed_request? + DISALLOWED_METHODS.include?(@env['REQUEST_METHOD']) && + !whitelisted_routes + end - def internal_routes - API_VERSIONS.flat_map { |version| "api/v#{version}/internal" } - end + def json_request? + request.media_type == APPLICATION_JSON + end - def disallowed_request? - DISALLOWED_METHODS.include?(@env['REQUEST_METHOD']) && !whitelisted_routes - end + def rack_flash + @rack_flash ||= ActionDispatch::Flash::FlashHash.from_session_value(rack_session) + end - def json_request? - request.media_type == APPLICATION_JSON - end + def rack_session + @env['rack.session'] + end - def rack_flash - @rack_flash ||= ActionDispatch::Flash::FlashHash.from_session_value(rack_session) - end + def request + @env['rack.request'] ||= Rack::Request.new(@env) + end - def rack_session - @env['rack.session'] - end + def last_visited_url + @env['HTTP_REFERER'] || rack_session['user_return_to'] || Gitlab::Routing.url_helpers.root_url + end - def request - @env['rack.request'] ||= Rack::Request.new(@env) - end + def route_hash + @route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {} + end - def last_visited_url - @env['HTTP_REFERER'] || rack_session['user_return_to'] || Gitlab::Routing.url_helpers.root_url - end + def whitelisted_routes + grack_route || ReadOnly.internal_routes.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route + end - def route_hash - @route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {} - end + def sidekiq_route + request.path.start_with?('/admin/sidekiq') + end - def whitelisted_routes - grack_route || @whitelisted.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route - 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') - def sidekiq_route - request.path.start_with?('/admin/sidekiq') - end + 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') - 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/lfs_api' && route_hash[:action] == 'batch' + end + end - route_hash[:controller] == 'projects/git_http' && route_hash[:action] == 'git_upload_pack' + def self.internal_routes + @internal_routes ||= + API_VERSIONS.map { |version| "api/v#{version}/internal" } 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') + def initialize(app) + @app = app + end - route_hash[:controller] == 'projects/lfs_api' && route_hash[:action] == 'batch' + def call(env) + 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) } -- cgit v1.2.3 From 31f1ec59a7cf7517cd5935ef3af540aceba07bb3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 7 Feb 2018 22:44:05 +0800 Subject: Release the entire env --- config.ru | 2 +- lib/gitlab/middleware/release_controller.rb | 9 --------- lib/gitlab/middleware/release_env.rb | 14 ++++++++++++++ .../lib/gitlab/middleware/release_controller_spec.rb | 20 -------------------- spec/lib/gitlab/middleware/release_env_spec.rb | 20 ++++++++++++++++++++ 5 files changed, 35 insertions(+), 30 deletions(-) delete mode 100644 lib/gitlab/middleware/release_controller.rb create mode 100644 lib/gitlab/middleware/release_env.rb delete mode 100644 spec/lib/gitlab/middleware/release_controller_spec.rb create mode 100644 spec/lib/gitlab/middleware/release_env_spec.rb 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_controller_spec.rb deleted file mode 100644 index 854bac6e751..00000000000 --- a/spec/lib/gitlab/middleware/release_controller_spec.rb +++ /dev/null @@ -1,20 +0,0 @@ -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 diff --git a/spec/lib/gitlab/middleware/release_env_spec.rb b/spec/lib/gitlab/middleware/release_env_spec.rb new file mode 100644 index 00000000000..657b705502a --- /dev/null +++ b/spec/lib/gitlab/middleware/release_env_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::Middleware::ReleaseEnv 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 -- cgit v1.2.3 From 5309d4457aea74729a8c6be9ec76d535f922bf8a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 7 Feb 2018 22:44:18 +0800 Subject: 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 -- cgit v1.2.3 From 461ecbcf07f0785b5ea50c62b114bf8217ac5199 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 7 Feb 2018 22:55:50 +0800 Subject: 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 -- cgit v1.2.3 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: 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 +++-- spec/lib/banzai/filter/autolink_filter_spec.rb | 65 +++++++++++++++++--------- spec/lib/gitlab/string_regex_marker_spec.rb | 35 ++++++++++---- 6 files changed, 89 insertions(+), 66 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 + found_links = doc.css('a') - context 'when the input contains link' do - it 'does parse_html back the rinku returned value' do - act = HTML::Pipeline.parse("

See #{link}

") + 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 - expect_any_instance_of(described_class).to receive(:parse_html).at_least(:once).and_call_original + it 'accepts link_attr options' do + doc = filter("See #{link}", link_attr: { class: 'custom' }) - filter(act).to_html - end + expect(doc.at_css('a')['class']).to eq 'custom' end - end - - context 'other schemes' do - let(:link) { 'foo://bar.baz/' } it 'autolinks smb' do link = 'smb:///Volumes/shared/foo.pdf' @@ -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 -- cgit v1.2.3 From e587c6b63748f45258d65ca80146e9d9b77d7d37 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Tue, 27 Feb 2018 23:52:05 -0500 Subject: Fix k8s metrics --- config/prometheus/additional_metrics.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/additional_metrics.yml index 601a86490d4..1628af9c38d 100644 --- a/config/prometheus/additional_metrics.yml +++ b/config/prometheus/additional_metrics.yml @@ -145,15 +145,15 @@ - container_memory_usage_bytes weight: 1 queries: - - query_range: '(sum(avg(container_memory_usage_bytes{container_name!="POD",environment="%{ci_environment_slug}"}) without (job))) / count(avg(container_memory_usage_bytes{container_name!="POD",environment="%{ci_environment_slug}"}) without (job)) /1024/1024' + - query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024' label: Average unit: MB - - title: "CPU Utilization" - y_label: "CPU Utilization (%)" + - title: "CPU Usage" + y_label: "Cores" required_metrics: - container_cpu_usage_seconds_total weight: 1 queries: - - query_range: 'sum(avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="%{ci_environment_slug}"}[2m])) without (job)) * 100' + - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)' label: Average - unit: "%" \ No newline at end of file + unit: "cores" \ No newline at end of file -- cgit v1.2.3 From 15966c309850ac3377a9b8770efe3cd5033273ed Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Wed, 28 Feb 2018 00:06:22 -0500 Subject: Update docs --- doc/user/project/integrations/prometheus_library/kubernetes.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md index 02adc562028..f30e19c8209 100644 --- a/doc/user/project/integrations/prometheus_library/kubernetes.md +++ b/doc/user/project/integrations/prometheus_library/kubernetes.md @@ -13,8 +13,8 @@ integration services must be enabled. | Name | Query | | ---- | ----- | -| Average Memory Usage (MB) | (sum(avg(container_memory_usage_bytes{container_name!="POD",environment="%{ci_environment_slug}"}) without (job))) / count(avg(container_memory_usage_bytes{container_name!="POD",environment="%{ci_environment_slug}"}) without (job)) /1024/1024 | -| Average CPU Utilization (%) | sum(avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="%{ci_environment_slug}"}[2m])) without (job)) * 100 | +| Average Memory Usage (MB) | avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024 | +| Average CPU Utilization (%) | avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) | ## Configuring Prometheus to monitor for Kubernetes node metrics @@ -29,5 +29,3 @@ Prometheus server up and running. You have two options here: In order to isolate and only display relevant CPU and Memory metrics for a given environment, GitLab needs a method to detect which containers it is running. Because these metrics are tracked at the container level, traditional Kubernetes labels are not available. Instead, the [Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) or [DaemonSet](https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/) name should begin with [CI_ENVIRONMENT_SLUG](../../../../ci/variables/README.md#predefined-variables-environment-variables). It can be followed by a `-` and additional content if desired. For example, a deployment name of `review-homepage-5620p5` would match the `review/homepage` environment. - -If you are using [GitLab Auto-Deploy](../../../../ci/autodeploy/index.md) and one of the two [provided Kubernetes monitoring solutions](../prometheus.md#getting-started-with-prometheus-monitoring), the `environment` label will be automatically added. -- cgit v1.2.3 From 9b9af870f2a94a20b91753d62fcfe65c4d4f977e Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Wed, 28 Feb 2018 14:55:15 -0500 Subject: Final updates for metrics --- config/prometheus/additional_metrics.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/prometheus/additional_metrics.yml b/config/prometheus/additional_metrics.yml index 1628af9c38d..c4f60eb2687 100644 --- a/config/prometheus/additional_metrics.yml +++ b/config/prometheus/additional_metrics.yml @@ -140,7 +140,7 @@ priority: 5 metrics: - title: "Memory Usage" - y_label: "Memory Usage (MB)" + y_label: "Memory Used per Pod" required_metrics: - container_memory_usage_bytes weight: 1 @@ -149,11 +149,11 @@ label: Average unit: MB - title: "CPU Usage" - y_label: "Cores" + y_label: "Cores per Pod" required_metrics: - container_cpu_usage_seconds_total weight: 1 queries: - - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)' + - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))' label: Average unit: "cores" \ No newline at end of file -- cgit v1.2.3 From 79a6be045122ca3fc91f6911ce8a6c6712fafbc7 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Wed, 28 Feb 2018 14:56:28 -0500 Subject: Update docs --- doc/user/project/integrations/prometheus_library/kubernetes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md index f30e19c8209..3a8317cc6b9 100644 --- a/doc/user/project/integrations/prometheus_library/kubernetes.md +++ b/doc/user/project/integrations/prometheus_library/kubernetes.md @@ -14,7 +14,7 @@ integration services must be enabled. | Name | Query | | ---- | ----- | | Average Memory Usage (MB) | avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024 | -| Average CPU Utilization (%) | avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) | +| Average CPU Utilization (%) | avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name)) | ## Configuring Prometheus to monitor for Kubernetes node metrics -- cgit v1.2.3 From 3b2654885b9d06044bcd4a5bc7678a3f0daf2a36 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Wed, 28 Feb 2018 15:08:15 -0500 Subject: Further updates to docs --- doc/user/project/integrations/prometheus_library/kubernetes.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md index 3a8317cc6b9..492e1dfea5b 100644 --- a/doc/user/project/integrations/prometheus_library/kubernetes.md +++ b/doc/user/project/integrations/prometheus_library/kubernetes.md @@ -16,13 +16,12 @@ integration services must be enabled. | Average Memory Usage (MB) | avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024 | | Average CPU Utilization (%) | avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name)) | -## Configuring Prometheus to monitor for Kubernetes node metrics +## Configuring Prometheus to monitor for Kubernetes metrics -In order for Prometheus to collect Kubernetes metrics, you first must have a -Prometheus server up and running. You have two options here: +Prometheus needs to be deployed into the cluster and configured properly in order to gather Kubernetes metrics. GitLab supports two methods for doing so: -- If you have an Omnibus based GitLab installation within your Kubernetes cluster, you can leverage the bundled Prometheus server to [monitor Kubernetes](../../../../administration/monitoring/prometheus/index.md#configuring-prometheus-to-monitor-kubernetes). -- To configure your own Prometheus server, you can follow the [Prometheus documentation](https://prometheus.io/docs/introduction/overview/) or [our guide](../../../../administration/monitoring/prometheus/index.md#configuring-your-own-prometheus-server-within-kubernetes). +- GitLab [integrates with Kubernetes](../../project/clusters/index.md), and can [deploy Prometheus into a connected cluster](../prometheus.html#managed-prometheus-on-kubernetes). It is automatically configured to collect Kubernetes metrics. +- To configure your own Prometheus server, you can follow the [Prometheus documentation](https://prometheus.io/docs/introduction/overview/). ## Specifying the Environment -- cgit v1.2.3 From 27a838c838853c0acdacc29a9cd264e226e0a304 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Wed, 28 Feb 2018 16:10:33 -0500 Subject: Fix doc link --- doc/user/project/integrations/prometheus_library/kubernetes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md index 492e1dfea5b..8ac753c07bf 100644 --- a/doc/user/project/integrations/prometheus_library/kubernetes.md +++ b/doc/user/project/integrations/prometheus_library/kubernetes.md @@ -20,7 +20,7 @@ integration services must be enabled. Prometheus needs to be deployed into the cluster and configured properly in order to gather Kubernetes metrics. GitLab supports two methods for doing so: -- GitLab [integrates with Kubernetes](../../project/clusters/index.md), and can [deploy Prometheus into a connected cluster](../prometheus.html#managed-prometheus-on-kubernetes). It is automatically configured to collect Kubernetes metrics. +- GitLab [integrates with Kubernetes](../../clusters/index.md), and can [deploy Prometheus into a connected cluster](../prometheus.html#managed-prometheus-on-kubernetes). It is automatically configured to collect Kubernetes metrics. - To configure your own Prometheus server, you can follow the [Prometheus documentation](https://prometheus.io/docs/introduction/overview/). ## Specifying the Environment -- cgit v1.2.3 From 812a2c193b8c92e9bd5e640e806df9cbe929e9d6 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Tue, 5 Dec 2017 22:27:23 -0600 Subject: Add project export API documentation --- doc/api/project_import_export.md | 78 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md index e442442c750..7c2c619b97b 100644 --- a/doc/api/project_import_export.md +++ b/doc/api/project_import_export.md @@ -4,6 +4,84 @@ [See also the project import/export documentation](../user/project/settings/import_export.md) +## Export start + +Start a new export. + +```http +POST /projects/:id/export +``` + +| 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 | + +```console +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/export +``` + +```json +{ + "message": "202 Accepted" +} +``` + +## Export status + +Get the status of export. + +```http +GET /projects/:id/export +``` + +| 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 | + +```console +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/1/export +``` + +Status can be one of `none`, `started`, or `finished`. + +```json +{ + "id": 1, + "description": "Itaque perspiciatis minima aspernatur corporis consequatur.", + "name": "Gitlab Test", + "name_with_namespace": "Gitlab Org / Gitlab Test", + "path": "gitlab-test", + "path_with_namespace": "gitlab-org/gitlab-test", + "created_at": "2017-08-29T04:36:44.383Z", + "export_status": "finished", + "_links": { + "api_url": "https://gitlab.example.com/api/v4/projects/1/export/download", + "web_url": "https://gitlab.example.com/gitlab-org/gitlab-test/download_export", + } +} +``` + +## Export download + +Download the finished export. + +```http +GET /projects/:id/export +``` + +| 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 | + +```console +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --remote-header-name --remote-name https://gitlab.example.com/api/v4/projects/5/export/download +``` + +```console +ls *export.tar.gz +2017-12-05_22-11-148_namespace_project_export.tar.gz +``` + ## Import a file ```http -- cgit v1.2.3 From 0bf6ed2e367a5f110405336880f33443c47848f2 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Tue, 5 Dec 2017 22:27:45 -0600 Subject: Add project export API schema --- .../public_api/v4/project/export_status.json | 17 +++++++++++++++++ .../api/schemas/public_api/v4/project/identity.json | 21 +++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 spec/fixtures/api/schemas/public_api/v4/project/export_status.json create mode 100644 spec/fixtures/api/schemas/public_api/v4/project/identity.json diff --git a/spec/fixtures/api/schemas/public_api/v4/project/export_status.json b/spec/fixtures/api/schemas/public_api/v4/project/export_status.json new file mode 100644 index 00000000000..d24a6f93f4b --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/project/export_status.json @@ -0,0 +1,17 @@ +{ + "type": "object", + "allOf": [ + { "$ref": "identity.json" }, + { + "required": [ + "export_status" + ], + "properties": { + "export_status": { + "type": "string", + "enum": ["none", "started", "finished"] + } + } + } + ] +} diff --git a/spec/fixtures/api/schemas/public_api/v4/project/identity.json b/spec/fixtures/api/schemas/public_api/v4/project/identity.json new file mode 100644 index 00000000000..e35ab023d44 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/project/identity.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "required": [ + "id", + "description", + "name", + "name_with_namespace", + "path", + "path_with_namespace", + "created_at" + ], + "properties": { + "id": { "type": "integer" }, + "description": { "type": ["string", "null"] }, + "name": { "type": "string" }, + "name_with_namespace": { "type": "string" }, + "path": { "type": "string" }, + "path_with_namespace": { "type": "string" }, + "created_at": { "type": "date" } + } +} -- cgit v1.2.3 From b20408372b6811520edc3e35702bfe9b053b013b Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Tue, 5 Dec 2017 22:28:12 -0600 Subject: Add project export API tests --- spec/requests/api/project_export_spec.rb | 290 +++++++++++++++++++++++++++++++ 1 file changed, 290 insertions(+) create mode 100644 spec/requests/api/project_export_spec.rb diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb new file mode 100644 index 00000000000..146bf3fd648 --- /dev/null +++ b/spec/requests/api/project_export_spec.rb @@ -0,0 +1,290 @@ +require 'spec_helper' + +describe API::ProjectExport do + set(:project) { create(:project) } + set(:project_none) { create(:project, path: 'export-none') } + set(:project_started) { create(:project, path: 'export-started') } + set(:project_finished) { create(:project, path: 'export-finished') } + set(:user) { create(:user) } + set(:admin) { create(:admin) } + + let(:path) { "/projects/#{project.id}/export" } + let(:path_none) { "/projects/#{project_none.id}/export" } + let(:path_started) { "/projects/#{project_started.id}/export" } + let(:path_finished) { "/projects/#{project_finished.id}/export" } + + let(:download_path) { "/projects/#{project.id}/export/download" } + let(:download_path_none) { "/projects/#{project_none.id}/export/download" } + let(:download_path_started) { "/projects/#{project_started.id}/export/download" } + let(:download_path_finished) { "/projects/#{project_finished.id}/export/download" } + + let(:export_path) { "#{Dir.tmpdir}/project_export_spec" } + + before do + allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) + + # simulate exporting work directory + FileUtils.mkdir_p File.join(project_started.export_path, 'securerandom-hex') + + # simulate exported + FileUtils.mkdir_p project_finished.export_path + FileUtils.touch File.join(project_finished.export_path, '_export.tar.gz') + end + + after do + FileUtils.rm_rf(export_path, secure: true) + end + + shared_examples_for 'when project export is disabled' do + before do + stub_application_setting(project_export_enabled?: false) + end + + it_behaves_like '404 response' + end + + describe 'GET /projects/:project_id/export' do + shared_examples_for 'get project export status not found' do + it_behaves_like '404 response' do + let(:request) { get api(path, user) } + end + end + + shared_examples_for 'get project export status denied' do + it_behaves_like '403 response' do + let(:request) { get api(path, user) } + end + end + + shared_examples_for 'get project export status ok' do + it 'is none' do + get api(path_none, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/project/export_status') + expect(json_response['export_status']).to eq('none') + end + + it 'is started' do + get api(path_started, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/project/export_status') + expect(json_response['export_status']).to eq('started') + end + + it 'is finished' do + get api(path_finished, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/project/export_status') + expect(json_response['export_status']).to eq('finished') + end + end + + it_behaves_like 'when project export is disabled' do + let(:request) { get api(path, admin) } + end + + context 'when project export is enabled' do + context 'when user is an admin' do + let(:user) { admin } + + it_behaves_like 'get project export status ok' + end + + context 'when user is a master' do + before do + project.add_master(user) + project_none.add_master(user) + project_started.add_master(user) + project_finished.add_master(user) + end + + it_behaves_like 'get project export status ok' + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it_behaves_like 'get project export status denied' + end + + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it_behaves_like 'get project export status denied' + end + + context 'when user is a guest' do + before do + project.add_guest(user) + end + + it_behaves_like 'get project export status denied' + end + + context 'when user is not a member' do + it_behaves_like 'get project export status not found' + end + end + end + + describe 'GET /projects/:project_id/export/download' do + shared_examples_for 'get project export download not found' do + it_behaves_like '404 response' do + let(:request) { get api(download_path, user) } + end + end + + shared_examples_for 'get project export download denied' do + it_behaves_like '403 response' do + let(:request) { get api(download_path, user) } + end + end + + shared_examples_for 'get project export download' do + it_behaves_like '404 response' do + let(:request) { get api(download_path_none, user) } + end + + it_behaves_like '404 response' do + let(:request) { get api(download_path_started, user) } + end + + it 'downloads' do + get api(download_path_finished, user) + + expect(response).to have_gitlab_http_status(200) + end + end + + it_behaves_like 'when project export is disabled' do + let(:request) { get api(download_path, admin) } + end + + context 'when project export is enabled' do + context 'when user is an admin' do + let(:user) { admin } + + it_behaves_like 'get project export download' + end + + context 'when user is a master' do + before do + project.add_master(user) + project_none.add_master(user) + project_started.add_master(user) + project_finished.add_master(user) + end + + it_behaves_like 'get project export download' + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it_behaves_like 'get project export download denied' + end + + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it_behaves_like 'get project export download denied' + end + + context 'when user is a guest' do + before do + project.add_guest(user) + end + + it_behaves_like 'get project export download denied' + end + + context 'when user is not a member' do + it_behaves_like 'get project export download not found' + end + end + end + + describe 'POST /projects/:project_id/export' do + shared_examples_for 'post project export start not found' do + it_behaves_like '404 response' do + let(:request) { post api(path, user) } + end + end + + shared_examples_for 'post project export start denied' do + it_behaves_like '403 response' do + let(:request) { post api(path, user) } + end + end + + shared_examples_for 'post project export start' do + it 'starts' do + post api(path, user) + + expect(response).to have_gitlab_http_status(202) + end + end + + it_behaves_like 'when project export is disabled' do + let(:request) { post api(path, admin) } + end + + context 'when project export is enabled' do + context 'when user is an admin' do + let(:user) { admin } + + it_behaves_like 'post project export start' + end + + context 'when user is a master' do + before do + project.add_master(user) + project_none.add_master(user) + project_started.add_master(user) + project_finished.add_master(user) + end + + it_behaves_like 'post project export start' + end + + context 'when user is a developer' do + before do + project.add_developer(user) + end + + it_behaves_like 'post project export start denied' + end + + context 'when user is a reporter' do + before do + project.add_reporter(user) + end + + it_behaves_like 'post project export start denied' + end + + context 'when user is a guest' do + before do + project.add_guest(user) + end + + it_behaves_like 'post project export start denied' + end + + context 'when user is not a member' do + it_behaves_like 'post project export start not found' + end + end + end +end -- cgit v1.2.3 From a4308c53e5db594a6e351b143e11ceba6033cdc7 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Sat, 24 Feb 2018 23:03:29 -0600 Subject: Add project export API entities --- app/models/project.rb | 15 +++++++++++++++ lib/api/entities.rb | 15 +++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/app/models/project.rb b/app/models/project.rb index ba278a49688..791b69bd9ee 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1567,6 +1567,21 @@ class Project < ActiveRecord::Base Dir.glob("#{export_path}/*export.tar.gz").max_by { |f| File.ctime(f) } end + def export_status + if export_in_progress? + :started + elsif export_project_path + :finished + else + :none + end + end + + def export_in_progress? + shared = Gitlab::ImportExport::Shared.new(relative_path: File.join(disk_path, 'work')) + File.directory?(shared.export_path) + end + def remove_exports return nil unless export_path.present? diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 167878ba600..f140563882f 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -91,6 +91,21 @@ module API expose :created_at end + class ProjectExportStatus < ProjectIdentity + include ::API::Helpers::RelatedResourcesHelpers + + expose :export_status + expose :_links, if: lambda { |project, options| project.export_status == :finished } do + expose :api_url do |project| + expose_url(api_v4_projects_export_download_path(id: project.id)) + end + + expose :web_url do |project| + Gitlab::Routing.url_helpers.download_export_project_url(project) + end + end + end + class ProjectImportStatus < ProjectIdentity expose :import_status -- cgit v1.2.3 From c75187df8ce7d376ddc2c7201ce74d45b49fe5d9 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Sat, 2 Dec 2017 13:49:01 -0600 Subject: Add project export API implementation --- app/models/project.rb | 5 ++- .../projects/import_export/export_service.rb | 2 +- lib/api/api.rb | 1 + lib/api/project_export.rb | 36 ++++++++++++++++++++++ lib/gitlab/import_export/importer.rb | 2 +- lib/gitlab/import_export/shared.rb | 18 ++++++++--- 6 files changed, 54 insertions(+), 10 deletions(-) create mode 100644 lib/api/project_export.rb diff --git a/app/models/project.rb b/app/models/project.rb index 791b69bd9ee..62a3b655ad1 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1560,7 +1560,7 @@ class Project < ActiveRecord::Base def export_path return nil unless namespace.present? || hashed_storage?(:repository) - File.join(Gitlab::ImportExport.storage_path, disk_path) + Gitlab::ImportExport::Shared.new(self).archive_path end def export_project_path @@ -1578,8 +1578,7 @@ class Project < ActiveRecord::Base end def export_in_progress? - shared = Gitlab::ImportExport::Shared.new(relative_path: File.join(disk_path, 'work')) - File.directory?(shared.export_path) + Gitlab::ImportExport::Shared.new(self).active_export_count > 0 end def remove_exports diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index fe4e8ea10bf..e26ce3089bb 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -2,7 +2,7 @@ module Projects module ImportExport class ExportService < BaseService def execute(_options = {}) - @shared = Gitlab::ImportExport::Shared.new(relative_path: File.join(project.disk_path, 'work')) + @shared = Gitlab::ImportExport::Shared.new(project) save_all end diff --git a/lib/api/api.rb b/lib/api/api.rb index 754549f72f0..6c96dccc08a 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -138,6 +138,7 @@ module API mount ::API::PagesDomains mount ::API::Pipelines mount ::API::PipelineSchedules + mount ::API::ProjectExport mount ::API::ProjectImport mount ::API::ProjectHooks mount ::API::Projects diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb new file mode 100644 index 00000000000..8b2c5bedc60 --- /dev/null +++ b/lib/api/project_export.rb @@ -0,0 +1,36 @@ +module API + class ProjectExport < Grape::API + before do + not_found! unless Gitlab::CurrentSettings.current_application_settings.project_export_enabled? + authorize_admin_project + end + + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects, requirements: { id: %r{[^/]+} } do + desc 'Get export status' do + success Entities::ProjectExportStatus + end + get ':id/export' do + present user_project, with: Entities::ProjectExportStatus + end + + desc 'Download export' + get ':id/export/download' do + path = user_project.export_project_path + + render_api_error!('404 Not found or has expired', 404) unless path + + present_file!(path, File.basename(path), 'application/gzip') + end + + desc 'Start export' + post ':id/export' do + user_project.add_export_job(current_user: current_user) + + accepted! + end + end + end +end diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb index a00795f553e..070dd630c10 100644 --- a/lib/gitlab/import_export/importer.rb +++ b/lib/gitlab/import_export/importer.rb @@ -9,7 +9,7 @@ module Gitlab @archive_file = project.import_source @current_user = project.creator @project = project - @shared = Gitlab::ImportExport::Shared.new(relative_path: path_with_namespace) + @shared = Gitlab::ImportExport::Shared.new(project) end def execute diff --git a/lib/gitlab/import_export/shared.rb b/lib/gitlab/import_export/shared.rb index b34cafc6876..fef6cfc08f0 100644 --- a/lib/gitlab/import_export/shared.rb +++ b/lib/gitlab/import_export/shared.rb @@ -1,13 +1,17 @@ module Gitlab module ImportExport class Shared - attr_reader :errors, :opts + attr_reader :errors, :project - def initialize(opts) - @opts = opts + def initialize(project) + @project = project @errors = [] end + def active_export_count + Dir[File.join(archive_path, '*')].count { |name| File.directory?(name) } + end + def export_path @export_path ||= Gitlab::ImportExport.export_path(relative_path: relative_path) end @@ -31,11 +35,15 @@ module Gitlab private def relative_path - File.join(opts[:relative_path], SecureRandom.hex) + File.join(relative_archive_path, SecureRandom.hex) end def relative_archive_path - File.join(opts[:relative_path], '..') + if @project.is_a?(Project) + @project.disk_path + else + @project[:relative_path] + end end def error_out(message, caller) -- cgit v1.2.3 From 3b474052b82e5e82df8f34a2bdb7e305118aef0f Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Mon, 11 Dec 2017 11:50:07 -0600 Subject: Add project export API changelog --- changelogs/unreleased/29130-api-project-export.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/29130-api-project-export.yml diff --git a/changelogs/unreleased/29130-api-project-export.yml b/changelogs/unreleased/29130-api-project-export.yml new file mode 100644 index 00000000000..7dee349232a --- /dev/null +++ b/changelogs/unreleased/29130-api-project-export.yml @@ -0,0 +1,5 @@ +--- +title: Add project export API +merge_request: 15860 +author: Travis Miller +type: added -- cgit v1.2.3 From b4fc556f10b9bed9af9a7d57de5b8fcdfa584ad9 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Sat, 24 Feb 2018 21:12:11 -0600 Subject: review: fix docs --- doc/api/project_import_export.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md index 7c2c619b97b..c6923f2a439 100644 --- a/doc/api/project_import_export.md +++ b/doc/api/project_import_export.md @@ -1,10 +1,10 @@ -# Project import API +# Project import/export API [Introduced][ce-41899] in GitLab 10.6 [See also the project import/export documentation](../user/project/settings/import_export.md) -## Export start +## Schedule an export Start a new export. @@ -66,7 +66,7 @@ Status can be one of `none`, `started`, or `finished`. Download the finished export. ```http -GET /projects/:id/export +GET /projects/:id/export/download ``` | Attribute | Type | Required | Description | -- cgit v1.2.3 From b1e3237677f9cdc4e0e792f26dbc3fc642c7ef28 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Sat, 24 Feb 2018 21:18:46 -0600 Subject: review: add feature introduction detail --- lib/api/project_export.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index 8b2c5bedc60..4041faae971 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -10,13 +10,16 @@ module API end resource :projects, requirements: { id: %r{[^/]+} } do desc 'Get export status' do + detail 'This feature was introduced in GitLab 10.6.' success Entities::ProjectExportStatus end get ':id/export' do present user_project, with: Entities::ProjectExportStatus end - desc 'Download export' + desc 'Download export' do + detail 'This feature was introduced in GitLab 10.6.' + end get ':id/export/download' do path = user_project.export_project_path @@ -25,7 +28,9 @@ module API present_file!(path, File.basename(path), 'application/gzip') end - desc 'Start export' + desc 'Start export' do + detail 'This feature was introduced in GitLab 10.6.' + end post ':id/export' do user_project.add_export_job(current_user: current_user) -- cgit v1.2.3 From 737b7e3e7d8098493804f2c7261eec58268af2d1 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Sat, 24 Feb 2018 22:43:16 -0600 Subject: review: normalize shared initizlier arguments --- lib/gitlab/import_export/shared.rb | 11 ++++++----- spec/lib/gitlab/import_export/avatar_restorer_spec.rb | 2 +- spec/lib/gitlab/import_export/avatar_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/file_importer_spec.rb | 2 +- spec/lib/gitlab/import_export/fork_spec.rb | 2 +- spec/lib/gitlab/import_export/project_tree_restorer_spec.rb | 2 +- spec/lib/gitlab/import_export/project_tree_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/reader_spec.rb | 2 +- spec/lib/gitlab/import_export/repo_restorer_spec.rb | 2 +- spec/lib/gitlab/import_export/repo_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/uploads_restorer_spec.rb | 2 +- spec/lib/gitlab/import_export/uploads_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/version_checker_spec.rb | 2 +- spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/wiki_restorer_spec.rb | 2 +- 15 files changed, 20 insertions(+), 19 deletions(-) diff --git a/lib/gitlab/import_export/shared.rb b/lib/gitlab/import_export/shared.rb index fef6cfc08f0..012228ca273 100644 --- a/lib/gitlab/import_export/shared.rb +++ b/lib/gitlab/import_export/shared.rb @@ -1,10 +1,11 @@ module Gitlab module ImportExport class Shared - attr_reader :errors, :project + attr_reader :errors, :project, :opts - def initialize(project) + def initialize(project, opts = {}) @project = project + @opts = opts @errors = [] end @@ -39,10 +40,10 @@ module Gitlab end def relative_archive_path - if @project.is_a?(Project) - @project.disk_path + if @opts[:relative_path] + @opts[:relative_path] else - @project[:relative_path] + @project.disk_path end end diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb index a93a921e459..5b1d9cd1e6c 100644 --- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::AvatarRestorer do include UploadHelpers - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') } + let(:shared) { Gitlab::ImportExport::Shared.new(project) } let(:project) { create(:project) } before do diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb index 3fb5ddde8b5..e688929b216 100644 --- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::ImportExport::AvatarSaver do - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') } + let(:shared) { Gitlab::ImportExport::Shared.new(project) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:project_with_avatar) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } let(:project) { create(:project) } diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb index 5cdc5138fda..a8447679ffd 100644 --- a/spec/lib/gitlab/import_export/file_importer_spec.rb +++ b/spec/lib/gitlab/import_export/file_importer_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::ImportExport::FileImporter do - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') } + let(:shared) { Gitlab::ImportExport::Shared.new(nil, relative_path: 'test') } let(:export_path) { "#{Dir.tmpdir}/file_importer_spec" } let(:valid_file) { "#{shared.export_path}/valid.json" } let(:symlink_file) { "#{shared.export_path}/invalid.json" } diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb index cfb15ee7e8b..49690c1f8b5 100644 --- a/spec/lib/gitlab/import_export/fork_spec.rb +++ b/spec/lib/gitlab/import_export/fork_spec.rb @@ -7,7 +7,7 @@ describe 'forked project import' do let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } let!(:project) { create(:project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { Gitlab::ImportExport::Shared.new(project) } let(:forked_from_project) { create(:project, :repository) } let(:forked_project) { fork_project(project_with_repo, nil, repository: true) } let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) } 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..c0b70535433 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -7,7 +7,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do @user = create(:user) RSpec::Mocks.with_temporary_scope do - @shared = Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') + @shared = Gitlab::ImportExport::Shared.new(@project) allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/') @project = create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') 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..a1b7a996ac5 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ImportExport::ProjectTreeSaver do describe 'saves the project tree into a json object' do - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { Gitlab::ImportExport::Shared.new(project) } let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb index e9f5273725d..1ef024d3078 100644 --- a/spec/lib/gitlab/import_export/reader_spec.rb +++ b/spec/lib/gitlab/import_export/reader_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::ImportExport::Reader do - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') } + let(:shared) { Gitlab::ImportExport::Shared.new(nil) } let(:test_config) { 'spec/support/import_export/import_export.yml' } let(:project_tree_hash) do { diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb index c49af602a01..16aa1413fd8 100644 --- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::ImportExport::RepoRestorer do let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } let!(:project) { create(:project) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { Gitlab::ImportExport::Shared.new(project) } let(:bundler) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) } let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } let(:restorer) do diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb index 44f972fe530..29f94906ec7 100644 --- a/spec/lib/gitlab/import_export/repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::ImportExport::RepoSaver do let(:user) { create(:user) } let!(:project) { create(:project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { Gitlab::ImportExport::Shared.new(project) } let(:bundler) { described_class.new(project: project, shared: shared) } before do diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb index 8a3a244be21..d466bfc7667 100644 --- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::UploadsRestorer do describe 'bundle a project Git repo' do let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { Gitlab::ImportExport::Shared.new(project) } before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb index 177036c109b..1714badfc3d 100644 --- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::ImportExport::UploadsSaver do describe 'bundle a project Git repo' do let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" } let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { Gitlab::ImportExport::Shared.new(project) } before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb index e7d50f75682..1388fca465e 100644 --- a/spec/lib/gitlab/import_export/version_checker_spec.rb +++ b/spec/lib/gitlab/import_export/version_checker_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' include ImportExport::CommonUtil describe Gitlab::ImportExport::VersionChecker do - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') } + let(:shared) { Gitlab::ImportExport::Shared.new(nil, relative_path: '') } describe 'bundle a project Git repo' do let(:version) { Gitlab::ImportExport.version } diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb index 1d1e7e7f89a..d445c69a897 100644 --- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::ImportExport::WikiRepoSaver do let(:user) { create(:user) } let!(:project) { create(:project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { Gitlab::ImportExport::Shared.new(project) } let(:wiki_bundler) { described_class.new(project: project, shared: shared) } let!(:project_wiki) { ProjectWiki.new(project, user) } diff --git a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb index 81b654e9c5f..4fcb1a0e13f 100644 --- a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::ImportExport::WikiRestorer do let!(:project_without_wiki) { create(:project) } let!(:project) { create(:project) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) } + let(:shared) { Gitlab::ImportExport::Shared.new(project) } let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_with_wiki, shared: shared) } let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } let(:restorer) do -- cgit v1.2.3 From 0d8aadeb9df3bd5e4f18e8c22510c2220b74420d Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Tue, 27 Feb 2018 08:11:13 -0600 Subject: eliminate need or opts argument in shared object --- lib/gitlab/import_export/shared.rb | 11 +++-------- spec/lib/gitlab/import_export/file_importer_spec.rb | 3 ++- spec/lib/gitlab/import_export/project_tree_restorer_spec.rb | 2 +- spec/lib/gitlab/import_export/version_checker_spec.rb | 3 ++- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/lib/gitlab/import_export/shared.rb b/lib/gitlab/import_export/shared.rb index 012228ca273..3d3d998a6a3 100644 --- a/lib/gitlab/import_export/shared.rb +++ b/lib/gitlab/import_export/shared.rb @@ -1,11 +1,10 @@ module Gitlab module ImportExport class Shared - attr_reader :errors, :project, :opts + attr_reader :errors, :project - def initialize(project, opts = {}) + def initialize(project) @project = project - @opts = opts @errors = [] end @@ -40,11 +39,7 @@ module Gitlab end def relative_archive_path - if @opts[:relative_path] - @opts[:relative_path] - else - @project.disk_path - end + @project.disk_path end def error_out(message, caller) diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb index a8447679ffd..58b9fb06cc5 100644 --- a/spec/lib/gitlab/import_export/file_importer_spec.rb +++ b/spec/lib/gitlab/import_export/file_importer_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::ImportExport::FileImporter do - let(:shared) { Gitlab::ImportExport::Shared.new(nil, relative_path: 'test') } + let(:shared) { Gitlab::ImportExport::Shared.new(nil) } let(:export_path) { "#{Dir.tmpdir}/file_importer_spec" } let(:valid_file) { "#{shared.export_path}/valid.json" } let(:symlink_file) { "#{shared.export_path}/invalid.json" } @@ -12,6 +12,7 @@ describe Gitlab::ImportExport::FileImporter do stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0) allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true) + allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('test') allow(SecureRandom).to receive(:hex).and_return('abcd') setup_files end 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 c0b70535433..974297fb078 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -259,7 +259,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do context 'Light JSON' do let(:user) { create(:user) } - let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') } + let(:shared) { Gitlab::ImportExport::Shared.new(project) } let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') } let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) } let(:restored_project_json) { project_tree_restorer.restore } diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb index 1388fca465e..49d857d9483 100644 --- a/spec/lib/gitlab/import_export/version_checker_spec.rb +++ b/spec/lib/gitlab/import_export/version_checker_spec.rb @@ -2,12 +2,13 @@ require 'spec_helper' include ImportExport::CommonUtil describe Gitlab::ImportExport::VersionChecker do - let(:shared) { Gitlab::ImportExport::Shared.new(nil, relative_path: '') } + let(:shared) { Gitlab::ImportExport::Shared.new(nil) } describe 'bundle a project Git repo' do let(:version) { Gitlab::ImportExport.version } before do + allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('') allow(File).to receive(:open).and_return(version) end -- cgit v1.2.3 From 2b176137c9b5617d38ff89b6f8ed4636018916c9 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Wed, 28 Feb 2018 21:25:54 -0600 Subject: review: instantiate shared import/export object in project method --- app/models/project.rb | 8 ++++++-- app/services/projects/import_export/export_service.rb | 2 +- lib/gitlab/import_export/importer.rb | 2 +- spec/lib/gitlab/import_export/avatar_restorer_spec.rb | 2 +- spec/lib/gitlab/import_export/avatar_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/fork_spec.rb | 2 +- spec/lib/gitlab/import_export/project_tree_restorer_spec.rb | 6 +++--- spec/lib/gitlab/import_export/project_tree_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/repo_restorer_spec.rb | 2 +- spec/lib/gitlab/import_export/repo_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/uploads_restorer_spec.rb | 2 +- spec/lib/gitlab/import_export/uploads_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/wiki_restorer_spec.rb | 2 +- 14 files changed, 21 insertions(+), 17 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 62a3b655ad1..190473aa196 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1557,10 +1557,14 @@ class Project < ActiveRecord::Base end end + def import_export + @import_export ||= Gitlab::ImportExport::Shared.new(self) + end + def export_path return nil unless namespace.present? || hashed_storage?(:repository) - Gitlab::ImportExport::Shared.new(self).archive_path + import_export.archive_path end def export_project_path @@ -1578,7 +1582,7 @@ class Project < ActiveRecord::Base end def export_in_progress? - Gitlab::ImportExport::Shared.new(self).active_export_count > 0 + import_export.active_export_count > 0 end def remove_exports diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index e26ce3089bb..2af228eff05 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -2,7 +2,7 @@ module Projects module ImportExport class ExportService < BaseService def execute(_options = {}) - @shared = Gitlab::ImportExport::Shared.new(project) + @shared = project.import_export save_all end diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb index 070dd630c10..8b780cbe3a1 100644 --- a/lib/gitlab/import_export/importer.rb +++ b/lib/gitlab/import_export/importer.rb @@ -9,7 +9,7 @@ module Gitlab @archive_file = project.import_source @current_user = project.creator @project = project - @shared = Gitlab::ImportExport::Shared.new(project) + @shared = project.import_export end def execute diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb index 5b1d9cd1e6c..c981386ef3d 100644 --- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::AvatarRestorer do include UploadHelpers - let(:shared) { Gitlab::ImportExport::Shared.new(project) } + let(:shared) { project.import_export } let(:project) { create(:project) } before do diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb index e688929b216..78055f3970a 100644 --- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::ImportExport::AvatarSaver do - let(:shared) { Gitlab::ImportExport::Shared.new(project) } + let(:shared) { project.import_export } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:project_with_avatar) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } let(:project) { create(:project) } diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb index 49690c1f8b5..77aa37a1992 100644 --- a/spec/lib/gitlab/import_export/fork_spec.rb +++ b/spec/lib/gitlab/import_export/fork_spec.rb @@ -7,7 +7,7 @@ describe 'forked project import' do let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } let!(:project) { create(:project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(project) } + let(:shared) { project.import_export } let(:forked_from_project) { create(:project, :repository) } let(:forked_project) { fork_project(project_with_repo, nil, repository: true) } let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) } 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 974297fb078..ae4bdc7be00 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -7,9 +7,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do @user = create(:user) RSpec::Mocks.with_temporary_scope do - @shared = Gitlab::ImportExport::Shared.new(@project) - allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/') @project = create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') + @shared = @project.import_export + allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/') allow_any_instance_of(Repository).to receive(:fetch_ref).and_return(true) allow_any_instance_of(Gitlab::Git::Repository).to receive(:branch_exists?).and_return(false) @@ -259,7 +259,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do context 'Light JSON' do let(:user) { create(:user) } - let(:shared) { Gitlab::ImportExport::Shared.new(project) } + let(:shared) { project.import_export } let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') } let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) } let(:restored_project_json) { project_tree_restorer.restore } 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 a1b7a996ac5..4daef697c1a 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ImportExport::ProjectTreeSaver do describe 'saves the project tree into a json object' do - let(:shared) { Gitlab::ImportExport::Shared.new(project) } + let(:shared) { project.import_export } let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb index 16aa1413fd8..3fdb40996bf 100644 --- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::ImportExport::RepoRestorer do let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } let!(:project) { create(:project) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(project) } + let(:shared) { project.import_export } let(:bundler) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) } let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } let(:restorer) do diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb index 29f94906ec7..74859d81c71 100644 --- a/spec/lib/gitlab/import_export/repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::ImportExport::RepoSaver do let(:user) { create(:user) } let!(:project) { create(:project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(project) } + let(:shared) { project.import_export } let(:bundler) { described_class.new(project: project, shared: shared) } before do diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb index d466bfc7667..e919a7d9cbb 100644 --- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::UploadsRestorer do describe 'bundle a project Git repo' do let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(project) } + let(:shared) { project.import_export } before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb index 1714badfc3d..8ac37850328 100644 --- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::ImportExport::UploadsSaver do describe 'bundle a project Git repo' do let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" } let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } - let(:shared) { Gitlab::ImportExport::Shared.new(project) } + let(:shared) { project.import_export } before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb index d445c69a897..fbd00cf5ce5 100644 --- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::ImportExport::WikiRepoSaver do let(:user) { create(:user) } let!(:project) { create(:project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(project) } + let(:shared) { project.import_export } let(:wiki_bundler) { described_class.new(project: project, shared: shared) } let!(:project_wiki) { ProjectWiki.new(project, user) } diff --git a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb index 4fcb1a0e13f..575ae837d64 100644 --- a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::ImportExport::WikiRestorer do let!(:project_without_wiki) { create(:project) } let!(:project) { create(:project) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { Gitlab::ImportExport::Shared.new(project) } + let(:shared) { project.import_export } let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_with_wiki, shared: shared) } let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } let(:restorer) do -- cgit v1.2.3 From 60642307c7bb0fe1917324966ed01393f3d25076 Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Wed, 28 Feb 2018 21:30:47 -0600 Subject: review: add note about _links present when export is finished --- doc/api/project_import_export.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md index c6923f2a439..677765368a8 100644 --- a/doc/api/project_import_export.md +++ b/doc/api/project_import_export.md @@ -44,6 +44,8 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/a Status can be one of `none`, `started`, or `finished`. +`_links` are only present when export has finished. + ```json { "id": 1, -- cgit v1.2.3 From 2f732a55e1172eac037efa183fb825ba1d533f3a Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Wed, 28 Feb 2018 21:32:03 -0600 Subject: review: remove un-necessary current_application_settings --- lib/api/project_export.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/project_export.rb b/lib/api/project_export.rb index 4041faae971..6ec2626df1a 100644 --- a/lib/api/project_export.rb +++ b/lib/api/project_export.rb @@ -1,7 +1,7 @@ module API class ProjectExport < Grape::API before do - not_found! unless Gitlab::CurrentSettings.current_application_settings.project_export_enabled? + not_found! unless Gitlab::CurrentSettings.project_export_enabled? authorize_admin_project end -- cgit v1.2.3 From e20061218c039859ecff6f89d709c7132ad7d18f Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Wed, 28 Feb 2018 21:33:14 -0600 Subject: review: specifying path in spec project creation is not necessary --- spec/requests/api/project_export_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb index 146bf3fd648..fbed527963f 100644 --- a/spec/requests/api/project_export_spec.rb +++ b/spec/requests/api/project_export_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' describe API::ProjectExport do set(:project) { create(:project) } - set(:project_none) { create(:project, path: 'export-none') } - set(:project_started) { create(:project, path: 'export-started') } - set(:project_finished) { create(:project, path: 'export-finished') } + set(:project_none) { create(:project) } + set(:project_started) { create(:project) } + set(:project_finished) { create(:project) } set(:user) { create(:user) } set(:admin) { create(:admin) } -- cgit v1.2.3 From 7b31095ef85982af2aa3bcc5861c5c22e284b15c Mon Sep 17 00:00:00 2001 From: Adam Pahlevi Date: Sun, 5 Feb 2017 14:25:06 +0700 Subject: /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 - app/services/slash_commands/interpret_service.rb | 336 ++++++++++ changelogs/unreleased/wip-new-mr-cmd.yml | 4 + .../services/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 -- cgit v1.2.3 From cc7b50265a1b5286b18c7627cefd28807d834ea7 Mon Sep 17 00:00:00 2001 From: Christian Couder Date: Mon, 26 Feb 2018 17:58:57 +0000 Subject: perforce.md: add note about repacking --- doc/user/project/import/perforce.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/user/project/import/perforce.md b/doc/user/project/import/perforce.md index aa7508e1e8e..a1ea716b606 100644 --- a/doc/user/project/import/perforce.md +++ b/doc/user/project/import/perforce.md @@ -48,3 +48,9 @@ Here's a few links to get you started: - [git-p4 manual page](https://www.kernel.org/pub/software/scm/git/docs/git-p4.html) - [git-p4 example usage](https://git.wiki.kernel.org/index.php/Git-p4_Usage) - [Git book migration guide](https://git-scm.com/book/en/v2/Git-and-Other-Systems-Migrating-to-Git#_perforce_import) + +Note that `git p4` and `git filter-branch` are not very good at +creating small and efficient Git pack files. So it might be a good +idea to spend time and CPU to properly repack your repository before +sending it for the first time to your GitLab server. See +[this StackOverflow question](https://stackoverflow.com/questions/28720151/git-gc-aggressive-vs-git-repack/). -- cgit v1.2.3 From 6297446d1773c95d86ecd31f591e1829b431f378 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Thu, 1 Mar 2018 11:32:39 -0300 Subject: 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 + app/services/quick_actions/interpret_service.rb | 6 +- app/services/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 -- cgit v1.2.3 From 249400fb4f363d6bda20ff0b58b862896042beed Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 1 Mar 2018 17:48:49 -0600 Subject: Add docs for persistent `ActiveRecord::PendingMigrationError` with Spring --- doc/development/database_debugging.md | 35 +++++++++++++++++++++++++++++++++++ doc/development/rake_tasks.md | 6 ++++++ 2 files changed, 41 insertions(+) diff --git a/doc/development/database_debugging.md b/doc/development/database_debugging.md index 50eb8005b44..32f392f1303 100644 --- a/doc/development/database_debugging.md +++ b/doc/development/database_debugging.md @@ -53,3 +53,38 @@ bundle exec rails db RAILS_ENV=development - `CREATE TABLE board_labels();`: Create a table called `board_labels` - `SELECT * FROM schema_migrations WHERE version = '20170926203418';`: Check if a migration was run - `DELETE FROM schema_migrations WHERE version = '20170926203418';`: Manually remove a migration + + +## FAQ + +### `ActiveRecord::PendingMigrationError` with Spring + +When running specs with the [Spring preloader](./rake_tasks.md#speed-up-tests-rake-tasks-and-migrations), +the test database can get into a corrupted state. Trying to run the migration or +dropping/resetting the test database has no effect. + +```sh +$ bundle exec spring rspec some_spec.rb +... +Failure/Error: ActiveRecord::Migration.maintain_test_schema! + +ActiveRecord::PendingMigrationError: + + + Migrations are pending. To resolve this issue, run: + + bin/rake db:migrate RAILS_ENV=test +# ~/.rvm/gems/ruby-2.3.3/gems/activerecord-4.2.10/lib/active_record/migration.rb:392:in `check_pending!' +... +0 examples, 0 failures, 1 error occurred outside of examples +``` + +To resolve, you can kill the spring server and app that lives between spec runs. + +```sh +$ ps aux | grep spring +eric 87304 1.3 2.9 3080836 482596 ?? Ss 10:12AM 4:08.36 spring app | gitlab | started 6 hours ago | test mode +eric 37709 0.0 0.0 2518640 7524 s006 S Wed11AM 0:00.79 spring server | gitlab | started 29 hours ago +$ kill 87304 +$ kill 37709 +``` diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index dc88ce1522c..fdfa1f10402 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -102,6 +102,12 @@ variable to `1`: export ENABLE_SPRING=1 ``` +Alternatively you can use the following on each spec run, + +``` +bundle exec spring rspec some_spec.rb +``` + ## Compile Frontend Assets You shouldn't ever need to compile frontend assets manually in development, but -- cgit v1.2.3 From 82ec8eafab2aa66eaf6fe7c9bc6a25bfbb291596 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 1 Mar 2018 17:06:42 +0100 Subject: Revert Project.public_or_visible_to_user changes These changes were introduced in MR https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17088. In https://gitlab.com/gitlab-com/infrastructure/issues/3772 we discovered these changes lead to a pretty drastic increase in SQL response timings. We'll revert these changes so we can work on a better solution in the mean time without GitLab.com (or other installations) experiecing reduced performance in the mean time. --- app/finders/snippets_finder.rb | 5 +-- app/models/project.rb | 46 ++++++---------------- .../revert-project-visibility-changes.yml | 5 +++ 3 files changed, 18 insertions(+), 38 deletions(-) create mode 100644 changelogs/unreleased/revert-project-visibility-changes.yml diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index a73c573736e..75e827b7f6e 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -62,9 +62,8 @@ class SnippetsFinder < UnionFinder # Don't return any project related snippets if the user cannot read cross project return table[:id].eq(nil) unless Ability.allowed?(current_user, :read_cross_project) - projects = Project.public_or_visible_to_user(current_user, use_where_in: false) do |part| - part.with_feature_available_for_user(:snippets, current_user) - end.select(:id) + projects = Project.public_or_visible_to_user(current_user) + .with_feature_available_for_user(:snippets, current_user).select(:id) arel_query = Arel::Nodes::SqlLiteral.new(projects.to_sql) table[:project_id].in(arel_query) diff --git a/app/models/project.rb b/app/models/project.rb index ba278a49688..ad4315e1601 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -317,42 +317,18 @@ class Project < ActiveRecord::Base # Returns a collection of projects that is either public or visible to the # logged in user. - # - # A caller may pass in a block to modify individual parts of - # the query, e.g. to apply .with_feature_available_for_user on top of it. - # This is useful for performance as we can stick those additional filters - # at the bottom of e.g. the UNION. - # - # Optionally, turning `use_where_in` off leads to returning a - # relation using #from instead of #where. This can perform much better - # but leads to trouble when used in conjunction with AR's #merge method. - def self.public_or_visible_to_user(user = nil, use_where_in: true, &block) - # If we don't get a block passed, use identity to avoid if/else repetitions - block = ->(part) { part } unless block_given? - - return block.call(public_to_user) unless user - - # If the user is allowed to see all projects, - # we can shortcut and just return. - return block.call(all) if user.full_private_access? - - authorized = user - .project_authorizations - .select(1) - .where('project_authorizations.project_id = projects.id') - authorized_projects = block.call(where('EXISTS (?)', authorized)) - - levels = Gitlab::VisibilityLevel.levels_for_user(user) - visible_projects = block.call(where(visibility_level: levels)) - - # We use a UNION here instead of OR clauses since this results in better - # performance. - union = Gitlab::SQL::Union.new([authorized_projects.select('projects.id'), visible_projects.select('projects.id')]) - - if use_where_in - where("projects.id IN (#{union.to_sql})") # rubocop:disable GitlabSecurity/SqlInjection + def self.public_or_visible_to_user(user = nil) + if user + authorized = user + .project_authorizations + .select(1) + .where('project_authorizations.project_id = projects.id') + + levels = Gitlab::VisibilityLevel.levels_for_user(user) + + where('EXISTS (?) OR projects.visibility_level IN (?)', authorized, levels) else - from("(#{union.to_sql}) AS #{table_name}") + public_to_user end end diff --git a/changelogs/unreleased/revert-project-visibility-changes.yml b/changelogs/unreleased/revert-project-visibility-changes.yml new file mode 100644 index 00000000000..86bab00b9b1 --- /dev/null +++ b/changelogs/unreleased/revert-project-visibility-changes.yml @@ -0,0 +1,5 @@ +--- +title: Revert Project.public_or_visible_to_user changes +merge_request: +author: +type: fixed -- cgit v1.2.3 From 82a32e2763db3196421412a537e9fd344b12c831 Mon Sep 17 00:00:00 2001 From: Andreas Brandl Date: Fri, 2 Mar 2018 09:29:12 +0100 Subject: Apply query changes to snippets only. This applies the changes introduced in `Project.public_or_visible_to_user` to the snippets finder only. We know that for `SnippetsFinder`, this change improves SQL timing from 5/23/25s to 0.7/2/4s (mean/p95/p99). At the same time, the scope was too broad, (negatively) affecting SQL timings in various other places: https://gitlab.com/gitlab-com/infrastructure/issues/3772 With this commit, the snippets dashboard stays usuable as we generally don't run into statement timeouts. In contrast to the earlier change in !17088, the scope of the change is limited to `SnippetsFinder` only, thus not affecting other places. --- app/finders/snippets_finder.rb | 35 ++++++++++++++++++++-- .../revert-project-visibility-changes.yml | 2 +- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index 75e827b7f6e..fc257933e57 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -58,12 +58,43 @@ class SnippetsFinder < UnionFinder .public_or_visible_to_user(current_user) end + # Returns a collection of projects that is either public or visible to the + # logged in user. + # + # A caller may pass in a block to modify individual parts of + # the query, e.g. to apply .with_feature_available_for_user on top of it. + # This is useful for performance as we can stick those additional filters + # at the bottom of e.g. the UNION. + def projects_for_user(&block) + return block.call(Project.public_to_user) unless current_user + + # If the current_user is allowed to see all projects, + # we can shortcut and just return. + return block.call(Project.all) if current_user.full_private_access? + + authorized = current_user + .project_authorizations + .select(1) + .where('project_authorizations.project_id = projects.id') + authorized_projects = block.call(Project.where('EXISTS (?)', authorized)) + + levels = Gitlab::VisibilityLevel.levels_for_user(current_user) + visible_projects = block.call(Project.where(visibility_level: levels)) + + # We use a UNION here instead of OR clauses since this results in better + # performance. + union = Gitlab::SQL::Union.new([authorized_projects.select('projects.id'), visible_projects.select('projects.id')]) + + Project.from("(#{union.to_sql}) AS #{Project.table_name}") + end + def feature_available_projects # Don't return any project related snippets if the user cannot read cross project return table[:id].eq(nil) unless Ability.allowed?(current_user, :read_cross_project) - projects = Project.public_or_visible_to_user(current_user) - .with_feature_available_for_user(:snippets, current_user).select(:id) + projects = projects_for_user do |part| + part.with_feature_available_for_user(:snippets, current_user) + end.select(:id) arel_query = Arel::Nodes::SqlLiteral.new(projects.to_sql) table[:project_id].in(arel_query) diff --git a/changelogs/unreleased/revert-project-visibility-changes.yml b/changelogs/unreleased/revert-project-visibility-changes.yml index 86bab00b9b1..df44fdb79b1 100644 --- a/changelogs/unreleased/revert-project-visibility-changes.yml +++ b/changelogs/unreleased/revert-project-visibility-changes.yml @@ -1,5 +1,5 @@ --- -title: Revert Project.public_or_visible_to_user changes +title: Revert Project.public_or_visible_to_user changes and only apply to snippets merge_request: author: type: fixed -- cgit v1.2.3 From 4927cb75e6ab82500b3d055f35ea621483c224fe Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Mar 2018 14:25:13 +0000 Subject: Remove IDE from CE --- .../ide/components/commit_sidebar/list.vue | 65 ---- .../components/commit_sidebar/list_collapsed.vue | 35 -- .../ide/components/commit_sidebar/list_item.vue | 36 -- app/assets/javascripts/ide/components/ide.vue | 99 ----- .../javascripts/ide/components/ide_context_bar.vue | 108 ------ .../ide/components/ide_project_branches_tree.vue | 47 --- .../ide/components/ide_project_tree.vue | 49 --- .../javascripts/ide/components/ide_repo_tree.vue | 74 ---- .../javascripts/ide/components/ide_side_bar.vue | 114 ------ .../javascripts/ide/components/ide_status_bar.vue | 66 ---- .../javascripts/ide/components/new_branch_form.vue | 108 ------ .../ide/components/new_dropdown/index.vue | 101 ----- .../ide/components/new_dropdown/modal.vue | 112 ------ .../ide/components/new_dropdown/upload.vue | 87 ----- .../ide/components/repo_commit_section.vue | 171 -------- .../ide/components/repo_edit_button.vue | 57 --- .../javascripts/ide/components/repo_editor.vue | 136 ------- .../javascripts/ide/components/repo_file.vue | 165 -------- .../ide/components/repo_file_buttons.vue | 60 --- .../ide/components/repo_loading_file.vue | 42 -- .../ide/components/repo_prev_directory.vue | 32 -- .../javascripts/ide/components/repo_preview.vue | 71 ---- app/assets/javascripts/ide/components/repo_tab.vue | 74 ---- .../javascripts/ide/components/repo_tabs.vue | 27 -- app/assets/javascripts/ide/ide_router.js | 101 ----- app/assets/javascripts/ide/index.js | 31 -- .../javascripts/ide/lib/common/disposable.js | 14 - app/assets/javascripts/ide/lib/common/model.js | 64 --- .../javascripts/ide/lib/common/model_manager.js | 32 -- .../javascripts/ide/lib/decorations/controller.js | 43 -- app/assets/javascripts/ide/lib/diff/controller.js | 71 ---- app/assets/javascripts/ide/lib/diff/diff.js | 30 -- app/assets/javascripts/ide/lib/diff/diff_worker.js | 10 - app/assets/javascripts/ide/lib/editor.js | 110 ------ app/assets/javascripts/ide/lib/editor_options.js | 2 - app/assets/javascripts/ide/monaco_loader.js | 16 - app/assets/javascripts/ide/services/index.js | 47 --- app/assets/javascripts/ide/stores/actions.js | 196 ---------- .../javascripts/ide/stores/actions/branch.js | 43 -- app/assets/javascripts/ide/stores/actions/file.js | 137 ------- .../javascripts/ide/stores/actions/project.js | 27 -- app/assets/javascripts/ide/stores/actions/tree.js | 188 --------- app/assets/javascripts/ide/stores/getters.js | 19 - app/assets/javascripts/ide/stores/index.js | 15 - .../javascripts/ide/stores/mutation_types.js | 46 --- app/assets/javascripts/ide/stores/mutations.js | 70 ---- .../javascripts/ide/stores/mutations/branch.js | 28 -- .../javascripts/ide/stores/mutations/file.js | 74 ---- .../javascripts/ide/stores/mutations/project.js | 23 -- .../javascripts/ide/stores/mutations/tree.js | 36 -- app/assets/javascripts/ide/stores/state.js | 23 -- app/assets/javascripts/ide/stores/utils.js | 177 --------- app/controllers/ide_controller.rb | 6 - app/views/ide/index.html.haml | 11 - config/routes.rb | 2 - config/webpack.config.js | 1 - .../projects/tree/create_directory_spec.rb | 57 --- spec/features/projects/tree/create_file_spec.rb | 47 --- spec/features/projects/tree/upload_file_spec.rb | 53 --- .../commit_sidebar/list_collapsed_spec.js | 33 -- .../components/commit_sidebar/list_item_spec.js | 53 --- .../repo/components/commit_sidebar/list_spec.js | 59 --- .../repo/components/ide_context_bar_spec.js | 49 --- .../repo/components/ide_repo_tree_spec.js | 63 --- .../repo/components/ide_side_bar_spec.js | 43 -- spec/javascripts/repo/components/ide_spec.js | 39 -- .../repo/components/new_branch_form_spec.js | 114 ------ .../repo/components/new_dropdown/index_spec.js | 77 ---- .../repo/components/new_dropdown/modal_spec.js | 237 ----------- .../repo/components/new_dropdown/upload_spec.js | 158 -------- .../repo/components/repo_commit_section_spec.js | 140 ------- .../repo/components/repo_edit_button_spec.js | 83 ---- .../repo/components/repo_editor_spec.js | 60 --- .../repo/components/repo_file_buttons_spec.js | 49 --- spec/javascripts/repo/components/repo_file_spec.js | 98 ----- .../repo/components/repo_loading_file_spec.js | 63 --- .../repo/components/repo_prev_directory_spec.js | 45 --- .../repo/components/repo_preview_spec.js | 37 -- spec/javascripts/repo/components/repo_tab_spec.js | 108 ------ spec/javascripts/repo/components/repo_tabs_spec.js | 37 -- spec/javascripts/repo/helpers.js | 16 - .../javascripts/repo/lib/common/disposable_spec.js | 44 --- .../repo/lib/common/model_manager_spec.js | 81 ---- spec/javascripts/repo/lib/common/model_spec.js | 84 ---- .../repo/lib/decorations/controller_spec.js | 120 ------ spec/javascripts/repo/lib/diff/controller_spec.js | 176 --------- spec/javascripts/repo/lib/diff/diff_spec.js | 80 ---- spec/javascripts/repo/lib/editor_options_spec.js | 7 - spec/javascripts/repo/lib/editor_spec.js | 128 ------ spec/javascripts/repo/monaco_loader_spec.js | 13 - .../javascripts/repo/stores/actions/branch_spec.js | 44 --- spec/javascripts/repo/stores/actions/file_spec.js | 431 -------------------- spec/javascripts/repo/stores/actions/tree_spec.js | 350 ----------------- spec/javascripts/repo/stores/actions_spec.js | 432 --------------------- spec/javascripts/repo/stores/getters_spec.js | 114 ------ .../repo/stores/mutations/branch_spec.js | 18 - .../javascripts/repo/stores/mutations/file_spec.js | 131 ------- .../javascripts/repo/stores/mutations/tree_spec.js | 71 ---- spec/javascripts/repo/stores/mutations_spec.js | 125 ------ spec/javascripts/repo/stores/utils_spec.js | 119 ------ 100 files changed, 8015 deletions(-) delete mode 100644 app/assets/javascripts/ide/components/commit_sidebar/list.vue delete mode 100644 app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue delete mode 100644 app/assets/javascripts/ide/components/commit_sidebar/list_item.vue delete mode 100644 app/assets/javascripts/ide/components/ide.vue delete mode 100644 app/assets/javascripts/ide/components/ide_context_bar.vue delete mode 100644 app/assets/javascripts/ide/components/ide_project_branches_tree.vue delete mode 100644 app/assets/javascripts/ide/components/ide_project_tree.vue delete mode 100644 app/assets/javascripts/ide/components/ide_repo_tree.vue delete mode 100644 app/assets/javascripts/ide/components/ide_side_bar.vue delete mode 100644 app/assets/javascripts/ide/components/ide_status_bar.vue delete mode 100644 app/assets/javascripts/ide/components/new_branch_form.vue delete mode 100644 app/assets/javascripts/ide/components/new_dropdown/index.vue delete mode 100644 app/assets/javascripts/ide/components/new_dropdown/modal.vue delete mode 100644 app/assets/javascripts/ide/components/new_dropdown/upload.vue delete mode 100644 app/assets/javascripts/ide/components/repo_commit_section.vue delete mode 100644 app/assets/javascripts/ide/components/repo_edit_button.vue delete mode 100644 app/assets/javascripts/ide/components/repo_editor.vue delete mode 100644 app/assets/javascripts/ide/components/repo_file.vue delete mode 100644 app/assets/javascripts/ide/components/repo_file_buttons.vue delete mode 100644 app/assets/javascripts/ide/components/repo_loading_file.vue delete mode 100644 app/assets/javascripts/ide/components/repo_prev_directory.vue delete mode 100644 app/assets/javascripts/ide/components/repo_preview.vue delete mode 100644 app/assets/javascripts/ide/components/repo_tab.vue delete mode 100644 app/assets/javascripts/ide/components/repo_tabs.vue delete mode 100644 app/assets/javascripts/ide/ide_router.js delete mode 100644 app/assets/javascripts/ide/index.js delete mode 100644 app/assets/javascripts/ide/lib/common/disposable.js delete mode 100644 app/assets/javascripts/ide/lib/common/model.js delete mode 100644 app/assets/javascripts/ide/lib/common/model_manager.js delete mode 100644 app/assets/javascripts/ide/lib/decorations/controller.js delete mode 100644 app/assets/javascripts/ide/lib/diff/controller.js delete mode 100644 app/assets/javascripts/ide/lib/diff/diff.js delete mode 100644 app/assets/javascripts/ide/lib/diff/diff_worker.js delete mode 100644 app/assets/javascripts/ide/lib/editor.js delete mode 100644 app/assets/javascripts/ide/lib/editor_options.js delete mode 100644 app/assets/javascripts/ide/monaco_loader.js delete mode 100644 app/assets/javascripts/ide/services/index.js delete mode 100644 app/assets/javascripts/ide/stores/actions.js delete mode 100644 app/assets/javascripts/ide/stores/actions/branch.js delete mode 100644 app/assets/javascripts/ide/stores/actions/file.js delete mode 100644 app/assets/javascripts/ide/stores/actions/project.js delete mode 100644 app/assets/javascripts/ide/stores/actions/tree.js delete mode 100644 app/assets/javascripts/ide/stores/getters.js delete mode 100644 app/assets/javascripts/ide/stores/index.js delete mode 100644 app/assets/javascripts/ide/stores/mutation_types.js delete mode 100644 app/assets/javascripts/ide/stores/mutations.js delete mode 100644 app/assets/javascripts/ide/stores/mutations/branch.js delete mode 100644 app/assets/javascripts/ide/stores/mutations/file.js delete mode 100644 app/assets/javascripts/ide/stores/mutations/project.js delete mode 100644 app/assets/javascripts/ide/stores/mutations/tree.js delete mode 100644 app/assets/javascripts/ide/stores/state.js delete mode 100644 app/assets/javascripts/ide/stores/utils.js delete mode 100644 app/controllers/ide_controller.rb delete mode 100644 app/views/ide/index.html.haml delete mode 100644 spec/features/projects/tree/create_directory_spec.rb delete mode 100644 spec/features/projects/tree/create_file_spec.rb delete mode 100644 spec/features/projects/tree/upload_file_spec.rb delete mode 100644 spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js delete mode 100644 spec/javascripts/repo/components/commit_sidebar/list_item_spec.js delete mode 100644 spec/javascripts/repo/components/commit_sidebar/list_spec.js delete mode 100644 spec/javascripts/repo/components/ide_context_bar_spec.js delete mode 100644 spec/javascripts/repo/components/ide_repo_tree_spec.js delete mode 100644 spec/javascripts/repo/components/ide_side_bar_spec.js delete mode 100644 spec/javascripts/repo/components/ide_spec.js delete mode 100644 spec/javascripts/repo/components/new_branch_form_spec.js delete mode 100644 spec/javascripts/repo/components/new_dropdown/index_spec.js delete mode 100644 spec/javascripts/repo/components/new_dropdown/modal_spec.js delete mode 100644 spec/javascripts/repo/components/new_dropdown/upload_spec.js delete mode 100644 spec/javascripts/repo/components/repo_commit_section_spec.js delete mode 100644 spec/javascripts/repo/components/repo_edit_button_spec.js delete mode 100644 spec/javascripts/repo/components/repo_editor_spec.js delete mode 100644 spec/javascripts/repo/components/repo_file_buttons_spec.js delete mode 100644 spec/javascripts/repo/components/repo_file_spec.js delete mode 100644 spec/javascripts/repo/components/repo_loading_file_spec.js delete mode 100644 spec/javascripts/repo/components/repo_prev_directory_spec.js delete mode 100644 spec/javascripts/repo/components/repo_preview_spec.js delete mode 100644 spec/javascripts/repo/components/repo_tab_spec.js delete mode 100644 spec/javascripts/repo/components/repo_tabs_spec.js delete mode 100644 spec/javascripts/repo/helpers.js delete mode 100644 spec/javascripts/repo/lib/common/disposable_spec.js delete mode 100644 spec/javascripts/repo/lib/common/model_manager_spec.js delete mode 100644 spec/javascripts/repo/lib/common/model_spec.js delete mode 100644 spec/javascripts/repo/lib/decorations/controller_spec.js delete mode 100644 spec/javascripts/repo/lib/diff/controller_spec.js delete mode 100644 spec/javascripts/repo/lib/diff/diff_spec.js delete mode 100644 spec/javascripts/repo/lib/editor_options_spec.js delete mode 100644 spec/javascripts/repo/lib/editor_spec.js delete mode 100644 spec/javascripts/repo/monaco_loader_spec.js delete mode 100644 spec/javascripts/repo/stores/actions/branch_spec.js delete mode 100644 spec/javascripts/repo/stores/actions/file_spec.js delete mode 100644 spec/javascripts/repo/stores/actions/tree_spec.js delete mode 100644 spec/javascripts/repo/stores/actions_spec.js delete mode 100644 spec/javascripts/repo/stores/getters_spec.js delete mode 100644 spec/javascripts/repo/stores/mutations/branch_spec.js delete mode 100644 spec/javascripts/repo/stores/mutations/file_spec.js delete mode 100644 spec/javascripts/repo/stores/mutations/tree_spec.js delete mode 100644 spec/javascripts/repo/stores/mutations_spec.js delete mode 100644 spec/javascripts/repo/stores/utils_spec.js diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list.vue b/app/assets/javascripts/ide/components/commit_sidebar/list.vue deleted file mode 100644 index a8459b011df..00000000000 --- a/app/assets/javascripts/ide/components/commit_sidebar/list.vue +++ /dev/null @@ -1,65 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue deleted file mode 100644 index 6a0262f271b..00000000000 --- a/app/assets/javascripts/ide/components/commit_sidebar/list_collapsed.vue +++ /dev/null @@ -1,35 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue deleted file mode 100644 index 742f746e02f..00000000000 --- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/ide.vue b/app/assets/javascripts/ide/components/ide.vue deleted file mode 100644 index 89981ab2c65..00000000000 --- a/app/assets/javascripts/ide/components/ide.vue +++ /dev/null @@ -1,99 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/ide_context_bar.vue b/app/assets/javascripts/ide/components/ide_context_bar.vue deleted file mode 100644 index 9d933b8891d..00000000000 --- a/app/assets/javascripts/ide/components/ide_context_bar.vue +++ /dev/null @@ -1,108 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue b/app/assets/javascripts/ide/components/ide_project_branches_tree.vue deleted file mode 100644 index 2fbff2bd789..00000000000 --- a/app/assets/javascripts/ide/components/ide_project_branches_tree.vue +++ /dev/null @@ -1,47 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/ide_project_tree.vue b/app/assets/javascripts/ide/components/ide_project_tree.vue deleted file mode 100644 index 32bf7175c88..00000000000 --- a/app/assets/javascripts/ide/components/ide_project_tree.vue +++ /dev/null @@ -1,49 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/ide_repo_tree.vue b/app/assets/javascripts/ide/components/ide_repo_tree.vue deleted file mode 100644 index 4a324264992..00000000000 --- a/app/assets/javascripts/ide/components/ide_repo_tree.vue +++ /dev/null @@ -1,74 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue deleted file mode 100644 index 18b5059a17f..00000000000 --- a/app/assets/javascripts/ide/components/ide_side_bar.vue +++ /dev/null @@ -1,114 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue deleted file mode 100644 index 97ae64b206d..00000000000 --- a/app/assets/javascripts/ide/components/ide_status_bar.vue +++ /dev/null @@ -1,66 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/new_branch_form.vue b/app/assets/javascripts/ide/components/new_branch_form.vue deleted file mode 100644 index 1e8d5bb6453..00000000000 --- a/app/assets/javascripts/ide/components/new_branch_form.vue +++ /dev/null @@ -1,108 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue deleted file mode 100644 index ef653357f5f..00000000000 --- a/app/assets/javascripts/ide/components/new_dropdown/index.vue +++ /dev/null @@ -1,101 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue deleted file mode 100644 index 36cd825c6dd..00000000000 --- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue +++ /dev/null @@ -1,112 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/new_dropdown/upload.vue b/app/assets/javascripts/ide/components/new_dropdown/upload.vue deleted file mode 100644 index 6244737fa43..00000000000 --- a/app/assets/javascripts/ide/components/new_dropdown/upload.vue +++ /dev/null @@ -1,87 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue deleted file mode 100644 index 37f2cf30a29..00000000000 --- a/app/assets/javascripts/ide/components/repo_commit_section.vue +++ /dev/null @@ -1,171 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/repo_edit_button.vue b/app/assets/javascripts/ide/components/repo_edit_button.vue deleted file mode 100644 index fe4320731d9..00000000000 --- a/app/assets/javascripts/ide/components/repo_edit_button.vue +++ /dev/null @@ -1,57 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue deleted file mode 100644 index f31cc12339b..00000000000 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ /dev/null @@ -1,136 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/repo_file.vue b/app/assets/javascripts/ide/components/repo_file.vue deleted file mode 100644 index cbbab765e1c..00000000000 --- a/app/assets/javascripts/ide/components/repo_file.vue +++ /dev/null @@ -1,165 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/repo_file_buttons.vue b/app/assets/javascripts/ide/components/repo_file_buttons.vue deleted file mode 100644 index aabc0d8eada..00000000000 --- a/app/assets/javascripts/ide/components/repo_file_buttons.vue +++ /dev/null @@ -1,60 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/repo_loading_file.vue b/app/assets/javascripts/ide/components/repo_loading_file.vue deleted file mode 100644 index 79af8c0b0c7..00000000000 --- a/app/assets/javascripts/ide/components/repo_loading_file.vue +++ /dev/null @@ -1,42 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/repo_prev_directory.vue b/app/assets/javascripts/ide/components/repo_prev_directory.vue deleted file mode 100644 index 7cd359ea4ed..00000000000 --- a/app/assets/javascripts/ide/components/repo_prev_directory.vue +++ /dev/null @@ -1,32 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/repo_preview.vue b/app/assets/javascripts/ide/components/repo_preview.vue deleted file mode 100644 index a216269e292..00000000000 --- a/app/assets/javascripts/ide/components/repo_preview.vue +++ /dev/null @@ -1,71 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue deleted file mode 100644 index 5656081c598..00000000000 --- a/app/assets/javascripts/ide/components/repo_tab.vue +++ /dev/null @@ -1,74 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/components/repo_tabs.vue b/app/assets/javascripts/ide/components/repo_tabs.vue deleted file mode 100644 index ca363bba0ef..00000000000 --- a/app/assets/javascripts/ide/components/repo_tabs.vue +++ /dev/null @@ -1,27 +0,0 @@ - - - diff --git a/app/assets/javascripts/ide/ide_router.js b/app/assets/javascripts/ide/ide_router.js deleted file mode 100644 index a7fb9e0588a..00000000000 --- a/app/assets/javascripts/ide/ide_router.js +++ /dev/null @@ -1,101 +0,0 @@ -import Vue from 'vue'; -import VueRouter from 'vue-router'; -import store from './stores'; -import flash from '../flash'; -import { - getTreeEntry, -} from './stores/utils'; - -Vue.use(VueRouter); - -/** - * Routes below /-/ide/: - -/project/h5bp/html5-boilerplate/blob/master -/project/h5bp/html5-boilerplate/blob/master/app/js/test.js - -/project/h5bp/html5-boilerplate/mr/123 -/project/h5bp/html5-boilerplate/mr/123/app/js/test.js - -/workspace/123 -/workspace/project/h5bp/html5-boilerplate/blob/my-special-branch -/workspace/project/h5bp/html5-boilerplate/mr/123 - -/ = /workspace - -/settings -*/ - -// Unfortunately Vue Router doesn't work without at least a fake component -// If you do only data handling -const EmptyRouterComponent = { - render(createElement) { - return createElement('div'); - }, -}; - -const router = new VueRouter({ - mode: 'history', - base: `${gon.relative_url_root}/-/ide/`, - routes: [ - { - path: '/project/:namespace/:project', - component: EmptyRouterComponent, - children: [ - { - path: ':targetmode/:branch/*', - component: EmptyRouterComponent, - }, - { - path: 'mr/:mrid', - component: EmptyRouterComponent, - }, - ], - }, - ], -}); - -router.beforeEach((to, from, next) => { - if (to.params.namespace && to.params.project) { - store.dispatch('getProjectData', { - namespace: to.params.namespace, - projectId: to.params.project, - }) - .then(() => { - const fullProjectId = `${to.params.namespace}/${to.params.project}`; - - if (to.params.branch) { - store.dispatch('getBranchData', { - projectId: fullProjectId, - branchId: to.params.branch, - }); - - store.dispatch('getTreeData', { - projectId: fullProjectId, - branch: to.params.branch, - endpoint: `/tree/${to.params.branch}`, - }) - .then(() => { - if (to.params[0]) { - const treeEntry = getTreeEntry(store, `${to.params.namespace}/${to.params.project}/${to.params.branch}`, to.params[0]); - if (treeEntry) { - store.dispatch('handleTreeEntryAction', treeEntry); - } - } - }) - .catch((e) => { - flash('Error while loading the branch files. Please try again.', 'alert', document, null, false, true); - throw e; - }); - } - }) - .catch((e) => { - flash('Error while loading the project data. Please try again.', 'alert', document, null, false, true); - throw e; - }); - } - - next(); -}); - -export default router; diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js deleted file mode 100644 index e8a19f47cee..00000000000 --- a/app/assets/javascripts/ide/index.js +++ /dev/null @@ -1,31 +0,0 @@ -import Vue from 'vue'; -import ide from './components/ide.vue'; -import store from './stores'; -import router from './ide_router'; -import Translate from '../vue_shared/translate'; - -function initIde(el) { - if (!el) return null; - - return new Vue({ - el, - store, - router, - components: { - ide, - }, - render(createElement) { - return createElement('ide', { - props: { - emptyStateSvgPath: el.dataset.emptyStateSvgPath, - }, - }); - }, - }); -} - -const ideElement = document.getElementById('ide'); - -Vue.use(Translate); - -initIde(ideElement); diff --git a/app/assets/javascripts/ide/lib/common/disposable.js b/app/assets/javascripts/ide/lib/common/disposable.js deleted file mode 100644 index 84b29bdb600..00000000000 --- a/app/assets/javascripts/ide/lib/common/disposable.js +++ /dev/null @@ -1,14 +0,0 @@ -export default class Disposable { - constructor() { - this.disposers = new Set(); - } - - add(...disposers) { - disposers.forEach(disposer => this.disposers.add(disposer)); - } - - dispose() { - this.disposers.forEach(disposer => disposer.dispose()); - this.disposers.clear(); - } -} diff --git a/app/assets/javascripts/ide/lib/common/model.js b/app/assets/javascripts/ide/lib/common/model.js deleted file mode 100644 index 14d9fe4771e..00000000000 --- a/app/assets/javascripts/ide/lib/common/model.js +++ /dev/null @@ -1,64 +0,0 @@ -/* global monaco */ -import Disposable from './disposable'; - -export default class Model { - constructor(monaco, file) { - this.monaco = monaco; - this.disposable = new Disposable(); - this.file = file; - this.content = file.content !== '' ? file.content : file.raw; - - this.disposable.add( - this.originalModel = this.monaco.editor.createModel( - this.file.raw, - undefined, - new this.monaco.Uri(null, null, `original/${this.file.path}`), - ), - this.model = this.monaco.editor.createModel( - this.content, - undefined, - new this.monaco.Uri(null, null, this.file.path), - ), - ); - - this.events = new Map(); - } - - get url() { - return this.model.uri.toString(); - } - - get language() { - return this.model.getModeId(); - } - - get eol() { - return this.model.getEOL() === '\n' ? 'LF' : 'CRLF'; - } - - get path() { - return this.file.path; - } - - getModel() { - return this.model; - } - - getOriginalModel() { - return this.originalModel; - } - - onChange(cb) { - this.events.set( - this.path, - this.disposable.add( - this.model.onDidChangeContent(e => cb(this.model, e)), - ), - ); - } - - dispose() { - this.disposable.dispose(); - this.events.clear(); - } -} diff --git a/app/assets/javascripts/ide/lib/common/model_manager.js b/app/assets/javascripts/ide/lib/common/model_manager.js deleted file mode 100644 index fd462252795..00000000000 --- a/app/assets/javascripts/ide/lib/common/model_manager.js +++ /dev/null @@ -1,32 +0,0 @@ -import Disposable from './disposable'; -import Model from './model'; - -export default class ModelManager { - constructor(monaco) { - this.monaco = monaco; - this.disposable = new Disposable(); - this.models = new Map(); - } - - hasCachedModel(path) { - return this.models.has(path); - } - - addModel(file) { - if (this.hasCachedModel(file.path)) { - return this.models.get(file.path); - } - - const model = new Model(this.monaco, file); - this.models.set(model.path, model); - this.disposable.add(model); - - return model; - } - - dispose() { - // dispose of all the models - this.disposable.dispose(); - this.models.clear(); - } -} diff --git a/app/assets/javascripts/ide/lib/decorations/controller.js b/app/assets/javascripts/ide/lib/decorations/controller.js deleted file mode 100644 index 0954b7973c4..00000000000 --- a/app/assets/javascripts/ide/lib/decorations/controller.js +++ /dev/null @@ -1,43 +0,0 @@ -export default class DecorationsController { - constructor(editor) { - this.editor = editor; - this.decorations = new Map(); - this.editorDecorations = new Map(); - } - - getAllDecorationsForModel(model) { - if (!this.decorations.has(model.url)) return []; - - const modelDecorations = this.decorations.get(model.url); - const decorations = []; - - modelDecorations.forEach(val => decorations.push(...val)); - - return decorations; - } - - addDecorations(model, decorationsKey, decorations) { - const decorationMap = this.decorations.get(model.url) || new Map(); - - decorationMap.set(decorationsKey, decorations); - - this.decorations.set(model.url, decorationMap); - - this.decorate(model); - } - - decorate(model) { - const decorations = this.getAllDecorationsForModel(model); - const oldDecorations = this.editorDecorations.get(model.url) || []; - - this.editorDecorations.set( - model.url, - this.editor.instance.deltaDecorations(oldDecorations, decorations), - ); - } - - dispose() { - this.decorations.clear(); - this.editorDecorations.clear(); - } -} diff --git a/app/assets/javascripts/ide/lib/diff/controller.js b/app/assets/javascripts/ide/lib/diff/controller.js deleted file mode 100644 index dc0b1c95e59..00000000000 --- a/app/assets/javascripts/ide/lib/diff/controller.js +++ /dev/null @@ -1,71 +0,0 @@ -/* global monaco */ -import { throttle } from 'underscore'; -import DirtyDiffWorker from './diff_worker'; -import Disposable from '../common/disposable'; - -export const getDiffChangeType = (change) => { - if (change.modified) { - return 'modified'; - } else if (change.added) { - return 'added'; - } else if (change.removed) { - return 'removed'; - } - - return ''; -}; - -export const getDecorator = change => ({ - range: new monaco.Range( - change.lineNumber, - 1, - change.endLineNumber, - 1, - ), - options: { - isWholeLine: true, - linesDecorationsClassName: `dirty-diff dirty-diff-${getDiffChangeType(change)}`, - }, -}); - -export default class DirtyDiffController { - constructor(modelManager, decorationsController) { - this.disposable = new Disposable(); - this.editorSimpleWorker = null; - this.modelManager = modelManager; - this.decorationsController = decorationsController; - this.dirtyDiffWorker = new DirtyDiffWorker(); - this.throttledComputeDiff = throttle(this.computeDiff, 250); - this.decorate = this.decorate.bind(this); - - this.dirtyDiffWorker.addEventListener('message', this.decorate); - } - - attachModel(model) { - model.onChange(() => this.throttledComputeDiff(model)); - } - - computeDiff(model) { - this.dirtyDiffWorker.postMessage({ - path: model.path, - originalContent: model.getOriginalModel().getValue(), - newContent: model.getModel().getValue(), - }); - } - - reDecorate(model) { - this.decorationsController.decorate(model); - } - - decorate({ data }) { - const decorations = data.changes.map(change => getDecorator(change)); - this.decorationsController.addDecorations(data.path, 'dirtyDiff', decorations); - } - - dispose() { - this.disposable.dispose(); - - this.dirtyDiffWorker.removeEventListener('message', this.decorate); - this.dirtyDiffWorker.terminate(); - } -} diff --git a/app/assets/javascripts/ide/lib/diff/diff.js b/app/assets/javascripts/ide/lib/diff/diff.js deleted file mode 100644 index 0e37f5c4704..00000000000 --- a/app/assets/javascripts/ide/lib/diff/diff.js +++ /dev/null @@ -1,30 +0,0 @@ -import { diffLines } from 'diff'; - -// eslint-disable-next-line import/prefer-default-export -export const computeDiff = (originalContent, newContent) => { - const changes = diffLines(originalContent, newContent); - - let lineNumber = 1; - return changes.reduce((acc, change) => { - const findOnLine = acc.find(c => c.lineNumber === lineNumber); - - if (findOnLine) { - Object.assign(findOnLine, change, { - modified: true, - endLineNumber: (lineNumber + change.count) - 1, - }); - } else if ('added' in change || 'removed' in change) { - acc.push(Object.assign({}, change, { - lineNumber, - modified: undefined, - endLineNumber: (lineNumber + change.count) - 1, - })); - } - - if (!change.removed) { - lineNumber += change.count; - } - - return acc; - }, []); -}; diff --git a/app/assets/javascripts/ide/lib/diff/diff_worker.js b/app/assets/javascripts/ide/lib/diff/diff_worker.js deleted file mode 100644 index e74c4046330..00000000000 --- a/app/assets/javascripts/ide/lib/diff/diff_worker.js +++ /dev/null @@ -1,10 +0,0 @@ -import { computeDiff } from './diff'; - -self.addEventListener('message', (e) => { - const data = e.data; - - self.postMessage({ - path: data.path, - changes: computeDiff(data.originalContent, data.newContent), - }); -}); diff --git a/app/assets/javascripts/ide/lib/editor.js b/app/assets/javascripts/ide/lib/editor.js deleted file mode 100644 index 51255f15658..00000000000 --- a/app/assets/javascripts/ide/lib/editor.js +++ /dev/null @@ -1,110 +0,0 @@ -import _ from 'underscore'; -import DecorationsController from './decorations/controller'; -import DirtyDiffController from './diff/controller'; -import Disposable from './common/disposable'; -import ModelManager from './common/model_manager'; -import editorOptions from './editor_options'; - -export default class Editor { - static create(monaco) { - this.editorInstance = new Editor(monaco); - - return this.editorInstance; - } - - constructor(monaco) { - this.monaco = monaco; - this.currentModel = null; - this.instance = null; - this.dirtyDiffController = null; - this.disposable = new Disposable(); - - this.disposable.add( - this.modelManager = new ModelManager(this.monaco), - this.decorationsController = new DecorationsController(this), - ); - - this.debouncedUpdate = _.debounce(() => { - this.updateDimensions(); - }, 200); - window.addEventListener('resize', this.debouncedUpdate, false); - } - - createInstance(domElement) { - if (!this.instance) { - this.disposable.add( - this.instance = this.monaco.editor.create(domElement, { - model: null, - readOnly: false, - contextmenu: true, - scrollBeyondLastLine: false, - minimap: { - enabled: false, - }, - }), - this.dirtyDiffController = new DirtyDiffController( - this.modelManager, this.decorationsController, - ), - ); - } - } - - createModel(file) { - return this.modelManager.addModel(file); - } - - attachModel(model) { - this.instance.setModel(model.getModel()); - if (this.dirtyDiffController) this.dirtyDiffController.attachModel(model); - - this.currentModel = model; - - this.instance.updateOptions(editorOptions.reduce((acc, obj) => { - Object.keys(obj).forEach((key) => { - Object.assign(acc, { - [key]: obj[key](model), - }); - }); - return acc; - }, {})); - - if (this.dirtyDiffController) this.dirtyDiffController.reDecorate(model); - } - - clearEditor() { - if (this.instance) { - this.instance.setModel(null); - } - } - - dispose() { - this.disposable.dispose(); - window.removeEventListener('resize', this.debouncedUpdate); - - // dispose main monaco instance - if (this.instance) { - this.instance = null; - } - } - - updateDimensions() { - this.instance.layout(); - } - - setPosition({ lineNumber, column }) { - this.instance.revealPositionInCenter({ - lineNumber, - column, - }); - this.instance.setPosition({ - lineNumber, - column, - }); - } - - onPositionChange(cb) { - this.disposable.add( - this.instance.onDidChangeCursorPosition(e => cb(this.instance, e)), - ); - } -} diff --git a/app/assets/javascripts/ide/lib/editor_options.js b/app/assets/javascripts/ide/lib/editor_options.js deleted file mode 100644 index 701affc466e..00000000000 --- a/app/assets/javascripts/ide/lib/editor_options.js +++ /dev/null @@ -1,2 +0,0 @@ -export default [{ -}]; diff --git a/app/assets/javascripts/ide/monaco_loader.js b/app/assets/javascripts/ide/monaco_loader.js deleted file mode 100644 index 142a220097b..00000000000 --- a/app/assets/javascripts/ide/monaco_loader.js +++ /dev/null @@ -1,16 +0,0 @@ -import monacoContext from 'monaco-editor/dev/vs/loader'; - -monacoContext.require.config({ - paths: { - vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase - }, -}); - -// ignore CDN config and use local assets path for service worker which cannot be cross-domain -const relativeRootPath = (gon && gon.relative_url_root) || ''; -const monacoPath = `${relativeRootPath}/assets/webpack/monaco-editor/vs`; -window.MonacoEnvironment = { getWorkerUrl: () => `${monacoPath}/base/worker/workerMain.js` }; - -// eslint-disable-next-line no-underscore-dangle -window.__monaco_context__ = monacoContext; -export default monacoContext.require; diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js deleted file mode 100644 index 1fb24e93f2e..00000000000 --- a/app/assets/javascripts/ide/services/index.js +++ /dev/null @@ -1,47 +0,0 @@ -import Vue from 'vue'; -import VueResource from 'vue-resource'; -import Api from '../../api'; - -Vue.use(VueResource); - -export default { - getTreeData(endpoint) { - return Vue.http.get(endpoint, { params: { format: 'json' } }); - }, - getFileData(endpoint) { - return Vue.http.get(endpoint, { params: { format: 'json' } }); - }, - getRawFileData(file) { - if (file.tempFile) { - return Promise.resolve(file.content); - } - - if (file.raw) { - return Promise.resolve(file.raw); - } - - return Vue.http.get(file.rawPath, { params: { format: 'json' } }) - .then(res => res.text()); - }, - getProjectData(namespace, project) { - return Api.project(`${namespace}/${project}`); - }, - getBranchData(projectId, currentBranchId) { - return Api.branchSingle(projectId, currentBranchId); - }, - createBranch(projectId, payload) { - const url = Api.buildUrl(Api.createBranchPath).replace(':id', projectId); - - return Vue.http.post(url, payload); - }, - commit(projectId, payload) { - return Api.commitMultiple(projectId, payload); - }, - getTreeLastCommit(endpoint) { - return Vue.http.get(endpoint, { - params: { - format: 'json', - }, - }); - }, -}; diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js deleted file mode 100644 index 2c690b1f635..00000000000 --- a/app/assets/javascripts/ide/stores/actions.js +++ /dev/null @@ -1,196 +0,0 @@ -import Vue from 'vue'; -import { visitUrl } from '~/lib/utils/url_utility'; -import flash from '~/flash'; -import service from '../services'; -import * as types from './mutation_types'; -import { stripHtml } from '../../lib/utils/text_utility'; - -export const redirectToUrl = (_, url) => visitUrl(url); - -export const setInitialData = ({ commit }, data) => - commit(types.SET_INITIAL_DATA, data); - -export const closeDiscardPopup = ({ commit }) => - commit(types.TOGGLE_DISCARD_POPUP, false); - -export const discardAllChanges = ({ commit, getters, dispatch }) => { - const changedFiles = getters.changedFiles; - - changedFiles.forEach((file) => { - commit(types.DISCARD_FILE_CHANGES, file); - - if (file.tempFile) { - dispatch('closeFile', { file, force: true }); - } - }); -}; - -export const closeAllFiles = ({ state, dispatch }) => { - state.openFiles.forEach(file => dispatch('closeFile', { file })); -}; - -export const toggleEditMode = ( - { state, commit, getters, dispatch }, - force = false, -) => { - const changedFiles = getters.changedFiles; - - if (changedFiles.length && !force) { - commit(types.TOGGLE_DISCARD_POPUP, true); - } else { - commit(types.TOGGLE_EDIT_MODE); - commit(types.TOGGLE_DISCARD_POPUP, false); - dispatch('toggleBlobView'); - - if (!state.editMode) { - dispatch('discardAllChanges'); - } - } -}; - -export const toggleBlobView = ({ commit, state }) => { - if (state.editMode) { - commit(types.SET_EDIT_MODE); - } else { - commit(types.SET_PREVIEW_MODE); - } -}; - -export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => { - if (side === 'left') { - commit(types.SET_LEFT_PANEL_COLLAPSED, collapsed); - } else { - commit(types.SET_RIGHT_PANEL_COLLAPSED, collapsed); - } -}; - -export const setResizingStatus = ({ commit }, resizing) => { - commit(types.SET_RESIZING_STATUS, resizing); -}; - -export const checkCommitStatus = ({ state }) => - service - .getBranchData(state.currentProjectId, state.currentBranchId) - .then(({ data }) => { - const { id } = data.commit; - const selectedBranch = - state.projects[state.currentProjectId].branches[state.currentBranchId]; - - if (selectedBranch.workingReference !== id) { - return true; - } - - return false; - }) - .catch(() => flash('Error checking branch data. Please try again.', 'alert', document, null, false, true)); - -export const commitChanges = ( - { commit, state, dispatch, getters }, - { payload, newMr }, -) => - service - .commit(state.currentProjectId, payload) - .then(({ data }) => { - const { branch } = payload; - if (!data.short_id) { - flash(data.message, 'alert', document, null, false, true); - return; - } - - const selectedProject = state.projects[state.currentProjectId]; - const lastCommit = { - commit_path: `${selectedProject.web_url}/commit/${data.id}`, - commit: { - message: data.message, - authored_date: data.committed_date, - }, - }; - - let commitMsg = `Your changes have been committed. Commit ${data.short_id}`; - if (data.stats) { - commitMsg += ` with ${data.stats.additions} additions, ${data.stats.deletions} deletions.`; - } - - flash( - commitMsg, - 'notice', - document, - null, - false, - true); - window.dispatchEvent(new Event('resize')); - - if (newMr) { - dispatch('discardAllChanges'); - dispatch( - 'redirectToUrl', - `${selectedProject.web_url}/merge_requests/new?merge_request%5Bsource_branch%5D=${branch}`, - ); - } else { - commit(types.SET_BRANCH_WORKING_REFERENCE, { - projectId: state.currentProjectId, - branchId: state.currentBranchId, - reference: data.id, - }); - - getters.changedFiles.forEach((entry) => { - commit(types.SET_LAST_COMMIT_DATA, { - entry, - lastCommit, - }); - }); - - dispatch('discardAllChanges'); - - window.scrollTo(0, 0); - } - }) - .catch((err) => { - let errMsg = 'Error committing changes. Please try again.'; - if (err.response.data && err.response.data.message) { - errMsg += ` (${stripHtml(err.response.data.message)})`; - } - flash(errMsg, 'alert', document, null, false, true); - window.dispatchEvent(new Event('resize')); - }); - -export const createTempEntry = ( - { state, dispatch }, - { projectId, branchId, parent, name, type, content = '', base64 = false }, -) => { - const selectedParent = parent || state.trees[`${projectId}/${branchId}`]; - if (type === 'tree') { - dispatch('createTempTree', { - projectId, - branchId, - parent: selectedParent, - name, - }); - } else if (type === 'blob') { - dispatch('createTempFile', { - projectId, - branchId, - parent: selectedParent, - name, - base64, - content, - }); - } -}; - -export const scrollToTab = () => { - Vue.nextTick(() => { - const tabs = document.getElementById('tabs'); - - if (tabs) { - const tabEl = tabs.querySelector('.active .repo-tab'); - - tabEl.focus(); - } - }); -}; - -export * from './actions/tree'; -export * from './actions/file'; -export * from './actions/project'; -export * from './actions/branch'; diff --git a/app/assets/javascripts/ide/stores/actions/branch.js b/app/assets/javascripts/ide/stores/actions/branch.js deleted file mode 100644 index bc6fd2d4163..00000000000 --- a/app/assets/javascripts/ide/stores/actions/branch.js +++ /dev/null @@ -1,43 +0,0 @@ -import service from '../../services'; -import flash from '../../../flash'; -import * as types from '../mutation_types'; - -export const getBranchData = ( - { commit, state, dispatch }, - { projectId, branchId, force = false } = {}, -) => new Promise((resolve, reject) => { - if ((typeof state.projects[`${projectId}`] === 'undefined' || - !state.projects[`${projectId}`].branches[branchId]) - || force) { - service.getBranchData(`${projectId}`, branchId) - .then(({ data }) => { - const { id } = data.commit; - commit(types.SET_BRANCH, { projectPath: `${projectId}`, branchName: branchId, branch: data }); - commit(types.SET_BRANCH_WORKING_REFERENCE, { projectId, branchId, reference: id }); - resolve(data); - }) - .catch(() => { - flash('Error loading branch data. Please try again.', 'alert', document, null, false, true); - reject(new Error(`Branch not loaded - ${projectId}/${branchId}`)); - }); - } else { - resolve(state.projects[`${projectId}`].branches[branchId]); - } -}); - -export const createNewBranch = ({ state, commit }, branch) => service.createBranch( - state.currentProjectId, - { - branch, - ref: state.currentBranchId, - }, -) -.then(res => res.json()) -.then((data) => { - const branchName = data.name; - const url = location.href.replace(state.currentBranchId, branchName); - - if (this.$router) this.$router.push(url); - - commit(types.SET_CURRENT_BRANCH, branchName); -}); diff --git a/app/assets/javascripts/ide/stores/actions/file.js b/app/assets/javascripts/ide/stores/actions/file.js deleted file mode 100644 index 670af2fb89e..00000000000 --- a/app/assets/javascripts/ide/stores/actions/file.js +++ /dev/null @@ -1,137 +0,0 @@ -import { normalizeHeaders } from '../../../lib/utils/common_utils'; -import flash from '../../../flash'; -import service from '../../services'; -import * as types from '../mutation_types'; -import router from '../../ide_router'; -import { - findEntry, - setPageTitle, - createTemp, - findIndexOfFile, -} from '../utils'; - -export const closeFile = ({ commit, state, dispatch }, { file, force = false }) => { - if ((file.changed || file.tempFile) && !force) return; - - const indexOfClosedFile = findIndexOfFile(state.openFiles, file); - const fileWasActive = file.active; - - commit(types.TOGGLE_FILE_OPEN, file); - commit(types.SET_FILE_ACTIVE, { file, active: false }); - - if (state.openFiles.length > 0 && fileWasActive) { - const nextIndexToOpen = indexOfClosedFile === 0 ? 0 : indexOfClosedFile - 1; - const nextFileToOpen = state.openFiles[nextIndexToOpen]; - - dispatch('setFileActive', nextFileToOpen); - } else if (!state.openFiles.length) { - router.push(`/project/${file.projectId}/tree/${file.branchId}/`); - } - - dispatch('getLastCommitData'); -}; - -export const setFileActive = ({ commit, state, getters, dispatch }, file) => { - const currentActiveFile = getters.activeFile; - - if (file.active) return; - - if (currentActiveFile) { - commit(types.SET_FILE_ACTIVE, { file: currentActiveFile, active: false }); - } - - commit(types.SET_FILE_ACTIVE, { file, active: true }); - dispatch('scrollToTab'); - - // reset hash for line highlighting - location.hash = ''; - - commit(types.SET_CURRENT_PROJECT, file.projectId); - commit(types.SET_CURRENT_BRANCH, file.branchId); -}; - -export const getFileData = ({ state, commit, dispatch }, file) => { - commit(types.TOGGLE_LOADING, file); - - service.getFileData(file.url) - .then((res) => { - const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']); - - setPageTitle(pageTitle); - - return res.json(); - }) - .then((data) => { - commit(types.SET_FILE_DATA, { data, file }); - commit(types.TOGGLE_FILE_OPEN, file); - dispatch('setFileActive', file); - commit(types.TOGGLE_LOADING, file); - }) - .catch(() => { - commit(types.TOGGLE_LOADING, file); - flash('Error loading file data. Please try again.', 'alert', document, null, false, true); - }); -}; - -export const getRawFileData = ({ commit, dispatch }, file) => service.getRawFileData(file) - .then((raw) => { - commit(types.SET_FILE_RAW_DATA, { file, raw }); - }) - .catch(() => flash('Error loading file content. Please try again.', 'alert', document, null, false, true)); - -export const changeFileContent = ({ commit }, { file, content }) => { - commit(types.UPDATE_FILE_CONTENT, { file, content }); -}; - -export const setFileLanguage = ({ state, commit }, { fileLanguage }) => { - if (state.selectedFile) { - commit(types.SET_FILE_LANGUAGE, { file: state.selectedFile, fileLanguage }); - } -}; - -export const setFileEOL = ({ state, commit }, { eol }) => { - if (state.selectedFile) { - commit(types.SET_FILE_EOL, { file: state.selectedFile, eol }); - } -}; - -export const setEditorPosition = ({ state, commit }, { editorRow, editorColumn }) => { - if (state.selectedFile) { - commit(types.SET_FILE_POSITION, { file: state.selectedFile, editorRow, editorColumn }); - } -}; - -export const createTempFile = ({ state, commit, dispatch }, { projectId, branchId, parent, name, content = '', base64 = '' }) => { - const path = parent.path !== undefined ? parent.path : ''; - // We need to do the replacement otherwise the web_url + file.url duplicate - const newUrl = `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${name}`; - const file = createTemp({ - projectId, - branchId, - name: name.replace(`${path}/`, ''), - path, - type: 'blob', - level: parent.level !== undefined ? parent.level + 1 : 0, - changed: true, - content, - base64, - url: newUrl, - }); - - if (findEntry(parent.tree, 'blob', file.name)) return flash(`The name "${file.name}" is already taken in this directory.`, 'alert', document, null, false, true); - - commit(types.CREATE_TMP_FILE, { - parent, - file, - }); - commit(types.TOGGLE_FILE_OPEN, file); - dispatch('setFileActive', file); - - if (!state.editMode && !file.base64) { - dispatch('toggleEditMode', true); - } - - router.push(`/project${file.url}`); - - return Promise.resolve(file); -}; diff --git a/app/assets/javascripts/ide/stores/actions/project.js b/app/assets/javascripts/ide/stores/actions/project.js deleted file mode 100644 index faeceb430a2..00000000000 --- a/app/assets/javascripts/ide/stores/actions/project.js +++ /dev/null @@ -1,27 +0,0 @@ -import service from '../../services'; -import flash from '../../../flash'; -import * as types from '../mutation_types'; - -// eslint-disable-next-line import/prefer-default-export -export const getProjectData = ( - { commit, state, dispatch }, - { namespace, projectId, force = false } = {}, -) => new Promise((resolve, reject) => { - if (!state.projects[`${namespace}/${projectId}`] || force) { - commit(types.TOGGLE_LOADING, state); - service.getProjectData(namespace, projectId) - .then(res => res.data) - .then((data) => { - commit(types.TOGGLE_LOADING, state); - commit(types.SET_PROJECT, { projectPath: `${namespace}/${projectId}`, project: data }); - if (!state.currentProjectId) commit(types.SET_CURRENT_PROJECT, `${namespace}/${projectId}`); - resolve(data); - }) - .catch(() => { - flash('Error loading project data. Please try again.', 'alert', document, null, false, true); - reject(new Error(`Project not loaded ${namespace}/${projectId}`)); - }); - } else { - resolve(state.projects[`${namespace}/${projectId}`]); - } -}); diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js deleted file mode 100644 index 302ba45edee..00000000000 --- a/app/assets/javascripts/ide/stores/actions/tree.js +++ /dev/null @@ -1,188 +0,0 @@ -import { visitUrl } from '../../../lib/utils/url_utility'; -import { normalizeHeaders } from '../../../lib/utils/common_utils'; -import flash from '../../../flash'; -import service from '../../services'; -import * as types from '../mutation_types'; -import router from '../../ide_router'; -import { - setPageTitle, - findEntry, - createTemp, - createOrMergeEntry, -} from '../utils'; - -export const getTreeData = ( - { commit, state, dispatch }, - { endpoint, tree = null, projectId, branch, force = false } = {}, -) => new Promise((resolve, reject) => { - // We already have the base tree so we resolve immediately - if (!tree && state.trees[`${projectId}/${branch}`] && !force) { - resolve(); - } else { - if (tree) commit(types.TOGGLE_LOADING, tree); - const selectedProject = state.projects[projectId]; - // We are merging the web_url that we got on the project info with the endpoint - // we got on the tree entry, as both contain the projectId, we replace it in the tree endpoint - const completeEndpoint = selectedProject.web_url + (endpoint).replace(projectId, ''); - if (completeEndpoint && (!tree || !tree.tempFile)) { - service.getTreeData(completeEndpoint) - .then((res) => { - const pageTitle = decodeURI(normalizeHeaders(res.headers)['PAGE-TITLE']); - - setPageTitle(pageTitle); - - return res.json(); - }) - .then((data) => { - if (!state.isInitialRoot) { - commit(types.SET_ROOT, data.path === '/'); - } - - dispatch('updateDirectoryData', { data, tree, projectId, branch }); - const selectedTree = tree || state.trees[`${projectId}/${branch}`]; - - commit(types.SET_PARENT_TREE_URL, data.parent_tree_url); - commit(types.SET_LAST_COMMIT_URL, { tree: selectedTree, url: data.last_commit_path }); - if (tree) commit(types.TOGGLE_LOADING, selectedTree); - - const prevLastCommitPath = selectedTree.lastCommitPath; - if (prevLastCommitPath !== null) { - dispatch('getLastCommitData', selectedTree); - } - resolve(data); - }) - .catch((e) => { - flash('Error loading tree data. Please try again.', 'alert', document, null, false, true); - if (tree) commit(types.TOGGLE_LOADING, tree); - reject(e); - }); - } else { - resolve(); - } - } -}); - -export const toggleTreeOpen = ({ commit, dispatch }, { endpoint, tree }) => { - if (tree.opened) { - // send empty data to clear the tree - const data = { trees: [], blobs: [], submodules: [] }; - - dispatch('updateDirectoryData', { data, tree, projectId: tree.projectId, branchId: tree.branchId }); - } else { - dispatch('getTreeData', { endpoint, tree, projectId: tree.projectId, branch: tree.branchId }); - } - - commit(types.TOGGLE_TREE_OPEN, tree); -}; - -export const handleTreeEntryAction = ({ commit, dispatch }, row) => { - if (row.type === 'tree') { - dispatch('toggleTreeOpen', { - endpoint: row.url, - tree: row, - }); - } else if (row.type === 'submodule') { - commit(types.TOGGLE_LOADING, row); - visitUrl(row.url); - } else if (row.type === 'blob' && row.opened) { - dispatch('setFileActive', row); - } else { - dispatch('getFileData', row); - } -}; - -export const createTempTree = ( - { state, commit, dispatch }, - { projectId, branchId, parent, name }, -) => { - let selectedTree = parent; - const dirNames = name.replace(new RegExp(`^${state.path}/`), '').split('/'); - - dirNames.forEach((dirName) => { - const foundEntry = findEntry(selectedTree.tree, 'tree', dirName); - - if (!foundEntry) { - const path = selectedTree.path !== undefined ? selectedTree.path : ''; - const tmpEntry = createTemp({ - projectId, - branchId, - name: dirName, - path, - type: 'tree', - level: selectedTree.level !== undefined ? selectedTree.level + 1 : 0, - tree: [], - url: `/${projectId}/blob/${branchId}/${path}${path ? '/' : ''}${dirName}`, - }); - - commit(types.CREATE_TMP_TREE, { - parent: selectedTree, - tmpEntry, - }); - commit(types.TOGGLE_TREE_OPEN, tmpEntry); - - router.push(`/project${tmpEntry.url}`); - - selectedTree = tmpEntry; - } else { - selectedTree = foundEntry; - } - }); -}; - -export const getLastCommitData = ({ state, commit, dispatch, getters }, tree = state) => { - if (!tree || tree.lastCommitPath === null || !tree.lastCommitPath) return; - - service.getTreeLastCommit(tree.lastCommitPath) - .then((res) => { - const lastCommitPath = normalizeHeaders(res.headers)['MORE-LOGS-URL'] || null; - - commit(types.SET_LAST_COMMIT_URL, { tree, url: lastCommitPath }); - - return res.json(); - }) - .then((data) => { - data.forEach((lastCommit) => { - const entry = findEntry(tree.tree, lastCommit.type, lastCommit.file_name); - - if (entry) { - commit(types.SET_LAST_COMMIT_DATA, { entry, lastCommit }); - } - }); - - dispatch('getLastCommitData', tree); - }) - .catch(() => flash('Error fetching log data.', 'alert', document, null, false, true)); -}; - -export const updateDirectoryData = ( - { commit, state }, - { data, tree, projectId, branch }, -) => { - if (!tree) { - const existingTree = state.trees[`${projectId}/${branch}`]; - if (!existingTree) { - commit(types.CREATE_TREE, { treePath: `${projectId}/${branch}` }); - } - } - - const selectedTree = tree || state.trees[`${projectId}/${branch}`]; - const level = selectedTree.level !== undefined ? selectedTree.level + 1 : 0; - const parentTreeUrl = data.parent_tree_url ? `${data.parent_tree_url}${data.path}` : state.endpoints.rootUrl; - const createEntry = (entry, type) => createOrMergeEntry({ - tree: selectedTree, - projectId: `${projectId}`, - branchId: branch, - entry, - level, - type, - parentTreeUrl, - }); - - const formattedData = [ - ...data.trees.map(t => createEntry(t, 'tree')), - ...data.submodules.map(m => createEntry(m, 'submodule')), - ...data.blobs.map(b => createEntry(b, 'blob')), - ]; - - commit(types.SET_DIRECTORY_DATA, { tree: selectedTree, data: formattedData }); -}; diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js deleted file mode 100644 index 6b51ccff817..00000000000 --- a/app/assets/javascripts/ide/stores/getters.js +++ /dev/null @@ -1,19 +0,0 @@ -export const changedFiles = state => state.openFiles.filter(file => file.changed); - -export const activeFile = state => state.openFiles.find(file => file.active) || null; - -export const activeFileExtension = (state) => { - const file = activeFile(state); - return file ? `.${file.path.split('.').pop()}` : ''; -}; - -export const canEditFile = (state) => { - const currentActiveFile = activeFile(state); - - return state.canCommit && - (currentActiveFile && !currentActiveFile.renderError && !currentActiveFile.binary); -}; - -export const addedFiles = state => changedFiles(state).filter(f => f.tempFile); - -export const modifiedFiles = state => changedFiles(state).filter(f => !f.tempFile); diff --git a/app/assets/javascripts/ide/stores/index.js b/app/assets/javascripts/ide/stores/index.js deleted file mode 100644 index 6ac9bfd8189..00000000000 --- a/app/assets/javascripts/ide/stores/index.js +++ /dev/null @@ -1,15 +0,0 @@ -import Vue from 'vue'; -import Vuex from 'vuex'; -import state from './state'; -import * as actions from './actions'; -import * as getters from './getters'; -import mutations from './mutations'; - -Vue.use(Vuex); - -export default new Vuex.Store({ - state: state(), - actions, - mutations, - getters, -}); diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js deleted file mode 100644 index 69b218a5e7d..00000000000 --- a/app/assets/javascripts/ide/stores/mutation_types.js +++ /dev/null @@ -1,46 +0,0 @@ -export const SET_INITIAL_DATA = 'SET_INITIAL_DATA'; -export const TOGGLE_LOADING = 'TOGGLE_LOADING'; -export const SET_PARENT_TREE_URL = 'SET_PARENT_TREE_URL'; -export const SET_ROOT = 'SET_ROOT'; -export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA'; -export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED'; -export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED'; -export const SET_RESIZING_STATUS = 'SET_RESIZING_STATUS'; - -// Project Mutation Types -export const SET_PROJECT = 'SET_PROJECT'; -export const SET_CURRENT_PROJECT = 'SET_CURRENT_PROJECT'; -export const TOGGLE_PROJECT_OPEN = 'TOGGLE_PROJECT_OPEN'; - -// Branch Mutation Types -export const SET_BRANCH = 'SET_BRANCH'; -export const SET_BRANCH_WORKING_REFERENCE = 'SET_BRANCH_WORKING_REFERENCE'; -export const TOGGLE_BRANCH_OPEN = 'TOGGLE_BRANCH_OPEN'; - -// Tree mutation types -export const SET_DIRECTORY_DATA = 'SET_DIRECTORY_DATA'; -export const TOGGLE_TREE_OPEN = 'TOGGLE_TREE_OPEN'; -export const CREATE_TMP_TREE = 'CREATE_TMP_TREE'; -export const SET_LAST_COMMIT_URL = 'SET_LAST_COMMIT_URL'; -export const CREATE_TREE = 'CREATE_TREE'; - -// File mutation types -export const SET_FILE_DATA = 'SET_FILE_DATA'; -export const TOGGLE_FILE_OPEN = 'TOGGLE_FILE_OPEN'; -export const SET_FILE_ACTIVE = 'SET_FILE_ACTIVE'; -export const SET_FILE_RAW_DATA = 'SET_FILE_RAW_DATA'; -export const UPDATE_FILE_CONTENT = 'UPDATE_FILE_CONTENT'; -export const SET_FILE_LANGUAGE = 'SET_FILE_LANGUAGE'; -export const SET_FILE_POSITION = 'SET_FILE_POSITION'; -export const SET_FILE_EOL = 'SET_FILE_EOL'; -export const DISCARD_FILE_CHANGES = 'DISCARD_FILE_CHANGES'; -export const CREATE_TMP_FILE = 'CREATE_TMP_FILE'; - -// Viewer mutation types -export const SET_PREVIEW_MODE = 'SET_PREVIEW_MODE'; -export const SET_EDIT_MODE = 'SET_EDIT_MODE'; -export const TOGGLE_EDIT_MODE = 'TOGGLE_EDIT_MODE'; -export const TOGGLE_DISCARD_POPUP = 'TOGGLE_DISCARD_POPUP'; - -export const SET_CURRENT_BRANCH = 'SET_CURRENT_BRANCH'; - diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js deleted file mode 100644 index 03d81be10a1..00000000000 --- a/app/assets/javascripts/ide/stores/mutations.js +++ /dev/null @@ -1,70 +0,0 @@ -import * as types from './mutation_types'; -import projectMutations from './mutations/project'; -import fileMutations from './mutations/file'; -import treeMutations from './mutations/tree'; -import branchMutations from './mutations/branch'; - -export default { - [types.SET_INITIAL_DATA](state, data) { - Object.assign(state, data); - }, - [types.SET_PREVIEW_MODE](state) { - Object.assign(state, { - currentBlobView: 'repo-preview', - }); - }, - [types.SET_EDIT_MODE](state) { - Object.assign(state, { - currentBlobView: 'repo-editor', - }); - }, - [types.TOGGLE_LOADING](state, entry) { - Object.assign(entry, { - loading: !entry.loading, - }); - }, - [types.TOGGLE_EDIT_MODE](state) { - Object.assign(state, { - editMode: !state.editMode, - }); - }, - [types.TOGGLE_DISCARD_POPUP](state, discardPopupOpen) { - Object.assign(state, { - discardPopupOpen, - }); - }, - [types.SET_ROOT](state, isRoot) { - Object.assign(state, { - isRoot, - isInitialRoot: isRoot, - }); - }, - [types.SET_LEFT_PANEL_COLLAPSED](state, collapsed) { - Object.assign(state, { - leftPanelCollapsed: collapsed, - }); - }, - [types.SET_RIGHT_PANEL_COLLAPSED](state, collapsed) { - Object.assign(state, { - rightPanelCollapsed: collapsed, - }); - }, - [types.SET_RESIZING_STATUS](state, resizing) { - Object.assign(state, { - panelResizing: resizing, - }); - }, - [types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) { - Object.assign(entry.lastCommit, { - id: lastCommit.commit.id, - url: lastCommit.commit_path, - message: lastCommit.commit.message, - author: lastCommit.commit.author_name, - updatedAt: lastCommit.commit.authored_date, - }); - }, - ...projectMutations, - ...fileMutations, - ...treeMutations, - ...branchMutations, -}; diff --git a/app/assets/javascripts/ide/stores/mutations/branch.js b/app/assets/javascripts/ide/stores/mutations/branch.js deleted file mode 100644 index 04b9582c5bb..00000000000 --- a/app/assets/javascripts/ide/stores/mutations/branch.js +++ /dev/null @@ -1,28 +0,0 @@ -import * as types from '../mutation_types'; - -export default { - [types.SET_CURRENT_BRANCH](state, currentBranchId) { - Object.assign(state, { - currentBranchId, - }); - }, - [types.SET_BRANCH](state, { projectPath, branchName, branch }) { - // Add client side properties - Object.assign(branch, { - treeId: `${projectPath}/${branchName}`, - active: true, - workingReference: '', - }); - - Object.assign(state.projects[projectPath], { - branches: { - [branchName]: branch, - }, - }); - }, - [types.SET_BRANCH_WORKING_REFERENCE](state, { projectId, branchId, reference }) { - Object.assign(state.projects[projectId].branches[branchId], { - workingReference: reference, - }); - }, -}; diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js deleted file mode 100644 index 72db1c180c9..00000000000 --- a/app/assets/javascripts/ide/stores/mutations/file.js +++ /dev/null @@ -1,74 +0,0 @@ -import * as types from '../mutation_types'; -import { findIndexOfFile } from '../utils'; - -export default { - [types.SET_FILE_ACTIVE](state, { file, active }) { - Object.assign(file, { - active, - }); - - Object.assign(state, { - selectedFile: file, - }); - }, - [types.TOGGLE_FILE_OPEN](state, file) { - Object.assign(file, { - opened: !file.opened, - }); - - if (file.opened) { - state.openFiles.push(file); - } else { - state.openFiles.splice(findIndexOfFile(state.openFiles, file), 1); - } - }, - [types.SET_FILE_DATA](state, { data, file }) { - Object.assign(file, { - blamePath: data.blame_path, - commitsPath: data.commits_path, - permalink: data.permalink, - rawPath: data.raw_path, - binary: data.binary, - html: data.html, - renderError: data.render_error, - }); - }, - [types.SET_FILE_RAW_DATA](state, { file, raw }) { - Object.assign(file, { - raw, - }); - }, - [types.UPDATE_FILE_CONTENT](state, { file, content }) { - const changed = content !== file.raw; - - Object.assign(file, { - content, - changed, - }); - }, - [types.SET_FILE_LANGUAGE](state, { file, fileLanguage }) { - Object.assign(file, { - fileLanguage, - }); - }, - [types.SET_FILE_EOL](state, { file, eol }) { - Object.assign(file, { - eol, - }); - }, - [types.SET_FILE_POSITION](state, { file, editorRow, editorColumn }) { - Object.assign(file, { - editorRow, - editorColumn, - }); - }, - [types.DISCARD_FILE_CHANGES](state, file) { - Object.assign(file, { - content: file.raw, - changed: false, - }); - }, - [types.CREATE_TMP_FILE](state, { file, parent }) { - parent.tree.push(file); - }, -}; diff --git a/app/assets/javascripts/ide/stores/mutations/project.js b/app/assets/javascripts/ide/stores/mutations/project.js deleted file mode 100644 index 2816562a919..00000000000 --- a/app/assets/javascripts/ide/stores/mutations/project.js +++ /dev/null @@ -1,23 +0,0 @@ -import * as types from '../mutation_types'; - -export default { - [types.SET_CURRENT_PROJECT](state, currentProjectId) { - Object.assign(state, { - currentProjectId, - }); - }, - [types.SET_PROJECT](state, { projectPath, project }) { - // Add client side properties - Object.assign(project, { - tree: [], - branches: {}, - active: true, - }); - - Object.assign(state, { - projects: Object.assign({}, state.projects, { - [projectPath]: project, - }), - }); - }, -}; diff --git a/app/assets/javascripts/ide/stores/mutations/tree.js b/app/assets/javascripts/ide/stores/mutations/tree.js deleted file mode 100644 index 4fe438ab465..00000000000 --- a/app/assets/javascripts/ide/stores/mutations/tree.js +++ /dev/null @@ -1,36 +0,0 @@ -import * as types from '../mutation_types'; - -export default { - [types.TOGGLE_TREE_OPEN](state, tree) { - Object.assign(tree, { - opened: !tree.opened, - }); - }, - [types.CREATE_TREE](state, { treePath }) { - Object.assign(state, { - trees: Object.assign({}, state.trees, { - [treePath]: { - tree: [], - }, - }), - }); - }, - [types.SET_DIRECTORY_DATA](state, { data, tree }) { - Object.assign(tree, { - tree: data, - }); - }, - [types.SET_PARENT_TREE_URL](state, url) { - Object.assign(state, { - parentTreeUrl: url, - }); - }, - [types.SET_LAST_COMMIT_URL](state, { tree = state, url }) { - Object.assign(tree, { - lastCommitPath: url, - }); - }, - [types.CREATE_TMP_TREE](state, { parent, tmpEntry }) { - parent.tree.push(tmpEntry); - }, -}; diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js deleted file mode 100644 index 61d12096946..00000000000 --- a/app/assets/javascripts/ide/stores/state.js +++ /dev/null @@ -1,23 +0,0 @@ -export default () => ({ - canCommit: false, - currentProjectId: '', - currentBranchId: '', - currentBlobView: 'repo-editor', - discardPopupOpen: false, - editMode: true, - endpoints: {}, - isRoot: false, - isInitialRoot: false, - lastCommitPath: '', - loading: false, - onTopOfBranch: false, - openFiles: [], - selectedFile: null, - path: '', - parentTreeUrl: '', - trees: {}, - projects: {}, - leftPanelCollapsed: false, - rightPanelCollapsed: true, - panelResizing: false, -}); diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js deleted file mode 100644 index d556404faa5..00000000000 --- a/app/assets/javascripts/ide/stores/utils.js +++ /dev/null @@ -1,177 +0,0 @@ -import _ from 'underscore'; - -export const dataStructure = () => ({ - id: '', - key: '', - type: '', - projectId: '', - branchId: '', - name: '', - url: '', - path: '', - level: 0, - tempFile: false, - icon: '', - tree: [], - loading: false, - opened: false, - active: false, - changed: false, - lastCommitPath: '', - lastCommit: { - id: '', - url: '', - message: '', - updatedAt: '', - author: '', - }, - tree_url: '', - blamePath: '', - commitsPath: '', - permalink: '', - rawPath: '', - binary: false, - html: '', - raw: '', - content: '', - parentTreeUrl: '', - renderError: false, - base64: false, - editorRow: 1, - editorColumn: 1, - fileLanguage: '', - eol: '', -}); - -export const decorateData = (entity) => { - const { - id, - projectId, - branchId, - type, - url, - name, - icon, - tree_url, - path, - renderError, - content = '', - tempFile = false, - active = false, - opened = false, - changed = false, - parentTreeUrl = '', - level = 0, - base64 = false, - } = entity; - - return { - ...dataStructure(), - id, - projectId, - branchId, - key: `${name}-${type}-${id}`, - type, - name, - url, - tree_url, - path, - level, - tempFile, - icon: `fa-${icon}`, - opened, - active, - parentTreeUrl, - changed, - renderError, - content, - base64, - }; -}; - -/* - Takes the multi-dimensional tree and returns a flattened array. - This allows for the table to recursively render the table rows but keeps the data - structure nested to make it easier to add new files/directories. -*/ -export const treeList = (state, treeId) => { - const baseTree = state.trees[treeId]; - if (baseTree) { - const mapTree = arr => (!arr.tree || !arr.tree.length ? - [] : _.map(arr.tree, a => [a, mapTree(a)])); - - return _.chain(baseTree.tree) - .map(arr => [arr, mapTree(arr)]) - .flatten() - .value(); - } - return []; -}; - -export const getTree = state => (namespace, projectId, branch) => state.trees[`${namespace}/${projectId}/${branch}`]; - -export const getTreeEntry = (store, treeId, path) => { - const fileList = treeList(store.state, treeId); - return fileList ? fileList.find(file => file.path === path) : null; -}; - -export const findEntry = (tree, type, name) => tree.find( - f => f.type === type && f.name === name, -); - -export const findIndexOfFile = (state, file) => state.findIndex(f => f.path === file.path); - -export const setPageTitle = (title) => { - document.title = title; -}; - -export const createTemp = ({ - projectId, branchId, name, path, type, level, changed, content, base64, url, -}) => { - const treePath = path ? `${path}/${name}` : name; - - return decorateData({ - id: new Date().getTime().toString(), - projectId, - branchId, - name, - type, - tempFile: true, - path: treePath, - icon: type === 'tree' ? 'folder' : 'file-text-o', - changed, - content, - parentTreeUrl: '', - level, - base64, - renderError: base64, - url, - }); -}; - -export const createOrMergeEntry = ({ tree, - projectId, - branchId, - entry, - type, - parentTreeUrl, - level }) => { - const found = findEntry(tree.tree || tree, type, entry.name); - - if (found) { - return Object.assign({}, found, { - id: entry.id, - url: entry.url, - tempFile: false, - }); - } - - return decorateData({ - ...entry, - projectId, - branchId, - type, - parentTreeUrl, - level, - }); -}; diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb deleted file mode 100644 index 1ff25a45398..00000000000 --- a/app/controllers/ide_controller.rb +++ /dev/null @@ -1,6 +0,0 @@ -class IdeController < ApplicationController - layout 'nav_only' - - def index - end -end diff --git a/app/views/ide/index.html.haml b/app/views/ide/index.html.haml deleted file mode 100644 index 3dbdfc97654..00000000000 --- a/app/views/ide/index.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -- @body_class = 'ide' -- page_title 'IDE' - -- content_for :page_specific_javascripts do - = webpack_bundle_tag 'common_vue' - = webpack_bundle_tag 'ide', force_same_domain: true - -#ide.ide-loading{ data: {"empty-state-svg-path" => image_path('illustrations/multi_file_editor_empty.svg')} } - .text-center - = icon('spinner spin 2x') - %h2.clgray= _('Loading the GitLab IDE...') diff --git a/config/routes.rb b/config/routes.rb index e72ea1881cd..35fd76fb119 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -43,8 +43,6 @@ Rails.application.routes.draw do get 'liveness' => 'health#liveness' get 'readiness' => 'health#readiness' post 'storage_check' => 'health#storage_check' - get 'ide' => 'ide#index' - get 'ide/*vueroute' => 'ide#index', format: false resources :metrics, only: [:index] mount Peek::Railtie => '/peek' diff --git a/config/webpack.config.js b/config/webpack.config.js index 6d672ff00ba..33d174d5d61 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -53,7 +53,6 @@ function generateEntries() { common_vue: './vue_shared/vue_resource_interceptor.js', locale: './locale/index.js', main: './main.js', - ide: './ide/index.js', raven: './raven/index.js', test: './test.js', webpack_runtime: './webpack.js', diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb deleted file mode 100644 index 0c67196f53e..00000000000 --- a/spec/features/projects/tree/create_directory_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'spec_helper' - -feature 'Multi-file editor new directory', :js do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - - before do - project.add_master(user) - sign_in(user) - - set_cookie('new_repo', 'true') - - visit project_tree_path(project, :master) - - wait_for_requests - - click_link('Web IDE') - - wait_for_requests - end - - after do - set_cookie('new_repo', 'false') - end - - it 'creates directory in current directory' do - find('.add-to-tree').click - - click_link('New directory') - - page.within('.modal') do - find('.form-control').set('folder name') - - click_button('Create directory') - end - - find('.add-to-tree').click - - click_link('New file') - - page.within('.modal-dialog') do - find('.form-control').set('file name') - - click_button('Create file') - end - - wait_for_requests - - find('.multi-file-commit-panel-collapse-btn').click - - fill_in('commit-message', with: 'commit message ide') - - click_button('Commit') - - expect(page).to have_content('folder name') - end -end diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb deleted file mode 100644 index 85f7318c05d..00000000000 --- a/spec/features/projects/tree/create_file_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'spec_helper' - -feature 'Multi-file editor new file', :js do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - - before do - project.add_master(user) - sign_in(user) - - set_cookie('new_repo', 'true') - - visit project_tree_path(project, :master) - - wait_for_requests - - click_link('Web IDE') - - wait_for_requests - end - - after do - set_cookie('new_repo', 'false') - end - - it 'creates file in current directory' do - find('.add-to-tree').click - - click_link('New file') - - page.within('.modal') do - find('.form-control').set('file name') - - click_button('Create file') - end - - wait_for_requests - - find('.multi-file-commit-panel-collapse-btn').click - - fill_in('commit-message', with: 'commit message ide') - - click_button('Commit') - - expect(page).to have_content('file name') - end -end diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb deleted file mode 100644 index f81e8677e92..00000000000 --- a/spec/features/projects/tree/upload_file_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'spec_helper' - -feature 'Multi-file editor upload file', :js do - let(:user) { create(:user) } - let(:project) { create(:project, :repository) } - let(:txt_file) { File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt') } - let(:img_file) { File.join(Rails.root, 'spec', 'fixtures', 'dk.png') } - - before do - project.add_master(user) - sign_in(user) - - set_cookie('new_repo', 'true') - - visit project_tree_path(project, :master) - - wait_for_requests - - click_link('Web IDE') - - wait_for_requests - end - - after do - set_cookie('new_repo', 'false') - end - - it 'uploads text file' do - find('.add-to-tree').click - - # make the field visible so capybara can use it - execute_script('document.querySelector("#file-upload").classList.remove("hidden")') - attach_file('file-upload', txt_file) - - find('.add-to-tree').click - - expect(page).to have_selector('.multi-file-tab', text: 'doc_sample.txt') - expect(find('.blob-editor-container .lines-content')['innerText']).to have_content(File.open(txt_file, &:readline)) - end - - it 'uploads image file' do - find('.add-to-tree').click - - # make the field visible so capybara can use it - execute_script('document.querySelector("#file-upload").classList.remove("hidden")') - attach_file('file-upload', img_file) - - find('.add-to-tree').click - - expect(page).to have_selector('.multi-file-tab', text: 'dk.png') - expect(page).not_to have_selector('.monaco-editor') - end -end diff --git a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js deleted file mode 100644 index b509cedbe80..00000000000 --- a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js +++ /dev/null @@ -1,33 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { file } from '../../helpers'; - -describe('Multi-file editor commit sidebar list collapsed', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(listCollapsed); - - vm = createComponentWithStore(Component, store); - - vm.$store.state.openFiles.push(file('file1'), file('file2')); - vm.$store.state.openFiles[0].tempFile = true; - vm.$store.state.openFiles.forEach((f) => { - Object.assign(f, { - changed: true, - }); - }); - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders added & modified files count', () => { - expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toBe('1 1'); - }); -}); diff --git a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js deleted file mode 100644 index 6f1a1d874d3..00000000000 --- a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js +++ /dev/null @@ -1,53 +0,0 @@ -import Vue from 'vue'; -import listItem from '~/ide/components/commit_sidebar/list_item.vue'; -import mountComponent from 'spec/helpers/vue_mount_component_helper'; -import { file } from '../../helpers'; - -describe('Multi-file editor commit sidebar list item', () => { - let vm; - let f; - - beforeEach(() => { - const Component = Vue.extend(listItem); - - f = file('test-file'); - - vm = mountComponent(Component, { - file: f, - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders file path', () => { - expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent.trim()).toBe(f.path); - }); - - describe('computed', () => { - describe('iconName', () => { - it('returns modified when not a tempFile', () => { - expect(vm.iconName).toBe('file-modified'); - }); - - it('returns addition when not a tempFile', () => { - f.tempFile = true; - - expect(vm.iconName).toBe('file-addition'); - }); - }); - - describe('iconClass', () => { - it('returns modified when not a tempFile', () => { - expect(vm.iconClass).toContain('multi-file-modified'); - }); - - it('returns addition when not a tempFile', () => { - f.tempFile = true; - - expect(vm.iconClass).toContain('multi-file-addition'); - }); - }); - }); -}); diff --git a/spec/javascripts/repo/components/commit_sidebar/list_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_spec.js deleted file mode 100644 index aeb9de9ace4..00000000000 --- a/spec/javascripts/repo/components/commit_sidebar/list_spec.js +++ /dev/null @@ -1,59 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import commitSidebarList from '~/ide/components/commit_sidebar/list.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { file } from '../../helpers'; - -describe('Multi-file editor commit sidebar list', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(commitSidebarList); - - vm = createComponentWithStore(Component, store, { - title: 'Staged', - fileList: [], - }); - - vm.$store.state.rightPanelCollapsed = false; - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('empty file list', () => { - it('renders no changes text', () => { - expect(vm.$el.querySelector('.help-block').textContent.trim()).toBe('No changes'); - }); - }); - - describe('with a list of files', () => { - beforeEach((done) => { - const f = file('file name'); - f.changed = true; - vm.fileList.push(f); - - Vue.nextTick(done); - }); - - it('renders list', () => { - expect(vm.$el.querySelectorAll('li').length).toBe(1); - }); - }); - - describe('collapsed', () => { - beforeEach((done) => { - vm.$store.state.rightPanelCollapsed = true; - - Vue.nextTick(done); - }); - - it('hides list', () => { - expect(vm.$el.querySelector('.list-unstyled')).toBeNull(); - expect(vm.$el.querySelector('.help-block')).toBeNull(); - }); - }); -}); diff --git a/spec/javascripts/repo/components/ide_context_bar_spec.js b/spec/javascripts/repo/components/ide_context_bar_spec.js deleted file mode 100644 index 935da259a99..00000000000 --- a/spec/javascripts/repo/components/ide_context_bar_spec.js +++ /dev/null @@ -1,49 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import ideContextBar from '~/ide/components/ide_context_bar.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; - -describe('Multi-file editor right context bar', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(ideContextBar); - - vm = createComponentWithStore(Component, store); - - vm.$store.state.rightPanelCollapsed = false; - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - describe('collapsed', () => { - beforeEach((done) => { - vm.$store.state.rightPanelCollapsed = true; - - Vue.nextTick(done); - }); - - it('adds collapsed class', () => { - expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull(); - }); - - it('shows correct icon', () => { - expect(vm.currentIcon).toBe('angle-double-left'); - }); - }); - - it('clicking toggle collapse button collapses the bar', () => { - spyOn(vm, 'setPanelCollapsedStatus').and.returnValue(Promise.resolve()); - - vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click(); - - expect(vm.setPanelCollapsedStatus).toHaveBeenCalledWith({ - side: 'right', - collapsed: true, - }); - }); -}); diff --git a/spec/javascripts/repo/components/ide_repo_tree_spec.js b/spec/javascripts/repo/components/ide_repo_tree_spec.js deleted file mode 100644 index e3bbda514da..00000000000 --- a/spec/javascripts/repo/components/ide_repo_tree_spec.js +++ /dev/null @@ -1,63 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import ideRepoTree from '~/ide/components/ide_repo_tree.vue'; -import { file, resetStore } from '../helpers'; - -describe('IdeRepoTree', () => { - let vm; - - beforeEach(() => { - const IdeRepoTree = Vue.extend(ideRepoTree); - - vm = new IdeRepoTree({ - store, - propsData: { - treeId: 'abcproject/mybranch', - }, - }); - - vm.$store.state.currentBranch = 'master'; - vm.$store.state.isRoot = true; - vm.$store.state.trees['abcproject/mybranch'] = { - tree: [file()], - }; - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - it('renders a sidebar', () => { - const tbody = vm.$el.querySelector('tbody'); - - expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy(); - expect(tbody.querySelector('.repo-file-options')).toBeFalsy(); - expect(tbody.querySelector('.prev-directory')).toBeFalsy(); - expect(tbody.querySelector('.loading-file')).toBeFalsy(); - expect(tbody.querySelector('.file')).toBeTruthy(); - }); - - it('renders 3 loading files if tree is loading', (done) => { - vm.treeId = '123'; - - Vue.nextTick(() => { - expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toEqual(3); - - done(); - }); - }); - - it('renders a prev directory if is not root', (done) => { - vm.$store.state.isRoot = false; - - Vue.nextTick(() => { - expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy(); - - done(); - }); - }); -}); diff --git a/spec/javascripts/repo/components/ide_side_bar_spec.js b/spec/javascripts/repo/components/ide_side_bar_spec.js deleted file mode 100644 index 79c3c8128e8..00000000000 --- a/spec/javascripts/repo/components/ide_side_bar_spec.js +++ /dev/null @@ -1,43 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import ideSidebar from '~/ide/components/ide_side_bar.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { resetStore } from '../helpers'; - -describe('IdeSidebar', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(ideSidebar); - - vm = createComponentWithStore(Component, store).$mount(); - - vm.$store.state.leftPanelCollapsed = false; - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - it('renders a sidebar', () => { - expect(vm.$el.querySelector('.multi-file-commit-panel-inner')).not.toBeNull(); - }); - - describe('collapsed', () => { - beforeEach((done) => { - vm.$store.state.leftPanelCollapsed = true; - - Vue.nextTick(done); - }); - - it('adds collapsed class', () => { - expect(vm.$el.classList).toContain('is-collapsed'); - }); - - it('shows correct icon', () => { - expect(vm.currentIcon).toBe('angle-double-right'); - }); - }); -}); diff --git a/spec/javascripts/repo/components/ide_spec.js b/spec/javascripts/repo/components/ide_spec.js deleted file mode 100644 index 18135177b5e..00000000000 --- a/spec/javascripts/repo/components/ide_spec.js +++ /dev/null @@ -1,39 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import ide from '~/ide/components/ide.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { file, resetStore } from '../helpers'; - -describe('ide component', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(ide); - - vm = createComponentWithStore(Component, store, { - emptyStateSvgPath: 'svg', - }).$mount(); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - it('does not render panel right when no files open', () => { - expect(vm.$el.querySelector('.panel-right')).toBeNull(); - }); - - it('renders panel right when files are open', (done) => { - vm.$store.state.trees['abcproject/mybranch'] = { - tree: [file()], - }; - - Vue.nextTick(() => { - expect(vm.$el.querySelector('.panel-right')).toBeNull(); - - done(); - }); - }); -}); diff --git a/spec/javascripts/repo/components/new_branch_form_spec.js b/spec/javascripts/repo/components/new_branch_form_spec.js deleted file mode 100644 index 82597fc75e8..00000000000 --- a/spec/javascripts/repo/components/new_branch_form_spec.js +++ /dev/null @@ -1,114 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import newBranchForm from '~/ide/components/new_branch_form.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { resetStore } from '../helpers'; - -describe('Multi-file editor new branch form', () => { - let vm; - - beforeEach(() => { - const Component = Vue.extend(newBranchForm); - - vm = createComponentWithStore(Component, store); - - vm.$store.state.currentBranch = 'master'; - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - describe('template', () => { - it('renders submit as disabled', () => { - expect(vm.$el.querySelector('.btn').getAttribute('disabled')).toBe('disabled'); - }); - - it('enables the submit button when branch is not empty', (done) => { - vm.branchName = 'testing'; - - Vue.nextTick(() => { - expect(vm.$el.querySelector('.btn').getAttribute('disabled')).toBeNull(); - - done(); - }); - }); - - it('displays current branch creating from', (done) => { - Vue.nextTick(() => { - expect(vm.$el.querySelector('p').textContent.replace(/\s+/g, ' ').trim()).toBe('Create from: master'); - - done(); - }); - }); - }); - - describe('submitNewBranch', () => { - beforeEach(() => { - spyOn(vm, 'createNewBranch').and.returnValue(Promise.resolve()); - }); - - it('sets to loading', () => { - vm.submitNewBranch(); - - expect(vm.loading).toBeTruthy(); - }); - - it('hides current flash element', (done) => { - vm.$refs.flashContainer.innerHTML = '
'; - - vm.submitNewBranch(); - - Vue.nextTick(() => { - expect(vm.$el.querySelector('.flash-alert')).toBeNull(); - - done(); - }); - }); - - it('calls createdNewBranch with branchName', () => { - vm.branchName = 'testing'; - - vm.submitNewBranch(); - - expect(vm.createNewBranch).toHaveBeenCalledWith('testing'); - }); - }); - - describe('submitNewBranch with error', () => { - beforeEach(() => { - spyOn(vm, 'createNewBranch').and.returnValue(Promise.reject({ - json: () => Promise.resolve({ - message: 'error message', - }), - })); - }); - - it('sets loading to false', (done) => { - vm.loading = true; - - vm.submitNewBranch(); - - setTimeout(() => { - expect(vm.loading).toBeFalsy(); - - done(); - }); - }); - - it('creates flash element', (done) => { - vm.submitNewBranch(); - - setTimeout(() => { - expect(vm.$el.querySelector('.flash-alert')).not.toBeNull(); - expect(vm.$el.querySelector('.flash-alert').textContent.trim()).toBe('error message'); - - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/repo/components/new_dropdown/index_spec.js b/spec/javascripts/repo/components/new_dropdown/index_spec.js deleted file mode 100644 index 4a8e4445e2f..00000000000 --- a/spec/javascripts/repo/components/new_dropdown/index_spec.js +++ /dev/null @@ -1,77 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import newDropdown from '~/ide/components/new_dropdown/index.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { resetStore } from '../../helpers'; - -describe('new dropdown component', () => { - let vm; - - beforeEach(() => { - const component = Vue.extend(newDropdown); - - vm = createComponentWithStore(component, store, { - branch: 'master', - path: '', - }); - - vm.$store.state.currentProjectId = 'abcproject'; - vm.$store.state.path = ''; - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - it('renders new file, upload and new directory links', () => { - expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file'); - expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('Upload file'); - expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe('New directory'); - }); - - describe('createNewItem', () => { - it('sets modalType to blob when new file is clicked', () => { - vm.$el.querySelectorAll('a')[0].click(); - - expect(vm.modalType).toBe('blob'); - }); - - it('sets modalType to tree when new directory is clicked', () => { - vm.$el.querySelectorAll('a')[2].click(); - - expect(vm.modalType).toBe('tree'); - }); - - it('opens modal when link is clicked', (done) => { - vm.$el.querySelectorAll('a')[0].click(); - - Vue.nextTick(() => { - expect(vm.$el.querySelector('.modal')).not.toBeNull(); - - done(); - }); - }); - }); - - describe('hideModal', () => { - beforeAll((done) => { - vm.openModal = true; - Vue.nextTick(done); - }); - - it('closes modal after toggling', (done) => { - vm.hideModal(); - - Vue.nextTick() - .then(() => { - expect(vm.$el.querySelector('.modal')).toBeNull(); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/repo/components/new_dropdown/modal_spec.js b/spec/javascripts/repo/components/new_dropdown/modal_spec.js deleted file mode 100644 index d6a1fdd115c..00000000000 --- a/spec/javascripts/repo/components/new_dropdown/modal_spec.js +++ /dev/null @@ -1,237 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import service from '~/ide/services'; -import modal from '~/ide/components/new_dropdown/modal.vue'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { file, resetStore } from '../../helpers'; - -describe('new file modal component', () => { - const Component = Vue.extend(modal); - let vm; - let projectTree; - - beforeEach(() => { - spyOn(service, 'getProjectData').and.returnValue(Promise.resolve({ - data: { - id: '123', - }, - })); - - spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({ - data: { - commit: { - id: '123branch', - }, - }, - })); - - spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({ - headers: { - 'page-title': 'test', - }, - json: () => Promise.resolve({ - last_commit_path: 'last_commit_path', - parent_tree_url: 'parent_tree_url', - path: '/', - trees: [{ name: 'tree' }], - blobs: [{ name: 'blob' }], - submodules: [{ name: 'submodule' }], - }), - })); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - ['tree', 'blob'].forEach((type) => { - describe(type, () => { - beforeEach(() => { - store.state.projects.abcproject = { - web_url: '', - }; - store.state.trees = []; - store.state.trees['abcproject/mybranch'] = { - tree: [], - }; - projectTree = store.state.trees['abcproject/mybranch']; - store.state.currentProjectId = 'abcproject'; - - vm = createComponentWithStore(Component, store, { - type, - branchId: 'master', - path: '', - parent: projectTree, - }); - - vm.entryName = 'testing'; - - vm.$mount(); - }); - - it(`sets modal title as ${type}`, () => { - const title = type === 'tree' ? 'directory' : 'file'; - - expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Create new ${title}`); - }); - - it(`sets button label as ${type}`, () => { - const title = type === 'tree' ? 'directory' : 'file'; - - expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Create ${title}`); - }); - - it(`sets form label as ${type}`, () => { - const title = type === 'tree' ? 'Directory' : 'File'; - - expect(vm.$el.querySelector('.label-light').textContent.trim()).toBe(`${title} name`); - }); - - describe('createEntryInStore', () => { - it('calls createTempEntry', () => { - spyOn(vm, 'createTempEntry'); - - vm.createEntryInStore(); - - expect(vm.createTempEntry).toHaveBeenCalledWith({ - projectId: 'abcproject', - branchId: 'master', - parent: projectTree, - name: 'testing', - type, - }); - }); - - it('sets editMode to true', (done) => { - vm.createEntryInStore(); - - setTimeout(() => { - expect(vm.$store.state.editMode).toBeTruthy(); - - done(); - }); - }); - - it('toggles blob view', (done) => { - vm.createEntryInStore(); - - setTimeout(() => { - expect(vm.$store.state.currentBlobView).toBe('repo-editor'); - - done(); - }); - }); - - it('opens newly created file', (done) => { - if (type === 'blob') { - vm.createEntryInStore(); - - setTimeout(() => { - expect(vm.$store.state.openFiles.length).toBe(1); - expect(vm.$store.state.openFiles[0].name).toBe(type === 'blob' ? 'testing' : '.gitkeep'); - - done(); - }); - } else { - done(); - } - }); - - if (type === 'blob') { - it('creates new file', (done) => { - vm.createEntryInStore(); - - setTimeout(() => { - const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; - expect(baseTree.length).toBe(1); - expect(baseTree[0].name).toBe('testing'); - expect(baseTree[0].type).toBe('blob'); - expect(baseTree[0].tempFile).toBeTruthy(); - - done(); - }); - }); - - it('does not create temp file when file already exists', (done) => { - const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; - baseTree.push(file('testing', '1', type)); - - vm.createEntryInStore(); - - setTimeout(() => { - expect(baseTree.length).toBe(1); - expect(baseTree[0].name).toBe('testing'); - expect(baseTree[0].type).toBe('blob'); - expect(baseTree[0].tempFile).toBeFalsy(); - - done(); - }); - }); - } else { - it('creates new tree', () => { - vm.createEntryInStore(); - - const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; - expect(baseTree.length).toBe(1); - expect(baseTree[0].name).toBe('testing'); - expect(baseTree[0].type).toBe('tree'); - expect(baseTree[0].tempFile).toBeTruthy(); - }); - - it('creates multiple trees when entryName has slashes', () => { - vm.entryName = 'app/test'; - vm.createEntryInStore(); - - const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; - expect(baseTree.length).toBe(1); - expect(baseTree[0].name).toBe('app'); - }); - - it('creates tree in existing tree', () => { - const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; - baseTree.push(file('app', '1', 'tree')); - - vm.entryName = 'app/test'; - vm.createEntryInStore(); - - expect(baseTree.length).toBe(1); - expect(baseTree[0].name).toBe('app'); - expect(baseTree[0].tempFile).toBeFalsy(); - expect(baseTree[0].tree[0].tempFile).toBeTruthy(); - expect(baseTree[0].tree[0].name).toBe('test'); - }); - - it('does not create new tree when already exists', () => { - const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; - baseTree.push(file('app', '1', 'tree')); - - vm.entryName = 'app'; - vm.createEntryInStore(); - - expect(baseTree.length).toBe(1); - expect(baseTree[0].name).toBe('app'); - expect(baseTree[0].tempFile).toBeFalsy(); - expect(baseTree[0].tree.length).toBe(0); - }); - } - }); - }); - }); - - it('focuses field on mount', () => { - document.body.innerHTML += '
'; - - vm = createComponentWithStore(Component, store, { - type: 'tree', - projectId: 'abcproject', - branchId: 'master', - path: '', - }).$mount('.js-test'); - - expect(document.activeElement).toBe(vm.$refs.fieldName); - - vm.$el.remove(); - }); -}); diff --git a/spec/javascripts/repo/components/new_dropdown/upload_spec.js b/spec/javascripts/repo/components/new_dropdown/upload_spec.js deleted file mode 100644 index ee8aab3a252..00000000000 --- a/spec/javascripts/repo/components/new_dropdown/upload_spec.js +++ /dev/null @@ -1,158 +0,0 @@ -import Vue from 'vue'; -import upload from '~/ide/components/new_dropdown/upload.vue'; -import store from '~/ide/stores'; -import service from '~/ide/services'; -import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; -import { resetStore } from '../../helpers'; - -describe('new dropdown upload', () => { - let vm; - let projectTree; - - beforeEach(() => { - spyOn(service, 'getProjectData').and.returnValue(Promise.resolve({ - data: { - id: '123', - }, - })); - - spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({ - data: { - commit: { - id: '123branch', - }, - }, - })); - - spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({ - headers: { - 'page-title': 'test', - }, - json: () => Promise.resolve({ - last_commit_path: 'last_commit_path', - parent_tree_url: 'parent_tree_url', - path: '/', - trees: [{ name: 'tree' }], - blobs: [{ name: 'blob' }], - submodules: [{ name: 'submodule' }], - }), - })); - - const Component = Vue.extend(upload); - - store.state.projects.abcproject = { - web_url: '', - }; - store.state.currentProjectId = 'abcproject'; - store.state.trees = []; - store.state.trees['abcproject/mybranch'] = { - tree: [], - }; - projectTree = store.state.trees['abcproject/mybranch']; - - vm = createComponentWithStore(Component, store, { - branchId: 'master', - path: '', - parent: projectTree, - }); - - vm.entryName = 'testing'; - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - describe('readFile', () => { - beforeEach(() => { - spyOn(FileReader.prototype, 'readAsText'); - spyOn(FileReader.prototype, 'readAsDataURL'); - }); - - it('calls readAsText for text files', () => { - const file = { - type: 'text/html', - }; - - vm.readFile(file); - - expect(FileReader.prototype.readAsText).toHaveBeenCalledWith(file); - }); - - it('calls readAsDataURL for non-text files', () => { - const file = { - type: 'images/png', - }; - - vm.readFile(file); - - expect(FileReader.prototype.readAsDataURL).toHaveBeenCalledWith(file); - }); - }); - - describe('createFile', () => { - const target = { - result: 'content', - }; - const binaryTarget = { - result: 'base64,base64content', - }; - const file = { - name: 'file', - }; - - it('creates new file', (done) => { - vm.createFile(target, file, true); - - vm.$nextTick(() => { - const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; - expect(baseTree.length).toBe(1); - expect(baseTree[0].name).toBe(file.name); - expect(baseTree[0].content).toBe(target.result); - - done(); - }); - }); - - it('creates new file in path', (done) => { - const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; - const tree = { - type: 'tree', - name: 'testing', - path: 'testing', - tree: [], - }; - baseTree.push(tree); - - vm.parent = tree; - vm.createFile(target, file, true); - - vm.$nextTick(() => { - expect(baseTree.length).toBe(1); - expect(baseTree[0].tree[0].name).toBe(file.name); - expect(baseTree[0].tree[0].content).toBe(target.result); - expect(baseTree[0].tree[0].path).toBe(`testing/${file.name}`); - - done(); - }); - }); - - it('splits content on base64 if binary', (done) => { - vm.createFile(binaryTarget, file, false); - - vm.$nextTick(() => { - const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree; - expect(baseTree.length).toBe(1); - expect(baseTree[0].name).toBe(file.name); - expect(baseTree[0].content).toBe(binaryTarget.result.split('base64,')[1]); - expect(baseTree[0].base64).toBe(true); - - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js deleted file mode 100644 index 934ada9dec2..00000000000 --- a/spec/javascripts/repo/components/repo_commit_section_spec.js +++ /dev/null @@ -1,140 +0,0 @@ -import Vue from 'vue'; -import * as urlUtils from '~/lib/utils/url_utility'; -import store from '~/ide/stores'; -import service from '~/ide/services'; -import repoCommitSection from '~/ide/components/repo_commit_section.vue'; -import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper'; -import { file, resetStore } from '../helpers'; - -describe('RepoCommitSection', () => { - let vm; - - function createComponent() { - const RepoCommitSection = Vue.extend(repoCommitSection); - - const comp = new RepoCommitSection({ - store, - }).$mount(); - - comp.$store.state.currentProjectId = 'abcproject'; - comp.$store.state.currentBranchId = 'master'; - comp.$store.state.projects.abcproject = { - web_url: '', - branches: { - master: { - workingReference: '1', - }, - }, - }; - - comp.$store.state.rightPanelCollapsed = false; - comp.$store.state.currentBranch = 'master'; - comp.$store.state.openFiles = [file('file1'), file('file2')]; - comp.$store.state.openFiles.forEach(f => Object.assign(f, { - changed: true, - content: 'testing', - })); - - return comp.$mount(); - } - - beforeEach((done) => { - vm = createComponent(); - - spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({ - headers: { - 'page-title': 'test', - }, - json: () => Promise.resolve({ - last_commit_path: 'last_commit_path', - parent_tree_url: 'parent_tree_url', - path: '/', - trees: [{ name: 'tree' }], - blobs: [{ name: 'blob' }], - submodules: [{ name: 'submodule' }], - }), - })); - - Vue.nextTick(done); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - it('renders a commit section', () => { - const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list li')]; - const submitCommit = vm.$el.querySelector('form .btn'); - - expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull(); - expect(changedFileElements.length).toEqual(2); - - changedFileElements.forEach((changedFile, i) => { - expect(changedFile.textContent.trim()).toEqual(vm.$store.getters.changedFiles[i].path); - }); - - expect(submitCommit.disabled).toBeTruthy(); - expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeNull(); - }); - - describe('when submitting', () => { - let changedFiles; - - beforeEach(() => { - vm.commitMessage = 'testing'; - changedFiles = JSON.parse(JSON.stringify(vm.$store.getters.changedFiles)); - - spyOn(service, 'commit').and.returnValue(Promise.resolve({ - data: { - short_id: '1', - stats: {}, - }, - })); - }); - - it('allows you to submit', () => { - expect(vm.$el.querySelector('form .btn').disabled).toBeTruthy(); - }); - - it('submits commit', (done) => { - vm.makeCommit(); - - // Wait for the branch check to finish - getSetTimeoutPromise() - .then(() => Vue.nextTick()) - .then(() => { - const args = service.commit.calls.allArgs()[0]; - const { commit_message, actions, branch: payloadBranch } = args[1]; - - expect(commit_message).toBe('testing'); - expect(actions.length).toEqual(2); - expect(payloadBranch).toEqual('master'); - expect(actions[0].action).toEqual('update'); - expect(actions[1].action).toEqual('update'); - expect(actions[0].content).toEqual(changedFiles[0].content); - expect(actions[1].content).toEqual(changedFiles[1].content); - expect(actions[0].file_path).toEqual(changedFiles[0].path); - expect(actions[1].file_path).toEqual(changedFiles[1].path); - }) - .then(done) - .catch(done.fail); - }); - - it('redirects to MR creation page if start new MR checkbox checked', (done) => { - spyOn(urlUtils, 'visitUrl'); - vm.startNewMR = true; - - vm.makeCommit(); - - getSetTimeoutPromise() - .then(() => Vue.nextTick()) - .then(() => { - expect(urlUtils.visitUrl).toHaveBeenCalled(); - }) - .then(done) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/repo/components/repo_edit_button_spec.js b/spec/javascripts/repo/components/repo_edit_button_spec.js deleted file mode 100644 index 2895b794506..00000000000 --- a/spec/javascripts/repo/components/repo_edit_button_spec.js +++ /dev/null @@ -1,83 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import repoEditButton from '~/ide/components/repo_edit_button.vue'; -import { file, resetStore } from '../helpers'; - -describe('RepoEditButton', () => { - let vm; - - beforeEach(() => { - const f = file(); - const RepoEditButton = Vue.extend(repoEditButton); - - vm = new RepoEditButton({ - store, - }); - - f.active = true; - vm.$store.dispatch('setInitialData', { - canCommit: true, - onTopOfBranch: true, - }); - vm.$store.state.openFiles.push(f); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - it('renders an edit button', () => { - vm.$mount(); - - expect(vm.$el.querySelector('.btn')).not.toBeNull(); - expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit'); - }); - - it('renders edit button with cancel text', () => { - vm.$store.state.editMode = true; - - vm.$mount(); - - expect(vm.$el.querySelector('.btn')).not.toBeNull(); - expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit'); - }); - - it('toggles edit mode on click', (done) => { - vm.$mount(); - - vm.$el.querySelector('.btn').click(); - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Edit'); - - done(); - }); - }); - - describe('discardPopupOpen', () => { - beforeEach(() => { - vm.$store.state.discardPopupOpen = true; - vm.$store.state.editMode = true; - vm.$store.state.openFiles[0].changed = true; - - vm.$mount(); - }); - - it('renders popup', () => { - expect(vm.$el.querySelector('.modal')).not.toBeNull(); - }); - - it('removes all changed files', (done) => { - vm.$el.querySelector('.btn-warning').click(); - - vm.$nextTick(() => { - expect(vm.$store.getters.changedFiles.length).toBe(0); - expect(vm.$el.querySelector('.modal')).toBeNull(); - - done(); - }); - }); - }); -}); diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js deleted file mode 100644 index e7b2ed08acd..00000000000 --- a/spec/javascripts/repo/components/repo_editor_spec.js +++ /dev/null @@ -1,60 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import repoEditor from '~/ide/components/repo_editor.vue'; -import monacoLoader from '~/ide/monaco_loader'; -import { file, resetStore } from '../helpers'; - -describe('RepoEditor', () => { - let vm; - - beforeEach((done) => { - const f = file(); - const RepoEditor = Vue.extend(repoEditor); - - vm = new RepoEditor({ - store, - }); - - f.active = true; - f.tempFile = true; - vm.$store.state.openFiles.push(f); - vm.$store.getters.activeFile.html = 'testing'; - vm.monaco = true; - - vm.$mount(); - - monacoLoader(['vs/editor/editor.main'], () => { - setTimeout(done, 0); - }); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - it('renders an ide container', (done) => { - Vue.nextTick(() => { - expect(vm.shouldHideEditor).toBeFalsy(); - - done(); - }); - }); - - describe('when open file is binary and not raw', () => { - beforeEach((done) => { - vm.$store.getters.activeFile.binary = true; - - Vue.nextTick(done); - }); - - it('does not render the IDE', () => { - expect(vm.shouldHideEditor).toBeTruthy(); - }); - - it('shows activeFile html', () => { - expect(vm.$el.textContent).toContain('testing'); - }); - }); -}); diff --git a/spec/javascripts/repo/components/repo_file_buttons_spec.js b/spec/javascripts/repo/components/repo_file_buttons_spec.js deleted file mode 100644 index 115569a9117..00000000000 --- a/spec/javascripts/repo/components/repo_file_buttons_spec.js +++ /dev/null @@ -1,49 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import repoFileButtons from '~/ide/components/repo_file_buttons.vue'; -import { file, resetStore } from '../helpers'; - -describe('RepoFileButtons', () => { - const activeFile = file(); - let vm; - - function createComponent() { - const RepoFileButtons = Vue.extend(repoFileButtons); - - activeFile.rawPath = 'test'; - activeFile.blamePath = 'test'; - activeFile.commitsPath = 'test'; - activeFile.active = true; - store.state.openFiles.push(activeFile); - - return new RepoFileButtons({ - store, - }).$mount(); - } - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - it('renders Raw, Blame, History, Permalink and Preview toggle', (done) => { - vm = createComponent(); - - vm.$nextTick(() => { - const raw = vm.$el.querySelector('.raw'); - const blame = vm.$el.querySelector('.blame'); - const history = vm.$el.querySelector('.history'); - - expect(raw.href).toMatch(`/${activeFile.rawPath}`); - expect(raw.textContent.trim()).toEqual('Raw'); - expect(blame.href).toMatch(`/${activeFile.blamePath}`); - expect(blame.textContent.trim()).toEqual('Blame'); - expect(history.href).toMatch(`/${activeFile.commitsPath}`); - expect(history.textContent.trim()).toEqual('History'); - expect(vm.$el.querySelector('.permalink').textContent.trim()).toEqual('Permalink'); - - done(); - }); - }); -}); diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js deleted file mode 100644 index 27b55ed1f87..00000000000 --- a/spec/javascripts/repo/components/repo_file_spec.js +++ /dev/null @@ -1,98 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import repoFile from '~/ide/components/repo_file.vue'; -import { file, resetStore } from '../helpers'; - -describe('RepoFile', () => { - const updated = 'updated'; - let vm; - - function createComponent(propsData) { - const RepoFile = Vue.extend(repoFile); - - return new RepoFile({ - store, - propsData, - }).$mount(); - } - - afterEach(() => { - resetStore(vm.$store); - }); - - it('renders link, icon and name', () => { - const RepoFile = Vue.extend(repoFile); - vm = new RepoFile({ - store, - propsData: { - file: file('t4'), - }, - }); - spyOn(vm, 'timeFormated').and.returnValue(updated); - vm.$mount(); - - const name = vm.$el.querySelector('.repo-file-name'); - - expect(name.href).toMatch(''); - expect(name.textContent.trim()).toEqual(vm.file.name); - }); - - it('does render if hasFiles is true and is loading tree', () => { - vm = createComponent({ - file: file('t1'), - }); - - expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy(); - }); - - it('does not render commit message and datetime if mini', (done) => { - vm = createComponent({ - file: file('t2'), - }); - vm.$store.state.openFiles.push(vm.file); - - vm.$nextTick(() => { - expect(vm.$el.querySelector('.commit-message')).toBeFalsy(); - expect(vm.$el.querySelector('.commit-update')).toBeFalsy(); - - done(); - }); - }); - - it('fires clickFile when the link is clicked', () => { - vm = createComponent({ - file: file('t3'), - }); - - spyOn(vm, 'clickFile'); - - vm.$el.click(); - - expect(vm.clickFile).toHaveBeenCalledWith(vm.file); - }); - - describe('submodule', () => { - let f; - - beforeEach(() => { - f = file('submodule name', '123456789'); - f.type = 'submodule'; - - vm = createComponent({ - file: f, - }); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders submodule short ID', () => { - expect(vm.$el.querySelector('.commit-sha').textContent.trim()).toBe('12345678'); - }); - - it('renders ID next to submodule name', () => { - expect(vm.$el.querySelector('td').textContent.replace(/\s+/g, ' ')).toContain('submodule name @ 12345678'); - }); - }); -}); diff --git a/spec/javascripts/repo/components/repo_loading_file_spec.js b/spec/javascripts/repo/components/repo_loading_file_spec.js deleted file mode 100644 index 18366fb89bc..00000000000 --- a/spec/javascripts/repo/components/repo_loading_file_spec.js +++ /dev/null @@ -1,63 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import repoLoadingFile from '~/ide/components/repo_loading_file.vue'; -import { resetStore } from '../helpers'; - -describe('RepoLoadingFile', () => { - let vm; - - function createComponent() { - const RepoLoadingFile = Vue.extend(repoLoadingFile); - - return new RepoLoadingFile({ - store, - }).$mount(); - } - - function assertLines(lines) { - lines.forEach((line, n) => { - const index = n + 1; - expect(line.classList.contains(`skeleton-line-${index}`)).toBeTruthy(); - }); - } - - function assertColumns(columns) { - columns.forEach((column) => { - const container = column.querySelector('.animation-container'); - const lines = [...container.querySelectorAll(':scope > div')]; - - expect(container).toBeTruthy(); - expect(lines.length).toEqual(6); - assertLines(lines); - }); - } - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - it('renders 3 columns of animated LoC', () => { - vm = createComponent(); - const columns = [...vm.$el.querySelectorAll('td')]; - - expect(columns.length).toEqual(3); - assertColumns(columns); - }); - - it('renders 1 column of animated LoC if isMini', (done) => { - vm = createComponent(); - vm.$store.state.leftPanelCollapsed = true; - vm.$store.state.openFiles.push('test'); - - vm.$nextTick(() => { - const columns = [...vm.$el.querySelectorAll('td')]; - - expect(columns.length).toEqual(1); - assertColumns(columns); - - done(); - }); - }); -}); diff --git a/spec/javascripts/repo/components/repo_prev_directory_spec.js b/spec/javascripts/repo/components/repo_prev_directory_spec.js deleted file mode 100644 index ff26cab2262..00000000000 --- a/spec/javascripts/repo/components/repo_prev_directory_spec.js +++ /dev/null @@ -1,45 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import repoPrevDirectory from '~/ide/components/repo_prev_directory.vue'; -import { resetStore } from '../helpers'; - -describe('RepoPrevDirectory', () => { - let vm; - const parentLink = 'parent'; - function createComponent() { - const RepoPrevDirectory = Vue.extend(repoPrevDirectory); - - const comp = new RepoPrevDirectory({ - store, - }); - - comp.$store.state.parentTreeUrl = parentLink; - - return comp.$mount(); - } - - beforeEach(() => { - vm = createComponent(); - }); - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - it('renders a prev dir link', () => { - const link = vm.$el.querySelector('a'); - - expect(link.href).toMatch(`/${parentLink}`); - expect(link.textContent).toEqual('...'); - }); - - it('clicking row triggers getTreeData', () => { - spyOn(vm, 'getTreeData'); - - vm.$el.querySelector('td').click(); - - expect(vm.getTreeData).toHaveBeenCalledWith({ endpoint: parentLink }); - }); -}); diff --git a/spec/javascripts/repo/components/repo_preview_spec.js b/spec/javascripts/repo/components/repo_preview_spec.js deleted file mode 100644 index e90837e4cb2..00000000000 --- a/spec/javascripts/repo/components/repo_preview_spec.js +++ /dev/null @@ -1,37 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import repoPreview from '~/ide/components/repo_preview.vue'; -import { file, resetStore } from '../helpers'; - -describe('RepoPreview', () => { - let vm; - - function createComponent() { - const f = file(); - const RepoPreview = Vue.extend(repoPreview); - - const comp = new RepoPreview({ - store, - }); - - f.active = true; - f.html = 'test'; - - comp.$store.state.openFiles.push(f); - - return comp.$mount(); - } - - afterEach(() => { - vm.$destroy(); - - resetStore(vm.$store); - }); - - it('renders a div with the activeFile html', () => { - vm = createComponent(); - - expect(vm.$el.tagName).toEqual('DIV'); - expect(vm.$el.innerHTML).toContain('test'); - }); -}); diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js deleted file mode 100644 index 933e8d3a06a..00000000000 --- a/spec/javascripts/repo/components/repo_tab_spec.js +++ /dev/null @@ -1,108 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import repoTab from '~/ide/components/repo_tab.vue'; -import { file, resetStore } from '../helpers'; - -describe('RepoTab', () => { - let vm; - - function createComponent(propsData) { - const RepoTab = Vue.extend(repoTab); - - return new RepoTab({ - store, - propsData, - }).$mount(); - } - - afterEach(() => { - resetStore(vm.$store); - }); - - it('renders a close link and a name link', () => { - vm = createComponent({ - tab: file(), - }); - vm.$store.state.openFiles.push(vm.tab); - const close = vm.$el.querySelector('.multi-file-tab-close'); - const name = vm.$el.querySelector(`[title="${vm.tab.url}"]`); - - expect(close.querySelector('.fa-times')).toBeTruthy(); - expect(name.textContent.trim()).toEqual(vm.tab.name); - }); - - it('fires clickFile when the link is clicked', () => { - vm = createComponent({ - tab: file(), - }); - - spyOn(vm, 'clickFile'); - - vm.$el.click(); - - expect(vm.clickFile).toHaveBeenCalledWith(vm.tab); - }); - - it('calls closeFile when clicking close button', () => { - vm = createComponent({ - tab: file(), - }); - - spyOn(vm, 'closeFile'); - - vm.$el.querySelector('.multi-file-tab-close').click(); - - expect(vm.closeFile).toHaveBeenCalledWith({ file: vm.tab }); - }); - - it('renders an fa-circle icon if tab is changed', () => { - const tab = file('changedFile'); - tab.changed = true; - vm = createComponent({ - tab, - }); - - expect(vm.$el.querySelector('.multi-file-tab-close .fa-circle')).not.toBeNull(); - }); - - describe('methods', () => { - describe('closeTab', () => { - it('does not close tab if is changed', (done) => { - const tab = file('closeFile'); - tab.changed = true; - tab.opened = true; - vm = createComponent({ - tab, - }); - vm.$store.state.openFiles.push(tab); - vm.$store.dispatch('setFileActive', tab); - - vm.$el.querySelector('.multi-file-tab-close').click(); - - vm.$nextTick(() => { - expect(tab.opened).toBeTruthy(); - - done(); - }); - }); - - it('closes tab when clicking close btn', (done) => { - const tab = file('lose'); - tab.opened = true; - vm = createComponent({ - tab, - }); - vm.$store.state.openFiles.push(tab); - vm.$store.dispatch('setFileActive', tab); - - vm.$el.querySelector('.multi-file-tab-close').click(); - - vm.$nextTick(() => { - expect(tab.opened).toBeFalsy(); - - done(); - }); - }); - }); - }); -}); diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js deleted file mode 100644 index 2c363364d70..00000000000 --- a/spec/javascripts/repo/components/repo_tabs_spec.js +++ /dev/null @@ -1,37 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import repoTabs from '~/ide/components/repo_tabs.vue'; -import { file, resetStore } from '../helpers'; - -describe('RepoTabs', () => { - const openedFiles = [file('open1'), file('open2')]; - let vm; - - function createComponent() { - const RepoTabs = Vue.extend(repoTabs); - - return new RepoTabs({ - store, - }).$mount(); - } - - afterEach(() => { - resetStore(vm.$store); - }); - - it('renders a list of tabs', (done) => { - vm = createComponent(); - openedFiles[0].active = true; - vm.$store.state.openFiles = openedFiles; - - vm.$nextTick(() => { - const tabs = [...vm.$el.querySelectorAll('.multi-file-tab')]; - - expect(tabs.length).toEqual(2); - expect(tabs[0].classList.contains('active')).toBeTruthy(); - expect(tabs[1].classList.contains('active')).toBeFalsy(); - - done(); - }); - }); -}); diff --git a/spec/javascripts/repo/helpers.js b/spec/javascripts/repo/helpers.js deleted file mode 100644 index ac43d221198..00000000000 --- a/spec/javascripts/repo/helpers.js +++ /dev/null @@ -1,16 +0,0 @@ -import { decorateData } from '~/ide/stores/utils'; -import state from '~/ide/stores/state'; - -export const resetStore = (store) => { - store.replaceState(state()); -}; - -export const file = (name = 'name', id = name, type = '') => decorateData({ - id, - type, - icon: 'icon', - url: 'url', - name, - path: name, - lastCommit: {}, -}); diff --git a/spec/javascripts/repo/lib/common/disposable_spec.js b/spec/javascripts/repo/lib/common/disposable_spec.js deleted file mode 100644 index af12ca15369..00000000000 --- a/spec/javascripts/repo/lib/common/disposable_spec.js +++ /dev/null @@ -1,44 +0,0 @@ -import Disposable from '~/ide/lib/common/disposable'; - -describe('Multi-file editor library disposable class', () => { - let instance; - let disposableClass; - - beforeEach(() => { - instance = new Disposable(); - - disposableClass = { - dispose: jasmine.createSpy('dispose'), - }; - }); - - afterEach(() => { - instance.dispose(); - }); - - describe('add', () => { - it('adds disposable classes', () => { - instance.add(disposableClass); - - expect(instance.disposers.size).toBe(1); - }); - }); - - describe('dispose', () => { - beforeEach(() => { - instance.add(disposableClass); - }); - - it('calls dispose on all cached disposers', () => { - instance.dispose(); - - expect(disposableClass.dispose).toHaveBeenCalled(); - }); - - it('clears cached disposers', () => { - instance.dispose(); - - expect(instance.disposers.size).toBe(0); - }); - }); -}); diff --git a/spec/javascripts/repo/lib/common/model_manager_spec.js b/spec/javascripts/repo/lib/common/model_manager_spec.js deleted file mode 100644 index 563c2e33834..00000000000 --- a/spec/javascripts/repo/lib/common/model_manager_spec.js +++ /dev/null @@ -1,81 +0,0 @@ -/* global monaco */ -import monacoLoader from '~/ide/monaco_loader'; -import ModelManager from '~/ide/lib/common/model_manager'; -import { file } from '../../helpers'; - -describe('Multi-file editor library model manager', () => { - let instance; - - beforeEach((done) => { - monacoLoader(['vs/editor/editor.main'], () => { - instance = new ModelManager(monaco); - - done(); - }); - }); - - afterEach(() => { - instance.dispose(); - }); - - describe('addModel', () => { - it('caches model', () => { - instance.addModel(file()); - - expect(instance.models.size).toBe(1); - }); - - it('caches model by file path', () => { - instance.addModel(file('path-name')); - - expect(instance.models.keys().next().value).toBe('path-name'); - }); - - it('adds model into disposable', () => { - spyOn(instance.disposable, 'add').and.callThrough(); - - instance.addModel(file()); - - expect(instance.disposable.add).toHaveBeenCalled(); - }); - - it('returns cached model', () => { - spyOn(instance.models, 'get').and.callThrough(); - - instance.addModel(file()); - instance.addModel(file()); - - expect(instance.models.get).toHaveBeenCalled(); - }); - }); - - describe('hasCachedModel', () => { - it('returns false when no models exist', () => { - expect(instance.hasCachedModel('path')).toBeFalsy(); - }); - - it('returns true when model exists', () => { - instance.addModel(file('path-name')); - - expect(instance.hasCachedModel('path-name')).toBeTruthy(); - }); - }); - - describe('dispose', () => { - it('clears cached models', () => { - instance.addModel(file()); - - instance.dispose(); - - expect(instance.models.size).toBe(0); - }); - - it('calls disposable dispose', () => { - spyOn(instance.disposable, 'dispose').and.callThrough(); - - instance.dispose(); - - expect(instance.disposable.dispose).toHaveBeenCalled(); - }); - }); -}); diff --git a/spec/javascripts/repo/lib/common/model_spec.js b/spec/javascripts/repo/lib/common/model_spec.js deleted file mode 100644 index 878a4a3f3fe..00000000000 --- a/spec/javascripts/repo/lib/common/model_spec.js +++ /dev/null @@ -1,84 +0,0 @@ -/* global monaco */ -import monacoLoader from '~/ide/monaco_loader'; -import Model from '~/ide/lib/common/model'; -import { file } from '../../helpers'; - -describe('Multi-file editor library model', () => { - let model; - - beforeEach((done) => { - monacoLoader(['vs/editor/editor.main'], () => { - model = new Model(monaco, file('path')); - - done(); - }); - }); - - afterEach(() => { - model.dispose(); - }); - - it('creates original model & new model', () => { - expect(model.originalModel).not.toBeNull(); - expect(model.model).not.toBeNull(); - }); - - describe('path', () => { - it('returns file path', () => { - expect(model.path).toBe('path'); - }); - }); - - describe('getModel', () => { - it('returns model', () => { - expect(model.getModel()).toBe(model.model); - }); - }); - - describe('getOriginalModel', () => { - it('returns original model', () => { - expect(model.getOriginalModel()).toBe(model.originalModel); - }); - }); - - describe('onChange', () => { - it('caches event by path', () => { - model.onChange(() => {}); - - expect(model.events.size).toBe(1); - expect(model.events.keys().next().value).toBe('path'); - }); - - it('calls callback on change', (done) => { - const spy = jasmine.createSpy(); - model.onChange(spy); - - model.getModel().setValue('123'); - - setTimeout(() => { - expect(spy).toHaveBeenCalledWith(model.getModel(), jasmine.anything()); - done(); - }); - }); - }); - - describe('dispose', () => { - it('calls disposable dispose', () => { - spyOn(model.disposable, 'dispose').and.callThrough(); - - model.dispose(); - - expect(model.disposable.dispose).toHaveBeenCalled(); - }); - - it('clears events', () => { - model.onChange(() => {}); - - expect(model.events.size).toBe(1); - - model.dispose(); - - expect(model.events.size).toBe(0); - }); - }); -}); diff --git a/spec/javascripts/repo/lib/decorations/controller_spec.js b/spec/javascripts/repo/lib/decorations/controller_spec.js deleted file mode 100644 index fea12d74dca..00000000000 --- a/spec/javascripts/repo/lib/decorations/controller_spec.js +++ /dev/null @@ -1,120 +0,0 @@ -/* global monaco */ -import monacoLoader from '~/ide/monaco_loader'; -import editor from '~/ide/lib/editor'; -import DecorationsController from '~/ide/lib/decorations/controller'; -import Model from '~/ide/lib/common/model'; -import { file } from '../../helpers'; - -describe('Multi-file editor library decorations controller', () => { - let editorInstance; - let controller; - let model; - - beforeEach((done) => { - monacoLoader(['vs/editor/editor.main'], () => { - editorInstance = editor.create(monaco); - editorInstance.createInstance(document.createElement('div')); - - controller = new DecorationsController(editorInstance); - model = new Model(monaco, file('path')); - - done(); - }); - }); - - afterEach(() => { - model.dispose(); - editorInstance.dispose(); - controller.dispose(); - }); - - describe('getAllDecorationsForModel', () => { - it('returns empty array when no decorations exist for model', () => { - const decorations = controller.getAllDecorationsForModel(model); - - expect(decorations).toEqual([]); - }); - - it('returns decorations by model URL', () => { - controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); - - const decorations = controller.getAllDecorationsForModel(model); - - expect(decorations[0]).toEqual({ decoration: 'decorationValue' }); - }); - }); - - describe('addDecorations', () => { - it('caches decorations in a new map', () => { - controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); - - expect(controller.decorations.size).toBe(1); - }); - - it('does not create new cache model', () => { - controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); - controller.addDecorations(model, 'key', [{ decoration: 'decorationValue2' }]); - - expect(controller.decorations.size).toBe(1); - }); - - it('caches decorations by model URL', () => { - controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); - - expect(controller.decorations.size).toBe(1); - expect(controller.decorations.keys().next().value).toBe('path'); - }); - - it('calls decorate method', () => { - spyOn(controller, 'decorate'); - - controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); - - expect(controller.decorate).toHaveBeenCalled(); - }); - }); - - describe('decorate', () => { - it('sets decorations on editor instance', () => { - spyOn(controller.editor.instance, 'deltaDecorations'); - - controller.decorate(model); - - expect(controller.editor.instance.deltaDecorations).toHaveBeenCalledWith([], []); - }); - - it('caches decorations', () => { - spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]); - - controller.decorate(model); - - expect(controller.editorDecorations.size).toBe(1); - }); - - it('caches decorations by model URL', () => { - spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]); - - controller.decorate(model); - - expect(controller.editorDecorations.keys().next().value).toBe('path'); - }); - }); - - describe('dispose', () => { - it('clears cached decorations', () => { - controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); - - controller.dispose(); - - expect(controller.decorations.size).toBe(0); - }); - - it('clears cached editorDecorations', () => { - controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]); - - controller.dispose(); - - expect(controller.editorDecorations.size).toBe(0); - }); - }); -}); diff --git a/spec/javascripts/repo/lib/diff/controller_spec.js b/spec/javascripts/repo/lib/diff/controller_spec.js deleted file mode 100644 index 1d55c165260..00000000000 --- a/spec/javascripts/repo/lib/diff/controller_spec.js +++ /dev/null @@ -1,176 +0,0 @@ -/* global monaco */ -import monacoLoader from '~/ide/monaco_loader'; -import editor from '~/ide/lib/editor'; -import ModelManager from '~/ide/lib/common/model_manager'; -import DecorationsController from '~/ide/lib/decorations/controller'; -import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/ide/lib/diff/controller'; -import { computeDiff } from '~/ide/lib/diff/diff'; -import { file } from '../../helpers'; - -describe('Multi-file editor library dirty diff controller', () => { - let editorInstance; - let controller; - let modelManager; - let decorationsController; - let model; - - beforeEach((done) => { - monacoLoader(['vs/editor/editor.main'], () => { - editorInstance = editor.create(monaco); - editorInstance.createInstance(document.createElement('div')); - - modelManager = new ModelManager(monaco); - decorationsController = new DecorationsController(editorInstance); - - model = modelManager.addModel(file()); - - controller = new DirtyDiffController(modelManager, decorationsController); - - done(); - }); - }); - - afterEach(() => { - controller.dispose(); - model.dispose(); - decorationsController.dispose(); - editorInstance.dispose(); - }); - - describe('getDiffChangeType', () => { - ['added', 'removed', 'modified'].forEach((type) => { - it(`returns ${type}`, () => { - const change = { - [type]: true, - }; - - expect(getDiffChangeType(change)).toBe(type); - }); - }); - }); - - describe('getDecorator', () => { - ['added', 'removed', 'modified'].forEach((type) => { - it(`returns with linesDecorationsClassName for ${type}`, () => { - const change = { - [type]: true, - }; - - expect( - getDecorator(change).options.linesDecorationsClassName, - ).toBe(`dirty-diff dirty-diff-${type}`); - }); - - it('returns with line numbers', () => { - const change = { - lineNumber: 1, - endLineNumber: 2, - [type]: true, - }; - - const range = getDecorator(change).range; - - expect(range.startLineNumber).toBe(1); - expect(range.endLineNumber).toBe(2); - expect(range.startColumn).toBe(1); - expect(range.endColumn).toBe(1); - }); - }); - }); - - describe('attachModel', () => { - it('adds change event callback', () => { - spyOn(model, 'onChange'); - - controller.attachModel(model); - - expect(model.onChange).toHaveBeenCalled(); - }); - - it('calls throttledComputeDiff on change', () => { - spyOn(controller, 'throttledComputeDiff'); - - controller.attachModel(model); - - model.getModel().setValue('123'); - - expect(controller.throttledComputeDiff).toHaveBeenCalled(); - }); - }); - - describe('computeDiff', () => { - it('posts to worker', () => { - spyOn(controller.dirtyDiffWorker, 'postMessage'); - - controller.computeDiff(model); - - expect(controller.dirtyDiffWorker.postMessage).toHaveBeenCalledWith({ - path: model.path, - originalContent: '', - newContent: '', - }); - }); - }); - - describe('reDecorate', () => { - it('calls decorations controller decorate', () => { - spyOn(controller.decorationsController, 'decorate'); - - controller.reDecorate(model); - - expect(controller.decorationsController.decorate).toHaveBeenCalledWith(model); - }); - }); - - describe('decorate', () => { - it('adds decorations into decorations controller', () => { - spyOn(controller.decorationsController, 'addDecorations'); - - controller.decorate({ data: { changes: [], path: 'path' } }); - - expect(controller.decorationsController.addDecorations).toHaveBeenCalledWith('path', 'dirtyDiff', jasmine.anything()); - }); - - it('adds decorations into editor', () => { - const spy = spyOn(controller.decorationsController.editor.instance, 'deltaDecorations'); - - controller.decorate({ data: { changes: computeDiff('123', '1234'), path: 'path' } }); - - expect(spy).toHaveBeenCalledWith([], [{ - range: new monaco.Range( - 1, 1, 1, 1, - ), - options: { - isWholeLine: true, - linesDecorationsClassName: 'dirty-diff dirty-diff-modified', - }, - }]); - }); - }); - - describe('dispose', () => { - it('calls disposable dispose', () => { - spyOn(controller.disposable, 'dispose').and.callThrough(); - - controller.dispose(); - - expect(controller.disposable.dispose).toHaveBeenCalled(); - }); - - it('terminates worker', () => { - spyOn(controller.dirtyDiffWorker, 'terminate').and.callThrough(); - - controller.dispose(); - - expect(controller.dirtyDiffWorker.terminate).toHaveBeenCalled(); - }); - - it('removes worker event listener', () => { - spyOn(controller.dirtyDiffWorker, 'removeEventListener').and.callThrough(); - - controller.dispose(); - - expect(controller.dirtyDiffWorker.removeEventListener).toHaveBeenCalledWith('message', jasmine.anything()); - }); - }); -}); diff --git a/spec/javascripts/repo/lib/diff/diff_spec.js b/spec/javascripts/repo/lib/diff/diff_spec.js deleted file mode 100644 index 57f3ac3d365..00000000000 --- a/spec/javascripts/repo/lib/diff/diff_spec.js +++ /dev/null @@ -1,80 +0,0 @@ -import { computeDiff } from '~/ide/lib/diff/diff'; - -describe('Multi-file editor library diff calculator', () => { - describe('computeDiff', () => { - it('returns empty array if no changes', () => { - const diff = computeDiff('123', '123'); - - expect(diff).toEqual([]); - }); - - describe('modified', () => { - it('', () => { - const diff = computeDiff('123', '1234')[0]; - - expect(diff.added).toBeTruthy(); - expect(diff.modified).toBeTruthy(); - expect(diff.removed).toBeUndefined(); - }); - - it('', () => { - const diff = computeDiff('123\n123\n123', '123\n1234\n123')[0]; - - expect(diff.added).toBeTruthy(); - expect(diff.modified).toBeTruthy(); - expect(diff.removed).toBeUndefined(); - expect(diff.lineNumber).toBe(2); - }); - }); - - describe('added', () => { - it('', () => { - const diff = computeDiff('123', '123\n123')[0]; - - expect(diff.added).toBeTruthy(); - expect(diff.modified).toBeUndefined(); - expect(diff.removed).toBeUndefined(); - }); - - it('', () => { - const diff = computeDiff('123\n123\n123', '123\n123\n1234\n123')[0]; - - expect(diff.added).toBeTruthy(); - expect(diff.modified).toBeUndefined(); - expect(diff.removed).toBeUndefined(); - expect(diff.lineNumber).toBe(3); - }); - }); - - describe('removed', () => { - it('', () => { - const diff = computeDiff('123', '')[0]; - - expect(diff.added).toBeUndefined(); - expect(diff.modified).toBeUndefined(); - expect(diff.removed).toBeTruthy(); - }); - - it('', () => { - const diff = computeDiff('123\n123\n123', '123\n123')[0]; - - expect(diff.added).toBeUndefined(); - expect(diff.modified).toBeTruthy(); - expect(diff.removed).toBeTruthy(); - expect(diff.lineNumber).toBe(2); - }); - }); - - it('includes line number of change', () => { - const diff = computeDiff('123', '')[0]; - - expect(diff.lineNumber).toBe(1); - }); - - it('includes end line number of change', () => { - const diff = computeDiff('123', '')[0]; - - expect(diff.endLineNumber).toBe(1); - }); - }); -}); diff --git a/spec/javascripts/repo/lib/editor_options_spec.js b/spec/javascripts/repo/lib/editor_options_spec.js deleted file mode 100644 index edbf5450dce..00000000000 --- a/spec/javascripts/repo/lib/editor_options_spec.js +++ /dev/null @@ -1,7 +0,0 @@ -import editorOptions from '~/ide/lib/editor_options'; - -describe('Multi-file editor library editor options', () => { - it('returns an array', () => { - expect(editorOptions).toEqual(jasmine.any(Array)); - }); -}); diff --git a/spec/javascripts/repo/lib/editor_spec.js b/spec/javascripts/repo/lib/editor_spec.js deleted file mode 100644 index 8d51d48a782..00000000000 --- a/spec/javascripts/repo/lib/editor_spec.js +++ /dev/null @@ -1,128 +0,0 @@ -/* global monaco */ -import monacoLoader from '~/ide/monaco_loader'; -import editor from '~/ide/lib/editor'; -import { file } from '../helpers'; - -describe('Multi-file editor library', () => { - let instance; - - beforeEach((done) => { - monacoLoader(['vs/editor/editor.main'], () => { - instance = editor.create(monaco); - - done(); - }); - }); - - afterEach(() => { - instance.dispose(); - }); - - it('creates instance of editor', () => { - expect(editor.editorInstance).not.toBeNull(); - }); - - describe('createInstance', () => { - let el; - - beforeEach(() => { - el = document.createElement('div'); - }); - - it('creates editor instance', () => { - spyOn(instance.monaco.editor, 'create').and.callThrough(); - - instance.createInstance(el); - - expect(instance.monaco.editor.create).toHaveBeenCalled(); - }); - - it('creates dirty diff controller', () => { - instance.createInstance(el); - - expect(instance.dirtyDiffController).not.toBeNull(); - }); - }); - - describe('createModel', () => { - it('calls model manager addModel', () => { - spyOn(instance.modelManager, 'addModel'); - - instance.createModel('FILE'); - - expect(instance.modelManager.addModel).toHaveBeenCalledWith('FILE'); - }); - }); - - describe('attachModel', () => { - let model; - - beforeEach(() => { - instance.createInstance(document.createElement('div')); - - model = instance.createModel(file()); - }); - - it('sets the current model on the instance', () => { - instance.attachModel(model); - - expect(instance.currentModel).toBe(model); - }); - - it('attaches the model to the current instance', () => { - spyOn(instance.instance, 'setModel'); - - instance.attachModel(model); - - expect(instance.instance.setModel).toHaveBeenCalledWith(model.getModel()); - }); - - it('attaches the model to the dirty diff controller', () => { - spyOn(instance.dirtyDiffController, 'attachModel'); - - instance.attachModel(model); - - expect(instance.dirtyDiffController.attachModel).toHaveBeenCalledWith(model); - }); - - it('re-decorates with the dirty diff controller', () => { - spyOn(instance.dirtyDiffController, 'reDecorate'); - - instance.attachModel(model); - - expect(instance.dirtyDiffController.reDecorate).toHaveBeenCalledWith(model); - }); - }); - - describe('clearEditor', () => { - it('resets the editor model', () => { - instance.createInstance(document.createElement('div')); - - spyOn(instance.instance, 'setModel'); - - instance.clearEditor(); - - expect(instance.instance.setModel).toHaveBeenCalledWith(null); - }); - }); - - describe('dispose', () => { - it('calls disposble dispose method', () => { - spyOn(instance.disposable, 'dispose').and.callThrough(); - - instance.dispose(); - - expect(instance.disposable.dispose).toHaveBeenCalled(); - }); - - it('resets instance', () => { - instance.createInstance(document.createElement('div')); - - expect(instance.instance).not.toBeNull(); - - instance.dispose(); - - expect(instance.instance).toBeNull(); - }); - }); -}); diff --git a/spec/javascripts/repo/monaco_loader_spec.js b/spec/javascripts/repo/monaco_loader_spec.js deleted file mode 100644 index b8ac36972aa..00000000000 --- a/spec/javascripts/repo/monaco_loader_spec.js +++ /dev/null @@ -1,13 +0,0 @@ -import monacoContext from 'monaco-editor/dev/vs/loader'; -import monacoLoader from '~/ide/monaco_loader'; - -describe('MonacoLoader', () => { - it('calls require.config and exports require', () => { - expect(monacoContext.require.getConfig()).toEqual(jasmine.objectContaining({ - paths: { - vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase - }, - })); - expect(monacoLoader).toBe(monacoContext.require); - }); -}); diff --git a/spec/javascripts/repo/stores/actions/branch_spec.js b/spec/javascripts/repo/stores/actions/branch_spec.js deleted file mode 100644 index 00d16fd790d..00000000000 --- a/spec/javascripts/repo/stores/actions/branch_spec.js +++ /dev/null @@ -1,44 +0,0 @@ -import store from '~/ide/stores'; -import service from '~/ide/services'; -import { resetStore } from '../../helpers'; - -describe('Multi-file store branch actions', () => { - afterEach(() => { - resetStore(store); - }); - - describe('createNewBranch', () => { - beforeEach(() => { - spyOn(service, 'createBranch').and.returnValue(Promise.resolve({ - json: () => ({ - name: 'testing', - }), - })); - spyOn(history, 'pushState'); - - store.state.currentProjectId = 'abcproject'; - store.state.currentBranchId = 'testing'; - store.state.projects.abcproject = { - branches: { - master: { - workingReference: '1', - }, - }, - }; - }); - - it('creates new branch', (done) => { - store.dispatch('createNewBranch', 'master') - .then(() => { - expect(store.state.currentBranchId).toBe('testing'); - expect(service.createBranch).toHaveBeenCalledWith('abcproject', { - branch: 'master', - ref: 'testing', - }); - - done(); - }) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/repo/stores/actions/file_spec.js b/spec/javascripts/repo/stores/actions/file_spec.js deleted file mode 100644 index e2d8f002e27..00000000000 --- a/spec/javascripts/repo/stores/actions/file_spec.js +++ /dev/null @@ -1,431 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import service from '~/ide/services'; -import { file, resetStore } from '../../helpers'; - -describe('Multi-file store file actions', () => { - afterEach(() => { - resetStore(store); - }); - - describe('closeFile', () => { - let localFile; - let getLastCommitDataSpy; - let oldGetLastCommitData; - - beforeEach(() => { - getLastCommitDataSpy = jasmine.createSpy('getLastCommitData'); - oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line - store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line - - localFile = file('testFile'); - localFile.active = true; - localFile.opened = true; - localFile.parentTreeUrl = 'parentTreeUrl'; - - store.state.openFiles.push(localFile); - }); - - afterEach(() => { - store._actions.getLastCommitData = oldGetLastCommitData; // eslint-disable-line - }); - - it('closes open files', (done) => { - store.dispatch('closeFile', { file: localFile }) - .then(() => { - expect(localFile.opened).toBeFalsy(); - expect(localFile.active).toBeFalsy(); - expect(store.state.openFiles.length).toBe(0); - - done(); - }).catch(done.fail); - }); - - it('does not close file if has changed', (done) => { - localFile.changed = true; - - store.dispatch('closeFile', { file: localFile }) - .then(() => { - expect(localFile.opened).toBeTruthy(); - expect(localFile.active).toBeTruthy(); - expect(store.state.openFiles.length).toBe(1); - - done(); - }).catch(done.fail); - }); - - it('does not close file if temp file', (done) => { - localFile.tempFile = true; - - store.dispatch('closeFile', { file: localFile }) - .then(() => { - expect(localFile.opened).toBeTruthy(); - expect(localFile.active).toBeTruthy(); - expect(store.state.openFiles.length).toBe(1); - - done(); - }).catch(done.fail); - }); - - it('force closes a changed file', (done) => { - localFile.changed = true; - - store.dispatch('closeFile', { file: localFile, force: true }) - .then(() => { - expect(localFile.opened).toBeFalsy(); - expect(localFile.active).toBeFalsy(); - expect(store.state.openFiles.length).toBe(0); - - done(); - }).catch(done.fail); - }); - - it('sets next file as active', (done) => { - const f = file('otherfile'); - store.state.openFiles.push(f); - - expect(f.active).toBeFalsy(); - - store.dispatch('closeFile', { file: localFile }) - .then(() => { - expect(f.active).toBeTruthy(); - - done(); - }).catch(done.fail); - }); - - it('calls getLastCommitData', (done) => { - store.dispatch('closeFile', { file: localFile }) - .then(() => { - expect(getLastCommitDataSpy).toHaveBeenCalled(); - - done(); - }).catch(done.fail); - }); - }); - - describe('setFileActive', () => { - let scrollToTabSpy; - let oldScrollToTab; - - beforeEach(() => { - scrollToTabSpy = jasmine.createSpy('scrollToTab'); - oldScrollToTab = store._actions.scrollToTab; // eslint-disable-line - store._actions.scrollToTab = [scrollToTabSpy]; // eslint-disable-line - }); - - afterEach(() => { - store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line - }); - - it('calls scrollToTab', (done) => { - store.dispatch('setFileActive', file('setThisActive')) - .then(() => { - expect(scrollToTabSpy).toHaveBeenCalled(); - - done(); - }).catch(done.fail); - }); - - it('sets the file active', (done) => { - const localFile = file('activeFile'); - - store.dispatch('setFileActive', localFile) - .then(() => { - expect(localFile.active).toBeTruthy(); - - done(); - }).catch(done.fail); - }); - - it('returns early if file is already active', (done) => { - const localFile = file('earlyActive'); - localFile.active = true; - - store.dispatch('setFileActive', localFile) - .then(() => { - expect(scrollToTabSpy).not.toHaveBeenCalled(); - - done(); - }).catch(done.fail); - }); - - it('sets current active file to not active', (done) => { - const localFile = file('currentActive'); - localFile.active = true; - store.state.openFiles.push(localFile); - - store.dispatch('setFileActive', file('newActive')) - .then(() => { - expect(localFile.active).toBeFalsy(); - - done(); - }).catch(done.fail); - }); - - it('resets location.hash for line highlighting', (done) => { - location.hash = 'test'; - - store.dispatch('setFileActive', file('otherActive')) - .then(() => { - expect(location.hash).not.toBe('test'); - - done(); - }).catch(done.fail); - }); - }); - - describe('getFileData', () => { - let localFile; - - beforeEach(() => { - spyOn(service, 'getFileData').and.returnValue(Promise.resolve({ - headers: { - 'page-title': 'testing getFileData', - }, - json: () => Promise.resolve({ - blame_path: 'blame_path', - commits_path: 'commits_path', - permalink: 'permalink', - raw_path: 'raw_path', - binary: false, - html: '123', - render_error: '', - }), - })); - - localFile = file('newCreate'); - localFile.url = 'getFileDataURL'; - }); - - afterEach(() => { - store.dispatch('closeFile', { - file: localFile, - force: true, - }); - }); - - it('calls the service', (done) => { - store.dispatch('getFileData', localFile) - .then(() => { - expect(service.getFileData).toHaveBeenCalledWith('getFileDataURL'); - - done(); - }).catch(done.fail); - }); - - it('sets the file data', (done) => { - store.dispatch('getFileData', localFile) - .then(Vue.nextTick) - .then(() => { - expect(localFile.blamePath).toBe('blame_path'); - - done(); - }).catch(done.fail); - }); - - it('sets document title', (done) => { - store.dispatch('getFileData', localFile) - .then(() => { - expect(document.title).toBe('testing getFileData'); - - done(); - }).catch(done.fail); - }); - - it('sets the file as active', (done) => { - store.dispatch('getFileData', localFile) - .then(Vue.nextTick) - .then(() => { - expect(localFile.active).toBeTruthy(); - - done(); - }).catch(done.fail); - }); - - it('adds the file to open files', (done) => { - store.dispatch('getFileData', localFile) - .then(Vue.nextTick) - .then(() => { - expect(store.state.openFiles.length).toBe(1); - expect(store.state.openFiles[0].name).toBe(localFile.name); - - done(); - }).catch(done.fail); - }); - - it('toggles the file loading', (done) => { - store.dispatch('getFileData', localFile) - .then(() => { - expect(localFile.loading).toBeTruthy(); - - return Vue.nextTick(); - }) - .then(() => { - expect(localFile.loading).toBeFalsy(); - - done(); - }).catch(done.fail); - }); - }); - - describe('getRawFileData', () => { - let tmpFile; - - beforeEach(() => { - spyOn(service, 'getRawFileData').and.returnValue(Promise.resolve('raw')); - - tmpFile = file('tmpFile'); - }); - - it('calls getRawFileData service method', (done) => { - store.dispatch('getRawFileData', tmpFile) - .then(() => { - expect(service.getRawFileData).toHaveBeenCalledWith(tmpFile); - - done(); - }).catch(done.fail); - }); - - it('updates file raw data', (done) => { - store.dispatch('getRawFileData', tmpFile) - .then(() => { - expect(tmpFile.raw).toBe('raw'); - - done(); - }).catch(done.fail); - }); - }); - - describe('changeFileContent', () => { - let tmpFile; - - beforeEach(() => { - tmpFile = file('tmpFile'); - }); - - it('updates file content', (done) => { - store.dispatch('changeFileContent', { - file: tmpFile, - content: 'content', - }) - .then(() => { - expect(tmpFile.content).toBe('content'); - - done(); - }).catch(done.fail); - }); - }); - - describe('createTempFile', () => { - let projectTree; - - beforeEach(() => { - document.body.innerHTML += '
'; - - store.state.currentProjectId = 'abcproject'; - store.state.currentBranchId = 'master'; - store.state.projects.abcproject = { - branches: { - master: { - workingReference: '1', - }, - }, - }; - - store.state.trees['abcproject/mybranch'] = { - tree: [], - }; - - projectTree = store.state.trees['abcproject/mybranch']; - }); - - afterEach(() => { - document.querySelector('.flash-container').remove(); - }); - - it('creates temp file', (done) => { - store.dispatch('createTempFile', { - name: 'test', - projectId: 'abcproject', - branchId: 'mybranch', - parent: projectTree, - }).then((f) => { - expect(f.tempFile).toBeTruthy(); - expect(store.state.trees['abcproject/mybranch'].tree.length).toBe(1); - - done(); - }).catch(done.fail); - }); - - it('adds tmp file to open files', (done) => { - store.dispatch('createTempFile', { - name: 'test', - projectId: 'abcproject', - branchId: 'mybranch', - parent: projectTree, - }).then((f) => { - expect(store.state.openFiles.length).toBe(1); - expect(store.state.openFiles[0].name).toBe(f.name); - - done(); - }).catch(done.fail); - }); - - it('sets tmp file as active', (done) => { - store.dispatch('createTempFile', { - name: 'test', - projectId: 'abcproject', - branchId: 'mybranch', - parent: projectTree, - }).then((f) => { - expect(f.active).toBeTruthy(); - - done(); - }).catch(done.fail); - }); - - it('enters edit mode if file is not base64', (done) => { - store.dispatch('createTempFile', { - name: 'test', - projectId: 'abcproject', - branchId: 'mybranch', - parent: projectTree, - }).then(() => { - expect(store.state.editMode).toBeTruthy(); - - done(); - }).catch(done.fail); - }); - - it('creates flash message is file already exists', (done) => { - store.state.trees['abcproject/mybranch'].tree.push(file('test', '1', 'blob')); - - store.dispatch('createTempFile', { - name: 'test', - projectId: 'abcproject', - branchId: 'mybranch', - parent: projectTree, - }).then(() => { - expect(document.querySelector('.flash-alert')).not.toBeNull(); - - done(); - }).catch(done.fail); - }); - - it('increases level of file', (done) => { - store.state.trees['abcproject/mybranch'].level = 1; - - store.dispatch('createTempFile', { - name: 'test', - projectId: 'abcproject', - branchId: 'mybranch', - parent: projectTree, - }).then((f) => { - expect(f.level).toBe(2); - - done(); - }).catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/repo/stores/actions/tree_spec.js b/spec/javascripts/repo/stores/actions/tree_spec.js deleted file mode 100644 index 65351dbb7d9..00000000000 --- a/spec/javascripts/repo/stores/actions/tree_spec.js +++ /dev/null @@ -1,350 +0,0 @@ -import Vue from 'vue'; -import store from '~/ide/stores'; -import service from '~/ide/services'; -import { file, resetStore } from '../../helpers'; - -describe('Multi-file store tree actions', () => { - let projectTree; - - const basicCallParameters = { - endpoint: 'rootEndpoint', - projectId: 'abcproject', - branch: 'master', - }; - - beforeEach(() => { - store.state.currentProjectId = 'abcproject'; - store.state.currentBranchId = 'master'; - store.state.projects.abcproject = { - web_url: '', - branches: { - master: { - workingReference: '1', - }, - }, - }; - }); - - afterEach(() => { - resetStore(store); - }); - - describe('getTreeData', () => { - beforeEach(() => { - spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({ - headers: { - 'page-title': 'test', - }, - json: () => Promise.resolve({ - last_commit_path: 'last_commit_path', - parent_tree_url: 'parent_tree_url', - path: '/', - trees: [{ name: 'tree' }], - blobs: [{ name: 'blob' }], - submodules: [{ name: 'submodule' }], - }), - })); - }); - - it('calls service getTreeData', (done) => { - store.dispatch('getTreeData', basicCallParameters) - .then(() => { - expect(service.getTreeData).toHaveBeenCalledWith('rootEndpoint'); - - done(); - }).catch(done.fail); - }); - - it('adds data into tree', (done) => { - store.dispatch('getTreeData', basicCallParameters) - .then(() => { - projectTree = store.state.trees['abcproject/master']; - expect(projectTree.tree.length).toBe(3); - expect(projectTree.tree[0].type).toBe('tree'); - expect(projectTree.tree[1].type).toBe('submodule'); - expect(projectTree.tree[2].type).toBe('blob'); - - done(); - }).catch(done.fail); - }); - - it('sets parent tree URL', (done) => { - store.dispatch('getTreeData', basicCallParameters) - .then(() => { - expect(store.state.parentTreeUrl).toBe('parent_tree_url'); - - done(); - }).catch(done.fail); - }); - - it('sets last commit path', (done) => { - store.dispatch('getTreeData', basicCallParameters) - .then(() => { - expect(store.state.trees['abcproject/master'].lastCommitPath).toBe('last_commit_path'); - - done(); - }).catch(done.fail); - }); - - it('sets root if not currently at root', (done) => { - store.state.isInitialRoot = false; - - store.dispatch('getTreeData', basicCallParameters) - .then(() => { - expect(store.state.isInitialRoot).toBeTruthy(); - expect(store.state.isRoot).toBeTruthy(); - - done(); - }).catch(done.fail); - }); - - it('sets page title', (done) => { - store.dispatch('getTreeData', basicCallParameters) - .then(() => { - expect(document.title).toBe('test'); - - done(); - }).catch(done.fail); - }); - - it('calls getLastCommitData if prevLastCommitPath is not null', (done) => { - const getLastCommitDataSpy = jasmine.createSpy('getLastCommitData'); - const oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line - store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line - store.state.prevLastCommitPath = 'test'; - - store.dispatch('getTreeData', basicCallParameters) - .then(() => { - expect(getLastCommitDataSpy).toHaveBeenCalledWith(projectTree); - - store._actions.getLastCommitData = oldGetLastCommitData; // eslint-disable-line - - done(); - }).catch(done.fail); - }); - }); - - describe('toggleTreeOpen', () => { - let oldGetTreeData; - let getTreeDataSpy; - let tree; - - beforeEach(() => { - getTreeDataSpy = jasmine.createSpy('getTreeData'); - - oldGetTreeData = store._actions.getTreeData; // eslint-disable-line - store._actions.getTreeData = [getTreeDataSpy]; // eslint-disable-line - - tree = { - projectId: 'abcproject', - branchId: 'master', - opened: false, - tree: [], - }; - }); - - afterEach(() => { - store._actions.getTreeData = oldGetTreeData; // eslint-disable-line - }); - - it('toggles the tree open', (done) => { - store.dispatch('toggleTreeOpen', { - endpoint: 'test', - tree, - }).then(() => { - expect(tree.opened).toBeTruthy(); - - done(); - }).catch(done.fail); - }); - - it('calls getTreeData if tree is closed', (done) => { - store.dispatch('toggleTreeOpen', { - endpoint: 'test', - tree, - }).then(() => { - expect(getTreeDataSpy).toHaveBeenCalledWith({ - projectId: 'abcproject', - branch: 'master', - endpoint: 'test', - tree, - }); - - done(); - }).catch(done.fail); - }); - - it('resets entries tree', (done) => { - Object.assign(tree, { - opened: true, - tree: ['a'], - }); - - store.dispatch('toggleTreeOpen', { - endpoint: 'test', - tree, - }).then(() => { - expect(tree.tree.length).toBe(0); - - done(); - }).catch(done.fail); - }); - }); - - describe('createTempTree', () => { - beforeEach(() => { - store.state.trees['abcproject/mybranch'] = { - tree: [], - }; - projectTree = store.state.trees['abcproject/mybranch']; - }); - - it('creates temp tree', (done) => { - store.dispatch('createTempTree', { - projectId: store.state.currentProjectId, - branchId: store.state.currentBranchId, - name: 'test', - parent: projectTree, - }) - .then(() => { - expect(projectTree.tree[0].name).toBe('test'); - expect(projectTree.tree[0].type).toBe('tree'); - - done(); - }).catch(done.fail); - }); - - it('creates new folder inside another tree', (done) => { - const tree = { - type: 'tree', - name: 'testing', - tree: [], - }; - - projectTree.tree.push(tree); - - store.dispatch('createTempTree', { - projectId: store.state.currentProjectId, - branchId: store.state.currentBranchId, - name: 'testing/test', - parent: projectTree, - }) - .then(() => { - expect(projectTree.tree[0].name).toBe('testing'); - expect(projectTree.tree[0].tree[0].tempFile).toBeTruthy(); - expect(projectTree.tree[0].tree[0].name).toBe('test'); - expect(projectTree.tree[0].tree[0].type).toBe('tree'); - - done(); - }).catch(done.fail); - }); - - it('does not create new tree if already exists', (done) => { - const tree = { - type: 'tree', - name: 'testing', - endpoint: 'test', - tree: [], - }; - - projectTree.tree.push(tree); - - store.dispatch('createTempTree', { - projectId: store.state.currentProjectId, - branchId: store.state.currentBranchId, - name: 'testing/test', - parent: projectTree, - }) - .then(() => { - expect(projectTree.tree[0].name).toBe('testing'); - expect(projectTree.tree[0].tempFile).toBeUndefined(); - - done(); - }).catch(done.fail); - }); - }); - - describe('getLastCommitData', () => { - beforeEach(() => { - spyOn(service, 'getTreeLastCommit').and.returnValue(Promise.resolve({ - headers: { - 'more-logs-url': null, - }, - json: () => Promise.resolve([{ - type: 'tree', - file_name: 'testing', - commit: { - message: 'commit message', - authored_date: '123', - }, - }]), - })); - - store.state.trees['abcproject/mybranch'] = { - tree: [], - }; - - projectTree = store.state.trees['abcproject/mybranch']; - projectTree.tree.push(file('testing', '1', 'tree')); - projectTree.lastCommitPath = 'lastcommitpath'; - }); - - it('calls service with lastCommitPath', (done) => { - store.dispatch('getLastCommitData', projectTree) - .then(() => { - expect(service.getTreeLastCommit).toHaveBeenCalledWith('lastcommitpath'); - - done(); - }).catch(done.fail); - }); - - it('updates trees last commit data', (done) => { - store.dispatch('getLastCommitData', projectTree) - .then(Vue.nextTick) - .then(() => { - expect(projectTree.tree[0].lastCommit.message).toBe('commit message'); - - done(); - }).catch(done.fail); - }); - - it('does not update entry if not found', (done) => { - projectTree.tree[0].name = 'a'; - - store.dispatch('getLastCommitData', projectTree) - .then(Vue.nextTick) - .then(() => { - expect(projectTree.tree[0].lastCommit.message).not.toBe('commit message'); - - done(); - }).catch(done.fail); - }); - }); - - describe('updateDirectoryData', () => { - it('adds data into tree', (done) => { - const tree = { - tree: [], - }; - const data = { - trees: [{ name: 'tree' }], - submodules: [{ name: 'submodule' }], - blobs: [{ name: 'blob' }], - }; - - store.dispatch('updateDirectoryData', { - data, - tree, - }).then(() => { - expect(tree.tree[0].name).toBe('tree'); - expect(tree.tree[0].type).toBe('tree'); - expect(tree.tree[1].name).toBe('submodule'); - expect(tree.tree[1].type).toBe('submodule'); - expect(tree.tree[2].name).toBe('blob'); - expect(tree.tree[2].type).toBe('blob'); - - done(); - }).catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/repo/stores/actions_spec.js b/spec/javascripts/repo/stores/actions_spec.js deleted file mode 100644 index f678967b092..00000000000 --- a/spec/javascripts/repo/stores/actions_spec.js +++ /dev/null @@ -1,432 +0,0 @@ -import Vue from 'vue'; -import * as urlUtils from '~/lib/utils/url_utility'; -import store from '~/ide/stores'; -import service from '~/ide/services'; -import { resetStore, file } from '../helpers'; - -describe('Multi-file store actions', () => { - afterEach(() => { - resetStore(store); - }); - - describe('redirectToUrl', () => { - it('calls visitUrl', (done) => { - spyOn(urlUtils, 'visitUrl'); - - store.dispatch('redirectToUrl', 'test') - .then(() => { - expect(urlUtils.visitUrl).toHaveBeenCalledWith('test'); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('setInitialData', () => { - it('commits initial data', (done) => { - store.dispatch('setInitialData', { canCommit: true }) - .then(() => { - expect(store.state.canCommit).toBeTruthy(); - done(); - }) - .catch(done.fail); - }); - }); - - describe('closeDiscardPopup', () => { - it('closes the discard popup', (done) => { - store.dispatch('closeDiscardPopup', false) - .then(() => { - expect(store.state.discardPopupOpen).toBeFalsy(); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('discardAllChanges', () => { - beforeEach(() => { - store.state.openFiles.push(file('discardAll')); - store.state.openFiles[0].changed = true; - }); - }); - - describe('closeAllFiles', () => { - beforeEach(() => { - store.state.openFiles.push(file('closeAll')); - store.state.openFiles[0].opened = true; - }); - - it('closes all open files', (done) => { - store.dispatch('closeAllFiles') - .then(() => { - expect(store.state.openFiles.length).toBe(0); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('toggleEditMode', () => { - it('toggles edit mode', (done) => { - store.state.editMode = true; - - store.dispatch('toggleEditMode') - .then(() => { - expect(store.state.editMode).toBeFalsy(); - - done(); - }).catch(done.fail); - }); - - it('sets preview mode', (done) => { - store.state.currentBlobView = 'repo-editor'; - store.state.editMode = true; - - store.dispatch('toggleEditMode') - .then(Vue.nextTick) - .then(() => { - expect(store.state.currentBlobView).toBe('repo-preview'); - - done(); - }).catch(done.fail); - }); - - it('opens discard popup if there are changed files', (done) => { - store.state.editMode = true; - store.state.openFiles.push(file('discardChanges')); - store.state.openFiles[0].changed = true; - - store.dispatch('toggleEditMode') - .then(() => { - expect(store.state.discardPopupOpen).toBeTruthy(); - - done(); - }).catch(done.fail); - }); - - it('can force closed if there are changed files', (done) => { - store.state.editMode = true; - - store.state.openFiles.push(file('forceClose')); - store.state.openFiles[0].changed = true; - - store.dispatch('toggleEditMode', true) - .then(() => { - expect(store.state.discardPopupOpen).toBeFalsy(); - expect(store.state.editMode).toBeFalsy(); - - done(); - }).catch(done.fail); - }); - - it('discards file changes', (done) => { - const f = file('discard'); - store.state.editMode = true; - store.state.openFiles.push(f); - f.changed = true; - - store.dispatch('toggleEditMode', true) - .then(Vue.nextTick) - .then(() => { - expect(f.changed).toBeFalsy(); - - done(); - }).catch(done.fail); - }); - }); - - describe('toggleBlobView', () => { - it('sets edit mode view if in edit mode', (done) => { - store.dispatch('toggleBlobView') - .then(() => { - expect(store.state.currentBlobView).toBe('repo-editor'); - - done(); - }) - .catch(done.fail); - }); - - it('sets preview mode view if not in edit mode', (done) => { - store.state.editMode = false; - - store.dispatch('toggleBlobView') - .then(() => { - expect(store.state.currentBlobView).toBe('repo-preview'); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('checkCommitStatus', () => { - beforeEach(() => { - store.state.currentProjectId = 'abcproject'; - store.state.currentBranchId = 'master'; - store.state.projects.abcproject = { - branches: { - master: { - workingReference: '1', - }, - }, - }; - }); - - it('calls service', (done) => { - spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({ - data: { - commit: { id: '123' }, - }, - })); - - store.dispatch('checkCommitStatus') - .then(() => { - expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master'); - - done(); - }) - .catch(done.fail); - }); - - it('returns true if current ref does not equal returned ID', (done) => { - spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({ - data: { - commit: { id: '123' }, - }, - })); - - store.dispatch('checkCommitStatus') - .then((val) => { - expect(val).toBeTruthy(); - - done(); - }) - .catch(done.fail); - }); - - it('returns false if current ref equals returned ID', (done) => { - spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({ - data: { - commit: { id: '1' }, - }, - })); - - store.dispatch('checkCommitStatus') - .then((val) => { - expect(val).toBeFalsy(); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('commitChanges', () => { - let payload; - - beforeEach(() => { - spyOn(window, 'scrollTo'); - - document.body.innerHTML += '
'; - - store.state.currentProjectId = 'abcproject'; - store.state.currentBranchId = 'master'; - store.state.projects.abcproject = { - web_url: 'webUrl', - branches: { - master: { - workingReference: '1', - }, - }, - }; - - payload = { - branch: 'master', - }; - }); - - afterEach(() => { - document.querySelector('.flash-container').remove(); - }); - - describe('success', () => { - beforeEach(() => { - spyOn(service, 'commit').and.returnValue(Promise.resolve({ - data: { - id: '123456', - short_id: '123', - message: 'test message', - committed_date: 'date', - stats: { - additions: '1', - deletions: '2', - }, - }, - })); - }); - - it('calls service', (done) => { - store.dispatch('commitChanges', { payload, newMr: false }) - .then(() => { - expect(service.commit).toHaveBeenCalledWith('abcproject', payload); - - done(); - }).catch(done.fail); - }); - - it('shows flash notice', (done) => { - store.dispatch('commitChanges', { payload, newMr: false }) - .then(() => { - const alert = document.querySelector('.flash-container'); - - expect(alert.querySelector('.flash-notice')).not.toBeNull(); - expect(alert.textContent.trim()).toBe( - 'Your changes have been committed. Commit 123 with 1 additions, 2 deletions.', - ); - - done(); - }).catch(done.fail); - }); - - it('adds commit data to changed files', (done) => { - const changedFile = file('changed'); - const f = file('newfile'); - changedFile.changed = true; - - store.state.openFiles.push(changedFile, f); - - store.dispatch('commitChanges', { payload, newMr: false }) - .then(() => { - expect(changedFile.lastCommit.message).toBe('test message'); - expect(f.lastCommit.message).not.toBe('test message'); - - done(); - }).catch(done.fail); - }); - - it('scrolls to top of page', (done) => { - store.dispatch('commitChanges', { payload, newMr: false }) - .then(() => { - expect(window.scrollTo).toHaveBeenCalledWith(0, 0); - - done(); - }).catch(done.fail); - }); - - it('redirects to new merge request page', (done) => { - spyOn(urlUtils, 'visitUrl'); - - store.dispatch('commitChanges', { payload, newMr: true }) - .then(() => { - expect(urlUtils.visitUrl).toHaveBeenCalledWith('webUrl/merge_requests/new?merge_request%5Bsource_branch%5D=master'); - - done(); - }).catch(done.fail); - }); - }); - - describe('failed', () => { - beforeEach(() => { - spyOn(service, 'commit').and.returnValue(Promise.resolve({ - data: { - message: 'failed message', - }, - })); - }); - - it('shows failed message', (done) => { - store.dispatch('commitChanges', { payload, newMr: false }) - .then(() => { - const alert = document.querySelector('.flash-container'); - - expect(alert.textContent.trim()).toBe( - 'failed message', - ); - - done(); - }).catch(done.fail); - }); - }); - }); - - describe('createTempEntry', () => { - beforeEach(() => { - store.state.trees['abcproject/mybranch'] = { - tree: [], - }; - store.state.projects.abcproject = { - web_url: '', - }; - }); - - it('creates a temp tree', (done) => { - const projectTree = store.state.trees['abcproject/mybranch']; - - store.dispatch('createTempEntry', { - projectId: 'abcproject', - branchId: 'mybranch', - parent: projectTree, - name: 'test', - type: 'tree', - }) - .then(() => { - const baseTree = projectTree.tree; - expect(baseTree.length).toBe(1); - expect(baseTree[0].tempFile).toBeTruthy(); - expect(baseTree[0].type).toBe('tree'); - - done(); - }) - .catch(done.fail); - }); - - it('creates temp file', (done) => { - const projectTree = store.state.trees['abcproject/mybranch']; - - store.dispatch('createTempEntry', { - projectId: 'abcproject', - branchId: 'mybranch', - parent: projectTree, - name: 'test', - type: 'blob', - }) - .then(() => { - const baseTree = projectTree.tree; - expect(baseTree.length).toBe(1); - expect(baseTree[0].tempFile).toBeTruthy(); - expect(baseTree[0].type).toBe('blob'); - - done(); - }) - .catch(done.fail); - }); - }); - - describe('popHistoryState', () => { - - }); - - describe('scrollToTab', () => { - it('focuses the current active element', (done) => { - document.body.innerHTML += '
'; - const el = document.querySelector('.repo-tab'); - spyOn(el, 'focus'); - - store.dispatch('scrollToTab') - .then(() => { - setTimeout(() => { - expect(el.focus).toHaveBeenCalled(); - - document.getElementById('tabs').remove(); - - done(); - }); - }) - .catch(done.fail); - }); - }); -}); diff --git a/spec/javascripts/repo/stores/getters_spec.js b/spec/javascripts/repo/stores/getters_spec.js deleted file mode 100644 index d0d5934f29a..00000000000 --- a/spec/javascripts/repo/stores/getters_spec.js +++ /dev/null @@ -1,114 +0,0 @@ -import * as getters from '~/ide/stores/getters'; -import state from '~/ide/stores/state'; -import { file } from '../helpers'; - -describe('Multi-file store getters', () => { - let localState; - - beforeEach(() => { - localState = state(); - }); - - describe('changedFiles', () => { - it('returns a list of changed opened files', () => { - localState.openFiles.push(file()); - localState.openFiles.push(file('changed')); - localState.openFiles[1].changed = true; - - const changedFiles = getters.changedFiles(localState); - - expect(changedFiles.length).toBe(1); - expect(changedFiles[0].name).toBe('changed'); - }); - }); - - describe('activeFile', () => { - it('returns the current active file', () => { - localState.openFiles.push(file()); - localState.openFiles.push(file('active')); - localState.openFiles[1].active = true; - - expect(getters.activeFile(localState).name).toBe('active'); - }); - - it('returns undefined if no active files are found', () => { - localState.openFiles.push(file()); - localState.openFiles.push(file('active')); - - expect(getters.activeFile(localState)).toBeNull(); - }); - }); - - describe('activeFileExtension', () => { - it('returns the file extension for the current active file', () => { - localState.openFiles.push(file('active')); - localState.openFiles[0].active = true; - localState.openFiles[0].path = 'test.js'; - - expect(getters.activeFileExtension(localState)).toBe('.js'); - - localState.openFiles[0].path = 'test.es6.js'; - - expect(getters.activeFileExtension(localState)).toBe('.js'); - }); - }); - - describe('canEditFile', () => { - beforeEach(() => { - localState.onTopOfBranch = true; - localState.canCommit = true; - - localState.openFiles.push(file()); - localState.openFiles[0].active = true; - }); - - it('returns true if user can commit and has open files', () => { - expect(getters.canEditFile(localState)).toBeTruthy(); - }); - - it('returns false if user can commit and has no open files', () => { - localState.openFiles = []; - - expect(getters.canEditFile(localState)).toBeFalsy(); - }); - - it('returns false if user can commit and active file is binary', () => { - localState.openFiles[0].binary = true; - - expect(getters.canEditFile(localState)).toBeFalsy(); - }); - - it('returns false if user cant commit', () => { - localState.canCommit = false; - - expect(getters.canEditFile(localState)).toBeFalsy(); - }); - }); - - describe('modifiedFiles', () => { - it('returns a list of modified files', () => { - localState.openFiles.push(file()); - localState.openFiles.push(file('changed')); - localState.openFiles[1].changed = true; - - const modifiedFiles = getters.modifiedFiles(localState); - - expect(modifiedFiles.length).toBe(1); - expect(modifiedFiles[0].name).toBe('changed'); - }); - }); - - describe('addedFiles', () => { - it('returns a list of added files', () => { - localState.openFiles.push(file()); - localState.openFiles.push(file('added')); - localState.openFiles[1].changed = true; - localState.openFiles[1].tempFile = true; - - const modifiedFiles = getters.addedFiles(localState); - - expect(modifiedFiles.length).toBe(1); - expect(modifiedFiles[0].name).toBe('added'); - }); - }); -}); diff --git a/spec/javascripts/repo/stores/mutations/branch_spec.js b/spec/javascripts/repo/stores/mutations/branch_spec.js deleted file mode 100644 index a7167537ef2..00000000000 --- a/spec/javascripts/repo/stores/mutations/branch_spec.js +++ /dev/null @@ -1,18 +0,0 @@ -import mutations from '~/ide/stores/mutations/branch'; -import state from '~/ide/stores/state'; - -describe('Multi-file store branch mutations', () => { - let localState; - - beforeEach(() => { - localState = state(); - }); - - describe('SET_CURRENT_BRANCH', () => { - it('sets currentBranch', () => { - mutations.SET_CURRENT_BRANCH(localState, 'master'); - - expect(localState.currentBranchId).toBe('master'); - }); - }); -}); diff --git a/spec/javascripts/repo/stores/mutations/file_spec.js b/spec/javascripts/repo/stores/mutations/file_spec.js deleted file mode 100644 index 6e204ef0404..00000000000 --- a/spec/javascripts/repo/stores/mutations/file_spec.js +++ /dev/null @@ -1,131 +0,0 @@ -import mutations from '~/ide/stores/mutations/file'; -import state from '~/ide/stores/state'; -import { file } from '../../helpers'; - -describe('Multi-file store file mutations', () => { - let localState; - let localFile; - - beforeEach(() => { - localState = state(); - localFile = file(); - }); - - describe('SET_FILE_ACTIVE', () => { - it('sets the file active', () => { - mutations.SET_FILE_ACTIVE(localState, { - file: localFile, - active: true, - }); - - expect(localFile.active).toBeTruthy(); - }); - }); - - describe('TOGGLE_FILE_OPEN', () => { - beforeEach(() => { - mutations.TOGGLE_FILE_OPEN(localState, localFile); - }); - - it('adds into opened files', () => { - expect(localFile.opened).toBeTruthy(); - expect(localState.openFiles.length).toBe(1); - }); - - it('removes from opened files', () => { - mutations.TOGGLE_FILE_OPEN(localState, localFile); - - expect(localFile.opened).toBeFalsy(); - expect(localState.openFiles.length).toBe(0); - }); - }); - - describe('SET_FILE_DATA', () => { - it('sets extra file data', () => { - mutations.SET_FILE_DATA(localState, { - data: { - blame_path: 'blame', - commits_path: 'commits', - permalink: 'permalink', - raw_path: 'raw', - binary: true, - html: 'html', - render_error: 'render_error', - }, - file: localFile, - }); - - expect(localFile.blamePath).toBe('blame'); - expect(localFile.commitsPath).toBe('commits'); - expect(localFile.permalink).toBe('permalink'); - expect(localFile.rawPath).toBe('raw'); - expect(localFile.binary).toBeTruthy(); - expect(localFile.html).toBe('html'); - expect(localFile.renderError).toBe('render_error'); - }); - }); - - describe('SET_FILE_RAW_DATA', () => { - it('sets raw data', () => { - mutations.SET_FILE_RAW_DATA(localState, { - file: localFile, - raw: 'testing', - }); - - expect(localFile.raw).toBe('testing'); - }); - }); - - describe('UPDATE_FILE_CONTENT', () => { - beforeEach(() => { - localFile.raw = 'test'; - }); - - it('sets content', () => { - mutations.UPDATE_FILE_CONTENT(localState, { - file: localFile, - content: 'test', - }); - - expect(localFile.content).toBe('test'); - }); - - it('sets changed if content does not match raw', () => { - mutations.UPDATE_FILE_CONTENT(localState, { - file: localFile, - content: 'testing', - }); - - expect(localFile.content).toBe('testing'); - expect(localFile.changed).toBeTruthy(); - }); - }); - - describe('DISCARD_FILE_CHANGES', () => { - beforeEach(() => { - localFile.content = 'test'; - localFile.changed = true; - }); - - it('resets content and changed', () => { - mutations.DISCARD_FILE_CHANGES(localState, localFile); - - expect(localFile.content).toBe(''); - expect(localFile.changed).toBeFalsy(); - }); - }); - - describe('CREATE_TMP_FILE', () => { - it('adds file into parent tree', () => { - const f = file('tmpFile'); - - mutations.CREATE_TMP_FILE(localState, { - file: f, - parent: localFile, - }); - - expect(localFile.tree.length).toBe(1); - expect(localFile.tree[0].name).toBe(f.name); - }); - }); -}); diff --git a/spec/javascripts/repo/stores/mutations/tree_spec.js b/spec/javascripts/repo/stores/mutations/tree_spec.js deleted file mode 100644 index e6ca8ea139e..00000000000 --- a/spec/javascripts/repo/stores/mutations/tree_spec.js +++ /dev/null @@ -1,71 +0,0 @@ -import mutations from '~/ide/stores/mutations/tree'; -import state from '~/ide/stores/state'; -import { file } from '../../helpers'; - -describe('Multi-file store tree mutations', () => { - let localState; - let localTree; - - beforeEach(() => { - localState = state(); - localTree = file(); - }); - - describe('TOGGLE_TREE_OPEN', () => { - it('toggles tree open', () => { - mutations.TOGGLE_TREE_OPEN(localState, localTree); - - expect(localTree.opened).toBeTruthy(); - - mutations.TOGGLE_TREE_OPEN(localState, localTree); - - expect(localTree.opened).toBeFalsy(); - }); - }); - - describe('SET_DIRECTORY_DATA', () => { - const data = [{ - name: 'tree', - }, - { - name: 'submodule', - }, - { - name: 'blob', - }]; - - it('adds directory data', () => { - mutations.SET_DIRECTORY_DATA(localState, { - data, - tree: localState, - }); - - expect(localState.tree.length).toBe(3); - expect(localState.tree[0].name).toBe('tree'); - expect(localState.tree[1].name).toBe('submodule'); - expect(localState.tree[2].name).toBe('blob'); - }); - }); - - describe('SET_PARENT_TREE_URL', () => { - it('sets the parent tree url', () => { - mutations.SET_PARENT_TREE_URL(localState, 'test'); - - expect(localState.parentTreeUrl).toBe('test'); - }); - }); - - describe('CREATE_TMP_TREE', () => { - it('adds tree into parent tree', () => { - const tmpEntry = file('tmpTree'); - - mutations.CREATE_TMP_TREE(localState, { - tmpEntry, - parent: localTree, - }); - - expect(localTree.tree.length).toBe(1); - expect(localTree.tree[0].name).toBe(tmpEntry.name); - }); - }); -}); diff --git a/spec/javascripts/repo/stores/mutations_spec.js b/spec/javascripts/repo/stores/mutations_spec.js deleted file mode 100644 index 5fd8ad94972..00000000000 --- a/spec/javascripts/repo/stores/mutations_spec.js +++ /dev/null @@ -1,125 +0,0 @@ -import mutations from '~/ide/stores/mutations'; -import state from '~/ide/stores/state'; -import { file } from '../helpers'; - -describe('Multi-file store mutations', () => { - let localState; - let entry; - - beforeEach(() => { - localState = state(); - entry = file(); - }); - - describe('SET_INITIAL_DATA', () => { - it('sets all initial data', () => { - mutations.SET_INITIAL_DATA(localState, { - test: 'test', - }); - - expect(localState.test).toBe('test'); - }); - }); - - describe('SET_PREVIEW_MODE', () => { - it('sets currentBlobView to repo-preview', () => { - mutations.SET_PREVIEW_MODE(localState); - - expect(localState.currentBlobView).toBe('repo-preview'); - - localState.currentBlobView = 'testing'; - - mutations.SET_PREVIEW_MODE(localState); - - expect(localState.currentBlobView).toBe('repo-preview'); - }); - }); - - describe('SET_EDIT_MODE', () => { - it('sets currentBlobView to repo-editor', () => { - mutations.SET_EDIT_MODE(localState); - - expect(localState.currentBlobView).toBe('repo-editor'); - - localState.currentBlobView = 'testing'; - - mutations.SET_EDIT_MODE(localState); - - expect(localState.currentBlobView).toBe('repo-editor'); - }); - }); - - describe('TOGGLE_LOADING', () => { - it('toggles loading of entry', () => { - mutations.TOGGLE_LOADING(localState, entry); - - expect(entry.loading).toBeTruthy(); - - mutations.TOGGLE_LOADING(localState, entry); - - expect(entry.loading).toBeFalsy(); - }); - }); - - describe('TOGGLE_EDIT_MODE', () => { - it('toggles editMode', () => { - mutations.TOGGLE_EDIT_MODE(localState); - - expect(localState.editMode).toBeFalsy(); - - mutations.TOGGLE_EDIT_MODE(localState); - - expect(localState.editMode).toBeTruthy(); - }); - }); - - describe('TOGGLE_DISCARD_POPUP', () => { - it('sets discardPopupOpen', () => { - mutations.TOGGLE_DISCARD_POPUP(localState, true); - - expect(localState.discardPopupOpen).toBeTruthy(); - - mutations.TOGGLE_DISCARD_POPUP(localState, false); - - expect(localState.discardPopupOpen).toBeFalsy(); - }); - }); - - describe('SET_ROOT', () => { - it('sets isRoot & initialRoot', () => { - mutations.SET_ROOT(localState, true); - - expect(localState.isRoot).toBeTruthy(); - expect(localState.isInitialRoot).toBeTruthy(); - - mutations.SET_ROOT(localState, false); - - expect(localState.isRoot).toBeFalsy(); - expect(localState.isInitialRoot).toBeFalsy(); - }); - }); - - describe('SET_LEFT_PANEL_COLLAPSED', () => { - it('sets left panel collapsed', () => { - mutations.SET_LEFT_PANEL_COLLAPSED(localState, true); - - expect(localState.leftPanelCollapsed).toBeTruthy(); - - mutations.SET_LEFT_PANEL_COLLAPSED(localState, false); - - expect(localState.leftPanelCollapsed).toBeFalsy(); - }); - }); - - describe('SET_RIGHT_PANEL_COLLAPSED', () => { - it('sets right panel collapsed', () => { - mutations.SET_RIGHT_PANEL_COLLAPSED(localState, true); - - expect(localState.rightPanelCollapsed).toBeTruthy(); - - mutations.SET_RIGHT_PANEL_COLLAPSED(localState, false); - - expect(localState.rightPanelCollapsed).toBeFalsy(); - }); - }); -}); diff --git a/spec/javascripts/repo/stores/utils_spec.js b/spec/javascripts/repo/stores/utils_spec.js deleted file mode 100644 index 89745a2029e..00000000000 --- a/spec/javascripts/repo/stores/utils_spec.js +++ /dev/null @@ -1,119 +0,0 @@ -import * as utils from '~/ide/stores/utils'; -import state from '~/ide/stores/state'; -import { file } from '../helpers'; - -describe('Multi-file store utils', () => { - describe('setPageTitle', () => { - it('sets the document page title', () => { - utils.setPageTitle('test'); - - expect(document.title).toBe('test'); - }); - }); - - describe('treeList', () => { - let localState; - - beforeEach(() => { - localState = state(); - }); - - it('returns flat tree list', () => { - localState.trees = []; - localState.trees['abcproject/mybranch'] = { - tree: [], - }; - const baseTree = localState.trees['abcproject/mybranch'].tree; - baseTree.push(file('1')); - baseTree[0].tree.push(file('2')); - baseTree[0].tree[0].tree.push(file('3')); - - const treeList = utils.treeList(localState, 'abcproject/mybranch'); - - expect(treeList.length).toBe(3); - expect(treeList[1].name).toBe(baseTree[0].tree[0].name); - expect(treeList[2].name).toBe(baseTree[0].tree[0].tree[0].name); - }); - }); - - describe('createTemp', () => { - it('creates temp tree', () => { - const tmp = utils.createTemp({ - name: 'test', - path: 'test', - type: 'tree', - level: 0, - changed: false, - content: '', - base64: '', - }); - - expect(tmp.tempFile).toBeTruthy(); - expect(tmp.icon).toBe('fa-folder'); - }); - - it('creates temp file', () => { - const tmp = utils.createTemp({ - name: 'test', - path: 'test', - type: 'blob', - level: 0, - changed: false, - content: '', - base64: '', - }); - - expect(tmp.tempFile).toBeTruthy(); - expect(tmp.icon).toBe('fa-file-text-o'); - }); - }); - - describe('findIndexOfFile', () => { - let localState; - - beforeEach(() => { - localState = [{ - path: '1', - }, { - path: '2', - }]; - }); - - it('finds in the index of an entry by path', () => { - const index = utils.findIndexOfFile(localState, { - path: '2', - }); - - expect(index).toBe(1); - }); - }); - - describe('findEntry', () => { - let localState; - - beforeEach(() => { - localState = { - tree: [{ - type: 'tree', - name: 'test', - }, { - type: 'blob', - name: 'file', - }], - }; - }); - - it('returns an entry found by name', () => { - const foundEntry = utils.findEntry(localState.tree, 'tree', 'test'); - - expect(foundEntry.type).toBe('tree'); - expect(foundEntry.name).toBe('test'); - }); - - it('returns undefined when no entry found', () => { - const foundEntry = utils.findEntry(localState.tree, 'blob', 'test'); - - expect(foundEntry).toBeUndefined(); - }); - }); -}); -- cgit v1.2.3 From 148cc3c21b7e7132111244cd9908a4bcbbb0891b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Mar 2018 15:05:25 +0000 Subject: removed helper methods --- app/helpers/application_helper.rb | 4 ---- app/helpers/blob_helper.rb | 14 -------------- app/views/projects/tree/_tree_header.html.haml | 5 ----- app/views/shared/_ref_switcher.html.haml | 6 ------ 4 files changed, 29 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 475341cf9b1..af9c8bf1bd3 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -320,10 +320,6 @@ module ApplicationHelper cookies["sidebar_collapsed"] == "true" end - def show_new_ide? - cookies["new_repo"] == "true" && body_data_page != 'projects:show' - end - def locale_path asset_path("locale/#{Gitlab::I18n.locale}/app.js") end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 0e806d16bc5..5ff09b23a78 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -33,20 +33,6 @@ module BlobHelper ref) end - def ide_edit_button(project = @project, ref = @ref, path = @path, options = {}) - return unless show_new_ide? - return unless blob = readable_blob(options, path, project, ref) - - common_classes = "btn js-edit-ide #{options[:extra_class]}" - - edit_button_tag(blob, - common_classes, - _('Web IDE'), - ide_edit_path(project, ref, path, options), - project, - ref) - end - def modify_file_button(project = @project, ref = @ref, path = @path, label:, action:, btn_class:, modal_type:) return unless current_user diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 39511435508..06bce52e709 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -72,11 +72,6 @@ #{ _('New tag') } .tree-controls - - if show_new_ide? - = succeed " " do - = link_to ide_edit_path(@project, @id), class: 'btn btn-default' do - = _('Web IDE') - = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn' = render 'projects/find_file_link' diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index 479bd2cdb38..2acb92160a7 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -1,6 +1,5 @@ - show_create = local_assigns.fetch(:show_create, false) -- show_new_branch_form = show_new_ide? && show_create && can?(current_user, :push_code, @project) - dropdown_toggle_text = @ref || @project.default_branch = form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do = hidden_field_tag :destination, destination @@ -22,8 +21,3 @@ %li %a.dropdown-toggle-page{ href: "#" } Create new branch - - if show_new_branch_form - .dropdown-page-two - = dropdown_title("Create new branch", options: { back: true }) - = dropdown_content do - .js-new-branch-dropdown -- cgit v1.2.3 From a78cf4d3250e7ebee79ae32a6ad5eb4cc4553a97 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Mar 2018 15:38:46 +0000 Subject: removed show_new_branch_form --- app/views/shared/_ref_switcher.html.haml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index 2acb92160a7..4c8c92d722a 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -15,9 +15,3 @@ = dropdown_filter _("Search branches and tags") = dropdown_content = dropdown_loading - - if show_new_branch_form - = dropdown_footer do - %ul.dropdown-footer-list - %li - %a.dropdown-toggle-page{ href: "#" } - Create new branch -- cgit v1.2.3 From 9a2d9290c946364706ebfc2bca7c1e786ba752e8 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 2 Mar 2018 08:40:40 +0000 Subject: removed IDE button --- app/views/projects/blob/_header.html.haml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/views/projects/blob/_header.html.haml b/app/views/projects/blob/_header.html.haml index 1b150ec3e5c..f93bb02acb9 100644 --- a/app/views/projects/blob/_header.html.haml +++ b/app/views/projects/blob/_header.html.haml @@ -12,7 +12,6 @@ .btn-group{ role: "group" }< = edit_blob_button - = ide_edit_button - if current_user = replace_blob_link = delete_blob_link -- cgit v1.2.3 From 0a3fc7e3c283c10ef8415a719e83e80f0122af7d Mon Sep 17 00:00:00 2001 From: Andreas Brandl Date: Fri, 2 Mar 2018 10:15:52 +0100 Subject: Use yield instead of block.call. --- app/finders/snippets_finder.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index fc257933e57..565955ffc05 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -61,25 +61,25 @@ class SnippetsFinder < UnionFinder # Returns a collection of projects that is either public or visible to the # logged in user. # - # A caller may pass in a block to modify individual parts of + # A caller must pass in a block to modify individual parts of # the query, e.g. to apply .with_feature_available_for_user on top of it. # This is useful for performance as we can stick those additional filters # at the bottom of e.g. the UNION. - def projects_for_user(&block) - return block.call(Project.public_to_user) unless current_user + def projects_for_user + return yield(Project.public_to_user) unless current_user # If the current_user is allowed to see all projects, # we can shortcut and just return. - return block.call(Project.all) if current_user.full_private_access? + return yield(Project.all) if current_user.full_private_access? authorized = current_user .project_authorizations .select(1) .where('project_authorizations.project_id = projects.id') - authorized_projects = block.call(Project.where('EXISTS (?)', authorized)) + authorized_projects = yield(Project.where('EXISTS (?)', authorized)) levels = Gitlab::VisibilityLevel.levels_for_user(current_user) - visible_projects = block.call(Project.where(visibility_level: levels)) + visible_projects = yield(Project.where(visibility_level: levels)) # We use a UNION here instead of OR clauses since this results in better # performance. -- cgit v1.2.3 From 30ef73afac3d57af87ee948bb7c90743f49e6da8 Mon Sep 17 00:00:00 2001 From: julien MILLAU Date: Fri, 2 Mar 2018 10:10:39 +0000 Subject: 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 -- cgit v1.2.3 From 960f981db7e5f3a1bd9ee2ad8682435e1bc71a46 Mon Sep 17 00:00:00 2001 From: Andrew Newdigate Date: Fri, 2 Mar 2018 10:25:11 +0000 Subject: 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 -- cgit v1.2.3 From cb55bc3c0770adf7122d7cf49b12cb45c43de7ec Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 22 Feb 2018 12:09:27 +0000 Subject: 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 ++++++++++++------ spec/lib/banzai/filter/autolink_filter_spec.rb | 73 ++++++++++++++++++++------ 2 files changed, 92 insertions(+), 31 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('') + expect(doc.children.last.text).to include('') + end end + end - it 'renders text after the link correctly for not other protocol' do - doc = filter(ERB::Util.html_escape_once("")) + # 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 - expect(doc.children.last.text).to include('') + 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 -- cgit v1.2.3 From f09fe848da02189a3dafe2d532fefb1379eacac4 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 1 Mar 2018 17:16:39 +0100 Subject: 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 -- cgit v1.2.3 From cd770946c420b92535bd6000d0b8ea2fc89cbd0a Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Thu, 1 Mar 2018 17:50:07 +0100 Subject: 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 | 360 +++++++++++++++-------------- 6 files changed, 193 insertions(+), 185 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 - - 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 - - context "where 'follow' == true" do - let(:options) { { ref: "master", follow: true } } + 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 - context "and 'path' is a directory" do - it "does not follow renames" do - log_commits = repository.log(options.merge(path: "encoding")) + 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 - 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 + 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 "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")) + 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")) 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) + expect(log_commits).not_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)) + 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).not_to include(commit_with_new_name) - expect(log_commits).to include(rename_commit) - expect(log_commits).to include(commit_with_old_name) + 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 - 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)) + 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)) - expect(log_commits).to contain_exactly(rename_commit) + 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 - 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)) + 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)) - aggregate_failures do - expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name) + expect(log_commits).to contain_exactly(rename_commit) 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)) + 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).not_to include(commit_with_new_name) - expect(log_commits).not_to include(rename_commit) - expect(log_commits).to include(commit_with_old_name) + aggregate_failures do + expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name) + end 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)) + 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)) - expect(log_commits).to contain_exactly(commit_with_old_name) + 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 '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)) + 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).not_to include(rename_commit) + expect(log_commits).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")) + context "unknown ref" do + it "returns an empty array" do + log_commits = repository.log(options.merge(ref: 'unknown')) - 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) + expect(log_commits).to eq([]) 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 "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 - context "and 'path' is a directory" do - let(:log_commits) do - repository.log(options.merge(path: "encoding")) + 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 - 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 - context "and 'path' is a file that matches the new filename" do - let(:log_commits) do - repository.log(options.merge(path: "encoding/CHANGELOG")) + 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 - 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 - context "and 'path' is a file that matches the old filename" do - let(:log_commits) do - repository.log(options.merge(path: "CHANGELOG")) + 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 - 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) + 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 "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 + context "where provides 'after' timestamp" do + options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') } - it "returns a list of commits" do - expect(log_commits.size).to eq(1) + 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 - end - context "where provides 'after' timestamp" do - options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') } + context "where provides 'before' timestamp" do + options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') } - it "should returns commits on or after that timestamp" do - commits = repository.log(options) + 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[:after] } + expect(commits.size).to be > 0 + expect(commits).to satisfy do |commits| + commits.all? { |commit| commit.committed_date <= options[:before] } + end end end - end - context "where provides 'before' timestamp" do - options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') } + context 'when multiple paths are provided' do + let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } } - 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] } + 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 - end - end - context 'when multiple paths are provided' do - let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } } + it 'only returns commits matching at least one path' do + commits = repository.log(options) - def commit_files(commit) - commit.rugged_diff_from_parent.deltas.flat_map do |delta| - [delta.old_file[:path], delta.new_file[:path]].uniq.compact + expect(commits.size).to be > 0 + expect(commits).to satisfy do |commits| + commits.none? { |commit| (commit_files(commit) & options[:path]).empty? } + end end end - it 'only returns commits matching at least one path' do - commits = repository.log(options) + context 'limit validation' do + where(:limit) do + [0, nil, '', 'foo'] + end - expect(commits.size).to be > 0 - expect(commits).to satisfy do |commits| - commits.none? { |commit| (commit_files(commit) & options[:path]).empty? } + with_them do + it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) } end end - end - context 'limit validation' do - where(:limit) do - [0, nil, '', 'foo'] - end + context 'with all' do + it 'returns a list of commits' do + commits = repository.log({ all: true, limit: 50 }) - with_them do - it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) } + expect(commits.size).to eq(37) + end end end - context 'with all' do - let(:options) { { all: true, limit: 50 } } - - it 'returns a list of commits' do - commits = repository.log(options) + context 'when Gitaly find_commits feature is enabled' do + it_behaves_like 'repository log' + end - 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 -- cgit v1.2.3 From 6f945f20b4c3683bc862ebc476bad9331d72784e Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 27 Feb 2018 19:15:25 +0000 Subject: Foreground verification of uploads and LFS objects --- app/models/lfs_object.rb | 4 ++ .../unreleased/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 ++++++++++ spec/tasks/gitlab/uploads/check_rake_spec.rb | 28 ++++++++++ spec/tasks/gitlab/uploads_rake_spec.rb | 27 --------- 17 files changed, 399 insertions(+), 81 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 create mode 100644 spec/tasks/gitlab/uploads/check_rake_spec.rb delete mode 100644 spec/tasks/gitlab/uploads_rake_spec.rb 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/check_rake_spec.rb b/spec/tasks/gitlab/uploads/check_rake_spec.rb new file mode 100644 index 00000000000..5d597c66133 --- /dev/null +++ b/spec/tasks/gitlab/uploads/check_rake_spec.rb @@ -0,0 +1,28 @@ +require 'rake_helper' + +describe 'gitlab:uploads rake tasks' do + describe 'check' do + let!(:upload) { create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) } + + before do + Rake.application.rake_require('tasks/gitlab/uploads/check') + stub_env('VERBOSE' => 'true') + end + + 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 + missing_upload = create(:upload) + + 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(/Checksum mismatch/).to_stdout + end + end +end diff --git a/spec/tasks/gitlab/uploads_rake_spec.rb b/spec/tasks/gitlab/uploads_rake_spec.rb deleted file mode 100644 index ac0005e51e0..00000000000 --- a/spec/tasks/gitlab/uploads_rake_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'rake_helper' - -describe 'gitlab:uploads rake tasks' do - describe 'check' do - let!(:upload) { create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) } - - before do - Rake.application.rake_require 'tasks/gitlab/uploads' - 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 - end - - it 'errors out about missing files on the file system' do - create(:upload) - - expect { run_rake_task('gitlab:uploads:check') }.to output(/File does not exist on the file system/).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 - end - end -end -- cgit v1.2.3 From daeeb7f8480d747d500ea3aeddb479a29e890562 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 2 Mar 2018 12:03:03 +0000 Subject: 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 ++-- ...il-did-not-pick-up-unsubscribe-quick-action.yml | 5 +++ .../issues/user_uses_slash_commands_spec.rb | 3 -- .../fixtures/emails/update_commands_only_reply.eml | 38 ++++++++++++++++ .../email/handler/create_note_handler_spec.rb | 27 +++++------- spec/services/notes/create_service_spec.rb | 51 ++++++++++++++++------ spec/services/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' -- cgit v1.2.3 From 64b4b7ecc407cbce657c21da0d7ebb8ea1213ce9 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Fri, 2 Mar 2018 11:16:41 -0500 Subject: Update docs to include information on the Geo extension --- doc/install/requirements.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index b2c9177e6eb..1f2b4d9d3d9 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -137,6 +137,14 @@ CREATE EXTENSION pg_trgm; On some systems you may need to install an additional package (e.g. `postgresql-contrib`) for this extension to become available. +#### Additional requirements for GitLab Geo + +If you are using [GitLab Geo](https://docs.gitlab.com/ee/development/geo.html), the [tracking database](https://docs.gitlab.com/ee/development/geo.html#geo-tracking-database) also requires the `postgres_fdw` extension. + +``` +CREATE EXTENSION postgres_fdw; +``` + ## Unicorn Workers It's possible to increase the amount of unicorn workers and this will usually help to reduce the response time of the applications and increase the ability to handle parallel requests. -- cgit v1.2.3 From bb4fcb7809aa9d14a60e5c90f11f07fac8f584a8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 3 Mar 2018 00:39:42 +0800 Subject: 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 | 14 +++++++------- spec/lib/gitlab/middleware/release_env_spec.rb | 8 ++------ 4 files changed, 15 insertions(+), 18 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 + let(:request) { Rack::MockRequest.new(rack_stack) } + subject do - app = described_class.new(fake_app) - app.extend(observe_env) - app + described_class.new(fake_app).tap do |app| + app.extend(observe_env) + end end - let(:request) { Rack::MockRequest.new(rack_stack) } - 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') -- cgit v1.2.3 From 39011be53daee921dac1648044d1e68b9706197c Mon Sep 17 00:00:00 2001 From: Andreas Brandl Date: Fri, 2 Mar 2018 16:01:06 +0100 Subject: Extract method User#authorizations_for_projects. --- app/finders/snippets_finder.rb | 6 +----- app/models/project.rb | 16 ++++------------ app/models/user.rb | 9 +++++++++ spec/models/user_spec.rb | 26 ++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index 565955ffc05..d498a2d6d11 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -72,11 +72,7 @@ class SnippetsFinder < UnionFinder # we can shortcut and just return. return yield(Project.all) if current_user.full_private_access? - authorized = current_user - .project_authorizations - .select(1) - .where('project_authorizations.project_id = projects.id') - authorized_projects = yield(Project.where('EXISTS (?)', authorized)) + authorized_projects = yield(Project.where('EXISTS (?)', current_user.authorizations_for_projects)) levels = Gitlab::VisibilityLevel.levels_for_user(current_user) visible_projects = yield(Project.where(visibility_level: levels)) diff --git a/app/models/project.rb b/app/models/project.rb index ad4315e1601..5b1f8b2658b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -319,14 +319,9 @@ class Project < ActiveRecord::Base # logged in user. def self.public_or_visible_to_user(user = nil) if user - authorized = user - .project_authorizations - .select(1) - .where('project_authorizations.project_id = projects.id') - - levels = Gitlab::VisibilityLevel.levels_for_user(user) - - where('EXISTS (?) OR projects.visibility_level IN (?)', authorized, levels) + where('EXISTS (?) OR projects.visibility_level IN (?)', + user.authorizations_for_projects, + Gitlab::VisibilityLevel.levels_for_user(user)) else public_to_user end @@ -347,14 +342,11 @@ class Project < ActiveRecord::Base elsif user column = ProjectFeature.quoted_access_level_column(feature) - authorized = user.project_authorizations.select(1) - .where('project_authorizations.project_id = projects.id') - with_project_feature .where("#{column} IN (?) OR (#{column} = ? AND EXISTS (?))", visible, ProjectFeature::PRIVATE, - authorized) + user.authorizations_for_projects) else with_feature_access_level(feature, visible) end diff --git a/app/models/user.rb b/app/models/user.rb index 9547506d33d..9c60adf0c90 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -601,6 +601,15 @@ class User < ActiveRecord::Base authorized_projects(min_access_level).exists?({ id: project.id }) end + # Typically used in conjunction with projects table to get projects + # a user has been given access to. + # + # Example use: + # `Project.where('EXISTS(?)', user.authorizations_for_projects)` + def authorizations_for_projects + project_authorizations.select(1).where('project_authorizations.project_id = projects.id') + end + # Returns the projects this user has reporter (or greater) access to, limited # to at most the given projects. # diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 3531de244bd..00b5226d874 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1635,6 +1635,32 @@ describe User do end end + describe '#authorizations_for_projects' do + let!(:user) { create(:user) } + subject { Project.where("EXISTS (?)", user.authorizations_for_projects) } + + it 'includes projects that belong to a user, but no other projects' do + owned = create(:project, :private, namespace: user.namespace) + member = create(:project, :private).tap { |p| p.add_master(user) } + other = create(:project) + + expect(subject).to include(owned) + expect(subject).to include(member) + expect(subject).not_to include(other) + end + + it 'includes projects a user has access to, but no other projects' do + other_user = create(:user) + accessible = create(:project, :private, namespace: other_user.namespace) do |project| + project.add_developer(user) + end + other = create(:project) + + expect(subject).to include(accessible) + expect(subject).not_to include(other) + end + end + describe '#authorized_projects', :delete do context 'with a minimum access level' do it 'includes projects for which the user is an owner' do -- cgit v1.2.3 From e4ec50b70b877c17b4f9f38e60da6fcc0752025a Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Fri, 2 Mar 2018 14:45:48 +0530 Subject: Add support for query params for labels endpoint --- .../filtered_search_visual_tokens.js | 23 ++++++++++++++++++++-- app/assets/javascripts/lib/utils/common_utils.js | 8 ++++++++ .../filtered_search_visual_tokens_spec.js | 18 +++++++++++++++++ spec/javascripts/lib/utils/common_utils_spec.js | 15 ++++++++++++++ 4 files changed, 62 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index a19bb882410..600024c21c3 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -1,5 +1,6 @@ import _ from 'underscore'; -import AjaxCache from '../lib/utils/ajax_cache'; +import AjaxCache from '~/lib/utils/ajax_cache'; +import { objectToQueryString } from '~/lib/utils/common_utils'; import Flash from '../flash'; import FilteredSearchContainer from './container'; import UsersCache from '../lib/utils/users_cache'; @@ -16,6 +17,21 @@ export default class FilteredSearchVisualTokens { }; } + /** + * Returns a computed API endpoint + * and query string composed of values from endpointQueryParams + * @param {String} endpoint + * @param {String} endpointQueryParams + */ + static getEndpointWithQueryParams(endpoint, endpointQueryParams) { + if (!endpointQueryParams) { + return endpoint; + } + + const queryString = objectToQueryString(JSON.parse(endpointQueryParams)); + return `${endpoint}?${queryString}`; + } + static unselectTokens() { const otherTokens = FilteredSearchContainer.container.querySelectorAll('.js-visual-token .selectable.selected'); [].forEach.call(otherTokens, t => t.classList.remove('selected')); @@ -86,7 +102,10 @@ export default class FilteredSearchVisualTokens { static updateLabelTokenColor(tokenValueContainer, tokenValue) { const filteredSearchInput = FilteredSearchContainer.container.querySelector('.filtered-search'); const baseEndpoint = filteredSearchInput.dataset.baseEndpoint; - const labelsEndpoint = `${baseEndpoint}/labels.json`; + const labelsEndpoint = FilteredSearchVisualTokens.getEndpointWithQueryParams( + `${baseEndpoint}/labels.json`, + filteredSearchInput.dataset.endpointQueryParams, + ); return AjaxCache.retrieve(labelsEndpoint) .then(FilteredSearchVisualTokens.preprocessLabel.bind(null, labelsEndpoint)) diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index e741789fbb6..ed90db317df 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -302,6 +302,14 @@ export const parseQueryStringIntoObject = (query = '') => { }, {}); }; +/** + * Converts object with key-value pairs + * into query-param string + * + * @param {Object} params + */ +export const objectToQueryString = (params = {}) => Object.keys(params).map(param => `${param}=${params[param]}`).join('&'); + export const buildUrlWithCurrentLocation = param => (param ? `${window.location.pathname}${param}` : window.location.pathname); /** diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js index f1da5f81c0f..756a654765b 100644 --- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js +++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js @@ -128,6 +128,24 @@ describe('Filtered Search Visual Tokens', () => { }); }); + describe('getEndpointWithQueryParams', () => { + it('returns `endpoint` string as is when second param `endpointQueryParams` is undefined, null or empty string', () => { + const endpoint = 'foo/bar/labels.json'; + expect(subject.getEndpointWithQueryParams(endpoint)).toBe(endpoint); + expect(subject.getEndpointWithQueryParams(endpoint, null)).toBe(endpoint); + expect(subject.getEndpointWithQueryParams(endpoint, '')).toBe(endpoint); + }); + + it('returns `endpoint` string with values of `endpointQueryParams`', () => { + const endpoint = 'foo/bar/labels.json'; + const singleQueryParams = '{"foo":"true"}'; + const multipleQueryParams = '{"foo":"true","bar":"true"}'; + + expect(subject.getEndpointWithQueryParams(endpoint, singleQueryParams)).toBe(`${endpoint}?foo=true`); + expect(subject.getEndpointWithQueryParams(endpoint, multipleQueryParams)).toBe(`${endpoint}?foo=true&bar=true`); + }); + }); + describe('unselectTokens', () => { it('does nothing when there are no tokens', () => { const beforeHTML = tokensContainer.innerHTML; diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 49799c31995..27f06573432 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -166,6 +166,21 @@ describe('common_utils', () => { }); }); + describe('objectToQueryString', () => { + it('returns empty string when `param` is undefined, null or empty string', () => { + expect(commonUtils.objectToQueryString()).toBe(''); + expect(commonUtils.objectToQueryString('')).toBe(''); + }); + + it('returns query string with values of `params`', () => { + const singleQueryParams = { foo: true }; + const multipleQueryParams = { foo: true, bar: true }; + + expect(commonUtils.objectToQueryString(singleQueryParams)).toBe('foo=true'); + expect(commonUtils.objectToQueryString(multipleQueryParams)).toBe('foo=true&bar=true'); + }); + }); + describe('buildUrlWithCurrentLocation', () => { it('should build an url with current location and given parameters', () => { expect(commonUtils.buildUrlWithCurrentLocation()).toEqual(window.location.pathname); -- cgit v1.2.3 From ca430d246d5d548264c909466ad334962508e3ab Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Fri, 2 Mar 2018 18:23:23 +0530 Subject: Add `isGroupDecendent` to init config --- .../javascripts/filtered_search/filtered_search_dropdown_manager.js | 2 ++ app/assets/javascripts/filtered_search/filtered_search_manager.js | 2 ++ app/assets/javascripts/pages/search/init_filtered_search.js | 2 ++ 3 files changed, 6 insertions(+) diff --git a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js index ee49a7be0b2..e6390f0855b 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_dropdown_manager.js @@ -16,6 +16,7 @@ export default class FilteredSearchDropdownManager { page, isGroup, isGroupAncestor, + isGroupDecendent, filteredSearchTokenKeys, }) { this.container = FilteredSearchContainer.container; @@ -26,6 +27,7 @@ export default class FilteredSearchDropdownManager { this.page = page; this.groupsOnly = isGroup; this.groupAncestor = isGroupAncestor; + this.isGroupDecendent = isGroupDecendent; this.setupMapping(); diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js index c6970d7837f..71b7e80335b 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js @@ -22,11 +22,13 @@ export default class FilteredSearchManager { page, isGroup = false, isGroupAncestor = false, + isGroupDecendent = false, filteredSearchTokenKeys = FilteredSearchTokenKeys, stateFiltersSelector = '.issues-state-filters', }) { this.isGroup = isGroup; this.isGroupAncestor = isGroupAncestor; + this.isGroupDecendent = isGroupDecendent; this.states = ['opened', 'closed', 'merged', 'all']; this.page = page; diff --git a/app/assets/javascripts/pages/search/init_filtered_search.js b/app/assets/javascripts/pages/search/init_filtered_search.js index 57f08701a4f..7fdf4ee0bf3 100644 --- a/app/assets/javascripts/pages/search/init_filtered_search.js +++ b/app/assets/javascripts/pages/search/init_filtered_search.js @@ -5,6 +5,7 @@ export default ({ filteredSearchTokenKeys, isGroup, isGroupAncestor, + isGroupDecendent, stateFiltersSelector, }) => { const filteredSearchEnabled = FilteredSearchManager && document.querySelector('.filtered-search'); @@ -13,6 +14,7 @@ export default ({ page, isGroup, isGroupAncestor, + isGroupDecendent, filteredSearchTokenKeys, stateFiltersSelector, }); -- cgit v1.2.3 From 57ce7c655cc58ee793cf6514fae590fa53850884 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Fri, 2 Mar 2018 17:10:50 +0000 Subject: 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 -- cgit v1.2.3 From 8510fc8701344afdfc76e4df7f97b686175dae70 Mon Sep 17 00:00:00 2001 From: Constance Okoghenun Date: Fri, 2 Mar 2018 20:04:55 +0100 Subject: Removed terminal webpack bundle tag --- .../javascripts/pages/projects/environments/terminal/index.js | 3 +++ app/assets/javascripts/terminal/index.js | 9 +++++++++ app/assets/javascripts/terminal/terminal_bundle.js | 9 --------- app/views/projects/environments/terminal.html.haml | 1 - config/webpack.config.js | 1 - 5 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 app/assets/javascripts/pages/projects/environments/terminal/index.js create mode 100644 app/assets/javascripts/terminal/index.js delete mode 100644 app/assets/javascripts/terminal/terminal_bundle.js diff --git a/app/assets/javascripts/pages/projects/environments/terminal/index.js b/app/assets/javascripts/pages/projects/environments/terminal/index.js new file mode 100644 index 00000000000..7129e24cee1 --- /dev/null +++ b/app/assets/javascripts/pages/projects/environments/terminal/index.js @@ -0,0 +1,3 @@ +import initTerminal from '~/terminal/'; + +document.addEventListener('DOMContentLoaded', initTerminal); diff --git a/app/assets/javascripts/terminal/index.js b/app/assets/javascripts/terminal/index.js new file mode 100644 index 00000000000..1a75e072c4e --- /dev/null +++ b/app/assets/javascripts/terminal/index.js @@ -0,0 +1,9 @@ +import 'vendor/xterm/encoding-indexes'; +import 'vendor/xterm/encoding'; +import Terminal from 'vendor/xterm/xterm'; +import 'vendor/xterm/fit'; +import './terminal'; + +window.Terminal = Terminal; + +export default () => new gl.Terminal({ selector: '#terminal' }); diff --git a/app/assets/javascripts/terminal/terminal_bundle.js b/app/assets/javascripts/terminal/terminal_bundle.js deleted file mode 100644 index 134522ef961..00000000000 --- a/app/assets/javascripts/terminal/terminal_bundle.js +++ /dev/null @@ -1,9 +0,0 @@ -import 'vendor/xterm/encoding-indexes'; -import 'vendor/xterm/encoding'; -import Terminal from 'vendor/xterm/xterm'; -import 'vendor/xterm/fit'; -import './terminal'; - -window.Terminal = Terminal; - -$(() => new gl.Terminal({ selector: '#terminal' })); diff --git a/app/views/projects/environments/terminal.html.haml b/app/views/projects/environments/terminal.html.haml index 7be4ef39117..6ec4ff56552 100644 --- a/app/views/projects/environments/terminal.html.haml +++ b/app/views/projects/environments/terminal.html.haml @@ -3,7 +3,6 @@ - content_for :page_specific_javascripts do = stylesheet_link_tag "xterm/xterm" - = webpack_bundle_tag("terminal") %div{ class: container_class } .top-area diff --git a/config/webpack.config.js b/config/webpack.config.js index 959cebe7487..0fedb6a8c72 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -45,7 +45,6 @@ function generateEntries() { const manualEntries = { monitoring: './monitoring/monitoring_bundle.js', mr_notes: './mr_notes/index.js', - terminal: './terminal/terminal_bundle.js', common: './commons/index.js', common_vue: './vue_shared/vue_resource_interceptor.js', -- cgit v1.2.3 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: 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 +++++++------- spec/lib/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 -- cgit v1.2.3 From b342d2ae52d8f821dedd513dc80b56c99d2868f8 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Fri, 2 Mar 2018 21:16:11 +0000 Subject: Remove mr_notes webpack bundle --- app/assets/javascripts/mr_notes/index.js | 4 ++-- app/assets/javascripts/pages/projects/merge_requests/show/index.js | 6 ++++++ app/views/projects/merge_requests/show.html.haml | 5 ----- config/webpack.config.js | 3 --- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/mr_notes/index.js b/app/assets/javascripts/mr_notes/index.js index f4cba998fa7..972fdb2b791 100644 --- a/app/assets/javascripts/mr_notes/index.js +++ b/app/assets/javascripts/mr_notes/index.js @@ -3,7 +3,7 @@ import notesApp from '../notes/components/notes_app.vue'; import discussionCounter from '../notes/components/discussion_counter.vue'; import store from '../notes/stores'; -document.addEventListener('DOMContentLoaded', () => { +export default function initMrNotes() { new Vue({ // eslint-disable-line el: '#js-vue-mr-discussions', components: { @@ -38,4 +38,4 @@ document.addEventListener('DOMContentLoaded', () => { return createElement('discussion-counter'); }, }); -}); +} diff --git a/app/assets/javascripts/pages/projects/merge_requests/show/index.js b/app/assets/javascripts/pages/projects/merge_requests/show/index.js index 3e72f7a6f37..e5b2827b50c 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/show/index.js +++ b/app/assets/javascripts/pages/projects/merge_requests/show/index.js @@ -1,7 +1,13 @@ +import { hasVueMRDiscussionsCookie } from '~/lib/utils/common_utils'; +import initMrNotes from '~/mr_notes'; import initSidebarBundle from '~/sidebar/sidebar_bundle'; import initShow from '../init_merge_request_show'; document.addEventListener('DOMContentLoaded', () => { initShow(); initSidebarBundle(); + + if (hasVueMRDiscussionsCookie()) { + initMrNotes(); + } }); diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml index f2e35ef6e0c..9866cc716ee 100644 --- a/app/views/projects/merge_requests/show.html.haml +++ b/app/views/projects/merge_requests/show.html.haml @@ -5,11 +5,6 @@ - page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests" - page_description @merge_request.description - page_card_attributes @merge_request.card_attributes -- content_for :page_specific_javascripts do - = webpack_bundle_tag('common_vue') - - - if has_vue_discussions_cookie? - = webpack_bundle_tag('mr_notes') .merge-request{ data: { mr_action: j(params[:tab].presence || 'show'), url: merge_request_path(@merge_request, format: :json), project_path: project_path(@merge_request.project) } } = render "projects/merge_requests/mr_title" diff --git a/config/webpack.config.js b/config/webpack.config.js index 0fedb6a8c72..cc098c8a3f9 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -43,9 +43,6 @@ function generateEntries() { autoEntriesCount = Object.keys(autoEntries).length; const manualEntries = { - monitoring: './monitoring/monitoring_bundle.js', - mr_notes: './mr_notes/index.js', - common: './commons/index.js', common_vue: './vue_shared/vue_resource_interceptor.js', locale: './locale/index.js', -- cgit v1.2.3 From 94dc43f37ce20347b5ad67aee0cf90f5c86a814f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 2 Mar 2018 10:14:19 -0800 Subject: 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 -- cgit v1.2.3 From 8ae57909a1ac5f398098f9d1c4581b3cf3d61674 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Thu, 1 Mar 2018 18:24:25 -0600 Subject: Backport BE changes from CI/CD projects ee!4567 See https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4567 --- app/controllers/projects_controller.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 913689a1e74..4a2f5ab3f88 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -41,7 +41,7 @@ class ProjectsController < Projects::ApplicationController cookies[:issue_board_welcome_hidden] = { path: project_path(@project), value: nil, expires: Time.at(0) } redirect_to( - project_path(@project), + project_path(@project, custom_import_params), notice: _("Project '%{project_name}' was successfully created.") % { project_name: @project.name } ) else @@ -103,7 +103,7 @@ class ProjectsController < Projects::ApplicationController def show if @project.import_in_progress? - redirect_to project_import_path(@project) + redirect_to project_import_path(@project, custom_import_params) return end @@ -359,6 +359,10 @@ class ProjectsController < Projects::ApplicationController ] end + def custom_import_params + {} + end + def repo_exists? project.repository_exists? && !project.empty_repo? -- cgit v1.2.3 From 0bba60f52c7557894007401b81f2e242c327bb7b Mon Sep 17 00:00:00 2001 From: Filip Mech Date: Sat, 3 Mar 2018 12:09:57 +0000 Subject: Update proofreader.md --- doc/development/i18n/proofreader.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md index 9aa3fb07abf..b732cc65b73 100644 --- a/doc/development/i18n/proofreader.md +++ b/doc/development/i18n/proofreader.md @@ -22,6 +22,8 @@ are very appreciative of the work done by translators and proofreaders! - Japanese - Korean - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve) +- Polish + - Filip Mech - [GitLab](https://gitlab.com/mehenz), [Crowdin](https://crowdin.com/profile/mehenz) - Portuguese, Brazilian - Paulo George Gomes Bezerra - [GitLab](https://gitlab.com/paulobezerra), [Crowdin](https://crowdin.com/profile/paulogomes.rep) - Russian -- cgit v1.2.3 From 885998c220d76cf373669666dab5a2bb8dfbe023 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 3 Mar 2018 22:18:32 -0800 Subject: Release libgit2 cache and open file descriptors after `git gc` run Relates to #21879 --- app/workers/git_garbage_collect_worker.rb | 4 ++++ changelogs/unreleased/sh-cleanup-after-git-gc.yml | 5 +++++ spec/workers/git_garbage_collect_worker_spec.rb | 6 ++++++ 3 files changed, 15 insertions(+) create mode 100644 changelogs/unreleased/sh-cleanup-after-git-gc.yml diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb index 7ba224d74c8..55fb817ca6e 100644 --- a/app/workers/git_garbage_collect_worker.rb +++ b/app/workers/git_garbage_collect_worker.rb @@ -44,6 +44,10 @@ class GitGarbageCollectWorker # Refresh the branch cache in case garbage collection caused a ref lookup to fail flush_ref_caches(project) if task == :gc + + # In case pack files are deleted, release libgit2 cache and open file + # descriptors ASAP instead of waiting for Ruby garbage collection + project.cleanup ensure cancel_lease(lease_key, lease_uuid) if lease_key.present? && lease_uuid.present? end diff --git a/changelogs/unreleased/sh-cleanup-after-git-gc.yml b/changelogs/unreleased/sh-cleanup-after-git-gc.yml new file mode 100644 index 00000000000..4b652f4d6ce --- /dev/null +++ b/changelogs/unreleased/sh-cleanup-after-git-gc.yml @@ -0,0 +1,5 @@ +--- +title: Release libgit2 cache and open file descriptors after `git gc` run +merge_request: +author: +type: fixed diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index 47297de738b..74539a7e493 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -195,6 +195,12 @@ describe GitGarbageCollectWorker do expect(File.exist?(bitmap_path(after_packs.first))).to eq(bitmaps_enabled) end + + it 'cleans up repository after finishing' do + expect_any_instance_of(Project).to receive(:cleanup).and_call_original + + subject.perform(project.id, 'gc', lease_key, lease_uuid) + end end context 'with bitmaps enabled' do -- cgit v1.2.3 From bb216e2b7cb15054381211844103d29c60f15424 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Sun, 4 Mar 2018 20:15:43 +0900 Subject: Stop loading spinner on error of milestone update on issue --- app/assets/javascripts/milestone_select.js | 3 +++ .../unreleased/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 -- cgit v1.2.3 From 29d87db896a6f67f72679694c3d481eff4275a18 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 5 Mar 2018 09:16:25 +0100 Subject: Remove unsued pipelines-related prometheus metric --- lib/gitlab/ci/pipeline/chain/create.rb | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lib/gitlab/ci/pipeline/chain/create.rb b/lib/gitlab/ci/pipeline/chain/create.rb index d19a2519803..d5e17a123df 100644 --- a/lib/gitlab/ci/pipeline/chain/create.rb +++ b/lib/gitlab/ci/pipeline/chain/create.rb @@ -17,27 +17,11 @@ module Gitlab end rescue ActiveRecord::RecordInvalid => e error("Failed to persist the pipeline: #{e}") - ensure - if pipeline.builds.where(stage_id: nil).any? - invalid_builds_counter.increment(node: hostname) - end end def break? !pipeline.persisted? end - - private - - def invalid_builds_counter - @counter ||= Gitlab::Metrics - .counter(:gitlab_ci_invalid_builds_total, - 'Invalid builds without stage assigned counter') - end - - def hostname - @hostname ||= Socket.gethostname - end end end end -- cgit v1.2.3 From 64857a9bf3bd64eaf543ae1d43e02344e287403b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 1 Mar 2018 17:40:00 +0000 Subject: Manage empty states in Pipelines page Adds i18n Adds test Fix broken tests Fixes empty tab state for external CI --- .../commit/pipelines/pipelines_table.vue | 38 +- .../pages/projects/pipelines/index/index.js | 14 + .../pipelines/components/blank_state.vue | 32 + .../pipelines/components/empty_state.vue | 49 +- .../pipelines/components/error_state.vue | 26 - .../pipelines/components/nav_controls.vue | 81 +-- .../javascripts/pipelines/components/pipelines.vue | 250 +++++--- .../javascripts/pipelines/mixins/pipelines.js | 25 +- .../pipelines/stores/pipelines_store.js | 7 +- app/views/projects/pipelines/index.html.haml | 9 +- .../unreleased/38587-pipelines-empty-state.yml | 5 + spec/features/projects/pipelines/pipelines_spec.rb | 39 +- spec/javascripts/pipelines/blank_state_spec.js | 29 + spec/javascripts/pipelines/empty_state_spec.js | 28 +- spec/javascripts/pipelines/error_state_spec.js | 27 - spec/javascripts/pipelines/nav_controls_spec.js | 84 +-- spec/javascripts/pipelines/pipelines_spec.js | 677 ++++++++++++++++++--- spec/javascripts/pipelines/pipelines_store_spec.js | 7 +- 18 files changed, 996 insertions(+), 431 deletions(-) create mode 100644 app/assets/javascripts/pipelines/components/blank_state.vue delete mode 100644 app/assets/javascripts/pipelines/components/error_state.vue create mode 100644 changelogs/unreleased/38587-pipelines-empty-state.yml create mode 100644 spec/javascripts/pipelines/blank_state_spec.js delete mode 100644 spec/javascripts/pipelines/error_state_spec.js diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue index ce19069f103..3b3072ebd3e 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue @@ -20,10 +20,6 @@ type: String, required: true, }, - emptyStateSvgPath: { - type: String, - required: true, - }, errorStateSvgPath: { type: String, required: true, @@ -45,23 +41,14 @@ }, computed: { - /** - * Empty state is only rendered if after the first request we receive no pipelines. - * - * @return {Boolean} - */ - shouldRenderEmptyState() { - return !this.state.pipelines.length && - !this.isLoading && - this.hasMadeRequest && - !this.hasError; - }, - shouldRenderTable() { return !this.isLoading && this.state.pipelines.length > 0 && !this.hasError; }, + shouldRenderErrorState() { + return this.hasError && !this.isLoading; + }, }, created() { this.service = new PipelinesService(this.endpoint); @@ -92,25 +79,22 @@
- - -
new Vue({ return { store, + dataset: document.querySelector(this.$options.el).dataset, }; }, + render(createElement) { return createElement('pipelines-component', { props: { store: this.store, + endpoint: this.dataset.endpoint, + helpPagePath: this.dataset.helpPagePath, + emptyStateSvgPath: this.dataset.emptyStateSvgPath, + errorStateSvgPath: this.dataset.errorStateSvgPath, + noPipelinesSvgPath: this.dataset.noPipelinesSvgPath, + autoDevopsPath: this.dataset.helpAutoDevopsPath, + newPipelinePath: this.dataset.newPipelinePath, + canCreatePipeline: convertPermissionToBoolean(this.dataset.canCreatePipeline), + hasGitlabCi: convertPermissionToBoolean(this.dataset.hasGitlabCi), + ciLintPath: this.dataset.ciLintPath, + resetCachePath: this.dataset.resetCachePath, }, }); }, diff --git a/app/assets/javascripts/pipelines/components/blank_state.vue b/app/assets/javascripts/pipelines/components/blank_state.vue new file mode 100644 index 00000000000..8d3d6223d7b --- /dev/null +++ b/app/assets/javascripts/pipelines/components/blank_state.vue @@ -0,0 +1,32 @@ + + + diff --git a/app/assets/javascripts/pipelines/components/empty_state.vue b/app/assets/javascripts/pipelines/components/empty_state.vue index dfaa2574091..51f05c7827e 100644 --- a/app/assets/javascripts/pipelines/components/empty_state.vue +++ b/app/assets/javascripts/pipelines/components/empty_state.vue @@ -1,5 +1,6 @@ @@ -22,22 +27,36 @@
-

- {{ s__("Pipelines|Build with confidence") }} -

-

- {{ s__(`Pipelines|Continous Integration can help -catch bugs by running your tests automatically, -while Continuous Deployment can help you deliver code to your product environment.`) }} + + + +

+ {{ s__('Pipelines|This project is not currently set up to run pipelines.') }}

- +
diff --git a/app/assets/javascripts/pipelines/components/error_state.vue b/app/assets/javascripts/pipelines/components/error_state.vue deleted file mode 100644 index 012853b201d..00000000000 --- a/app/assets/javascripts/pipelines/components/error_state.vue +++ /dev/null @@ -1,26 +0,0 @@ - - - diff --git a/app/assets/javascripts/pipelines/components/nav_controls.vue b/app/assets/javascripts/pipelines/components/nav_controls.vue index f31a91c3403..383ab51fe56 100644 --- a/app/assets/javascripts/pipelines/components/nav_controls.vue +++ b/app/assets/javascripts/pipelines/components/nav_controls.vue @@ -1,67 +1,52 @@ diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue index 90930d5ff44..d983d8c0af8 100644 --- a/app/assets/javascripts/pipelines/components/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -1,12 +1,12 @@ + + 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(); + }); + }); +}); -- cgit v1.2.3 From 5c8854864ae4886c8a642536f6b4297a633144d2 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:07:52 +0530 Subject: LabelsSelect DropdownButton Component --- .../sidebar/labels_select/dropdown_button.vue | 78 ++++++++++++++++++++ .../sidebar/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); + }); + }); +}); -- cgit v1.2.3 From 523093220beab3c2768a748542829242f6a22c06 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:08:13 +0530 Subject: LabelsSelect DropdownCreateLabel Component --- .../labels_select/dropdown_create_label.vue | 84 ++++++++++++++++++++++ .../labels_select/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'); + }); + }); +}); -- cgit v1.2.3 From ab1bc5c7f76bcb893004749918a2913c2c965a1b Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:08:44 +0530 Subject: LabelsSelect DropdownFooter Component --- .../sidebar/labels_select/dropdown_footer.vue | 34 ++++++++++++++++++ .../sidebar/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'); + }); + }); +}); -- cgit v1.2.3 From 5989aa18f4ec49973e4d42015c1af8c93d3aed1f Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:08:56 +0530 Subject: LabelsSelect DropdownHeader Component --- .../sidebar/labels_select/dropdown_header.vue | 21 +++++++++++++ .../sidebar/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(); + }); + }); +}); -- cgit v1.2.3 From b4bd9777fb519a537c011d87651d29683587893e Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:09:12 +0530 Subject: LabelsSelect DropdownHiddenInput Component --- .../labels_select/dropdown_hidden_input.vue | 22 +++++++++++++ .../labels_select/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}`); + }); + }); +}); -- cgit v1.2.3 From 2e5497f0057021daa57d3e8958c840f6b8a82098 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:09:27 +0530 Subject: LabelsSelect DropdownSearchInput Component --- .../labels_select/dropdown_search_input.vue | 27 +++++++++++++++ .../labels_select/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(); + }); + }); +}); -- cgit v1.2.3 From 8b44ad6e6ab5408dca800b9cdb6a29335704109a Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:09:43 +0530 Subject: LabelsSelect DropdownTitle Component --- .../sidebar/labels_select/dropdown_title.vue | 30 ++++++++++++++++ .../sidebar/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'); + }); + }); +}); -- cgit v1.2.3 From c1ed7f3f961348ca9897beadc75ea9cf2494f344 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:09:55 +0530 Subject: LabelsSelect DropdownValue Component --- .../sidebar/labels_select/dropdown_value.vue | 63 +++++++++++++++ .../sidebar/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); + }); + }); +}); -- cgit v1.2.3 From 00db4cb733e5c1314d63972a280108a1d81c7301 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:10:13 +0530 Subject: LabelsSelect DropdownValueCollapsed Component --- .../labels_select/dropdown_value_collapsed.vue | 48 ++++++++++++++ .../labels_select/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}`); + }); + }); +}); -- cgit v1.2.3 From 4051d01c9cbd867b2478743ffe1ad50dfb030f6d Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Mon, 5 Mar 2018 18:10:23 +0530 Subject: 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 -- cgit v1.2.3 From d0b3d1201d4a465208cd7993409d628272920a9b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 5 Mar 2018 12:48:00 +0000 Subject: Fix broken empty tab state --- app/assets/javascripts/pipelines/components/pipelines.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue index d983d8c0af8..54f5fb7678b 100644 --- a/app/assets/javascripts/pipelines/components/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -133,7 +133,7 @@ return stateMap.tableList; } - if (this.hasGitlabCi) { + if ((this.scope !== 'all' && this.scope !== null) || this.hasGitlabCi) { return stateMap.emptyTab; } -- cgit v1.2.3 From 47d4890d3ad8a1c2ecb2b9c497d537c044c76e25 Mon Sep 17 00:00:00 2001 From: bunufi Date: Mon, 5 Mar 2018 12:57:47 +0000 Subject: Update API: add search param to branches --- ...42712_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) -- cgit v1.2.3 From 168ff28506dfe66a13a6c1bc5e3b772445a5bd18 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Wed, 28 Feb 2018 12:16:29 +0100 Subject: 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 +++++ .../unreleased/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') -- cgit v1.2.3 From 4ed79b4d4898221dee1b075ddfd17f8561699172 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 5 Mar 2018 10:24:18 -0300 Subject: 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 -- cgit v1.2.3 From 8403d35b2cf2d3c3a6c3c20571e286d9619bb938 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 5 Mar 2018 10:25:44 -0300 Subject: 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 -- cgit v1.2.3 From 741caf93e14758c223b6ef819390f5889bdd108b Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 5 Mar 2018 13:25:56 +0000 Subject: Use limited count queries also for scoped searches --- app/finders/notes_finder.rb | 12 ++++++++ app/views/search/_category.html.haml | 8 ++--- changelogs/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 ++++++++ spec/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 -- cgit v1.2.3 From 25ed4a7e324f3d08f121436296efd41365e01258 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 5 Mar 2018 10:26:54 -0300 Subject: 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 -- cgit v1.2.3 From 86df5c679318ded0a7b2680346841b552eaabd53 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 5 Mar 2018 15:41:54 +0200 Subject: Replace deprecated path_with_namespace with full_path Signed-off-by: Dmitriy Zaporozhets --- app/helpers/import_helper.rb | 14 +++++++------- .../concerns/gitlab/github_import/object_importer.rb | 2 +- .../gitlab/github_import/stage/finish_import_worker.rb | 2 +- spec/features/dashboard/issues_spec.rb | 2 +- spec/models/project_wiki_spec.rb | 6 +++--- spec/requests/api/pages_domains_spec.rb | 2 +- .../container_registry_authentication_service_spec.rb | 18 +++++++++--------- spec/services/system_hooks_service_spec.rb | 4 ++++ 8 files changed, 27 insertions(+), 23 deletions(-) diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb index a18ebfb6030..cdcb38b63ee 100644 --- a/app/helpers/import_helper.rb +++ b/app/helpers/import_helper.rb @@ -4,16 +4,16 @@ module ImportHelper "#{namespace}/#{name}" end - def provider_project_link(provider, path_with_namespace) - url = __send__("#{provider}_project_url", path_with_namespace) # rubocop:disable GitlabSecurity/PublicSend + def provider_project_link(provider, full_path) + url = __send__("#{provider}_project_url", full_path) # rubocop:disable GitlabSecurity/PublicSend - link_to path_with_namespace, url, target: '_blank', rel: 'noopener noreferrer' + link_to full_path, url, target: '_blank', rel: 'noopener noreferrer' end private - def github_project_url(path_with_namespace) - "#{github_root_url}/#{path_with_namespace}" + def github_project_url(full_path) + "#{github_root_url}/#{full_path}" end def github_root_url @@ -23,7 +23,7 @@ module ImportHelper @github_url = provider.fetch('url', 'https://github.com') if provider end - def gitea_project_url(path_with_namespace) - "#{@gitea_host_url.sub(%r{/+\z}, '')}/#{path_with_namespace}" + def gitea_project_url(full_path) + "#{@gitea_host_url.sub(%r{/+\z}, '')}/#{full_path}" end end diff --git a/app/workers/concerns/gitlab/github_import/object_importer.rb b/app/workers/concerns/gitlab/github_import/object_importer.rb index 9a9fbaad653..100d86e38c8 100644 --- a/app/workers/concerns/gitlab/github_import/object_importer.rb +++ b/app/workers/concerns/gitlab/github_import/object_importer.rb @@ -22,7 +22,7 @@ module Gitlab importer_class.new(object, project, client).execute - counter.increment(project: project.path_with_namespace) + counter.increment(project: project.full_path) end def counter diff --git a/app/workers/gitlab/github_import/stage/finish_import_worker.rb b/app/workers/gitlab/github_import/stage/finish_import_worker.rb index 073d6608082..a779e631516 100644 --- a/app/workers/gitlab/github_import/stage/finish_import_worker.rb +++ b/app/workers/gitlab/github_import/stage/finish_import_worker.rb @@ -16,7 +16,7 @@ module Gitlab def report_import_time(project) duration = Time.zone.now - project.created_at - path = project.path_with_namespace + path = project.full_path histogram.observe({ project: path }, duration) counter.increment diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index 54652e2d849..a7f0fa3d9e0 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -84,7 +84,7 @@ RSpec.describe 'Dashboard Issues' do wait_for_requests - project_path = "/#{project.path_with_namespace}" + project_path = "/#{project.full_path}" project_json = { name: project.name_with_namespace, url: project_path }.to_json # simulate selection, and prevent overlap by dropdown menu diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 1e7671476f1..8b4b5873704 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -14,13 +14,13 @@ describe ProjectWiki do it { is_expected.to delegate_method(:repository_storage_path).to :project } it { is_expected.to delegate_method(:hashed_storage?).to :project } - describe "#path_with_namespace" do + describe "#full_path" do it "returns the project path with namespace with the .wiki extension" do - expect(subject.path_with_namespace).to eq(project.full_path + '.wiki') + expect(subject.full_path).to eq(project.full_path + '.wiki') end it 'returns the same value as #full_path' do - expect(subject.path_with_namespace).to eq(subject.full_path) + expect(subject.full_path).to eq(subject.full_path) end end diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb index 025165622b7..dc3a116c060 100644 --- a/spec/requests/api/pages_domains_spec.rb +++ b/spec/requests/api/pages_domains_spec.rb @@ -16,7 +16,7 @@ describe API::PagesDomains do let(:route) { "/projects/#{project.id}/pages/domains" } let(:route_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain.domain}" } - let(:route_domain_path) { "/projects/#{project.path_with_namespace.gsub('/', '%2F')}/pages/domains/#{pages_domain.domain}" } + let(:route_domain_path) { "/projects/#{project.full_path.gsub('/', '%2F')}/pages/domains/#{pages_domain.domain}" } let(:route_secure_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain_secure.domain}" } let(:route_expired_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain_expired.domain}" } let(:route_vacant_domain) { "/projects/#{project.id}/pages/domains/www.vacant-domain.test" } diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb index 9128280eb5a..290eeae828e 100644 --- a/spec/services/auth/container_registry_authentication_service_spec.rb +++ b/spec/services/auth/container_registry_authentication_service_spec.rb @@ -172,7 +172,7 @@ describe Auth::ContainerRegistryAuthenticationService do end let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:*" } + { scope: "repository:#{project.full_path}:*" } end it_behaves_like 'an inaccessible' @@ -200,7 +200,7 @@ describe Auth::ContainerRegistryAuthenticationService do end let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:*" } + { scope: "repository:#{project.full_path}:*" } end it_behaves_like 'an inaccessible' @@ -239,7 +239,7 @@ describe Auth::ContainerRegistryAuthenticationService do end let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:*" } + { scope: "repository:#{project.full_path}:*" } end it_behaves_like 'an inaccessible' @@ -270,7 +270,7 @@ describe Auth::ContainerRegistryAuthenticationService do context 'disallow anyone to delete images' do let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:*" } + { scope: "repository:#{project.full_path}:*" } end it_behaves_like 'an inaccessible' @@ -311,7 +311,7 @@ describe Auth::ContainerRegistryAuthenticationService do context 'disallow anyone to delete images' do let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:*" } + { scope: "repository:#{project.full_path}:*" } end it_behaves_like 'an inaccessible' @@ -323,7 +323,7 @@ describe Auth::ContainerRegistryAuthenticationService do context 'disallow anyone to pull or push images' do let(:current_user) { create(:user, external: true) } let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:pull,push" } + { scope: "repository:#{project.full_path}:pull,push" } end it_behaves_like 'an inaccessible' @@ -333,7 +333,7 @@ describe Auth::ContainerRegistryAuthenticationService do context 'disallow anyone to delete images' do let(:current_user) { create(:user, external: true) } let(:current_params) do - { scope: "repository:#{project.path_with_namespace}:*" } + { scope: "repository:#{project.full_path}:*" } end it_behaves_like 'an inaccessible' @@ -359,7 +359,7 @@ describe Auth::ContainerRegistryAuthenticationService do context 'allow to delete images' do let(:current_params) do - { scope: "repository:#{current_project.path_with_namespace}:*" } + { scope: "repository:#{current_project.full_path}:*" } end it_behaves_like 'a deletable' do @@ -398,7 +398,7 @@ describe Auth::ContainerRegistryAuthenticationService do context 'disallow to delete images' do let(:current_params) do - { scope: "repository:#{current_project.path_with_namespace}:*" } + { scope: "repository:#{current_project.full_path}:*" } end it_behaves_like 'an inaccessible' do diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index c40cd5b7548..08b26597723 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -30,6 +30,7 @@ describe SystemHooksService do :old_path_with_namespace ) end + it do project.old_path_with_namespace = 'transfered_from_path' expect(event_data(project, :transfer)).to include( @@ -45,18 +46,21 @@ describe SystemHooksService do :owner_name, :owner_email ) end + it do expect(event_data(group, :destroy)).to include( :event_name, :name, :created_at, :updated_at, :path, :group_id, :owner_name, :owner_email ) end + it do expect(event_data(group_member, :create)).to include( :event_name, :created_at, :updated_at, :group_name, :group_path, :group_id, :user_id, :user_username, :user_name, :user_email, :group_access ) end + it do expect(event_data(group_member, :destroy)).to include( :event_name, :created_at, :updated_at, :group_name, :group_path, -- cgit v1.2.3 From 1b54a8cdda14ae5455fe1667ac15420a2f9bf051 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 5 Mar 2018 15:45:35 +0200 Subject: Replace deprecated name_with_namespace with full_name in app/views Signed-off-by: Dmitriy Zaporozhets --- app/views/admin/dashboard/index.html.haml | 2 +- app/views/admin/groups/show.html.haml | 4 ++-- app/views/admin/projects/show.html.haml | 6 +++--- app/views/admin/runners/show.html.haml | 6 +++--- app/views/admin/users/projects.html.haml | 4 ++-- app/views/groups/projects.html.haml | 2 +- app/views/invites/show.html.haml | 2 +- app/views/layouts/nav/projects_dropdown/_show.html.haml | 2 +- app/views/layouts/project.html.haml | 2 +- app/views/notify/project_was_exported_email.html.haml | 2 +- app/views/notify/project_was_moved_email.html.haml | 2 +- app/views/profiles/chat_names/_chat_name.html.haml | 2 +- app/views/projects/ci/builds/_build.html.haml | 2 +- .../generic_commit_statuses/_generic_commit_status.html.haml | 2 +- app/views/projects/issues/_merge_requests.html.haml | 2 +- .../services/mattermost_slash_commands/_detailed_help.html.haml | 4 ++-- app/views/projects/services/slack_slash_commands/_help.html.haml | 2 +- app/views/search/_filter.html.haml | 2 +- app/views/search/_results.html.haml | 2 +- app/views/search/results/_issue.html.haml | 2 +- app/views/search/results/_merge_request.html.haml | 2 +- app/views/search/results/_note.html.haml | 2 +- app/views/search/results/_snippet_title.html.haml | 2 +- app/views/sent_notifications/unsubscribe.html.haml | 2 +- app/views/shared/milestones/_issuable.html.haml | 2 +- app/views/shared/milestones/_milestone.html.haml | 2 +- app/views/shared/milestones/_top.html.haml | 2 +- app/views/shared/snippets/_snippet.html.haml | 2 +- 28 files changed, 35 insertions(+), 35 deletions(-) diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index e3711421b61..05c41082882 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -164,7 +164,7 @@ %h4 Latest projects - @projects.each do |project| %p - = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60' + = link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated-60' %span.light.pull-right #{time_ago_with_tooltip(project.created_at)} .col-md-4 diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 2545cecc721..324f3c0a22f 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -68,7 +68,7 @@ - @projects.each do |project| %li %strong - = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] + = link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project] %span.badge = storage_counter(project.statistics.storage_size) %span.pull-right.light @@ -86,7 +86,7 @@ - @group.shared_projects.sort_by(&:name).each do |project| %li %strong - = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] + = link_to project.full_name, [:admin, project.namespace.becomes(Namespace), project] %span.badge = storage_counter(project.statistics.storage_size) %span.pull-right.light diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 42f92079d85..c02ddafe108 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -1,8 +1,8 @@ - add_to_breadcrumbs "Projects", admin_projects_path -- breadcrumb_title @project.name_with_namespace -- page_title @project.name_with_namespace, "Projects" +- breadcrumb_title @project.full_name +- page_title @project.full_name, "Projects" %h3.page-title - Project: #{@project.name_with_namespace} + Project: #{@project.full_name} = link_to edit_project_path(@project), class: "btn btn-nr pull-right" do %i.fa.fa-pencil-square-o Edit diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml index 6d8fad0eb8d..185e9d7b35d 100644 --- a/app/views/admin/runners/show.html.haml +++ b/app/views/admin/runners/show.html.haml @@ -39,7 +39,7 @@ %tr.alert-info %td %strong - = project.name_with_namespace + = project.full_name %td .pull-right = link_to 'Disable', [:admin, project.namespace.becomes(Namespace), project, runner_project], method: :delete, class: 'btn btn-danger btn-xs' @@ -61,7 +61,7 @@ - @projects.each do |project| %tr %td - = project.name_with_namespace + = project.full_name %td .pull-right = form_for [:admin, project.namespace.becomes(Namespace), project, project.runner_projects.new] do |f| @@ -95,7 +95,7 @@ %td.status - if project - = project.name_with_namespace + = project.full_name %td.build-link - if project diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml index 4a440f3f6d4..96835ee9af5 100644 --- a/app/views/admin/users/projects.html.haml +++ b/app/views/admin/users/projects.html.haml @@ -29,12 +29,12 @@ .panel.panel-default .panel-heading Joined projects (#{@joined_projects.count}) %ul.well-list - - @joined_projects.sort_by(&:name_with_namespace).each do |project| + - @joined_projects.sort_by(&:full_name).each do |project| - member = project.team.find_member(@user.id) %li.project_member .list-item-name = link_to admin_project_path(project), class: dom_class(project) do - = project.name_with_namespace + = project.full_name - if member .pull-right diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index 8d2bc810a7d..ef181b425bc 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -14,7 +14,7 @@ .list-item-name %span{ class: visibility_level_color(project.visibility_level) } = visibility_level_icon(project.visibility_level) - %strong= link_to project.name_with_namespace, project + %strong= link_to project.full_name, project .pull-right - if project.archived %span.label.label-warning archived diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml index ad6213b4efd..c2bb1216c5f 100644 --- a/app/views/invites/show.html.haml +++ b/app/views/invites/show.html.haml @@ -12,7 +12,7 @@ - project = @member.source project %strong - = link_to project.name_with_namespace, project_url(project) + = link_to project.full_name, project_url(project) - when Group - group = @member.source group diff --git a/app/views/layouts/nav/projects_dropdown/_show.html.haml b/app/views/layouts/nav/projects_dropdown/_show.html.haml index 59becb043d3..5809d6f7fea 100644 --- a/app/views/layouts/nav/projects_dropdown/_show.html.haml +++ b/app/views/layouts/nav/projects_dropdown/_show.html.haml @@ -1,4 +1,4 @@ -- project_meta = { id: @project.id, name: @project.name, namespace: @project.name_with_namespace, web_url: project_path(@project), avatar_url: @project.avatar_url } if @project&.persisted? +- project_meta = { id: @project.id, name: @project.name, namespace: @project.full_name, web_url: project_path(@project), avatar_url: @project.avatar_url } if @project&.persisted? .projects-dropdown-container .project-dropdown-sidebar.qa-projects-dropdown-sidebar %ul diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 6b847fb4b7c..6b51483810e 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -1,4 +1,4 @@ -- page_title @project.name_with_namespace +- page_title @project.full_name - page_description @project.description unless page_description - header_title project_title(@project) unless header_title - nav "project" diff --git a/app/views/notify/project_was_exported_email.html.haml b/app/views/notify/project_was_exported_email.html.haml index f0ba7827cef..71c62f6be4e 100644 --- a/app/views/notify/project_was_exported_email.html.haml +++ b/app/views/notify/project_was_exported_email.html.haml @@ -3,6 +3,6 @@ %p The project export can be downloaded from: = link_to download_export_project_url(@project), rel: 'nofollow', download: '' do - = @project.name_with_namespace + " export" + = @project.full_name + " export" %p The download link will expire in 24 hours. diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml index c476a39b661..1b6b1a81665 100644 --- a/app/views/notify/project_was_moved_email.html.haml +++ b/app/views/notify/project_was_moved_email.html.haml @@ -3,7 +3,7 @@ %p The project is now located under = link_to project_url(@project) do - = @project.name_with_namespace + = @project.full_name %p To update the remote url in your local repository run (for ssh): %p{ style: "background: #f5f5f5; padding:10px; border:1px solid #ddd" } diff --git a/app/views/profiles/chat_names/_chat_name.html.haml b/app/views/profiles/chat_names/_chat_name.html.haml index fe1cf802971..c7094800fb2 100644 --- a/app/views/profiles/chat_names/_chat_name.html.haml +++ b/app/views/profiles/chat_names/_chat_name.html.haml @@ -4,7 +4,7 @@ %td %strong - if can?(current_user, :read_project, project) - = link_to project.name_with_namespace, project_path(project) + = link_to project.full_name, project_path(project) - else .light N/A %td diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 0cd2d45c74b..9126476e79e 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -63,7 +63,7 @@ - if admin %td - if job.project - = link_to job.project.name_with_namespace, admin_project_path(job.project) + = link_to job.project.full_name, admin_project_path(job.project) %td - if job.try(:runner) = runner_link(job.runner) diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 2599ce5c4b8..620fd1906ba 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -53,7 +53,7 @@ - if admin %td - if generic_commit_status.project - = link_to generic_commit_status.project.name_with_namespace, admin_project_path(generic_commit_status.project) + = link_to generic_commit_status.project.full_name, admin_project_path(generic_commit_status.project) %td - if generic_commit_status.try(:runner) = runner_link(generic_commit_status.runner) diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index 5f97d31f610..5c36d2202a6 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -18,7 +18,7 @@ - unless @issue.project.id == merge_request.target_project.id in - project = merge_request.target_project - = link_to project.name_with_namespace, project_path(project) + = link_to project.full_name, project_path(project) - if merge_request.merged? %span.merge-request-status.prepend-left-10.merged diff --git a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml index 5dbcbf7eba6..2ab0227126a 100644 --- a/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml @@ -1,4 +1,4 @@ -- run_actions_text = "Perform common operations on GitLab project: #{@project.name_with_namespace}" +- run_actions_text = "Perform common operations on GitLab project: #{@project.full_name}" %p To setup this service: %ul.list-unstyled.indent-list @@ -20,7 +20,7 @@ .form-group = label_tag :display_name, 'Display name', class: 'col-sm-2 col-xs-12 control-label' .col-sm-10.col-xs-12.input-group - = text_field_tag :display_name, "GitLab / #{@project.name_with_namespace}", class: 'form-control input-sm', readonly: 'readonly' + = text_field_tag :display_name, "GitLab / #{@project.full_name}", class: 'form-control input-sm', readonly: 'readonly' .input-group-btn = clipboard_button(target: '#display_name') diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml index c31c95608c6..d592a5e4663 100644 --- a/app/views/projects/services/slack_slash_commands/_help.html.haml +++ b/app/views/projects/services/slack_slash_commands/_help.html.haml @@ -1,4 +1,4 @@ -- pretty_name = defined?(@project) ? @project.name_with_namespace : 'namespace / path' +- pretty_name = defined?(@project) ? @project.full_name : 'namespace / path' - run_actions_text = "Perform common operations on GitLab project: #{pretty_name}" .well diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml index e43796e9654..e4902d368e7 100644 --- a/app/views/search/_filter.html.haml +++ b/app/views/search/_filter.html.haml @@ -22,7 +22,7 @@ %span.dropdown-toggle-text Project: - if @project.present? - = @project.name_with_namespace + = @project.full_name - else Any = icon("chevron-down") diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index 60ef44482f0..ab56f48ba4d 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -6,7 +6,7 @@ = search_entries_info(@search_objects, @scope, @search_term) - unless @show_snippets - if @project - in project #{link_to @project.name_with_namespace, [@project.namespace.becomes(Namespace), @project]} + in project #{link_to @project.full_name, [@project.namespace.becomes(Namespace), @project]} - elsif @group in group #{link_to @group.name, @group} diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml index b4bc8982c05..b7a27ef6be2 100644 --- a/app/views/search/results/_issue.html.haml +++ b/app/views/search/results/_issue.html.haml @@ -10,4 +10,4 @@ .description.term = search_md_sanitize(issue, :description) %span.light - #{issue.project.name_with_namespace} + #{issue.project.full_name} diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml index 1a5499e4d58..8b0fd74f680 100644 --- a/app/views/search/results/_merge_request.html.haml +++ b/app/views/search/results/_merge_request.html.haml @@ -11,4 +11,4 @@ .description.term = search_md_sanitize(merge_request, :description) %span.light - #{merge_request.project.name_with_namespace} + #{merge_request.project.full_name} diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml index a7e178dfa71..e4ab7b0541f 100644 --- a/app/views/search/results/_note.html.haml +++ b/app/views/search/results/_note.html.haml @@ -7,7 +7,7 @@ %i.fa.fa-comment = link_to_member(project, note.author, avatar: false) commented on - = link_to project.name_with_namespace, project + = link_to project.full_name, project · - if note.for_commit? diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml index 65710c09a89..d46c4d11e51 100644 --- a/app/views/search/results/_snippet_title.html.haml +++ b/app/views/search/results/_snippet_title.html.haml @@ -11,7 +11,7 @@ %small.pull-right.cgray - if snippet_title.project_id? - = link_to snippet_title.project.name_with_namespace, project_path(snippet_title.project) + = link_to snippet_title.project.full_name, project_path(snippet_title.project) .snippet-info = snippet_title.to_reference diff --git a/app/views/sent_notifications/unsubscribe.html.haml b/app/views/sent_notifications/unsubscribe.html.haml index de52fd00157..7d3e243495f 100644 --- a/app/views/sent_notifications/unsubscribe.html.haml +++ b/app/views/sent_notifications/unsubscribe.html.haml @@ -1,7 +1,7 @@ - noteable = @sent_notification.noteable - noteable_type = @sent_notification.noteable_type.titleize.downcase - noteable_text = %(#{noteable.title} (#{noteable.to_reference})) -- page_title "Unsubscribe", noteable_text, noteable_type.pluralize, @sent_notification.project.name_with_namespace +- page_title "Unsubscribe", noteable_text, noteable_type.pluralize, @sent_notification.project.full_name %h3.page-title Unsubscribe from #{noteable_type} diff --git a/app/views/shared/milestones/_issuable.html.haml b/app/views/shared/milestones/_issuable.html.haml index 129f6ab604e..eba64daaadc 100644 --- a/app/views/shared/milestones/_issuable.html.haml +++ b/app/views/shared/milestones/_issuable.html.haml @@ -12,7 +12,7 @@ - if show_project_name %strong #{project.name} · - elsif show_full_project_name - %strong #{project.name_with_namespace} · + %strong #{project.full_name} · - if issuable.is_a?(Issue) = confidential_icon(issuable) = link_to issuable.title, issuable_url_args, title: issuable.title diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index e3b2b53833e..da01fc02d07 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -27,7 +27,7 @@ - milestone.milestones.each do |milestone| = link_to milestone_path(milestone) do %span.label.label-gray - = dashboard ? milestone.project.name_with_namespace : milestone.project.name + = dashboard ? milestone.project.full_name : milestone.project.name - if @group .col-sm-6.milestone-actions - if can?(current_user, :admin_milestones, @group) diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml index fd0760d83a5..6006ab8b43f 100644 --- a/app/views/shared/milestones/_top.html.haml +++ b/app/views/shared/milestones/_top.html.haml @@ -56,7 +56,7 @@ - milestone.milestones.each do |ms| %tr %td - - project_name = group ? ms.project.name : ms.project.name_with_namespace + - project_name = group ? ms.project.name : ms.project.full_name = link_to project_name, project_milestone_path(ms.project, ms) %td = ms.issues_visible_to_user(current_user).opened.count diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index 491a8a41090..3acec88c2e3 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -31,7 +31,7 @@ %span.hidden-xs in = link_to project_path(snippet.project) do - = snippet.project.name_with_namespace + = snippet.project.full_name .pull-right.snippet-updated-at %span updated #{time_ago_with_tooltip(snippet.updated_at, placement: 'bottom')} -- cgit v1.2.3 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: Fix n+1 issue by not reloading fully loaded blobs --- app/controllers/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 -- cgit v1.2.3 From 8fe9995f11010749c5fb183f2d8129f547ae6d4a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 5 Mar 2018 16:15:26 +0200 Subject: Replace deprecated name_with_namespace with full_name in app and spec Signed-off-by: Dmitriy Zaporozhets --- app/controllers/invites_controller.rb | 2 +- app/controllers/projects/blob_controller.rb | 2 +- app/controllers/projects/tree_controller.rb | 2 +- app/controllers/projects_controller.rb | 2 +- app/helpers/issuables_helper.rb | 2 +- app/helpers/projects_helper.rb | 8 ++++---- app/helpers/search_helper.rb | 2 +- app/helpers/todos_helper.rb | 2 +- app/models/event.rb | 2 +- app/models/project_services/asana_service.rb | 2 +- app/models/project_services/campfire_service.rb | 2 +- .../project_services/chat_notification_service.rb | 2 +- app/models/project_services/hipchat_service.rb | 4 ++-- .../mattermost_slash_commands_service.rb | 2 +- app/models/project_services/pushover_service.rb | 4 ++-- spec/controllers/autocomplete_controller_spec.rb | 4 ++-- spec/features/admin/admin_groups_spec.rb | 2 +- spec/features/admin/admin_projects_spec.rb | 2 +- spec/features/admin/admin_runners_spec.rb | 8 ++++---- spec/features/dashboard/issues_spec.rb | 6 +++--- spec/features/dashboard/merge_requests_spec.rb | 4 ++-- .../dashboard/todos/todos_filtering_spec.rb | 8 ++++---- spec/features/issues/move_spec.rb | 2 +- .../members/master_manages_access_requests_spec.rb | 4 ++-- .../projects/members/user_requests_access_spec.rb | 2 +- .../settings/user_manages_project_members_spec.rb | 2 +- .../features/search/user_searches_for_code_spec.rb | 2 +- .../search/user_searches_for_issues_spec.rb | 2 +- .../user_searches_for_merge_requests_spec.rb | 2 +- .../search/user_searches_for_milestones_spec.rb | 2 +- .../search/user_searches_for_wiki_pages_spec.rb | 2 +- .../search/user_uses_search_filters_spec.rb | 6 +++--- spec/helpers/members_helper_spec.rb | 10 +++++----- spec/helpers/todos_helper_spec.rb | 4 ++-- spec/javascripts/gl_dropdown_spec.js | 6 +++--- .../projects_dropdown/store/projects_store_spec.js | 2 +- .../banzai/filter/label_reference_filter_spec.rb | 8 ++++---- spec/lib/gitlab/data_builder/build_spec.rb | 2 +- spec/mailers/notify_spec.rb | 22 +++++++++++----------- spec/models/project_services/asana_service_spec.rb | 2 +- .../project_services/hipchat_service_spec.rb | 4 ++-- .../mattermost_slash_commands_service_spec.rb | 6 +++--- 42 files changed, 83 insertions(+), 83 deletions(-) diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 52430ea771f..025d8270b7c 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -62,7 +62,7 @@ class InvitesController < ApplicationController case source when Project project = member.source - label = "project #{project.name_with_namespace}" + label = "project #{project.full_name}" path = project_path(project) when Group group = member.source diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 74c25505e36..405726c017c 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -38,7 +38,7 @@ class Projects::BlobController < Projects::ApplicationController end format.json do - page_title @blob.path, @ref, @project.name_with_namespace + page_title @blob.path, @ref, @project.full_name show_json end diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index f752a46f828..ee9b5458282 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -36,7 +36,7 @@ class Projects::TreeController < Projects::ApplicationController end format.json do - page_title @path.presence || _("Files"), @ref, @project.name_with_namespace + page_title @path.presence || _("Files"), @ref, @project.full_name # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38261 Gitlab::GitalyClient.allow_n_plus_1_calls do diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 913689a1e74..af9281e0d02 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -130,7 +130,7 @@ class ProjectsController < Projects::ApplicationController return access_denied! unless can?(current_user, :remove_project, @project) ::Projects::DestroyService.new(@project, current_user, {}).async_execute - flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.name_with_namespace } + flash[:notice] = _("Project '%{project_name}' is in the process of being deleted.") % { project_name: @project.full_name } redirect_to dashboard_projects_path, status: 302 rescue Projects::DestroyService::DestroyError => ex diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 44ecc2212f2..f6ddb6d4cfe 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -99,7 +99,7 @@ module IssuablesHelper project = Project.find_by(id: project_id) if project - project.name_with_namespace + project.full_name else default_label end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index cc1c69a1999..da9fe734f1c 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -97,13 +97,13 @@ module ProjectsHelper end def remove_project_message(project) - _("You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") % - { project_name_with_namespace: project.name_with_namespace } + _("You are going to remove %{project_full_name}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?") % + { project_full_name: project.full_name } end def transfer_project_message(project) - _("You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?") % - { project_name_with_namespace: project.name_with_namespace } + _("You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?") % + { project_full_name: project.full_name } end def remove_fork_project_message(project) diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index e6a6496871a..761c1252fc8 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -110,7 +110,7 @@ module SearchHelper category: "Projects", id: p.id, value: "#{search_result_sanitize(p.name)}", - label: "#{search_result_sanitize(p.name_with_namespace)}", + label: "#{search_result_sanitize(p.full_name)}", url: project_path(p) } end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index ddb48371c79..f7620e0b6b8 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -114,7 +114,7 @@ module TodosHelper projects = current_user.authorized_projects.sorted_by_activity.non_archived.with_route projects = projects.map do |project| - { id: project.id, text: project.name_with_namespace } + { id: project.id, text: project.full_name } end projects.unshift({ id: '', text: 'Any Project' }).to_json diff --git a/app/models/event.rb b/app/models/event.rb index 75538ba196c..be0fc7efa9a 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -158,7 +158,7 @@ class Event < ActiveRecord::Base def project_name if project - project.name_with_namespace + project.full_name else "(deleted project)" end diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb index 109258d1eb7..4f289e6e215 100644 --- a/app/models/project_services/asana_service.rb +++ b/app/models/project_services/asana_service.rb @@ -68,7 +68,7 @@ http://app.asana.com/-/account_api' end user = data[:user_name] - project_name = project.name_with_namespace + project_name = project.full_name data[:commits].each do |commit| push_msg = "#{user} pushed to branch #{branch} of #{project_name} ( #{commit[:url]} ):" diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index c3f5b310619..8d7a4fceb08 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -86,7 +86,7 @@ class CampfireService < Service after = push[:after] message = "" - message << "[#{project.name_with_namespace}] " + message << "[#{project.full_name}] " message << "#{push[:user_name]} " if Gitlab::Git.blank_ref?(before) diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb index e2ffdf72201..dab0ea1a681 100644 --- a/app/models/project_services/chat_notification_service.rb +++ b/app/models/project_services/chat_notification_service.rb @@ -129,7 +129,7 @@ class ChatNotificationService < Service end def project_name - project.name_with_namespace.gsub(/\s/, '') + project.full_name.gsub(/\s/, '') end def project_url diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index bfe7ac29c18..f31c3f02af2 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -120,7 +120,7 @@ class HipchatService < Service else message << "pushed to #{ref_type} #{ref} " - message << "of #{project.name_with_namespace.gsub!(/\s/, '')} " + message << "of #{project.full_name.gsub!(/\s/, '')} " message << "(Compare changes)" push[:commits].take(MAX_COMMITS).each do |commit| @@ -274,7 +274,7 @@ class HipchatService < Service end def project_name - project.name_with_namespace.gsub(/\s/, '') + project.full_name.gsub(/\s/, '') end def project_url diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index 4d2037286a2..227d430083d 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -37,7 +37,7 @@ class MattermostSlashCommandsService < SlashCommandsService private def command(params) - pretty_project_name = project.name_with_namespace + pretty_project_name = project.full_name params.merge( auto_complete: true, diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb index aa7bd4c3c84..e3a1ca2d45f 100644 --- a/app/models/project_services/pushover_service.rb +++ b/app/models/project_services/pushover_service.rb @@ -88,10 +88,10 @@ class PushoverService < Service user: user_key, device: device, priority: priority, - title: "#{project.name_with_namespace}", + title: "#{project.full_name}", message: message, url: data[:project][:web_url], - url_title: "See project #{project.name_with_namespace}" + url_title: "See project #{project.full_name}" } # Sound parameter MUST NOT be sent to API if not selected diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index b7257fac608..d12cd83ac4a 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -246,7 +246,7 @@ describe AutocompleteController do expect(json_response.size).to eq(1) expect(json_response.first['id']).to eq authorized_project.id - expect(json_response.first['name_with_namespace']).to eq authorized_project.name_with_namespace + expect(json_response.first['full_name']).to eq authorized_project.full_name end end end @@ -267,7 +267,7 @@ describe AutocompleteController do expect(json_response.size).to eq(1) expect(json_response.first['id']).to eq authorized_search_project.id - expect(json_response.first['name_with_namespace']).to eq authorized_search_project.name_with_namespace + expect(json_response.first['full_name']).to eq authorized_search_project.full_name end end end diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb index a5f22848031..d5e603baeae 100644 --- a/spec/features/admin/admin_groups_spec.rb +++ b/spec/features/admin/admin_groups_spec.rb @@ -173,7 +173,7 @@ feature 'Admin Groups' do visit admin_group_path(group) - expect(page).to have_content(empty_project.name_with_namespace) + expect(page).to have_content(empty_project.full_name) expect(page).to have_content('Projects shared with') end end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index d02ac6c2e2a..6d8350e99f1 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -58,7 +58,7 @@ describe "Admin::Projects" do expect(current_path).to eq admin_project_path(project) expect(page).to have_content(project.path) expect(page).to have_content(project.name) - expect(page).to have_content(project.name_with_namespace) + expect(page).to have_content(project.full_name) expect(page).to have_content(project.creator.name) end end diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb index 7eeed7da998..8de2e3d199b 100644 --- a/spec/features/admin/admin_runners_spec.rb +++ b/spec/features/admin/admin_runners_spec.rb @@ -76,8 +76,8 @@ describe "Admin Runners" do describe 'projects' do it 'contains project names' do - expect(page).to have_content(@project1.name_with_namespace) - expect(page).to have_content(@project2.name_with_namespace) + expect(page).to have_content(@project1.full_name) + expect(page).to have_content(@project2.full_name) end end @@ -89,8 +89,8 @@ describe "Admin Runners" do end it 'contains name of correct project' do - expect(page).to have_content(@project1.name_with_namespace) - expect(page).not_to have_content(@project2.name_with_namespace) + expect(page).to have_content(@project1.full_name) + expect(page).not_to have_content(@project2.full_name) end end diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb index a7f0fa3d9e0..8d1d5a51750 100644 --- a/spec/features/dashboard/issues_spec.rb +++ b/spec/features/dashboard/issues_spec.rb @@ -74,8 +74,8 @@ RSpec.describe 'Dashboard Issues' do find('.new-project-item-select-button').click page.within('.select2-results') do - expect(page).to have_content(project.name_with_namespace) - expect(page).not_to have_content(project_with_issues_disabled.name_with_namespace) + expect(page).to have_content(project.full_name) + expect(page).not_to have_content(project_with_issues_disabled.full_name) end end @@ -85,7 +85,7 @@ RSpec.describe 'Dashboard Issues' do wait_for_requests project_path = "/#{project.full_path}" - project_json = { name: project.name_with_namespace, url: project_path }.to_json + project_json = { name: project.full_name, url: project_path }.to_json # simulate selection, and prevent overlap by dropdown menu first('.project-item-select', visible: false) diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index 744041ac425..c8f3a8449f5 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -28,8 +28,8 @@ feature 'Dashboard Merge Requests' do find('.new-project-item-select-button').click page.within('.select2-results') do - expect(page).to have_content(project.name_with_namespace) - expect(page).not_to have_content(project_with_disabled_merge_requests.name_with_namespace) + expect(page).to have_content(project.full_name) + expect(page).not_to have_content(project_with_disabled_merge_requests.full_name) end end end diff --git a/spec/features/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb index 2fc34301d51..7b359b0c651 100644 --- a/spec/features/dashboard/todos/todos_filtering_spec.rb +++ b/spec/features/dashboard/todos/todos_filtering_spec.rb @@ -24,14 +24,14 @@ feature 'Dashboard > User filters todos', :js do it 'filters by project' do click_button 'Project' within '.dropdown-menu-project' do - fill_in 'Search projects', with: project_1.name_with_namespace - click_link project_1.name_with_namespace + fill_in 'Search projects', with: project_1.full_name + click_link project_1.full_name end wait_for_requests - expect(page).to have_content project_1.name_with_namespace - expect(page).not_to have_content project_2.name_with_namespace + expect(page).to have_content project_1.full_name + expect(page).not_to have_content project_2.full_name end context 'Author filter' do diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb index 076a02150a4..3c01ff345fc 100644 --- a/spec/features/issues/move_spec.rb +++ b/spec/features/issues/move_spec.rb @@ -73,7 +73,7 @@ feature 'issue move to another project' do wait_for_requests page.within '.js-sidebar-move-issue-block' do - expect(page).to have_content new_project.name_with_namespace + expect(page).to have_content new_project.full_name end end end diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb index d575596937d..1f4eec0a317 100644 --- a/spec/features/projects/members/master_manages_access_requests_spec.rb +++ b/spec/features/projects/members/master_manages_access_requests_spec.rb @@ -25,7 +25,7 @@ feature 'Projects > Members > Master manages access requests' do perform_enqueued_jobs { click_on 'Grant access' } expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.name_with_namespace} project was granted" + expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.full_name} project was granted" end scenario 'master can deny access' do @@ -36,7 +36,7 @@ feature 'Projects > Members > Master manages access requests' do perform_enqueued_jobs { click_on 'Deny access' } expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.name_with_namespace} project was denied" + expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.full_name} project was denied" end def expect_visible_access_request(project, user) diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb index 4eb36156812..672d5daa3d8 100644 --- a/spec/features/projects/members/user_requests_access_spec.rb +++ b/spec/features/projects/members/user_requests_access_spec.rb @@ -21,7 +21,7 @@ feature 'Projects > Members > User requests access', :js do perform_enqueued_jobs { click_link 'Request Access' } expect(ActionMailer::Base.deliveries.last.to).to eq [master.notification_email] - expect(ActionMailer::Base.deliveries.last.subject).to eq "Request to join the #{project.name_with_namespace} project" + expect(ActionMailer::Base.deliveries.last.subject).to eq "Request to join the #{project.full_name} project" expect(project.requesters.exists?(user_id: user)).to be_truthy expect(page).to have_content 'Your request for access has been queued for review.' diff --git a/spec/features/projects/settings/user_manages_project_members_spec.rb b/spec/features/projects/settings/user_manages_project_members_spec.rb index 2709047b8de..0a4f57bcd21 100644 --- a/spec/features/projects/settings/user_manages_project_members_spec.rb +++ b/spec/features/projects/settings/user_manages_project_members_spec.rb @@ -39,7 +39,7 @@ describe 'User manages project members' do click_link('Import') end - select(project2.name_with_namespace, from: 'source_project_id') + select(project2.full_name, from: 'source_project_id') click_button('Import') project_member = project.project_members.find_by(user_id: user_mike.id) diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb index 77212fb105b..9e089c5a6cb 100644 --- a/spec/features/search/user_searches_for_code_spec.rb +++ b/spec/features/search/user_searches_for_code_spec.rb @@ -35,7 +35,7 @@ describe 'User searches for code' do find('.js-search-project-dropdown').click page.within('.project-filter') do - click_link(project.name_with_namespace) + click_link(project.full_name) end fill_in('dashboard_search', with: 'rspec') diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb index ef9553f2a91..d6120ff8517 100644 --- a/spec/features/search/user_searches_for_issues_spec.rb +++ b/spec/features/search/user_searches_for_issues_spec.rb @@ -34,7 +34,7 @@ describe 'User searches for issues', :js do find('.js-search-project-dropdown').click page.within('.project-filter') do - click_link(project.name_with_namespace) + click_link(project.full_name) end fill_in('dashboard_search', with: issue1.title) diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb index 3b6739aecbd..68e2f7a857d 100644 --- a/spec/features/search/user_searches_for_merge_requests_spec.rb +++ b/spec/features/search/user_searches_for_merge_requests_spec.rb @@ -33,7 +33,7 @@ describe 'User searches for merge requests', :js do find('.js-search-project-dropdown').click page.within('.project-filter') do - click_link(project.name_with_namespace) + click_link(project.full_name) end fill_in('dashboard_search', with: merge_request1.title) diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb index 6e197aee498..fc6cd81eb68 100644 --- a/spec/features/search/user_searches_for_milestones_spec.rb +++ b/spec/features/search/user_searches_for_milestones_spec.rb @@ -33,7 +33,7 @@ describe 'User searches for milestones', :js do find('.js-search-project-dropdown').click page.within('.project-filter') do - click_link(project.name_with_namespace) + click_link(project.full_name) end fill_in('dashboard_search', with: milestone1.title) diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb index 00af625dc86..7934779058f 100644 --- a/spec/features/search/user_searches_for_wiki_pages_spec.rb +++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb @@ -18,7 +18,7 @@ describe 'User searches for wiki pages', :js do find('.js-search-project-dropdown').click page.within('.project-filter') do - click_link(project.name_with_namespace) + click_link(project.full_name) end fill_in('dashboard_search', with: 'content') diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb index aa883c964d2..66afe163447 100644 --- a/spec/features/search/user_uses_search_filters_spec.rb +++ b/spec/features/search/user_uses_search_filters_spec.rb @@ -31,7 +31,7 @@ describe 'User uses search filters', :js do wait_for_requests - expect(page).to have_link(group_project.name_with_namespace) + expect(page).to have_link(group_project.full_name) end end end @@ -43,10 +43,10 @@ describe 'User uses search filters', :js do wait_for_requests - click_link(project.name_with_namespace) + click_link(project.full_name) end - expect(find('.js-search-project-dropdown')).to have_content(project.name_with_namespace) + expect(find('.js-search-project-dropdown')).to have_content(project.full_name) end end end diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb index 45ffbeb27a4..4590904c93d 100644 --- a/spec/helpers/members_helper_spec.rb +++ b/spec/helpers/members_helper_spec.rb @@ -12,10 +12,10 @@ describe MembersHelper do let(:group_member_invite) { build(:group_member, group: group).tap { |m| m.generate_invite_token! } } let(:group_member_request) { group.request_access(requester) } - it { expect(remove_member_message(project_member)).to eq "Are you sure you want to remove #{project_member.user.name} from the #{project.name_with_namespace} project?" } - it { expect(remove_member_message(project_member_invite)).to eq "Are you sure you want to revoke the invitation for #{project_member_invite.invite_email} to join the #{project.name_with_namespace} project?" } - it { expect(remove_member_message(project_member_request)).to eq "Are you sure you want to deny #{requester.name}'s request to join the #{project.name_with_namespace} project?" } - it { expect(remove_member_message(project_member_request, user: requester)).to eq "Are you sure you want to withdraw your access request for the #{project.name_with_namespace} project?" } + it { expect(remove_member_message(project_member)).to eq "Are you sure you want to remove #{project_member.user.name} from the #{project.full_name} project?" } + it { expect(remove_member_message(project_member_invite)).to eq "Are you sure you want to revoke the invitation for #{project_member_invite.invite_email} to join the #{project.full_name} project?" } + it { expect(remove_member_message(project_member_request)).to eq "Are you sure you want to deny #{requester.name}'s request to join the #{project.full_name} project?" } + it { expect(remove_member_message(project_member_request, user: requester)).to eq "Are you sure you want to withdraw your access request for the #{project.full_name} project?" } it { expect(remove_member_message(group_member)).to eq "Are you sure you want to remove #{group_member.user.name} from the #{group.name} group?" } it { expect(remove_member_message(group_member_invite)).to eq "Are you sure you want to revoke the invitation for #{group_member_invite.invite_email} to join the #{group.name} group?" } it { expect(remove_member_message(group_member_request)).to eq "Are you sure you want to deny #{requester.name}'s request to join the #{group.name} group?" } @@ -42,7 +42,7 @@ describe MembersHelper do let(:group) { build_stubbed(:group) } let(:user) { build_stubbed(:user) } - it { expect(leave_confirmation_message(project)).to eq "Are you sure you want to leave the \"#{project.name_with_namespace}\" project?" } + it { expect(leave_confirmation_message(project)).to eq "Are you sure you want to leave the \"#{project.full_name}\" project?" } it { expect(leave_confirmation_message(group)).to eq "Are you sure you want to leave the \"#{group.name}\" group?" } end end diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb index f55163c26e9..63806ef91f3 100644 --- a/spec/helpers/todos_helper_spec.rb +++ b/spec/helpers/todos_helper_spec.rb @@ -26,8 +26,8 @@ describe TodosHelper do expected_results = [ { 'id' => '', 'text' => 'Any Project' }, - { 'id' => projects.second.id, 'text' => projects.second.name_with_namespace }, - { 'id' => projects.first.id, 'text' => projects.first.name_with_namespace } + { 'id' => projects.second.id, 'text' => projects.second.full_name }, + { 'id' => projects.first.id, 'text' => projects.first.full_name } ] expect(JSON.parse(helper.todo_projects_options)).to match_array(expected_results) diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index 67b854f61c0..20728e3bf76 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -50,7 +50,7 @@ describe('glDropdown', function describeDropdown() { search: { fields: ['name'] }, - text: project => (project.name_with_namespace || project.name), + text: project => (project.full_name || project.name), id: project => project.id, }, extraOpts); this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown(options); @@ -76,7 +76,7 @@ describe('glDropdown', function describeDropdown() { }); it('escapes HTML as text', () => { - this.projectsData[0].name_with_namespace = ''; + this.projectsData[0].full_name = ''; initDropDown.call(this, false); @@ -88,7 +88,7 @@ describe('glDropdown', function describeDropdown() { }); it('should output HTML when highlighting', () => { - this.projectsData[0].name_with_namespace = 'testing'; + this.projectsData[0].full_name = 'testing'; $('.dropdown-input .dropdown-input-field').val('test'); initDropDown.call(this, false, true, { diff --git a/spec/javascripts/projects_dropdown/store/projects_store_spec.js b/spec/javascripts/projects_dropdown/store/projects_store_spec.js index e57399d37cd..ff1cb7abf20 100644 --- a/spec/javascripts/projects_dropdown/store/projects_store_spec.js +++ b/spec/javascripts/projects_dropdown/store/projects_store_spec.js @@ -24,7 +24,7 @@ describe('ProjectsStore', () => { const processedProjects = store.getSearchedProjects(); expect(processedProjects.length).toBe(1); expect(processedProjects[0].id).toBe(mockRawProject.id); - expect(processedProjects[0].namespace).toBe(mockRawProject.name_with_namespace); + expect(processedProjects[0].namespace).toBe(mockRawProject.full_name); expect(processedProjects[0].webUrl).toBe(mockRawProject.web_url); expect(processedProjects[0].avatarUrl).toBe(mockRawProject.avatar_url); }); diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 862b1fe3fd3..0c524a1551f 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -381,11 +381,11 @@ describe Banzai::Filter::LabelReferenceFilter do end it 'has valid link text' do - expect(result.css('a').first.text).to eq "#{label.name} in #{project2.name_with_namespace}" + expect(result.css('a').first.text).to eq "#{label.name} in #{project2.full_name}" end it 'has valid text' do - expect(result.text).to eq "See #{label.name} in #{project2.name_with_namespace}" + expect(result.text).to eq "See #{label.name} in #{project2.full_name}" end it 'ignores invalid IDs on the referenced label' do @@ -481,12 +481,12 @@ describe Banzai::Filter::LabelReferenceFilter do it 'has valid link text' do expect(result.css('a').first.text) - .to eq "#{group_label.name} in #{another_project.name_with_namespace}" + .to eq "#{group_label.name} in #{another_project.full_name}" end it 'has valid text' do expect(result.text) - .to eq "See #{group_label.name} in #{another_project.name_with_namespace}" + .to eq "See #{group_label.name} in #{another_project.full_name}" end it 'ignores invalid IDs on the referenced label' do diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb index 91c43f2bdc0..ee91decafad 100644 --- a/spec/lib/gitlab/data_builder/build_spec.rb +++ b/spec/lib/gitlab/data_builder/build_spec.rb @@ -16,7 +16,7 @@ describe Gitlab::DataBuilder::Build do it { expect(data[:build_status]).to eq(build.status) } it { expect(data[:build_allow_failure]).to eq(false) } it { expect(data[:project_id]).to eq(build.project.id) } - it { expect(data[:project_name]).to eq(build.project.name_with_namespace) } + it { expect(data[:project_name]).to eq(build.project.full_name) } context 'commit author_url' do context 'when no commit present' do diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index bcbb9287199..83c33797bbc 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -457,7 +457,7 @@ describe Notify do it 'has the correct subject and body' do is_expected.to have_subject("#{project.name} | Project was moved") - is_expected.to have_html_escaped_body_text project.name_with_namespace + is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text(project.ssh_url_to_repo) end end @@ -483,8 +483,8 @@ describe Notify do to_emails = subject.header[:to].addrs.map(&:address) expect(to_emails).to eq([recipient.notification_email]) - is_expected.to have_subject "Request to join the #{project.name_with_namespace} project" - is_expected.to have_html_escaped_body_text project.name_with_namespace + is_expected.to have_subject "Request to join the #{project.full_name} project" + is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text project_project_members_url(project) is_expected.to have_body_text project_member.human_access end @@ -503,8 +503,8 @@ describe Notify do it_behaves_like "a user cannot unsubscribe through footer link" it 'contains all the useful information' do - is_expected.to have_subject "Access to the #{project.name_with_namespace} project was denied" - is_expected.to have_html_escaped_body_text project.name_with_namespace + is_expected.to have_subject "Access to the #{project.full_name} project was denied" + is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text project.web_url end end @@ -520,8 +520,8 @@ describe Notify do it_behaves_like "a user cannot unsubscribe through footer link" it 'contains all the useful information' do - is_expected.to have_subject "Access to the #{project.name_with_namespace} project was granted" - is_expected.to have_html_escaped_body_text project.name_with_namespace + is_expected.to have_subject "Access to the #{project.full_name} project was granted" + is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.human_access end @@ -550,8 +550,8 @@ describe Notify do it_behaves_like "a user cannot unsubscribe through footer link" it 'contains all the useful information' do - is_expected.to have_subject "Invitation to join the #{project.name_with_namespace} project" - is_expected.to have_html_escaped_body_text project.name_with_namespace + is_expected.to have_subject "Invitation to join the #{project.full_name} project" + is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.human_access is_expected.to have_body_text project_member.invite_token @@ -575,7 +575,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject 'Invitation accepted' - is_expected.to have_html_escaped_body_text project.name_with_namespace + is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.invite_email is_expected.to have_html_escaped_body_text invited_user.name @@ -598,7 +598,7 @@ describe Notify do it 'contains all the useful information' do is_expected.to have_subject 'Invitation declined' - is_expected.to have_html_escaped_body_text project.name_with_namespace + is_expected.to have_html_escaped_body_text project.full_name is_expected.to have_body_text project.web_url is_expected.to have_body_text project_member.invite_email end diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb index 04440d890aa..e66109fd98f 100644 --- a/spec/models/project_services/asana_service_spec.rb +++ b/spec/models/project_services/asana_service_spec.rb @@ -47,7 +47,7 @@ describe AsanaService do it 'calls Asana service to create a story' do data = create_data_for_commits('Message from commit. related to #123456') - expected_message = "#{data[:user_name]} pushed to branch #{data[:ref]} of #{project.name_with_namespace} ( #{data[:commits][0][:url]} ): #{data[:commits][0][:message]}" + expected_message = "#{data[:user_name]} pushed to branch #{data[:ref]} of #{project.full_name} ( #{data[:commits][0][:url]} ): #{data[:commits][0][:message]}" d1 = double('Asana::Task') expect(d1).to receive(:add_comment).with(text: expected_message) diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 23db29cb541..3e2a166cdd6 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -29,7 +29,7 @@ describe HipchatService do let(:user) { create(:user) } let(:project) { create(:project, :repository) } let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' } - let(:project_name) { project.name_with_namespace.gsub(/\s/, '') } + let(:project_name) { project.full_name.gsub(/\s/, '') } let(:token) { 'verySecret' } let(:server_url) { 'https://hipchat.example.com'} let(:push_sample_data) do @@ -303,7 +303,7 @@ describe HipchatService do message = hipchat.__send__(:create_pipeline_message, data) project_url = project.web_url - project_name = project.name_with_namespace.gsub(/\s/, '') + project_name = project.full_name.gsub(/\s/, '') pipeline_attributes = data[:object_attributes] ref = pipeline_attributes[:ref] ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch' diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb index 522cf15f3ba..a5bdf9a9337 100644 --- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb +++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb @@ -31,10 +31,10 @@ describe MattermostSlashCommandsService do url: 'http://trigger.url', icon_url: 'http://icon.url/icon.png', auto_complete: true, - auto_complete_desc: "Perform common operations on: #{project.name_with_namespace}", + auto_complete_desc: "Perform common operations on: #{project.full_name}", auto_complete_hint: '[help]', - description: "Perform common operations on: #{project.name_with_namespace}", - display_name: "GitLab / #{project.name_with_namespace}", + description: "Perform common operations on: #{project.full_name}", + display_name: "GitLab / #{project.full_name}", method: 'P', username: 'GitLab' }.to_json) -- cgit v1.2.3 From 641b058b9e8c0faeb6d0de9e0c97b97e7a423c97 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 5 Mar 2018 14:15:58 +0000 Subject: 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 -- cgit v1.2.3 From 0b7d10851456018328da137beeca931767b4fd0a Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Mon, 5 Mar 2018 08:25:27 +0100 Subject: 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 ---- ...20180304204842_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 0cc68aff494..12bfcc177c7 100644 Binary files a/spec/features/projects/import_export/test_project_export.tar.gz and b/spec/features/projects/import_export/test_project_export.tar.gz differ diff --git a/vendor/project_templates/express.tar.gz b/vendor/project_templates/express.tar.gz index dcf5e4a0416..06093deb459 100644 Binary files a/vendor/project_templates/express.tar.gz and b/vendor/project_templates/express.tar.gz differ diff --git a/vendor/project_templates/rails.tar.gz b/vendor/project_templates/rails.tar.gz index d4856090ed9..85cc1b6bb78 100644 Binary files a/vendor/project_templates/rails.tar.gz and b/vendor/project_templates/rails.tar.gz differ diff --git a/vendor/project_templates/spring.tar.gz b/vendor/project_templates/spring.tar.gz index 6ee7e76f676..e98d3ce7b8f 100644 Binary files a/vendor/project_templates/spring.tar.gz and b/vendor/project_templates/spring.tar.gz differ -- cgit v1.2.3 From 2d03e6706e0ac5fe487225909f9138fc9f674d0b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 5 Mar 2018 14:24:23 +0000 Subject: Merge branch 'master-i18n' into 'master' New Crowdin translations See merge request gitlab-org/gitlab-ee!4823 --- locale/bg/gitlab.po | 511 +++++- locale/de/gitlab.po | 509 +++++- locale/eo/gitlab.po | 511 +++++- locale/es/gitlab.po | 1041 ++++++++---- locale/fil_PH/gitlab.po | 4351 +++++++++++++++++++++++++++++++++++++++++++++++ locale/fr/gitlab.po | 1339 ++++++++++----- locale/id_ID/gitlab.po | 4325 ++++++++++++++++++++++++++++++++++++++++++++++ locale/it/gitlab.po | 507 +++++- locale/ja/gitlab.po | 506 +++++- locale/ko/gitlab.po | 508 +++++- locale/nl_NL/gitlab.po | 501 +++++- locale/pl_PL/gitlab.po | 530 +++++- locale/pt_BR/gitlab.po | 515 +++++- locale/ru/gitlab.po | 656 +++++-- locale/tr_TR/gitlab.po | 4351 +++++++++++++++++++++++++++++++++++++++++++++++ locale/uk/gitlab.po | 974 ++++++++--- locale/zh_CN/gitlab.po | 514 +++++- locale/zh_HK/gitlab.po | 508 +++++- locale/zh_TW/gitlab.po | 510 +++++- 19 files changed, 21535 insertions(+), 1632 deletions(-) create mode 100644 locale/fil_PH/gitlab.po create mode 100644 locale/id_ID/gitlab.po create mode 100644 locale/tr_TR/gitlab.po diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index badd665c08c..c996b30d7fa 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 03:58-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:19-0500\n" "Last-Translator: gitlab \n" "Language-Team: Bulgarian\n" "Language: bg_BG\n" @@ -49,6 +49,9 @@ msgid_plural "%s additional commits have been omitted to prevent performance iss msgstr[0] "%s подаване беше пропуснато, за да не се натоварва системата." msgstr[1] "%s подавания бяха пропуснати, за да не се натоварва системата." +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -57,6 +60,9 @@ msgid_plural "%{count} participants" msgstr[0] "" msgstr[1] "" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "" @@ -66,6 +72,9 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "" +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "" @@ -130,9 +139,15 @@ msgstr "Добавяне на ръководство за сътрудничес msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "" +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "Добавяне на лиценз" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "Добавяне на нова папка" @@ -151,12 +166,45 @@ msgstr "" msgid "AdminArea|Stopping jobs failed" msgstr "" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" msgid "AdminHealthPageLink|health page" msgstr "" +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + msgid "Advanced" msgstr "" @@ -181,6 +229,12 @@ msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" @@ -190,12 +244,36 @@ msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while fetching the pipeline." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + msgid "An error occurred while loading filenames" msgstr "" +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + msgid "An error occurred while rendering KaTeX" msgstr "" @@ -208,6 +286,12 @@ msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + msgid "An error occurred while validating username" msgstr "" @@ -232,15 +316,15 @@ msgstr "Архивиран проект! Хранилището е само за msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Наистина ли искате да изтриете този план за схема?" -msgid "Are you sure you want to discard your changes?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "" msgid "Are you sure you want to reset the health check token?" msgstr "" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "" @@ -280,6 +364,9 @@ msgstr "" msgid "Authors: %{authors}" msgstr "" +msgid "Auto DevOps enabled" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -304,7 +391,13 @@ msgstr "" msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" msgstr "" msgid "Available" @@ -316,6 +409,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Billing" msgstr "" @@ -370,13 +466,10 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "Клон" -msgstr[1] "Клони" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "" +msgstr[1] "" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "Клонът %{branch_name} беше създаден. За да настроите автоматичното внедряване, изберете Yaml шаблон за GitLab CI и подайте промените си. %{link_to_autodeploy_doc}" @@ -669,6 +762,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + msgid "Click to expand text" msgstr "" @@ -723,6 +819,9 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -771,6 +870,9 @@ msgstr "" msgid "ClusterIntegration|Ingress" msgstr "" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "" @@ -822,9 +924,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" msgstr "" @@ -960,6 +1059,12 @@ msgstr "" msgid "Collapse" msgstr "" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "" @@ -968,6 +1073,11 @@ msgid_plural "Commits" msgstr[0] "Подаване" msgstr[1] "Подавания" +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" +msgstr[1] "" + msgid "Commit Message" msgstr "" @@ -980,6 +1090,9 @@ msgstr "Съобщение за подаването" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Подаване" @@ -1121,6 +1234,9 @@ msgstr "Копиране на адреса в буфера за обмен" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Копиране на идентификатора на подаването в буфера за обмен" @@ -1133,9 +1249,18 @@ msgstr "" msgid "Create New Directory" msgstr "Създаване на нова папка" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "Създайте си личен жетон за достъп в акаунта си, за да можете да изтегляте и изпращате промени чрез %{protocol}." +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "Създаване на папка" @@ -1154,6 +1279,9 @@ msgstr "" msgid "Create merge request" msgstr "Създаване на заявка за сливане" +msgid "Create merge request and branch" +msgstr "" + msgid "Create new branch" msgstr "" @@ -1178,6 +1306,12 @@ msgstr "Етикет" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "си създадете личен жетон за достъп" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + msgid "Creating epic" msgstr "" @@ -1232,6 +1366,9 @@ msgstr "" msgid "December" msgstr "" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "Задайте потребителски шаблон, използвайки синтаксиса на „Cron“" @@ -1264,7 +1401,7 @@ msgstr "Име на папката" msgid "Disable" msgstr "" -msgid "Discard changes" +msgid "Discard draft" msgstr "" msgid "Discover GitLab Geo." @@ -1324,6 +1461,9 @@ msgstr "" msgid "Enable" msgstr "" +msgid "Enable Auto DevOps" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1378,9 +1518,18 @@ msgstr "" msgid "Epics" msgstr "" +msgid "Epics Roadmap" +msgstr "" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "" @@ -1447,12 +1596,36 @@ msgstr "" msgid "Explore public groups" msgstr "" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + msgid "Failed to change the owner" msgstr "Собственикът не може да бъде променен" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "Планът за схема не може да бъде премахнат" +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "" @@ -1468,6 +1641,9 @@ msgstr "" msgid "Files" msgstr "Файлове" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "Филтриране по съобщение" @@ -1503,6 +1679,9 @@ msgstr "От създаването на проблема до внедрява msgid "From merge request merge until deploy to production" msgstr "От прилагането на заявката за сливане до внедряването в крайната версия" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "" @@ -1650,6 +1829,24 @@ msgstr "" msgid "Got it!" msgstr "" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1748,6 +1945,9 @@ msgstr "" msgid "Housekeeping successfully started" msgstr "Освежаването започна успешно" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "Внасяне на хранилище" @@ -1760,6 +1960,9 @@ msgstr "" msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "" +msgid "Install Runner on Kubernetes" +msgstr "" + msgid "Install a Runner compatible with GitLab CI" msgstr "" @@ -1810,6 +2013,9 @@ msgstr "" msgid "January" msgstr "" +msgid "Jobs" +msgstr "" + msgid "Jul" msgstr "" @@ -1840,6 +2046,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1887,6 +2096,12 @@ msgstr "" msgid "Learn more" msgstr "" +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + msgid "Learn more in the" msgstr "Научете повече в" @@ -1905,6 +2120,9 @@ msgstr "Напускане на проекта" msgid "License" msgstr "" +msgid "List" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -1914,6 +2132,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" @@ -1923,6 +2144,9 @@ msgstr "" msgid "Locked Files" msgstr "" +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + msgid "Login" msgstr "" @@ -1953,9 +2177,6 @@ msgstr "Медиана" msgid "Members" msgstr "" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "" @@ -1968,6 +2189,9 @@ msgstr "" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" @@ -1992,9 +2216,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "добавите SSH ключ" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "" @@ -2090,9 +2323,6 @@ msgstr "Няма хранилище" msgid "No schedules" msgstr "Няма планове" -msgid "No time spent" -msgstr "" - msgid "None" msgstr "" @@ -2108,6 +2338,9 @@ msgstr "" msgid "Not enough data" msgstr "Няма достатъчно данни" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "Събития за известяване" @@ -2210,6 +2443,9 @@ msgstr "" msgid "Options" msgstr "Опции" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + msgid "Overview" msgstr "" @@ -2315,6 +2551,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "всички" @@ -2348,6 +2602,9 @@ msgstr "" msgid "Private - The group and its projects can only be viewed by members." msgstr "" +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "" @@ -2510,12 +2767,30 @@ msgstr "" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "" +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "" msgid "PrometheusService|Finding and configuring metrics..." msgstr "" +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + msgid "PrometheusService|Metrics" msgstr "" @@ -2537,9 +2812,18 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + msgid "PrometheusService|Time-series monitoring service" msgstr "" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + msgid "PrometheusService|View environments" msgstr "" @@ -2558,6 +2842,12 @@ msgstr "" msgid "Push events" msgstr "" +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + msgid "PushRule|Committer restriction" msgstr "" @@ -2621,6 +2911,9 @@ msgstr "" msgid "Repository" msgstr "" +msgid "Repository has no locks." +msgstr "" + msgid "Request Access" msgstr "Заявка за достъп" @@ -2633,6 +2926,9 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" @@ -2644,6 +2940,9 @@ msgstr "Отмяна на това подаване" msgid "Revert this merge request" msgstr "Отмяна на тази заявка за сливане" +msgid "Roadmap" +msgstr "" + msgid "SSH Keys" msgstr "" @@ -2689,12 +2988,18 @@ msgstr "" msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "Изберете формата на архива" msgid "Select a timezone" msgstr "Изберете часова зона" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2707,6 +3012,9 @@ msgstr "Изберете целеви клон" msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "" @@ -2719,6 +3027,9 @@ msgstr "" msgid "Service Templates" msgstr "" +msgid "Service URL" +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Задайте парола на акаунта си, за да можете да изтегляте и изпращате промени чрез %{protocol}." @@ -2728,15 +3039,15 @@ msgstr "" msgid "Set up Koding" msgstr "Настройка на „Koding“" -msgid "Set up auto deploy" -msgstr "Настройка на авт. внедряване" - msgid "SetPasswordToCloneLink|set a password" msgstr "зададете парола" msgid "Settings" msgstr "" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" @@ -2746,6 +3057,9 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2787,12 +3101,24 @@ msgstr "" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" @@ -2898,6 +3224,9 @@ msgstr "" msgid "Source" msgstr "" +msgid "Source (branch or tag)" +msgstr "" + msgid "Source code" msgstr "Изходен код" @@ -2937,10 +3266,10 @@ msgstr "Преминаване към клон/етикет" msgid "System Hooks" msgstr "" -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "Етикет" -msgstr[1] "Етикети" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "" +msgstr[1] "" msgid "Tags" msgstr "Етикети" @@ -3071,9 +3400,15 @@ msgstr "Всеки може да има достъп до проекта, без msgid "The repository for this project does not exist." msgstr "Хранилището за този проект не съществува." +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "Етапът на преглед и одобрение показва времето от създаването на заявката за сливане до прилагането ѝ. Данните ще бъдат добавени автоматично след като приложите първата си заявка за сливане." +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "Етапът на подготовка за издаване показва времето между прилагането на заявката за сливане и внедряването на кода в средата на работещата крайна версия. Данните ще бъдат добавени автоматично след като направите първото си внедряване в крайната версия." @@ -3167,6 +3502,9 @@ msgstr "Това означава, че няма да можете да изпр msgid "This merge request is locked." msgstr "" +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3336,9 +3674,15 @@ msgstr[1] "мин" msgid "Time|s" msgstr "сек" +msgid "Tip:" +msgstr "" + msgid "Title" msgstr "" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + msgid "Todo" msgstr "" @@ -3354,19 +3698,16 @@ msgstr "" msgid "Total Time" msgstr "Общо време" -msgid "Total issue time spent" -msgstr "" - msgid "Total test time for all commits/merges" msgstr "Общо време за тестване на всички подавания/сливания" -msgid "Track activity with Contribution Analytics." +msgid "Total: %{total}" msgstr "" -msgid "Track groups of issues that share a theme, across projects and milestones" +msgid "Track activity with Contribution Analytics." msgstr "" -msgid "Total: %{total}" +msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" msgid "Track time with quick actions" @@ -3378,9 +3719,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3396,6 +3734,9 @@ msgstr "" msgid "Unlocked" msgstr "" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "Без звезда" @@ -3441,6 +3782,9 @@ msgstr "Използване на глобалната Ви настройка msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" +msgid "View epics list" +msgstr "" + msgid "View file @ " msgstr "" @@ -3477,6 +3821,9 @@ msgstr "Няма достатъчно данни за този етап." msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "" +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "" @@ -3597,6 +3944,9 @@ msgstr "" msgid "Withdraw Access Request" msgstr "Оттегляне на заявката за достъп" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "На път сте да премахнете „%{group_name}“. Ако я премахнете, групата НЕ може да бъде възстановена! НАИСТИНА ли искате това?" @@ -3609,10 +3959,13 @@ msgstr "На път сте да премахнете връзката на ра msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "На път сте да прехвърлите „%{project_name_with_namespace}“ към друг собственик. НАИСТИНА ли искате това?" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3630,12 +3983,24 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "Не можете да създавате повече проекти" +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "Трябва да се впишете, за да отбележите проект със звезда" +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "Нуждаете се от разрешение." @@ -3669,6 +4034,9 @@ msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -3696,7 +4064,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3708,7 +4076,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3723,6 +4091,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3735,6 +4112,12 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + msgid "commit" msgstr "" @@ -3752,11 +4135,41 @@ msgstr[1] "дни" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" msgstr[1] "" +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3790,6 +4203,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3823,6 +4239,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3877,6 +4296,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3924,3 +4346,6 @@ msgstr "" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po index 4a0ca1e7efb..76982ff61b1 100644 --- a/locale/de/gitlab.po +++ b/locale/de/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 04:00-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:21-0500\n" "Last-Translator: gitlab \n" "Language-Team: German\n" "Language: de_DE\n" @@ -49,6 +49,9 @@ msgid_plural "%s additional commits have been omitted to prevent performance iss msgstr[0] "%s zusätzlicher Commit wurde ausgelassen um Leistungsprobleme zu verhindern." msgstr[1] "%s zusätzliche Commits wurden ausgelassen um Leistungsprobleme zu verhindern." +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -57,6 +60,9 @@ msgid_plural "%{count} participants" msgstr[0] "" msgstr[1] "" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "" @@ -66,6 +72,9 @@ msgstr "%{number_of_failures} von %{maximum_failures} Fehlschlägen. GitLab wird msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} von %{maximum_failures} Fehlschlägen. GitLab wird es nicht weiter versuchen. Setze die Speicherinformation nach Behebung des Problems zurück." +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "%{storage_name}: fehlgeschlagener Speicherzugriff auf Host:" @@ -130,9 +139,15 @@ msgstr "Mitarbeitsanleitung hinzufügen" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "" +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "Lizenz hinzufügen" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "Erstelle eine neues Verzeichnis" @@ -151,12 +166,45 @@ msgstr "" msgid "AdminArea|Stopping jobs failed" msgstr "" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" msgid "AdminHealthPageLink|health page" msgstr "" +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + msgid "Advanced" msgstr "" @@ -181,6 +229,12 @@ msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" @@ -190,12 +244,36 @@ msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while fetching the pipeline." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + msgid "An error occurred while loading filenames" msgstr "" +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + msgid "An error occurred while rendering KaTeX" msgstr "" @@ -208,6 +286,12 @@ msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + msgid "An error occurred while validating username" msgstr "" @@ -232,15 +316,15 @@ msgstr "Archiviertes Projekt! Repository ist nicht änderbar." msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Bist Du sicher, dass Du diesen Pipeline-Zeitplan löschen möchtest?" -msgid "Are you sure you want to discard your changes?" -msgstr "Bist Du sicher, dass Du alle Änderungen zurücksetzen willst?" - msgid "Are you sure you want to reset registration token?" msgstr "Bist Du sicher, dass Du den Registrierungstoken zurücksetzen willst?" msgid "Are you sure you want to reset the health check token?" msgstr "Bist Du sicher, dass Du den Systemüberwachungstoken zurücksetzen willst?" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "Bist Du sicher?" @@ -280,6 +364,9 @@ msgstr "" msgid "Authors: %{authors}" msgstr "" +msgid "Auto DevOps enabled" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -304,7 +391,13 @@ msgstr "" msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" msgstr "" msgid "Available" @@ -316,6 +409,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Billing" msgstr "" @@ -370,13 +466,10 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "Zweig" -msgstr[1] "Zweige" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "" +msgstr[1] "" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "Branch %{branch_name} wurde erstellt. Um die automatische Bereitstellung einzurichten, wähle eine GitLab CI Yaml Vorlage und committe Deine Änderungen. %{link_to_autodeploy_doc}" @@ -669,6 +762,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + msgid "Click to expand text" msgstr "" @@ -723,6 +819,9 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -771,6 +870,9 @@ msgstr "" msgid "ClusterIntegration|Ingress" msgstr "" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "" @@ -822,9 +924,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" msgstr "" @@ -960,6 +1059,12 @@ msgstr "" msgid "Collapse" msgstr "" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "Kommentare" @@ -968,6 +1073,11 @@ msgid_plural "Commits" msgstr[0] "Commit" msgstr[1] "Commits" +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" +msgstr[1] "" + msgid "Commit Message" msgstr "" @@ -980,6 +1090,9 @@ msgstr "Commit Nachricht" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Commit" @@ -1121,6 +1234,9 @@ msgstr "Kopiere URL in die Zwischenablage" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Kopiere Commit SHA in die Zwischenablage" @@ -1133,9 +1249,18 @@ msgstr "" msgid "Create New Directory" msgstr "Erstelle neues Verzeichnis" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "Erstelle einen persönlichen Zugriffstoken in Deinem Konto um mittels %{protocol} zu übertragen (push) oder abzurufen (pull)." +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "Erstelle Verzeichnis" @@ -1154,6 +1279,9 @@ msgstr "" msgid "Create merge request" msgstr "Erstelle Merge Request" +msgid "Create merge request and branch" +msgstr "" + msgid "Create new branch" msgstr "" @@ -1178,6 +1306,12 @@ msgstr "Tag " msgid "CreateTokenToCloneLink|create a personal access token" msgstr "Erstelle einen persönlichen Zugriffstoken" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + msgid "Creating epic" msgstr "" @@ -1232,6 +1366,9 @@ msgstr "" msgid "December" msgstr "" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "Erstelle ein individuelles Muster mittels Cron Syntax" @@ -1264,8 +1401,8 @@ msgstr "Verzeichnisname" msgid "Disable" msgstr "" -msgid "Discard changes" -msgstr "Änderungen verwerfen" +msgid "Discard draft" +msgstr "" msgid "Discover GitLab Geo." msgstr "" @@ -1324,6 +1461,9 @@ msgstr "E-Mails" msgid "Enable" msgstr "" +msgid "Enable Auto DevOps" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1378,9 +1518,18 @@ msgstr "" msgid "Epics" msgstr "" +msgid "Epics Roadmap" +msgstr "" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "" @@ -1447,12 +1596,36 @@ msgstr "" msgid "Explore public groups" msgstr "" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + msgid "Failed to change the owner" msgstr "Wechsel des Besitzers fehlgeschlagen" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "Entfernung der Pipelineplanung fehlgeschlagen" +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "" @@ -1468,6 +1641,9 @@ msgstr "" msgid "Files" msgstr "Dateien" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "Filter nach Commit Nachricht" @@ -1503,6 +1679,9 @@ msgstr "Von der Ticketbeschreibung bis zur Bereitstellung" msgid "From merge request merge until deploy to production" msgstr "Vom Umsetzen des Merge Request bis zur Bereitstellung auf dem Produktivsystem" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "" @@ -1650,6 +1829,24 @@ msgstr "" msgid "Got it!" msgstr "" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1748,6 +1945,9 @@ msgstr "" msgid "Housekeeping successfully started" msgstr "Aufräumen erfolgreich gestartet" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "Repository importieren" @@ -1760,6 +1960,9 @@ msgstr "" msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "" +msgid "Install Runner on Kubernetes" +msgstr "" + msgid "Install a Runner compatible with GitLab CI" msgstr "Installiere einen Runner der mit GitLab CI kompatibel ist" @@ -1810,6 +2013,9 @@ msgstr "" msgid "January" msgstr "" +msgid "Jobs" +msgstr "" + msgid "Jul" msgstr "" @@ -1840,6 +2046,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1887,6 +2096,12 @@ msgstr "am" msgid "Learn more" msgstr "" +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + msgid "Learn more in the" msgstr "Erfahre mehr in den" @@ -1905,6 +2120,9 @@ msgstr "Verlasse das Projekt" msgid "License" msgstr "" +msgid "List" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -1914,6 +2132,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" @@ -1923,6 +2144,9 @@ msgstr "" msgid "Locked Files" msgstr "" +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + msgid "Login" msgstr "" @@ -1953,9 +2177,6 @@ msgstr "Median" msgid "Members" msgstr "Mitglieder" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "" @@ -1968,6 +2189,9 @@ msgstr "" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" @@ -1992,9 +2216,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "einen SSH Schlüssel hinzufügst" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "Überwachung" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "hier" @@ -2090,9 +2323,6 @@ msgstr "Kein Repository" msgid "No schedules" msgstr "Keine Zeitpläne" -msgid "No time spent" -msgstr "" - msgid "None" msgstr "" @@ -2108,6 +2338,9 @@ msgstr "" msgid "Not enough data" msgstr "Nicht genügend Daten" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "Benachrichtigungsereignisse" @@ -2210,6 +2443,9 @@ msgstr "" msgid "Options" msgstr "Optionen" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + msgid "Overview" msgstr "Übersicht" @@ -2315,6 +2551,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "Alle" @@ -2348,6 +2602,9 @@ msgstr "" msgid "Private - The group and its projects can only be viewed by members." msgstr "" +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "Profil" @@ -2510,12 +2767,30 @@ msgstr "" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "" +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "" msgid "PrometheusService|Finding and configuring metrics..." msgstr "" +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + msgid "PrometheusService|Metrics" msgstr "" @@ -2537,9 +2812,18 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + msgid "PrometheusService|Time-series monitoring service" msgstr "" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + msgid "PrometheusService|View environments" msgstr "" @@ -2558,6 +2842,12 @@ msgstr "" msgid "Push events" msgstr "Übertragungsereignisse" +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + msgid "PushRule|Committer restriction" msgstr "" @@ -2621,6 +2911,9 @@ msgstr "" msgid "Repository" msgstr "" +msgid "Repository has no locks." +msgstr "" + msgid "Request Access" msgstr "Anfrage auf Zugriff" @@ -2633,6 +2926,9 @@ msgstr "Zugriffstoken für Systemzustand zurücksetzen" msgid "Reset runners registration token" msgstr "Registrierungstoken für Runner zurücksetzen" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" @@ -2644,6 +2940,9 @@ msgstr "Commit zurücksetzen" msgid "Revert this merge request" msgstr "Merge Request zurücksetzen" +msgid "Roadmap" +msgstr "" + msgid "SSH Keys" msgstr "SSH-Schlüssel" @@ -2689,12 +2988,18 @@ msgstr "" msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "Archivierungsformat auswählen" msgid "Select a timezone" msgstr "Zeitzone auswählen" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2707,6 +3012,9 @@ msgstr "Zielbranch auswählen" msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "" @@ -2719,6 +3027,9 @@ msgstr "" msgid "Service Templates" msgstr "" +msgid "Service URL" +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Lege ein Passwort für dein Konto fest, um mittels %{protocol} zu übertragen (push) oder abzurufen (pull)." @@ -2728,15 +3039,15 @@ msgstr "" msgid "Set up Koding" msgstr "Koding einrichten" -msgid "Set up auto deploy" -msgstr "Automatische Bereitstellung einrichten" - msgid "SetPasswordToCloneLink|set a password" msgstr "ein Passwort festlegst" msgid "Settings" msgstr "Einstellungen" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" @@ -2746,6 +3057,9 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2787,12 +3101,24 @@ msgstr "" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" @@ -2898,6 +3224,9 @@ msgstr "" msgid "Source" msgstr "" +msgid "Source (branch or tag)" +msgstr "" + msgid "Source code" msgstr "Quellcode" @@ -2937,8 +3266,8 @@ msgstr "Zu Branch/Tag wechseln" msgid "System Hooks" msgstr "" -msgid "Tag" -msgid_plural "Tags" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" msgstr[0] "" msgstr[1] "" @@ -3071,9 +3400,15 @@ msgstr "Auf das Projekt kann ohne Authentifizierung zugegriffen werden." msgid "The repository for this project does not exist." msgstr "Das Repository für das Projekt existiert nicht." +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "Die Überprüfungsphase stellt die Zeit vom Anlegen eines Merge Requests bis dessen Umsetzung dar. Sobald Du Deinen ersten Merge Request abschließt, werden dessen Daten hier automatisch angezeigt." +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "Die Staging-Phase stellt die Zeit zwischen der Umsetzung eines Merge Requests und der Bereitstellung des Codes auf dem Produktivsystem dar. Sobald Du das erste Mal auf das Produktivsystem ausgeliefert hast, werden dessen Daten hier automatisch angezeigt." @@ -3167,6 +3502,9 @@ msgstr "Dies bedeutet, dass Du keinen Code übertragen kannst, bevor Du kein lee msgid "This merge request is locked." msgstr "" +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3336,9 +3674,15 @@ msgstr[1] "Min." msgid "Time|s" msgstr "Sek." +msgid "Tip:" +msgstr "" + msgid "Title" msgstr "" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + msgid "Todo" msgstr "" @@ -3354,19 +3698,16 @@ msgstr "" msgid "Total Time" msgstr "Gesamtzeit" -msgid "Total issue time spent" -msgstr "" - msgid "Total test time for all commits/merges" msgstr "Gesamte Testzeit für alle Commits/Merges" -msgid "Track activity with Contribution Analytics." +msgid "Total: %{total}" msgstr "" -msgid "Track groups of issues that share a theme, across projects and milestones" +msgid "Track activity with Contribution Analytics." msgstr "" -msgid "Total: %{total}" +msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" msgid "Track time with quick actions" @@ -3378,9 +3719,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3396,6 +3734,9 @@ msgstr "" msgid "Unlocked" msgstr "" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "Entfavorisieren" @@ -3441,6 +3782,9 @@ msgstr "Benutze Deine globalen Benachrichtigungseinstellungen" msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" +msgid "View epics list" +msgstr "" + msgid "View file @ " msgstr "" @@ -3477,6 +3821,9 @@ msgstr "Es liegen nicht genügend Daten vor, um diese Phase anzuzeigen." msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "" +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "" @@ -3597,6 +3944,9 @@ msgstr "" msgid "Withdraw Access Request" msgstr "Zugriffsanfrage widerrufen" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "Du bist dabei %{group_name} zu entfernen. Entfernte Gruppen können NICHT wiederhergestellt werden! Bist Du dir WIRKLICH sicher?" @@ -3609,10 +3959,13 @@ msgstr "Du bist dabei, die Beziehung des Ablegers zum Ursprungsprojekt %{forked_ msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Du bist dabei %{project_name_with_namespace} einem andere Besitzer zu übergeben. Bist Du dir WIRKLICH sicher?" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3630,12 +3983,24 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "Du hast die Projektbegrenzung erreicht." +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "Du musst angemeldet sein, um ein Projekt zu favorisieren." +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "Du brauchst eine Genehmigung." @@ -3669,6 +4034,9 @@ msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -3696,7 +4064,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3708,7 +4076,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3723,6 +4091,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3735,6 +4112,12 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + msgid "commit" msgstr "" @@ -3752,11 +4135,41 @@ msgstr[1] "Tage" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" msgstr[1] "" +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3790,6 +4203,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3823,6 +4239,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3877,6 +4296,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3924,3 +4346,6 @@ msgstr "" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po index 7d4648c55f1..2613d719882 100644 --- a/locale/eo/gitlab.po +++ b/locale/eo/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 03:59-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:22-0500\n" "Last-Translator: gitlab \n" "Language-Team: Esperanto\n" "Language: eo_UY\n" @@ -49,6 +49,9 @@ msgid_plural "%s additional commits have been omitted to prevent performance iss msgstr[0] "%s enmetado estis transsaltita, por ne troŝarĝi la sistemon." msgstr[1] "%s enmetadoj estis transsaltitaj, por ne troŝarĝi la sistemon." +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -57,6 +60,9 @@ msgid_plural "%{count} participants" msgstr[0] "" msgstr[1] "" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "" @@ -66,6 +72,9 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "" +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "" @@ -130,9 +139,15 @@ msgstr "Aldoni gvidliniojn por kontribuado" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "" +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "Aldoni rajtigilon" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "Aldoni novan dosierujon" @@ -151,12 +166,45 @@ msgstr "" msgid "AdminArea|Stopping jobs failed" msgstr "" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" msgid "AdminHealthPageLink|health page" msgstr "" +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + msgid "Advanced" msgstr "" @@ -181,6 +229,12 @@ msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" @@ -190,12 +244,36 @@ msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while fetching the pipeline." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + msgid "An error occurred while loading filenames" msgstr "" +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + msgid "An error occurred while rendering KaTeX" msgstr "" @@ -208,6 +286,12 @@ msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + msgid "An error occurred while validating username" msgstr "" @@ -232,15 +316,15 @@ msgstr "Arkivita projekto! La deponejo permesas nur legadon" msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Ĉu vi certe volas forigi ĉi tiun ĉenstablan planon?" -msgid "Are you sure you want to discard your changes?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "" msgid "Are you sure you want to reset the health check token?" msgstr "" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "" @@ -280,6 +364,9 @@ msgstr "" msgid "Authors: %{authors}" msgstr "" +msgid "Auto DevOps enabled" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -304,7 +391,13 @@ msgstr "" msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" msgstr "" msgid "Available" @@ -316,6 +409,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Billing" msgstr "" @@ -370,13 +466,10 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "Branĉo" -msgstr[1] "Branĉoj" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "" +msgstr[1] "" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "La branĉo %{branch_name} estis kreita. Por agordi aŭtomatan disponigadon, bonvolu elekti Yaml-ŝablonon por GitLab CI kaj enmeti viajn ŝanĝojn. %{link_to_autodeploy_doc}" @@ -669,6 +762,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + msgid "Click to expand text" msgstr "" @@ -723,6 +819,9 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -771,6 +870,9 @@ msgstr "" msgid "ClusterIntegration|Ingress" msgstr "" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "" @@ -822,9 +924,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" msgstr "" @@ -960,6 +1059,12 @@ msgstr "" msgid "Collapse" msgstr "" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "" @@ -968,6 +1073,11 @@ msgid_plural "Commits" msgstr[0] "Enmetado" msgstr[1] "Enmetadoj" +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" +msgstr[1] "" + msgid "Commit Message" msgstr "" @@ -980,6 +1090,9 @@ msgstr "Mesaĝo pri la enmetado" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Enmeti" @@ -1121,6 +1234,9 @@ msgstr "Kopii la adreson en la kopibufron" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Kopii la identigilon de la enmetado" @@ -1133,9 +1249,18 @@ msgstr "" msgid "Create New Directory" msgstr "Krei novan dosierujon" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "Kreu propran atingoĵetonon en via konto por ebligi al vi eltiri kaj alpuŝi per %{protocol}." +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "Krei dosierujon" @@ -1154,6 +1279,9 @@ msgstr "" msgid "Create merge request" msgstr "Krei peton pri kunfando" +msgid "Create merge request and branch" +msgstr "" + msgid "Create new branch" msgstr "" @@ -1178,6 +1306,12 @@ msgstr "Etikedo" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "kreos propran atingoĵetonon" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + msgid "Creating epic" msgstr "" @@ -1232,6 +1366,9 @@ msgstr "" msgid "December" msgstr "" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "Difini propran ŝablonon, uzante la sintakson de Cron" @@ -1264,7 +1401,7 @@ msgstr "Nomo de dosierujo" msgid "Disable" msgstr "" -msgid "Discard changes" +msgid "Discard draft" msgstr "" msgid "Discover GitLab Geo." @@ -1324,6 +1461,9 @@ msgstr "" msgid "Enable" msgstr "" +msgid "Enable Auto DevOps" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1378,9 +1518,18 @@ msgstr "" msgid "Epics" msgstr "" +msgid "Epics Roadmap" +msgstr "" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "" @@ -1447,12 +1596,36 @@ msgstr "" msgid "Explore public groups" msgstr "" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + msgid "Failed to change the owner" msgstr "Ne eblas ŝanĝi la posedanton" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "Ne eblas forigi la ĉenstablan planon" +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "" @@ -1468,6 +1641,9 @@ msgstr "" msgid "Files" msgstr "Dosieroj" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "Filtri per mesaĝo" @@ -1503,6 +1679,9 @@ msgstr "De la kreado de la problemo ĝis la disponigado en la publika versio" msgid "From merge request merge until deploy to production" msgstr "De la kunfandado de la peto pri kunfando ĝis la disponigado en la publika versio" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "" @@ -1650,6 +1829,24 @@ msgstr "" msgid "Got it!" msgstr "" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1748,6 +1945,9 @@ msgstr "" msgid "Housekeeping successfully started" msgstr "La refreŝigo komenciĝis sukcese" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "Enporti deponejon" @@ -1760,6 +1960,9 @@ msgstr "" msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "" +msgid "Install Runner on Kubernetes" +msgstr "" + msgid "Install a Runner compatible with GitLab CI" msgstr "" @@ -1810,6 +2013,9 @@ msgstr "" msgid "January" msgstr "" +msgid "Jobs" +msgstr "" + msgid "Jul" msgstr "" @@ -1840,6 +2046,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1887,6 +2096,12 @@ msgstr "" msgid "Learn more" msgstr "" +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + msgid "Learn more in the" msgstr "Lernu pli en la" @@ -1905,6 +2120,9 @@ msgstr "Forlasi la projekton" msgid "License" msgstr "" +msgid "List" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -1914,6 +2132,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" @@ -1923,6 +2144,9 @@ msgstr "" msgid "Locked Files" msgstr "" +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + msgid "Login" msgstr "" @@ -1953,9 +2177,6 @@ msgstr "Mediano" msgid "Members" msgstr "" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "" @@ -1968,6 +2189,9 @@ msgstr "" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" @@ -1992,9 +2216,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "aldonos SSH-ŝlosilon" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "" @@ -2090,9 +2323,6 @@ msgstr "Ne estas deponejo" msgid "No schedules" msgstr "Ne estas planoj" -msgid "No time spent" -msgstr "" - msgid "None" msgstr "" @@ -2108,6 +2338,9 @@ msgstr "" msgid "Not enough data" msgstr "Ne estas sufiĉe da datenoj" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "Sciigaj eventoj" @@ -2210,6 +2443,9 @@ msgstr "" msgid "Options" msgstr "Opcioj" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + msgid "Overview" msgstr "" @@ -2315,6 +2551,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "ĉiuj" @@ -2348,6 +2602,9 @@ msgstr "" msgid "Private - The group and its projects can only be viewed by members." msgstr "" +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "" @@ -2510,12 +2767,30 @@ msgstr "" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "" +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "" msgid "PrometheusService|Finding and configuring metrics..." msgstr "" +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + msgid "PrometheusService|Metrics" msgstr "" @@ -2537,9 +2812,18 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + msgid "PrometheusService|Time-series monitoring service" msgstr "" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + msgid "PrometheusService|View environments" msgstr "" @@ -2558,6 +2842,12 @@ msgstr "" msgid "Push events" msgstr "" +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + msgid "PushRule|Committer restriction" msgstr "" @@ -2621,6 +2911,9 @@ msgstr "" msgid "Repository" msgstr "" +msgid "Repository has no locks." +msgstr "" + msgid "Request Access" msgstr "Peti atingeblon" @@ -2633,6 +2926,9 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" @@ -2644,6 +2940,9 @@ msgstr "Malfari ĉi tiun enmetadon" msgid "Revert this merge request" msgstr "Malfari ĉi tiun peton pri kunfando" +msgid "Roadmap" +msgstr "" + msgid "SSH Keys" msgstr "" @@ -2689,12 +2988,18 @@ msgstr "" msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "Elektu formaton de arkivo" msgid "Select a timezone" msgstr "Elektu horzonon" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2707,6 +3012,9 @@ msgstr "Elektu celan branĉon" msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "" @@ -2719,6 +3027,9 @@ msgstr "" msgid "Service Templates" msgstr "" +msgid "Service URL" +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Kreu pasvorton por via konto por ebligi al vi eltiri kaj alpuŝi per %{protocol}." @@ -2728,15 +3039,15 @@ msgstr "" msgid "Set up Koding" msgstr "Agordi „Koding“" -msgid "Set up auto deploy" -msgstr "Agordi aŭtomatan disponigadon" - msgid "SetPasswordToCloneLink|set a password" msgstr "kreos pasvorton" msgid "Settings" msgstr "" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" @@ -2746,6 +3057,9 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2787,12 +3101,24 @@ msgstr "" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" @@ -2898,6 +3224,9 @@ msgstr "" msgid "Source" msgstr "" +msgid "Source (branch or tag)" +msgstr "" + msgid "Source code" msgstr "Kodo" @@ -2937,10 +3266,10 @@ msgstr "Iri al branĉo/etikedo" msgid "System Hooks" msgstr "" -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "Etikedo" -msgstr[1] "Etikedoj" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "" +msgstr[1] "" msgid "Tags" msgstr "Etikedoj" @@ -3071,9 +3400,15 @@ msgstr "Ĉiu povas havi atingon al la projekto, sen ensaluti" msgid "The repository for this project does not exist." msgstr "La deponejo por ĉi tiu projekto ne ekzistas." +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "La etapo de la kontrolo montras la tempon de la kreado de la peto pri kunfando ĝis ĝia aplikado. La datenoj aldoniĝos aŭtomate post kiam vi aplikos la unuan peton pri kunfando." +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "La etapo de preparo por eldono montras la tempon inter la aplikado de la peto pri kunfando kaj la disponigado de la kodo en la publika versio. La datenoj aldoniĝos aŭtomate post kiam vi faros la unuan disponigadon en la publika versio." @@ -3167,6 +3502,9 @@ msgstr "Ĉi tiu signifas, ke vi ne povos alpuŝi kodon, antaŭ ol vi kreos malpl msgid "This merge request is locked." msgstr "" +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3336,9 +3674,15 @@ msgstr[1] "min" msgid "Time|s" msgstr "s" +msgid "Tip:" +msgstr "" + msgid "Title" msgstr "" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + msgid "Todo" msgstr "" @@ -3354,19 +3698,16 @@ msgstr "" msgid "Total Time" msgstr "Totala tempo" -msgid "Total issue time spent" -msgstr "" - msgid "Total test time for all commits/merges" msgstr "Totala tempo por la testado de ĉiuj enmetadoj/kunfandoj" -msgid "Track activity with Contribution Analytics." +msgid "Total: %{total}" msgstr "" -msgid "Track groups of issues that share a theme, across projects and milestones" +msgid "Track activity with Contribution Analytics." msgstr "" -msgid "Total: %{total}" +msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" msgid "Track time with quick actions" @@ -3378,9 +3719,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3396,6 +3734,9 @@ msgstr "" msgid "Unlocked" msgstr "" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "Malsteligi" @@ -3441,6 +3782,9 @@ msgstr "Uzi vian ĝeneralan agordon pri la sciigoj" msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" +msgid "View epics list" +msgstr "" + msgid "View file @ " msgstr "" @@ -3477,6 +3821,9 @@ msgstr "Ne estas sufiĉe da datenoj por montri ĉi tiun etapon." msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "" +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "" @@ -3597,6 +3944,9 @@ msgstr "" msgid "Withdraw Access Request" msgstr "Nuligi la peton pri atingeblo" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "Vi forigos „%{group_name}“. Oni NE POVAS malfari la forigon de grupo! Ĉu vi estas ABSOLUTE certa?" @@ -3609,10 +3959,13 @@ msgstr "Vi forigos la rilaton de la disbranĉigo al la originala projekto, „%{ msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Vi estas transigonta „%{project_name_with_namespace}“ al alia posedanto. Ĉu vi estas ABSOLUTE certa?" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3630,12 +3983,24 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "Vi ne povas krei pliajn projektojn" +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "Oni devas ensaluti por steligi projekton" +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "VI bezonas permeson." @@ -3669,6 +4034,9 @@ msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -3696,7 +4064,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3708,7 +4076,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3723,6 +4091,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3735,6 +4112,12 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + msgid "commit" msgstr "" @@ -3752,11 +4135,41 @@ msgstr[1] "tagoj" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" msgstr[1] "" +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3790,6 +4203,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3823,6 +4239,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3877,6 +4296,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3924,3 +4346,6 @@ msgstr "" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po index 7d28a7064d3..76926fd5c64 100644 --- a/locale/es/gitlab.po +++ b/locale/es/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 04:01-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:20-0500\n" "Last-Translator: gitlab \n" "Language-Team: Spanish\n" "Language: es_ES\n" @@ -49,6 +49,9 @@ msgid_plural "%s additional commits have been omitted to prevent performance iss msgstr[0] "%s cambio adicional ha sido omitido para evitar problemas de rendimiento." msgstr[1] "%s cambios adicionales han sido omitidos para evitar problemas de rendimiento." +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -57,6 +60,9 @@ msgid_plural "%{count} participants" msgstr[0] "%{count} participante" msgstr[1] "%{count} participantes" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "%{number_commits_behind} commits detrás de %{default_branch}, %{number_commits_ahead} commits por delante" @@ -66,6 +72,9 @@ msgstr "%{number_of_failures} de %{maximum_failures} intentos fallidos. GitLab p msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} de %{maximum_failures} intentos fallidos. GitLab no reintentará automáticamente. Debe reinicializar la información de almacenamiento cuando el problema sea solventado." +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "%{storage_name}: intento de acceso fallido al almacenamiento en host:" @@ -130,9 +139,15 @@ msgstr "Agregar guía de contribución" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "Añadir Webhooks Grupales y Gitlab Enterprise Edition." +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "Agregar Licencia" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "Agregar nuevo directorio" @@ -140,31 +155,64 @@ msgid "Add todo" msgstr "" msgid "AdminArea|Stop all jobs" -msgstr "" +msgstr "Detener todos los trabajos" msgid "AdminArea|Stop all jobs?" -msgstr "" +msgstr "Detener todos los trabajos?" msgid "AdminArea|Stop jobs" -msgstr "" +msgstr "Detener trabajos" msgid "AdminArea|Stopping jobs failed" -msgstr "" +msgstr "Error al detener trabajos" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" msgid "AdminHealthPageLink|health page" msgstr "Página de estado" -msgid "Advanced" +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" msgstr "" +msgid "Advanced" +msgstr "Avanzado" + msgid "Advanced settings" msgstr "Configuración avanzada" msgid "All" -msgstr "" +msgstr "Todos" msgid "All changes are committed" msgstr "" @@ -173,7 +221,7 @@ msgid "Allows you to add and manage Kubernetes clusters." msgstr "" msgid "An error occurred previewing the blob" -msgstr "" +msgstr "Ha ocurrido un error visualizando el blob" msgid "An error occurred when toggling the notification subscription" msgstr "Se produjo un error al activar/desactivar la suscripción de notificación" @@ -181,6 +229,12 @@ msgstr "Se produjo un error al activar/desactivar la suscripción de notificaci msgid "An error occurred when updating the issue weight" msgstr "Se produjo un error al actualizar el peso de la incidencia" +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" @@ -190,12 +244,36 @@ msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "Se produjo un error al obtener datos de la barra lateral" +msgid "An error occurred while fetching the pipeline." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + msgid "An error occurred while loading filenames" msgstr "" +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + msgid "An error occurred while rendering KaTeX" msgstr "" @@ -208,6 +286,12 @@ msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + msgid "An error occurred while validating username" msgstr "" @@ -232,15 +316,15 @@ msgstr "¡Proyecto archivado! El repositorio es de solo lectura" msgid "Are you sure you want to delete this pipeline schedule?" msgstr "¿Estás seguro que deseas eliminar esta programación del pipeline?" -msgid "Are you sure you want to discard your changes?" -msgstr "¿Está seguro que desea descartar sus cambios?" - msgid "Are you sure you want to reset registration token?" msgstr "¿Está seguro que desea reinicializar el token de registro?" msgid "Are you sure you want to reset the health check token?" msgstr "¿Está seguro que desea reinicializar el token de Verificación de Estado?" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "¿Estás seguro?" @@ -251,16 +335,16 @@ msgid "Assign custom color like #FF0000" msgstr "" msgid "Assign labels" -msgstr "" +msgstr "Asignar etiquetas" msgid "Assign milestone" -msgstr "" +msgstr "Asignar milestone" msgid "Assign to" -msgstr "" +msgstr "Asignar a" msgid "Assignee" -msgstr "" +msgstr "Asignado a" msgid "Attach a file by drag & drop or %{upload_link}" msgstr "Adjunte un archivo arrastrando & soltando o %{upload_link}" @@ -275,9 +359,12 @@ msgid "Authentication Log" msgstr "Registro de Autenticación" msgid "Author" -msgstr "Autor" +msgstr "" msgid "Authors: %{authors}" +msgstr "Autores: %{authors}" + +msgid "Auto DevOps enabled" msgstr "" msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." @@ -287,24 +374,30 @@ msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} t msgstr "" msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." -msgstr "" +msgstr "Tanto las Auto Review Apps como Auto Deploy necesitan un dominio para funcionar correctamente." msgid "AutoDevOps|Auto DevOps (Beta)" -msgstr "" +msgstr "Auto DevOps (Beta)" msgid "AutoDevOps|Auto DevOps documentation" -msgstr "" +msgstr "Documentación de Auto DevOps" msgid "AutoDevOps|Enable in settings" -msgstr "" +msgstr "Habilitar en la configuración" msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration." -msgstr "" +msgstr "Automáticamente construirán, probarán y desplegarán su aplicación con base en la configuración predefinida de CI/CD." msgid "AutoDevOps|Learn more in the %{link_to_documentation}" +msgstr "Más información en %{link_to_documentation}" + +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." msgstr "" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" msgstr "" msgid "Available" @@ -314,81 +407,81 @@ msgid "Avatar will be removed. Are you sure?" msgstr "" msgid "Average per day: %{average}" -msgstr "" +msgstr "Promedio por día: %{average}" + +msgid "Begin with the selected commit" +msgstr "Iniciar con el commit seleccionado" msgid "Billing" msgstr "Facturación" msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan." -msgstr "" +msgstr "%{group_name} está actualmente en el plan %{plan_link}." msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available." -msgstr "" +msgstr "Aumentar o disminuir automáticamente las características de algunos Planes no está disponible actualmente." msgid "BillingPlans|Current plan" -msgstr "" +msgstr "Plan actual" msgid "BillingPlans|Customer Support" -msgstr "" +msgstr "Atención al cliente" msgid "BillingPlans|Downgrade" msgstr "" msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}." -msgstr "" +msgstr "Obtenga más información sobre cada plan al leer nuestro %{faq_link}." msgid "BillingPlans|Manage plan" -msgstr "" +msgstr "Gestionar plan" msgid "BillingPlans|Please contact %{customer_support_link} in that case." -msgstr "" +msgstr "Por favor contacte a %{customer_support_link} en ese caso." msgid "BillingPlans|See all %{plan_name} features" -msgstr "" +msgstr "Ver todas la funcionalidades del plan %{plan_name}" msgid "BillingPlans|This group uses the plan associated with its parent group." msgstr "" msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}." -msgstr "" +msgstr "Para gestionar el plan para este grupo, visite la sección de facturación de %{parent_billing_page_link}." msgid "BillingPlans|Upgrade" msgstr "" msgid "BillingPlans|You are currently on the %{plan_link} plan." -msgstr "" +msgstr "Actualmente estás en el plan %{plan_link}." msgid "BillingPlans|frequently asked questions" -msgstr "" +msgstr "preguntas frecuentes" msgid "BillingPlans|monthly" -msgstr "" +msgstr "mensual" msgid "BillingPlans|paid annually at %{price_per_year}" -msgstr "" +msgstr "%{price_per_year} pagados anualmente" msgid "BillingPlans|per user" -msgstr "" - -msgid "Begin with the selected commit" -msgstr "" +msgstr "por usuario" -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "Rama" -msgstr[1] "Ramas" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "" +msgstr[1] "" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "La rama %{branch_name} fue creada. Para configurar el auto despliegue, escoge una plantilla Yaml para GitLab CI y envía tus cambios. %{link_to_autodeploy_doc}" msgid "Branch has changed" -msgstr "" +msgstr "La rama ha cambiado" msgid "Branch is already taken" -msgstr "" +msgstr "La rama ya existe" msgid "Branch name" -msgstr "" +msgstr "Nombre de la rama" msgid "BranchSwitcherPlaceholder|Search branches" msgstr "Buscar ramas" @@ -400,34 +493,34 @@ msgid "Branches" msgstr "Ramas" msgid "Branches|Cant find HEAD commit for this branch" -msgstr "" +msgstr "No puedo encontrar el commit HEAD para esta rama" msgid "Branches|Compare" -msgstr "" +msgstr "Comparar" msgid "Branches|Delete all branches that are merged into '%{default_branch}'" -msgstr "" +msgstr "Borrar todas las ramas que se fusionen con '%{default_branch}'" msgid "Branches|Delete branch" -msgstr "" +msgstr "Eliminar la rama" msgid "Branches|Delete merged branches" -msgstr "" +msgstr "Borrar ramas fusionadas" msgid "Branches|Delete protected branch" -msgstr "" +msgstr "Borrar rama protegida" msgid "Branches|Delete protected branch '%{branch_name}'?" -msgstr "" +msgstr "¿Borrar rama protegida '%{branch_name}'?" msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?" -msgstr "" +msgstr "El borrado de la rama '%{branch_name}' no podrá deshacerse. ¿Está seguro?" msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?" -msgstr "" +msgstr "El borrado de las ramas fusionadas no podrá deshacerse. ¿Está usted seguro?" msgid "Branches|Filter by branch name" -msgstr "" +msgstr "Filtrar por nombre de rama" msgid "Branches|Merged into %{default_branch}" msgstr "" @@ -499,10 +592,10 @@ msgid "ByAuthor|by" msgstr "por" msgid "CI / CD" -msgstr "" +msgstr "CI / CD" msgid "CI/CD configuration" -msgstr "" +msgstr "Configuración de CI/CD" msgid "CICD|Jobs" msgstr "" @@ -511,13 +604,13 @@ msgid "Cancel" msgstr "Cancelar" msgid "Cancel edit" -msgstr "" +msgstr "Cancelar edición" msgid "Cannot modify managed Kubernetes cluster" msgstr "" msgid "Change Weight" -msgstr "" +msgstr "Cambiar peso" msgid "ChangeTypeActionLabel|Pick into branch" msgstr "Escoger en la rama" @@ -550,10 +643,10 @@ msgid "Check interval" msgstr "" msgid "Checking %{text} availability…" -msgstr "" +msgstr "Comprobando disponibilidad de %{text}..." msgid "Checking branch availability..." -msgstr "" +msgstr "Verificando disponibilidad de la rama..." msgid "Cherry-pick this commit" msgstr "Escoger este cambio" @@ -568,13 +661,13 @@ msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to s msgstr "" msgid "Choose file..." -msgstr "" +msgstr "Elegir archivo..." msgid "Choose which groups you wish to synchronize to this secondary node." -msgstr "" +msgstr "Elija qué grupos desea sincronizar a este nodo secundario." msgid "Choose which shards you wish to synchronize to this secondary node." -msgstr "" +msgstr "Elija qué fragmentos desea sincronizar a este nodo secundario." msgid "CiStatusLabel|canceled" msgstr "cancelado" @@ -669,17 +762,20 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" -msgid "Click to expand text" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" msgstr "" +msgid "Click to expand text" +msgstr "Haga clic para expandir el texto" + msgid "Clone repository" msgstr "" msgid "Close" -msgstr "" +msgstr "Cerrar" msgid "Closed" -msgstr "" +msgstr "Cerrado" msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" msgstr "" @@ -721,16 +817,19 @@ msgid "ClusterIntegration|Copy API URL" msgstr "" msgid "ClusterIntegration|Copy CA Certificate" +msgstr "Copiar Certificado CA" + +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" msgstr "" msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" msgid "ClusterIntegration|Copy Token" -msgstr "" +msgstr "Copiar Token" msgid "ClusterIntegration|Create Kubernetes cluster" -msgstr "" +msgstr "Crear cluster de Kubernetes" msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" msgstr "" @@ -739,10 +838,10 @@ msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes E msgstr "" msgid "ClusterIntegration|Create on GKE" -msgstr "" +msgstr "Crear en GKE" msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" -msgstr "" +msgstr "Ingrese los detalles para un cluster Kubernetes existente" msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" msgstr "" @@ -751,49 +850,52 @@ msgid "ClusterIntegration|Environment scope" msgstr "" msgid "ClusterIntegration|GitLab Integration" -msgstr "" +msgstr "Integración GitLab" msgid "ClusterIntegration|GitLab Runner" -msgstr "" +msgstr "GitLab Runner" msgid "ClusterIntegration|Google Cloud Platform project ID" -msgstr "" +msgstr "ID del proyecto de Google Cloud Platform" msgid "ClusterIntegration|Google Kubernetes Engine" -msgstr "" +msgstr "Google Kubernetes Engine" msgid "ClusterIntegration|Google Kubernetes Engine project" -msgstr "" +msgstr "Proyecto Google Kubernetes Engine" msgid "ClusterIntegration|Helm Tiller" -msgstr "" +msgstr "Helm Tiller" msgid "ClusterIntegration|Ingress" msgstr "" -msgid "ClusterIntegration|Install" +msgid "ClusterIntegration|Ingress IP Address" msgstr "" +msgid "ClusterIntegration|Install" +msgstr "Instalar" + msgid "ClusterIntegration|Installed" -msgstr "" +msgstr "Instalado" msgid "ClusterIntegration|Installing" -msgstr "" +msgstr "Instalando" msgid "ClusterIntegration|Integrate Kubernetes cluster automation" msgstr "" msgid "ClusterIntegration|Integration status" -msgstr "" +msgstr "Estado de integración" msgid "ClusterIntegration|Kubernetes cluster" -msgstr "" +msgstr "cluster de Kubernetes" msgid "ClusterIntegration|Kubernetes cluster details" -msgstr "" +msgstr "Detalles del cluster de Kubernetes" msgid "ClusterIntegration|Kubernetes cluster integration" -msgstr "" +msgstr "Integración de cluster de Kubernetes" msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." msgstr "" @@ -808,7 +910,7 @@ msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernet msgstr "" msgid "ClusterIntegration|Kubernetes cluster name" -msgstr "" +msgstr "Nombre de cluster de Kubernetes" msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" msgstr "" @@ -820,25 +922,22 @@ msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" -msgstr "" - -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" +msgstr "Conozca más sobre %{link_to_documentation}" msgid "ClusterIntegration|Learn more about environments" -msgstr "" +msgstr "Conozca más sobre los entornos" msgid "ClusterIntegration|Machine type" -msgstr "" +msgstr "Tipo de máquina" msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" msgstr "" msgid "ClusterIntegration|Manage" -msgstr "" +msgstr "Administrar" msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" -msgstr "" +msgstr "Administre su cluster de Kubernetes visitando %{link_gke}" msgid "ClusterIntegration|More information" msgstr "" @@ -847,19 +946,19 @@ msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab E msgstr "" msgid "ClusterIntegration|Note:" -msgstr "" +msgstr "Nota:" msgid "ClusterIntegration|Number of nodes" -msgstr "" +msgstr "Número de nodos" msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" msgstr "" msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" -msgstr "" +msgstr "Asegúrese de que su cuenta de Google cumpla con los siguientes requisitos:" msgid "ClusterIntegration|Project ID" -msgstr "" +msgstr "ID de Proyecto" msgid "ClusterIntegration|Project namespace" msgstr "" @@ -877,43 +976,43 @@ msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "" msgid "ClusterIntegration|Remove integration" -msgstr "" +msgstr "Eliminar integración" msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" -msgstr "" +msgstr "Falló la solicitud para iniciar la instalación" msgid "ClusterIntegration|Save changes" -msgstr "" +msgstr "Guardar cambios" msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" msgstr "" msgid "ClusterIntegration|See machine types" -msgstr "" +msgstr "Ver tipos de máquina" msgid "ClusterIntegration|See your projects" -msgstr "" +msgstr "Ver tus proyectos" msgid "ClusterIntegration|See zones" -msgstr "" +msgstr "Ver zonas" msgid "ClusterIntegration|Service token" msgstr "" msgid "ClusterIntegration|Show" -msgstr "" +msgstr "Mostrar" msgid "ClusterIntegration|Something went wrong on our end." -msgstr "" +msgstr "Algo salió mal de nuestro lado." msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" -msgstr "" +msgstr "Algo salió mal durante la instalación de %{title}" msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" msgstr "" @@ -925,51 +1024,62 @@ msgid "ClusterIntegration|Toggle Kubernetes cluster" msgstr "" msgid "ClusterIntegration|Token" -msgstr "" +msgstr "Token" msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." msgstr "" msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" -msgstr "" +msgstr "Su cuenta debe tener %{link_to_kubernetes_engine}" msgid "ClusterIntegration|Zone" -msgstr "" +msgstr "Zona" msgid "ClusterIntegration|access to Google Kubernetes Engine" -msgstr "" +msgstr "acceso a Google Kubernetes Engine" msgid "ClusterIntegration|check the pricing here" msgstr "" msgid "ClusterIntegration|documentation" -msgstr "" +msgstr "documentación" msgid "ClusterIntegration|help page" -msgstr "" +msgstr "página de ayuda" msgid "ClusterIntegration|installing applications" -msgstr "" +msgstr "Instalando aplicaciones" msgid "ClusterIntegration|meets the requirements" -msgstr "" +msgstr "cumple con los requisitos" msgid "ClusterIntegration|properly configured" msgstr "" msgid "Collapse" +msgstr "Contraer" + +msgid "Comment and resolve discussion" msgstr "" -msgid "Comments" +msgid "Comment and unresolve discussion" msgstr "" +msgid "Comments" +msgstr "Comentarios" + msgid "Commit" msgid_plural "Commits" msgstr[0] "Cambio" msgstr[1] "Cambios" +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" +msgstr[1] "" + msgid "Commit Message" -msgstr "" +msgstr "Mensaje del commit" msgid "Commit duration in minutes for last 30 commits" msgstr "Duración de los cambios en minutos para los últimos 30" @@ -980,6 +1090,9 @@ msgstr "Mensaje del cambio" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Cambio" @@ -1041,49 +1154,49 @@ msgid "CompareBranches|There isn't anything to compare." msgstr "" msgid "Confidentiality" -msgstr "" +msgstr "Confidencialidad" msgid "Container Registry" msgstr "" msgid "ContainerRegistry|Created" -msgstr "" +msgstr "Creado" msgid "ContainerRegistry|First log in to GitLab’s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:" msgstr "" msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:" -msgstr "" +msgstr "Gitlab soporta hasta 3 niveles para nombres de imágenes. Los siguientes ejemplos de imágenes son válidos para tu proyecto:" msgid "ContainerRegistry|How to use the Container Registry" msgstr "" msgid "ContainerRegistry|Learn more about" -msgstr "" +msgstr "Conozca más sobre" msgid "ContainerRegistry|No tags in Container Registry for this container image." -msgstr "" +msgstr "No hay etiquetas en el Container Registry para esta imagen de contenedor." msgid "ContainerRegistry|Once you log in, you’re free to create and upload a container image using the common %{build} and %{push} commands" -msgstr "" +msgstr "Una vez que inicies sesión, eres libre de crear y cargar una imagen de contenedor utilizando los comandos comunes %{build} y %{push}" msgid "ContainerRegistry|Remove repository" -msgstr "" +msgstr "Borrar repositorio" msgid "ContainerRegistry|Remove tag" -msgstr "" +msgstr "Borrar etiqueta" msgid "ContainerRegistry|Size" -msgstr "" +msgstr "Tamaño" msgid "ContainerRegistry|Tag" -msgstr "" +msgstr "Etiqueta" msgid "ContainerRegistry|Tag ID" -msgstr "" +msgstr "Etiqueta ID" msgid "ContainerRegistry|Use different image names" -msgstr "" +msgstr "Usar nombres de imagen diferentes" msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images." msgstr "" @@ -1113,7 +1226,7 @@ msgid "Control the maximum concurrency of repository backfill for this secondary msgstr "" msgid "Copy SSH public key to clipboard" -msgstr "" +msgstr "Copiar la clave pública SSH al portapapeles" msgid "Copy URL to clipboard" msgstr "Copiar URL al portapapeles" @@ -1121,6 +1234,9 @@ msgstr "Copiar URL al portapapeles" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Copiar SHA del cambio al portapapeles" @@ -1128,14 +1244,23 @@ msgid "Copy reference to clipboard" msgstr "" msgid "Create" -msgstr "" +msgstr "Crear" msgid "Create New Directory" msgstr "Crear Nuevo Directorio" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "Crear un token de acceso personal en tu cuenta para actualizar o enviar a través de %{protocol}." +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "Crear directorio" @@ -1143,10 +1268,10 @@ msgid "Create empty bare repository" msgstr "Crear repositorio vacío" msgid "Create epic" -msgstr "" +msgstr "Crear epic" msgid "Create file" -msgstr "" +msgstr "Crear archivo" msgid "Create lists from labels. Issues with that label appear in that list." msgstr "" @@ -1154,17 +1279,20 @@ msgstr "" msgid "Create merge request" msgstr "Crear solicitud de fusión" -msgid "Create new branch" +msgid "Create merge request and branch" msgstr "" +msgid "Create new branch" +msgstr "Crear una nueva rama" + msgid "Create new directory" -msgstr "" +msgstr "Crear nuevo directorio" msgid "Create new file" -msgstr "" +msgstr "Crear nuevo archivo" msgid "Create new label" -msgstr "" +msgstr "Crear nueva etiqueta" msgid "Create new..." msgstr "Crear nuevo..." @@ -1178,9 +1306,15 @@ msgstr "Etiqueta" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "crear un token de acceso personal" -msgid "Creating epic" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" msgstr "" +msgid "Creating epic" +msgstr "Creando epic" + msgid "Cron Timezone" msgstr "Zona horaria del Cron" @@ -1188,7 +1322,7 @@ msgid "Cron syntax" msgstr "Sintaxis de Cron" msgid "Current node" -msgstr "" +msgstr "Nodo actual" msgid "Custom notification events" msgstr "Eventos de notificaciones personalizadas" @@ -1221,15 +1355,18 @@ msgid "CycleAnalyticsStage|Test" msgstr "Pruebas" msgid "DashboardProjects|All" -msgstr "" +msgstr "Todos" msgid "DashboardProjects|Personal" -msgstr "" +msgstr "Personales" msgid "Dec" -msgstr "" +msgstr "Dic" msgid "December" +msgstr "Diciembre" + +msgid "Default classification label" msgstr "" msgid "Define a custom pattern with cron syntax" @@ -1253,7 +1390,7 @@ msgid "Description templates allow you to define context-specific templates for msgstr "" msgid "Details" -msgstr "" +msgstr "Detalles" msgid "Diffs|No file name available" msgstr "" @@ -1262,9 +1399,9 @@ msgid "Directory name" msgstr "Nombre del directorio" msgid "Disable" -msgstr "" +msgstr "Deshabilitar" -msgid "Discard changes" +msgid "Discard draft" msgstr "" msgid "Discover GitLab Geo." @@ -1322,6 +1459,9 @@ msgid "Emails" msgstr "" msgid "Enable" +msgstr "Habilitar" + +msgid "Enable Auto DevOps" msgstr "" msgid "Environments|An error occurred while fetching the environments." @@ -1367,23 +1507,32 @@ msgid "Environments|Show all" msgstr "" msgid "Environments|Updated" -msgstr "" +msgstr "Actualizado" msgid "Environments|You don't have any environments right now." -msgstr "" +msgstr "No tiene ningún entorno en este momento." msgid "Epic will be removed! Are you sure?" msgstr "" msgid "Epics" +msgstr "Epics" + +msgid "Epics Roadmap" msgstr "" msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "" -msgid "Error creating epic" +msgid "Error checking branch data. Please try again." msgstr "" +msgid "Error committing changes. Please try again." +msgstr "" + +msgid "Error creating epic" +msgstr "Error creando epic" + msgid "Error fetching contributors data." msgstr "" @@ -1400,7 +1549,7 @@ msgid "Error fetching usage ping data." msgstr "" msgid "Error occurred when toggling the notification subscription" -msgstr "" +msgstr "Se produjo un error al activar/desactivar la suscripción de notificación" msgid "Error saving label update." msgstr "" @@ -1415,7 +1564,7 @@ msgid "EventFilterBy|Filter by all" msgstr "" msgid "EventFilterBy|Filter by comments" -msgstr "" +msgstr "Filtrar por comentarios" msgid "EventFilterBy|Filter by issue events" msgstr "" @@ -1427,7 +1576,7 @@ msgid "EventFilterBy|Filter by push events" msgstr "" msgid "EventFilterBy|Filter by team" -msgstr "" +msgstr "Filtrar por equipo" msgid "Every day (at 4:00am)" msgstr "Todos los días (a las 4:00 am)" @@ -1439,35 +1588,62 @@ msgid "Every week (Sundays at 4:00am)" msgstr "Todas las semanas (domingos a las 4:00 am)" msgid "Expand" -msgstr "" +msgstr "Expandir" msgid "Explore projects" -msgstr "" +msgstr "Explorar proyectos" msgid "Explore public groups" +msgstr "Explorar grupos públicos" + +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" msgstr "" msgid "Failed to change the owner" msgstr "Error al cambiar el propietario" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "Error al eliminar la programación del pipeline" +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "" msgid "February" -msgstr "" +msgstr "Febrero" msgid "Fields on this page are now uneditable, you can configure" msgstr "" msgid "File name" -msgstr "" +msgstr "Nombre de archivo" msgid "Files" msgstr "Archivos" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "Filtrar por mensaje del cambio" @@ -1495,7 +1671,7 @@ msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)" msgstr "" msgid "Format" -msgstr "" +msgstr "Formato" msgid "From issue creation until deploy to production" msgstr "Desde la creación de la incidencia hasta el despliegue a producción" @@ -1503,9 +1679,12 @@ msgstr "Desde la creación de la incidencia hasta el despliegue a producción" msgid "From merge request merge until deploy to production" msgstr "Desde la integración de la solicitud de fusión hasta el despliegue a producción" -msgid "GPG Keys" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" msgstr "" +msgid "GPG Keys" +msgstr "Llaves GPG" + msgid "Generate a default set of labels" msgstr "" @@ -1513,10 +1692,10 @@ msgid "Geo Nodes" msgstr "" msgid "GeoNodeSyncStatus|Node is failing or broken." -msgstr "" +msgstr "El nodo está fallando o dañado." msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." -msgstr "" +msgstr "El nodo está lento, sobrecargado o se esta recuperando de una interrupción." msgid "GeoNodes|Database replication lag:" msgstr "" @@ -1603,7 +1782,7 @@ msgid "Geo|All projects" msgstr "" msgid "Geo|File sync capacity" -msgstr "" +msgstr "Capacidad de sincronización de archivos" msgid "Geo|Groups to synchronize" msgstr "" @@ -1615,10 +1794,10 @@ msgid "Geo|Projects in certain storage shards" msgstr "" msgid "Geo|Repository sync capacity" -msgstr "" +msgstr "Capacidad de sincronización del Repositorio" msgid "Geo|Select groups to replicate." -msgstr "" +msgstr "Seleccionar grupos a replicar." msgid "Geo|Shards to synchronize" msgstr "" @@ -1633,7 +1812,7 @@ msgid "Git version" msgstr "" msgid "GitLab Runner section" -msgstr "" +msgstr "Sección GitLab Runner" msgid "Gitaly Servers" msgstr "" @@ -1645,14 +1824,32 @@ msgid "GoToYourFork|Fork" msgstr "Bifurcación" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." -msgstr "" +msgstr "La autenticación de Google no se encuentra %{link_to_documentation}. Pregúntale a tu administrador de GitLab si quieres usar este servicio." msgid "Got it!" msgstr "" -msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" msgstr "" +msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" +msgstr "Prevenir que se comparta un proyecto de %{group} con otros grupos" + msgid "GroupSettings|Share with group lock" msgstr "" @@ -1681,7 +1878,7 @@ msgid "GroupsEmptyState|If you organize your projects under a group, it works li msgstr "" msgid "GroupsEmptyState|No groups found" -msgstr "" +msgstr "No se encuentran grupos" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "" @@ -1690,31 +1887,31 @@ msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group msgstr "" msgid "GroupsTree|Create a project in this group." -msgstr "" +msgstr "Crear un proyecto en este grupo." msgid "GroupsTree|Create a subgroup in this group." -msgstr "" +msgstr "Crear un sub-grupo en este grupo." msgid "GroupsTree|Edit group" -msgstr "" +msgstr "Editar grupo" msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner." -msgstr "" +msgstr "No pudiste dejar el grupo. Por favor, asegúrate que no seas el único propietario." msgid "GroupsTree|Filter by name..." -msgstr "" +msgstr "Filtrar por nombre..." msgid "GroupsTree|Leave this group" -msgstr "" +msgstr "Dejar este grupo" msgid "GroupsTree|Loading groups" -msgstr "" +msgstr "Cargando grupos" msgid "GroupsTree|Sorry, no groups matched your search" -msgstr "" +msgstr "Lo sentimos, no existen grupos que coincidan con su búsqueda" msgid "GroupsTree|Sorry, no groups or projects matched your search" -msgstr "" +msgstr "Lo sentimos, no existen grupos ni proyectos que coincidan con su búsqueda" msgid "Have your users email" msgstr "" @@ -1726,16 +1923,16 @@ msgid "Health information can be retrieved from the following endpoints. More in msgstr "" msgid "HealthCheck|Access token is" -msgstr "" +msgstr "Token de Acceso es" msgid "HealthCheck|Healthy" -msgstr "" +msgstr "Saludable" msgid "HealthCheck|No Health Problems Detected" msgstr "" msgid "HealthCheck|Unhealthy" -msgstr "" +msgstr "Poco Saludable" msgid "Hide value" msgid_plural "Hide values" @@ -1743,30 +1940,36 @@ msgstr[0] "" msgstr[1] "" msgid "History" -msgstr "" +msgstr "Historial" msgid "Housekeeping successfully started" msgstr "Servicio de limpieza iniciado con éxito" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "Importar repositorio" msgid "Improve Issue boards with GitLab Enterprise Edition." -msgstr "" +msgstr "Mejore los tableros de Incidencias con GitLab Enterprise Edition." msgid "Improve issues management with Issue weight and GitLab Enterprise Edition." -msgstr "" +msgstr "Mejore la gestión de incidencias con Peso de Incidencias y GitLab Enterprise Edition." msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "" -msgid "Install a Runner compatible with GitLab CI" +msgid "Install Runner on Kubernetes" msgstr "" +msgid "Install a Runner compatible with GitLab CI" +msgstr "Instala un Runner compatible con GitLab CI" + msgid "Instance" msgid_plural "Instances" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Instancia" +msgstr[1] "Instancias" msgid "Instance does not support multiple Kubernetes clusters" msgstr "" @@ -1775,10 +1978,10 @@ msgid "Interested parties can even contribute by pushing commits if they want to msgstr "" msgid "Internal - The group and any internal projects can be viewed by any logged in user." -msgstr "" +msgstr "Interno - cualquier usuario que haya iniciado sesión puede ver el grupo y cualquier proyecto interno." msgid "Internal - The project can be accessed by any logged in user." -msgstr "" +msgstr "Interno - cualquier usuario haya iniciado sesión puede acceder a este proyecto." msgid "Interval Pattern" msgstr "Patrón de intervalo" @@ -1787,16 +1990,16 @@ msgid "Introducing Cycle Analytics" msgstr "Introducción a Cycle Analytics" msgid "Issue board focus mode" -msgstr "" +msgstr "Modo enfocado del Tablero de Incidencias" msgid "Issue events" -msgstr "" +msgstr "Eventos de incidencia" msgid "IssueBoards|Board" -msgstr "" +msgstr "Tablero" msgid "IssueBoards|Boards" -msgstr "" +msgstr "Tableros" msgid "Issues" msgstr "" @@ -1808,6 +2011,9 @@ msgid "Jan" msgstr "" msgid "January" +msgstr "Enero" + +msgid "Jobs" msgstr "" msgid "Jul" @@ -1820,7 +2026,7 @@ msgid "Jun" msgstr "" msgid "June" -msgstr "" +msgstr "Junio" msgid "Kubernetes" msgstr "" @@ -1840,6 +2046,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1870,13 +2079,13 @@ msgid "Last edited %{date}" msgstr "" msgid "Last edited by %{name}" -msgstr "" +msgstr "Última edición por %{name}" msgid "Last update" -msgstr "" +msgstr "Última actualización" msgid "Last updated" -msgstr "" +msgstr "Última actualización" msgid "LastPushEvent|You pushed to" msgstr "" @@ -1885,6 +2094,12 @@ msgid "LastPushEvent|at" msgstr "" msgid "Learn more" +msgstr "Conozca más" + +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" msgstr "" msgid "Learn more in the" @@ -1903,28 +2118,37 @@ msgid "Leave project" msgstr "Abandonar proyecto" msgid "License" +msgstr "Licencia" + +msgid "List" msgstr "" msgid "Loading the GitLab IDE..." msgstr "" msgid "Lock" -msgstr "" +msgstr "Bloquear" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" msgid "Locked" -msgstr "" +msgstr "Bloqueado" msgid "Locked Files" +msgstr "Archivos Bloqueados" + +msgid "Locks give the ability to lock specific file or folder." msgstr "" msgid "Login" -msgstr "" +msgstr "Iniciar sesión" msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." msgstr "" @@ -1936,7 +2160,7 @@ msgid "Mar" msgstr "" msgid "March" -msgstr "" +msgstr "Marzo" msgid "Mark done" msgstr "" @@ -1951,10 +2175,7 @@ msgid "Median" msgstr "Mediana" msgid "Members" -msgstr "" - -msgid "Merge Request" -msgstr "" +msgstr "Miembros" msgid "Merge Requests" msgstr "" @@ -1968,11 +2189,14 @@ msgstr "" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" msgid "Messages" -msgstr "" +msgstr "Mensajes" msgid "Milestone" msgstr "" @@ -1992,9 +2216,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "agregar una clave SSH" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "" @@ -2028,19 +2261,19 @@ msgid "New branch" msgstr "Nueva rama" msgid "New branch unavailable" -msgstr "" +msgstr "Nueva rama no disponible" msgid "New directory" msgstr "Nuevo directorio" msgid "New epic" -msgstr "" +msgstr "Nuevo epic" msgid "New file" msgstr "Nuevo archivo" msgid "New group" -msgstr "" +msgstr "Nuevo grupo" msgid "New issue" msgstr "Nueva incidencia" @@ -2052,7 +2285,7 @@ msgid "New merge request" msgstr "Nueva solicitud de fusión" msgid "New project" -msgstr "" +msgstr "Nuevo proyecto" msgid "New schedule" msgstr "Nueva programación" @@ -2061,7 +2294,7 @@ msgid "New snippet" msgstr "Nuevo fragmento de código" msgid "New subgroup" -msgstr "" +msgstr "Nuevo sub-grupo" msgid "New tag" msgstr "Nueva etiqueta" @@ -2090,11 +2323,8 @@ msgstr "No hay repositorio" msgid "No schedules" msgstr "No hay programaciones" -msgid "No time spent" -msgstr "" - msgid "None" -msgstr "" +msgstr "Ninguno" msgid "Not allowed to merge" msgstr "" @@ -2108,6 +2338,9 @@ msgstr "" msgid "Not enough data" msgstr "No hay suficientes datos" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "Eventos de notificación" @@ -2163,7 +2396,7 @@ msgid "NotificationLevel|Watch" msgstr "Vigilancia" msgid "Notifications" -msgstr "" +msgstr "Notificaciones" msgid "Notifications off" msgstr "" @@ -2175,7 +2408,7 @@ msgid "Nov" msgstr "" msgid "November" -msgstr "" +msgstr "Noviembre" msgid "Number of access attempts" msgstr "" @@ -2187,32 +2420,35 @@ msgid "Oct" msgstr "" msgid "October" -msgstr "" +msgstr "Octubre" msgid "OfSearchInADropdown|Filter" msgstr "Filtrar" msgid "Only project members can comment." -msgstr "" +msgstr "Sólo los miembros de proyecto pueden comentar." msgid "Open" msgstr "" msgid "Opened" -msgstr "" +msgstr "Abierto" msgid "OpenedNDaysAgo|Opened" msgstr "Abierto" msgid "Opens in a new window" -msgstr "" +msgstr "Abre en una nueva ventana" msgid "Options" msgstr "Opciones" -msgid "Overview" +msgid "Otherwise it is recommended you start with one of the options below." msgstr "" +msgid "Overview" +msgstr "Resumen" + msgid "Owner" msgstr "Propietario" @@ -2229,10 +2465,10 @@ msgid "Pagination|« First" msgstr "" msgid "Password" -msgstr "" +msgstr "Contraseña" msgid "Pipeline" -msgstr "" +msgstr "Pipeline" msgid "Pipeline Health" msgstr "Estado del Pipeline" @@ -2295,7 +2531,7 @@ msgid "PipelineSheduleIntervalPattern|Custom" msgstr "Personalizado" msgid "Pipelines" -msgstr "" +msgstr "Pipelines" msgid "Pipelines charts" msgstr "Gráficos de los pipelines" @@ -2315,6 +2551,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "todos" @@ -2337,19 +2591,22 @@ msgid "Please solve the reCAPTCHA" msgstr "" msgid "Preferences" -msgstr "" +msgstr "Preferencias" msgid "Primary" msgstr "" msgid "Private - Project access must be granted explicitly to each user." -msgstr "" +msgstr "Privado - El acceso al proyecto debe ser concedido explícitamente para cada usuario." msgid "Private - The group and its projects can only be viewed by members." +msgstr "Privado - El grupo y sus proyectos sólo pueden ser vistos por sus miembros." + +msgid "Private projects can be created in your personal namespace with:" msgstr "" msgid "Profile" -msgstr "" +msgstr "Perfil" msgid "Profiles|Account scheduled for removal." msgstr "" @@ -2367,31 +2624,31 @@ msgid "Profiles|Deleting an account has the following effects:" msgstr "" msgid "Profiles|Invalid password" -msgstr "" +msgstr "Contraseña inválida" msgid "Profiles|Invalid username" -msgstr "" +msgstr "Nombre de usuario inválido" msgid "Profiles|Type your %{confirmationValue} to confirm:" -msgstr "" +msgstr "Escribe tu %{confirmationValue} para confirmar:" msgid "Profiles|You don't have access to delete this user." -msgstr "" +msgstr "No tienes acceso para eliminar este usuario." msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account." -msgstr "" +msgstr "Debes transferir o eliminar estos grupos antes de que puedas eliminar tu cuenta." msgid "Profiles|Your account is currently an owner in these groups:" -msgstr "" +msgstr "Actualmente su cuenta es propietaria de estos grupos:" msgid "Profiles|your account" -msgstr "" +msgstr "tu cuenta" msgid "Programming languages used in this repository" msgstr "" msgid "Project '%{project_name}' is in the process of being deleted." -msgstr "" +msgstr "Proyecto '%{project_name}' está en proceso de ser eliminado." msgid "Project '%{project_name}' queued for deletion." msgstr "Proyecto ‘%{project_name}’ en cola para eliminación." @@ -2415,7 +2672,7 @@ msgid "Project cache successfully reset." msgstr "" msgid "Project details" -msgstr "" +msgstr "Detalles del Proyecto" msgid "Project export could not be deleted." msgstr "No se pudo eliminar la exportación del proyecto." @@ -2430,7 +2687,7 @@ msgid "Project export started. A download link will be sent by email." msgstr "Se inició la exportación del proyecto. Se enviará un enlace de descarga por correo electrónico." msgid "ProjectActivityRSS|Subscribe" -msgstr "" +msgstr "Suscribirse" msgid "ProjectCreationLevel|Allowed to create projects" msgstr "" @@ -2469,98 +2726,131 @@ msgid "ProjectNetworkGraph|Graph" msgstr "Historial gráfico" msgid "ProjectSettings|Contact an admin to change this setting." -msgstr "" +msgstr "Póngase en contacto con un administrador para cambiar esta opción." msgid "ProjectSettings|Only signed commits can be pushed to this repository." -msgstr "" +msgstr "Solo se pueden enviar commits firmados a este repositorio." msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." -msgstr "" +msgstr "Esta configuración se aplica a nivel del servidor y puede ser redefinida por un administrador." msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project." -msgstr "" +msgstr "Esta configuración se aplica a nivel del servidor pero se ha redefinido para este proyecto." msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin." -msgstr "" +msgstr "Esta configuración se aplicará a todos los proyectos a menos que sea redefinida por un administrador." msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails." msgstr "" msgid "Projects" -msgstr "" +msgstr "Proyectos" msgid "ProjectsDropdown|Frequently visited" msgstr "" msgid "ProjectsDropdown|Loading projects" -msgstr "" +msgstr "Cargando proyectos" msgid "ProjectsDropdown|Projects you visit often will appear here" msgstr "" msgid "ProjectsDropdown|Search your projects" -msgstr "" +msgstr "Busca en tus proyectos" msgid "ProjectsDropdown|Something went wrong on our end." -msgstr "" +msgstr "Algo salió mal por nuestra parte." msgid "ProjectsDropdown|Sorry, no projects matched your search" -msgstr "" +msgstr "Lo sentimos, no hay proyectos que coincidan con su búsqueda" msgid "ProjectsDropdown|This feature requires browser localStorage support" +msgstr "Esta función requiere que el navegador soporte localStorage" + +msgid "PrometheusService|Active" msgstr "" -msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." +msgid "PrometheusService|Auto configuration" msgstr "" +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + +msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." +msgstr "De manera predeterminada, Prometheus escucha en 'http://localhost:9090'. No se recomienda cambiar la dirección y el puerto predeterminados, ya que esto podría afectar o entrar en conflicto con otros servicios que se ejecutan en el servidor de GitLab." + msgid "PrometheusService|Finding and configuring metrics..." +msgstr "Encontrar y configurar métricas..." + +msgid "PrometheusService|Install Prometheus on clusters" msgstr "" -msgid "PrometheusService|Metrics" +msgid "PrometheusService|Manage clusters" msgstr "" -msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters." +msgid "PrometheusService|Manual configuration" msgstr "" +msgid "PrometheusService|Metrics" +msgstr "Métricas" + +msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters." +msgstr "Las métricas se configuran y supervisan automáticamente en función de una biblioteca de métricas de exportadores populares." + msgid "PrometheusService|Missing environment variable" -msgstr "" +msgstr "Falta la variable de entorno" msgid "PrometheusService|Monitored" -msgstr "" +msgstr "Monitoreado" msgid "PrometheusService|More information" -msgstr "" +msgstr "Más información" msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment." -msgstr "" +msgstr "No se están supervisando las métricas. Para comenzar a monitorear, despliega en un entorno." msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" +msgstr "URL Base de Prometheus, como http://prometheus.example.com/" + +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" msgstr "" msgid "PrometheusService|Time-series monitoring service" msgstr "" -msgid "PrometheusService|View environments" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" msgstr "" +msgid "PrometheusService|View environments" +msgstr "Ver entornos" + msgid "Protip:" msgstr "" msgid "Public - The group and any public projects can be viewed without any authentication." -msgstr "" +msgstr "Público - El grupo y cualquier proyecto público puede ser visto sin autenticación." msgid "Public - The project can be accessed without any authentication." -msgstr "" +msgstr "Público - El proyecto puede ser accedido sin ninguna autenticación." msgid "Push Rules" -msgstr "" +msgstr "Reglas Push" msgid "Push events" +msgstr "Eventos Push" + +msgid "Push project from command line" msgstr "" -msgid "PushRule|Committer restriction" +msgid "Push to create a project" msgstr "" +msgid "PushRule|Committer restriction" +msgstr "Restricción de Committer" + msgid "Quick actions can be used in the issues description and comment boxes." msgstr "" @@ -2583,7 +2873,7 @@ msgid "Register / Sign In" msgstr "" msgid "Registry" -msgstr "" +msgstr "Registro" msgid "Related Commits" msgstr "Cambios Relacionados" @@ -2619,6 +2909,9 @@ msgid "Repair authentication" msgstr "" msgid "Repository" +msgstr "Repositorio" + +msgid "Repository has no locks." msgstr "" msgid "Request Access" @@ -2631,6 +2924,9 @@ msgid "Reset health check access token" msgstr "" msgid "Reset runners registration token" +msgstr "Reinicializar el token de registro del runner" + +msgid "Resolve discussion" msgstr "" msgid "Reveal value" @@ -2644,11 +2940,14 @@ msgstr "Revertir este cambio" msgid "Revert this merge request" msgstr "Revertir esta solicitud de fusión" -msgid "SSH Keys" +msgid "Roadmap" msgstr "" +msgid "SSH Keys" +msgstr "Llaves SSH" + msgid "Save changes" -msgstr "" +msgstr "Guardar los cambios" msgid "Save pipeline schedule" msgstr "Guardar programación del pipeline" @@ -2681,20 +2980,26 @@ msgid "Search users" msgstr "" msgid "Seconds before reseting failure information" -msgstr "" +msgstr "Segundos antes de reinicializar información de fallas" msgid "Seconds to wait for a storage access attempt" -msgstr "" +msgstr "Segundos a esperar para intentar acceder al almacenamiento" msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "Seleccionar formato de archivo" msgid "Select a timezone" msgstr "Selecciona una zona horaria" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2707,16 +3012,22 @@ msgstr "Selecciona una rama de destino" msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "" msgid "September" -msgstr "" +msgstr "Septiembre" msgid "Server version" msgstr "" msgid "Service Templates" +msgstr "Plantillas de Servicio" + +msgid "Service URL" msgstr "" msgid "Set a password on your account to pull or push via %{protocol}." @@ -2728,13 +3039,13 @@ msgstr "" msgid "Set up Koding" msgstr "Configurar Koding" -msgid "Set up auto deploy" -msgstr "Configurar auto despliegue" - msgid "SetPasswordToCloneLink|set a password" msgstr "establecer una contraseña" msgid "Settings" +msgstr "Configuración" + +msgid "Setup a specific Runner automatically" msgstr "" msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." @@ -2746,9 +3057,12 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" -msgid "Show parent pages" +msgid "Show command" msgstr "" +msgid "Show parent pages" +msgstr "Mostrar páginas padre" + msgid "Show parent subgroups" msgstr "" @@ -2787,17 +3101,29 @@ msgstr "" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" msgid "Sort by" -msgstr "" +msgstr "Ordenar por" msgid "SortOptions|Access level, ascending" msgstr "" @@ -2896,6 +3222,9 @@ msgid "SortOptions|Weight" msgstr "" msgid "Source" +msgstr "Fuente" + +msgid "Source (branch or tag)" msgstr "" msgid "Source code" @@ -2914,7 +3243,7 @@ msgid "StarProject|Star" msgstr "Destacar" msgid "Starred projects" -msgstr "" +msgstr "Proyectos favoritos" msgid "Start a %{new_merge_request} with these changes" msgstr "Iniciar una %{new_merge_request} con estos cambios" @@ -2923,24 +3252,24 @@ msgid "Start the Runner!" msgstr "" msgid "Stopped" -msgstr "" +msgstr "Detenido" msgid "Storage" msgstr "" msgid "Subgroups" -msgstr "" +msgstr "Sub-grupos" msgid "Switch branch/tag" msgstr "Cambiar rama/etiqueta" msgid "System Hooks" -msgstr "" +msgstr "Hooks de sistema" -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "Etiqueta" -msgstr[1] "Etiquetas" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "" +msgstr[1] "" msgid "Tags" msgstr "Etiquetas" @@ -3018,10 +3347,10 @@ msgid "Target Branch" msgstr "Rama de destino" msgid "Team" -msgstr "" +msgstr "Equipo" msgid "Thanks! Don't show me this again" -msgstr "" +msgstr "Gracias! No mostrar esto nuevamente" msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." msgstr "" @@ -3071,9 +3400,15 @@ msgstr "El proyecto puede accederse sin ninguna autenticación." msgid "The repository for this project does not exist." msgstr "El repositorio para este proyecto no existe." +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "La etapa de revisión muestra el tiempo desde la creación de la solicitud de fusión hasta que los cambios se fusionaron. Los datos se añadirán automáticamente después de fusionar su primera solicitud de fusión." +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "La etapa de puesta en escena muestra el tiempo entre la fusión y el despliegue de código en el entorno de producción. Los datos se añadirán automáticamente una vez que se despliega a producción por primera vez." @@ -3167,6 +3502,9 @@ msgstr "Esto significa que no puede enviar código hasta que cree un repositorio msgid "This merge request is locked." msgstr "" +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3336,7 +3674,13 @@ msgstr[1] "mins" msgid "Time|s" msgstr "s" +msgid "Tip:" +msgstr "" + msgid "Title" +msgstr "Título" + +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." msgstr "" msgid "Todo" @@ -3354,19 +3698,16 @@ msgstr "" msgid "Total Time" msgstr "Tiempo Total" -msgid "Total issue time spent" -msgstr "" - msgid "Total test time for all commits/merges" msgstr "Tiempo total de pruebas para todos los cambios o integraciones" -msgid "Track activity with Contribution Analytics." +msgid "Total: %{total}" msgstr "" -msgid "Track groups of issues that share a theme, across projects and milestones" +msgid "Track activity with Contribution Analytics." msgstr "" -msgid "Total: %{total}" +msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" msgid "Track time with quick actions" @@ -3378,9 +3719,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3388,12 +3726,15 @@ msgid "Unknown" msgstr "" msgid "Unlock" -msgstr "" +msgstr "Desbloquear" msgid "Unlock this %{issuableDisplayName}? Everyone will be able to comment." msgstr "" msgid "Unlocked" +msgstr "Desbloqueado" + +msgid "Unresolve discussion" msgstr "" msgid "Unstar" @@ -3441,9 +3782,12 @@ msgstr "Utiliza tu configuración de notificación global" msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" -msgid "View file @ " +msgid "View epics list" msgstr "" +msgid "View file @ " +msgstr "Ver archivo @ " + msgid "View labels" msgstr "" @@ -3451,7 +3795,7 @@ msgid "View open merge request" msgstr "Ver solicitud de fusión abierta" msgid "View replaced file @ " -msgstr "" +msgstr "Ver archivo reemplazado @ " msgid "VisibilityLevel|Internal" msgstr "Interno" @@ -3477,14 +3821,17 @@ msgstr "No hay suficientes datos para mostrar en esta etapa." msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "" +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "" msgid "Weight" -msgstr "" +msgstr "Peso" msgid "Wiki" -msgstr "" +msgstr "Wiki" msgid "WikiClone|Clone your wiki" msgstr "" @@ -3597,6 +3944,9 @@ msgstr "" msgid "Withdraw Access Request" msgstr "Retirar Solicitud de Acceso" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "Va a eliminar %{group_name}. ¡El grupo eliminado NO puede ser restaurado! ¿Estás TOTALMENTE seguro?" @@ -3609,10 +3959,13 @@ msgstr "Vas a eliminar el enlace de la bifurcación con el proyecto original %{f msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Vas a transferir %{project_name_with_namespace} a otro propietario. ¿Estás TOTALMENTE seguro?" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3630,12 +3983,24 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "Has alcanzado el límite de tu proyecto" +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "Debes iniciar sesión para destacar un proyecto" +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "Necesitas permisos." @@ -3667,28 +4032,31 @@ msgid "You'll need to use different branch names to get a valid comparison." msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "La información del cluster de Kubernetes en esta página aún se puede editar, pero se recomienda desactivarla y modificar" + +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" msgstr "" msgid "Your comment will not be visible to the public." -msgstr "" +msgstr "Tus comentarios no serán visibles al público." msgid "Your groups" -msgstr "" +msgstr "Tus grupos" msgid "Your name" msgstr "Tu nombre" msgid "Your projects" -msgstr "" +msgstr "Tus proyectos" msgid "assign yourself" msgstr "" msgid "branch name" -msgstr "" +msgstr "nombre de la rama" msgid "by" -msgstr "" +msgstr "por" msgid "ciReport|Code quality" msgstr "" @@ -3696,7 +4064,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3708,7 +4076,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3723,6 +4091,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3735,9 +4112,15 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" -msgid "commit" +msgid "ciReport|no security vulnerabilities" msgstr "" +msgid "command line instructions" +msgstr "" + +msgid "commit" +msgstr "commit" + msgid "confidentiality|You are going to turn off the confidentiality. This means everyone will be able to see and leave a comment on this issue." msgstr "" @@ -3752,11 +4135,41 @@ msgstr[1] "días" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" msgstr[1] "" +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3790,6 +4203,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3823,6 +4239,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3877,6 +4296,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3901,10 +4323,10 @@ msgstr[0] "padre" msgstr[1] "padres" msgid "password" -msgstr "" +msgstr "contraseña" msgid "personal access token" -msgstr "" +msgstr "token de acceso personal" msgid "remove due date" msgstr "" @@ -3916,11 +4338,14 @@ msgid "spendCommand|%{slash_command} will update the sum of the time spent." msgstr "" msgid "to help your contributors communicate effectively!" -msgstr "" +msgstr "para ayudar a sus colaboradores a comunicarse de manera efectiva!" msgid "username" -msgstr "" +msgstr "usuario" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/fil_PH/gitlab.po b/locale/fil_PH/gitlab.po new file mode 100644 index 00000000000..1037ec675e9 --- /dev/null +++ b/locale/fil_PH/gitlab.po @@ -0,0 +1,4351 @@ +msgid "" +msgstr "" +"Project-Id-Version: gitlab-ee\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:19-0500\n" +"Last-Translator: gitlab \n" +"Language-Team: Filipino\n" +"Language: fil_PH\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: crowdin.com\n" +"X-Crowdin-Project: gitlab-ee\n" +"X-Crowdin-Language: fil\n" +"X-Crowdin-File: /master/locale/gitlab.pot\n" + +msgid " and" +msgstr "" + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "" +msgstr[1] "" + +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" +msgstr[1] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" +msgstr[1] "" + +msgid "%d layer" +msgid_plural "%d layers" +msgstr[0] "" +msgstr[1] "" + +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "%s additional commits have been omitted to prevent performance issues." +msgstr[0] "" +msgstr[1] "" + +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" + +msgid "%{count} participant" +msgid_plural "%{count} participants" +msgstr[0] "" +msgstr[1] "" + +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + +msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" +msgstr "" + +msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." +msgstr "" + +msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." +msgstr "" + +msgid "%{openOrClose} %{noteable}" +msgstr "" + +msgid "%{storage_name}: failed storage access attempt on host:" +msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" +msgstr[0] "" +msgstr[1] "" + +msgid "%{text} is available" +msgstr "" + +msgid "(checkout the %{link} for information on how to install it)." +msgstr "" + +msgid "+ %{moreCount} more" +msgstr "" + +msgid "- show less" +msgstr "" + +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "" +msgstr[1] "" + +msgid "1st contribution!" +msgstr "" + +msgid "2FA enabled" +msgstr "" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "" + +msgid "About auto deploy" +msgstr "" + +msgid "Abuse Reports" +msgstr "" + +msgid "Access Tokens" +msgstr "" + +msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." +msgstr "" + +msgid "Account" +msgstr "" + +msgid "Active" +msgstr "" + +msgid "Activity" +msgstr "" + +msgid "Add" +msgstr "" + +msgid "Add Changelog" +msgstr "" + +msgid "Add Contribution guide" +msgstr "" + +msgid "Add Group Webhooks and GitLab Enterprise Edition." +msgstr "" + +msgid "Add Kubernetes cluster" +msgstr "" + +msgid "Add License" +msgstr "" + +msgid "Add Readme" +msgstr "" + +msgid "Add new directory" +msgstr "" + +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." +msgstr "" + +msgid "AdminHealthPageLink|health page" +msgstr "" + +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + +msgid "Advanced" +msgstr "" + +msgid "Advanced settings" +msgstr "" + +msgid "All" +msgstr "" + +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + +msgid "An error occurred when toggling the notification subscription" +msgstr "" + +msgid "An error occurred when updating the issue weight" +msgstr "" + +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + +msgid "An error occurred while fetching sidebar data" +msgstr "" + +msgid "An error occurred while fetching the pipeline." +msgstr "" + +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + +msgid "An error occurred. Please try again." +msgstr "" + +msgid "Appearance" +msgstr "" + +msgid "Applications" +msgstr "" + +msgid "Apr" +msgstr "" + +msgid "April" +msgstr "" + +msgid "Archived project! Repository is read-only" +msgstr "" + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "" + +msgid "Are you sure you want to reset registration token?" +msgstr "" + +msgid "Are you sure you want to reset the health check token?" +msgstr "" + +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + +msgid "Are you sure?" +msgstr "" + +msgid "Artifacts" +msgstr "" + +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "" + +msgid "Aug" +msgstr "" + +msgid "August" +msgstr "" + +msgid "Authentication Log" +msgstr "" + +msgid "Author" +msgstr "" + +msgid "Authors: %{authors}" +msgstr "" + +msgid "Auto DevOps enabled" +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." +msgstr "" + +msgid "AutoDevOps|Auto DevOps (Beta)" +msgstr "" + +msgid "AutoDevOps|Auto DevOps documentation" +msgstr "" + +msgid "AutoDevOps|Enable in settings" +msgstr "" + +msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration." +msgstr "" + +msgid "AutoDevOps|Learn more in the %{link_to_documentation}" +msgstr "" + +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" +msgstr "" + +msgid "Available" +msgstr "" + +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + +msgid "Begin with the selected commit" +msgstr "" + +msgid "Billing" +msgstr "" + +msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan." +msgstr "" + +msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available." +msgstr "" + +msgid "BillingPlans|Current plan" +msgstr "" + +msgid "BillingPlans|Customer Support" +msgstr "" + +msgid "BillingPlans|Downgrade" +msgstr "" + +msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}." +msgstr "" + +msgid "BillingPlans|Manage plan" +msgstr "" + +msgid "BillingPlans|Please contact %{customer_support_link} in that case." +msgstr "" + +msgid "BillingPlans|See all %{plan_name} features" +msgstr "" + +msgid "BillingPlans|This group uses the plan associated with its parent group." +msgstr "" + +msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}." +msgstr "" + +msgid "BillingPlans|Upgrade" +msgstr "" + +msgid "BillingPlans|You are currently on the %{plan_link} plan." +msgstr "" + +msgid "BillingPlans|frequently asked questions" +msgstr "" + +msgid "BillingPlans|monthly" +msgstr "" + +msgid "BillingPlans|paid annually at %{price_per_year}" +msgstr "" + +msgid "BillingPlans|per user" +msgstr "" + +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "" +msgstr[1] "" + +msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" +msgstr "" + +msgid "Branch has changed" +msgstr "" + +msgid "Branch is already taken" +msgstr "" + +msgid "Branch name" +msgstr "" + +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "" + +msgid "Branches" +msgstr "" + +msgid "Branches|Cant find HEAD commit for this branch" +msgstr "" + +msgid "Branches|Compare" +msgstr "" + +msgid "Branches|Delete all branches that are merged into '%{default_branch}'" +msgstr "" + +msgid "Branches|Delete branch" +msgstr "" + +msgid "Branches|Delete merged branches" +msgstr "" + +msgid "Branches|Delete protected branch" +msgstr "" + +msgid "Branches|Delete protected branch '%{branch_name}'?" +msgstr "" + +msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?" +msgstr "" + +msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?" +msgstr "" + +msgid "Branches|Filter by branch name" +msgstr "" + +msgid "Branches|Merged into %{default_branch}" +msgstr "" + +msgid "Branches|New branch" +msgstr "" + +msgid "Branches|No branches to show" +msgstr "" + +msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered." +msgstr "" + +msgid "Branches|Only a project master or owner can delete a protected branch" +msgstr "" + +msgid "Branches|Protected branches can be managed in %{project_settings_link}" +msgstr "" + +msgid "Branches|Sort by" +msgstr "" + +msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart." +msgstr "" + +msgid "Branches|The default branch cannot be deleted" +msgstr "" + +msgid "Branches|This branch hasn’t been merged into %{default_branch}." +msgstr "" + +msgid "Branches|To avoid data loss, consider merging this branch before deleting it." +msgstr "" + +msgid "Branches|To confirm, type %{branch_name_confirmation}:" +msgstr "" + +msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above." +msgstr "" + +msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}." +msgstr "" + +msgid "Branches|diverged from upstream" +msgstr "" + +msgid "Branches|merged" +msgstr "" + +msgid "Branches|project settings" +msgstr "" + +msgid "Branches|protected" +msgstr "" + +msgid "Browse Directory" +msgstr "" + +msgid "Browse File" +msgstr "" + +msgid "Browse Files" +msgstr "" + +msgid "Browse files" +msgstr "" + +msgid "ByAuthor|by" +msgstr "" + +msgid "CI / CD" +msgstr "" + +msgid "CI/CD configuration" +msgstr "" + +msgid "CICD|Jobs" +msgstr "" + +msgid "Cancel" +msgstr "" + +msgid "Cancel edit" +msgstr "" + +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + +msgid "Change Weight" +msgstr "" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "" + +msgid "ChangeTypeAction|Revert" +msgstr "" + +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + +msgid "Changelog" +msgstr "" + +msgid "Changes are shown as if the source revision was being merged into the target revision." +msgstr "" + +msgid "Charts" +msgstr "" + +msgid "Chat" +msgstr "" + +msgid "Check interval" +msgstr "" + +msgid "Checking %{text} availability…" +msgstr "" + +msgid "Checking branch availability..." +msgstr "" + +msgid "Cherry-pick this commit" +msgstr "" + +msgid "Cherry-pick this merge request" +msgstr "" + +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." +msgstr "" + +msgid "CiStatusLabel|canceled" +msgstr "" + +msgid "CiStatusLabel|created" +msgstr "" + +msgid "CiStatusLabel|failed" +msgstr "" + +msgid "CiStatusLabel|manual action" +msgstr "" + +msgid "CiStatusLabel|passed" +msgstr "" + +msgid "CiStatusLabel|passed with warnings" +msgstr "" + +msgid "CiStatusLabel|pending" +msgstr "" + +msgid "CiStatusLabel|skipped" +msgstr "" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "" + +msgid "CiStatusText|blocked" +msgstr "" + +msgid "CiStatusText|canceled" +msgstr "" + +msgid "CiStatusText|created" +msgstr "" + +msgid "CiStatusText|failed" +msgstr "" + +msgid "CiStatusText|manual" +msgstr "" + +msgid "CiStatusText|passed" +msgstr "" + +msgid "CiStatusText|pending" +msgstr "" + +msgid "CiStatusText|skipped" +msgstr "" + +msgid "CiStatus|running" +msgstr "" + +msgid "CiVariables|Input variable key" +msgstr "" + +msgid "CiVariables|Input variable value" +msgstr "" + +msgid "CiVariables|Remove variable row" +msgstr "" + +msgid "CiVariable|* (All environments)" +msgstr "" + +msgid "CiVariable|All environments" +msgstr "" + +msgid "CiVariable|Create wildcard" +msgstr "" + +msgid "CiVariable|Error occured while saving variables" +msgstr "" + +msgid "CiVariable|New environment" +msgstr "" + +msgid "CiVariable|Protected" +msgstr "" + +msgid "CiVariable|Search environments" +msgstr "" + +msgid "CiVariable|Toggle protected" +msgstr "" + +msgid "CiVariable|Validation failed" +msgstr "" + +msgid "CircuitBreakerApiLink|circuitbreaker api" +msgstr "" + +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + +msgid "Click to expand text" +msgstr "" + +msgid "Clone repository" +msgstr "" + +msgid "Close" +msgstr "" + +msgid "Closed" +msgstr "" + +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|API URL" +msgstr "" + +msgid "ClusterIntegration|Add Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Add an existing Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "" + +msgid "ClusterIntegration|Applications" +msgstr "" + +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|CA Certificate" +msgstr "" + +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgstr "" + +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" +msgstr "" + +msgid "ClusterIntegration|Copy API URL" +msgstr "" + +msgid "ClusterIntegration|Copy CA Certificate" +msgstr "" + +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + +msgid "ClusterIntegration|Copy Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Copy Token" +msgstr "" + +msgid "ClusterIntegration|Create Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" +msgstr "" + +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" +msgstr "" + +msgid "ClusterIntegration|Create on GKE" +msgstr "" + +msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Environment scope" +msgstr "" + +msgid "ClusterIntegration|GitLab Integration" +msgstr "" + +msgid "ClusterIntegration|GitLab Runner" +msgstr "" + +msgid "ClusterIntegration|Google Cloud Platform project ID" +msgstr "" + +msgid "ClusterIntegration|Google Kubernetes Engine" +msgstr "" + +msgid "ClusterIntegration|Google Kubernetes Engine project" +msgstr "" + +msgid "ClusterIntegration|Helm Tiller" +msgstr "" + +msgid "ClusterIntegration|Ingress" +msgstr "" + +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + +msgid "ClusterIntegration|Install" +msgstr "" + +msgid "ClusterIntegration|Installed" +msgstr "" + +msgid "ClusterIntegration|Installing" +msgstr "" + +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" +msgstr "" + +msgid "ClusterIntegration|Learn more about %{link_to_documentation}" +msgstr "" + +msgid "ClusterIntegration|Learn more about environments" +msgstr "" + +msgid "ClusterIntegration|Machine type" +msgstr "" + +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" +msgstr "" + +msgid "ClusterIntegration|Manage" +msgstr "" + +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" +msgstr "" + +msgid "ClusterIntegration|More information" +msgstr "" + +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" +msgstr "" + +msgid "ClusterIntegration|Note:" +msgstr "" + +msgid "ClusterIntegration|Number of nodes" +msgstr "" + +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" +msgstr "" + +msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgstr "" + +msgid "ClusterIntegration|Project ID" +msgstr "" + +msgid "ClusterIntegration|Project namespace" +msgstr "" + +msgid "ClusterIntegration|Project namespace (optional, unique)" +msgstr "" + +msgid "ClusterIntegration|Prometheus" +msgstr "" + +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Remove integration" +msgstr "" + +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|Request to begin installing failed" +msgstr "" + +msgid "ClusterIntegration|Save changes" +msgstr "" + +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|See machine types" +msgstr "" + +msgid "ClusterIntegration|See your projects" +msgstr "" + +msgid "ClusterIntegration|See zones" +msgstr "" + +msgid "ClusterIntegration|Service token" +msgstr "" + +msgid "ClusterIntegration|Show" +msgstr "" + +msgid "ClusterIntegration|Something went wrong on our end." +msgstr "" + +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" +msgstr "" + +msgid "ClusterIntegration|Something went wrong while installing %{title}" +msgstr "" + +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" +msgstr "" + +msgid "ClusterIntegration|Toggle Kubernetes Cluster" +msgstr "" + +msgid "ClusterIntegration|Toggle Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Token" +msgstr "" + +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgstr "" + +msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" +msgstr "" + +msgid "ClusterIntegration|Zone" +msgstr "" + +msgid "ClusterIntegration|access to Google Kubernetes Engine" +msgstr "" + +msgid "ClusterIntegration|check the pricing here" +msgstr "" + +msgid "ClusterIntegration|documentation" +msgstr "" + +msgid "ClusterIntegration|help page" +msgstr "" + +msgid "ClusterIntegration|installing applications" +msgstr "" + +msgid "ClusterIntegration|meets the requirements" +msgstr "" + +msgid "ClusterIntegration|properly configured" +msgstr "" + +msgid "Collapse" +msgstr "" + +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + +msgid "Comments" +msgstr "" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "" +msgstr[1] "" + +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" +msgstr[1] "" + +msgid "Commit Message" +msgstr "" + +msgid "Commit duration in minutes for last 30 commits" +msgstr "" + +msgid "Commit message" +msgstr "" + +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + +msgid "Commit to %{branchName} branch" +msgstr "" + +msgid "CommitBoxTitle|Commit" +msgstr "" + +msgid "CommitMessage|Add %{file_name}" +msgstr "" + +msgid "Commits" +msgstr "" + +msgid "Commits feed" +msgstr "" + +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + +msgid "Commits|History" +msgstr "" + +msgid "Commits|No related merge requests found" +msgstr "" + +msgid "Committed by" +msgstr "" + +msgid "Compare" +msgstr "" + +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + +msgid "Container Registry" +msgstr "" + +msgid "ContainerRegistry|Created" +msgstr "" + +msgid "ContainerRegistry|First log in to GitLab’s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:" +msgstr "" + +msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:" +msgstr "" + +msgid "ContainerRegistry|How to use the Container Registry" +msgstr "" + +msgid "ContainerRegistry|Learn more about" +msgstr "" + +msgid "ContainerRegistry|No tags in Container Registry for this container image." +msgstr "" + +msgid "ContainerRegistry|Once you log in, you’re free to create and upload a container image using the common %{build} and %{push} commands" +msgstr "" + +msgid "ContainerRegistry|Remove repository" +msgstr "" + +msgid "ContainerRegistry|Remove tag" +msgstr "" + +msgid "ContainerRegistry|Size" +msgstr "" + +msgid "ContainerRegistry|Tag" +msgstr "" + +msgid "ContainerRegistry|Tag ID" +msgstr "" + +msgid "ContainerRegistry|Use different image names" +msgstr "" + +msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images." +msgstr "" + +msgid "Contribution guide" +msgstr "" + +msgid "Contributors" +msgstr "" + +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + +msgid "ContributorsPage|Building repository graph." +msgstr "" + +msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits." +msgstr "" + +msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready." +msgstr "" + +msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node" +msgstr "" + +msgid "Control the maximum concurrency of repository backfill for this secondary node" +msgstr "" + +msgid "Copy SSH public key to clipboard" +msgstr "" + +msgid "Copy URL to clipboard" +msgstr "" + +msgid "Copy branch name to clipboard" +msgstr "" + +msgid "Copy command to clipboard" +msgstr "" + +msgid "Copy commit SHA to clipboard" +msgstr "" + +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + +msgid "Create New Directory" +msgstr "" + +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + +msgid "Create a personal access token on your account to pull or push via %{protocol}." +msgstr "" + +msgid "Create branch" +msgstr "" + +msgid "Create directory" +msgstr "" + +msgid "Create empty bare repository" +msgstr "" + +msgid "Create epic" +msgstr "" + +msgid "Create file" +msgstr "" + +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + +msgid "Create merge request" +msgstr "" + +msgid "Create merge request and branch" +msgstr "" + +msgid "Create new branch" +msgstr "" + +msgid "Create new directory" +msgstr "" + +msgid "Create new file" +msgstr "" + +msgid "Create new label" +msgstr "" + +msgid "Create new..." +msgstr "" + +msgid "CreateNewFork|Fork" +msgstr "" + +msgid "CreateTag|Tag" +msgstr "" + +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "" + +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + +msgid "Creating epic" +msgstr "" + +msgid "Cron Timezone" +msgstr "" + +msgid "Cron syntax" +msgstr "" + +msgid "Current node" +msgstr "" + +msgid "Custom notification events" +msgstr "" + +msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}." +msgstr "" + +msgid "Cycle Analytics" +msgstr "" + +msgid "CycleAnalyticsStage|Code" +msgstr "" + +msgid "CycleAnalyticsStage|Issue" +msgstr "" + +msgid "CycleAnalyticsStage|Plan" +msgstr "" + +msgid "CycleAnalyticsStage|Production" +msgstr "" + +msgid "CycleAnalyticsStage|Review" +msgstr "" + +msgid "CycleAnalyticsStage|Staging" +msgstr "" + +msgid "CycleAnalyticsStage|Test" +msgstr "" + +msgid "DashboardProjects|All" +msgstr "" + +msgid "DashboardProjects|Personal" +msgstr "" + +msgid "Dec" +msgstr "" + +msgid "December" +msgstr "" + +msgid "Default classification label" +msgstr "" + +msgid "Define a custom pattern with cron syntax" +msgstr "" + +msgid "Delete" +msgstr "" + +msgid "Deploy" +msgid_plural "Deploys" +msgstr[0] "" +msgstr[1] "" + +msgid "Deploy Keys" +msgstr "" + +msgid "Description" +msgstr "" + +msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project." +msgstr "" + +msgid "Details" +msgstr "" + +msgid "Diffs|No file name available" +msgstr "" + +msgid "Directory name" +msgstr "" + +msgid "Disable" +msgstr "" + +msgid "Discard draft" +msgstr "" + +msgid "Discover GitLab Geo." +msgstr "" + +msgid "Dismiss Cycle Analytics introduction box" +msgstr "" + +msgid "Dismiss Merge Request promotion" +msgstr "" + +msgid "Don't show again" +msgstr "" + +msgid "Download" +msgstr "" + +msgid "Download tar" +msgstr "" + +msgid "Download tar.bz2" +msgstr "" + +msgid "Download tar.gz" +msgstr "" + +msgid "Download zip" +msgstr "" + +msgid "DownloadArtifacts|Download" +msgstr "" + +msgid "DownloadCommit|Email Patches" +msgstr "" + +msgid "DownloadCommit|Plain Diff" +msgstr "" + +msgid "DownloadSource|Download" +msgstr "" + +msgid "Due date" +msgstr "" + +msgid "Edit" +msgstr "" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "" + +msgid "Edit files in the editor and commit changes here" +msgstr "" + +msgid "Emails" +msgstr "" + +msgid "Enable" +msgstr "" + +msgid "Enable Auto DevOps" +msgstr "" + +msgid "Environments|An error occurred while fetching the environments." +msgstr "" + +msgid "Environments|An error occurred while making the request." +msgstr "" + +msgid "Environments|Commit" +msgstr "" + +msgid "Environments|Deployment" +msgstr "" + +msgid "Environments|Environment" +msgstr "" + +msgid "Environments|Environments" +msgstr "" + +msgid "Environments|Job" +msgstr "" + +msgid "Environments|New environment" +msgstr "" + +msgid "Environments|No deployments yet" +msgstr "" + +msgid "Environments|Open" +msgstr "" + +msgid "Environments|Re-deploy" +msgstr "" + +msgid "Environments|Read more about environments" +msgstr "" + +msgid "Environments|Rollback" +msgstr "" + +msgid "Environments|Show all" +msgstr "" + +msgid "Environments|Updated" +msgstr "" + +msgid "Environments|You don't have any environments right now." +msgstr "" + +msgid "Epic will be removed! Are you sure?" +msgstr "" + +msgid "Epics" +msgstr "" + +msgid "Epics Roadmap" +msgstr "" + +msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + +msgid "Error creating epic" +msgstr "" + +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + +msgid "Error occurred when toggling the notification subscription" +msgstr "" + +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + +msgid "EventFilterBy|Filter by all" +msgstr "" + +msgid "EventFilterBy|Filter by comments" +msgstr "" + +msgid "EventFilterBy|Filter by issue events" +msgstr "" + +msgid "EventFilterBy|Filter by merge events" +msgstr "" + +msgid "EventFilterBy|Filter by push events" +msgstr "" + +msgid "EventFilterBy|Filter by team" +msgstr "" + +msgid "Every day (at 4:00am)" +msgstr "" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "" + +msgid "Every week (Sundays at 4:00am)" +msgstr "" + +msgid "Expand" +msgstr "" + +msgid "Explore projects" +msgstr "" + +msgid "Explore public groups" +msgstr "" + +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + +msgid "Failed to change the owner" +msgstr "" + +msgid "Failed to remove issue from board, please try again." +msgstr "" + +msgid "Failed to remove the pipeline schedule" +msgstr "" + +msgid "Failed to update issues, please try again." +msgstr "" + +msgid "Feb" +msgstr "" + +msgid "February" +msgstr "" + +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + +msgid "File name" +msgstr "" + +msgid "Files" +msgstr "" + +msgid "Files (%{human_size})" +msgstr "" + +msgid "Filter by commit message" +msgstr "" + +msgid "Find by path" +msgstr "" + +msgid "Find file" +msgstr "" + +msgid "FirstPushedBy|First" +msgstr "" + +msgid "FirstPushedBy|pushed by" +msgstr "" + +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "" +msgstr[1] "" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "" + +msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)" +msgstr "" + +msgid "Format" +msgstr "" + +msgid "From issue creation until deploy to production" +msgstr "" + +msgid "From merge request merge until deploy to production" +msgstr "" + +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + +msgid "GPG Keys" +msgstr "" + +msgid "Generate a default set of labels" +msgstr "" + +msgid "Geo Nodes" +msgstr "" + +msgid "GeoNodeSyncStatus|Node is failing or broken." +msgstr "" + +msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." +msgstr "" + +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" +msgstr "" + +msgid "Geo|File sync capacity" +msgstr "" + +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" +msgstr "" + +msgid "Geo|Repository sync capacity" +msgstr "" + +msgid "Geo|Select groups to replicate." +msgstr "" + +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + +msgid "Git storage health information has been reset" +msgstr "" + +msgid "Git version" +msgstr "" + +msgid "GitLab Runner section" +msgstr "" + +msgid "Gitaly Servers" +msgstr "" + +msgid "Go to your fork" +msgstr "" + +msgid "GoToYourFork|Fork" +msgstr "" + +msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." +msgstr "" + +msgid "Got it!" +msgstr "" + +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + +msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" +msgstr "" + +msgid "GroupSettings|Share with group lock" +msgstr "" + +msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup." +msgstr "" + +msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}." +msgstr "" + +msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}." +msgstr "" + +msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually." +msgstr "" + +msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group" +msgstr "" + +msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}" +msgstr "" + +msgid "GroupsEmptyState|A group is a collection of several projects." +msgstr "" + +msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder." +msgstr "" + +msgid "GroupsEmptyState|No groups found" +msgstr "" + +msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." +msgstr "" + +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" +msgstr "" + +msgid "GroupsTree|Create a project in this group." +msgstr "" + +msgid "GroupsTree|Create a subgroup in this group." +msgstr "" + +msgid "GroupsTree|Edit group" +msgstr "" + +msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner." +msgstr "" + +msgid "GroupsTree|Filter by name..." +msgstr "" + +msgid "GroupsTree|Leave this group" +msgstr "" + +msgid "GroupsTree|Loading groups" +msgstr "" + +msgid "GroupsTree|Sorry, no groups matched your search" +msgstr "" + +msgid "GroupsTree|Sorry, no groups or projects matched your search" +msgstr "" + +msgid "Have your users email" +msgstr "" + +msgid "Health Check" +msgstr "" + +msgid "Health information can be retrieved from the following endpoints. More information is available" +msgstr "" + +msgid "HealthCheck|Access token is" +msgstr "" + +msgid "HealthCheck|Healthy" +msgstr "" + +msgid "HealthCheck|No Health Problems Detected" +msgstr "" + +msgid "HealthCheck|Unhealthy" +msgstr "" + +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" + +msgid "History" +msgstr "" + +msgid "Housekeeping successfully started" +msgstr "" + +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + +msgid "Import repository" +msgstr "" + +msgid "Improve Issue boards with GitLab Enterprise Edition." +msgstr "" + +msgid "Improve issues management with Issue weight and GitLab Enterprise Edition." +msgstr "" + +msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." +msgstr "" + +msgid "Install Runner on Kubernetes" +msgstr "" + +msgid "Install a Runner compatible with GitLab CI" +msgstr "" + +msgid "Instance" +msgid_plural "Instances" +msgstr[0] "" +msgstr[1] "" + +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + +msgid "Internal - The group and any internal projects can be viewed by any logged in user." +msgstr "" + +msgid "Internal - The project can be accessed by any logged in user." +msgstr "" + +msgid "Interval Pattern" +msgstr "" + +msgid "Introducing Cycle Analytics" +msgstr "" + +msgid "Issue board focus mode" +msgstr "" + +msgid "Issue events" +msgstr "" + +msgid "IssueBoards|Board" +msgstr "" + +msgid "IssueBoards|Boards" +msgstr "" + +msgid "Issues" +msgstr "" + +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + +msgid "Jan" +msgstr "" + +msgid "January" +msgstr "" + +msgid "Jobs" +msgstr "" + +msgid "Jul" +msgstr "" + +msgid "July" +msgstr "" + +msgid "Jun" +msgstr "" + +msgid "June" +msgstr "" + +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes configured" +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" +msgstr "" + +msgid "LFSStatus|Disabled" +msgstr "" + +msgid "LFSStatus|Enabled" +msgstr "" + +msgid "Labels" +msgstr "" + +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + +msgid "Last %d day" +msgid_plural "Last %d days" +msgstr[0] "" +msgstr[1] "" + +msgid "Last Pipeline" +msgstr "" + +msgid "Last commit" +msgstr "" + +msgid "Last edited %{date}" +msgstr "" + +msgid "Last edited by %{name}" +msgstr "" + +msgid "Last update" +msgstr "" + +msgid "Last updated" +msgstr "" + +msgid "LastPushEvent|You pushed to" +msgstr "" + +msgid "LastPushEvent|at" +msgstr "" + +msgid "Learn more" +msgstr "" + +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + +msgid "Learn more in the" +msgstr "" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "" + +msgid "Leave" +msgstr "" + +msgid "Leave group" +msgstr "" + +msgid "Leave project" +msgstr "" + +msgid "License" +msgstr "" + +msgid "List" +msgstr "" + +msgid "Loading the GitLab IDE..." +msgstr "" + +msgid "Lock" +msgstr "" + +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock not found" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." +msgstr "" + +msgid "Locked" +msgstr "" + +msgid "Locked Files" +msgstr "" + +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + +msgid "Login" +msgstr "" + +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + +msgid "Mar" +msgstr "" + +msgid "March" +msgstr "" + +msgid "Mark done" +msgstr "" + +msgid "Maximum git storage failures" +msgstr "" + +msgid "May" +msgstr "" + +msgid "Median" +msgstr "" + +msgid "Members" +msgstr "" + +msgid "Merge Requests" +msgstr "" + +msgid "Merge events" +msgstr "" + +msgid "Merge request" +msgstr "" + +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "MergeRequest|Approved" +msgstr "" + +msgid "Merged" +msgstr "" + +msgid "Messages" +msgstr "" + +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "" + +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + +msgid "Monitoring" +msgstr "" + +msgid "More information" +msgstr "" + +msgid "More information is available|here" +msgstr "" + +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + +msgid "Multiple issue boards" +msgstr "" + +msgid "Name new label" +msgstr "" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "" +msgstr[1] "" + +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + +msgid "New Pipeline Schedule" +msgstr "" + +msgid "New branch" +msgstr "" + +msgid "New branch unavailable" +msgstr "" + +msgid "New directory" +msgstr "" + +msgid "New epic" +msgstr "" + +msgid "New file" +msgstr "" + +msgid "New group" +msgstr "" + +msgid "New issue" +msgstr "" + +msgid "New label" +msgstr "" + +msgid "New merge request" +msgstr "" + +msgid "New project" +msgstr "" + +msgid "New schedule" +msgstr "" + +msgid "New snippet" +msgstr "" + +msgid "New subgroup" +msgstr "" + +msgid "New tag" +msgstr "" + +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" +msgstr "" + +msgid "No repository" +msgstr "" + +msgid "No schedules" +msgstr "" + +msgid "None" +msgstr "" + +msgid "Not allowed to merge" +msgstr "" + +msgid "Not available" +msgstr "" + +msgid "Not confidential" +msgstr "" + +msgid "Not enough data" +msgstr "" + +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + +msgid "Notification events" +msgstr "" + +msgid "NotificationEvent|Close issue" +msgstr "" + +msgid "NotificationEvent|Close merge request" +msgstr "" + +msgid "NotificationEvent|Failed pipeline" +msgstr "" + +msgid "NotificationEvent|Merge merge request" +msgstr "" + +msgid "NotificationEvent|New issue" +msgstr "" + +msgid "NotificationEvent|New merge request" +msgstr "" + +msgid "NotificationEvent|New note" +msgstr "" + +msgid "NotificationEvent|Reassign issue" +msgstr "" + +msgid "NotificationEvent|Reassign merge request" +msgstr "" + +msgid "NotificationEvent|Reopen issue" +msgstr "" + +msgid "NotificationEvent|Successful pipeline" +msgstr "" + +msgid "NotificationLevel|Custom" +msgstr "" + +msgid "NotificationLevel|Disabled" +msgstr "" + +msgid "NotificationLevel|Global" +msgstr "" + +msgid "NotificationLevel|On mention" +msgstr "" + +msgid "NotificationLevel|Participate" +msgstr "" + +msgid "NotificationLevel|Watch" +msgstr "" + +msgid "Notifications" +msgstr "" + +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + +msgid "Nov" +msgstr "" + +msgid "November" +msgstr "" + +msgid "Number of access attempts" +msgstr "" + +msgid "OK" +msgstr "" + +msgid "Oct" +msgstr "" + +msgid "October" +msgstr "" + +msgid "OfSearchInADropdown|Filter" +msgstr "" + +msgid "Only project members can comment." +msgstr "" + +msgid "Open" +msgstr "" + +msgid "Opened" +msgstr "" + +msgid "OpenedNDaysAgo|Opened" +msgstr "" + +msgid "Opens in a new window" +msgstr "" + +msgid "Options" +msgstr "" + +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + +msgid "Overview" +msgstr "" + +msgid "Owner" +msgstr "" + +msgid "Pagination|Last »" +msgstr "" + +msgid "Pagination|Next" +msgstr "" + +msgid "Pagination|Prev" +msgstr "" + +msgid "Pagination|« First" +msgstr "" + +msgid "Password" +msgstr "" + +msgid "Pipeline" +msgstr "" + +msgid "Pipeline Health" +msgstr "" + +msgid "Pipeline Schedule" +msgstr "" + +msgid "Pipeline Schedules" +msgstr "" + +msgid "Pipeline quota" +msgstr "" + +msgid "PipelineCharts|Failed:" +msgstr "" + +msgid "PipelineCharts|Overall statistics" +msgstr "" + +msgid "PipelineCharts|Success ratio:" +msgstr "" + +msgid "PipelineCharts|Successful:" +msgstr "" + +msgid "PipelineCharts|Total:" +msgstr "" + +msgid "PipelineSchedules|Activated" +msgstr "" + +msgid "PipelineSchedules|Active" +msgstr "" + +msgid "PipelineSchedules|All" +msgstr "" + +msgid "PipelineSchedules|Inactive" +msgstr "" + +msgid "PipelineSchedules|Next Run" +msgstr "" + +msgid "PipelineSchedules|None" +msgstr "" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "" + +msgid "PipelineSchedules|Take ownership" +msgstr "" + +msgid "PipelineSchedules|Target" +msgstr "" + +msgid "PipelineSchedules|Variables" +msgstr "" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "" + +msgid "Pipelines" +msgstr "" + +msgid "Pipelines charts" +msgstr "" + +msgid "Pipelines for last month" +msgstr "" + +msgid "Pipelines for last week" +msgstr "" + +msgid "Pipelines for last year" +msgstr "" + +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|all" +msgstr "" + +msgid "Pipeline|success" +msgstr "" + +msgid "Pipeline|with stage" +msgstr "" + +msgid "Pipeline|with stages" +msgstr "" + +msgid "Play" +msgstr "" + +msgid "Please enable billing for one of your projects to be able to create a Kubernetes cluster, then try again." +msgstr "" + +msgid "Please solve the reCAPTCHA" +msgstr "" + +msgid "Preferences" +msgstr "" + +msgid "Primary" +msgstr "" + +msgid "Private - Project access must be granted explicitly to each user." +msgstr "" + +msgid "Private - The group and its projects can only be viewed by members." +msgstr "" + +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + +msgid "Profile" +msgstr "" + +msgid "Profiles|Account scheduled for removal." +msgstr "" + +msgid "Profiles|Delete Account" +msgstr "" + +msgid "Profiles|Delete account" +msgstr "" + +msgid "Profiles|Delete your account?" +msgstr "" + +msgid "Profiles|Deleting an account has the following effects:" +msgstr "" + +msgid "Profiles|Invalid password" +msgstr "" + +msgid "Profiles|Invalid username" +msgstr "" + +msgid "Profiles|Type your %{confirmationValue} to confirm:" +msgstr "" + +msgid "Profiles|You don't have access to delete this user." +msgstr "" + +msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account." +msgstr "" + +msgid "Profiles|Your account is currently an owner in these groups:" +msgstr "" + +msgid "Profiles|your account" +msgstr "" + +msgid "Programming languages used in this repository" +msgstr "" + +msgid "Project '%{project_name}' is in the process of being deleted." +msgstr "" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "" + +msgid "Project '%{project_name}' was successfully created." +msgstr "" + +msgid "Project '%{project_name}' was successfully updated." +msgstr "" + +msgid "Project access must be granted explicitly to each user." +msgstr "" + +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + +msgid "Project details" +msgstr "" + +msgid "Project export could not be deleted." +msgstr "" + +msgid "Project export has been deleted." +msgstr "" + +msgid "Project export link has expired. Please generate a new export from your project settings." +msgstr "" + +msgid "Project export started. A download link will be sent by email." +msgstr "" + +msgid "ProjectActivityRSS|Subscribe" +msgstr "" + +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + +msgid "ProjectFeature|Disabled" +msgstr "" + +msgid "ProjectFeature|Everyone with access" +msgstr "" + +msgid "ProjectFeature|Only team members" +msgstr "" + +msgid "ProjectFileTree|Name" +msgstr "" + +msgid "ProjectLastActivity|Never" +msgstr "" + +msgid "ProjectLifecycle|Stage" +msgstr "" + +msgid "ProjectNetworkGraph|Graph" +msgstr "" + +msgid "ProjectSettings|Contact an admin to change this setting." +msgstr "" + +msgid "ProjectSettings|Only signed commits can be pushed to this repository." +msgstr "" + +msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." +msgstr "" + +msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project." +msgstr "" + +msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin." +msgstr "" + +msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails." +msgstr "" + +msgid "Projects" +msgstr "" + +msgid "ProjectsDropdown|Frequently visited" +msgstr "" + +msgid "ProjectsDropdown|Loading projects" +msgstr "" + +msgid "ProjectsDropdown|Projects you visit often will appear here" +msgstr "" + +msgid "ProjectsDropdown|Search your projects" +msgstr "" + +msgid "ProjectsDropdown|Something went wrong on our end." +msgstr "" + +msgid "ProjectsDropdown|Sorry, no projects matched your search" +msgstr "" + +msgid "ProjectsDropdown|This feature requires browser localStorage support" +msgstr "" + +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + +msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." +msgstr "" + +msgid "PrometheusService|Finding and configuring metrics..." +msgstr "" + +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + +msgid "PrometheusService|Metrics" +msgstr "" + +msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters." +msgstr "" + +msgid "PrometheusService|Missing environment variable" +msgstr "" + +msgid "PrometheusService|Monitored" +msgstr "" + +msgid "PrometheusService|More information" +msgstr "" + +msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment." +msgstr "" + +msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" +msgstr "" + +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + +msgid "PrometheusService|Time-series monitoring service" +msgstr "" + +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + +msgid "PrometheusService|View environments" +msgstr "" + +msgid "Protip:" +msgstr "" + +msgid "Public - The group and any public projects can be viewed without any authentication." +msgstr "" + +msgid "Public - The project can be accessed without any authentication." +msgstr "" + +msgid "Push Rules" +msgstr "" + +msgid "Push events" +msgstr "" + +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + +msgid "PushRule|Committer restriction" +msgstr "" + +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + +msgid "Read more" +msgstr "" + +msgid "Readme" +msgstr "" + +msgid "RefSwitcher|Branches" +msgstr "" + +msgid "RefSwitcher|Tags" +msgstr "" + +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + +msgid "Registry" +msgstr "" + +msgid "Related Commits" +msgstr "" + +msgid "Related Deployed Jobs" +msgstr "" + +msgid "Related Issues" +msgstr "" + +msgid "Related Jobs" +msgstr "" + +msgid "Related Merge Requests" +msgstr "" + +msgid "Related Merged Requests" +msgstr "" + +msgid "Remind later" +msgstr "" + +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + +msgid "Remove project" +msgstr "" + +msgid "Repair authentication" +msgstr "" + +msgid "Repository" +msgstr "" + +msgid "Repository has no locks." +msgstr "" + +msgid "Request Access" +msgstr "" + +msgid "Reset git storage health information" +msgstr "" + +msgid "Reset health check access token" +msgstr "" + +msgid "Reset runners registration token" +msgstr "" + +msgid "Resolve discussion" +msgstr "" + +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" + +msgid "Revert this commit" +msgstr "" + +msgid "Revert this merge request" +msgstr "" + +msgid "Roadmap" +msgstr "" + +msgid "SSH Keys" +msgstr "" + +msgid "Save changes" +msgstr "" + +msgid "Save pipeline schedule" +msgstr "" + +msgid "Save variables" +msgstr "" + +msgid "Schedule a new pipeline" +msgstr "" + +msgid "Schedules" +msgstr "" + +msgid "Scheduling Pipelines" +msgstr "" + +msgid "Scoped issue boards" +msgstr "" + +msgid "Search branches and tags" +msgstr "" + +msgid "Search milestones" +msgstr "" + +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "" + +msgid "Seconds before reseting failure information" +msgstr "" + +msgid "Seconds to wait for a storage access attempt" +msgstr "" + +msgid "Secret variables" +msgstr "" + +msgid "Security report" +msgstr "" + +msgid "Select Archive Format" +msgstr "" + +msgid "Select a timezone" +msgstr "" + +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + +msgid "Select target branch" +msgstr "" + +msgid "Selective synchronization" +msgstr "" + +msgid "Send email" +msgstr "" + +msgid "Sep" +msgstr "" + +msgid "September" +msgstr "" + +msgid "Server version" +msgstr "" + +msgid "Service Templates" +msgstr "" + +msgid "Service URL" +msgstr "" + +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "" + +msgid "Set up CI/CD" +msgstr "" + +msgid "Set up Koding" +msgstr "" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "" + +msgid "Settings" +msgstr "" + +msgid "Setup a specific Runner automatically" +msgstr "" + +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + +msgid "Show command" +msgstr "" + +msgid "Show parent pages" +msgstr "" + +msgid "Show parent subgroups" +msgstr "" + +msgid "Showing %d event" +msgid_plural "Showing %d events" +msgstr[0] "" +msgstr[1] "" + +msgid "Sidebar|Change weight" +msgstr "" + +msgid "Sidebar|No" +msgstr "" + +msgid "Sidebar|None" +msgstr "" + +msgid "Sidebar|Weight" +msgstr "" + +msgid "Snippets" +msgstr "" + +msgid "Something went wrong on our end" +msgstr "" + +msgid "Something went wrong on our end." +msgstr "" + +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + +msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" +msgstr "" + +msgid "Something went wrong when toggling the button" +msgstr "" + +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + +msgid "Something went wrong while fetching the projects." +msgstr "" + +msgid "Something went wrong while fetching the registry list." +msgstr "" + +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + +msgid "Something went wrong. Please try again." +msgstr "" + +msgid "Sort by" +msgstr "" + +msgid "SortOptions|Access level, ascending" +msgstr "" + +msgid "SortOptions|Access level, descending" +msgstr "" + +msgid "SortOptions|Created date" +msgstr "" + +msgid "SortOptions|Due date" +msgstr "" + +msgid "SortOptions|Due later" +msgstr "" + +msgid "SortOptions|Due soon" +msgstr "" + +msgid "SortOptions|Label priority" +msgstr "" + +msgid "SortOptions|Largest group" +msgstr "" + +msgid "SortOptions|Largest repository" +msgstr "" + +msgid "SortOptions|Last created" +msgstr "" + +msgid "SortOptions|Last joined" +msgstr "" + +msgid "SortOptions|Last updated" +msgstr "" + +msgid "SortOptions|Least popular" +msgstr "" + +msgid "SortOptions|Less weight" +msgstr "" + +msgid "SortOptions|Milestone" +msgstr "" + +msgid "SortOptions|Milestone due later" +msgstr "" + +msgid "SortOptions|Milestone due soon" +msgstr "" + +msgid "SortOptions|More weight" +msgstr "" + +msgid "SortOptions|Most popular" +msgstr "" + +msgid "SortOptions|Name" +msgstr "" + +msgid "SortOptions|Name, ascending" +msgstr "" + +msgid "SortOptions|Name, descending" +msgstr "" + +msgid "SortOptions|Oldest created" +msgstr "" + +msgid "SortOptions|Oldest joined" +msgstr "" + +msgid "SortOptions|Oldest sign in" +msgstr "" + +msgid "SortOptions|Oldest updated" +msgstr "" + +msgid "SortOptions|Popularity" +msgstr "" + +msgid "SortOptions|Priority" +msgstr "" + +msgid "SortOptions|Recent sign in" +msgstr "" + +msgid "SortOptions|Start later" +msgstr "" + +msgid "SortOptions|Start soon" +msgstr "" + +msgid "SortOptions|Weight" +msgstr "" + +msgid "Source" +msgstr "" + +msgid "Source (branch or tag)" +msgstr "" + +msgid "Source code" +msgstr "" + +msgid "Source is not available" +msgstr "" + +msgid "Spam Logs" +msgstr "" + +msgid "Specify the following URL during the Runner setup:" +msgstr "" + +msgid "StarProject|Star" +msgstr "" + +msgid "Starred projects" +msgstr "" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "" + +msgid "Start the Runner!" +msgstr "" + +msgid "Stopped" +msgstr "" + +msgid "Storage" +msgstr "" + +msgid "Subgroups" +msgstr "" + +msgid "Switch branch/tag" +msgstr "" + +msgid "System Hooks" +msgstr "" + +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "" +msgstr[1] "" + +msgid "Tags" +msgstr "" + +msgid "TagsPage|Browse commits" +msgstr "" + +msgid "TagsPage|Browse files" +msgstr "" + +msgid "TagsPage|Can't find HEAD commit for this tag" +msgstr "" + +msgid "TagsPage|Cancel" +msgstr "" + +msgid "TagsPage|Create tag" +msgstr "" + +msgid "TagsPage|Delete tag" +msgstr "" + +msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?" +msgstr "" + +msgid "TagsPage|Edit release notes" +msgstr "" + +msgid "TagsPage|Existing branch name, tag, or commit SHA" +msgstr "" + +msgid "TagsPage|Filter by tag name" +msgstr "" + +msgid "TagsPage|New Tag" +msgstr "" + +msgid "TagsPage|New tag" +msgstr "" + +msgid "TagsPage|Optionally, add a message to the tag." +msgstr "" + +msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page." +msgstr "" + +msgid "TagsPage|Release notes" +msgstr "" + +msgid "TagsPage|Repository has no tags yet." +msgstr "" + +msgid "TagsPage|Sort by" +msgstr "" + +msgid "TagsPage|Tags" +msgstr "" + +msgid "TagsPage|Tags give the ability to mark specific points in history as being important" +msgstr "" + +msgid "TagsPage|This tag has no release notes." +msgstr "" + +msgid "TagsPage|Use git tag command to add a new one:" +msgstr "" + +msgid "TagsPage|Write your release notes or drag files here..." +msgstr "" + +msgid "TagsPage|protected" +msgstr "" + +msgid "Target Branch" +msgstr "" + +msgid "Team" +msgstr "" + +msgid "Thanks! Don't show me this again" +msgstr "" + +msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." +msgstr "" + +msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." +msgstr "" + +msgid "The collection of events added to the data gathered for that stage." +msgstr "" + +msgid "The fork relationship has been removed." +msgstr "" + +msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." +msgstr "" + +msgid "The maximum file size allowed is 200KB." +msgstr "" + +msgid "The number of attempts GitLab will make to access a storage." +msgstr "" + +msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." +msgstr "" + +msgid "The phase of the development lifecycle." +msgstr "" + +msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." +msgstr "" + +msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle." +msgstr "" + +msgid "The project can be accessed by any logged in user." +msgstr "" + +msgid "The project can be accessed without any authentication." +msgstr "" + +msgid "The repository for this project does not exist." +msgstr "" + +msgid "The repository for this project is empty" +msgstr "" + +msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." +msgstr "" + +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + +msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." +msgstr "" + +msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running." +msgstr "" + +msgid "The time in seconds GitLab will keep failure information. When no failures occur during this time, information about the mount is reset." +msgstr "" + +msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." +msgstr "" + +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + +msgid "The time taken by each data entry gathered by that stage." +msgstr "" + +msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." +msgstr "" + +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + +msgid "There are problems accessing Git storage: " +msgstr "" + +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + +msgid "This board\\'s scope is reduced" +msgstr "" + +msgid "This directory" +msgstr "" + +msgid "This is a confidential issue." +msgstr "" + +msgid "This is the author's first Merge Request to this project." +msgstr "" + +msgid "This issue is confidential" +msgstr "" + +msgid "This issue is confidential and locked." +msgstr "" + +msgid "This issue is locked." +msgstr "" + +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + +msgid "This means you can not push code until you create an empty repository or import existing one." +msgstr "" + +msgid "This merge request is locked." +msgstr "" + +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + +msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." +msgstr "" + +msgid "Time before an issue gets scheduled" +msgstr "" + +msgid "Time before an issue starts implementation" +msgstr "" + +msgid "Time between merge request creation and merge/close" +msgstr "" + +msgid "Time tracking" +msgstr "" + +msgid "Time until first merge request" +msgstr "" + +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + +msgid "Timeago|%s days ago" +msgstr "" + +msgid "Timeago|%s days remaining" +msgstr "" + +msgid "Timeago|%s hours remaining" +msgstr "" + +msgid "Timeago|%s minutes ago" +msgstr "" + +msgid "Timeago|%s minutes remaining" +msgstr "" + +msgid "Timeago|%s months ago" +msgstr "" + +msgid "Timeago|%s months remaining" +msgstr "" + +msgid "Timeago|%s seconds remaining" +msgstr "" + +msgid "Timeago|%s weeks ago" +msgstr "" + +msgid "Timeago|%s weeks remaining" +msgstr "" + +msgid "Timeago|%s years ago" +msgstr "" + +msgid "Timeago|%s years remaining" +msgstr "" + +msgid "Timeago|1 day remaining" +msgstr "" + +msgid "Timeago|1 hour remaining" +msgstr "" + +msgid "Timeago|1 minute remaining" +msgstr "" + +msgid "Timeago|1 month remaining" +msgstr "" + +msgid "Timeago|1 week remaining" +msgstr "" + +msgid "Timeago|1 year remaining" +msgstr "" + +msgid "Timeago|Past due" +msgstr "" + +msgid "Timeago|a day ago" +msgstr "" + +msgid "Timeago|a month ago" +msgstr "" + +msgid "Timeago|a week ago" +msgstr "" + +msgid "Timeago|a year ago" +msgstr "" + +msgid "Timeago|about %s hours ago" +msgstr "" + +msgid "Timeago|about a minute ago" +msgstr "" + +msgid "Timeago|about an hour ago" +msgstr "" + +msgid "Timeago|in %s days" +msgstr "" + +msgid "Timeago|in %s hours" +msgstr "" + +msgid "Timeago|in %s minutes" +msgstr "" + +msgid "Timeago|in %s months" +msgstr "" + +msgid "Timeago|in %s seconds" +msgstr "" + +msgid "Timeago|in %s weeks" +msgstr "" + +msgid "Timeago|in %s years" +msgstr "" + +msgid "Timeago|in 1 day" +msgstr "" + +msgid "Timeago|in 1 hour" +msgstr "" + +msgid "Timeago|in 1 minute" +msgstr "" + +msgid "Timeago|in 1 month" +msgstr "" + +msgid "Timeago|in 1 week" +msgstr "" + +msgid "Timeago|in 1 year" +msgstr "" + +msgid "Timeago|in a while" +msgstr "" + +msgid "Timeago|less than a minute ago" +msgstr "" + +msgid "Time|hr" +msgid_plural "Time|hrs" +msgstr[0] "" +msgstr[1] "" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "" +msgstr[1] "" + +msgid "Time|s" +msgstr "" + +msgid "Tip:" +msgstr "" + +msgid "Title" +msgstr "" + +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + +msgid "Total Time" +msgstr "" + +msgid "Total test time for all commits/merges" +msgstr "" + +msgid "Total: %{total}" +msgstr "" + +msgid "Track activity with Contribution Analytics." +msgstr "" + +msgid "Track groups of issues that share a theme, across projects and milestones" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + +msgid "Turn on Service Desk" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + +msgid "Unlock" +msgstr "" + +msgid "Unlock this %{issuableDisplayName}? Everyone will be able to comment." +msgstr "" + +msgid "Unlocked" +msgstr "" + +msgid "Unresolve discussion" +msgstr "" + +msgid "Unstar" +msgstr "" + +msgid "Up to date" +msgstr "" + +msgid "Upgrade your plan to activate Advanced Global Search." +msgstr "" + +msgid "Upgrade your plan to activate Contribution Analytics." +msgstr "" + +msgid "Upgrade your plan to activate Group Webhooks." +msgstr "" + +msgid "Upgrade your plan to activate Issue weight." +msgstr "" + +msgid "Upgrade your plan to improve Issue boards." +msgstr "" + +msgid "Upload New File" +msgstr "" + +msgid "Upload file" +msgstr "" + +msgid "Upload new avatar" +msgstr "" + +msgid "UploadLink|click to upload" +msgstr "" + +msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab" +msgstr "" + +msgid "Use the following registration token during setup:" +msgstr "" + +msgid "Use your global notification setting" +msgstr "" + +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + +msgid "View epics list" +msgstr "" + +msgid "View file @ " +msgstr "" + +msgid "View labels" +msgstr "" + +msgid "View open merge request" +msgstr "" + +msgid "View replaced file @ " +msgstr "" + +msgid "VisibilityLevel|Internal" +msgstr "" + +msgid "VisibilityLevel|Private" +msgstr "" + +msgid "VisibilityLevel|Public" +msgstr "" + +msgid "VisibilityLevel|Unknown" +msgstr "" + +msgid "Want to see the data? Please ask an administrator for access." +msgstr "" + +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + +msgid "We don't have enough data to show this stage." +msgstr "" + +msgid "We want to be sure it is you, please confirm you are not a robot." +msgstr "" + +msgid "Web IDE" +msgstr "" + +msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." +msgstr "" + +msgid "Weight" +msgstr "" + +msgid "Wiki" +msgstr "" + +msgid "WikiClone|Clone your wiki" +msgstr "" + +msgid "WikiClone|Git Access" +msgstr "" + +msgid "WikiClone|Install Gollum" +msgstr "" + +msgid "WikiClone|It is recommended to install %{markdown} so that GFM features render locally:" +msgstr "" + +msgid "WikiClone|Start Gollum and edit locally" +msgstr "" + +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + +msgid "WikiEmptyPageError|You are not allowed to create wiki pages" +msgstr "" + +msgid "WikiHistoricalPage|This is an old version of this page." +msgstr "" + +msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}." +msgstr "" + +msgid "WikiHistoricalPage|history" +msgstr "" + +msgid "WikiHistoricalPage|most recent version" +msgstr "" + +msgid "WikiMarkdownDocs|More examples are in the %{docs_link}" +msgstr "" + +msgid "WikiMarkdownDocs|documentation" +msgstr "" + +msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}" +msgstr "" + +msgid "WikiNewPagePlaceholder|how-to-setup" +msgstr "" + +msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories." +msgstr "" + +msgid "WikiNewPageTitle|New Wiki Page" +msgstr "" + +msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?" +msgstr "" + +msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs." +msgstr "" + +msgid "WikiPageConflictMessage|the page" +msgstr "" + +msgid "WikiPageCreate|Create %{page_title}" +msgstr "" + +msgid "WikiPageEdit|Update %{page_title}" +msgstr "" + +msgid "WikiPage|Page slug" +msgstr "" + +msgid "WikiPage|Write your content or drag files here..." +msgstr "" + +msgid "Wiki|Create Page" +msgstr "" + +msgid "Wiki|Create page" +msgstr "" + +msgid "Wiki|Edit Page" +msgstr "" + +msgid "Wiki|Empty page" +msgstr "" + +msgid "Wiki|More Pages" +msgstr "" + +msgid "Wiki|New page" +msgstr "" + +msgid "Wiki|Page history" +msgstr "" + +msgid "Wiki|Page version" +msgstr "" + +msgid "Wiki|Pages" +msgstr "" + +msgid "Wiki|Wiki Pages" +msgstr "" + +msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members." +msgstr "" + +msgid "Withdraw Access Request" +msgstr "" + +msgid "Write a commit message..." +msgstr "" + +msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You can also create a project from the command line." +msgstr "" + +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can only add files when you are on a branch" +msgstr "" + +msgid "You can only edit files when you are on a branch" +msgstr "" + +msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." +msgstr "" + +msgid "You cannot write to this read-only GitLab instance." +msgstr "" + +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + +msgid "You have reached your project limit" +msgstr "" + +msgid "You must have master access to force delete a lock" +msgstr "" + +msgid "You must sign in to star a project" +msgstr "" + +msgid "You need a different license to enable FileLocks feature" +msgstr "" + +msgid "You need permission." +msgstr "" + +msgid "You will not get any notifications via email" +msgstr "" + +msgid "You will only receive notifications for the events you choose" +msgstr "" + +msgid "You will only receive notifications for threads you have participated in" +msgstr "" + +msgid "You will receive notifications for any activity" +msgstr "" + +msgid "You will receive notifications only for comments in which you were @mentioned" +msgstr "" + +msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account" +msgstr "" + +msgid "You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile" +msgstr "" + +msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" +msgstr "" + +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + +msgid "Your comment will not be visible to the public." +msgstr "" + +msgid "Your groups" +msgstr "" + +msgid "Your name" +msgstr "" + +msgid "Your projects" +msgstr "" + +msgid "assign yourself" +msgstr "" + +msgid "branch name" +msgstr "" + +msgid "by" +msgstr "" + +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load %{reportName} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading %{reportName} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + +msgid "commit" +msgstr "" + +msgid "confidentiality|You are going to turn off the confidentiality. This means everyone will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with at least Reporter access are able to see and leave comments on the issue." +msgstr "" + +msgid "day" +msgid_plural "days" +msgstr[0] "" +msgstr[1] "" + +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Remove your approval" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|branch does not exist." +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + +msgid "new merge request" +msgstr "" + +msgid "notification emails" +msgstr "" + +msgid "or" +msgstr "" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "" +msgstr[1] "" + +msgid "password" +msgstr "" + +msgid "personal access token" +msgstr "" + +msgid "remove due date" +msgstr "" + +msgid "source" +msgstr "" + +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + +msgid "to help your contributors communicate effectively!" +msgstr "" + +msgid "username" +msgstr "" + +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po index d176e67937f..85fe26b83a7 100644 --- a/locale/fr/gitlab.po +++ b/locale/fr/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 03:59-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 06:24-0500\n" "Last-Translator: gitlab \n" "Language-Team: French\n" "Language: fr_FR\n" @@ -17,7 +17,7 @@ msgstr "" "X-Crowdin-File: /master/locale/gitlab.pot\n" msgid " and" -msgstr "" +msgstr " et" msgid "%d commit" msgid_plural "%d commits" @@ -26,13 +26,13 @@ msgstr[1] "%d commits" msgid "%d commit behind" msgid_plural "%d commits behind" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d commit de retard" +msgstr[1] "%d commits de retard" msgid "%d issue" msgid_plural "%d issues" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d ticket" +msgstr[1] "%d tickets" msgid "%d layer" msgid_plural "%d layers" @@ -41,24 +41,30 @@ msgstr[1] "%d couches" msgid "%d merge request" msgid_plural "%d merge requests" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "%d demande de fusion" +msgstr[1] "%d demandes de fusion" msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "%s validation supplémentaire a été masquée afin d'éviter de créer de problèmes de performances." -msgstr[1] "%s validations supplémentaires ont été masquées afin d'éviter de créer de problèmes de performances." +msgstr[1] "%s commits supplémentaires ont été masqués afin d'éviter un problème de performance." + +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "%{actionText} et %{openOrClose} %{noteable}" msgid "%{commit_author_link} authored %{commit_timeago}" -msgstr "" +msgstr "%{commit_author_link} a créé %{commit_timeago}" msgid "%{count} participant" msgid_plural "%{count} participants" msgstr[0] "%{count} participant•e" msgstr[1] "%{count} participant•e•s" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "%{lock_path} est verrouillé par l’utilisateur GitLab %{lock_user_id}" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" -msgstr "%{number_commits_behind} validations de retard sur %{default_branch}, %{number_commits_ahead} validations d'avance" +msgstr "%{number_commits_behind} commits de retard sur %{default_branch}, %{number_commits_ahead} commits d'avance" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "%{number_of_failures} sur %{maximum_failures} tentative(s). GitLab va vous permettre d'accéder à la prochaine tentative." @@ -66,6 +72,9 @@ msgstr "%{number_of_failures} sur %{maximum_failures} tentative(s). GitLab va vo msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} échecs sur %{maximum_failures}. GitLab ne va plus réessayer automatiquement. Réinitialisez les informations de stockage lorsque le problème est résolu." +msgid "%{openOrClose} %{noteable}" +msgstr "%{openOrClose} %{noteable}" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "%{storage_name} : la tentative d’accès au stockage a échouée sur l’hôte :" @@ -130,35 +139,74 @@ msgstr "Ajouter un guide de contribution" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "Ajouter des Webhooks de groupe et GitLab Enterprise Edition." +msgid "Add Kubernetes cluster" +msgstr "Ajouter un cluster Kubernetes" + msgid "Add License" msgstr "Ajouter une licence" +msgid "Add Readme" +msgstr "Ajouter un fichier Readme" + msgid "Add new directory" msgstr "Ajouter un nouveau dossier" msgid "Add todo" -msgstr "" +msgstr "Ajouter à la liste à faire" msgid "AdminArea|Stop all jobs" -msgstr "" +msgstr "Arrêtez tous les travaux" msgid "AdminArea|Stop all jobs?" -msgstr "" +msgstr "Arrêtez tous les travaux?" msgid "AdminArea|Stop jobs" -msgstr "" +msgstr "Arrêtez les travaux" msgid "AdminArea|Stopping jobs failed" -msgstr "" +msgstr "L’arrêt des travaux a échoué" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." -msgstr "" +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." +msgstr "Vous êtes sur le point d’arrêter tous les travaux. Tous les travaux en cours seront interrompus." msgid "AdminHealthPageLink|health page" msgstr "État des services" +msgid "AdminProjects|Delete" +msgstr "Supprimer" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "Supprimer le projet %{projectName} ?" + +msgid "AdminProjects|Delete project" +msgstr "Supprimer le projet" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "Spécifiez un domaine à utiliser par défaut pour les étapes Auto Review Apps et Auto Deploy de chaque projet." + +msgid "AdminUsers|Block user" +msgstr "Bloquer l’utilisateur•rice" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "Supprimer l’utilisateur•rice %{username} et ses contributions ?" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "Supprimer l’utilisateur•rice %{username} ?" + +msgid "AdminUsers|Delete user" +msgstr "Supprimer un•e utilisateur•rice" + +msgid "AdminUsers|Delete user and contributions" +msgstr "Supprimer l’utilisateur•rice et ses contributions" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "Pour confirmer, veuillez saisir %{projectName}" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "Pour confirmer, veuillez saisir %{username}" + msgid "Advanced" -msgstr "" +msgstr "Avancé" msgid "Advanced settings" msgstr "Paramètres avancés" @@ -167,13 +215,13 @@ msgid "All" msgstr "Tous" msgid "All changes are committed" -msgstr "" +msgstr "Toutes les modifications sont validées" msgid "Allows you to add and manage Kubernetes clusters." -msgstr "" +msgstr "Vous permet d’ajouter et de gérer des clusters Kubernetes." msgid "An error occurred previewing the blob" -msgstr "" +msgstr "Une erreur s’est produite lors de la prévisualisation du blob" msgid "An error occurred when toggling the notification subscription" msgstr "Une erreur s’est produite lors de l’activation/désactivation de l’abonnement aux notifications" @@ -181,38 +229,74 @@ msgstr "Une erreur s’est produite lors de l’activation/désactivation de l msgid "An error occurred when updating the issue weight" msgstr "Une erreur s'est produite lors de la mise à jour du poids du ticket" +msgid "An error occurred while adding approver" +msgstr "Une erreur s’est produite lors de l’ajout de l'approbateur•rice" + +msgid "An error occurred while detecting host keys" +msgstr "Une erreur s’est produite lors de la détection des clés de l'hôte" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." -msgstr "" +msgstr "Une erreur s’est produite lors de la révocation de la mise en avant de la fonctionnalité. Actualisez la page et essayez de la révoquer à nouveau." msgid "An error occurred while fetching markdown preview" -msgstr "" +msgstr "Une erreur s'est produite lors de la prévisualisation markdown" msgid "An error occurred while fetching sidebar data" msgstr "Une erreur s'est produite lors de la récupération des données de la barre latérale" +msgid "An error occurred while fetching the pipeline." +msgstr "Une erreur est survenue pendant la récupération du pipeline." + msgid "An error occurred while getting projects" -msgstr "" +msgstr "Une erreur s’est produite lors de la récupération des projets" + +msgid "An error occurred while importing project" +msgstr "Une erreur s’est produite lors de l’importation du projet" + +msgid "An error occurred while initializing path locks" +msgstr "Une erreur s’est produite lors de l’initialisation des verrous de chemin" + +msgid "An error occurred while loading commits" +msgstr "Une erreur s’est produite lors du chargement des commits" + +msgid "An error occurred while loading diff" +msgstr "Une erreur s’est produite lors du chargement du diff" msgid "An error occurred while loading filenames" -msgstr "" +msgstr "Une erreur s’est produite lors du chargement des noms de fichiers" + +msgid "An error occurred while loading the file" +msgstr "Une erreur s’est produite lors du chargement du fichier" + +msgid "An error occurred while making the request." +msgstr "Une erreur s’est produite lors de la requête." + +msgid "An error occurred while removing approver" +msgstr "Une erreur s’est produite lors de la suppression de l’approbateur•rice" msgid "An error occurred while rendering KaTeX" -msgstr "" +msgstr "Une erreur s’est produite lors du rendu de KaTeX" msgid "An error occurred while rendering preview broadcast message" -msgstr "" +msgstr "Une erreur s’est produite lors de la prévisualisation de la bannière" msgid "An error occurred while retrieving calendar activity" -msgstr "" +msgstr "Une erreur s’est produite lors de la récupération de l’activité du calendrier" msgid "An error occurred while retrieving diff" -msgstr "" +msgstr "Une erreur s’est produite lors de la récupération du diff" + +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "Une erreur s’est produite lors de l’enregistrement du statut de remplacement LDAP. Veuillez réessayer." + +msgid "An error occurred while saving assignees" +msgstr "Une erreur s’est produite lors de l’enregistrement des destinataires" msgid "An error occurred while validating username" -msgstr "" +msgstr "Une erreur s’est produite lors de la validation du nom d’utilisateur•rice" msgid "An error occurred. Please try again." -msgstr "Une erreur est survenue. Merci de réessayer." +msgstr "Une erreur s’est produite. Veuillez réessayer." msgid "Appearance" msgstr "Apparence" @@ -232,15 +316,15 @@ msgstr "Projet archivé ! Le dépôt est en lecture seule" msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Êtes-vous sûr·e de vouloir supprimer ce pipeline programmé ?" -msgid "Are you sure you want to discard your changes?" -msgstr "Êtes-vous sûr·e de vouloir annuler vos modifications ?" - msgid "Are you sure you want to reset registration token?" msgstr "Êtes-vous sûr·e de vouloir réinitialiser le jeton d’inscription ?" msgid "Are you sure you want to reset the health check token?" msgstr "Êtes-vous sûr de vouloir réinitialiser le jeton de bilan de santé ?" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "Êtes-vous sûr•e de vouloir déverrouiller %{path_lock_path} ?" + msgid "Are you sure?" msgstr "Êtes-vous certain ?" @@ -248,19 +332,19 @@ msgid "Artifacts" msgstr "Artéfacts" msgid "Assign custom color like #FF0000" -msgstr "" +msgstr "Attribuer une couleur personnalisée comme #FF0000" msgid "Assign labels" -msgstr "" +msgstr "Attribuer des labels" msgid "Assign milestone" -msgstr "" +msgstr "Attribuer un jalon" msgid "Assign to" -msgstr "" +msgstr "Assigner à" msgid "Assignee" -msgstr "" +msgstr "Assigné•e" msgid "Attach a file by drag & drop or %{upload_link}" msgstr "Attachez un fichier par glisser & déposer ou %{upload_link}" @@ -278,13 +362,16 @@ msgid "Author" msgstr "Auteur" msgid "Authors: %{authors}" -msgstr "" +msgstr "Auteur•e•s : %{authors}" + +msgid "Auto DevOps enabled" +msgstr "Auto DevOps activé" msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." -msgstr "" +msgstr "Auto Review Apps et Auto Deploy ont besoin d’un %{kubernetes} qui fonctionne correctement." msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." -msgstr "" +msgstr "Auto Review Apps et Auto Deploy ont besoin d’un nom de domaine et d’un %{kubernetes} qui fonctionne correctement." msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "Les applications de révision automatique et de déploiement automatique requièrent un nom de domaine pour fonctionner correctement." @@ -304,17 +391,26 @@ msgstr "AutoDevOps vous permet de construire, tester et déployer automatiquemen msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "En savoir plus dans %{link_to_documentation}" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." -msgstr "Vous pouvez activer %{link_to_settings} pour ce projet." +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "Vous pouvez automatiquement générer et tester votre application si vous %{link_to_auto_devops_settings} pour ce projet. Vous pouvez aussi la déployer automatiquement, si vous %{link_to_add_kubernetes_cluster}." + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "ajouter un cluster Kubernetes" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" +msgstr "activer Auto DevOps (Bêta)" msgid "Available" msgstr "Disponible" msgid "Avatar will be removed. Are you sure?" -msgstr "" +msgstr "L’avatar sera supprimé. Êtes-vous sûr•e ?" msgid "Average per day: %{average}" -msgstr "" +msgstr "Moyenne par jour : %{average}" + +msgid "Begin with the selected commit" +msgstr "Commencer avec le commit sélectionné" msgid "Billing" msgstr "Facturation" @@ -370,13 +466,10 @@ msgstr "payé annuellement pour %{price_per_year}" msgid "BillingPlans|per user" msgstr "par utilisateur" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "Branche" -msgstr[1] "Branches" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "Branche (%{branch_count})" +msgstr[1] "Branches (%{branch_count})" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "La branche %{branch_name} a été créée. Pour mettre en place le déploiement automatisé, sélectionnez un modèle de fichier Yaml pour l'intégration continue (CI) de GitLab, et validez les modifications. %{link_to_autodeploy_doc}" @@ -400,7 +493,7 @@ msgid "Branches" msgstr "Branches" msgid "Branches|Cant find HEAD commit for this branch" -msgstr "Impossible de trouver la validation HEAD pour cette branche" +msgstr "Impossible de trouver le commit HEAD pour cette branche" msgid "Branches|Compare" msgstr "Comparer" @@ -502,7 +595,7 @@ msgid "CI / CD" msgstr "Intégration continu / Déploiement continu" msgid "CI/CD configuration" -msgstr "" +msgstr "Configuration CI/CD" msgid "CICD|Jobs" msgstr "Tâches" @@ -514,7 +607,7 @@ msgid "Cancel edit" msgstr "Annuler modification" msgid "Cannot modify managed Kubernetes cluster" -msgstr "" +msgstr "Impossible de modifier le cluster géré par Kubernetes" msgid "Change Weight" msgstr "Changer le poids" @@ -532,13 +625,13 @@ msgid "ChangeTypeAction|Revert" msgstr "Défaire" msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." -msgstr "" +msgstr "Cela va créer un nouveau commit afin de défaire les modifications existantes." msgid "Changelog" msgstr "Journal des modifications" msgid "Changes are shown as if the source revision was being merged into the target revision." -msgstr "" +msgstr "Les modifications sont affichées comme si la révision source était fusionnée dans la révisioncible." msgid "Charts" msgstr "Statistiques" @@ -547,7 +640,7 @@ msgid "Chat" msgstr "Chat" msgid "Check interval" -msgstr "" +msgstr "Intervalle de vérification" msgid "Checking %{text} availability…" msgstr "Vérification de la disponibilité de %{text}…" @@ -556,25 +649,25 @@ msgid "Checking branch availability..." msgstr "Vérification de la disponibilité du nom de branche..." msgid "Cherry-pick this commit" -msgstr "Picorer cette validation" +msgstr "Picorer ce commit" msgid "Cherry-pick this merge request" msgstr "Picorer cette demande de fusion" msgid "Choose File ..." -msgstr "" +msgstr "Choisir le fichier…" msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." -msgstr "" +msgstr "Choisissez une branche / tag (par exemple %{master}) ou entrez un commit (par exemple %{sha}) pour voir ce qui a changé ou pour créer une demande de fusion." msgid "Choose file..." -msgstr "" +msgstr "Choisir le fichier…" msgid "Choose which groups you wish to synchronize to this secondary node." -msgstr "" +msgstr "Choisissez les groupes que vous souhaitez synchroniser avec ce nœud secondaire." msgid "Choose which shards you wish to synchronize to this secondary node." -msgstr "" +msgstr "Choisissez les partitions que vous souhaitez synchroniser avec ce nœud secondaire." msgid "CiStatusLabel|canceled" msgstr "annulé" @@ -631,46 +724,49 @@ msgid "CiStatus|running" msgstr "en cours" msgid "CiVariables|Input variable key" -msgstr "" +msgstr "Nom de la variable" msgid "CiVariables|Input variable value" -msgstr "" +msgstr "Valeur de la variable" msgid "CiVariables|Remove variable row" -msgstr "" +msgstr "Supprimer cette variable" msgid "CiVariable|* (All environments)" -msgstr "" +msgstr "* (Tous les environnements)" msgid "CiVariable|All environments" -msgstr "" +msgstr "Tous les environnements" msgid "CiVariable|Create wildcard" -msgstr "" +msgstr "Créer un caractère générique" msgid "CiVariable|Error occured while saving variables" -msgstr "" +msgstr "Une erreur s’est produite pendant la sauvegarde des variables" msgid "CiVariable|New environment" -msgstr "" +msgstr "Nouvel environnement" msgid "CiVariable|Protected" -msgstr "" +msgstr "Protégée" msgid "CiVariable|Search environments" -msgstr "" +msgstr "Chercher dans les environnements" msgid "CiVariable|Toggle protected" -msgstr "" +msgstr "Changer l’état de protection" msgid "CiVariable|Validation failed" -msgstr "" +msgstr "La validation a échoué" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "CircuitBreaker API" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "Cliquez sur le bouton ci-dessous pour lancer le processus d’installation en accédant à la page Kubernetes" + msgid "Click to expand text" -msgstr "" +msgstr "Cliquez pour agrandir le texte" msgid "Clone repository" msgstr "Cloner le dépôt" @@ -679,28 +775,28 @@ msgid "Close" msgstr "Fermer" msgid "Closed" -msgstr "" +msgstr "Fermée" msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" -msgstr "" +msgstr "%{appList} a été installé avec succès sur votre cluster Kubernetes" msgid "ClusterIntegration|API URL" msgstr "URL de l'API" msgid "ClusterIntegration|Add Kubernetes cluster" -msgstr "" +msgstr "Ajouter un cluster Kubernetes" msgid "ClusterIntegration|Add an existing Kubernetes cluster" -msgstr "" +msgstr "Ajouter un cluster Kubernetes existant" msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" -msgstr "" +msgstr "Options avancées sur l’intégration de ce cluster Kubernetes" msgid "ClusterIntegration|Applications" msgstr "Applications" msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." -msgstr "" +msgstr "Êtes-vous sûr•e de vouloir supprimer l'intégration de ce cluster Kubernetes? Cela ne supprimera pas votre cluster Kubernetes." msgid "ClusterIntegration|CA Certificate" msgstr "Certificat d‘autorité de certification" @@ -709,13 +805,13 @@ msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" msgstr "Paquet de l‘Autorité de certification (format PEM)" msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" -msgstr "" +msgstr "Choisissez comment configurer l’intégration de cluster Kubernetes" msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." -msgstr "" +msgstr "Choisissez les environnements de votre projet qui utiliseront ce cluster Kubernetes." msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" -msgstr "" +msgstr "Contrôlez l’intégration de votre cluster Kubernetes avec GitLab" msgid "ClusterIntegration|Copy API URL" msgstr "Copier l’URL de l’API" @@ -723,20 +819,23 @@ msgstr "Copier l’URL de l’API" msgid "ClusterIntegration|Copy CA Certificate" msgstr "Copier le certificat CA" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "Copier l’adresse IP entrante dans le presse-papiers" + msgid "ClusterIntegration|Copy Kubernetes cluster name" -msgstr "" +msgstr "Copier le nom du cluster Kubernetes" msgid "ClusterIntegration|Copy Token" msgstr "Copier le jeton" msgid "ClusterIntegration|Create Kubernetes cluster" -msgstr "" +msgstr "Créer un cluster Kubernetes" msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" -msgstr "" +msgstr "Créer un cluster Kubernetes sur Google Kubernetes Engine" msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" -msgstr "" +msgstr "Créer un nouveau cluster Kubernetes sur Google Kubernetes Engine directement depuis GitLab" msgid "ClusterIntegration|Create on GKE" msgstr "Créer sur GKE" @@ -745,13 +844,13 @@ msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "Entrer les détails pour le cluster Kubernetes existant" msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" -msgstr "" +msgstr "Entrez les détails de votre cluster Kubernetes" msgid "ClusterIntegration|Environment scope" -msgstr "" +msgstr "Portée de l’environnement" msgid "ClusterIntegration|GitLab Integration" -msgstr "" +msgstr "Intégration GitLab" msgid "ClusterIntegration|GitLab Runner" msgstr "Éxécuteur GitLab" @@ -771,6 +870,9 @@ msgstr "Helm Tiller" msgid "ClusterIntegration|Ingress" msgstr "Ingress" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "Adresse IP entrante" + msgid "ClusterIntegration|Install" msgstr "Installer" @@ -781,70 +883,67 @@ msgid "ClusterIntegration|Installing" msgstr "En cours d’installation" msgid "ClusterIntegration|Integrate Kubernetes cluster automation" -msgstr "" +msgstr "Intégrez l’automatisation du cluster Kubernetes" msgid "ClusterIntegration|Integration status" -msgstr "" +msgstr "Statut d’intégration" msgid "ClusterIntegration|Kubernetes cluster" -msgstr "" +msgstr "Cluster Kubernetes" msgid "ClusterIntegration|Kubernetes cluster details" -msgstr "" +msgstr "Détails du cluster Kubernetes" msgid "ClusterIntegration|Kubernetes cluster integration" -msgstr "" +msgstr "Intégration d’un cluster Kubernetes" msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." -msgstr "" +msgstr "L’intégration de cluster Kubernetes est désactivée pour ce projet." msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." -msgstr "" +msgstr "L’intégration de cluster Kubernetes est activée pour ce projet." msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." -msgstr "" +msgstr "L’intégration de cluster Kubernetes est activée pour ce projet. La désactivation de cette intégration n’affectera pas votre cluster Kubernetes, elle ne désactivera que temporairement la connexion de GitLab." msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." -msgstr "" +msgstr "Le cluster Kubernetes est en cours de création sur Google Kubernetes Engine…" msgid "ClusterIntegration|Kubernetes cluster name" -msgstr "" +msgstr "Nom du cluster Kubernetes" msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" -msgstr "" +msgstr "Le cluster Kubernetes a été créé avec succès sur Google Kubernetes Engine. Actualisez la page pour voir les détails du cluster Kubernetes" msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" -msgstr "" +msgstr "Les clusters Kubernetes vous permettent d’utiliser des applications de révision, de déployer vos applications, d’exécuter vos pipelines et bien plus encore. %{link_to_help_page}" msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" -msgstr "" +msgstr "Les clusters Kubernetes peuvent être utilisés pour déployer des applications et fournir des applications de révision pour ce projet" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "En savoir plus sur %{link_to_documentation}" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" -msgstr "" +msgstr "En savoir plus sur les environnements" msgid "ClusterIntegration|Machine type" msgstr "Type de machine" msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" -msgstr "" +msgstr "Assurez-vous que votre compte %{link_to_requirements} pour créer des clusters Kubernetes" msgid "ClusterIntegration|Manage" -msgstr "" +msgstr "Gérer" msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" -msgstr "" +msgstr "Gérez votre cluster Kubernetes en visitant %{link_gke}" msgid "ClusterIntegration|More information" -msgstr "" +msgstr "Plus d’informations" msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" -msgstr "" +msgstr "Plusieurs clusters Kubernetes sont disponibles dans GitLab Enterprise Edition Premium et Ultimate" msgid "ClusterIntegration|Note:" msgstr "Remarque :" @@ -853,7 +952,7 @@ msgid "ClusterIntegration|Number of nodes" msgstr "Nombre de nœuds" msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" -msgstr "" +msgstr "Veuillez entrer les informations d’accès de votre cluster Kubernetes. Si vous avez besoin d'aide, vous pouvez lire notre %{link_to_help_page} sur Kubernetes" msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "Veuillez vous assurer que votre compte Google répond aux exigences suivantes : " @@ -868,19 +967,19 @@ msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "Espace de noms du projet (facultatif, unique)" msgid "ClusterIntegration|Prometheus" -msgstr "" +msgstr "Prometheus" msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." -msgstr "" +msgstr "Lisez notre %{link_to_help_page} sur l’intégration d’un cluster Kubernetes." msgid "ClusterIntegration|Remove Kubernetes cluster integration" -msgstr "" +msgstr "Supprimer l’intégration du cluster Kubernetes" msgid "ClusterIntegration|Remove integration" msgstr "Retirer l’intégration" msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." -msgstr "" +msgstr "Supprimer la configuration de ce cluster Kubernetes de ce projet. Cela ne supprimera pas votre cluster Kubernetes actuel." msgid "ClusterIntegration|Request to begin installing failed" msgstr "La demande de lancement d'installation a échoué" @@ -889,7 +988,7 @@ msgid "ClusterIntegration|Save changes" msgstr "Enregistrer les modifications" msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" -msgstr "" +msgstr "Voir et modifier les détails de votre cluster Kubernetes" msgid "ClusterIntegration|See machine types" msgstr "Voir les types de machine" @@ -910,25 +1009,25 @@ msgid "ClusterIntegration|Something went wrong on our end." msgstr "Un problème est survenu de notre côté." msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" -msgstr "" +msgstr "Une erreur s’est produite lors de la création de votre cluster Kubernetes sur Google Kubernetes Engine" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "Une erreur s’est produite lors de l'installation de %{title}" msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" -msgstr "" +msgstr "Ce compte doit disposer des autorisations pour créer un cluster Kubernetes dans le %{link_to_container_project} spécifié ci-dessous" msgid "ClusterIntegration|Toggle Kubernetes Cluster" -msgstr "" +msgstr "Activer le cluster Kubernetes" msgid "ClusterIntegration|Toggle Kubernetes cluster" -msgstr "" +msgstr "Activer/désactiver le cluster Kubernetes" msgid "ClusterIntegration|Token" msgstr "Jeton" msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." -msgstr "" +msgstr "Avec un cluster Kubernetes associé à ce projet, vous pouvez utiliser des applications de révision, déployer vos applications, exécuter vos pipelines, et bien plus encore." msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" msgstr "Votre compte doit disposer de %{link_to_kubernetes_engine}" @@ -940,7 +1039,7 @@ msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "Accèder à Google Kubernetes Engine" msgid "ClusterIntegration|check the pricing here" -msgstr "" +msgstr "vérifiez le prix ici" msgid "ClusterIntegration|documentation" msgstr "documentation" @@ -958,6 +1057,12 @@ msgid "ClusterIntegration|properly configured" msgstr "correctement configuré" msgid "Collapse" +msgstr "Réduire" + +msgid "Comment and resolve discussion" +msgstr "Commenter et résoudre la discussion" + +msgid "Comment and unresolve discussion" msgstr "" msgid "Comments" @@ -965,53 +1070,61 @@ msgstr "Commentaires" msgid "Commit" msgid_plural "Commits" -msgstr[0] "Validation" -msgstr[1] "Validations" +msgstr[0] "Commit" +msgstr[1] "Commits" + +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "Commit (%{commit_count})" +msgstr[1] "Commits (%{commit_count})" msgid "Commit Message" -msgstr "Message de validation" +msgstr "Message du commit" msgid "Commit duration in minutes for last 30 commits" msgstr "Durée des 30 derniers pipelines en minutes" msgid "Commit message" -msgstr "Message de validation" +msgstr "Message de commit" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "Statistiques des commits pour %{ref} %{start_time} - %{end_time}" + +msgid "Commit to %{branchName} branch" msgstr "" msgid "CommitBoxTitle|Commit" -msgstr "Validation" +msgstr "Commit" msgid "CommitMessage|Add %{file_name}" msgstr "Ajout de %{file_name}" msgid "Commits" -msgstr "Validations" +msgstr "Commits" msgid "Commits feed" -msgstr "Flux de validations" +msgstr "Flux des commits" msgid "Commits per day hour (UTC)" -msgstr "" +msgstr "Commits par heure du jour (UTC)" msgid "Commits per day of month" -msgstr "" +msgstr "Commits par jour du mois" msgid "Commits per weekday" -msgstr "" +msgstr "Commits par jour de la semaine" msgid "Commits|An error occurred while fetching merge requests data." -msgstr "" +msgstr "Une erreur s'est produite lors de la récupération des données de demandes de fusion." msgid "Commits|Commit: %{commitText}" -msgstr "" +msgstr "Commit : %{commitText}" msgid "Commits|History" msgstr "Historique" msgid "Commits|No related merge requests found" -msgstr "" +msgstr "Aucune demande de fusion associée trouvée" msgid "Committed by" msgstr "Validé par" @@ -1020,28 +1133,28 @@ msgid "Compare" msgstr "Comparer" msgid "Compare Git revisions" -msgstr "" +msgstr "Comparer les révisions Git" msgid "Compare Revisions" -msgstr "" +msgstr "Comparer les révisions" msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." -msgstr "" +msgstr "%{source_branch} et %{target_branch} sont identiques." msgid "CompareBranches|Compare" -msgstr "" +msgstr "Comparer" msgid "CompareBranches|Source" -msgstr "" +msgstr "Source" msgid "CompareBranches|Target" -msgstr "" +msgstr "Cible" msgid "CompareBranches|There isn't anything to compare." -msgstr "" +msgstr "Il n’y a rien à comparer." msgid "Confidentiality" -msgstr "" +msgstr "Confidentialité" msgid "Container Registry" msgstr "Registre de conteneur" @@ -1095,13 +1208,13 @@ msgid "Contributors" msgstr "Contributeurs" msgid "ContributorsPage|%{startDate} – %{endDate}" -msgstr "" +msgstr "%{startDate} - %{endDate}" msgid "ContributorsPage|Building repository graph." msgstr "Construction du graphique du dépôt." msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits." -msgstr "Validations sur %{branch_name}, à l’exclusion des validations de fusion. Limité à 6 000 validations." +msgstr "Commit sur %{branch_name}, à l'exclusion des commits de fusion. Limité à 6 000 commits." msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready." msgstr "Veuillez patienter, cette page va être automatiquement actualisée." @@ -1119,23 +1232,35 @@ msgid "Copy URL to clipboard" msgstr "Copier l'URL dans le presse-papier" msgid "Copy branch name to clipboard" -msgstr "" +msgstr "Copier le nom de la branche dans le presse-papiers" + +msgid "Copy command to clipboard" +msgstr "Copier la commande dans le presse-papiers" msgid "Copy commit SHA to clipboard" -msgstr "Copier le SHA de la validation" +msgstr "Copier le SHA du commit" msgid "Copy reference to clipboard" -msgstr "" +msgstr "Copier la référence dans le presse-papiers" msgid "Create" -msgstr "" +msgstr "Créer" msgid "Create New Directory" msgstr "Créer un nouveau dossier" +msgid "Create a new branch" +msgstr "Créer une nouvelle branche" + +msgid "Create a new branch and merge request" +msgstr "Créer une nouvelle branche et demande de fusion" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "Créer un jeton d’accès personnel pour votre compte afin de récupérer ou pousser par %{protocol}." +msgid "Create branch" +msgstr "Créer une branche" + msgid "Create directory" msgstr "Créer un dossier" @@ -1149,11 +1274,14 @@ msgid "Create file" msgstr "Créer un fichier" msgid "Create lists from labels. Issues with that label appear in that list." -msgstr "" +msgstr "Créer des listes à partir de labels. Les tickets avec ce label apparaissent dans cette liste." msgid "Create merge request" msgstr "Créer une demande de fusion" +msgid "Create merge request and branch" +msgstr "Créer une demande de fusion et une branche" + msgid "Create new branch" msgstr "Créer une nouvelle branche" @@ -1164,7 +1292,7 @@ msgid "Create new file" msgstr "Créer un nouveau fichier" msgid "Create new label" -msgstr "" +msgstr "Créer un nouveau label" msgid "Create new..." msgstr "Créer nouveau..." @@ -1178,6 +1306,12 @@ msgstr "Tag" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "Créer un jeton d'accès personnel" +msgid "Creates a new branch from %{branchName}" +msgstr "Crée une nouvelle branche à partir de %{branchName}" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "Crée une nouvelle branche à partir de %{branchName} et redirige pour créer une nouvelle demande de fusion" + msgid "Creating epic" msgstr "Création de l'épopée en cours" @@ -1188,7 +1322,7 @@ msgid "Cron syntax" msgstr "Syntaxe Cron" msgid "Current node" -msgstr "" +msgstr "Nœud actuel" msgid "Custom notification events" msgstr "Événements de notification personnalisés" @@ -1232,6 +1366,9 @@ msgstr "Déc." msgid "December" msgstr "Décembre" +msgid "Default classification label" +msgstr "Label de classement par défaut" + msgid "Define a custom pattern with cron syntax" msgstr "Définir un schéma personnalisé avec une syntaxe Cron" @@ -1256,19 +1393,19 @@ msgid "Details" msgstr "Détails" msgid "Diffs|No file name available" -msgstr "" +msgstr "Aucun nom de fichier disponible" msgid "Directory name" msgstr "Nom du dossier" msgid "Disable" -msgstr "" +msgstr "Désactiver" -msgid "Discard changes" -msgstr "Supprimer les modifications" +msgid "Discard draft" +msgstr "Supprimer le brouillon" msgid "Discover GitLab Geo." -msgstr "" +msgstr "Découvrez GitLab Geo." msgid "Dismiss Cycle Analytics introduction box" msgstr "Passer l’introduction Cycle Analytics" @@ -1307,7 +1444,7 @@ msgid "DownloadSource|Download" msgstr "Télécharger" msgid "Due date" -msgstr "" +msgstr "Date d’échéance" msgid "Edit" msgstr "Éditer" @@ -1316,13 +1453,16 @@ msgid "Edit Pipeline Schedule %{id}" msgstr "Éditer le pipeline programmé %{id}" msgid "Edit files in the editor and commit changes here" -msgstr "" +msgstr "Modifier les fichiers dans l'éditeur et valider les modifications ici" msgid "Emails" msgstr "Courriels" msgid "Enable" -msgstr "" +msgstr "Activer" + +msgid "Enable Auto DevOps" +msgstr "Activer Auto DevOps" msgid "Environments|An error occurred while fetching the environments." msgstr "Une erreur s‘est produite lors de la récupération des environnements." @@ -1331,7 +1471,7 @@ msgid "Environments|An error occurred while making the request." msgstr "Une erreur s’est produite lors de la requête." msgid "Environments|Commit" -msgstr "Validation" +msgstr "Commit" msgid "Environments|Deployment" msgstr "Déploiement" @@ -1378,38 +1518,47 @@ msgstr "L’épopée sera supprimée ! Êtes-vous sûr•e ?" msgid "Epics" msgstr "Épopées" +msgid "Epics Roadmap" +msgstr "Feuille de route des épopées" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "Les épopées vous permettent de gérer votre portefeuille de projets plus efficacement et avec moins d'effort" +msgid "Error checking branch data. Please try again." +msgstr "Erreur lors de la vérification des données de branche. Veuillez réessayer." + +msgid "Error committing changes. Please try again." +msgstr "Erreur lors de la validation des modifications. Veuillez réessayer." + msgid "Error creating epic" msgstr "Erreur lors de la création de l’épopée" msgid "Error fetching contributors data." -msgstr "" +msgstr "Erreur lors de l’extraction des données des contributeur•rice•s." msgid "Error fetching labels." -msgstr "" +msgstr "Erreur lors de la récupération des labels." msgid "Error fetching network graph." -msgstr "" +msgstr "Erreur lors de la récupération du graphique du réseau." msgid "Error fetching refs" -msgstr "" +msgstr "Erreur lors de la récupération des refs" msgid "Error fetching usage ping data." -msgstr "" +msgstr "Erreur lors de la récupération des données d’utilisation." msgid "Error occurred when toggling the notification subscription" msgstr "Une erreur s’est produite lors de l’activation/désactivation de l’abonnement aux notifications" msgid "Error saving label update." -msgstr "" +msgstr "Erreur lors de la mise à jour du label." msgid "Error updating status for all todos." -msgstr "" +msgstr "Erreur lors de la mise à jour de l’état de la liste de tâches à faire." msgid "Error updating todo status." -msgstr "" +msgstr "Erreur lors de la mise à jour du statut de la tâche à faire." msgid "EventFilterBy|Filter by all" msgstr "Aucun filtre" @@ -1439,7 +1588,7 @@ msgid "Every week (Sundays at 4:00am)" msgstr "Chaque semaine (dimanche à 4h00 du matin)" msgid "Expand" -msgstr "" +msgstr "Ouvrir" msgid "Explore projects" msgstr "Explorer les projets" @@ -1447,12 +1596,36 @@ msgstr "Explorer les projets" msgid "Explore public groups" msgstr "Explorer les groupes publics" +msgid "External Classification Policy Authorization" +msgstr "Autorisation de politique de classification externe" + +msgid "External authorization denied access to this project" +msgstr "L’autorisation externe a refusé l’accès à ce projet" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "Label de classification" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "Label de classification" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "Quand aucun label de classification n’est défini, le label par défaut `%{default_label}` sera utilisé." + +msgid "Failed Jobs" +msgstr "Travaux ayant échoué" + msgid "Failed to change the owner" msgstr "Échec du changement de propriétaire" +msgid "Failed to remove issue from board, please try again." +msgstr "Impossible de supprimer le ticket du tableau, veuillez réessayer." + msgid "Failed to remove the pipeline schedule" msgstr "Échec de la suppression du pipeline programmé" +msgid "Failed to update issues, please try again." +msgstr "Échec de la mise à jour du ticket. Veuillez réessayer." + msgid "Feb" msgstr "Févr." @@ -1460,7 +1633,7 @@ msgid "February" msgstr "Février" msgid "Fields on this page are now uneditable, you can configure" -msgstr "" +msgstr "Les champs sur cette page sont désormais non modifiables, vous pouvez configurer" msgid "File name" msgstr "Nom du fichier" @@ -1468,8 +1641,11 @@ msgstr "Nom du fichier" msgid "Files" msgstr "Fichiers" +msgid "Files (%{human_size})" +msgstr "Fichiers (%{human_size})" + msgid "Filter by commit message" -msgstr "Filtrer par message de validation" +msgstr "Filtrer par message de commit" msgid "Find by path" msgstr "Rechercher par chemin" @@ -1503,11 +1679,14 @@ msgstr "Depuis la création du ticket jusqu'au déploiement en production" msgid "From merge request merge until deploy to production" msgstr "Depuis la fusion de la demande de fusion jusqu'au déploiement en production" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "À partir de l’affichage des détails du cluster Kubernetes, installez un Exécuteur à partir de la liste des applications" + msgid "GPG Keys" msgstr "Clés GPG" msgid "Generate a default set of labels" -msgstr "" +msgstr "Générer un ensemble de labels par défaut" msgid "Geo Nodes" msgstr "Nœuds Geo" @@ -1519,100 +1698,100 @@ msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an msgstr "Le nœud est lent, surchargé, ou il vient juste de récupérer après un problème." msgid "GeoNodes|Database replication lag:" -msgstr "" +msgstr "Retard de réplication de la base de données :" msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" -msgstr "" +msgstr "La désactivation d’un nœud arrête le processus de synchronisation. Êtes-vous sûr•e?" msgid "GeoNodes|Does not match the primary storage configuration" -msgstr "" +msgstr "Ne correspond pas à la configuration de stockage principale" msgid "GeoNodes|Failed" -msgstr "" +msgstr "Échec" msgid "GeoNodes|Full" -msgstr "" +msgstr "Complet" msgid "GeoNodes|GitLab version does not match the primary node version" -msgstr "" +msgstr "La version de GitLab ne correspond pas à la version du nœud principal" msgid "GeoNodes|GitLab version:" -msgstr "" +msgstr "Version de GitLab :" msgid "GeoNodes|Health status:" -msgstr "" +msgstr "État :" msgid "GeoNodes|Last event ID processed by cursor:" -msgstr "" +msgstr "Dernier ID d’événement traité par le curseur :" msgid "GeoNodes|Last event ID seen from primary:" -msgstr "" +msgstr "Dernier événement vu du nœud primaire :" msgid "GeoNodes|Loading nodes" -msgstr "" +msgstr "Chargement des nœuds" msgid "GeoNodes|Local Attachments:" -msgstr "" +msgstr "Pièces jointes locales :" msgid "GeoNodes|Local LFS objects:" -msgstr "" +msgstr "Objets LFS locaux :" msgid "GeoNodes|Local job artifacts:" -msgstr "" +msgstr "Artefacts de tâches locaux :" msgid "GeoNodes|New node" -msgstr "" +msgstr "Nouveau nœud" msgid "GeoNodes|Out of sync" -msgstr "" +msgstr "Désynchronisé" msgid "GeoNodes|Replication slot WAL:" -msgstr "" +msgstr "Emplacement de réplication WAL :" msgid "GeoNodes|Replication slots:" -msgstr "" +msgstr "Emplacements de réplication :" msgid "GeoNodes|Repositories:" -msgstr "" +msgstr "Dépôts :" msgid "GeoNodes|Selective" -msgstr "" +msgstr "Sélectif" msgid "GeoNodes|Storage config:" -msgstr "" +msgstr "Configuration de stockage :" msgid "GeoNodes|Sync settings:" -msgstr "" +msgstr "Paramètres de synchronisation :" msgid "GeoNodes|Synced" -msgstr "" +msgstr "Synchronisé" msgid "GeoNodes|Unused slots" -msgstr "" +msgstr "Emplacements non utilisés" msgid "GeoNodes|Used slots" -msgstr "" +msgstr "Emplacements utilisés" msgid "GeoNodes|Wikis:" -msgstr "" +msgstr "Wikis :" msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." -msgstr "" +msgstr "Vous avez configuré des nœuds Geo en utilisant une connexion HTTP non sécurisée. Nous recommandons l’utilisation de HTTPS." msgid "Geo|All projects" -msgstr "" +msgstr "Tous les projets" msgid "Geo|File sync capacity" msgstr "Capacité de synchronisation de fichier" msgid "Geo|Groups to synchronize" -msgstr "" +msgstr "Groupes à synchroniser" msgid "Geo|Projects in certain groups" -msgstr "" +msgstr "Projets dans certains groupes" msgid "Geo|Projects in certain storage shards" -msgstr "" +msgstr "Projets dans certains fragments de stockage" msgid "Geo|Repository sync capacity" msgstr "Capacité de synchronisation du dépôt" @@ -1621,22 +1800,22 @@ msgid "Geo|Select groups to replicate." msgstr "Sélectionner les groupes à répliquer." msgid "Geo|Shards to synchronize" -msgstr "" +msgstr "Fragments à synchroniser" msgid "Git revision" -msgstr "" +msgstr "Révision de Git" msgid "Git storage health information has been reset" msgstr "Les informations de santé du stockage Git ont été réinitialisées" msgid "Git version" -msgstr "" +msgstr "Version de Git" msgid "GitLab Runner section" msgstr "Section de l'Exécuteur GitLab" msgid "Gitaly Servers" -msgstr "" +msgstr "Serveurs Gitaly" msgid "Go to your fork" msgstr "Aller à votre fourche" @@ -1648,7 +1827,25 @@ msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab ad msgstr "L’authentification Google n’est pas %{link_to_documentation}. Demandez à votre administrateur GitLab si vous souhaitez utiliser ce service." msgid "Got it!" -msgstr "" +msgstr "Compris !" + +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "Les épopées vous permettent de gérer votre portefeuille de projets plus efficacement et avec moins d’effort" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "À partir de %{dateWord}" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "Chargement de la feuille de route" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "Une erreur s’est produite lors de la récupération des épopées" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "Pour afficher la feuille de route, ajoutez une date de début ou de fin planifiée à l'une de vos épopées dans ce groupe ou ses sous-groupes. Seules les épopées des 3 derniers mois et des 3 prochains mois sont affichées – de %{startDate} à %{endDate}." + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "Jusqu’au %{dateWord}" msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "Empêcher le partage d'un projet de %{group} avec d'autres groupes" @@ -1687,7 +1884,7 @@ msgid "GroupsEmptyState|You can manage your group member’s permissions and acc msgstr "Vous pouvez gérer les autorisations des membres de votre groupe et accéder à chacun de ses projets." msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" -msgstr "" +msgstr "Êtes-vous sûr•e de vouloir quitter le groupe \"${group.fullName}\" ?" msgid "GroupsTree|Create a project in this group." msgstr "Créez un projet dans ce groupe." @@ -1739,8 +1936,8 @@ msgstr "En mauvaise santé" msgid "Hide value" msgid_plural "Hide values" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Masquer la valeur" +msgstr[1] "Masquer les valeurs" msgid "History" msgstr "Historique" @@ -1748,6 +1945,9 @@ msgstr "Historique" msgid "Housekeeping successfully started" msgstr "Maintenance démarrée avec succès" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "Si vous avez déjà des fichiers, vous pouvez les pousser à l’aide de la %{link_to_cli} ci-dessous." + msgid "Import repository" msgstr "Importer un dépôt" @@ -1760,6 +1960,9 @@ msgstr "Améliorer la gestion des tickets avec les poids de ticket et GitLab Ent msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "Améliorer la recherche avec la Recherche Globale Avancée et GitLab Enterprise Edition." +msgid "Install Runner on Kubernetes" +msgstr "Installez un Exécuteur sur Kubernetes" + msgid "Install a Runner compatible with GitLab CI" msgstr "Installez un Exécuteur compatible avec l'intégration continue de GitLab" @@ -1769,10 +1972,10 @@ msgstr[0] "Instance" msgstr[1] "Instances" msgid "Instance does not support multiple Kubernetes clusters" -msgstr "" +msgstr "L’instance ne prend pas en charge plusieurs clusters Kubernetes" msgid "Interested parties can even contribute by pushing commits if they want to." -msgstr "" +msgstr "Les parties intéressées peuvent même contribuer en poussant des commits si elles le souhaitent." msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "Interne - Le groupe ainsi que tous les projets internes sont accessibles pour n’importe quel·le utilisa·teur·trice connecté·e." @@ -1802,7 +2005,7 @@ msgid "Issues" msgstr "Tickets" msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." -msgstr "" +msgstr "Les tickets peuvent être des bugs, des tâches ou des idées à discuter. De plus, les tickets sont consultables et filtrables." msgid "Jan" msgstr "Janv." @@ -1810,6 +2013,9 @@ msgstr "Janv." msgid "January" msgstr "Janvier" +msgid "Jobs" +msgstr "Tâches" + msgid "Jul" msgstr "Juill." @@ -1823,25 +2029,28 @@ msgid "June" msgstr "Juin" msgid "Kubernetes" -msgstr "" +msgstr "Kubernetes" msgid "Kubernetes Cluster" -msgstr "" +msgstr "Cluster Kubernetes" msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" -msgstr "" +msgstr "Le temps de création du cluster Kubernetes dépasse le délai d’expiration ; %{timeout}" msgid "Kubernetes cluster integration was not removed." -msgstr "" +msgstr "L’intégration du cluster Kubernetes n’a pas été supprimée." msgid "Kubernetes cluster integration was successfully removed." -msgstr "" +msgstr "L’intégration du cluster Kubernetes a été supprimée avec succès." msgid "Kubernetes cluster was successfully updated." -msgstr "" +msgstr "Le cluster Kubernetes a été mis à jour avec succès." + +msgid "Kubernetes configured" +msgstr "Kubernetes configuré" msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" -msgstr "" +msgstr "L’intégration du service Kubernetes est obsolète. %{deprecated_message_content} vos clusters Kubernetes en utilisant la nouvelle page Clusters Kubernetes" msgid "LFSStatus|Disabled" msgstr "Désactivé" @@ -1853,7 +2062,7 @@ msgid "Labels" msgstr "Labels" msgid "Labels can be applied to issues and merge requests to categorize them." -msgstr "" +msgstr "Les labels peuvent être appliqués aux tickets et aux demandes de fusion pour les classer." msgid "Last %d day" msgid_plural "Last %d days" @@ -1864,7 +2073,7 @@ msgid "Last Pipeline" msgstr "Dernier pipeline" msgid "Last commit" -msgstr "Dernière validation" +msgstr "Dernier commit" msgid "Last edited %{date}" msgstr "Dernière modification le %{date}" @@ -1885,7 +2094,13 @@ msgid "LastPushEvent|at" msgstr "à" msgid "Learn more" -msgstr "" +msgstr "En savoir plus" + +msgid "Learn more about Kubernetes" +msgstr "En savoir plus sur Kubernetes" + +msgid "Learn more about protected branches" +msgstr "En savoir plus sur les branches protégées" msgid "Learn more in the" msgstr "En apprendre plus dans le" @@ -1905,17 +2120,23 @@ msgstr "Quitter le projet" msgid "License" msgstr "Licence" +msgid "List" +msgstr "Liste" + msgid "Loading the GitLab IDE..." -msgstr "" +msgstr "Chargement de l’IDE GitLab…" msgid "Lock" msgstr "Verrouiller" msgid "Lock %{issuableDisplayName}" -msgstr "" +msgstr "Verrouiller %{issuableDisplayName}" + +msgid "Lock not found" +msgstr "Verrou non trouvé" msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." -msgstr "" +msgstr "Verrouiller ce•tte %{issuableDisplayName} ? Seul•e•s les membres du projet seront en mesure de commenter." msgid "Locked" msgstr "Verrouillé" @@ -1923,14 +2144,17 @@ msgstr "Verrouillé" msgid "Locked Files" msgstr "Fichiers verrouillés" +msgid "Locks give the ability to lock specific file or folder." +msgstr "Les verrous donnent la possibilité de verrouiller un fichier ou un dossier spécifique." + msgid "Login" msgstr "Se connecter" msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." -msgstr "" +msgstr "Rendez votre équipe plus productive quel que soit son emplacement. GitLab Geo crée des miroirs en lecture seule de votre instance GitLab afin que vous puissiez réduire le temps nécessaire pour cloner et récupérer de gros dépôts." msgid "Manage labels" -msgstr "" +msgstr "Gérer les labels" msgid "Mar" msgstr "Mars" @@ -1939,7 +2163,7 @@ msgid "March" msgstr "Mars" msgid "Mark done" -msgstr "" +msgstr "Marquer comme fait" msgid "Maximum git storage failures" msgstr "Nombre maximum d’échecs du stockage git" @@ -1953,9 +2177,6 @@ msgstr "Médian" msgid "Members" msgstr "Membres" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "Demandes de fusion" @@ -1966,49 +2187,61 @@ msgid "Merge request" msgstr "Demande de fusion" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" -msgstr "" +msgstr "Les demandes de fusion permettent de proposer les modifications que vous avez apportées à un projet et de discuter de ces modifications avec les autres" + +msgid "MergeRequest|Approved" +msgstr "Approuvée" msgid "Merged" -msgstr "" +msgstr "Fusionnée" msgid "Messages" msgstr "Messages" msgid "Milestone" -msgstr "" +msgstr "Jalon" msgid "Milestones|Delete milestone" -msgstr "" +msgstr "Supprimer le jalon" msgid "Milestones|Delete milestone %{milestoneTitle}?" -msgstr "" +msgstr "Supprimer le jalon %{milestoneTitle} ?" msgid "Milestones|Failed to delete milestone %{milestoneTitle}" -msgstr "" +msgstr "Impossible de supprimer le jalon %{milestoneTitle}" msgid "Milestones|Milestone %{milestoneTitle} was not found" -msgstr "" +msgstr "Le jalon %{milestoneTitle} est introuvable" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "ajouter une clé SSH" +msgid "Modal|Cancel" +msgstr "Annuler" + +msgid "Modal|Close" +msgstr "Fermer" + msgid "Monitoring" msgstr "Surveillance" +msgid "More information" +msgstr "Plus d’informations" + msgid "More information is available|here" msgstr "ici" msgid "Move" -msgstr "" +msgstr "Déplacer" msgid "Move issue" -msgstr "" +msgstr "Déplacer le ticket" msgid "Multiple issue boards" msgstr "Multiple tableaux de tickets" msgid "Name new label" -msgstr "" +msgstr "Nommez le nouveau label" msgid "New Issue" msgid_plural "New Issues" @@ -2016,10 +2249,10 @@ msgstr[0] "Nouveau ticket" msgstr[1] "Nouveaux tickets" msgid "New Kubernetes Cluster" -msgstr "" +msgstr "Nouveau cluster Kubernetes" msgid "New Kubernetes cluster" -msgstr "" +msgstr "Nouveau cluster Kubernetes" msgid "New Pipeline Schedule" msgstr "Nouveau pipeline programmé" @@ -2046,7 +2279,7 @@ msgid "New issue" msgstr "Nouveau ticket" msgid "New label" -msgstr "" +msgstr "Nouveau label" msgid "New merge request" msgstr "Nouvelle demande de fusion" @@ -2067,22 +2300,22 @@ msgid "New tag" msgstr "Nouveau tag" msgid "No assignee" -msgstr "" +msgstr "Aucune personne assignée" msgid "No changes" -msgstr "" +msgstr "Aucun changement" msgid "No connection could be made to a Gitaly Server, please check your logs!" -msgstr "" +msgstr "Aucune connexion n’a pu être établie avec un serveur Gitaly, veuillez vérifier vos logs !" msgid "No due date" -msgstr "" +msgstr "Aucune date d’échéance" msgid "No estimate or time spent" -msgstr "" +msgstr "Aucune estimation ou temps passé" msgid "No file chosen" -msgstr "" +msgstr "Aucun fichier sélectionné" msgid "No repository" msgstr "Pas de dépôt" @@ -2090,24 +2323,24 @@ msgstr "Pas de dépôt" msgid "No schedules" msgstr "Aucun programme" -msgid "No time spent" -msgstr "Pas de temps passé" - msgid "None" msgstr "Aucun·e" msgid "Not allowed to merge" -msgstr "" +msgstr "Non autorisé•e à fusionner" msgid "Not available" msgstr "Indisponible" msgid "Not confidential" -msgstr "" +msgstr "Pas confidentiel•le" msgid "Not enough data" msgstr "Données insuffisantes" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "Notez que la branche principale est automatiquement protégée. %{link_to_protected_branches}" + msgid "Notification events" msgstr "Événement de notifications" @@ -2166,10 +2399,10 @@ msgid "Notifications" msgstr "Notifications" msgid "Notifications off" -msgstr "" +msgstr "Notifications désactivées" msgid "Notifications on" -msgstr "" +msgstr "Notifications activées" msgid "Nov" msgstr "Nov." @@ -2181,7 +2414,7 @@ msgid "Number of access attempts" msgstr "Nombre de tentatives d'accès" msgid "OK" -msgstr "" +msgstr "OK" msgid "Oct" msgstr "Oct." @@ -2196,7 +2429,7 @@ msgid "Only project members can comment." msgstr "Seuls les membres du projet peuvent commenter." msgid "Open" -msgstr "" +msgstr "Ouvrir" msgid "Opened" msgstr "Ouvert" @@ -2210,6 +2443,9 @@ msgstr "Ouvrir dans une nouvelle fenêtre" msgid "Options" msgstr "Paramètres" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "Sinon, il est recommandé de commencer avec l’une des options ci-dessous." + msgid "Overview" msgstr "Vue d'ensemble" @@ -2310,10 +2546,28 @@ msgid "Pipelines for last year" msgstr "Pipelines de l’année dernière" msgid "Pipelines|Build with confidence" -msgstr "" +msgstr "Construire en toute confiance" msgid "Pipelines|Get started with Pipelines" -msgstr "" +msgstr "Premiers pas avec les Pipelines" + +msgid "Pipeline|Retry pipeline" +msgstr "Relancer le pipeline" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "Relancer le pipeline #%{pipelineId} ?" + +msgid "Pipeline|Stop pipeline" +msgstr "Arrêter le pipeline" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "Arrêter le pipeline #%{pipelineId} ?" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "Vous êtes sur le point de relancer le pipeline %{pipelineId}." + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "Vous êtes sur le point d’arrêter le pipeline %{pipelineId}." msgid "Pipeline|all" msgstr "Tous" @@ -2328,10 +2582,10 @@ msgid "Pipeline|with stages" msgstr "avec les étapes" msgid "Play" -msgstr "" +msgstr "Lancer" msgid "Please enable billing for one of your projects to be able to create a Kubernetes cluster, then try again." -msgstr "" +msgstr "Merci d’activer la facturation pour un de vos projets afin d’être en mesure de créer un cluster Kubernetes, puis essayez à nouveau." msgid "Please solve the reCAPTCHA" msgstr "Veuillez résoudre le reCAPTCHA" @@ -2340,7 +2594,7 @@ msgid "Preferences" msgstr "Préférences" msgid "Primary" -msgstr "" +msgstr "Principal" msgid "Private - Project access must be granted explicitly to each user." msgstr "Privé - L’accès au projet doit être autorisé explicitement pour chaque utilisa·teur·trice." @@ -2348,6 +2602,9 @@ msgstr "Privé - L’accès au projet doit être autorisé explicitement pour ch msgid "Private - The group and its projects can only be viewed by members." msgstr "Privé - Le groupe ainsi que ses projets ne sont accessibles qu’à ses membres." +msgid "Private projects can be created in your personal namespace with:" +msgstr "Des projets privés peuvent être créés dans votre espace de noms personnel avec :" + msgid "Profile" msgstr "Profil" @@ -2388,7 +2645,7 @@ msgid "Profiles|your account" msgstr "votre compte" msgid "Programming languages used in this repository" -msgstr "" +msgstr "Langages de programmation utilisés dans ce dépôt" msgid "Project '%{project_name}' is in the process of being deleted." msgstr "Le projet “%{project_name}” est en train d’être supprimé." @@ -2406,13 +2663,13 @@ msgid "Project access must be granted explicitly to each user." msgstr "L’accès au projet doit être explicitement accordé à chaque utilisateur." msgid "Project avatar" -msgstr "" +msgstr "Avatar du projet" msgid "Project avatar in repository: %{link}" -msgstr "" +msgstr "Avatar du project dans le dépôt : %{link}" msgid "Project cache successfully reset." -msgstr "" +msgstr "Le cache du projet a bien été réinitialisé." msgid "Project details" msgstr "Détails du projet" @@ -2433,19 +2690,19 @@ msgid "ProjectActivityRSS|Subscribe" msgstr "S’abonner" msgid "ProjectCreationLevel|Allowed to create projects" -msgstr "" +msgstr "Autorisé•e à créer des projets" msgid "ProjectCreationLevel|Default project creation protection" -msgstr "" +msgstr "Protection de création de projet par défaut" msgid "ProjectCreationLevel|Developers + Masters" -msgstr "" +msgstr "Développeur•euse•s + Expert•e•s" msgid "ProjectCreationLevel|Masters" -msgstr "" +msgstr "Expert•e•s" msgid "ProjectCreationLevel|No one" -msgstr "" +msgstr "Personne" msgid "ProjectFeature|Disabled" msgstr "Désactivé" @@ -2472,7 +2729,7 @@ msgid "ProjectSettings|Contact an admin to change this setting." msgstr "Contactez un administrateur pour modifier ce paramètre." msgid "ProjectSettings|Only signed commits can be pushed to this repository." -msgstr "Seules les validations signées peuvent être poussées sur ce dépôt." +msgstr "Seules les commits signés peuvent être poussés sur ce dépôt." msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." msgstr "Ce paramètre est appliqué au niveau du serveur et peut être modifié par un administrateur." @@ -2484,7 +2741,7 @@ msgid "ProjectSettings|This setting will be applied to all projects unless overr msgstr "Ce paramètre s’appliquera à tous les projets à moins qu’un•e administrat•eur•rice ne le modifie." msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails." -msgstr "Les utilisateurs peuvent uniquement pousser sur ce dépôt des validations qui ont été validées avec une de leurs adresses courriels vérifiées." +msgstr "Les utilisateurs peuvent uniquement pousser sur ce dépôt des commits qui ont été validés avec une de leurs adresses courriels vérifiées." msgid "Projects" msgstr "Projets" @@ -2510,12 +2767,30 @@ msgstr "Désolé, aucun projet ne correspond à votre recherche" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "Cette fonctionnalité requiert le support du localStorage par votre navigateur" +msgid "PrometheusService|Active" +msgstr "Actif" + +msgid "PrometheusService|Auto configuration" +msgstr "Configuration automatique" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "Déployer et configurer automatiquement Prometheus sur vos clusters pour surveiller les environnements de vos projets" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "Par défaut, Prometheus écoute sur 'http://localhost:9090'. Il n’est pas recommandé de changer l’adresse et le port par défaut car cela pourrait affecter ou entrer en conflit avec d'autres services fonctionnant sur le serveur GitLab." msgid "PrometheusService|Finding and configuring metrics..." msgstr "Recherche et configuration des métriques en cours…" +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "Installer Prometheus sur les clusters" + +msgid "PrometheusService|Manage clusters" +msgstr "Gérer les clusters" + +msgid "PrometheusService|Manual configuration" +msgstr "Configuration manuelle" + msgid "PrometheusService|Metrics" msgstr "Métriques" @@ -2537,14 +2812,23 @@ msgstr "Aucune métrique n’est surveillée. Pour démarrer la surveillance, d msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "URL de base de l’API Prometheus, comme http://prometheus.example.com/" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "Prometheus est géré automatiquement sur vos clusters" + msgid "PrometheusService|Time-series monitoring service" -msgstr "" +msgstr "Service de surveillance de séries temporelles" + +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "Pour activer la configuration manuelle, désinstallez Prometheus de vos clusters" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "Pour activer l’installation de Prometheus sur vos clusters, désactivez la configuration manuelle ci-dessous" msgid "PrometheusService|View environments" msgstr "Afficher les environnements" msgid "Protip:" -msgstr "" +msgstr "Astuce :" msgid "Public - The group and any public projects can be viewed without any authentication." msgstr "Public - Le groupe ainsi que n’importe quel projet public est accessible sans authentification." @@ -2558,11 +2842,17 @@ msgstr "Règles de poussée" msgid "Push events" msgstr "Évènements de poussée" +msgid "Push project from command line" +msgstr "Pousser le projet en ligne de commande" + +msgid "Push to create a project" +msgstr "Pousser pour créer un projet" + msgid "PushRule|Committer restriction" msgstr "Restriction du validateur" msgid "Quick actions can be used in the issues description and comment boxes." -msgstr "" +msgstr "Les actions rapides peuvent être utilisées dans la description des tickets et dans les zones de commentaire." msgid "Read more" msgstr "Lire plus" @@ -2577,16 +2867,16 @@ msgid "RefSwitcher|Tags" msgstr "Tags" msgid "Reference:" -msgstr "" +msgstr "Référence :" msgid "Register / Sign In" -msgstr "" +msgstr "Inscription / Connexion" msgid "Registry" msgstr "Registre" msgid "Related Commits" -msgstr "Validations liées" +msgstr "Commits liés" msgid "Related Deployed Jobs" msgstr "Tâches de déploiement liées" @@ -2607,20 +2897,23 @@ msgid "Remind later" msgstr "Me le rappeler ultérieurement" msgid "Remove" -msgstr "" +msgstr "Supprimer" msgid "Remove avatar" -msgstr "" +msgstr "Supprimer l’avatar" msgid "Remove project" msgstr "Supprimer le projet" msgid "Repair authentication" -msgstr "" +msgstr "Réparer l’authentification" msgid "Repository" msgstr "Dépôt" +msgid "Repository has no locks." +msgstr "Le dépôt n’a pas de verrous." + msgid "Request Access" msgstr "Demander l'accès" @@ -2633,17 +2926,23 @@ msgstr "Réinitialiser le jeton d’accès au bilan de santé" msgid "Reset runners registration token" msgstr "Réinitialiser le jeton d’inscription des exécuteurs" +msgid "Resolve discussion" +msgstr "Résoudre la discussion" + msgid "Reveal value" msgid_plural "Reveal values" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "Révéler la valeur" +msgstr[1] "Révéler les valeurs" msgid "Revert this commit" -msgstr "Défaire cette validation" +msgstr "Défaire ce commit" msgid "Revert this merge request" msgstr "Défaire cette demande de fusion" +msgid "Roadmap" +msgstr "Feuille de route" + msgid "SSH Keys" msgstr "Clés SSH" @@ -2654,7 +2953,7 @@ msgid "Save pipeline schedule" msgstr "Sauvegarder le pipeline programmé" msgid "Save variables" -msgstr "" +msgstr "Enregistrer les variables" msgid "Schedule a new pipeline" msgstr "Programmer un nouveau pipeline" @@ -2672,13 +2971,13 @@ msgid "Search branches and tags" msgstr "Rechercher dans les branches et les étiquettes" msgid "Search milestones" -msgstr "" +msgstr "Recherche de jalons" msgid "Search project" -msgstr "" +msgstr "Recherche de projets" msgid "Search users" -msgstr "" +msgstr "Recherche d’utilisateur•rice•s" msgid "Seconds before reseting failure information" msgstr "Nombre de secondes avant de réinitialiser les informations d’échec" @@ -2687,7 +2986,10 @@ msgid "Seconds to wait for a storage access attempt" msgstr "Nombre de secondes d’attente pour un essai d'accès au stockage" msgid "Secret variables" -msgstr "" +msgstr "Variables secrètes" + +msgid "Security report" +msgstr "Rapport de sécurité" msgid "Select Archive Format" msgstr "Sélectionnez le format de l'archive" @@ -2695,17 +2997,23 @@ msgstr "Sélectionnez le format de l'archive" msgid "Select a timezone" msgstr "Sélectionnez un fuseau horaire" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "Sélectionnez un cluster Kubernetes existant ou créez-en un nouveau" + msgid "Select assignee" -msgstr "" +msgstr "Sélectionner une personne assignée" msgid "Select branch/tag" -msgstr "" +msgstr "Sélectionnez une branche / un tag" msgid "Select target branch" msgstr "Sélectionnez une branche cible" msgid "Selective synchronization" -msgstr "" +msgstr "Synchronisation sélective" + +msgid "Send email" +msgstr "Envoyer un courriel" msgid "Sep" msgstr "Sept." @@ -2714,37 +3022,43 @@ msgid "September" msgstr "Septembre" msgid "Server version" -msgstr "" +msgstr "Version du serveur" msgid "Service Templates" msgstr "Modèles de service" +msgid "Service URL" +msgstr "URL du service" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Définissez un mot de passe pour votre compte pour pouvoir tirer ou pousser par %{protocol}." msgid "Set up CI/CD" -msgstr "" +msgstr "Configurer CI/CD" msgid "Set up Koding" msgstr "Mettre en place Koding" -msgid "Set up auto deploy" -msgstr "Mettre en place l’auto-déploiement" - msgid "SetPasswordToCloneLink|set a password" msgstr "définir un mot de passe" msgid "Settings" msgstr "Paramètres" +msgid "Setup a specific Runner automatically" +msgstr "Configurer un Exécuteur spécifique automatiquement" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." -msgstr "" +msgstr "En réinitialisant les minutes du pipeline pour cet espace de noms, les minutes actuellement utilisées seront remises à zéro." msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" -msgstr "" +msgstr "Réinitialiser les minutes du pipeline" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" -msgstr "" +msgstr "Réinitialiser les minutes de pipeline utilisées" + +msgid "Show command" +msgstr "Afficher la commande" msgid "Show parent pages" msgstr "Afficher les pages parentes" @@ -2773,19 +3087,25 @@ msgid "Snippets" msgstr "Extraits de code" msgid "Something went wrong on our end" -msgstr "" +msgstr "Une erreur est survenue de notre côté" msgid "Something went wrong on our end." msgstr "Une erreur est survenue de notre côté." msgid "Something went wrong trying to change the confidentiality of this issue" -msgstr "" +msgstr "Quelque chose s’est mal passé en essayant de changer la confidentialité de ce ticket" msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "Quelque chose ne s‘est pas bien passé en essayant de changer l’état de verrouillage de cette ${this.issuableDisplayName}" msgid "Something went wrong when toggling the button" -msgstr "" +msgstr "Une erreur s’est produite lors du basculement du bouton" + +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "Une erreur s’est produite lors de la fermeture du/de la %{issuable}. Veuillez réessayer plus tard" + +msgid "Something went wrong while fetching SAST." +msgstr "Une erreur s’est produite lors de la récupération de la SAST." msgid "Something went wrong while fetching the projects." msgstr "Une erreur s'est produite lors de la récupération des projets." @@ -2793,9 +3113,15 @@ msgstr "Une erreur s'est produite lors de la récupération des projets." msgid "Something went wrong while fetching the registry list." msgstr "Une erreur s'est produite lors de la récupération de la liste du registre." -msgid "Something went wrong. Please try again." +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "Une erreur s’est produite lors de la réouverture du / de la %{issuable}. Veuillez réessayer plus tard" + +msgid "Something went wrong while resolving this discussion. Please try again." msgstr "" +msgid "Something went wrong. Please try again." +msgstr "Quelque chose s’est mal passé. Veuillez réessayer." + msgid "Sort by" msgstr "Trier par" @@ -2898,6 +3224,9 @@ msgstr "Poids" msgid "Source" msgstr "Source" +msgid "Source (branch or tag)" +msgstr "Source (branche ou tag)" + msgid "Source code" msgstr "Code source" @@ -2926,7 +3255,7 @@ msgid "Stopped" msgstr "Arrêté" msgid "Storage" -msgstr "" +msgstr "Stockage" msgid "Subgroups" msgstr "Sous-groupes" @@ -2937,22 +3266,22 @@ msgstr "Changer de branche / tag" msgid "System Hooks" msgstr "Crochets système" -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "Étiquette" -msgstr[1] "Tags" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "Tag (%{tag_count})" +msgstr[1] "Tags (%{tag_count})" msgid "Tags" msgstr "Tags" msgid "TagsPage|Browse commits" -msgstr "Parcourir les validations" +msgstr "Parcourir les commits" msgid "TagsPage|Browse files" msgstr "Parcourir les fichiers" msgid "TagsPage|Can't find HEAD commit for this tag" -msgstr "Impossible de trouver la validation HEAD pour ce tag" +msgstr "Impossible de trouver le commit HEAD pour ce tag" msgid "TagsPage|Cancel" msgstr "Annuler" @@ -2970,7 +3299,7 @@ msgid "TagsPage|Edit release notes" msgstr "Modifier les notes de version" msgid "TagsPage|Existing branch name, tag, or commit SHA" -msgstr "Nom de branche, tag, ou SHA de validation existant" +msgstr "Nom de branche, tag, ou SHA de commit existant" msgid "TagsPage|Filter by tag name" msgstr "Filtrer par nom de tag" @@ -3027,13 +3356,13 @@ msgid "The Advanced Global Search in GitLab is a powerful search service that sa msgstr "La Recherche Globale Avancée de Gitlab est un outils puissant qui vous fait gagner du temps. Au lieu de créer du code similaire et perdre du temps, vous pouvez maintenant chercher dans le code d'autres équipes pour vous aider sur votre projet." msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" -msgstr "" +msgstr "Le suivi des tickets est l’endroit où ajouter des éléments à améliorer ou à résoudre dans un projet" msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." -msgstr "" +msgstr "Le suivi des tickets est l’endroit où ajouter des éléments à améliorer ou à résoudre dans un projet. Vous pouvez vous inscrire ou vous connecter pour créer des tickets pour ce projet." msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." -msgstr "L’étape de développement montre le temps entre la première validation et la création de la demande de fusion. Les données seront automatiquement ajoutées ici une fois que vous aurez créé votre première demande de fusion." +msgstr "L’étape de développement montre le temps entre le premier commit et la création de la demande de fusion. Les données seront automatiquement ajoutées ici une fois que vous aurez créé votre première demande de fusion." msgid "The collection of events added to the data gathered for that stage." msgstr "L’ensemble d’évènements ajoutés aux données récupérées pour cette étape." @@ -3045,7 +3374,7 @@ msgid "The issue stage shows the time it takes from creating an issue to assigni msgstr "L'étape des tickets montre le temps nécessaire entre la création d'un ticket et son assignation à un jalon, ou son ajout à une liste d'un tableau de tickets. Commencez par créer des tickets pour voir des données pour cette étape." msgid "The maximum file size allowed is 200KB." -msgstr "" +msgstr "La taille de fichier maximale autorisée est de 200 Ko." msgid "The number of attempts GitLab will make to access a storage." msgstr "Le nombre de tentatives que GitLab va effectuer pour accéder au stockage." @@ -3057,7 +3386,7 @@ msgid "The phase of the development lifecycle." msgstr "Les étapes du cycle de développement." msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." -msgstr "L’étape de planification montre le temps entre l’étape précédente et l’envoi de votre première validation. Ce temps sera automatiquement ajouté quand vous pousserez votre première validation." +msgstr "L’étape de planification montre le temps entre l’étape précédente et l’envoi de votre premier commit. Ce temps sera automatiquement ajouté quand vous pousserez votre premier commit." msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle." msgstr "L’étape de mise en production montre le temps nécessaire entre la création d’un ticket et le déploiement du code en production. Les données seront automatiquement ajoutées une fois que vous aurez complété le cycle complet, depuis l’idée jusqu’à la mise en production." @@ -3071,9 +3400,15 @@ msgstr "Votre projet peut être accédé sans aucune authentification." msgid "The repository for this project does not exist." msgstr "Le dépôt pour ce projet n'existe pas." +msgid "The repository for this project is empty" +msgstr "Le dépôt de ce projet est vide" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "L’étape d’évaluation montre le temps entre la création de la demande de fusion et la fusion effective de celle-ci. Ces données seront automatiquement ajoutées après que vous ayez fusionné votre première demande de fusion." +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "La feuille de route montre la progression de vos épopées dans le temps" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "L’étape de pré-production indique le temps entre la fusion de la DF et le déploiement du code dans l’environnent de production. Les données seront automatiquement ajoutées une fois que vous déploierez en production pour la première fois." @@ -3087,7 +3422,7 @@ msgid "The time in seconds GitLab will try to access storage. After this time a msgstr "Temps en secondes pendant lequel GitLab essaiera d’accéder au stockage. Après ce délai, une erreur d’expiration d’attente sera déclenchée." msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." -msgstr "" +msgstr "Le temps en secondes entre les vérifications de stockage. GitLab ne débute pas de nouvelle vérification si une vérification est déjà en cours." msgid "The time taken by each data entry gathered by that stage." msgstr "Le temps pris par chaque entrée récoltée durant cette étape." @@ -3096,37 +3431,37 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet msgstr "La valeur située au point médian d’une série de valeur observée. C.à.d., entre 3, 5, 9, le médian est 5. Entre 3, 5, 7, 8, le médian est (5+7)/2 = 6." msgid "There are no issues to show" -msgstr "" +msgstr "Il n’y a aucun ticket à afficher" msgid "There are no merge requests to show" -msgstr "" +msgstr "Il n’y a aucune demande de fusion à afficher" msgid "There are problems accessing Git storage: " msgstr "Il y a des difficultés à accéder aux données Git : " msgid "There was an error loading users activity calendar." -msgstr "" +msgstr "Une erreur s’est produite lors du chargement du calendrier d’activité des utilisateurs." msgid "There was an error saving your notification settings." -msgstr "" +msgstr "Une erreur s’est produite lors de l’enregistrement de vos paramètres de notification." msgid "There was an error subscribing to this label." -msgstr "" +msgstr "Une erreur s’est produite lors de l’abonnement à ce label." msgid "There was an error when reseting email token." -msgstr "" +msgstr "Une erreur s’est produite lors de la réinitialisation du jeton de courriel." msgid "There was an error when subscribing to this label." -msgstr "" +msgstr "Une erreur s’est produite lors de l’abonnement à ce label." msgid "There was an error when unsubscribing from this label." -msgstr "" +msgstr "Une erreur s’est produite lors de la désinscription à ce label." msgid "This board\\'s scope is reduced" msgstr "La portée de ce tableau est limitée" msgid "This directory" -msgstr "" +msgstr "Ce répertoire" msgid "This is a confidential issue." msgstr "Ce ticket est confidentiel." @@ -3135,7 +3470,7 @@ msgid "This is the author's first Merge Request to this project." msgstr "C’est la première demande de fusion de cet auteur pour ce projet." msgid "This issue is confidential" -msgstr "" +msgstr "Ce ticket est confidentiel" msgid "This issue is confidential and locked." msgstr "Ce ticket est confidentiel et verrouillé." @@ -3144,22 +3479,22 @@ msgid "This issue is locked." msgstr "Ce ticket est verrouillé." msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" -msgstr "" +msgstr "Cette tâche dépend de l’utilisateur•rice pour déclencher son processus. Elles sont souvent utilisées pour déployer du code dans des environnements de production" msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" -msgstr "" +msgstr "Cette tâche dépend des tâches en amont qui doivent réussir pour que celle-ci soit déclenchée" msgid "This job has not been triggered yet" -msgstr "" +msgstr "Cette tâche n’a pas encore été déclenchée" msgid "This job has not started yet" -msgstr "" +msgstr "Cete tâche n’a pas encore commencée" msgid "This job is in pending state and is waiting to be picked by a runner" -msgstr "" +msgstr "Cette tâche est en attente et attend d’être choisie par un exécuteur" msgid "This job requires a manual action" -msgstr "" +msgstr "Cette tâche nécessite une action manuelle" msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "Cela signifie que vous ne pouvez pas pousser du code tant que vous n’avez pas créé un dépôt vide, ou que vous n’avez pas importé un dépôt existant." @@ -3167,11 +3502,14 @@ msgstr "Cela signifie que vous ne pouvez pas pousser du code tant que vous n’a msgid "This merge request is locked." msgstr "Cette demande de fusion est verrouillée." +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "Cette page n’est pas disponible car vous n’êtes pas autorisé à lire des informations dans plusieurs projets." + msgid "This project" -msgstr "" +msgstr "Ce projet" msgid "This repository" -msgstr "" +msgstr "Ce dépôt" msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." msgstr "Ces emails deviennent automatiquement des tickets (les commentaires étant extrait de la conversation par email) répertoriés ici." @@ -3186,19 +3524,19 @@ msgid "Time between merge request creation and merge/close" msgstr "Temps entre la création d'une demande de fusion et sa fusion/clôture" msgid "Time tracking" -msgstr "" +msgstr "Suivi du temps" msgid "Time until first merge request" msgstr "Temps jusqu’à la première demande de fusion" msgid "TimeTrackingEstimated|Est" -msgstr "" +msgstr "Est" msgid "TimeTracking|Estimated:" -msgstr "" +msgstr "Estimé :" msgid "TimeTracking|Spent" -msgstr "" +msgstr "Passé" msgid "Timeago|%s days ago" msgstr "il y a %s jours" @@ -3336,29 +3674,35 @@ msgstr[1] "mins" msgid "Time|s" msgstr "s" +msgid "Tip:" +msgstr "Astuce :" + msgid "Title" msgstr "Titre" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "Pour afficher la feuille de route, ajoutez une date de début ou de fin planifiée à l’une de vos épopées dans ce groupe ou ses sous-groupes. Seules les épopées des 3 derniers mois et des 3 prochains mois sont affichées." + msgid "Todo" -msgstr "" +msgstr "Tâche" msgid "Toggle sidebar" -msgstr "" +msgstr "Afficher/masquer la barre latérale" msgid "ToggleButton|Toggle Status: OFF" -msgstr "" +msgstr "État du commutateur : Inactif" msgid "ToggleButton|Toggle Status: ON" -msgstr "" +msgstr "État du commutateur : Actif" msgid "Total Time" msgstr "Temps total" -msgid "Total issue time spent" -msgstr "Temps total passé sur les tickets" - msgid "Total test time for all commits/merges" -msgstr "Temps total de test pour toutes les validations/fusions" +msgstr "Temps total de test pour tous les commits/fusions" + +msgid "Total: %{total}" +msgstr "Total : %{total}" msgid "Track activity with Contribution Analytics." msgstr "Suivre l’activité avec Contribution Analytics." @@ -3366,41 +3710,38 @@ msgstr "Suivre l’activité avec Contribution Analytics." msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "Suivez les groupes de tickets qui partagent un thème, entre projets et jalons" -msgid "Total: %{total}" -msgstr "" - msgid "Track time with quick actions" -msgstr "" +msgstr "Suivre le temps estimé/passé avec les actions rapides" msgid "Trigger this manual action" -msgstr "" +msgstr "Déclencher cette action manuelle" msgid "Turn on Service Desk" msgstr "Activer le Service Desk" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." -msgstr "" +msgstr "Impossible de réinitialiser le cache du projet." msgid "Unknown" -msgstr "" +msgstr "Inconnu" msgid "Unlock" msgstr "Déverrouiller" msgid "Unlock this %{issuableDisplayName}? Everyone will be able to comment." -msgstr "" +msgstr "Débloquez le•a %{issuableDisplayName} ? Tout le monde sera en mesure de commenter." msgid "Unlocked" msgstr "Déverrouillé" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "Supprimer des favoris" msgid "Up to date" -msgstr "" +msgstr "À jour" msgid "Upgrade your plan to activate Advanced Global Search." msgstr "Mettez à jour votre abonnement pour activer la Recherche Globale Avancée." @@ -3424,7 +3765,7 @@ msgid "Upload file" msgstr "Téléverser un fichier" msgid "Upload new avatar" -msgstr "" +msgstr "Importer un nouvel avatar" msgid "UploadLink|click to upload" msgstr "Cliquez pour envoyer" @@ -3439,13 +3780,16 @@ msgid "Use your global notification setting" msgstr "Utiliser vos paramètres de notification globaux" msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." -msgstr "" +msgstr "Les variables sont appliquées aux environnements via l’exécuteur. Elles peuvent être protégées en les exposant uniquement à des branches ou des tags protégées. Vous pouvez utiliser des variables pour les mots de passe, les clés secrètes ou tout ce que vous voulez." + +msgid "View epics list" +msgstr "Afficher la liste des épopées" msgid "View file @ " msgstr "Voir le fichier @ " msgid "View labels" -msgstr "" +msgstr "Afficher les labels" msgid "View open merge request" msgstr "Afficher la demande de fusion" @@ -3469,7 +3813,7 @@ msgid "Want to see the data? Please ask an administrator for access." msgstr "Vous voulez voir les données ? Merci de contacter un administrateur pour en obtenir l’accès." msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." -msgstr "" +msgstr "Nous n’avons pas pu vérifier que l’un de vos projets sur GCP est activé pour la facturation. Veuillez réessayer." msgid "We don't have enough data to show this stage." msgstr "Nous n'avons pas suffisamment de données pour afficher cette étape." @@ -3477,6 +3821,9 @@ msgstr "Nous n'avons pas suffisamment de données pour afficher cette étape." msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "Nous voulons être sûrs que c'est bien vous, merci de confirmer que vous n’êtes pas un robot." +msgid "Web IDE" +msgstr "Web IDE" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "Les webhooks vous permettent d’appeler une URL si, par exemple, du nouveau code est poussé ou un nouveau ticket est créé. Vous pouvez configurer les webhooks pour écouter les événements spécifiques comme des poussées de code, des tickets ou des demandes de fusion. Les webhooks de groupes s’appliqueront à tous les projets dans un groupe, ce qui vous permet de normaliser la fonctionnalité du webhook dans votre groupe entier." @@ -3502,10 +3849,10 @@ msgid "WikiClone|Start Gollum and edit locally" msgstr "Démarrer Gollum et modifier localement" msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." -msgstr "" +msgstr "Astuce : Vous pouvez déplacer cette page en ajoutant le chemin au début du titre." msgid "WikiEdit|There is already a page with the same title in that path." -msgstr "" +msgstr "Il y a déjà une page avec le même titre pour ce chemin." msgid "WikiEmptyPageError|You are not allowed to create wiki pages" msgstr "Vous n’êtes pas autorisé·e à créer des pages wiki" @@ -3597,6 +3944,9 @@ msgstr "Avec Contribution Analytics vous pouvez avoir un aperçu de l’activit msgid "Withdraw Access Request" msgstr "Retirer la demande d'accès" +msgid "Write a commit message..." +msgstr "Écrire un message de commit…" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "Vous êtes sur le point de supprimer %{group_name}. Les groupes supprimés NE PEUVENT PAS être restaurés ! Êtes vous ABSOLUMENT sûr·e ?" @@ -3609,20 +3959,23 @@ msgstr "Vous allez supprimer la relation de fourche avec le projet source %{fork msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Vous allez transférer %{project_name_with_namespace} à un nouveau propriétaire. Êtes vous ABSOLUMENT sûr·e ?" +msgid "You can also create a project from the command line." +msgstr "Vous pouvez également créer un projet en ligne de commande." + msgid "You can also star a label to make it a priority label." -msgstr "" +msgstr "Vous pouvez marquer un label comme important pour en faire un label prioritaire." -msgid "You can move around the graph by using the arrow keys." -msgstr "" +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" +msgstr "Vous pouvez facilement installer un Exécuteur sur un cluster Kubernetes. %{link_to_help_page}" msgid "You can move around the graph by using the arrow keys." -msgstr "" +msgstr "Vous pouvez vous déplacer dans le graphique en utilisant les touches fléchées." msgid "You can only add files when you are on a branch" msgstr "Vous ne pouvez ajouter de fichier que dans une branche" msgid "You can only edit files when you are on a branch" -msgstr "" +msgstr "Vous ne pouvez éditer de fichier que dans une branche" msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "Vous ne pouvez pas écrire sur une instance GitLab Geo secondaire en lecture-seule. Veuillez utiliser le %{link_to_primary_node} à la place." @@ -3630,12 +3983,24 @@ msgstr "Vous ne pouvez pas écrire sur une instance GitLab Geo secondaire en lec msgid "You cannot write to this read-only GitLab instance." msgstr "Vous ne pouvez pas écrire sur cette instance GitLab en lecture-seule." +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "Vous ne disposez pas des autorisations appropriées pour remplacer les paramètres de synchronisation du groupe LDAP." + +msgid "You have no permissions" +msgstr "Vous n’avez pas les autorisations" + msgid "You have reached your project limit" msgstr "Vous avez atteint votre limite de projet" +msgid "You must have master access to force delete a lock" +msgstr "Vous devez avoir un accès Expert pour forcer la suppression d’un verrou" + msgid "You must sign in to star a project" msgstr "Vous devez vous connecter pour mettre un projet en favori" +msgid "You need a different license to enable FileLocks feature" +msgstr "Vous avez besoin d’une licence différente pour activer la fonctionnalité FileLocks" + msgid "You need permission." msgstr "Vous avez besoin d’une autorisation." @@ -3664,10 +4029,13 @@ msgid "You won't be able to pull or push project code via SSH until you add an S msgstr "Vous ne pourrez pas récupérer ou pousser de code par SSH tant que vous n’aurez pas ajouté de clé SSH à votre profil" msgid "You'll need to use different branch names to get a valid comparison." -msgstr "" +msgstr "Vous devrez utiliser différents noms de branches pour obtenir une comparaison valide." msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" -msgstr "" +msgstr "Vos informations de cluster Kubernetes sur cette page sont toujours modifiables, mais il est conseillé de désactiver et reconfigurer" + +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "Vos modifications ont été validées. Commit %{commitId} %{commitStats}" msgid "Your comment will not be visible to the public." msgstr "Votre commentaire ne sera pas visible publiquement." @@ -3682,7 +4050,7 @@ msgid "Your projects" msgstr "Vos projets" msgid "assign yourself" -msgstr "" +msgstr "assignez vous" msgid "branch name" msgstr "nom de la branche" @@ -3691,58 +4059,73 @@ msgid "by" msgstr "par" msgid "ciReport|Code quality" -msgstr "" +msgstr "Qualité du code" msgid "ciReport|DAST detected no alerts by analyzing the review app" -msgstr "" +msgstr "DAST n’a détecté aucune alerte en analysant l’application de revue" -msgid "ciReport|Failed to load ${type} report" -msgstr "" +msgid "ciReport|Failed to load %{reportName} report" +msgstr "Impossible de charger le rapport %{reportName}" msgid "ciReport|Fixed:" -msgstr "" +msgstr "Réparé :" msgid "ciReport|Instances" -msgstr "" +msgstr "Instances" msgid "ciReport|Learn more about whitelisting" -msgstr "" +msgstr "En savoir plus sur la liste blanche" -msgid "ciReport|Loading ${type} report" -msgstr "" +msgid "ciReport|Loading %{reportName} report" +msgstr "Chargement du rapport %{reportName}" msgid "ciReport|No changes to code quality" -msgstr "" +msgstr "Aucun changement dans la qualité du code" msgid "ciReport|No changes to performance metrics" -msgstr "" +msgstr "Aucun changement dans les indicateurs de performance" msgid "ciReport|Performance metrics" -msgstr "" +msgstr "Indicateurs de performance" msgid "ciReport|SAST" -msgstr "" +msgstr "SAST" + +msgid "ciReport|SAST degraded on" +msgstr "SAST dégradée sur" + +msgid "ciReport|SAST detected" +msgstr "SAST détectée" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "SAST n’a détecté aucune nouvelle vulnérabilité de sécurité" msgid "ciReport|SAST detected no security vulnerabilities" -msgstr "" +msgstr "SAST n’a détecté aucune vulnérabilité de sécurité" msgid "ciReport|SAST:container no vulnerabilities were found" -msgstr "" +msgstr "SAST:conteneur aucune vulnérabilité n’a été trouvée" msgid "ciReport|Show complete code vulnerabilities report" -msgstr "" +msgstr "Afficher le rapport complet sur les vulnérabilités du code" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" -msgstr "" +msgstr "Les vulnérabilités non approuvées (en rouge) peuvent être marquées comme approuvées. %{helpLink}" + +msgid "ciReport|no security vulnerabilities" +msgstr "aucune vulnérabilité de sécurité" + +msgid "command line instructions" +msgstr "instructions en ligne de commande" msgid "commit" msgstr "validation" msgid "confidentiality|You are going to turn off the confidentiality. This means everyone will be able to see and leave a comment on this issue." -msgstr "" +msgstr "Vous allez désactiver la confidentialité. Cela signifie que tout le monde sera en mesure de voir et de laisser un commentaire sur ce ticket." msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with at least Reporter access are able to see and leave comments on the issue." -msgstr "" +msgstr "Vous allez activer la confidentialité. Cela signifie que seuls les membres de l’équipe avec au moins un accès Reporter sont capables de voir et de laisser des commentaires sur le ticket." msgid "day" msgid_plural "days" @@ -3750,141 +4133,180 @@ msgstr[0] "jour" msgstr[1] "jours" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." -msgstr "" +msgstr "%{slash_command} mettra à jour la durée estimée avec la dernière commande." + +msgid "is invalid because there is downstream lock" +msgstr "est invalide car il y a un verrou en aval" + +msgid "is invalid because there is upstream lock" +msgstr "est invalide car il y a un verrou en amont" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "verrouillé par %{path_lock_user_name} %{created_at}" msgid "merge request" msgid_plural "merge requests" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "demande de fusion" +msgstr[1] "demandes de fusion" + +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "Veuillez la restaurer ou utiliser une autre branche %{missingBranchName}" + +msgid "mrWidget|Add approval" +msgstr "Ajouter une approbation" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "Une erreur est survenue lors de la suppression de votre approbation." + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "Une erreur s’est produite lors de la récupération des données d’approbation pour cette demande de fusion." + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "Une erreur est survenue pendant l’envoi de votre approbation." + +msgid "mrWidget|Approve" +msgstr "Approuver" + +msgid "mrWidget|Approved by" +msgstr "Approuvée par" msgid "mrWidget|Cancel automatic merge" -msgstr "" +msgstr "Annuler la fusion automatique" msgid "mrWidget|Check out branch" -msgstr "" +msgstr "Récupérer la branche" msgid "mrWidget|Checking ability to merge automatically" -msgstr "" +msgstr "Vérification de la possibilité de fusion automatique" msgid "mrWidget|Cherry-pick" -msgstr "" +msgstr "Picorer" msgid "mrWidget|Cherry-pick this merge request in a new merge request" -msgstr "" +msgstr "Picorer cette demande de fusion dans une nouvelle demande de fusion" msgid "mrWidget|Closed" -msgstr "" +msgstr "Fermée" msgid "mrWidget|Closed by" -msgstr "" +msgstr "Fermée par" msgid "mrWidget|Closes" -msgstr "" +msgstr "Résout" msgid "mrWidget|Did not close" -msgstr "" +msgstr "N’a pas résolu" msgid "mrWidget|Email patches" -msgstr "" +msgstr "Patchs par courriel" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" -msgstr "" +msgstr "Si la branche %{branch} existe dans votre dépôt local, vous pouvez fusionner cette demande de fusion manuellement à l’aide de" + +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "Si la branche %{missingBranchName} existe dans votre dépôt local, vous pouvez fusionner cette demande de fusion manuellement en ligne de commande" msgid "mrWidget|Mentions" -msgstr "" +msgstr "Mentionne" msgid "mrWidget|Merge" -msgstr "" +msgstr "Fusionner" msgid "mrWidget|Merge failed." -msgstr "" +msgstr "La fusion a échoué." msgid "mrWidget|Merge locally" -msgstr "" +msgstr "Fusionner localement" msgid "mrWidget|Merged by" -msgstr "" +msgstr "Fusionnée par" msgid "mrWidget|Plain diff" -msgstr "" +msgstr "Diff simple" msgid "mrWidget|Refresh" -msgstr "" +msgstr "Actualiser" msgid "mrWidget|Refresh now" -msgstr "" +msgstr "Actualiser maintenant" msgid "mrWidget|Refreshing now" -msgstr "" +msgstr "Actualisation en cours" msgid "mrWidget|Remove Source Branch" -msgstr "" +msgstr "Supprimer la branche source" msgid "mrWidget|Remove source branch" -msgstr "" +msgstr "Supprimer la branche source" + +msgid "mrWidget|Remove your approval" +msgstr "Supprimer votre approbation" msgid "mrWidget|Request to merge" -msgstr "" +msgstr "Demander la fusion" msgid "mrWidget|Resolve conflicts" -msgstr "" +msgstr "Résoudre les conflits" msgid "mrWidget|Revert" -msgstr "" +msgstr "Défaire" msgid "mrWidget|Revert this merge request in a new merge request" -msgstr "" +msgstr "Défaire cette demande de fusion dans une nouvelle demande de fusion" msgid "mrWidget|Set by" -msgstr "" +msgstr "Marqué par" msgid "mrWidget|The changes were merged into" -msgstr "" +msgstr "Les modifications ont été fusionnées dans" msgid "mrWidget|The changes were not merged into" -msgstr "" +msgstr "Les modifications n’ont pas été fusionnées dans" msgid "mrWidget|The changes will be merged into" -msgstr "" +msgstr "Les modifications seront fusionnées dans" msgid "mrWidget|The source branch has been removed" -msgstr "" +msgstr "La branche source a été supprimée" msgid "mrWidget|The source branch is being removed" -msgstr "" +msgstr "La branche source est en cours de suppression" msgid "mrWidget|The source branch will be removed" -msgstr "" +msgstr "La branche source sera supprimée" msgid "mrWidget|The source branch will not be removed" -msgstr "" +msgstr "La branche source ne sera pas supprimée" msgid "mrWidget|There are merge conflicts" -msgstr "" +msgstr "Il y a des conflits de fusion" msgid "mrWidget|This merge request failed to be merged automatically" -msgstr "" +msgstr "Cette demande de fusion n’a pas pu être fusionnée automatiquement" msgid "mrWidget|This merge request is in the process of being merged" -msgstr "" +msgstr "Cette demande de fusion est en cours de fusion" msgid "mrWidget|This project is archived, write access has been disabled" -msgstr "" +msgstr "Ce projet est archivé, l’accès en écriture a été désactivé" msgid "mrWidget|You can merge this merge request manually using the" -msgstr "" +msgstr "Vous pouvez fusionner cette demande de fusion manuellement à l’aide de la" msgid "mrWidget|You can remove source branch now" -msgstr "" +msgstr "Vous pouvez maintenant supprimer la branche source" + +msgid "mrWidget|branch does not exist." +msgstr "la branche n’existe pas." msgid "mrWidget|command line" -msgstr "" +msgstr "ligne de commande" msgid "mrWidget|into" -msgstr "" +msgstr "dans" msgid "mrWidget|to be merged automatically when the pipeline succeeds" -msgstr "" +msgstr "pour être fusionnée automatiquement lorsque le pipeline réussit" msgid "new merge request" msgstr "nouvelle demande de fusion" @@ -3893,7 +4315,7 @@ msgid "notification emails" msgstr "courriels de notification" msgid "or" -msgstr "" +msgstr "ou" msgid "parent" msgid_plural "parents" @@ -3907,13 +4329,13 @@ msgid "personal access token" msgstr "jeton d’accès personnel" msgid "remove due date" -msgstr "" +msgstr "supprimer la date d’échéance" msgid "source" msgstr "source" msgid "spendCommand|%{slash_command} will update the sum of the time spent." -msgstr "" +msgstr "%{slash_command} mettra à jour la somme du temps passé." msgid "to help your contributors communicate effectively!" msgstr "pour aider vos contributeurs à communiquer efficacement !" @@ -3922,5 +4344,8 @@ msgid "username" msgstr "nom d’utilisateur" msgid "uses Kubernetes clusters to deploy your code!" -msgstr "" +msgstr "utilise les clusters Kubernetes pour déployer votre code !" + +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "avec %{additions} ajouts, %{deletions} suppressions." diff --git a/locale/id_ID/gitlab.po b/locale/id_ID/gitlab.po new file mode 100644 index 00000000000..183cb996b0a --- /dev/null +++ b/locale/id_ID/gitlab.po @@ -0,0 +1,4325 @@ +msgid "" +msgstr "" +"Project-Id-Version: gitlab-ee\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:23-0500\n" +"Last-Translator: gitlab \n" +"Language-Team: Indonesian\n" +"Language: id_ID\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: crowdin.com\n" +"X-Crowdin-Project: gitlab-ee\n" +"X-Crowdin-Language: id\n" +"X-Crowdin-File: /master/locale/gitlab.pot\n" + +msgid " and" +msgstr "" + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "" + +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" + +msgid "%d layer" +msgid_plural "%d layers" +msgstr[0] "" + +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" + +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "%s additional commits have been omitted to prevent performance issues." +msgstr[0] "" + +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" + +msgid "%{count} participant" +msgid_plural "%{count} participants" +msgstr[0] "" + +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + +msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" +msgstr "" + +msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." +msgstr "" + +msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." +msgstr "" + +msgid "%{openOrClose} %{noteable}" +msgstr "" + +msgid "%{storage_name}: failed storage access attempt on host:" +msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" +msgstr[0] "" + +msgid "%{text} is available" +msgstr "" + +msgid "(checkout the %{link} for information on how to install it)." +msgstr "" + +msgid "+ %{moreCount} more" +msgstr "" + +msgid "- show less" +msgstr "" + +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "" + +msgid "1st contribution!" +msgstr "" + +msgid "2FA enabled" +msgstr "" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "" + +msgid "About auto deploy" +msgstr "" + +msgid "Abuse Reports" +msgstr "" + +msgid "Access Tokens" +msgstr "" + +msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." +msgstr "" + +msgid "Account" +msgstr "" + +msgid "Active" +msgstr "" + +msgid "Activity" +msgstr "" + +msgid "Add" +msgstr "" + +msgid "Add Changelog" +msgstr "" + +msgid "Add Contribution guide" +msgstr "" + +msgid "Add Group Webhooks and GitLab Enterprise Edition." +msgstr "" + +msgid "Add Kubernetes cluster" +msgstr "" + +msgid "Add License" +msgstr "" + +msgid "Add Readme" +msgstr "" + +msgid "Add new directory" +msgstr "" + +msgid "Add todo" +msgstr "" + +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." +msgstr "" + +msgid "AdminHealthPageLink|health page" +msgstr "" + +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + +msgid "Advanced" +msgstr "" + +msgid "Advanced settings" +msgstr "" + +msgid "All" +msgstr "" + +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "" + +msgid "An error occurred previewing the blob" +msgstr "" + +msgid "An error occurred when toggling the notification subscription" +msgstr "" + +msgid "An error occurred when updating the issue weight" +msgstr "" + +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "" + +msgid "An error occurred while fetching sidebar data" +msgstr "" + +msgid "An error occurred while fetching the pipeline." +msgstr "" + +msgid "An error occurred while getting projects" +msgstr "" + +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "" + +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "" + +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "" + +msgid "An error occurred. Please try again." +msgstr "" + +msgid "Appearance" +msgstr "" + +msgid "Applications" +msgstr "" + +msgid "Apr" +msgstr "" + +msgid "April" +msgstr "" + +msgid "Archived project! Repository is read-only" +msgstr "" + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "" + +msgid "Are you sure you want to reset registration token?" +msgstr "" + +msgid "Are you sure you want to reset the health check token?" +msgstr "" + +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + +msgid "Are you sure?" +msgstr "" + +msgid "Artifacts" +msgstr "" + +msgid "Assign custom color like #FF0000" +msgstr "" + +msgid "Assign labels" +msgstr "" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "" + +msgid "Assignee" +msgstr "" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "" + +msgid "Aug" +msgstr "" + +msgid "August" +msgstr "" + +msgid "Authentication Log" +msgstr "" + +msgid "Author" +msgstr "" + +msgid "Authors: %{authors}" +msgstr "" + +msgid "Auto DevOps enabled" +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." +msgstr "" + +msgid "AutoDevOps|Auto DevOps (Beta)" +msgstr "" + +msgid "AutoDevOps|Auto DevOps documentation" +msgstr "" + +msgid "AutoDevOps|Enable in settings" +msgstr "" + +msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration." +msgstr "" + +msgid "AutoDevOps|Learn more in the %{link_to_documentation}" +msgstr "" + +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" +msgstr "" + +msgid "Available" +msgstr "" + +msgid "Avatar will be removed. Are you sure?" +msgstr "" + +msgid "Average per day: %{average}" +msgstr "" + +msgid "Begin with the selected commit" +msgstr "" + +msgid "Billing" +msgstr "" + +msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan." +msgstr "" + +msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available." +msgstr "" + +msgid "BillingPlans|Current plan" +msgstr "" + +msgid "BillingPlans|Customer Support" +msgstr "" + +msgid "BillingPlans|Downgrade" +msgstr "" + +msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}." +msgstr "" + +msgid "BillingPlans|Manage plan" +msgstr "" + +msgid "BillingPlans|Please contact %{customer_support_link} in that case." +msgstr "" + +msgid "BillingPlans|See all %{plan_name} features" +msgstr "" + +msgid "BillingPlans|This group uses the plan associated with its parent group." +msgstr "" + +msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}." +msgstr "" + +msgid "BillingPlans|Upgrade" +msgstr "" + +msgid "BillingPlans|You are currently on the %{plan_link} plan." +msgstr "" + +msgid "BillingPlans|frequently asked questions" +msgstr "" + +msgid "BillingPlans|monthly" +msgstr "" + +msgid "BillingPlans|paid annually at %{price_per_year}" +msgstr "" + +msgid "BillingPlans|per user" +msgstr "" + +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "" + +msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" +msgstr "" + +msgid "Branch has changed" +msgstr "" + +msgid "Branch is already taken" +msgstr "" + +msgid "Branch name" +msgstr "" + +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "" + +msgid "Branches" +msgstr "" + +msgid "Branches|Cant find HEAD commit for this branch" +msgstr "" + +msgid "Branches|Compare" +msgstr "" + +msgid "Branches|Delete all branches that are merged into '%{default_branch}'" +msgstr "" + +msgid "Branches|Delete branch" +msgstr "" + +msgid "Branches|Delete merged branches" +msgstr "" + +msgid "Branches|Delete protected branch" +msgstr "" + +msgid "Branches|Delete protected branch '%{branch_name}'?" +msgstr "" + +msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?" +msgstr "" + +msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?" +msgstr "" + +msgid "Branches|Filter by branch name" +msgstr "" + +msgid "Branches|Merged into %{default_branch}" +msgstr "" + +msgid "Branches|New branch" +msgstr "" + +msgid "Branches|No branches to show" +msgstr "" + +msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered." +msgstr "" + +msgid "Branches|Only a project master or owner can delete a protected branch" +msgstr "" + +msgid "Branches|Protected branches can be managed in %{project_settings_link}" +msgstr "" + +msgid "Branches|Sort by" +msgstr "" + +msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart." +msgstr "" + +msgid "Branches|The default branch cannot be deleted" +msgstr "" + +msgid "Branches|This branch hasn’t been merged into %{default_branch}." +msgstr "" + +msgid "Branches|To avoid data loss, consider merging this branch before deleting it." +msgstr "" + +msgid "Branches|To confirm, type %{branch_name_confirmation}:" +msgstr "" + +msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above." +msgstr "" + +msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}." +msgstr "" + +msgid "Branches|diverged from upstream" +msgstr "" + +msgid "Branches|merged" +msgstr "" + +msgid "Branches|project settings" +msgstr "" + +msgid "Branches|protected" +msgstr "" + +msgid "Browse Directory" +msgstr "" + +msgid "Browse File" +msgstr "" + +msgid "Browse Files" +msgstr "" + +msgid "Browse files" +msgstr "" + +msgid "ByAuthor|by" +msgstr "" + +msgid "CI / CD" +msgstr "" + +msgid "CI/CD configuration" +msgstr "" + +msgid "CICD|Jobs" +msgstr "" + +msgid "Cancel" +msgstr "" + +msgid "Cancel edit" +msgstr "" + +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + +msgid "Change Weight" +msgstr "" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "" + +msgid "ChangeTypeAction|Revert" +msgstr "" + +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + +msgid "Changelog" +msgstr "" + +msgid "Changes are shown as if the source revision was being merged into the target revision." +msgstr "" + +msgid "Charts" +msgstr "" + +msgid "Chat" +msgstr "" + +msgid "Check interval" +msgstr "" + +msgid "Checking %{text} availability…" +msgstr "" + +msgid "Checking branch availability..." +msgstr "" + +msgid "Cherry-pick this commit" +msgstr "" + +msgid "Cherry-pick this merge request" +msgstr "" + +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." +msgstr "" + +msgid "CiStatusLabel|canceled" +msgstr "" + +msgid "CiStatusLabel|created" +msgstr "" + +msgid "CiStatusLabel|failed" +msgstr "" + +msgid "CiStatusLabel|manual action" +msgstr "" + +msgid "CiStatusLabel|passed" +msgstr "" + +msgid "CiStatusLabel|passed with warnings" +msgstr "" + +msgid "CiStatusLabel|pending" +msgstr "" + +msgid "CiStatusLabel|skipped" +msgstr "" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "" + +msgid "CiStatusText|blocked" +msgstr "" + +msgid "CiStatusText|canceled" +msgstr "" + +msgid "CiStatusText|created" +msgstr "" + +msgid "CiStatusText|failed" +msgstr "" + +msgid "CiStatusText|manual" +msgstr "" + +msgid "CiStatusText|passed" +msgstr "" + +msgid "CiStatusText|pending" +msgstr "" + +msgid "CiStatusText|skipped" +msgstr "" + +msgid "CiStatus|running" +msgstr "" + +msgid "CiVariables|Input variable key" +msgstr "" + +msgid "CiVariables|Input variable value" +msgstr "" + +msgid "CiVariables|Remove variable row" +msgstr "" + +msgid "CiVariable|* (All environments)" +msgstr "" + +msgid "CiVariable|All environments" +msgstr "" + +msgid "CiVariable|Create wildcard" +msgstr "" + +msgid "CiVariable|Error occured while saving variables" +msgstr "" + +msgid "CiVariable|New environment" +msgstr "" + +msgid "CiVariable|Protected" +msgstr "" + +msgid "CiVariable|Search environments" +msgstr "" + +msgid "CiVariable|Toggle protected" +msgstr "" + +msgid "CiVariable|Validation failed" +msgstr "" + +msgid "CircuitBreakerApiLink|circuitbreaker api" +msgstr "" + +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + +msgid "Click to expand text" +msgstr "" + +msgid "Clone repository" +msgstr "" + +msgid "Close" +msgstr "" + +msgid "Closed" +msgstr "" + +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|API URL" +msgstr "" + +msgid "ClusterIntegration|Add Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Add an existing Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "" + +msgid "ClusterIntegration|Applications" +msgstr "" + +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|CA Certificate" +msgstr "" + +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgstr "" + +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" +msgstr "" + +msgid "ClusterIntegration|Copy API URL" +msgstr "" + +msgid "ClusterIntegration|Copy CA Certificate" +msgstr "" + +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + +msgid "ClusterIntegration|Copy Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Copy Token" +msgstr "" + +msgid "ClusterIntegration|Create Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" +msgstr "" + +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" +msgstr "" + +msgid "ClusterIntegration|Create on GKE" +msgstr "" + +msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Environment scope" +msgstr "" + +msgid "ClusterIntegration|GitLab Integration" +msgstr "" + +msgid "ClusterIntegration|GitLab Runner" +msgstr "" + +msgid "ClusterIntegration|Google Cloud Platform project ID" +msgstr "" + +msgid "ClusterIntegration|Google Kubernetes Engine" +msgstr "" + +msgid "ClusterIntegration|Google Kubernetes Engine project" +msgstr "" + +msgid "ClusterIntegration|Helm Tiller" +msgstr "" + +msgid "ClusterIntegration|Ingress" +msgstr "" + +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + +msgid "ClusterIntegration|Install" +msgstr "" + +msgid "ClusterIntegration|Installed" +msgstr "" + +msgid "ClusterIntegration|Installing" +msgstr "" + +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" +msgstr "" + +msgid "ClusterIntegration|Learn more about %{link_to_documentation}" +msgstr "" + +msgid "ClusterIntegration|Learn more about environments" +msgstr "" + +msgid "ClusterIntegration|Machine type" +msgstr "" + +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" +msgstr "" + +msgid "ClusterIntegration|Manage" +msgstr "" + +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" +msgstr "" + +msgid "ClusterIntegration|More information" +msgstr "" + +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" +msgstr "" + +msgid "ClusterIntegration|Note:" +msgstr "" + +msgid "ClusterIntegration|Number of nodes" +msgstr "" + +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" +msgstr "" + +msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgstr "" + +msgid "ClusterIntegration|Project ID" +msgstr "" + +msgid "ClusterIntegration|Project namespace" +msgstr "" + +msgid "ClusterIntegration|Project namespace (optional, unique)" +msgstr "" + +msgid "ClusterIntegration|Prometheus" +msgstr "" + +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Remove integration" +msgstr "" + +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|Request to begin installing failed" +msgstr "" + +msgid "ClusterIntegration|Save changes" +msgstr "" + +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|See machine types" +msgstr "" + +msgid "ClusterIntegration|See your projects" +msgstr "" + +msgid "ClusterIntegration|See zones" +msgstr "" + +msgid "ClusterIntegration|Service token" +msgstr "" + +msgid "ClusterIntegration|Show" +msgstr "" + +msgid "ClusterIntegration|Something went wrong on our end." +msgstr "" + +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" +msgstr "" + +msgid "ClusterIntegration|Something went wrong while installing %{title}" +msgstr "" + +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" +msgstr "" + +msgid "ClusterIntegration|Toggle Kubernetes Cluster" +msgstr "" + +msgid "ClusterIntegration|Toggle Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Token" +msgstr "" + +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgstr "" + +msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" +msgstr "" + +msgid "ClusterIntegration|Zone" +msgstr "" + +msgid "ClusterIntegration|access to Google Kubernetes Engine" +msgstr "" + +msgid "ClusterIntegration|check the pricing here" +msgstr "" + +msgid "ClusterIntegration|documentation" +msgstr "" + +msgid "ClusterIntegration|help page" +msgstr "" + +msgid "ClusterIntegration|installing applications" +msgstr "" + +msgid "ClusterIntegration|meets the requirements" +msgstr "" + +msgid "ClusterIntegration|properly configured" +msgstr "" + +msgid "Collapse" +msgstr "" + +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + +msgid "Comments" +msgstr "" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "" + +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" + +msgid "Commit Message" +msgstr "" + +msgid "Commit duration in minutes for last 30 commits" +msgstr "" + +msgid "Commit message" +msgstr "" + +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + +msgid "Commit to %{branchName} branch" +msgstr "" + +msgid "CommitBoxTitle|Commit" +msgstr "" + +msgid "CommitMessage|Add %{file_name}" +msgstr "" + +msgid "Commits" +msgstr "" + +msgid "Commits feed" +msgstr "" + +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + +msgid "Commits|History" +msgstr "" + +msgid "Commits|No related merge requests found" +msgstr "" + +msgid "Committed by" +msgstr "" + +msgid "Compare" +msgstr "" + +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + +msgid "Container Registry" +msgstr "" + +msgid "ContainerRegistry|Created" +msgstr "" + +msgid "ContainerRegistry|First log in to GitLab’s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:" +msgstr "" + +msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:" +msgstr "" + +msgid "ContainerRegistry|How to use the Container Registry" +msgstr "" + +msgid "ContainerRegistry|Learn more about" +msgstr "" + +msgid "ContainerRegistry|No tags in Container Registry for this container image." +msgstr "" + +msgid "ContainerRegistry|Once you log in, you’re free to create and upload a container image using the common %{build} and %{push} commands" +msgstr "" + +msgid "ContainerRegistry|Remove repository" +msgstr "" + +msgid "ContainerRegistry|Remove tag" +msgstr "" + +msgid "ContainerRegistry|Size" +msgstr "" + +msgid "ContainerRegistry|Tag" +msgstr "" + +msgid "ContainerRegistry|Tag ID" +msgstr "" + +msgid "ContainerRegistry|Use different image names" +msgstr "" + +msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images." +msgstr "" + +msgid "Contribution guide" +msgstr "" + +msgid "Contributors" +msgstr "" + +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + +msgid "ContributorsPage|Building repository graph." +msgstr "" + +msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits." +msgstr "" + +msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready." +msgstr "" + +msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node" +msgstr "" + +msgid "Control the maximum concurrency of repository backfill for this secondary node" +msgstr "" + +msgid "Copy SSH public key to clipboard" +msgstr "" + +msgid "Copy URL to clipboard" +msgstr "" + +msgid "Copy branch name to clipboard" +msgstr "" + +msgid "Copy command to clipboard" +msgstr "" + +msgid "Copy commit SHA to clipboard" +msgstr "" + +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + +msgid "Create New Directory" +msgstr "" + +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + +msgid "Create a personal access token on your account to pull or push via %{protocol}." +msgstr "" + +msgid "Create branch" +msgstr "" + +msgid "Create directory" +msgstr "" + +msgid "Create empty bare repository" +msgstr "" + +msgid "Create epic" +msgstr "" + +msgid "Create file" +msgstr "" + +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + +msgid "Create merge request" +msgstr "" + +msgid "Create merge request and branch" +msgstr "" + +msgid "Create new branch" +msgstr "" + +msgid "Create new directory" +msgstr "" + +msgid "Create new file" +msgstr "" + +msgid "Create new label" +msgstr "" + +msgid "Create new..." +msgstr "" + +msgid "CreateNewFork|Fork" +msgstr "" + +msgid "CreateTag|Tag" +msgstr "" + +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "" + +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + +msgid "Creating epic" +msgstr "" + +msgid "Cron Timezone" +msgstr "" + +msgid "Cron syntax" +msgstr "" + +msgid "Current node" +msgstr "" + +msgid "Custom notification events" +msgstr "" + +msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}." +msgstr "" + +msgid "Cycle Analytics" +msgstr "" + +msgid "CycleAnalyticsStage|Code" +msgstr "" + +msgid "CycleAnalyticsStage|Issue" +msgstr "" + +msgid "CycleAnalyticsStage|Plan" +msgstr "" + +msgid "CycleAnalyticsStage|Production" +msgstr "" + +msgid "CycleAnalyticsStage|Review" +msgstr "" + +msgid "CycleAnalyticsStage|Staging" +msgstr "" + +msgid "CycleAnalyticsStage|Test" +msgstr "" + +msgid "DashboardProjects|All" +msgstr "" + +msgid "DashboardProjects|Personal" +msgstr "" + +msgid "Dec" +msgstr "" + +msgid "December" +msgstr "" + +msgid "Default classification label" +msgstr "" + +msgid "Define a custom pattern with cron syntax" +msgstr "" + +msgid "Delete" +msgstr "" + +msgid "Deploy" +msgid_plural "Deploys" +msgstr[0] "" + +msgid "Deploy Keys" +msgstr "" + +msgid "Description" +msgstr "" + +msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project." +msgstr "" + +msgid "Details" +msgstr "" + +msgid "Diffs|No file name available" +msgstr "" + +msgid "Directory name" +msgstr "" + +msgid "Disable" +msgstr "" + +msgid "Discard draft" +msgstr "" + +msgid "Discover GitLab Geo." +msgstr "" + +msgid "Dismiss Cycle Analytics introduction box" +msgstr "" + +msgid "Dismiss Merge Request promotion" +msgstr "" + +msgid "Don't show again" +msgstr "" + +msgid "Download" +msgstr "" + +msgid "Download tar" +msgstr "" + +msgid "Download tar.bz2" +msgstr "" + +msgid "Download tar.gz" +msgstr "" + +msgid "Download zip" +msgstr "" + +msgid "DownloadArtifacts|Download" +msgstr "" + +msgid "DownloadCommit|Email Patches" +msgstr "" + +msgid "DownloadCommit|Plain Diff" +msgstr "" + +msgid "DownloadSource|Download" +msgstr "" + +msgid "Due date" +msgstr "" + +msgid "Edit" +msgstr "" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "" + +msgid "Edit files in the editor and commit changes here" +msgstr "" + +msgid "Emails" +msgstr "" + +msgid "Enable" +msgstr "" + +msgid "Enable Auto DevOps" +msgstr "" + +msgid "Environments|An error occurred while fetching the environments." +msgstr "" + +msgid "Environments|An error occurred while making the request." +msgstr "" + +msgid "Environments|Commit" +msgstr "" + +msgid "Environments|Deployment" +msgstr "" + +msgid "Environments|Environment" +msgstr "" + +msgid "Environments|Environments" +msgstr "" + +msgid "Environments|Job" +msgstr "" + +msgid "Environments|New environment" +msgstr "" + +msgid "Environments|No deployments yet" +msgstr "" + +msgid "Environments|Open" +msgstr "" + +msgid "Environments|Re-deploy" +msgstr "" + +msgid "Environments|Read more about environments" +msgstr "" + +msgid "Environments|Rollback" +msgstr "" + +msgid "Environments|Show all" +msgstr "" + +msgid "Environments|Updated" +msgstr "" + +msgid "Environments|You don't have any environments right now." +msgstr "" + +msgid "Epic will be removed! Are you sure?" +msgstr "" + +msgid "Epics" +msgstr "" + +msgid "Epics Roadmap" +msgstr "" + +msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + +msgid "Error creating epic" +msgstr "" + +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + +msgid "Error occurred when toggling the notification subscription" +msgstr "" + +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + +msgid "EventFilterBy|Filter by all" +msgstr "" + +msgid "EventFilterBy|Filter by comments" +msgstr "" + +msgid "EventFilterBy|Filter by issue events" +msgstr "" + +msgid "EventFilterBy|Filter by merge events" +msgstr "" + +msgid "EventFilterBy|Filter by push events" +msgstr "" + +msgid "EventFilterBy|Filter by team" +msgstr "" + +msgid "Every day (at 4:00am)" +msgstr "" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "" + +msgid "Every week (Sundays at 4:00am)" +msgstr "" + +msgid "Expand" +msgstr "" + +msgid "Explore projects" +msgstr "" + +msgid "Explore public groups" +msgstr "" + +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + +msgid "Failed to change the owner" +msgstr "" + +msgid "Failed to remove issue from board, please try again." +msgstr "" + +msgid "Failed to remove the pipeline schedule" +msgstr "" + +msgid "Failed to update issues, please try again." +msgstr "" + +msgid "Feb" +msgstr "" + +msgid "February" +msgstr "" + +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + +msgid "File name" +msgstr "" + +msgid "Files" +msgstr "" + +msgid "Files (%{human_size})" +msgstr "" + +msgid "Filter by commit message" +msgstr "" + +msgid "Find by path" +msgstr "" + +msgid "Find file" +msgstr "" + +msgid "FirstPushedBy|First" +msgstr "" + +msgid "FirstPushedBy|pushed by" +msgstr "" + +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "" + +msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)" +msgstr "" + +msgid "Format" +msgstr "" + +msgid "From issue creation until deploy to production" +msgstr "" + +msgid "From merge request merge until deploy to production" +msgstr "" + +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + +msgid "GPG Keys" +msgstr "" + +msgid "Generate a default set of labels" +msgstr "" + +msgid "Geo Nodes" +msgstr "" + +msgid "GeoNodeSyncStatus|Node is failing or broken." +msgstr "" + +msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." +msgstr "" + +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" +msgstr "" + +msgid "Geo|File sync capacity" +msgstr "" + +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" +msgstr "" + +msgid "Geo|Repository sync capacity" +msgstr "" + +msgid "Geo|Select groups to replicate." +msgstr "" + +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + +msgid "Git storage health information has been reset" +msgstr "" + +msgid "Git version" +msgstr "" + +msgid "GitLab Runner section" +msgstr "" + +msgid "Gitaly Servers" +msgstr "" + +msgid "Go to your fork" +msgstr "" + +msgid "GoToYourFork|Fork" +msgstr "" + +msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." +msgstr "" + +msgid "Got it!" +msgstr "" + +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + +msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" +msgstr "" + +msgid "GroupSettings|Share with group lock" +msgstr "" + +msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup." +msgstr "" + +msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}." +msgstr "" + +msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}." +msgstr "" + +msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually." +msgstr "" + +msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group" +msgstr "" + +msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}" +msgstr "" + +msgid "GroupsEmptyState|A group is a collection of several projects." +msgstr "" + +msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder." +msgstr "" + +msgid "GroupsEmptyState|No groups found" +msgstr "" + +msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." +msgstr "" + +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" +msgstr "" + +msgid "GroupsTree|Create a project in this group." +msgstr "" + +msgid "GroupsTree|Create a subgroup in this group." +msgstr "" + +msgid "GroupsTree|Edit group" +msgstr "" + +msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner." +msgstr "" + +msgid "GroupsTree|Filter by name..." +msgstr "" + +msgid "GroupsTree|Leave this group" +msgstr "" + +msgid "GroupsTree|Loading groups" +msgstr "" + +msgid "GroupsTree|Sorry, no groups matched your search" +msgstr "" + +msgid "GroupsTree|Sorry, no groups or projects matched your search" +msgstr "" + +msgid "Have your users email" +msgstr "" + +msgid "Health Check" +msgstr "" + +msgid "Health information can be retrieved from the following endpoints. More information is available" +msgstr "" + +msgid "HealthCheck|Access token is" +msgstr "" + +msgid "HealthCheck|Healthy" +msgstr "" + +msgid "HealthCheck|No Health Problems Detected" +msgstr "" + +msgid "HealthCheck|Unhealthy" +msgstr "" + +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" + +msgid "History" +msgstr "" + +msgid "Housekeeping successfully started" +msgstr "" + +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + +msgid "Import repository" +msgstr "" + +msgid "Improve Issue boards with GitLab Enterprise Edition." +msgstr "" + +msgid "Improve issues management with Issue weight and GitLab Enterprise Edition." +msgstr "" + +msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." +msgstr "" + +msgid "Install Runner on Kubernetes" +msgstr "" + +msgid "Install a Runner compatible with GitLab CI" +msgstr "" + +msgid "Instance" +msgid_plural "Instances" +msgstr[0] "" + +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + +msgid "Internal - The group and any internal projects can be viewed by any logged in user." +msgstr "" + +msgid "Internal - The project can be accessed by any logged in user." +msgstr "" + +msgid "Interval Pattern" +msgstr "" + +msgid "Introducing Cycle Analytics" +msgstr "" + +msgid "Issue board focus mode" +msgstr "" + +msgid "Issue events" +msgstr "" + +msgid "IssueBoards|Board" +msgstr "" + +msgid "IssueBoards|Boards" +msgstr "" + +msgid "Issues" +msgstr "" + +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + +msgid "Jan" +msgstr "" + +msgid "January" +msgstr "" + +msgid "Jobs" +msgstr "" + +msgid "Jul" +msgstr "" + +msgid "July" +msgstr "" + +msgid "Jun" +msgstr "" + +msgid "June" +msgstr "" + +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes configured" +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" +msgstr "" + +msgid "LFSStatus|Disabled" +msgstr "" + +msgid "LFSStatus|Enabled" +msgstr "" + +msgid "Labels" +msgstr "" + +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + +msgid "Last %d day" +msgid_plural "Last %d days" +msgstr[0] "" + +msgid "Last Pipeline" +msgstr "" + +msgid "Last commit" +msgstr "" + +msgid "Last edited %{date}" +msgstr "" + +msgid "Last edited by %{name}" +msgstr "" + +msgid "Last update" +msgstr "" + +msgid "Last updated" +msgstr "" + +msgid "LastPushEvent|You pushed to" +msgstr "" + +msgid "LastPushEvent|at" +msgstr "" + +msgid "Learn more" +msgstr "" + +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + +msgid "Learn more in the" +msgstr "" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "" + +msgid "Leave" +msgstr "" + +msgid "Leave group" +msgstr "" + +msgid "Leave project" +msgstr "" + +msgid "License" +msgstr "" + +msgid "List" +msgstr "" + +msgid "Loading the GitLab IDE..." +msgstr "" + +msgid "Lock" +msgstr "" + +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock not found" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." +msgstr "" + +msgid "Locked" +msgstr "" + +msgid "Locked Files" +msgstr "" + +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + +msgid "Login" +msgstr "" + +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + +msgid "Mar" +msgstr "" + +msgid "March" +msgstr "" + +msgid "Mark done" +msgstr "" + +msgid "Maximum git storage failures" +msgstr "" + +msgid "May" +msgstr "" + +msgid "Median" +msgstr "" + +msgid "Members" +msgstr "" + +msgid "Merge Requests" +msgstr "" + +msgid "Merge events" +msgstr "" + +msgid "Merge request" +msgstr "" + +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "MergeRequest|Approved" +msgstr "" + +msgid "Merged" +msgstr "" + +msgid "Messages" +msgstr "" + +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "" + +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + +msgid "Monitoring" +msgstr "" + +msgid "More information" +msgstr "" + +msgid "More information is available|here" +msgstr "" + +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + +msgid "Multiple issue boards" +msgstr "" + +msgid "Name new label" +msgstr "" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "" + +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + +msgid "New Pipeline Schedule" +msgstr "" + +msgid "New branch" +msgstr "" + +msgid "New branch unavailable" +msgstr "" + +msgid "New directory" +msgstr "" + +msgid "New epic" +msgstr "" + +msgid "New file" +msgstr "" + +msgid "New group" +msgstr "" + +msgid "New issue" +msgstr "" + +msgid "New label" +msgstr "" + +msgid "New merge request" +msgstr "" + +msgid "New project" +msgstr "" + +msgid "New schedule" +msgstr "" + +msgid "New snippet" +msgstr "" + +msgid "New subgroup" +msgstr "" + +msgid "New tag" +msgstr "" + +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" +msgstr "" + +msgid "No repository" +msgstr "" + +msgid "No schedules" +msgstr "" + +msgid "None" +msgstr "" + +msgid "Not allowed to merge" +msgstr "" + +msgid "Not available" +msgstr "" + +msgid "Not confidential" +msgstr "" + +msgid "Not enough data" +msgstr "" + +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + +msgid "Notification events" +msgstr "" + +msgid "NotificationEvent|Close issue" +msgstr "" + +msgid "NotificationEvent|Close merge request" +msgstr "" + +msgid "NotificationEvent|Failed pipeline" +msgstr "" + +msgid "NotificationEvent|Merge merge request" +msgstr "" + +msgid "NotificationEvent|New issue" +msgstr "" + +msgid "NotificationEvent|New merge request" +msgstr "" + +msgid "NotificationEvent|New note" +msgstr "" + +msgid "NotificationEvent|Reassign issue" +msgstr "" + +msgid "NotificationEvent|Reassign merge request" +msgstr "" + +msgid "NotificationEvent|Reopen issue" +msgstr "" + +msgid "NotificationEvent|Successful pipeline" +msgstr "" + +msgid "NotificationLevel|Custom" +msgstr "" + +msgid "NotificationLevel|Disabled" +msgstr "" + +msgid "NotificationLevel|Global" +msgstr "" + +msgid "NotificationLevel|On mention" +msgstr "" + +msgid "NotificationLevel|Participate" +msgstr "" + +msgid "NotificationLevel|Watch" +msgstr "" + +msgid "Notifications" +msgstr "" + +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + +msgid "Nov" +msgstr "" + +msgid "November" +msgstr "" + +msgid "Number of access attempts" +msgstr "" + +msgid "OK" +msgstr "" + +msgid "Oct" +msgstr "" + +msgid "October" +msgstr "" + +msgid "OfSearchInADropdown|Filter" +msgstr "" + +msgid "Only project members can comment." +msgstr "" + +msgid "Open" +msgstr "" + +msgid "Opened" +msgstr "" + +msgid "OpenedNDaysAgo|Opened" +msgstr "" + +msgid "Opens in a new window" +msgstr "" + +msgid "Options" +msgstr "" + +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + +msgid "Overview" +msgstr "" + +msgid "Owner" +msgstr "" + +msgid "Pagination|Last »" +msgstr "" + +msgid "Pagination|Next" +msgstr "" + +msgid "Pagination|Prev" +msgstr "" + +msgid "Pagination|« First" +msgstr "" + +msgid "Password" +msgstr "" + +msgid "Pipeline" +msgstr "" + +msgid "Pipeline Health" +msgstr "" + +msgid "Pipeline Schedule" +msgstr "" + +msgid "Pipeline Schedules" +msgstr "" + +msgid "Pipeline quota" +msgstr "" + +msgid "PipelineCharts|Failed:" +msgstr "" + +msgid "PipelineCharts|Overall statistics" +msgstr "" + +msgid "PipelineCharts|Success ratio:" +msgstr "" + +msgid "PipelineCharts|Successful:" +msgstr "" + +msgid "PipelineCharts|Total:" +msgstr "" + +msgid "PipelineSchedules|Activated" +msgstr "" + +msgid "PipelineSchedules|Active" +msgstr "" + +msgid "PipelineSchedules|All" +msgstr "" + +msgid "PipelineSchedules|Inactive" +msgstr "" + +msgid "PipelineSchedules|Next Run" +msgstr "" + +msgid "PipelineSchedules|None" +msgstr "" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "" + +msgid "PipelineSchedules|Take ownership" +msgstr "" + +msgid "PipelineSchedules|Target" +msgstr "" + +msgid "PipelineSchedules|Variables" +msgstr "" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "" + +msgid "Pipelines" +msgstr "" + +msgid "Pipelines charts" +msgstr "" + +msgid "Pipelines for last month" +msgstr "" + +msgid "Pipelines for last week" +msgstr "" + +msgid "Pipelines for last year" +msgstr "" + +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|all" +msgstr "" + +msgid "Pipeline|success" +msgstr "" + +msgid "Pipeline|with stage" +msgstr "" + +msgid "Pipeline|with stages" +msgstr "" + +msgid "Play" +msgstr "" + +msgid "Please enable billing for one of your projects to be able to create a Kubernetes cluster, then try again." +msgstr "" + +msgid "Please solve the reCAPTCHA" +msgstr "" + +msgid "Preferences" +msgstr "" + +msgid "Primary" +msgstr "" + +msgid "Private - Project access must be granted explicitly to each user." +msgstr "" + +msgid "Private - The group and its projects can only be viewed by members." +msgstr "" + +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + +msgid "Profile" +msgstr "" + +msgid "Profiles|Account scheduled for removal." +msgstr "" + +msgid "Profiles|Delete Account" +msgstr "" + +msgid "Profiles|Delete account" +msgstr "" + +msgid "Profiles|Delete your account?" +msgstr "" + +msgid "Profiles|Deleting an account has the following effects:" +msgstr "" + +msgid "Profiles|Invalid password" +msgstr "" + +msgid "Profiles|Invalid username" +msgstr "" + +msgid "Profiles|Type your %{confirmationValue} to confirm:" +msgstr "" + +msgid "Profiles|You don't have access to delete this user." +msgstr "" + +msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account." +msgstr "" + +msgid "Profiles|Your account is currently an owner in these groups:" +msgstr "" + +msgid "Profiles|your account" +msgstr "" + +msgid "Programming languages used in this repository" +msgstr "" + +msgid "Project '%{project_name}' is in the process of being deleted." +msgstr "" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "" + +msgid "Project '%{project_name}' was successfully created." +msgstr "" + +msgid "Project '%{project_name}' was successfully updated." +msgstr "" + +msgid "Project access must be granted explicitly to each user." +msgstr "" + +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + +msgid "Project details" +msgstr "" + +msgid "Project export could not be deleted." +msgstr "" + +msgid "Project export has been deleted." +msgstr "" + +msgid "Project export link has expired. Please generate a new export from your project settings." +msgstr "" + +msgid "Project export started. A download link will be sent by email." +msgstr "" + +msgid "ProjectActivityRSS|Subscribe" +msgstr "" + +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + +msgid "ProjectFeature|Disabled" +msgstr "" + +msgid "ProjectFeature|Everyone with access" +msgstr "" + +msgid "ProjectFeature|Only team members" +msgstr "" + +msgid "ProjectFileTree|Name" +msgstr "" + +msgid "ProjectLastActivity|Never" +msgstr "" + +msgid "ProjectLifecycle|Stage" +msgstr "" + +msgid "ProjectNetworkGraph|Graph" +msgstr "" + +msgid "ProjectSettings|Contact an admin to change this setting." +msgstr "" + +msgid "ProjectSettings|Only signed commits can be pushed to this repository." +msgstr "" + +msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." +msgstr "" + +msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project." +msgstr "" + +msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin." +msgstr "" + +msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails." +msgstr "" + +msgid "Projects" +msgstr "" + +msgid "ProjectsDropdown|Frequently visited" +msgstr "" + +msgid "ProjectsDropdown|Loading projects" +msgstr "" + +msgid "ProjectsDropdown|Projects you visit often will appear here" +msgstr "" + +msgid "ProjectsDropdown|Search your projects" +msgstr "" + +msgid "ProjectsDropdown|Something went wrong on our end." +msgstr "" + +msgid "ProjectsDropdown|Sorry, no projects matched your search" +msgstr "" + +msgid "ProjectsDropdown|This feature requires browser localStorage support" +msgstr "" + +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + +msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." +msgstr "" + +msgid "PrometheusService|Finding and configuring metrics..." +msgstr "" + +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + +msgid "PrometheusService|Metrics" +msgstr "" + +msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters." +msgstr "" + +msgid "PrometheusService|Missing environment variable" +msgstr "" + +msgid "PrometheusService|Monitored" +msgstr "" + +msgid "PrometheusService|More information" +msgstr "" + +msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment." +msgstr "" + +msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" +msgstr "" + +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + +msgid "PrometheusService|Time-series monitoring service" +msgstr "" + +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + +msgid "PrometheusService|View environments" +msgstr "" + +msgid "Protip:" +msgstr "" + +msgid "Public - The group and any public projects can be viewed without any authentication." +msgstr "" + +msgid "Public - The project can be accessed without any authentication." +msgstr "" + +msgid "Push Rules" +msgstr "" + +msgid "Push events" +msgstr "" + +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + +msgid "PushRule|Committer restriction" +msgstr "" + +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + +msgid "Read more" +msgstr "" + +msgid "Readme" +msgstr "" + +msgid "RefSwitcher|Branches" +msgstr "" + +msgid "RefSwitcher|Tags" +msgstr "" + +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + +msgid "Registry" +msgstr "" + +msgid "Related Commits" +msgstr "" + +msgid "Related Deployed Jobs" +msgstr "" + +msgid "Related Issues" +msgstr "" + +msgid "Related Jobs" +msgstr "" + +msgid "Related Merge Requests" +msgstr "" + +msgid "Related Merged Requests" +msgstr "" + +msgid "Remind later" +msgstr "" + +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + +msgid "Remove project" +msgstr "" + +msgid "Repair authentication" +msgstr "" + +msgid "Repository" +msgstr "" + +msgid "Repository has no locks." +msgstr "" + +msgid "Request Access" +msgstr "" + +msgid "Reset git storage health information" +msgstr "" + +msgid "Reset health check access token" +msgstr "" + +msgid "Reset runners registration token" +msgstr "" + +msgid "Resolve discussion" +msgstr "" + +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" + +msgid "Revert this commit" +msgstr "" + +msgid "Revert this merge request" +msgstr "" + +msgid "Roadmap" +msgstr "" + +msgid "SSH Keys" +msgstr "" + +msgid "Save changes" +msgstr "" + +msgid "Save pipeline schedule" +msgstr "" + +msgid "Save variables" +msgstr "" + +msgid "Schedule a new pipeline" +msgstr "" + +msgid "Schedules" +msgstr "" + +msgid "Scheduling Pipelines" +msgstr "" + +msgid "Scoped issue boards" +msgstr "" + +msgid "Search branches and tags" +msgstr "" + +msgid "Search milestones" +msgstr "" + +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "" + +msgid "Seconds before reseting failure information" +msgstr "" + +msgid "Seconds to wait for a storage access attempt" +msgstr "" + +msgid "Secret variables" +msgstr "" + +msgid "Security report" +msgstr "" + +msgid "Select Archive Format" +msgstr "" + +msgid "Select a timezone" +msgstr "" + +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + +msgid "Select target branch" +msgstr "" + +msgid "Selective synchronization" +msgstr "" + +msgid "Send email" +msgstr "" + +msgid "Sep" +msgstr "" + +msgid "September" +msgstr "" + +msgid "Server version" +msgstr "" + +msgid "Service Templates" +msgstr "" + +msgid "Service URL" +msgstr "" + +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "" + +msgid "Set up CI/CD" +msgstr "" + +msgid "Set up Koding" +msgstr "" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "" + +msgid "Settings" +msgstr "" + +msgid "Setup a specific Runner automatically" +msgstr "" + +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + +msgid "Show command" +msgstr "" + +msgid "Show parent pages" +msgstr "" + +msgid "Show parent subgroups" +msgstr "" + +msgid "Showing %d event" +msgid_plural "Showing %d events" +msgstr[0] "" + +msgid "Sidebar|Change weight" +msgstr "" + +msgid "Sidebar|No" +msgstr "" + +msgid "Sidebar|None" +msgstr "" + +msgid "Sidebar|Weight" +msgstr "" + +msgid "Snippets" +msgstr "" + +msgid "Something went wrong on our end" +msgstr "" + +msgid "Something went wrong on our end." +msgstr "" + +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + +msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" +msgstr "" + +msgid "Something went wrong when toggling the button" +msgstr "" + +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + +msgid "Something went wrong while fetching the projects." +msgstr "" + +msgid "Something went wrong while fetching the registry list." +msgstr "" + +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + +msgid "Something went wrong. Please try again." +msgstr "" + +msgid "Sort by" +msgstr "" + +msgid "SortOptions|Access level, ascending" +msgstr "" + +msgid "SortOptions|Access level, descending" +msgstr "" + +msgid "SortOptions|Created date" +msgstr "" + +msgid "SortOptions|Due date" +msgstr "" + +msgid "SortOptions|Due later" +msgstr "" + +msgid "SortOptions|Due soon" +msgstr "" + +msgid "SortOptions|Label priority" +msgstr "" + +msgid "SortOptions|Largest group" +msgstr "" + +msgid "SortOptions|Largest repository" +msgstr "" + +msgid "SortOptions|Last created" +msgstr "" + +msgid "SortOptions|Last joined" +msgstr "" + +msgid "SortOptions|Last updated" +msgstr "" + +msgid "SortOptions|Least popular" +msgstr "" + +msgid "SortOptions|Less weight" +msgstr "" + +msgid "SortOptions|Milestone" +msgstr "" + +msgid "SortOptions|Milestone due later" +msgstr "" + +msgid "SortOptions|Milestone due soon" +msgstr "" + +msgid "SortOptions|More weight" +msgstr "" + +msgid "SortOptions|Most popular" +msgstr "" + +msgid "SortOptions|Name" +msgstr "" + +msgid "SortOptions|Name, ascending" +msgstr "" + +msgid "SortOptions|Name, descending" +msgstr "" + +msgid "SortOptions|Oldest created" +msgstr "" + +msgid "SortOptions|Oldest joined" +msgstr "" + +msgid "SortOptions|Oldest sign in" +msgstr "" + +msgid "SortOptions|Oldest updated" +msgstr "" + +msgid "SortOptions|Popularity" +msgstr "" + +msgid "SortOptions|Priority" +msgstr "" + +msgid "SortOptions|Recent sign in" +msgstr "" + +msgid "SortOptions|Start later" +msgstr "" + +msgid "SortOptions|Start soon" +msgstr "" + +msgid "SortOptions|Weight" +msgstr "" + +msgid "Source" +msgstr "" + +msgid "Source (branch or tag)" +msgstr "" + +msgid "Source code" +msgstr "" + +msgid "Source is not available" +msgstr "" + +msgid "Spam Logs" +msgstr "" + +msgid "Specify the following URL during the Runner setup:" +msgstr "" + +msgid "StarProject|Star" +msgstr "" + +msgid "Starred projects" +msgstr "" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "" + +msgid "Start the Runner!" +msgstr "" + +msgid "Stopped" +msgstr "" + +msgid "Storage" +msgstr "" + +msgid "Subgroups" +msgstr "" + +msgid "Switch branch/tag" +msgstr "" + +msgid "System Hooks" +msgstr "" + +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "" + +msgid "Tags" +msgstr "" + +msgid "TagsPage|Browse commits" +msgstr "" + +msgid "TagsPage|Browse files" +msgstr "" + +msgid "TagsPage|Can't find HEAD commit for this tag" +msgstr "" + +msgid "TagsPage|Cancel" +msgstr "" + +msgid "TagsPage|Create tag" +msgstr "" + +msgid "TagsPage|Delete tag" +msgstr "" + +msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?" +msgstr "" + +msgid "TagsPage|Edit release notes" +msgstr "" + +msgid "TagsPage|Existing branch name, tag, or commit SHA" +msgstr "" + +msgid "TagsPage|Filter by tag name" +msgstr "" + +msgid "TagsPage|New Tag" +msgstr "" + +msgid "TagsPage|New tag" +msgstr "" + +msgid "TagsPage|Optionally, add a message to the tag." +msgstr "" + +msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page." +msgstr "" + +msgid "TagsPage|Release notes" +msgstr "" + +msgid "TagsPage|Repository has no tags yet." +msgstr "" + +msgid "TagsPage|Sort by" +msgstr "" + +msgid "TagsPage|Tags" +msgstr "" + +msgid "TagsPage|Tags give the ability to mark specific points in history as being important" +msgstr "" + +msgid "TagsPage|This tag has no release notes." +msgstr "" + +msgid "TagsPage|Use git tag command to add a new one:" +msgstr "" + +msgid "TagsPage|Write your release notes or drag files here..." +msgstr "" + +msgid "TagsPage|protected" +msgstr "" + +msgid "Target Branch" +msgstr "" + +msgid "Team" +msgstr "" + +msgid "Thanks! Don't show me this again" +msgstr "" + +msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." +msgstr "" + +msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." +msgstr "" + +msgid "The collection of events added to the data gathered for that stage." +msgstr "" + +msgid "The fork relationship has been removed." +msgstr "" + +msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." +msgstr "" + +msgid "The maximum file size allowed is 200KB." +msgstr "" + +msgid "The number of attempts GitLab will make to access a storage." +msgstr "" + +msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." +msgstr "" + +msgid "The phase of the development lifecycle." +msgstr "" + +msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." +msgstr "" + +msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle." +msgstr "" + +msgid "The project can be accessed by any logged in user." +msgstr "" + +msgid "The project can be accessed without any authentication." +msgstr "" + +msgid "The repository for this project does not exist." +msgstr "" + +msgid "The repository for this project is empty" +msgstr "" + +msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." +msgstr "" + +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + +msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." +msgstr "" + +msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running." +msgstr "" + +msgid "The time in seconds GitLab will keep failure information. When no failures occur during this time, information about the mount is reset." +msgstr "" + +msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." +msgstr "" + +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + +msgid "The time taken by each data entry gathered by that stage." +msgstr "" + +msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." +msgstr "" + +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + +msgid "There are problems accessing Git storage: " +msgstr "" + +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + +msgid "This board\\'s scope is reduced" +msgstr "" + +msgid "This directory" +msgstr "" + +msgid "This is a confidential issue." +msgstr "" + +msgid "This is the author's first Merge Request to this project." +msgstr "" + +msgid "This issue is confidential" +msgstr "" + +msgid "This issue is confidential and locked." +msgstr "" + +msgid "This issue is locked." +msgstr "" + +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + +msgid "This means you can not push code until you create an empty repository or import existing one." +msgstr "" + +msgid "This merge request is locked." +msgstr "" + +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + +msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." +msgstr "" + +msgid "Time before an issue gets scheduled" +msgstr "" + +msgid "Time before an issue starts implementation" +msgstr "" + +msgid "Time between merge request creation and merge/close" +msgstr "" + +msgid "Time tracking" +msgstr "" + +msgid "Time until first merge request" +msgstr "" + +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + +msgid "Timeago|%s days ago" +msgstr "" + +msgid "Timeago|%s days remaining" +msgstr "" + +msgid "Timeago|%s hours remaining" +msgstr "" + +msgid "Timeago|%s minutes ago" +msgstr "" + +msgid "Timeago|%s minutes remaining" +msgstr "" + +msgid "Timeago|%s months ago" +msgstr "" + +msgid "Timeago|%s months remaining" +msgstr "" + +msgid "Timeago|%s seconds remaining" +msgstr "" + +msgid "Timeago|%s weeks ago" +msgstr "" + +msgid "Timeago|%s weeks remaining" +msgstr "" + +msgid "Timeago|%s years ago" +msgstr "" + +msgid "Timeago|%s years remaining" +msgstr "" + +msgid "Timeago|1 day remaining" +msgstr "" + +msgid "Timeago|1 hour remaining" +msgstr "" + +msgid "Timeago|1 minute remaining" +msgstr "" + +msgid "Timeago|1 month remaining" +msgstr "" + +msgid "Timeago|1 week remaining" +msgstr "" + +msgid "Timeago|1 year remaining" +msgstr "" + +msgid "Timeago|Past due" +msgstr "" + +msgid "Timeago|a day ago" +msgstr "" + +msgid "Timeago|a month ago" +msgstr "" + +msgid "Timeago|a week ago" +msgstr "" + +msgid "Timeago|a year ago" +msgstr "" + +msgid "Timeago|about %s hours ago" +msgstr "" + +msgid "Timeago|about a minute ago" +msgstr "" + +msgid "Timeago|about an hour ago" +msgstr "" + +msgid "Timeago|in %s days" +msgstr "" + +msgid "Timeago|in %s hours" +msgstr "" + +msgid "Timeago|in %s minutes" +msgstr "" + +msgid "Timeago|in %s months" +msgstr "" + +msgid "Timeago|in %s seconds" +msgstr "" + +msgid "Timeago|in %s weeks" +msgstr "" + +msgid "Timeago|in %s years" +msgstr "" + +msgid "Timeago|in 1 day" +msgstr "" + +msgid "Timeago|in 1 hour" +msgstr "" + +msgid "Timeago|in 1 minute" +msgstr "" + +msgid "Timeago|in 1 month" +msgstr "" + +msgid "Timeago|in 1 week" +msgstr "" + +msgid "Timeago|in 1 year" +msgstr "" + +msgid "Timeago|in a while" +msgstr "" + +msgid "Timeago|less than a minute ago" +msgstr "" + +msgid "Time|hr" +msgid_plural "Time|hrs" +msgstr[0] "" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "" + +msgid "Time|s" +msgstr "" + +msgid "Tip:" +msgstr "" + +msgid "Title" +msgstr "" + +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + +msgid "Total Time" +msgstr "" + +msgid "Total test time for all commits/merges" +msgstr "" + +msgid "Total: %{total}" +msgstr "" + +msgid "Track activity with Contribution Analytics." +msgstr "" + +msgid "Track groups of issues that share a theme, across projects and milestones" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + +msgid "Turn on Service Desk" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + +msgid "Unlock" +msgstr "" + +msgid "Unlock this %{issuableDisplayName}? Everyone will be able to comment." +msgstr "" + +msgid "Unlocked" +msgstr "" + +msgid "Unresolve discussion" +msgstr "" + +msgid "Unstar" +msgstr "" + +msgid "Up to date" +msgstr "" + +msgid "Upgrade your plan to activate Advanced Global Search." +msgstr "" + +msgid "Upgrade your plan to activate Contribution Analytics." +msgstr "" + +msgid "Upgrade your plan to activate Group Webhooks." +msgstr "" + +msgid "Upgrade your plan to activate Issue weight." +msgstr "" + +msgid "Upgrade your plan to improve Issue boards." +msgstr "" + +msgid "Upload New File" +msgstr "" + +msgid "Upload file" +msgstr "" + +msgid "Upload new avatar" +msgstr "" + +msgid "UploadLink|click to upload" +msgstr "" + +msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab" +msgstr "" + +msgid "Use the following registration token during setup:" +msgstr "" + +msgid "Use your global notification setting" +msgstr "" + +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + +msgid "View epics list" +msgstr "" + +msgid "View file @ " +msgstr "" + +msgid "View labels" +msgstr "" + +msgid "View open merge request" +msgstr "" + +msgid "View replaced file @ " +msgstr "" + +msgid "VisibilityLevel|Internal" +msgstr "" + +msgid "VisibilityLevel|Private" +msgstr "" + +msgid "VisibilityLevel|Public" +msgstr "" + +msgid "VisibilityLevel|Unknown" +msgstr "" + +msgid "Want to see the data? Please ask an administrator for access." +msgstr "" + +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + +msgid "We don't have enough data to show this stage." +msgstr "" + +msgid "We want to be sure it is you, please confirm you are not a robot." +msgstr "" + +msgid "Web IDE" +msgstr "" + +msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." +msgstr "" + +msgid "Weight" +msgstr "" + +msgid "Wiki" +msgstr "" + +msgid "WikiClone|Clone your wiki" +msgstr "" + +msgid "WikiClone|Git Access" +msgstr "" + +msgid "WikiClone|Install Gollum" +msgstr "" + +msgid "WikiClone|It is recommended to install %{markdown} so that GFM features render locally:" +msgstr "" + +msgid "WikiClone|Start Gollum and edit locally" +msgstr "" + +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + +msgid "WikiEmptyPageError|You are not allowed to create wiki pages" +msgstr "" + +msgid "WikiHistoricalPage|This is an old version of this page." +msgstr "" + +msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}." +msgstr "" + +msgid "WikiHistoricalPage|history" +msgstr "" + +msgid "WikiHistoricalPage|most recent version" +msgstr "" + +msgid "WikiMarkdownDocs|More examples are in the %{docs_link}" +msgstr "" + +msgid "WikiMarkdownDocs|documentation" +msgstr "" + +msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}" +msgstr "" + +msgid "WikiNewPagePlaceholder|how-to-setup" +msgstr "" + +msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories." +msgstr "" + +msgid "WikiNewPageTitle|New Wiki Page" +msgstr "" + +msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?" +msgstr "" + +msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs." +msgstr "" + +msgid "WikiPageConflictMessage|the page" +msgstr "" + +msgid "WikiPageCreate|Create %{page_title}" +msgstr "" + +msgid "WikiPageEdit|Update %{page_title}" +msgstr "" + +msgid "WikiPage|Page slug" +msgstr "" + +msgid "WikiPage|Write your content or drag files here..." +msgstr "" + +msgid "Wiki|Create Page" +msgstr "" + +msgid "Wiki|Create page" +msgstr "" + +msgid "Wiki|Edit Page" +msgstr "" + +msgid "Wiki|Empty page" +msgstr "" + +msgid "Wiki|More Pages" +msgstr "" + +msgid "Wiki|New page" +msgstr "" + +msgid "Wiki|Page history" +msgstr "" + +msgid "Wiki|Page version" +msgstr "" + +msgid "Wiki|Pages" +msgstr "" + +msgid "Wiki|Wiki Pages" +msgstr "" + +msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members." +msgstr "" + +msgid "Withdraw Access Request" +msgstr "" + +msgid "Write a commit message..." +msgstr "" + +msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You can also create a project from the command line." +msgstr "" + +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can only add files when you are on a branch" +msgstr "" + +msgid "You can only edit files when you are on a branch" +msgstr "" + +msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." +msgstr "" + +msgid "You cannot write to this read-only GitLab instance." +msgstr "" + +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + +msgid "You have reached your project limit" +msgstr "" + +msgid "You must have master access to force delete a lock" +msgstr "" + +msgid "You must sign in to star a project" +msgstr "" + +msgid "You need a different license to enable FileLocks feature" +msgstr "" + +msgid "You need permission." +msgstr "" + +msgid "You will not get any notifications via email" +msgstr "" + +msgid "You will only receive notifications for the events you choose" +msgstr "" + +msgid "You will only receive notifications for threads you have participated in" +msgstr "" + +msgid "You will receive notifications for any activity" +msgstr "" + +msgid "You will receive notifications only for comments in which you were @mentioned" +msgstr "" + +msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account" +msgstr "" + +msgid "You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile" +msgstr "" + +msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" +msgstr "" + +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + +msgid "Your comment will not be visible to the public." +msgstr "" + +msgid "Your groups" +msgstr "" + +msgid "Your name" +msgstr "" + +msgid "Your projects" +msgstr "" + +msgid "assign yourself" +msgstr "" + +msgid "branch name" +msgstr "" + +msgid "by" +msgstr "" + +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load %{reportName} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading %{reportName} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + +msgid "commit" +msgstr "" + +msgid "confidentiality|You are going to turn off the confidentiality. This means everyone will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with at least Reporter access are able to see and leave comments on the issue." +msgstr "" + +msgid "day" +msgid_plural "days" +msgstr[0] "" + +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" + +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Remove your approval" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|branch does not exist." +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + +msgid "new merge request" +msgstr "" + +msgid "notification emails" +msgstr "" + +msgid "or" +msgstr "" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "" + +msgid "password" +msgstr "" + +msgid "personal access token" +msgstr "" + +msgid "remove due date" +msgstr "" + +msgid "source" +msgstr "" + +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + +msgid "to help your contributors communicate effectively!" +msgstr "" + +msgid "username" +msgstr "" + +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po index 62a6da1604a..0cb814ac59c 100644 --- a/locale/it/gitlab.po +++ b/locale/it/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 04:00-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:21-0500\n" "Last-Translator: gitlab \n" "Language-Team: Italian\n" "Language: it_IT\n" @@ -49,6 +49,9 @@ msgid_plural "%s additional commits have been omitted to prevent performance iss msgstr[0] "%s commit aggiuntivo è stato omesso per evitare degradi di prestazioni negli issues." msgstr[1] "%s commit aggiuntivi sono stati omessi per evitare degradi di prestazioni negli issues." +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -57,6 +60,9 @@ msgid_plural "%{count} participants" msgstr[0] "%{count} partecipante" msgstr[1] "%{count} partecipanti" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "%{number_commits_behind} commits precedenti %{default_branch}, %{number_commits_ahead} commits avanti" @@ -66,6 +72,9 @@ msgstr "%{number_of_failures} di %{maximum_failures} fallimenti. GitLab consenti msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} di %{maximum_failures} fallimenti. Gitlab non ritenterà automaticamente. Ripristina l'informazioni d'archiviazione quando il problema è risolto." +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "%{storage_name}: tentativo d'accesso all'archiviazione fallito da parte dell'host:" @@ -130,9 +139,15 @@ msgstr "Aggiungi Guida per contribuire" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "" +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "Aggiungi Licenza" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "Aggiungi una directory (cartella)" @@ -151,12 +166,45 @@ msgstr "" msgid "AdminArea|Stopping jobs failed" msgstr "" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" msgid "AdminHealthPageLink|health page" msgstr "Pagina di stato" +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + msgid "Advanced" msgstr "" @@ -181,6 +229,12 @@ msgstr "Errore durante l'attivazione/disattivazione della sottoscrizione per l'i msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" @@ -190,12 +244,36 @@ msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "Errore durante il recupero dei dati della barra laterale" +msgid "An error occurred while fetching the pipeline." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + msgid "An error occurred while loading filenames" msgstr "" +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + msgid "An error occurred while rendering KaTeX" msgstr "" @@ -208,6 +286,12 @@ msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + msgid "An error occurred while validating username" msgstr "" @@ -232,15 +316,15 @@ msgstr "Progetto archiviato! La Repository è sola-lettura" msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Sei sicuro di voler cancellare questa pipeline programmata?" -msgid "Are you sure you want to discard your changes?" -msgstr "Vuoi davvero ignorare le modifiche?" - msgid "Are you sure you want to reset registration token?" msgstr "Sei sicuro di voler ripristinare il token di registrazione?" msgid "Are you sure you want to reset the health check token?" msgstr "Confermi di voler resettare il token di controllo di stato?" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "Sei sicuro?" @@ -280,6 +364,9 @@ msgstr "Autore" msgid "Authors: %{authors}" msgstr "" +msgid "Auto DevOps enabled" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -304,8 +391,14 @@ msgstr "Farà automaticamente le build, i test e i rilasci della tua applicazion msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "Approfondisci: %{link_to_documentation}" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." -msgstr "Puoi attivare %{link_to_settings} per questo progetto." +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" +msgstr "" msgid "Available" msgstr "Disponibile" @@ -316,6 +409,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Billing" msgstr "" @@ -370,11 +466,8 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" msgstr[0] "" msgstr[1] "" @@ -669,6 +762,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "api circuitbreaker" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + msgid "Click to expand text" msgstr "" @@ -723,6 +819,9 @@ msgstr "Copia URL API" msgid "ClusterIntegration|Copy CA Certificate" msgstr "Copia Certificato CA" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -771,6 +870,9 @@ msgstr "Helm Tiller" msgid "ClusterIntegration|Ingress" msgstr "Ingresso" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "Installa" @@ -822,9 +924,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" msgstr "" @@ -960,6 +1059,12 @@ msgstr "" msgid "Collapse" msgstr "" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "Commenti" @@ -968,6 +1073,11 @@ msgid_plural "Commits" msgstr[0] "" msgstr[1] "" +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" +msgstr[1] "" + msgid "Commit Message" msgstr "Messaggio di commit" @@ -980,6 +1090,9 @@ msgstr "Messaggio del commit" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Commit" @@ -1121,6 +1234,9 @@ msgstr "Copia URL negli appunti" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Copia l'SHA del commit negli appunti" @@ -1133,9 +1249,18 @@ msgstr "" msgid "Create New Directory" msgstr "Crea una nuova cartella" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "Creare un token di accesso sul tuo account per eseguire pull o push tramite %{protocol}" +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "Crea cartella" @@ -1154,6 +1279,9 @@ msgstr "" msgid "Create merge request" msgstr "Crea una richiesta di merge" +msgid "Create merge request and branch" +msgstr "" + msgid "Create new branch" msgstr "Crea un nuova branch" @@ -1178,6 +1306,12 @@ msgstr "Tag" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "Crea token d'accesso personale" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + msgid "Creating epic" msgstr "" @@ -1232,6 +1366,9 @@ msgstr "Dic" msgid "December" msgstr "Dicembre" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "Definisci un patter personalizzato mediante la sintassi cron" @@ -1264,8 +1401,8 @@ msgstr "Nome cartella" msgid "Disable" msgstr "" -msgid "Discard changes" -msgstr "Annulla modifiche" +msgid "Discard draft" +msgstr "" msgid "Discover GitLab Geo." msgstr "" @@ -1324,6 +1461,9 @@ msgstr "E-mail" msgid "Enable" msgstr "" +msgid "Enable Auto DevOps" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "Errore durante il fetch degli ambienti." @@ -1378,9 +1518,18 @@ msgstr "" msgid "Epics" msgstr "" +msgid "Epics Roadmap" +msgstr "" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "" @@ -1447,12 +1596,36 @@ msgstr "Esplora progetti" msgid "Explore public groups" msgstr "Esplora gruppi pubblici" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + msgid "Failed to change the owner" msgstr "Impossibile cambiare owner" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "Impossibile rimuovere la pipeline pianificata" +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "Feb" @@ -1468,6 +1641,9 @@ msgstr "Nome file" msgid "Files" msgstr "Files" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "Filtra per messaggio di commit" @@ -1503,6 +1679,9 @@ msgstr "Dalla creazione di un issue fino al rilascio in produzione" msgid "From merge request merge until deploy to production" msgstr "Dalla richiesta di merge fino effettua il merge fino al rilascio in produzione" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "Chiavi GPG" @@ -1650,6 +1829,24 @@ msgstr "L'autenticazione Google non è %{link_to_documentation}. Richiedi al tuo msgid "Got it!" msgstr "" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "Blocca la condivisione di un progetto di %{group} con altri gruppi" @@ -1748,6 +1945,9 @@ msgstr "Cronologia" msgid "Housekeeping successfully started" msgstr "Housekeeping iniziato con successo" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "Importa repository" @@ -1760,6 +1960,9 @@ msgstr "" msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "" +msgid "Install Runner on Kubernetes" +msgstr "" + msgid "Install a Runner compatible with GitLab CI" msgstr "" @@ -1810,6 +2013,9 @@ msgstr "Gen" msgid "January" msgstr "Gennaio" +msgid "Jobs" +msgstr "" + msgid "Jul" msgstr "Lug" @@ -1840,6 +2046,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1887,6 +2096,12 @@ msgstr "" msgid "Learn more" msgstr "" +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + msgid "Learn more in the" msgstr "Leggi di più su" @@ -1905,6 +2120,9 @@ msgstr "Abbandona il progetto" msgid "License" msgstr "" +msgid "List" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -1914,6 +2132,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" @@ -1923,6 +2144,9 @@ msgstr "Bloccato" msgid "Locked Files" msgstr "" +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + msgid "Login" msgstr "Login" @@ -1953,9 +2177,6 @@ msgstr "Mediano" msgid "Members" msgstr "Membri" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "Richieste di merge" @@ -1968,6 +2189,9 @@ msgstr "Richiesta di merge" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" @@ -1992,9 +2216,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "aggiungi una chiave SSH" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "Monitoraggio" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "Ulteriori informazioni sono disponibili | qui" @@ -2090,9 +2323,6 @@ msgstr "Nessuna Repository" msgid "No schedules" msgstr "Nessuna pianificazione" -msgid "No time spent" -msgstr "" - msgid "None" msgstr "Nessuno" @@ -2108,6 +2338,9 @@ msgstr "" msgid "Not enough data" msgstr "Dati insufficienti " +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "Notifica eventi" @@ -2210,6 +2443,9 @@ msgstr "Si apre in una nuova finestra" msgid "Options" msgstr "Opzioni" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + msgid "Overview" msgstr "Panoramica" @@ -2315,6 +2551,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "tutto" @@ -2348,6 +2602,9 @@ msgstr "Privato - L'accesso al progetto deve essere fornito esplicitamente ad og msgid "Private - The group and its projects can only be viewed by members." msgstr "Privato - Il gruppo e i suoi progetti possono essere visualizzati solo dai membri." +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "Profilo" @@ -2510,12 +2767,30 @@ msgstr "Siamo spiacenti, non ci sono progetti che corrispondono alla tua ricerca msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "Questa feature richiede il supporto del localStorage del browser" +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "Di default, Prometheus è in ascolto su ‘http://localhost:9090‘. Non è consigliabile cambiare l'indirizzo e la porta di default in quanto ciò potrebbe influenzare o causare conflitto con altri servizi in esecuzione sul server GitLab." msgid "PrometheusService|Finding and configuring metrics..." msgstr "Ricerco e configuro le metriche..." +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + msgid "PrometheusService|Metrics" msgstr "Metriche" @@ -2537,9 +2812,18 @@ msgstr "Nessuna metrica è stata monitorata. Per iniziare a monitorare, rilascia msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + msgid "PrometheusService|Time-series monitoring service" msgstr "" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + msgid "PrometheusService|View environments" msgstr "" @@ -2558,6 +2842,12 @@ msgstr "" msgid "Push events" msgstr "" +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + msgid "PushRule|Committer restriction" msgstr "" @@ -2621,6 +2911,9 @@ msgstr "" msgid "Repository" msgstr "" +msgid "Repository has no locks." +msgstr "" + msgid "Request Access" msgstr "Richiedi accesso" @@ -2633,6 +2926,9 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" @@ -2644,6 +2940,9 @@ msgstr "Ripristina questo commit" msgid "Revert this merge request" msgstr "Ripristina questa richiesta di merge" +msgid "Roadmap" +msgstr "" + msgid "SSH Keys" msgstr "Chiavi SSH" @@ -2689,12 +2988,18 @@ msgstr "" msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "Seleziona formato d'archivio" msgid "Select a timezone" msgstr "Seleziona una timezone" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2707,6 +3012,9 @@ msgstr "Seleziona una branch di destinazione" msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "Set" @@ -2719,6 +3027,9 @@ msgstr "" msgid "Service Templates" msgstr "" +msgid "Service URL" +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Establezca una contraseña en su cuenta para actualizar o enviar a través de %{protocol}." @@ -2728,15 +3039,15 @@ msgstr "" msgid "Set up Koding" msgstr "Configura Koding" -msgid "Set up auto deploy" -msgstr "Configura il rilascio automatico" - msgid "SetPasswordToCloneLink|set a password" msgstr "imposta una password" msgid "Settings" msgstr "Impostazioni" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" @@ -2746,6 +3057,9 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2787,12 +3101,24 @@ msgstr "" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "Qualcosa è andato storto durante il fetch dei progetti." msgid "Something went wrong while fetching the registry list." msgstr "Qualcosa è andato storto durante il recupero dell'elenco dei registri." +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" @@ -2898,6 +3224,9 @@ msgstr "" msgid "Source" msgstr "" +msgid "Source (branch or tag)" +msgstr "" + msgid "Source code" msgstr "Codice Sorgente" @@ -2937,8 +3266,8 @@ msgstr "Cambia branch/tag" msgid "System Hooks" msgstr "" -msgid "Tag" -msgid_plural "Tags" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" msgstr[0] "" msgstr[1] "" @@ -3071,9 +3400,15 @@ msgstr "Chiunque può accedere a questo progetto (senza alcuna autenticazione)." msgid "The repository for this project does not exist." msgstr "La repository di questo progetto non esiste." +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "Lo stadio di revisione mostra il tempo tra una richiesta di merge al suo svolgimento effettivo. Questo dato sarà disponibile appena avrai completato una MR (Merger Request)" +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "Lo stadio di pre-rilascio mostra il tempo che trascorre da una MR (Richiesta di Merge) completata al suo rilascio in ambiente di produzione. Questa informazione sarà disponibile dal tuo primo rilascio in produzione" @@ -3167,6 +3502,9 @@ msgstr "Questo significa che non è possibile effettuare push di codice fino a c msgid "This merge request is locked." msgstr "" +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3336,9 +3674,15 @@ msgstr[1] "mins" msgid "Time|s" msgstr "s" +msgid "Tip:" +msgstr "" + msgid "Title" msgstr "" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + msgid "Todo" msgstr "" @@ -3354,19 +3698,16 @@ msgstr "" msgid "Total Time" msgstr "Tempo Totale" -msgid "Total issue time spent" -msgstr "" - msgid "Total test time for all commits/merges" msgstr "Tempo totale di test per tutti i commits/merges" -msgid "Track activity with Contribution Analytics." +msgid "Total: %{total}" msgstr "" -msgid "Track groups of issues that share a theme, across projects and milestones" +msgid "Track activity with Contribution Analytics." msgstr "" -msgid "Total: %{total}" +msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" msgid "Track time with quick actions" @@ -3378,9 +3719,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3396,6 +3734,9 @@ msgstr "" msgid "Unlocked" msgstr "" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "" @@ -3441,6 +3782,9 @@ msgstr "Usa le tue impostazioni globali " msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" +msgid "View epics list" +msgstr "" + msgid "View file @ " msgstr "" @@ -3477,6 +3821,9 @@ msgstr "Non ci sono sufficienti dati da mostrare su questo stadio" msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "" +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "" @@ -3597,6 +3944,9 @@ msgstr "" msgid "Withdraw Access Request" msgstr "Ritira richiesta d'accesso" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "Stai per rimuovere il gruppo %{group_name}. I gruppi rimossi NON POSSONO esser ripristinati! Sei ASSOLUTAMENTE sicuro?" @@ -3609,10 +3959,13 @@ msgstr "Stai per rimuovere la relazione con il progetto sorgente %{forked_from_p msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Stai per trasferire %{project_name_with_namespace} ad un altro owner. Sei ASSOLUTAMENTE sicuro?" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3630,12 +3983,24 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "Hai raggiunto il tuo limite di progetto" +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "Devi accedere per porre una star al progetto" +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "Necessiti del permesso." @@ -3669,6 +4034,9 @@ msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -3696,7 +4064,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3708,7 +4076,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3723,6 +4091,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3735,6 +4112,12 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + msgid "commit" msgstr "" @@ -3752,11 +4135,41 @@ msgstr[1] "giorni" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" msgstr[1] "" +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3790,6 +4203,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3823,6 +4239,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3877,6 +4296,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3924,3 +4346,6 @@ msgstr "" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po index 31c4422c928..180f1f0fbe7 100644 --- a/locale/ja/gitlab.po +++ b/locale/ja/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 04:00-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:21-0500\n" "Last-Translator: gitlab \n" "Language-Team: Japanese\n" "Language: ja_JP\n" @@ -43,6 +43,9 @@ msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "パフォーマンス低下を避けるため %s 個のコミットを省略しました。" +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -50,6 +53,9 @@ msgid "%{count} participant" msgid_plural "%{count} participants" msgstr[0] "" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "" @@ -59,6 +65,9 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "" +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "" @@ -121,9 +130,15 @@ msgstr "貢献者向けガイドを追加" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "" +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "ライセンスを追加" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "新規ディレクトリを追加" @@ -142,12 +157,45 @@ msgstr "" msgid "AdminArea|Stopping jobs failed" msgstr "" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" msgid "AdminHealthPageLink|health page" msgstr "" +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + msgid "Advanced" msgstr "" @@ -172,6 +220,12 @@ msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" @@ -181,12 +235,36 @@ msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while fetching the pipeline." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + msgid "An error occurred while loading filenames" msgstr "" +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + msgid "An error occurred while rendering KaTeX" msgstr "" @@ -199,6 +277,12 @@ msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + msgid "An error occurred while validating username" msgstr "" @@ -223,15 +307,15 @@ msgstr "アーカイブ済みプロジェクト!(レポジトリーは読み msgid "Are you sure you want to delete this pipeline schedule?" msgstr "このパイプラインスケジュールを削除しますか?" -msgid "Are you sure you want to discard your changes?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "" msgid "Are you sure you want to reset the health check token?" msgstr "" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "" @@ -271,6 +355,9 @@ msgstr "" msgid "Authors: %{authors}" msgstr "" +msgid "Auto DevOps enabled" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -295,7 +382,13 @@ msgstr "" msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" msgstr "" msgid "Available" @@ -307,6 +400,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Billing" msgstr "" @@ -361,12 +457,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "ブランチ" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "%{branch_name} ブランチが作成されました。自動デプロイを設定するには、GitLab CI Yaml テンプレートを選択して、変更をコミットしてください。 %{link_to_autodeploy_doc}" @@ -659,6 +752,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + msgid "Click to expand text" msgstr "" @@ -713,6 +809,9 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -761,6 +860,9 @@ msgstr "" msgid "ClusterIntegration|Ingress" msgstr "" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "" @@ -812,9 +914,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" msgstr "" @@ -950,6 +1049,12 @@ msgstr "" msgid "Collapse" msgstr "" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "" @@ -957,6 +1062,10 @@ msgid "Commit" msgid_plural "Commits" msgstr[0] "コミット" +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" + msgid "Commit Message" msgstr "" @@ -969,6 +1078,9 @@ msgstr "コミットメッセージ" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "コミット" @@ -1110,6 +1222,9 @@ msgstr "クリップボードにURLをコピー" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "コミットのSHAをクリップボードにコピー" @@ -1122,9 +1237,18 @@ msgstr "" msgid "Create New Directory" msgstr "新規ディレクトリを作成" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "%{protocol} でプッシュやプルするためのあなた個人用アクセストークンを作成" +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "ディレクトリを作成" @@ -1143,6 +1267,9 @@ msgstr "" msgid "Create merge request" msgstr "マージリクエストを作成" +msgid "Create merge request and branch" +msgstr "" + msgid "Create new branch" msgstr "" @@ -1167,6 +1294,12 @@ msgstr "タグ" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "個人用アクセストークンを作成" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + msgid "Creating epic" msgstr "" @@ -1221,6 +1354,9 @@ msgstr "" msgid "December" msgstr "" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "Cron 構文でカスタムなパターンを指定する" @@ -1252,7 +1388,7 @@ msgstr "ディレクトリ名" msgid "Disable" msgstr "" -msgid "Discard changes" +msgid "Discard draft" msgstr "" msgid "Discover GitLab Geo." @@ -1312,6 +1448,9 @@ msgstr "" msgid "Enable" msgstr "" +msgid "Enable Auto DevOps" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1366,9 +1505,18 @@ msgstr "" msgid "Epics" msgstr "" +msgid "Epics Roadmap" +msgstr "" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "" @@ -1435,12 +1583,36 @@ msgstr "" msgid "Explore public groups" msgstr "" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + msgid "Failed to change the owner" msgstr "オーナーを変更できませんでした" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "パイプラインスケジュールを削除できませんでした" +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "" @@ -1456,6 +1628,9 @@ msgstr "" msgid "Files" msgstr "ファイル" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "コミットメッセージで絞り込み" @@ -1490,6 +1665,9 @@ msgstr "課題が登録されてからプロダクションにデプロイされ msgid "From merge request merge until deploy to production" msgstr "マージリクエストがマージされてからプロダクションにデプロイされるまで" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "" @@ -1637,6 +1815,24 @@ msgstr "" msgid "Got it!" msgstr "" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1734,6 +1930,9 @@ msgstr "" msgid "Housekeeping successfully started" msgstr "ハウスキーピングは正常に起動しました。" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "レポジトリーをインポート" @@ -1746,6 +1945,9 @@ msgstr "" msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "" +msgid "Install Runner on Kubernetes" +msgstr "" + msgid "Install a Runner compatible with GitLab CI" msgstr "" @@ -1795,6 +1997,9 @@ msgstr "" msgid "January" msgstr "" +msgid "Jobs" +msgstr "" + msgid "Jul" msgstr "" @@ -1825,6 +2030,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1871,6 +2079,12 @@ msgstr "" msgid "Learn more" msgstr "" +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + msgid "Learn more in the" msgstr "詳しく見る:" @@ -1889,6 +2103,9 @@ msgstr "プロジェクトを離脱" msgid "License" msgstr "" +msgid "List" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -1898,6 +2115,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" @@ -1907,6 +2127,9 @@ msgstr "" msgid "Locked Files" msgstr "" +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + msgid "Login" msgstr "" @@ -1937,9 +2160,6 @@ msgstr "中央値" msgid "Members" msgstr "" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "" @@ -1952,6 +2172,9 @@ msgstr "" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" @@ -1976,9 +2199,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "SSH 鍵を追加" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "" @@ -2073,9 +2305,6 @@ msgstr "レポジトリーはありません" msgid "No schedules" msgstr "スケジュールなし" -msgid "No time spent" -msgstr "" - msgid "None" msgstr "" @@ -2091,6 +2320,9 @@ msgstr "" msgid "Not enough data" msgstr "データ不足" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "イベント通知" @@ -2193,6 +2425,9 @@ msgstr "" msgid "Options" msgstr "オプション" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + msgid "Overview" msgstr "" @@ -2298,6 +2533,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "全件" @@ -2331,6 +2584,9 @@ msgstr "" msgid "Private - The group and its projects can only be viewed by members." msgstr "" +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "" @@ -2493,12 +2749,30 @@ msgstr "" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "" +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "" msgid "PrometheusService|Finding and configuring metrics..." msgstr "" +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + msgid "PrometheusService|Metrics" msgstr "" @@ -2520,9 +2794,18 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + msgid "PrometheusService|Time-series monitoring service" msgstr "" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + msgid "PrometheusService|View environments" msgstr "" @@ -2541,6 +2824,12 @@ msgstr "" msgid "Push events" msgstr "" +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + msgid "PushRule|Committer restriction" msgstr "" @@ -2604,6 +2893,9 @@ msgstr "" msgid "Repository" msgstr "" +msgid "Repository has no locks." +msgstr "" + msgid "Request Access" msgstr "アクセス権限をリクエストする" @@ -2616,6 +2908,9 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" @@ -2626,6 +2921,9 @@ msgstr "このコミットをリバート" msgid "Revert this merge request" msgstr "このマージリクエストをリバート" +msgid "Roadmap" +msgstr "" + msgid "SSH Keys" msgstr "" @@ -2671,12 +2969,18 @@ msgstr "" msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "アーカイブのフォーマットを選択" msgid "Select a timezone" msgstr "タイムゾーンを選択" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2689,6 +2993,9 @@ msgstr "ターゲットブランチを選択" msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "" @@ -2701,6 +3008,9 @@ msgstr "" msgid "Service Templates" msgstr "" +msgid "Service URL" +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "%{protocol} プロコトル経由でプル、プッシュするためにアカウントのパスワードを設定。" @@ -2710,15 +3020,15 @@ msgstr "" msgid "Set up Koding" msgstr "Koding を設定" -msgid "Set up auto deploy" -msgstr "自動デプロイを設定" - msgid "SetPasswordToCloneLink|set a password" msgstr "パスワードを設定" msgid "Settings" msgstr "" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" @@ -2728,6 +3038,9 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2768,12 +3081,24 @@ msgstr "" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" @@ -2879,6 +3204,9 @@ msgstr "" msgid "Source" msgstr "" +msgid "Source (branch or tag)" +msgstr "" + msgid "Source code" msgstr "ソースコード" @@ -2918,9 +3246,9 @@ msgstr "ブランチ・タグ切り替え" msgid "System Hooks" msgstr "" -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "タグ" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "" msgid "Tags" msgstr "タグ" @@ -3051,9 +3379,15 @@ msgstr "プロジェクトは、ログインなしに誰でもアクセスでき msgid "The repository for this project does not exist." msgstr "このプロジェクトにレポジトリーはありません。" +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "レビューステージとは、マージリクエストを作成してからマージするまでの時間です。このデータは最初のマージリクエストがマージされたときに自動的に追加されます。" +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "ステージングステージでは、マージリクエストがマージされてからコードがプロダクション環境にデプロイされるまでの時間が表示されます。このデータは最初にプロダクションにデプロイしたときに自動的に追加されます。" @@ -3147,6 +3481,9 @@ msgstr "空レポジトリーを作成または既存レポジトリーをイン msgid "This merge request is locked." msgstr "" +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3314,9 +3651,15 @@ msgstr[0] "分" msgid "Time|s" msgstr "秒" +msgid "Tip:" +msgstr "" + msgid "Title" msgstr "" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + msgid "Todo" msgstr "" @@ -3332,19 +3675,16 @@ msgstr "" msgid "Total Time" msgstr "合計時間" -msgid "Total issue time spent" -msgstr "" - msgid "Total test time for all commits/merges" msgstr "すべてのコミット/マージの合計テスト時間" -msgid "Track activity with Contribution Analytics." +msgid "Total: %{total}" msgstr "" -msgid "Track groups of issues that share a theme, across projects and milestones" +msgid "Track activity with Contribution Analytics." msgstr "" -msgid "Total: %{total}" +msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" msgid "Track time with quick actions" @@ -3356,9 +3696,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3374,6 +3711,9 @@ msgstr "" msgid "Unlocked" msgstr "" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "スターを外す" @@ -3419,6 +3759,9 @@ msgstr "全体通知設定を利用" msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" +msgid "View epics list" +msgstr "" + msgid "View file @ " msgstr "" @@ -3455,6 +3798,9 @@ msgstr "データ不足のため、このステージの表示はできません msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "" +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "" @@ -3575,6 +3921,9 @@ msgstr "" msgid "Withdraw Access Request" msgstr "アクセスリクエストを取り消す" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "%{group_name} グループを削除しようとしています。 削除されたグループは絶対に元に戻せません!本当によろしいですか?" @@ -3587,10 +3936,13 @@ msgstr "元のプロジェクト (%{forked_from_project}) とのリレーショ msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "%{project_name_with_namespace} プロジェクトを別のオーナーに移譲しようとしています。本当によろしいですか?" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3608,12 +3960,24 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "プロジェクト数の上限に達しています" +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "プロジェクトにスターをつけたい場合はログインしてください" +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "権限が必要です" @@ -3647,6 +4011,9 @@ msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -3674,7 +4041,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3686,7 +4053,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3701,6 +4068,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3713,6 +4089,12 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + msgid "commit" msgstr "" @@ -3729,10 +4111,40 @@ msgstr[0] "日" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3766,6 +4178,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3799,6 +4214,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3853,6 +4271,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3899,3 +4320,6 @@ msgstr "" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po index 73909e6f6de..13634091ed7 100644 --- a/locale/ko/gitlab.po +++ b/locale/ko/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 04:00-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:19-0500\n" "Last-Translator: gitlab \n" "Language-Team: Korean\n" "Language: ko_KR\n" @@ -43,6 +43,9 @@ msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "%s 추가 커밋은 성능 이슈를 방지하기 위해 생략되었습니다." +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -50,6 +53,9 @@ msgid "%{count} participant" msgid_plural "%{count} participants" msgstr[0] "" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "" @@ -59,6 +65,9 @@ msgstr "%{number_of_failures} / %{maximum_failures} 실패. GitLab 은 다음 msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} / %{maximum_failures} 실패. GitLab 은 자동으로 다시 시도하지 않습니다. 문제가 해결되면 저장 공간 정보를 초기화 해주세요. " +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "" @@ -121,9 +130,15 @@ msgstr "기여 가이드 추가" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "" +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "라이선스 추가" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "새 디렉토리 추가" @@ -142,12 +157,45 @@ msgstr "" msgid "AdminArea|Stopping jobs failed" msgstr "" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" msgid "AdminHealthPageLink|health page" msgstr "" +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + msgid "Advanced" msgstr "" @@ -172,6 +220,12 @@ msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" @@ -181,12 +235,36 @@ msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while fetching the pipeline." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + msgid "An error occurred while loading filenames" msgstr "" +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + msgid "An error occurred while rendering KaTeX" msgstr "" @@ -199,6 +277,12 @@ msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + msgid "An error occurred while validating username" msgstr "" @@ -223,15 +307,15 @@ msgstr "프로젝트가 보관되었습니다! 저장소는 읽기만 가능합 msgid "Are you sure you want to delete this pipeline schedule?" msgstr "이 파이프라인 스케쥴을 삭제 하시겠습니까?" -msgid "Are you sure you want to discard your changes?" -msgstr "변경 내용을 취소하시겠습니까?" - msgid "Are you sure you want to reset registration token?" msgstr "등록 토큰을 초기화 하시겠습니까?" msgid "Are you sure you want to reset the health check token?" msgstr "헬스 체크 토큰을 초기화 하시겠습니까?" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "확실합니까?" @@ -271,6 +355,9 @@ msgstr "" msgid "Authors: %{authors}" msgstr "" +msgid "Auto DevOps enabled" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -295,7 +382,13 @@ msgstr "" msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" msgstr "" msgid "Available" @@ -307,6 +400,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Billing" msgstr "" @@ -361,12 +457,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "브랜치" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "%{branch_name} 브랜치가 생성되었습니다. 자동 배포를 설정하려면 GitLab CI Yaml 템플릿을 선택하고 변경 사항을 적용하십시오. %{link_to_autodeploy_doc}" @@ -659,6 +752,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + msgid "Click to expand text" msgstr "" @@ -713,6 +809,9 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -761,6 +860,9 @@ msgstr "" msgid "ClusterIntegration|Ingress" msgstr "" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "" @@ -812,9 +914,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" msgstr "" @@ -950,6 +1049,12 @@ msgstr "" msgid "Collapse" msgstr "" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "" @@ -957,6 +1062,10 @@ msgid "Commit" msgid_plural "Commits" msgstr[0] "커밋" +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" + msgid "Commit Message" msgstr "" @@ -969,6 +1078,9 @@ msgstr "커밋 메시지" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "커밋" @@ -1110,6 +1222,9 @@ msgstr "URL을 클립보드에 복사" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "커밋의 SHA를 클립보드로 복사합니다" @@ -1122,9 +1237,18 @@ msgstr "" msgid "Create New Directory" msgstr "새 디렉토리 만들기" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "%{protocol}을 (를) 통해 Pull 하거나 Push 할 개인 액세스 토큰을 만드십시오." +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "디렉토리 만들기" @@ -1143,6 +1267,9 @@ msgstr "" msgid "Create merge request" msgstr "머지 리퀘스트 만들기" +msgid "Create merge request and branch" +msgstr "" + msgid "Create new branch" msgstr "" @@ -1167,6 +1294,12 @@ msgstr "태그" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "개인 액세스 토큰 만들기" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + msgid "Creating epic" msgstr "" @@ -1221,6 +1354,9 @@ msgstr "" msgid "December" msgstr "" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "cron 구문을 사용하여 사용자 정의 패턴 정의" @@ -1252,8 +1388,8 @@ msgstr "디렉토리 이름" msgid "Disable" msgstr "" -msgid "Discard changes" -msgstr "변경 내용 취소" +msgid "Discard draft" +msgstr "" msgid "Discover GitLab Geo." msgstr "" @@ -1312,6 +1448,9 @@ msgstr "" msgid "Enable" msgstr "" +msgid "Enable Auto DevOps" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1366,9 +1505,18 @@ msgstr "" msgid "Epics" msgstr "" +msgid "Epics Roadmap" +msgstr "" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "" @@ -1435,12 +1583,36 @@ msgstr "" msgid "Explore public groups" msgstr "" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + msgid "Failed to change the owner" msgstr "소유자를 변경하지 못했습니다" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "파이프라인 스케줄을 제거하지 못했습니다." +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "" @@ -1456,6 +1628,9 @@ msgstr "" msgid "Files" msgstr "파일" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "커밋 메시지로 필터" @@ -1490,6 +1665,9 @@ msgstr "이슈 생성에서 프로덕션 배포까지" msgid "From merge request merge until deploy to production" msgstr "머지 리퀘스트 머지에서 프로덕션 환경에 배포까지" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "" @@ -1637,6 +1815,24 @@ msgstr "" msgid "Got it!" msgstr "" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1734,6 +1930,9 @@ msgstr "" msgid "Housekeeping successfully started" msgstr "Housekeeping이 성공적으로 시작되었습니다" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "저장소 가져 오기" @@ -1746,6 +1945,9 @@ msgstr "" msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "" +msgid "Install Runner on Kubernetes" +msgstr "" + msgid "Install a Runner compatible with GitLab CI" msgstr "GitLab CI 와 호환되는 Runner 설치" @@ -1795,6 +1997,9 @@ msgstr "" msgid "January" msgstr "" +msgid "Jobs" +msgstr "" + msgid "Jul" msgstr "" @@ -1825,6 +2030,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1871,6 +2079,12 @@ msgstr "at" msgid "Learn more" msgstr "" +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + msgid "Learn more in the" msgstr "더 자세히 알아보기" @@ -1889,6 +2103,9 @@ msgstr "프로젝트에서 나가기" msgid "License" msgstr "" +msgid "List" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -1898,6 +2115,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" @@ -1907,6 +2127,9 @@ msgstr "" msgid "Locked Files" msgstr "" +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + msgid "Login" msgstr "" @@ -1937,9 +2160,6 @@ msgstr "중앙값" msgid "Members" msgstr "" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "" @@ -1952,6 +2172,9 @@ msgstr "" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" @@ -1976,9 +2199,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "SSH 키 추가" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "여기" @@ -2073,9 +2305,6 @@ msgstr "저장소 없음" msgid "No schedules" msgstr "일정 없음" -msgid "No time spent" -msgstr "" - msgid "None" msgstr "" @@ -2091,6 +2320,9 @@ msgstr "" msgid "Not enough data" msgstr "데이터가 충분하지 않습니다." +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "알림 이벤트" @@ -2193,6 +2425,9 @@ msgstr "" msgid "Options" msgstr "옵션 " +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + msgid "Overview" msgstr "" @@ -2298,6 +2533,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "모두" @@ -2331,6 +2584,9 @@ msgstr "" msgid "Private - The group and its projects can only be viewed by members." msgstr "" +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "" @@ -2493,12 +2749,30 @@ msgstr "" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "" +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "" msgid "PrometheusService|Finding and configuring metrics..." msgstr "" +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + msgid "PrometheusService|Metrics" msgstr "" @@ -2520,9 +2794,18 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + msgid "PrometheusService|Time-series monitoring service" msgstr "" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + msgid "PrometheusService|View environments" msgstr "" @@ -2541,6 +2824,12 @@ msgstr "" msgid "Push events" msgstr "푸쉬 이벤트" +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + msgid "PushRule|Committer restriction" msgstr "" @@ -2604,6 +2893,9 @@ msgstr "" msgid "Repository" msgstr "" +msgid "Repository has no locks." +msgstr "" + msgid "Request Access" msgstr "액세스 요청" @@ -2616,6 +2908,9 @@ msgstr "헬스 체크 접근 토큰 초기화" msgid "Reset runners registration token" msgstr "runner 등록 토큰 초기화" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" @@ -2626,6 +2921,9 @@ msgstr "이 커밋 되돌리기" msgid "Revert this merge request" msgstr "이 머지 리퀘스트 되돌리기" +msgid "Roadmap" +msgstr "" + msgid "SSH Keys" msgstr "" @@ -2671,12 +2969,18 @@ msgstr "" msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "아카이브 포맷 선택" msgid "Select a timezone" msgstr "시간대 선택" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2689,6 +2993,9 @@ msgstr "대상 브랜치 선택" msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "" @@ -2701,6 +3008,9 @@ msgstr "" msgid "Service Templates" msgstr "" +msgid "Service URL" +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "%{protocol} 프로토콜을 통해 Pull 하거나 Push하려면 계정에 패스워드를 설정하십시오." @@ -2710,15 +3020,15 @@ msgstr "" msgid "Set up Koding" msgstr "Koding 설정" -msgid "Set up auto deploy" -msgstr "자동 배포 설정" - msgid "SetPasswordToCloneLink|set a password" msgstr "패스워드 설정" msgid "Settings" msgstr "" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" @@ -2728,6 +3038,9 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2768,12 +3081,24 @@ msgstr "" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" @@ -2879,6 +3204,9 @@ msgstr "" msgid "Source" msgstr "" +msgid "Source (branch or tag)" +msgstr "" + msgid "Source code" msgstr "소스 코드" @@ -2918,9 +3246,9 @@ msgstr "스위치 브랜치/태그" msgid "System Hooks" msgstr "" -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "태그" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "" msgid "Tags" msgstr "태그 " @@ -3051,9 +3379,15 @@ msgstr "이 프로젝트는 인증없이 액세스 할 수 있습니다." msgid "The repository for this project does not exist." msgstr "이 프로젝트의 저장소가 존재하지 않습니다." +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "Review 단계에서는 머지 리퀘스트를 작성한 후 머지하기까지의 시간을 보여줍니다. 데이터는 첫 번째 머지 리퀘스트을 머지 한 후에 자동으로 추가됩니다." +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "Staging 단계에서는 MR 머지과 프로덕션 환경에 코드 배포 사이의 시간을 보여줍니다. 데이터를 Production 환경에 처음 배포하면 데이터가 자동으로 추가됩니다." @@ -3147,6 +3481,9 @@ msgstr "즉, 빈 저장소를 만들거나 기존 저장소를 가져올 때까 msgid "This merge request is locked." msgstr "" +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3314,9 +3651,15 @@ msgstr[0] "분" msgid "Time|s" msgstr "초" +msgid "Tip:" +msgstr "" + msgid "Title" msgstr "" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + msgid "Todo" msgstr "" @@ -3332,19 +3675,16 @@ msgstr "" msgid "Total Time" msgstr "시간 합계:" -msgid "Total issue time spent" -msgstr "" - msgid "Total test time for all commits/merges" msgstr "모든 커밋 / 머지의 총 테스트 시간" -msgid "Track activity with Contribution Analytics." +msgid "Total: %{total}" msgstr "" -msgid "Track groups of issues that share a theme, across projects and milestones" +msgid "Track activity with Contribution Analytics." msgstr "" -msgid "Total: %{total}" +msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" msgid "Track time with quick actions" @@ -3356,9 +3696,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3374,6 +3711,9 @@ msgstr "" msgid "Unlocked" msgstr "" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "별표 제거" @@ -3419,6 +3759,9 @@ msgstr "전체 알림 설정 사용" msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" +msgid "View epics list" +msgstr "" + msgid "View file @ " msgstr "" @@ -3455,6 +3798,9 @@ msgstr "이 단계를 보여주기에 충분한 데이터가 없습니다." msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "" +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "" @@ -3575,6 +3921,9 @@ msgstr "" msgid "Withdraw Access Request" msgstr "액세스 요청 철회" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "%{group_name} 그룹을 제거하려고합니다. \\\"정말로\\\" 확실합니까?" @@ -3587,10 +3936,13 @@ msgstr "포크 관계를 소스 프로젝트 %{forked_from_project}에 대해 msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "%{project_name_with_namespace}을 다른 소유자에게 이전하려고합니다. \"정말로\" 확실합니까?" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3608,12 +3960,24 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "프로젝트 숫자 한도에 도달했습니다." +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "프로젝트에 별표를 표시하려면 로그인 해야 합니다." +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "권한이 필요합니다." @@ -3647,6 +4011,9 @@ msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -3674,7 +4041,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3686,7 +4053,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3701,6 +4068,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3713,6 +4089,12 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + msgid "commit" msgstr "" @@ -3729,10 +4111,40 @@ msgstr[0] "일" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3766,6 +4178,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3799,6 +4214,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3853,6 +4271,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3899,3 +4320,6 @@ msgstr "" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/nl_NL/gitlab.po b/locale/nl_NL/gitlab.po index 451be6434db..ce3f2e5627e 100644 --- a/locale/nl_NL/gitlab.po +++ b/locale/nl_NL/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 03:59-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:22-0500\n" "Last-Translator: gitlab \n" "Language-Team: Dutch\n" "Language: nl_NL\n" @@ -49,6 +49,9 @@ msgid_plural "%s additional commits have been omitted to prevent performance iss msgstr[0] "%s andere commit is weggelaten om prestatieproblemen te voorkomen." msgstr[1] "%s andere commits zijn weggelaten om prestatieproblemen te voorkomen." +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -57,6 +60,9 @@ msgid_plural "%{count} participants" msgstr[0] "" msgstr[1] "" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "" @@ -66,6 +72,9 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "" +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "" @@ -130,9 +139,15 @@ msgstr "" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "" +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "Licentie toevoegen" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "Nieuwe map toevoegen" @@ -151,12 +166,45 @@ msgstr "" msgid "AdminArea|Stopping jobs failed" msgstr "" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" msgid "AdminHealthPageLink|health page" msgstr "" +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + msgid "Advanced" msgstr "" @@ -181,6 +229,12 @@ msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" @@ -190,12 +244,36 @@ msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while fetching the pipeline." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + msgid "An error occurred while loading filenames" msgstr "" +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + msgid "An error occurred while rendering KaTeX" msgstr "" @@ -208,6 +286,12 @@ msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + msgid "An error occurred while validating username" msgstr "" @@ -232,15 +316,15 @@ msgstr "" msgid "Are you sure you want to delete this pipeline schedule?" msgstr "" -msgid "Are you sure you want to discard your changes?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "" msgid "Are you sure you want to reset the health check token?" msgstr "" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "" @@ -280,6 +364,9 @@ msgstr "Auteur" msgid "Authors: %{authors}" msgstr "" +msgid "Auto DevOps enabled" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -304,7 +391,13 @@ msgstr "" msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" msgstr "" msgid "Available" @@ -316,6 +409,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Billing" msgstr "" @@ -370,11 +466,8 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" msgstr[0] "" msgstr[1] "" @@ -669,6 +762,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + msgid "Click to expand text" msgstr "" @@ -723,6 +819,9 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -771,6 +870,9 @@ msgstr "" msgid "ClusterIntegration|Ingress" msgstr "" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "" @@ -822,9 +924,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" msgstr "" @@ -960,6 +1059,12 @@ msgstr "" msgid "Collapse" msgstr "" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "Opmerkingen" @@ -968,6 +1073,11 @@ msgid_plural "Commits" msgstr[0] "" msgstr[1] "" +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" +msgstr[1] "" + msgid "Commit Message" msgstr "" @@ -980,6 +1090,9 @@ msgstr "" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Commit" @@ -1121,6 +1234,9 @@ msgstr "" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "" @@ -1133,9 +1249,18 @@ msgstr "" msgid "Create New Directory" msgstr "" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "" +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "Maak map aan" @@ -1154,6 +1279,9 @@ msgstr "" msgid "Create merge request" msgstr "" +msgid "Create merge request and branch" +msgstr "" + msgid "Create new branch" msgstr "" @@ -1178,6 +1306,12 @@ msgstr "" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + msgid "Creating epic" msgstr "" @@ -1232,6 +1366,9 @@ msgstr "" msgid "December" msgstr "" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "" @@ -1264,7 +1401,7 @@ msgstr "" msgid "Disable" msgstr "" -msgid "Discard changes" +msgid "Discard draft" msgstr "" msgid "Discover GitLab Geo." @@ -1324,6 +1461,9 @@ msgstr "" msgid "Enable" msgstr "" +msgid "Enable Auto DevOps" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1378,9 +1518,18 @@ msgstr "" msgid "Epics" msgstr "" +msgid "Epics Roadmap" +msgstr "" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "" @@ -1447,12 +1596,36 @@ msgstr "" msgid "Explore public groups" msgstr "" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + msgid "Failed to change the owner" msgstr "" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "" +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "" @@ -1468,6 +1641,9 @@ msgstr "" msgid "Files" msgstr "" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "" @@ -1503,6 +1679,9 @@ msgstr "" msgid "From merge request merge until deploy to production" msgstr "" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "" @@ -1650,6 +1829,24 @@ msgstr "" msgid "Got it!" msgstr "" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1748,6 +1945,9 @@ msgstr "" msgid "Housekeeping successfully started" msgstr "" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "" @@ -1760,6 +1960,9 @@ msgstr "" msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "" +msgid "Install Runner on Kubernetes" +msgstr "" + msgid "Install a Runner compatible with GitLab CI" msgstr "" @@ -1810,6 +2013,9 @@ msgstr "" msgid "January" msgstr "" +msgid "Jobs" +msgstr "" + msgid "Jul" msgstr "" @@ -1840,6 +2046,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1887,6 +2096,12 @@ msgstr "" msgid "Learn more" msgstr "" +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + msgid "Learn more in the" msgstr "" @@ -1905,6 +2120,9 @@ msgstr "" msgid "License" msgstr "" +msgid "List" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -1914,6 +2132,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" @@ -1923,6 +2144,9 @@ msgstr "" msgid "Locked Files" msgstr "" +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + msgid "Login" msgstr "" @@ -1953,9 +2177,6 @@ msgstr "" msgid "Members" msgstr "" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "" @@ -1968,6 +2189,9 @@ msgstr "" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" @@ -1992,9 +2216,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "" @@ -2090,9 +2323,6 @@ msgstr "" msgid "No schedules" msgstr "" -msgid "No time spent" -msgstr "" - msgid "None" msgstr "" @@ -2108,6 +2338,9 @@ msgstr "" msgid "Not enough data" msgstr "" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "" @@ -2210,6 +2443,9 @@ msgstr "" msgid "Options" msgstr "" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + msgid "Overview" msgstr "" @@ -2315,6 +2551,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "" @@ -2348,6 +2602,9 @@ msgstr "" msgid "Private - The group and its projects can only be viewed by members." msgstr "" +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "" @@ -2510,12 +2767,30 @@ msgstr "" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "" +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "" msgid "PrometheusService|Finding and configuring metrics..." msgstr "" +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + msgid "PrometheusService|Metrics" msgstr "" @@ -2537,9 +2812,18 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + msgid "PrometheusService|Time-series monitoring service" msgstr "" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + msgid "PrometheusService|View environments" msgstr "" @@ -2558,6 +2842,12 @@ msgstr "" msgid "Push events" msgstr "" +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + msgid "PushRule|Committer restriction" msgstr "" @@ -2621,6 +2911,9 @@ msgstr "" msgid "Repository" msgstr "" +msgid "Repository has no locks." +msgstr "" + msgid "Request Access" msgstr "" @@ -2633,6 +2926,9 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" @@ -2644,6 +2940,9 @@ msgstr "" msgid "Revert this merge request" msgstr "" +msgid "Roadmap" +msgstr "" + msgid "SSH Keys" msgstr "" @@ -2689,12 +2988,18 @@ msgstr "" msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "" msgid "Select a timezone" msgstr "" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2707,6 +3012,9 @@ msgstr "" msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "" @@ -2719,6 +3027,9 @@ msgstr "" msgid "Service Templates" msgstr "" +msgid "Service URL" +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" @@ -2728,15 +3039,15 @@ msgstr "" msgid "Set up Koding" msgstr "" -msgid "Set up auto deploy" -msgstr "" - msgid "SetPasswordToCloneLink|set a password" msgstr "" msgid "Settings" msgstr "" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" @@ -2746,6 +3057,9 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2787,12 +3101,24 @@ msgstr "" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" @@ -2898,6 +3224,9 @@ msgstr "" msgid "Source" msgstr "" +msgid "Source (branch or tag)" +msgstr "" + msgid "Source code" msgstr "" @@ -2937,8 +3266,8 @@ msgstr "" msgid "System Hooks" msgstr "" -msgid "Tag" -msgid_plural "Tags" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" msgstr[0] "" msgstr[1] "" @@ -3071,9 +3400,15 @@ msgstr "" msgid "The repository for this project does not exist." msgstr "" +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "" +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "" @@ -3167,6 +3502,9 @@ msgstr "" msgid "This merge request is locked." msgstr "" +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3336,9 +3674,15 @@ msgstr[1] "" msgid "Time|s" msgstr "s" +msgid "Tip:" +msgstr "" + msgid "Title" msgstr "" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + msgid "Todo" msgstr "" @@ -3354,10 +3698,10 @@ msgstr "" msgid "Total Time" msgstr "" -msgid "Total issue time spent" +msgid "Total test time for all commits/merges" msgstr "" -msgid "Total test time for all commits/merges" +msgid "Total: %{total}" msgstr "" msgid "Track activity with Contribution Analytics." @@ -3366,9 +3710,6 @@ msgstr "" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" -msgid "Total: %{total}" -msgstr "" - msgid "Track time with quick actions" msgstr "" @@ -3378,9 +3719,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3396,6 +3734,9 @@ msgstr "" msgid "Unlocked" msgstr "" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "" @@ -3441,6 +3782,9 @@ msgstr "" msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" +msgid "View epics list" +msgstr "" + msgid "View file @ " msgstr "" @@ -3477,6 +3821,9 @@ msgstr "" msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "" +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "" @@ -3597,6 +3944,9 @@ msgstr "" msgid "Withdraw Access Request" msgstr "" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "" @@ -3609,10 +3959,13 @@ msgstr "" msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3630,12 +3983,24 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "" +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "" +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "" @@ -3669,6 +4034,9 @@ msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -3696,7 +4064,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3708,7 +4076,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3723,6 +4091,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3735,6 +4112,12 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + msgid "commit" msgstr "" @@ -3752,11 +4135,41 @@ msgstr[1] "" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" msgstr[1] "" +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3790,6 +4203,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3823,6 +4239,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3877,6 +4296,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3924,3 +4346,6 @@ msgstr "" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/pl_PL/gitlab.po b/locale/pl_PL/gitlab.po index 9c5455eac67..8e414d0d07b 100644 --- a/locale/pl_PL/gitlab.po +++ b/locale/pl_PL/gitlab.po @@ -2,15 +2,15 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 04:01-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:21-0500\n" "Last-Translator: gitlab \n" "Language-Team: Polish\n" "Language: pl_PL\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=((n == 1) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || n%10 == 1 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 12 && n%100 <= 14)) ? 2 : 3));\n" "X-Generator: crowdin.com\n" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: pl\n" @@ -24,36 +24,45 @@ msgid_plural "%d commits" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "%d commit behind" msgid_plural "%d commits behind" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "%d issue" msgid_plural "%d issues" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "%d layer" msgid_plural "%d layers" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "%d merge request" msgid_plural "%d merge requests" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" + +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -63,6 +72,10 @@ msgid_plural "%{count} participants" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" + +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "" @@ -73,11 +86,15 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "" +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "%{text} is available" msgstr "" @@ -96,6 +113,7 @@ msgid_plural "%d pipelines" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "1st contribution!" msgstr "" @@ -139,9 +157,15 @@ msgstr "" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "" +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "" @@ -160,12 +184,45 @@ msgstr "" msgid "AdminArea|Stopping jobs failed" msgstr "" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" msgid "AdminHealthPageLink|health page" msgstr "" +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + msgid "Advanced" msgstr "" @@ -190,6 +247,12 @@ msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" @@ -199,12 +262,36 @@ msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while fetching the pipeline." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + msgid "An error occurred while loading filenames" msgstr "" +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + msgid "An error occurred while rendering KaTeX" msgstr "" @@ -217,6 +304,12 @@ msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + msgid "An error occurred while validating username" msgstr "" @@ -241,15 +334,15 @@ msgstr "" msgid "Are you sure you want to delete this pipeline schedule?" msgstr "" -msgid "Are you sure you want to discard your changes?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "" msgid "Are you sure you want to reset the health check token?" msgstr "" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "" @@ -289,6 +382,9 @@ msgstr "" msgid "Authors: %{authors}" msgstr "" +msgid "Auto DevOps enabled" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -313,7 +409,13 @@ msgstr "" msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" msgstr "" msgid "Available" @@ -325,6 +427,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Billing" msgstr "" @@ -379,14 +484,12 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "" @@ -679,6 +782,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + msgid "Click to expand text" msgstr "" @@ -733,6 +839,9 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -781,6 +890,9 @@ msgstr "" msgid "ClusterIntegration|Ingress" msgstr "" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "" @@ -832,9 +944,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" msgstr "" @@ -970,6 +1079,12 @@ msgstr "" msgid "Collapse" msgstr "" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "" @@ -978,6 +1093,14 @@ msgid_plural "Commits" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" + +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" msgid "Commit Message" msgstr "" @@ -991,6 +1114,9 @@ msgstr "" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "" @@ -1132,6 +1258,9 @@ msgstr "" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "" @@ -1144,9 +1273,18 @@ msgstr "" msgid "Create New Directory" msgstr "" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "" +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "" @@ -1165,6 +1303,9 @@ msgstr "" msgid "Create merge request" msgstr "" +msgid "Create merge request and branch" +msgstr "" + msgid "Create new branch" msgstr "" @@ -1189,6 +1330,12 @@ msgstr "" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + msgid "Creating epic" msgstr "" @@ -1243,6 +1390,9 @@ msgstr "" msgid "December" msgstr "" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "" @@ -1254,6 +1404,7 @@ msgid_plural "Deploys" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "Deploy Keys" msgstr "" @@ -1276,7 +1427,7 @@ msgstr "" msgid "Disable" msgstr "" -msgid "Discard changes" +msgid "Discard draft" msgstr "" msgid "Discover GitLab Geo." @@ -1336,6 +1487,9 @@ msgstr "" msgid "Enable" msgstr "" +msgid "Enable Auto DevOps" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1390,9 +1544,18 @@ msgstr "" msgid "Epics" msgstr "" +msgid "Epics Roadmap" +msgstr "" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "" @@ -1459,12 +1622,36 @@ msgstr "" msgid "Explore public groups" msgstr "" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + msgid "Failed to change the owner" msgstr "" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "" +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "" @@ -1480,6 +1667,9 @@ msgstr "" msgid "Files" msgstr "" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "" @@ -1500,6 +1690,7 @@ msgid_plural "Forks" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "ForkedFromProjectPath|Forked from" msgstr "" @@ -1516,6 +1707,9 @@ msgstr "" msgid "From merge request merge until deploy to production" msgstr "" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "" @@ -1663,6 +1857,24 @@ msgstr "" msgid "Got it!" msgstr "" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1755,6 +1967,7 @@ msgid_plural "Hide values" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "History" msgstr "" @@ -1762,6 +1975,9 @@ msgstr "" msgid "Housekeeping successfully started" msgstr "" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "" @@ -1774,6 +1990,9 @@ msgstr "" msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "" +msgid "Install Runner on Kubernetes" +msgstr "" + msgid "Install a Runner compatible with GitLab CI" msgstr "" @@ -1782,6 +2001,7 @@ msgid_plural "Instances" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "Instance does not support multiple Kubernetes clusters" msgstr "" @@ -1825,6 +2045,9 @@ msgstr "" msgid "January" msgstr "" +msgid "Jobs" +msgstr "" + msgid "Jul" msgstr "" @@ -1855,6 +2078,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1875,6 +2101,7 @@ msgid_plural "Last %d days" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "Last Pipeline" msgstr "" @@ -1903,6 +2130,12 @@ msgstr "" msgid "Learn more" msgstr "" +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + msgid "Learn more in the" msgstr "" @@ -1921,6 +2154,9 @@ msgstr "" msgid "License" msgstr "" +msgid "List" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -1930,6 +2166,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" @@ -1939,6 +2178,9 @@ msgstr "" msgid "Locked Files" msgstr "" +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + msgid "Login" msgstr "" @@ -1969,9 +2211,6 @@ msgstr "" msgid "Members" msgstr "" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "" @@ -1984,6 +2223,9 @@ msgstr "" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" @@ -2008,9 +2250,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "" @@ -2031,6 +2282,7 @@ msgid_plural "New Issues" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "New Kubernetes Cluster" msgstr "" @@ -2107,9 +2359,6 @@ msgstr "" msgid "No schedules" msgstr "" -msgid "No time spent" -msgstr "" - msgid "None" msgstr "" @@ -2125,6 +2374,9 @@ msgstr "" msgid "Not enough data" msgstr "" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "" @@ -2227,6 +2479,9 @@ msgstr "" msgid "Options" msgstr "" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + msgid "Overview" msgstr "" @@ -2332,6 +2587,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "" @@ -2365,6 +2638,9 @@ msgstr "" msgid "Private - The group and its projects can only be viewed by members." msgstr "" +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "" @@ -2527,12 +2803,30 @@ msgstr "" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "" +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "" msgid "PrometheusService|Finding and configuring metrics..." msgstr "" +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + msgid "PrometheusService|Metrics" msgstr "" @@ -2554,9 +2848,18 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + msgid "PrometheusService|Time-series monitoring service" msgstr "" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + msgid "PrometheusService|View environments" msgstr "" @@ -2575,6 +2878,12 @@ msgstr "" msgid "Push events" msgstr "" +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + msgid "PushRule|Committer restriction" msgstr "" @@ -2638,6 +2947,9 @@ msgstr "" msgid "Repository" msgstr "" +msgid "Repository has no locks." +msgstr "" + msgid "Request Access" msgstr "" @@ -2650,11 +2962,15 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "Revert this commit" msgstr "" @@ -2662,6 +2978,9 @@ msgstr "" msgid "Revert this merge request" msgstr "" +msgid "Roadmap" +msgstr "" + msgid "SSH Keys" msgstr "" @@ -2707,12 +3026,18 @@ msgstr "" msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "" msgid "Select a timezone" msgstr "" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2725,6 +3050,9 @@ msgstr "" msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "" @@ -2737,6 +3065,9 @@ msgstr "" msgid "Service Templates" msgstr "" +msgid "Service URL" +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "" @@ -2746,15 +3077,15 @@ msgstr "" msgid "Set up Koding" msgstr "" -msgid "Set up auto deploy" -msgstr "" - msgid "SetPasswordToCloneLink|set a password" msgstr "" msgid "Settings" msgstr "" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" @@ -2764,6 +3095,9 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2775,6 +3109,7 @@ msgid_plural "Showing %d events" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "Sidebar|Change weight" msgstr "" @@ -2806,12 +3141,24 @@ msgstr "" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" @@ -2917,6 +3264,9 @@ msgstr "" msgid "Source" msgstr "" +msgid "Source (branch or tag)" +msgstr "" + msgid "Source code" msgstr "" @@ -2956,11 +3306,12 @@ msgstr "" msgid "System Hooks" msgstr "" -msgid "Tag" -msgid_plural "Tags" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "Tags" msgstr "" @@ -3091,9 +3442,15 @@ msgstr "" msgid "The repository for this project does not exist." msgstr "" +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "" +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "" @@ -3187,6 +3544,9 @@ msgstr "" msgid "This merge request is locked." msgstr "" +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3348,19 +3708,27 @@ msgid_plural "Time|hrs" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "Time|min" msgid_plural "Time|mins" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "Time|s" msgstr "" +msgid "Tip:" +msgstr "" + msgid "Title" msgstr "" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + msgid "Todo" msgstr "" @@ -3376,10 +3744,10 @@ msgstr "" msgid "Total Time" msgstr "" -msgid "Total issue time spent" +msgid "Total test time for all commits/merges" msgstr "" -msgid "Total test time for all commits/merges" +msgid "Total: %{total}" msgstr "" msgid "Track activity with Contribution Analytics." @@ -3388,9 +3756,6 @@ msgstr "" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" -msgid "Total: %{total}" -msgstr "" - msgid "Track time with quick actions" msgstr "" @@ -3400,9 +3765,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3418,6 +3780,9 @@ msgstr "" msgid "Unlocked" msgstr "" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "" @@ -3463,6 +3828,9 @@ msgstr "" msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" +msgid "View epics list" +msgstr "" + msgid "View file @ " msgstr "" @@ -3499,6 +3867,9 @@ msgstr "" msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "" +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "" @@ -3619,6 +3990,9 @@ msgstr "" msgid "Withdraw Access Request" msgstr "" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "" @@ -3631,10 +4005,13 @@ msgstr "" msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3652,12 +4029,24 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "" +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "" +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "" @@ -3691,6 +4080,9 @@ msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -3718,7 +4110,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3730,7 +4122,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3745,6 +4137,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3757,6 +4158,12 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + msgid "commit" msgstr "" @@ -3771,15 +4178,47 @@ msgid_plural "days" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" + +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3814,6 +4253,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3847,6 +4289,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3901,6 +4346,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3924,6 +4372,7 @@ msgid_plural "parents" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "password" msgstr "" @@ -3949,3 +4398,6 @@ msgstr "" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po index 5aef8f45234..277552a8d02 100644 --- a/locale/pt_BR/gitlab.po +++ b/locale/pt_BR/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 04:01-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:20-0500\n" "Last-Translator: gitlab \n" "Language-Team: Portuguese, Brazilian\n" "Language: pt_BR\n" @@ -49,6 +49,9 @@ msgid_plural "%s additional commits have been omitted to prevent performance iss msgstr[0] "%s commit adicional foi omitido para prevenir problemas de performance." msgstr[1] "%s commits adicionais foram omitidos para prevenir problemas de performance." +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -57,6 +60,9 @@ msgid_plural "%{count} participants" msgstr[0] "%{count} participante" msgstr[1] "%{count} participantes" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "%{number_commits_behind} commits atrás de %{default_branch}, %{number_commits_ahead} commits à frente" @@ -66,6 +72,9 @@ msgstr "%{number_of_failures} de %{maximum_failures} falhas. O GitLab permitirá msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} de %{maximum_failures} falhas. O GitLab não tentará mais automaticamente. Redefina as informações de storage quando o problema for resolvido." +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "%{storage_name}: falha na tentativa de acesso ao storage no host:" @@ -130,9 +139,15 @@ msgstr "Adicionar Guia de contribuição" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "Adicione o Webhooks de Grupos e GitLab Enterprise Edition." +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "Adicionar Licença" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "Adicionar novo diretório" @@ -151,12 +166,45 @@ msgstr "" msgid "AdminArea|Stopping jobs failed" msgstr "" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" msgid "AdminHealthPageLink|health page" msgstr "página de saúde" +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + msgid "Advanced" msgstr "" @@ -181,6 +229,12 @@ msgstr "Erro ao modificar notificação de assinatura" msgid "An error occurred when updating the issue weight" msgstr "Um erro aconteceu ao atualizar o peso da issue" +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" @@ -190,12 +244,36 @@ msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "Erro ao recuperar informações da barra lateral" +msgid "An error occurred while fetching the pipeline." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + msgid "An error occurred while loading filenames" msgstr "" +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + msgid "An error occurred while rendering KaTeX" msgstr "" @@ -208,6 +286,12 @@ msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + msgid "An error occurred while validating username" msgstr "" @@ -232,15 +316,15 @@ msgstr "Projeto arquivado! O repositório é somente leitura" msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Tem certeza que deseja excluir este agendamento de pipeline?" -msgid "Are you sure you want to discard your changes?" -msgstr "Você tem certeza que deseja descartar suas alterações?" - msgid "Are you sure you want to reset registration token?" msgstr "Você tem certeza que quer recriar o token de registro?" msgid "Are you sure you want to reset the health check token?" msgstr "Você tem certeza que quer reiniciar o token de status de saúde?" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "Você tem certeza?" @@ -280,6 +364,9 @@ msgstr "Autor" msgid "Authors: %{authors}" msgstr "" +msgid "Auto DevOps enabled" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -304,8 +391,14 @@ msgstr "Ele gerará a build, testará e fará deploy de sua aplicação automati msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "Saiba mais em %{link_to_documentation}" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." -msgstr "Você pode ativar %{link_to_settings} para esse projeto." +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" +msgstr "" msgid "Available" msgstr "Disponível" @@ -316,6 +409,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Billing" msgstr "Cobrança" @@ -370,13 +466,10 @@ msgstr "pago %{price_per_year} anualmente" msgid "BillingPlans|per user" msgstr "por usuário" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "Branch" -msgstr[1] "Branches" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "" +msgstr[1] "" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "O branch %{branch_name} foi criado. Para configurar o deploy automático, selecione um modelo de Yaml do GitLab CI e commit suas mudanças. %{link_to_autodeploy_doc}" @@ -669,6 +762,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "interruptor da api" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + msgid "Click to expand text" msgstr "" @@ -723,6 +819,9 @@ msgstr "Copiar URL da API" msgid "ClusterIntegration|Copy CA Certificate" msgstr "Copiar certificado CA" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -771,6 +870,9 @@ msgstr "Helm Tiller" msgid "ClusterIntegration|Ingress" msgstr "Ingressar" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "Instalar" @@ -822,9 +924,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "Leia mais sobre %{link_to_documentation}" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" msgstr "" @@ -960,6 +1059,12 @@ msgstr "configurado corretamente" msgid "Collapse" msgstr "" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "Comentários" @@ -968,6 +1073,11 @@ msgid_plural "Commits" msgstr[0] "Commit" msgstr[1] "Commits" +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" +msgstr[1] "" + msgid "Commit Message" msgstr "Mensagem de Commit" @@ -980,6 +1090,9 @@ msgstr "Mensagem de commit" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Commit" @@ -1121,6 +1234,9 @@ msgstr "Copiar URL para área de transferência" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Copiar SHA do commit para a área de transferência" @@ -1133,9 +1249,18 @@ msgstr "" msgid "Create New Directory" msgstr "Criar Novo Diretório" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "Crie um token de acesso pessoal na sua conta para dar pull ou push via %{protocol}." +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "Criar diretório" @@ -1154,6 +1279,9 @@ msgstr "" msgid "Create merge request" msgstr "Criar merge request" +msgid "Create merge request and branch" +msgstr "" + msgid "Create new branch" msgstr "Criar novo branch" @@ -1178,6 +1306,12 @@ msgstr "Tag" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "criar um token de acesso pessoal" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + msgid "Creating epic" msgstr "Criando épico" @@ -1232,6 +1366,9 @@ msgstr "Dez" msgid "December" msgstr "Dezembro" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "Defina um padrão personalizado utilizando a sintaxe do cron" @@ -1264,8 +1401,8 @@ msgstr "Nome do diretório" msgid "Disable" msgstr "" -msgid "Discard changes" -msgstr "Descartar alterações" +msgid "Discard draft" +msgstr "" msgid "Discover GitLab Geo." msgstr "" @@ -1324,6 +1461,9 @@ msgstr "Emails" msgid "Enable" msgstr "" +msgid "Enable Auto DevOps" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "Um erro ocorreu ao recuperar ambientes." @@ -1378,9 +1518,18 @@ msgstr "Épico será removido! Tem certeza?" msgid "Epics" msgstr "Épicos" +msgid "Epics Roadmap" +msgstr "" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "Epics permite que você gerencie seu portfólio de projetos de forma mais eficiente e com menos esforço" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "Erro ao criar épico" @@ -1447,12 +1596,36 @@ msgstr "Explorar projetos" msgid "Explore public groups" msgstr "Explorar grupos públicos" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + msgid "Failed to change the owner" msgstr "Erro ao alterar o proprietário" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "Erro ao excluir o agendamento do pipeline" +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "Fev" @@ -1468,6 +1641,9 @@ msgstr "Nome do arquivo" msgid "Files" msgstr "Arquivos" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "Filtrar por mensagem de commit" @@ -1503,6 +1679,9 @@ msgstr "Da abertura de tarefas até a implantação para a produção" msgid "From merge request merge until deploy to production" msgstr "Do merge request até a implantação em produção" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "Chaves GPG" @@ -1650,6 +1829,24 @@ msgstr "Autenticação do Google não está %{link_to_documentation}. Peça ao a msgid "Got it!" msgstr "" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "Bloquear compartilhamento de projetos do grupo %{group} com outros grupos" @@ -1748,6 +1945,9 @@ msgstr "Histórico" msgid "Housekeeping successfully started" msgstr "Manutenção iniciada com sucesso" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "Importar repositório" @@ -1760,6 +1960,9 @@ msgstr "Melhore a gerência de issues com pesos no GitLab Enterprise Edition." msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "Encontre o que precisa mais facilmente com a pesquisa global avançada com GitLab Enterprise Edition." +msgid "Install Runner on Kubernetes" +msgstr "" + msgid "Install a Runner compatible with GitLab CI" msgstr "Instalar um Runner compatível com o GitLab CI" @@ -1810,6 +2013,9 @@ msgstr "Jan" msgid "January" msgstr "Janeiro" +msgid "Jobs" +msgstr "" + msgid "Jul" msgstr "Jul" @@ -1840,6 +2046,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1887,6 +2096,12 @@ msgstr "em" msgid "Learn more" msgstr "" +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + msgid "Learn more in the" msgstr "Saiba mais em" @@ -1905,6 +2120,9 @@ msgstr "Sair do projeto" msgid "License" msgstr "Licença" +msgid "List" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -1914,6 +2132,9 @@ msgstr "Bloquear" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" @@ -1923,6 +2144,9 @@ msgstr "Bloqueado" msgid "Locked Files" msgstr "Arquivos bloqueados" +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + msgid "Login" msgstr "Entrar" @@ -1953,9 +2177,6 @@ msgstr "Mediana" msgid "Members" msgstr "Membros" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "Merge Requests" @@ -1968,6 +2189,9 @@ msgstr "Merge requests" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" @@ -1992,9 +2216,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "adicione uma chave SSH" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "Monitoramento" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "Mais informações estão disponíveis|aqui" @@ -2090,9 +2323,6 @@ msgstr "Nenhum repositório" msgid "No schedules" msgstr "Nenhum agendamento" -msgid "No time spent" -msgstr "Nenhum tempo gasto" - msgid "None" msgstr "Nenhum" @@ -2108,6 +2338,9 @@ msgstr "" msgid "Not enough data" msgstr "Dados insuficientes" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "Eventos de notificação" @@ -2210,6 +2443,9 @@ msgstr "Abrir em nova janela" msgid "Options" msgstr "Opções" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + msgid "Overview" msgstr "Visão geral" @@ -2315,6 +2551,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "todos" @@ -2348,6 +2602,9 @@ msgstr "Privado - O acesso ao projeto deve ser concedido explicitamente para cad msgid "Private - The group and its projects can only be viewed by members." msgstr "Privado - O grupo e seus projetos só podem ser vistos por seus membros." +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "Perfil" @@ -2510,12 +2767,30 @@ msgstr "Desculpe, nenhum projeto corresponde a sua pesquisa" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "Esta funcionalidade necessita de suporte à localStorage do navegador" +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "Por padrão, Prometheus escuta em 'http://localhost:9090'. Não é recomendado mudar o endereço padrão e sua porta, porque pode conflitar com outros serviços que estão executando no sevidor do Gitlab." msgid "PrometheusService|Finding and configuring metrics..." msgstr "Encontrando e configurando métricas..." +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + msgid "PrometheusService|Metrics" msgstr "Métricas" @@ -2537,9 +2812,18 @@ msgstr "Nenhuma métrica está sendo monitorada. Para inicar o monitoramento, fa msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "URL da API base do Prometheus. como http://prometheus.example.com/" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + msgid "PrometheusService|Time-series monitoring service" msgstr "" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + msgid "PrometheusService|View environments" msgstr "Ver ambientes" @@ -2558,6 +2842,12 @@ msgstr "Regras de push" msgid "Push events" msgstr "Eventos de push" +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + msgid "PushRule|Committer restriction" msgstr "Restrição de commit" @@ -2621,6 +2911,9 @@ msgstr "" msgid "Repository" msgstr "Repositório" +msgid "Repository has no locks." +msgstr "" + msgid "Request Access" msgstr "Solicitar acesso" @@ -2633,6 +2926,9 @@ msgstr "Recriar o token de status de saúde" msgid "Reset runners registration token" msgstr "Recriar o token de registro de runners" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" @@ -2644,6 +2940,9 @@ msgstr "Reverter este commit" msgid "Revert this merge request" msgstr "Reverter esse merge request" +msgid "Roadmap" +msgstr "" + msgid "SSH Keys" msgstr "Chaves SSH" @@ -2689,12 +2988,18 @@ msgstr "Segundo de espera para tentativa de acesso ao storage" msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "Selecionar Formato do Arquivo" msgid "Select a timezone" msgstr "Selecionar fuso horário" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2707,6 +3012,9 @@ msgstr "Selecionar branch de destino" msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "Set" @@ -2719,6 +3027,9 @@ msgstr "" msgid "Service Templates" msgstr "Modelos de serviço" +msgid "Service URL" +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Defina uma senha para sua conta para aceitar ou entregar código via %{protocol}." @@ -2728,15 +3039,15 @@ msgstr "" msgid "Set up Koding" msgstr "Configurar Koding" -msgid "Set up auto deploy" -msgstr "Configurar implantação automática" - msgid "SetPasswordToCloneLink|set a password" msgstr "defina uma senha" msgid "Settings" msgstr "Configurações" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" @@ -2746,6 +3057,9 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "" + msgid "Show parent pages" msgstr "Mostrar páginas acima" @@ -2787,12 +3101,24 @@ msgstr "Algo deu errado ao tentar mudar o estado de ${this.issuableDisplayName}" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "Algo deu errado ao recuperar os projetos." msgid "Something went wrong while fetching the registry list." msgstr "Algo deu errado ao recuperar a lista de registro." +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" @@ -2898,6 +3224,9 @@ msgstr "Peso" msgid "Source" msgstr "Origem" +msgid "Source (branch or tag)" +msgstr "" + msgid "Source code" msgstr "Código-fonte" @@ -2937,10 +3266,10 @@ msgstr "Trocar branch/tag" msgid "System Hooks" msgstr "Hooks do sistema" -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "Tag" -msgstr[1] "Tags" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "" +msgstr[1] "" msgid "Tags" msgstr "Tags" @@ -3071,9 +3400,15 @@ msgstr "O projeto pode ser acessado sem a necessidade de autenticação." msgid "The repository for this project does not exist." msgstr "Não existe repositório para este projeto." +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "A etapa de revisão mostra o tempo de criação de uma solicitação de incorporação até sua aceitação. Os dados serão automaticamente adicionados depois que sua primeira solicitação de incorporação for aceita." +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "A etapa de homologação mostra o tempo entre o aceite da solicitação de incorporação e a implantação do código no ambiente de produção. Os dados serão automaticamente adicionados depois que você implantar em produção pela primeira vez." @@ -3167,6 +3502,9 @@ msgstr "Isto significa que você não pode entregar código até que crie um rep msgid "This merge request is locked." msgstr "Esse merge request está bloqueado." +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3336,9 +3674,15 @@ msgstr[1] "mins" msgid "Time|s" msgstr "s" +msgid "Tip:" +msgstr "" + msgid "Title" msgstr "Título" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + msgid "Todo" msgstr "" @@ -3354,21 +3698,18 @@ msgstr "" msgid "Total Time" msgstr "Tempo Total" -msgid "Total issue time spent" -msgstr "Tempo total gasto" - msgid "Total test time for all commits/merges" msgstr "Tempo de teste total para todos os commits/merges" +msgid "Total: %{total}" +msgstr "" + msgid "Track activity with Contribution Analytics." msgstr "Acompanhe a atividade com o Contribution Analytics." msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "Acompanhe grupos de questões que compartilhem um tema, em projetos e milestones" -msgid "Total: %{total}" -msgstr "" - msgid "Track time with quick actions" msgstr "" @@ -3378,9 +3719,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "Ativar Service Desk" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3396,6 +3734,9 @@ msgstr "" msgid "Unlocked" msgstr "Desbloqueado" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "Desmarcar" @@ -3441,6 +3782,9 @@ msgstr "Utilizar configuração de notificação global" msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" +msgid "View epics list" +msgstr "" + msgid "View file @ " msgstr "Ver arquivo @ " @@ -3477,6 +3821,9 @@ msgstr "Esta etapa não possui dados suficientes para exibição." msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "Queremos ter certeza de que é você, confirme que você não é um robô." +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "Webhooks permitem que você acione uma URL se, por exemplo, quando um novo código for feito push ou uma nova issue criada. Você pode configurar os webhooks para escutar eventos específicos como push, issue ou merge request. Webhooks de grupo aplicarão para todos os projetos no grupo, permitindo você padronizar o funcionamento em todo o grupo." @@ -3597,6 +3944,9 @@ msgstr "Com a análise de contribuição, você pode ter uma visão geral da ati msgid "Withdraw Access Request" msgstr "Remover Requisição de Acesso" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "Você vai remover %{group_name}. Grupos removidos NÃO PODEM ser restaurados! Você está ABSOLUTAMENTE certo?" @@ -3609,10 +3959,13 @@ msgstr "Você está prestes a remover a relação de fork do projeto original %{ msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Você irá transferir %{project_name_with_namespace} para outro proprietário. Tem certeza ABSOLUTA?" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3630,12 +3983,24 @@ msgstr "Você não pode escrever numa instância secundária de somente leitura msgid "You cannot write to this read-only GitLab instance." msgstr "Você não pode escrever nesta instância somente-leitura do GitLab." +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "Você atingiu o limite de seu projeto" +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "Você deve estar autenticado para marcar um projeto" +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "Você precisa de permissão." @@ -3669,6 +4034,9 @@ msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "Seu comentário não estará visível ao público." @@ -3696,7 +4064,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3708,7 +4076,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3723,6 +4091,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3735,6 +4112,12 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + msgid "commit" msgstr "commit" @@ -3752,11 +4135,41 @@ msgstr[1] "dias" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" msgstr[1] "" +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3790,6 +4203,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3823,6 +4239,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3877,6 +4296,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3924,3 +4346,6 @@ msgstr "nome do usuário" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po index 0adb8e5b716..6214460fc21 100644 --- a/locale/ru/gitlab.po +++ b/locale/ru/gitlab.po @@ -2,15 +2,15 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 04:01-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:53-0500\n" "Last-Translator: gitlab \n" "Language-Team: Russian\n" "Language: ru_RU\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" "X-Generator: crowdin.com\n" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: ru\n" @@ -24,36 +24,45 @@ msgid_plural "%d commits" msgstr[0] "%d коммит" msgstr[1] "%d коммита" msgstr[2] "%d коммитов" +msgstr[3] "%d коммитов" msgid "%d commit behind" msgid_plural "%d commits behind" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "на %d коммит позади" +msgstr[1] "на %d коммита позади" +msgstr[2] "на %d коммитов позади" +msgstr[3] "на %d коммитов позади" msgid "%d issue" msgid_plural "%d issues" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d обсуждение" +msgstr[1] "%d обсуждений" +msgstr[2] "%d обсуждений" +msgstr[3] "%d обсуждений" msgid "%d layer" msgid_plural "%d layers" msgstr[0] "%d слой" msgstr[1] "%d слоя" -msgstr[2] "%d слоёв" +msgstr[2] "%d слоев" +msgstr[3] "%d слоёв" msgid "%d merge request" msgid_plural "%d merge requests" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d запрос на слияние" +msgstr[1] "%d запроса на слияние" +msgstr[2] "%d запросов на слияние" +msgstr[3] "%d запросов на слияние" msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." -msgstr[0] "%s добавленный коммит был исключен для предотвращения проблем с производительностью." -msgstr[1] "%s добавленных коммита были исключены для предотвращения проблем с производительностью." -msgstr[2] "%s добавленных коммитов были исключены для предотвращения проблем с производительностью." +msgstr[0] "%s дополнительный коммит был пропущен для предотвращения проблем с производительностью." +msgstr[1] "%s дополнительных коммита было пропущено для предотвращения проблем с производительностью." +msgstr[2] "%s дополнительных коммитов было пропущено для предотвращения проблем с производительностью." +msgstr[3] "%s дополнительных коммитов было пропущено для предотвращения проблем с производительностью." + +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -63,6 +72,10 @@ msgid_plural "%{count} participants" msgstr[0] "%{count} участник" msgstr[1] "%{count} участника" msgstr[2] "%{count} участников" +msgstr[3] "%{count} участников" + +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "на %{number_commits_behind} коммитов позади %{default_branch}, на %{number_commits_ahead} коммитов впереди" @@ -73,11 +86,15 @@ msgstr "%{number_of_failures} из %{maximum_failures} возможных неу msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} из %{maximum_failures} возможных неудачных попыток. GitLab не будет автоматически повторять попытку. Сбросьте информацию хранилища после устранения проблемы." +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "%{storage_name}: неудачная попытка доступа к хранилищу на хосте:" -msgstr[1] "%{storage_name}: %{failed_attempts} - неудачные попытки доступа к хранилищу:" -msgstr[2] "%{storage_name}: %{failed_attempts} - неудачные попытки доступа к хранилищу:" +msgstr[1] "%{storage_name}: %{failed_attempts} неудачные попытки доступа к хранилищу:" +msgstr[2] "%{storage_name}: %{failed_attempts} неудачных попыток доступа к хранилищу:" +msgstr[3] "%{storage_name}: %{failed_attempts} неудачных попыток доступа к хранилищу:" msgid "%{text} is available" msgstr "%{text} доступен" @@ -96,6 +113,7 @@ msgid_plural "%d pipelines" msgstr[0] "1 сборочная линия" msgstr[1] "%d сборочных линии" msgstr[2] "%d сборочных линий" +msgstr[3] "%d сборочных линий" msgid "1st contribution!" msgstr "Первый вклад!" @@ -139,9 +157,15 @@ msgstr "Добавить Руководство участника" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "Добавить групповые веб-обработчики и GitLab Enterprise Edition." +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "Добавить Лицензию" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "Добавить новый каталог" @@ -149,23 +173,56 @@ msgid "Add todo" msgstr "" msgid "AdminArea|Stop all jobs" -msgstr "" +msgstr "Остановить все задания" msgid "AdminArea|Stop all jobs?" -msgstr "" +msgstr "Остановить все задания?" msgid "AdminArea|Stop jobs" -msgstr "" +msgstr "Остановить задания" msgid "AdminArea|Stopping jobs failed" -msgstr "" +msgstr "Остановка заданий не удалась" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." -msgstr "" +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." +msgstr "Вы собираетесь остановить все задания. Это действие остановит все текущие задания, находящиеся в процессе выполнения." msgid "AdminHealthPageLink|health page" msgstr "страница работоспособности" +msgid "AdminProjects|Delete" +msgstr "Удалить" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "Удалить Проект %{projectName}?" + +msgid "AdminProjects|Delete project" +msgstr "Удалить проект" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "Заблокировать пользователя" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "Удалить Пользователя %{username} и внесённые им изменения ?" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "Удалить пользователя %{username} ?" + +msgid "AdminUsers|Delete user" +msgstr "Удалить пользователя" + +msgid "AdminUsers|Delete user and contributions" +msgstr "Удалить пользователя и внесённые им изменения" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "Для подтверждения, введите %{projectName}" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "Для подтверждения, введите %{username}" + msgid "Advanced" msgstr "" @@ -182,7 +239,7 @@ msgid "Allows you to add and manage Kubernetes clusters." msgstr "" msgid "An error occurred previewing the blob" -msgstr "" +msgstr "Произошла ошибка при предварительном просмотре объекта" msgid "An error occurred when toggling the notification subscription" msgstr "Произошла ошибка при переключении подписки на оповещения" @@ -190,35 +247,71 @@ msgstr "Произошла ошибка при переключении подп msgid "An error occurred when updating the issue weight" msgstr "Произошла ошибка при обновлении веса обсуждения" +msgid "An error occurred while adding approver" +msgstr "Произошла ошибка при добавлении утверждающего" + +msgid "An error occurred while detecting host keys" +msgstr "Произошла ошибка при обнаружении ключей хоста" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." -msgstr "" +msgstr "Произошла ошибка при отключении уведомления о функции. Обновите страницу и повторите попытку." msgid "An error occurred while fetching markdown preview" -msgstr "" +msgstr "Произошла ошибка при предварительном просмотре markdown" msgid "An error occurred while fetching sidebar data" msgstr "Произошла ошибка при получении денег данных для боковой панели" +msgid "An error occurred while fetching the pipeline." +msgstr "Произошла ошибка при получении сборочной линии." + msgid "An error occurred while getting projects" -msgstr "" +msgstr "Произошла ошибка при получении списка проектов" + +msgid "An error occurred while importing project" +msgstr "Произошла ошибка при импорте проекта" + +msgid "An error occurred while initializing path locks" +msgstr "Произошла ошибка при инициализации блокировок пути" + +msgid "An error occurred while loading commits" +msgstr "Произошла ошибка при загрузке коммитов" + +msgid "An error occurred while loading diff" +msgstr "Произошла ошибка при загрузке различий" msgid "An error occurred while loading filenames" -msgstr "" +msgstr "Произошла ошибка при загрузке списка файлов" + +msgid "An error occurred while loading the file" +msgstr "Произошла ошибка при загрузке файла" + +msgid "An error occurred while making the request." +msgstr "Произошла ошибка при выполнении запроса." + +msgid "An error occurred while removing approver" +msgstr "Произошла ошибка при удалении утверждающего" msgid "An error occurred while rendering KaTeX" -msgstr "" +msgstr "Произошла ошибка при рендеринге KaTeX" msgid "An error occurred while rendering preview broadcast message" -msgstr "" +msgstr "Произошла ошибка при визуализации просмотра широковещательного сообщения" msgid "An error occurred while retrieving calendar activity" -msgstr "" +msgstr "Произошла ошибка при получении календаря активности" msgid "An error occurred while retrieving diff" -msgstr "" +msgstr "Произошла ошибка при получении различий" + +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "Произошла ошибка при сохранении статуса настроенного LDAP. Пожалуйста, попробуйте еще раз." + +msgid "An error occurred while saving assignees" +msgstr "Произошла ошибка при сохранении исполнителя" msgid "An error occurred while validating username" -msgstr "" +msgstr "Произошла ошибка при проверке имени пользователя" msgid "An error occurred. Please try again." msgstr "Произошла ошибка. Пожалуйста, попробуйте снова." @@ -241,15 +334,15 @@ msgstr "Архивный проект! Репозиторий доступен msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Вы действительно хотите удалить это расписание сборочной линии?" -msgid "Are you sure you want to discard your changes?" -msgstr "Вы уверены, что хотите отменить ваши изменения?" - msgid "Are you sure you want to reset registration token?" msgstr "Вы уверены, что хотите сбросить этот регистрационный токен?" msgid "Are you sure you want to reset the health check token?" msgstr "Вы уверены, что хотите сбросить этот токен проверки работоспособности?" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "Вы уверены?" @@ -289,6 +382,9 @@ msgstr "Автор" msgid "Authors: %{authors}" msgstr "" +msgid "Auto DevOps enabled" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -313,8 +409,14 @@ msgstr "Для этого проекта может быть активиров msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "Подробнее по ссылке %{link_to_documentation}" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." -msgstr "Вы можете активировать %{link_to_settings} для этого проекта." +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" +msgstr "" msgid "Available" msgstr "Доступен" @@ -325,6 +427,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Billing" msgstr "Тариф" @@ -379,14 +484,12 @@ msgstr "оплачивается ежегодно в размере %{price_per_ msgid "BillingPlans|per user" msgstr "за пользователя" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "Ветка" -msgstr[1] "Ветки" -msgstr[2] "Ветки" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "Ветка (%{branch_count})" +msgstr[1] "Ветки (%{branch_count})" +msgstr[2] "Ветки (%{branch_count})" +msgstr[3] "Ветки (%{branch_count})" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "Ветка %{branch_name} создана. Для настройки автоматического развертывания выберите YAML-шаблон для GitLab CI и зафиксируйте свои изменения. %{link_to_autodeploy_doc}" @@ -679,6 +782,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "CircuitBreaker API" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + msgid "Click to expand text" msgstr "" @@ -733,6 +839,9 @@ msgstr "Скопировать адрес API" msgid "ClusterIntegration|Copy CA Certificate" msgstr "Копировать Сертификат Удостоверяющего Центра" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -781,6 +890,9 @@ msgstr "Helm Tiller" msgid "ClusterIntegration|Ingress" msgstr "Ingress" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "Установить" @@ -832,9 +944,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "Узнайте больше на %{link_to_documentation}" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" msgstr "" @@ -970,14 +1079,28 @@ msgstr "правильно настроен" msgid "Collapse" msgstr "" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "Комментарии" msgid "Commit" msgid_plural "Commits" msgstr[0] "Коммит" -msgstr[1] "Коммиты" -msgstr[2] "Коммиты" +msgstr[1] "Коммита" +msgstr[2] "Коммитов" +msgstr[3] "Коммиты" + +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "Коммит (%{commit_count})" +msgstr[1] "Коммита (%{commit_count})" +msgstr[2] "Коммитов (%{commit_count})" +msgstr[3] "Коммитов (%{commit_count})" msgid "Commit Message" msgstr "Описание Коммита" @@ -991,6 +1114,9 @@ msgstr "Описание коммита" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "Коммит" @@ -1013,7 +1139,7 @@ msgid "Commits per weekday" msgstr "" msgid "Commits|An error occurred while fetching merge requests data." -msgstr "" +msgstr "Произошла ошибка при получении данных запроса на слияния." msgid "Commits|Commit: %{commitText}" msgstr "" @@ -1132,6 +1258,9 @@ msgstr "Копировать URL в буфер обмена" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "Копировать SHA коммита в буфер обмена" @@ -1144,9 +1273,18 @@ msgstr "" msgid "Create New Directory" msgstr "Создать Новый каталог" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "Создать личный токен на аккаунте для получения или отправки через %{protocol}." +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "Создать каталог" @@ -1165,6 +1303,9 @@ msgstr "" msgid "Create merge request" msgstr "Создать запрос на слияние" +msgid "Create merge request and branch" +msgstr "" + msgid "Create new branch" msgstr "Создать новую ветку" @@ -1189,6 +1330,12 @@ msgstr "Тег" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "создать персональный токен доступа" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + msgid "Creating epic" msgstr "Создание эпика" @@ -1243,6 +1390,9 @@ msgstr "Дек." msgid "December" msgstr "Декабрь" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "Определить настраиваемый шаблон с синтаксисом cron" @@ -1251,9 +1401,10 @@ msgstr "Удалить" msgid "Deploy" msgid_plural "Deploys" -msgstr[0] "Развернуть" -msgstr[1] "Развертывание" -msgstr[2] "Развертывание" +msgstr[0] "Развертывание" +msgstr[1] "Развертывания" +msgstr[2] "Развертываний" +msgstr[3] "Развертывания" msgid "Deploy Keys" msgstr "Ключи Развертывания" @@ -1276,8 +1427,8 @@ msgstr "Имя каталога" msgid "Disable" msgstr "" -msgid "Discard changes" -msgstr "Отменить изменения" +msgid "Discard draft" +msgstr "" msgid "Discover GitLab Geo." msgstr "" @@ -1336,6 +1487,9 @@ msgstr "Email-адреса" msgid "Enable" msgstr "" +msgid "Enable Auto DevOps" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "Произошла ошибка при получении окружений." @@ -1390,9 +1544,18 @@ msgstr "Эпик будет удален! Вы уверены?" msgid "Epics" msgstr "Эпики" +msgid "Epics Roadmap" +msgstr "" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "Эпики позволят вам управлять портфелем проектов более эффективно и с меньшими усилиями" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "Ошибка создания эпика" @@ -1459,12 +1622,36 @@ msgstr "Обзор проектов" msgid "Explore public groups" msgstr "Исследовать публичные группы" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "Невыполненные Задания" + msgid "Failed to change the owner" msgstr "Не удалось изменить владельца" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "Не удалось удалить расписание сборочной линии" +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "Фев." @@ -1480,6 +1667,9 @@ msgstr "Имя файла" msgid "Files" msgstr "Файлы" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "Фильтр по комментариями к коммитам" @@ -1497,9 +1687,10 @@ msgstr "отправлено автором" msgid "Fork" msgid_plural "Forks" -msgstr[0] "Ответвление" -msgstr[1] "Ответвления" -msgstr[2] "Ответвления" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" msgid "ForkedFromProjectPath|Forked from" msgstr "Ответвлено от" @@ -1516,6 +1707,9 @@ msgstr "От создания обсуждения до развертывани msgid "From merge request merge until deploy to production" msgstr "От запроса на слияние до развертывания в рабочей среде" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "GPG Ключи" @@ -1663,6 +1857,24 @@ msgstr "Аутентификация Google не %{link_to_documentation}. По msgid "Got it!" msgstr "" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "Запретить публикацию проектов из %{group} в других группах" @@ -1755,6 +1967,7 @@ msgid_plural "Hide values" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "History" msgstr "История" @@ -1762,6 +1975,9 @@ msgstr "История" msgid "Housekeeping successfully started" msgstr "Очистка успешно запущена" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "Импорт репозитория" @@ -1774,14 +1990,18 @@ msgstr "Улучшить управление обсуждениями возм msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "Улучшить поиск при помощи Расширенного Глобального Поиска и GitLab Enterprise Edition." +msgid "Install Runner on Kubernetes" +msgstr "" + msgid "Install a Runner compatible with GitLab CI" msgstr "Установите Gitlab Runner совместимый с Gitlab CI" msgid "Instance" msgid_plural "Instances" -msgstr[0] "Экземпляр" -msgstr[1] "Экземпляра" -msgstr[2] "Экземпляров" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" msgid "Instance does not support multiple Kubernetes clusters" msgstr "" @@ -1825,6 +2045,9 @@ msgstr "Янв." msgid "January" msgstr "Январь" +msgid "Jobs" +msgstr "Задания" + msgid "Jul" msgstr "Июл." @@ -1855,6 +2078,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1873,8 +2099,9 @@ msgstr "" msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "Последний %d день" -msgstr[1] "Последние %d дни" -msgstr[2] "Последние %d дни" +msgstr[1] "Последние %d дня" +msgstr[2] "Последние %d дней" +msgstr[3] "Последние %d дни" msgid "Last Pipeline" msgstr "Последняя Сборочная Линия" @@ -1903,6 +2130,12 @@ msgstr "в" msgid "Learn more" msgstr "" +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + msgid "Learn more in the" msgstr "Узнайте больше в" @@ -1921,6 +2154,9 @@ msgstr "Покинуть проект" msgid "License" msgstr "Лицензия" +msgid "List" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -1930,6 +2166,9 @@ msgstr "Блокировка" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" @@ -1939,6 +2178,9 @@ msgstr "Заблокировано" msgid "Locked Files" msgstr "Заблокированные Файлы" +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + msgid "Login" msgstr "Войти" @@ -1969,9 +2211,6 @@ msgstr "Среднее" msgid "Members" msgstr "Участники" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "Запросы на Слияние" @@ -1984,6 +2223,9 @@ msgstr "Запрос на слияние" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" @@ -2008,9 +2250,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "добавить ключ SSH" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "Мониторинг" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "Больше информации доступно|тут" @@ -2031,6 +2282,7 @@ msgid_plural "New Issues" msgstr[0] "Новое Обсуждение" msgstr[1] "Новых Обсуждения" msgstr[2] "Новых Обсуждений" +msgstr[3] "Новые Обсуждения" msgid "New Kubernetes Cluster" msgstr "" @@ -2107,9 +2359,6 @@ msgstr "Нет репозитория" msgid "No schedules" msgstr "Нет расписаний" -msgid "No time spent" -msgstr "Нет затраченного времени" - msgid "None" msgstr "Пусто" @@ -2125,6 +2374,9 @@ msgstr "" msgid "Not enough data" msgstr "Недостаточно данных" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "Уведомления о событиях" @@ -2227,6 +2479,9 @@ msgstr "Откроется в новом окне" msgid "Options" msgstr "Настройки" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + msgid "Overview" msgstr "Обзор" @@ -2332,6 +2587,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "все" @@ -2365,6 +2638,9 @@ msgstr "Приватный - Доступ к проекту должен пре msgid "Private - The group and its projects can only be viewed by members." msgstr "Приватная - Группу и включённые в неё проекты могут видеть только члены группы." +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "Профиль" @@ -2527,12 +2803,30 @@ msgstr "К сожалению, по вашему запросу проекты msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "Эта функциональность требует поддержки localStorage в вашем браузере" +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "По умолчанию, Prometheus запускается по адресу ‘http://localhost:9090’. Не рекомендуется изменять адрес и порт по умолчанию, так как это может привести к конфликту с другим сервисами запущенными на GitLab сервере." msgid "PrometheusService|Finding and configuring metrics..." msgstr "Определение и настройка метрик..." +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + msgid "PrometheusService|Metrics" msgstr "Метрики" @@ -2554,9 +2848,18 @@ msgstr "Ни одной метрики не отслеживается. Для msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "Базовый адрес Prometheus API, например http://prometheus.example.com/" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + msgid "PrometheusService|Time-series monitoring service" msgstr "" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + msgid "PrometheusService|View environments" msgstr "Просмотр окружений" @@ -2575,6 +2878,12 @@ msgstr "Правила Отправки" msgid "Push events" msgstr "События отправки" +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + msgid "PushRule|Committer restriction" msgstr "Ограничения для коммитера" @@ -2606,13 +2915,13 @@ msgid "Related Commits" msgstr "Связанные коммиты" msgid "Related Deployed Jobs" -msgstr "Связанные задачи выгрузки" +msgstr "Связанные Задания Развертывания" msgid "Related Issues" msgstr "Связанные Обсуждения" msgid "Related Jobs" -msgstr "Связанные задачи" +msgstr "Связанные Задания" msgid "Related Merge Requests" msgstr "Связанные Запросы на Слияние" @@ -2638,6 +2947,9 @@ msgstr "" msgid "Repository" msgstr "Репозиторий" +msgid "Repository has no locks." +msgstr "" + msgid "Request Access" msgstr "Запрос доступа" @@ -2650,11 +2962,15 @@ msgstr "Сбросить ключ доступа проверки работос msgid "Reset runners registration token" msgstr "Сбросить ключ регистрации Gitlab Runners" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" msgid "Revert this commit" msgstr "Отменить это коммит" @@ -2662,6 +2978,9 @@ msgstr "Отменить это коммит" msgid "Revert this merge request" msgstr "Отменить этот запрос на слияние" +msgid "Roadmap" +msgstr "" + msgid "SSH Keys" msgstr "SSH Ключи" @@ -2707,12 +3026,18 @@ msgstr "Секунд задержки между попытками доступ msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "Выбрать формат архива" msgid "Select a timezone" msgstr "Выбор временной зоны" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2725,6 +3050,9 @@ msgstr "Выбор целевой ветки" msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "Сент." @@ -2737,6 +3065,9 @@ msgstr "" msgid "Service Templates" msgstr "Шаблоны Служб" +msgid "Service URL" +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Установите пароль в своем аккаунте, чтобы отправлять или получать код через %{protocol}." @@ -2746,15 +3077,15 @@ msgstr "" msgid "Set up Koding" msgstr "Настройка Koding" -msgid "Set up auto deploy" -msgstr "Настройка автоматического развертывания" - msgid "SetPasswordToCloneLink|set a password" msgstr "установите пароль" msgid "Settings" msgstr "Настройки" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" @@ -2764,6 +3095,9 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "" + msgid "Show parent pages" msgstr "Показать родительские страницы" @@ -2773,8 +3107,9 @@ msgstr "Показать родительские подгруппы" msgid "Showing %d event" msgid_plural "Showing %d events" msgstr[0] "Показано %d событие" -msgstr[1] "Показано %d событий" +msgstr[1] "Показано %d события" msgstr[2] "Показано %d событий" +msgstr[3] "Показано %d событий" msgid "Sidebar|Change weight" msgstr "Изменить вес" @@ -2806,12 +3141,24 @@ msgstr "Что-то пошло не так при попытке изменен msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "Что-то пошло не так при получении проектов." msgid "Something went wrong while fetching the registry list." msgstr "Что-то пошло не так при получении списка реестров." +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" @@ -2917,6 +3264,9 @@ msgstr "Вес" msgid "Source" msgstr "Источник" +msgid "Source (branch or tag)" +msgstr "" + msgid "Source code" msgstr "Исходный код" @@ -2956,11 +3306,12 @@ msgstr "Переключить ветка/тег" msgid "System Hooks" msgstr "Системные Обработчики" -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "Тег" -msgstr[1] "Теги" -msgstr[2] "Теги" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" msgid "Tags" msgstr "Теги" @@ -3091,9 +3442,15 @@ msgstr "Доступ к проекту возможен без какой-либ msgid "The repository for this project does not exist." msgstr "Репозиторий для этого проекта не существует." +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "Этап обзора показывает время от создания запроса слияния до его выполнения. Данные будут автоматически добавлены после завершения первого запроса на слияние." +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "Этап постановки показывает время между слиянием \"MR\" и развертыванием кода в производственной среде. Данные будут автоматически добавлены после развертывания в производстве первый раз." @@ -3167,7 +3524,7 @@ msgid "This job depends on a user to trigger its process. Often they are used to msgstr "" msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" -msgstr "" +msgstr "Это задание зависит от рабочих заданий на верхнем уровне, которые должны завершиться успешно, чтобы это задание было запущено" msgid "This job has not been triggered yet" msgstr "" @@ -3187,6 +3544,9 @@ msgstr "Это означает, что вы не можете отправит msgid "This merge request is locked." msgstr "Запрос на слияние заблокирован." +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3348,19 +3708,27 @@ msgid_plural "Time|hrs" msgstr[0] "ч" msgstr[1] "ч" msgstr[2] "ч" +msgstr[3] "ч" msgid "Time|min" msgid_plural "Time|mins" msgstr[0] "мин" msgstr[1] "мин" msgstr[2] "мин" +msgstr[3] "мин" msgid "Time|s" msgstr "с" +msgid "Tip:" +msgstr "" + msgid "Title" msgstr "Заголовок" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + msgid "Todo" msgstr "" @@ -3376,21 +3744,18 @@ msgstr "" msgid "Total Time" msgstr "Общее время" -msgid "Total issue time spent" -msgstr "Общее время, затраченное на обсуждение" - msgid "Total test time for all commits/merges" msgstr "Общее время тестирования фиксаций/слияний" +msgid "Total: %{total}" +msgstr "" + msgid "Track activity with Contribution Analytics." msgstr "Отслеживать активность с помощью Аналитики Участников." msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "Следите за обсуждениями, сгруппированными по темам, сразу из нескольких проектов и этапов" -msgid "Total: %{total}" -msgstr "" - msgid "Track time with quick actions" msgstr "" @@ -3400,9 +3765,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "Включить Службу Поддержки" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3418,6 +3780,9 @@ msgstr "" msgid "Unlocked" msgstr "Разблокировано" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "Снять отметку" @@ -3463,6 +3828,9 @@ msgstr "Используются глобальный настройки уве msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" +msgid "View epics list" +msgstr "" + msgid "View file @ " msgstr "Просмотр файла @ " @@ -3499,6 +3867,9 @@ msgstr "Информация по этапу отсутствует." msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "Мы хотим быть уверены, что это вы, пожалуйста, подтвердите, что вы не робот." +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "Веб-обработчики позволяют вам вызывать адрес URL если, например, отправлен новый код или создано новое обсуждение. Вы можете настроить веб-обработчики так, чтобы они реагировали на определённые события, такие как отправки кода, обсуждения или запросы на слияние. Групповые веб-обработчики применяются ко всем проектам в группе и позволяют вам стандартизовать функциональность веб-обработчиков для всей вашей группы." @@ -3619,6 +3990,9 @@ msgstr "С аналитикой участников вы можете изуч msgid "Withdraw Access Request" msgstr "Отменить запрос доступа" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "Вы собираетесь удалить %{group_name}. Удаленные группы НЕ МОГУТ быть восстановлены! Вы АБСОЛЮТНО уверены?" @@ -3631,10 +4005,13 @@ msgstr "Вы собираетесь удалить связь ответвлен msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Вы собираетесь передать проект %{project_name_with_namespace} другому владельцу. Вы АБСОЛЮТНО уверены?" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3652,12 +4029,24 @@ msgstr "Вы не можете записывать на подчиненные msgid "You cannot write to this read-only GitLab instance." msgstr "Вы не можете записывать на этот экземпляр \"только для чтения\" кластера GitLab." +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "Вы достигли ограничения в вашем проекте" +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "Необходимо войти, чтобы оценить проект" +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "Вам нужно разрешение." @@ -3691,6 +4080,9 @@ msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "Ваш комментарий не будет виден всем." @@ -3718,7 +4110,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3730,7 +4122,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3745,6 +4137,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3757,6 +4158,12 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + msgid "commit" msgstr "коммит" @@ -3768,18 +4175,50 @@ msgstr "" msgid "day" msgid_plural "days" -msgstr[0] "день" -msgstr[1] "дней" -msgstr[2] "дней" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" msgstr[1] "" msgstr[2] "" +msgstr[3] "" + +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3814,6 +4253,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3847,6 +4289,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3901,6 +4346,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3921,9 +4369,10 @@ msgstr "" msgid "parent" msgid_plural "parents" -msgstr[0] "источник" -msgstr[1] "источники" -msgstr[2] "источники" +msgstr[0] "" +msgstr[1] "" +msgstr[2] "" +msgstr[3] "" msgid "password" msgstr "пароль" @@ -3949,3 +4398,6 @@ msgstr "имя пользователя" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/tr_TR/gitlab.po b/locale/tr_TR/gitlab.po new file mode 100644 index 00000000000..b4df4c4b1af --- /dev/null +++ b/locale/tr_TR/gitlab.po @@ -0,0 +1,4351 @@ +msgid "" +msgstr "" +"Project-Id-Version: gitlab-ee\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:20-0500\n" +"Last-Translator: gitlab \n" +"Language-Team: Turkish\n" +"Language: tr_TR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: crowdin.com\n" +"X-Crowdin-Project: gitlab-ee\n" +"X-Crowdin-Language: tr\n" +"X-Crowdin-File: /master/locale/gitlab.pot\n" + +msgid " and" +msgstr " ve" + +msgid "%d commit" +msgid_plural "%d commits" +msgstr[0] "" +msgstr[1] "" + +msgid "%d commit behind" +msgid_plural "%d commits behind" +msgstr[0] "" +msgstr[1] "" + +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" +msgstr[1] "" + +msgid "%d layer" +msgid_plural "%d layers" +msgstr[0] "" +msgstr[1] "" + +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "%s additional commit has been omitted to prevent performance issues." +msgid_plural "%s additional commits have been omitted to prevent performance issues." +msgstr[0] "" +msgstr[1] "" + +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + +msgid "%{commit_author_link} authored %{commit_timeago}" +msgstr "" + +msgid "%{count} participant" +msgid_plural "%{count} participants" +msgstr[0] "%{count} katılımcı" +msgstr[1] "%{count} katılımcı" + +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + +msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" +msgstr "" + +msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." +msgstr "" + +msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." +msgstr "" + +msgid "%{openOrClose} %{noteable}" +msgstr "" + +msgid "%{storage_name}: failed storage access attempt on host:" +msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" +msgstr[0] "" +msgstr[1] "" + +msgid "%{text} is available" +msgstr "" + +msgid "(checkout the %{link} for information on how to install it)." +msgstr "" + +msgid "+ %{moreCount} more" +msgstr "+ %{moreCount} daha fazla" + +msgid "- show less" +msgstr "- daha az göster" + +msgid "1 pipeline" +msgid_plural "%d pipelines" +msgstr[0] "" +msgstr[1] "" + +msgid "1st contribution!" +msgstr "İlk katkı!" + +msgid "2FA enabled" +msgstr "" + +msgid "A collection of graphs regarding Continuous Integration" +msgstr "" + +msgid "About auto deploy" +msgstr "" + +msgid "Abuse Reports" +msgstr "Kötüye Kullanım Raporları" + +msgid "Access Tokens" +msgstr "Erişim anahtarları" + +msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." +msgstr "" + +msgid "Account" +msgstr "Hesap" + +msgid "Active" +msgstr "Etkin" + +msgid "Activity" +msgstr "Etkinlik" + +msgid "Add" +msgstr "Ekle" + +msgid "Add Changelog" +msgstr "Değişiklik Bilgisi Ekle" + +msgid "Add Contribution guide" +msgstr "Katkı kılavuzu ekle" + +msgid "Add Group Webhooks and GitLab Enterprise Edition." +msgstr "" + +msgid "Add Kubernetes cluster" +msgstr "" + +msgid "Add License" +msgstr "Lisans Ekle" + +msgid "Add Readme" +msgstr "" + +msgid "Add new directory" +msgstr "Yeni dizin ekle" + +msgid "Add todo" +msgstr "Yapılacaklara Ekle" + +msgid "AdminArea|Stop all jobs" +msgstr "Tüm işleri durdur" + +msgid "AdminArea|Stop all jobs?" +msgstr "Tüm işleri durdur?" + +msgid "AdminArea|Stop jobs" +msgstr "İşleri durdur" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." +msgstr "" + +msgid "AdminHealthPageLink|health page" +msgstr "" + +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + +msgid "Advanced" +msgstr "Gelişmiş" + +msgid "Advanced settings" +msgstr "Gelişmiş ayarlar" + +msgid "All" +msgstr "Tümü" + +msgid "All changes are committed" +msgstr "" + +msgid "Allows you to add and manage Kubernetes clusters." +msgstr "Kubernetes kümelerini eklemeye ve yönetmenize olanak tanır." + +msgid "An error occurred previewing the blob" +msgstr "" + +msgid "An error occurred when toggling the notification subscription" +msgstr "" + +msgid "An error occurred when updating the issue weight" +msgstr "" + +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + +msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." +msgstr "" + +msgid "An error occurred while fetching markdown preview" +msgstr "Markdown ön izlemesi yüklenirken hata oluştu" + +msgid "An error occurred while fetching sidebar data" +msgstr "" + +msgid "An error occurred while fetching the pipeline." +msgstr "" + +msgid "An error occurred while getting projects" +msgstr "Projeler yüklenirken bir hata oluştu" + +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + +msgid "An error occurred while loading filenames" +msgstr "Dosya isimleri yüklenirken bir hata oluştu" + +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + +msgid "An error occurred while rendering KaTeX" +msgstr "KaTeX'i işlerken bir hata oluştu" + +msgid "An error occurred while rendering preview broadcast message" +msgstr "Önizleme yayını iletisi oluşturulurken bir hata oluştu" + +msgid "An error occurred while retrieving calendar activity" +msgstr "" + +msgid "An error occurred while retrieving diff" +msgstr "Değişiklikler yüklenirken bir hata oluştu" + +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + +msgid "An error occurred while validating username" +msgstr "Kullanıcı adı doğrulanırken bir hata oluştu" + +msgid "An error occurred. Please try again." +msgstr "Bir hata oluştu. Lütfen tekrar deneyin." + +msgid "Appearance" +msgstr "Görünüm" + +msgid "Applications" +msgstr "Uygulamalar" + +msgid "Apr" +msgstr "Nis" + +msgid "April" +msgstr "Nisan" + +msgid "Archived project! Repository is read-only" +msgstr "Arşivlenmiş proje! Sadece okuma yapılabilir" + +msgid "Are you sure you want to delete this pipeline schedule?" +msgstr "" + +msgid "Are you sure you want to reset registration token?" +msgstr "" + +msgid "Are you sure you want to reset the health check token?" +msgstr "" + +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + +msgid "Are you sure?" +msgstr "Emin misiniz?" + +msgid "Artifacts" +msgstr "" + +msgid "Assign custom color like #FF0000" +msgstr "#FF0000 gibi özel renk ata" + +msgid "Assign labels" +msgstr "Etiket tanımla" + +msgid "Assign milestone" +msgstr "" + +msgid "Assign to" +msgstr "Ata" + +msgid "Assignee" +msgstr "" + +msgid "Attach a file by drag & drop or %{upload_link}" +msgstr "Sürükleyip bırakarak bir dosya ekle veya %{upload_link}" + +msgid "Aug" +msgstr "Ağustos" + +msgid "August" +msgstr "Ağustos" + +msgid "Authentication Log" +msgstr "Kimlik Doğrulama Günlüğü" + +msgid "Author" +msgstr "Yazar" + +msgid "Authors: %{authors}" +msgstr "Yazarlar: %{authors}" + +msgid "Auto DevOps enabled" +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." +msgstr "" + +msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." +msgstr "" + +msgid "AutoDevOps|Auto DevOps (Beta)" +msgstr "" + +msgid "AutoDevOps|Auto DevOps documentation" +msgstr "" + +msgid "AutoDevOps|Enable in settings" +msgstr "" + +msgid "AutoDevOps|It will automatically build, test, and deploy your application based on a predefined CI/CD configuration." +msgstr "" + +msgid "AutoDevOps|Learn more in the %{link_to_documentation}" +msgstr "" + +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" +msgstr "" + +msgid "Available" +msgstr "Kullanılabilir" + +msgid "Avatar will be removed. Are you sure?" +msgstr "Avatar kaldırılacak. Emin misiniz?" + +msgid "Average per day: %{average}" +msgstr "" + +msgid "Begin with the selected commit" +msgstr "" + +msgid "Billing" +msgstr "" + +msgid "BillingPlans|%{group_name} is currently on the %{plan_link} plan." +msgstr "" + +msgid "BillingPlans|Automatic downgrade and upgrade to some plans is currently not available." +msgstr "" + +msgid "BillingPlans|Current plan" +msgstr "" + +msgid "BillingPlans|Customer Support" +msgstr "" + +msgid "BillingPlans|Downgrade" +msgstr "" + +msgid "BillingPlans|Learn more about each plan by reading our %{faq_link}." +msgstr "" + +msgid "BillingPlans|Manage plan" +msgstr "" + +msgid "BillingPlans|Please contact %{customer_support_link} in that case." +msgstr "" + +msgid "BillingPlans|See all %{plan_name} features" +msgstr "" + +msgid "BillingPlans|This group uses the plan associated with its parent group." +msgstr "" + +msgid "BillingPlans|To manage the plan for this group, visit the billing section of %{parent_billing_page_link}." +msgstr "" + +msgid "BillingPlans|Upgrade" +msgstr "" + +msgid "BillingPlans|You are currently on the %{plan_link} plan." +msgstr "" + +msgid "BillingPlans|frequently asked questions" +msgstr "" + +msgid "BillingPlans|monthly" +msgstr "" + +msgid "BillingPlans|paid annually at %{price_per_year}" +msgstr "" + +msgid "BillingPlans|per user" +msgstr "" + +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "" +msgstr[1] "" + +msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" +msgstr "" + +msgid "Branch has changed" +msgstr "" + +msgid "Branch is already taken" +msgstr "" + +msgid "Branch name" +msgstr "" + +msgid "BranchSwitcherPlaceholder|Search branches" +msgstr "" + +msgid "BranchSwitcherTitle|Switch branch" +msgstr "" + +msgid "Branches" +msgstr "" + +msgid "Branches|Cant find HEAD commit for this branch" +msgstr "" + +msgid "Branches|Compare" +msgstr "" + +msgid "Branches|Delete all branches that are merged into '%{default_branch}'" +msgstr "" + +msgid "Branches|Delete branch" +msgstr "" + +msgid "Branches|Delete merged branches" +msgstr "" + +msgid "Branches|Delete protected branch" +msgstr "" + +msgid "Branches|Delete protected branch '%{branch_name}'?" +msgstr "" + +msgid "Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?" +msgstr "" + +msgid "Branches|Deleting the merged branches cannot be undone. Are you sure?" +msgstr "" + +msgid "Branches|Filter by branch name" +msgstr "" + +msgid "Branches|Merged into %{default_branch}" +msgstr "" + +msgid "Branches|New branch" +msgstr "" + +msgid "Branches|No branches to show" +msgstr "" + +msgid "Branches|Once you confirm and press %{delete_protected_branch}, it cannot be undone or recovered." +msgstr "" + +msgid "Branches|Only a project master or owner can delete a protected branch" +msgstr "" + +msgid "Branches|Protected branches can be managed in %{project_settings_link}" +msgstr "" + +msgid "Branches|Sort by" +msgstr "" + +msgid "Branches|The branch could not be updated automatically because it has diverged from its upstream counterpart." +msgstr "" + +msgid "Branches|The default branch cannot be deleted" +msgstr "" + +msgid "Branches|This branch hasn’t been merged into %{default_branch}." +msgstr "" + +msgid "Branches|To avoid data loss, consider merging this branch before deleting it." +msgstr "" + +msgid "Branches|To confirm, type %{branch_name_confirmation}:" +msgstr "" + +msgid "Branches|To discard the local changes and overwrite the branch with the upstream version, delete it here and choose 'Update Now' above." +msgstr "" + +msgid "Branches|You’re about to permanently delete the protected branch %{branch_name}." +msgstr "" + +msgid "Branches|diverged from upstream" +msgstr "" + +msgid "Branches|merged" +msgstr "" + +msgid "Branches|project settings" +msgstr "" + +msgid "Branches|protected" +msgstr "" + +msgid "Browse Directory" +msgstr "" + +msgid "Browse File" +msgstr "" + +msgid "Browse Files" +msgstr "" + +msgid "Browse files" +msgstr "" + +msgid "ByAuthor|by" +msgstr "" + +msgid "CI / CD" +msgstr "" + +msgid "CI/CD configuration" +msgstr "" + +msgid "CICD|Jobs" +msgstr "" + +msgid "Cancel" +msgstr "" + +msgid "Cancel edit" +msgstr "" + +msgid "Cannot modify managed Kubernetes cluster" +msgstr "" + +msgid "Change Weight" +msgstr "" + +msgid "ChangeTypeActionLabel|Pick into branch" +msgstr "" + +msgid "ChangeTypeActionLabel|Revert in branch" +msgstr "" + +msgid "ChangeTypeAction|Cherry-pick" +msgstr "" + +msgid "ChangeTypeAction|Revert" +msgstr "" + +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + +msgid "Changelog" +msgstr "" + +msgid "Changes are shown as if the source revision was being merged into the target revision." +msgstr "" + +msgid "Charts" +msgstr "" + +msgid "Chat" +msgstr "" + +msgid "Check interval" +msgstr "" + +msgid "Checking %{text} availability…" +msgstr "" + +msgid "Checking branch availability..." +msgstr "" + +msgid "Cherry-pick this commit" +msgstr "" + +msgid "Cherry-pick this merge request" +msgstr "" + +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + +msgid "Choose which groups you wish to synchronize to this secondary node." +msgstr "" + +msgid "Choose which shards you wish to synchronize to this secondary node." +msgstr "" + +msgid "CiStatusLabel|canceled" +msgstr "" + +msgid "CiStatusLabel|created" +msgstr "" + +msgid "CiStatusLabel|failed" +msgstr "" + +msgid "CiStatusLabel|manual action" +msgstr "" + +msgid "CiStatusLabel|passed" +msgstr "" + +msgid "CiStatusLabel|passed with warnings" +msgstr "" + +msgid "CiStatusLabel|pending" +msgstr "" + +msgid "CiStatusLabel|skipped" +msgstr "" + +msgid "CiStatusLabel|waiting for manual action" +msgstr "" + +msgid "CiStatusText|blocked" +msgstr "" + +msgid "CiStatusText|canceled" +msgstr "" + +msgid "CiStatusText|created" +msgstr "" + +msgid "CiStatusText|failed" +msgstr "" + +msgid "CiStatusText|manual" +msgstr "" + +msgid "CiStatusText|passed" +msgstr "" + +msgid "CiStatusText|pending" +msgstr "" + +msgid "CiStatusText|skipped" +msgstr "" + +msgid "CiStatus|running" +msgstr "" + +msgid "CiVariables|Input variable key" +msgstr "" + +msgid "CiVariables|Input variable value" +msgstr "" + +msgid "CiVariables|Remove variable row" +msgstr "" + +msgid "CiVariable|* (All environments)" +msgstr "" + +msgid "CiVariable|All environments" +msgstr "" + +msgid "CiVariable|Create wildcard" +msgstr "" + +msgid "CiVariable|Error occured while saving variables" +msgstr "" + +msgid "CiVariable|New environment" +msgstr "" + +msgid "CiVariable|Protected" +msgstr "" + +msgid "CiVariable|Search environments" +msgstr "" + +msgid "CiVariable|Toggle protected" +msgstr "" + +msgid "CiVariable|Validation failed" +msgstr "" + +msgid "CircuitBreakerApiLink|circuitbreaker api" +msgstr "" + +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + +msgid "Click to expand text" +msgstr "" + +msgid "Clone repository" +msgstr "" + +msgid "Close" +msgstr "" + +msgid "Closed" +msgstr "" + +msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|API URL" +msgstr "" + +msgid "ClusterIntegration|Add Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Add an existing Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration" +msgstr "" + +msgid "ClusterIntegration|Applications" +msgstr "" + +msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|CA Certificate" +msgstr "" + +msgid "ClusterIntegration|Certificate Authority bundle (PEM format)" +msgstr "" + +msgid "ClusterIntegration|Choose how to set up Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Choose which of your project's environments will use this Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab" +msgstr "" + +msgid "ClusterIntegration|Copy API URL" +msgstr "" + +msgid "ClusterIntegration|Copy CA Certificate" +msgstr "" + +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + +msgid "ClusterIntegration|Copy Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Copy Token" +msgstr "" + +msgid "ClusterIntegration|Create Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Create Kubernetes cluster on Google Kubernetes Engine" +msgstr "" + +msgid "ClusterIntegration|Create a new Kubernetes cluster on Google Kubernetes Engine right from GitLab" +msgstr "" + +msgid "ClusterIntegration|Create on GKE" +msgstr "" + +msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Enter the details for your Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Environment scope" +msgstr "" + +msgid "ClusterIntegration|GitLab Integration" +msgstr "" + +msgid "ClusterIntegration|GitLab Runner" +msgstr "" + +msgid "ClusterIntegration|Google Cloud Platform project ID" +msgstr "" + +msgid "ClusterIntegration|Google Kubernetes Engine" +msgstr "" + +msgid "ClusterIntegration|Google Kubernetes Engine project" +msgstr "" + +msgid "ClusterIntegration|Helm Tiller" +msgstr "" + +msgid "ClusterIntegration|Ingress" +msgstr "" + +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + +msgid "ClusterIntegration|Install" +msgstr "" + +msgid "ClusterIntegration|Installed" +msgstr "" + +msgid "ClusterIntegration|Installing" +msgstr "" + +msgid "ClusterIntegration|Integrate Kubernetes cluster automation" +msgstr "" + +msgid "ClusterIntegration|Integration status" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is disabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster name" +msgstr "" + +msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" +msgstr "" + +msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" +msgstr "" + +msgid "ClusterIntegration|Learn more about %{link_to_documentation}" +msgstr "" + +msgid "ClusterIntegration|Learn more about environments" +msgstr "" + +msgid "ClusterIntegration|Machine type" +msgstr "" + +msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" +msgstr "" + +msgid "ClusterIntegration|Manage" +msgstr "" + +msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" +msgstr "" + +msgid "ClusterIntegration|More information" +msgstr "" + +msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" +msgstr "" + +msgid "ClusterIntegration|Note:" +msgstr "" + +msgid "ClusterIntegration|Number of nodes" +msgstr "" + +msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" +msgstr "" + +msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" +msgstr "" + +msgid "ClusterIntegration|Project ID" +msgstr "" + +msgid "ClusterIntegration|Project namespace" +msgstr "" + +msgid "ClusterIntegration|Project namespace (optional, unique)" +msgstr "" + +msgid "ClusterIntegration|Prometheus" +msgstr "" + +msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." +msgstr "" + +msgid "ClusterIntegration|Remove Kubernetes cluster integration" +msgstr "" + +msgid "ClusterIntegration|Remove integration" +msgstr "" + +msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." +msgstr "" + +msgid "ClusterIntegration|Request to begin installing failed" +msgstr "" + +msgid "ClusterIntegration|Save changes" +msgstr "" + +msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|See machine types" +msgstr "" + +msgid "ClusterIntegration|See your projects" +msgstr "" + +msgid "ClusterIntegration|See zones" +msgstr "" + +msgid "ClusterIntegration|Service token" +msgstr "" + +msgid "ClusterIntegration|Show" +msgstr "" + +msgid "ClusterIntegration|Something went wrong on our end." +msgstr "" + +msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" +msgstr "" + +msgid "ClusterIntegration|Something went wrong while installing %{title}" +msgstr "" + +msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" +msgstr "" + +msgid "ClusterIntegration|Toggle Kubernetes Cluster" +msgstr "" + +msgid "ClusterIntegration|Toggle Kubernetes cluster" +msgstr "" + +msgid "ClusterIntegration|Token" +msgstr "" + +msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." +msgstr "" + +msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" +msgstr "" + +msgid "ClusterIntegration|Zone" +msgstr "" + +msgid "ClusterIntegration|access to Google Kubernetes Engine" +msgstr "" + +msgid "ClusterIntegration|check the pricing here" +msgstr "" + +msgid "ClusterIntegration|documentation" +msgstr "" + +msgid "ClusterIntegration|help page" +msgstr "" + +msgid "ClusterIntegration|installing applications" +msgstr "" + +msgid "ClusterIntegration|meets the requirements" +msgstr "" + +msgid "ClusterIntegration|properly configured" +msgstr "" + +msgid "Collapse" +msgstr "" + +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + +msgid "Comments" +msgstr "" + +msgid "Commit" +msgid_plural "Commits" +msgstr[0] "" +msgstr[1] "" + +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" +msgstr[1] "" + +msgid "Commit Message" +msgstr "" + +msgid "Commit duration in minutes for last 30 commits" +msgstr "" + +msgid "Commit message" +msgstr "" + +msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" +msgstr "" + +msgid "Commit to %{branchName} branch" +msgstr "" + +msgid "CommitBoxTitle|Commit" +msgstr "" + +msgid "CommitMessage|Add %{file_name}" +msgstr "" + +msgid "Commits" +msgstr "" + +msgid "Commits feed" +msgstr "" + +msgid "Commits per day hour (UTC)" +msgstr "" + +msgid "Commits per day of month" +msgstr "" + +msgid "Commits per weekday" +msgstr "" + +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + +msgid "Commits|Commit: %{commitText}" +msgstr "" + +msgid "Commits|History" +msgstr "" + +msgid "Commits|No related merge requests found" +msgstr "" + +msgid "Committed by" +msgstr "" + +msgid "Compare" +msgstr "" + +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + +msgid "Confidentiality" +msgstr "" + +msgid "Container Registry" +msgstr "" + +msgid "ContainerRegistry|Created" +msgstr "" + +msgid "ContainerRegistry|First log in to GitLab’s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:" +msgstr "" + +msgid "ContainerRegistry|GitLab supports up to 3 levels of image names. The following examples of images are valid for your project:" +msgstr "" + +msgid "ContainerRegistry|How to use the Container Registry" +msgstr "" + +msgid "ContainerRegistry|Learn more about" +msgstr "" + +msgid "ContainerRegistry|No tags in Container Registry for this container image." +msgstr "" + +msgid "ContainerRegistry|Once you log in, you’re free to create and upload a container image using the common %{build} and %{push} commands" +msgstr "" + +msgid "ContainerRegistry|Remove repository" +msgstr "" + +msgid "ContainerRegistry|Remove tag" +msgstr "" + +msgid "ContainerRegistry|Size" +msgstr "" + +msgid "ContainerRegistry|Tag" +msgstr "" + +msgid "ContainerRegistry|Tag ID" +msgstr "" + +msgid "ContainerRegistry|Use different image names" +msgstr "" + +msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images." +msgstr "" + +msgid "Contribution guide" +msgstr "" + +msgid "Contributors" +msgstr "" + +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + +msgid "ContributorsPage|Building repository graph." +msgstr "" + +msgid "ContributorsPage|Commits to %{branch_name}, excluding merge commits. Limited to 6,000 commits." +msgstr "" + +msgid "ContributorsPage|Please wait a moment, this page will automatically refresh when ready." +msgstr "" + +msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node" +msgstr "" + +msgid "Control the maximum concurrency of repository backfill for this secondary node" +msgstr "" + +msgid "Copy SSH public key to clipboard" +msgstr "" + +msgid "Copy URL to clipboard" +msgstr "" + +msgid "Copy branch name to clipboard" +msgstr "" + +msgid "Copy command to clipboard" +msgstr "" + +msgid "Copy commit SHA to clipboard" +msgstr "" + +msgid "Copy reference to clipboard" +msgstr "" + +msgid "Create" +msgstr "" + +msgid "Create New Directory" +msgstr "" + +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + +msgid "Create a personal access token on your account to pull or push via %{protocol}." +msgstr "" + +msgid "Create branch" +msgstr "" + +msgid "Create directory" +msgstr "" + +msgid "Create empty bare repository" +msgstr "" + +msgid "Create epic" +msgstr "" + +msgid "Create file" +msgstr "" + +msgid "Create lists from labels. Issues with that label appear in that list." +msgstr "" + +msgid "Create merge request" +msgstr "" + +msgid "Create merge request and branch" +msgstr "" + +msgid "Create new branch" +msgstr "" + +msgid "Create new directory" +msgstr "" + +msgid "Create new file" +msgstr "" + +msgid "Create new label" +msgstr "" + +msgid "Create new..." +msgstr "" + +msgid "CreateNewFork|Fork" +msgstr "" + +msgid "CreateTag|Tag" +msgstr "" + +msgid "CreateTokenToCloneLink|create a personal access token" +msgstr "" + +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + +msgid "Creating epic" +msgstr "" + +msgid "Cron Timezone" +msgstr "" + +msgid "Cron syntax" +msgstr "" + +msgid "Current node" +msgstr "" + +msgid "Custom notification events" +msgstr "" + +msgid "Custom notification levels are the same as participating levels. With custom notification levels you will also receive notifications for select events. To find out more, check out %{notification_link}." +msgstr "" + +msgid "Cycle Analytics" +msgstr "" + +msgid "CycleAnalyticsStage|Code" +msgstr "" + +msgid "CycleAnalyticsStage|Issue" +msgstr "" + +msgid "CycleAnalyticsStage|Plan" +msgstr "" + +msgid "CycleAnalyticsStage|Production" +msgstr "" + +msgid "CycleAnalyticsStage|Review" +msgstr "" + +msgid "CycleAnalyticsStage|Staging" +msgstr "" + +msgid "CycleAnalyticsStage|Test" +msgstr "" + +msgid "DashboardProjects|All" +msgstr "" + +msgid "DashboardProjects|Personal" +msgstr "" + +msgid "Dec" +msgstr "" + +msgid "December" +msgstr "" + +msgid "Default classification label" +msgstr "" + +msgid "Define a custom pattern with cron syntax" +msgstr "" + +msgid "Delete" +msgstr "" + +msgid "Deploy" +msgid_plural "Deploys" +msgstr[0] "" +msgstr[1] "" + +msgid "Deploy Keys" +msgstr "" + +msgid "Description" +msgstr "" + +msgid "Description templates allow you to define context-specific templates for issue and merge request description fields for your project." +msgstr "" + +msgid "Details" +msgstr "" + +msgid "Diffs|No file name available" +msgstr "" + +msgid "Directory name" +msgstr "" + +msgid "Disable" +msgstr "" + +msgid "Discard draft" +msgstr "" + +msgid "Discover GitLab Geo." +msgstr "" + +msgid "Dismiss Cycle Analytics introduction box" +msgstr "" + +msgid "Dismiss Merge Request promotion" +msgstr "" + +msgid "Don't show again" +msgstr "" + +msgid "Download" +msgstr "" + +msgid "Download tar" +msgstr "" + +msgid "Download tar.bz2" +msgstr "" + +msgid "Download tar.gz" +msgstr "" + +msgid "Download zip" +msgstr "" + +msgid "DownloadArtifacts|Download" +msgstr "" + +msgid "DownloadCommit|Email Patches" +msgstr "" + +msgid "DownloadCommit|Plain Diff" +msgstr "" + +msgid "DownloadSource|Download" +msgstr "" + +msgid "Due date" +msgstr "" + +msgid "Edit" +msgstr "" + +msgid "Edit Pipeline Schedule %{id}" +msgstr "" + +msgid "Edit files in the editor and commit changes here" +msgstr "" + +msgid "Emails" +msgstr "" + +msgid "Enable" +msgstr "" + +msgid "Enable Auto DevOps" +msgstr "" + +msgid "Environments|An error occurred while fetching the environments." +msgstr "" + +msgid "Environments|An error occurred while making the request." +msgstr "" + +msgid "Environments|Commit" +msgstr "" + +msgid "Environments|Deployment" +msgstr "" + +msgid "Environments|Environment" +msgstr "" + +msgid "Environments|Environments" +msgstr "" + +msgid "Environments|Job" +msgstr "" + +msgid "Environments|New environment" +msgstr "" + +msgid "Environments|No deployments yet" +msgstr "" + +msgid "Environments|Open" +msgstr "" + +msgid "Environments|Re-deploy" +msgstr "" + +msgid "Environments|Read more about environments" +msgstr "" + +msgid "Environments|Rollback" +msgstr "" + +msgid "Environments|Show all" +msgstr "" + +msgid "Environments|Updated" +msgstr "" + +msgid "Environments|You don't have any environments right now." +msgstr "" + +msgid "Epic will be removed! Are you sure?" +msgstr "" + +msgid "Epics" +msgstr "" + +msgid "Epics Roadmap" +msgstr "" + +msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + +msgid "Error creating epic" +msgstr "" + +msgid "Error fetching contributors data." +msgstr "" + +msgid "Error fetching labels." +msgstr "" + +msgid "Error fetching network graph." +msgstr "" + +msgid "Error fetching refs" +msgstr "" + +msgid "Error fetching usage ping data." +msgstr "" + +msgid "Error occurred when toggling the notification subscription" +msgstr "" + +msgid "Error saving label update." +msgstr "" + +msgid "Error updating status for all todos." +msgstr "" + +msgid "Error updating todo status." +msgstr "" + +msgid "EventFilterBy|Filter by all" +msgstr "" + +msgid "EventFilterBy|Filter by comments" +msgstr "" + +msgid "EventFilterBy|Filter by issue events" +msgstr "" + +msgid "EventFilterBy|Filter by merge events" +msgstr "" + +msgid "EventFilterBy|Filter by push events" +msgstr "" + +msgid "EventFilterBy|Filter by team" +msgstr "" + +msgid "Every day (at 4:00am)" +msgstr "" + +msgid "Every month (on the 1st at 4:00am)" +msgstr "" + +msgid "Every week (Sundays at 4:00am)" +msgstr "" + +msgid "Expand" +msgstr "" + +msgid "Explore projects" +msgstr "" + +msgid "Explore public groups" +msgstr "" + +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + +msgid "Failed to change the owner" +msgstr "" + +msgid "Failed to remove issue from board, please try again." +msgstr "" + +msgid "Failed to remove the pipeline schedule" +msgstr "" + +msgid "Failed to update issues, please try again." +msgstr "" + +msgid "Feb" +msgstr "" + +msgid "February" +msgstr "" + +msgid "Fields on this page are now uneditable, you can configure" +msgstr "" + +msgid "File name" +msgstr "" + +msgid "Files" +msgstr "" + +msgid "Files (%{human_size})" +msgstr "" + +msgid "Filter by commit message" +msgstr "" + +msgid "Find by path" +msgstr "" + +msgid "Find file" +msgstr "" + +msgid "FirstPushedBy|First" +msgstr "" + +msgid "FirstPushedBy|pushed by" +msgstr "" + +msgid "Fork" +msgid_plural "Forks" +msgstr[0] "" +msgstr[1] "" + +msgid "ForkedFromProjectPath|Forked from" +msgstr "" + +msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)" +msgstr "" + +msgid "Format" +msgstr "" + +msgid "From issue creation until deploy to production" +msgstr "" + +msgid "From merge request merge until deploy to production" +msgstr "" + +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + +msgid "GPG Keys" +msgstr "" + +msgid "Generate a default set of labels" +msgstr "" + +msgid "Geo Nodes" +msgstr "" + +msgid "GeoNodeSyncStatus|Node is failing or broken." +msgstr "" + +msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage." +msgstr "" + +msgid "GeoNodes|Database replication lag:" +msgstr "" + +msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" +msgstr "" + +msgid "GeoNodes|Does not match the primary storage configuration" +msgstr "" + +msgid "GeoNodes|Failed" +msgstr "" + +msgid "GeoNodes|Full" +msgstr "" + +msgid "GeoNodes|GitLab version does not match the primary node version" +msgstr "" + +msgid "GeoNodes|GitLab version:" +msgstr "" + +msgid "GeoNodes|Health status:" +msgstr "" + +msgid "GeoNodes|Last event ID processed by cursor:" +msgstr "" + +msgid "GeoNodes|Last event ID seen from primary:" +msgstr "" + +msgid "GeoNodes|Loading nodes" +msgstr "" + +msgid "GeoNodes|Local Attachments:" +msgstr "" + +msgid "GeoNodes|Local LFS objects:" +msgstr "" + +msgid "GeoNodes|Local job artifacts:" +msgstr "" + +msgid "GeoNodes|New node" +msgstr "" + +msgid "GeoNodes|Out of sync" +msgstr "" + +msgid "GeoNodes|Replication slot WAL:" +msgstr "" + +msgid "GeoNodes|Replication slots:" +msgstr "" + +msgid "GeoNodes|Repositories:" +msgstr "" + +msgid "GeoNodes|Selective" +msgstr "" + +msgid "GeoNodes|Storage config:" +msgstr "" + +msgid "GeoNodes|Sync settings:" +msgstr "" + +msgid "GeoNodes|Synced" +msgstr "" + +msgid "GeoNodes|Unused slots" +msgstr "" + +msgid "GeoNodes|Used slots" +msgstr "" + +msgid "GeoNodes|Wikis:" +msgstr "" + +msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." +msgstr "" + +msgid "Geo|All projects" +msgstr "" + +msgid "Geo|File sync capacity" +msgstr "" + +msgid "Geo|Groups to synchronize" +msgstr "" + +msgid "Geo|Projects in certain groups" +msgstr "" + +msgid "Geo|Projects in certain storage shards" +msgstr "" + +msgid "Geo|Repository sync capacity" +msgstr "" + +msgid "Geo|Select groups to replicate." +msgstr "" + +msgid "Geo|Shards to synchronize" +msgstr "" + +msgid "Git revision" +msgstr "" + +msgid "Git storage health information has been reset" +msgstr "" + +msgid "Git version" +msgstr "" + +msgid "GitLab Runner section" +msgstr "" + +msgid "Gitaly Servers" +msgstr "" + +msgid "Go to your fork" +msgstr "" + +msgid "GoToYourFork|Fork" +msgstr "" + +msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." +msgstr "" + +msgid "Got it!" +msgstr "" + +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + +msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" +msgstr "" + +msgid "GroupSettings|Share with group lock" +msgstr "" + +msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup." +msgstr "" + +msgid "GroupSettings|This setting is applied on %{ancestor_group}. To share projects in this group with another group, ask the owner to override the setting or %{remove_ancestor_share_with_group_lock}." +msgstr "" + +msgid "GroupSettings|This setting is applied on %{ancestor_group}. You can override the setting or %{remove_ancestor_share_with_group_lock}." +msgstr "" + +msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually." +msgstr "" + +msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group" +msgstr "" + +msgid "GroupSettings|remove the share with group lock from %{ancestor_group_name}" +msgstr "" + +msgid "GroupsEmptyState|A group is a collection of several projects." +msgstr "" + +msgid "GroupsEmptyState|If you organize your projects under a group, it works like a folder." +msgstr "" + +msgid "GroupsEmptyState|No groups found" +msgstr "" + +msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." +msgstr "" + +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" +msgstr "" + +msgid "GroupsTree|Create a project in this group." +msgstr "" + +msgid "GroupsTree|Create a subgroup in this group." +msgstr "" + +msgid "GroupsTree|Edit group" +msgstr "" + +msgid "GroupsTree|Failed to leave the group. Please make sure you are not the only owner." +msgstr "" + +msgid "GroupsTree|Filter by name..." +msgstr "" + +msgid "GroupsTree|Leave this group" +msgstr "" + +msgid "GroupsTree|Loading groups" +msgstr "" + +msgid "GroupsTree|Sorry, no groups matched your search" +msgstr "" + +msgid "GroupsTree|Sorry, no groups or projects matched your search" +msgstr "" + +msgid "Have your users email" +msgstr "" + +msgid "Health Check" +msgstr "" + +msgid "Health information can be retrieved from the following endpoints. More information is available" +msgstr "" + +msgid "HealthCheck|Access token is" +msgstr "" + +msgid "HealthCheck|Healthy" +msgstr "" + +msgid "HealthCheck|No Health Problems Detected" +msgstr "" + +msgid "HealthCheck|Unhealthy" +msgstr "" + +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" + +msgid "History" +msgstr "" + +msgid "Housekeeping successfully started" +msgstr "" + +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + +msgid "Import repository" +msgstr "" + +msgid "Improve Issue boards with GitLab Enterprise Edition." +msgstr "" + +msgid "Improve issues management with Issue weight and GitLab Enterprise Edition." +msgstr "" + +msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." +msgstr "" + +msgid "Install Runner on Kubernetes" +msgstr "" + +msgid "Install a Runner compatible with GitLab CI" +msgstr "" + +msgid "Instance" +msgid_plural "Instances" +msgstr[0] "" +msgstr[1] "" + +msgid "Instance does not support multiple Kubernetes clusters" +msgstr "" + +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + +msgid "Internal - The group and any internal projects can be viewed by any logged in user." +msgstr "" + +msgid "Internal - The project can be accessed by any logged in user." +msgstr "" + +msgid "Interval Pattern" +msgstr "" + +msgid "Introducing Cycle Analytics" +msgstr "" + +msgid "Issue board focus mode" +msgstr "" + +msgid "Issue events" +msgstr "" + +msgid "IssueBoards|Board" +msgstr "" + +msgid "IssueBoards|Boards" +msgstr "" + +msgid "Issues" +msgstr "" + +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + +msgid "Jan" +msgstr "" + +msgid "January" +msgstr "" + +msgid "Jobs" +msgstr "" + +msgid "Jul" +msgstr "" + +msgid "July" +msgstr "" + +msgid "Jun" +msgstr "" + +msgid "June" +msgstr "" + +msgid "Kubernetes" +msgstr "" + +msgid "Kubernetes Cluster" +msgstr "" + +msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" +msgstr "" + +msgid "Kubernetes cluster integration was not removed." +msgstr "" + +msgid "Kubernetes cluster integration was successfully removed." +msgstr "" + +msgid "Kubernetes cluster was successfully updated." +msgstr "" + +msgid "Kubernetes configured" +msgstr "" + +msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" +msgstr "" + +msgid "LFSStatus|Disabled" +msgstr "" + +msgid "LFSStatus|Enabled" +msgstr "" + +msgid "Labels" +msgstr "" + +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + +msgid "Last %d day" +msgid_plural "Last %d days" +msgstr[0] "" +msgstr[1] "" + +msgid "Last Pipeline" +msgstr "" + +msgid "Last commit" +msgstr "" + +msgid "Last edited %{date}" +msgstr "" + +msgid "Last edited by %{name}" +msgstr "" + +msgid "Last update" +msgstr "" + +msgid "Last updated" +msgstr "" + +msgid "LastPushEvent|You pushed to" +msgstr "" + +msgid "LastPushEvent|at" +msgstr "" + +msgid "Learn more" +msgstr "" + +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + +msgid "Learn more in the" +msgstr "" + +msgid "Learn more in the|pipeline schedules documentation" +msgstr "" + +msgid "Leave" +msgstr "" + +msgid "Leave group" +msgstr "" + +msgid "Leave project" +msgstr "" + +msgid "License" +msgstr "" + +msgid "List" +msgstr "" + +msgid "Loading the GitLab IDE..." +msgstr "" + +msgid "Lock" +msgstr "" + +msgid "Lock %{issuableDisplayName}" +msgstr "" + +msgid "Lock not found" +msgstr "" + +msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." +msgstr "" + +msgid "Locked" +msgstr "" + +msgid "Locked Files" +msgstr "" + +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + +msgid "Login" +msgstr "" + +msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." +msgstr "" + +msgid "Manage labels" +msgstr "" + +msgid "Mar" +msgstr "" + +msgid "March" +msgstr "" + +msgid "Mark done" +msgstr "" + +msgid "Maximum git storage failures" +msgstr "" + +msgid "May" +msgstr "" + +msgid "Median" +msgstr "" + +msgid "Members" +msgstr "" + +msgid "Merge Requests" +msgstr "" + +msgid "Merge events" +msgstr "" + +msgid "Merge request" +msgstr "" + +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "MergeRequest|Approved" +msgstr "" + +msgid "Merged" +msgstr "" + +msgid "Messages" +msgstr "" + +msgid "Milestone" +msgstr "" + +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + +msgid "MissingSSHKeyWarningLink|add an SSH key" +msgstr "" + +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + +msgid "Monitoring" +msgstr "" + +msgid "More information" +msgstr "" + +msgid "More information is available|here" +msgstr "" + +msgid "Move" +msgstr "" + +msgid "Move issue" +msgstr "" + +msgid "Multiple issue boards" +msgstr "" + +msgid "Name new label" +msgstr "" + +msgid "New Issue" +msgid_plural "New Issues" +msgstr[0] "" +msgstr[1] "" + +msgid "New Kubernetes Cluster" +msgstr "" + +msgid "New Kubernetes cluster" +msgstr "" + +msgid "New Pipeline Schedule" +msgstr "" + +msgid "New branch" +msgstr "" + +msgid "New branch unavailable" +msgstr "" + +msgid "New directory" +msgstr "" + +msgid "New epic" +msgstr "" + +msgid "New file" +msgstr "" + +msgid "New group" +msgstr "" + +msgid "New issue" +msgstr "" + +msgid "New label" +msgstr "" + +msgid "New merge request" +msgstr "" + +msgid "New project" +msgstr "" + +msgid "New schedule" +msgstr "" + +msgid "New snippet" +msgstr "" + +msgid "New subgroup" +msgstr "" + +msgid "New tag" +msgstr "" + +msgid "No assignee" +msgstr "" + +msgid "No changes" +msgstr "" + +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No due date" +msgstr "" + +msgid "No estimate or time spent" +msgstr "" + +msgid "No file chosen" +msgstr "" + +msgid "No repository" +msgstr "" + +msgid "No schedules" +msgstr "" + +msgid "None" +msgstr "" + +msgid "Not allowed to merge" +msgstr "" + +msgid "Not available" +msgstr "" + +msgid "Not confidential" +msgstr "" + +msgid "Not enough data" +msgstr "" + +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + +msgid "Notification events" +msgstr "" + +msgid "NotificationEvent|Close issue" +msgstr "" + +msgid "NotificationEvent|Close merge request" +msgstr "" + +msgid "NotificationEvent|Failed pipeline" +msgstr "" + +msgid "NotificationEvent|Merge merge request" +msgstr "" + +msgid "NotificationEvent|New issue" +msgstr "" + +msgid "NotificationEvent|New merge request" +msgstr "" + +msgid "NotificationEvent|New note" +msgstr "" + +msgid "NotificationEvent|Reassign issue" +msgstr "" + +msgid "NotificationEvent|Reassign merge request" +msgstr "" + +msgid "NotificationEvent|Reopen issue" +msgstr "" + +msgid "NotificationEvent|Successful pipeline" +msgstr "" + +msgid "NotificationLevel|Custom" +msgstr "" + +msgid "NotificationLevel|Disabled" +msgstr "" + +msgid "NotificationLevel|Global" +msgstr "" + +msgid "NotificationLevel|On mention" +msgstr "" + +msgid "NotificationLevel|Participate" +msgstr "" + +msgid "NotificationLevel|Watch" +msgstr "" + +msgid "Notifications" +msgstr "" + +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + +msgid "Nov" +msgstr "" + +msgid "November" +msgstr "" + +msgid "Number of access attempts" +msgstr "" + +msgid "OK" +msgstr "" + +msgid "Oct" +msgstr "" + +msgid "October" +msgstr "" + +msgid "OfSearchInADropdown|Filter" +msgstr "" + +msgid "Only project members can comment." +msgstr "" + +msgid "Open" +msgstr "" + +msgid "Opened" +msgstr "" + +msgid "OpenedNDaysAgo|Opened" +msgstr "" + +msgid "Opens in a new window" +msgstr "" + +msgid "Options" +msgstr "" + +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + +msgid "Overview" +msgstr "" + +msgid "Owner" +msgstr "" + +msgid "Pagination|Last »" +msgstr "" + +msgid "Pagination|Next" +msgstr "" + +msgid "Pagination|Prev" +msgstr "" + +msgid "Pagination|« First" +msgstr "" + +msgid "Password" +msgstr "" + +msgid "Pipeline" +msgstr "" + +msgid "Pipeline Health" +msgstr "" + +msgid "Pipeline Schedule" +msgstr "" + +msgid "Pipeline Schedules" +msgstr "" + +msgid "Pipeline quota" +msgstr "" + +msgid "PipelineCharts|Failed:" +msgstr "" + +msgid "PipelineCharts|Overall statistics" +msgstr "" + +msgid "PipelineCharts|Success ratio:" +msgstr "" + +msgid "PipelineCharts|Successful:" +msgstr "" + +msgid "PipelineCharts|Total:" +msgstr "" + +msgid "PipelineSchedules|Activated" +msgstr "" + +msgid "PipelineSchedules|Active" +msgstr "" + +msgid "PipelineSchedules|All" +msgstr "" + +msgid "PipelineSchedules|Inactive" +msgstr "" + +msgid "PipelineSchedules|Next Run" +msgstr "" + +msgid "PipelineSchedules|None" +msgstr "" + +msgid "PipelineSchedules|Provide a short description for this pipeline" +msgstr "" + +msgid "PipelineSchedules|Take ownership" +msgstr "" + +msgid "PipelineSchedules|Target" +msgstr "" + +msgid "PipelineSchedules|Variables" +msgstr "" + +msgid "PipelineSheduleIntervalPattern|Custom" +msgstr "" + +msgid "Pipelines" +msgstr "" + +msgid "Pipelines charts" +msgstr "" + +msgid "Pipelines for last month" +msgstr "" + +msgid "Pipelines for last week" +msgstr "" + +msgid "Pipelines for last year" +msgstr "" + +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|all" +msgstr "" + +msgid "Pipeline|success" +msgstr "" + +msgid "Pipeline|with stage" +msgstr "" + +msgid "Pipeline|with stages" +msgstr "" + +msgid "Play" +msgstr "" + +msgid "Please enable billing for one of your projects to be able to create a Kubernetes cluster, then try again." +msgstr "" + +msgid "Please solve the reCAPTCHA" +msgstr "" + +msgid "Preferences" +msgstr "" + +msgid "Primary" +msgstr "" + +msgid "Private - Project access must be granted explicitly to each user." +msgstr "" + +msgid "Private - The group and its projects can only be viewed by members." +msgstr "" + +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + +msgid "Profile" +msgstr "" + +msgid "Profiles|Account scheduled for removal." +msgstr "" + +msgid "Profiles|Delete Account" +msgstr "" + +msgid "Profiles|Delete account" +msgstr "" + +msgid "Profiles|Delete your account?" +msgstr "" + +msgid "Profiles|Deleting an account has the following effects:" +msgstr "" + +msgid "Profiles|Invalid password" +msgstr "" + +msgid "Profiles|Invalid username" +msgstr "" + +msgid "Profiles|Type your %{confirmationValue} to confirm:" +msgstr "" + +msgid "Profiles|You don't have access to delete this user." +msgstr "" + +msgid "Profiles|You must transfer ownership or delete these groups before you can delete your account." +msgstr "" + +msgid "Profiles|Your account is currently an owner in these groups:" +msgstr "" + +msgid "Profiles|your account" +msgstr "" + +msgid "Programming languages used in this repository" +msgstr "" + +msgid "Project '%{project_name}' is in the process of being deleted." +msgstr "" + +msgid "Project '%{project_name}' queued for deletion." +msgstr "" + +msgid "Project '%{project_name}' was successfully created." +msgstr "" + +msgid "Project '%{project_name}' was successfully updated." +msgstr "" + +msgid "Project access must be granted explicitly to each user." +msgstr "" + +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + +msgid "Project details" +msgstr "" + +msgid "Project export could not be deleted." +msgstr "" + +msgid "Project export has been deleted." +msgstr "" + +msgid "Project export link has expired. Please generate a new export from your project settings." +msgstr "" + +msgid "Project export started. A download link will be sent by email." +msgstr "" + +msgid "ProjectActivityRSS|Subscribe" +msgstr "" + +msgid "ProjectCreationLevel|Allowed to create projects" +msgstr "" + +msgid "ProjectCreationLevel|Default project creation protection" +msgstr "" + +msgid "ProjectCreationLevel|Developers + Masters" +msgstr "" + +msgid "ProjectCreationLevel|Masters" +msgstr "" + +msgid "ProjectCreationLevel|No one" +msgstr "" + +msgid "ProjectFeature|Disabled" +msgstr "" + +msgid "ProjectFeature|Everyone with access" +msgstr "" + +msgid "ProjectFeature|Only team members" +msgstr "" + +msgid "ProjectFileTree|Name" +msgstr "" + +msgid "ProjectLastActivity|Never" +msgstr "" + +msgid "ProjectLifecycle|Stage" +msgstr "" + +msgid "ProjectNetworkGraph|Graph" +msgstr "" + +msgid "ProjectSettings|Contact an admin to change this setting." +msgstr "" + +msgid "ProjectSettings|Only signed commits can be pushed to this repository." +msgstr "" + +msgid "ProjectSettings|This setting is applied on the server level and can be overridden by an admin." +msgstr "" + +msgid "ProjectSettings|This setting is applied on the server level but has been overridden for this project." +msgstr "" + +msgid "ProjectSettings|This setting will be applied to all projects unless overridden by an admin." +msgstr "" + +msgid "ProjectSettings|Users can only push commits to this repository that were committed with one of their own verified emails." +msgstr "" + +msgid "Projects" +msgstr "" + +msgid "ProjectsDropdown|Frequently visited" +msgstr "" + +msgid "ProjectsDropdown|Loading projects" +msgstr "" + +msgid "ProjectsDropdown|Projects you visit often will appear here" +msgstr "" + +msgid "ProjectsDropdown|Search your projects" +msgstr "" + +msgid "ProjectsDropdown|Something went wrong on our end." +msgstr "" + +msgid "ProjectsDropdown|Sorry, no projects matched your search" +msgstr "" + +msgid "ProjectsDropdown|This feature requires browser localStorage support" +msgstr "" + +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + +msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." +msgstr "" + +msgid "PrometheusService|Finding and configuring metrics..." +msgstr "" + +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + +msgid "PrometheusService|Metrics" +msgstr "" + +msgid "PrometheusService|Metrics are automatically configured and monitored based on a library of metrics from popular exporters." +msgstr "" + +msgid "PrometheusService|Missing environment variable" +msgstr "" + +msgid "PrometheusService|Monitored" +msgstr "" + +msgid "PrometheusService|More information" +msgstr "" + +msgid "PrometheusService|No metrics are being monitored. To start monitoring, deploy to an environment." +msgstr "" + +msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" +msgstr "" + +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + +msgid "PrometheusService|Time-series monitoring service" +msgstr "" + +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + +msgid "PrometheusService|View environments" +msgstr "" + +msgid "Protip:" +msgstr "" + +msgid "Public - The group and any public projects can be viewed without any authentication." +msgstr "" + +msgid "Public - The project can be accessed without any authentication." +msgstr "" + +msgid "Push Rules" +msgstr "" + +msgid "Push events" +msgstr "" + +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + +msgid "PushRule|Committer restriction" +msgstr "" + +msgid "Quick actions can be used in the issues description and comment boxes." +msgstr "" + +msgid "Read more" +msgstr "" + +msgid "Readme" +msgstr "" + +msgid "RefSwitcher|Branches" +msgstr "" + +msgid "RefSwitcher|Tags" +msgstr "" + +msgid "Reference:" +msgstr "" + +msgid "Register / Sign In" +msgstr "" + +msgid "Registry" +msgstr "" + +msgid "Related Commits" +msgstr "" + +msgid "Related Deployed Jobs" +msgstr "" + +msgid "Related Issues" +msgstr "" + +msgid "Related Jobs" +msgstr "" + +msgid "Related Merge Requests" +msgstr "" + +msgid "Related Merged Requests" +msgstr "" + +msgid "Remind later" +msgstr "" + +msgid "Remove" +msgstr "" + +msgid "Remove avatar" +msgstr "" + +msgid "Remove project" +msgstr "" + +msgid "Repair authentication" +msgstr "" + +msgid "Repository" +msgstr "" + +msgid "Repository has no locks." +msgstr "" + +msgid "Request Access" +msgstr "" + +msgid "Reset git storage health information" +msgstr "" + +msgid "Reset health check access token" +msgstr "" + +msgid "Reset runners registration token" +msgstr "" + +msgid "Resolve discussion" +msgstr "" + +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" + +msgid "Revert this commit" +msgstr "" + +msgid "Revert this merge request" +msgstr "" + +msgid "Roadmap" +msgstr "" + +msgid "SSH Keys" +msgstr "" + +msgid "Save changes" +msgstr "" + +msgid "Save pipeline schedule" +msgstr "" + +msgid "Save variables" +msgstr "" + +msgid "Schedule a new pipeline" +msgstr "" + +msgid "Schedules" +msgstr "" + +msgid "Scheduling Pipelines" +msgstr "" + +msgid "Scoped issue boards" +msgstr "" + +msgid "Search branches and tags" +msgstr "" + +msgid "Search milestones" +msgstr "" + +msgid "Search project" +msgstr "" + +msgid "Search users" +msgstr "" + +msgid "Seconds before reseting failure information" +msgstr "" + +msgid "Seconds to wait for a storage access attempt" +msgstr "" + +msgid "Secret variables" +msgstr "" + +msgid "Security report" +msgstr "" + +msgid "Select Archive Format" +msgstr "" + +msgid "Select a timezone" +msgstr "" + +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + +msgid "Select assignee" +msgstr "" + +msgid "Select branch/tag" +msgstr "" + +msgid "Select target branch" +msgstr "" + +msgid "Selective synchronization" +msgstr "" + +msgid "Send email" +msgstr "" + +msgid "Sep" +msgstr "" + +msgid "September" +msgstr "" + +msgid "Server version" +msgstr "" + +msgid "Service Templates" +msgstr "" + +msgid "Service URL" +msgstr "" + +msgid "Set a password on your account to pull or push via %{protocol}." +msgstr "" + +msgid "Set up CI/CD" +msgstr "" + +msgid "Set up Koding" +msgstr "" + +msgid "SetPasswordToCloneLink|set a password" +msgstr "" + +msgid "Settings" +msgstr "" + +msgid "Setup a specific Runner automatically" +msgstr "" + +msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" +msgstr "" + +msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" +msgstr "" + +msgid "Show command" +msgstr "" + +msgid "Show parent pages" +msgstr "" + +msgid "Show parent subgroups" +msgstr "" + +msgid "Showing %d event" +msgid_plural "Showing %d events" +msgstr[0] "" +msgstr[1] "" + +msgid "Sidebar|Change weight" +msgstr "" + +msgid "Sidebar|No" +msgstr "" + +msgid "Sidebar|None" +msgstr "" + +msgid "Sidebar|Weight" +msgstr "" + +msgid "Snippets" +msgstr "" + +msgid "Something went wrong on our end" +msgstr "" + +msgid "Something went wrong on our end." +msgstr "" + +msgid "Something went wrong trying to change the confidentiality of this issue" +msgstr "" + +msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" +msgstr "" + +msgid "Something went wrong when toggling the button" +msgstr "" + +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + +msgid "Something went wrong while fetching the projects." +msgstr "" + +msgid "Something went wrong while fetching the registry list." +msgstr "" + +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + +msgid "Something went wrong. Please try again." +msgstr "" + +msgid "Sort by" +msgstr "" + +msgid "SortOptions|Access level, ascending" +msgstr "" + +msgid "SortOptions|Access level, descending" +msgstr "" + +msgid "SortOptions|Created date" +msgstr "" + +msgid "SortOptions|Due date" +msgstr "" + +msgid "SortOptions|Due later" +msgstr "" + +msgid "SortOptions|Due soon" +msgstr "" + +msgid "SortOptions|Label priority" +msgstr "" + +msgid "SortOptions|Largest group" +msgstr "" + +msgid "SortOptions|Largest repository" +msgstr "" + +msgid "SortOptions|Last created" +msgstr "" + +msgid "SortOptions|Last joined" +msgstr "" + +msgid "SortOptions|Last updated" +msgstr "" + +msgid "SortOptions|Least popular" +msgstr "" + +msgid "SortOptions|Less weight" +msgstr "" + +msgid "SortOptions|Milestone" +msgstr "" + +msgid "SortOptions|Milestone due later" +msgstr "" + +msgid "SortOptions|Milestone due soon" +msgstr "" + +msgid "SortOptions|More weight" +msgstr "" + +msgid "SortOptions|Most popular" +msgstr "" + +msgid "SortOptions|Name" +msgstr "" + +msgid "SortOptions|Name, ascending" +msgstr "" + +msgid "SortOptions|Name, descending" +msgstr "" + +msgid "SortOptions|Oldest created" +msgstr "" + +msgid "SortOptions|Oldest joined" +msgstr "" + +msgid "SortOptions|Oldest sign in" +msgstr "" + +msgid "SortOptions|Oldest updated" +msgstr "" + +msgid "SortOptions|Popularity" +msgstr "" + +msgid "SortOptions|Priority" +msgstr "" + +msgid "SortOptions|Recent sign in" +msgstr "" + +msgid "SortOptions|Start later" +msgstr "" + +msgid "SortOptions|Start soon" +msgstr "" + +msgid "SortOptions|Weight" +msgstr "" + +msgid "Source" +msgstr "" + +msgid "Source (branch or tag)" +msgstr "" + +msgid "Source code" +msgstr "" + +msgid "Source is not available" +msgstr "" + +msgid "Spam Logs" +msgstr "" + +msgid "Specify the following URL during the Runner setup:" +msgstr "" + +msgid "StarProject|Star" +msgstr "" + +msgid "Starred projects" +msgstr "" + +msgid "Start a %{new_merge_request} with these changes" +msgstr "" + +msgid "Start the Runner!" +msgstr "" + +msgid "Stopped" +msgstr "" + +msgid "Storage" +msgstr "" + +msgid "Subgroups" +msgstr "" + +msgid "Switch branch/tag" +msgstr "" + +msgid "System Hooks" +msgstr "" + +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "" +msgstr[1] "" + +msgid "Tags" +msgstr "" + +msgid "TagsPage|Browse commits" +msgstr "" + +msgid "TagsPage|Browse files" +msgstr "" + +msgid "TagsPage|Can't find HEAD commit for this tag" +msgstr "" + +msgid "TagsPage|Cancel" +msgstr "" + +msgid "TagsPage|Create tag" +msgstr "" + +msgid "TagsPage|Delete tag" +msgstr "" + +msgid "TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?" +msgstr "" + +msgid "TagsPage|Edit release notes" +msgstr "" + +msgid "TagsPage|Existing branch name, tag, or commit SHA" +msgstr "" + +msgid "TagsPage|Filter by tag name" +msgstr "" + +msgid "TagsPage|New Tag" +msgstr "" + +msgid "TagsPage|New tag" +msgstr "" + +msgid "TagsPage|Optionally, add a message to the tag." +msgstr "" + +msgid "TagsPage|Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page." +msgstr "" + +msgid "TagsPage|Release notes" +msgstr "" + +msgid "TagsPage|Repository has no tags yet." +msgstr "" + +msgid "TagsPage|Sort by" +msgstr "" + +msgid "TagsPage|Tags" +msgstr "" + +msgid "TagsPage|Tags give the ability to mark specific points in history as being important" +msgstr "" + +msgid "TagsPage|This tag has no release notes." +msgstr "" + +msgid "TagsPage|Use git tag command to add a new one:" +msgstr "" + +msgid "TagsPage|Write your release notes or drag files here..." +msgstr "" + +msgid "TagsPage|protected" +msgstr "" + +msgid "Target Branch" +msgstr "" + +msgid "Team" +msgstr "" + +msgid "Thanks! Don't show me this again" +msgstr "" + +msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project." +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." +msgstr "" + +msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." +msgstr "" + +msgid "The collection of events added to the data gathered for that stage." +msgstr "" + +msgid "The fork relationship has been removed." +msgstr "" + +msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." +msgstr "" + +msgid "The maximum file size allowed is 200KB." +msgstr "" + +msgid "The number of attempts GitLab will make to access a storage." +msgstr "" + +msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." +msgstr "" + +msgid "The phase of the development lifecycle." +msgstr "" + +msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." +msgstr "" + +msgid "The production stage shows the total time it takes between creating an issue and deploying the code to production. The data will be automatically added once you have completed the full idea to production cycle." +msgstr "" + +msgid "The project can be accessed by any logged in user." +msgstr "" + +msgid "The project can be accessed without any authentication." +msgstr "" + +msgid "The repository for this project does not exist." +msgstr "" + +msgid "The repository for this project is empty" +msgstr "" + +msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." +msgstr "" + +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + +msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." +msgstr "" + +msgid "The testing stage shows the time GitLab CI takes to run every pipeline for the related merge request. The data will automatically be added after your first pipeline finishes running." +msgstr "" + +msgid "The time in seconds GitLab will keep failure information. When no failures occur during this time, information about the mount is reset." +msgstr "" + +msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." +msgstr "" + +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + +msgid "The time taken by each data entry gathered by that stage." +msgstr "" + +msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." +msgstr "" + +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + +msgid "There are problems accessing Git storage: " +msgstr "" + +msgid "There was an error loading users activity calendar." +msgstr "" + +msgid "There was an error saving your notification settings." +msgstr "" + +msgid "There was an error subscribing to this label." +msgstr "" + +msgid "There was an error when reseting email token." +msgstr "" + +msgid "There was an error when subscribing to this label." +msgstr "" + +msgid "There was an error when unsubscribing from this label." +msgstr "" + +msgid "This board\\'s scope is reduced" +msgstr "" + +msgid "This directory" +msgstr "" + +msgid "This is a confidential issue." +msgstr "" + +msgid "This is the author's first Merge Request to this project." +msgstr "" + +msgid "This issue is confidential" +msgstr "" + +msgid "This issue is confidential and locked." +msgstr "" + +msgid "This issue is locked." +msgstr "" + +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + +msgid "This means you can not push code until you create an empty repository or import existing one." +msgstr "" + +msgid "This merge request is locked." +msgstr "" + +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + +msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here." +msgstr "" + +msgid "Time before an issue gets scheduled" +msgstr "" + +msgid "Time before an issue starts implementation" +msgstr "" + +msgid "Time between merge request creation and merge/close" +msgstr "" + +msgid "Time tracking" +msgstr "" + +msgid "Time until first merge request" +msgstr "" + +msgid "TimeTrackingEstimated|Est" +msgstr "" + +msgid "TimeTracking|Estimated:" +msgstr "" + +msgid "TimeTracking|Spent" +msgstr "" + +msgid "Timeago|%s days ago" +msgstr "" + +msgid "Timeago|%s days remaining" +msgstr "" + +msgid "Timeago|%s hours remaining" +msgstr "" + +msgid "Timeago|%s minutes ago" +msgstr "" + +msgid "Timeago|%s minutes remaining" +msgstr "" + +msgid "Timeago|%s months ago" +msgstr "" + +msgid "Timeago|%s months remaining" +msgstr "" + +msgid "Timeago|%s seconds remaining" +msgstr "" + +msgid "Timeago|%s weeks ago" +msgstr "" + +msgid "Timeago|%s weeks remaining" +msgstr "" + +msgid "Timeago|%s years ago" +msgstr "" + +msgid "Timeago|%s years remaining" +msgstr "" + +msgid "Timeago|1 day remaining" +msgstr "" + +msgid "Timeago|1 hour remaining" +msgstr "" + +msgid "Timeago|1 minute remaining" +msgstr "" + +msgid "Timeago|1 month remaining" +msgstr "" + +msgid "Timeago|1 week remaining" +msgstr "" + +msgid "Timeago|1 year remaining" +msgstr "" + +msgid "Timeago|Past due" +msgstr "" + +msgid "Timeago|a day ago" +msgstr "" + +msgid "Timeago|a month ago" +msgstr "" + +msgid "Timeago|a week ago" +msgstr "" + +msgid "Timeago|a year ago" +msgstr "" + +msgid "Timeago|about %s hours ago" +msgstr "" + +msgid "Timeago|about a minute ago" +msgstr "" + +msgid "Timeago|about an hour ago" +msgstr "" + +msgid "Timeago|in %s days" +msgstr "" + +msgid "Timeago|in %s hours" +msgstr "" + +msgid "Timeago|in %s minutes" +msgstr "" + +msgid "Timeago|in %s months" +msgstr "" + +msgid "Timeago|in %s seconds" +msgstr "" + +msgid "Timeago|in %s weeks" +msgstr "" + +msgid "Timeago|in %s years" +msgstr "" + +msgid "Timeago|in 1 day" +msgstr "" + +msgid "Timeago|in 1 hour" +msgstr "" + +msgid "Timeago|in 1 minute" +msgstr "" + +msgid "Timeago|in 1 month" +msgstr "" + +msgid "Timeago|in 1 week" +msgstr "" + +msgid "Timeago|in 1 year" +msgstr "" + +msgid "Timeago|in a while" +msgstr "" + +msgid "Timeago|less than a minute ago" +msgstr "" + +msgid "Time|hr" +msgid_plural "Time|hrs" +msgstr[0] "" +msgstr[1] "" + +msgid "Time|min" +msgid_plural "Time|mins" +msgstr[0] "" +msgstr[1] "" + +msgid "Time|s" +msgstr "" + +msgid "Tip:" +msgstr "" + +msgid "Title" +msgstr "" + +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + +msgid "Todo" +msgstr "" + +msgid "Toggle sidebar" +msgstr "" + +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + +msgid "Total Time" +msgstr "" + +msgid "Total test time for all commits/merges" +msgstr "" + +msgid "Total: %{total}" +msgstr "" + +msgid "Track activity with Contribution Analytics." +msgstr "" + +msgid "Track groups of issues that share a theme, across projects and milestones" +msgstr "" + +msgid "Track time with quick actions" +msgstr "" + +msgid "Trigger this manual action" +msgstr "" + +msgid "Turn on Service Desk" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + +msgid "Unknown" +msgstr "" + +msgid "Unlock" +msgstr "" + +msgid "Unlock this %{issuableDisplayName}? Everyone will be able to comment." +msgstr "" + +msgid "Unlocked" +msgstr "" + +msgid "Unresolve discussion" +msgstr "" + +msgid "Unstar" +msgstr "" + +msgid "Up to date" +msgstr "" + +msgid "Upgrade your plan to activate Advanced Global Search." +msgstr "" + +msgid "Upgrade your plan to activate Contribution Analytics." +msgstr "" + +msgid "Upgrade your plan to activate Group Webhooks." +msgstr "" + +msgid "Upgrade your plan to activate Issue weight." +msgstr "" + +msgid "Upgrade your plan to improve Issue boards." +msgstr "" + +msgid "Upload New File" +msgstr "" + +msgid "Upload file" +msgstr "" + +msgid "Upload new avatar" +msgstr "" + +msgid "UploadLink|click to upload" +msgstr "" + +msgid "Use Service Desk to connect with your users (e.g. to offer customer support) through email right inside GitLab" +msgstr "" + +msgid "Use the following registration token during setup:" +msgstr "" + +msgid "Use your global notification setting" +msgstr "" + +msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." +msgstr "" + +msgid "View epics list" +msgstr "" + +msgid "View file @ " +msgstr "" + +msgid "View labels" +msgstr "" + +msgid "View open merge request" +msgstr "" + +msgid "View replaced file @ " +msgstr "" + +msgid "VisibilityLevel|Internal" +msgstr "" + +msgid "VisibilityLevel|Private" +msgstr "" + +msgid "VisibilityLevel|Public" +msgstr "" + +msgid "VisibilityLevel|Unknown" +msgstr "" + +msgid "Want to see the data? Please ask an administrator for access." +msgstr "" + +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + +msgid "We don't have enough data to show this stage." +msgstr "" + +msgid "We want to be sure it is you, please confirm you are not a robot." +msgstr "" + +msgid "Web IDE" +msgstr "" + +msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." +msgstr "" + +msgid "Weight" +msgstr "" + +msgid "Wiki" +msgstr "" + +msgid "WikiClone|Clone your wiki" +msgstr "" + +msgid "WikiClone|Git Access" +msgstr "" + +msgid "WikiClone|Install Gollum" +msgstr "" + +msgid "WikiClone|It is recommended to install %{markdown} so that GFM features render locally:" +msgstr "" + +msgid "WikiClone|Start Gollum and edit locally" +msgstr "" + +msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." +msgstr "" + +msgid "WikiEdit|There is already a page with the same title in that path." +msgstr "" + +msgid "WikiEmptyPageError|You are not allowed to create wiki pages" +msgstr "" + +msgid "WikiHistoricalPage|This is an old version of this page." +msgstr "" + +msgid "WikiHistoricalPage|You can view the %{most_recent_link} or browse the %{history_link}." +msgstr "" + +msgid "WikiHistoricalPage|history" +msgstr "" + +msgid "WikiHistoricalPage|most recent version" +msgstr "" + +msgid "WikiMarkdownDocs|More examples are in the %{docs_link}" +msgstr "" + +msgid "WikiMarkdownDocs|documentation" +msgstr "" + +msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}" +msgstr "" + +msgid "WikiNewPagePlaceholder|how-to-setup" +msgstr "" + +msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories." +msgstr "" + +msgid "WikiNewPageTitle|New Wiki Page" +msgstr "" + +msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?" +msgstr "" + +msgid "WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs." +msgstr "" + +msgid "WikiPageConflictMessage|the page" +msgstr "" + +msgid "WikiPageCreate|Create %{page_title}" +msgstr "" + +msgid "WikiPageEdit|Update %{page_title}" +msgstr "" + +msgid "WikiPage|Page slug" +msgstr "" + +msgid "WikiPage|Write your content or drag files here..." +msgstr "" + +msgid "Wiki|Create Page" +msgstr "" + +msgid "Wiki|Create page" +msgstr "" + +msgid "Wiki|Edit Page" +msgstr "" + +msgid "Wiki|Empty page" +msgstr "" + +msgid "Wiki|More Pages" +msgstr "" + +msgid "Wiki|New page" +msgstr "" + +msgid "Wiki|Page history" +msgstr "" + +msgid "Wiki|Page version" +msgstr "" + +msgid "Wiki|Pages" +msgstr "" + +msgid "Wiki|Wiki Pages" +msgstr "" + +msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members." +msgstr "" + +msgid "Withdraw Access Request" +msgstr "" + +msgid "Write a commit message..." +msgstr "" + +msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You are going to remove %{project_name_with_namespace}. Removed project CANNOT be restored! Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You are going to remove the fork relationship to source project %{forked_from_project}. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" +msgstr "" + +msgid "You can also create a project from the command line." +msgstr "" + +msgid "You can also star a label to make it a priority label." +msgstr "" + +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" +msgstr "" + +msgid "You can move around the graph by using the arrow keys." +msgstr "" + +msgid "You can only add files when you are on a branch" +msgstr "" + +msgid "You can only edit files when you are on a branch" +msgstr "" + +msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." +msgstr "" + +msgid "You cannot write to this read-only GitLab instance." +msgstr "" + +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + +msgid "You have reached your project limit" +msgstr "" + +msgid "You must have master access to force delete a lock" +msgstr "" + +msgid "You must sign in to star a project" +msgstr "" + +msgid "You need a different license to enable FileLocks feature" +msgstr "" + +msgid "You need permission." +msgstr "" + +msgid "You will not get any notifications via email" +msgstr "" + +msgid "You will only receive notifications for the events you choose" +msgstr "" + +msgid "You will only receive notifications for threads you have participated in" +msgstr "" + +msgid "You will receive notifications for any activity" +msgstr "" + +msgid "You will receive notifications only for comments in which you were @mentioned" +msgstr "" + +msgid "You won't be able to pull or push project code via %{protocol} until you %{set_password_link} on your account" +msgstr "" + +msgid "You won't be able to pull or push project code via SSH until you %{add_ssh_key_link} to your profile" +msgstr "" + +msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" +msgstr "" + +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + +msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "" + +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + +msgid "Your comment will not be visible to the public." +msgstr "" + +msgid "Your groups" +msgstr "" + +msgid "Your name" +msgstr "" + +msgid "Your projects" +msgstr "" + +msgid "assign yourself" +msgstr "" + +msgid "branch name" +msgstr "" + +msgid "by" +msgstr "" + +msgid "ciReport|Code quality" +msgstr "" + +msgid "ciReport|DAST detected no alerts by analyzing the review app" +msgstr "" + +msgid "ciReport|Failed to load %{reportName} report" +msgstr "" + +msgid "ciReport|Fixed:" +msgstr "" + +msgid "ciReport|Instances" +msgstr "" + +msgid "ciReport|Learn more about whitelisting" +msgstr "" + +msgid "ciReport|Loading %{reportName} report" +msgstr "" + +msgid "ciReport|No changes to code quality" +msgstr "" + +msgid "ciReport|No changes to performance metrics" +msgstr "" + +msgid "ciReport|Performance metrics" +msgstr "" + +msgid "ciReport|SAST" +msgstr "" + +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST detected no security vulnerabilities" +msgstr "" + +msgid "ciReport|SAST:container no vulnerabilities were found" +msgstr "" + +msgid "ciReport|Show complete code vulnerabilities report" +msgstr "" + +msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" +msgstr "" + +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + +msgid "commit" +msgstr "" + +msgid "confidentiality|You are going to turn off the confidentiality. This means everyone will be able to see and leave a comment on this issue." +msgstr "" + +msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with at least Reporter access are able to see and leave comments on the issue." +msgstr "" + +msgid "day" +msgid_plural "days" +msgstr[0] "" +msgstr[1] "" + +msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." +msgstr "" + +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Check out branch" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Closes" +msgstr "" + +msgid "mrWidget|Did not close" +msgstr "" + +msgid "mrWidget|Email patches" +msgstr "" + +msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + +msgid "mrWidget|Mentions" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Plain diff" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Remove your approval" +msgstr "" + +msgid "mrWidget|Request to merge" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can merge this merge request manually using the" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|branch does not exist." +msgstr "" + +msgid "mrWidget|command line" +msgstr "" + +msgid "mrWidget|into" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + +msgid "new merge request" +msgstr "" + +msgid "notification emails" +msgstr "" + +msgid "or" +msgstr "" + +msgid "parent" +msgid_plural "parents" +msgstr[0] "" +msgstr[1] "" + +msgid "password" +msgstr "" + +msgid "personal access token" +msgstr "" + +msgid "remove due date" +msgstr "" + +msgid "source" +msgstr "" + +msgid "spendCommand|%{slash_command} will update the sum of the time spent." +msgstr "" + +msgid "to help your contributors communicate effectively!" +msgstr "" + +msgid "username" +msgstr "" + +msgid "uses Kubernetes clusters to deploy your code!" +msgstr "" + +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po index f775a511780..44dbbe8141b 100644 --- a/locale/uk/gitlab.po +++ b/locale/uk/gitlab.po @@ -2,15 +2,15 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 06:15-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:20-0500\n" "Last-Translator: gitlab \n" "Language-Team: Ukrainian\n" "Language: uk_UA\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n" "X-Generator: crowdin.com\n" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: uk\n" @@ -24,36 +24,45 @@ msgid_plural "%d commits" msgstr[0] "%d коміт" msgstr[1] "%d коміта" msgstr[2] "%d комітів" +msgstr[3] "%d комітів" msgid "%d commit behind" msgid_plural "%d commits behind" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "%d коміт позаду" +msgstr[1] "%d коміта позаду" +msgstr[2] "%d комітів позаду" +msgstr[3] "%d комітів позаду" msgid "%d issue" msgid_plural "%d issues" msgstr[0] "%d проблема" msgstr[1] "%d проблеми" msgstr[2] "%d проблем" +msgstr[3] "%d проблем" msgid "%d layer" msgid_plural "%d layers" msgstr[0] "%d шар" msgstr[1] "%d шари" msgstr[2] "%d шарів" +msgstr[3] "%d шарів" msgid "%d merge request" msgid_plural "%d merge requests" msgstr[0] "%d запит на злиття" msgstr[1] "%d запита на злиття" msgstr[2] "%d запитів на злиття" +msgstr[3] "%d запитів на злиття" msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "%s доданий коміт був виключений для запобігання проблем із швидкодією." msgstr[1] "%s доданих коміта були виключені для запобігання проблем із швидкодією." msgstr[2] "%s доданих комітів були виключені для запобігання проблем із швидкодією." +msgstr[3] "%s доданих комітів були виключені для запобігання проблем із швидкодією." + +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "%{actionText} і %{openOrClose} %{noteable}" msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "%{commit_author_link} закомітив %{commit_timeago}" @@ -63,6 +72,10 @@ msgid_plural "%{count} participants" msgstr[0] "%{count} участник" msgstr[1] "%{count} участника" msgstr[2] "%{count} участників" +msgstr[3] "%{count} участників" + +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "%{lock_path} заблоковано користувачем GitLab %{lock_user_id}" msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "на %{number_commits_behind} комітів позаду %{default_branch}, на %{number_commits_ahead} комітів попереду" @@ -73,11 +86,15 @@ msgstr "%{number_of_failures} від %{maximum_failures} невдач. GitLab н msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "%{number_of_failures} від %{maximum_failures} невдач. GitLab автоматично не повторюватиме спробу. Скиньте інформацію сховища при усуненні проблеми." +msgid "%{openOrClose} %{noteable}" +msgstr "%{openOrClose} %{noteable}" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "%{storage_name}: спроба невдалого доступу до сховища на хості:" msgstr[1] "%{storage_name}: %{failed_attempts} невдалі спроби доступу до сховища:" msgstr[2] "%{storage_name}: %{failed_attempts} невдалих спроб доступу до сховища:" +msgstr[3] "%{storage_name}: %{failed_attempts} невдалих спроб доступу до сховища:" msgid "%{text} is available" msgstr "%{text} доступний" @@ -96,6 +113,7 @@ msgid_plural "%d pipelines" msgstr[0] "1 конвеєр" msgstr[1] "%d конвеєра" msgstr[2] "%d конвеєрів" +msgstr[3] "%d конвеєрів" msgid "1st contribution!" msgstr "Перший внесок!" @@ -139,9 +157,15 @@ msgstr "Додати керівництво для учасників" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "Додайте групові веб-гуки та GitLab Enterprise Edition." +msgid "Add Kubernetes cluster" +msgstr "Додати Kubernetes-кластер" + msgid "Add License" msgstr "Додати ліцензію" +msgid "Add Readme" +msgstr "Додати інструкцію" + msgid "Add new directory" msgstr "Додати новий каталог" @@ -160,12 +184,45 @@ msgstr "Зупинити завдання" msgid "AdminArea|Stopping jobs failed" msgstr "Зупинка завдань пройшла невдало" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." -msgstr "" +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." +msgstr "Зараз ви зупинете всі завдання. Це обірве усі запущені завдання." msgid "AdminHealthPageLink|health page" msgstr "сторінка статусу" +msgid "AdminProjects|Delete" +msgstr "Видалити" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "Видалити проект %{projectName}?" + +msgid "AdminProjects|Delete project" +msgstr "Видалити проект" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "Вкажіть домен, який буде використовуватися в проекті за замовчуванням для стадій Auto Review Apps і Auto Deploy." + +msgid "AdminUsers|Block user" +msgstr "Заблоквати користувача" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "Видалити користувача %{username} та його внески?" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "Видалити користувача %{username}?" + +msgid "AdminUsers|Delete user" +msgstr "Видалити користувача" + +msgid "AdminUsers|Delete user and contributions" +msgstr "Видалити користувача і його внески" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "Для підтвердження введіть %{projectName}" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "Для підтвердження введіть %{username}" + msgid "Advanced" msgstr "Розширений" @@ -176,13 +233,13 @@ msgid "All" msgstr "Всі" msgid "All changes are committed" -msgstr "" +msgstr "Всі зміни закомічені" msgid "Allows you to add and manage Kubernetes clusters." -msgstr "" +msgstr "Дозволяє додавати та керувати кластерами Kubernetes." msgid "An error occurred previewing the blob" -msgstr "" +msgstr "Сталася помилка під час попереднього перегляду об'єкта" msgid "An error occurred when toggling the notification subscription" msgstr "Виникла помилка під час зміни підписки на сповіщення" @@ -190,36 +247,72 @@ msgstr "Виникла помилка під час зміни підписки msgid "An error occurred when updating the issue weight" msgstr "Збій під час оновлення ваги проблеми" +msgid "An error occurred while adding approver" +msgstr "Помилка при додаванні учасника для затвердження" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" msgid "An error occurred while fetching markdown preview" -msgstr "" +msgstr "Сталася помилка при попередньому перегляді markdown" msgid "An error occurred while fetching sidebar data" msgstr "Виникла помилка під час завантаження даних для бічної панелі" +msgid "An error occurred while fetching the pipeline." +msgstr "Помилка при отриманні данних конвеєра." + msgid "An error occurred while getting projects" -msgstr "" +msgstr "Сталася помилка при отриманні проектів" + +msgid "An error occurred while importing project" +msgstr "Помилка при імпорті проекту" + +msgid "An error occurred while initializing path locks" +msgstr "Помилка при ініціалізації блокування файлових шляхів" + +msgid "An error occurred while loading commits" +msgstr "Помилка при завантажені комітів" + +msgid "An error occurred while loading diff" +msgstr "Помилка при завантаженні разниці (diff)" msgid "An error occurred while loading filenames" -msgstr "" +msgstr "Сталася помилка при завантаженні файлів" + +msgid "An error occurred while loading the file" +msgstr "Помилка при завантаженні файлу" + +msgid "An error occurred while making the request." +msgstr "Помилка при створенні запиту." + +msgid "An error occurred while removing approver" +msgstr "Помилка при видаленні учасника для затвердження" msgid "An error occurred while rendering KaTeX" -msgstr "" +msgstr "Сталася помилка при рендерингу KaTeX" msgid "An error occurred while rendering preview broadcast message" msgstr "" msgid "An error occurred while retrieving calendar activity" -msgstr "" +msgstr "Сталася помилка при отриманні календаря активності" msgid "An error occurred while retrieving diff" +msgstr "Сталася помилка при отриманні різниці" + +msgid "An error occurred while saving LDAP override status. Please try again." msgstr "" -msgid "An error occurred while validating username" +msgid "An error occurred while saving assignees" msgstr "" +msgid "An error occurred while validating username" +msgstr "Сталася помилка під час перевірки імені користувача" + msgid "An error occurred. Please try again." msgstr "Сталась помилка. Спробуйте ще раз." @@ -241,15 +334,15 @@ msgstr "Заархівований проект! Репозиторій дост msgid "Are you sure you want to delete this pipeline schedule?" msgstr "Ви впевнені, що хочете видалити цей розклад для конвеєра?" -msgid "Are you sure you want to discard your changes?" -msgstr "Ви впевнені, що бажаєте скасувати ваші зміни?" - msgid "Are you sure you want to reset registration token?" msgstr "Ви впевнені, що бажаєте скинути реєстраційний токен?" msgid "Are you sure you want to reset the health check token?" msgstr "Ви впевнені, що хочете скинути цей ключ перевірки працездатності?" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "Ви впевнені, що хочете розблокувати %{path_lock_path}?" + msgid "Are you sure?" msgstr "Ви впевнені?" @@ -289,11 +382,14 @@ msgstr "Автор" msgid "Authors: %{authors}" msgstr "Автори: %{authors}" +msgid "Auto DevOps enabled" +msgstr "Auto DevOps увімкнено" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." -msgstr "" +msgstr "Для коректної роботи Auto Review Apps та Auto Deploy необхіден %{kubernetes}." msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." -msgstr "" +msgstr "Для коректної роботи Auto Review Apps та Auto Deploy необхідно вказати доменне ім’я та %{kubernetes}." msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "Для коректної роботи Auto Review Apps та Auto Deploy необхідно вказати доменне ім’я." @@ -313,8 +409,14 @@ msgstr "AutoDevOps буде автоматично збирати, тестув msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "Дізнайтеся більше в %{link_to_documentation}" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." -msgstr "Ви можете активувати %{link_to_settings} для цього проекту." +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "Ви можете автоматично збирати й тестувати ваш застосунок, якщо %{link_to_auto_devops_settings} для цього проекту. Ви також можете його автоматично розгортати, якщо %{link_to_add_kubernetes_cluster}." + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "додати Kubernetes-кластер" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" +msgstr "Увімкнути Auto DevOps (Beta)" msgid "Available" msgstr "Доступний" @@ -325,6 +427,9 @@ msgstr "Аватар буде видалено. Ви впевнені?" msgid "Average per day: %{average}" msgstr "В середньому за день: %{average}" +msgid "Begin with the selected commit" +msgstr "Почати із виділеного коміту" + msgid "Billing" msgstr "Білінг" @@ -379,14 +484,12 @@ msgstr "Оплачується щорічно %{price_per_year}" msgid "BillingPlans|per user" msgstr "за користувача" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "Гілка" -msgstr[1] "Гілки" -msgstr[2] "Гілок" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "Гілка (%{branch_count})" +msgstr[1] "Гілки (%{branch_count})" +msgstr[2] "Гілок (%{branch_count})" +msgstr[3] "Гілок (%{branch_count})" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "Гілка %{branch_name} створена. Для настройки автоматичного розгортання виберіть GitLab CI Yaml-шаблон і закомітьте зміни. %{link_to_autodeploy_doc}" @@ -491,7 +594,7 @@ msgid "Branches|project settings" msgstr "Налаштуваннях проекту" msgid "Branches|protected" -msgstr "захищені" +msgstr "захищена" msgid "Browse Directory" msgstr "Переглянути каталог" @@ -524,7 +627,7 @@ msgid "Cancel edit" msgstr "Відмінити правку" msgid "Cannot modify managed Kubernetes cluster" -msgstr "" +msgstr "Неможливо змінити керований кластер Kubernetes" msgid "Change Weight" msgstr "Вага зміни" @@ -542,7 +645,7 @@ msgid "ChangeTypeAction|Revert" msgstr "Анулювати коміт" msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." -msgstr "" +msgstr "Це створить новий коміт, щоб анулювати існуючі зміни." msgid "Changelog" msgstr "Список змін" @@ -575,16 +678,16 @@ msgid "Choose File ..." msgstr "Виберіть файл ..." msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." -msgstr "" +msgstr "Виберіть гілку чи тег (напр. %{master}) або введіть коміт (напр. %{sha}) для перегляду змін або для створення запиту на злиття." msgid "Choose file..." msgstr "Виберіть файл..." msgid "Choose which groups you wish to synchronize to this secondary node." -msgstr "" +msgstr "Виберіть групи для синхронізації на цей вторинний вузол." msgid "Choose which shards you wish to synchronize to this secondary node." -msgstr "" +msgstr "Виберіть сегменти для синхронізації на цей вторинний вузол." msgid "CiStatusLabel|canceled" msgstr "скасовано" @@ -647,7 +750,7 @@ msgid "CiVariables|Input variable value" msgstr "Значення вхідної змінної" msgid "CiVariables|Remove variable row" -msgstr "" +msgstr "Видалити рядок змінних" msgid "CiVariable|* (All environments)" msgstr "* (Всі середовища)" @@ -656,10 +759,10 @@ msgid "CiVariable|All environments" msgstr "Всі середовища" msgid "CiVariable|Create wildcard" -msgstr "" +msgstr "Створити шаблон" msgid "CiVariable|Error occured while saving variables" -msgstr "" +msgstr "Помилка при збереженні змінних" msgid "CiVariable|New environment" msgstr "Нове середовище" @@ -668,10 +771,10 @@ msgid "CiVariable|Protected" msgstr "Захищений" msgid "CiVariable|Search environments" -msgstr "" +msgstr "Пошук середовищ" msgid "CiVariable|Toggle protected" -msgstr "" +msgstr "Ввімкнути/вимкнути захист" msgid "CiVariable|Validation failed" msgstr "Перевірка невдала" @@ -679,6 +782,9 @@ msgstr "Перевірка невдала" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "circuitbreaker api" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "Натисніть кнопку нижче, щоб розпочати процес встановлення, перейшовши на сторінку Kubernetes" + msgid "Click to expand text" msgstr "Натисніть, щоб розгорнути текст" @@ -733,6 +839,9 @@ msgstr "Скопіювати URL API" msgid "ClusterIntegration|Copy CA Certificate" msgstr "Скопіювати сертифікат центру сертифікації" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "Копіювати IP-адресу Ingress в буфер обміну" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "Скопіювати ім’я Kubernetes-кластера" @@ -781,6 +890,9 @@ msgstr "Helm Tiller" msgid "ClusterIntegration|Ingress" msgstr "Ingress" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "Ingress IP-адреса" + msgid "ClusterIntegration|Install" msgstr "Встановити" @@ -791,7 +903,7 @@ msgid "ClusterIntegration|Installing" msgstr "Встановлення" msgid "ClusterIntegration|Integrate Kubernetes cluster automation" -msgstr "" +msgstr "Інтеграція кластерної автоматизації Kubernetes" msgid "ClusterIntegration|Integration status" msgstr "Статус інтеграції" @@ -812,29 +924,26 @@ msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this pro msgstr "Інтеграція із Kubernetes-кластером увімкнена для цього проекту." msgid "ClusterIntegration|Kubernetes cluster integration is enabled for this project. Disabling this integration will not affect your Kubernetes cluster, it will only temporarily turn off GitLab's connection to it." -msgstr "" +msgstr "Інтеграція із Kubernetes-кластером увімкнена для цього проекту. Вимкнення цієї інтеграції не вплине на кластер, а лише тимчасово перерве з’єднання з GitLab." msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..." -msgstr "" +msgstr "Kubernetes-кластер створюється на Google Kubernetes Engine..." msgid "ClusterIntegration|Kubernetes cluster name" msgstr "Ім’я Kubernetes-кластера" msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details" -msgstr "" +msgstr "Kubernetes-кластер був успішно створений на Google Kubernetes Engine. Оновіть сторінку, щоб побачити параметри кластера" msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" -msgstr "" +msgstr "Kubernetes-кластери дозволяють вам використовувати Review Apps, розгортати ваші застосунки, запускати конвеєри і багато іншого простим способом. %{link_to_help_page}" msgid "ClusterIntegration|Kubernetes clusters can be used to deploy applications and to provide Review Apps for this project" -msgstr "" +msgstr "Kubernetes-кластери можуть бути використані для розгортання застосунків і використання Review Apps для цього проекту" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "Дізнайтеся більше про %{link_to_documentation}" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "Дізнайтеся більше про Kubernetes" - msgid "ClusterIntegration|Learn more about environments" msgstr "Дізнайтеся більше про середовища" @@ -842,19 +951,19 @@ msgid "ClusterIntegration|Machine type" msgstr "Тип машини" msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create Kubernetes clusters" -msgstr "" +msgstr "Впевніться, що ваш обліковий запис задовільняє %{link_to_requirements} для створення Kubernetes-кластерів" msgid "ClusterIntegration|Manage" msgstr "Управління" msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}" -msgstr "" +msgstr "Керуйте вашим Kubernetes-кластером за допомогою %{link_gke}" msgid "ClusterIntegration|More information" msgstr "Додаткова інформація" msgid "ClusterIntegration|Multiple Kubernetes clusters are available in GitLab Enterprise Edition Premium and Ultimate" -msgstr "" +msgstr "Декілька Kubernetes-кластерів доступні в GitLab Enterprise Edition Premium та Ultimate" msgid "ClusterIntegration|Note:" msgstr "Примітка:" @@ -863,7 +972,7 @@ msgid "ClusterIntegration|Number of nodes" msgstr "Кількість вузлів" msgid "ClusterIntegration|Please enter access information for your Kubernetes cluster. If you need help, you can read our %{link_to_help_page} on Kubernetes" -msgstr "" +msgstr "Введіть інформацію про доступ до свого Kubernetes-кластера. Якщо вам потрібна допомога, ви можете прочитати наші %{link_to_help_page} про Kubernetes" msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "Будь-ласка впевніться, що ваш обліковий запис Google задовольняє наступним вимогам:" @@ -881,7 +990,7 @@ msgid "ClusterIntegration|Prometheus" msgstr "Prometheus" msgid "ClusterIntegration|Read our %{link_to_help_page} on Kubernetes cluster integration." -msgstr "" +msgstr "Перегляньте нашу %{link_to_help_page} про інтеграцію із Kubernetes." msgid "ClusterIntegration|Remove Kubernetes cluster integration" msgstr "Відалити інтеграцію із Kubernetes-кластером" @@ -890,7 +999,7 @@ msgid "ClusterIntegration|Remove integration" msgstr "Видалити інтеграцію" msgid "ClusterIntegration|Remove this Kubernetes cluster's configuration from this project. This will not delete your actual Kubernetes cluster." -msgstr "" +msgstr "Видалити конфігурацію Kubernetes-кластера для цього проекту. Це не призведе до видалення самого кластера." msgid "ClusterIntegration|Request to begin installing failed" msgstr "Запит про початок встановлення не виконано" @@ -899,7 +1008,7 @@ msgid "ClusterIntegration|Save changes" msgstr "Зберегти зміни" msgid "ClusterIntegration|See and edit the details for your Kubernetes cluster" -msgstr "" +msgstr "Переглянути та редагувати параметри вашого Kubernetes-кластера" msgid "ClusterIntegration|See machine types" msgstr "Переглянути типи машин" @@ -920,13 +1029,13 @@ msgid "ClusterIntegration|Something went wrong on our end." msgstr "Щось пішло не так з нашого боку." msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine" -msgstr "" +msgstr "Помилка при створенні вашого Kubernetes-кластера на Google Kubernetes Engine" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "Під час встановлення %{title} сталася помилка" msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below" -msgstr "" +msgstr "Цей обліковий запис повинен мати наступні права для створення Kubernetes-кластера в %{link_to_container_project}" msgid "ClusterIntegration|Toggle Kubernetes Cluster" msgstr "Увімкнути/вимкнути Kubernetes-кластер" @@ -938,7 +1047,7 @@ msgid "ClusterIntegration|Token" msgstr "Токен" msgid "ClusterIntegration|With a Kubernetes cluster associated to this project, you can use review apps, deploy your applications, run your pipelines, and much more in an easy way." -msgstr "" +msgstr "За допомогою підключеного до цього проекту Kubernetes-кластера, ви можете використовувати Review Apps, розгортати ваші проекти, запускати конвеєри збірки тощо." msgid "ClusterIntegration|Your account must have %{link_to_kubernetes_engine}" msgstr "Ваш обліковий запис повинен мати %{link_to_kubernetes_engine}" @@ -950,7 +1059,7 @@ msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "доступ до Google Kubernetes Engine" msgid "ClusterIntegration|check the pricing here" -msgstr "" +msgstr "переглянути ціни тут" msgid "ClusterIntegration|documentation" msgstr "документації" @@ -970,6 +1079,12 @@ msgstr "правильно налаштований" msgid "Collapse" msgstr "Згорнути" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "Коментарі" @@ -978,6 +1093,14 @@ msgid_plural "Commits" msgstr[0] "Коміт" msgstr[1] "Коміта" msgstr[2] "Комітів" +msgstr[3] "Комітів" + +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "Коміт (%{commit_count})" +msgstr[1] "Коміта (%{commit_count})" +msgstr[2] "Комітів (%{commit_count})" +msgstr[3] "Комітів (%{commit_count})" msgid "Commit Message" msgstr "Коміт-повідомелння" @@ -989,7 +1112,10 @@ msgid "Commit message" msgstr "Коміт-повідомлення" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" -msgstr "" +msgstr "Статистика комітів для %{ref} %{start_time} - %{end_time}" + +msgid "Commit to %{branchName} branch" +msgstr "Закомітити в гілку %{branchName}" msgid "CommitBoxTitle|Commit" msgstr "Коміт" @@ -1004,16 +1130,16 @@ msgid "Commits feed" msgstr "Канал комітів" msgid "Commits per day hour (UTC)" -msgstr "" +msgstr "Комітів по годинам дня (за Грінвічем)" msgid "Commits per day of month" -msgstr "" +msgstr "Комітів по дням місяця" msgid "Commits per weekday" -msgstr "" +msgstr "Комітів по дням тижня" msgid "Commits|An error occurred while fetching merge requests data." -msgstr "" +msgstr "Сталася помилка під час отримання даних запиту на злиття." msgid "Commits|Commit: %{commitText}" msgstr "Коміт: %{commitText}" @@ -1022,7 +1148,7 @@ msgid "Commits|History" msgstr "Історія" msgid "Commits|No related merge requests found" -msgstr "" +msgstr "Не знайдено пов'язаних запитів на злиття" msgid "Committed by" msgstr "Коміт від" @@ -1031,13 +1157,13 @@ msgid "Compare" msgstr "Порівняти" msgid "Compare Git revisions" -msgstr "" +msgstr "Порівняти Git-редакції" msgid "Compare Revisions" msgstr "Порівняння редакцій" msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." -msgstr "" +msgstr "%{source_branch} і %{target_branch} однакові." msgid "CompareBranches|Compare" msgstr "Порівняти" @@ -1046,7 +1172,7 @@ msgid "CompareBranches|Source" msgstr "Джерело" msgid "CompareBranches|Target" -msgstr "" +msgstr "Ціль" msgid "CompareBranches|There isn't anything to compare." msgstr "" @@ -1103,7 +1229,7 @@ msgid "Contribution guide" msgstr "Інструкція для учасників" msgid "Contributors" -msgstr "Контриб’ютори" +msgstr "Учасники" msgid "ContributorsPage|%{startDate} – %{endDate}" msgstr "%{startDate} – %{endDate}" @@ -1132,11 +1258,14 @@ msgstr "Скопіювати URL в буфер обміну" msgid "Copy branch name to clipboard" msgstr "Скопіювати назву гілки в буфер обміну" +msgid "Copy command to clipboard" +msgstr "Скопіювати команду в буфер обміну" + msgid "Copy commit SHA to clipboard" msgstr "Скопіювати ідентифікатор в буфер обміну" msgid "Copy reference to clipboard" -msgstr "" +msgstr "Скопіювати посилання в буфер обміну" msgid "Create" msgstr "Створити" @@ -1144,9 +1273,18 @@ msgstr "Створити" msgid "Create New Directory" msgstr "Створити новий каталог" +msgid "Create a new branch" +msgstr "Створити нову гілку" + +msgid "Create a new branch and merge request" +msgstr "Створити нову гілку і запит на злиття" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "Створіть токен доступу для вашого аккаунта, щоб відправляти та отримувати через %{protocol}." +msgid "Create branch" +msgstr "Створити гілку" + msgid "Create directory" msgstr "Створити каталог" @@ -1160,11 +1298,14 @@ msgid "Create file" msgstr "Створити файл" msgid "Create lists from labels. Issues with that label appear in that list." -msgstr "" +msgstr "Створити список на остнові міток. В ньому будуть проблеми з такими мітками." msgid "Create merge request" msgstr "Створити запит на злиття" +msgid "Create merge request and branch" +msgstr "Створити запит на злиття та гілку" + msgid "Create new branch" msgstr "Створити нову гілку" @@ -1189,6 +1330,12 @@ msgstr "Тег" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "Створити токен для особистого доступу" +msgid "Creates a new branch from %{branchName}" +msgstr "Створює нову гілку із %{branchName}" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "Створює нову гілку із %{branchName} і перенаправляє для створення нового запиту на злиття" + msgid "Creating epic" msgstr "Створення епіку" @@ -1243,6 +1390,9 @@ msgstr "груд." msgid "December" msgstr "грудень" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "Визначте власний шаблон за допомогою синтаксису cron" @@ -1254,6 +1404,7 @@ msgid_plural "Deploys" msgstr[0] "Розгортання" msgstr[1] "Розгортання" msgstr[2] "Розгортань" +msgstr[3] "Розгортань" msgid "Deploy Keys" msgstr "Ключі для розгортування" @@ -1268,7 +1419,7 @@ msgid "Details" msgstr "Деталі" msgid "Diffs|No file name available" -msgstr "" +msgstr "Ім'я файлу не доступне" msgid "Directory name" msgstr "Ім'я каталогу" @@ -1276,11 +1427,11 @@ msgstr "Ім'я каталогу" msgid "Disable" msgstr "Вимкнути" -msgid "Discard changes" -msgstr "Скасувати зміни" +msgid "Discard draft" +msgstr "Видалити чернетку" msgid "Discover GitLab Geo." -msgstr "" +msgstr "Відкрийте GitLab Geo." msgid "Dismiss Cycle Analytics introduction box" msgstr "Відмінити блок вступу до Аналитики Циклу" @@ -1310,7 +1461,7 @@ msgid "DownloadArtifacts|Download" msgstr "Завантажити" msgid "DownloadCommit|Email Patches" -msgstr "Email-патчи" +msgstr "Email-патчі" msgid "DownloadCommit|Plain Diff" msgstr "Просте порівняння (diff)" @@ -1336,6 +1487,9 @@ msgstr "Адреси електронної пошти" msgid "Enable" msgstr "Увімкнути" +msgid "Enable Auto DevOps" +msgstr "Увімкнути Auto DevOps" + msgid "Environments|An error occurred while fetching the environments." msgstr "Виникла помилка при завантаженні середовищ." @@ -1390,14 +1544,23 @@ msgstr "Епік буде видалено! Ви впевнені?" msgid "Epics" msgstr "Епіки" +msgid "Epics Roadmap" +msgstr "План-графік епіків" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "Епіки дозволяють керувати вашим портфелем проектів ефективніше та з меншими зусиллями" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "Помилка при створенні епіку" msgid "Error fetching contributors data." -msgstr "" +msgstr "Помилка отримання даних учасників." msgid "Error fetching labels." msgstr "Помилка завантаження міток." @@ -1415,7 +1578,7 @@ msgid "Error occurred when toggling the notification subscription" msgstr "Сталася помилка під час підключення підписки на сповіщення" msgid "Error saving label update." -msgstr "" +msgstr "Помилка при збереженні мітки." msgid "Error updating status for all todos." msgstr "Помилка оновлення статусу для всіх задач." @@ -1459,12 +1622,36 @@ msgstr "Огляд проектів" msgid "Explore public groups" msgstr "Переглянути публічні групи" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "Невдалі Завдання" + msgid "Failed to change the owner" msgstr "Не вдалося змінити власника" +msgid "Failed to remove issue from board, please try again." +msgstr "Не вдалося видалити проблему з дошки, будь ласка, спробуйте ще раз." + msgid "Failed to remove the pipeline schedule" msgstr "Не вдалося видалити розклад конвеєра" +msgid "Failed to update issues, please try again." +msgstr "Не вдалося оновити проблеми. Будь ласка, спробуйте ще раз." + msgid "Feb" msgstr "лют." @@ -1472,7 +1659,7 @@ msgid "February" msgstr "лютий" msgid "Fields on this page are now uneditable, you can configure" -msgstr "" +msgstr "Поля на цій сторінці зараз недоступні для редагування, ви можете налаштувати" msgid "File name" msgstr "Ім'я файлу" @@ -1480,6 +1667,9 @@ msgstr "Ім'я файлу" msgid "Files" msgstr "Файли" +msgid "Files (%{human_size})" +msgstr "Файли (%{human_size})" + msgid "Filter by commit message" msgstr "Фільтрувати за коміт-повідомленням" @@ -1500,6 +1690,7 @@ msgid_plural "Forks" msgstr[0] "Форк" msgstr[1] "Форки" msgstr[2] "Форків" +msgstr[3] "Форків" msgid "ForkedFromProjectPath|Forked from" msgstr "Форк від" @@ -1516,11 +1707,14 @@ msgstr "З моменту створення проблеми до розгор msgid "From merge request merge until deploy to production" msgstr "Від виконання запиту на злиття до розгортання на production" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "GPG ключі" msgid "Generate a default set of labels" -msgstr "" +msgstr "Створити стандартний набір міткок" msgid "Geo Nodes" msgstr "Гео-Вузли" @@ -1532,13 +1726,13 @@ msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an msgstr "Вузол працює повільно, перевантажений або тільки що відновився після збою." msgid "GeoNodes|Database replication lag:" -msgstr "" +msgstr "Затримка реплікації бази даних:" msgid "GeoNodes|Disabling a node stops the sync process. Are you sure?" -msgstr "" +msgstr "Відключення вузла зупиняє процес синхронізації. Ви впевнені?" msgid "GeoNodes|Does not match the primary storage configuration" -msgstr "" +msgstr "Не відповідає основній конфігурації сховища" msgid "GeoNodes|Failed" msgstr "Невдало" @@ -1547,85 +1741,85 @@ msgid "GeoNodes|Full" msgstr "Повний" msgid "GeoNodes|GitLab version does not match the primary node version" -msgstr "" +msgstr "Версія GitLab не відповідає версії основного вузла" msgid "GeoNodes|GitLab version:" -msgstr "" +msgstr "Версія GitLab:" msgid "GeoNodes|Health status:" -msgstr "" +msgstr "Стан працездатності:" msgid "GeoNodes|Last event ID processed by cursor:" -msgstr "" +msgstr "Ідентифікатор останньої події, обробленої курсором:" msgid "GeoNodes|Last event ID seen from primary:" -msgstr "" +msgstr "Ідентифікатор останньої події видимої із основного:" msgid "GeoNodes|Loading nodes" -msgstr "" +msgstr "Завантаження вузлів" msgid "GeoNodes|Local Attachments:" -msgstr "" +msgstr "Локальні вкладення (attachments):" msgid "GeoNodes|Local LFS objects:" -msgstr "" +msgstr "Локальні LFS-об’єкти:" msgid "GeoNodes|Local job artifacts:" -msgstr "" +msgstr "Артефакти локальних завдань:" msgid "GeoNodes|New node" -msgstr "" +msgstr "Новий вузол" msgid "GeoNodes|Out of sync" -msgstr "" +msgstr "Розсинхронізовані" msgid "GeoNodes|Replication slot WAL:" -msgstr "" +msgstr "Слот реплікації WAL:" msgid "GeoNodes|Replication slots:" -msgstr "" +msgstr "Слоти реплікації:" msgid "GeoNodes|Repositories:" -msgstr "" +msgstr "Репозиторії:" msgid "GeoNodes|Selective" -msgstr "" +msgstr "Вибіркові" msgid "GeoNodes|Storage config:" -msgstr "" +msgstr "Конфігурація сховища:" msgid "GeoNodes|Sync settings:" -msgstr "" +msgstr "Налаштування синхронізації:" msgid "GeoNodes|Synced" -msgstr "" +msgstr "Синхронізовано" msgid "GeoNodes|Unused slots" -msgstr "" +msgstr "Невикористані слоти" msgid "GeoNodes|Used slots" -msgstr "" +msgstr "Використані слоти" msgid "GeoNodes|Wikis:" -msgstr "" +msgstr "Wiki:" msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS." -msgstr "" +msgstr "Ви налаштували Geo-вузли через незахищене HTTP-з’єднання. Ми рекомендуємо використовувати HTTPS." msgid "Geo|All projects" -msgstr "" +msgstr "Всі проекти" msgid "Geo|File sync capacity" msgstr "Пропускна здатність синхронізації файлів" msgid "Geo|Groups to synchronize" -msgstr "" +msgstr "Групи для синхронізації" msgid "Geo|Projects in certain groups" -msgstr "" +msgstr "Проекти в певних групах" msgid "Geo|Projects in certain storage shards" -msgstr "" +msgstr "Проекти в певних сегментах сховищ" msgid "Geo|Repository sync capacity" msgstr "Пропускна здатність синхронізації репозиторіїв" @@ -1634,22 +1828,22 @@ msgid "Geo|Select groups to replicate." msgstr "Виберіть групи для реплікації." msgid "Geo|Shards to synchronize" -msgstr "" +msgstr "Сегменти для синхронізації" msgid "Git revision" -msgstr "" +msgstr "Git-редакція" msgid "Git storage health information has been reset" msgstr "Інформація про статус зберігання Git була скинута" msgid "Git version" -msgstr "" +msgstr "Git-версія" msgid "GitLab Runner section" msgstr "Розділ GitLab Runner" msgid "Gitaly Servers" -msgstr "" +msgstr "Сервери Gitaly" msgid "Go to your fork" msgstr "Перейти до вашого форку" @@ -1661,7 +1855,25 @@ msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab ad msgstr "Аутентифікація Google не %{link_to_documentation}. Попросіть свого адміністратора GitLab, якщо ви хочете скористатися цим сервісом." msgid "Got it!" -msgstr "" +msgstr "Зрозуміло!" + +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "Епіки дозволють вам керувати портфоліо ваших проектів більш ефективно і з меншими зусиллями" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "Від %{dateWord}" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "Завантаження плану-графіку" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "Проблема при завантаженні епіків" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "Для перегляду плану-графіку додайте заплановані дати початку та завершення до одного з ваших епіків у цій групі або її підгрупах. Відображаються лише епіки за попередні та наступні 3 місяці: від %{startDate} до %{endDate}." + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "До %{dateWord}" msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "Заборонити спільний доступ до проекту в рамках %{group} з іншими групами" @@ -1700,7 +1912,7 @@ msgid "GroupsEmptyState|You can manage your group member’s permissions and acc msgstr "Ви можете керувати правами доступу членів групи мати доступ до кожного проекту в ній." msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" -msgstr "" +msgstr "Ви впевнені, що хочете залишити групу \"${group.fullName}\"?" msgid "GroupsTree|Create a project in this group." msgstr "Створити проект у групі." @@ -1752,9 +1964,10 @@ msgstr "Нездоровий" msgid "Hide value" msgid_plural "Hide values" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Сховати значення" +msgstr[1] "Сховати значення" +msgstr[2] "Сховати значень" +msgstr[3] "Сховати значень" msgid "History" msgstr "Історія" @@ -1762,6 +1975,9 @@ msgstr "Історія" msgid "Housekeeping successfully started" msgstr "Очищення успішно розпочато" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "Якщо у вас уже є файли, ви можете відправити їх за допомогою %{link_to_cli} нижче." + msgid "Import repository" msgstr "Імпорт репозиторію" @@ -1774,6 +1990,9 @@ msgstr "Покращити управління проблемами з можл msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "Покращити пошук за допомогою розширеного глобального пошук в версії GitLab Enterprise Edition." +msgid "Install Runner on Kubernetes" +msgstr "Встановити Runner на Kubernetes" + msgid "Install a Runner compatible with GitLab CI" msgstr "Встановіть Runner, сумісний з GitLab CI" @@ -1782,12 +2001,13 @@ msgid_plural "Instances" msgstr[0] "Інстанс" msgstr[1] "Iнстанси" msgstr[2] "Інстансів" +msgstr[3] "Інстансів" msgid "Instance does not support multiple Kubernetes clusters" -msgstr "" +msgstr "Цей інстанс не підтримує декілька Kubernetes-кластерів" msgid "Interested parties can even contribute by pushing commits if they want to." -msgstr "" +msgstr "Зацікавлені сторони за бажанням можуть навіть робити внески шляхом відправлення комітів." msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "Внутрішня — будь-який автентифікований користувач має доступ до цієї групи та усіх її внутрішніх проектів." @@ -1817,7 +2037,7 @@ msgid "Issues" msgstr "Проблеми" msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." -msgstr "" +msgstr "Проблеми можуть бути помилками, завданнями чи ідеями для обговорення. Крім того, проблеми доступні для пошуку та фільтрування." msgid "Jan" msgstr "січ." @@ -1825,6 +2045,9 @@ msgstr "січ." msgid "January" msgstr "січень" +msgid "Jobs" +msgstr "Завдання" + msgid "Jul" msgstr "лип." @@ -1844,16 +2067,19 @@ msgid "Kubernetes Cluster" msgstr "Кластер Kubernetes" msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}" -msgstr "" +msgstr "Перевищення ліміту часу при створенні Kubernetes-кластера; %{timeout}" msgid "Kubernetes cluster integration was not removed." -msgstr "" +msgstr "Інтеграція із Kubernetes-кластером не була видалена." msgid "Kubernetes cluster integration was successfully removed." -msgstr "" +msgstr "Інтеграція із Kubernetes-кластером була успішно видалена." msgid "Kubernetes cluster was successfully updated." -msgstr "" +msgstr "Kubernetes-кластер успішно оновлено." + +msgid "Kubernetes configured" +msgstr "Kubernetes налаштовано" msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1875,6 +2101,7 @@ msgid_plural "Last %d days" msgstr[0] "Останній %d день" msgstr[1] "Останніх %d дні" msgstr[2] "Останніх %d днів" +msgstr[3] "Останніх %d днів" msgid "Last Pipeline" msgstr "Останній Конвеєр" @@ -1903,6 +2130,12 @@ msgstr "в" msgid "Learn more" msgstr "Дізнатися більше" +msgid "Learn more about Kubernetes" +msgstr "Дізнайтеся більше про Kubernetes" + +msgid "Learn more about protected branches" +msgstr "Дізнайтеся більше про захищені гілки" + msgid "Learn more in the" msgstr "Дізнайтесь більше" @@ -1921,6 +2154,9 @@ msgstr "Залишити проект" msgid "License" msgstr "Ліцензія" +msgid "List" +msgstr "Список" + msgid "Loading the GitLab IDE..." msgstr "Завантаження IDE GitLab..." @@ -1930,8 +2166,11 @@ msgstr "Блокування" msgid "Lock %{issuableDisplayName}" msgstr "Заблокувати %{issuableDisplayName}" +msgid "Lock not found" +msgstr "Блокування не знайдено" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." -msgstr "" +msgstr "Заблокувати цю %{issuableDisplayName}? Лише члени проекту зможуть коментувати." msgid "Locked" msgstr "Заблоковано" @@ -1939,11 +2178,14 @@ msgstr "Заблоковано" msgid "Locked Files" msgstr "Заблоковані файли" +msgid "Locks give the ability to lock specific file or folder." +msgstr "Блокування може бути застосоване до конкретного файлу або директорії." + msgid "Login" msgstr "Вхід" msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos." -msgstr "" +msgstr "Зробіть кожного учасника команди більш продуктивним незалежно від його місцезнаходження. GitLab Geo створює копії \"тільки для читання\" вашого GitLab сервера, щоб скоротити час для клонування і отримання коду з великих репозиторіїв." msgid "Manage labels" msgstr "Керувати мітками" @@ -1955,7 +2197,7 @@ msgid "March" msgstr "березень" msgid "Mark done" -msgstr "" +msgstr "Позначити виконаним" msgid "Maximum git storage failures" msgstr "Максимальна кількість невдач в сховищі даних git" @@ -1969,9 +2211,6 @@ msgstr "Медіана" msgid "Members" msgstr "Користувачі" -msgid "Merge Request" -msgstr "Запит на злиття" - msgid "Merge Requests" msgstr "Запити на злиття" @@ -1984,8 +2223,11 @@ msgstr "Запит на злиття" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "Затверджено" + msgid "Merged" -msgstr "" +msgstr "Злиті" msgid "Messages" msgstr "Повідомлення" @@ -2008,9 +2250,18 @@ msgstr "Етап %{milestoneTitle} не знайдено" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "не додасте SSH ключ" +msgid "Modal|Cancel" +msgstr "Скасувати" + +msgid "Modal|Close" +msgstr "Закрити" + msgid "Monitoring" msgstr "Моніторинг" +msgid "More information" +msgstr "Детальніше" + msgid "More information is available|here" msgstr "тут" @@ -2024,19 +2275,20 @@ msgid "Multiple issue boards" msgstr "Кілька дошок обговорення" msgid "Name new label" -msgstr "" +msgstr "Назвіть нову мітку" msgid "New Issue" msgid_plural "New Issues" msgstr[0] "Нова проблема" msgstr[1] "Нові проблеми" msgstr[2] "Нових проблем" +msgstr[3] "Нових проблем" msgid "New Kubernetes Cluster" -msgstr "" +msgstr "Новий Kubernetes-кластер" msgid "New Kubernetes cluster" -msgstr "" +msgstr "Новий Kubernetes-кластер" msgid "New Pipeline Schedule" msgstr "Новий розклад Конвеєра" @@ -2093,10 +2345,10 @@ msgid "No connection could be made to a Gitaly Server, please check your logs!" msgstr "" msgid "No due date" -msgstr "Немає запланованої дати завершення" +msgstr "Немає" msgid "No estimate or time spent" -msgstr "" +msgstr "Немає запланованого або витраченого часу" msgid "No file chosen" msgstr "Файл не вибрано" @@ -2107,14 +2359,11 @@ msgstr "Немає репозиторію" msgid "No schedules" msgstr "немає Розкладів" -msgid "No time spent" -msgstr "Немає витраченого часу" - msgid "None" -msgstr "Жоден" +msgstr "Немає" msgid "Not allowed to merge" -msgstr "" +msgstr "Злиття не допускається" msgid "Not available" msgstr "Недоступний" @@ -2125,6 +2374,9 @@ msgstr "Не конфіденційно" msgid "Not enough data" msgstr "Недостатньо даних" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "Майте на увазі, що гілка master захищена автоматично. %{link_to_protected_branches}" + msgid "Notification events" msgstr "Повідомлення про події" @@ -2227,6 +2479,9 @@ msgstr "Відкривається у новому вікні" msgid "Options" msgstr "Параметри" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "В іншому разі рекомендується почати з одного з наведених нижче варіантів." + msgid "Overview" msgstr "Огляд" @@ -2330,7 +2585,25 @@ msgid "Pipelines|Build with confidence" msgstr "" msgid "Pipelines|Get started with Pipelines" -msgstr "" +msgstr "Початок роботи з Конвеєрами" + +msgid "Pipeline|Retry pipeline" +msgstr "Перезапустити конвеєр" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "Перезапустити конвеєр #%{pipelineId}?" + +msgid "Pipeline|Stop pipeline" +msgstr "Зупинити конвеєр" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "Зупинити конвеєр #%{pipelineId}?" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "Зараз ви перезапустите конвеєр %{pipelineId}." + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "Зараз ви зупинете конвеєр %{pipelineId}." msgid "Pipeline|all" msgstr "всі" @@ -2357,7 +2630,7 @@ msgid "Preferences" msgstr "Налаштування" msgid "Primary" -msgstr "" +msgstr "Головний" msgid "Private - Project access must be granted explicitly to each user." msgstr "Приватний — доступ до проекту повинен надаватися кожному користувачеві." @@ -2365,6 +2638,9 @@ msgstr "Приватний — доступ до проекту повинен msgid "Private - The group and its projects can only be viewed by members." msgstr "Приватна — цю групу та її проекти можуть бачити тільки її користувачі." +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "Профіль" @@ -2405,7 +2681,7 @@ msgid "Profiles|your account" msgstr "ваш обліковий запис" msgid "Programming languages used in this repository" -msgstr "" +msgstr "Мови програмування, що використовується в цьому репозиторії" msgid "Project '%{project_name}' is in the process of being deleted." msgstr "Проект '%{project_name}' перебуває в процесі видалення." @@ -2426,7 +2702,7 @@ msgid "Project avatar" msgstr "Аватар проекту" msgid "Project avatar in repository: %{link}" -msgstr "" +msgstr "Аватар проекту в репозиторії: %{link}" msgid "Project cache successfully reset." msgstr "Кеш проекту успішно скинуто." @@ -2450,13 +2726,13 @@ msgid "ProjectActivityRSS|Subscribe" msgstr "Підписатися" msgid "ProjectCreationLevel|Allowed to create projects" -msgstr "" +msgstr "Дозволено створювати проекти" msgid "ProjectCreationLevel|Default project creation protection" msgstr "" msgid "ProjectCreationLevel|Developers + Masters" -msgstr "" +msgstr "Розробники + Керівники" msgid "ProjectCreationLevel|Masters" msgstr "Керівники" @@ -2527,12 +2803,30 @@ msgstr "На жаль, по вашоу запиту проектів не зна msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "Ця функція потребує підтримки localStorage вашим браузером" +msgid "PrometheusService|Active" +msgstr "Активний" + +msgid "PrometheusService|Auto configuration" +msgstr "Автоматична конфігурація" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "Автоматично розгортайте та налаштовуйте Prometheus на ваші кластери для моніторингу середовищ проекту" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "За замовчуванням, Prometheus запускається за адресою ‘http://localhost:9090’. Не рекомендується змінювати цю адресу і порт, бо це може призвести до конфлікту з іншими сервісами запущеними на GitLab сервері." msgid "PrometheusService|Finding and configuring metrics..." msgstr "Пошук та налаштування метрик..." +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "Встановити Prometheus на кластери" + +msgid "PrometheusService|Manage clusters" +msgstr "Управління кластерами" + +msgid "PrometheusService|Manual configuration" +msgstr "Ручні налаштування" + msgid "PrometheusService|Metrics" msgstr "Метрики" @@ -2554,8 +2848,17 @@ msgstr "Жодні метрики не відслідковуються. Для msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "Базова адреса Prometheus API, наприклад http://prometheus.example.com/" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "Prometheus автоматично налаштований на ваших кластерах" + msgid "PrometheusService|Time-series monitoring service" -msgstr "" +msgstr "Сервіс моніторингу часових рядів" + +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "Для можливості ручного налаштування, видаліть Prometheus із ваших кластерів" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "Для можливості встановлення Prometheus на ваші кластери, деактивуйте ручні налаштування нижче" msgid "PrometheusService|View environments" msgstr "Перегляд середовищ" @@ -2575,11 +2878,17 @@ msgstr "Правила відправлення" msgid "Push events" msgstr "Події відправлення (push)" +msgid "Push project from command line" +msgstr "Виконати push проекту за допомогою командного рядка" + +msgid "Push to create a project" +msgstr "Натисніть, щоб створити проект" + msgid "PushRule|Committer restriction" msgstr "Обмеження для коміттера" msgid "Quick actions can be used in the issues description and comment boxes." -msgstr "" +msgstr "Швидкі дії можна використовувати в описах проблем і коментарях." msgid "Read more" msgstr "Докладніше" @@ -2594,7 +2903,7 @@ msgid "RefSwitcher|Tags" msgstr "Теги" msgid "Reference:" -msgstr "" +msgstr "Посилання:" msgid "Register / Sign In" msgstr "Зареєструватися / Увійти" @@ -2633,11 +2942,14 @@ msgid "Remove project" msgstr "Видалити проект" msgid "Repair authentication" -msgstr "" +msgstr "Відновити аутентифікацію" msgid "Repository" msgstr "Репозиторій" +msgid "Repository has no locks." +msgstr "Репозиторій не має блокувань." + msgid "Request Access" msgstr "Запит доступу" @@ -2650,11 +2962,15 @@ msgstr "Оновити токен доступу для перевірки пр msgid "Reset runners registration token" msgstr "Скинути реєстраційний токен runner-ів" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" +msgstr[0] "Показати значення" +msgstr[1] "Показати значення" +msgstr[2] "Показати значень" +msgstr[3] "Показати значень" msgid "Revert this commit" msgstr "Анулювати цей коміт" @@ -2662,6 +2978,9 @@ msgstr "Анулювати цей коміт" msgid "Revert this merge request" msgstr "Анулювати цей запит на злиття" +msgid "Roadmap" +msgstr "План-графік" + msgid "SSH Keys" msgstr "Ключі SSH" @@ -2672,7 +2991,7 @@ msgid "Save pipeline schedule" msgstr "Зберегти розклад конвеєра" msgid "Save variables" -msgstr "" +msgstr "Зберегти змінні" msgid "Schedule a new pipeline" msgstr "Розклад нового конвеєра" @@ -2693,7 +3012,7 @@ msgid "Search milestones" msgstr "Пошук етапів" msgid "Search project" -msgstr "" +msgstr "Пошук в проекті" msgid "Search users" msgstr "Пошук користувачів" @@ -2705,7 +3024,10 @@ msgid "Seconds to wait for a storage access attempt" msgstr "Кількість секунд очікування перед повторною спробою доступу до сховища даних" msgid "Secret variables" -msgstr "" +msgstr "Секретні змінні" + +msgid "Security report" +msgstr "Звіт по безпеці" msgid "Select Archive Format" msgstr "Виберіть формат архіву" @@ -2713,6 +3035,9 @@ msgstr "Виберіть формат архіву" msgid "Select a timezone" msgstr "Вибрати часовий пояс" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "Виберіть існуючий кластер Kubernetes або створіть новий" + msgid "Select assignee" msgstr "Виберіть виконавця" @@ -2723,7 +3048,10 @@ msgid "Select target branch" msgstr "Вибір цільової гілки" msgid "Selective synchronization" -msgstr "" +msgstr "Вибіркова синхронізація" + +msgid "Send email" +msgstr "Надіслати листа" msgid "Sep" msgstr "вер." @@ -2735,7 +3063,10 @@ msgid "Server version" msgstr "Версія сервера" msgid "Service Templates" -msgstr "Сервіс шаблонів" +msgstr "Шаблони сервісів" + +msgid "Service URL" +msgstr "Сервіс URL" msgid "Set a password on your account to pull or push via %{protocol}." msgstr "Встановіть пароль для свого облікового запису, щоб мати можливість відправляти та отримувати через %{protocol}." @@ -2746,24 +3077,27 @@ msgstr "Налаштування CI/CD" msgid "Set up Koding" msgstr "Налаштування Koding" -msgid "Set up auto deploy" -msgstr "Налаштування автоматичного розгортання" - msgid "SetPasswordToCloneLink|set a password" msgstr "встановити пароль" msgid "Settings" msgstr "Налаштування" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" msgid "SharedRunnersMinutesSettings|Reset pipeline minutes" -msgstr "" +msgstr "Скинути хвилини в конвеєрі" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "Показати команду" + msgid "Show parent pages" msgstr "Показати батьківські сторінки" @@ -2775,6 +3109,7 @@ msgid_plural "Showing %d events" msgstr[0] "Показано %d подію" msgstr[1] "Показано %d події" msgstr[2] "Показано %d подій" +msgstr[3] "Показано %d подій" msgid "Sidebar|Change weight" msgstr "Змінити вагу" @@ -2792,29 +3127,41 @@ msgid "Snippets" msgstr "Сніпети" msgid "Something went wrong on our end" -msgstr "" +msgstr "Щось пішло не так з нашого боку" msgid "Something went wrong on our end." msgstr "Щось пішло не так з нашого боку" msgid "Something went wrong trying to change the confidentiality of this issue" -msgstr "" +msgstr "Помилка при зміні конфіденційності цієї проблеми" msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" msgstr "Щось пішло не так, при спробі зміни стану блокування ${this.issuableDisplayName}" msgid "Something went wrong when toggling the button" +msgstr "Помилка при перемиканні кнопки" + +msgid "Something went wrong while closing the %{issuable}. Please try again later" msgstr "" +msgid "Something went wrong while fetching SAST." +msgstr "Помилка при отриманні SAST." + msgid "Something went wrong while fetching the projects." msgstr "Щось пішло не так під час отримання проектів" msgid "Something went wrong while fetching the registry list." msgstr "Щось пішло не так при отриманні списку із реєстру." -msgid "Something went wrong. Please try again." +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." msgstr "" +msgid "Something went wrong. Please try again." +msgstr "Щось пішло не так. Будь ласка спробуйте ще раз." + msgid "Sort by" msgstr "Сортувати за" @@ -2917,6 +3264,9 @@ msgstr "Вага" msgid "Source" msgstr "Джерело" +msgid "Source (branch or tag)" +msgstr "Джерело (гілка або тег)" + msgid "Source code" msgstr "Код" @@ -2945,7 +3295,7 @@ msgid "Stopped" msgstr "Зупинено" msgid "Storage" -msgstr "" +msgstr "Сховище" msgid "Subgroups" msgstr "Підгрупи" @@ -2956,11 +3306,12 @@ msgstr "Перейти в гілку/тег" msgid "System Hooks" msgstr "Системні гуки" -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "Тег" -msgstr[1] "Теги" -msgstr[2] "Тегів" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "Тег (%{tag_count})" +msgstr[1] "Тега (%{tag_count})" +msgstr[2] "Тегів (%{tag_count})" +msgstr[3] "Тегів (%{tag_count})" msgid "Tags" msgstr "Теги" @@ -3065,7 +3416,7 @@ msgid "The issue stage shows the time it takes from creating an issue to assigni msgstr "Стадія \"Проблема\" показує, скільки часу потрібно від створення проблеми до включення її до якогось етапу, або додавання проблеми на дошку. Почніть створювати проблеми, щоб переглядати дані для цієї стадії." msgid "The maximum file size allowed is 200KB." -msgstr "" +msgstr "Максимальний розмір файлу — 200 Кб." msgid "The number of attempts GitLab will make to access a storage." msgstr "Кількість спроб, які зробить GitLab для отримання доступу до сховища даних." @@ -3091,9 +3442,15 @@ msgstr "Доступ до проекту можливий без будь-яко msgid "The repository for this project does not exist." msgstr "Репозиторій для цього проекту не існує." +msgid "The repository for this project is empty" +msgstr "Репозиторій цього проекту порожній" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "Стадія \"Затвердження\" показує час від створення запиту про об'єднання до його виконання. Дані будуть автоматично додані після завершення першого запиту на злиття." +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "План-графік показує стан ваших епіків у часі" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "Стадія \"Staging\" показує час між виконання запиту на злиття та розгортанням коду у production. Дані автоматично додаються після розгортання у production вперше." @@ -3116,31 +3473,31 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet msgstr "Середнє значення в рядку. Приклад: між 3, 5, 9, середніми 5, між 3, 5, 7, 8, середніми (5 + 7) / 2 = 6." msgid "There are no issues to show" -msgstr "" +msgstr "Немає проблем для відображення" msgid "There are no merge requests to show" -msgstr "" +msgstr "Немає запитів на злиття для відображення" msgid "There are problems accessing Git storage: " msgstr "Є проблеми з доступом до сховища git: " msgid "There was an error loading users activity calendar." -msgstr "" +msgstr "Помилка при завантаженні календаря активності користувачів." msgid "There was an error saving your notification settings." -msgstr "" +msgstr "Помилка при збереженні ваших налаштувань сповіщень." msgid "There was an error subscribing to this label." -msgstr "" +msgstr "Помилка при підписці на цю мітку." msgid "There was an error when reseting email token." -msgstr "" +msgstr "Помилка при скиданні токена електронної пошти." msgid "There was an error when subscribing to this label." -msgstr "" +msgstr "Помилка при підписці на цю мітку." msgid "There was an error when unsubscribing from this label." -msgstr "" +msgstr "Помилка при відписці від цієї мітки." msgid "This board\\'s scope is reduced" msgstr "Видимість цієї дошки обмежена" @@ -3167,19 +3524,19 @@ msgid "This job depends on a user to trigger its process. Often they are used to msgstr "" msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" -msgstr "" +msgstr "Це завдання залежить від попередніх, які повинні завершитися успішно для його запуску" msgid "This job has not been triggered yet" -msgstr "" +msgstr "Завдання ще не було запущене" msgid "This job has not started yet" -msgstr "" +msgstr "Ця завдання ще не розпочалася" msgid "This job is in pending state and is waiting to be picked by a runner" msgstr "" msgid "This job requires a manual action" -msgstr "" +msgstr "Завдання вимагає ручних дій" msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "Це означає, що ви не можете відправляти код, поки не створите порожній репозиторій або не імпортуєте існуючий." @@ -3187,6 +3544,9 @@ msgstr "Це означає, що ви не можете відправляти msgid "This merge request is locked." msgstr "Цей запит на злиття заблоковано." +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "Ця сторінка недоступна, тому що ви не можете переглядати інформацію по кількох проектах." + msgid "This project" msgstr "Цей проект" @@ -3212,13 +3572,13 @@ msgid "Time until first merge request" msgstr "Час до першого запиту на злиття" msgid "TimeTrackingEstimated|Est" -msgstr "" +msgstr "Запланований час" msgid "TimeTracking|Estimated:" -msgstr "" +msgstr "Запланований час:" msgid "TimeTracking|Spent" -msgstr "" +msgstr "Витрачено" msgid "Timeago|%s days ago" msgstr "%s днів тому" @@ -3348,19 +3708,27 @@ msgid_plural "Time|hrs" msgstr[0] "година" msgstr[1] "години" msgstr[2] "годин" +msgstr[3] "годин" msgid "Time|min" msgid_plural "Time|mins" msgstr[0] "хвилина" msgstr[1] "хвилини" msgstr[2] "хвилин" +msgstr[3] "хвилин" msgid "Time|s" msgstr "секунд(а)" +msgid "Tip:" +msgstr "Порада:" + msgid "Title" msgstr "Назва" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "Для перегляду плану-графіку додайте заплановані дати початку та завершення до одного з ваших епіків у цій групі або її підгрупах. Відображаються лише епіки за попередні та наступні 3 місяці." + msgid "Todo" msgstr "Задача" @@ -3376,35 +3744,29 @@ msgstr "Статус перемикача: УВІМКНЕНО" msgid "Total Time" msgstr "Загальний час" -msgid "Total issue time spent" -msgstr "Загальний витрачений час на проблему" - msgid "Total test time for all commits/merges" msgstr "Загальний час, щоб перевірити всі коміти/злиття" +msgid "Total: %{total}" +msgstr "Всього: %{total}" + msgid "Track activity with Contribution Analytics." msgstr "Відстежувати активність за допомогою Аналітики учасників." msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "Відстежуйте групи проблем зі спільною темою з різних проектів та етапів" -msgid "Total: %{total}" -msgstr "" - msgid "Track time with quick actions" msgstr "" msgid "Trigger this manual action" -msgstr "" +msgstr "Запустити цю ручну дію" msgid "Turn on Service Desk" msgstr "Ввімкнути Service Desk" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." -msgstr "" +msgstr "Неможливо скинуте кеш проекту." msgid "Unknown" msgstr "Невідомо" @@ -3413,16 +3775,19 @@ msgid "Unlock" msgstr "Розблокувати" msgid "Unlock this %{issuableDisplayName}? Everyone will be able to comment." -msgstr "" +msgstr "Розблокувати %{issuableDisplayName}? Будь-хто зможе залишати коментарі." msgid "Unlocked" msgstr "Розблоковано" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "Видалити із обраних" msgid "Up to date" -msgstr "" +msgstr "Актуальний" msgid "Upgrade your plan to activate Advanced Global Search." msgstr "Перейдіть на вищий тарифний план щоб активувати Покращений Глобальний Пошук." @@ -3446,7 +3811,7 @@ msgid "Upload file" msgstr "Завантажити файл" msgid "Upload new avatar" -msgstr "" +msgstr "Завантажити новий аватар" msgid "UploadLink|click to upload" msgstr "Натисніть, щоб завантажити" @@ -3461,13 +3826,16 @@ msgid "Use your global notification setting" msgstr "Використовуються глобальні налаштування повідомлень" msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." -msgstr "" +msgstr "Змінні застосовуються до середовищ через runner. Вони можуть бути захищені, в такому випадку вони доступні тільки для захищених гілок та тегів. Ви можете використовувати змінні для паролів, секретний ключів тощо." + +msgid "View epics list" +msgstr "Переглянути список епіків" msgid "View file @ " msgstr "Перегляд файла @ " msgid "View labels" -msgstr "" +msgstr "Переглянути мітки" msgid "View open merge request" msgstr "Перегляд відкритих запитів на злиття" @@ -3499,6 +3867,9 @@ msgstr "Ми не маємо достатньо даних для відобра msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "Ми хочемо бути впевнені, що це ви, будь ласка, підтвердіть, що ви не робот." +msgid "Web IDE" +msgstr "Веб-IDE" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "Веб-гук дозволяє вам викликати URL якщо, наприклад, був відправлений новий код або створено нову проблему. Ви можете налаштувати його так, щоб він реагував на певні події (відправки коду, проблеми або запити на злиття). Групові веб-гуки застосовуються до всіх проектів в групі і дозволяють вам стандартизувати їх для всієї вашої групи." @@ -3524,7 +3895,7 @@ msgid "WikiClone|Start Gollum and edit locally" msgstr "Запустіть Gollum і редагуйте локально" msgid "WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title." -msgstr "" +msgstr "Порада: Ви можете перемістити цю сторінку, додавши шлях до початку заголовка." msgid "WikiEdit|There is already a page with the same title in that path." msgstr "" @@ -3619,6 +3990,9 @@ msgstr "З аналітикою учасників ви може вивчати msgid "Withdraw Access Request" msgstr "Скасувати запит доступу" +msgid "Write a commit message..." +msgstr "Напишіть коміт-повідомлення..." + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "Ви хочете видалити %{group_name}. Видалені групи НЕ МОЖНА буду відновити! Ви АБСОЛЮТНО впевнені?" @@ -3631,20 +4005,23 @@ msgstr "Ви збираєтеся видалити зв'язок з форка msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "Ви збираєтеся передати проект %{project_name_with_namespace} іншому власнику. Ви АБСОЛЮТНО впевнені?" +msgid "You can also create a project from the command line." +msgstr "Ви також можете створити проект із командного рядка." + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." -msgstr "" +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" +msgstr "Ви можете легко встановити Runner на кластері Kubernetes. %{link_to_help_page}" msgid "You can move around the graph by using the arrow keys." -msgstr "" +msgstr "Ви можете пересуватися по графіку за допомогою клавіш зі стрілками." msgid "You can only add files when you are on a branch" msgstr "Ви можете додавати файли тільки коли перебуваєте в гілці" msgid "You can only edit files when you are on a branch" -msgstr "" +msgstr "Ви можете редагувати файли, лише перебуваючи у якійсь гілці" msgid "You cannot write to a read-only secondary GitLab Geo instance. Please use %{link_to_primary_node} instead." msgstr "Ви не можете записувати на вторинні інстанси \"тільки для читання\" GitLab Geo. Будь ласка використовуйте %{link_to_primary_node}." @@ -3652,12 +4029,24 @@ msgstr "Ви не можете записувати на вторинні інс msgid "You cannot write to this read-only GitLab instance." msgstr "Ви не можете записувати на цей \"тільки для читання\" інстанс GitLab." +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "У вас немає необхідних прав доступу, щоб перевизначити налаштування синхронізації LDAP-груп." + +msgid "You have no permissions" +msgstr "У вас немає прав доступу" + msgid "You have reached your project limit" msgstr "Ви досягли обмеження в вашому проекті" +msgid "You must have master access to force delete a lock" +msgstr "У вас повинен бути доступ на рівні керівника, для примусового видалення блокування" + msgid "You must sign in to star a project" msgstr "Необхідно увійти, щоб оцінити проект" +msgid "You need a different license to enable FileLocks feature" +msgstr "Для активації функції Блокування Файлів вам потрібна інша ліцензія" + msgid "You need permission." msgstr "Вам потрібен дозвіл" @@ -3686,9 +4075,12 @@ msgid "You won't be able to pull or push project code via SSH until you add an S msgstr "Ви не зможете відправляти та отримувати код проекту через SSH, поки не додасте в свій профіль SSH ключ" msgid "You'll need to use different branch names to get a valid comparison." -msgstr "" +msgstr "Вам необхідно використовувати різні імена гілок для коректного порівняння." msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" +msgstr "Інформація про ваш Kubernetes-кластер все ще доступна для редагування на цій сторінці, але ми радимо вимкнути і повторно налаштувати" + +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" msgstr "" msgid "Your comment will not be visible to the public." @@ -3716,13 +4108,13 @@ msgid "ciReport|Code quality" msgstr "Якість коду" msgid "ciReport|DAST detected no alerts by analyzing the review app" -msgstr "" +msgstr "DAST не виявив попереджень при аналізі цього review app" -msgid "ciReport|Failed to load ${type} report" -msgstr "Завантаження звіту ${type} пройшло невдало" +msgid "ciReport|Failed to load %{reportName} report" +msgstr "Помилка при завантаженні звіту %{reportName}" msgid "ciReport|Fixed:" -msgstr "" +msgstr "Виправлено:" msgid "ciReport|Instances" msgstr "Інстанси" @@ -3730,11 +4122,11 @@ msgstr "Інстанси" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" -msgstr "" +msgid "ciReport|Loading %{reportName} report" +msgstr "Завантаження звіту %{reportName}" msgid "ciReport|No changes to code quality" -msgstr "" +msgstr "Немає змін у якості коду" msgid "ciReport|No changes to performance metrics" msgstr "" @@ -3745,41 +4137,88 @@ msgstr "" msgid "ciReport|SAST" msgstr "SAST" +msgid "ciReport|SAST degraded on" +msgstr "SAST погіршився на" + +msgid "ciReport|SAST detected" +msgstr "SAST виявив" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "SAST не виявив нових вразливостей" + msgid "ciReport|SAST detected no security vulnerabilities" -msgstr "" +msgstr "SAST не виявив жодних вразливостей" msgid "ciReport|SAST:container no vulnerabilities were found" msgstr "" msgid "ciReport|Show complete code vulnerabilities report" -msgstr "" +msgstr "Показати повний звіт про вразливості в коді" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" -msgstr "" +msgstr "Незатверджені вразливості (червоні) можуть бути відмічені як затверджені. %{helpLink}" + +msgid "ciReport|no security vulnerabilities" +msgstr "вразливостей немає" + +msgid "command line instructions" +msgstr "інструкції для командного рядка" msgid "commit" msgstr "коміт" msgid "confidentiality|You are going to turn off the confidentiality. This means everyone will be able to see and leave a comment on this issue." -msgstr "" +msgstr "ви вимикаєте конфіденційність. Це означає, що будь-хто зможе бачити і залишати коментарі для цієї проблеми." msgid "confidentiality|You are going to turn on the confidentiality. This means that only team members with at least Reporter access are able to see and leave comments on the issue." -msgstr "" +msgstr "Ви вмикаєте конфіденційність. Це означає що лише члени команди рівня репортер або вище матимуть змогу бачити та залишати коментарі для цієї проблеми." msgid "day" msgid_plural "days" msgstr[0] "день" msgstr[1] "дні" msgstr[2] "днів" +msgstr[3] "днів" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "неправильний через наявність блокувань на нижчих рівнях" + +msgid "is invalid because there is upstream lock" +msgstr "неправильний через наявність блокувань на вищих рівнях" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "заблоковано %{path_lock_user_name} %{created_at}" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "запит на злиття" msgstr[1] "запити на злиття" msgstr[2] "запитів на злиття" +msgstr[3] "запитів на злиття" + +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "Будь ласка відновіть її або використовуйте іншу %{missingBranchName} гілку" + +msgid "mrWidget|Add approval" +msgstr "Додати затвердження" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "Під час видалення вашого затвердження сталася помилка." + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "Затвердити" + +msgid "mrWidget|Approved by" +msgstr "" msgid "mrWidget|Cancel automatic merge" msgstr "Скасувати автоматичне злиття" @@ -3788,34 +4227,37 @@ msgid "mrWidget|Check out branch" msgstr "" msgid "mrWidget|Checking ability to merge automatically" -msgstr "" +msgstr "перевірка можливості автоматичного злиття" msgid "mrWidget|Cherry-pick" -msgstr "" +msgstr "вибір (коміта)" msgid "mrWidget|Cherry-pick this merge request in a new merge request" msgstr "" msgid "mrWidget|Closed" -msgstr "" +msgstr "Закриті" msgid "mrWidget|Closed by" -msgstr "" +msgstr "Закритий" msgid "mrWidget|Closes" msgstr "" msgid "mrWidget|Did not close" -msgstr "" +msgstr "Не закрив" msgid "mrWidget|Email patches" -msgstr "" +msgstr "Email-патчі" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" -msgstr "" +msgstr "Якщо гілка %{branch} існує у вашому локальному репозиторії, то ви можете застосувати цей запит на злиття вручну за допомогою" + +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "Якщо гілка %{missingBranchName} існує у вашому локальному репозиторії, то ви можете застосувати цей запит на злиття вручну за допомогою командного рядка" msgid "mrWidget|Mentions" -msgstr "" +msgstr "Згадки" msgid "mrWidget|Merge" msgstr "Злиття" @@ -3824,13 +4266,13 @@ msgid "mrWidget|Merge failed." msgstr "Злиття пройшло невдало." msgid "mrWidget|Merge locally" -msgstr "" +msgstr "Злити локально" msgid "mrWidget|Merged by" -msgstr "" +msgstr "Злито" msgid "mrWidget|Plain diff" -msgstr "" +msgstr "Просте порівняння (diff)" msgid "mrWidget|Refresh" msgstr "Оновити" @@ -3839,76 +4281,82 @@ msgid "mrWidget|Refresh now" msgstr "Оновити зараз" msgid "mrWidget|Refreshing now" -msgstr "" +msgstr "Відбувається оновлення" msgid "mrWidget|Remove Source Branch" -msgstr "" +msgstr "Видалити гілку-джерело" msgid "mrWidget|Remove source branch" -msgstr "" +msgstr "Видалити гілку-джерело" + +msgid "mrWidget|Remove your approval" +msgstr "Видалити ваше затвердження" msgid "mrWidget|Request to merge" -msgstr "" +msgstr "Запит на злиття" msgid "mrWidget|Resolve conflicts" -msgstr "" +msgstr "Вирішити конфлікти" msgid "mrWidget|Revert" -msgstr "" +msgstr "Анулювати" msgid "mrWidget|Revert this merge request in a new merge request" -msgstr "" +msgstr "Анулювати цей запит на злиття за допомогою нового запиту на злиття" msgid "mrWidget|Set by" -msgstr "" +msgstr "Встановлено" msgid "mrWidget|The changes were merged into" -msgstr "" +msgstr "Зміни були злиті в" msgid "mrWidget|The changes were not merged into" -msgstr "" +msgstr "Зміни не були злиті в" msgid "mrWidget|The changes will be merged into" -msgstr "" +msgstr "Зміни будуть злиті в" msgid "mrWidget|The source branch has been removed" -msgstr "" +msgstr "Гілку-джерело видалено" msgid "mrWidget|The source branch is being removed" -msgstr "" +msgstr "Гілка-джерело в процесі видалення" msgid "mrWidget|The source branch will be removed" -msgstr "" +msgstr "Гілку-джерело буде видалено" msgid "mrWidget|The source branch will not be removed" -msgstr "" +msgstr "Гілку-джерело не буде видалено" msgid "mrWidget|There are merge conflicts" -msgstr "" +msgstr "існують конфлікти при злитті" msgid "mrWidget|This merge request failed to be merged automatically" msgstr "Відбулася помилка при автоматичному злитті цього запиту" msgid "mrWidget|This merge request is in the process of being merged" -msgstr "" +msgstr "Запит на злиття в процесі виконання" msgid "mrWidget|This project is archived, write access has been disabled" -msgstr "" +msgstr "Цей проект заархівований, доступ до запису було відключено" msgid "mrWidget|You can merge this merge request manually using the" -msgstr "" +msgstr "Ви можете прийняти цей запит на злиття вручну за допомогою" msgid "mrWidget|You can remove source branch now" -msgstr "" +msgstr "Тепер ви можете видалити гілку-джерело" + +msgid "mrWidget|branch does not exist." +msgstr "гілка не існує." msgid "mrWidget|command line" -msgstr "" +msgstr "командний рядок" msgid "mrWidget|into" -msgstr "" +msgstr "в" msgid "mrWidget|to be merged automatically when the pipeline succeeds" -msgstr "" +msgstr "буде злито автоматично, коли конвеєр завершиться успішно" msgid "new merge request" msgstr "Новий запит на злиття" @@ -3924,6 +4372,7 @@ msgid_plural "parents" msgstr[0] "батьківський об’єкт" msgstr[1] "батьківські об’єкти" msgstr[2] "батьківський об’єктів" +msgstr[3] "батьківський об’єктів" msgid "password" msgstr "пароль" @@ -3947,5 +4396,8 @@ msgid "username" msgstr "ім'я користувача" msgid "uses Kubernetes clusters to deploy your code!" +msgstr "використовує кластери Kubernetes для розгортання коду!" + +msgid "with %{additions} additions, %{deletions} deletions." msgstr "" diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po index 441f080596c..6a43a434cb8 100644 --- a/locale/zh_CN/gitlab.po +++ b/locale/zh_CN/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 03:58-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:21-0500\n" "Last-Translator: gitlab \n" "Language-Team: Chinese Simplified\n" "Language: zh_CN\n" @@ -43,6 +43,9 @@ msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "为提高页面加载速度及性能,已省略了 %s 次提交。" +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -50,6 +53,9 @@ msgid "%{count} participant" msgid_plural "%{count} participants" msgstr[0] "%{count} 位参与者" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "%{number_commits_behind} 个落后 %{default_branch} 分支的提交, %{number_commits_ahead} 早超前的提交" @@ -59,6 +65,9 @@ msgstr "已失败 %{number_of_failures} 次/最多允许失败失败 %{maximum_f msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "已失败 %{number_of_failures} 次/最多允许失败 %{maximum_failures} 次,GitLab 不会继续自动重试。请在问题解决后重置存储健康信息。" +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "%{storage_name}:已 %{failed_attempts} 次尝试访问存储失败:" @@ -121,9 +130,15 @@ msgstr "添加贡献指南" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "添加组Webhooks和GitLab企业版。" +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "添加许可证" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "添加目录" @@ -142,12 +157,45 @@ msgstr "" msgid "AdminArea|Stopping jobs failed" msgstr "" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" msgid "AdminHealthPageLink|health page" msgstr "健康页面" +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + msgid "Advanced" msgstr "" @@ -172,6 +220,12 @@ msgstr "切换通知订阅时发生错误" msgid "An error occurred when updating the issue weight" msgstr "更新议题权重时发生错误" +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" @@ -181,12 +235,36 @@ msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "获取侧边栏数据时发生错误" +msgid "An error occurred while fetching the pipeline." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + msgid "An error occurred while loading filenames" msgstr "" +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + msgid "An error occurred while rendering KaTeX" msgstr "" @@ -199,6 +277,12 @@ msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + msgid "An error occurred while validating username" msgstr "" @@ -223,15 +307,15 @@ msgstr "项目已归档!存储库为只读状态" msgid "Are you sure you want to delete this pipeline schedule?" msgstr "确定要删除此流水线计划吗?" -msgid "Are you sure you want to discard your changes?" -msgstr "确定要放弃修改吗?" - msgid "Are you sure you want to reset registration token?" msgstr "确定要重置注册令牌吗?" msgid "Are you sure you want to reset the health check token?" msgstr "确定要重置健康检查令牌吗?" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "确定吗?" @@ -271,6 +355,9 @@ msgstr "作者" msgid "Authors: %{authors}" msgstr "" +msgid "Auto DevOps enabled" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -295,8 +382,14 @@ msgstr "将根据预定义的 CI/CD 配置自动构建、测试和部署应用 msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "想了解更多请访问 %{link_to_documentation}" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." -msgstr "您可以为此项目激活 %{link_to_settings}。" +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" +msgstr "" msgid "Available" msgstr "可用的" @@ -307,6 +400,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Billing" msgstr "账单" @@ -361,12 +457,9 @@ msgstr "每年支付 %{price_per_year}" msgid "BillingPlans|per user" msgstr "每用户" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "分支" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "已创建分支 %{branch_name} 。如需设置自动部署, 请选择合适的 GitLab CI Yaml 模板并提交更改。%{link_to_autodeploy_doc}" @@ -659,6 +752,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "断路器 API" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + msgid "Click to expand text" msgstr "" @@ -713,6 +809,9 @@ msgstr "复制API地址" msgid "ClusterIntegration|Copy CA Certificate" msgstr "复制CA证书" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -761,6 +860,9 @@ msgstr "Helm Tiller" msgid "ClusterIntegration|Ingress" msgstr "入口" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "安装" @@ -812,9 +914,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "了解详细%{link_to_documentation}" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" msgstr "" @@ -950,6 +1049,12 @@ msgstr "正确配置" msgid "Collapse" msgstr "" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "评论" @@ -957,6 +1062,10 @@ msgid "Commit" msgid_plural "Commits" msgstr[0] "提交" +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" + msgid "Commit Message" msgstr "提交消息" @@ -969,6 +1078,9 @@ msgstr "提交信息" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "提交" @@ -1110,6 +1222,9 @@ msgstr "复制 URL 到剪贴板" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "复制提交 SHA 的值到剪贴板" @@ -1122,9 +1237,18 @@ msgstr "" msgid "Create New Directory" msgstr "创建新目录" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "在帐户上创建个人访问令牌,以通过 %{protocol} 来拉取或推送。" +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "创建目录" @@ -1143,6 +1267,9 @@ msgstr "" msgid "Create merge request" msgstr "创建合并请求" +msgid "Create merge request and branch" +msgstr "" + msgid "Create new branch" msgstr "创建新分支" @@ -1167,6 +1294,12 @@ msgstr "标签" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "创建个人访问令牌" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + msgid "Creating epic" msgstr "创建EPIC中" @@ -1221,6 +1354,9 @@ msgstr "十二" msgid "December" msgstr "十二月" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "使用 Cron 语法定义自定义模式" @@ -1252,8 +1388,8 @@ msgstr "目录名称" msgid "Disable" msgstr "" -msgid "Discard changes" -msgstr "放弃更改" +msgid "Discard draft" +msgstr "" msgid "Discover GitLab Geo." msgstr "" @@ -1312,6 +1448,9 @@ msgstr "电子邮件" msgid "Enable" msgstr "" +msgid "Enable Auto DevOps" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "获取环境时发生错误。" @@ -1366,9 +1505,18 @@ msgstr "EPIC将被删除!是否确定?" msgid "Epics" msgstr "EPIC" +msgid "Epics Roadmap" +msgstr "" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "EPIC让你更有效率地管理你的项目组合,而且不费吹灰之力" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "创建EPIC时出错" @@ -1435,12 +1583,36 @@ msgstr "查看项目" msgid "Explore public groups" msgstr "搜索公共群组" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + msgid "Failed to change the owner" msgstr "无法变更所有者" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "无法删除流水线计划" +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "二" @@ -1456,6 +1628,9 @@ msgstr "文件名" msgid "Files" msgstr "文件" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "按提交消息过滤" @@ -1490,6 +1665,9 @@ msgstr "从创建议题到部署至生产环境" msgid "From merge request merge until deploy to production" msgstr "从合并请求被合并后到部署至生产环境" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "GPG 密钥" @@ -1637,6 +1815,24 @@ msgstr "Google 身份验证不是%{link_to_documentation}。如果您想使用 msgid "Got it!" msgstr "" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "禁止与其他群组共享 %{group} 中的项目" @@ -1734,6 +1930,9 @@ msgstr "历史" msgid "Housekeeping successfully started" msgstr "已开始维护" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "导入存储库" @@ -1746,12 +1945,15 @@ msgstr "协助改善GitLab 企业版的议题管理与权重。" msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "协助改进GitLab 企业版的搜索和高级全局搜索 。" +msgid "Install Runner on Kubernetes" +msgstr "" + msgid "Install a Runner compatible with GitLab CI" msgstr "安装一个与 GitLab CI 兼容的 Runner" msgid "Instance" msgid_plural "Instances" -msgstr[0] "例子" +msgstr[0] "实例" msgid "Instance does not support multiple Kubernetes clusters" msgstr "" @@ -1795,6 +1997,9 @@ msgstr "一" msgid "January" msgstr "一月" +msgid "Jobs" +msgstr "" + msgid "Jul" msgstr "七" @@ -1825,6 +2030,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1871,6 +2079,12 @@ msgstr "于" msgid "Learn more" msgstr "" +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + msgid "Learn more in the" msgstr "了解更多" @@ -1889,6 +2103,9 @@ msgstr "退出项目" msgid "License" msgstr "许可协议" +msgid "List" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -1898,6 +2115,9 @@ msgstr "锁定" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" @@ -1907,6 +2127,9 @@ msgstr "已锁定" msgid "Locked Files" msgstr "已锁定文件" +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + msgid "Login" msgstr "登录" @@ -1937,9 +2160,6 @@ msgstr "中位数" msgid "Members" msgstr "成员" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "合并请求" @@ -1952,6 +2172,9 @@ msgstr "合并请求" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" @@ -1976,9 +2199,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "新建 SSH 公钥" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "监控" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "帮助文档" @@ -2073,9 +2305,6 @@ msgstr "没有存储库" msgid "No schedules" msgstr "没有计划" -msgid "No time spent" -msgstr "没有花费时间" - msgid "None" msgstr "无" @@ -2091,6 +2320,9 @@ msgstr "" msgid "Not enough data" msgstr "数据不足" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "通知事件" @@ -2193,6 +2425,9 @@ msgstr "打开一个新窗口" msgid "Options" msgstr "操作" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + msgid "Overview" msgstr "概览" @@ -2298,6 +2533,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "所有" @@ -2331,6 +2584,9 @@ msgstr "私人 - 必须向每个用户明确授予项目访问权限。" msgid "Private - The group and its projects can only be viewed by members." msgstr "私人 - 群组及其项目只能由成员查看。" +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "用户信息" @@ -2493,12 +2749,30 @@ msgstr "对不起,没有搜索到符合条件的项目" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "此功能需要浏览器支持 localStorage" +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "默认情况下,Prometheus 侦听 ‘http://localhost:9090’。不建议更改默认地址和端口,因为这可能会影响或冲突在 GitLab 服务器上运行的其他服务。" msgid "PrometheusService|Finding and configuring metrics..." msgstr "查找和配置指标..." +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + msgid "PrometheusService|Metrics" msgstr "指标" @@ -2520,9 +2794,18 @@ msgstr "没有监测指标。要开始监测,请部署到环境中。" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "Prometheus API 地址,例如 http://prometheus.example.com/" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + msgid "PrometheusService|Time-series monitoring service" msgstr "" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + msgid "PrometheusService|View environments" msgstr "查看环境" @@ -2541,6 +2824,12 @@ msgstr "推送规则" msgid "Push events" msgstr "推送事件" +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + msgid "PushRule|Committer restriction" msgstr "提交限制" @@ -2604,6 +2893,9 @@ msgstr "" msgid "Repository" msgstr "存储库" +msgid "Repository has no locks." +msgstr "" + msgid "Request Access" msgstr "申请权限" @@ -2616,6 +2908,9 @@ msgstr "重置健康检查访问令牌" msgid "Reset runners registration token" msgstr "重置 Runner 注册令牌" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" @@ -2626,6 +2921,9 @@ msgstr "还原此提交" msgid "Revert this merge request" msgstr "还原此合并请求" +msgid "Roadmap" +msgstr "" + msgid "SSH Keys" msgstr "SSH 密钥" @@ -2671,12 +2969,18 @@ msgstr "等待存储访问尝试时间(秒)" msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "选择下载格式" msgid "Select a timezone" msgstr "选择时区" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2689,6 +2993,9 @@ msgstr "选择目标分支" msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "九" @@ -2701,6 +3008,9 @@ msgstr "" msgid "Service Templates" msgstr "服务模板" +msgid "Service URL" +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "为账号创建一个用于推送或拉取的 %{protocol} 密码。" @@ -2710,15 +3020,15 @@ msgstr "" msgid "Set up Koding" msgstr "设置 Koding" -msgid "Set up auto deploy" -msgstr "设置自动部署" - msgid "SetPasswordToCloneLink|set a password" msgstr "设置密码" msgid "Settings" msgstr "设置" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" @@ -2728,6 +3038,9 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "" + msgid "Show parent pages" msgstr "查看上级页面" @@ -2748,7 +3061,7 @@ msgid "Sidebar|None" msgstr "无" msgid "Sidebar|Weight" -msgstr "宽度" +msgstr "权重" msgid "Snippets" msgstr "代码片段" @@ -2768,12 +3081,24 @@ msgstr "试图改变 ${this.issuableDisplayName} 的锁定状态时出错了" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "拉取项目时发生错误。" msgid "Something went wrong while fetching the registry list." msgstr "拉取注册表列表时发生错误。" +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" @@ -2879,6 +3204,9 @@ msgstr "权重" msgid "Source" msgstr "源" +msgid "Source (branch or tag)" +msgstr "" + msgid "Source code" msgstr "源代码" @@ -2918,9 +3246,9 @@ msgstr "切换分支/标签" msgid "System Hooks" msgstr "系统钩子" -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "标签" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "" msgid "Tags" msgstr "标签" @@ -3051,9 +3379,15 @@ msgstr "该项目允许任何人访问。" msgid "The repository for this project does not exist." msgstr "此项目的存储库不存在。" +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "评审阶段概述了从创建合并请求到被合并的时间。当创建第一个合并请求后,数据将自动添加到此处。" +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "预发布阶段概述了从合并请求被合并到部署至生产环境的总时间。首次部署到生产环境后,数据将自动添加到此处。" @@ -3147,6 +3481,9 @@ msgstr "在创建一个空的存储库或导入现有存储库之前,将无法 msgid "This merge request is locked." msgstr "此合并请求已锁定。" +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3314,9 +3651,15 @@ msgstr[0] "分钟" msgid "Time|s" msgstr "秒" +msgid "Tip:" +msgstr "" + msgid "Title" msgstr "标题" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + msgid "Todo" msgstr "" @@ -3332,21 +3675,18 @@ msgstr "" msgid "Total Time" msgstr "总时间" -msgid "Total issue time spent" -msgstr "议题花费时间总计" - msgid "Total test time for all commits/merges" msgstr "所有提交和合并的总测试时间" +msgid "Total: %{total}" +msgstr "" + msgid "Track activity with Contribution Analytics." msgstr "跟踪活动与贡献的分析。" msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "在项目和里程碑之间跟踪共享主题的议题组" -msgid "Total: %{total}" -msgstr "" - msgid "Track time with quick actions" msgstr "" @@ -3356,9 +3696,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "打开服务台" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3374,6 +3711,9 @@ msgstr "" msgid "Unlocked" msgstr "已解锁" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "取消星标" @@ -3419,6 +3759,9 @@ msgstr "使用全局通知设置" msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" +msgid "View epics list" +msgstr "" + msgid "View file @ " msgstr "浏览文件 @ " @@ -3455,6 +3798,9 @@ msgstr "该阶段的数据不足,无法显示。" msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "我们要确定你是不是机器人。" +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "如果有新的推送或新的议题,Webhook将自动触发您设置URL。 您可以配置 Webhook 来监听特定事件,如推送、议题或合并请求。 群组 Webhook 将适用于团队中的所有项目,并允许您设置整个团队中的 Webhook 。" @@ -3575,6 +3921,9 @@ msgstr "通过贡献分析,您可以分析您的组织及其成员的议题、 msgid "Withdraw Access Request" msgstr "取消权限申请" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "即将删除 %{group_name}。已删除的群组无法恢复!确定继续吗?" @@ -3587,10 +3936,13 @@ msgstr "即将删除与源项目 %{forked_from_project} 的派生关系。确定 msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "即将 %{project_name_with_namespace} 转移给另一个所有者。确定继续吗?" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3608,12 +3960,24 @@ msgstr "您不能写入只读的辅助 GitLab Geo 实例。请改用%{link_to_pr msgid "You cannot write to this read-only GitLab instance." msgstr "您不能写入这个只读的 GitLab 实例。" +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "您已达到项目数量限制" +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "必须登录才能对项目加星标" +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "需要相关的权限。" @@ -3647,6 +4011,9 @@ msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "您的评论将不会公开显示。" @@ -3674,7 +4041,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3686,7 +4053,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3701,6 +4068,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3713,6 +4089,12 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + msgid "commit" msgstr "提交" @@ -3729,10 +4111,40 @@ msgstr[0] "天" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3766,6 +4178,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3799,6 +4214,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3853,6 +4271,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3899,3 +4320,6 @@ msgstr "用户名" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index c79a46c93f7..0174e945bab 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 03:58-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:22-0500\n" "Last-Translator: gitlab \n" "Language-Team: Chinese Traditional, Hong Kong\n" "Language: zh_HK\n" @@ -43,6 +43,9 @@ msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "為提高頁面加載速度及性能,已省略了 %s 次提交。" +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -50,6 +53,9 @@ msgid "%{count} participant" msgid_plural "%{count} participants" msgstr[0] "" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "" @@ -59,6 +65,9 @@ msgstr "已失敗 %{number_of_failures} 次,最大失敗 %{maximum_failures} msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "已失敗 %{number_of_failures} 次,最大失敗 %{maximum_failures} 次,GitLab不會重試。當問題解決時重置存儲信息。" +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "%{storage_name}:已訪問此主機失敗 %{failed_attempts} 次" @@ -121,9 +130,15 @@ msgstr "添加貢獻指南" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "" +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "添加許可證" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "添加新目錄" @@ -142,12 +157,45 @@ msgstr "" msgid "AdminArea|Stopping jobs failed" msgstr "" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" msgid "AdminHealthPageLink|health page" msgstr "" +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + msgid "Advanced" msgstr "" @@ -172,6 +220,12 @@ msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" @@ -181,12 +235,36 @@ msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while fetching the pipeline." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + msgid "An error occurred while loading filenames" msgstr "" +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + msgid "An error occurred while rendering KaTeX" msgstr "" @@ -199,6 +277,12 @@ msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + msgid "An error occurred while validating username" msgstr "" @@ -223,15 +307,15 @@ msgstr "歸檔項目!存儲庫為只讀" msgid "Are you sure you want to delete this pipeline schedule?" msgstr "確定要刪除此流水線計劃嗎?" -msgid "Are you sure you want to discard your changes?" -msgstr "確定要放棄修改嗎?" - msgid "Are you sure you want to reset registration token?" msgstr "確定要重置註冊令牌嗎?" msgid "Are you sure you want to reset the health check token?" msgstr "確定要重置健康檢查令牌嗎?" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "確定嗎?" @@ -271,6 +355,9 @@ msgstr "" msgid "Authors: %{authors}" msgstr "" +msgid "Auto DevOps enabled" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -295,7 +382,13 @@ msgstr "" msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" msgstr "" msgid "Available" @@ -307,6 +400,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Billing" msgstr "" @@ -361,12 +457,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "分支" +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "分支 %{branch_name} 已創建。如需設置自動部署, 請選擇合適的 GitLab CI Yaml 模板併提交更改。%{link_to_autodeploy_doc}" @@ -659,6 +752,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + msgid "Click to expand text" msgstr "" @@ -713,6 +809,9 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -761,6 +860,9 @@ msgstr "" msgid "ClusterIntegration|Ingress" msgstr "" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "" @@ -812,9 +914,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" msgstr "" @@ -950,6 +1049,12 @@ msgstr "" msgid "Collapse" msgstr "" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "評論" @@ -957,6 +1062,10 @@ msgid "Commit" msgid_plural "Commits" msgstr[0] "提交" +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" + msgid "Commit Message" msgstr "" @@ -969,6 +1078,9 @@ msgstr "提交信息" msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "提交" @@ -1110,6 +1222,9 @@ msgstr "複製URL到剪貼板" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "複製提交 SHA 到剪貼板" @@ -1122,9 +1237,18 @@ msgstr "" msgid "Create New Directory" msgstr "創建新目錄" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "在帳戶上創建個人訪問令牌,以通過 %{protocol} 來拉取或推送。" +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "創建目錄" @@ -1143,6 +1267,9 @@ msgstr "" msgid "Create merge request" msgstr "創建合併請求" +msgid "Create merge request and branch" +msgstr "" + msgid "Create new branch" msgstr "" @@ -1167,6 +1294,12 @@ msgstr "標籤" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "創建個人訪問令牌" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + msgid "Creating epic" msgstr "" @@ -1221,6 +1354,9 @@ msgstr "" msgid "December" msgstr "" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "使用 Cron 語法定義自定義模式" @@ -1252,8 +1388,8 @@ msgstr "目錄名稱" msgid "Disable" msgstr "" -msgid "Discard changes" -msgstr "放棄更改" +msgid "Discard draft" +msgstr "" msgid "Discover GitLab Geo." msgstr "" @@ -1312,6 +1448,9 @@ msgstr "" msgid "Enable" msgstr "" +msgid "Enable Auto DevOps" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1366,9 +1505,18 @@ msgstr "" msgid "Epics" msgstr "" +msgid "Epics Roadmap" +msgstr "" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "" @@ -1435,12 +1583,36 @@ msgstr "" msgid "Explore public groups" msgstr "" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + msgid "Failed to change the owner" msgstr "無法變更所有者" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "無法刪除流水線計劃" +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "" @@ -1456,6 +1628,9 @@ msgstr "" msgid "Files" msgstr "文件" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "按提交消息過濾" @@ -1490,6 +1665,9 @@ msgstr "從創建議題到部署到生產環境" msgid "From merge request merge until deploy to production" msgstr "從合併請求的合併到部署至生產環境" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "" @@ -1637,6 +1815,24 @@ msgstr "" msgid "Got it!" msgstr "" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "" @@ -1734,6 +1930,9 @@ msgstr "" msgid "Housekeeping successfully started" msgstr "已開始維護" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "導入存儲庫" @@ -1746,6 +1945,9 @@ msgstr "" msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "" +msgid "Install Runner on Kubernetes" +msgstr "" + msgid "Install a Runner compatible with GitLab CI" msgstr "安裝壹個與 GitLab CI 兼容的 Runner" @@ -1795,6 +1997,9 @@ msgstr "" msgid "January" msgstr "" +msgid "Jobs" +msgstr "" + msgid "Jul" msgstr "" @@ -1825,6 +2030,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1871,6 +2079,12 @@ msgstr "在" msgid "Learn more" msgstr "" +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + msgid "Learn more in the" msgstr "了解更多" @@ -1889,6 +2103,9 @@ msgstr "退出項目" msgid "License" msgstr "" +msgid "List" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -1898,6 +2115,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" @@ -1907,6 +2127,9 @@ msgstr "" msgid "Locked Files" msgstr "" +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + msgid "Login" msgstr "" @@ -1937,9 +2160,6 @@ msgstr "中位數" msgid "Members" msgstr "" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "" @@ -1952,6 +2172,9 @@ msgstr "" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" @@ -1976,9 +2199,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "添加壹個 SSH 公鑰" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "幫助文檔" @@ -2073,9 +2305,6 @@ msgstr "沒有存儲庫" msgid "No schedules" msgstr "沒有計劃" -msgid "No time spent" -msgstr "" - msgid "None" msgstr "" @@ -2091,6 +2320,9 @@ msgstr "" msgid "Not enough data" msgstr "數據不足" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "通知事件" @@ -2193,6 +2425,9 @@ msgstr "" msgid "Options" msgstr "操作" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + msgid "Overview" msgstr "" @@ -2298,6 +2533,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "所有" @@ -2331,6 +2584,9 @@ msgstr "" msgid "Private - The group and its projects can only be viewed by members." msgstr "" +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "" @@ -2493,12 +2749,30 @@ msgstr "" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "" +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "" msgid "PrometheusService|Finding and configuring metrics..." msgstr "" +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + msgid "PrometheusService|Metrics" msgstr "" @@ -2520,9 +2794,18 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + msgid "PrometheusService|Time-series monitoring service" msgstr "" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + msgid "PrometheusService|View environments" msgstr "" @@ -2541,6 +2824,12 @@ msgstr "" msgid "Push events" msgstr "推送事件 (push event) " +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + msgid "PushRule|Committer restriction" msgstr "" @@ -2604,6 +2893,9 @@ msgstr "" msgid "Repository" msgstr "存儲庫" +msgid "Repository has no locks." +msgstr "" + msgid "Request Access" msgstr "申請權限" @@ -2616,6 +2908,9 @@ msgstr "重置健康檢查訪問令牌" msgid "Reset runners registration token" msgstr "重置 Runner 註冊令牌" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" @@ -2626,6 +2921,9 @@ msgstr "還原此提交" msgid "Revert this merge request" msgstr "還原此合併請求" +msgid "Roadmap" +msgstr "" + msgid "SSH Keys" msgstr "" @@ -2671,12 +2969,18 @@ msgstr "" msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "選擇下載格式" msgid "Select a timezone" msgstr "選擇時區" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2689,6 +2993,9 @@ msgstr "選擇目標分支" msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "" @@ -2701,6 +3008,9 @@ msgstr "" msgid "Service Templates" msgstr "" +msgid "Service URL" +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "為賬號添加壹個用於推送或拉取的 %{protocol} 密碼。" @@ -2710,15 +3020,15 @@ msgstr "" msgid "Set up Koding" msgstr "設置 Koding" -msgid "Set up auto deploy" -msgstr "設置自動部署" - msgid "SetPasswordToCloneLink|set a password" msgstr "設置密碼" msgid "Settings" msgstr "" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" @@ -2728,6 +3038,9 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "" + msgid "Show parent pages" msgstr "" @@ -2768,12 +3081,24 @@ msgstr "" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" @@ -2879,6 +3204,9 @@ msgstr "" msgid "Source" msgstr "" +msgid "Source (branch or tag)" +msgstr "" + msgid "Source code" msgstr "源代碼" @@ -2918,9 +3246,9 @@ msgstr "切換分支/標籤" msgid "System Hooks" msgstr "" -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "標籤" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "" msgid "Tags" msgstr "標籤" @@ -3051,9 +3379,15 @@ msgstr "該項目允許任何人訪問。" msgid "The repository for this project does not exist." msgstr "此項目的存儲庫不存在。" +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "評審階段概述了從創建合併請求到合併的時間。當創建第壹個合併請求後,數據將自動添加到此處。" +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "預發布階段概述了合併請求的合併到部署代碼到生產環境的總時間。當首次部署到生產環境後,數據將自動添加到此處。" @@ -3147,6 +3481,9 @@ msgstr "在創建壹個空的存儲庫或導入現有存儲庫之前,您將無 msgid "This merge request is locked." msgstr "" +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3314,9 +3651,15 @@ msgstr[0] "分鐘" msgid "Time|s" msgstr "秒" +msgid "Tip:" +msgstr "" + msgid "Title" msgstr "" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + msgid "Todo" msgstr "" @@ -3332,19 +3675,16 @@ msgstr "" msgid "Total Time" msgstr "總時間" -msgid "Total issue time spent" -msgstr "" - msgid "Total test time for all commits/merges" msgstr "所有提交和合併的總測試時間" -msgid "Track activity with Contribution Analytics." +msgid "Total: %{total}" msgstr "" -msgid "Track groups of issues that share a theme, across projects and milestones" +msgid "Track activity with Contribution Analytics." msgstr "" -msgid "Total: %{total}" +msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" msgid "Track time with quick actions" @@ -3356,9 +3696,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3374,6 +3711,9 @@ msgstr "" msgid "Unlocked" msgstr "" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "取消星標" @@ -3419,6 +3759,9 @@ msgstr "使用全局通知設置" msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" +msgid "View epics list" +msgstr "" + msgid "View file @ " msgstr "" @@ -3455,6 +3798,9 @@ msgstr "該階段的數據不足,無法顯示。" msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "" +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "" @@ -3575,6 +3921,9 @@ msgstr "" msgid "Withdraw Access Request" msgstr "取消權限申请" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "即將刪除 %{group_name}。已刪除的群組無法恢復!確定繼續嗎?" @@ -3587,10 +3936,13 @@ msgstr "即將刪除與源項目 %{forked_from_project} 的派生關系。確定 msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "即將 %{project_name_with_namespace} 轉義給另壹個所有者。確定繼續嗎?" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3608,12 +3960,24 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "您已達到項目數量限制" +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "必須登錄才能對項目加星標" +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "需要相關的權限。" @@ -3647,6 +4011,9 @@ msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -3674,7 +4041,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3686,7 +4053,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3701,6 +4068,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3713,6 +4089,12 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + msgid "commit" msgstr "" @@ -3729,10 +4111,40 @@ msgstr[0] "天" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3766,6 +4178,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3799,6 +4214,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3853,6 +4271,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3899,3 +4320,6 @@ msgstr "" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po index 635f5c6c449..025af6fa2d9 100644 --- a/locale/zh_TW/gitlab.po +++ b/locale/zh_TW/gitlab.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab-ee\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-02-07 11:38-0600\n" -"PO-Revision-Date: 2018-02-12 03:58-0500\n" +"POT-Creation-Date: 2018-03-02 13:39+0100\n" +"PO-Revision-Date: 2018-03-05 03:23-0500\n" "Last-Translator: gitlab \n" "Language-Team: Chinese Traditional\n" "Language: zh_TW\n" @@ -43,6 +43,9 @@ msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "因效能考量,已隱藏 %s 個更動 (commit)。" +msgid "%{actionText} & %{openOrClose} %{noteable}" +msgstr "" + msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" @@ -50,6 +53,9 @@ msgid "%{count} participant" msgid_plural "%{count} participants" msgstr[0] "%{count} 參與者" +msgid "%{lock_path} is locked by GitLab User %{lock_user_id}" +msgstr "" + msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead" msgstr "" @@ -59,6 +65,9 @@ msgstr "目前已失敗 %{number_of_failures} 次。GitLab 允許在 %{maximum_f msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "已失敗 %{number_of_failures} / %{maximum_failures} 次,GitLab 將不再自動重試。請在確認問題解決後手動重置儲存空間資訊。" +msgid "%{openOrClose} %{noteable}" +msgstr "" + msgid "%{storage_name}: failed storage access attempt on host:" msgid_plural "%{storage_name}: %{failed_attempts} failed storage access attempts:" msgstr[0] "%{storage_name}:已存取此主機失敗 %{failed_attempts} 次" @@ -121,9 +130,15 @@ msgstr "新增協作指南" msgid "Add Group Webhooks and GitLab Enterprise Edition." msgstr "" +msgid "Add Kubernetes cluster" +msgstr "" + msgid "Add License" msgstr "新增授權條款" +msgid "Add Readme" +msgstr "" + msgid "Add new directory" msgstr "新增目錄" @@ -142,12 +157,45 @@ msgstr "" msgid "AdminArea|Stopping jobs failed" msgstr "" -msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgid "AdminArea|You’re about to stop all jobs.This will halt all current jobs that are running." msgstr "" msgid "AdminHealthPageLink|health page" msgstr "系統狀態" +msgid "AdminProjects|Delete" +msgstr "" + +msgid "AdminProjects|Delete Project %{projectName}?" +msgstr "" + +msgid "AdminProjects|Delete project" +msgstr "" + +msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages." +msgstr "" + +msgid "AdminUsers|Block user" +msgstr "" + +msgid "AdminUsers|Delete User %{username} and contributions?" +msgstr "" + +msgid "AdminUsers|Delete User %{username}?" +msgstr "" + +msgid "AdminUsers|Delete user" +msgstr "" + +msgid "AdminUsers|Delete user and contributions" +msgstr "" + +msgid "AdminUsers|To confirm, type %{projectName}" +msgstr "" + +msgid "AdminUsers|To confirm, type %{username}" +msgstr "" + msgid "Advanced" msgstr "" @@ -172,6 +220,12 @@ msgstr "" msgid "An error occurred when updating the issue weight" msgstr "" +msgid "An error occurred while adding approver" +msgstr "" + +msgid "An error occurred while detecting host keys" +msgstr "" + msgid "An error occurred while dismissing the feature highlight. Refresh the page and try dismissing again." msgstr "" @@ -181,12 +235,36 @@ msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while fetching the pipeline." +msgstr "" + msgid "An error occurred while getting projects" msgstr "" +msgid "An error occurred while importing project" +msgstr "" + +msgid "An error occurred while initializing path locks" +msgstr "" + +msgid "An error occurred while loading commits" +msgstr "" + +msgid "An error occurred while loading diff" +msgstr "" + msgid "An error occurred while loading filenames" msgstr "" +msgid "An error occurred while loading the file" +msgstr "" + +msgid "An error occurred while making the request." +msgstr "" + +msgid "An error occurred while removing approver" +msgstr "" + msgid "An error occurred while rendering KaTeX" msgstr "" @@ -199,6 +277,12 @@ msgstr "" msgid "An error occurred while retrieving diff" msgstr "" +msgid "An error occurred while saving LDAP override status. Please try again." +msgstr "" + +msgid "An error occurred while saving assignees" +msgstr "" + msgid "An error occurred while validating username" msgstr "" @@ -223,15 +307,15 @@ msgstr "此專案已封存!檔案庫 (repository) 為唯讀狀態" msgid "Are you sure you want to delete this pipeline schedule?" msgstr "確定要刪除此流水線 (pipeline) 排程嗎?" -msgid "Are you sure you want to discard your changes?" -msgstr "確定要放棄修改嗎?" - msgid "Are you sure you want to reset registration token?" msgstr "確定要重置註冊憑證 (registration token) 嗎?" msgid "Are you sure you want to reset the health check token?" msgstr "確定要重置健康檢查存取憑證 (access token) 嗎?" +msgid "Are you sure you want to unlock %{path_lock_path}?" +msgstr "" + msgid "Are you sure?" msgstr "確定嗎?" @@ -271,6 +355,9 @@ msgstr "作者" msgid "Authors: %{authors}" msgstr "" +msgid "Auto DevOps enabled" +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." msgstr "" @@ -295,8 +382,14 @@ msgstr "將根據設定的的 CI / CD 流程自動建構、測試和部署應用 msgid "AutoDevOps|Learn more in the %{link_to_documentation}" msgstr "了解更多於 %{link_to_documentation}" -msgid "AutoDevOps|You can activate %{link_to_settings} for this project." -msgstr "你可以為此專案啟動 %{link_to_settings}" +msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}." +msgstr "" + +msgid "AutoDevOps|add a Kubernetes cluster" +msgstr "" + +msgid "AutoDevOps|enable Auto DevOps (Beta)" +msgstr "" msgid "Available" msgstr "" @@ -307,6 +400,9 @@ msgstr "" msgid "Average per day: %{average}" msgstr "" +msgid "Begin with the selected commit" +msgstr "" + msgid "Billing" msgstr "" @@ -361,12 +457,9 @@ msgstr "" msgid "BillingPlans|per user" msgstr "" -msgid "Begin with the selected commit" -msgstr "" - -msgid "Branch" -msgid_plural "Branches" -msgstr[0] "分支 (branch) " +msgid "Branch (%{branch_count})" +msgid_plural "Branches (%{branch_count})" +msgstr[0] "" msgid "Branch %{branch_name} was created. To set up auto deploy, choose a GitLab CI Yaml template and commit your changes. %{link_to_autodeploy_doc}" msgstr "已建立分支 (branch) %{branch_name} 。如需設定自動部署, 在選擇合適的 GitLab CI Yaml 模板後,請送交 (commit) 您的編輯內容。%{link_to_autodeploy_doc}" @@ -659,6 +752,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "斷路器 (circuitbreaker) API" +msgid "Click the button below to begin the install process by navigating to the Kubernetes page" +msgstr "" + msgid "Click to expand text" msgstr "" @@ -713,6 +809,9 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -761,6 +860,9 @@ msgstr "" msgid "ClusterIntegration|Ingress" msgstr "" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "" @@ -812,9 +914,6 @@ msgstr "" msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "學習更多有關於%{link_to_documentation}" -msgid "ClusterIntegration|Learn more about Kubernetes" -msgstr "" - msgid "ClusterIntegration|Learn more about environments" msgstr "" @@ -950,6 +1049,12 @@ msgstr "設定正確" msgid "Collapse" msgstr "" +msgid "Comment and resolve discussion" +msgstr "" + +msgid "Comment and unresolve discussion" +msgstr "" + msgid "Comments" msgstr "留言" @@ -957,6 +1062,10 @@ msgid "Commit" msgid_plural "Commits" msgstr[0] "更動記錄 (commit) " +msgid "Commit (%{commit_count})" +msgid_plural "Commits (%{commit_count})" +msgstr[0] "" + msgid "Commit Message" msgstr "更動訊息" @@ -969,6 +1078,9 @@ msgstr "更動說明 (commit) " msgid "Commit statistics for %{ref} %{start_time} - %{end_time}" msgstr "" +msgid "Commit to %{branchName} branch" +msgstr "" + msgid "CommitBoxTitle|Commit" msgstr "送交" @@ -1110,6 +1222,9 @@ msgstr "複製網址到剪貼簿" msgid "Copy branch name to clipboard" msgstr "" +msgid "Copy command to clipboard" +msgstr "" + msgid "Copy commit SHA to clipboard" msgstr "複製更動記錄 (commit) 的 SHA 值到剪貼簿" @@ -1122,9 +1237,18 @@ msgstr "" msgid "Create New Directory" msgstr "建立新目錄" +msgid "Create a new branch" +msgstr "" + +msgid "Create a new branch and merge request" +msgstr "" + msgid "Create a personal access token on your account to pull or push via %{protocol}." msgstr "建立個人存取憑證 (access token) 以使用 %{protocol} 來上傳 (push) 或下載 (pull) 。" +msgid "Create branch" +msgstr "" + msgid "Create directory" msgstr "建立目錄" @@ -1143,6 +1267,9 @@ msgstr "" msgid "Create merge request" msgstr "發出合併請求 (merge request) " +msgid "Create merge request and branch" +msgstr "" + msgid "Create new branch" msgstr "新增分支(branch)" @@ -1167,6 +1294,12 @@ msgstr "建立標籤" msgid "CreateTokenToCloneLink|create a personal access token" msgstr "建立個人存取憑證 (access token)" +msgid "Creates a new branch from %{branchName}" +msgstr "" + +msgid "Creates a new branch from %{branchName} and re-directs to create a new merge request" +msgstr "" + msgid "Creating epic" msgstr "" @@ -1221,6 +1354,9 @@ msgstr "" msgid "December" msgstr "" +msgid "Default classification label" +msgstr "" + msgid "Define a custom pattern with cron syntax" msgstr "使用 Cron 語法自訂排程" @@ -1252,8 +1388,8 @@ msgstr "目錄名稱" msgid "Disable" msgstr "" -msgid "Discard changes" -msgstr "放棄修改" +msgid "Discard draft" +msgstr "" msgid "Discover GitLab Geo." msgstr "" @@ -1312,6 +1448,9 @@ msgstr "電子郵件" msgid "Enable" msgstr "" +msgid "Enable Auto DevOps" +msgstr "" + msgid "Environments|An error occurred while fetching the environments." msgstr "" @@ -1366,9 +1505,18 @@ msgstr "" msgid "Epics" msgstr "" +msgid "Epics Roadmap" +msgstr "" + msgid "Epics let you manage your portfolio of projects more efficiently and with less effort" msgstr "" +msgid "Error checking branch data. Please try again." +msgstr "" + +msgid "Error committing changes. Please try again." +msgstr "" + msgid "Error creating epic" msgstr "" @@ -1435,12 +1583,36 @@ msgstr "瀏覽專案" msgid "Explore public groups" msgstr "搜尋公開的群組" +msgid "External Classification Policy Authorization" +msgstr "" + +msgid "External authorization denied access to this project" +msgstr "" + +msgid "ExternalAuthorizationService|Classification Label" +msgstr "" + +msgid "ExternalAuthorizationService|Classification label" +msgstr "" + +msgid "ExternalAuthorizationService|When no classification label is set the default label `%{default_label}` will be used." +msgstr "" + +msgid "Failed Jobs" +msgstr "" + msgid "Failed to change the owner" msgstr "無法變更所有權" +msgid "Failed to remove issue from board, please try again." +msgstr "" + msgid "Failed to remove the pipeline schedule" msgstr "無法刪除流水線 (pipeline) 排程" +msgid "Failed to update issues, please try again." +msgstr "" + msgid "Feb" msgstr "" @@ -1456,6 +1628,9 @@ msgstr "檔案名稱" msgid "Files" msgstr "檔案" +msgid "Files (%{human_size})" +msgstr "" + msgid "Filter by commit message" msgstr "以更動說明篩選" @@ -1490,6 +1665,9 @@ msgstr "從議題 (issue) 建立直到部署至營運環境" msgid "From merge request merge until deploy to production" msgstr "從請求被合併後 (merge request merged) 直到部署至營運環境" +msgid "From the Kubernetes cluster details view, install Runner from the applications list" +msgstr "" + msgid "GPG Keys" msgstr "GPG 金鑰" @@ -1637,6 +1815,24 @@ msgstr "Google 身份驗證不是 %{link_to_documentation}。如果您想使用 msgid "Got it!" msgstr "" +msgid "GroupRoadmap|Epics let you manage your portfolio of projects more efficiently and with less effort" +msgstr "" + +msgid "GroupRoadmap|From %{dateWord}" +msgstr "" + +msgid "GroupRoadmap|Loading roadmap" +msgstr "" + +msgid "GroupRoadmap|Something went wrong while fetching epics" +msgstr "" + +msgid "GroupRoadmap|To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown – from %{startDate} to %{endDate}." +msgstr "" + +msgid "GroupRoadmap|Until %{dateWord}" +msgstr "" + msgid "GroupSettings|Prevent sharing a project within %{group} with other groups" msgstr "禁止與其他群組共享 %{group} 中的專案" @@ -1734,6 +1930,9 @@ msgstr "歷史" msgid "Housekeeping successfully started" msgstr "已開始維護" +msgid "If you already have files you can push them using the %{link_to_cli} below." +msgstr "" + msgid "Import repository" msgstr "匯入檔案庫 (repository)" @@ -1746,6 +1945,9 @@ msgstr "" msgid "Improve search with Advanced Global Search and GitLab Enterprise Edition." msgstr "" +msgid "Install Runner on Kubernetes" +msgstr "" + msgid "Install a Runner compatible with GitLab CI" msgstr "安裝與 GitLab CI 相容的 Runner" @@ -1795,6 +1997,9 @@ msgstr "" msgid "January" msgstr "" +msgid "Jobs" +msgstr "" + msgid "Jul" msgstr "" @@ -1825,6 +2030,9 @@ msgstr "" msgid "Kubernetes cluster was successfully updated." msgstr "" +msgid "Kubernetes configured" +msgstr "" + msgid "Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new Kubernetes Clusters page" msgstr "" @@ -1871,6 +2079,12 @@ msgstr "於" msgid "Learn more" msgstr "" +msgid "Learn more about Kubernetes" +msgstr "" + +msgid "Learn more about protected branches" +msgstr "" + msgid "Learn more in the" msgstr "了解更多" @@ -1889,6 +2103,9 @@ msgstr "退出專案" msgid "License" msgstr "" +msgid "List" +msgstr "" + msgid "Loading the GitLab IDE..." msgstr "" @@ -1898,6 +2115,9 @@ msgstr "鎖定" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock not found" +msgstr "" + msgid "Lock this %{issuableDisplayName}? Only project members will be able to comment." msgstr "" @@ -1907,6 +2127,9 @@ msgstr "鎖定" msgid "Locked Files" msgstr "" +msgid "Locks give the ability to lock specific file or folder." +msgstr "" + msgid "Login" msgstr "登入" @@ -1937,9 +2160,6 @@ msgstr "中位數" msgid "Members" msgstr "成員" -msgid "Merge Request" -msgstr "" - msgid "Merge Requests" msgstr "合併請求 (merge request)" @@ -1952,6 +2172,9 @@ msgstr "合併請求" msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" +msgid "MergeRequest|Approved" +msgstr "" + msgid "Merged" msgstr "" @@ -1976,9 +2199,18 @@ msgstr "" msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "新增 SSH 金鑰" +msgid "Modal|Cancel" +msgstr "" + +msgid "Modal|Close" +msgstr "" + msgid "Monitoring" msgstr "監控" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "健康檢查" @@ -2073,9 +2305,6 @@ msgstr "找不到檔案庫 (repository)" msgid "No schedules" msgstr "沒有排程" -msgid "No time spent" -msgstr "" - msgid "None" msgstr "無" @@ -2091,6 +2320,9 @@ msgstr "" msgid "Not enough data" msgstr "資料不足" +msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}" +msgstr "" + msgid "Notification events" msgstr "事件通知" @@ -2193,6 +2425,9 @@ msgstr "於新視窗開啟" msgid "Options" msgstr "選項" +msgid "Otherwise it is recommended you start with one of the options below." +msgstr "" + msgid "Overview" msgstr "總覽" @@ -2298,6 +2533,24 @@ msgstr "" msgid "Pipelines|Get started with Pipelines" msgstr "" +msgid "Pipeline|Retry pipeline" +msgstr "" + +msgid "Pipeline|Retry pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|Stop pipeline" +msgstr "" + +msgid "Pipeline|Stop pipeline #%{pipelineId}?" +msgstr "" + +msgid "Pipeline|You’re about to retry pipeline %{pipelineId}." +msgstr "" + +msgid "Pipeline|You’re about to stop pipeline %{pipelineId}." +msgstr "" + msgid "Pipeline|all" msgstr "所有" @@ -2331,6 +2584,9 @@ msgstr "私有 - 專案權限必須一一指派給每個使用者" msgid "Private - The group and its projects can only be viewed by members." msgstr "私有 - 群組及旗下專案只能被該群組成員查看" +msgid "Private projects can be created in your personal namespace with:" +msgstr "" + msgid "Profile" msgstr "個人資料" @@ -2493,12 +2749,30 @@ msgstr "抱歉,沒有符合搜尋條件的專案" msgid "ProjectsDropdown|This feature requires browser localStorage support" msgstr "此功能需要瀏覽器支援 localStorage" +msgid "PrometheusService|Active" +msgstr "" + +msgid "PrometheusService|Auto configuration" +msgstr "" + +msgid "PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments" +msgstr "" + msgid "PrometheusService|By default, Prometheus listens on ‘http://localhost:9090’. It’s not recommended to change the default address and port as this might affect or conflict with other services running on the GitLab server." msgstr "" msgid "PrometheusService|Finding and configuring metrics..." msgstr "" +msgid "PrometheusService|Install Prometheus on clusters" +msgstr "" + +msgid "PrometheusService|Manage clusters" +msgstr "" + +msgid "PrometheusService|Manual configuration" +msgstr "" + msgid "PrometheusService|Metrics" msgstr "" @@ -2520,9 +2794,18 @@ msgstr "" msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/" msgstr "" +msgid "PrometheusService|Prometheus is being automatically managed on your clusters" +msgstr "" + msgid "PrometheusService|Time-series monitoring service" msgstr "" +msgid "PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters" +msgstr "" + +msgid "PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below" +msgstr "" + msgid "PrometheusService|View environments" msgstr "" @@ -2541,6 +2824,12 @@ msgstr "" msgid "Push events" msgstr "推送 (push) 事件" +msgid "Push project from command line" +msgstr "" + +msgid "Push to create a project" +msgstr "" + msgid "PushRule|Committer restriction" msgstr "" @@ -2604,6 +2893,9 @@ msgstr "" msgid "Repository" msgstr "檔案庫 (repository)" +msgid "Repository has no locks." +msgstr "" + msgid "Request Access" msgstr "申請權限" @@ -2616,6 +2908,9 @@ msgstr "重置健康檢查存取憑證 (access token)" msgid "Reset runners registration token" msgstr "重置 Runner 註冊憑證 (registration token)" +msgid "Resolve discussion" +msgstr "" + msgid "Reveal value" msgid_plural "Reveal values" msgstr[0] "" @@ -2626,6 +2921,9 @@ msgstr "還原此更動記錄 (commit)" msgid "Revert this merge request" msgstr "還原此合併請求 (merge request) " +msgid "Roadmap" +msgstr "" + msgid "SSH Keys" msgstr "SSH 金鑰" @@ -2671,12 +2969,18 @@ msgstr "等待存取儲存空間的嘗試時間(秒)" msgid "Secret variables" msgstr "" +msgid "Security report" +msgstr "" + msgid "Select Archive Format" msgstr "選擇下載格式" msgid "Select a timezone" msgstr "選擇時區" +msgid "Select an existing Kubernetes cluster or create a new one" +msgstr "" + msgid "Select assignee" msgstr "" @@ -2689,6 +2993,9 @@ msgstr "選擇目標分支 (branch) " msgid "Selective synchronization" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "" @@ -2701,6 +3008,9 @@ msgstr "" msgid "Service Templates" msgstr "服務範本" +msgid "Service URL" +msgstr "" + msgid "Set a password on your account to pull or push via %{protocol}." msgstr "請先設定密碼,才能使用 %{protocol} 來上傳 (push) 或下載 (pull) 。" @@ -2710,15 +3020,15 @@ msgstr "" msgid "Set up Koding" msgstr "設定 Koding" -msgid "Set up auto deploy" -msgstr "設定自動部署" - msgid "SetPasswordToCloneLink|set a password" msgstr "設定密碼" msgid "Settings" msgstr "設定" +msgid "Setup a specific Runner automatically" +msgstr "" + msgid "SharedRunnersMinutesSettings|By resetting the pipeline minutes for this namespace, the currently used minutes will be set to zero." msgstr "" @@ -2728,6 +3038,9 @@ msgstr "" msgid "SharedRunnersMinutesSettings|Reset used pipeline minutes" msgstr "" +msgid "Show command" +msgstr "" + msgid "Show parent pages" msgstr "顯示上層頁面" @@ -2768,12 +3081,24 @@ msgstr "" msgid "Something went wrong when toggling the button" msgstr "" +msgid "Something went wrong while closing the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while fetching SAST." +msgstr "" + msgid "Something went wrong while fetching the projects." msgstr "讀取專案時發生錯誤。" msgid "Something went wrong while fetching the registry list." msgstr "讀取註冊列表時發生錯誤。" +msgid "Something went wrong while reopening the %{issuable}. Please try again later" +msgstr "" + +msgid "Something went wrong while resolving this discussion. Please try again." +msgstr "" + msgid "Something went wrong. Please try again." msgstr "" @@ -2879,6 +3204,9 @@ msgstr "" msgid "Source" msgstr "" +msgid "Source (branch or tag)" +msgstr "" + msgid "Source code" msgstr "原始碼" @@ -2918,9 +3246,9 @@ msgstr "切換分支 (branch) 或標籤" msgid "System Hooks" msgstr "系統鉤子" -msgid "Tag" -msgid_plural "Tags" -msgstr[0] "標籤" +msgid "Tag (%{tag_count})" +msgid_plural "Tags (%{tag_count})" +msgstr[0] "" msgid "Tags" msgstr "標籤" @@ -3051,9 +3379,15 @@ msgstr "本專案可讓任何人存取" msgid "The repository for this project does not exist." msgstr "本專案沒有檔案庫 (repository) " +msgid "The repository for this project is empty" +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "複閱階段顯示從合併請求 (merge request) 建立後至被合併的時間。當建立第一個合併請求 (merge request) 後,資料將自動填入。" +msgid "The roadmap shows the progress of your epics along a timeline" +msgstr "" + msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "試營運段顯示從合併請求 (merge request) 被合併後至部署營運的時間。當第一次部署營運後,資料將自動填入" @@ -3147,6 +3481,9 @@ msgstr "這代表在您建立一個空的檔案庫 (repository) 或是匯入一 msgid "This merge request is locked." msgstr "這個合併請求已被鎖定。" +msgid "This page is unavailable because you are not allowed to read information across multiple projects." +msgstr "" + msgid "This project" msgstr "" @@ -3314,9 +3651,15 @@ msgstr[0] "分鐘" msgid "Time|s" msgstr "秒" +msgid "Tip:" +msgstr "" + msgid "Title" msgstr "" +msgid "To view the roadmap, add a planned start or finish date to one of your epics in this group or its subgroups. Only epics in the past 3 months and the next 3 months are shown." +msgstr "" + msgid "Todo" msgstr "" @@ -3332,19 +3675,16 @@ msgstr "" msgid "Total Time" msgstr "總時間" -msgid "Total issue time spent" -msgstr "" - msgid "Total test time for all commits/merges" msgstr "合併 (merge) 與更動記錄 (commit) 的總測試時間" -msgid "Track activity with Contribution Analytics." +msgid "Total: %{total}" msgstr "" -msgid "Track groups of issues that share a theme, across projects and milestones" +msgid "Track activity with Contribution Analytics." msgstr "" -msgid "Total: %{total}" +msgid "Track groups of issues that share a theme, across projects and milestones" msgstr "" msgid "Track time with quick actions" @@ -3356,9 +3696,6 @@ msgstr "" msgid "Turn on Service Desk" msgstr "" -msgid "Type %{value} to confirm:" -msgstr "" - msgid "Unable to reset project cache." msgstr "" @@ -3374,6 +3711,9 @@ msgstr "" msgid "Unlocked" msgstr "已解鎖" +msgid "Unresolve discussion" +msgstr "" + msgid "Unstar" msgstr "取消收藏" @@ -3419,6 +3759,9 @@ msgstr "使用全域通知設定" msgid "Variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use variables for passwords, secret keys, or whatever you want." msgstr "" +msgid "View epics list" +msgstr "" + msgid "View file @ " msgstr "瀏覽檔案 @ " @@ -3455,6 +3798,9 @@ msgstr "因該階段的資料不足而無法顯示相關資訊" msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "" +msgid "Web IDE" +msgstr "" + msgid "Webhooks allow you to trigger a URL if, for example, new code is pushed or a new issue is created. You can configure webhooks to listen for specific events like pushes, issues or merge requests. Group webhooks will apply to all projects in a group, allowing you to standardize webhook functionality across your entire group." msgstr "" @@ -3575,6 +3921,9 @@ msgstr "" msgid "Withdraw Access Request" msgstr "取消權限申請" +msgid "Write a commit message..." +msgstr "" + msgid "You are going to remove %{group_name}. Removed groups CANNOT be restored! Are you ABSOLUTELY sure?" msgstr "將要刪除 %{group_name}。被刪除的群組無法復原!真的「確定」要這麼做嗎?" @@ -3587,10 +3936,13 @@ msgstr "將要刪除本分支專案與主幹 %{forked_from_project} 的所有關 msgid "You are going to transfer %{project_name_with_namespace} to another owner. Are you ABSOLUTELY sure?" msgstr "將要把 %{project_name_with_namespace} 的所有權轉移給另一個人。真的「確定」要這麼做嗎?" +msgid "You can also create a project from the command line." +msgstr "" + msgid "You can also star a label to make it a priority label." msgstr "" -msgid "You can move around the graph by using the arrow keys." +msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}" msgstr "" msgid "You can move around the graph by using the arrow keys." @@ -3608,12 +3960,24 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "您不能修改這個唯讀的 GitLab 主機。" +msgid "You do not have the correct permissions to override the settings from the LDAP group sync." +msgstr "" + +msgid "You have no permissions" +msgstr "" + msgid "You have reached your project limit" msgstr "您已達到專案數量限制" +msgid "You must have master access to force delete a lock" +msgstr "" + msgid "You must sign in to star a project" msgstr "必須登入才能收藏專案" +msgid "You need a different license to enable FileLocks feature" +msgstr "" + msgid "You need permission." msgstr "需要權限才能這麼做。" @@ -3647,6 +4011,9 @@ msgstr "" msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" msgstr "" +msgid "Your changes have been committed. Commit %{commitId} %{commitStats}" +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "你的留言將不會被公開。" @@ -3674,7 +4041,7 @@ msgstr "" msgid "ciReport|DAST detected no alerts by analyzing the review app" msgstr "" -msgid "ciReport|Failed to load ${type} report" +msgid "ciReport|Failed to load %{reportName} report" msgstr "" msgid "ciReport|Fixed:" @@ -3686,7 +4053,7 @@ msgstr "" msgid "ciReport|Learn more about whitelisting" msgstr "" -msgid "ciReport|Loading ${type} report" +msgid "ciReport|Loading %{reportName} report" msgstr "" msgid "ciReport|No changes to code quality" @@ -3701,6 +4068,15 @@ msgstr "" msgid "ciReport|SAST" msgstr "" +msgid "ciReport|SAST degraded on" +msgstr "" + +msgid "ciReport|SAST detected" +msgstr "" + +msgid "ciReport|SAST detected no new security vulnerabilities" +msgstr "" + msgid "ciReport|SAST detected no security vulnerabilities" msgstr "" @@ -3713,6 +4089,12 @@ msgstr "" msgid "ciReport|Unapproved vulnerabilities (red) can be marked as approved. %{helpLink}" msgstr "" +msgid "ciReport|no security vulnerabilities" +msgstr "" + +msgid "command line instructions" +msgstr "" + msgid "commit" msgstr "" @@ -3729,10 +4111,40 @@ msgstr[0] "天" msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command." msgstr "" +msgid "is invalid because there is downstream lock" +msgstr "" + +msgid "is invalid because there is upstream lock" +msgstr "" + +msgid "locked by %{path_lock_user_name} %{created_at}" +msgstr "" + msgid "merge request" msgid_plural "merge requests" msgstr[0] "" +msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch" +msgstr "" + +msgid "mrWidget|Add approval" +msgstr "" + +msgid "mrWidget|An error occured while removing your approval." +msgstr "" + +msgid "mrWidget|An error occured while retrieving approval data for this merge request." +msgstr "" + +msgid "mrWidget|An error occured while submitting your approval." +msgstr "" + +msgid "mrWidget|Approve" +msgstr "" + +msgid "mrWidget|Approved by" +msgstr "" + msgid "mrWidget|Cancel automatic merge" msgstr "" @@ -3766,6 +4178,9 @@ msgstr "" msgid "mrWidget|If the %{branch} branch exists in your local repository, you can merge this merge request manually using the" msgstr "" +msgid "mrWidget|If the %{missingBranchName} branch exists in your local repository, you can merge this merge request manually using the command line" +msgstr "" + msgid "mrWidget|Mentions" msgstr "" @@ -3799,6 +4214,9 @@ msgstr "" msgid "mrWidget|Remove source branch" msgstr "" +msgid "mrWidget|Remove your approval" +msgstr "" + msgid "mrWidget|Request to merge" msgstr "" @@ -3853,6 +4271,9 @@ msgstr "" msgid "mrWidget|You can remove source branch now" msgstr "" +msgid "mrWidget|branch does not exist." +msgstr "" + msgid "mrWidget|command line" msgstr "" @@ -3899,3 +4320,6 @@ msgstr "使用者名稱" msgid "uses Kubernetes clusters to deploy your code!" msgstr "" +msgid "with %{additions} additions, %{deletions} deletions." +msgstr "" + -- cgit v1.2.3 From 5c5fc89b6c2ca4a65bd007dfedb1a026d66c7bff Mon Sep 17 00:00:00 2001 From: Riccardo Padovani Date: Mon, 5 Mar 2018 14:51:20 +0000 Subject: #43691: DiffNotes not counted by ContributionsCalendar --- .../feature--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) -- cgit v1.2.3 From a87bd1d401a324e19f1292afe8734193ae43198c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 5 Mar 2018 16:55:51 +0200 Subject: Update workers spec to use full_path instead of path_with_namespace Signed-off-by: Dmitriy Zaporozhets --- spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb | 2 +- spec/workers/gitlab/github_import/import_issue_worker_spec.rb | 2 +- spec/workers/gitlab/github_import/import_note_worker_spec.rb | 2 +- spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb index 7c8c665a9b3..48e7eaf32fc 100644 --- a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb +++ b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::GithubImport::ImportDiffNoteWorker do describe '#import' do it 'imports a diff note' do - project = double(:project, path_with_namespace: 'foo/bar') + project = double(:project, full_path: 'foo/bar') client = double(:client) importer = double(:importer) hash = { diff --git a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb index 4116380ff4d..8cf6ac15919 100644 --- a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb +++ b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::GithubImport::ImportIssueWorker do describe '#import' do it 'imports an issue' do - project = double(:project, path_with_namespace: 'foo/bar') + project = double(:project, full_path: 'foo/bar') client = double(:client) importer = double(:importer) hash = { diff --git a/spec/workers/gitlab/github_import/import_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_note_worker_spec.rb index 0ca825a722b..677697c02df 100644 --- a/spec/workers/gitlab/github_import/import_note_worker_spec.rb +++ b/spec/workers/gitlab/github_import/import_note_worker_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::GithubImport::ImportNoteWorker do describe '#import' do it 'imports a note' do - project = double(:project, path_with_namespace: 'foo/bar') + project = double(:project, full_path: 'foo/bar') client = double(:client) importer = double(:importer) hash = { diff --git a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb index d49f560af42..e287ddbe0d7 100644 --- a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb +++ b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::GithubImport::ImportPullRequestWorker do describe '#import' do it 'imports a pull request' do - project = double(:project, path_with_namespace: 'foo/bar') + project = double(:project, full_path: 'foo/bar') client = double(:client) importer = double(:importer) hash = { -- cgit v1.2.3 From f53cf3c486fbf7bf66b7207ff0d92f23aceb8a71 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 5 Mar 2018 17:31:33 +0200 Subject: Revert js changes to name_with_namespace Signed-off-by: Dmitriy Zaporozhets --- spec/javascripts/gl_dropdown_spec.js | 6 +++--- spec/javascripts/projects_dropdown/store/projects_store_spec.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index 20728e3bf76..67b854f61c0 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -50,7 +50,7 @@ describe('glDropdown', function describeDropdown() { search: { fields: ['name'] }, - text: project => (project.full_name || project.name), + text: project => (project.name_with_namespace || project.name), id: project => project.id, }, extraOpts); this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown(options); @@ -76,7 +76,7 @@ describe('glDropdown', function describeDropdown() { }); it('escapes HTML as text', () => { - this.projectsData[0].full_name = ''; + this.projectsData[0].name_with_namespace = ''; initDropDown.call(this, false); @@ -88,7 +88,7 @@ describe('glDropdown', function describeDropdown() { }); it('should output HTML when highlighting', () => { - this.projectsData[0].full_name = 'testing'; + this.projectsData[0].name_with_namespace = 'testing'; $('.dropdown-input .dropdown-input-field').val('test'); initDropDown.call(this, false, true, { diff --git a/spec/javascripts/projects_dropdown/store/projects_store_spec.js b/spec/javascripts/projects_dropdown/store/projects_store_spec.js index ff1cb7abf20..e57399d37cd 100644 --- a/spec/javascripts/projects_dropdown/store/projects_store_spec.js +++ b/spec/javascripts/projects_dropdown/store/projects_store_spec.js @@ -24,7 +24,7 @@ describe('ProjectsStore', () => { const processedProjects = store.getSearchedProjects(); expect(processedProjects.length).toBe(1); expect(processedProjects[0].id).toBe(mockRawProject.id); - expect(processedProjects[0].namespace).toBe(mockRawProject.full_name); + expect(processedProjects[0].namespace).toBe(mockRawProject.name_with_namespace); expect(processedProjects[0].webUrl).toBe(mockRawProject.web_url); expect(processedProjects[0].avatarUrl).toBe(mockRawProject.avatar_url); }); -- cgit v1.2.3 From 2cc43aaaf3162db8c584df3bb9d1a42d92084fae Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Fri, 2 Mar 2018 21:04:32 +0100 Subject: 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) -- cgit v1.2.3 From 9fb733c3234318d31be09543fd788a75717db12d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 5 Mar 2018 18:25:52 +0200 Subject: Revert few more broken specs related to *_with_namespace methods Signed-off-by: Dmitriy Zaporozhets --- spec/controllers/autocomplete_controller_spec.rb | 4 ++-- spec/workers/concerns/gitlab/github_import/object_importer_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index d12cd83ac4a..fb6d82d7de3 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -246,7 +246,7 @@ describe AutocompleteController do expect(json_response.size).to eq(1) expect(json_response.first['id']).to eq authorized_project.id - expect(json_response.first['full_name']).to eq authorized_project.full_name + expect(json_response.first['name_with_namespace']).to eq authorized_project.full_name end end end @@ -267,7 +267,7 @@ describe AutocompleteController do expect(json_response.size).to eq(1) expect(json_response.first['id']).to eq authorized_search_project.id - expect(json_response.first['full_name']).to eq authorized_search_project.full_name + expect(json_response.first['name_with_namespace']).to eq authorized_search_project.full_name end end end diff --git a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb index 68cfe9d5545..615462380e0 100644 --- a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb +++ b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb @@ -25,7 +25,7 @@ describe Gitlab::GithubImport::ObjectImporter do importer_class = double(:importer_class) importer_instance = double(:importer_instance) representation = double(:representation) - project = double(:project, path_with_namespace: 'foo/bar') + project = double(:project, full_path: 'foo/bar') client = double(:client) expect(worker) -- cgit v1.2.3 From 631eed028bffc55f0a80b72ab3598bc73e49272b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Mon, 5 Mar 2018 16:41:48 +0000 Subject: Remove default scope from todos This was causing todo priority sorting to fail. --- app/finders/todos_finder.rb | 4 +--- app/models/todo.rb | 14 ++++++++------ spec/finders/todos_finder_spec.rb | 27 +++++++++++++++------------ 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/app/finders/todos_finder.rb b/app/finders/todos_finder.rb index 47c8b9b60ed..150f4c7688b 100644 --- a/app/finders/todos_finder.rb +++ b/app/finders/todos_finder.rb @@ -150,9 +150,7 @@ class TodosFinder if project? items.where(project: project) else - projects = Project - .public_or_visible_to_user(current_user) - .order_id_desc + projects = Project.public_or_visible_to_user(current_user) items.joins(:project).merge(projects) end diff --git a/app/models/todo.rb b/app/models/todo.rb index bb5965e20eb..8afacd188e0 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -32,8 +32,6 @@ class Todo < ActiveRecord::Base validates :target_id, presence: true, unless: :for_commit? validates :commit_id, presence: true, if: :for_commit? - default_scope { reorder(id: :desc) } - scope :pending, -> { with_state(:pending) } scope :done, -> { with_state(:done) } @@ -53,10 +51,14 @@ class Todo < ActiveRecord::Base # milestones, but still show something if the user has a URL with that # selected. def sort(method) - case method.to_s - when 'priority', 'label_priority' then order_by_labels_priority - else order_by(method) - end + sorted = + case method.to_s + when 'priority', 'label_priority' then order_by_labels_priority + else order_by(method) + end + + # Break ties with the ID column for pagination + sorted.order(id: :desc) end # Order by priority depending on which issue/merge request the Todo belongs to diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb index 90eb0fe21e4..9747b9402a7 100644 --- a/spec/finders/todos_finder_spec.rb +++ b/spec/finders/todos_finder_spec.rb @@ -2,12 +2,13 @@ require 'spec_helper' describe TodosFinder do describe '#execute' do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:finder) { described_class } + let(:user) { create(:user) } + let(:group) { create(:group) } + let(:project) { create(:project, namespace: group) } + let(:finder) { described_class } before do - project.add_developer(user) + group.add_developer(user) end describe '#sort' do @@ -34,17 +35,20 @@ describe TodosFinder do end it "sorts by priority" do + project_2 = create(:project) + label_1 = create(:label, title: 'label_1', project: project, priority: 1) label_2 = create(:label, title: 'label_2', project: project, priority: 2) label_3 = create(:label, title: 'label_3', project: project, priority: 3) + label_1_2 = create(:label, title: 'label_1', project: project_2, priority: 1) issue_1 = create(:issue, title: 'issue_1', project: project) issue_2 = create(:issue, title: 'issue_2', project: project) issue_3 = create(:issue, title: 'issue_3', project: project) issue_4 = create(:issue, title: 'issue_4', project: project) - merge_request_1 = create(:merge_request, source_project: project) + merge_request_1 = create(:merge_request, source_project: project_2) - merge_request_1.labels << label_1 + merge_request_1.labels << label_1_2 # Covers the case where Todo has more than one label issue_3.labels << label_1 @@ -57,15 +61,14 @@ describe TodosFinder do todo_2 = create(:todo, user: user, project: project, target: issue_2) todo_3 = create(:todo, user: user, project: project, target: issue_3, created_at: 2.hours.ago) todo_4 = create(:todo, user: user, project: project, target: issue_1) - todo_5 = create(:todo, user: user, project: project, target: merge_request_1, created_at: 1.hour.ago) + todo_5 = create(:todo, user: user, project: project_2, target: merge_request_1, created_at: 1.hour.ago) + + project_2.add_developer(user) todos = finder.new(user, { sort: 'priority' }).execute - expect(todos.first).to eq(todo_3) - expect(todos.second).to eq(todo_5) - expect(todos.third).to eq(todo_4) - expect(todos.fourth).to eq(todo_2) - expect(todos.fifth).to eq(todo_1) + puts todos.to_sql + expect(todos).to eq([todo_3, todo_5, todo_4, todo_2, todo_1]) end end end -- cgit v1.2.3 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: 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 ++++++--------------- .../import_export/project_tree_restorer_spec.rb | 4 + .../import_export/project_tree_saver_spec.rb | 7 + .../gitlab/import_export/safe_model_attributes.yml | 9 + .../lib/gitlab/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 ++++++++++ spec/validators/url_placeholder_validator_spec.rb | 39 ++ spec/validators/url_validator_spec.rb | 46 ++ spec/views/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 -- cgit v1.2.3 From e3251a4077d4667239b23437921f49a1d195406c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 5 Mar 2018 18:40:04 +0000 Subject: Changes after review --- .../commit/pipelines/pipelines_table.vue | 2 +- .../pages/projects/pipelines/index/index.js | 9 ++-- .../pipelines/components/empty_state.vue | 2 +- .../javascripts/pipelines/components/pipelines.vue | 2 +- .../pipelines/stores/pipelines_store.js | 7 +-- spec/javascripts/pipelines/pipelines_spec.js | 52 +++++++++++----------- spec/javascripts/pipelines/pipelines_store_spec.js | 7 +-- 7 files changed, 35 insertions(+), 46 deletions(-) diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue index 3b3072ebd3e..466a5b5d635 100644 --- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue +++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue @@ -88,7 +88,7 @@ diff --git a/app/assets/javascripts/pages/projects/pipelines/index/index.js b/app/assets/javascripts/pages/projects/pipelines/index/index.js index 4c092c60e72..a84e2790680 100644 --- a/app/assets/javascripts/pages/projects/pipelines/index/index.js +++ b/app/assets/javascripts/pages/projects/pipelines/index/index.js @@ -12,14 +12,13 @@ document.addEventListener('DOMContentLoaded', () => new Vue({ pipelinesComponent, }, data() { - const store = new PipelinesStore(); - return { - store, - dataset: document.querySelector(this.$options.el).dataset, + store: new PipelinesStore(), }; }, - + created() { + this.dataset = document.querySelector(this.$options.el).dataset; + }, render(createElement) { return createElement('pipelines-component', { props: { diff --git a/app/assets/javascripts/pipelines/components/empty_state.vue b/app/assets/javascripts/pipelines/components/empty_state.vue index 51f05c7827e..10ac8c08bed 100644 --- a/app/assets/javascripts/pipelines/components/empty_state.vue +++ b/app/assets/javascripts/pipelines/components/empty_state.vue @@ -43,7 +43,7 @@
{{ s__('Pipelines|Get started with Pipelines') }} diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue index 54f5fb7678b..6e5ee68eeb1 100644 --- a/app/assets/javascripts/pipelines/components/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -323,7 +323,7 @@ diff --git a/app/assets/javascripts/pipelines/stores/pipelines_store.js b/app/assets/javascripts/pipelines/stores/pipelines_store.js index 2f738edd241..651251d2623 100644 --- a/app/assets/javascripts/pipelines/stores/pipelines_store.js +++ b/app/assets/javascripts/pipelines/stores/pipelines_store.js @@ -5,12 +5,7 @@ export default class PipelinesStore { this.state = {}; this.state.pipelines = []; - this.state.count = { - all: 0, - finished: 0, - pending: 0, - running: 0, - }; + this.state.count = {}; this.state.pageInfo = {}; } diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js index 043c8cfd8bc..84fd0329f08 100644 --- a/spec/javascripts/pipelines/pipelines_spec.js +++ b/spec/javascripts/pipelines/pipelines_spec.js @@ -183,10 +183,10 @@ describe('Pipelines', () => { }); it('does not render tabs nor buttons', () => { - expect(vm.$el.querySelector('.js-pipelines-tab-all')).toBe(null); - expect(vm.$el.querySelector('.js-run-pipeline')).toBe(null); - expect(vm.$el.querySelector('.js-ci-lint')).toBe(null); - expect(vm.$el.querySelector('.js-clear-cache')).toBe(null); + expect(vm.$el.querySelector('.js-pipelines-tab-all')).toBeNull(); + expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull(); + expect(vm.$el.querySelector('.js-ci-lint')).toBeNull(); + expect(vm.$el.querySelector('.js-clear-cache')).toBeNull(); }); }); @@ -222,7 +222,7 @@ describe('Pipelines', () => { }); it('renders error state', () => { - expect(vm.$el.querySelector('.empty-state').textContent.trim()).toContain('There was an error with fetching the pipelines.'); + expect(vm.$el.querySelector('.empty-state').textContent.trim()).toContain('There was an error fetching the pipelines.'); }); }); }); @@ -254,9 +254,9 @@ describe('Pipelines', () => { }); it('does not render buttons', () => { - expect(vm.$el.querySelector('.js-run-pipeline')).toBe(null); - expect(vm.$el.querySelector('.js-ci-lint')).toBe(null); - expect(vm.$el.querySelector('.js-clear-cache')).toBe(null); + expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull(); + expect(vm.$el.querySelector('.js-ci-lint')).toBeNull(); + expect(vm.$el.querySelector('.js-clear-cache')).toBeNull(); }); it('renders pipelines table', () => { @@ -291,9 +291,9 @@ describe('Pipelines', () => { }); it('does not render buttons', () => { - expect(vm.$el.querySelector('.js-run-pipeline')).toBe(null); - expect(vm.$el.querySelector('.js-ci-lint')).toBe(null); - expect(vm.$el.querySelector('.js-clear-cache')).toBe(null); + expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull(); + expect(vm.$el.querySelector('.js-ci-lint')).toBeNull(); + expect(vm.$el.querySelector('.js-clear-cache')).toBeNull(); }); it('renders tab empty state', () => { @@ -324,14 +324,14 @@ describe('Pipelines', () => { it('renders empty state without button to set CI', () => { expect(vm.$el.querySelector('.js-empty-state').textContent.trim()).toEqual('This project is not currently set up to run pipelines.'); - expect(vm.$el.querySelector('.js-get-started-pipelines')).toBe(null); + expect(vm.$el.querySelector('.js-get-started-pipelines')).toBeNull(); }); - it('does not render tabs nor buttons', () => { - expect(vm.$el.querySelector('.js-pipelines-tab-all')).toBe(null); - expect(vm.$el.querySelector('.js-run-pipeline')).toBe(null); - expect(vm.$el.querySelector('.js-ci-lint')).toBe(null); - expect(vm.$el.querySelector('.js-clear-cache')).toBe(null); + it('does not render tabs or buttons', () => { + expect(vm.$el.querySelector('.js-pipelines-tab-all')).toBeNull(); + expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull(); + expect(vm.$el.querySelector('.js-ci-lint')).toBeNull(); + expect(vm.$el.querySelector('.js-clear-cache')).toBeNull(); }); }); @@ -361,13 +361,13 @@ describe('Pipelines', () => { }); it('does not renders buttons', () => { - expect(vm.$el.querySelector('.js-run-pipeline')).toBe(null); - expect(vm.$el.querySelector('.js-ci-lint')).toBe(null); - expect(vm.$el.querySelector('.js-clear-cache')).toBe(null); + expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull(); + expect(vm.$el.querySelector('.js-ci-lint')).toBeNull(); + expect(vm.$el.querySelector('.js-clear-cache')).toBeNull(); }); it('renders error state', () => { - expect(vm.$el.querySelector('.empty-state').textContent.trim()).toContain('There was an error with fetching the pipelines.'); + expect(vm.$el.querySelector('.empty-state').textContent.trim()).toContain('There was an error fetching the pipelines.'); }); }); }); @@ -527,10 +527,10 @@ describe('Pipelines', () => { describe('tabs', () => { it('returns default tabs', () => { expect(vm.tabs).toEqual([ - { name: 'All', scope: 'all', count: 0, isActive: true }, - { name: 'Pending', scope: 'pending', count: 0, isActive: false }, - { name: 'Running', scope: 'running', count: 0, isActive: false }, - { name: 'Finished', scope: 'finished', count: 0, isActive: false }, + { name: 'All', scope: 'all', count: undefined, isActive: true }, + { name: 'Pending', scope: 'pending', count: undefined, isActive: false }, + { name: 'Running', scope: 'running', count: undefined, isActive: false }, + { name: 'Finished', scope: 'finished', count: undefined, isActive: false }, { name: 'Branches', scope: 'branches', isActive: false }, { name: 'Tags', scope: 'tags', isActive: false }, ]); @@ -646,7 +646,7 @@ describe('Pipelines', () => { }); }); - it('returs true when state is empty tab & has already made the first request', (done) => { + it('returns true when state is empty tab & has already made the first request', (done) => { vm.isLoading = false; vm.state.count.all = 10; vm.hasMadeRequest = true; diff --git a/spec/javascripts/pipelines/pipelines_store_spec.js b/spec/javascripts/pipelines/pipelines_store_spec.js index 0d628285a7b..10ff0c6bb84 100644 --- a/spec/javascripts/pipelines/pipelines_store_spec.js +++ b/spec/javascripts/pipelines/pipelines_store_spec.js @@ -9,12 +9,7 @@ describe('Pipelines Store', () => { it('should be initialized with an empty state', () => { expect(store.state.pipelines).toEqual([]); - expect(store.state.count).toEqual({ - all: 0, - finished: 0, - pending: 0, - running: 0, - }); + expect(store.state.count).toEqual({}); expect(store.state.pageInfo).toEqual({}); }); -- cgit v1.2.3 From 4990bcc51036a0e0423261a4c846d1afdad3898c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Brendan=20O=27Leary=20=F0=9F=90=A2?= Date: Mon, 5 Mar 2018 19:19:21 +0000 Subject: Resolve "SSH key add text" --- app/views/profiles/keys/index.html.haml | 4 +++- changelogs/unreleased/43829-update-ssh-addtion-text.yml | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/43829-update-ssh-addtion-text.yml diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index 457583cfd35..1e206def7ee 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -12,7 +12,9 @@ Add an SSH key %p.profile-settings-content Before you can add an SSH key you need to - = link_to "generate it.", help_page_path("ssh/README") + = link_to "generate one", help_page_path("ssh/README", anchor: 'generating-a-new-ssh-key-pair') + or use an + = link_to "existing key.", help_page_path("ssh/README", anchor: 'locating-an-existing-ssh-key-pair') = render 'form' %hr %h5 diff --git a/changelogs/unreleased/43829-update-ssh-addtion-text.yml b/changelogs/unreleased/43829-update-ssh-addtion-text.yml new file mode 100644 index 00000000000..b7052bb171e --- /dev/null +++ b/changelogs/unreleased/43829-update-ssh-addtion-text.yml @@ -0,0 +1,5 @@ +--- +title: Update SSH key link to include existing keys +merge_request: +author: Brendan O'Leary +type: changed -- cgit v1.2.3 From 32d121343d91eea44d1de344b398d30014180da0 Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Tue, 20 Feb 2018 20:23:49 -0600 Subject: CI/CD-only projects FE See https://gitlab.com/gitlab-org/gitlab-ee/issues/3839 Conflicts: app/assets/javascripts/projects/project_new.js app/views/shared/_import_form.html.haml ee/app/controllers/ee/projects_controller.rb ee/spec/features/projects/new_project_spec.rb locale/gitlab.pot --- .../javascripts/behaviors/toggler_behavior.js | 4 +- app/controllers/projects_controller.rb | 6 ++- app/helpers/import_helper.rb | 26 +++++++++++ app/views/projects/_new_project_fields.html.haml | 2 + app/views/projects/imports/show.html.haml | 13 +++--- app/views/projects/new.html.haml | 31 +++++++----- app/views/shared/_import_form.html.haml | 19 +++++--- .../import/img/import_projects_from_repo_url.png | Bin 0 -> 150259 bytes doc/user/project/import/index.md | 1 + doc/user/project/import/repo_by_url.md | 12 +++++ locale/gitlab.pot | 52 ++++++++++++++++++++- spec/features/projects/new_project_spec.rb | 2 +- 12 files changed, 137 insertions(+), 31 deletions(-) create mode 100644 doc/user/project/import/img/import_projects_from_repo_url.png create mode 100644 doc/user/project/import/repo_by_url.md diff --git a/app/assets/javascripts/behaviors/toggler_behavior.js b/app/assets/javascripts/behaviors/toggler_behavior.js index 417ac31fc86..81c89441424 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.js +++ b/app/assets/javascripts/behaviors/toggler_behavior.js @@ -12,7 +12,7 @@ $(() => { const $container = $(container); $container - .find('.js-toggle-button .fa') + .find('.js-toggle-button .fa-chevron-up, .js-toggle-button .fa-chevron-down') .toggleClass('fa-chevron-up', toggleState) .toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined); @@ -22,7 +22,7 @@ $(() => { } $('body').on('click', '.js-toggle-button', function toggleButton(e) { - e.target.classList.toggle('open'); + e.currentTarget.classList.toggle(e.currentTarget.dataset.toggleOpenClass || 'open'); toggleContainer($(this).closest('.js-toggle-container')); const targetTag = e.currentTarget.tagName.toLowerCase(); diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 4a2f5ab3f88..67fad15ff92 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -45,7 +45,7 @@ class ProjectsController < Projects::ApplicationController notice: _("Project '%{project_name}' was successfully created.") % { project_name: @project.name } ) else - render 'new', locals: { active_tab: ('import' if project_params[:import_url].present?) } + render 'new', locals: { active_tab: active_new_project_tab } end end @@ -363,6 +363,10 @@ class ProjectsController < Projects::ApplicationController {} end + def active_new_project_tab + project_params[:import_url].present? ? 'import' : 'blank' + end + def repo_exists? project.repository_exists? && !project.empty_repo? diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb index a18ebfb6030..9af481f0b4f 100644 --- a/app/helpers/import_helper.rb +++ b/app/helpers/import_helper.rb @@ -1,4 +1,8 @@ module ImportHelper + def has_ci_cd_only_params? + false + end + def import_project_target(owner, name) namespace = current_user.can_create_group? ? owner : current_user.namespace_path "#{namespace}/#{name}" @@ -10,6 +14,28 @@ module ImportHelper link_to path_with_namespace, url, target: '_blank', rel: 'noopener noreferrer' end + def import_will_timeout_message(_ci_cd_only) + timeout = time_interval_in_words(Gitlab.config.gitlab_shell.git_timeout) + _('The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination.') % { timeout: timeout } + end + + def import_svn_message(_ci_cd_only) + svn_link = link_to _('this document'), help_page_path('user/project/import/svn') + _('To import an SVN repository, check out %{svn_link}.').html_safe % { svn_link: svn_link } + end + + def import_in_progress_title + if @project.forked? + _('Forking in progress') + else + _('Import in progress') + end + end + + def import_wait_and_refresh_message + _('Please wait while we import the repository for you. Refresh at will.') + end + private def github_project_url(path_with_namespace) diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml index d367bd6be7b..f4b5ef1555e 100644 --- a/app/views/projects/_new_project_fields.html.haml +++ b/app/views/projects/_new_project_fields.html.haml @@ -1,6 +1,8 @@ - visibility_level = params.dig(:project, :visibility_level) || default_project_visibility +- ci_cd_only = local_assigns.fetch(:ci_cd_only, false) .row{ id: project_name_id } + = f.hidden_field :ci_cd_only, value: ci_cd_only .form-group.project-path.col-sm-6 = f.label :namespace_id, class: 'label-light' do %span diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml index 8c490773a56..3b0c828ccd1 100644 --- a/app/views/projects/imports/show.html.haml +++ b/app/views/projects/imports/show.html.haml @@ -1,12 +1,11 @@ -- page_title @project.forked? ? "Forking in progress" : "Import in progress" +- page_title import_in_progress_title + .save-project-loader .center %h2 %i.fa.fa-spinner.fa-spin - - if @project.forked? - Forking in progress. - - else - Import in progress. - - if @project.external_import? + = import_in_progress_title + - if !has_ci_cd_only_params? && @project.external_import? %p.monospace git clone --bare #{@project.safe_import_url} - %p Please wait while we import the repository for you. Refresh at will. + %p + = import_wait_and_refresh_message diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 679ba23a4db..1d31b58a2cc 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -12,11 +12,14 @@ .row.prepend-top-default .col-lg-3.profile-settings-sidebar %h4.prepend-top-0 - New project + = _('New project') %p - A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), #{link_to 'among other things', help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank'}. + - among_other_things_link = link_to _('among other things'), help_page_path("user/project/index.md", anchor: "projects-features"), target: '_blank' + = _('A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}.').html_safe % { among_other_things_link: among_other_things_link } %p - All features are enabled when you create a project, but you can disable the ones you don’t need in the project settings. + = _('All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings.') + -# EE-specific start + -# EE-specific end .md = brand_new_project_guidelines %p @@ -28,36 +31,38 @@ .col-lg-9.js-toggle-container %ul.nav-links.gitlab-tabs{ role: 'tablist' } - %li{ class: ('active' if active_tab == 'blank'), role: 'presentation' } + %li{ class: active_when(active_tab == 'blank'), role: 'presentation' } %a{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab' }, role: 'tab' } %span.hidden-xs Blank project %span.visible-xs Blank - %li{ class: ('active' if active_tab == 'template'), role: 'presentation' } + %li{ class: active_when(active_tab == 'template'), role: 'presentation' } %a{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab' }, role: 'tab' } %span.hidden-xs Create from template %span.visible-xs Template - %li{ class: ('active' if active_tab == 'import'), role: 'presentation' } + %li{ class: active_when(active_tab == 'import'), role: 'presentation' } %a{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab' }, role: 'tab' } %span.hidden-xs Import project %span.visible-xs Import + -# EE-specific start + -# EE-specific end .tab-content.gitlab-tab-content - .tab-pane{ id: 'blank-project-pane', class: ('active' if active_tab == 'blank'), role: 'tabpanel' } + .tab-pane{ id: 'blank-project-pane', class: active_when(active_tab == 'blank'), role: 'tabpanel' } = form_for @project, html: { class: 'new_project' } do |f| = render 'new_project_fields', f: f, project_name_id: "blank-project-name" - .tab-pane.no-padding{ id: 'create-from-template-pane', class: ('active' if active_tab == 'template'), role: 'tabpanel' } + .tab-pane.no-padding{ id: 'create-from-template-pane', class: active_when(active_tab == 'template'), role: 'tabpanel' } = form_for @project, html: { class: 'new_project' } do |f| .project-template .form-group %div = render 'project_templates', f: f - .tab-pane.import-project-pane{ id: 'import-project-pane', class: ('active' if active_tab == 'import'), role: 'tabpanel' } + .tab-pane.import-project-pane.js-toggle-container{ id: 'import-project-pane', class: active_when(active_tab == 'import'), role: 'tabpanel' } = form_for @project, html: { class: 'new_project' } do |f| - if import_sources_enabled? .project-import.row - .col-sm-12 + .col-lg-12 .form-group.import-btn-container.clearfix = f.label :visibility_level, class: 'label-light' do #the label here seems wrong Import project from @@ -97,7 +102,7 @@ Gitea %div - if git_import_enabled? - %button.btn.js-toggle-button.import_git{ type: "button" } + %button.btn.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' } } = icon('git', text: 'Repo by URL') .col-lg-12 .js-toggle-content.toggle-import-form{ class: ('hide' if active_tab != 'import') } @@ -105,6 +110,10 @@ = render "shared/import_form", f: f = render 'new_project_fields', f: f, project_name_id: "import-url-name" + + -# EE-specific start + -# EE-specific end + .save-project-loader.hide .center %h2 diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml index 736afa085e8..5eaaa1448d5 100644 --- a/app/views/shared/_import_form.html.haml +++ b/app/views/shared/_import_form.html.haml @@ -1,17 +1,22 @@ +- ci_cd_only = local_assigns.fetch(:ci_cd_only, false) + .form-group.import-url-data = f.label :import_url, class: 'label-light' do - %span Git repository URL + %span + = _('Git repository URL') - = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git' + = f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', required: true .well.prepend-top-20 %ul %li - The repository must be accessible over http://, https:// or git://. + = _('The repository must be accessible over http://, https:// or git://.').html_safe %li - If your HTTP repository is not publicly accessible, add authentication information to the URL: https://username:password@gitlab.company.com/group/project.git. + = _('If your HTTP repository is not publicly accessible, add authentication information to the URL: https://username:password@gitlab.company.com/group/project.git.').html_safe %li - The import will time out after #{time_interval_in_words(Gitlab.config.gitlab_shell.git_timeout)}. - For repositories that take longer, use a clone/push combination. + = import_will_timeout_message(ci_cd_only) %li - To migrate an SVN repository, check out #{link_to "this document", help_page_path('user/project/import/svn')}. + = import_svn_message(ci_cd_only) + +-# EE-specific start +-# EE-specific end diff --git a/doc/user/project/import/img/import_projects_from_repo_url.png b/doc/user/project/import/img/import_projects_from_repo_url.png new file mode 100644 index 00000000000..ec867da1087 Binary files /dev/null and b/doc/user/project/import/img/import_projects_from_repo_url.png differ diff --git a/doc/user/project/import/index.md b/doc/user/project/import/index.md index e2b285678c3..72cc58546b7 100644 --- a/doc/user/project/import/index.md +++ b/doc/user/project/import/index.md @@ -10,6 +10,7 @@ 1. [From Perforce](perforce.md) 1. [From SVN](svn.md) 1. [From TFS](tfs.md) +1. [From repo by URL](repo_by_url.md) In addition to the specific migration documentation above, you can import any Git repository via HTTP from the New Project page. Be aware that if the diff --git a/doc/user/project/import/repo_by_url.md b/doc/user/project/import/repo_by_url.md new file mode 100644 index 00000000000..f43e384de88 --- /dev/null +++ b/doc/user/project/import/repo_by_url.md @@ -0,0 +1,12 @@ +# Import project from repo by URL + +You can import your existing repositories by providing the Git URL: + +1. From your GitLab dashboard click **New project** +1. Switch to the **Import project** tab +1. Click on the **Repo by URL** button +1. Fill in the "Git repository URL" and the remaining project fields +1. Click **Create project** to being the import process +1. Once complete, you will be redirected to your newly created project + +![Import project by repo URL](img/import_projects_from_repo_url.png) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c9b17f1e826..8a2176a4d72 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2018-03-01 18:03+0100\n" -"PO-Revision-Date: 2018-03-01 18:03+0100\n" +"POT-Creation-Date: 2018-03-05 13:02-0600\n" +"PO-Revision-Date: 2018-03-05 13:02-0600\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -102,6 +102,9 @@ msgstr "" msgid "A collection of graphs regarding Continuous Integration" msgstr "" +msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}." +msgstr "" + msgid "About auto deploy" msgstr "" @@ -201,6 +204,9 @@ msgstr "" msgid "All" msgstr "" +msgid "All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings." +msgstr "" + msgid "Allows you to add and manage Kubernetes clusters." msgstr "" @@ -698,6 +704,9 @@ msgstr "" msgid "ClusterIntegration|Copy CA Certificate" msgstr "" +msgid "ClusterIntegration|Copy Ingress IP Address to clipboard" +msgstr "" + msgid "ClusterIntegration|Copy Kubernetes cluster name" msgstr "" @@ -746,6 +755,9 @@ msgstr "" msgid "ClusterIntegration|Ingress" msgstr "" +msgid "ClusterIntegration|Ingress IP Address" +msgstr "" + msgid "ClusterIntegration|Install" msgstr "" @@ -1444,6 +1456,9 @@ msgstr "" msgid "ForkedFromProjectPath|Forked from %{project_name} (deleted)" msgstr "" +msgid "Forking in progress" +msgstr "" + msgid "Format" msgstr "" @@ -1462,6 +1477,9 @@ msgstr "" msgid "Generate a default set of labels" msgstr "" +msgid "Git repository URL" +msgstr "" + msgid "Git revision" msgstr "" @@ -1587,6 +1605,12 @@ msgstr "" msgid "If you already have files you can push them using the %{link_to_cli} below." msgstr "" +msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: https://username:password@gitlab.company.com/group/project.git." +msgstr "" + +msgid "Import in progress" +msgstr "" + msgid "Import repository" msgstr "" @@ -1823,6 +1847,9 @@ msgstr "" msgid "Monitoring" msgstr "" +msgid "More information" +msgstr "" + msgid "More information is available|here" msgstr "" @@ -2164,6 +2191,9 @@ msgstr "" msgid "Please solve the reCAPTCHA" msgstr "" +msgid "Please wait while we import the repository for you. Refresh at will." +msgstr "" + msgid "Preferences" msgstr "" @@ -2520,6 +2550,9 @@ msgstr "" msgid "Select target branch" msgstr "" +msgid "Send email" +msgstr "" + msgid "Sep" msgstr "" @@ -2833,6 +2866,9 @@ msgstr "" msgid "The fork relationship has been removed." msgstr "" +msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination." +msgstr "" + msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "" @@ -2866,6 +2902,9 @@ msgstr "" msgid "The repository for this project is empty" msgstr "" +msgid "The repository must be accessible over http://, https:// or git://." +msgstr "" + msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "" @@ -3131,6 +3170,9 @@ msgstr "" msgid "Tip:" msgstr "" +msgid "To import an SVN repository, check out %{svn_link}." +msgstr "" + msgid "Todo" msgstr "" @@ -3437,6 +3479,9 @@ msgstr "" msgid "Your projects" msgstr "" +msgid "among other things" +msgstr "" + msgid "assign yourself" msgstr "" @@ -3632,6 +3677,9 @@ msgstr "" msgid "spendCommand|%{slash_command} will update the sum of the time spent." msgstr "" +msgid "this document" +msgstr "" + msgid "username" msgstr "" diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index b5104747d00..fd561288091 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -142,7 +142,7 @@ feature 'New project' do context 'from git repository url, "Repo by URL"' do before do - first('.import_git').click + first('.js-import-git-toggle-button').click end it 'does not autocomplete sensitive git repo URL' do -- cgit v1.2.3 From 6d1c5014e96d96ad44fed71dd99bf6c1d4af3cec Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Mon, 5 Mar 2018 13:53:40 -0600 Subject: review: rename import_export to import_export_shared --- app/models/project.rb | 8 ++++---- app/services/projects/import_export/export_service.rb | 2 +- lib/gitlab/import_export/importer.rb | 2 +- spec/lib/gitlab/import_export/avatar_restorer_spec.rb | 2 +- spec/lib/gitlab/import_export/avatar_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/fork_spec.rb | 2 +- spec/lib/gitlab/import_export/project_tree_restorer_spec.rb | 4 ++-- spec/lib/gitlab/import_export/project_tree_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/repo_restorer_spec.rb | 2 +- spec/lib/gitlab/import_export/repo_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/uploads_restorer_spec.rb | 2 +- spec/lib/gitlab/import_export/uploads_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb | 2 +- spec/lib/gitlab/import_export/wiki_restorer_spec.rb | 2 +- 14 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 190473aa196..666614bb3d9 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1557,14 +1557,14 @@ class Project < ActiveRecord::Base end end - def import_export - @import_export ||= Gitlab::ImportExport::Shared.new(self) + def import_export_shared + @import_export_shared ||= Gitlab::ImportExport::Shared.new(self) end def export_path return nil unless namespace.present? || hashed_storage?(:repository) - import_export.archive_path + import_export_shared.archive_path end def export_project_path @@ -1582,7 +1582,7 @@ class Project < ActiveRecord::Base end def export_in_progress? - import_export.active_export_count > 0 + import_export_shared.active_export_count > 0 end def remove_exports diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb index 2af228eff05..af41ce82f65 100644 --- a/app/services/projects/import_export/export_service.rb +++ b/app/services/projects/import_export/export_service.rb @@ -2,7 +2,7 @@ module Projects module ImportExport class ExportService < BaseService def execute(_options = {}) - @shared = project.import_export + @shared = project.import_export_shared save_all end diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb index 8b780cbe3a1..c38df9102eb 100644 --- a/lib/gitlab/import_export/importer.rb +++ b/lib/gitlab/import_export/importer.rb @@ -9,7 +9,7 @@ module Gitlab @archive_file = project.import_source @current_user = project.creator @project = project - @shared = project.import_export + @shared = project.import_export_shared end def execute diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb index c981386ef3d..4897d604bc1 100644 --- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::AvatarRestorer do include UploadHelpers - let(:shared) { project.import_export } + let(:shared) { project.import_export_shared } let(:project) { create(:project) } before do diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb index 78055f3970a..f40d4bc2d08 100644 --- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb +++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::ImportExport::AvatarSaver do - let(:shared) { project.import_export } + let(:shared) { project.import_export_shared } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:project_with_avatar) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } let(:project) { create(:project) } diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb index 77aa37a1992..17e06a6a83f 100644 --- a/spec/lib/gitlab/import_export/fork_spec.rb +++ b/spec/lib/gitlab/import_export/fork_spec.rb @@ -7,7 +7,7 @@ describe 'forked project import' do let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } let!(:project) { create(:project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { project.import_export } + let(:shared) { project.import_export_shared } let(:forked_from_project) { create(:project, :repository) } let(:forked_project) { fork_project(project_with_repo, nil, repository: true) } let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) } 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 ae4bdc7be00..777f11ef7cf 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do RSpec::Mocks.with_temporary_scope do @project = create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') - @shared = @project.import_export + @shared = @project.import_export_shared allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/') allow_any_instance_of(Repository).to receive(:fetch_ref).and_return(true) @@ -259,7 +259,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do context 'Light JSON' do let(:user) { create(:user) } - let(:shared) { project.import_export } + let(:shared) { project.import_export_shared } let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') } let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) } let(:restored_project_json) { project_tree_restorer.restore } 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 4daef697c1a..4738d991e6c 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::ImportExport::ProjectTreeSaver do describe 'saves the project tree into a json object' do - let(:shared) { project.import_export } + let(:shared) { project.import_export_shared } let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } let(:user) { create(:user) } diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb index 3fdb40996bf..dc806d036ff 100644 --- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::ImportExport::RepoRestorer do let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') } let!(:project) { create(:project) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { project.import_export } + let(:shared) { project.import_export_shared } let(:bundler) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) } let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } let(:restorer) do diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb index 74859d81c71..187ec8fcfa2 100644 --- a/spec/lib/gitlab/import_export/repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::ImportExport::RepoSaver do let(:user) { create(:user) } let!(:project) { create(:project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { project.import_export } + let(:shared) { project.import_export_shared } let(:bundler) { described_class.new(project: project, shared: shared) } before do diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb index e919a7d9cbb..acef97459b8 100644 --- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::ImportExport::UploadsRestorer do describe 'bundle a project Git repo' do let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" } - let(:shared) { project.import_export } + let(:shared) { project.import_export_shared } before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb index 8ac37850328..1304d8fabfc 100644 --- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb +++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb @@ -4,7 +4,7 @@ describe Gitlab::ImportExport::UploadsSaver do describe 'bundle a project Git repo' do let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" } let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } - let(:shared) { project.import_export } + let(:shared) { project.import_export_shared } before do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb index fbd00cf5ce5..d2bd8ccdf3f 100644 --- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::ImportExport::WikiRepoSaver do let(:user) { create(:user) } let!(:project) { create(:project, :public, name: 'searchable_project') } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { project.import_export } + let(:shared) { project.import_export_shared } let(:wiki_bundler) { described_class.new(project: project, shared: shared) } let!(:project_wiki) { ProjectWiki.new(project, user) } diff --git a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb index 575ae837d64..5c01ee0ebb8 100644 --- a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::ImportExport::WikiRestorer do let!(:project_without_wiki) { create(:project) } let!(:project) { create(:project) } let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" } - let(:shared) { project.import_export } + let(:shared) { project.import_export_shared } let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_with_wiki, shared: shared) } let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) } let(:restorer) do -- cgit v1.2.3 From 3e71955befba95f823ba92290dedc13a9bf332ff Mon Sep 17 00:00:00 2001 From: Travis Miller Date: Mon, 5 Mar 2018 13:55:20 -0600 Subject: review: prefix un-used argument with underscore --- lib/api/entities.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index f140563882f..99424f1d867 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -95,7 +95,7 @@ module API include ::API::Helpers::RelatedResourcesHelpers expose :export_status - expose :_links, if: lambda { |project, options| project.export_status == :finished } do + expose :_links, if: lambda { |project, _options| project.export_status == :finished } do expose :api_url do |project| expose_url(api_v4_projects_export_download_path(id: project.id)) end -- cgit v1.2.3 From 36a0f6aaa3d3999d743fc11a39a9f8dd2f127d70 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 5 Mar 2018 16:32:51 -0300 Subject: Use host URL to build JIRA remote link icon --- app/models/project_services/jira_service.rb | 6 +++++- changelogs/unreleased/issue_31081.yml | 5 +++++ spec/models/project_services/jira_service_spec.rb | 3 +-- spec/services/system_note_service_spec.rb | 6 +++--- 4 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 changelogs/unreleased/issue_31081.yml diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 436a870b0c4..e5035c81df0 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -1,5 +1,7 @@ class JiraService < IssueTrackerService include Gitlab::Routing + include ApplicationHelper + include ActionView::Helpers::AssetUrlHelper validates :url, url: true, presence: true, if: :activated? validates :api_url, url: true, allow_blank: true @@ -268,7 +270,9 @@ class JiraService < IssueTrackerService url: url, title: title, status: status, - icon: { title: 'GitLab', url16x16: 'https://gitlab.com/favicon.ico' } + icon: { + title: 'GitLab', url16x16: asset_url('favicon.ico', host: gitlab_config.url) + } } } end diff --git a/changelogs/unreleased/issue_31081.yml b/changelogs/unreleased/issue_31081.yml new file mode 100644 index 00000000000..ac547c285db --- /dev/null +++ b/changelogs/unreleased/issue_31081.yml @@ -0,0 +1,5 @@ +--- +title: Use host URL to build JIRA remote link icon +merge_request: +author: +type: other diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 748c366efca..54ef0be67ff 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -166,7 +166,6 @@ describe JiraService do # Creates comment expect(WebMock).to have_requested(:post, @comment_url) - # Creates Remote Link in JIRA issue fields expect(WebMock).to have_requested(:post, @remote_link_url).with( body: hash_including( @@ -174,7 +173,7 @@ describe JiraService do object: { url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/#{merge_request.diff_head_sha}", title: "GitLab: Solved by commit #{merge_request.diff_head_sha}.", - icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" }, + icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" }, status: { resolved: true } } ) diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 5b5edc1aa0d..a3893188c6e 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -789,7 +789,7 @@ describe SystemNoteService do object: { url: project_commit_url(project, commit), title: "GitLab: Mentioned on commit - #{commit.title}", - icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" }, + icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" }, status: { resolved: false } } ) @@ -815,7 +815,7 @@ describe SystemNoteService do object: { url: project_issue_url(project, issue), title: "GitLab: Mentioned on issue - #{issue.title}", - icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" }, + icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" }, status: { resolved: false } } ) @@ -841,7 +841,7 @@ describe SystemNoteService do object: { url: project_snippet_url(project, snippet), title: "GitLab: Mentioned on snippet - #{snippet.title}", - icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" }, + icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" }, status: { resolved: false } } ) -- cgit v1.2.3 From 5d568260bd7d3af312fee0c361f96be6aab87c3a Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Mon, 5 Mar 2018 15:59:12 +0100 Subject: Add Turkish, Filipino and Indonesian New languages! --- changelogs/unreleased/bvl-port-of-ee-translations.yml | 5 +++++ lib/gitlab/i18n.rb | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/bvl-port-of-ee-translations.yml diff --git a/changelogs/unreleased/bvl-port-of-ee-translations.yml b/changelogs/unreleased/bvl-port-of-ee-translations.yml new file mode 100644 index 00000000000..8f232ec8da3 --- /dev/null +++ b/changelogs/unreleased/bvl-port-of-ee-translations.yml @@ -0,0 +1,5 @@ +--- +title: Started translation into Turkish, Indonesian and Filipino +merge_request: 17526 +author: +type: other diff --git a/lib/gitlab/i18n.rb b/lib/gitlab/i18n.rb index bdc0f04b56b..3772ef11c7f 100644 --- a/lib/gitlab/i18n.rb +++ b/lib/gitlab/i18n.rb @@ -18,7 +18,10 @@ module Gitlab 'uk' => 'Українська', 'ja' => '日本語', 'ko' => '한국어', - 'nl_NL' => 'Nederlands' + 'nl_NL' => 'Nederlands', + 'tr_TR' => 'Türkçe', + 'id_ID' => 'Bahasa Indonesia', + 'fil_PH' => 'Filipino' }.freeze def available_locales -- cgit v1.2.3 From 2d1ceca077a1624a4bdc6aa26ab8a5113a5f6394 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 5 Mar 2018 13:37:37 -0800 Subject: Don't error out in system hook if user has `nil` datetime columns Deleting a user would fail in the system hooks if the user had `nil` column in `datetime` or `updated_at` fields. Closes #43871 --- app/services/system_hooks_service.rb | 4 ++-- changelogs/unreleased/sh-fix-issue-43871-system-hooks.yml | 5 +++++ spec/services/system_hooks_service_spec.rb | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/sh-fix-issue-43871-system-hooks.yml diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index af8c02a10b7..ba7946fd23c 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -20,8 +20,8 @@ class SystemHooksService def build_event_data(model, event) data = { event_name: build_event_name(model, event), - created_at: model.created_at.xmlschema, - updated_at: model.updated_at.xmlschema + created_at: model.created_at&.xmlschema, + updated_at: model.updated_at&.xmlschema } case model diff --git a/changelogs/unreleased/sh-fix-issue-43871-system-hooks.yml b/changelogs/unreleased/sh-fix-issue-43871-system-hooks.yml new file mode 100644 index 00000000000..7c7ef39cb75 --- /dev/null +++ b/changelogs/unreleased/sh-fix-issue-43871-system-hooks.yml @@ -0,0 +1,5 @@ +--- +title: Don't error out in system hook if user has `nil` datetime columns +merge_request: +author: +type: fixed diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index c40cd5b7548..a538d0c109a 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -70,6 +70,14 @@ describe SystemHooksService do expect(data[:project_visibility]).to eq('private') end + it 'handles nil datetime columns' do + user.update_attributes(created_at: nil, updated_at: nil) + data = event_data(user, :destroy) + + expect(data[:created_at]).to be(nil) + expect(data[:updated_at]).to be(nil) + end + context 'group_rename' do it 'contains old and new path' do allow(group).to receive(:path_was).and_return('old-path') -- cgit v1.2.3 From 6d3cb7e22ea3567887fa521d8056b7d5618aa699 Mon Sep 17 00:00:00 2001 From: Horatiu Eugen Vlad Date: Mon, 5 Mar 2018 22:26:40 +0000 Subject: Make oauth provider login generic --- changelogs/unreleased/oauth_generic_provider.yml | 4 ++++ lib/gitlab/auth.rb | 30 +++++++++++++++++------- lib/gitlab/auth/database/authentication.rb | 16 +++++++++++++ lib/gitlab/auth/ldap/authentication.rb | 10 +++----- lib/gitlab/auth/o_auth/authentication.rb | 21 +++++++++++++++++ lib/gitlab/auth/o_auth/provider.rb | 17 ++++++++++++++ lib/gitlab/auth/o_auth/user.rb | 2 +- spec/requests/git_http_spec.rb | 6 ++--- 8 files changed, 86 insertions(+), 20 deletions(-) create mode 100644 changelogs/unreleased/oauth_generic_provider.yml create mode 100644 lib/gitlab/auth/database/authentication.rb create mode 100644 lib/gitlab/auth/o_auth/authentication.rb diff --git a/changelogs/unreleased/oauth_generic_provider.yml b/changelogs/unreleased/oauth_generic_provider.yml new file mode 100644 index 00000000000..3b6f8b04529 --- /dev/null +++ b/changelogs/unreleased/oauth_generic_provider.yml @@ -0,0 +1,4 @@ +--- +title: Make oauth provider login generic +merge_request: 8809 +author: Horatiu Eugen Vlad \ No newline at end of file diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 86393ee254d..f5ccf952cf9 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -40,8 +40,8 @@ module Gitlab end def find_with_user_password(login, password) - # Avoid resource intensive login checks if password is not provided - return unless password.present? + # Avoid resource intensive checks if login credentials are not provided + return unless login.present? && password.present? # Nothing to do here if internal auth is disabled and LDAP is # not configured @@ -50,14 +50,26 @@ module Gitlab Gitlab::Auth::UniqueIpsLimiter.limit_user! do user = User.by_login(login) - # If no user is found, or it's an LDAP server, try LDAP. - # LDAP users are only authenticated via LDAP - if user.nil? || user.ldap_user? - # Second chance - try LDAP authentication - Gitlab::Auth::LDAP::Authentication.login(login, password) - elsif Gitlab::CurrentSettings.password_authentication_enabled_for_git? - user if user.active? && user.valid_password?(password) + return if user && !user.active? + + authenticators = [] + + if user + authenticators << Gitlab::Auth::OAuth::Provider.authentication(user, 'database') + + # Add authenticators for all identities if user is not nil + user&.identities&.each do |identity| + authenticators << Gitlab::Auth::OAuth::Provider.authentication(user, identity.provider) + end + else + # If no user is provided, try LDAP. + # LDAP users are only authenticated via LDAP + authenticators << Gitlab::Auth::LDAP::Authentication end + + authenticators.compact! + + user if authenticators.find { |auth| auth.login(login, password) } end end diff --git a/lib/gitlab/auth/database/authentication.rb b/lib/gitlab/auth/database/authentication.rb new file mode 100644 index 00000000000..260a77058a4 --- /dev/null +++ b/lib/gitlab/auth/database/authentication.rb @@ -0,0 +1,16 @@ +# These calls help to authenticate to OAuth provider by providing username and password +# + +module Gitlab + module Auth + module Database + class Authentication < Gitlab::Auth::OAuth::Authentication + def login(login, password) + return false unless Gitlab::CurrentSettings.password_authentication_enabled_for_git? + + user&.valid_password?(password) + end + end + end + end +end diff --git a/lib/gitlab/auth/ldap/authentication.rb b/lib/gitlab/auth/ldap/authentication.rb index cbb9cf4bb9c..e70c3ab6b46 100644 --- a/lib/gitlab/auth/ldap/authentication.rb +++ b/lib/gitlab/auth/ldap/authentication.rb @@ -7,7 +7,7 @@ module Gitlab module Auth module LDAP - class Authentication + class Authentication < Gitlab::Auth::OAuth::Authentication def self.login(login, password) return unless Gitlab::Auth::LDAP::Config.enabled? return unless login.present? && password.present? @@ -28,11 +28,7 @@ module Gitlab Gitlab::Auth::LDAP::Config.providers end - attr_accessor :provider, :ldap_user - - def initialize(provider) - @provider = provider - end + attr_accessor :ldap_user def login(login, password) @ldap_user = adapter.bind_as( @@ -62,7 +58,7 @@ module Gitlab end def user - return nil unless ldap_user + return unless ldap_user Gitlab::Auth::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider) end diff --git a/lib/gitlab/auth/o_auth/authentication.rb b/lib/gitlab/auth/o_auth/authentication.rb new file mode 100644 index 00000000000..ed03b9f8b40 --- /dev/null +++ b/lib/gitlab/auth/o_auth/authentication.rb @@ -0,0 +1,21 @@ +# These calls help to authenticate to OAuth provider by providing username and password +# + +module Gitlab + module Auth + module OAuth + class Authentication + attr_reader :provider, :user + + def initialize(provider, user = nil) + @provider = provider + @user = user + end + + def login(login, password) + raise NotImplementedError + end + end + end + end +end diff --git a/lib/gitlab/auth/o_auth/provider.rb b/lib/gitlab/auth/o_auth/provider.rb index f8ab8ee1388..5fb61ffe00d 100644 --- a/lib/gitlab/auth/o_auth/provider.rb +++ b/lib/gitlab/auth/o_auth/provider.rb @@ -8,11 +8,28 @@ module Gitlab "google_oauth2" => "Google" }.freeze + def self.authentication(user, provider) + return unless user + return unless enabled?(provider) + + authenticator = + case provider + when /^ldap/ + Gitlab::Auth::LDAP::Authentication + when 'database' + Gitlab::Auth::Database::Authentication + end + + authenticator&.new(provider, user) + end + def self.providers Devise.omniauth_providers end def self.enabled?(name) + return true if name == 'database' + providers.include?(name.to_sym) end diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb index acd785bb02d..b6a96081278 100644 --- a/lib/gitlab/auth/o_auth/user.rb +++ b/lib/gitlab/auth/o_auth/user.rb @@ -161,7 +161,7 @@ module Gitlab def find_by_uid_and_provider identity = Identity.with_extern_uid(auth_hash.provider, auth_hash.uid).take - identity && identity.user + identity&.user end def build_new_user diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 0467e0251b3..6dbbb1ad7bb 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -795,9 +795,9 @@ describe 'Git HTTP requests' do let(:path) { 'doesnt/exist.git' } before do - allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true) - allow(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(nil) - allow(Gitlab::Auth::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user) + allow(Gitlab::Auth::OAuth::Provider).to receive(:enabled?).and_return(true) + allow_any_instance_of(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(nil) + allow_any_instance_of(Gitlab::Auth::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user) end it_behaves_like 'pulls require Basic HTTP Authentication' -- cgit v1.2.3 From 9cb7e93f09b1ff0c1c889a004479a8ef21abbae2 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 3 Mar 2018 05:59:40 -0800 Subject: Fix project dashboard showing the wrong timestamps Use the max of the `last_activity_at` and `last_repository_updated_at` columns. The latter is updated only when a push happens, but the former is updated whenever any activity (e.g. issue creation) happens. Closes #27181 --- app/models/project.rb | 5 +++-- changelogs/unreleased/sh-dashboard-sort-fix.yml | 5 +++++ spec/features/dashboard/projects_spec.rb | 8 ++++++++ spec/models/project_spec.rb | 14 ++++++++++++++ 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/sh-dashboard-sort-fix.yml diff --git a/app/models/project.rb b/app/models/project.rb index 5b1f8b2658b..099eaa53d23 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -274,7 +274,8 @@ class Project < ActiveRecord::Base scope :without_storage_feature, ->(feature) { where('storage_version < :version OR storage_version IS NULL', version: HASHED_STORAGE_FEATURES[feature]) } scope :with_unmigrated_storage, -> { where('storage_version < :version OR storage_version IS NULL', version: LATEST_STORAGE_VERSION) } - scope :sorted_by_activity, -> { reorder(last_activity_at: :desc) } + # last_activity_at is throttled every minute, but last_repository_updated_at is updated with every push + scope :sorted_by_activity, -> { reorder("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC") } scope :sorted_by_stars, -> { reorder('projects.star_count DESC') } scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) } @@ -776,7 +777,7 @@ class Project < ActiveRecord::Base end def last_activity_date - last_repository_updated_at || last_activity_at || updated_at + [last_activity_at, last_repository_updated_at, updated_at].compact.max end def project_id diff --git a/changelogs/unreleased/sh-dashboard-sort-fix.yml b/changelogs/unreleased/sh-dashboard-sort-fix.yml new file mode 100644 index 00000000000..6fd252f6707 --- /dev/null +++ b/changelogs/unreleased/sh-dashboard-sort-fix.yml @@ -0,0 +1,5 @@ +--- +title: Fix project dashboard showing the wrong timestamps +merge_request: +author: +type: fixed diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb index 586c7b48d0b..986f864f0b5 100644 --- a/spec/features/dashboard/projects_spec.rb +++ b/spec/features/dashboard/projects_spec.rb @@ -37,6 +37,14 @@ feature 'Dashboard Projects' do expect(page).to have_xpath("//time[@datetime='#{project.last_repository_updated_at.getutc.iso8601}']") end + + it 'shows the last_activity_at attribute as the update date' do + project.update_attributes!(last_repository_updated_at: 1.hour.ago, last_activity_at: Time.now) + + visit dashboard_projects_path + + expect(page).to have_xpath("//time[@datetime='#{project.last_activity_at.getutc.iso8601}']") + end end context 'when last_repository_updated_at and last_activity_at are missing' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f4faec9e52a..27bb1a2e3bd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -517,6 +517,20 @@ describe Project do it 'returns the project\'s last update date if it has no events' do expect(project.last_activity_date).to eq(project.updated_at) end + + it 'returns the most recent timestamp' do + project.update_attributes(updated_at: nil, + last_activity_at: timestamp, + last_repository_updated_at: timestamp - 1.hour) + + expect(project.last_activity_date).to eq(timestamp) + + project.update_attributes(updated_at: timestamp, + last_activity_at: timestamp - 1.hour, + last_repository_updated_at: nil) + + expect(project.last_activity_date).to eq(timestamp) + end end end -- cgit v1.2.3 From 1c931f0784fc15601101fe356585812c93ac1587 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 5 Mar 2018 21:57:48 +0900 Subject: Rework to minimize changes --- app/services/projects/update_pages_service.rb | 23 +++++++++++++++-------- app/workers/pages_worker.rb | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index c760bd3b626..d3e792b9232 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -1,5 +1,6 @@ module Projects class UpdatePagesService < BaseService + InvaildStateError = Class.new(StandardError) BLOCK_SIZE = 32.kilobytes MAX_SIZE = 1.terabyte SITE_PATH = 'public/'.freeze @@ -11,13 +12,15 @@ module Projects end def execute + register_attempt + # Create status notifying the deployment of pages @status = create_status @status.enqueue! @status.run! - raise 'missing pages artifacts' unless build.artifacts? - raise 'pages are outdated' unless latest? + raise InvaildStateError, 'missing pages artifacts' unless build.artifacts? + raise InvaildStateError, 'pages are outdated' unless latest? # Create temporary directory in which we will extract the artifacts FileUtils.mkdir_p(tmp_path) @@ -26,24 +29,22 @@ module Projects # Check if we did extract public directory archive_public_path = File.join(archive_path, 'public') - raise 'pages miss the public folder' unless Dir.exist?(archive_public_path) - raise 'pages are outdated' unless latest? + raise InvaildStateError, 'pages miss the public folder' unless Dir.exist?(archive_public_path) + raise InvaildStateError, 'pages are outdated' unless latest? deploy_page!(archive_public_path) success end - rescue => e + rescue InvaildStateError => e register_failure error(e.message) - ensure - register_attempt - build.erase_artifacts! unless build.has_expiring_artifacts? end private def success @status.success + delete_artifact! super end @@ -52,6 +53,7 @@ module Projects @status.allow_failure = !latest? @status.description = message @status.drop(:script_failure) + delete_artifact! super end @@ -163,6 +165,11 @@ module Projects build.artifacts_file.path end + def delete_artifact! + build.reload + build.erase_artifacts! unless build.has_expiring_artifacts? + end + def latest_sha project.commit(build.ref).try(:sha).to_s end diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb index d3b95009364..66a0ff83bef 100644 --- a/app/workers/pages_worker.rb +++ b/app/workers/pages_worker.rb @@ -1,7 +1,7 @@ class PagesWorker include ApplicationWorker - sidekiq_options retry: false + sidekiq_options retry: 3 def perform(action, *arg) send(action, *arg) # rubocop:disable GitlabSecurity/PublicSend -- cgit v1.2.3 From bbbf8e6a0234f1195a69facabab840679fb70dde Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 5 Mar 2018 21:59:16 +0900 Subject: Fix comment --- app/services/projects/update_pages_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index d3e792b9232..016660d5a72 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -166,7 +166,7 @@ module Projects end def delete_artifact! - build.reload + build.reload # Reload stable object to prevent erase artifacts with old state build.erase_artifacts! unless build.has_expiring_artifacts? end -- cgit v1.2.3 From 259a85e6658d91a7eb17b752b3f54c023625e08d Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 5 Mar 2018 22:02:50 +0900 Subject: Drop running_or_pending_build_count --- app/models/ci/build.rb | 1 - app/models/project.rb | 6 ------ 2 files changed, 7 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index b230b7f47ef..3f037822154 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -359,7 +359,6 @@ module Ci project.execute_hooks(build_data.dup, :job_hooks) project.execute_services(build_data.dup, :job_hooks) PagesService.new(build_data).execute - project.running_or_pending_build_count(force: true) end def artifacts_metadata_entry(path, **options) diff --git a/app/models/project.rb b/app/models/project.rb index a11b1e4f554..58c4094918c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1494,12 +1494,6 @@ class Project < ActiveRecord::Base update_column(:import_jid, nil) end - def running_or_pending_build_count(force: false) - Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do - builds.running_or_pending.count(:all) - end - end - # Lazy loading of the `pipeline_status` attribute def pipeline_status @pipeline_status ||= Gitlab::Cache::Ci::ProjectPipelineStatus.load_for_project(self) -- cgit v1.2.3 From c8d1a04f30f858ff66cff6f9168b4a3fc6f88acf Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 6 Mar 2018 03:15:42 +0900 Subject: Add empty line after custom error difinition --- app/services/projects/update_pages_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index 016660d5a72..cc50a23e8f7 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -1,6 +1,7 @@ module Projects class UpdatePagesService < BaseService InvaildStateError = Class.new(StandardError) + BLOCK_SIZE = 32.kilobytes MAX_SIZE = 1.terabyte SITE_PATH = 'public/'.freeze -- cgit v1.2.3 From 031794f57dd1b300e8442d57aa2822b70021ff3a Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 6 Mar 2018 03:33:54 +0900 Subject: Add changelog --- changelogs/unreleased/fix-sm-fix_pages_worker.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/fix-sm-fix_pages_worker.yml diff --git a/changelogs/unreleased/fix-sm-fix_pages_worker.yml b/changelogs/unreleased/fix-sm-fix_pages_worker.yml new file mode 100644 index 00000000000..fb0c3713215 --- /dev/null +++ b/changelogs/unreleased/fix-sm-fix_pages_worker.yml @@ -0,0 +1,5 @@ +--- +title: Fix pages flaky falure by reloading stale object +merge_request: 17522 +author: +type: fixed -- cgit v1.2.3 From 7421604bd0855f7855f5562769acc9bc871fb631 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 6 Mar 2018 04:37:09 +0900 Subject: Introduce FailedToExtractError. Fix spec. Add DNS test mock. --- app/services/projects/update_pages_service.rb | 15 ++++++++------- spec/features/projects/jobs/user_browses_job_spec.rb | 2 -- spec/features/projects/pages_spec.rb | 2 +- spec/services/projects/update_pages_service_spec.rb | 13 +++++++++++++ 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb index cc50a23e8f7..00fdd047208 100644 --- a/app/services/projects/update_pages_service.rb +++ b/app/services/projects/update_pages_service.rb @@ -1,6 +1,7 @@ module Projects class UpdatePagesService < BaseService InvaildStateError = Class.new(StandardError) + FailedToExtractError = Class.new(StandardError) BLOCK_SIZE = 32.kilobytes MAX_SIZE = 1.terabyte @@ -30,13 +31,13 @@ module Projects # Check if we did extract public directory archive_public_path = File.join(archive_path, 'public') - raise InvaildStateError, 'pages miss the public folder' unless Dir.exist?(archive_public_path) + raise FailedToExtractError, 'pages miss the public folder' unless Dir.exist?(archive_public_path) raise InvaildStateError, 'pages are outdated' unless latest? deploy_page!(archive_public_path) success end - rescue InvaildStateError => e + rescue InvaildStateError, FailedToExtractError => e register_failure error(e.message) end @@ -75,7 +76,7 @@ module Projects elsif artifacts.ends_with?('.zip') extract_zip_archive!(temp_path) else - raise 'unsupported artifacts format' + raise FailedToExtractError, 'unsupported artifacts format' end end @@ -84,17 +85,17 @@ module Projects %W(dd bs=#{BLOCK_SIZE} count=#{blocks}), %W(tar -x -C #{temp_path} #{SITE_PATH}), err: '/dev/null') - raise 'pages failed to extract' unless results.compact.all?(&:success?) + raise FailedToExtractError, 'pages failed to extract' unless results.compact.all?(&:success?) end def extract_zip_archive!(temp_path) - raise 'missing artifacts metadata' unless build.artifacts_metadata? + raise FailedToExtractError, 'missing artifacts metadata' unless build.artifacts_metadata? # Calculate page size after extract public_entry = build.artifacts_metadata_entry(SITE_PATH, recursive: true) if public_entry.total_size > max_size - raise "artifacts for pages are too large: #{public_entry.total_size}" + raise FailedToExtractError, "artifacts for pages are too large: #{public_entry.total_size}" end # Requires UnZip at least 6.00 Info-ZIP. @@ -103,7 +104,7 @@ module Projects # We add * to end of SITE_PATH, because we want to extract SITE_PATH and all subdirectories site_path = File.join(SITE_PATH, '*') unless system(*%W(unzip -qq -n #{artifacts} #{site_path} -d #{temp_path})) - raise 'pages failed to extract' + raise FailedToExtractError, 'pages failed to extract' end end diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 4c49cff30d4..48462c193f1 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -31,7 +31,5 @@ describe 'User browses a job', :js do page.within('.erased') do expect(page).to have_content('Job has been erased') end - - expect(build.project.running_or_pending_build_count).to eq(build.project.builds.running_or_pending.count(:all)) end end diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb index 2a0d235ef04..233d2e67b9d 100644 --- a/spec/features/projects/pages_spec.rb +++ b/spec/features/projects/pages_spec.rb @@ -258,7 +258,7 @@ feature 'Pages' do end let(:ci_build) do - build( + create( :ci_build, project: project, pipeline: pipeline, diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index bfb86284d86..505aff1acf4 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -34,6 +34,7 @@ describe Projects::UpdatePagesService do context 'with expiry date' do before do build.artifacts_expire_in = "2 days" + build.save! end it "doesn't delete artifacts" do @@ -105,6 +106,7 @@ describe Projects::UpdatePagesService do context 'with expiry date' do before do build.artifacts_expire_in = "2 days" + build.save! end it "doesn't delete artifacts" do @@ -159,6 +161,17 @@ describe Projects::UpdatePagesService do expect(execute).not_to eq(:success) end + + context 'when timeout happens by DNS error' do + before do + allow_any_instance_of(Projects::UpdatePagesService) + .to receive(:extract_zip_archive!).and_raise(SocketError) + end + + it 'raises an error' do + expect { execute }.to raise_error(SocketError) + end + end end end -- cgit v1.2.3 From 58f0ad7a4cf5521296e3359ba084176f10bdb51a Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 6 Mar 2018 16:50:49 +0900 Subject: Fix staticanalysys --- spec/services/projects/update_pages_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index 505aff1acf4..e91504194fe 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -164,7 +164,7 @@ describe Projects::UpdatePagesService do context 'when timeout happens by DNS error' do before do - allow_any_instance_of(Projects::UpdatePagesService) + allow_any_instance_of(described_class) .to receive(:extract_zip_archive!).and_raise(SocketError) end -- cgit v1.2.3 From b5f5b6dc50b2963f3190caecf12f63d4ba2da878 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 26 Feb 2018 18:21:03 +0900 Subject: Add checksum to ci_job_artifacts --- app/models/ci/job_artifact.rb | 5 +++++ app/uploaders/job_artifact_uploader.rb | 10 ++++++++++ db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb | 8 ++++++++ db/schema.rb | 1 + 4 files changed, 24 insertions(+) create mode 100644 db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index 0a599f72bc7..a06a4bc502e 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -6,6 +6,7 @@ module Ci belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id before_save :set_size, if: :file_changed? + before_save :set_checksum, if: :file_changed? mount_uploader :file, JobArtifactUploader @@ -25,6 +26,10 @@ module Ci self.size = file.size end + def set_checksum + self.checksum = file.checksum + end + def expire_in expire_at - Time.now if expire_at end diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb index ad5385f45a4..5560700f442 100644 --- a/app/uploaders/job_artifact_uploader.rb +++ b/app/uploaders/job_artifact_uploader.rb @@ -9,6 +9,12 @@ class JobArtifactUploader < GitlabUploader model.size end + def checksum + return calc_checksum if model.checksum.nil? + + model.checksum + end + def store_dir dynamic_segment end @@ -21,6 +27,10 @@ class JobArtifactUploader < GitlabUploader private + def calc_checksum + Digest::SHA256.file(file.path).hexdigest + end + def dynamic_segment creation_date = model.created_at.utc.strftime('%Y_%m_%d') diff --git a/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb b/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb new file mode 100644 index 00000000000..30973f6f5c5 --- /dev/null +++ b/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb @@ -0,0 +1,8 @@ +class AddChecksumToCiJobArtifacts < ActiveRecord::Migration + DOWNTIME = false + + def change + add_column :ci_job_artifacts, :checksum, :string, limit: 64 + end +end + diff --git a/db/schema.rb b/db/schema.rb index 9e117440ed2..6a752593c96 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -346,6 +346,7 @@ ActiveRecord::Schema.define(version: 20180304204842) do t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "expire_at" t.string "file" + t.string "checksum", limit: 64 end add_index "ci_job_artifacts", ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id", using: :btree -- cgit v1.2.3 From c2954f38150fa5654fe63dd37f36de550e5f3679 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Feb 2018 20:49:50 +0900 Subject: Change column type to binary from string --- db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb | 3 +-- db/schema.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb b/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb index 30973f6f5c5..dd1b9339b28 100644 --- a/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb +++ b/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb @@ -2,7 +2,6 @@ class AddChecksumToCiJobArtifacts < ActiveRecord::Migration DOWNTIME = false def change - add_column :ci_job_artifacts, :checksum, :string, limit: 64 + add_column :ci_job_artifacts, :checksum, :binary end end - diff --git a/db/schema.rb b/db/schema.rb index 6a752593c96..bfe95199e83 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -346,7 +346,7 @@ ActiveRecord::Schema.define(version: 20180304204842) do t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "expire_at" t.string "file" - t.string "checksum", limit: 64 + t.binary "checksum" end add_index "ci_job_artifacts", ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id", using: :btree -- cgit v1.2.3 From 4dddd5858af5b9770fad43b58185373153e0f653 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Feb 2018 20:50:48 +0900 Subject: Import use_file method from EE and use it for calculation of checksum --- app/uploaders/gitlab_uploader.rb | 8 ++++++++ app/uploaders/job_artifact_uploader.rb | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb index 010100f2da1..9f693269808 100644 --- a/app/uploaders/gitlab_uploader.rb +++ b/app/uploaders/gitlab_uploader.rb @@ -71,6 +71,14 @@ class GitlabUploader < CarrierWave::Uploader::Base !!model end + ## + # ObjectStorage::Concern extends this method for remote files + def use_file + if file_storage? + return yield path + end + end + private # Designed to be overridden by child uploaders that have a dynamic path diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb index 5560700f442..f994b89dd00 100644 --- a/app/uploaders/job_artifact_uploader.rb +++ b/app/uploaders/job_artifact_uploader.rb @@ -28,7 +28,9 @@ class JobArtifactUploader < GitlabUploader private def calc_checksum - Digest::SHA256.file(file.path).hexdigest + use_file do |file_path| + return Digest::SHA256.file(file_path).hexdigest + end end def dynamic_segment -- cgit v1.2.3 From 22a7f1f9ad4504c51657b1957b01a7646eee78cd Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Feb 2018 18:43:35 +0900 Subject: Add ObjectStorageQueue concern and test --- app/workers/concerns/object_storage_queue.rb | 8 ++++++++ spec/workers/concerns/object_storage_queue_spec.rb | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 app/workers/concerns/object_storage_queue.rb create mode 100644 spec/workers/concerns/object_storage_queue_spec.rb diff --git a/app/workers/concerns/object_storage_queue.rb b/app/workers/concerns/object_storage_queue.rb new file mode 100644 index 00000000000..a80f473a6d4 --- /dev/null +++ b/app/workers/concerns/object_storage_queue.rb @@ -0,0 +1,8 @@ +# Concern for setting Sidekiq settings for the various GitLab ObjectStorage workers. +module ObjectStorageQueue + extend ActiveSupport::Concern + + included do + queue_namespace :object_storage + end +end diff --git a/spec/workers/concerns/object_storage_queue_spec.rb b/spec/workers/concerns/object_storage_queue_spec.rb new file mode 100644 index 00000000000..f725fc9a4b1 --- /dev/null +++ b/spec/workers/concerns/object_storage_queue_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe ObjectStorageQueue do + let(:worker) do + Class.new do + def self.name + 'DummyWorker' + end + + include ApplicationWorker + include ObjectStorageQueue + end + end + + it 'sets a default object storage queue automatically' do + expect(worker.sidekiq_options['queue']) + .to eq 'object_storage:dummy' + end +end -- cgit v1.2.3 From 99b0542cb0be67fd9af2de74133efb8911519947 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Feb 2018 21:11:38 +0900 Subject: Add post migration for checksum calculation --- app/workers/all_queues.yml | 2 ++ app/workers/update_artifact_checksum_worker.rb | 11 ++++++++++ ...8121020_update_checksum_for_ci_job_artifacts.rb | 25 ++++++++++++++++++++++ 3 files changed, 38 insertions(+) create mode 100644 app/workers/update_artifact_checksum_worker.rb create mode 100644 db/post_migrate/20180228121020_update_checksum_for_ci_job_artifacts.rb diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 328db19be29..e47a8ca3985 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -104,3 +104,5 @@ - update_user_activity - upload_checksum - web_hook + +- object_storage:update_artifact_checksum_worker \ No newline at end of file diff --git a/app/workers/update_artifact_checksum_worker.rb b/app/workers/update_artifact_checksum_worker.rb new file mode 100644 index 00000000000..a83fad04b95 --- /dev/null +++ b/app/workers/update_artifact_checksum_worker.rb @@ -0,0 +1,11 @@ +class UpdateArtifactChecksumWorker + include ApplicationWorker + include ObjectStorageQueue + + def perform(job_artifact_id) + Ci::JobArtifact.find_by(id: job_artifact_id).try do |job_artifact| + job_artifact.set_checksum + job_artifact.save! + end + end +end diff --git a/db/post_migrate/20180228121020_update_checksum_for_ci_job_artifacts.rb b/db/post_migrate/20180228121020_update_checksum_for_ci_job_artifacts.rb new file mode 100644 index 00000000000..bf69c647b4d --- /dev/null +++ b/db/post_migrate/20180228121020_update_checksum_for_ci_job_artifacts.rb @@ -0,0 +1,25 @@ +class UpdateChecksumForCiJobArtifacts < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + BATCH_SIZE = 2500 + + class JobArtifact < ActiveRecord::Base + include EachBatch + self.table_name = 'ci_job_artifacts' + end + + def up + UpdateChecksumForCiJobArtifacts::JobArtifact + .where('checksum IS NULL') + .each_batch(of: BATCH_SIZE) do |relation| + ids = relation.pluck(:id).map { |id| [id] } + + UpdateArtifactChecksumWorker.bulk_perform_async(ids) + end + end + + def down + # no-op + end +end -- cgit v1.2.3 From f00cec607f6ffc99c6170e66c6ecfa99c9e15b75 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 1 Mar 2018 18:27:57 +0900 Subject: Revert logic of calculating checksum --- app/models/ci/job_artifact.rb | 5 ----- app/uploaders/gitlab_uploader.rb | 8 ------- app/uploaders/job_artifact_uploader.rb | 12 ----------- app/workers/all_queues.yml | 2 -- app/workers/concerns/object_storage_queue.rb | 8 ------- app/workers/update_artifact_checksum_worker.rb | 11 ---------- ...8121020_update_checksum_for_ci_job_artifacts.rb | 25 ---------------------- spec/workers/concerns/object_storage_queue_spec.rb | 19 ---------------- 8 files changed, 90 deletions(-) delete mode 100644 app/workers/concerns/object_storage_queue.rb delete mode 100644 app/workers/update_artifact_checksum_worker.rb delete mode 100644 db/post_migrate/20180228121020_update_checksum_for_ci_job_artifacts.rb delete mode 100644 spec/workers/concerns/object_storage_queue_spec.rb diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb index a06a4bc502e..0a599f72bc7 100644 --- a/app/models/ci/job_artifact.rb +++ b/app/models/ci/job_artifact.rb @@ -6,7 +6,6 @@ module Ci belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id before_save :set_size, if: :file_changed? - before_save :set_checksum, if: :file_changed? mount_uploader :file, JobArtifactUploader @@ -26,10 +25,6 @@ module Ci self.size = file.size end - def set_checksum - self.checksum = file.checksum - end - def expire_in expire_at - Time.now if expire_at end diff --git a/app/uploaders/gitlab_uploader.rb b/app/uploaders/gitlab_uploader.rb index 9f693269808..010100f2da1 100644 --- a/app/uploaders/gitlab_uploader.rb +++ b/app/uploaders/gitlab_uploader.rb @@ -71,14 +71,6 @@ class GitlabUploader < CarrierWave::Uploader::Base !!model end - ## - # ObjectStorage::Concern extends this method for remote files - def use_file - if file_storage? - return yield path - end - end - private # Designed to be overridden by child uploaders that have a dynamic path diff --git a/app/uploaders/job_artifact_uploader.rb b/app/uploaders/job_artifact_uploader.rb index f994b89dd00..ad5385f45a4 100644 --- a/app/uploaders/job_artifact_uploader.rb +++ b/app/uploaders/job_artifact_uploader.rb @@ -9,12 +9,6 @@ class JobArtifactUploader < GitlabUploader model.size end - def checksum - return calc_checksum if model.checksum.nil? - - model.checksum - end - def store_dir dynamic_segment end @@ -27,12 +21,6 @@ class JobArtifactUploader < GitlabUploader private - def calc_checksum - use_file do |file_path| - return Digest::SHA256.file(file_path).hexdigest - end - end - def dynamic_segment creation_date = model.created_at.utc.strftime('%Y_%m_%d') diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index e47a8ca3985..328db19be29 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -104,5 +104,3 @@ - update_user_activity - upload_checksum - web_hook - -- object_storage:update_artifact_checksum_worker \ No newline at end of file diff --git a/app/workers/concerns/object_storage_queue.rb b/app/workers/concerns/object_storage_queue.rb deleted file mode 100644 index a80f473a6d4..00000000000 --- a/app/workers/concerns/object_storage_queue.rb +++ /dev/null @@ -1,8 +0,0 @@ -# Concern for setting Sidekiq settings for the various GitLab ObjectStorage workers. -module ObjectStorageQueue - extend ActiveSupport::Concern - - included do - queue_namespace :object_storage - end -end diff --git a/app/workers/update_artifact_checksum_worker.rb b/app/workers/update_artifact_checksum_worker.rb deleted file mode 100644 index a83fad04b95..00000000000 --- a/app/workers/update_artifact_checksum_worker.rb +++ /dev/null @@ -1,11 +0,0 @@ -class UpdateArtifactChecksumWorker - include ApplicationWorker - include ObjectStorageQueue - - def perform(job_artifact_id) - Ci::JobArtifact.find_by(id: job_artifact_id).try do |job_artifact| - job_artifact.set_checksum - job_artifact.save! - end - end -end diff --git a/db/post_migrate/20180228121020_update_checksum_for_ci_job_artifacts.rb b/db/post_migrate/20180228121020_update_checksum_for_ci_job_artifacts.rb deleted file mode 100644 index bf69c647b4d..00000000000 --- a/db/post_migrate/20180228121020_update_checksum_for_ci_job_artifacts.rb +++ /dev/null @@ -1,25 +0,0 @@ -class UpdateChecksumForCiJobArtifacts < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - BATCH_SIZE = 2500 - - class JobArtifact < ActiveRecord::Base - include EachBatch - self.table_name = 'ci_job_artifacts' - end - - def up - UpdateChecksumForCiJobArtifacts::JobArtifact - .where('checksum IS NULL') - .each_batch(of: BATCH_SIZE) do |relation| - ids = relation.pluck(:id).map { |id| [id] } - - UpdateArtifactChecksumWorker.bulk_perform_async(ids) - end - end - - def down - # no-op - end -end diff --git a/spec/workers/concerns/object_storage_queue_spec.rb b/spec/workers/concerns/object_storage_queue_spec.rb deleted file mode 100644 index f725fc9a4b1..00000000000 --- a/spec/workers/concerns/object_storage_queue_spec.rb +++ /dev/null @@ -1,19 +0,0 @@ -require 'spec_helper' - -describe ObjectStorageQueue do - let(:worker) do - Class.new do - def self.name - 'DummyWorker' - end - - include ApplicationWorker - include ObjectStorageQueue - end - end - - it 'sets a default object storage queue automatically' do - expect(worker.sidekiq_options['queue']) - .to eq 'object_storage:dummy' - end -end -- cgit v1.2.3 From a1c612ce2dc2b42137664f73c3a70f3a283bcc0a Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 1 Mar 2018 22:10:44 +0900 Subject: Add checksum at runner grape api --- lib/api/runner.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 91cdc564002..8d2bcc73bb3 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -204,6 +204,7 @@ module API optional 'file.path', type: String, desc: %q(path to locally stored body (generated by Workhorse)) optional 'file.name', type: String, desc: %q(real filename as send in Content-Disposition (generated by Workhorse)) optional 'file.type', type: String, desc: %q(real content type as send in Content-Type (generated by Workhorse)) + optional 'file.sha256', type: String, desc: %q(checksum of the file) optional 'metadata.path', type: String, desc: %q(path to locally stored body (generated by Workhorse)) optional 'metadata.name', type: String, desc: %q(filename (generated by Workhorse)) end @@ -224,7 +225,7 @@ module API expire_in = params['expire_in'] || Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in - job.build_job_artifacts_archive(project: job.project, file_type: :archive, file: artifacts, expire_in: expire_in) + job.build_job_artifacts_archive(project: job.project, file_type: :archive, file: artifacts, checksum: params['file.sha256'], expire_in: expire_in) job.build_job_artifacts_metadata(project: job.project, file_type: :metadata, file: metadata, expire_in: expire_in) if metadata job.artifacts_expire_in = expire_in -- cgit v1.2.3 From 03438886e1de94df6bd89471e67aa6347fd5fb2e Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 6 Mar 2018 17:16:33 +0900 Subject: Change column to file_sha256. Add test. Add changelog --- changelogs/unreleased/feature-sm-add-check-sum-to-job-artifacts.yml | 5 +++++ db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb | 2 +- db/schema.rb | 2 +- lib/api/runner.rb | 4 ++-- spec/requests/api/runner_spec.rb | 4 ++++ 5 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/feature-sm-add-check-sum-to-job-artifacts.yml diff --git a/changelogs/unreleased/feature-sm-add-check-sum-to-job-artifacts.yml b/changelogs/unreleased/feature-sm-add-check-sum-to-job-artifacts.yml new file mode 100644 index 00000000000..23a870d6e9f --- /dev/null +++ b/changelogs/unreleased/feature-sm-add-check-sum-to-job-artifacts.yml @@ -0,0 +1,5 @@ +--- +title: Store sha256 checksum to job artifacts +merge_request: 17354 +author: +type: performance diff --git a/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb b/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb index dd1b9339b28..54e6e35449e 100644 --- a/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb +++ b/db/migrate/20180226050030_add_checksum_to_ci_job_artifacts.rb @@ -2,6 +2,6 @@ class AddChecksumToCiJobArtifacts < ActiveRecord::Migration DOWNTIME = false def change - add_column :ci_job_artifacts, :checksum, :binary + add_column :ci_job_artifacts, :file_sha256, :binary end end diff --git a/db/schema.rb b/db/schema.rb index bfe95199e83..eb92cf030ee 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -346,7 +346,7 @@ ActiveRecord::Schema.define(version: 20180304204842) do t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "expire_at" t.string "file" - t.binary "checksum" + t.binary "file_sha256" end add_index "ci_job_artifacts", ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id", using: :btree diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 8d2bcc73bb3..7e6c33ec33d 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -204,7 +204,7 @@ module API optional 'file.path', type: String, desc: %q(path to locally stored body (generated by Workhorse)) optional 'file.name', type: String, desc: %q(real filename as send in Content-Disposition (generated by Workhorse)) optional 'file.type', type: String, desc: %q(real content type as send in Content-Type (generated by Workhorse)) - optional 'file.sha256', type: String, desc: %q(checksum of the file) + optional 'file.sha256', type: String, desc: %q(sha256 checksum of the file) optional 'metadata.path', type: String, desc: %q(path to locally stored body (generated by Workhorse)) optional 'metadata.name', type: String, desc: %q(filename (generated by Workhorse)) end @@ -225,7 +225,7 @@ module API expire_in = params['expire_in'] || Gitlab::CurrentSettings.current_application_settings.default_artifacts_expire_in - job.build_job_artifacts_archive(project: job.project, file_type: :archive, file: artifacts, checksum: params['file.sha256'], expire_in: expire_in) + job.build_job_artifacts_archive(project: job.project, file_type: :archive, file: artifacts, file_sha256: params['file.sha256'], expire_in: expire_in) job.build_job_artifacts_metadata(project: job.project, file_type: :metadata, file: metadata, expire_in: expire_in) if metadata job.artifacts_expire_in = expire_in diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 72cafac3f90..ce1311ac97c 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -1100,11 +1100,13 @@ describe API::Runner do context 'posts artifacts file and metadata file' do let!(:artifacts) { file_upload } + let!(:artifacts_sha256) { Digest::SHA256.file(artifacts.path).hexdigest } let!(:metadata) { file_upload2 } let(:stored_artifacts_file) { job.reload.artifacts_file.file } let(:stored_metadata_file) { job.reload.artifacts_metadata.file } let(:stored_artifacts_size) { job.reload.artifacts_size } + let(:stored_artifacts_sha256) { job.reload.job_artifacts_archive.file_sha256 } before do post(api("/jobs/#{job.id}/artifacts"), post_data, headers_with_token) @@ -1114,6 +1116,7 @@ describe API::Runner do let(:post_data) do { 'file.path' => artifacts.path, 'file.name' => artifacts.original_filename, + 'file.sha256' => artifacts_sha256, 'metadata.path' => metadata.path, 'metadata.name' => metadata.original_filename } end @@ -1123,6 +1126,7 @@ describe API::Runner do expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename) expect(stored_metadata_file.original_filename).to eq(metadata.original_filename) expect(stored_artifacts_size).to eq(72821) + expect(stored_artifacts_sha256).to eq(artifacts_sha256) end end -- cgit v1.2.3 From 7237ed59ac90148945efebf9624949c80c1298a4 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Tue, 6 Mar 2018 08:26:50 +0000 Subject: Resolve "Enable privileged mode for Runner installed on Kubernetes" --- app/models/clusters/applications/runner.rb | 5 ++-- .../43793-enable-privileged-mode-for-runner.yml | 5 ++++ .../20180305144721_add_privileged_to_runner.rb | 18 +++++++++++++ db/schema.rb | 3 ++- spec/models/clusters/applications/runner_spec.rb | 30 ++++++++++++++++++++++ vendor/runner/values.yaml | 2 -- 6 files changed, 58 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/43793-enable-privileged-mode-for-runner.yml create mode 100644 db/migrate/20180305144721_add_privileged_to_runner.rb diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index 7adf1663c35..16efe90fa27 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -56,12 +56,13 @@ module Clusters def specification { "gitlabUrl" => gitlab_url, - "runnerToken" => ensure_runner.token + "runnerToken" => ensure_runner.token, + "runners" => { "privileged" => privileged } } end def content_values - specification.merge(YAML.load_file(chart_values_file)) + YAML.load_file(chart_values_file).deep_merge!(specification) end end end diff --git a/changelogs/unreleased/43793-enable-privileged-mode-for-runner.yml b/changelogs/unreleased/43793-enable-privileged-mode-for-runner.yml new file mode 100644 index 00000000000..08109632e8e --- /dev/null +++ b/changelogs/unreleased/43793-enable-privileged-mode-for-runner.yml @@ -0,0 +1,5 @@ +--- +title: Enable privileged mode for GitLab Runner +merge_request: 17528 +author: +type: added diff --git a/db/migrate/20180305144721_add_privileged_to_runner.rb b/db/migrate/20180305144721_add_privileged_to_runner.rb new file mode 100644 index 00000000000..32e73dba8d5 --- /dev/null +++ b/db/migrate/20180305144721_add_privileged_to_runner.rb @@ -0,0 +1,18 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddPrivilegedToRunner < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default :clusters_applications_runners, :privileged, :boolean, default: true, allow_null: false + end + + def down + remove_column :clusters_applications_runners, :privileged + end +end diff --git a/db/schema.rb b/db/schema.rb index 9e117440ed2..e28a7560d00 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: 20180304204842) do +ActiveRecord::Schema.define(version: 20180305144721) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -603,6 +603,7 @@ ActiveRecord::Schema.define(version: 20180304204842) do t.datetime_with_timezone "updated_at", null: false t.string "version", null: false t.text "status_reason" + t.boolean "privileged", default: true, null: false end add_index "clusters_applications_runners", ["cluster_id"], name: "index_clusters_applications_runners_on_cluster_id", unique: true, using: :btree diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 612a3c8e413..a574779e39d 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -34,6 +34,8 @@ describe Clusters::Applications::Runner do is_expected.to include('checkInterval') is_expected.to include('rbac') is_expected.to include('runners') + is_expected.to include('privileged: true') + is_expected.to include('image: ubuntu:16.04') is_expected.to include('resources') is_expected.to include("runnerToken: #{ci_runner.token}") is_expected.to include("gitlabUrl: #{Gitlab::Routing.url_helpers.root_url}") @@ -65,5 +67,33 @@ describe Clusters::Applications::Runner do expect(gitlab_runner.runner).not_to be_nil end end + + context 'with duplicated values on vendor/runner/values.yaml' do + let(:values) do + { + "concurrent" => 4, + "checkInterval" => 3, + "rbac" => { + "create" => false + }, + "clusterWideAccess" => false, + "runners" => { + "privileged" => false, + "image" => "ubuntu:16.04", + "builds" => {}, + "services" => {}, + "helpers" => {} + } + } + end + + before do + allow(gitlab_runner).to receive(:chart_values).and_return(values) + end + + it 'should overwrite values.yaml' do + is_expected.to include("privileged: #{gitlab_runner.privileged}") + end + end end end diff --git a/vendor/runner/values.yaml b/vendor/runner/values.yaml index b7e2e24acaf..e5f95152ac7 100644 --- a/vendor/runner/values.yaml +++ b/vendor/runner/values.yaml @@ -15,10 +15,8 @@ rbac: clusterWideAccess: false ## Configuration for the Pods that that the runner launches for each new job -## runners: image: ubuntu:16.04 - privileged: false builds: {} services: {} helpers: {} -- cgit v1.2.3 From fe7d45f26a8e83808ba9a1e15a3830e4ba43f2c9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 6 Mar 2018 10:02:21 +0100 Subject: Fix race condition when previewing docs Cancel the pipeline that gets created the first time the remote branch is created in order not to overwrite the one we trigger afterwards. Closes https://gitlab.com/gitlab-com/gitlab-docs/issues/154 --- scripts/trigger-build-docs | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/scripts/trigger-build-docs b/scripts/trigger-build-docs index a270823b857..ae8cac0cf02 100755 --- a/scripts/trigger-build-docs +++ b/scripts/trigger-build-docs @@ -7,7 +7,7 @@ require 'gitlab' # Gitlab.configure do |config| config.endpoint = 'https://gitlab.com/api/v4' - config.private_token = ENV["DOCS_API_TOKEN"] # GitLab Docs bot access token which has only Developer access to gitlab-docs + config.private_token = ENV["DOCS_API_TOKEN"] # GitLab Docs bot access token with Developer access to gitlab-docs end # @@ -31,13 +31,24 @@ def docs_branch end # -# Create a remote branch in gitlab-docs +# Create a remote branch in gitlab-docs and immediately cancel the pipeline +# to avoid race conditions, since a triggered pipeline will also run right +# after the branch creation. This only happens the very first time a branch +# is created and will be skipped in subsequent runs. Read more in +# https://gitlab.com/gitlab-com/gitlab-docs/issues/154. # def create_remote_branch Gitlab.create_branch(GITLAB_DOCS_REPO, docs_branch, 'master') - puts "Remote branch '#{docs_branch}' created" + puts "=> Remote branch '#{docs_branch}' created" + + # Get the latest pipeline ID which is also the first + pipeline_id = Gitlab.pipelines(GITLAB_DOCS_REPO, { ref: docs_branch }).last.id + + # Cancel the pipeline + Gitlab.cancel_pipeline(GITLAB_DOCS_REPO, pipeline_id) + puts "=> Canceled uneeded pipeline #{pipeline_id} for '#{docs_branch}'" rescue Gitlab::Error::BadRequest - puts "Remote branch '#{docs_branch}' already exists" + puts "=> Remote branch '#{docs_branch}' already exists" end # @@ -45,7 +56,7 @@ end # def remove_remote_branch Gitlab.delete_branch(GITLAB_DOCS_REPO, docs_branch) - puts "Remote branch '#{docs_branch}' deleted" + puts "=> Remote branch '#{docs_branch}' deleted" end # @@ -78,18 +89,22 @@ def trigger_pipeline # The review app URL app_url = "http://#{docs_branch}.#{ENV["DOCS_REVIEW_APPS_DOMAIN"]}/#{slug}" - # Create the pipeline - puts "=> Triggering a pipeline..." + # Create the cross project pipeline using CI_JOB_TOKEN pipeline = Gitlab.run_trigger(GITLAB_DOCS_REPO, ENV["CI_JOB_TOKEN"], docs_branch, { param_name => ENV["CI_COMMIT_REF_NAME"] }) - puts "=> Pipeline created:" + puts "=> Follow the status of the triggered pipeline:" puts "" puts "https://gitlab.com/gitlab-com/gitlab-docs/pipelines/#{pipeline.id}" puts "" - puts "=> Preview your changes live at:" + puts "=> In a few minutes, you will be able to preview your changes under the following URL:" puts "" puts app_url puts "" + puts "=> For more information, read the documentation" + puts "=> https://docs.gitlab.com/ee/development/writing_documentation.html#previewing-the-changes-live" + puts "" + puts "=> If something doesn't work, drop a line in the #docs chat channel." + puts "" end # -- cgit v1.2.3 From 1ff8add7098009722bcbaa1cfe3922d460befd17 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 6 Mar 2018 18:11:43 +0900 Subject: Fix typo in changelog --- changelogs/unreleased/fix-sm-fix_pages_worker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelogs/unreleased/fix-sm-fix_pages_worker.yml b/changelogs/unreleased/fix-sm-fix_pages_worker.yml index fb0c3713215..190c7d3e83e 100644 --- a/changelogs/unreleased/fix-sm-fix_pages_worker.yml +++ b/changelogs/unreleased/fix-sm-fix_pages_worker.yml @@ -1,5 +1,5 @@ --- -title: Fix pages flaky falure by reloading stale object +title: Fix pages flaky failure by reloading stale object merge_request: 17522 author: type: fixed -- cgit v1.2.3 From 6066437385fa0527e97d93fbac46eb6e455a219b Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 6 Mar 2018 18:17:37 +0900 Subject: Add test for ensuring the file remains after socker error --- spec/services/projects/update_pages_service_spec.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb index e91504194fe..934106627a9 100644 --- a/spec/services/projects/update_pages_service_spec.rb +++ b/spec/services/projects/update_pages_service_spec.rb @@ -170,6 +170,9 @@ describe Projects::UpdatePagesService do it 'raises an error' do expect { execute }.to raise_error(SocketError) + + build.reload + expect(build.artifacts?).to eq(true) end end end -- cgit v1.2.3 From 3814edf9d38ef7ce921098d9bb617595c273f42e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 6 Mar 2018 18:19:54 +0800 Subject: So that it's consistent with other entries and EE --- 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 ce7072631dd..e930758ec9d 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 -- cgit v1.2.3 From cf34f33ac906eb9011a6f1dc0979c9bfc4e09e59 Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Fri, 2 Mar 2018 14:03:11 +0100 Subject: Lock gRPC gem to 1.8 Given 1.9 has issues we're aware of, for now we're locking to 1.8. Closes gitlab-org/gitaly#1059 and will probably be removed if 1.10 is tested an deemed ok. --- Gemfile | 4 ++++ Gemfile.lock | 1 + 2 files changed, 5 insertions(+) diff --git a/Gemfile b/Gemfile index a3352b8923c..35f9b081fdf 100644 --- a/Gemfile +++ b/Gemfile @@ -412,6 +412,10 @@ end # Gitaly GRPC client gem 'gitaly-proto', '~> 0.88.0', require: 'gitaly' +# Explicitly lock grpc as we know 1.9 is bad +# 1.10 is still being tested. See gitlab-org/gitaly#1059 +gem 'grpc', '~> 1.8.3' + # 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 a5c94a9e074..010d4f7b56a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1073,6 +1073,7 @@ DEPENDENCIES grape-entity (~> 0.6.0) grape-route-helpers (~> 2.1.0) grape_logging (~> 1.7) + grpc (~> 1.8.3) haml_lint (~> 0.26.0) hamlit (~> 2.6.1) hashie-forbidden_attributes -- cgit v1.2.3 From bdaaa43ff423132e7b46bc1ebc7b991d8a4dbd26 Mon Sep 17 00:00:00 2001 From: Shah El-Rahman Date: Tue, 6 Mar 2018 11:01:37 +0000 Subject: Resolve "Group Leave action is broken on Groups Dashboard and Homepage" --- app/assets/javascripts/groups/components/app.vue | 10 ++-- spec/javascripts/groups/components/app_spec.js | 59 +++++++++++++----------- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue index b8f0566f48c..0578f43d5af 100644 --- a/app/assets/javascripts/groups/components/app.vue +++ b/app/assets/javascripts/groups/components/app.vue @@ -152,14 +152,14 @@ export default { showLeaveGroupModal(group, parentGroup) { this.targetGroup = group; this.targetParentGroup = parentGroup; - this.updateModal = true; + this.showModal = true; this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`); }, hideLeaveGroupModal() { - this.updateModal = false; + this.showModal = false; }, leaveGroup() { - this.updateModal = false; + this.showModal = false; this.targetGroup.isBeingRemoved = true; this.service.leaveGroup(this.targetGroup.leavePath) .then(res => res.json()) @@ -208,9 +208,9 @@ export default { :page-info="pageInfo" /> { vm.fetchGroups({}); setTimeout(() => { - expect(vm.isLoading).toBeFalsy(); + expect(vm.isLoading).toBe(false); expect($.scrollTo).toHaveBeenCalledWith(0); expect(window.Flash).toHaveBeenCalledWith('An error occurred. Please try again.'); done(); @@ -144,10 +144,10 @@ describe('AppComponent', () => { spyOn(vm, 'updateGroups').and.callThrough(); vm.fetchAllGroups(); - expect(vm.isLoading).toBeTruthy(); + expect(vm.isLoading).toBe(true); expect(vm.fetchGroups).toHaveBeenCalled(); setTimeout(() => { - expect(vm.isLoading).toBeFalsy(); + expect(vm.isLoading).toBe(false); expect(vm.updateGroups).toHaveBeenCalled(); done(); }, 0); @@ -181,7 +181,7 @@ describe('AppComponent', () => { spyOn($, 'scrollTo'); vm.fetchPage(2, null, null, true); - expect(vm.isLoading).toBeTruthy(); + expect(vm.isLoading).toBe(true); expect(vm.fetchGroups).toHaveBeenCalledWith({ page: 2, filterGroupsBy: null, @@ -190,7 +190,7 @@ describe('AppComponent', () => { archived: true, }); setTimeout(() => { - expect(vm.isLoading).toBeFalsy(); + expect(vm.isLoading).toBe(false); expect($.scrollTo).toHaveBeenCalledWith(0); expect(utils.mergeUrlParams).toHaveBeenCalledWith({ page: 2 }, jasmine.any(String)); expect(window.history.replaceState).toHaveBeenCalledWith({ @@ -216,7 +216,7 @@ describe('AppComponent', () => { spyOn(vm.store, 'setGroupChildren'); vm.toggleChildren(groupItem); - expect(groupItem.isChildrenLoading).toBeTruthy(); + expect(groupItem.isChildrenLoading).toBe(true); expect(vm.fetchGroups).toHaveBeenCalledWith({ parentId: groupItem.id, }); @@ -232,7 +232,7 @@ describe('AppComponent', () => { vm.toggleChildren(groupItem); expect(vm.fetchGroups).not.toHaveBeenCalled(); - expect(groupItem.isOpen).toBeTruthy(); + expect(groupItem.isOpen).toBe(true); }); it('should collapse group if it is already expanded', () => { @@ -241,16 +241,16 @@ describe('AppComponent', () => { vm.toggleChildren(groupItem); expect(vm.fetchGroups).not.toHaveBeenCalled(); - expect(groupItem.isOpen).toBeFalsy(); + expect(groupItem.isOpen).toBe(false); }); it('should set `isChildrenLoading` back to `false` if load request fails', (done) => { spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise({}, true)); vm.toggleChildren(groupItem); - expect(groupItem.isChildrenLoading).toBeTruthy(); + expect(groupItem.isChildrenLoading).toBe(true); setTimeout(() => { - expect(groupItem.isChildrenLoading).toBeFalsy(); + expect(groupItem.isChildrenLoading).toBe(false); done(); }, 0); }); @@ -268,10 +268,10 @@ describe('AppComponent', () => { it('updates props which show modal confirmation dialog', () => { const group = Object.assign({}, mockParentGroupItem); - expect(vm.updateModal).toBeFalsy(); + expect(vm.showModal).toBe(false); expect(vm.groupLeaveConfirmationMessage).toBe(''); vm.showLeaveGroupModal(group, mockParentGroupItem); - expect(vm.updateModal).toBeTruthy(); + expect(vm.showModal).toBe(true); expect(vm.groupLeaveConfirmationMessage).toBe(`Are you sure you want to leave the "${group.fullName}" group?`); }); }); @@ -280,9 +280,9 @@ describe('AppComponent', () => { it('hides modal confirmation which is shown before leaving the group', () => { const group = Object.assign({}, mockParentGroupItem); vm.showLeaveGroupModal(group, mockParentGroupItem); - expect(vm.updateModal).toBeTruthy(); + expect(vm.showModal).toBe(true); vm.hideLeaveGroupModal(); - expect(vm.updateModal).toBeFalsy(); + expect(vm.showModal).toBe(false); }); }); @@ -307,8 +307,8 @@ describe('AppComponent', () => { spyOn($, 'scrollTo'); vm.leaveGroup(); - expect(vm.updateModal).toBeFalsy(); - expect(vm.targetGroup.isBeingRemoved).toBeTruthy(); + expect(vm.showModal).toBe(false); + expect(vm.targetGroup.isBeingRemoved).toBe(true); expect(vm.service.leaveGroup).toHaveBeenCalledWith(vm.targetGroup.leavePath); setTimeout(() => { expect($.scrollTo).toHaveBeenCalledWith(0); @@ -325,12 +325,12 @@ describe('AppComponent', () => { spyOn(window, 'Flash'); vm.leaveGroup(); - expect(vm.targetGroup.isBeingRemoved).toBeTruthy(); + expect(vm.targetGroup.isBeingRemoved).toBe(true); expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath); setTimeout(() => { expect(vm.store.removeGroup).not.toHaveBeenCalled(); expect(window.Flash).toHaveBeenCalledWith(message); - expect(vm.targetGroup.isBeingRemoved).toBeFalsy(); + expect(vm.targetGroup.isBeingRemoved).toBe(false); done(); }, 0); }); @@ -342,12 +342,12 @@ describe('AppComponent', () => { spyOn(window, 'Flash'); vm.leaveGroup(childGroupItem, groupItem); - expect(vm.targetGroup.isBeingRemoved).toBeTruthy(); + expect(vm.targetGroup.isBeingRemoved).toBe(true); expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath); setTimeout(() => { expect(vm.store.removeGroup).not.toHaveBeenCalled(); expect(window.Flash).toHaveBeenCalledWith(message); - expect(vm.targetGroup.isBeingRemoved).toBeFalsy(); + expect(vm.targetGroup.isBeingRemoved).toBe(false); done(); }, 0); }); @@ -379,10 +379,10 @@ describe('AppComponent', () => { it('should set `isSearchEmpty` prop based on groups count', () => { vm.updateGroups(mockGroups); - expect(vm.isSearchEmpty).toBeFalsy(); + expect(vm.isSearchEmpty).toBe(false); vm.updateGroups([]); - expect(vm.isSearchEmpty).toBeTruthy(); + expect(vm.isSearchEmpty).toBe(true); }); }); }); @@ -473,13 +473,16 @@ describe('AppComponent', () => { }); }); - it('renders modal confirmation dialog', () => { + it('renders modal confirmation dialog', (done) => { vm.groupLeaveConfirmationMessage = 'Are you sure you want to leave the "foo" group?'; - vm.updateModal = true; - const modalDialogEl = vm.$el.querySelector('.modal'); - expect(modalDialogEl).not.toBe(null); - expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?'); - expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave'); + vm.showModal = true; + Vue.nextTick(() => { + const modalDialogEl = vm.$el.querySelector('.modal'); + expect(modalDialogEl).not.toBe(null); + expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?'); + expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave'); + done(); + }); }); }); }); -- cgit v1.2.3 From 7de317d4751496694a05c311f9068c03f36b0445 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 6 Mar 2018 19:30:13 +0800 Subject: Unify lib/api/job_artifacts.rb with EE --- lib/api/job_artifacts.rb | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb index 2a8fa7659bf..47e5eeab31d 100644 --- a/lib/api/job_artifacts.rb +++ b/lib/api/job_artifacts.rb @@ -2,20 +2,28 @@ module API class JobArtifacts < Grape::API before { authenticate_non_get! } + # EE::API::JobArtifacts would override the following helpers + helpers do + def authorize_download_artifacts! + authorize_read_builds! + end + end + params do requires :id, type: String, desc: 'The ID of a project' end resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do - desc 'Download the artifacts file from a job' do + desc 'Download the artifacts archive from a job' do detail 'This feature was introduced in GitLab 8.10' end params do requires :ref_name, type: String, desc: 'The ref from repository' requires :job, type: String, desc: 'The name for the job' end + route_setting :authentication, job_token_allowed: true get ':id/jobs/artifacts/:ref_name/download', requirements: { ref_name: /.+/ } do - authorize_read_builds! + authorize_download_artifacts! builds = user_project.latest_successful_builds_for(params[:ref_name]) latest_build = builds.find_by!(name: params[:job]) @@ -23,14 +31,15 @@ module API present_artifacts!(latest_build.artifacts_file) end - desc 'Download the artifacts file from a job' do + desc 'Download the artifacts archive from a job' do detail 'This feature was introduced in GitLab 8.5' end params do requires :job_id, type: Integer, desc: 'The ID of a job' end + route_setting :authentication, job_token_allowed: true get ':id/jobs/:job_id/artifacts' do - authorize_read_builds! + authorize_download_artifacts! build = find_build!(params[:job_id]) -- cgit v1.2.3 From c428aaac6613b9fcfecd479f7bb510a6e74b761c Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 6 Mar 2018 20:33:42 +0900 Subject: Revert unnecessary code running_or_pending_build_count removal --- app/models/ci/build.rb | 1 + app/models/project.rb | 6 ++++++ spec/features/projects/jobs/user_browses_job_spec.rb | 2 ++ 3 files changed, 9 insertions(+) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 3f037822154..b230b7f47ef 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -359,6 +359,7 @@ module Ci project.execute_hooks(build_data.dup, :job_hooks) project.execute_services(build_data.dup, :job_hooks) PagesService.new(build_data).execute + project.running_or_pending_build_count(force: true) end def artifacts_metadata_entry(path, **options) diff --git a/app/models/project.rb b/app/models/project.rb index 58c4094918c..a11b1e4f554 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1494,6 +1494,12 @@ class Project < ActiveRecord::Base update_column(:import_jid, nil) end + def running_or_pending_build_count(force: false) + Rails.cache.fetch(['projects', id, 'running_or_pending_build_count'], force: force) do + builds.running_or_pending.count(:all) + end + end + # Lazy loading of the `pipeline_status` attribute def pipeline_status @pipeline_status ||= Gitlab::Cache::Ci::ProjectPipelineStatus.load_for_project(self) diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 48462c193f1..4c49cff30d4 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -31,5 +31,7 @@ describe 'User browses a job', :js do page.within('.erased') do expect(page).to have_content('Job has been erased') end + + expect(build.project.running_or_pending_build_count).to eq(build.project.builds.running_or_pending.count(:all)) end end -- cgit v1.2.3 From 580d8953636266e40802fd8ea525c4908ebc8b9f Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Wed, 15 Nov 2017 23:56:36 +0900 Subject: Add overview of branches and a filter for active/stale branches --- app/controllers/projects/branches_controller.rb | 41 +++++- app/finders/branches_finder.rb | 2 +- app/helpers/branches_helper.rb | 11 -- app/views/projects/branches/_panel.html.haml | 19 +++ app/views/projects/branches/index.html.haml | 53 +++++--- ...branch-dashboard-with-active-stale-branches.yml | 5 + config/routes/repository.rb | 1 + lib/gitlab/git/branch.rb | 14 +++ .../projects/branches_controller_spec.rb | 33 +++++ .../projects/branches/download_buttons_spec.rb | 2 +- spec/features/projects/branches_spec.rb | 137 +++++++++++++++++++-- .../projects/environments/environment_spec.rb | 2 +- .../features/projects/merge_request_button_spec.rb | 4 +- spec/lib/gitlab/git/branch_spec.rb | 64 ++++++++++ 14 files changed, 338 insertions(+), 50 deletions(-) create mode 100644 app/views/projects/branches/_panel.html.haml create mode 100644 changelogs/unreleased/40187-project-branch-dashboard-with-active-stale-branches.yml diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index cabafe26357..965cece600e 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -7,13 +7,19 @@ class Projects::BranchesController < Projects::ApplicationController before_action :authorize_download_code! before_action :authorize_push_code!, only: [:new, :create, :destroy, :destroy_all_merged] - def index - @sort = params[:sort].presence || sort_value_recently_updated - @branches = BranchesFinder.new(@repository, params.merge(sort: @sort)).execute - @branches = Kaminari.paginate_array(@branches).page(params[:page]) + # Support legacy URLs + before_action :redirect_for_legacy_index_sort_or_search, only: [:index] + def index respond_to do |format| format.html do + @sort = params[:sort].presence || sort_value_recently_updated + @mode = params[:state].presence || 'overview' + @overview_max_branches = 5 + + # Fetch branches for the specified mode + fetch_branches_by_mode + @refs_pipelines = @project.pipelines.latest_successful_for_refs(@branches.map(&:name)) @merged_branch_names = repository.merged_branch_names(@branches.map(&:name)) @@ -28,7 +34,9 @@ class Projects::BranchesController < Projects::ApplicationController end end format.json do - render json: @branches.map(&:name) + branches = BranchesFinder.new(@repository, params).execute + branches = Kaminari.paginate_array(branches).page(params[:page]) + render json: branches.map(&:name) end end end @@ -123,4 +131,27 @@ class Projects::BranchesController < Projects::ApplicationController context: 'autodeploy' ) end + + def redirect_for_legacy_index_sort_or_search + # Normalize a legacy URL with redirect + if request.format != :json && !params[:state].presence && [:sort, :search, :page].any? { |key| params[key].presence } + redirect_to project_branches_filtered_path(@project, state: 'all'), notice: 'Update your bookmarked URLs as filtered/sorted branches URL has been changed.' + end + end + + def fetch_branches_by_mode + if @mode == 'overview' + # overview mode + @active_branches, @stale_branches = BranchesFinder.new(@repository, sort: sort_value_recently_updated).execute.partition(&:active?) + # Here we get one more branch to indicate if there are more data we're not showing + @active_branches = @active_branches.first(@overview_max_branches + 1) + @stale_branches = @stale_branches.first(@overview_max_branches + 1) + @branches = @active_branches + @stale_branches + else + # active/stale/all view mode + @branches = BranchesFinder.new(@repository, params.merge(sort: @sort)).execute + @branches = @branches.select { |b| b.state.to_s == @mode } if %w[active stale].include?(@mode) + @branches = Kaminari.paginate_array(@branches).page(params[:page]) + end + end end diff --git a/app/finders/branches_finder.rb b/app/finders/branches_finder.rb index 852eac3647d..8bb1366867c 100644 --- a/app/finders/branches_finder.rb +++ b/app/finders/branches_finder.rb @@ -1,5 +1,5 @@ class BranchesFinder - def initialize(repository, params) + def initialize(repository, params = {}) @repository = repository @params = params end diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb index 00b9a0e00eb..07b1fc3d7cf 100644 --- a/app/helpers/branches_helper.rb +++ b/app/helpers/branches_helper.rb @@ -1,15 +1,4 @@ module BranchesHelper - def filter_branches_path(options = {}) - exist_opts = { - search: params[:search], - sort: params[:sort] - } - - options = exist_opts.merge(options) - - project_branches_path(@project, @id, options) - end - def project_branches options_for_select(@project.repository.branch_names, @project.default_branch) end diff --git a/app/views/projects/branches/_panel.html.haml b/app/views/projects/branches/_panel.html.haml new file mode 100644 index 00000000000..12e5a8e8d69 --- /dev/null +++ b/app/views/projects/branches/_panel.html.haml @@ -0,0 +1,19 @@ +- branches = local_assigns.fetch(:branches) +- state = local_assigns.fetch(:state) +- panel_title = local_assigns.fetch(:panel_title) +- show_more_text = local_assigns.fetch(:show_more_text) +- project = local_assigns.fetch(:project) +- overview_max_branches = local_assigns.fetch(:overview_max_branches) + +- return unless branches.any? + +.panel.panel-default.prepend-top-10 + .panel-heading + %h4.panel-title + = panel_title + %ul.content-list.all-branches + - branches.first(overview_max_branches).each do |branch| + = render "projects/branches/branch", branch: branch, merged: project.repository.merged_to_root_ref?(branch) + - if branches.size > overview_max_branches + .panel-footer.text-center + = link_to show_more_text, project_branches_filtered_path(project, state: state), id: "state-#{state}", data: { state: state } diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index fb770764364..5dcc72d8263 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -3,26 +3,35 @@ %div{ class: container_class } .top-area.adjust - - if can?(current_user, :admin_project, @project) - .nav-text - - project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project) - = s_('Branches|Protected branches can be managed in %{project_settings_link}').html_safe % { project_settings_link: project_settings_link } + %ul.nav-links.issues-state-filters + %li{ class: active_when(@mode == 'overview') }> + = link_to s_('Branches|Overview'), project_branches_path(@project), title: s_('Branches|Show overview of the branches') + + %li{ class: active_when(@mode == 'active') }> + = link_to s_('Branches|Active'), project_branches_filtered_path(@project, state: 'active'), title: s_('Branches|Show active branches') + + %li{ class: active_when(@mode == 'stale') }> + = link_to s_('Branches|Stale'), project_branches_filtered_path(@project, state: 'stale'), title: s_('Branches|Show stale branches') + + %li{ class: active_when(!%w[overview active stale].include?(@mode)) }> + = link_to s_('Branches|All'), project_branches_filtered_path(@project, state: 'all'), title: s_('Branches|Show all branches') .nav-controls - = form_tag(filter_branches_path, method: :get) do + = form_tag(project_branches_filtered_path(@project, state: 'all'), method: :get) do = search_field_tag :search, params[:search], { placeholder: s_('Branches|Filter by branch name'), id: 'branch-search', class: 'form-control search-text-input input-short', spellcheck: false } - .dropdown.inline> - %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } - %span.light - = branches_sort_options_hash[@sort] - = icon('chevron-down') - %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable - %li.dropdown-header - = s_('Branches|Sort by') - - branches_sort_options_hash.each do |value, title| - %li - = link_to title, filter_branches_path(sort: value), class: ("is-active" if @sort == value) + - unless @mode == 'overview' + .dropdown.inline> + %button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' } + %span.light + = branches_sort_options_hash[@sort] + = icon('chevron-down') + %ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable + %li.dropdown-header + = s_('Branches|Sort by') + - branches_sort_options_hash.each do |value, title| + %li + = link_to title, project_branches_filtered_path(@project, state: 'all', search: params[:search], sort: value), class: ("is-active" if @sort == value) - if can? current_user, :push_code, @project = link_to project_merged_branches_path(@project), @@ -35,7 +44,17 @@ = link_to new_project_branch_path(@project), class: 'btn btn-create' do = s_('Branches|New branch') - - if @branches.any? + - if can?(current_user, :admin_project, @project) + - project_settings_link = link_to s_('Branches|project settings'), project_protected_branches_path(@project) + .row-content-block + %h5 + = s_('Branches|Protected branches can be managed in %{project_settings_link}.').html_safe % { project_settings_link: project_settings_link } + + - if @mode == 'overview' && (@active_branches.any? || @stale_branches.any?) + = render "projects/branches/panel", branches: @active_branches, state: 'active', panel_title: s_('Branches|Active branches'), show_more_text: s_('Branches|Show more active branches'), project: @project, overview_max_branches: @overview_max_branches + = render "projects/branches/panel", branches: @stale_branches, state: 'stale', panel_title: s_('Branches|Stale branches'), show_more_text: s_('Branches|Show more stale branches'), project: @project, overview_max_branches: @overview_max_branches + + - elsif @branches.any? %ul.content-list.all-branches - @branches.each do |branch| = render "projects/branches/branch", branch: branch, merged: @merged_branch_names.include?(branch.name) diff --git a/changelogs/unreleased/40187-project-branch-dashboard-with-active-stale-branches.yml b/changelogs/unreleased/40187-project-branch-dashboard-with-active-stale-branches.yml new file mode 100644 index 00000000000..3833aab42dd --- /dev/null +++ b/changelogs/unreleased/40187-project-branch-dashboard-with-active-stale-branches.yml @@ -0,0 +1,5 @@ +--- +title: Add overview of branches and a filter for active/stale branches +merge_request: 15402 +author: Takuya Noguchi +type: added diff --git a/config/routes/repository.rb b/config/routes/repository.rb index 9ffdebbcff1..eace3a615b4 100644 --- a/config/routes/repository.rb +++ b/config/routes/repository.rb @@ -49,6 +49,7 @@ scope format: false do end end + get '/branches/:state', to: 'branches#index', as: :branches_filtered, constraints: { state: /active|stale|all/ } resources :branches, only: [:index, :new, :create, :destroy] delete :merged_branches, controller: 'branches', action: :destroy_all_merged resources :tags, only: [:index, :show, :new, :create, :destroy] do diff --git a/lib/gitlab/git/branch.rb b/lib/gitlab/git/branch.rb index ae7e88f0503..6351cfb83e3 100644 --- a/lib/gitlab/git/branch.rb +++ b/lib/gitlab/git/branch.rb @@ -1,6 +1,8 @@ module Gitlab module Git class Branch < Ref + STALE_BRANCH_THRESHOLD = 3.months + def self.find(repo, branch_name) if branch_name.is_a?(Gitlab::Git::Branch) branch_name @@ -12,6 +14,18 @@ module Gitlab def initialize(repository, name, target, target_commit) super(repository, name, target, target_commit) end + + def active? + self.dereferenced_target.committed_date >= STALE_BRANCH_THRESHOLD.ago + end + + def stale? + !active? + end + + def state + active? ? :active : :stale + end end end end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index 734396ddf7b..3b9e06cb5ad 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -407,10 +407,43 @@ describe Projects::BranchesController do get :index, namespace_id: project.namespace, project_id: project, + state: 'all', format: :html expect(response).to have_gitlab_http_status(200) end end + + context 'when depreated sort/search/page parameters are specified' do + it 'returns with a status 301 when sort specified' do + get :index, + namespace_id: project.namespace, + project_id: project, + sort: 'updated_asc', + format: :html + + expect(response).to redirect_to project_branches_filtered_path(project, state: 'all') + end + + it 'returns with a status 301 when search specified' do + get :index, + namespace_id: project.namespace, + project_id: project, + search: 'feature', + format: :html + + expect(response).to redirect_to project_branches_filtered_path(project, state: 'all') + end + + it 'returns with a status 301 when page specified' do + get :index, + namespace_id: project.namespace, + project_id: project, + page: 2, + format: :html + + expect(response).to redirect_to project_branches_filtered_path(project, state: 'all') + end + end end end diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb index 39bcea013e7..605298ba8ab 100644 --- a/spec/features/projects/branches/download_buttons_spec.rb +++ b/spec/features/projects/branches/download_buttons_spec.rb @@ -29,7 +29,7 @@ feature 'Download buttons in branches page' do describe 'when checking branches' do context 'with artifacts' do before do - visit project_branches_path(project, search: 'binary-encoding') + visit project_branches_filtered_path(project, state: 'all', search: 'binary-encoding') end scenario 'shows download artifacts button' do diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb index 2fddd274078..2a9d9e6416c 100644 --- a/spec/features/projects/branches_spec.rb +++ b/spec/features/projects/branches_spec.rb @@ -11,15 +11,109 @@ describe 'Branches' do project.add_developer(user) end - describe 'Initial branches page' do - it 'shows all the branches sorted by last updated by default' do + context 'on the projects with 6 active branches and 4 stale branches' do + let(:project) { create(:project, :public, :empty_repo) } + let(:repository) { project.repository } + let(:threshold) { Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD } + + before do + # Add 4 stale branches + (1..4).reverse_each do |i| + Timecop.freeze((threshold + i).ago) { create_file(message: "a commit in stale-#{i}", branch_name: "stale-#{i}") } + end + # Add 6 active branches + (1..6).each do |i| + Timecop.freeze((threshold - i).ago) { create_file(message: "a commit in active-#{i}", branch_name: "active-#{i}") } + end + end + + describe 'Overview page of the branches' do + it 'shows the first 5 active branches and the first 4 stale branches sorted by last updated' do + visit project_branches_path(project) + + expect(page).to have_content(sorted_branches(repository, count: 5, sort_by: :updated_desc, state: 'active')) + expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_desc, state: 'stale')) + + expect(page).to have_link('Show more active branches', href: project_branches_filtered_path(project, state: 'active')) + expect(page).not_to have_content('Show more stale branches') + end + end + + describe 'Active branches page' do + it 'shows 6 active branches sorted by last updated' do + visit project_branches_filtered_path(project, state: 'active') + + expect(page).to have_content(sorted_branches(repository, count: 6, sort_by: :updated_desc, state: 'active')) + end + end + + describe 'Stale branches page' do + it 'shows 4 active branches sorted by last updated' do + visit project_branches_filtered_path(project, state: 'stale') + + expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_desc, state: 'stale')) + end + end + + describe 'All branches page' do + it 'shows 10 branches sorted by last updated' do + visit project_branches_filtered_path(project, state: 'all') + + expect(page).to have_content(sorted_branches(repository, count: 10, sort_by: :updated_desc)) + end + end + + context 'with branches over more than one page' do + before do + allow(Kaminari.config).to receive(:default_per_page).and_return(5) + end + + it 'shows only default_per_page active branches sorted by last updated' do + visit project_branches_filtered_path(project, state: 'active') + + expect(page).to have_content(sorted_branches(repository, count: Kaminari.config.default_per_page, sort_by: :updated_desc, state: 'active')) + end + + it 'shows only default_per_page branches sorted by last updated on All branches' do + visit project_branches_filtered_path(project, state: 'all') + + expect(page).to have_content(sorted_branches(repository, count: Kaminari.config.default_per_page, sort_by: :updated_desc)) + end + end + end + + describe 'Find branches' do + it 'shows filtered branches', :js do visit project_branches_path(project) + fill_in 'branch-search', with: 'fix' + find('#branch-search').native.send_keys(:enter) + + expect(page).to have_content('fix') + expect(find('.all-branches')).to have_selector('li', count: 1) + end + end + + describe 'Delete unprotected branch on Overview' do + it 'removes branch after confirmation', :js do + visit project_branches_filtered_path(project, state: 'all') + + expect(all('.all-branches').last).to have_selector('li', count: 20) + accept_confirm { find('.js-branch-add-pdf-text-binary .btn-remove').click } + + expect(all('.all-branches').last).to have_selector('li', count: 19) + end + end + + describe 'All branches page' do + it 'shows all the branches sorted by last updated by default' do + visit project_branches_filtered_path(project, state: 'all') + expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_desc)) end it 'sorts the branches by name' do - visit project_branches_path(project) + visit project_branches_filtered_path(project, state: 'all') click_button "Last updated" # Open sorting dropdown click_link "Name" @@ -28,7 +122,7 @@ describe 'Branches' do end it 'sorts the branches by oldest updated' do - visit project_branches_path(project) + visit project_branches_filtered_path(project, state: 'all') click_button "Last updated" # Open sorting dropdown click_link "Oldest updated" @@ -41,13 +135,13 @@ describe 'Branches' do %w(one two three four five).each { |ref| repository.add_branch(user, ref, 'master') } - expect { visit project_branches_path(project) }.not_to exceed_query_limit(control_count) + expect { visit project_branches_filtered_path(project, state: 'all') }.not_to exceed_query_limit(control_count) end end - describe 'Find branches' do + describe 'Find branches on All branches' do it 'shows filtered branches', :js do - visit project_branches_path(project) + visit project_branches_filtered_path(project, state: 'all') fill_in 'branch-search', with: 'fix' find('#branch-search').native.send_keys(:enter) @@ -57,9 +151,9 @@ describe 'Branches' do end end - describe 'Delete unprotected branch' do + describe 'Delete unprotected branch on All branches' do it 'removes branch after confirmation', :js do - visit project_branches_path(project) + visit project_branches_filtered_path(project, state: 'all') fill_in 'branch-search', with: 'fix' @@ -73,6 +167,19 @@ describe 'Branches' do expect(find('.all-branches')).to have_selector('li', count: 0) end end + + context 'on project with 0 branch' do + let(:project) { create(:project, :public, :empty_repo) } + let(:repository) { project.repository } + + describe '0 branches on Overview' do + it 'shows warning' do + visit project_branches_path(project) + + expect(page).not_to have_selector('.all-branches') + end + end + end end context 'logged in as master' do @@ -83,7 +190,7 @@ describe 'Branches' do describe 'Initial branches page' do it 'shows description for admin' do - visit project_branches_path(project) + visit project_branches_filtered_path(project, state: 'all') expect(page).to have_content("Protected branches can be managed in project settings") end @@ -102,12 +209,18 @@ describe 'Branches' do end end - def sorted_branches(repository, count:, sort_by:) + def sorted_branches(repository, count:, sort_by:, state: nil) + branches = repository.branches_sorted_by(sort_by) + branches = branches.select { |b| state == 'active' ? b.active? : b.stale? } if state sorted_branches = - repository.branches_sorted_by(sort_by).first(count).map do |branch| + branches.first(count).map do |branch| Regexp.escape(branch.name) end Regexp.new(sorted_branches.join('.*')) end + + def create_file(message: 'message', branch_name:) + repository.create_file(user, generate(:branch), 'content', message: message, branch_name: branch_name) + end end diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index 64e600144e0..b233af83eec 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -234,7 +234,7 @@ feature 'Environment' do end scenario 'user deletes the branch with running environment' do - visit project_branches_path(project, search: 'feature') + visit project_branches_filtered_path(project, state: 'all', search: 'feature') remove_branch_with_hooks(project, user, 'feature') do page.within('.js-branch-feature') { find('a.btn-remove').click } diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb index 85d518c0cc3..40689964b91 100644 --- a/spec/features/projects/merge_request_button_spec.rb +++ b/spec/features/projects/merge_request_button_spec.rb @@ -81,8 +81,8 @@ feature 'Merge Request button' do context 'on branches page' do it_behaves_like 'Merge request button only shown when allowed' do let(:label) { 'Merge request' } - let(:url) { project_branches_path(project, search: 'feature') } - let(:fork_url) { project_branches_path(forked_project, search: 'feature') } + let(:url) { project_branches_filtered_path(project, state: 'all', search: 'feature') } + let(:fork_url) { project_branches_filtered_path(forked_project, state: 'all', search: 'feature') } end end diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb index 708870060e7..a19155ed5b0 100644 --- a/spec/lib/gitlab/git/branch_spec.rb +++ b/spec/lib/gitlab/git/branch_spec.rb @@ -59,5 +59,69 @@ describe Gitlab::Git::Branch, seed_helper: true do it { expect(branch.dereferenced_target.sha).to eq(SeedRepo::LastCommit::ID) } end + context 'with active, stale and future branches' do + let(:repository) do + Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') + end + + let(:user) { create(:user) } + let(:committer) do + Gitlab::Git.committer_hash(email: user.email, name: user.name) + end + let(:params) do + parents = [repository.rugged.head.target] + tree = parents.first.tree + + { + message: 'commit message', + author: committer, + committer: committer, + tree: tree, + parents: parents + } + end + let(:stale_sha) { Timecop.freeze(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago - 5.days) { create_commit } } + let(:active_sha) { Timecop.freeze(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago + 5.days) { create_commit } } + let(:future_sha) { Timecop.freeze(100.days.since) { create_commit } } + + before do + repository.create_branch('stale-1', stale_sha) + repository.create_branch('active-1', active_sha) + repository.create_branch('future-1', future_sha) + end + + after do + ensure_seeds + end + + describe 'examine if the branch is active or stale' do + let(:stale_branch) { repository.find_branch('stale-1') } + let(:active_branch) { repository.find_branch('active-1') } + let(:future_branch) { repository.find_branch('future-1') } + + describe '#active?' do + it { expect(stale_branch.active?).to be_falsey } + it { expect(active_branch.active?).to be_truthy } + it { expect(future_branch.active?).to be_truthy } + end + + describe '#stale?' do + it { expect(stale_branch.stale?).to be_truthy } + it { expect(active_branch.stale?).to be_falsey } + it { expect(future_branch.stale?).to be_falsey } + end + + describe '#state' do + it { expect(stale_branch.state).to eq(:stale) } + it { expect(active_branch.state).to eq(:active) } + it { expect(future_branch.state).to eq(:active) } + end + end + end + it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) } + + def create_commit + repository.create_commit(params.merge(committer: committer.merge(time: Time.now))) + end end -- cgit v1.2.3 From f0d7a2ffc419e5e4893f9161c008d4ead5d3a660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Fri, 23 Feb 2018 13:08:38 +0100 Subject: Proper fix for artifacts trace migration which is fully safe --- app/workers/create_trace_artifact_worker.rb | 4 +++- lib/gitlab/ci/trace.rb | 31 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/app/workers/create_trace_artifact_worker.rb b/app/workers/create_trace_artifact_worker.rb index 11cda58021e..3283e8d79f0 100644 --- a/app/workers/create_trace_artifact_worker.rb +++ b/app/workers/create_trace_artifact_worker.rb @@ -2,9 +2,11 @@ class CreateTraceArtifactWorker include ApplicationWorker include PipelineQueue + # TODO: this worker should use BackgroundMigration or ObjectStorage queue + def perform(job_id) Ci::Build.preload(:project, :user).find_by(id: job_id).try do |job| - Ci::CreateTraceArtifactService.new(job.project, job.user).execute(job) + job.trace.archive! end end end diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb index f2e5124c8a8..defc8160121 100644 --- a/lib/gitlab/ci/trace.rb +++ b/lib/gitlab/ci/trace.rb @@ -93,8 +93,39 @@ module Gitlab job.erase_old_trace! end + def archive! + return if trace_artifact + + if current_path + File.open(current_path) do |f| + archive_stream!(f) + f.unlink + end + elsif old_trace + StringIO(old_trace).tap do |stream| + archive_stream!(stream) + job.erase_old_trace! + end + end + end + private + def archive_stream!(stream) + file = Tempfile.new('trace.log') + size = IO.copy_stream(file, stream) + raise 'Not all saved' unless size == stream.size + file.close + + job.create_job_artifacts_trace!( + project: job.project, + file_type: :trace, + file: file) + ensure + file&.close + file&.unlink + end + def ensure_path return current_path if current_path -- cgit v1.2.3 From 32c1501d69d1618452e0088b343a6806769d79a9 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 26 Feb 2018 23:06:04 +0900 Subject: Add rake task. Adopt the latest fix. Drop CreateTraceArtifactService --- app/services/ci/create_trace_artifact_service.rb | 36 ------------------------ app/workers/archive_legacy_trace_worker.rb | 10 +++++++ app/workers/create_trace_artifact_worker.rb | 4 +-- lib/gitlab/ci/trace.rb | 34 ++++++++++++++-------- lib/tasks/gitlab/traces.rake | 17 +++++++++++ 5 files changed, 50 insertions(+), 51 deletions(-) delete mode 100644 app/services/ci/create_trace_artifact_service.rb create mode 100644 app/workers/archive_legacy_trace_worker.rb create mode 100644 lib/tasks/gitlab/traces.rake diff --git a/app/services/ci/create_trace_artifact_service.rb b/app/services/ci/create_trace_artifact_service.rb deleted file mode 100644 index ffde824972c..00000000000 --- a/app/services/ci/create_trace_artifact_service.rb +++ /dev/null @@ -1,36 +0,0 @@ -module Ci - class CreateTraceArtifactService < BaseService - def execute(job) - return if job.job_artifacts_trace - - job.trace.read do |stream| - break unless stream.file? - - clone_file!(stream.path, JobArtifactUploader.workhorse_upload_path) do |clone_path| - create_job_trace!(job, clone_path) - FileUtils.rm(stream.path) - end - end - end - - private - - def create_job_trace!(job, path) - File.open(path) do |stream| - job.create_job_artifacts_trace!( - project: job.project, - file_type: :trace, - file: stream) - end - end - - def clone_file!(src_path, temp_dir) - FileUtils.mkdir_p(temp_dir) - Dir.mktmpdir('tmp-trace', temp_dir) do |dir_path| - temp_path = File.join(dir_path, "job.log") - FileUtils.copy(src_path, temp_path) - yield(temp_path) - end - end - end -end diff --git a/app/workers/archive_legacy_trace_worker.rb b/app/workers/archive_legacy_trace_worker.rb new file mode 100644 index 00000000000..01b6224494b --- /dev/null +++ b/app/workers/archive_legacy_trace_worker.rb @@ -0,0 +1,10 @@ +class ArchiveLegacyTraceWorker + include ApplicationWorker + include ObjectStorageQueue + + def perform(job_id) + Ci::Build.find_by(id: job_id).try do |job| + job.trace.archive! + end + end +end diff --git a/app/workers/create_trace_artifact_worker.rb b/app/workers/create_trace_artifact_worker.rb index 3283e8d79f0..a0cec43157e 100644 --- a/app/workers/create_trace_artifact_worker.rb +++ b/app/workers/create_trace_artifact_worker.rb @@ -2,10 +2,8 @@ class CreateTraceArtifactWorker include ApplicationWorker include PipelineQueue - # TODO: this worker should use BackgroundMigration or ObjectStorage queue - def perform(job_id) - Ci::Build.preload(:project, :user).find_by(id: job_id).try do |job| + Ci::Build.find_by(id: job_id).try do |job| job.trace.archive! end end diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb index defc8160121..ef5e7fcac23 100644 --- a/lib/gitlab/ci/trace.rb +++ b/lib/gitlab/ci/trace.rb @@ -112,18 +112,28 @@ module Gitlab private def archive_stream!(stream) - file = Tempfile.new('trace.log') - size = IO.copy_stream(file, stream) - raise 'Not all saved' unless size == stream.size - file.close - - job.create_job_artifacts_trace!( - project: job.project, - file_type: :trace, - file: file) - ensure - file&.close - file&.unlink + clone_file!(stream, JobArtifactUploader.workhorse_upload_path) do |clone_path| + create_job_trace!(job, clone_path) + end + end + + def clone_file!(src_stream, temp_dir) + FileUtils.mkdir_p(temp_dir) + Dir.mktmpdir('tmp-trace', temp_dir) do |dir_path| + temp_path = File.join(dir_path, "job.log") + size = IO.write(src_stream, temp_path) + raise 'Not all saved' unless size == src_stream.size + yield(temp_path) + end + end + + def create_job_trace!(job, path) + File.open(path) do |stream| + job.create_job_artifacts_trace!( + project: job.project, + file_type: :trace, + file: stream) + end end def ensure_path diff --git a/lib/tasks/gitlab/traces.rake b/lib/tasks/gitlab/traces.rake new file mode 100644 index 00000000000..f45edc922aa --- /dev/null +++ b/lib/tasks/gitlab/traces.rake @@ -0,0 +1,17 @@ +require 'logger' +require 'resolv-replace' + +desc "GitLab | Archive legacy traces to trace artifacts" +namespace :gitlab do + namespace :traces do + task archive: :environment do + logger = Logger.new(STDOUT) + logger.info('Archiving legacy traces') + + job_ids = Ci::Build.complete.without_trace_artifact.pluck(:id) + job_ids = job_ids.map { |build_id| [build_id] } + + ArchiveLegacyTraceWorker.bulk_perform_async(job_ids) + end + end +end -- cgit v1.2.3 From cd3931876590448108ba54fa89274941ff8a97fe Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Feb 2018 01:24:01 +0900 Subject: Add object_storage queue --- app/workers/all_queues.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 328db19be29..d8fa2a0eddb 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -104,3 +104,5 @@ - update_user_activity - upload_checksum - web_hook + +- object_storage:archive_legacy_trace_worker \ No newline at end of file -- cgit v1.2.3 From c645d40a56a8291e61a597e956bb2a3659fe123a Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Feb 2018 01:24:23 +0900 Subject: Use find_in_batches for rake task --- lib/tasks/gitlab/traces.rake | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/tasks/gitlab/traces.rake b/lib/tasks/gitlab/traces.rake index f45edc922aa..04b939d23c9 100644 --- a/lib/tasks/gitlab/traces.rake +++ b/lib/tasks/gitlab/traces.rake @@ -8,10 +8,18 @@ namespace :gitlab do logger = Logger.new(STDOUT) logger.info('Archiving legacy traces') - job_ids = Ci::Build.complete.without_trace_artifact.pluck(:id) - job_ids = job_ids.map { |build_id| [build_id] } + Ci::Build.joins('RIGHT JOIN ci_job_artifacts ON ci_job_artifacts.job_id = ci_builds.id') + .finished + .where('ci_job_artifacts.file_type <> 3') + .group('ci_builds.id') + .order(id: :asc) + .find_in_batches(batch_size: 1000) do |jobs| + job_ids = jobs.map { |job| [job.id] } - ArchiveLegacyTraceWorker.bulk_perform_async(job_ids) + ArchiveLegacyTraceWorker.bulk_perform_async(job_ids) + + logger.info("Scheduled #{job_ids.count} jobs. From #{job_ids.min} #{job_ids.max}") + end end end end -- cgit v1.2.3 From 011b849719cd0afc91c5cfbe6e7f46bce4611983 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Feb 2018 03:44:13 +0900 Subject: Add soec for achevie! method. Fixed the method --- lib/gitlab/ci/trace.rb | 11 +- spec/lib/gitlab/ci/trace_spec.rb | 125 +++++++++++++++++++++ .../ci/create_trace_artifact_service_spec.rb | 63 ----------- 3 files changed, 131 insertions(+), 68 deletions(-) delete mode 100644 spec/services/ci/create_trace_artifact_service_spec.rb diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb index ef5e7fcac23..34765020287 100644 --- a/lib/gitlab/ci/trace.rb +++ b/lib/gitlab/ci/trace.rb @@ -97,12 +97,12 @@ module Gitlab return if trace_artifact if current_path - File.open(current_path) do |f| - archive_stream!(f) - f.unlink + File.open(current_path) do |stream| + archive_stream!(stream) + FileUtils.rm(current_path) end elsif old_trace - StringIO(old_trace).tap do |stream| + StringIO.new(old_trace, 'rb').tap do |stream| archive_stream!(stream) job.erase_old_trace! end @@ -121,7 +121,8 @@ module Gitlab FileUtils.mkdir_p(temp_dir) Dir.mktmpdir('tmp-trace', temp_dir) do |dir_path| temp_path = File.join(dir_path, "job.log") - size = IO.write(src_stream, temp_path) + FileUtils.touch(temp_path) + size = IO.copy_stream(src_stream, temp_path) raise 'Not all saved' unless size == src_stream.size yield(temp_path) end diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index 91c9625ba06..e52f5fc39d4 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -399,4 +399,129 @@ describe Gitlab::Ci::Trace do end end end + + describe '#archive!' do + subject { trace.archive! } + + shared_examples 'archive trace file' do + it do + expect { subject }.to change { Ci::JobArtifact.count }.by(1) + + build.reload + expect(build.trace.exist?).to be_truthy + expect(build.job_artifacts_trace.file.exists?).to be_truthy + expect(build.job_artifacts_trace.file.filename).to eq('job.log') + expect(File.exist?(src_path)).to be_falsy + expect(src_checksum) + .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).digest) + end + end + + shared_examples 'source trace file stays intact' do |error:| + it do + expect { subject }.to raise_error(error) + + build.reload + expect(build.trace.exist?).to be_truthy + expect(build.job_artifacts_trace).to be_nil + expect(File.exist?(src_path)).to be_truthy + end + end + + shared_examples 'archive trace in database' do + it do + expect { subject }.to change { Ci::JobArtifact.count }.by(1) + + build.reload + expect(build.trace.exist?).to be_truthy + expect(build.job_artifacts_trace.file.exists?).to be_truthy + expect(build.job_artifacts_trace.file.filename).to eq('job.log') + expect(build.old_trace).to be_nil + expect(src_checksum) + .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).digest) + end + end + + shared_examples 'source trace in database stays intact' do |error:| + it do + expect { subject }.to raise_error(error) + + build.reload + expect(build.trace.exist?).to be_truthy + expect(build.job_artifacts_trace).to be_nil + expect(build.old_trace).to eq(trace_content) + end + end + + context 'when job does not have trace artifact' do + context 'when trace file stored in default path' do + let!(:build) { create(:ci_build, :trace_live) } + let!(:src_path) { trace.read { |s| return s.path } } + let!(:src_checksum) { Digest::SHA256.file(src_path).digest } + + it_behaves_like 'archive trace file' + + context 'when failed to create clone file' do + before do + allow_any_instance_of(Gitlab::Ci::Trace) + .to receive(:clone_file!).and_raise('Not all saved') + end + + it_behaves_like 'source trace file stays intact', error: 'Not all saved' + end + + context 'when failed to create job artifact record' do + before do + allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false) + allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages) + .and_return(["Error", 'Error']) + end + + it_behaves_like 'source trace file stays intact', error: ActiveRecord::RecordInvalid + end + end + + context 'when trace stored in database' do + let(:trace_content) { IO.read(expand_fixture_path('trace/sample_trace')) } + let!(:src_checksum) { Digest::SHA256.digest(trace_content) } + + before do + build.update_column(:trace, trace_content) + end + + it_behaves_like 'archive trace in database' + + context 'when failed to create clone file' do + before do + allow_any_instance_of(Gitlab::Ci::Trace) + .to receive(:clone_file!).and_raise('Not all saved') + end + + it_behaves_like 'source trace in database stays intact', error: 'Not all saved' + end + + context 'when failed to create job artifact record' do + before do + allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false) + allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages) + .and_return(["Error", 'Error']) + end + + it_behaves_like 'source trace in database stays intact', error: ActiveRecord::RecordInvalid + end + end + end + + context 'when job has trace artifact' do + before do + create(:ci_job_artifact, :trace, job: build) + end + + it 'does not archive' do + expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive_stream!) + expect { subject }.not_to change { Ci::JobArtifact.count } + expect(build.job_artifacts_trace.file.exists?).to be_truthy + end + end + end end diff --git a/spec/services/ci/create_trace_artifact_service_spec.rb b/spec/services/ci/create_trace_artifact_service_spec.rb deleted file mode 100644 index 8c5e8e438c7..00000000000 --- a/spec/services/ci/create_trace_artifact_service_spec.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'spec_helper' - -describe Ci::CreateTraceArtifactService do - describe '#execute' do - subject { described_class.new(nil, nil).execute(job) } - - context 'when the job does not have trace artifact' do - context 'when the job has a trace file' do - let!(:job) { create(:ci_build, :trace_live) } - let!(:legacy_path) { job.trace.read { |stream| return stream.path } } - let!(:legacy_checksum) { Digest::SHA256.file(legacy_path).hexdigest } - let(:new_path) { job.job_artifacts_trace.file.path } - let(:new_checksum) { Digest::SHA256.file(new_path).hexdigest } - - it { expect(File.exist?(legacy_path)).to be_truthy } - - it 'creates trace artifact' do - expect { subject }.to change { Ci::JobArtifact.count }.by(1) - - expect(File.exist?(legacy_path)).to be_falsy - expect(File.exist?(new_path)).to be_truthy - expect(new_checksum).to eq(legacy_checksum) - expect(job.job_artifacts_trace.file.exists?).to be_truthy - expect(job.job_artifacts_trace.file.filename).to eq('job.log') - end - - context 'when failed to create trace artifact record' do - before do - # When ActiveRecord error happens - allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false) - allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages) - .and_return("Error") - - subject rescue nil - - job.reload - end - - it 'keeps legacy trace and removes trace artifact' do - expect(File.exist?(legacy_path)).to be_truthy - expect(job.job_artifacts_trace).to be_nil - end - end - end - - context 'when the job does not have a trace file' do - let!(:job) { create(:ci_build) } - - it 'does not create trace artifact' do - expect { subject }.not_to change { Ci::JobArtifact.count } - end - end - end - - context 'when the job has already had trace artifact' do - let!(:job) { create(:ci_build, :trace_artifact) } - - it 'does not create trace artifact' do - expect { subject }.not_to change { Ci::JobArtifact.count } - end - end - end -end -- cgit v1.2.3 From 9ca8a5d43f103fcbcb278e08d6afa15b33ad2e29 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Feb 2018 17:29:24 +0900 Subject: Add changelog --- changelogs/unreleased/proper-fix-for-artifacts-service.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/proper-fix-for-artifacts-service.yml diff --git a/changelogs/unreleased/proper-fix-for-artifacts-service.yml b/changelogs/unreleased/proper-fix-for-artifacts-service.yml new file mode 100644 index 00000000000..e92e995dbf5 --- /dev/null +++ b/changelogs/unreleased/proper-fix-for-artifacts-service.yml @@ -0,0 +1,5 @@ +--- +title: Add archive feature to trace +merge_request: 17314 +author: +type: added -- cgit v1.2.3 From 91117452e1c106352171aea56ef336dbafd322a6 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Feb 2018 18:14:41 +0900 Subject: Raise an error if conditions are not fulfilled in archive method --- lib/gitlab/ci/trace.rb | 3 ++- spec/lib/gitlab/ci/trace_spec.rb | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb index 34765020287..98481f3580a 100644 --- a/lib/gitlab/ci/trace.rb +++ b/lib/gitlab/ci/trace.rb @@ -94,7 +94,8 @@ module Gitlab end def archive! - return if trace_artifact + raise 'Already archived' if trace_artifact + raise 'Job is not finished yet' unless job.complete? if current_path File.open(current_path) do |stream| diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index e52f5fc39d4..ba7153ed04d 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -455,7 +455,7 @@ describe Gitlab::Ci::Trace do context 'when job does not have trace artifact' do context 'when trace file stored in default path' do - let!(:build) { create(:ci_build, :trace_live) } + let!(:build) { create(:ci_build, :success, :trace_live) } let!(:src_path) { trace.read { |s| return s.path } } let!(:src_checksum) { Digest::SHA256.file(src_path).digest } @@ -481,7 +481,8 @@ describe Gitlab::Ci::Trace do end end - context 'when trace stored in database' do + context 'when trace is stored in database' do + let(:build) { create(:ci_build, :success) } let(:trace_content) { IO.read(expand_fixture_path('trace/sample_trace')) } let!(:src_checksum) { Digest::SHA256.digest(trace_content) } @@ -519,9 +520,19 @@ describe Gitlab::Ci::Trace do it 'does not archive' do expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive_stream!) - expect { subject }.not_to change { Ci::JobArtifact.count } + expect { subject }.to raise_error('Already archived') expect(build.job_artifacts_trace.file.exists?).to be_truthy end end + + context 'when job is not finished yet' do + let!(:build) { create(:ci_build, :running, :trace_live) } + + it 'does not archive' do + expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive_stream!) + expect { subject }.to raise_error('Job is not finished yet') + expect(build.trace.exist?).to be_truthy + end + end end end -- cgit v1.2.3 From 824af79d64b8be277dbddad0936c2b7b8237ce5d Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Feb 2018 18:43:19 +0900 Subject: Fix rake task to use corrrect SQL --- lib/tasks/gitlab/traces.rake | 9 +++--- spec/tasks/gitlab/traces_rake_spec.rb | 55 +++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 spec/tasks/gitlab/traces_rake_spec.rb diff --git a/lib/tasks/gitlab/traces.rake b/lib/tasks/gitlab/traces.rake index 04b939d23c9..46e2f0e523c 100644 --- a/lib/tasks/gitlab/traces.rake +++ b/lib/tasks/gitlab/traces.rake @@ -8,17 +8,16 @@ namespace :gitlab do logger = Logger.new(STDOUT) logger.info('Archiving legacy traces') - Ci::Build.joins('RIGHT JOIN ci_job_artifacts ON ci_job_artifacts.job_id = ci_builds.id') - .finished - .where('ci_job_artifacts.file_type <> 3') - .group('ci_builds.id') + Ci::Build.finished + .where('NOT EXISTS (?)', + Ci::JobArtifact.select(1).trace.where('ci_builds.id = ci_job_artifacts.job_id')) .order(id: :asc) .find_in_batches(batch_size: 1000) do |jobs| job_ids = jobs.map { |job| [job.id] } ArchiveLegacyTraceWorker.bulk_perform_async(job_ids) - logger.info("Scheduled #{job_ids.count} jobs. From #{job_ids.min} #{job_ids.max}") + logger.info("Scheduled #{job_ids.count} jobs. From #{job_ids.min} to #{job_ids.max}") end end end diff --git a/spec/tasks/gitlab/traces_rake_spec.rb b/spec/tasks/gitlab/traces_rake_spec.rb new file mode 100644 index 00000000000..d2eaa88f287 --- /dev/null +++ b/spec/tasks/gitlab/traces_rake_spec.rb @@ -0,0 +1,55 @@ +require 'rake_helper' + +describe 'gitlab:traces rake tasks' do + before do + Rake.application.rake_require 'tasks/gitlab/traces' + end + + shared_examples 'passes the job id to worker' do + it do + expect(ArchiveLegacyTraceWorker).to receive(:bulk_perform_async).with([[job.id]]) + + run_rake_task('gitlab:traces:archive') + end + end + + shared_examples 'does not pass the job id to worker' do + it do + expect(ArchiveLegacyTraceWorker).not_to receive(:bulk_perform_async) + + run_rake_task('gitlab:traces:archive') + end + end + + context 'when trace file stored in default path' do + let!(:job) { create(:ci_build, :success, :trace_live) } + + it_behaves_like 'passes the job id to worker' + end + + context 'when trace is stored in database' do + let!(:job) { create(:ci_build, :success) } + + before do + job.update_column(:trace, 'trace in db') + end + + it_behaves_like 'passes the job id to worker' + end + + context 'when job has trace artifact' do + let!(:job) { create(:ci_build, :success) } + + before do + create(:ci_job_artifact, :trace, job: job) + end + + it_behaves_like 'does not pass the job id to worker' + end + + context 'when job is not finished yet' do + let!(:build) { create(:ci_build, :running, :trace_live) } + + it_behaves_like 'does not pass the job id to worker' + end +end -- cgit v1.2.3 From 583b49f08e5799508d5ebdf7e6f6ccbbe0a0d0bf Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 28 Feb 2018 18:48:08 +0900 Subject: Add spec for workers --- spec/workers/archive_legacy_trace_worker_spec.rb | 30 +++++++++++++++++++++++ spec/workers/create_trace_artifact_worker_spec.rb | 6 ++--- 2 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 spec/workers/archive_legacy_trace_worker_spec.rb diff --git a/spec/workers/archive_legacy_trace_worker_spec.rb b/spec/workers/archive_legacy_trace_worker_spec.rb new file mode 100644 index 00000000000..28b247e3058 --- /dev/null +++ b/spec/workers/archive_legacy_trace_worker_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe ArchiveLegacyTraceWorker do + it { is_expected.to be_a(ObjectStorageQueue) } + it { is_expected.not_to be_a(PipelineQueue) } + + describe '#perform' do + subject { described_class.new.perform(job&.id) } + + context 'when job is found' do + let(:job) { create(:ci_build) } + + it 'executes service' do + expect_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!) + + subject + end + end + + context 'when job is not found' do + let(:job) { nil } + + it 'does not execute service' do + expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive!) + + subject + end + end + end +end diff --git a/spec/workers/create_trace_artifact_worker_spec.rb b/spec/workers/create_trace_artifact_worker_spec.rb index 854abd9cca7..5fc2b183abc 100644 --- a/spec/workers/create_trace_artifact_worker_spec.rb +++ b/spec/workers/create_trace_artifact_worker_spec.rb @@ -8,8 +8,7 @@ describe CreateTraceArtifactWorker do let(:job) { create(:ci_build) } it 'executes service' do - expect_any_instance_of(Ci::CreateTraceArtifactService) - .to receive(:execute).with(job) + expect_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!) subject end @@ -19,8 +18,7 @@ describe CreateTraceArtifactWorker do let(:job) { nil } it 'does not execute service' do - expect_any_instance_of(Ci::CreateTraceArtifactService) - .not_to receive(:execute) + expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive!) subject end -- cgit v1.2.3 From 3a5cb44f65ac2329e1119ddf01f15bf72ac86765 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 1 Mar 2018 17:38:44 +0900 Subject: Fix static analysys --- lib/gitlab/ci/trace.rb | 1 + spec/lib/gitlab/ci/trace_spec.rb | 14 +++++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb index 98481f3580a..256cd2c5e4a 100644 --- a/lib/gitlab/ci/trace.rb +++ b/lib/gitlab/ci/trace.rb @@ -125,6 +125,7 @@ module Gitlab FileUtils.touch(temp_path) size = IO.copy_stream(src_stream, temp_path) raise 'Not all saved' unless size == src_stream.size + yield(temp_path) end end diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index ba7153ed04d..e25a904416c 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -439,7 +439,7 @@ describe Gitlab::Ci::Trace do expect(build.old_trace).to be_nil expect(src_checksum) .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).digest) - end + end end shared_examples 'source trace in database stays intact' do |error:| @@ -463,7 +463,7 @@ describe Gitlab::Ci::Trace do context 'when failed to create clone file' do before do - allow_any_instance_of(Gitlab::Ci::Trace) + allow_any_instance_of(described_class) .to receive(:clone_file!).and_raise('Not all saved') end @@ -474,7 +474,7 @@ describe Gitlab::Ci::Trace do before do allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false) allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages) - .and_return(["Error", 'Error']) + .and_return(%w[Error Error]) end it_behaves_like 'source trace file stays intact', error: ActiveRecord::RecordInvalid @@ -494,7 +494,7 @@ describe Gitlab::Ci::Trace do context 'when failed to create clone file' do before do - allow_any_instance_of(Gitlab::Ci::Trace) + allow_any_instance_of(described_class) .to receive(:clone_file!).and_raise('Not all saved') end @@ -505,7 +505,7 @@ describe Gitlab::Ci::Trace do before do allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false) allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages) - .and_return(["Error", 'Error']) + .and_return(%w[Error Error]) end it_behaves_like 'source trace in database stays intact', error: ActiveRecord::RecordInvalid @@ -519,7 +519,7 @@ describe Gitlab::Ci::Trace do end it 'does not archive' do - expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive_stream!) + expect_any_instance_of(described_class).not_to receive(:archive_stream!) expect { subject }.to raise_error('Already archived') expect(build.job_artifacts_trace.file.exists?).to be_truthy end @@ -529,7 +529,7 @@ describe Gitlab::Ci::Trace do let!(:build) { create(:ci_build, :running, :trace_live) } it 'does not archive' do - expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive_stream!) + expect_any_instance_of(described_class).not_to receive(:archive_stream!) expect { subject }.to raise_error('Job is not finished yet') expect(build.trace.exist?).to be_truthy end -- cgit v1.2.3 From fe4894ff5a9525c8551fd33570a9eaa18bb9c74d Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 1 Mar 2018 17:45:14 +0900 Subject: Fix spec failure on mysql because it can not store long traces --- spec/lib/gitlab/ci/trace_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index e25a904416c..66520f6236c 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -483,7 +483,7 @@ describe Gitlab::Ci::Trace do context 'when trace is stored in database' do let(:build) { create(:ci_build, :success) } - let(:trace_content) { IO.read(expand_fixture_path('trace/sample_trace')) } + let(:trace_content) { 'Sample trace' } let!(:src_checksum) { Digest::SHA256.digest(trace_content) } before do -- cgit v1.2.3 From d4c9c5225118eedb731045f7d387cb0c192067f4 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 1 Mar 2018 17:49:37 +0900 Subject: Add object_storage queue to sidekiq_queues.ym. and correct queue name in all_queues.yml. --- app/workers/all_queues.yml | 2 +- config/sidekiq_queues.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index d8fa2a0eddb..1d669556201 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -105,4 +105,4 @@ - upload_checksum - web_hook -- object_storage:archive_legacy_trace_worker \ No newline at end of file +- object_storage:archive_legacy_trace \ No newline at end of file diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 4845dc28a4a..841f089d9e1 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -69,3 +69,4 @@ - [storage_migrator, 1] - [pages_domain_verification, 1] - [plugin, 1] + - [object_storage, 1] -- cgit v1.2.3 From 481d0bc3db1c83fe50d6e1441d0d13aa227a757b Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 1 Mar 2018 18:01:55 +0900 Subject: Change the place of definition of object_storage:archive_legacy_trace --- app/workers/all_queues.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 1d669556201..160a6a92dcb 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -62,6 +62,8 @@ - repository_check:repository_check_clear - repository_check:repository_check_single_repository +- object_storage:archive_legacy_trace + - default - mailers # ActionMailer::DeliveryJob.queue_name @@ -104,5 +106,3 @@ - update_user_activity - upload_checksum - web_hook - -- object_storage:archive_legacy_trace \ No newline at end of file -- cgit v1.2.3 From 7bbd5f6e31036a9ab6305b14eedbad25c5501648 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 2 Mar 2018 23:30:31 +0900 Subject: Define Trace::ArchiveError to make it explit as an error --- lib/gitlab/ci/trace.rb | 8 +++++--- spec/lib/gitlab/ci/trace_spec.rb | 10 ++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb index 256cd2c5e4a..c5356af2f02 100644 --- a/lib/gitlab/ci/trace.rb +++ b/lib/gitlab/ci/trace.rb @@ -1,6 +1,8 @@ module Gitlab module Ci class Trace + ArchiveError = Class.new(StandardError) + attr_reader :job delegate :old_trace, to: :job @@ -94,8 +96,8 @@ module Gitlab end def archive! - raise 'Already archived' if trace_artifact - raise 'Job is not finished yet' unless job.complete? + raise ArchiveError, 'Already archived' if trace_artifact + raise ArchiveError, 'Job is not finished yet' unless job.complete? if current_path File.open(current_path) do |stream| @@ -124,7 +126,7 @@ module Gitlab temp_path = File.join(dir_path, "job.log") FileUtils.touch(temp_path) size = IO.copy_stream(src_stream, temp_path) - raise 'Not all saved' unless size == src_stream.size + raise ArchiveError, 'Failed to copy stream' unless size == src_stream.size yield(temp_path) end diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb index 66520f6236c..1c73043cfbd 100644 --- a/spec/lib/gitlab/ci/trace_spec.rb +++ b/spec/lib/gitlab/ci/trace_spec.rb @@ -463,11 +463,10 @@ describe Gitlab::Ci::Trace do context 'when failed to create clone file' do before do - allow_any_instance_of(described_class) - .to receive(:clone_file!).and_raise('Not all saved') + allow(IO).to receive(:copy_stream).and_return(0) end - it_behaves_like 'source trace file stays intact', error: 'Not all saved' + it_behaves_like 'source trace file stays intact', error: Gitlab::Ci::Trace::ArchiveError end context 'when failed to create job artifact record' do @@ -494,11 +493,10 @@ describe Gitlab::Ci::Trace do context 'when failed to create clone file' do before do - allow_any_instance_of(described_class) - .to receive(:clone_file!).and_raise('Not all saved') + allow(IO).to receive(:copy_stream).and_return(0) end - it_behaves_like 'source trace in database stays intact', error: 'Not all saved' + it_behaves_like 'source trace in database stays intact', error: Gitlab::Ci::Trace::ArchiveError end context 'when failed to create job artifact record' do -- cgit v1.2.3 From 335bc0fec05d282e2e4daa0e4a1bcb82ddec0594 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 6 Mar 2018 20:20:36 +0900 Subject: Integrate two workers into one ArchiveTraceWorker with pipeline_background queue. This queue takes loqer precedence than pipeline_default. --- app/workers/all_queues.yml | 4 +- app/workers/archive_legacy_trace_worker.rb | 10 ---- app/workers/archive_trace_worker.rb | 10 ++++ app/workers/build_finished_worker.rb | 2 +- app/workers/concerns/pipeline_background_queue.rb | 10 ++++ app/workers/create_trace_artifact_worker.rb | 10 ---- config/sidekiq_queues.yml | 2 +- ..._migrate_create_trace_artifact_sidekiq_queue.rb | 13 +++++ db/schema.rb | 2 +- lib/tasks/gitlab/traces.rake | 2 +- .../migrate_create_trace_artifact_sidekiq_queue.rb | 66 ++++++++++++++++++++++ .../concerns/pipeline_background_queue_spec.rb | 19 +++++++ 12 files changed, 123 insertions(+), 27 deletions(-) delete mode 100644 app/workers/archive_legacy_trace_worker.rb create mode 100644 app/workers/archive_trace_worker.rb create mode 100644 app/workers/concerns/pipeline_background_queue.rb delete mode 100644 app/workers/create_trace_artifact_worker.rb create mode 100644 db/post_migrate/20180306074045_migrate_create_trace_artifact_sidekiq_queue.rb create mode 100644 spec/migrations/migrate_create_trace_artifact_sidekiq_queue.rb create mode 100644 spec/workers/concerns/pipeline_background_queue_spec.rb diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 160a6a92dcb..9962eaccade 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -43,9 +43,9 @@ - pipeline_cache:expire_pipeline_cache - pipeline_creation:create_pipeline - pipeline_creation:run_pipeline_schedule +- pipeline_background:archive_trace - pipeline_default:build_coverage - pipeline_default:build_trace_sections -- pipeline_default:create_trace_artifact - pipeline_default:pipeline_metrics - pipeline_default:pipeline_notification - pipeline_default:update_head_pipeline_for_merge_request @@ -62,8 +62,6 @@ - repository_check:repository_check_clear - repository_check:repository_check_single_repository -- object_storage:archive_legacy_trace - - default - mailers # ActionMailer::DeliveryJob.queue_name diff --git a/app/workers/archive_legacy_trace_worker.rb b/app/workers/archive_legacy_trace_worker.rb deleted file mode 100644 index 01b6224494b..00000000000 --- a/app/workers/archive_legacy_trace_worker.rb +++ /dev/null @@ -1,10 +0,0 @@ -class ArchiveLegacyTraceWorker - include ApplicationWorker - include ObjectStorageQueue - - def perform(job_id) - Ci::Build.find_by(id: job_id).try do |job| - job.trace.archive! - end - end -end diff --git a/app/workers/archive_trace_worker.rb b/app/workers/archive_trace_worker.rb new file mode 100644 index 00000000000..dea7425ad88 --- /dev/null +++ b/app/workers/archive_trace_worker.rb @@ -0,0 +1,10 @@ +class ArchiveTraceWorker + include ApplicationWorker + include PipelineBackgroundQueue + + def perform(job_id) + Ci::Build.find_by(id: job_id).try do |job| + job.trace.archive! + end + end +end diff --git a/app/workers/build_finished_worker.rb b/app/workers/build_finished_worker.rb index b5ed8d607b3..46f1ac09915 100644 --- a/app/workers/build_finished_worker.rb +++ b/app/workers/build_finished_worker.rb @@ -12,7 +12,7 @@ class BuildFinishedWorker # We execute that async as this are two indepentent operations that can be executed after TraceSections and Coverage BuildHooksWorker.perform_async(build.id) - CreateTraceArtifactWorker.perform_async(build.id) + ArchiveTraceWorker.perform_async(build.id) end end end diff --git a/app/workers/concerns/pipeline_background_queue.rb b/app/workers/concerns/pipeline_background_queue.rb new file mode 100644 index 00000000000..8bf43de6b26 --- /dev/null +++ b/app/workers/concerns/pipeline_background_queue.rb @@ -0,0 +1,10 @@ +## +# Concern for setting Sidekiq settings for the low priority CI pipeline workers. +# +module PipelineBackgroundQueue + extend ActiveSupport::Concern + + included do + queue_namespace :pipeline_background + end +end diff --git a/app/workers/create_trace_artifact_worker.rb b/app/workers/create_trace_artifact_worker.rb deleted file mode 100644 index a0cec43157e..00000000000 --- a/app/workers/create_trace_artifact_worker.rb +++ /dev/null @@ -1,10 +0,0 @@ -class CreateTraceArtifactWorker - include ApplicationWorker - include PipelineQueue - - def perform(job_id) - Ci::Build.find_by(id: job_id).try do |job| - job.trace.archive! - end - end -end diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 841f089d9e1..554502c5d83 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -69,4 +69,4 @@ - [storage_migrator, 1] - [pages_domain_verification, 1] - [plugin, 1] - - [object_storage, 1] + - [pipeline_background, 1] diff --git a/db/post_migrate/20180306074045_migrate_create_trace_artifact_sidekiq_queue.rb b/db/post_migrate/20180306074045_migrate_create_trace_artifact_sidekiq_queue.rb new file mode 100644 index 00000000000..0af1c3bc0a5 --- /dev/null +++ b/db/post_migrate/20180306074045_migrate_create_trace_artifact_sidekiq_queue.rb @@ -0,0 +1,13 @@ +class MigrateCreateTraceArtifactSidekiqQueue < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + sidekiq_queue_migrate 'pipeline_default:create_trace_artifact', to: 'pipeline_background:archive_trace' + end + + def down + sidekiq_queue_migrate 'pipeline_background:archive_trace', to: 'pipeline_default:create_trace_artifact' + end +end diff --git a/db/schema.rb b/db/schema.rb index 0881a1af945..5bb87fa2b86 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: 20180305144721) do +ActiveRecord::Schema.define(version: 20180306074045) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/lib/tasks/gitlab/traces.rake b/lib/tasks/gitlab/traces.rake index 46e2f0e523c..fd2a4f2d11a 100644 --- a/lib/tasks/gitlab/traces.rake +++ b/lib/tasks/gitlab/traces.rake @@ -15,7 +15,7 @@ namespace :gitlab do .find_in_batches(batch_size: 1000) do |jobs| job_ids = jobs.map { |job| [job.id] } - ArchiveLegacyTraceWorker.bulk_perform_async(job_ids) + ArchiveTraceWorker.bulk_perform_async(job_ids) logger.info("Scheduled #{job_ids.count} jobs. From #{job_ids.min} to #{job_ids.max}") end diff --git a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue.rb b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue.rb new file mode 100644 index 00000000000..c18ae3b76d3 --- /dev/null +++ b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue.rb @@ -0,0 +1,66 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180306074045_migrate_create_trace_artifact_sidekiq_queue.rb') + +describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do + include Gitlab::Database::MigrationHelpers + + context 'when there are jobs in the queues' do + it 'correctly migrates queue when migrating up' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) + + described_class.new.up + + expect(sidekiq_queue_length('pipeline_default:create_trace_artifact')).to eq 0 + expect(sidekiq_queue_length('pipeline_background:archive_trace')).to eq 2 + end + end + + it 'does not affect other queues under the same namespace' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + + described_class.new.up + + expect(sidekiq_queue_length('pipeline_default:build_coverage')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:build_trace_sections')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:pipeline_metrics')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:pipeline_notification')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:update_head_pipeline_for_merge_request')).to eq 1 + end + end + + it 'correctly migrates queue when migrating down' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) + + described_class.new.down + + expect(sidekiq_queue_length('pipeline_default:create_trace_artifact')).to eq 1 + expect(sidekiq_queue_length('pipeline_background:archive_trace')).to eq 0 + end + end + end + + context 'when there are no jobs in the queues' do + it 'does not raise error when migrating up' do + expect { described_class.new.up }.not_to raise_error + end + + it 'does not raise error when migrating down' do + expect { described_class.new.down }.not_to raise_error + end + end + + def stubbed_worker(queue:) + Class.new do + include Sidekiq::Worker + sidekiq_options queue: queue + end + end +end diff --git a/spec/workers/concerns/pipeline_background_queue_spec.rb b/spec/workers/concerns/pipeline_background_queue_spec.rb new file mode 100644 index 00000000000..e1ee85ae238 --- /dev/null +++ b/spec/workers/concerns/pipeline_background_queue_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe PipelineBackgroundQueue do + let(:worker) do + Class.new do + def self.name + 'DummyWorker' + end + + include ApplicationWorker + include PipelineBackgroundQueue + end + end + + it 'sets a default object storage queue automatically' do + expect(worker.sidekiq_options['queue']) + .to eq 'object_storage:dummy' + end +end -- cgit v1.2.3 From 0ac1322045bd6d069aa74da04df7fb2d2797a3f0 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 6 Mar 2018 21:40:50 +0900 Subject: Fix tests by latest proposal changes --- .../migrate_create_trace_artifact_sidekiq_queue.rb | 66 ---------------------- ...ate_create_trace_artifact_sidekiq_queue_spec.rb | 66 ++++++++++++++++++++++ spec/requests/api/runner_spec.rb | 4 +- spec/tasks/gitlab/traces_rake_spec.rb | 4 +- spec/workers/archive_legacy_trace_worker_spec.rb | 30 ---------- spec/workers/archive_trace_worker_spec.rb | 27 +++++++++ spec/workers/build_finished_worker_spec.rb | 2 +- .../concerns/pipeline_background_queue_spec.rb | 2 +- spec/workers/create_trace_artifact_worker_spec.rb | 27 --------- 9 files changed, 99 insertions(+), 129 deletions(-) delete mode 100644 spec/migrations/migrate_create_trace_artifact_sidekiq_queue.rb create mode 100644 spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb delete mode 100644 spec/workers/archive_legacy_trace_worker_spec.rb create mode 100644 spec/workers/archive_trace_worker_spec.rb delete mode 100644 spec/workers/create_trace_artifact_worker_spec.rb diff --git a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue.rb b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue.rb deleted file mode 100644 index c18ae3b76d3..00000000000 --- a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue.rb +++ /dev/null @@ -1,66 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20180306074045_migrate_create_trace_artifact_sidekiq_queue.rb') - -describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do - include Gitlab::Database::MigrationHelpers - - context 'when there are jobs in the queues' do - it 'correctly migrates queue when migrating up' do - Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) - - described_class.new.up - - expect(sidekiq_queue_length('pipeline_default:create_trace_artifact')).to eq 0 - expect(sidekiq_queue_length('pipeline_background:archive_trace')).to eq 2 - end - end - - it 'does not affect other queues under the same namespace' do - Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1]) - stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1]) - - described_class.new.up - - expect(sidekiq_queue_length('pipeline_default:build_coverage')).to eq 1 - expect(sidekiq_queue_length('pipeline_default:build_trace_sections')).to eq 1 - expect(sidekiq_queue_length('pipeline_default:pipeline_metrics')).to eq 1 - expect(sidekiq_queue_length('pipeline_default:pipeline_notification')).to eq 1 - expect(sidekiq_queue_length('pipeline_default:update_head_pipeline_for_merge_request')).to eq 1 - end - end - - it 'correctly migrates queue when migrating down' do - Sidekiq::Testing.disable! do - stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) - - described_class.new.down - - expect(sidekiq_queue_length('pipeline_default:create_trace_artifact')).to eq 1 - expect(sidekiq_queue_length('pipeline_background:archive_trace')).to eq 0 - end - end - end - - context 'when there are no jobs in the queues' do - it 'does not raise error when migrating up' do - expect { described_class.new.up }.not_to raise_error - end - - it 'does not raise error when migrating down' do - expect { described_class.new.down }.not_to raise_error - end - end - - def stubbed_worker(queue:) - Class.new do - include Sidekiq::Worker - sidekiq_options queue: queue - end - end -end diff --git a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb new file mode 100644 index 00000000000..c18ae3b76d3 --- /dev/null +++ b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb @@ -0,0 +1,66 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180306074045_migrate_create_trace_artifact_sidekiq_queue.rb') + +describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do + include Gitlab::Database::MigrationHelpers + + context 'when there are jobs in the queues' do + it 'correctly migrates queue when migrating up' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) + + described_class.new.up + + expect(sidekiq_queue_length('pipeline_default:create_trace_artifact')).to eq 0 + expect(sidekiq_queue_length('pipeline_background:archive_trace')).to eq 2 + end + end + + it 'does not affect other queues under the same namespace' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1]) + stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1]) + + described_class.new.up + + expect(sidekiq_queue_length('pipeline_default:build_coverage')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:build_trace_sections')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:pipeline_metrics')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:pipeline_notification')).to eq 1 + expect(sidekiq_queue_length('pipeline_default:update_head_pipeline_for_merge_request')).to eq 1 + end + end + + it 'correctly migrates queue when migrating down' do + Sidekiq::Testing.disable! do + stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1]) + + described_class.new.down + + expect(sidekiq_queue_length('pipeline_default:create_trace_artifact')).to eq 1 + expect(sidekiq_queue_length('pipeline_background:archive_trace')).to eq 0 + end + end + end + + context 'when there are no jobs in the queues' do + it 'does not raise error when migrating up' do + expect { described_class.new.up }.not_to raise_error + end + + it 'does not raise error when migrating down' do + expect { described_class.new.down }.not_to raise_error + end + end + + def stubbed_worker(queue:) + Class.new do + include Sidekiq::Worker + sidekiq_options queue: queue + end + end +end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index ce1311ac97c..95c23726a79 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -698,10 +698,10 @@ describe API::Runner do end end - context 'when tace is given' do + context 'when trace is given' do it 'creates a trace artifact' do allow(BuildFinishedWorker).to receive(:perform_async).with(job.id) do - CreateTraceArtifactWorker.new.perform(job.id) + ArchiveTraceWorker.new.perform(job.id) end update_job(state: 'success', trace: 'BUILD TRACE UPDATED') diff --git a/spec/tasks/gitlab/traces_rake_spec.rb b/spec/tasks/gitlab/traces_rake_spec.rb index d2eaa88f287..bd18e8ffc1e 100644 --- a/spec/tasks/gitlab/traces_rake_spec.rb +++ b/spec/tasks/gitlab/traces_rake_spec.rb @@ -7,7 +7,7 @@ describe 'gitlab:traces rake tasks' do shared_examples 'passes the job id to worker' do it do - expect(ArchiveLegacyTraceWorker).to receive(:bulk_perform_async).with([[job.id]]) + expect(ArchiveTraceWorker).to receive(:bulk_perform_async).with([[job.id]]) run_rake_task('gitlab:traces:archive') end @@ -15,7 +15,7 @@ describe 'gitlab:traces rake tasks' do shared_examples 'does not pass the job id to worker' do it do - expect(ArchiveLegacyTraceWorker).not_to receive(:bulk_perform_async) + expect(ArchiveTraceWorker).not_to receive(:bulk_perform_async) run_rake_task('gitlab:traces:archive') end diff --git a/spec/workers/archive_legacy_trace_worker_spec.rb b/spec/workers/archive_legacy_trace_worker_spec.rb deleted file mode 100644 index 28b247e3058..00000000000 --- a/spec/workers/archive_legacy_trace_worker_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'spec_helper' - -describe ArchiveLegacyTraceWorker do - it { is_expected.to be_a(ObjectStorageQueue) } - it { is_expected.not_to be_a(PipelineQueue) } - - describe '#perform' do - subject { described_class.new.perform(job&.id) } - - context 'when job is found' do - let(:job) { create(:ci_build) } - - it 'executes service' do - expect_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!) - - subject - end - end - - context 'when job is not found' do - let(:job) { nil } - - it 'does not execute service' do - expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive!) - - subject - end - end - end -end diff --git a/spec/workers/archive_trace_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb new file mode 100644 index 00000000000..b768588c6e1 --- /dev/null +++ b/spec/workers/archive_trace_worker_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe ArchiveTraceWorker do + describe '#perform' do + subject { described_class.new.perform(job&.id) } + + context 'when job is found' do + let(:job) { create(:ci_build) } + + it 'executes service' do + expect_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!) + + subject + end + end + + context 'when job is not found' do + let(:job) { nil } + + it 'does not execute service' do + expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive!) + + subject + end + end + end +end diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb index c7ff8cf3b92..acd8da11d8d 100644 --- a/spec/workers/build_finished_worker_spec.rb +++ b/spec/workers/build_finished_worker_spec.rb @@ -14,7 +14,7 @@ describe BuildFinishedWorker do expect_any_instance_of(BuildTraceSectionsWorker).to receive(:perform) expect_any_instance_of(BuildCoverageWorker).to receive(:perform) expect(BuildHooksWorker).to receive(:perform_async) - expect(CreateTraceArtifactWorker).to receive(:perform_async) + expect(ArchiveTraceWorker).to receive(:perform_async) described_class.new.perform(build.id) end diff --git a/spec/workers/concerns/pipeline_background_queue_spec.rb b/spec/workers/concerns/pipeline_background_queue_spec.rb index e1ee85ae238..24c0a3c6a20 100644 --- a/spec/workers/concerns/pipeline_background_queue_spec.rb +++ b/spec/workers/concerns/pipeline_background_queue_spec.rb @@ -14,6 +14,6 @@ describe PipelineBackgroundQueue do it 'sets a default object storage queue automatically' do expect(worker.sidekiq_options['queue']) - .to eq 'object_storage:dummy' + .to eq 'pipeline_background:dummy' end end diff --git a/spec/workers/create_trace_artifact_worker_spec.rb b/spec/workers/create_trace_artifact_worker_spec.rb deleted file mode 100644 index 5fc2b183abc..00000000000 --- a/spec/workers/create_trace_artifact_worker_spec.rb +++ /dev/null @@ -1,27 +0,0 @@ -require 'spec_helper' - -describe CreateTraceArtifactWorker do - describe '#perform' do - subject { described_class.new.perform(job&.id) } - - context 'when job is found' do - let(:job) { create(:ci_build) } - - it 'executes service' do - expect_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!) - - subject - end - end - - context 'when job is not found' do - let(:job) { nil } - - it 'does not execute service' do - expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive!) - - subject - end - end - end -end -- cgit v1.2.3 From b5ec6097b75819626083726d1073978d11aba7e3 Mon Sep 17 00:00:00 2001 From: Andrew Newdigate Date: Tue, 6 Mar 2018 13:32:48 +0000 Subject: Fix Gitaly n+1 in NetworkController#show --- app/controllers/projects/network_controller.rb | 25 ++++++++++------------ app/models/network/commit.rb | 7 +----- .../unreleased/an-network-controller-fix.yml | 5 +++++ 3 files changed, 17 insertions(+), 20 deletions(-) create mode 100644 changelogs/unreleased/an-network-controller-fix.yml diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb index 3b10a93e97f..35fec229db7 100644 --- a/app/controllers/projects/network_controller.rb +++ b/app/controllers/projects/network_controller.rb @@ -9,25 +9,22 @@ class Projects::NetworkController < Projects::ApplicationController before_action :assign_commit def show - # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/37602 - Gitlab::GitalyClient.allow_n_plus_1_calls do - @url = project_network_path(@project, @ref, @options.merge(format: :json)) - @commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s") - - respond_to do |format| - format.html do - if @options[:extended_sha1] && !@commit - flash.now[:alert] = "Git revision '#{@options[:extended_sha1]}' does not exist." - end - end + @url = project_network_path(@project, @ref, @options.merge(format: :json)) + @commit_url = project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s") - format.json do - @graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref]) + respond_to do |format| + format.html do + if @options[:extended_sha1] && !@commit + flash.now[:alert] = "Git revision '#{@options[:extended_sha1]}' does not exist." end end - render + format.json do + @graph = Network::Graph.new(project, @ref, @commit, @options[:filter_ref]) + end end + + render end def assign_commit diff --git a/app/models/network/commit.rb b/app/models/network/commit.rb index 9357e55b419..22d48c9e661 100644 --- a/app/models/network/commit.rb +++ b/app/models/network/commit.rb @@ -24,12 +24,7 @@ module Network end def parents(map) - @commit.parents.map do |p| - if map.include?(p.id) - map[p.id] - end - end - .compact + map.values_at(*@commit.parent_ids).compact end end end diff --git a/changelogs/unreleased/an-network-controller-fix.yml b/changelogs/unreleased/an-network-controller-fix.yml new file mode 100644 index 00000000000..cb2c447b957 --- /dev/null +++ b/changelogs/unreleased/an-network-controller-fix.yml @@ -0,0 +1,5 @@ +--- +title: Prevent the graphs page from generating unnecessary Gitaly requests +merge_request: 37602 +author: +type: performance -- cgit v1.2.3 From 1e08d00032c1160202567dacdf24c3b47458d86c Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Mon, 5 Mar 2018 10:37:16 +0100 Subject: Lazy fetch parent commits for a commit --- app/models/commit.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index b9106309142..cceae5efb72 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -9,6 +9,7 @@ class Commit include Mentionable include Referable include StaticModel + include ::Gitlab::Utils::StrongMemoize attr_mentionable :safe_message, pipeline: :single_line @@ -225,11 +226,13 @@ class Commit end def parents - @parents ||= parent_ids.map { |id| project.commit(id) } + @parents ||= parent_ids.map { |oid| Commit.lazy(project, oid) } end def parent - @parent ||= project.commit(self.parent_id) if self.parent_id + strong_memoize(:parent) do + project.commit_by(oid: self.parent_id) if self.parent_id + end end def notes -- cgit v1.2.3 From ce18ba7a0a3a098760ea6836c530f898f04704d5 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Tue, 6 Mar 2018 22:35:58 +0900 Subject: Remove extra breadcrumb on tags --- app/views/projects/tags/index.html.haml | 1 - changelogs/unreleased/43924-breadcrumbs-on-project-tags.yml | 5 +++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/43924-breadcrumbs-on-project-tags.yml diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index da364b58e36..10415d011d6 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -1,7 +1,6 @@ - @no_container = true - @sort ||= sort_value_recently_updated - page_title s_('TagsPage|Tags') -- add_to_breadcrumbs("Repository", project_tree_path(@project)) .flex-list{ class: container_class } .top-area.adjust diff --git a/changelogs/unreleased/43924-breadcrumbs-on-project-tags.yml b/changelogs/unreleased/43924-breadcrumbs-on-project-tags.yml new file mode 100644 index 00000000000..67c223b31c5 --- /dev/null +++ b/changelogs/unreleased/43924-breadcrumbs-on-project-tags.yml @@ -0,0 +1,5 @@ +--- +title: Remove extra breadcrumb on tags +merge_request: 17562 +author: Takuya Noguchi +type: fixed -- cgit v1.2.3