From f2a4420d66216e3a9172f4ab45c6b4fa96578117 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 16 Apr 2017 13:14:39 +0200 Subject: [PATCH 01/38] Store retried in database for CI builds --- app/models/ci/build.rb | 4 +-- app/models/commit_status.rb | 11 ++---- app/services/ci/process_pipeline_service.rb | 20 +++++++++++ app/services/ci/retry_build_service.rb | 13 +++++-- app/services/ci/retry_pipeline_service.rb | 2 +- ...tore-retried-in-database-for-ci-builds.yml | 4 +++ .../20170503004426_add_retried_to_ci_build.rb | 15 ++++++++ ...170503004427_upate_retried_for_ci_build.rb | 29 ++++++++++++++++ db/schema.rb | 1 + .../import_export/safe_model_attributes.yml | 1 + spec/models/ci/pipeline_spec.rb | 34 +++++++++++-------- spec/models/ci/stage_spec.rb | 4 +++ spec/models/commit_status_spec.rb | 24 ++++++++++--- spec/requests/api/commit_statuses_spec.rb | 4 +-- .../ci/process_pipeline_service_spec.rb | 15 ++++++++ spec/services/ci/retry_build_service_spec.rb | 11 ++++-- .../ci/retry_pipeline_service_spec.rb | 2 +- .../pipelines/_stage.html.haml_spec.rb | 5 ++- 18 files changed, 158 insertions(+), 41 deletions(-) create mode 100644 changelogs/unreleased/store-retried-in-database-for-ci-builds.yml create mode 100644 db/migrate/20170503004426_add_retried_to_ci_build.rb create mode 100644 db/post_migrate/20170503004427_upate_retried_for_ci_build.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 971ab7cb0ee..3c4a4d93349 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -124,8 +124,8 @@ module Ci success? || failed? || canceled? end - def retried? - !self.pipeline.statuses.latest.include?(self) + def latest? + !retried? end def expanded_environment_name diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 2c4033146bf..528bf4d87ac 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -18,13 +18,7 @@ class CommitStatus < ActiveRecord::Base validates :name, presence: true alias_attribute :author, :user - - scope :latest, -> do - max_id = unscope(:select).select("max(#{quoted_table_name}.id)") - - where(id: max_id.group(:name, :commit_id)) - end - + scope :failed_but_allowed, -> do where(allow_failure: true, status: [:failed, :canceled]) end @@ -37,7 +31,8 @@ class CommitStatus < ActiveRecord::Base false, all_state_names - [:failed, :canceled, :manual]) end - scope :retried, -> { where.not(id: latest) } + scope :latest, -> { where(retried: false) } + scope :retried, -> { where(retried: true) } scope :ordered, -> { order(:name) } scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) } scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) } diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 33edcd60944..647836de384 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -5,6 +5,8 @@ module Ci def execute(pipeline) @pipeline = pipeline + update_retried + new_builds = stage_indexes_of_created_builds.map do |index| process_stage(index) @@ -71,5 +73,23 @@ module Ci def created_builds pipeline.builds.created end + + # This method is for compatibility and data consistency and should be removed with 9.3 version of GitLab + # This replicates what is db/post_migrate/20170416103934_upate_retried_for_ci_build.rb + # and ensures that functionality will not be broken before migration is run + # this updates only when there are data that needs to be updated, there are two groups with no retried flag + def update_retried + # find the latest builds for each name + latest_statuses = pipeline.statuses.latest + .group(:name) + .having('count(*) > 1') + .pluck('max(id)', 'name') + + # mark builds that are retried + pipeline.statuses.latest + .where(name: latest_statuses.map(&:second)) + .where.not(id: latest_statuses.map(&:first)) + .update_all(retried: true) if latest_statuses.any? + end end end diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb index 89da05b72bb..9c532b585a1 100644 --- a/app/services/ci/retry_build_service.rb +++ b/app/services/ci/retry_build_service.rb @@ -6,7 +6,7 @@ module Ci description tag_list].freeze def execute(build) - reprocess(build).tap do |new_build| + reprocess!(build).tap do |new_build| build.pipeline.mark_as_processable_after_stage(build.stage_idx) new_build.enqueue! @@ -17,7 +17,7 @@ module Ci end end - def reprocess(build) + def reprocess!(build) unless can?(current_user, :update_build, build) raise Gitlab::Access::AccessDeniedError end @@ -28,7 +28,14 @@ module Ci attributes.push([:user, current_user]) - project.builds.create(Hash[attributes]) + Ci::Build.transaction do + # mark all other builds of that name as retried + build.pipeline.builds.where(name: build.name) + .where.not(retried: true) + .update_all(retried: true) + + project.builds.create!(Hash[attributes]) + end end end end diff --git a/app/services/ci/retry_pipeline_service.rb b/app/services/ci/retry_pipeline_service.rb index 5b207157345..c5a43869990 100644 --- a/app/services/ci/retry_pipeline_service.rb +++ b/app/services/ci/retry_pipeline_service.rb @@ -11,7 +11,7 @@ module Ci next unless can?(current_user, :update_build, build) Ci::RetryBuildService.new(project, current_user) - .reprocess(build) + .reprocess!(build) end pipeline.builds.latest.skipped.find_each do |skipped| diff --git a/changelogs/unreleased/store-retried-in-database-for-ci-builds.yml b/changelogs/unreleased/store-retried-in-database-for-ci-builds.yml new file mode 100644 index 00000000000..9185113f51c --- /dev/null +++ b/changelogs/unreleased/store-retried-in-database-for-ci-builds.yml @@ -0,0 +1,4 @@ +--- +title: Store retried in database for CI Builds +merge_request: +author: diff --git a/db/migrate/20170503004426_add_retried_to_ci_build.rb b/db/migrate/20170503004426_add_retried_to_ci_build.rb new file mode 100644 index 00000000000..9f509f97f14 --- /dev/null +++ b/db/migrate/20170503004426_add_retried_to_ci_build.rb @@ -0,0 +1,15 @@ +class AddRetriedToCiBuild < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default(:ci_builds, :retried, :boolean, default: false) + end + + def down + remove_column(:ci_builds, :retried) + end +end diff --git a/db/post_migrate/20170503004427_upate_retried_for_ci_build.rb b/db/post_migrate/20170503004427_upate_retried_for_ci_build.rb new file mode 100644 index 00000000000..e12180930a7 --- /dev/null +++ b/db/post_migrate/20170503004427_upate_retried_for_ci_build.rb @@ -0,0 +1,29 @@ +class UpateRetriedForCiBuild < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + disable_statement_timeout + + latest_id = <<-SQL.strip_heredoc + SELECT MAX(ci_builds2.id) + FROM ci_builds ci_builds2 + WHERE ci_builds.commit_id=ci_builds2.commit_id + AND ci_builds.name=ci_builds2.name + SQL + + # This is slow update as it does single-row query + # This is designed to be run as idle, or a post deployment migration + is_retried = Arel.sql("((#{latest_id}) != ci_builds.id)") + + update_column_in_batches(:ci_builds, :retried, is_retried) do |table, query| + query.where(table[:retried].eq(false)) + end + end + + def down + end +end diff --git a/db/schema.rb b/db/schema.rb index 61af066ed3b..8046e9b48a7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -232,6 +232,7 @@ ActiveRecord::Schema.define(version: 20170504102911) do t.integer "lock_version" t.string "coverage_regex" t.integer "auto_canceled_by_id" + t.boolean "retried", default: false, null: false end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index a66086f8b47..56b9729a870 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -229,6 +229,7 @@ CommitStatus: - lock_version - coverage_regex - auto_canceled_by_id +- retried Ci::Variable: - id - project_id diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 3b222ea1c3d..d79f1e22f32 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -59,8 +59,8 @@ describe Ci::Pipeline, models: true do subject { pipeline.retried } before do - @build1 = FactoryGirl.create :ci_build, pipeline: pipeline, name: 'deploy' - @build2 = FactoryGirl.create :ci_build, pipeline: pipeline, name: 'deploy' + @build1 = create(:ci_build, pipeline: pipeline, name: 'deploy', retried: true) + @build2 = create(:ci_build, pipeline: pipeline, name: 'deploy') end it 'returns old builds' do @@ -69,31 +69,31 @@ describe Ci::Pipeline, models: true do end describe "coverage" do - let(:project) { FactoryGirl.create :empty_project, build_coverage_regex: "/.*/" } - let(:pipeline) { FactoryGirl.create :ci_empty_pipeline, project: project } + let(:project) { create(:empty_project, build_coverage_regex: "/.*/") } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } it "calculates average when there are two builds with coverage" do - FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline - FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline + create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline) + create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline) expect(pipeline.coverage).to eq("35.00") end it "calculates average when there are two builds with coverage and one with nil" do - FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline - FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline - FactoryGirl.create :ci_build, pipeline: pipeline + create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline) + create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline) + create(:ci_build, pipeline: pipeline) expect(pipeline.coverage).to eq("35.00") end it "calculates average when there are two builds with coverage and one is retried" do - FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline - FactoryGirl.create :ci_build, name: "rubocop", coverage: 30, pipeline: pipeline - FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline + create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline) + create(:ci_build, name: "rubocop", coverage: 30, pipeline: pipeline, retried: true) + create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline) expect(pipeline.coverage).to eq("35.00") end it "calculates average when there is one build without coverage" do - FactoryGirl.create :ci_build, pipeline: pipeline + FactoryGirl.create(:ci_build, pipeline: pipeline) expect(pipeline.coverage).to be_nil end end @@ -221,13 +221,15 @@ describe Ci::Pipeline, models: true do %w(deploy running)]) end - context 'when commit status is retried' do + context 'when commit status is retried' do before do create(:commit_status, pipeline: pipeline, stage: 'build', name: 'mac', stage_idx: 0, status: 'success') + + pipeline.process! end it 'ignores the previous state' do @@ -488,6 +490,10 @@ describe Ci::Pipeline, models: true do context 'there are multiple of the same name' do let!(:manual2) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') } + before do + manual.update(retried: true) + end + it 'returns latest one' do is_expected.to contain_exactly(manual2) end diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 372b662fab2..8f6ab908987 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -102,6 +102,10 @@ describe Ci::Stage, models: true do context 'and builds are retried' do let!(:new_build) { create_job(:ci_build, status: :success) } + before do + stage_build.update(retried: true) + end + it "returns status of latest build" do is_expected.to eq('success') end diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 0ee85489574..6947affcc1e 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -157,9 +157,9 @@ describe CommitStatus, :models do subject { described_class.latest.order(:id) } let(:statuses) do - [create_status(name: 'aa', ref: 'bb', status: 'running'), - create_status(name: 'cc', ref: 'cc', status: 'pending'), - create_status(name: 'aa', ref: 'cc', status: 'success'), + [create_status(name: 'aa', ref: 'bb', status: 'running', retried: true), + create_status(name: 'cc', ref: 'cc', status: 'pending', retried: true), + create_status(name: 'aa', ref: 'cc', status: 'success', retried: true), create_status(name: 'cc', ref: 'bb', status: 'success'), create_status(name: 'aa', ref: 'bb', status: 'success')] end @@ -169,6 +169,22 @@ describe CommitStatus, :models do end end + describe '.retried' do + subject { described_class.retried.order(:id) } + + let(:statuses) do + [create_status(name: 'aa', ref: 'bb', status: 'running', retried: true), + create_status(name: 'cc', ref: 'cc', status: 'pending', retried: true), + create_status(name: 'aa', ref: 'cc', status: 'success', retried: true), + create_status(name: 'cc', ref: 'bb', status: 'success'), + create_status(name: 'aa', ref: 'bb', status: 'success')] + end + + it 'returns unique statuses' do + is_expected.to contain_exactly(*statuses.values_at(0, 1, 2)) + end + end + describe '.running_or_pending' do subject { described_class.running_or_pending.order(:id) } @@ -181,7 +197,7 @@ describe CommitStatus, :models do end it 'returns statuses that are running or pending' do - is_expected.to eq(statuses.values_at(0, 1)) + is_expected.to contain_exactly(*statuses.values_at(0, 1)) end end diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 1233cdc64c4..1c163cee152 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -26,8 +26,8 @@ describe API::CommitStatuses do create(:commit_status, { pipeline: commit, ref: commit.ref }.merge(opts)) end - let!(:status1) { create_status(master, status: 'running') } - let!(:status2) { create_status(master, name: 'coverage', status: 'pending') } + let!(:status1) { create_status(master, status: 'running', retried: true) } + let!(:status2) { create_status(master, name: 'coverage', status: 'pending', retried: true) } let!(:status3) { create_status(develop, status: 'running', allow_failure: true) } let!(:status4) { create_status(master, name: 'coverage', status: 'success') } let!(:status5) { create_status(develop, name: 'coverage', status: 'success') } diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb index cf773866a6f..f1e1e1b6067 100644 --- a/spec/services/ci/process_pipeline_service_spec.rb +++ b/spec/services/ci/process_pipeline_service_spec.rb @@ -425,6 +425,21 @@ describe Ci::ProcessPipelineService, '#execute', :services do end end + context 'updates a list of retried builds' do + subject { described_class.retried.order(:id) } + + let!(:build_retried) { create_build('build') } + let!(:build) { create_build('build') } + let!(:test) { create_build('test') } + + it 'returns unique statuses' do + process_pipeline + + expect(all_builds.latest).to contain_exactly(build, test) + expect(all_builds.retried).to contain_exactly(build_retried) + end + end + def process_pipeline described_class.new(pipeline.project, user).execute(pipeline) end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index b2d37657770..7254e6b357a 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -22,7 +22,7 @@ describe Ci::RetryBuildService, :services do %i[type lock_version target_url base_tags commit_id deployments erased_by_id last_deployment project_id runner_id tag_taggings taggings tags trigger_request_id - user_id auto_canceled_by_id].freeze + user_id auto_canceled_by_id retried].freeze shared_examples 'build duplication' do let(:build) do @@ -115,7 +115,7 @@ describe Ci::RetryBuildService, :services do end describe '#reprocess' do - let(:new_build) { service.reprocess(build) } + let(:new_build) { service.reprocess!(build) } context 'when user has ability to execute build' do before do @@ -131,11 +131,16 @@ describe Ci::RetryBuildService, :services do it 'does not enqueue the new build' do expect(new_build).to be_created end + + it 'does mark old build as retried' do + expect(new_build).to be_latest + expect(build.reload).to be_retried + end end context 'when user does not have ability to execute build' do it 'raises an error' do - expect { service.reprocess(build) } + expect { service.reprocess!(build) } .to raise_error Gitlab::Access::AccessDeniedError end end diff --git a/spec/services/ci/retry_pipeline_service_spec.rb b/spec/services/ci/retry_pipeline_service_spec.rb index 40e151545c9..d941d56c0d8 100644 --- a/spec/services/ci/retry_pipeline_service_spec.rb +++ b/spec/services/ci/retry_pipeline_service_spec.rb @@ -13,7 +13,7 @@ describe Ci::RetryPipelineService, '#execute', :services do context 'when there are already retried jobs present' do before do - create_build('rspec', :canceled, 0) + create_build('rspec', :canceled, 0, retried: true) create_build('rspec', :failed, 0) end diff --git a/spec/views/projects/pipelines/_stage.html.haml_spec.rb b/spec/views/projects/pipelines/_stage.html.haml_spec.rb index 10095ad7694..9c91c4e0fbd 100644 --- a/spec/views/projects/pipelines/_stage.html.haml_spec.rb +++ b/spec/views/projects/pipelines/_stage.html.haml_spec.rb @@ -39,9 +39,8 @@ describe 'projects/pipelines/_stage', :view do context 'when there are retried builds present' do before do - create_list(:ci_build, 2, name: 'test:build', - stage: stage.name, - pipeline: pipeline) + create(:ci_build, name: 'test:build', stage: stage.name, pipeline: pipeline, retried: true) + create(:ci_build, name: 'test:build', stage: stage.name, pipeline: pipeline) end it 'shows only latest builds' do From cb38a85046094dc6e4dda3b0e1ffb7994584b93e Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Mon, 8 May 2017 10:11:06 +0000 Subject: [PATCH 02/38] Center related merge request items horizontally --- app/assets/stylesheets/pages/issues.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index ad3b6e0344b..0b347d80d3c 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -51,6 +51,7 @@ ul.related-merge-requests > li { display: -ms-flexbox; display: -webkit-flex; display: flex; + align-items: center; .merge-request-id { flex-shrink: 0; From ffb05ea534ca2c5b5e7b395aa2f73f3510488582 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Tue, 9 May 2017 13:13:30 +0100 Subject: [PATCH 03/38] Remove related issue ci icon top margin --- app/assets/stylesheets/pages/issues.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 0b347d80d3c..93785f887f0 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -115,7 +115,6 @@ ul.related-merge-requests > li { .related-merge-requests { .ci-status-link { display: block; - margin-top: 3px; margin-right: 5px; } From 1e0df21a74017deb780023ed83f04dbbeed13899 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 9 May 2017 16:59:20 +0200 Subject: [PATCH 04/38] Hide GCP install guide from index page until our image is added --- doc/install/README.md | 2 -- doc/install/google_cloud_platform/index.md | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/install/README.md b/doc/install/README.md index 58cc7d312fd..eabea002af9 100644 --- a/doc/install/README.md +++ b/doc/install/README.md @@ -18,8 +18,6 @@ the hardware requirements. Useful for unsupported systems like *BSD. For an overview of the directory structure, read the [structure documentation](structure.md). - [Docker](https://docs.gitlab.com/omnibus/docker/) - Install GitLab using Docker. -- [Installation on Google Cloud Platform](google_cloud_platform/index.md) - Install - GitLab on Google Cloud Platform using our official image. - Testing only! [DigitalOcean and Docker Machine](digitaloceandocker.md) - Quickly test any version of GitLab on DigitalOcean using Docker Machine. diff --git a/doc/install/google_cloud_platform/index.md b/doc/install/google_cloud_platform/index.md index 26506111548..35220119e9b 100644 --- a/doc/install/google_cloud_platform/index.md +++ b/doc/install/google_cloud_platform/index.md @@ -2,6 +2,10 @@ ![GCP landing page](img/gcp_landing.png) +>**Important note:** +GitLab has no official images in Google Cloud Platform yet. This guide serves +as a template for when the GitLab VM will be available. + The fastest way to get started on [Google Cloud Platform (GCP)][gcp] is through the [Google Cloud Launcher][launcher] program. From f09dcbd9bd2590bb66ee49b65637525a3c71a307 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 9 May 2017 13:14:45 +0200 Subject: [PATCH 05/38] Make retried to be nullable --- app/models/commit_status.rb | 2 +- app/models/concerns/has_status.rb | 2 +- app/services/ci/retry_build_service.rb | 4 ++-- db/migrate/20170503004426_add_retried_to_ci_build.rb | 10 ++-------- .../20170503004427_upate_retried_for_ci_build.rb | 2 +- db/schema.rb | 2 +- 6 files changed, 8 insertions(+), 14 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 528bf4d87ac..7855c16ee5b 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -31,7 +31,7 @@ class CommitStatus < ActiveRecord::Base false, all_state_names - [:failed, :canceled, :manual]) end - scope :latest, -> { where(retried: false) } + scope :latest, -> { where(retried: [false, nil]) } scope :retried, -> { where(retried: true) } scope :ordered, -> { order(:name) } scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) } diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index dff7b6e3523..3e4f8e6bff8 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -22,7 +22,7 @@ module HasStatus skipped = scope.skipped.select('count(*)').to_sql canceled = scope.canceled.select('count(*)').to_sql - "(CASE + "(CASE WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{success}) THEN 'success' WHEN (#{builds})=(#{created}) THEN 'created' diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb index 9c532b585a1..f51e9fd1d54 100644 --- a/app/services/ci/retry_build_service.rb +++ b/app/services/ci/retry_build_service.rb @@ -30,8 +30,8 @@ module Ci Ci::Build.transaction do # mark all other builds of that name as retried - build.pipeline.builds.where(name: build.name) - .where.not(retried: true) + build.pipeline.builds.latest + .where(name: build.name) .update_all(retried: true) project.builds.create!(Hash[attributes]) diff --git a/db/migrate/20170503004426_add_retried_to_ci_build.rb b/db/migrate/20170503004426_add_retried_to_ci_build.rb index 9f509f97f14..2851e3de473 100644 --- a/db/migrate/20170503004426_add_retried_to_ci_build.rb +++ b/db/migrate/20170503004426_add_retried_to_ci_build.rb @@ -3,13 +3,7 @@ class AddRetriedToCiBuild < ActiveRecord::Migration DOWNTIME = false - disable_ddl_transaction! - - def up - add_column_with_default(:ci_builds, :retried, :boolean, default: false) - end - - def down - remove_column(:ci_builds, :retried) + def change + add_column(:ci_builds, :retried, :boolean) end end diff --git a/db/post_migrate/20170503004427_upate_retried_for_ci_build.rb b/db/post_migrate/20170503004427_upate_retried_for_ci_build.rb index e12180930a7..80215d662e4 100644 --- a/db/post_migrate/20170503004427_upate_retried_for_ci_build.rb +++ b/db/post_migrate/20170503004427_upate_retried_for_ci_build.rb @@ -20,7 +20,7 @@ class UpateRetriedForCiBuild < ActiveRecord::Migration is_retried = Arel.sql("((#{latest_id}) != ci_builds.id)") update_column_in_batches(:ci_builds, :retried, is_retried) do |table, query| - query.where(table[:retried].eq(false)) + query.where(table[:retried].eq(nil)) end end diff --git a/db/schema.rb b/db/schema.rb index 8046e9b48a7..a54a57bd4da 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -232,7 +232,7 @@ ActiveRecord::Schema.define(version: 20170504102911) do t.integer "lock_version" t.string "coverage_regex" t.integer "auto_canceled_by_id" - t.boolean "retried", default: false, null: false + t.boolean "retried" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree From 7eaab72b983a89040a01b9f32184fef258fb29f2 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 9 May 2017 23:03:50 +0200 Subject: [PATCH 06/38] Fix rubocop failure --- app/models/concerns/has_status.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb index 3e4f8e6bff8..dff7b6e3523 100644 --- a/app/models/concerns/has_status.rb +++ b/app/models/concerns/has_status.rb @@ -22,7 +22,7 @@ module HasStatus skipped = scope.skipped.select('count(*)').to_sql canceled = scope.canceled.select('count(*)').to_sql - "(CASE + "(CASE WHEN (#{builds})=(#{skipped}) THEN 'skipped' WHEN (#{builds})=(#{success}) THEN 'success' WHEN (#{builds})=(#{created}) THEN 'created' From cc37d1dfa11691f2d8357151d4b722b3d114881e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 8 May 2017 18:52:55 -0500 Subject: [PATCH 07/38] Remove unused toggle from diff discussion diff headers --- app/views/discussions/_diff_with_notes.html.haml | 2 +- app/views/projects/diffs/_file_header.html.haml | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/views/discussions/_diff_with_notes.html.haml b/app/views/discussions/_diff_with_notes.html.haml index 78c5b0c1dda..c3f55ff821f 100644 --- a/app/views/discussions/_diff_with_notes.html.haml +++ b/app/views/discussions/_diff_with_notes.html.haml @@ -3,7 +3,7 @@ .diff-file.file-holder .js-file-title.file-title - = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: discussion.project, url: discussion_path(discussion) + = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_file.content_commit, project: discussion.project, url: discussion_path(discussion), show_toggle: false .diff-content.code.js-syntax-highlight %table diff --git a/app/views/projects/diffs/_file_header.html.haml b/app/views/projects/diffs/_file_header.html.haml index 7d6b3701f95..4e4fdb73ae3 100644 --- a/app/views/projects/diffs/_file_header.html.haml +++ b/app/views/projects/diffs/_file_header.html.haml @@ -1,4 +1,8 @@ -%i.fa.diff-toggle-caret.fa-fw +- show_toggle = local_assigns.fetch(:show_toggle, true) + +- if show_toggle + %i.fa.diff-toggle-caret.fa-fw + - if defined?(blob) && blob && diff_file.submodule? %span = icon('archive fw') From 18ae0742bcd4481379386db72ef142cfed796da1 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Wed, 10 May 2017 13:37:31 +0100 Subject: [PATCH 08/38] Added @annabeldunstone vertical-align fix --- app/assets/stylesheets/pages/issues.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 93785f887f0..bee9b13b375 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -60,6 +60,14 @@ ul.related-merge-requests > li { .merge-request-info { margin-left: 5px; } + + .row_title { + vertical-align: bottom; + } + + gl-emoji { + font-size: 1em; + } } .merge-requests-title, From 1af540122563c372c03668f36e6a2123bff94222 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Tue, 2 May 2017 13:59:18 +0200 Subject: [PATCH 09/38] Add post-deploy migrate to cleanup projects in pending delete state There are many projects in `pending_delete` state, this post-deploy migration cleans them up. The script is based on https://gitlab.com/gitlab-org/gitlab-ce/snippets/1648654 and https://gitlab.com/gitlab-org/gitlab-ce/snippets/1611429. The use of these scripts were described in https://gitlab.com/gitlab-com/infrastructure/issues/888. --- .../tc-clean-pending-delete-projects.yml | 4 ++ ...101023_clean_up_pending_delete_projects.rb | 47 ++++++++++++ .../clean_up_pending_delete_projects_spec.rb | 72 +++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 changelogs/unreleased/tc-clean-pending-delete-projects.yml create mode 100644 db/post_migrate/20170502101023_clean_up_pending_delete_projects.rb create mode 100644 spec/migrations/clean_up_pending_delete_projects_spec.rb diff --git a/changelogs/unreleased/tc-clean-pending-delete-projects.yml b/changelogs/unreleased/tc-clean-pending-delete-projects.yml new file mode 100644 index 00000000000..31b43999c31 --- /dev/null +++ b/changelogs/unreleased/tc-clean-pending-delete-projects.yml @@ -0,0 +1,4 @@ +--- +title: Add post-deploy migration to clean up projects in `pending_delete` state +merge_request: 11044 +author: diff --git a/db/post_migrate/20170502101023_clean_up_pending_delete_projects.rb b/db/post_migrate/20170502101023_clean_up_pending_delete_projects.rb new file mode 100644 index 00000000000..52c7c160ea2 --- /dev/null +++ b/db/post_migrate/20170502101023_clean_up_pending_delete_projects.rb @@ -0,0 +1,47 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CleanUpPendingDeleteProjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + admin = User.find_by(admin: true) + return unless admin + + Project.unscoped.where(pending_delete: true).each { |project| delete_project(project, admin) } + end + + def down + # noop + end + + private + + def delete_project(project, user) + project.team.truncate + + unlink_fork(project) if project.forked? + + [:events, :issues, :merge_requests, :labels, :milestones, :notes, :snippets].each do |thing| + project.send(thing).delete_all + end + + # Override Project#remove_pages for this instance so it doesn't do anything + def project.remove_pages + end + + project.destroy! + end + + def unlink_fork(project) + merge_requests = project.forked_from_project.merge_requests.opened.from_project(project) + + merge_requests.update_all(state: 'closed') + + project.forked_project_link.destroy + end +end diff --git a/spec/migrations/clean_up_pending_delete_projects_spec.rb b/spec/migrations/clean_up_pending_delete_projects_spec.rb new file mode 100644 index 00000000000..a7c13f91245 --- /dev/null +++ b/spec/migrations/clean_up_pending_delete_projects_spec.rb @@ -0,0 +1,72 @@ +# encoding: utf-8 + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170502101023_clean_up_pending_delete_projects.rb') + +describe CleanUpPendingDeleteProjects do + let(:migration) { described_class.new } + let!(:admin) { create(:admin) } + let!(:project) { create(:empty_project, pending_delete: true) } + + describe '#up' do + it 'only cleans up pending delete projects' do + create(:empty_project) + + expect do + migration.up + end.to change { Project.unscoped.count }.by(-1) + end + + it "truncates the project's team" do + project.add_master(admin) + + expect_any_instance_of(ProjectTeam).to receive(:truncate) + + migration.up + end + + it 'calls Project#destroy!' do + expect_any_instance_of(Project).to receive(:destroy!) + + migration.up + end + + it 'does not do anything in Project#remove_pages method' do + expect(Gitlab::PagesTransfer).not_to receive(:new) + + migration.up + end + + context 'project not a fork of another project' do + it "doesn't call unlink_fork" do + expect(migration).not_to receive(:unlink_fork) + + migration.up + end + end + + context 'project forked from another' do + let!(:parent_project) { create(:empty_project) } + + before do + create(:forked_project_link, forked_to_project: project, forked_from_project: parent_project) + end + + it 'closes open merge requests' do + project.update_attribute(:pending_delete, false) # needed to create the MR + merge_request = create(:merge_request, source_project: project, target_project: parent_project) + project.update_attribute(:pending_delete, true) + + migration.up + + expect(merge_request.reload).to be_closed + end + + it 'destroys the link' do + migration.up + + expect(parent_project.forked_project_links).to be_empty + end + end + end +end From 4f446dd45a4068a77f606c02c95389c5cf6a6647 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Wed, 3 May 2017 16:48:20 +0200 Subject: [PATCH 10/38] No user is needed to delete a project --- .../20170502101023_clean_up_pending_delete_projects.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/db/post_migrate/20170502101023_clean_up_pending_delete_projects.rb b/db/post_migrate/20170502101023_clean_up_pending_delete_projects.rb index 52c7c160ea2..dd812964902 100644 --- a/db/post_migrate/20170502101023_clean_up_pending_delete_projects.rb +++ b/db/post_migrate/20170502101023_clean_up_pending_delete_projects.rb @@ -9,9 +9,6 @@ class CleanUpPendingDeleteProjects < ActiveRecord::Migration disable_ddl_transaction! def up - admin = User.find_by(admin: true) - return unless admin - Project.unscoped.where(pending_delete: true).each { |project| delete_project(project, admin) } end @@ -21,7 +18,7 @@ class CleanUpPendingDeleteProjects < ActiveRecord::Migration private - def delete_project(project, user) + def delete_project(project) project.team.truncate unlink_fork(project) if project.forked? From 0ad80cab40097658b1eec5b87d440cfcd60d2755 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Mon, 8 May 2017 17:13:02 +0200 Subject: [PATCH 11/38] Use worker to destroy namespaceless projects in post-deploy Destroying projects can be very time consuming. So instead of destroying them in the post-deploy, just schedule them and make Sidekiq do the hard work. They are scheduled in batches of 5000 records. This way the number of database requests is limited while also the amount data read to memory is limited. --- .../namespaceless_project_destroy_worker.rb | 46 +++++++++++ config/sidekiq_queues.yml | 1 + ...101023_clean_up_pending_delete_projects.rb | 44 ---------- ...p_namespaceless_pending_delete_projects.rb | 50 ++++++++++++ .../clean_up_pending_delete_projects_spec.rb | 72 ----------------- ...espaceless_pending_delete_projects_spec.rb | 24 ++++++ ...mespaceless_project_destroy_worker_spec.rb | 81 +++++++++++++++++++ 7 files changed, 202 insertions(+), 116 deletions(-) create mode 100644 app/workers/namespaceless_project_destroy_worker.rb delete mode 100644 db/post_migrate/20170502101023_clean_up_pending_delete_projects.rb create mode 100644 db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb delete mode 100644 spec/migrations/clean_up_pending_delete_projects_spec.rb create mode 100644 spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb create mode 100644 spec/workers/namespaceless_project_destroy_worker_spec.rb diff --git a/app/workers/namespaceless_project_destroy_worker.rb b/app/workers/namespaceless_project_destroy_worker.rb new file mode 100644 index 00000000000..da02084c688 --- /dev/null +++ b/app/workers/namespaceless_project_destroy_worker.rb @@ -0,0 +1,46 @@ +# Worker to destroy projects that do not have a namespace +# +# It destroys everything it can without having the info about the namespace it +# used to belong to. Projects in this state should be rare. +# The worker will reject doing anything for projects that *do* have a +# namespace. For those use ProjectDestroyWorker instead. +class NamespacelessProjectDestroyWorker + include Sidekiq::Worker + include DedicatedSidekiqQueue + + def self.bulk_perform_async(args_list) + Sidekiq::Client.push_bulk('class' => self, 'queue' => sidekiq_options['queue'], 'args' => args_list) + end + + def perform(project_id, user_id, params) + begin + project = Project.unscoped.find(project_id) + rescue ActiveRecord::RecordNotFound + return + end + return unless project.namespace_id.nil? # Reject doing anything for projects that *do* have a namespace + + user = User.find(user_id) + return unless user.can?(:remove_project, project) + + project.team.truncate + + unlink_fork(project) if project.forked? + + # Override Project#remove_pages for this instance so it doesn't do anything + def project.remove_pages + end + + project.destroy! + end + + private + + def unlink_fork(project) + merge_requests = project.forked_from_project.merge_requests.opened.from_project(project) + + merge_requests.update_all(state: 'closed') + + project.forked_project_link.destroy + end +end diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index 433381e79d3..0ca1f565185 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -40,6 +40,7 @@ - [expire_build_instance_artifacts, 1] - [group_destroy, 1] - [irker, 1] + - [namespaceless_project_destroy, 1] - [project_cache, 1] - [project_destroy, 1] - [project_export, 1] diff --git a/db/post_migrate/20170502101023_clean_up_pending_delete_projects.rb b/db/post_migrate/20170502101023_clean_up_pending_delete_projects.rb deleted file mode 100644 index dd812964902..00000000000 --- a/db/post_migrate/20170502101023_clean_up_pending_delete_projects.rb +++ /dev/null @@ -1,44 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class CleanUpPendingDeleteProjects < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - disable_ddl_transaction! - - def up - Project.unscoped.where(pending_delete: true).each { |project| delete_project(project, admin) } - end - - def down - # noop - end - - private - - def delete_project(project) - project.team.truncate - - unlink_fork(project) if project.forked? - - [:events, :issues, :merge_requests, :labels, :milestones, :notes, :snippets].each do |thing| - project.send(thing).delete_all - end - - # Override Project#remove_pages for this instance so it doesn't do anything - def project.remove_pages - end - - project.destroy! - end - - def unlink_fork(project) - merge_requests = project.forked_from_project.merge_requests.opened.from_project(project) - - merge_requests.update_all(state: 'closed') - - project.forked_project_link.destroy - end -end diff --git a/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb b/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb new file mode 100644 index 00000000000..76ed109898b --- /dev/null +++ b/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb @@ -0,0 +1,50 @@ +# This is the counterpart of RequeuePendingDeleteProjects and cleans all +# projects with `pending_delete = true` and that do not have a namespace. +class CleanupNamespacelessPendingDeleteProjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + admin = User.find_by(admin: true) + return unless admin + + @offset = 0 + + loop do + ids = pending_delete_batch + + break if ids.rows.count.zero? + + args = ids.map { |id| [id['id'], admin.id, {}] } + + NamespacelessProjectDestroyWorker.bulk_perform_async(args) + + @offset += 1 + end + end + + def down + # noop + end + + private + + def pending_delete_batch + connection.exec_query(find_batch) + end + + BATCH_SIZE = 5000 + + def find_batch + projects = Arel::Table.new(:projects) + projects.project(projects[:id]). + where(projects[:pending_delete].eq(true)). + where(projects[:namespace_id].eq(nil)). + skip(@offset * BATCH_SIZE). + take(BATCH_SIZE). + to_sql + end +end diff --git a/spec/migrations/clean_up_pending_delete_projects_spec.rb b/spec/migrations/clean_up_pending_delete_projects_spec.rb deleted file mode 100644 index a7c13f91245..00000000000 --- a/spec/migrations/clean_up_pending_delete_projects_spec.rb +++ /dev/null @@ -1,72 +0,0 @@ -# encoding: utf-8 - -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170502101023_clean_up_pending_delete_projects.rb') - -describe CleanUpPendingDeleteProjects do - let(:migration) { described_class.new } - let!(:admin) { create(:admin) } - let!(:project) { create(:empty_project, pending_delete: true) } - - describe '#up' do - it 'only cleans up pending delete projects' do - create(:empty_project) - - expect do - migration.up - end.to change { Project.unscoped.count }.by(-1) - end - - it "truncates the project's team" do - project.add_master(admin) - - expect_any_instance_of(ProjectTeam).to receive(:truncate) - - migration.up - end - - it 'calls Project#destroy!' do - expect_any_instance_of(Project).to receive(:destroy!) - - migration.up - end - - it 'does not do anything in Project#remove_pages method' do - expect(Gitlab::PagesTransfer).not_to receive(:new) - - migration.up - end - - context 'project not a fork of another project' do - it "doesn't call unlink_fork" do - expect(migration).not_to receive(:unlink_fork) - - migration.up - end - end - - context 'project forked from another' do - let!(:parent_project) { create(:empty_project) } - - before do - create(:forked_project_link, forked_to_project: project, forked_from_project: parent_project) - end - - it 'closes open merge requests' do - project.update_attribute(:pending_delete, false) # needed to create the MR - merge_request = create(:merge_request, source_project: project, target_project: parent_project) - project.update_attribute(:pending_delete, true) - - migration.up - - expect(merge_request.reload).to be_closed - end - - it 'destroys the link' do - migration.up - - expect(parent_project.forked_project_links).to be_empty - end - end - end -end diff --git a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb new file mode 100644 index 00000000000..cee12d521dc --- /dev/null +++ b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20170502101023_cleanup_namespaceless_pending_delete_projects.rb') + +describe CleanupNamespacelessPendingDeleteProjects do + before do + # Stub after_save callbacks that will fail when Project has no namespace + allow_any_instance_of(Project).to receive(:ensure_dir_exist).and_return(nil) + allow_any_instance_of(Project).to receive(:update_project_statistics).and_return(nil) + end + + describe '#up' do + it 'only cleans up pending delete projects' do + admin = create(:admin) + create(:empty_project) + create(:empty_project, pending_delete: true) + project = build(:empty_project, pending_delete: true, namespace_id: nil) + project.save(validate: false) + + expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id.to_s, admin.id, {}]]) + + described_class.new.up + end + end +end diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb new file mode 100644 index 00000000000..ed78236ebc0 --- /dev/null +++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb @@ -0,0 +1,81 @@ +require 'spec_helper' + +describe NamespacelessProjectDestroyWorker do + subject { described_class.new } + + before do + # Stub after_save callbacks that will fail when Project has no namespace + allow_any_instance_of(Project).to receive(:ensure_dir_exist).and_return(nil) + allow_any_instance_of(Project).to receive(:update_project_statistics).and_return(nil) + end + + describe '#perform' do + context 'project has namespace' do + it 'does not do anything' do + project = create(:empty_project) + + subject.perform(project.id, project.owner.id, {}) + + expect(Project.unscoped.all).to include(project) + end + end + + context 'project has no namespace' do + let(:admin) { create(:admin) } + + let!(:project) do + project = build(:empty_project, namespace_id: nil) + project.save(validate: false) + project + end + + context 'project not a fork of another project' do + it "truncates the project's team" do + expect_any_instance_of(ProjectTeam).to receive(:truncate) + + subject.perform(project.id, admin.id, {}) + end + + it 'deletes the project' do + subject.perform(project.id, admin.id, {}) + + expect(Project.unscoped.all).not_to include(project) + end + + it 'does not call unlink_fork' do + is_expected.not_to receive(:unlink_fork) + + subject.perform(project.id, admin.id, {}) + end + + it 'does not do anything in Project#remove_pages method' do + expect(Gitlab::PagesTransfer).not_to receive(:new) + + subject.perform(project.id, admin.id, {}) + end + end + + context 'project forked from another' do + let!(:parent_project) { create(:empty_project) } + + before do + create(:forked_project_link, forked_to_project: project, forked_from_project: parent_project) + end + + it 'closes open merge requests' do + merge_request = create(:merge_request, source_project: project, target_project: parent_project) + + subject.perform(project.id, admin.id, {}) + + expect(merge_request.reload).to be_closed + end + + it 'destroys the link' do + subject.perform(project.id, admin.id, {}) + + expect(parent_project.forked_project_links).to be_empty + end + end + end + end +end From 37a79409d446955eeb443ea5397d5bf263601a2d Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Tue, 9 May 2017 22:16:52 +0200 Subject: [PATCH 12/38] No user needed to cleanup namespaceless pending delete projects Since this is a cleanup, ran by a post-deploy, there is no need to lookup the admin to run the cleanup. --- .../namespaceless_project_destroy_worker.rb | 5 +---- ...anup_namespaceless_pending_delete_projects.rb | 9 +++------ ...namespaceless_pending_delete_projects_spec.rb | 12 ++++++++++-- .../namespaceless_project_destroy_worker_spec.rb | 16 +++++++--------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/workers/namespaceless_project_destroy_worker.rb b/app/workers/namespaceless_project_destroy_worker.rb index da02084c688..bfae0c77700 100644 --- a/app/workers/namespaceless_project_destroy_worker.rb +++ b/app/workers/namespaceless_project_destroy_worker.rb @@ -12,7 +12,7 @@ class NamespacelessProjectDestroyWorker Sidekiq::Client.push_bulk('class' => self, 'queue' => sidekiq_options['queue'], 'args' => args_list) end - def perform(project_id, user_id, params) + def perform(project_id) begin project = Project.unscoped.find(project_id) rescue ActiveRecord::RecordNotFound @@ -20,9 +20,6 @@ class NamespacelessProjectDestroyWorker end return unless project.namespace_id.nil? # Reject doing anything for projects that *do* have a namespace - user = User.find(user_id) - return unless user.can?(:remove_project, project) - project.team.truncate unlink_fork(project) if project.forked? diff --git a/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb b/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb index 76ed109898b..2d242da9ef8 100644 --- a/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb +++ b/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb @@ -8,17 +8,14 @@ class CleanupNamespacelessPendingDeleteProjects < ActiveRecord::Migration disable_ddl_transaction! def up - admin = User.find_by(admin: true) - return unless admin - @offset = 0 loop do ids = pending_delete_batch - break if ids.rows.count.zero? + break if ids.empty? - args = ids.map { |id| [id['id'], admin.id, {}] } + args = ids.map { |id| Array(id) } NamespacelessProjectDestroyWorker.bulk_perform_async(args) @@ -33,7 +30,7 @@ class CleanupNamespacelessPendingDeleteProjects < ActiveRecord::Migration private def pending_delete_batch - connection.exec_query(find_batch) + connection.exec_query(find_batch).map{ |row| row['id'] } end BATCH_SIZE = 5000 diff --git a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb index cee12d521dc..0b8af5010ba 100644 --- a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb +++ b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb @@ -10,13 +10,21 @@ describe CleanupNamespacelessPendingDeleteProjects do describe '#up' do it 'only cleans up pending delete projects' do - admin = create(:admin) create(:empty_project) create(:empty_project, pending_delete: true) project = build(:empty_project, pending_delete: true, namespace_id: nil) project.save(validate: false) - expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id.to_s, admin.id, {}]]) + expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id.to_s]]) + + described_class.new.up + end + + it 'does nothing when no pending delete projects without namespace found' do + create(:empty_project) + create(:empty_project, pending_delete: true) + + expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async) described_class.new.up end diff --git a/spec/workers/namespaceless_project_destroy_worker_spec.rb b/spec/workers/namespaceless_project_destroy_worker_spec.rb index ed78236ebc0..8533b7b85e9 100644 --- a/spec/workers/namespaceless_project_destroy_worker_spec.rb +++ b/spec/workers/namespaceless_project_destroy_worker_spec.rb @@ -14,15 +14,13 @@ describe NamespacelessProjectDestroyWorker do it 'does not do anything' do project = create(:empty_project) - subject.perform(project.id, project.owner.id, {}) + subject.perform(project.id) expect(Project.unscoped.all).to include(project) end end context 'project has no namespace' do - let(:admin) { create(:admin) } - let!(:project) do project = build(:empty_project, namespace_id: nil) project.save(validate: false) @@ -33,11 +31,11 @@ describe NamespacelessProjectDestroyWorker do it "truncates the project's team" do expect_any_instance_of(ProjectTeam).to receive(:truncate) - subject.perform(project.id, admin.id, {}) + subject.perform(project.id) end it 'deletes the project' do - subject.perform(project.id, admin.id, {}) + subject.perform(project.id) expect(Project.unscoped.all).not_to include(project) end @@ -45,13 +43,13 @@ describe NamespacelessProjectDestroyWorker do it 'does not call unlink_fork' do is_expected.not_to receive(:unlink_fork) - subject.perform(project.id, admin.id, {}) + subject.perform(project.id) end it 'does not do anything in Project#remove_pages method' do expect(Gitlab::PagesTransfer).not_to receive(:new) - subject.perform(project.id, admin.id, {}) + subject.perform(project.id) end end @@ -65,13 +63,13 @@ describe NamespacelessProjectDestroyWorker do it 'closes open merge requests' do merge_request = create(:merge_request, source_project: project, target_project: parent_project) - subject.perform(project.id, admin.id, {}) + subject.perform(project.id) expect(merge_request.reload).to be_closed end it 'destroys the link' do - subject.perform(project.id, admin.id, {}) + subject.perform(project.id) expect(parent_project.forked_project_links).to be_empty end From 00fc9f260706cfd6d6f4261eea24eb3ff455b3e0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 8 May 2017 18:52:05 -0500 Subject: [PATCH 13/38] Load tree readme asynchronously --- app/assets/javascripts/blob/viewer/index.js | 5 ++++- app/assets/javascripts/dispatcher.js | 2 ++ app/models/readme_blob.rb | 13 +++++++++++++ app/models/repository.rb | 2 +- app/models/tree.rb | 5 +---- app/views/projects/_readme.html.haml | 6 +++--- app/views/projects/blob/_viewer.html.haml | 4 ++-- app/views/projects/tree/_readme.html.haml | 6 +++--- changelogs/unreleased/dm-async-tree-readme.yml | 4 ++++ features/project/project.feature | 1 + features/project/source/markdown_render.feature | 3 +++ features/steps/project/project.rb | 2 ++ features/steps/project/source/markdown_render.rb | 1 + spec/models/repository_spec.rb | 2 +- spec/views/projects/tree/show.html.haml_spec.rb | 2 +- 15 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 app/models/readme_blob.rb create mode 100644 changelogs/unreleased/dm-async-tree-readme.yml diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index 07d67d49aa5..69ff2f95799 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -8,7 +8,10 @@ export default class BlobViewer { this.richViewer = document.querySelector('.blob-viewer[data-type="rich"]'); this.$fileHolder = $('.file-holder'); - let initialViewerName = document.querySelector('.blob-viewer:not(.hidden)').getAttribute('data-type'); + const initialViewer = document.querySelector('.blob-viewer:not(.hidden)'); + if (!initialViewer) return; + + let initialViewerName = initialViewer.getAttribute('data-type'); this.initBindings(); diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index abb871c3af0..43ad127a4db 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -246,6 +246,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); new NotificationsForm(); if ($('#tree-slider').length) { new TreeView(); + new BlobViewer(); } break; case 'projects:pipelines:builds': @@ -300,6 +301,7 @@ const ShortcutsBlob = require('./shortcuts_blob'); case 'projects:tree:show': shortcut_handler = new ShortcutsNavigation(); new TreeView(); + new BlobViewer(); gl.TargetBranchDropDown.bootstrap(); break; case 'projects:find_file:show': diff --git a/app/models/readme_blob.rb b/app/models/readme_blob.rb new file mode 100644 index 00000000000..1863a08f1de --- /dev/null +++ b/app/models/readme_blob.rb @@ -0,0 +1,13 @@ +class ReadmeBlob < SimpleDelegator + attr_reader :repository + + def initialize(blob, repository) + @repository = repository + + super(blob) + end + + def rendered_markup + repository.rendered_readme + end +end diff --git a/app/models/repository.rb b/app/models/repository.rb index 0c797dd5814..0c36a9e8762 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -518,7 +518,7 @@ class Repository def readme if head = tree(:head) - head.readme + ReadmeBlob.new(head.readme, self) end end diff --git a/app/models/tree.rb b/app/models/tree.rb index fe148b0ec65..c89b8eca9be 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -40,10 +40,7 @@ class Tree readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name) - git_repo = repository.raw_repository - @readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path) - @readme.load_all_data!(git_repo) - @readme + @readme = repository.blob_at(sha, readme_path) end def trees diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml index c0d12cbc66e..cf09d9db6b7 100644 --- a/app/views/projects/_readme.html.haml +++ b/app/views/projects/_readme.html.haml @@ -2,9 +2,9 @@ %article.readme-holder .pull-right - if can?(current_user, :push_code, @project) - = link_to icon('pencil'), namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light edit-project-readme' - .file-content.wiki - = markup(readme.name, readme.data, rendered: @repository.rendered_readme) + = link_to icon('pencil'), namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.path)), class: 'light edit-project-readme' + + = render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.path), viewer: :rich, format: :json) - else .row-content-block.second-block.center %h3.page-title diff --git a/app/views/projects/blob/_viewer.html.haml b/app/views/projects/blob/_viewer.html.haml index 5326bb3e0cf..41187d5ce66 100644 --- a/app/views/projects/blob/_viewer.html.haml +++ b/app/views/projects/blob/_viewer.html.haml @@ -2,8 +2,8 @@ - render_error = viewer.render_error - load_asynchronously = local_assigns.fetch(:load_asynchronously, viewer.server_side?) && render_error.nil? -- url = url_for(params.merge(viewer: viewer.type, format: :json)) if load_asynchronously -.blob-viewer{ data: { type: viewer.type, url: url }, class: ('hidden' if hidden) } +- viewer_url = local_assigns.fetch(:viewer_url) { url_for(params.merge(viewer: viewer.type, format: :json)) } if load_asynchronously +.blob-viewer{ data: { type: viewer.type, url: viewer_url }, class: ('hidden' if hidden) } - if load_asynchronously .text-center.prepend-top-default.append-bottom-default = icon('spinner spin 2x', 'aria-hidden' => 'true', 'aria-label' => 'Loading content') diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml index 01599060844..2c2f64283f5 100644 --- a/app/views/projects/tree/_readme.html.haml +++ b/app/views/projects/tree/_readme.html.haml @@ -1,8 +1,8 @@ %article.file-holder.readme-holder .js-file-title.file-title = blob_icon readme.mode, readme.name - = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, @path, readme.name)) do + = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path)) do %strong = readme.name - .file-content.wiki - = markup(readme.name, readme.data) + + = render 'projects/blob/viewer', viewer: readme.rich_viewer, viewer_url: namespace_project_blob_path(@project.namespace, @project, tree_join(@ref, readme.path), viewer: :rich, format: :json) diff --git a/changelogs/unreleased/dm-async-tree-readme.yml b/changelogs/unreleased/dm-async-tree-readme.yml new file mode 100644 index 00000000000..fb1cfeb210a --- /dev/null +++ b/changelogs/unreleased/dm-async-tree-readme.yml @@ -0,0 +1,4 @@ +--- +title: Load tree readme asynchronously +merge_request: +author: diff --git a/features/project/project.feature b/features/project/project.feature index aa22401c88e..23817ef3ac9 100644 --- a/features/project/project.feature +++ b/features/project/project.feature @@ -18,6 +18,7 @@ Feature: Project Then I should see the default project avatar And I should not see the "Remove avatar" button + @javascript Scenario: I should have readme on page And I visit project "Shop" page Then I should see project "Shop" README diff --git a/features/project/source/markdown_render.feature b/features/project/source/markdown_render.feature index fd583618dcf..fe4466ad241 100644 --- a/features/project/source/markdown_render.feature +++ b/features/project/source/markdown_render.feature @@ -19,12 +19,14 @@ Feature: Project Source Markdown Render And I click on Gitlab API in README Then I should see correct document rendered + @javascript Scenario: I view README in markdown branch Then I should see files from repository in markdown And I should see rendered README which contains correct links And I click on Rake tasks in README Then I should see correct directory rendered + @javascript Scenario: I view README in markdown branch to see reference links to directory Then I should see files from repository in markdown And I should see rendered README which contains correct links @@ -74,6 +76,7 @@ Feature: Project Source Markdown Render And I click on Gitlab API in README Then I should see correct document rendered for markdown branch + @javascript Scenario: I browse directory from markdown branch When I visit markdown branch Then I should see files from repository in markdown branch diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 280d70925f7..9c2196a8ef7 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -2,6 +2,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths + include WaitForAjax step 'change project settings' do fill_in 'project_name_edit', with: 'NewName' @@ -86,6 +87,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps end step 'I should see project "Shop" README' do + wait_for_ajax page.within('.readme-holder') do expect(page).to have_content 'testme' end diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb index abdbd795cd5..ada0ff20585 100644 --- a/features/steps/project/source/markdown_render.rb +++ b/features/steps/project/source/markdown_render.rb @@ -120,6 +120,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps When 'I visit markdown branch' do visit namespace_project_tree_path(@project.namespace, @project, "markdown") + wait_for_ajax end When 'I visit markdown branch "README.md" blob' do diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index dd6514b3b50..cea8db7a926 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1634,7 +1634,7 @@ describe Repository, models: true do context 'with an existing repository' do it 'returns the README' do - expect(repository.readme).to be_an_instance_of(Gitlab::Git::Blob) + expect(repository.readme).to be_an_instance_of(ReadmeBlob) end end end diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb index 900f8d4732f..835a93e620e 100644 --- a/spec/views/projects/tree/show.html.haml_spec.rb +++ b/spec/views/projects/tree/show.html.haml_spec.rb @@ -31,7 +31,7 @@ describe 'projects/tree/show' do it 'displays correctly' do render expect(rendered).to have_css('.js-project-refs-dropdown .dropdown-toggle-text', text: ref) - expect(rendered).to have_css('.readme-holder .file-content', text: ref) + expect(rendered).to have_css('.readme-holder') end end end From 99996b6bc7c13e7e7f871919942907b380d4b58c Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 2 May 2017 14:52:19 +0000 Subject: [PATCH 14/38] Merge branch 'bvl-security-9-1-markup-pipeline' (security-9-1) Render asciidoc & other markup using banzai in a pipeline See merge request !2098 --- app/helpers/markup_helper.rb | 12 ++++++------ changelogs/unreleased/bvl-markup-pipeline.yml | 4 ++++ lib/banzai/pipeline/markup_pipeline.rb | 13 +++++++++++++ lib/gitlab/asciidoc.rb | 8 ++++---- lib/gitlab/other_markup.rb | 6 +++--- spec/lib/gitlab/asciidoc_spec.rb | 14 +++++++++++--- spec/lib/gitlab/other_markup_spec.rb | 2 +- 7 files changed, 42 insertions(+), 17 deletions(-) create mode 100644 changelogs/unreleased/bvl-markup-pipeline.yml create mode 100644 lib/banzai/pipeline/markup_pipeline.rb diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index b241a14740b..b636233c426 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -116,13 +116,13 @@ module MarkupHelper if gitlab_markdown?(file_name) markdown_unsafe(text, context) elsif asciidoc?(file_name) - asciidoc_unsafe(text) + asciidoc_unsafe(text, context) elsif plain?(file_name) content_tag :pre, class: 'plain-readme' do text end else - other_markup_unsafe(file_name, text) + other_markup_unsafe(file_name, text, context) end rescue RuntimeError simple_format(text) @@ -217,12 +217,12 @@ module MarkupHelper Banzai.render(text, context) end - def asciidoc_unsafe(text) - Gitlab::Asciidoc.render(text) + def asciidoc_unsafe(text, context = {}) + Gitlab::Asciidoc.render(text, context) end - def other_markup_unsafe(file_name, text) - Gitlab::OtherMarkup.render(file_name, text) + def other_markup_unsafe(file_name, text, context = {}) + Gitlab::OtherMarkup.render(file_name, text, context) end def prepare_for_rendering(html, context = {}) diff --git a/changelogs/unreleased/bvl-markup-pipeline.yml b/changelogs/unreleased/bvl-markup-pipeline.yml new file mode 100644 index 00000000000..d73bad03340 --- /dev/null +++ b/changelogs/unreleased/bvl-markup-pipeline.yml @@ -0,0 +1,4 @@ +--- +title: Make Asciidoc & other markup go through pipeline to prevent XSS +merge_request: +author: diff --git a/lib/banzai/pipeline/markup_pipeline.rb b/lib/banzai/pipeline/markup_pipeline.rb new file mode 100644 index 00000000000..c56d908009f --- /dev/null +++ b/lib/banzai/pipeline/markup_pipeline.rb @@ -0,0 +1,13 @@ +module Banzai + module Pipeline + class MarkupPipeline < BasePipeline + def self.filters + @filters ||= FilterArray[ + Filter::SanitizationFilter, + Filter::ExternalLinkFilter, + Filter::PlantumlFilter + ] + end + end + end +end diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb index fba80c7132e..96d38f6daa0 100644 --- a/lib/gitlab/asciidoc.rb +++ b/lib/gitlab/asciidoc.rb @@ -15,17 +15,17 @@ module Gitlab # # input - the source text in Asciidoc format # - def self.render(input) + def self.render(input, context) asciidoc_opts = { safe: :secure, backend: :gitlab_html5, attributes: DEFAULT_ADOC_ATTRS } + context[:pipeline] = :markup + plantuml_setup html = ::Asciidoctor.convert(input, asciidoc_opts) - - filter = Banzai::Filter::SanitizationFilter.new(html) - html = filter.call.to_s + html = Banzai.render(html, context) html.html_safe end diff --git a/lib/gitlab/other_markup.rb b/lib/gitlab/other_markup.rb index c2adc9aa10b..31a24460f0f 100644 --- a/lib/gitlab/other_markup.rb +++ b/lib/gitlab/other_markup.rb @@ -5,12 +5,12 @@ module Gitlab # # input - the source text in a markup format # - def self.render(file_name, input) + def self.render(file_name, input, context) html = GitHub::Markup.render(file_name, input). force_encoding(input.encoding) + context[:pipeline] = :markup - filter = Banzai::Filter::SanitizationFilter.new(html) - html = filter.call.to_s + html = Banzai.render(html, context) html.html_safe end diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index 0f47fb2fbd9..f284dd14cec 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -22,7 +22,7 @@ module Gitlab expect(Asciidoctor).to receive(:convert) .with(input, expected_asciidoc_opts).and_return(html) - expect(render(input)).to eq(html) + expect(render(input, context)).to eq(html) end context "XSS" do @@ -33,7 +33,7 @@ module Gitlab }, 'images' => { input: 'image:https://localhost.com/image.png[Alt text" onerror="alert(7)]', - output: "
\n

\"Alt

\n
" + output: "\"Alt" }, 'pre' => { input: '```mypre">', @@ -43,10 +43,18 @@ module Gitlab links.each do |name, data| it "does not convert dangerous #{name} into HTML" do - expect(render(data[:input])).to eq(data[:output]) + expect(render(data[:input], context)).to include(data[:output]) end end end + + context 'external links' do + it 'adds the `rel` attribute to the link' do + output = render('link:https://google.com[Google]', context) + + expect(output).to include('rel="nofollow noreferrer"') + end + end end def render(*args) diff --git a/spec/lib/gitlab/other_markup_spec.rb b/spec/lib/gitlab/other_markup_spec.rb index d6d53e8586c..c0f5fa9dc1f 100644 --- a/spec/lib/gitlab/other_markup_spec.rb +++ b/spec/lib/gitlab/other_markup_spec.rb @@ -13,7 +13,7 @@ describe Gitlab::OtherMarkup, lib: true do } links.each do |name, data| it "does not convert dangerous #{name} into HTML" do - expect(render(data[:file], data[:input])).to eq(data[:output]) + expect(render(data[:file], data[:input], context)).to eq(data[:output]) end end end From da13d1af3ecfdf124d63c5cf53aca6cac8a9f36d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 2 May 2017 22:25:58 +0000 Subject: [PATCH 15/38] Merge branch 'bvl-security-9-1-validate-urls-in-markdown-using-uri' (security-9-1) Add correct `rel` attributes to external links when rendering markdown See merge request !2097 --- ...vl-validate-urls-in-markdown-using-uri.yml | 4 + lib/banzai/filter/external_link_filter.rb | 36 ++++---- .../filter/external_link_filter_spec.rb | 85 +++++++++++-------- spec/lib/gitlab/asciidoc_spec.rb | 17 +++- 4 files changed, 86 insertions(+), 56 deletions(-) create mode 100644 changelogs/unreleased/bvl-validate-urls-in-markdown-using-uri.yml diff --git a/changelogs/unreleased/bvl-validate-urls-in-markdown-using-uri.yml b/changelogs/unreleased/bvl-validate-urls-in-markdown-using-uri.yml new file mode 100644 index 00000000000..03c4e531d73 --- /dev/null +++ b/changelogs/unreleased/bvl-validate-urls-in-markdown-using-uri.yml @@ -0,0 +1,4 @@ +--- +title: Validate URLs in markdown using URI to detect the host correctly +merge_request: +author: diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb index d67d466bce8..7d15a0f6d44 100644 --- a/lib/banzai/filter/external_link_filter.rb +++ b/lib/banzai/filter/external_link_filter.rb @@ -2,16 +2,17 @@ module Banzai module Filter # HTML Filter to modify the attributes of external links class ExternalLinkFilter < HTML::Pipeline::Filter + SCHEMES = ['http', 'https', nil].freeze + def call links.each do |node| - href = href_to_lowercase_scheme(node["href"].to_s) + uri = uri(node['href'].to_s) + next unless uri - unless node["href"].to_s == href - node.set_attribute('href', href) - end + node.set_attribute('href', uri.to_s) - if href =~ %r{\A(https?:)?//[^/]} && external_url?(href) - node.set_attribute('rel', 'nofollow noreferrer') + if SCHEMES.include?(uri.scheme) && external_url?(uri) + node.set_attribute('rel', 'nofollow noreferrer noopener') node.set_attribute('target', '_blank') end end @@ -21,27 +22,26 @@ module Banzai private + def uri(href) + URI.parse(href) + rescue URI::InvalidURIError + nil + end + def links query = 'descendant-or-self::a[@href and not(@href = "")]' doc.xpath(query) end - def href_to_lowercase_scheme(href) - scheme_match = href.match(/\A(\w+):\/\//) + def external_url?(uri) + # Relative URLs miss a hostname + return false unless uri.hostname - if scheme_match - scheme_match.to_s.downcase + scheme_match.post_match - else - href - end - end - - def external_url?(url) - !url.start_with?(internal_url) + uri.hostname != internal_url.hostname end def internal_url - @internal_url ||= Gitlab.config.gitlab.url + @internal_url ||= URI.parse(Gitlab.config.gitlab.url) end end end diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb index d9e4525cb28..6f6c215be87 100644 --- a/spec/lib/banzai/filter/external_link_filter_spec.rb +++ b/spec/lib/banzai/filter/external_link_filter_spec.rb @@ -1,5 +1,22 @@ require 'spec_helper' +shared_examples 'an external link with rel attribute' do + it 'adds rel="nofollow" to external links' do + expect(doc.at_css('a')).to have_attribute('rel') + expect(doc.at_css('a')['rel']).to include 'nofollow' + end + + it 'adds rel="noreferrer" to external links' do + expect(doc.at_css('a')).to have_attribute('rel') + expect(doc.at_css('a')['rel']).to include 'noreferrer' + end + + it 'adds rel="noopener" to external links' do + expect(doc.at_css('a')).to have_attribute('rel') + expect(doc.at_css('a')['rel']).to include 'noopener' + end +end + describe Banzai::Filter::ExternalLinkFilter, lib: true do include FilterSpecHelper @@ -22,49 +39,51 @@ describe Banzai::Filter::ExternalLinkFilter, lib: true do context 'for root links on document' do let(:doc) { filter %q(Google) } - it 'adds rel="nofollow" to external links' do - expect(doc.at_css('a')).to have_attribute('rel') - expect(doc.at_css('a')['rel']).to include 'nofollow' - end - - it 'adds rel="noreferrer" to external links' do - expect(doc.at_css('a')).to have_attribute('rel') - expect(doc.at_css('a')['rel']).to include 'noreferrer' - end + it_behaves_like 'an external link with rel attribute' end context 'for nested links on document' do let(:doc) { filter %q(

Google

) } - it 'adds rel="nofollow" to external links' do - expect(doc.at_css('a')).to have_attribute('rel') - expect(doc.at_css('a')['rel']).to include 'nofollow' + it_behaves_like 'an external link with rel attribute' + end + + context 'for invalid urls' do + it 'skips broken hrefs' do + doc = filter %q(

Google

) + expected = %q(

Google

) + + expect(doc.to_html).to eq(expected) + end + end + + context 'for links with a username' do + context 'with a valid username' do + let(:doc) { filter %q(Google) } + + it_behaves_like 'an external link with rel attribute' end - it 'adds rel="noreferrer" to external links' do - expect(doc.at_css('a')).to have_attribute('rel') - expect(doc.at_css('a')['rel']).to include 'noreferrer' + context 'with an impersonated username' do + let(:internal) { Gitlab.config.gitlab.url } + + let(:doc) { filter %Q(Reverse Tabnabbing) } + + it_behaves_like 'an external link with rel attribute' end end context 'for non-lowercase scheme links' do - let(:doc_with_http) { filter %q(

Google

) } - let(:doc_with_https) { filter %q(

Google

) } + context 'with http' do + let(:doc) { filter %q(

Google

) } - it 'adds rel="nofollow" to external links' do - expect(doc_with_http.at_css('a')).to have_attribute('rel') - expect(doc_with_https.at_css('a')).to have_attribute('rel') - - expect(doc_with_http.at_css('a')['rel']).to include 'nofollow' - expect(doc_with_https.at_css('a')['rel']).to include 'nofollow' + it_behaves_like 'an external link with rel attribute' end - it 'adds rel="noreferrer" to external links' do - expect(doc_with_http.at_css('a')).to have_attribute('rel') - expect(doc_with_https.at_css('a')).to have_attribute('rel') + context 'with https' do + let(:doc) { filter %q(

Google

) } - expect(doc_with_http.at_css('a')['rel']).to include 'noreferrer' - expect(doc_with_https.at_css('a')['rel']).to include 'noreferrer' + it_behaves_like 'an external link with rel attribute' end it 'skips internal links' do @@ -84,14 +103,6 @@ describe Banzai::Filter::ExternalLinkFilter, lib: true do context 'for protocol-relative links' do let(:doc) { filter %q(

Google

) } - it 'adds rel="nofollow" to external links' do - expect(doc.at_css('a')).to have_attribute('rel') - expect(doc.at_css('a')['rel']).to include 'nofollow' - end - - it 'adds rel="noreferrer" to external links' do - expect(doc.at_css('a')).to have_attribute('rel') - expect(doc.at_css('a')['rel']).to include 'noreferrer' - end + it_behaves_like 'an external link with rel attribute' end end diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb index f284dd14cec..2c7ebb15fd7 100644 --- a/spec/lib/gitlab/asciidoc_spec.rb +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -25,6 +25,21 @@ module Gitlab expect(render(input, context)).to eq(html) end + context "with asciidoc_opts" do + it "merges the options with default ones" do + expected_asciidoc_opts = { + safe: :secure, + backend: :gitlab_html5, + attributes: described_class::DEFAULT_ADOC_ATTRS + } + + expect(Asciidoctor).to receive(:convert) + .with(input, expected_asciidoc_opts).and_return(html) + + render(input, context) + end + end + context "XSS" do links = { 'links' => { @@ -52,7 +67,7 @@ module Gitlab it 'adds the `rel` attribute to the link' do output = render('link:https://google.com[Google]', context) - expect(output).to include('rel="nofollow noreferrer"') + expect(output).to include('rel="nofollow noreferrer noopener"') end end end From 9ae401cf91c9d545602b9aa86afcd306fc6e3467 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 10 Apr 2017 16:55:31 +0000 Subject: [PATCH 16/38] Merge branch 'rs-sanitize-submodule-urls' into 'security' Sanitize submodule URLs before linking to them in the file tree view See merge request !2084 --- app/helpers/submodule_helper.rb | 46 ++++++++++++------- .../unreleased/rs-sanitize-submodule-urls.yml | 4 ++ spec/helpers/submodule_helper_spec.rb | 12 +++++ 3 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 changelogs/unreleased/rs-sanitize-submodule-urls.yml diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb index a762b320d56..b739554a7a4 100644 --- a/app/helpers/submodule_helper.rb +++ b/app/helpers/submodule_helper.rb @@ -1,28 +1,30 @@ module SubmoduleHelper include Gitlab::ShellAdapter + VALID_SUBMODULE_PROTOCOLS = %w[http https git ssh].freeze + # links to files listing for submodule if submodule is a project on this server def submodule_links(submodule_item, ref = nil, repository = @repository) url = repository.submodule_url_for(ref, submodule_item.path) - return url, nil unless url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/ + if url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/ + namespace, project = $1, $2 + project.sub!(/\.git\z/, '') - namespace = $1 - project = $2 - project.chomp!('.git') - - if self_url?(url, namespace, project) - return namespace_project_path(namespace, project), - namespace_project_tree_path(namespace, project, - submodule_item.id) - elsif relative_self_url?(url) - relative_self_links(url, submodule_item.id) - elsif github_dot_com_url?(url) - standard_links('github.com', namespace, project, submodule_item.id) - elsif gitlab_dot_com_url?(url) - standard_links('gitlab.com', namespace, project, submodule_item.id) + if self_url?(url, namespace, project) + [namespace_project_path(namespace, project), + namespace_project_tree_path(namespace, project, submodule_item.id)] + elsif relative_self_url?(url) + relative_self_links(url, submodule_item.id) + elsif github_dot_com_url?(url) + standard_links('github.com', namespace, project, submodule_item.id) + elsif gitlab_dot_com_url?(url) + standard_links('gitlab.com', namespace, project, submodule_item.id) + else + [sanitize_submodule_url(url), nil] + end else - return url, nil + [sanitize_submodule_url(url), nil] end end @@ -73,4 +75,16 @@ module SubmoduleHelper namespace_project_tree_path(namespace, base, commit) ] end + + def sanitize_submodule_url(url) + uri = URI.parse(url) + + if uri.scheme.in?(VALID_SUBMODULE_PROTOCOLS) + uri.to_s + else + nil + end + rescue URI::InvalidURIError + nil + end end diff --git a/changelogs/unreleased/rs-sanitize-submodule-urls.yml b/changelogs/unreleased/rs-sanitize-submodule-urls.yml new file mode 100644 index 00000000000..463b3695687 --- /dev/null +++ b/changelogs/unreleased/rs-sanitize-submodule-urls.yml @@ -0,0 +1,4 @@ +--- +title: Sanitize submodule URLs before linking to them in the file tree view +merge_request: +author: diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb index 345bc33a67b..9da33792659 100644 --- a/spec/helpers/submodule_helper_spec.rb +++ b/spec/helpers/submodule_helper_spec.rb @@ -109,6 +109,18 @@ describe SubmoduleHelper do end context 'submodule on unsupported' do + it 'sanitizes unsupported protocols' do + stub_url('javascript:alert("XSS");') + + expect(helper.submodule_links(submodule_item)).to eq([nil, nil]) + end + + it 'sanitizes unsupported protocols disguised as a repository URL' do + stub_url('javascript:alert("XSS");foo/bar.git') + + expect(helper.submodule_links(submodule_item)).to eq([nil, nil]) + end + it 'returns original' do stub_url('http://mygitserver.com/gitlab-org/gitlab-ce') expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil]) From d9ec830a8348fca93775c5f0b1f81a83e8c4f95a Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 25 Apr 2017 14:41:26 +0000 Subject: [PATCH 17/38] Merge branch 'snippets_visibility' into 'security' Fix snippets visibility for show action - external users can not see internal snippets See merge request !2087 --- app/controllers/snippets_controller.rb | 18 +++++++-------- changelogs/unreleased/snippets_visibility.yml | 4 ++++ spec/controllers/snippets_controller_spec.rb | 6 ++--- .../snippets/internal_snippet_spec.rb | 23 +++++++++++++++++++ 4 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 changelogs/unreleased/snippets_visibility.yml create mode 100644 spec/features/snippets/internal_snippet_spec.rb diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 19e07e3ab86..656a365b701 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -103,20 +103,20 @@ class SnippetsController < ApplicationController protected def snippet - @snippet ||= if current_user - PersonalSnippet.where("author_id = ? OR visibility_level IN (?)", - current_user.id, - [Snippet::PUBLIC, Snippet::INTERNAL]). - find(params[:id]) - else - PersonalSnippet.find(params[:id]) - end + @snippet ||= PersonalSnippet.find_by(id: params[:id]) end + alias_method :awardable, :snippet alias_method :spammable, :snippet def authorize_read_snippet! - authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet) + return if can?(current_user, :read_personal_snippet, @snippet) + + if current_user + render_404 + else + authenticate_user! + end end def authorize_update_snippet! diff --git a/changelogs/unreleased/snippets_visibility.yml b/changelogs/unreleased/snippets_visibility.yml new file mode 100644 index 00000000000..4c10c6882ab --- /dev/null +++ b/changelogs/unreleased/snippets_visibility.yml @@ -0,0 +1,4 @@ +--- +title: Fix snippets visibility for show action - external users can not see internal snippets +merge_request: +author: diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index 41cd5bdcdd8..da46431b700 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -132,7 +132,7 @@ describe SnippetsController do it 'responds with status 404' do get :show, id: 'doesntexist' - expect(response).to have_http_status(404) + expect(response).to redirect_to(new_user_session_path) end end end @@ -478,10 +478,10 @@ describe SnippetsController do end context 'when not signed in' do - it 'responds with status 404' do + it 'redirects to the sign in path' do get :raw, id: 'doesntexist' - expect(response).to have_http_status(404) + expect(response).to redirect_to(new_user_session_path) end end end diff --git a/spec/features/snippets/internal_snippet_spec.rb b/spec/features/snippets/internal_snippet_spec.rb new file mode 100644 index 00000000000..93382f4c359 --- /dev/null +++ b/spec/features/snippets/internal_snippet_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +feature 'Internal Snippets', feature: true, js: true do + let(:internal_snippet) { create(:personal_snippet, :internal) } + + describe 'normal user' do + before do + login_as :user + end + + scenario 'sees internal snippets' do + visit snippet_path(internal_snippet) + + expect(page).to have_content(internal_snippet.content) + end + + scenario 'sees raw internal snippets' do + visit raw_snippet_path(internal_snippet) + + expect(page).to have_content(internal_snippet.content) + end + end +end From 61a81a3ac225296f8aefc4d2f350de72a531bf3d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 24 Apr 2017 15:09:29 +0000 Subject: [PATCH 18/38] Merge branch '31157-respect-project-features-in-wiki-search' into 'security' Respect project features in wiki and blob search See merge request !2089 --- ...espect-project-features-in-wiki-search.yml | 4 + lib/gitlab/project_search_results.rb | 4 + .../lib/gitlab/project_search_results_spec.rb | 75 ++++++++++++++++++- 3 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/31157-respect-project-features-in-wiki-search.yml diff --git a/changelogs/unreleased/31157-respect-project-features-in-wiki-search.yml b/changelogs/unreleased/31157-respect-project-features-in-wiki-search.yml new file mode 100644 index 00000000000..721bb435a2e --- /dev/null +++ b/changelogs/unreleased/31157-respect-project-features-in-wiki-search.yml @@ -0,0 +1,4 @@ +--- +title: Enforce project features when searching blobs and wikis +merge_request: +author: diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index 0b8959f2fb9..47cfe412715 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -82,6 +82,8 @@ module Gitlab private def blobs + return [] unless Ability.allowed?(@current_user, :download_code, @project) + @blobs ||= begin blobs = project.repository.search_files_by_content(query, repository_ref).first(100) found_file_names = Set.new @@ -102,6 +104,8 @@ module Gitlab end def wiki_blobs + return [] unless Ability.allowed?(@current_user, :read_wiki, @project) + @wiki_blobs ||= begin if project.wiki_enabled? && query.present? project_wiki = ProjectWiki.new(project) diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb index a7c8e7f1f57..6e0b1192706 100644 --- a/spec/lib/gitlab/project_search_results_spec.rb +++ b/spec/lib/gitlab/project_search_results_spec.rb @@ -22,8 +22,37 @@ describe Gitlab::ProjectSearchResults, lib: true do end describe 'blob search' do - let(:project) { create(:project, :repository) } - let(:results) { described_class.new(user, project, 'files').objects('blobs') } + let(:project) { create(:project, :public, :repository) } + + subject(:results) { described_class.new(user, project, 'files').objects('blobs') } + + context 'when repository is disabled' do + let(:project) { create(:project, :public, :repository, :repository_disabled) } + + it 'hides blobs from members' do + project.add_reporter(user) + + is_expected.to be_empty + end + + it 'hides blobs from non-members' do + is_expected.to be_empty + end + end + + context 'when repository is internal' do + let(:project) { create(:project, :public, :repository, :repository_private) } + + it 'finds blobs for members' do + project.add_reporter(user) + + is_expected.not_to be_empty + end + + it 'hides blobs from non-members' do + is_expected.to be_empty + end + end it 'finds by name' do expect(results).to include(["files/images/wm.svg", nil]) @@ -70,6 +99,46 @@ describe Gitlab::ProjectSearchResults, lib: true do end end + describe 'wiki search' do + let(:project) { create(:project, :public) } + let(:wiki) { build(:project_wiki, project: project) } + let!(:wiki_page) { wiki.create_page('Title', 'Content') } + + subject(:results) { described_class.new(user, project, 'Content').objects('wiki_blobs') } + + context 'when wiki is disabled' do + let(:project) { create(:project, :public, :wiki_disabled) } + + it 'hides wiki blobs from members' do + project.add_reporter(user) + + is_expected.to be_empty + end + + it 'hides wiki blobs from non-members' do + is_expected.to be_empty + end + end + + context 'when wiki is internal' do + let(:project) { create(:project, :public, :wiki_private) } + + it 'finds wiki blobs for members' do + project.add_reporter(user) + + is_expected.not_to be_empty + end + + it 'hides wiki blobs from non-members' do + is_expected.to be_empty + end + end + + it 'finds by content' do + expect(results).to include("master:Title.md:1:Content\n") + end + end + it 'does not list issues on private projects' do issue = create(:issue, project: project) @@ -79,7 +148,6 @@ describe Gitlab::ProjectSearchResults, lib: true do end describe 'confidential issues' do - let(:project) { create(:empty_project) } let(:query) { 'issue' } let(:author) { create(:user) } let(:assignee) { create(:user) } @@ -277,6 +345,7 @@ describe Gitlab::ProjectSearchResults, lib: true do context 'by commit hash' do let(:project) { create(:project, :public, :repository) } let(:commit) { project.repository.commit('0b4bc9a') } + commit_hashes = { short: '0b4bc9a', full: '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } commit_hashes.each do |type, commit_hash| From 576e244b6c017dcda2d2d848670ec3b60db63409 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 3 May 2017 14:28:46 +0000 Subject: [PATCH 19/38] Merge branch 'branch-name-escape' into 'security' Fix XSS in branches dropdown See merge request !2093 --- changelogs/unreleased/branch-name-escape.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/branch-name-escape.yml diff --git a/changelogs/unreleased/branch-name-escape.yml b/changelogs/unreleased/branch-name-escape.yml new file mode 100644 index 00000000000..bf46235fd79 --- /dev/null +++ b/changelogs/unreleased/branch-name-escape.yml @@ -0,0 +1,4 @@ +--- +title: Fixed branches dropdown rendering branch names as HTML +merge_request: +author: From ad309f5d110ebf8859b2e7196c7a1d0b039c0d7c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 28 Apr 2017 22:06:27 +0000 Subject: [PATCH 20/38] Merge branch 'snippets-finder-visibility' into 'security' Refactor snippets finder & dont return internal snippets for external users See merge request !2094 --- .../dashboard/snippets_controller.rb | 7 +- .../explore/snippets_controller.rb | 2 +- .../projects/snippets_controller.rb | 5 +- app/controllers/snippets_controller.rb | 8 +- app/controllers/users_controller.rb | 7 +- app/finders/notes_finder.rb | 2 +- app/finders/snippets_finder.rb | 102 +++++++------- app/models/snippet.rb | 13 -- app/policies/project_snippet_policy.rb | 2 +- app/services/search/snippet_service.rb | 2 +- .../unreleased/snippets-finder-visibility.yml | 4 + lib/api/project_snippets.rb | 3 +- lib/api/snippets.rb | 4 +- lib/api/v3/project_snippets.rb | 3 +- lib/api/v3/snippets.rb | 4 +- spec/controllers/snippets_controller_spec.rb | 28 ++++ spec/features/dashboard/snippets_spec.rb | 47 +++++++ spec/features/projects/snippets_spec.rb | 24 +++- spec/features/snippets/explore_spec.rb | 25 +++- spec/features/users/snippets_spec.rb | 46 ++++++- spec/finders/snippets_finder_spec.rb | 125 ++++++++++++++---- spec/models/snippet_spec.rb | 40 ------ spec/policies/project_snippet_policy_spec.rb | 80 ++++++++--- 23 files changed, 399 insertions(+), 184 deletions(-) create mode 100644 changelogs/unreleased/snippets-finder-visibility.yml diff --git a/app/controllers/dashboard/snippets_controller.rb b/app/controllers/dashboard/snippets_controller.rb index bcfdbe14be9..8dd91264451 100644 --- a/app/controllers/dashboard/snippets_controller.rb +++ b/app/controllers/dashboard/snippets_controller.rb @@ -1,11 +1,10 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController def index - @snippets = SnippetsFinder.new.execute( + @snippets = SnippetsFinder.new( current_user, - filter: :by_user, - user: current_user, + author: current_user, scope: params[:scope] - ) + ).execute @snippets = @snippets.page(params[:page]) end end diff --git a/app/controllers/explore/snippets_controller.rb b/app/controllers/explore/snippets_controller.rb index 28760c3f84b..d3f0e033068 100644 --- a/app/controllers/explore/snippets_controller.rb +++ b/app/controllers/explore/snippets_controller.rb @@ -1,6 +1,6 @@ class Explore::SnippetsController < Explore::ApplicationController def index - @snippets = SnippetsFinder.new.execute(current_user, filter: :all) + @snippets = SnippetsFinder.new(current_user).execute @snippets = @snippets.page(params[:page]) end end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 66f913f8f9d..3b2b0d9e502 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -23,12 +23,11 @@ class Projects::SnippetsController < Projects::ApplicationController respond_to :html def index - @snippets = SnippetsFinder.new.execute( + @snippets = SnippetsFinder.new( current_user, - filter: :by_project, project: @project, scope: params[:scope] - ) + ).execute @snippets = @snippets.page(params[:page]) if @snippets.out_of_range? && @snippets.total_pages != 0 redirect_to namespace_project_snippets_path(page: @snippets.total_pages) diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 656a365b701..7445f61195d 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -27,12 +27,8 @@ class SnippetsController < ApplicationController return render_404 unless @user - @snippets = SnippetsFinder.new.execute(current_user, { - filter: :by_user, - user: @user, - scope: params[:scope] - }) - .page(params[:page]) + @snippets = SnippetsFinder.new(current_user, author: @user, scope: params[:scope]) + .execute.page(params[:page]) render 'index' else diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index ca89ed221c6..ba22b2f9d29 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -128,12 +128,11 @@ class UsersController < ApplicationController end def load_snippets - @snippets = SnippetsFinder.new.execute( + @snippets = SnippetsFinder.new( current_user, - filter: :by_user, - user: user, + author: user, scope: params[:scope] - ).page(params[:page]) + ).execute.page(params[:page]) end def projects_for_current_user diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index dc6a8ad1f66..02eb983bf55 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -67,7 +67,7 @@ class NotesFinder when "merge_request" MergeRequestsFinder.new(@current_user, project_id: @project.id).execute when "snippet", "project_snippet" - SnippetsFinder.new.execute(@current_user, filter: :by_project, project: @project) + SnippetsFinder.new(@current_user, project: @project).execute when "personal_snippet" PersonalSnippet.all else diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb index da6e6e87a6f..c04f61de79c 100644 --- a/app/finders/snippets_finder.rb +++ b/app/finders/snippets_finder.rb @@ -1,66 +1,74 @@ -class SnippetsFinder - def execute(current_user, params = {}) - filter = params[:filter] - user = params.fetch(:user, current_user) +class SnippetsFinder < UnionFinder + attr_accessor :current_user, :params - case filter - when :all then - snippets(current_user).fresh - when :public then - Snippet.are_public.fresh - when :by_user then - by_user(current_user, user, params[:scope]) - when :by_project - by_project(current_user, params[:project], params[:scope]) - end + def initialize(current_user, params = {}) + @current_user = current_user + @params = params + end + + def execute + items = init_collection + items = by_project(items) + items = by_author(items) + items = by_visibility(items) + + items.fresh end private - def snippets(current_user) - if current_user - Snippet.public_and_internal - else - # Not authenticated - # - # Return only: - # public snippets - Snippet.are_public - end + def init_collection + items = Snippet.all + + accessible(items) end - def by_user(current_user, user, scope) - snippets = user.snippets.fresh + def accessible(items) + segments = [] + segments << items.public_to_user(current_user) + segments << authorized_to_user(items) if current_user - if current_user - include_private = user == current_user - by_scope(snippets, scope, include_private) - else - snippets.are_public - end + find_union(segments, Snippet) end - def by_project(current_user, project, scope) - snippets = project.snippets.fresh - - if current_user - include_private = project.team.member?(current_user) || current_user.admin? - by_scope(snippets, scope, include_private) - else - snippets.are_public - end + def authorized_to_user(items) + items.where( + 'author_id = :author_id + OR project_id IN (:project_ids)', + author_id: current_user.id, + project_ids: current_user.authorized_projects.select(:id)) end - def by_scope(snippets, scope = nil, include_private = false) - case scope.to_s + def by_visibility(items) + visibility = params[:visibility] || visibility_from_scope + + return items unless visibility + + items.where(visibility_level: visibility) + end + + def by_author(items) + return items unless params[:author] + + items.where(author_id: params[:author].id) + end + + def by_project(items) + return items unless params[:project] + + items.where(project_id: params[:project].id) + end + + def visibility_from_scope + case params[:scope].to_s when 'are_private' - include_private ? snippets.are_private : Snippet.none + Snippet::PRIVATE when 'are_internal' - snippets.are_internal + Snippet::INTERNAL when 'are_public' - snippets.are_public + Snippet::PUBLIC else - include_private ? snippets : snippets.public_and_internal + nil end end end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index abfbefdf9a0..882e2fa0594 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -152,18 +152,5 @@ class Snippet < ActiveRecord::Base where(table[:content].matches(pattern)) end - - def accessible_to(user) - return are_public unless user.present? - return all if user.admin? - - where( - 'visibility_level IN (:visibility_levels) - OR author_id = :author_id - OR project_id IN (:project_ids)', - visibility_levels: [Snippet::PUBLIC, Snippet::INTERNAL], - author_id: user.id, - project_ids: user.authorized_projects.select(:id)) - end end end diff --git a/app/policies/project_snippet_policy.rb b/app/policies/project_snippet_policy.rb index 3a96836917e..cf8ff92617f 100644 --- a/app/policies/project_snippet_policy.rb +++ b/app/policies/project_snippet_policy.rb @@ -13,7 +13,7 @@ class ProjectSnippetPolicy < BasePolicy can! :read_project_snippet end - if @subject.private? && @subject.project.team.member?(@user) + if @subject.project.team.member?(@user) can! :read_project_snippet end end diff --git a/app/services/search/snippet_service.rb b/app/services/search/snippet_service.rb index 4f161beea4d..85da0be6fff 100644 --- a/app/services/search/snippet_service.rb +++ b/app/services/search/snippet_service.rb @@ -7,7 +7,7 @@ module Search end def execute - snippets = Snippet.accessible_to(current_user) + snippets = SnippetsFinder.new(current_user).execute Gitlab::SnippetSearchResults.new(snippets, params[:search]) end diff --git a/changelogs/unreleased/snippets-finder-visibility.yml b/changelogs/unreleased/snippets-finder-visibility.yml new file mode 100644 index 00000000000..fde2262cc8d --- /dev/null +++ b/changelogs/unreleased/snippets-finder-visibility.yml @@ -0,0 +1,4 @@ +--- +title: Refactor snippets finder & dont return internal snippets for external users +merge_request: +author: diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index cfee38a9baf..98bc9c28527 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -17,8 +17,7 @@ module API end def snippets_for_current_user - finder_params = { filter: :by_project, project: user_project } - SnippetsFinder.new.execute(current_user, finder_params) + SnippetsFinder.new(current_user, project: user_project).execute end end diff --git a/lib/api/snippets.rb b/lib/api/snippets.rb index b93fdc62808..53f5953a8fb 100644 --- a/lib/api/snippets.rb +++ b/lib/api/snippets.rb @@ -8,11 +8,11 @@ module API resource :snippets do helpers do def snippets_for_current_user - SnippetsFinder.new.execute(current_user, filter: :by_user, user: current_user) + SnippetsFinder.new(current_user, author: current_user).execute end def public_snippets - SnippetsFinder.new.execute(current_user, filter: :public) + SnippetsFinder.new(current_user, visibility: Snippet::PUBLIC).execute end end diff --git a/lib/api/v3/project_snippets.rb b/lib/api/v3/project_snippets.rb index fc065a22d74..c41fee32610 100644 --- a/lib/api/v3/project_snippets.rb +++ b/lib/api/v3/project_snippets.rb @@ -18,8 +18,7 @@ module API end def snippets_for_current_user - finder_params = { filter: :by_project, project: user_project } - SnippetsFinder.new.execute(current_user, finder_params) + SnippetsFinder.new(current_user, project: user_project).execute end end diff --git a/lib/api/v3/snippets.rb b/lib/api/v3/snippets.rb index 07dac7e9904..0762fc02d70 100644 --- a/lib/api/v3/snippets.rb +++ b/lib/api/v3/snippets.rb @@ -8,11 +8,11 @@ module API resource :snippets do helpers do def snippets_for_current_user - SnippetsFinder.new.execute(current_user, filter: :by_user, user: current_user) + SnippetsFinder.new(current_user, author: current_user).execute end def public_snippets - SnippetsFinder.new.execute(current_user, filter: :public) + SnippetsFinder.new(current_user, visibility: Snippet::PUBLIC).execute end end diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index da46431b700..930415a4778 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -3,6 +3,34 @@ require 'spec_helper' describe SnippetsController do let(:user) { create(:user) } + describe 'GET #index' do + let(:user) { create(:user) } + + context 'when username parameter is present' do + it 'renders snippets of a user when username is present' do + get :index, username: user.username + + expect(response).to render_template(:index) + end + end + + context 'when username parameter is not present' do + it 'redirects to explore snippets page when user is not logged in' do + get :index + + expect(response).to redirect_to(explore_snippets_path) + end + + it 'redirects to snippets dashboard page when user is logged in' do + sign_in(user) + + get :index + + expect(response).to redirect_to(dashboard_snippets_path) + end + end + end + describe 'GET #new' do context 'when signed in' do before do diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb index 62937688c22..c6ba118220a 100644 --- a/spec/features/dashboard/snippets_spec.rb +++ b/spec/features/dashboard/snippets_spec.rb @@ -12,4 +12,51 @@ describe 'Dashboard snippets', feature: true do it_behaves_like 'paginated snippets' end + + context 'filtering by visibility' do + let(:user) { create(:user) } + let!(:snippets) do + [ + create(:personal_snippet, :public, author: user), + create(:personal_snippet, :internal, author: user), + create(:personal_snippet, :private, author: user), + create(:personal_snippet, :public) + ] + end + + before do + login_as(user) + + visit dashboard_snippets_path + end + + it 'contains all snippets of logged user' do + expect(page).to have_selector('.snippet-row', count: 3) + + expect(page).to have_content(snippets[0].title) + expect(page).to have_content(snippets[1].title) + expect(page).to have_content(snippets[2].title) + end + + it 'contains all private snippets of logged user when clicking on private' do + click_link('Private') + + expect(page).to have_selector('.snippet-row', count: 1) + expect(page).to have_content(snippets[2].title) + end + + it 'contains all internal snippets of logged user when clicking on internal' do + click_link('Internal') + + expect(page).to have_selector('.snippet-row', count: 1) + expect(page).to have_content(snippets[1].title) + end + + it 'contains all public snippets of logged user when clicking on public' do + click_link('Public') + + expect(page).to have_selector('.snippet-row', count: 1) + expect(page).to have_content(snippets[0].title) + end + end end diff --git a/spec/features/projects/snippets_spec.rb b/spec/features/projects/snippets_spec.rb index d37e8ed4699..18689c17fe9 100644 --- a/spec/features/projects/snippets_spec.rb +++ b/spec/features/projects/snippets_spec.rb @@ -4,11 +4,27 @@ describe 'Project snippets', feature: true do context 'when the project has snippets' do let(:project) { create(:empty_project, :public) } let!(:snippets) { create_list(:project_snippet, 2, :public, author: project.owner, project: project) } - before do - allow(Snippet).to receive(:default_per_page).and_return(1) - visit namespace_project_snippets_path(project.namespace, project) + let!(:other_snippet) { create(:project_snippet) } + + context 'pagination' do + before do + allow(Snippet).to receive(:default_per_page).and_return(1) + + visit namespace_project_snippets_path(project.namespace, project) + end + + it_behaves_like 'paginated snippets' end - it_behaves_like 'paginated snippets' + context 'list content' do + it 'contains all project snippets' do + visit namespace_project_snippets_path(project.namespace, project) + + expect(page).to have_selector('.snippet-row', count: 2) + + expect(page).to have_content(snippets[0].title) + expect(page).to have_content(snippets[1].title) + end + end end end diff --git a/spec/features/snippets/explore_spec.rb b/spec/features/snippets/explore_spec.rb index 10a4597e467..fd097fe2e74 100644 --- a/spec/features/snippets/explore_spec.rb +++ b/spec/features/snippets/explore_spec.rb @@ -1,11 +1,11 @@ require 'rails_helper' feature 'Explore Snippets', feature: true do - scenario 'User should see snippets that are not private' do - public_snippet = create(:personal_snippet, :public) - internal_snippet = create(:personal_snippet, :internal) - private_snippet = create(:personal_snippet, :private) + let!(:public_snippet) { create(:personal_snippet, :public) } + let!(:internal_snippet) { create(:personal_snippet, :internal) } + let!(:private_snippet) { create(:personal_snippet, :private) } + scenario 'User should see snippets that are not private' do login_as create(:user) visit explore_snippets_path @@ -13,4 +13,21 @@ feature 'Explore Snippets', feature: true do expect(page).to have_content(internal_snippet.title) expect(page).not_to have_content(private_snippet.title) end + + scenario 'External user should see only public snippets' do + login_as create(:user, :external) + visit explore_snippets_path + + expect(page).to have_content(public_snippet.title) + expect(page).not_to have_content(internal_snippet.title) + expect(page).not_to have_content(private_snippet.title) + end + + scenario 'Not authenticated user should see only public snippets' do + visit explore_snippets_path + + expect(page).to have_content(public_snippet.title) + expect(page).not_to have_content(internal_snippet.title) + expect(page).not_to have_content(private_snippet.title) + end end diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb index 1546a06b80c..4efbd672322 100644 --- a/spec/features/users/snippets_spec.rb +++ b/spec/features/users/snippets_spec.rb @@ -3,14 +3,46 @@ require 'spec_helper' describe 'Snippets tab on a user profile', feature: true, js: true do context 'when the user has snippets' do let(:user) { create(:user) } - let!(:snippets) { create_list(:snippet, 2, :public, author: user) } - before do - allow(Snippet).to receive(:default_per_page).and_return(1) - visit user_path(user) - page.within('.user-profile-nav') { click_link 'Snippets' } - wait_for_ajax + + context 'pagination' do + let!(:snippets) { create_list(:snippet, 2, :public, author: user) } + + before do + allow(Snippet).to receive(:default_per_page).and_return(1) + visit user_path(user) + page.within('.user-profile-nav') { click_link 'Snippets' } + wait_for_ajax + end + + it_behaves_like 'paginated snippets', remote: true end - it_behaves_like 'paginated snippets', remote: true + context 'list content' do + let!(:public_snippet) { create(:snippet, :public, author: user) } + let!(:internal_snippet) { create(:snippet, :internal, author: user) } + let!(:private_snippet) { create(:snippet, :private, author: user) } + let!(:other_snippet) { create(:snippet, :public) } + + it 'contains only internal and public snippets of a user when a user is logged in' do + login_as(:user) + visit user_path(user) + page.within('.user-profile-nav') { click_link 'Snippets' } + wait_for_ajax + + expect(page).to have_selector('.snippet-row', count: 2) + + expect(page).to have_content(public_snippet.title) + expect(page).to have_content(internal_snippet.title) + end + + it 'contains only public snippets of a user when a user is not logged in' do + visit user_path(user) + page.within('.user-profile-nav') { click_link 'Snippets' } + wait_for_ajax + + expect(page).to have_selector('.snippet-row', count: 1) + expect(page).to have_content(public_snippet.title) + end + end end end diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index cb6c80d1bd0..9171fb9c4af 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -8,79 +8,145 @@ describe SnippetsFinder do let(:project1) { create(:empty_project, :public, group: group) } let(:project2) { create(:empty_project, :private, group: group) } - context ':all filter' do + context 'all snippets visible to a user' do let!(:snippet1) { create(:personal_snippet, :private) } let!(:snippet2) { create(:personal_snippet, :internal) } let!(:snippet3) { create(:personal_snippet, :public) } + let!(:project_snippet1) { create(:project_snippet, :private) } + let!(:project_snippet2) { create(:project_snippet, :internal) } + let!(:project_snippet3) { create(:project_snippet, :public) } it "returns all private and internal snippets" do - snippets = described_class.new.execute(user, filter: :all) - expect(snippets).to include(snippet2, snippet3) - expect(snippets).not_to include(snippet1) + snippets = described_class.new(user, scope: :all).execute + expect(snippets).to include(snippet2, snippet3, project_snippet2, project_snippet3) + expect(snippets).not_to include(snippet1, project_snippet1) end it "returns all public snippets" do - snippets = described_class.new.execute(nil, filter: :all) - expect(snippets).to include(snippet3) - expect(snippets).not_to include(snippet1, snippet2) + snippets = described_class.new(nil, scope: :all).execute + expect(snippets).to include(snippet3, project_snippet3) + expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2) + end + + it "returns all public and internal snippets for normal user" do + snippets = SnippetsFinder.new(user).execute + + expect(snippets).to include(snippet2, snippet3, project_snippet2, project_snippet3) + expect(snippets).not_to include(snippet1, project_snippet1) + end + + it "returns all public snippets for non authorized user" do + snippets = SnippetsFinder.new(nil).execute + + expect(snippets).to include(snippet3, project_snippet3) + expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2) + end + + it "returns all public and authored snippets for external user" do + external_user = create(:user, :external) + authored_snippet = create(:personal_snippet, :internal, author: external_user) + + snippets = SnippetsFinder.new(external_user).execute + + expect(snippets).to include(snippet3, project_snippet3, authored_snippet) + expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2) end end - context ':public filter' do + context 'filter by visibility' do let!(:snippet1) { create(:personal_snippet, :private) } let!(:snippet2) { create(:personal_snippet, :internal) } let!(:snippet3) { create(:personal_snippet, :public) } - it "returns public public snippets" do - snippets = described_class.new.execute(nil, filter: :public) + it "returns public snippets when visibility is PUBLIC" do + snippets = SnippetsFinder.new(nil, visibility: Snippet::PUBLIC).execute expect(snippets).to include(snippet3) expect(snippets).not_to include(snippet1, snippet2) end end - context ':by_user filter' do + context 'filter by scope' do + let!(:snippet1) { create(:personal_snippet, :private, author: user) } + let!(:snippet2) { create(:personal_snippet, :internal, author: user) } + let!(:snippet3) { create(:personal_snippet, :public, author: user) } + + it "returns all snippets for 'all' scope" do + snippets = SnippetsFinder.new(user, scope: :all).execute + + expect(snippets).to include(snippet1, snippet2, snippet3) + end + + it "returns all snippets for 'are_private' scope" do + snippets = SnippetsFinder.new(user, scope: :are_private).execute + + expect(snippets).to include(snippet1) + expect(snippets).not_to include(snippet2, snippet3) + end + + it "returns all snippets for 'are_interna;' scope" do + snippets = SnippetsFinder.new(user, scope: :are_internal).execute + + expect(snippets).to include(snippet2) + expect(snippets).not_to include(snippet1, snippet3) + end + + it "returns all snippets for 'are_private' scope" do + snippets = SnippetsFinder.new(user, scope: :are_public).execute + + expect(snippets).to include(snippet3) + expect(snippets).not_to include(snippet1, snippet2) + end + end + + context 'filter by author' do let!(:snippet1) { create(:personal_snippet, :private, author: user) } let!(:snippet2) { create(:personal_snippet, :internal, author: user) } let!(:snippet3) { create(:personal_snippet, :public, author: user) } it "returns all public and internal snippets" do - snippets = described_class.new.execute(user1, filter: :by_user, user: user) + snippets = SnippetsFinder.new(user1, author: user).execute + expect(snippets).to include(snippet2, snippet3) expect(snippets).not_to include(snippet1) end it "returns internal snippets" do - snippets = described_class.new.execute(user, filter: :by_user, user: user, scope: "are_internal") + snippets = SnippetsFinder.new(user, author: user, visibility: Snippet::INTERNAL).execute + expect(snippets).to include(snippet2) expect(snippets).not_to include(snippet1, snippet3) end it "returns private snippets" do - snippets = described_class.new.execute(user, filter: :by_user, user: user, scope: "are_private") + snippets = SnippetsFinder.new(user, author: user, visibility: Snippet::PRIVATE).execute + expect(snippets).to include(snippet1) expect(snippets).not_to include(snippet2, snippet3) end it "returns public snippets" do - snippets = described_class.new.execute(user, filter: :by_user, user: user, scope: "are_public") + snippets = SnippetsFinder.new(user, author: user, visibility: Snippet::PUBLIC).execute + expect(snippets).to include(snippet3) expect(snippets).not_to include(snippet1, snippet2) end it "returns all snippets" do - snippets = described_class.new.execute(user, filter: :by_user, user: user) + snippets = SnippetsFinder.new(user, author: user).execute + expect(snippets).to include(snippet1, snippet2, snippet3) end it "returns only public snippets if unauthenticated user" do - snippets = described_class.new.execute(nil, filter: :by_user, user: user) + snippets = SnippetsFinder.new(nil, author: user).execute + expect(snippets).to include(snippet3) expect(snippets).not_to include(snippet2, snippet1) end end - context 'by_project filter' do + context 'filter by project' do before do @snippet1 = create(:project_snippet, :private, project: project1) @snippet2 = create(:project_snippet, :internal, project: project1) @@ -88,43 +154,52 @@ describe SnippetsFinder do end it "returns public snippets for unauthorized user" do - snippets = described_class.new.execute(nil, filter: :by_project, project: project1) + snippets = SnippetsFinder.new(nil, project: project1).execute + expect(snippets).to include(@snippet3) expect(snippets).not_to include(@snippet1, @snippet2) end it "returns public and internal snippets for non project members" do - snippets = described_class.new.execute(user, filter: :by_project, project: project1) + snippets = SnippetsFinder.new(user, project: project1).execute + expect(snippets).to include(@snippet2, @snippet3) expect(snippets).not_to include(@snippet1) end it "returns public snippets for non project members" do - snippets = described_class.new.execute(user, filter: :by_project, project: project1, scope: "are_public") + snippets = SnippetsFinder.new(user, project: project1, visibility: Snippet::PUBLIC).execute + expect(snippets).to include(@snippet3) expect(snippets).not_to include(@snippet1, @snippet2) end it "returns internal snippets for non project members" do - snippets = described_class.new.execute(user, filter: :by_project, project: project1, scope: "are_internal") + snippets = SnippetsFinder.new(user, project: project1, visibility: Snippet::INTERNAL).execute + expect(snippets).to include(@snippet2) expect(snippets).not_to include(@snippet1, @snippet3) end it "does not return private snippets for non project members" do - snippets = described_class.new.execute(user, filter: :by_project, project: project1, scope: "are_private") + snippets = SnippetsFinder.new(user, project: project1, visibility: Snippet::PRIVATE).execute + expect(snippets).not_to include(@snippet1, @snippet2, @snippet3) end it "returns all snippets for project members" do project1.team << [user, :developer] - snippets = described_class.new.execute(user, filter: :by_project, project: project1) + + snippets = SnippetsFinder.new(user, project: project1).execute + expect(snippets).to include(@snippet1, @snippet2, @snippet3) end it "returns private snippets for project members" do project1.team << [user, :developer] - snippets = described_class.new.execute(user, filter: :by_project, project: project1, scope: "are_private") + + snippets = SnippetsFinder.new(user, project: project1, visibility: Snippet::PRIVATE).execute + expect(snippets).to include(@snippet1) end end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 75b1fc7e216..1e5c96fe593 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -131,46 +131,6 @@ describe Snippet, models: true do end end - describe '.accessible_to' do - let(:author) { create(:author) } - let(:project) { create(:empty_project) } - - let!(:public_snippet) { create(:snippet, :public) } - let!(:internal_snippet) { create(:snippet, :internal) } - let!(:private_snippet) { create(:snippet, :private, author: author) } - - let!(:project_public_snippet) { create(:snippet, :public, project: project) } - let!(:project_internal_snippet) { create(:snippet, :internal, project: project) } - let!(:project_private_snippet) { create(:snippet, :private, project: project) } - - it 'returns only public snippets when user is blank' do - expect(described_class.accessible_to(nil)).to match_array [public_snippet, project_public_snippet] - end - - it 'returns only public, and internal snippets for regular users' do - user = create(:user) - - expect(described_class.accessible_to(user)).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet] - end - - it 'returns public, internal snippets and project private snippets for project members' do - member = create(:user) - project.team << [member, :developer] - - expect(described_class.accessible_to(member)).to match_array [public_snippet, internal_snippet, project_public_snippet, project_internal_snippet, project_private_snippet] - end - - it 'returns private snippets where the user is the author' do - expect(described_class.accessible_to(author)).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet] - end - - it 'returns all snippets when for admins' do - admin = create(:admin) - - expect(described_class.accessible_to(admin)).to match_array [public_snippet, internal_snippet, private_snippet, project_public_snippet, project_internal_snippet, project_private_snippet] - end - end - describe '#participants' do let(:project) { create(:empty_project, :public) } let(:snippet) { create(:snippet, content: 'foo', project: project) } diff --git a/spec/policies/project_snippet_policy_spec.rb b/spec/policies/project_snippet_policy_spec.rb index d0758af57dd..e1771b636b8 100644 --- a/spec/policies/project_snippet_policy_spec.rb +++ b/spec/policies/project_snippet_policy_spec.rb @@ -1,7 +1,9 @@ require 'spec_helper' describe ProjectSnippetPolicy, models: true do - let(:current_user) { create(:user) } + let(:regular_user) { create(:user) } + let(:external_user) { create(:user, :external) } + let(:project) { create(:empty_project) } let(:author_permissions) do [ @@ -10,13 +12,15 @@ describe ProjectSnippetPolicy, models: true do ] end - subject { described_class.abilities(current_user, project_snippet).to_set } + def abilities(user, snippet_visibility) + snippet = create(:project_snippet, snippet_visibility, project: project) + + described_class.abilities(user, snippet).to_set + end context 'public snippet' do - let(:project_snippet) { create(:project_snippet, :public) } - context 'no user' do - let(:current_user) { nil } + subject { abilities(nil, :public) } it do is_expected.to include(:read_project_snippet) @@ -25,6 +29,17 @@ describe ProjectSnippetPolicy, models: true do end context 'regular user' do + subject { abilities(regular_user, :public) } + + it do + is_expected.to include(:read_project_snippet) + is_expected.not_to include(*author_permissions) + end + end + + context 'external user' do + subject { abilities(external_user, :public) } + it do is_expected.to include(:read_project_snippet) is_expected.not_to include(*author_permissions) @@ -33,10 +48,8 @@ describe ProjectSnippetPolicy, models: true do end context 'internal snippet' do - let(:project_snippet) { create(:project_snippet, :internal) } - context 'no user' do - let(:current_user) { nil } + subject { abilities(nil, :internal) } it do is_expected.not_to include(:read_project_snippet) @@ -45,6 +58,28 @@ describe ProjectSnippetPolicy, models: true do end context 'regular user' do + subject { abilities(regular_user, :internal) } + + it do + is_expected.to include(:read_project_snippet) + is_expected.not_to include(*author_permissions) + end + end + + context 'external user' do + subject { abilities(external_user, :internal) } + + it do + is_expected.not_to include(:read_project_snippet) + is_expected.not_to include(*author_permissions) + end + end + + context 'project team member external user' do + subject { abilities(external_user, :internal) } + + before { project.team << [external_user, :developer] } + it do is_expected.to include(:read_project_snippet) is_expected.not_to include(*author_permissions) @@ -53,10 +88,8 @@ describe ProjectSnippetPolicy, models: true do end context 'private snippet' do - let(:project_snippet) { create(:project_snippet, :private) } - context 'no user' do - let(:current_user) { nil } + subject { abilities(nil, :private) } it do is_expected.not_to include(:read_project_snippet) @@ -65,6 +98,8 @@ describe ProjectSnippetPolicy, models: true do end context 'regular user' do + subject { abilities(regular_user, :private) } + it do is_expected.not_to include(:read_project_snippet) is_expected.not_to include(*author_permissions) @@ -72,7 +107,9 @@ describe ProjectSnippetPolicy, models: true do end context 'snippet author' do - let(:project_snippet) { create(:project_snippet, :private, author: current_user) } + let(:snippet) { create(:project_snippet, :private, author: regular_user) } + + subject { described_class.abilities(regular_user, snippet).to_set } it do is_expected.to include(:read_project_snippet) @@ -80,8 +117,21 @@ describe ProjectSnippetPolicy, models: true do end end - context 'project team member' do - before { project_snippet.project.team << [current_user, :developer] } + context 'project team member normal user' do + subject { abilities(regular_user, :private) } + + before { project.team << [regular_user, :developer] } + + it do + is_expected.to include(:read_project_snippet) + is_expected.not_to include(*author_permissions) + end + end + + context 'project team member external user' do + subject { abilities(external_user, :private) } + + before { project.team << [external_user, :developer] } it do is_expected.to include(:read_project_snippet) @@ -90,7 +140,7 @@ describe ProjectSnippetPolicy, models: true do end context 'admin user' do - let(:current_user) { create(:admin) } + subject { abilities(create(:admin), :private) } it do is_expected.to include(:read_project_snippet) From e5e94618c573fc85118ae76c1582be1ab30a72af Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 2 May 2017 21:32:14 +0000 Subject: [PATCH 21/38] Merge branch 'fix-hamlit-xss' into 'security-9-1' New Hamlit XSS fix, does not include extraneous changes See merge request !2095 --- app/views/import/base/create.js.haml | 2 +- app/views/projects/imports/new.html.haml | 2 +- app/views/projects/wikis/git_access.html.haml | 2 +- changelogs/unreleased/hamlit-xss-fix.yml | 4 ++++ .../projects/imports/new.html.haml_spec.rb | 22 +++++++++++++++++++ 5 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/hamlit-xss-fix.yml create mode 100644 spec/views/projects/imports/new.html.haml_spec.rb diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml index 8e929538351..57e8c3ca1e1 100644 --- a/app/views/import/base/create.js.haml +++ b/app/views/import/base/create.js.haml @@ -10,4 +10,4 @@ - else :plain job = $("tr#repo_#{@repo_id}") - job.find(".import-actions").html(" Error saving project: #{escape_javascript(@project.errors.full_messages.join(','))}") + job.find(".import-actions").html(" Error saving project: #{escape_javascript(h(@project.errors.full_messages.join(',')))}") diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index 2cd8d03e30e..25a87411cac 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -10,7 +10,7 @@ .panel-body %pre :preserve - #{sanitize_repo_path(@project, @project.import_error)} + #{h(sanitize_repo_path(@project, @project.import_error))} = form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f| = render "shared/import_form", f: f diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index fb0efd85dcd..68862206248 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -28,7 +28,7 @@ %h3 Clone your wiki %pre.dark :preserve - git clone #{ content_tag(:span, default_url_to_repo(@project_wiki), class: 'clone')} + git clone #{ content_tag(:span, h(default_url_to_repo(@project_wiki)), class: 'clone')} cd #{h @project_wiki.path} %h3 Start Gollum and edit locally diff --git a/changelogs/unreleased/hamlit-xss-fix.yml b/changelogs/unreleased/hamlit-xss-fix.yml new file mode 100644 index 00000000000..ba4713846e9 --- /dev/null +++ b/changelogs/unreleased/hamlit-xss-fix.yml @@ -0,0 +1,4 @@ +--- +title: Fix for XSS in project import view caused by Hamlit filter usage. +merge_request: +author: diff --git a/spec/views/projects/imports/new.html.haml_spec.rb b/spec/views/projects/imports/new.html.haml_spec.rb new file mode 100644 index 00000000000..9b293065797 --- /dev/null +++ b/spec/views/projects/imports/new.html.haml_spec.rb @@ -0,0 +1,22 @@ +require "spec_helper" + +describe "projects/imports/new.html.haml" do + let(:user) { create(:user) } + + context 'when import fails' do + let(:project) { create(:project_empty_repo, import_status: :failed, import_error: 'Foo', import_type: :gitlab_project, import_source: '/var/opt/gitlab/gitlab-rails/shared/tmp/project_exports/uploads/t.tar.gz', import_url: nil) } + + before do + sign_in(user) + project.team << [user, :master] + end + + it "escapes HTML in import errors" do + assign(:project, project) + + render + + expect(rendered).not_to have_link('Foo', href: "http://googl.com") + end + end +end From ea4eb460479a24fea9ee890c8ba8f6f4dec7f44b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 3 May 2017 23:51:25 +0000 Subject: [PATCH 22/38] Merge branch 'tc-fix-private-subgroups-shown' into 'security' Use GroupsFinder to find subgroups the user has access to See merge request !2096 --- app/controllers/explore/groups_controller.rb | 2 +- app/controllers/groups_controller.rb | 2 +- app/finders/groups_finder.rb | 20 +++++-- .../tc-fix-private-subgroups-shown.yml | 4 ++ lib/api/groups.rb | 2 +- lib/api/v3/groups.rb | 2 +- spec/controllers/groups_controller_spec.rb | 35 ++++++++++++ spec/finders/groups_finder_spec.rb | 57 +++++++++++++++---- 8 files changed, 105 insertions(+), 19 deletions(-) create mode 100644 changelogs/unreleased/tc-fix-private-subgroups-shown.yml diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb index 68228c095da..81883c543ba 100644 --- a/app/controllers/explore/groups_controller.rb +++ b/app/controllers/explore/groups_controller.rb @@ -1,6 +1,6 @@ class Explore::GroupsController < Explore::ApplicationController def index - @groups = GroupsFinder.new.execute(current_user) + @groups = GroupsFinder.new(current_user).execute @groups = @groups.search(params[:filter_groups]) if params[:filter_groups].present? @groups = @groups.sort(@sort = params[:sort]) @groups = @groups.page(params[:page]) diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 46c3ff10694..1515173d0ac 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -64,7 +64,7 @@ class GroupsController < Groups::ApplicationController end def subgroups - @nested_groups = group.children + @nested_groups = GroupsFinder.new(current_user, parent: group).execute @nested_groups = @nested_groups.search(params[:filter_groups]) if params[:filter_groups].present? end diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb index d932a17883f..f68610e197c 100644 --- a/app/finders/groups_finder.rb +++ b/app/finders/groups_finder.rb @@ -1,13 +1,19 @@ class GroupsFinder < UnionFinder - def execute(current_user = nil) - segments = all_groups(current_user) + def initialize(current_user = nil, params = {}) + @current_user = current_user + @params = params + end - find_union(segments, Group).with_route.order_id_desc + def execute + groups = find_union(all_groups, Group).with_route.order_id_desc + by_parent(groups) end private - def all_groups(current_user) + attr_reader :current_user, :params + + def all_groups groups = [] groups << current_user.authorized_groups if current_user @@ -15,4 +21,10 @@ class GroupsFinder < UnionFinder groups end + + def by_parent(groups) + return groups unless params[:parent] + + groups.where(parent: params[:parent]) + end end diff --git a/changelogs/unreleased/tc-fix-private-subgroups-shown.yml b/changelogs/unreleased/tc-fix-private-subgroups-shown.yml new file mode 100644 index 00000000000..82e03921854 --- /dev/null +++ b/changelogs/unreleased/tc-fix-private-subgroups-shown.yml @@ -0,0 +1,4 @@ +--- +title: "Do not show private groups on subgroups page if user doesn't have access to" +merge_request: +author: diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 09d105f6b4c..9ccc75681f9 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -52,7 +52,7 @@ module API elsif current_user.admin Group.all elsif params[:all_available] - GroupsFinder.new.execute(current_user) + GroupsFinder.new(current_user).execute else current_user.groups end diff --git a/lib/api/v3/groups.rb b/lib/api/v3/groups.rb index 63d464b926b..dbf7a3cf785 100644 --- a/lib/api/v3/groups.rb +++ b/lib/api/v3/groups.rb @@ -45,7 +45,7 @@ module API groups = if current_user.admin Group.all elsif params[:all_available] - GroupsFinder.new.execute(current_user) + GroupsFinder.new(current_user).execute else current_user.groups end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 073b87a1cb4..4c8d82a1677 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -26,6 +26,41 @@ describe GroupsController do end end + describe 'GET #subgroups' do + let!(:public_subgroup) { create(:group, :public, parent: group) } + let!(:private_subgroup) { create(:group, :private, parent: group) } + + context 'as a user' do + before do + sign_in(user) + end + + it 'shows the public subgroups' do + get :subgroups, id: group.to_param + + expect(assigns(:nested_groups)).to contain_exactly(public_subgroup) + end + + context 'being member' do + it 'shows public and private subgroups the user is member of' do + private_subgroup.add_guest(user) + + get :subgroups, id: group.to_param + + expect(assigns(:nested_groups)).to contain_exactly(public_subgroup, private_subgroup) + end + end + end + + context 'as a guest' do + it 'shows the public subgroups' do + get :subgroups, id: group.to_param + + expect(assigns(:nested_groups)).to contain_exactly(public_subgroup) + end + end + end + describe 'GET #issues' do let(:issue_1) { create(:issue, project: project) } let(:issue_2) { create(:issue, project: project) } diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb index d5d111e8d15..5b3591550c1 100644 --- a/spec/finders/groups_finder_spec.rb +++ b/spec/finders/groups_finder_spec.rb @@ -3,29 +3,64 @@ require 'spec_helper' describe GroupsFinder do describe '#execute' do let(:user) { create(:user) } - let!(:private_group) { create(:group, :private) } - let!(:internal_group) { create(:group, :internal) } - let!(:public_group) { create(:group, :public) } - let(:finder) { described_class.new } - describe 'execute' do - describe 'without a user' do - subject { finder.execute } + context 'root level groups' do + let!(:private_group) { create(:group, :private) } + let!(:internal_group) { create(:group, :internal) } + let!(:public_group) { create(:group, :public) } + + context 'without a user' do + subject { described_class.new.execute } it { is_expected.to eq([public_group]) } end - describe 'with a user' do - subject { finder.execute(user) } + context 'with a user' do + subject { described_class.new(user).execute } context 'normal user' do - it { is_expected.to eq([public_group, internal_group]) } + it { is_expected.to contain_exactly(public_group, internal_group) } end context 'external user' do let(:user) { create(:user, external: true) } - it { is_expected.to eq([public_group]) } + it { is_expected.to contain_exactly(public_group) } + end + + context 'user is member of the private group' do + before do + private_group.add_guest(user) + end + + it { is_expected.to contain_exactly(public_group, internal_group, private_group) } + end + end + end + + context 'subgroups' do + let!(:parent_group) { create(:group, :public) } + let!(:public_subgroup) { create(:group, :public, parent: parent_group) } + let!(:internal_subgroup) { create(:group, :internal, parent: parent_group) } + let!(:private_subgroup) { create(:group, :private, parent: parent_group) } + + context 'without a user' do + it 'only returns public subgroups' do + expect(described_class.new(nil, parent: parent_group).execute).to contain_exactly(public_subgroup) + end + end + + context 'with a user' do + it 'returns public and internal subgroups' do + expect(described_class.new(user, parent: parent_group).execute).to contain_exactly(public_subgroup, internal_subgroup) + end + + context 'being member' do + it 'returns public subgroups, internal subgroups, and private subgroups user is member of' do + private_subgroup.add_guest(user) + + expect(described_class.new(user, parent: parent_group).execute).to contain_exactly(public_subgroup, internal_subgroup, private_subgroup) + end end end end From 19e91e4ba9f037b087640d55b3385febe243cf46 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Wed, 10 May 2017 11:21:33 +0200 Subject: [PATCH 23/38] Use `described_class` in SnippetsFinder-spec --- spec/finders/snippets_finder_spec.rb | 42 ++++++++++++++-------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb index 9171fb9c4af..35f1683eef9 100644 --- a/spec/finders/snippets_finder_spec.rb +++ b/spec/finders/snippets_finder_spec.rb @@ -29,14 +29,14 @@ describe SnippetsFinder do end it "returns all public and internal snippets for normal user" do - snippets = SnippetsFinder.new(user).execute + snippets = described_class.new(user).execute expect(snippets).to include(snippet2, snippet3, project_snippet2, project_snippet3) expect(snippets).not_to include(snippet1, project_snippet1) end it "returns all public snippets for non authorized user" do - snippets = SnippetsFinder.new(nil).execute + snippets = described_class.new(nil).execute expect(snippets).to include(snippet3, project_snippet3) expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2) @@ -46,7 +46,7 @@ describe SnippetsFinder do external_user = create(:user, :external) authored_snippet = create(:personal_snippet, :internal, author: external_user) - snippets = SnippetsFinder.new(external_user).execute + snippets = described_class.new(external_user).execute expect(snippets).to include(snippet3, project_snippet3, authored_snippet) expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2) @@ -59,7 +59,7 @@ describe SnippetsFinder do let!(:snippet3) { create(:personal_snippet, :public) } it "returns public snippets when visibility is PUBLIC" do - snippets = SnippetsFinder.new(nil, visibility: Snippet::PUBLIC).execute + snippets = described_class.new(nil, visibility: Snippet::PUBLIC).execute expect(snippets).to include(snippet3) expect(snippets).not_to include(snippet1, snippet2) @@ -72,27 +72,27 @@ describe SnippetsFinder do let!(:snippet3) { create(:personal_snippet, :public, author: user) } it "returns all snippets for 'all' scope" do - snippets = SnippetsFinder.new(user, scope: :all).execute + snippets = described_class.new(user, scope: :all).execute expect(snippets).to include(snippet1, snippet2, snippet3) end it "returns all snippets for 'are_private' scope" do - snippets = SnippetsFinder.new(user, scope: :are_private).execute + snippets = described_class.new(user, scope: :are_private).execute expect(snippets).to include(snippet1) expect(snippets).not_to include(snippet2, snippet3) end it "returns all snippets for 'are_interna;' scope" do - snippets = SnippetsFinder.new(user, scope: :are_internal).execute + snippets = described_class.new(user, scope: :are_internal).execute expect(snippets).to include(snippet2) expect(snippets).not_to include(snippet1, snippet3) end it "returns all snippets for 'are_private' scope" do - snippets = SnippetsFinder.new(user, scope: :are_public).execute + snippets = described_class.new(user, scope: :are_public).execute expect(snippets).to include(snippet3) expect(snippets).not_to include(snippet1, snippet2) @@ -105,41 +105,41 @@ describe SnippetsFinder do let!(:snippet3) { create(:personal_snippet, :public, author: user) } it "returns all public and internal snippets" do - snippets = SnippetsFinder.new(user1, author: user).execute + snippets = described_class.new(user1, author: user).execute expect(snippets).to include(snippet2, snippet3) expect(snippets).not_to include(snippet1) end it "returns internal snippets" do - snippets = SnippetsFinder.new(user, author: user, visibility: Snippet::INTERNAL).execute + snippets = described_class.new(user, author: user, visibility: Snippet::INTERNAL).execute expect(snippets).to include(snippet2) expect(snippets).not_to include(snippet1, snippet3) end it "returns private snippets" do - snippets = SnippetsFinder.new(user, author: user, visibility: Snippet::PRIVATE).execute + snippets = described_class.new(user, author: user, visibility: Snippet::PRIVATE).execute expect(snippets).to include(snippet1) expect(snippets).not_to include(snippet2, snippet3) end it "returns public snippets" do - snippets = SnippetsFinder.new(user, author: user, visibility: Snippet::PUBLIC).execute + snippets = described_class.new(user, author: user, visibility: Snippet::PUBLIC).execute expect(snippets).to include(snippet3) expect(snippets).not_to include(snippet1, snippet2) end it "returns all snippets" do - snippets = SnippetsFinder.new(user, author: user).execute + snippets = described_class.new(user, author: user).execute expect(snippets).to include(snippet1, snippet2, snippet3) end it "returns only public snippets if unauthenticated user" do - snippets = SnippetsFinder.new(nil, author: user).execute + snippets = described_class.new(nil, author: user).execute expect(snippets).to include(snippet3) expect(snippets).not_to include(snippet2, snippet1) @@ -154,35 +154,35 @@ describe SnippetsFinder do end it "returns public snippets for unauthorized user" do - snippets = SnippetsFinder.new(nil, project: project1).execute + snippets = described_class.new(nil, project: project1).execute expect(snippets).to include(@snippet3) expect(snippets).not_to include(@snippet1, @snippet2) end it "returns public and internal snippets for non project members" do - snippets = SnippetsFinder.new(user, project: project1).execute + snippets = described_class.new(user, project: project1).execute expect(snippets).to include(@snippet2, @snippet3) expect(snippets).not_to include(@snippet1) end it "returns public snippets for non project members" do - snippets = SnippetsFinder.new(user, project: project1, visibility: Snippet::PUBLIC).execute + snippets = described_class.new(user, project: project1, visibility: Snippet::PUBLIC).execute expect(snippets).to include(@snippet3) expect(snippets).not_to include(@snippet1, @snippet2) end it "returns internal snippets for non project members" do - snippets = SnippetsFinder.new(user, project: project1, visibility: Snippet::INTERNAL).execute + snippets = described_class.new(user, project: project1, visibility: Snippet::INTERNAL).execute expect(snippets).to include(@snippet2) expect(snippets).not_to include(@snippet1, @snippet3) end it "does not return private snippets for non project members" do - snippets = SnippetsFinder.new(user, project: project1, visibility: Snippet::PRIVATE).execute + snippets = described_class.new(user, project: project1, visibility: Snippet::PRIVATE).execute expect(snippets).not_to include(@snippet1, @snippet2, @snippet3) end @@ -190,7 +190,7 @@ describe SnippetsFinder do it "returns all snippets for project members" do project1.team << [user, :developer] - snippets = SnippetsFinder.new(user, project: project1).execute + snippets = described_class.new(user, project: project1).execute expect(snippets).to include(@snippet1, @snippet2, @snippet3) end @@ -198,7 +198,7 @@ describe SnippetsFinder do it "returns private snippets for project members" do project1.team << [user, :developer] - snippets = SnippetsFinder.new(user, project: project1, visibility: Snippet::PRIVATE).execute + snippets = described_class.new(user, project: project1, visibility: Snippet::PRIVATE).execute expect(snippets).to include(@snippet1) end From ebd8b7f60f41358df562625a4692f352b86b8c80 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Wed, 10 May 2017 12:03:29 +0200 Subject: [PATCH 24/38] Use new SnippetsFinder signature in API --- lib/api/helpers.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 86bf567fe69..c643ea8e5a7 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -91,8 +91,8 @@ module API end def find_project_snippet(id) - finder_params = { filter: :by_project, project: user_project } - SnippetsFinder.new.execute(current_user, finder_params).find(id) + finder_params = { project: user_project } + SnippetsFinder.new(current_user, finder_params).execute.find(id) end def find_merge_request_with_access(iid, access_level = :read_merge_request) From ca9033bd5dcf8c3abc9fe17c2a23da495d6e60d4 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Wed, 10 May 2017 15:09:36 +0000 Subject: [PATCH 25/38] Find the nothing-here-block before asserting page content when on explore page of shortcuts_spec.rb --- spec/features/dashboard/shortcuts_spec.rb | 33 ++++++++++++----------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index 4c9adcabe34..af7ef55a57e 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -feature 'Dashboard shortcuts', feature: true, js: true do +feature 'Dashboard shortcuts', :feature, :js do context 'logged in' do before do login_as :user @@ -8,21 +8,21 @@ feature 'Dashboard shortcuts', feature: true, js: true do end scenario 'Navigate to tabs' do - find('body').native.send_keys([:shift, 'P']) - - check_page_title('Projects') - - find('body').native.send_key([:shift, 'I']) + find('body').send_key([:shift, 'I']) check_page_title('Issues') - find('body').native.send_key([:shift, 'M']) + find('body').send_key([:shift, 'M']) check_page_title('Merge Requests') - find('body').native.send_keys([:shift, 'T']) + find('body').send_keys([:shift, 'T']) check_page_title('Todos') + + find('body').send_keys([:shift, 'P']) + + check_page_title('Projects') end end @@ -32,21 +32,24 @@ feature 'Dashboard shortcuts', feature: true, js: true do end scenario 'Navigate to tabs' do - find('body').native.send_keys([:shift, 'P']) - - expect(page).to have_content('No projects found') - - find('body').native.send_keys([:shift, 'G']) + find('body').send_keys([:shift, 'G']) + find('.nothing-here-block') expect(page).to have_content('No public groups') - find('body').native.send_keys([:shift, 'S']) + find('body').send_keys([:shift, 'S']) + find('.nothing-here-block') expect(page).to have_selector('.snippets-list-holder') + + find('body').send_keys([:shift, 'P']) + + find('.nothing-here-block') + expect(page).to have_content('No projects found') end end def check_page_title(title) expect(find('.header-content .title')).to have_content(title) end -end +end \ No newline at end of file From f328f38e5b1f013c3b21f6f572cc22c71aad8fa3 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 3 May 2017 18:21:14 -0400 Subject: [PATCH 26/38] Disallow multiselect for Milestone dropdown --- .../boards/components/board_sidebar.js | 3 + app/assets/javascripts/milestone_select.js | 37 ++++++++---- .../components/sidebar/_milestone.html.haml | 3 +- .../issuable/_milestone_dropdown.html.haml | 2 +- app/views/shared/issuable/_sidebar.html.haml | 2 +- ...ne-dropdown-should-not-be-multi-select.yml | 4 ++ .../dashboard/milestone_filter_spec.rb | 59 +++++++++++++++++++ 7 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 changelogs/unreleased/31474-issue-boards-sidebar-milestone-dropdown-should-not-be-multi-select.yml create mode 100644 spec/features/dashboard/milestone_filter_spec.rb diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 317cef9f227..9bcea302da2 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -36,6 +36,9 @@ gl.issueBoards.BoardSidebar = Vue.extend({ }, assigneeId() { return this.issue.assignee ? this.issue.assignee.id : 0; + }, + milestoneTitle() { + return this.issue.milestone ? this.issue.milestone.title : 'No Milestone'; } }, watch: { diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 11e68c0a3be..9d481d7c003 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -18,12 +18,11 @@ } $els.each(function(i, dropdown) { - var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, showStarted, useId, showMenuAbove; + var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, defaultNo, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, selectedMilestoneDefault, showAny, showNo, showUpcoming, showStarted, useId, showMenuAbove; $dropdown = $(dropdown); projectId = $dropdown.data('project-id'); milestonesUrl = $dropdown.data('milestones'); issueUpdateURL = $dropdown.data('issueUpdate'); - selectedMilestone = $dropdown.data('selected'); showNo = $dropdown.data('show-no'); showAny = $dropdown.data('show-any'); showMenuAbove = $dropdown.data('showMenuAbove'); @@ -31,6 +30,7 @@ showStarted = $dropdown.data('show-started'); useId = $dropdown.data('use-id'); defaultLabel = $dropdown.data('default-label'); + defaultNo = $dropdown.data('default-no'); issuableId = $dropdown.data('issuable-id'); abilityName = $dropdown.data('ability-name'); $selectbox = $dropdown.closest('.selectbox'); @@ -38,6 +38,9 @@ $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); $value = $block.find('.value'); $loading = $block.find('.block-loading').fadeOut(); + selectedMilestoneDefault = (showAny ? '' : null); + selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault); + selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault; if (issueUpdateURL) { milestoneLinkTemplate = _.template('<%- title %>'); milestoneLinkNoneTemplate = 'None'; @@ -86,8 +89,18 @@ if (showMenuAbove) { $dropdown.data('glDropdown').positionMenuAbove(); } + $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active'); }); }, + renderRow: function(milestone) { + return ` +
  • + + ${_.escape(milestone.title)} + +
  • + `; + }, filterable: true, search: { fields: ['title'] @@ -120,15 +133,24 @@ // display:block overrides the hide-collapse rule return $value.css('display', ''); }, + opened: function(e) { + const $el = $(e.currentTarget); + if ($dropdown.hasClass('js-issue-board-sidebar')) { + selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault; + } + $('a.is-active', $el).removeClass('is-active'); + $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active'); + }, vue: $dropdown.hasClass('js-issue-board-sidebar'), clicked: function(options) { const { $el, e } = options; let selected = options.selectedObj; - - var data, isIssueIndex, isMRIndex, page, boardsStore; + var data, isIssueIndex, isMRIndex, isSelecting, page, boardsStore; page = $('body').data('page'); isIssueIndex = page === 'projects:issues:index'; isMRIndex = (page === page && page === 'projects:merge_requests:index'); + isSelecting = (selected.name !== selectedMilestone); + selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault; if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { e.preventDefault(); return; @@ -142,16 +164,11 @@ boardsStore[$dropdown.data('field-name')] = selected.name; e.preventDefault(); } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { - if (selected.name != null) { - selectedMilestone = selected.name; - } else { - selectedMilestone = ''; - } return Issuable.filterResults($dropdown.closest('form')); } else if ($dropdown.hasClass('js-filter-submit')) { return $dropdown.closest('form').submit(); } else if ($dropdown.hasClass('js-issue-board-sidebar')) { - if (selected.id !== -1) { + if (selected.id !== -1 && isSelecting) { gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({ id: selected.id, title: selected.name diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/projects/boards/components/sidebar/_milestone.html.haml index 190e7290303..4e46351bf8a 100644 --- a/app/views/projects/boards/components/sidebar/_milestone.html.haml +++ b/app/views/projects/boards/components/sidebar/_milestone.html.haml @@ -16,7 +16,8 @@ name: "issue[milestone_id]", "v-if" => "issue.milestone" } .dropdown - %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true" }, + %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true", default_no: "true" }, + ":data-selected" => "milestoneTitle", ":data-issuable-id" => "issue.id", ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } Milestone diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index f0d50828e2a..6750921338a 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -6,7 +6,7 @@ - if selected.present? || params[:milestone_title].present? = hidden_field_tag(name, name == :milestone_title ? selected_text : selected.id) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", - placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, show_started: show_started, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do + placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, show_started: show_started, field_name: name, selected: selected_text, project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if project %ul.dropdown-footer-list - if can? current_user, :admin_milestone, project diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 44e624c15a7..b2dd9b582fa 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -91,7 +91,7 @@ .selectbox.hide-collapsed = f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil - = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true }}) + = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true }}) - if issuable.has_attribute?(:time_estimate) #issuable-time-tracker.block // Fallback while content is loading diff --git a/changelogs/unreleased/31474-issue-boards-sidebar-milestone-dropdown-should-not-be-multi-select.yml b/changelogs/unreleased/31474-issue-boards-sidebar-milestone-dropdown-should-not-be-multi-select.yml new file mode 100644 index 00000000000..88e79e3b6ea --- /dev/null +++ b/changelogs/unreleased/31474-issue-boards-sidebar-milestone-dropdown-should-not-be-multi-select.yml @@ -0,0 +1,4 @@ +--- +title: Disallow multiple selections for Milestone dropdown +merge_request: 11084 +author: diff --git a/spec/features/dashboard/milestone_filter_spec.rb b/spec/features/dashboard/milestone_filter_spec.rb new file mode 100644 index 00000000000..628627f70d4 --- /dev/null +++ b/spec/features/dashboard/milestone_filter_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe 'Dashboard > milestone filter', feature: true, js: true do + let(:user) { create(:user) } + let(:project) { create(:project, name: 'test', namespace: user.namespace) } + let(:milestone) { create(:milestone, title: "v1.0", project: project) } + let(:milestone2) { create(:milestone, title: "v2.0", project: project) } + let!(:issue) { create :issue, author: user, project: project, milestone: milestone } + let!(:issue2) { create :issue, author: user, project: project, milestone: milestone2 } + + before do + login_as(user) + visit issues_dashboard_path(author_id: user.id) + end + + context 'default state' do + it 'shows issues with Any Milestone' do + page.all('.issue-info').each do |issue_info| + expect(issue_info.text).to match(/v\d.0/) + end + end + end + + context 'filtering by milestone' do + milestone_select = '.js-milestone-select' + + before do + find(milestone_select).click + + page.within('.dropdown-content') do + click_link 'v1.0' + end + + find(milestone_select).click + end + + it 'shows issues with Milestone v1.0' do + expect(find('.issues-list')).to have_selector('.issue', count: 1) + + find(milestone_select).click + + expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) + end + + it 'should not change active Milestone unless clicked' do + find(milestone_select).click + + expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) + + # open & close dropdown + find('.dropdown-menu-close').click + expect(find('.milestone-filter')).not_to have_selector('.dropdown.open') + find(milestone_select).click + + expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) + expect(find('.dropdown-content a.is-active')).to have_content('v1.0') + end + end +end From 851e69e55465404b920e3369b18c7de5b5f8958b Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 10 May 2017 16:40:46 +0100 Subject: [PATCH 27/38] Fixed the stage title translation in cycle analytics --- app/views/projects/cycle_analytics/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index b158a81471c..74255167352 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -51,7 +51,7 @@ %ul %li.stage-header %span.stage-name - {{ __('ProjectLifecycle|Stage') }} + {{ s__('ProjectLifecycle|Stage') }} %i.has-tooltip.fa.fa-question-circle{ "data-placement" => "top", title: _("The phase of the development lifecycle."), "aria-hidden" => "true" } %li.median-header %span.stage-name From 8480118d2baeec3113709febb6a54735c3758be2 Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Wed, 10 May 2017 15:53:11 +0000 Subject: [PATCH 28/38] Fix `send_key` to `send_keys` and add EOF newline --- spec/features/dashboard/shortcuts_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb index af7ef55a57e..349b948eaee 100644 --- a/spec/features/dashboard/shortcuts_spec.rb +++ b/spec/features/dashboard/shortcuts_spec.rb @@ -8,11 +8,11 @@ feature 'Dashboard shortcuts', :feature, :js do end scenario 'Navigate to tabs' do - find('body').send_key([:shift, 'I']) + find('body').send_keys([:shift, 'I']) check_page_title('Issues') - find('body').send_key([:shift, 'M']) + find('body').send_keys([:shift, 'M']) check_page_title('Merge Requests') @@ -52,4 +52,4 @@ feature 'Dashboard shortcuts', :feature, :js do def check_page_title(title) expect(find('.header-content .title')).to have_content(title) end -end \ No newline at end of file +end From d40e1f547ea9e31e822229bb808aaa6b9201f473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 3 May 2017 13:22:03 +0200 Subject: [PATCH 29/38] Enable the Style/TrailingCommaInLiteral cop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the EnforcedStyleForMultiline: no_comma option. Signed-off-by: Rémy Coutable --- .rubocop.yml | 3 ++- app/controllers/autocomplete_controller.rb | 2 +- app/controllers/concerns/lfs_request.rb | 6 +++--- app/controllers/health_controller.rb | 2 +- app/controllers/jwt_controller.rb | 2 +- app/controllers/projects/issues_controller.rb | 2 +- app/controllers/projects/lfs_api_controller.rb | 4 ++-- app/controllers/projects/pipelines_controller.rb | 2 +- app/controllers/projects/tree_controller.rb | 2 +- app/controllers/projects_controller.rb | 2 +- app/helpers/diff_helper.rb | 2 +- app/helpers/emails_helper.rb | 2 +- app/helpers/events_helper.rb | 2 +- app/helpers/explore_helper.rb | 2 +- app/helpers/markup_helper.rb | 2 +- app/helpers/merge_requests_helper.rb | 2 +- app/helpers/notes_helper.rb | 4 ++-- app/helpers/search_helper.rb | 6 +++--- app/helpers/selects_helper.rb | 2 +- app/helpers/sorting_helper.rb | 2 +- app/helpers/todos_helper.rb | 2 +- app/models/blob.rb | 2 +- app/models/commit.rb | 2 +- app/models/environment.rb | 2 +- app/models/hooks/web_hook.rb | 2 +- app/models/project.rb | 2 +- app/models/project_services/bamboo_service.rb | 2 +- .../chat_notification_service.rb | 2 +- .../project_services/emails_on_push_service.rb | 2 +- .../project_services/external_wiki_service.rb | 2 +- app/models/project_services/hipchat_service.rb | 2 +- app/models/project_services/irker_service.rb | 2 +- app/models/project_services/jira_service.rb | 2 +- .../project_services/kubernetes_service.rb | 2 +- .../project_services/microsoft_teams_service.rb | 2 +- app/models/project_services/mock_ci_service.rb | 2 +- .../project_services/pipelines_email_service.rb | 2 +- app/models/project_services/pushover_service.rb | 2 +- app/models/project_services/teamcity_service.rb | 4 ++-- app/services/akismet_service.rb | 2 +- app/services/audit_event_service.rb | 2 +- .../update_pages_configuration_service.rb | 2 +- app/services/system_hooks_service.rb | 2 +- config/initializers/static_files.rb | 2 +- .../20160810142633_remove_redundant_indexes.rb | 2 +- .../20160829114652_add_markdown_cache_columns.rb | 2 +- lib/api/internal.rb | 2 +- lib/api/projects.rb | 2 +- lib/api/services.rb | 6 +++--- lib/api/subscriptions.rb | 2 +- lib/api/v3/services.rb | 2 +- lib/api/v3/subscriptions.rb | 2 +- lib/ci/ansi2html.rb | 2 +- lib/ci/gitlab_ci_yaml_processor.rb | 2 +- lib/gitlab/access.rb | 6 +++--- lib/gitlab/chat_commands/command.rb | 2 +- lib/gitlab/cycle_analytics/permissions.rb | 2 +- lib/gitlab/data_builder/build.rb | 6 +++--- lib/gitlab/etag_caching/router.rb | 2 +- lib/gitlab/git/repository.rb | 2 +- lib/gitlab/kubernetes.rb | 4 ++-- lib/gitlab/workhorse.rb | 8 ++++---- lib/tasks/gemojione.rake | 2 +- lib/tasks/gitlab/update_templates.rake | 2 +- lib/tasks/spec.rake | 2 +- scripts/trigger-build | 2 +- spec/factories/services.rb | 2 +- spec/features/auto_deploy_spec.rb | 2 +- spec/features/merge_requests/conflicts_spec.rb | 2 +- .../projects/features_visibility_spec.rb | 2 +- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 6 +++--- spec/lib/expand_variables_spec.rb | 6 +++--- spec/lib/gitlab/backup/manager_spec.rb | 2 +- spec/lib/gitlab/diff/position_tracer_spec.rb | 8 ++++---- spec/lib/gitlab/git/encoding_helper_spec.rb | 16 ++++++++-------- spec/lib/gitlab/git/util_spec.rb | 2 +- .../import_export/relation_factory_spec.rb | 2 +- spec/lib/gitlab/repo_path_spec.rb | 2 +- spec/lib/gitlab/workhorse_spec.rb | 4 ++-- spec/models/application_setting_spec.rb | 2 +- spec/models/ci/build_spec.rb | 4 ++-- spec/models/environment_spec.rb | 2 +- spec/models/global_milestone_spec.rb | 2 +- spec/models/project_authorization_spec.rb | 2 +- .../project_services/asana_service_spec.rb | 2 +- .../chat_message/issue_message_spec.rb | 2 +- .../chat_message/merge_message_spec.rb | 2 +- .../chat_message/note_message_spec.rb | 2 +- .../chat_message/push_message_spec.rb | 4 ++-- .../chat_message/wiki_page_message_spec.rb | 4 ++-- .../project_services/kubernetes_service_spec.rb | 2 +- .../pivotaltracker_service_spec.rb | 2 +- spec/models/project_spec.rb | 2 +- spec/models/user_spec.rb | 2 +- spec/requests/api/files_spec.rb | 2 +- spec/requests/api/groups_spec.rb | 2 +- spec/requests/api/projects_spec.rb | 2 +- spec/requests/api/v3/files_spec.rb | 4 ++-- spec/requests/api/v3/groups_spec.rb | 2 +- spec/requests/api/v3/projects_spec.rb | 4 ++-- spec/requests/lfs_http_spec.rb | 6 +++--- spec/requests/openid_connect_spec.rb | 2 +- spec/serializers/analytics_issue_entity_spec.rb | 2 +- .../analytics_issue_serializer_spec.rb | 2 +- spec/services/cohorts_service_spec.rb | 2 +- spec/services/create_deployment_service_spec.rb | 2 +- .../issuable/bulk_update_service_spec.rb | 2 +- .../merge_requests/refresh_service_spec.rb | 2 +- spec/services/notification_service_spec.rb | 2 +- spec/support/kubernetes_helpers.rb | 8 ++++---- spec/support/repo_helpers.rb | 4 ++-- spec/support/workhorse_helpers.rb | 2 +- spec/tasks/gitlab/gitaly_rake_spec.rb | 2 +- 113 files changed, 157 insertions(+), 156 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index e53af97a92c..7438763984d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -494,7 +494,8 @@ Style/TrailingBlankLines: # This cop checks for trailing comma in array and hash literals. Style/TrailingCommaInLiteral: - Enabled: false + Enabled: true + EnforcedStyleForMultiline: no_comma # Checks for %W when interpolation is not needed. Style/UnneededCapitalW: diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index b79ca034c5b..e2f5aa8508e 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -41,7 +41,7 @@ class AutocompleteController < ApplicationController no_project = { id: 0, - name_with_namespace: 'No project', + name_with_namespace: 'No project' } projects.unshift(no_project) unless params[:offset_id].present? diff --git a/app/controllers/concerns/lfs_request.rb b/app/controllers/concerns/lfs_request.rb index ed22b1e5470..ae91e02488a 100644 --- a/app/controllers/concerns/lfs_request.rb +++ b/app/controllers/concerns/lfs_request.rb @@ -23,7 +23,7 @@ module LfsRequest render( json: { message: 'Git LFS is not enabled on this GitLab server, contact your admin.', - documentation_url: help_url, + documentation_url: help_url }, status: 501 ) @@ -48,7 +48,7 @@ module LfsRequest render( json: { message: 'Access forbidden. Check your access level.', - documentation_url: help_url, + documentation_url: help_url }, content_type: "application/vnd.git-lfs+json", status: 403 @@ -59,7 +59,7 @@ module LfsRequest render( json: { message: 'Not found.', - documentation_url: help_url, + documentation_url: help_url }, content_type: "application/vnd.git-lfs+json", status: 404 diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb index df0fc3132ed..125746d0426 100644 --- a/app/controllers/health_controller.rb +++ b/app/controllers/health_controller.rb @@ -5,7 +5,7 @@ class HealthController < ActionController::Base CHECKS = [ Gitlab::HealthChecks::DbCheck, Gitlab::HealthChecks::RedisCheck, - Gitlab::HealthChecks::FsShardsCheck, + Gitlab::HealthChecks::FsShardsCheck ].freeze def readiness diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb index 3109439b2ff..1c01be06451 100644 --- a/app/controllers/jwt_controller.rb +++ b/app/controllers/jwt_controller.rb @@ -4,7 +4,7 @@ class JwtController < ApplicationController before_action :authenticate_project_or_user SERVICES = { - Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService, + Auth::ContainerRegistryAuthenticationService::AUDIENCE => Auth::ContainerRegistryAuthenticationService }.freeze def auth diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 58d41e0478d..c376bc8a785 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -209,7 +209,7 @@ class Projects::IssuesController < Projects::ApplicationController description_text: @issue.description, task_status: @issue.task_status, issue_number: @issue.iid, - updated_at: @issue.updated_at, + updated_at: @issue.updated_at } end diff --git a/app/controllers/projects/lfs_api_controller.rb b/app/controllers/projects/lfs_api_controller.rb index 8a5a645ed0e..1b0d3aab3fa 100644 --- a/app/controllers/projects/lfs_api_controller.rb +++ b/app/controllers/projects/lfs_api_controller.rb @@ -22,7 +22,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController render( json: { message: 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.', - documentation_url: "#{Gitlab.config.gitlab.url}/help", + documentation_url: "#{Gitlab.config.gitlab.url}/help" }, status: 501 ) @@ -55,7 +55,7 @@ class Projects::LfsApiController < Projects::GitHttpClientController else object[:error] = { code: 404, - message: "Object does not exist on the server or you don't have permissions to access it", + message: "Object does not exist on the server or you don't have permissions to access it" } end end diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index 7fe3c3c116c..602d3dd8c1c 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -44,7 +44,7 @@ class Projects::PipelinesController < Projects::ApplicationController all: @pipelines_count, running: @running_count, pending: @pending_count, - finished: @finished_count, + finished: @finished_count } } end diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 5e2182c883e..3ce65b29b3c 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -48,7 +48,7 @@ class Projects::TreeController < Projects::ApplicationController @dir_name = File.join(@path, params[:dir_name]) @commit_params = { file_path: @dir_name, - commit_message: params[:commit_message], + commit_message: params[:commit_message] } end end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 69310b26e76..63d018c8cbf 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -220,7 +220,7 @@ class ProjectsController < Projects::ApplicationController branches = BranchesFinder.new(@repository, params).execute.map(&:name) options = { - 'Branches' => branches.take(100), + 'Branches' => branches.take(100) } unless @repository.tag_count.zero? diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index dc144906548..63bc3b11b56 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -98,7 +98,7 @@ module DiffHelper [ content_tag(:span, link_to(truncate(blob.name, length: 40), tree)), '@', - content_tag(:span, commit_id, class: 'monospace'), + content_tag(:span, commit_id, class: 'monospace') ].join(' ').html_safe end diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index f927cfc998f..3b24f183785 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -12,7 +12,7 @@ module EmailsHelper "action" => { "@type" => "ViewAction", "name" => name, - "url" => url, + "url" => url } } diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 960111ca045..c14438da281 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -41,7 +41,7 @@ module EventsHelper link_opts = { class: "event-filter-link", id: "#{key}_event_filter", - title: "Filter by #{tooltip.downcase}", + title: "Filter by #{tooltip.downcase}" } content_tag :li, class: active do diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb index 7bd212a3ef9..b981a1e8242 100644 --- a/app/helpers/explore_helper.rb +++ b/app/helpers/explore_helper.rb @@ -10,7 +10,7 @@ module ExploreHelper personal: params[:personal], archived: params[:archived], shared: params[:shared], - namespace_id: params[:namespace_id], + namespace_id: params[:namespace_id] } options = exist_opts.merge(options).delete_if { |key, value| value.blank? } diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb index b241a14740b..752d0079818 100644 --- a/app/helpers/markup_helper.rb +++ b/app/helpers/markup_helper.rb @@ -32,7 +32,7 @@ module MarkupHelper context = { project: @project, current_user: (current_user if defined?(current_user)), - pipeline: :single_line, + pipeline: :single_line } gfm_body = Banzai.render(body, context) diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 23e55539f0a..39d30631646 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -54,7 +54,7 @@ module MergeRequestsHelper source_project_id: merge_request.source_project_id, target_project_id: merge_request.target_project_id, source_branch: merge_request.source_branch, - target_branch: merge_request.target_branch, + target_branch: merge_request.target_branch }, change_branches: true ) diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 52403640c05..375110b77e2 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -19,7 +19,7 @@ module NotesHelper id: noteable.id, class: noteable.class.name, resources: noteable.class.table_name, - project_id: noteable.project.id, + project_id: noteable.project.id }.to_json end @@ -34,7 +34,7 @@ module NotesHelper data = { line_code: line_code, - line_type: line_type, + line_type: line_type } if @use_legacy_diff_notes diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 8ff8db16514..9c46035057f 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -42,7 +42,7 @@ module SearchHelper { category: "Settings", label: "User settings", url: profile_path }, { category: "Settings", label: "SSH Keys", url: profile_keys_path }, { category: "Settings", label: "Dashboard", url: root_path }, - { category: "Settings", label: "Admin Section", url: admin_root_path }, + { category: "Settings", label: "Admin Section", url: admin_root_path } ] end @@ -57,7 +57,7 @@ module SearchHelper { category: "Help", label: "SSH Keys Help", url: help_page_path("ssh/README") }, { category: "Help", label: "System Hooks Help", url: help_page_path("system_hooks/system_hooks") }, { category: "Help", label: "Webhooks Help", url: help_page_path("user/project/integrations/webhooks") }, - { category: "Help", label: "Workflow Help", url: help_page_path("workflow/README") }, + { category: "Help", label: "Workflow Help", url: help_page_path("workflow/README") } ] end @@ -76,7 +76,7 @@ module SearchHelper { category: "Current Project", label: "Milestones", url: namespace_project_milestones_path(@project.namespace, @project) }, { category: "Current Project", label: "Snippets", url: namespace_project_snippets_path(@project.namespace, @project) }, { category: "Current Project", label: "Members", url: namespace_project_settings_members_path(@project.namespace, @project) }, - { category: "Current Project", label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) }, + { category: "Current Project", label: "Wiki", url: namespace_project_wikis_path(@project.namespace, @project) } ] else [] diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index 8706876ae4a..a7d1fe4aa47 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -67,7 +67,7 @@ module SelectsHelper current_user: opts[:current_user] || false, "push-code-to-protected-branches" => opts[:push_code_to_protected_branches], author_id: opts[:author_id] || '', - skip_users: opts[:skip_users] ? opts[:skip_users].map(&:id) : nil, + skip_users: opts[:skip_users] ? opts[:skip_users].map(&:id) : nil } end end diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 4882d9b71d2..b408ec0c6a4 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -58,7 +58,7 @@ module SortingHelper sort_value_due_date_soon => sort_title_due_date_soon, sort_value_due_date_later => sort_title_due_date_later, sort_value_start_date_soon => sort_title_start_date_soon, - sort_value_start_date_later => sort_title_start_date_later, + sort_value_start_date_later => sort_title_start_date_later } end diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index f19e2f9db9c..0ff2d5ce4cd 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -63,7 +63,7 @@ module TodosHelper project_id: params[:project_id], author_id: params[:author_id], type: params[:type], - action_id: params[:action_id], + action_id: params[:action_id] } end diff --git a/app/models/blob.rb b/app/models/blob.rb index eaf0b713122..8452a96e909 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -33,7 +33,7 @@ class Blob < SimpleDelegator BlobViewer::PDF, BlobViewer::BinarySTL, - BlobViewer::TextSTL, + BlobViewer::TextSTL ].freeze BINARY_VIEWERS = RICH_VIEWERS.select(&:binary?).freeze diff --git a/app/models/commit.rb b/app/models/commit.rb index dea18bfedef..3a143a5a1f6 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -49,7 +49,7 @@ class Commit def max_diff_options { max_files: DIFF_HARD_LIMIT_FILES, - max_lines: DIFF_HARD_LIMIT_LINES, + max_lines: DIFF_HARD_LIMIT_LINES } end diff --git a/app/models/environment.rb b/app/models/environment.rb index bf33010fd21..61efc1b2d17 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -62,7 +62,7 @@ class Environment < ActiveRecord::Base def predefined_variables [ { key: 'CI_ENVIRONMENT_NAME', value: name, public: true }, - { key: 'CI_ENVIRONMENT_SLUG', value: slug, public: true }, + { key: 'CI_ENVIRONMENT_SLUG', value: slug, public: true } ] end diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 595602e80fe..22a177ed367 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -31,7 +31,7 @@ class WebHook < ActiveRecord::Base post_url = url.gsub("#{parsed_url.userinfo}@", '') auth = { username: CGI.unescape(parsed_url.user), - password: CGI.unescape(parsed_url.password), + password: CGI.unescape(parsed_url.password) } response = WebHook.post(post_url, body: data.to_json, diff --git a/app/models/project.rb b/app/models/project.rb index 1f550cc02e2..be4960aef8a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -967,7 +967,7 @@ class Project < ActiveRecord::Base namespace: namespace.name, visibility_level: visibility_level, path_with_namespace: path_with_namespace, - default_branch: default_branch, + default_branch: default_branch } # Backward compatibility diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 400020ee04a..3f5b3eb159b 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -52,7 +52,7 @@ class BambooService < CiService placeholder: 'Bamboo build plan key like KEY' }, { type: 'text', name: 'username', placeholder: 'A user with API access, if applicable' }, - { type: 'password', name: 'password' }, + { type: 'password', name: 'password' } ] end diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb index 6464bf3f4a4..779ef54cfcb 100644 --- a/app/models/project_services/chat_notification_service.rb +++ b/app/models/project_services/chat_notification_service.rb @@ -39,7 +39,7 @@ class ChatNotificationService < Service { type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" }, { type: 'text', name: 'username', placeholder: 'e.g. GitLab' }, { type: 'checkbox', name: 'notify_only_broken_pipelines' }, - { type: 'checkbox', name: 'notify_only_default_branch' }, + { type: 'checkbox', name: 'notify_only_default_branch' } ] end diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index f4f913ee0b6..1a236e232f9 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -47,7 +47,7 @@ class EmailsOnPushService < Service help: "Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. #{domains})." }, { type: 'checkbox', name: 'disable_diffs', title: "Disable code diffs", help: "Don't include possibly sensitive code diffs in notification body." }, - { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' }, + { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' } ] end end diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb index bdf6fa6a586..b4d7c977ce4 100644 --- a/app/models/project_services/external_wiki_service.rb +++ b/app/models/project_services/external_wiki_service.rb @@ -19,7 +19,7 @@ class ExternalWikiService < Service def fields [ - { type: 'text', name: 'external_wiki_url', placeholder: 'The URL of the external Wiki' }, + { type: 'text', name: 'external_wiki_url', placeholder: 'The URL of the external Wiki' } ] end diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 8b181221bb0..c19fed339ba 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -41,7 +41,7 @@ class HipchatService < Service placeholder: 'Leave blank for default (v2)' }, { type: 'text', name: 'server', placeholder: 'Leave blank for default. https://hipchat.example.com' }, - { type: 'checkbox', name: 'notify_only_broken_pipelines' }, + { type: 'checkbox', name: 'notify_only_broken_pipelines' } ] end diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index c62bb4fa120..a51d43adcb9 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -58,7 +58,7 @@ class IrkerService < Service ' want to use a password, you have to omit the "#" on the channel). If you ' \ ' specify a default IRC URI to prepend before each recipient, you can just ' \ ' give a channel name.' }, - { type: 'checkbox', name: 'colorize_messages' }, + { type: 'checkbox', name: 'colorize_messages' } ] end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 97e997d3899..f388773efee 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -149,7 +149,7 @@ class JiraService < IssueTrackerService data = { user: { name: author.name, - url: resource_url(user_path(author)), + url: resource_url(user_path(author)) }, project: { name: self.project.path_with_namespace, diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index 9c56518c991..b2494a0be6e 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -73,7 +73,7 @@ class KubernetesService < DeploymentService { type: 'textarea', name: 'ca_pem', title: 'Custom CA bundle', - placeholder: 'Certificate Authority bundle (PEM format)' }, + placeholder: 'Certificate Authority bundle (PEM format)' } ] end diff --git a/app/models/project_services/microsoft_teams_service.rb b/app/models/project_services/microsoft_teams_service.rb index 9b218fd81b4..2facff53e26 100644 --- a/app/models/project_services/microsoft_teams_service.rb +++ b/app/models/project_services/microsoft_teams_service.rb @@ -35,7 +35,7 @@ class MicrosoftTeamsService < ChatNotificationService [ { type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" }, { type: 'checkbox', name: 'notify_only_broken_pipelines' }, - { type: 'checkbox', name: 'notify_only_default_branch' }, + { type: 'checkbox', name: 'notify_only_default_branch' } ] end diff --git a/app/models/project_services/mock_ci_service.rb b/app/models/project_services/mock_ci_service.rb index a8d581a1f67..546b6e0a498 100644 --- a/app/models/project_services/mock_ci_service.rb +++ b/app/models/project_services/mock_ci_service.rb @@ -21,7 +21,7 @@ class MockCiService < CiService [ { type: 'text', name: 'mock_service_url', - placeholder: 'http://localhost:4004' }, + placeholder: 'http://localhost:4004' } ] end diff --git a/app/models/project_services/pipelines_email_service.rb b/app/models/project_services/pipelines_email_service.rb index ac617f409d9..f824171ad09 100644 --- a/app/models/project_services/pipelines_email_service.rb +++ b/app/models/project_services/pipelines_email_service.rb @@ -55,7 +55,7 @@ class PipelinesEmailService < Service name: 'recipients', placeholder: 'Emails separated by comma' }, { type: 'checkbox', - name: 'notify_only_broken_pipelines' }, + name: 'notify_only_broken_pipelines' } ] end diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb index 3e618a8dbf1..fc29a5277bb 100644 --- a/app/models/project_services/pushover_service.rb +++ b/app/models/project_services/pushover_service.rb @@ -55,7 +55,7 @@ class PushoverService < Service ['Pushover Echo (long)', 'echo'], ['Up Down (long)', 'updown'], ['None (silent)', 'none'] - ] }, + ] } ] end diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index cbaffb8ce48..b16beb406b9 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -55,7 +55,7 @@ class TeamcityService < CiService placeholder: 'Build configuration ID' }, { type: 'text', name: 'username', placeholder: 'A user with permissions to trigger a manual build' }, - { type: 'password', name: 'password' }, + { type: 'password', name: 'password' } ] end @@ -78,7 +78,7 @@ class TeamcityService < CiService auth = { username: username, - password: password, + password: password } branch = Gitlab::Git.ref_name(data[:ref]) diff --git a/app/services/akismet_service.rb b/app/services/akismet_service.rb index 76b9f1feda7..8e11a2a36a7 100644 --- a/app/services/akismet_service.rb +++ b/app/services/akismet_service.rb @@ -16,7 +16,7 @@ class AkismetService created_at: DateTime.now, author: owner.name, author_email: owner.email, - referrer: options[:referrer], + referrer: options[:referrer] } begin diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb index 8a000585e89..5ad9a50687c 100644 --- a/app/services/audit_event_service.rb +++ b/app/services/audit_event_service.rb @@ -8,7 +8,7 @@ class AuditEventService with: @details[:with], target_id: @author.id, target_type: 'User', - target_details: @author.name, + target_details: @author.name } self diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index eb4809afa85..cacb74b1205 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -27,7 +27,7 @@ module Projects { domain: domain.domain, certificate: domain.certificate, - key: domain.key, + key: domain.key } end end diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index af0ddbe5934..628d6d7b4f6 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -113,7 +113,7 @@ class SystemHooksService user_name: model.user.name, user_email: model.user.email, user_id: model.user.id, - group_access: model.human_access, + group_access: model.human_access } end end diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb index 74aba6c5d06..cc4f6a1d0fb 100644 --- a/config/initializers/static_files.rb +++ b/config/initializers/static_files.rb @@ -23,7 +23,7 @@ if app.config.serve_static_files host: dev_server.host, port: dev_server.port, manifest_host: dev_server.host, - manifest_port: dev_server.port, + manifest_port: dev_server.port } if Rails.env.development? diff --git a/db/migrate/20160810142633_remove_redundant_indexes.rb b/db/migrate/20160810142633_remove_redundant_indexes.rb index d7ab022d7bc..ea7d1f9a436 100644 --- a/db/migrate/20160810142633_remove_redundant_indexes.rb +++ b/db/migrate/20160810142633_remove_redundant_indexes.rb @@ -69,7 +69,7 @@ class RemoveRedundantIndexes < ActiveRecord::Migration [:namespaces, 'index_namespaces_on_created_at_and_id'], [:notes, 'index_notes_on_created_at_and_id'], [:projects, 'index_projects_on_created_at_and_id'], - [:users, 'index_users_on_created_at_and_id'], + [:users, 'index_users_on_created_at_and_id'] ] transaction do diff --git a/db/migrate/20160829114652_add_markdown_cache_columns.rb b/db/migrate/20160829114652_add_markdown_cache_columns.rb index 9cb44dfa9f9..6ad7237f4cd 100644 --- a/db/migrate/20160829114652_add_markdown_cache_columns.rb +++ b/db/migrate/20160829114652_add_markdown_cache_columns.rb @@ -25,7 +25,7 @@ class AddMarkdownCacheColumns < ActiveRecord::Migration notes: [:note], projects: [:description], releases: [:description], - snippets: [:title, :content], + snippets: [:title, :content] }.freeze def change diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 2a11790b215..96aaaf868ea 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -90,7 +90,7 @@ module API { api_version: API.version, gitlab_version: Gitlab::VERSION, - gitlab_rev: Gitlab::REVISION, + gitlab_rev: Gitlab::REVISION } end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 9a6cb43abf7..687efe7da65 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -226,7 +226,7 @@ module API :shared_runners_enabled, :snippets_enabled, :visibility, - :wiki_enabled, + :wiki_enabled ] optional :name, type: String, desc: 'The name of the project' optional :default_branch, type: String, desc: 'The default branch of the project' diff --git a/lib/api/services.rb b/lib/api/services.rb index 23ef62c2258..cb07df9e249 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -356,7 +356,7 @@ module API name: :ca_pem, type: String, desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)' - }, + } ], 'mattermost-slash-commands' => [ { @@ -559,7 +559,7 @@ module API SlackService, MattermostService, MicrosoftTeamsService, - TeamcityService, + TeamcityService ] if Rails.env.development? @@ -577,7 +577,7 @@ module API service_classes += [ MockCiService, MockDeploymentService, - MockMonitoringService, + MockMonitoringService ] end diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb index dbe54d3cd31..91567909998 100644 --- a/lib/api/subscriptions.rb +++ b/lib/api/subscriptions.rb @@ -5,7 +5,7 @@ module API subscribable_types = { 'merge_requests' => proc { |id| find_merge_request_with_access(id, :update_merge_request) }, 'issues' => proc { |id| find_project_issue(id) }, - 'labels' => proc { |id| find_project_label(id) }, + 'labels' => proc { |id| find_project_label(id) } } params do diff --git a/lib/api/v3/services.rb b/lib/api/v3/services.rb index 61629a04174..118c6df6549 100644 --- a/lib/api/v3/services.rb +++ b/lib/api/v3/services.rb @@ -377,7 +377,7 @@ module API name: :ca_pem, type: String, desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)' - }, + } ], 'mattermost-slash-commands' => [ { diff --git a/lib/api/v3/subscriptions.rb b/lib/api/v3/subscriptions.rb index 068750ec077..690768db82f 100644 --- a/lib/api/v3/subscriptions.rb +++ b/lib/api/v3/subscriptions.rb @@ -7,7 +7,7 @@ module API 'merge_request' => proc { |id| find_merge_request_with_access(id, :update_merge_request) }, 'merge_requests' => proc { |id| find_merge_request_with_access(id, :update_merge_request) }, 'issues' => proc { |id| find_project_issue(id) }, - 'labels' => proc { |id| find_project_label(id) }, + 'labels' => proc { |id| find_project_label(id) } } params do diff --git a/lib/ci/ansi2html.rb b/lib/ci/ansi2html.rb index b439b0ee29b..55402101e43 100644 --- a/lib/ci/ansi2html.rb +++ b/lib/ci/ansi2html.rb @@ -20,7 +20,7 @@ module Ci italic: 0x02, underline: 0x04, conceal: 0x08, - cross: 0x10, + cross: 0x10 }.freeze def self.convert(ansi, state = nil) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 15a461a16dd..b06474cda7f 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -70,7 +70,7 @@ module Ci cache: job[:cache], dependencies: job[:dependencies], after_script: job[:after_script], - environment: job[:environment], + environment: job[:environment] }.compact } end diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index 8c28009b9c6..4714ab18cc1 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -32,7 +32,7 @@ module Gitlab "Guest" => GUEST, "Reporter" => REPORTER, "Developer" => DEVELOPER, - "Master" => MASTER, + "Master" => MASTER } end @@ -47,7 +47,7 @@ module Gitlab guest: GUEST, reporter: REPORTER, developer: DEVELOPER, - master: MASTER, + master: MASTER } end @@ -60,7 +60,7 @@ module Gitlab "Not protected: Both developers and masters can push new commits, force push, or delete the branch." => PROTECTION_NONE, "Protected against pushes: Developers cannot push new commits, but are allowed to accept merge requests to the branch." => PROTECTION_DEV_CAN_MERGE, "Partially protected: Developers can push new commits, but cannot force push or delete the branch. Masters can do all of those." => PROTECTION_DEV_CAN_PUSH, - "Fully protected: Developers cannot push new commits, force push, or delete the branch. Only masters can do any of those." => PROTECTION_FULL, + "Fully protected: Developers cannot push new commits, force push, or delete the branch. Only masters can do any of those." => PROTECTION_FULL } end diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index f34ed0f4cf2..3e0c30c33b7 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -5,7 +5,7 @@ module Gitlab Gitlab::ChatCommands::IssueShow, Gitlab::ChatCommands::IssueNew, Gitlab::ChatCommands::IssueSearch, - Gitlab::ChatCommands::Deploy, + Gitlab::ChatCommands::Deploy ].freeze def execute diff --git a/lib/gitlab/cycle_analytics/permissions.rb b/lib/gitlab/cycle_analytics/permissions.rb index bef3b95ff1b..1e11e84a9cb 100644 --- a/lib/gitlab/cycle_analytics/permissions.rb +++ b/lib/gitlab/cycle_analytics/permissions.rb @@ -7,7 +7,7 @@ module Gitlab test: :read_build, review: :read_merge_request, staging: :read_build, - production: :read_issue, + production: :read_issue }.freeze def self.get(*args) diff --git a/lib/gitlab/data_builder/build.rb b/lib/gitlab/data_builder/build.rb index f78106f5b10..8e74e18a311 100644 --- a/lib/gitlab/data_builder/build.rb +++ b/lib/gitlab/data_builder/build.rb @@ -36,7 +36,7 @@ module Gitlab user: { id: user.try(:id), name: user.try(:name), - email: user.try(:email), + email: user.try(:email) }, commit: { @@ -49,7 +49,7 @@ module Gitlab status: commit.status, duration: commit.duration, started_at: commit.started_at, - finished_at: commit.finished_at, + finished_at: commit.finished_at }, repository: { @@ -60,7 +60,7 @@ module Gitlab git_http_url: project.http_url_to_repo, git_ssh_url: project.ssh_url_to_repo, visibility_level: project.visibility_level - }, + } } data diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb index 692c909d838..31a5b9d108b 100644 --- a/lib/gitlab/etag_caching/router.rb +++ b/lib/gitlab/etag_caching/router.rb @@ -40,7 +40,7 @@ module Gitlab Gitlab::EtagCaching::Router::Route.new( %r(^(?!.*(#{RESERVED_WORDS})).*/pipelines/\d+\.json\z), 'project_pipeline' - ), + ) ].freeze def self.match(env) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 9ed12ead023..256318cb833 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -258,7 +258,7 @@ module Gitlab 'RepoPath' => path, 'ArchivePrefix' => prefix, 'ArchivePath' => archive_file_path(prefix, storage_path, format), - 'CommitId' => commit.id, + 'CommitId' => commit.id } end diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb index 3a7af363548..4a6091488c8 100644 --- a/lib/gitlab/kubernetes.rb +++ b/lib/gitlab/kubernetes.rb @@ -38,7 +38,7 @@ module Gitlab url: container_exec_url(api_url, namespace, pod_name, container["name"]), subprotocols: ['channel.k8s.io'], headers: Hash.new { |h, k| h[k] = [] }, - created_at: created_at, + created_at: created_at } end end @@ -64,7 +64,7 @@ module Gitlab tty: true, stdin: true, stdout: true, - stderr: true, + stderr: true }.to_query + '&' + EXEC_COMMAND case url.scheme diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 8c5ad01e8c2..f3b3dea5318 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -22,7 +22,7 @@ module Gitlab params = { GL_ID: Gitlab::GlId.gl_id(user), GL_REPOSITORY: Gitlab::GlRepository.gl_repository(project, is_wiki), - RepoPath: repo_path, + RepoPath: repo_path } if Gitlab.config.gitaly.enabled @@ -51,7 +51,7 @@ module Gitlab { StoreLFSPath: "#{Gitlab.config.lfs.storage_path}/tmp/upload", LfsOid: oid, - LfsSize: size, + LfsSize: size } end @@ -62,7 +62,7 @@ module Gitlab def send_git_blob(repository, blob) params = { 'RepoPath' => repository.path_to_repo, - 'BlobId' => blob.id, + 'BlobId' => blob.id } [ @@ -127,7 +127,7 @@ module Gitlab 'Subprotocols' => terminal[:subprotocols], 'Url' => terminal[:url], 'Header' => terminal[:headers], - 'MaxSessionTime' => terminal[:max_session_time], + 'MaxSessionTime' => terminal[:max_session_time] } } details['Terminal']['CAPem'] = terminal[:ca_pem] if terminal.has_key?(:ca_pem) diff --git a/lib/tasks/gemojione.rake b/lib/tasks/gemojione.rake index b5572a39d30..87ca39b079b 100644 --- a/lib/tasks/gemojione.rake +++ b/lib/tasks/gemojione.rake @@ -21,7 +21,7 @@ namespace :gemojione do moji: emoji_hash['moji'], description: emoji_hash['description'], unicodeVersion: Gitlab::Emoji.emoji_unicode_version(name), - digest: hash_digest, + digest: hash_digest } resultant_emoji_map[name] = entry diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake index 1b04e1350ed..59c32bbe7a4 100644 --- a/lib/tasks/gitlab/update_templates.rake +++ b/lib/tasks/gitlab/update_templates.rake @@ -49,7 +49,7 @@ namespace :gitlab do Template.new( "https://gitlab.com/gitlab-org/Dockerfile.git", /(\.{1,2}|LICENSE|CONTRIBUTING.md|\.Dockerfile)\z/ - ), + ) ].freeze def vendor_directory diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake index 602c60be828..2eddcb3c777 100644 --- a/lib/tasks/spec.rake +++ b/lib/tasks/spec.rake @@ -60,7 +60,7 @@ desc "GitLab | Run specs" task :spec do cmds = [ %w(rake gitlab:setup), - %w(rspec spec), + %w(rspec spec) ] run_commands(cmds) end diff --git a/scripts/trigger-build b/scripts/trigger-build index 741e6361f01..565bc314ef1 100755 --- a/scripts/trigger-build +++ b/scripts/trigger-build @@ -8,7 +8,7 @@ params = { "ref" => ENV["OMNIBUS_BRANCH"] || "master", "token" => ENV["BUILD_TRIGGER_TOKEN"], "variables[GITLAB_VERSION]" => ENV["CI_COMMIT_SHA"], - "variables[ALTERNATIVE_SOURCES]" => true, + "variables[ALTERNATIVE_SOURCES]" => true } Dir.glob("*_VERSION").each do |version_file| diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 62aa71ae8d8..28ddd0da753 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -22,7 +22,7 @@ FactoryGirl.define do properties({ namespace: 'somepath', api_url: 'https://kubernetes.example.com', - token: 'a' * 40, + token: 'a' * 40 }) end diff --git a/spec/features/auto_deploy_spec.rb b/spec/features/auto_deploy_spec.rb index 67b0f006854..eba1bca83a8 100644 --- a/spec/features/auto_deploy_spec.rb +++ b/spec/features/auto_deploy_spec.rb @@ -10,7 +10,7 @@ describe 'Auto deploy' do properties: { namespace: project.path, api_url: 'https://kubernetes.example.com', - token: 'a' * 40, + token: 'a' * 40 } ) project.team << [user, :master] diff --git a/spec/features/merge_requests/conflicts_spec.rb b/spec/features/merge_requests/conflicts_spec.rb index 43977ad2fc5..04b7593ce68 100644 --- a/spec/features/merge_requests/conflicts_spec.rb +++ b/spec/features/merge_requests/conflicts_spec.rb @@ -151,7 +151,7 @@ feature 'Merge request conflict resolution', js: true, feature: true do 'conflict-too-large' => 'when the conflicts contain a large file', 'conflict-binary-file' => 'when the conflicts contain a binary file', 'conflict-missing-side' => 'when the conflicts contain a file edited in one branch and deleted in another', - 'conflict-non-utf8' => 'when the conflicts contain a non-UTF-8 file', + 'conflict-non-utf8' => 'when the conflicts contain a non-UTF-8 file' }.freeze UNRESOLVABLE_CONFLICTS.each do |source_branch, description| diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb index e1781cf320a..4533a6fb144 100644 --- a/spec/features/projects/features_visibility_spec.rb +++ b/spec/features/projects/features_visibility_spec.rb @@ -74,7 +74,7 @@ describe 'Edit Project Settings', feature: true do issues: namespace_project_issues_path(project.namespace, project), wiki: namespace_project_wiki_path(project.namespace, project, :home), snippets: namespace_project_snippets_path(project.namespace, project), - merge_requests: namespace_project_merge_requests_path(project.namespace, project), + merge_requests: namespace_project_merge_requests_path(project.namespace, project) } end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 53abc056602..d1d2dcc4998 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -225,7 +225,7 @@ module Ci before_script: ["pwd"], rspec: { script: "rspec", type: "test", only: %w(master deploy) }, staging: { script: "deploy", type: "deploy", only: %w(master deploy) }, - production: { script: "deploy", type: "deploy", only: ["master@path", "deploy"] }, + production: { script: "deploy", type: "deploy", only: ["master@path", "deploy"] } }) config_processor = GitlabCiYamlProcessor.new(config, 'fork') @@ -381,7 +381,7 @@ module Ci before_script: ["pwd"], rspec: { script: "rspec", type: "test", except: ["master", "deploy", "test@fork"] }, staging: { script: "deploy", type: "deploy", except: ["master"] }, - production: { script: "deploy", type: "deploy", except: ["master@fork"] }, + production: { script: "deploy", type: "deploy", except: ["master@fork"] } }) config_processor = GitlabCiYamlProcessor.new(config, 'fork') @@ -743,7 +743,7 @@ module Ci cache: { paths: ["logs/", "binaries/"], untracked: true, key: 'global' }, rspec: { script: "rspec", - cache: { paths: ["test/"], untracked: false, key: 'local' }, + cache: { paths: ["test/"], untracked: false, key: 'local' } } }) diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb index 90628917943..7faa0f31b68 100644 --- a/spec/lib/expand_variables_spec.rb +++ b/spec/lib/expand_variables_spec.rb @@ -25,7 +25,7 @@ describe ExpandVariables do result: 'keyvalueresult', variables: [ { key: 'variable', value: 'value' }, - { key: 'variable2', value: 'result' }, + { key: 'variable2', value: 'result' } ] }, { value: 'key${variable}${variable2}', result: 'keyvalueresult', @@ -37,7 +37,7 @@ describe ExpandVariables do result: 'keyresultvalue', variables: [ { key: 'variable', value: 'value' }, - { key: 'variable2', value: 'result' }, + { key: 'variable2', value: 'result' } ] }, { value: 'key${variable2}${variable}', result: 'keyresultvalue', @@ -49,7 +49,7 @@ describe ExpandVariables do result: 'review/feature/add-review-apps', variables: [ { key: 'CI_COMMIT_REF_NAME', value: 'feature/add-review-apps' } - ] }, + ] } ] tests.each do |test| diff --git a/spec/lib/gitlab/backup/manager_spec.rb b/spec/lib/gitlab/backup/manager_spec.rb index f84782ab440..c59ff7fb290 100644 --- a/spec/lib/gitlab/backup/manager_spec.rb +++ b/spec/lib/gitlab/backup/manager_spec.rb @@ -151,7 +151,7 @@ describe Backup::Manager, lib: true do allow(Dir).to receive(:glob).and_return( [ '1451606400_2016_01_01_gitlab_backup.tar', - '1451520000_2015_12_31_gitlab_backup.tar', + '1451520000_2015_12_31_gitlab_backup.tar' ] ) end diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb index a10a251dc4a..4d202a76e1b 100644 --- a/spec/lib/gitlab/diff/position_tracer_spec.rb +++ b/spec/lib/gitlab/diff/position_tracer_spec.rb @@ -1372,7 +1372,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do nil, { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, { old_path: file_name, old_line: 6 }, - { new_path: file_name, new_line: 7 }, + { new_path: file_name, new_line: 7 } ] expect_positions(old_position_attrs, new_position_attrs) @@ -1444,7 +1444,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do nil, { old_path: file_name, new_path: file_name, old_line: 5, new_line: 5 }, { old_path: file_name, old_line: 6 }, - { new_path: file_name, new_line: 7 }, + { new_path: file_name, new_line: 7 } ] expect_positions(old_position_attrs, new_position_attrs) @@ -1498,7 +1498,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do { old_path: file_name, new_path: file_name, old_line: 5, new_line: 4 }, { old_path: file_name, new_path: file_name, old_line: 6, new_line: 5 }, nil, - { new_path: file_name, new_line: 6 }, + { new_path: file_name, new_line: 6 } ] expect_positions(old_position_attrs, new_position_attrs) @@ -1746,7 +1746,7 @@ describe Gitlab::Diff::PositionTracer, lib: true do { old_path: file_name, new_path: file_name, old_line: 4, new_line: 5 }, { old_path: file_name, old_line: 5 }, { new_path: file_name, new_line: 6 }, - { new_path: file_name, new_line: 7 }, + { new_path: file_name, new_line: 7 } ] expect_positions(old_position_attrs, new_position_attrs) diff --git a/spec/lib/gitlab/git/encoding_helper_spec.rb b/spec/lib/gitlab/git/encoding_helper_spec.rb index f6ac7b23d1d..1a3bf802a07 100644 --- a/spec/lib/gitlab/git/encoding_helper_spec.rb +++ b/spec/lib/gitlab/git/encoding_helper_spec.rb @@ -19,8 +19,8 @@ describe Gitlab::Git::EncodingHelper do [ 'removes invalid bytes from ASCII-8bit encoded multibyte string. This can occur when a git diff match line truncates in the middle of a multibyte character. This occurs after the second word in this example. The test string is as short as we can get while still triggering the error condition when not looking at `detect[:confidence]`.', "mu ns\xC3\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ".force_encoding('ASCII-8BIT'), - "mu ns\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ", - ], + "mu ns\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi " + ] ].each do |description, test_string, xpect| it description do expect(ext_class.encode!(test_string)).to eq(xpect) @@ -37,18 +37,18 @@ describe Gitlab::Git::EncodingHelper do [ "encodes valid utf8 encoded string to utf8", "λ, λ, λ".encode("UTF-8"), - "λ, λ, λ".encode("UTF-8"), + "λ, λ, λ".encode("UTF-8") ], [ "encodes valid ASCII-8BIT encoded string to utf8", "ascii only".encode("ASCII-8BIT"), - "ascii only".encode("UTF-8"), + "ascii only".encode("UTF-8") ], [ "encodes valid ISO-8859-1 encoded string to utf8", "Rüby ist eine Programmiersprache. Wir verlängern den text damit ICU die Sprache erkennen kann.".encode("ISO-8859-1", "UTF-8"), - "Rüby ist eine Programmiersprache. Wir verlängern den text damit ICU die Sprache erkennen kann.".encode("UTF-8"), - ], + "Rüby ist eine Programmiersprache. Wir verlängern den text damit ICU die Sprache erkennen kann.".encode("UTF-8") + ] ].each do |description, test_string, xpect| it description do r = ext_class.encode_utf8(test_string.force_encoding('UTF-8')) @@ -77,8 +77,8 @@ describe Gitlab::Git::EncodingHelper do [ 'removes invalid bytes from ASCII-8bit encoded multibyte string.', "Lorem ipsum\xC3\n dolor sit amet, xy\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg".force_encoding('ASCII-8BIT'), - "Lorem ipsum\n dolor sit amet, xyàyùabcdùefg", - ], + "Lorem ipsum\n dolor sit amet, xyàyùabcdùefg" + ] ].each do |description, test_string, xpect| it description do expect(ext_class.encode!(test_string)).to eq(xpect) diff --git a/spec/lib/gitlab/git/util_spec.rb b/spec/lib/gitlab/git/util_spec.rb index 69d3ca55397..88c871855df 100644 --- a/spec/lib/gitlab/git/util_spec.rb +++ b/spec/lib/gitlab/git/util_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Git::Util do ["", 0], ["foo", 1], ["foo\n", 1], - ["foo\n\n", 2], + ["foo\n\n", 2] ].each do |string, line_count| it "counts #{line_count} lines in #{string.inspect}" do expect(described_class.count_lines(string)).to eq(line_count) diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb index 06cd8ab87ed..744fed44925 100644 --- a/spec/lib/gitlab/import_export/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb @@ -95,7 +95,7 @@ describe Gitlab::ImportExport::RelationFactory, lib: true do 'random_id' => 99, 'milestone_id' => 99, 'project_id' => 99, - 'user_id' => 99, + 'user_id' => 99 } end diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb index f94c9c2e315..f9025397107 100644 --- a/spec/lib/gitlab/repo_path_spec.rb +++ b/spec/lib/gitlab/repo_path_spec.rb @@ -29,7 +29,7 @@ describe ::Gitlab::RepoPath do before do allow(Gitlab.config.repositories).to receive(:storages).and_return({ 'storage1' => { 'path' => '/foo' }, - 'storage2' => { 'path' => '/bar' }, + 'storage2' => { 'path' => '/bar' } }) end diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index beb1791a429..67b759f7dcd 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -202,7 +202,7 @@ describe Gitlab::Workhorse, lib: true do context 'when Gitaly is enabled' do let(:gitaly_params) do { - GitalyAddress: Gitlab::GitalyClient.get_address('default'), + GitalyAddress: Gitlab::GitalyClient.get_address('default') } end @@ -214,7 +214,7 @@ describe Gitlab::Workhorse, lib: true do repo_param = { Repository: { path: repo_path, storage_name: 'default', - relative_path: project.full_path + '.git', + relative_path: project.full_path + '.git' } } expect(subject).to include(repo_param) diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index c2c19c62048..3c3ae3832de 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -89,7 +89,7 @@ describe ApplicationSetting, models: true do storages = { 'custom1' => 'tmp/tests/custom_repositories_1', 'custom2' => 'tmp/tests/custom_repositories_2', - 'custom3' => 'tmp/tests/custom_repositories_3', + 'custom3' => 'tmp/tests/custom_repositories_3' } allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 5231ce28c9d..e971b4bc3f9 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -972,7 +972,7 @@ describe Ci::Build, :models do 'fix-1-foo' => 'fix-1-foo', 'a' * 63 => 'a' * 63, 'a' * 64 => 'a' * 63, - 'FOO' => 'foo', + 'FOO' => 'foo' }.each do |ref, slug| it "transforms #{ref} to #{slug}" do build.ref = ref @@ -1144,7 +1144,7 @@ describe Ci::Build, :models do { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }, { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true }, { key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false }, - { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false }, + { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false } ] end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 28e5c3f80f4..edc1c204014 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -438,7 +438,7 @@ describe Environment, models: true do "foo**bar" => "foo-bar" + SUFFIX, "*-foo" => "env-foo" + SUFFIX, "staging-12345678-" => "staging-12345678" + SUFFIX, - "staging-12345678-01234567" => "staging-12345678" + SUFFIX, + "staging-12345678-01234567" => "staging-12345678" + SUFFIX }.each do |name, matcher| it "returns a slug matching #{matcher}, given #{name}" do slug = described_class.new(name: name).generate_slug diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index 55b87d1c48a..a14efda3eda 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -137,7 +137,7 @@ describe GlobalMilestone, models: true do [ milestone1_project1, milestone1_project2, - milestone1_project3, + milestone1_project3 ] milestones_relation = Milestone.where(id: milestones.map(&:id)) diff --git a/spec/models/project_authorization_spec.rb b/spec/models/project_authorization_spec.rb index 33ef67f97a7..cd0a4a94809 100644 --- a/spec/models/project_authorization_spec.rb +++ b/spec/models/project_authorization_spec.rb @@ -16,7 +16,7 @@ describe ProjectAuthorization do it 'inserts rows in batches' do described_class.insert_authorizations([ [user.id, project1.id, Gitlab::Access::MASTER], - [user.id, project2.id, Gitlab::Access::MASTER], + [user.id, project2.id, Gitlab::Access::MASTER] ], 1) expect(user.project_authorizations.count).to eq(2) diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb index 48aef3a93f2..95c35162d96 100644 --- a/spec/models/project_services/asana_service_spec.rb +++ b/spec/models/project_services/asana_service_spec.rb @@ -28,7 +28,7 @@ describe AsanaService, models: true do commits: messages.map do |m| { message: m, - url: 'https://gitlab.com/', + url: 'https://gitlab.com/' } end } diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb index 34e2d94b1ed..c159ab00ab1 100644 --- a/spec/models/project_services/chat_message/issue_message_spec.rb +++ b/spec/models/project_services/chat_message/issue_message_spec.rb @@ -48,7 +48,7 @@ describe ChatMessage::IssueMessage, models: true do title: "#100 Issue title", title_link: "http://url.com", text: "issue description", - color: color, + color: color } ]) end diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb index fa0a1f4a5b7..61f17031172 100644 --- a/spec/models/project_services/chat_message/merge_message_spec.rb +++ b/spec/models/project_services/chat_message/merge_message_spec.rb @@ -22,7 +22,7 @@ describe ChatMessage::MergeMessage, models: true do state: 'opened', description: 'merge request description', source_branch: 'source_branch', - target_branch: 'target_branch', + target_branch: 'target_branch' } } end diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb index 7cd9c61ee2b..7996536218a 100644 --- a/spec/models/project_services/chat_message/note_message_spec.rb +++ b/spec/models/project_services/chat_message/note_message_spec.rb @@ -15,7 +15,7 @@ describe ChatMessage::NoteMessage, models: true do project_url: 'http://somewhere.com', repository: { name: 'project_name', - url: 'http://somewhere.com', + url: 'http://somewhere.com' }, object_attributes: { id: 10, diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb index 63eb078c44e..c794f659c41 100644 --- a/spec/models/project_services/chat_message/push_message_spec.rb +++ b/spec/models/project_services/chat_message/push_message_spec.rb @@ -21,7 +21,7 @@ describe ChatMessage::PushMessage, models: true do before do args[:commits] = [ { message: 'message1', url: 'http://url1.com', id: 'abcdefghijkl', author: { name: 'author1' } }, - { message: 'message2', url: 'http://url2.com', id: '123456789012', author: { name: 'author2' } }, + { message: 'message2', url: 'http://url2.com', id: '123456789012', author: { name: 'author2' } } ] end @@ -33,7 +33,7 @@ describe ChatMessage::PushMessage, models: true do expect(subject.attachments).to eq([{ text: ": message1 - author1\n\n"\ ": message2 - author2", - color: color, + color: color }]) end end diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb index 0df7db2abc2..4ca1b8aa7b7 100644 --- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb +++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb @@ -53,7 +53,7 @@ describe ChatMessage::WikiPageMessage, models: true do expect(subject.attachments).to eq([ { text: "Wiki page description", - color: color, + color: color } ]) end @@ -66,7 +66,7 @@ describe ChatMessage::WikiPageMessage, models: true do expect(subject.attachments).to eq([ { text: "Wiki page description", - color: color, + color: color } ]) end diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index e69eb0098dd..80966b99292 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -54,7 +54,7 @@ describe KubernetesService, models: true, caching: true do 'a' * 63 => true, 'a' * 64 => false, 'a.b' => false, - 'a*b' => false, + 'a*b' => false }.each do |namespace, validity| it "validates #{namespace} as #{validity ? 'valid' : 'invalid'}" do subject.namespace = namespace diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb index 45b2f1068bf..a76e909d04d 100644 --- a/spec/models/project_services/pivotaltracker_service_spec.rb +++ b/spec/models/project_services/pivotaltracker_service_spec.rb @@ -40,7 +40,7 @@ describe PivotaltrackerService, models: true do name: 'Some User' }, url: 'https://example.com/commit', - message: 'commit message', + message: 'commit message' } ] } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 28aa44d8458..f2b4e9070b4 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -973,7 +973,7 @@ describe Project, models: true do before do storages = { 'default' => { 'path' => 'tmp/tests/repositories' }, - 'picked' => { 'path' => 'tmp/tests/repositories' }, + 'picked' => { 'path' => 'tmp/tests/repositories' } } allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index b845e85b295..f2c059010f4 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -676,7 +676,7 @@ describe User, models: true do protocol_and_expectation = { 'http' => false, 'ssh' => true, - '' => true, + '' => true } protocol_and_expectation.each do |protocol, expected| diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index fa28047d49c..deb2cac6869 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -329,7 +329,7 @@ describe API::Files do end let(:get_params) do { - ref: 'master', + ref: 'master' } end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index ed93a8815d3..90b36374ded 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -73,7 +73,7 @@ describe API::Groups do storage_size: 702, repository_size: 123, lfs_objects_size: 234, - build_artifacts_size: 345, + build_artifacts_size: 345 }.stringify_keys exposed_attributes = attributes.dup exposed_attributes['job_artifacts_size'] = exposed_attributes.delete('build_artifacts_size') diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index ab70ce5cd2f..d5c3b5b34ad 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -661,7 +661,7 @@ describe API::Projects do 'name' => user.namespace.name, 'path' => user.namespace.path, 'kind' => user.namespace.kind, - 'full_path' => user.namespace.full_path, + 'full_path' => user.namespace.full_path }) end diff --git a/spec/requests/api/v3/files_spec.rb b/spec/requests/api/v3/files_spec.rb index 5bcbb441979..378ca1720ff 100644 --- a/spec/requests/api/v3/files_spec.rb +++ b/spec/requests/api/v3/files_spec.rb @@ -53,7 +53,7 @@ describe API::V3::Files do let(:params) do { file_path: 'app/models/application.rb', - ref: 'master', + ref: 'master' } end @@ -263,7 +263,7 @@ describe API::V3::Files do let(:get_params) do { file_path: file_path, - ref: 'master', + ref: 'master' } end diff --git a/spec/requests/api/v3/groups_spec.rb b/spec/requests/api/v3/groups_spec.rb index 065cb7ecfe4..bc261b5e07c 100644 --- a/spec/requests/api/v3/groups_spec.rb +++ b/spec/requests/api/v3/groups_spec.rb @@ -69,7 +69,7 @@ describe API::V3::Groups do storage_size: 702, repository_size: 123, lfs_objects_size: 234, - build_artifacts_size: 345, + build_artifacts_size: 345 }.stringify_keys project1.statistics.update!(attributes) diff --git a/spec/requests/api/v3/projects_spec.rb b/spec/requests/api/v3/projects_spec.rb index e15b90d7a9e..dc7c3d125b1 100644 --- a/spec/requests/api/v3/projects_spec.rb +++ b/spec/requests/api/v3/projects_spec.rb @@ -227,7 +227,7 @@ describe API::V3::Projects do storage_size: 702, repository_size: 123, lfs_objects_size: 234, - build_artifacts_size: 345, + build_artifacts_size: 345 } project4.statistics.update!(attributes) @@ -706,7 +706,7 @@ describe API::V3::Projects do 'name' => user.namespace.name, 'path' => user.namespace.path, 'kind' => user.namespace.kind, - 'full_path' => user.namespace.full_path, + 'full_path' => user.namespace.full_path }) end diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 5d495bc9e7d..0c9b4121adf 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -425,7 +425,7 @@ describe 'Git LFS API and storage' do 'size' => sample_size, 'error' => { 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", + 'message' => "Object does not exist on the server or you don't have permissions to access it" } } ] @@ -456,7 +456,7 @@ describe 'Git LFS API and storage' do 'size' => 1575078, 'error' => { 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", + 'message' => "Object does not exist on the server or you don't have permissions to access it" } } ] @@ -493,7 +493,7 @@ describe 'Git LFS API and storage' do 'size' => 1575078, 'error' => { 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", + 'message' => "Object does not exist on the server or you don't have permissions to access it" } }, { diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index fbb69bc0920..1a5d388382d 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -79,7 +79,7 @@ describe 'OpenID Connect requests' do 'email_verified' => true, 'website' => 'https://example.com', 'profile' => 'http://localhost/alice', - 'picture' => "http://localhost/uploads/user/avatar/#{user.id}/dk.png", + 'picture' => "http://localhost/uploads/user/avatar/#{user.id}/dk.png" }) end end diff --git a/spec/serializers/analytics_issue_entity_spec.rb b/spec/serializers/analytics_issue_entity_spec.rb index 68086216ba9..75d606d5eb3 100644 --- a/spec/serializers/analytics_issue_entity_spec.rb +++ b/spec/serializers/analytics_issue_entity_spec.rb @@ -9,7 +9,7 @@ describe AnalyticsIssueEntity do iid: "1", id: "1", created_at: "2016-11-12 15:04:02.948604", - author: user, + author: user } end diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb index ba24cf8e481..7c14c198a74 100644 --- a/spec/serializers/analytics_issue_serializer_spec.rb +++ b/spec/serializers/analytics_issue_serializer_spec.rb @@ -16,7 +16,7 @@ describe AnalyticsIssueSerializer do iid: "1", id: "1", created_at: "2016-11-12 15:04:02.948604", - author: user, + author: user } end diff --git a/spec/services/cohorts_service_spec.rb b/spec/services/cohorts_service_spec.rb index 1e99442fdcb..77595d7ba2d 100644 --- a/spec/services/cohorts_service_spec.rb +++ b/spec/services/cohorts_service_spec.rb @@ -89,7 +89,7 @@ describe CohortsService do activity_months: [{ total: 2, percentage: 100 }], total: 2, inactive: 1 - }, + } ] expect(described_class.new.execute).to eq(months_included: 12, diff --git a/spec/services/create_deployment_service_spec.rb b/spec/services/create_deployment_service_spec.rb index a883705bd45..f35d7a33548 100644 --- a/spec/services/create_deployment_service_spec.rb +++ b/spec/services/create_deployment_service_spec.rb @@ -255,7 +255,7 @@ describe CreateDeploymentService, services: true do environment: 'production', ref: 'master', tag: false, - sha: '97de212e80737a608d939f648d959671fb0a0142b', + sha: '97de212e80737a608d939f648d959671fb0a0142b' } end diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb index 5b1639ca0d6..3ddd0badd39 100644 --- a/spec/services/issuable/bulk_update_service_spec.rb +++ b/spec/services/issuable/bulk_update_service_spec.rb @@ -163,7 +163,7 @@ describe Issuable::BulkUpdateService, services: true do { label_ids: labels.map(&:id), add_label_ids: add_labels.map(&:id), - remove_label_ids: remove_labels.map(&:id), + remove_label_ids: remove_labels.map(&:id) } end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 03215a4624a..1f109eab268 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -348,7 +348,7 @@ describe MergeRequests::RefreshService, services: true do title: 'fixup! Fix issue', work_in_progress?: true, to_reference: 'ccccccc' - ), + ) ]) refresh_service.execute(@oldrev, @newrev, 'refs/heads/wip') reload_mrs diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 74f96b97909..de3bbc6b6a1 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -350,7 +350,7 @@ describe NotificationService, services: true do create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_participant), create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_mentioned), create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_disabled), - create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_note_author), + create(:note_on_personal_snippet, noteable: snippet, note: 'note', author: @u_note_author) ] end diff --git a/spec/support/kubernetes_helpers.rb b/spec/support/kubernetes_helpers.rb index b5ed71ba3be..d2a1ded57ff 100644 --- a/spec/support/kubernetes_helpers.rb +++ b/spec/support/kubernetes_helpers.rb @@ -5,7 +5,7 @@ module KubernetesHelpers { "kind" => "APIResourceList", "resources" => [ - { "name" => "pods", "namespaced" => true, "kind" => "Pod" }, + { "name" => "pods", "namespaced" => true, "kind" => "Pod" } ] } end @@ -22,13 +22,13 @@ module KubernetesHelpers "metadata" => { "name" => "kube-pod", "creationTimestamp" => "2016-11-25T19:55:19Z", - "labels" => { "app" => app }, + "labels" => { "app" => app } }, "spec" => { "containers" => [ { "name" => "container-0" }, - { "name" => "container-1" }, - ], + { "name" => "container-1" } + ] }, "status" => { "phase" => "Running" } } diff --git a/spec/support/repo_helpers.rb b/spec/support/repo_helpers.rb index e9d5c7b12ae..3c6956cf5e0 100644 --- a/spec/support/repo_helpers.rb +++ b/spec/support/repo_helpers.rb @@ -92,11 +92,11 @@ eos changes = [ { line_code: 'a5cc2925ca8258af241be7e5b0381edf30266302_20_20', - file_path: '.gitignore', + file_path: '.gitignore' }, { line_code: '7445606fbf8f3683cd42bdc54b05d7a0bc2dfc44_4_6', - file_path: '.gitmodules', + file_path: '.gitmodules' } ] diff --git a/spec/support/workhorse_helpers.rb b/spec/support/workhorse_helpers.rb index 47673cd4c3a..ef1f9f68671 100644 --- a/spec/support/workhorse_helpers.rb +++ b/spec/support/workhorse_helpers.rb @@ -9,7 +9,7 @@ module WorkhorseHelpers header = split_header.join(':') [ type, - JSON.parse(Base64.urlsafe_decode64(header)), + JSON.parse(Base64.urlsafe_decode64(header)) ] end end diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb index aaf998a546f..f035504320b 100644 --- a/spec/tasks/gitlab/gitaly_rake_spec.rb +++ b/spec/tasks/gitlab/gitaly_rake_spec.rb @@ -80,7 +80,7 @@ describe 'gitlab:gitaly namespace rake task' do it 'prints storage configuration in a TOML format' do config = { 'default' => { 'path' => '/path/to/default' }, - 'nfs_01' => { 'path' => '/path/to/nfs_01' }, + 'nfs_01' => { 'path' => '/path/to/nfs_01' } } allow(Gitlab.config.repositories).to receive(:storages).and_return(config) From 3db37e05622aa3daa2be41bb9edc486cd2e54bcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 3 May 2017 13:27:17 +0200 Subject: [PATCH 30/38] Enable the Style/TrailingCommaInArguments cop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the EnforcedStyleForMultiline: no_comma option. Signed-off-by: Rémy Coutable --- .rubocop.yml | 5 +++++ .rubocop_todo.yml | 7 ------- app/controllers/profiles/preferences_controller.rb | 2 +- app/controllers/projects/issues_controller.rb | 2 +- app/models/diff_discussion.rb | 2 +- app/models/key.rb | 2 +- app/models/namespace.rb | 2 +- app/models/project_services/flowdock_service.rb | 2 +- app/models/repository.rb | 2 +- app/models/sent_notification.rb | 2 +- app/services/boards/issues/move_service.rb | 2 +- app/services/notification_service.rb | 2 +- app/services/system_hooks_service.rb | 2 +- app/workers/repository_check/clear_worker.rb | 2 +- app/workers/repository_check/single_repository_worker.rb | 2 +- config/initializers/ar_monkey_patch.rb | 2 +- config/initializers/hamlit.rb | 4 ++-- config/initializers/static_files.rb | 4 ++-- features/steps/explore/projects.rb | 2 +- lib/api/groups.rb | 2 +- lib/api/helpers.rb | 2 +- lib/api/projects.rb | 2 +- lib/api/v3/groups.rb | 2 +- lib/api/v3/projects.rb | 2 +- lib/gitlab/git/blob.rb | 2 +- lib/gitlab/git/tree.rb | 2 +- lib/gitlab/gitaly_client/commit.rb | 2 +- lib/gitlab/gitaly_client/util.rb | 2 +- lib/gitlab/ldap/config.rb | 2 +- lib/gitlab/sentry.rb | 2 +- lib/gitlab/workhorse.rb | 2 +- spec/controllers/groups_controller_spec.rb | 2 +- spec/features/admin/admin_uses_repository_checks_spec.rb | 2 +- spec/features/copy_as_gfm_spec.rb | 2 +- spec/features/projects/pipelines/pipelines_spec.rb | 2 +- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 6 +++--- spec/lib/gitlab/auth_spec.rb | 4 ++-- spec/lib/gitlab/ci/config/entry/global_spec.rb | 2 +- spec/lib/gitlab/contributions_calendar_spec.rb | 2 +- spec/lib/gitlab/git/diff_spec.rb | 2 +- spec/lib/gitlab/gitaly_client/commit_spec.rb | 4 ++-- spec/models/project_services/kubernetes_service_spec.rb | 4 ++-- spec/models/project_statistics_spec.rb | 4 ++-- spec/requests/ci/api/builds_spec.rb | 2 +- spec/requests/openid_connect_spec.rb | 2 +- spec/services/issues/build_service_spec.rb | 2 +- spec/services/issues/resolve_discussions_spec.rb | 2 +- spec/workers/git_garbage_collect_worker_spec.rb | 2 +- spec/workers/repository_check/clear_worker_spec.rb | 2 +- 49 files changed, 60 insertions(+), 62 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 7438763984d..4e1d456d8d1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -497,6 +497,11 @@ Style/TrailingCommaInLiteral: Enabled: true EnforcedStyleForMultiline: no_comma +# This cop checks for trailing comma in argument lists. +Style/TrailingCommaInArguments: + Enabled: true + EnforcedStyleForMultiline: no_comma + # Checks for %W when interpolation is not needed. Style/UnneededCapitalW: Enabled: true diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 38b22afdf82..7582f761bcb 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -369,13 +369,6 @@ Style/SymbolProc: Style/TernaryParentheses: Enabled: false -# Offense count: 53 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyleForMultiline, SupportedStylesForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInArguments: - Enabled: false - # Offense count: 18 # Cop supports --auto-correct. # Configuration parameters: AllowNamedUnderscoreVariables. diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb index 0d891ef4004..5414142e2df 100644 --- a/app/controllers/profiles/preferences_controller.rb +++ b/app/controllers/profiles/preferences_controller.rb @@ -33,7 +33,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController :color_scheme_id, :layout, :dashboard, - :project_view, + :project_view ) end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index c376bc8a785..7c0459648ef 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -269,7 +269,7 @@ class Projects::IssuesController < Projects::ApplicationController def issue_params params.require(:issue).permit( :title, :assignee_id, :position, :description, :confidential, - :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: [], assignee_ids: [], + :milestone_id, :due_date, :state_event, :task_num, :lock_version, label_ids: [], assignee_ids: [] ) end diff --git a/app/models/diff_discussion.rb b/app/models/diff_discussion.rb index d627fbe327f..14ddd2fcc88 100644 --- a/app/models/diff_discussion.rb +++ b/app/models/diff_discussion.rb @@ -39,7 +39,7 @@ class DiffDiscussion < Discussion def reply_attributes super.merge( original_position: original_position.to_json, - position: position.to_json, + position: position.to_json ) end end diff --git a/app/models/key.rb b/app/models/key.rb index 9c74ca84753..b7956052c3f 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -74,7 +74,7 @@ class Key < ActiveRecord::Base GitlabShellWorker.perform_async( :remove_key, shell_id, - key, + key ) end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 397dc7a25ab..a7ede5e3b9e 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -56,7 +56,7 @@ class Namespace < ActiveRecord::Base 'COALESCE(SUM(ps.storage_size), 0) AS storage_size', 'COALESCE(SUM(ps.repository_size), 0) AS repository_size', 'COALESCE(SUM(ps.lfs_objects_size), 0) AS lfs_objects_size', - 'COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size', + 'COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size' ) end diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 10a13c3fbdc..2a05d757eb4 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -37,7 +37,7 @@ class FlowdockService < Service repo: project.repository.path_to_repo, repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}", commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s", - diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s", + diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s" ) end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 0c797dd5814..0f645c9f1da 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -833,7 +833,7 @@ class Repository actual_options = options.merge( parents: [our_commit, their_commit], - tree: merge_index.write_tree(rugged), + tree: merge_index.write_tree(rugged) ) commit_id = create_commit(actual_options) diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index bfaf0eb2fae..0ae5864615a 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -39,7 +39,7 @@ class SentNotification < ActiveRecord::Base noteable_type: noteable.class.name, noteable_id: noteable_id, - commit_id: commit_id, + commit_id: commit_id ) create(attrs) diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index e73b1a4361a..ecabb2a48e4 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -38,7 +38,7 @@ module Boards attrs.merge!( add_label_ids: add_label_ids, remove_label_ids: remove_label_ids, - state_event: issue_state, + state_event: issue_state ) end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index c65c66d7150..646ccbdb2bf 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -298,7 +298,7 @@ class NotificationService recipients ||= NotificationRecipientService.new(pipeline.project).build_pipeline_recipients( pipeline, pipeline.user, - action: pipeline.status, + action: pipeline.status ).map(&:notification_email) if recipients.any? diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 628d6d7b4f6..ed476fc9d0c 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -51,7 +51,7 @@ class SystemHooksService path: model.path, group_id: model.id, owner_name: owner.respond_to?(:name) ? owner.name : nil, - owner_email: owner.respond_to?(:email) ? owner.email : nil, + owner_email: owner.respond_to?(:email) ? owner.email : nil ) when GroupMember data.merge!(group_member_data(model)) diff --git a/app/workers/repository_check/clear_worker.rb b/app/workers/repository_check/clear_worker.rb index 1f1b38540ee..85bc9103538 100644 --- a/app/workers/repository_check/clear_worker.rb +++ b/app/workers/repository_check/clear_worker.rb @@ -8,7 +8,7 @@ module RepositoryCheck Project.select(:id).find_in_batches(batch_size: 100) do |batch| Project.where(id: batch.map(&:id)).update_all( last_repository_check_failed: nil, - last_repository_check_at: nil, + last_repository_check_at: nil ) end end diff --git a/app/workers/repository_check/single_repository_worker.rb b/app/workers/repository_check/single_repository_worker.rb index 3d8bfc6fc6c..164586cf0b7 100644 --- a/app/workers/repository_check/single_repository_worker.rb +++ b/app/workers/repository_check/single_repository_worker.rb @@ -7,7 +7,7 @@ module RepositoryCheck project = Project.find(project_id) project.update_columns( last_repository_check_failed: !check(project), - last_repository_check_at: Time.now, + last_repository_check_at: Time.now ) end diff --git a/config/initializers/ar_monkey_patch.rb b/config/initializers/ar_monkey_patch.rb index 6979f4641b0..9266ff0f615 100644 --- a/config/initializers/ar_monkey_patch.rb +++ b/config/initializers/ar_monkey_patch.rb @@ -33,7 +33,7 @@ module ActiveRecord affected_rows = relation.where( self.class.primary_key => id, - lock_col => previous_lock_value, + lock_col => previous_lock_value ).update_all( attributes_for_update(attribute_names).map do |name| [name, _read_attribute(name)] diff --git a/config/initializers/hamlit.rb b/config/initializers/hamlit.rb index 7b545d8c06c..51dbffeda05 100644 --- a/config/initializers/hamlit.rb +++ b/config/initializers/hamlit.rb @@ -3,7 +3,7 @@ module Hamlit def call(template) Engine.new( generator: Temple::Generators::RailsOutputBuffer, - attr_quote: '"', + attr_quote: '"' ).call(template.source) end end @@ -11,7 +11,7 @@ end ActionView::Template.register_template_handler( :haml, - Hamlit::TemplateHandler.new, + Hamlit::TemplateHandler.new ) Hamlit::Filters.remove_filter('coffee') diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb index cc4f6a1d0fb..9ed96ddb0b4 100644 --- a/config/initializers/static_files.rb +++ b/config/initializers/static_files.rb @@ -30,14 +30,14 @@ if app.config.serve_static_files settings.merge!( host: Gitlab.config.gitlab.host, port: Gitlab.config.gitlab.port, - https: Gitlab.config.gitlab.https, + https: Gitlab.config.gitlab.https ) app.config.middleware.insert_before( Gitlab::Middleware::Static, Gitlab::Middleware::WebpackProxy, proxy_path: app.config.webpack.public_path, proxy_host: dev_server.host, - proxy_port: dev_server.port, + proxy_port: dev_server.port ) end diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb index 7dc33ab5683..b2194275751 100644 --- a/features/steps/explore/projects.rb +++ b/features/steps/explore/projects.rb @@ -101,7 +101,7 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps create(:merge_request, title: "Bug fix for public project", source_project: public_project, - target_project: public_project, + target_project: public_project ) end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 09d105f6b4c..450b2f8d9a9 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -24,7 +24,7 @@ module API def present_groups(groups, options = {}) options = options.reverse_merge( with: Entities::Group, - current_user: current_user, + current_user: current_user ) groups = groups.with_statistics if options[:statistics] diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 86bf567fe69..4caa210b9f2 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -301,7 +301,7 @@ module API UploadedFile.new( file_path, params["#{field}.name"], - params["#{field}.type"] || 'application/octet-stream', + params["#{field}.type"] || 'application/octet-stream' ) end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 687efe7da65..ed5004e8d1a 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -69,7 +69,7 @@ module API options = options.reverse_merge( with: Entities::Project, current_user: current_user, - simple: params[:simple], + simple: params[:simple] ) projects = filter_projects(projects) diff --git a/lib/api/v3/groups.rb b/lib/api/v3/groups.rb index 63d464b926b..0a63cfd3df1 100644 --- a/lib/api/v3/groups.rb +++ b/lib/api/v3/groups.rb @@ -20,7 +20,7 @@ module API def present_groups(groups, options = {}) options = options.reverse_merge( with: Entities::Group, - current_user: current_user, + current_user: current_user ) groups = groups.with_statistics if options[:statistics] diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb index 06cc704afc6..164612cb8dd 100644 --- a/lib/api/v3/projects.rb +++ b/lib/api/v3/projects.rb @@ -88,7 +88,7 @@ module API options = options.reverse_merge( with: ::API::V3::Entities::Project, current_user: current_user, - simple: params[:simple], + simple: params[:simple] ) projects = filter_projects(projects) diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index 12458f9f410..c1b31618e0d 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -90,7 +90,7 @@ module Gitlab name: blob_entry[:name], data: '', path: path, - commit_id: sha, + commit_id: sha ) end end diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb index b722d8a9f56..d41256d9a84 100644 --- a/lib/gitlab/git/tree.rb +++ b/lib/gitlab/git/tree.rb @@ -35,7 +35,7 @@ module Gitlab type: entry[:type], mode: entry[:filemode].to_s(8), path: path ? File.join(path, entry[:name]) : entry[:name], - commit_id: sha, + commit_id: sha ) end end diff --git a/lib/gitlab/gitaly_client/commit.rb b/lib/gitlab/gitaly_client/commit.rb index 0b001a9903d..8e9323b05e1 100644 --- a/lib/gitlab/gitaly_client/commit.rb +++ b/lib/gitlab/gitaly_client/commit.rb @@ -34,7 +34,7 @@ module Gitlab left_commit_id: parent_id, right_commit_id: commit.id, ignore_whitespace_change: options.fetch(:ignore_whitespace_change, false), - paths: options.fetch(:paths, []), + paths: options.fetch(:paths, []) ) Gitlab::Git::DiffCollection.new(stub.commit_diff(request), options) diff --git a/lib/gitlab/gitaly_client/util.rb b/lib/gitlab/gitaly_client/util.rb index 4acd297f5cb..86d055d3533 100644 --- a/lib/gitlab/gitaly_client/util.rb +++ b/lib/gitlab/gitaly_client/util.rb @@ -6,7 +6,7 @@ module Gitlab Gitaly::Repository.new( path: File.join(Gitlab.config.repositories.storages[repository_storage]['path'], relative_path), storage_name: repository_storage, - relative_path: relative_path, + relative_path: relative_path ) end end diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb index 46deea3cc9f..6fdf68641e2 100644 --- a/lib/gitlab/ldap/config.rb +++ b/lib/gitlab/ldap/config.rb @@ -39,7 +39,7 @@ module Gitlab def adapter_options opts = base_options.merge( - encryption: encryption, + encryption: encryption ) opts.merge!(auth_options) if has_auth? diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb index 117fc508135..2442c2ded3b 100644 --- a/lib/gitlab/sentry.rb +++ b/lib/gitlab/sentry.rb @@ -11,7 +11,7 @@ module Gitlab Raven.user_context( id: current_user.id, email: current_user.email, - username: current_user.username, + username: current_user.username ) end end diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index f3b3dea5318..351e2b10595 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -165,7 +165,7 @@ module Gitlab encoded_message, secret, true, - { iss: 'gitlab-workhorse', verify_iss: true, algorithm: 'HS256' }, + { iss: 'gitlab-workhorse', verify_iss: true, algorithm: 'HS256' } ) end diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index 073b87a1cb4..765d2bdbf7a 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -33,7 +33,7 @@ describe GroupsController do before do create_list(:award_emoji, 3, awardable: issue_2) create_list(:award_emoji, 2, awardable: issue_1) - create_list(:award_emoji, 2, :downvote, awardable: issue_2,) + create_list(:award_emoji, 2, :downvote, awardable: issue_2) sign_in(user) end diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb index 855247de2ea..ab5c42365fe 100644 --- a/spec/features/admin/admin_uses_repository_checks_spec.rb +++ b/spec/features/admin/admin_uses_repository_checks_spec.rb @@ -23,7 +23,7 @@ feature 'Admin uses repository checks', feature: true do project = create(:empty_project) project.update_columns( last_repository_check_failed: true, - last_repository_check_at: Time.now, + last_repository_check_at: Time.now ) visit_admin_project_page(project) diff --git a/spec/features/copy_as_gfm_spec.rb b/spec/features/copy_as_gfm_spec.rb index f197fb44608..be615519a09 100644 --- a/spec/features/copy_as_gfm_spec.rb +++ b/spec/features/copy_as_gfm_spec.rb @@ -96,7 +96,7 @@ describe 'Copy as GFM', feature: true, js: true do # issue link "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue)})", # issue link with note anchor - "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})", + "[Issue](#{namespace_project_issue_url(@project.namespace, @project, @feat.issue, anchor: 'note_123')})" ) verify( diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 8cc96c7b00f..5f82cf2f5e5 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -22,7 +22,7 @@ describe 'Pipelines', :feature, :js do project: project, ref: 'master', status: 'running', - sha: project.commit.id, + sha: project.commit.id ) end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index d1d2dcc4998..fe2c00bb2ca 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -716,7 +716,7 @@ module Ci expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( paths: ["logs/", "binaries/"], untracked: true, - key: 'key', + key: 'key' ) end @@ -734,7 +734,7 @@ module Ci expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( paths: ["logs/", "binaries/"], untracked: true, - key: 'key', + key: 'key' ) end @@ -753,7 +753,7 @@ module Ci expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( paths: ["test/"], untracked: false, - key: 'local', + key: 'local' ) end end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb index d4a43192d03..50bc3ef1b7c 100644 --- a/spec/lib/gitlab/auth_spec.rb +++ b/spec/lib/gitlab/auth_spec.rb @@ -175,7 +175,7 @@ describe Gitlab::Auth, lib: true do user = create( :user, username: 'normal_user', - password: 'my-secret', + password: 'my-secret' ) expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')) @@ -186,7 +186,7 @@ describe Gitlab::Auth, lib: true do user = create( :user, username: 'oauth2', - password: 'my-secret', + password: 'my-secret' ) expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip')) diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb index 684d01e9056..cf03acbfd3a 100644 --- a/spec/lib/gitlab/ci/config/entry/global_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb @@ -167,7 +167,7 @@ describe Gitlab::Ci::Config::Entry::Global do cache: { key: 'k', untracked: true, paths: ['public/'] }, variables: {}, ignore: false, - after_script: ['make clean'] }, + after_script: ['make clean'] } ) end end diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb index e18a219ef36..79632e2b6a3 100644 --- a/spec/lib/gitlab/contributions_calendar_spec.rb +++ b/spec/lib/gitlab/contributions_calendar_spec.rb @@ -47,7 +47,7 @@ describe Gitlab::ContributionsCalendar do action: Event::CREATED, target: @targets[project], author: contributor, - created_at: day, + created_at: day ) end diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index 7253a2edeff..4189aaef643 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -120,7 +120,7 @@ EOT new_mode: 0100644, from_id: '357406f3075a57708d0163752905cc1576fceacc', to_id: '8e5177d718c561d36efde08bad36b43687ee6bf0', - raw_chunks: raw_chunks, + raw_chunks: raw_chunks ) ) end diff --git a/spec/lib/gitlab/gitaly_client/commit_spec.rb b/spec/lib/gitlab/gitaly_client/commit_spec.rb index abe08ccdfa1..08c072caf8c 100644 --- a/spec/lib/gitlab/gitaly_client/commit_spec.rb +++ b/spec/lib/gitlab/gitaly_client/commit_spec.rb @@ -12,7 +12,7 @@ describe Gitlab::GitalyClient::Commit do request = Gitaly::CommitDiffRequest.new( repository: repository_message, left_commit_id: 'cfe32cf61b73a0d5e9f13e774abde7ff789b1660', - right_commit_id: commit.id, + right_commit_id: commit.id ) expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request) @@ -27,7 +27,7 @@ describe Gitlab::GitalyClient::Commit do request = Gitaly::CommitDiffRequest.new( repository: repository_message, left_commit_id: '4b825dc642cb6eb9a060e54bf8d69288fbee4904', - right_commit_id: initial_commit.id, + right_commit_id: initial_commit.id ) expect_any_instance_of(Gitaly::Diff::Stub).to receive(:commit_diff).with(request) diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 80966b99292..c1c2f2a7219 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -168,7 +168,7 @@ describe KubernetesService, models: true, caching: true do { key: 'KUBE_TOKEN', value: 'token', public: false }, { key: 'KUBE_NAMESPACE', value: 'my-project', public: true }, { key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true }, - { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true }, + { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true } ) end end @@ -179,7 +179,7 @@ describe KubernetesService, models: true, caching: true do { key: 'KUBE_URL', value: 'https://kube.domain.com', public: true }, { key: 'KUBE_TOKEN', value: 'token', public: false }, { key: 'KUBE_CA_PEM', value: 'CA PEM DATA', public: true }, - { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true }, + { key: 'KUBE_CA_PEM_FILE', value: 'CA PEM DATA', public: true, file: true } ) end diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb index ff29f6f66ba..c5ffbda9821 100644 --- a/spec/models/project_statistics_spec.rb +++ b/spec/models/project_statistics_spec.rb @@ -35,7 +35,7 @@ describe ProjectStatistics, models: true do commit_count: 8.exabytes - 1, repository_size: 2.exabytes, lfs_objects_size: 2.exabytes, - build_artifacts_size: 4.exabytes - 1, + build_artifacts_size: 4.exabytes - 1 ) statistics.reload @@ -149,7 +149,7 @@ describe ProjectStatistics, models: true do it "sums all storage counters" do statistics.update!( repository_size: 2, - lfs_objects_size: 3, + lfs_objects_size: 3 ) statistics.reload diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 108f73bb965..286de277ae7 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -185,7 +185,7 @@ describe Ci::API::Builds do { "key" => "CI_PIPELINE_TRIGGERED", "value" => "true", "public" => true }, { "key" => "DB_NAME", "value" => "postgres", "public" => true }, { "key" => "SECRET_KEY", "value" => "secret_value", "public" => false }, - { "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false }, + { "key" => "TRIGGER_KEY_1", "value" => "TRIGGER_VALUE_1", "public" => false } ) end end diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index 1a5d388382d..05176c3beaa 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -61,7 +61,7 @@ describe 'OpenID Connect requests' do email: private_email.email, public_email: public_email.email, website_url: 'https://example.com', - avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png"), + avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png") ) end diff --git a/spec/services/issues/build_service_spec.rb b/spec/services/issues/build_service_spec.rb index 55d635235b0..bed25fe7ccf 100644 --- a/spec/services/issues/build_service_spec.rb +++ b/spec/services/issues/build_service_spec.rb @@ -136,7 +136,7 @@ describe Issues::BuildService, services: true do user, title: 'Issue #1', description: 'Issue description', - milestone_id: milestone.id, + milestone_id: milestone.id ).execute expect(issue.title).to eq('Issue #1') diff --git a/spec/services/issues/resolve_discussions_spec.rb b/spec/services/issues/resolve_discussions_spec.rb index c3b4c2176ee..86f218dec12 100644 --- a/spec/services/issues/resolve_discussions_spec.rb +++ b/spec/services/issues/resolve_discussions_spec.rb @@ -77,7 +77,7 @@ describe Issues::ResolveDiscussions, services: true do _second_discussion = Discussion.new([create(:diff_note_on_merge_request, :resolved, noteable: merge_request, project: merge_request.target_project, - line_number: 15, + line_number: 15 )]) service = DummyService.new( project, diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb index 7a590f64e3c..8c5303b61cc 100644 --- a/spec/workers/git_garbage_collect_worker_spec.rb +++ b/spec/workers/git_garbage_collect_worker_spec.rb @@ -105,7 +105,7 @@ describe GitGarbageCollectWorker do author: Gitlab::Git.committer_hash(email: 'foo@bar', name: 'baz'), committer: Gitlab::Git.committer_hash(email: 'foo@bar', name: 'baz'), tree: old_commit.tree, - parents: [old_commit], + parents: [old_commit] ) GitOperationService.new(nil, project.repository).send( :update_ref, diff --git a/spec/workers/repository_check/clear_worker_spec.rb b/spec/workers/repository_check/clear_worker_spec.rb index a3b70c74787..3b1a64c5057 100644 --- a/spec/workers/repository_check/clear_worker_spec.rb +++ b/spec/workers/repository_check/clear_worker_spec.rb @@ -5,7 +5,7 @@ describe RepositoryCheck::ClearWorker do project = create(:empty_project) project.update_columns( last_repository_check_failed: true, - last_repository_check_at: Time.now, + last_repository_check_at: Time.now ) described_class.new.perform From 9d08dddfdb0ef66de9ec3b7c7cbfd96e2f949678 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 10 May 2017 13:30:54 -0300 Subject: [PATCH 31/38] Ensure all Serializers receive `current_user` instead `user` --- app/controllers/projects/deployments_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/deployments_controller.rb b/app/controllers/projects/deployments_controller.rb index b33c0b00ad9..dcdb2a8fa3e 100644 --- a/app/controllers/projects/deployments_controller.rb +++ b/app/controllers/projects/deployments_controller.rb @@ -6,7 +6,7 @@ class Projects::DeploymentsController < Projects::ApplicationController deployments = environment.deployments.reorder(created_at: :desc) deployments = deployments.where('created_at > ?', params[:after].to_time) if params[:after]&.to_time - render json: { deployments: DeploymentSerializer.new(user: @current_user, project: project) + render json: { deployments: DeploymentSerializer.new(current_user: @current_user, project: project) .represent_concise(deployments) } end From 343e37b88e06cda9fd3744ac15dff7f15e1c0aa4 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 10 May 2017 16:57:39 +0000 Subject: [PATCH 32/38] Center all empty states --- app/assets/stylesheets/framework/blocks.scss | 21 +++---------------- app/assets/stylesheets/pages/pipelines.scss | 4 ---- .../shared/empty_states/_issues.html.haml | 4 ++-- .../shared/empty_states/_labels.html.haml | 4 ++-- .../empty_states/_merge_requests.html.haml | 4 ++-- changelogs/unreleased/30949-empty-states.yml | 4 ++++ 6 files changed, 13 insertions(+), 28 deletions(-) create mode 100644 changelogs/unreleased/30949-empty-states.yml diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index ac1fc0eb8ae..3dec911d289 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -312,7 +312,7 @@ } .empty-state { - margin: 100px 0 0; + margin: 5% auto 0; .text-content { max-width: 460px; @@ -335,27 +335,12 @@ } .btn { - margin: $btn-side-margin $btn-side-margin 0 0; - } + margin: $btn-side-margin 5px; - @media(max-width: $screen-xs-max) { - margin-top: 50px; - text-align: center; - - .btn { + @media(max-width: $screen-xs-max) { width: 100%; } } - - @media(min-width: $screen-xs-max) { - &.merge-requests .text-content { - margin-top: 40px; - } - - &.labels .text-content { - margin-top: 70px; - } - } } .flex-container-block { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index e7553c7a4bf..685b9775fe1 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -8,10 +8,6 @@ white-space: nowrap; } - .empty-state { - margin: 5% auto 0; - } - .table-holder { width: 100%; diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml index c229d18903f..12d99c3ab4b 100644 --- a/app/views/shared/empty_states/_issues.html.haml +++ b/app/views/shared/empty_states/_issues.html.haml @@ -3,10 +3,10 @@ - has_button = button_path || project_select_button .row.empty-state - .pull-right.col-xs-12{ class: "#{'col-sm-6' if has_button}" } + .col-xs-12 .svg-content = render 'shared/empty_states/icons/issues.svg' - .col-xs-12{ class: "#{'col-sm-6' if has_button}" } + .col-xs-12.text-center .text-content - if has_button && current_user %h4 diff --git a/app/views/shared/empty_states/_labels.html.haml b/app/views/shared/empty_states/_labels.html.haml index 00fb77bdb3b..5e2f4cf109d 100644 --- a/app/views/shared/empty_states/_labels.html.haml +++ b/app/views/shared/empty_states/_labels.html.haml @@ -1,8 +1,8 @@ .row.empty-state.labels - .pull-right.col-xs-12.col-sm-6 + .col-xs-12 .svg-content = render 'shared/empty_states/icons/labels.svg' - .col-xs-12.col-sm-6 + .col-xs-12.text-center .text-content %h4 Labels can be applied to issues and merge requests to categorize them. %p You can also star a label to make it a priority label. diff --git a/app/views/shared/empty_states/_merge_requests.html.haml b/app/views/shared/empty_states/_merge_requests.html.haml index 7f2f99f3406..3e64f403b8b 100644 --- a/app/views/shared/empty_states/_merge_requests.html.haml +++ b/app/views/shared/empty_states/_merge_requests.html.haml @@ -3,10 +3,10 @@ - has_button = button_path || project_select_button .row.empty-state.merge-requests - .col-xs-12{ class: "#{'col-sm-6 pull-right' if has_button}" } + .col-xs-12 .svg-content = render 'shared/empty_states/icons/merge_requests.svg' - .col-xs-12{ class: "#{'col-sm-6' if has_button}" } + .col-xs-12.text-center .text-content - if has_button %h4 diff --git a/changelogs/unreleased/30949-empty-states.yml b/changelogs/unreleased/30949-empty-states.yml new file mode 100644 index 00000000000..bef87a954b7 --- /dev/null +++ b/changelogs/unreleased/30949-empty-states.yml @@ -0,0 +1,4 @@ +--- +title: Center all empty states +merge_request: +author: From f4b6865aef48b6c328fd297b499b53f5d37260d9 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 10 May 2017 13:36:15 -0400 Subject: [PATCH 33/38] Fix aria labels on custom notifications modal --- .../shared/notifications/_custom_notifications.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/shared/notifications/_custom_notifications.html.haml b/app/views/shared/notifications/_custom_notifications.html.haml index 708adbc38f1..183ed34fba1 100644 --- a/app/views/shared/notifications/_custom_notifications.html.haml +++ b/app/views/shared/notifications/_custom_notifications.html.haml @@ -1,9 +1,9 @@ -.modal.fade{ tabindex: "-1", role: "dialog", id: notifications_menu_identifier("modal", notification_setting), aria: { labelledby: "custom-notifications-title" } } +.modal.fade{ tabindex: "-1", role: "dialog", id: notifications_menu_identifier("modal", notification_setting), "aria-labelledby": "custom-notifications-title" } .modal-dialog .modal-content .modal-header - %button.close{ type: "button", data: { dismiss: "modal" }, aria: { label: "close" } } - %span{ aria: { hidden: "true" } } × + %button.close{ type: "button", "aria-label": "close", data: { dismiss: "modal" } } + %span{ "aria-hidden": "true" } } × %h4#custom-notifications-title.modal-title Custom notification events From 368bf99eb758a738d225134e3baa6763869e7541 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 10 May 2017 15:21:31 -0300 Subject: [PATCH 34/38] Remove unnecessary user hash being passed to DeploymentSerializer --- app/controllers/projects/deployments_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/deployments_controller.rb b/app/controllers/projects/deployments_controller.rb index dcdb2a8fa3e..f06a4d943f3 100644 --- a/app/controllers/projects/deployments_controller.rb +++ b/app/controllers/projects/deployments_controller.rb @@ -6,7 +6,7 @@ class Projects::DeploymentsController < Projects::ApplicationController deployments = environment.deployments.reorder(created_at: :desc) deployments = deployments.where('created_at > ?', params[:after].to_time) if params[:after]&.to_time - render json: { deployments: DeploymentSerializer.new(current_user: @current_user, project: project) + render json: { deployments: DeploymentSerializer.new(project: project) .represent_concise(deployments) } end From d5eb66663e757706e734ebff7f61918fed898075 Mon Sep 17 00:00:00 2001 From: Marcia Ramos Date: Wed, 10 May 2017 15:48:07 -0300 Subject: [PATCH 35/38] update API doc with +API (h1 heading) --- doc/api/access_requests.md | 2 +- doc/api/enviroments.md | 2 +- doc/api/groups.md | 2 +- doc/api/issues.md | 2 +- doc/api/keys.md | 2 +- doc/api/labels.md | 2 +- doc/api/members.md | 2 +- doc/api/merge_requests.md | 2 +- doc/api/notification_settings.md | 2 +- doc/api/projects.md | 2 +- doc/api/settings.md | 2 +- doc/api/snippets.md | 2 +- doc/api/todos.md | 2 +- doc/api/users.md | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/doc/api/access_requests.md b/doc/api/access_requests.md index 21de7d18632..603fa4a8194 100644 --- a/doc/api/access_requests.md +++ b/doc/api/access_requests.md @@ -1,4 +1,4 @@ -# Group and project access requests +# Group and project access requests API >**Note:** This feature was introduced in GitLab 8.11 diff --git a/doc/api/enviroments.md b/doc/api/enviroments.md index 49930f01945..5ca766bf87d 100644 --- a/doc/api/enviroments.md +++ b/doc/api/enviroments.md @@ -1,4 +1,4 @@ -# Environments +# Environments API ## List environments diff --git a/doc/api/groups.md b/doc/api/groups.md index bc61bfec9b9..2b3d8e125c8 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -1,4 +1,4 @@ -# Groups +# Groups API ## List groups diff --git a/doc/api/issues.md b/doc/api/issues.md index 1d43b1298b9..75794cc8d04 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -1,4 +1,4 @@ -# Issues +# Issues API Every API call to issues must be authenticated. diff --git a/doc/api/keys.md b/doc/api/keys.md index 3ace1040f38..376ac27df3a 100644 --- a/doc/api/keys.md +++ b/doc/api/keys.md @@ -1,4 +1,4 @@ -# Keys +# Keys API ## Get SSH key with user by ID of an SSH key diff --git a/doc/api/labels.md b/doc/api/labels.md index 778348ea371..ec93cf50e7a 100644 --- a/doc/api/labels.md +++ b/doc/api/labels.md @@ -1,4 +1,4 @@ -# Labels +# Labels API ## List labels diff --git a/doc/api/members.md b/doc/api/members.md index 3c661284f11..3234f833eae 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -1,4 +1,4 @@ -# Group and project members +# Group and project members API **Valid access levels** diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index dde855b2bd4..cb22b67f556 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -1,4 +1,4 @@ -# Merge requests +# Merge requests API ## List merge requests diff --git a/doc/api/notification_settings.md b/doc/api/notification_settings.md index 43047917f77..3a2c398e355 100644 --- a/doc/api/notification_settings.md +++ b/doc/api/notification_settings.md @@ -1,4 +1,4 @@ -# Notification settings +# Notification settings API >**Note:** This feature was [introduced][ce-5632] in GitLab 8.12. diff --git a/doc/api/projects.md b/doc/api/projects.md index 188fbe7447d..673cf02705d 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1,4 +1,4 @@ -# Projects +# Projects API ### Project visibility level diff --git a/doc/api/settings.md b/doc/api/settings.md index d99695ca986..eefbdda42ce 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -1,4 +1,4 @@ -# Application settings +# Application settings API These API calls allow you to read and modify GitLab instance application settings as appear in `/admin/application_settings`. You have to be an diff --git a/doc/api/snippets.md b/doc/api/snippets.md index e09d930698e..fb8cf97896c 100644 --- a/doc/api/snippets.md +++ b/doc/api/snippets.md @@ -1,4 +1,4 @@ -# Snippets +# Snippets API > [Introduced][ce-6373] in GitLab 8.15. diff --git a/doc/api/todos.md b/doc/api/todos.md index 77667a57195..dd4c737b729 100644 --- a/doc/api/todos.md +++ b/doc/api/todos.md @@ -1,4 +1,4 @@ -# Todos +# Todos API > [Introduced][ce-3188] in GitLab 8.10. diff --git a/doc/api/users.md b/doc/api/users.md index 86027bcc05c..331f9a9b80b 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -1,4 +1,4 @@ -# Users +# Users API ## List users From 2434d135d6d7953726d1a521fd9faf6ee4590b8a Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Sun, 7 May 2017 22:20:37 -0400 Subject: [PATCH 36/38] Keep input data after creating existing tag --- app/controllers/projects/tags_controller.rb | 2 ++ app/views/projects/_zen.html.haml | 3 ++- app/views/projects/tags/new.html.haml | 4 ++-- ...l-inputs-when-you-try-to-add-a-tag-that-already-exists.yml | 4 ++++ 4 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/31625-tag-editor-loses-all-inputs-when-you-try-to-add-a-tag-that-already-exists.yml diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 750c3ec486a..afbea3e2b40 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -38,6 +38,8 @@ class Projects::TagsController < Projects::ApplicationController redirect_to namespace_project_tag_path(@project.namespace, @project, @tag.name) else @error = result[:message] + @message = params[:message] + @release_description = params[:release_description] render action: 'new' end end diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml index 0c8241053e7..3b3d08ddd3c 100644 --- a/app/views/projects/_zen.html.haml +++ b/app/views/projects/_zen.html.haml @@ -1,10 +1,11 @@ - @gfm_form = true +- current_text ||= nil - supports_slash_commands = local_assigns.fetch(:supports_slash_commands, false) .zen-backdrop - classes << ' js-gfm-input js-autosize markdown-area' - if defined?(f) && f = f.text_area attr, class: classes, placeholder: placeholder, data: { supports_slash_commands: supports_slash_commands } - else - = text_area_tag attr, nil, class: classes, placeholder: placeholder + = text_area_tag attr, current_text, class: classes, placeholder: placeholder %a.zen-control.zen-control-leave.js-zen-leave{ href: "#" } = icon('compress') diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index 7c607d2956b..cbf841762b7 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -22,14 +22,14 @@ .form-group = label_tag :message, nil, class: 'control-label' .col-sm-10 - = text_area_tag :message, nil, required: false, tabindex: 3, class: 'form-control', rows: 5 + = text_area_tag :message, @message, required: false, tabindex: 3, class: 'form-control', rows: 5 .help-block Optionally, add a message to the tag. %hr .form-group = label_tag :release_description, 'Release notes', class: 'control-label' .col-sm-10 = render layout: 'projects/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do - = render 'projects/zen', attr: :release_description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here..." + = render 'projects/zen', attr: :release_description, classes: 'note-textarea', placeholder: "Write your release notes or drag files here...", current_text: @release_description = render 'shared/notes/hints' .help-block Optionally, add release notes to the tag. They will be stored in the GitLab database and displayed on the tags page. .form-actions diff --git a/changelogs/unreleased/31625-tag-editor-loses-all-inputs-when-you-try-to-add-a-tag-that-already-exists.yml b/changelogs/unreleased/31625-tag-editor-loses-all-inputs-when-you-try-to-add-a-tag-that-already-exists.yml new file mode 100644 index 00000000000..aae760b0ef5 --- /dev/null +++ b/changelogs/unreleased/31625-tag-editor-loses-all-inputs-when-you-try-to-add-a-tag-that-already-exists.yml @@ -0,0 +1,4 @@ +--- +title: Keep input data after creating a tag that already exists +merge_request: 11155 +author: From b304d41232ce591bcea9a2b86df9b924950126ef Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Wed, 10 May 2017 22:01:00 +0000 Subject: [PATCH 37/38] Track pending requests in AjaxCache --- .../javascripts/lib/utils/ajax_cache.js | 68 ++++++++++++------- spec/javascripts/lib/utils/ajax_cache_spec.js | 57 ++++++++++++---- 2 files changed, 88 insertions(+), 37 deletions(-) diff --git a/app/assets/javascripts/lib/utils/ajax_cache.js b/app/assets/javascripts/lib/utils/ajax_cache.js index d99eefb5089..cf030d613df 100644 --- a/app/assets/javascripts/lib/utils/ajax_cache.js +++ b/app/assets/javascripts/lib/utils/ajax_cache.js @@ -1,32 +1,54 @@ -const AjaxCache = { - internalStorage: { }, +class AjaxCache { + constructor() { + this.internalStorage = { }; + this.pendingRequests = { }; + } + get(endpoint) { return this.internalStorage[endpoint]; - }, + } + hasData(endpoint) { return Object.prototype.hasOwnProperty.call(this.internalStorage, endpoint); - }, - purge(endpoint) { + } + + remove(endpoint) { delete this.internalStorage[endpoint]; - }, + } + retrieve(endpoint) { - if (AjaxCache.hasData(endpoint)) { - return Promise.resolve(AjaxCache.get(endpoint)); + if (this.hasData(endpoint)) { + return Promise.resolve(this.get(endpoint)); } - return new Promise((resolve, reject) => { - $.ajax(endpoint) // eslint-disable-line promise/catch-or-return - .then(data => resolve(data), - (jqXHR, textStatus, errorThrown) => { - const error = new Error(`${endpoint}: ${errorThrown}`); - error.textStatus = textStatus; - reject(error); - }, - ); - }) - .then((data) => { this.internalStorage[endpoint] = data; }) - .then(() => AjaxCache.get(endpoint)); - }, -}; + let pendingRequest = this.pendingRequests[endpoint]; -export default AjaxCache; + if (!pendingRequest) { + pendingRequest = new Promise((resolve, reject) => { + // jQuery 2 is not Promises/A+ compatible (missing catch) + $.ajax(endpoint) // eslint-disable-line promise/catch-or-return + .then(data => resolve(data), + (jqXHR, textStatus, errorThrown) => { + const error = new Error(`${endpoint}: ${errorThrown}`); + error.textStatus = textStatus; + reject(error); + }, + ); + }) + .then((data) => { + this.internalStorage[endpoint] = data; + delete this.pendingRequests[endpoint]; + }) + .catch((error) => { + delete this.pendingRequests[endpoint]; + throw error; + }); + + this.pendingRequests[endpoint] = pendingRequest; + } + + return pendingRequest.then(() => this.get(endpoint)); + } +} + +export default new AjaxCache(); diff --git a/spec/javascripts/lib/utils/ajax_cache_spec.js b/spec/javascripts/lib/utils/ajax_cache_spec.js index 7b466a11b92..e1747a82329 100644 --- a/spec/javascripts/lib/utils/ajax_cache_spec.js +++ b/spec/javascripts/lib/utils/ajax_cache_spec.js @@ -5,19 +5,13 @@ describe('AjaxCache', () => { const dummyResponse = { important: 'dummy data', }; - let ajaxSpy = (url) => { - expect(url).toBe(dummyEndpoint); - const deferred = $.Deferred(); - deferred.resolve(dummyResponse); - return deferred.promise(); - }; beforeEach(() => { AjaxCache.internalStorage = { }; - spyOn(jQuery, 'ajax').and.callFake(url => ajaxSpy(url)); + AjaxCache.pendingRequests = { }; }); - describe('#get', () => { + describe('get', () => { it('returns undefined if cache is empty', () => { const data = AjaxCache.get(dummyEndpoint); @@ -41,7 +35,7 @@ describe('AjaxCache', () => { }); }); - describe('#hasData', () => { + describe('hasData', () => { it('returns false if cache is empty', () => { expect(AjaxCache.hasData(dummyEndpoint)).toBe(false); }); @@ -59,9 +53,9 @@ describe('AjaxCache', () => { }); }); - describe('#purge', () => { + describe('remove', () => { it('does nothing if cache is empty', () => { - AjaxCache.purge(dummyEndpoint); + AjaxCache.remove(dummyEndpoint); expect(AjaxCache.internalStorage).toEqual({ }); }); @@ -69,7 +63,7 @@ describe('AjaxCache', () => { it('does nothing if cache contains no matching data', () => { AjaxCache.internalStorage['not matching'] = dummyResponse; - AjaxCache.purge(dummyEndpoint); + AjaxCache.remove(dummyEndpoint); expect(AjaxCache.internalStorage['not matching']).toBe(dummyResponse); }); @@ -77,14 +71,27 @@ describe('AjaxCache', () => { it('removes matching data', () => { AjaxCache.internalStorage[dummyEndpoint] = dummyResponse; - AjaxCache.purge(dummyEndpoint); + AjaxCache.remove(dummyEndpoint); expect(AjaxCache.internalStorage).toEqual({ }); }); }); - describe('#retrieve', () => { + describe('retrieve', () => { + let ajaxSpy; + + beforeEach(() => { + spyOn(jQuery, 'ajax').and.callFake(url => ajaxSpy(url)); + }); + it('stores and returns data from Ajax call if cache is empty', (done) => { + ajaxSpy = (url) => { + expect(url).toBe(dummyEndpoint); + const deferred = $.Deferred(); + deferred.resolve(dummyResponse); + return deferred.promise(); + }; + AjaxCache.retrieve(dummyEndpoint) .then((data) => { expect(data).toBe(dummyResponse); @@ -94,6 +101,28 @@ describe('AjaxCache', () => { .catch(fail); }); + it('makes no Ajax call if request is pending', () => { + const responseDeferred = $.Deferred(); + + ajaxSpy = (url) => { + expect(url).toBe(dummyEndpoint); + // neither reject nor resolve to keep request pending + return responseDeferred.promise(); + }; + + const unexpectedResponse = data => fail(`Did not expect response: ${data}`); + + AjaxCache.retrieve(dummyEndpoint) + .then(unexpectedResponse) + .catch(fail); + + AjaxCache.retrieve(dummyEndpoint) + .then(unexpectedResponse) + .catch(fail); + + expect($.ajax.calls.count()).toBe(1); + }); + it('returns undefined if Ajax call fails and cache is empty', (done) => { const dummyStatusText = 'exploded'; const dummyErrorMessage = 'server exploded'; From 08edfb67b141b4f1b51fe624264c79dab3d2028e Mon Sep 17 00:00:00 2001 From: Eric Eastwood Date: Wed, 10 May 2017 22:15:03 -0500 Subject: [PATCH 38/38] Fix flickering of system notes Fix https://gitlab.com/gitlab-org/gitlab-ce/issues/32087 Easy reproduction: 1. Edit an issue title 1. Notice the system note added 1. Switch to another tab and back (to fire the polling immediately) 1. Notice the flicker (without this fix) --- app/views/shared/notes/_note.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml index 5c1156b06fb..87aae793966 100644 --- a/app/views/shared/notes/_note.html.haml +++ b/app/views/shared/notes/_note.html.haml @@ -29,6 +29,8 @@ - if note.system %span.system-note-message = note.redacted_note_html + .original-note-content.hidden + = note.note %a{ href: "##{dom_id(note)}" } = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago') - unless note.system?