diff options
60 files changed, 479 insertions, 226 deletions
diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml index 8f8cbc2072a..211ad359951 100644 --- a/.haml-lint_todo.yml +++ b/.haml-lint_todo.yml @@ -356,6 +356,7 @@ linters: - 'app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml' - 'app/views/shared/_commit_message_container.html.haml' - 'app/views/shared/_confirm_modal.html.haml' + - 'app/views/shared/_confirm_fork_modal.html.haml' - 'app/views/shared/_delete_label_modal.html.haml' - 'app/views/shared/_group_form.html.haml' - 'app/views/shared/_group_tips.html.haml' diff --git a/.rubocop.yml b/.rubocop.yml index 835c321c943..049340f90d4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -178,6 +178,11 @@ Gitlab/ModuleWithInstanceVariables: - spec/support/**/*.rb - features/steps/**/*.rb +Gitlab/ConstGetInheritFalse: + Enabled: true + Exclude: + - 'qa/bin/*' + Gitlab/HTTParty: Enabled: true Exclude: diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 6d1ec16b0c2..5c24b0e1704 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -32,6 +32,14 @@ module BlobHelper File.join(segments) end + def ide_fork_and_edit_path(project = @project, ref = @ref, path = @path, options = {}) + if current_user + project_forks_path(project, + namespace_key: current_user&.namespace&.id, + continue: edit_blob_fork_params(ide_edit_path(project, ref, path))) + end + end + def encode_ide_path(path) url_encode(path).gsub('%2F', '/') end diff --git a/app/models/clusters/concerns/application_version.rb b/app/models/clusters/concerns/application_version.rb index db94e8e08c9..6c0b014662c 100644 --- a/app/models/clusters/concerns/application_version.rb +++ b/app/models/clusters/concerns/application_version.rb @@ -8,13 +8,13 @@ module Clusters included do state_machine :status do before_transition any => [:installed, :updated] do |application| - application.version = application.class.const_get(:VERSION) + application.version = application.class.const_get(:VERSION, false) end end end def update_available? - version != self.class.const_get(:VERSION) + version != self.class.const_get(:VERSION, false) end end end diff --git a/app/models/concerns/prometheus_adapter.rb b/app/models/concerns/prometheus_adapter.rb index aab0589f7ca..9df77b565da 100644 --- a/app/models/concerns/prometheus_adapter.rb +++ b/app/models/concerns/prometheus_adapter.rb @@ -44,7 +44,7 @@ module PrometheusAdapter end def query_klass_for(query_name) - Gitlab::Prometheus::Queries.const_get("#{query_name.to_s.classify}Query") + Gitlab::Prometheus::Queries.const_get("#{query_name.to_s.classify}Query", false) end def build_query_args(*args) diff --git a/app/models/note.rb b/app/models/note.rb index 34736482387..4e9fd8d2dd1 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -24,7 +24,7 @@ class Note < ApplicationRecord class << self def values - constants.map {|const| self.const_get(const)} + constants.map {|const| self.const_get(const, false)} end def value?(val) diff --git a/app/models/upload.rb b/app/models/upload.rb index df8f9c56fa8..8c409641452 100644 --- a/app/models/upload.rb +++ b/app/models/upload.rb @@ -138,7 +138,7 @@ class Upload < ApplicationRecord end def uploader_class - Object.const_get(uploader) + Object.const_get(uploader, false) end def identifier diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml index 41cd044a5b0..38422d4533d 100644 --- a/app/views/projects/tree/_tree_header.html.haml +++ b/app/views/projects/tree/_tree_header.html.haml @@ -41,7 +41,7 @@ %li = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do #{ _('New directory') } - - elsif can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project) + - elsif can_create_mr_from_fork %li - continue_params = { to: project_new_blob_path(@project, @id), notice: edit_in_new_fork_notice, @@ -81,10 +81,15 @@ = render 'projects/find_file_link' - - if can_collaborate + - if can_create_mr_from_fork = succeed " " do - = link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do - = _('Web IDE') + - if can_collaborate || current_user&.already_forked?(@project) + = link_to ide_edit_path(@project, @ref, @path), class: 'btn btn-default qa-web-ide-button' do + = _('Web IDE') + - else + = link_to '#modal-confirm-fork', class: 'btn btn-default qa-web-ide-button', data: { target: '#modal-confirm-fork', toggle: 'modal'} do + = _('Web IDE') + = render 'shared/confirm_fork_modal', fork_path: ide_fork_and_edit_path(@project, @ref, @path) - if show_xcode_link?(@project) .project-action-button.project-xcode.inline diff --git a/app/views/shared/_confirm_fork_modal.html.haml b/app/views/shared/_confirm_fork_modal.html.haml new file mode 100644 index 00000000000..db50ea41387 --- /dev/null +++ b/app/views/shared/_confirm_fork_modal.html.haml @@ -0,0 +1,12 @@ +#modal-confirm-fork.modal.qa-confirm-fork-modal + .modal-dialog + .modal-content + .modal-header + %h3.page-title= _('Fork project?') + %button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') } + %span{ "aria-hidden": true } × + .modal-body.p-3 + %p= _("You're not allowed to %{tag_start}edit%{tag_end} files in this project directly. Please fork this project, make your changes there, and submit a merge request.") % { tag_start: '', tag_end: ''} + .modal-footer + = link_to _('Cancel'), '#', class: "btn btn-cancel", "data-dismiss" => "modal" + = link_to _('Fork project'), fork_path, class: 'btn btn-success', method: :post diff --git a/changelogs/unreleased/29835-webide-fork.yml b/changelogs/unreleased/29835-webide-fork.yml new file mode 100644 index 00000000000..1849b414a2d --- /dev/null +++ b/changelogs/unreleased/29835-webide-fork.yml @@ -0,0 +1,6 @@ +--- +title: Web IDE button should fork and open forked project when selected from read-only + project +merge_request: 17672 +author: +type: added diff --git a/config/initializers/fog_core_patch.rb b/config/initializers/fog_core_patch.rb index d3d02216d45..053e0460a19 100644 --- a/config/initializers/fog_core_patch.rb +++ b/config/initializers/fog_core_patch.rb @@ -34,6 +34,7 @@ module Fog # Gems that have not yet updated with the new fog-core namespace LEGACY_FOG_PROVIDERS = %w(google rackspace aliyun).freeze + # rubocop:disable Gitlab/ConstGetInheritFalse def service_provider_constant(service_name, provider_name) args = service_provider_search_args(service_name, provider_name) Fog.const_get(args.first).const_get(*const_get_args(args.second)) @@ -48,5 +49,6 @@ module Fog [provider_name, service_name] end end + # rubocop:enable Gitlab/ConstGetInheritFalse end end diff --git a/config/initializers/zz_metrics.rb b/config/initializers/zz_metrics.rb index 501ec8ccc06..bc28780cc77 100644 --- a/config/initializers/zz_metrics.rb +++ b/config/initializers/zz_metrics.rb @@ -13,7 +13,7 @@ def instrument_classes(instrumentation) instrumentation.instrument_methods(Gitlab::Git) Gitlab::Git.constants.each do |name| - const = Gitlab::Git.const_get(name) + const = Gitlab::Git.const_get(name, false) next unless const.is_a?(Module) @@ -75,7 +75,7 @@ def instrument_classes(instrumentation) instrumentation.instrument_instance_methods(Rouge::Formatters::HTMLGitlab) [:XML, :HTML].each do |namespace| - namespace_mod = Nokogiri.const_get(namespace) + namespace_mod = Nokogiri.const_get(namespace, false) instrumentation.instrument_methods(namespace_mod) instrumentation.instrument_methods(namespace_mod::Document) diff --git a/config/settings.rb b/config/settings.rb index 8756c120645..767c6c56337 100644 --- a/config/settings.rb +++ b/config/settings.rb @@ -104,10 +104,10 @@ class Settings < Settingslogic # check that `current` (string or integer) is a contant in `modul`. def verify_constant(modul, current, default) - constant = modul.constants.find { |name| modul.const_get(name) == current } - value = constant.nil? ? default : modul.const_get(constant) + constant = modul.constants.find { |name| modul.const_get(name, false) == current } + value = constant.nil? ? default : modul.const_get(constant, false) if current.is_a? String - value = modul.const_get(current.upcase) rescue default + value = modul.const_get(current.upcase, false) rescue default end value diff --git a/doc/administration/geo/disaster_recovery/planned_failover.md b/doc/administration/geo/disaster_recovery/planned_failover.md index 75e07bcf863..8fee172ec64 100644 --- a/doc/administration/geo/disaster_recovery/planned_failover.md +++ b/doc/administration/geo/disaster_recovery/planned_failover.md @@ -43,23 +43,14 @@ will go smoothly. ### Object storage -Some classes of non-repository data can use object storage in preference to -file storage. Geo [does not replicate data in object storage](../replication/object_storage.md), -leaving that task up to the object store itself. For a planned failover, this -means you can decouple the replication of this data from the failover of the -GitLab service. - -If you're already using object storage, simply verify that your **secondary** -node has access to the same data as the **primary** node - they must either they share the -same object storage configuration, or the **secondary** node should be configured to -access a [geographically-replicated][os-repl] copy provided by the object store -itself. - If you have a large GitLab installation or cannot tolerate downtime, consider [migrating to Object Storage][os-conf] **before** scheduling a planned failover. Doing so reduces both the length of the maintenance window, and the risk of data loss as a result of a poorly executed planned failover. +In GitLab 12.4, you can optionally allow GitLab to manage replication of Object Storage for +**secondary** nodes. For more information, see [Object Storage replication][os-conf]. + ### Review the configuration of each **secondary** node Database settings are automatically replicated to the **secondary** node, but the @@ -224,5 +215,4 @@ Don't forget to remove the broadcast message after failover is complete. [background-verification]: background_verification.md [limitations]: ../replication/index.md#current-limitations [moving-repositories]: ../../operations/moving_repositories.md -[os-conf]: ../replication/object_storage.md#configuration -[os-repl]: ../replication/object_storage.md#replication +[os-conf]: ../replication/object_storage.md diff --git a/doc/administration/geo/replication/index.md b/doc/administration/geo/replication/index.md index bcad820531c..0e1ae2a2628 100644 --- a/doc/administration/geo/replication/index.md +++ b/doc/administration/geo/replication/index.md @@ -283,7 +283,6 @@ You can keep track of the progress to include the missing items in: | [Maven Packages](../../../user/packages/maven_repository/index.md) | No | No | | [Conan Packages](../../../user/packages/conan_repository/index.md) | No | No | | [External merge request diffs](../../merge_request_diffs.md) | No, if they are on-disk | No | -| Content in object storage ([track progress](https://gitlab.com/groups/gitlab-org/-/epics/1526)) | No | No | 1. The integrity can be verified manually using [Integrity Check Rake Task](../../raketasks/check.md) on both nodes and comparing the output between them. diff --git a/doc/administration/geo/replication/object_storage.md b/doc/administration/geo/replication/object_storage.md index 878b67a8f8e..c85005a3f91 100644 --- a/doc/administration/geo/replication/object_storage.md +++ b/doc/administration/geo/replication/object_storage.md @@ -1,16 +1,30 @@ # Geo with Object storage **(PREMIUM ONLY)** -Geo can be used in combination with Object Storage (AWS S3, or -other compatible object storage). +Geo can be used in combination with Object Storage (AWS S3, or other compatible object storage). -## Configuration +Currently, **secondary** nodes can use either: -At this time it is required that if object storage is enabled on the -**primary** node, it must also be enabled on each **secondary** node. +- The same storage bucket as the **primary** node. +- A replicated storage bucket. -**Secondary** nodes can use the same storage bucket as the **primary** node, or -they can use a replicated storage bucket. At this time GitLab does not -take care of content replication in object storage. +To have: + +- GitLab manage replication, follow [Enabling GitLab replication](#enabling-gitlab-managed-object-storage-replication). +- Third-party services manage replication, follow [Third-party replication services](#third-party-replication-services). + +## Enabling GitLab managed object storage replication + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/10586) in GitLab 12.4. + +**Secondary** nodes can replicate files stored on the **primary** node regardless of +whether they are stored on the local filesystem or in object storage. + +To enable GitLab replication, you must: + +1. Go to **Admin Area > Geo**. +1. Press **Edit** on the **secondary** node. +1. Enable the **Allow this secondary node to replicate content on Object Storage** + checkbox. For LFS, follow the documentation to [set up LFS object storage](../../../workflow/lfs/lfs_administration.md#storing-lfs-objects-in-remote-object-storage). @@ -20,12 +34,21 @@ For CI job artifacts, there is similar documentation to configure For user uploads, there is similar documentation to configure [upload object storage](../../uploads.md#using-object-storage-core-only) -You should enable and configure object storage on both **primary** and **secondary** -nodes. Migrating existing data to object storage should be performed on the -**primary** node only. **Secondary** nodes will automatically notice that the migrated -files are now in object storage. +If you want to migrate the **primary** node's files to object storage, you can +configure the **secondary** in a few ways: + +- Use the exact same object storage. +- Use a separate object store but leverage your object storage solution's built-in + replication. +- Use a separate object store and enable the **Allow this secondary node to replicate + content on Object Storage** setting. + +GitLab does not currently support the case where both: + +- The **primary** node uses local storage. +- A **secondary** node uses object storage. -## Replication +## Third-party replication services When using Amazon S3, you can use [CRR](https://docs.aws.amazon.com/AmazonS3/latest/dev/crr.html) to diff --git a/doc/administration/troubleshooting/postgresql.md b/doc/administration/troubleshooting/postgresql.md index 3bbc3f23d83..f427cd88ce0 100644 --- a/doc/administration/troubleshooting/postgresql.md +++ b/doc/administration/troubleshooting/postgresql.md @@ -31,30 +31,30 @@ This section is for links to information elsewhere in the GitLab documentation. - Destructively reseeding the GitLab database. - Guidance around updating packaged PostgreSQL, including how to stop it happening automatically. -- [More about external PostgreSQL](/ee/administration/external_database.html) +- [More about external PostgreSQL](../external_database.md) -- [Running GEO with external PostgreSQL](/ee/administration/geo/replication/external_database.html) +- [Running GEO with external PostgreSQL](../geo/replication/external_database.md) - [Upgrades when running PostgreSQL configured for HA.](https://docs.gitlab.com/omnibus/settings/database.html#upgrading-a-gitlab-ha-cluster) -- Consuming PostgreSQL from [within CI runners](/ee/ci/services/postgres.html) +- Consuming PostgreSQL from [within CI runners](../../ci/services/postgres.md) -- [Using Slony to update PostgreSQL](/ee/update/upgrading_postgresql_using_slony.html) +- [Using Slony to update PostgreSQL](../../update/upgrading_postgresql_using_slony.md) - Uses replication to handle PostgreSQL upgrades - providing the schemas are the same. - Reduces downtime to a short window for swinging over to the newer vewrsion. - Managing Omnibus PostgreSQL versions [from the development docs](https://docs.gitlab.com/omnibus/development/managing-postgresql-versions.html) -- [PostgreSQL scaling and HA](/ee/administration/high_availability/database.html) - - including [troubleshooting](/ee/administration/high_availability/database.html#troubleshooting) gitlab-ctl repmgr-check-master and pgbouncer errors +- [PostgreSQL scaling and HA](../high_availability/database.md) + - including [troubleshooting](../high_availability/database.md#troubleshooting) gitlab-ctl repmgr-check-master and pgbouncer errors -- [Developer database documentation](/ee/development/README.html#database-guides) - some of which is absolutely not for production use. Including: +- [Developer database documentation](../../development/README.md#database-guides) - some of which is absolutely not for production use. Including: - understanding EXPLAIN plans ### Troubleshooting/Fixes -- [GitLab database requirements](/ee/install/requirements.html#database) including - - Support for MySQL was removed in GitLab 12.1; [migrate to PostgreSQL](/ee/update/mysql_to_postgresql.html) +- [GitLab database requirements](../../install/requirements.md#database) including + - Support for MySQL was removed in GitLab 12.1; [migrate to PostgreSQL](../../update/mysql_to_postgresql.md) - required extension pg_trgm - required extension postgres_fdw for Geo @@ -71,7 +71,7 @@ pg_basebackup: could not create temporary replication slot "pg_basebackup_12345" HINT: Free one or increase max_replication_slots. ``` -- GEO [replication errors](/ee/administration/geo/replication/troubleshooting.html#fixing-replication-errors) including: +- GEO [replication errors](../geo/replication/troubleshooting.md#fixing-replication-errors) including: ``` ERROR: replication slots can only be used if max_replication_slots > 0 @@ -83,11 +83,11 @@ Command exceeded allowed execution time PANIC: could not write to file ‘pg_xlog/xlogtemp.123’: No space left on device ``` -- [Checking GEO configuration](/ee/administration/geo/replication/troubleshooting.html#checking-configuration) including +- [Checking GEO configuration](../geo/replication/troubleshooting.md#checking-configuration) including - reconfiguring hosts/ports - checking and fixing user/password mappings -- [Common GEO errors](/ee/administration/geo/replication/troubleshooting.html#fixing-common-errors) +- [Common GEO errors](../geo/replication/troubleshooting.md#fixing-common-errors) ## Support topics diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 114581e1e5d..628c3f01043 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1087,7 +1087,7 @@ Manual actions are considered to be write actions, so permissions for a user wants to trigger an action. In other words, in order to trigger a manual action assigned to a branch that the pipeline is running for, the user needs to have the ability to merge to this branch. It is possible to use protected environments -to more strictly [protect manual deployments](#protecting-manual-jobs) from being +to more strictly [protect manual deployments](#protecting-manual-jobs-premium) from being run by unauthorized users. NOTE: **Note:** @@ -1095,36 +1095,38 @@ Using `when:manual` and `trigger` together results in the error `jobs:#{job-name should be on_success, on_failure or always`, because `when:manual` prevents triggers being used. -##### Protecting manual jobs +##### Protecting manual jobs **(PREMIUM)** It's possible to use [protected environments](../environments/protected_environments.md) to define a precise list of users authorized to run a manual job. By allowing only users associated with a protected environment to trigger manual jobs, it is possible to implement some special use cases, such as: -- more precisely limiting who can deploy to an environment. -- enabling a pipeline to be blocked until an approved user "approves" it. - -To do this, you must add an environment to the job. For example: - -```yaml -deploy_prod: - stage: deploy - script: - - echo "Deploy to production server" - environment: - name: production - url: https://example.com - when: manual - only: - - master -``` - -Then, in the [protected environments settings](../environments/protected_environments.md#protecting-environments), -select the environment (`production` in the example above) and add the users, roles or groups -that are authorized to trigger the manual job to the **Allowed to Deploy** list. Only those in -this list will be able to trigger this manual job, as well as GitLab admins who are always able -to use protected environments. +- More precisely limiting who can deploy to an environment. +- Enabling a pipeline to be blocked until an approved user "approves" it. + +To do this, you must: + +1. Add an `environment` to the job. For example: + + ```yaml + deploy_prod: + stage: deploy + script: + - echo "Deploy to production server" + environment: + name: production + url: https://example.com + when: manual + only: + - master + ``` + +1. In the [protected environments settings](../environments/protected_environments.md#protecting-environments), + select the environment (`production` in the example above) and add the users, roles or groups + that are authorized to trigger the manual job to the **Allowed to Deploy** list. Only those in + this list will be able to trigger this manual job, as well as GitLab administrators + who are always able to use protected environments. Additionally, if a manual job is defined as blocking by adding `allow_failure: false`, the next stages of the pipeline will not run until the manual job is triggered. This diff --git a/doc/user/analytics/productivity_analytics.md b/doc/user/analytics/productivity_analytics.md index bd3cbd62ba0..87a92fcbf2a 100644 --- a/doc/user/analytics/productivity_analytics.md +++ b/doc/user/analytics/productivity_analytics.md @@ -21,6 +21,7 @@ Productivity Analytics allows GitLab users to: - Visualize typical merge request (MR) lifetime and statistics. Use a histogram that shows the distribution of the time elapsed between creating and merging merge requests. - Drill down into the most time consuming merge requests, select a number of outliers, and filter down all subsequent charts to investigate potential causes. - Filter by group, project, author, label, milestone, or a specific date range. Filter down, for example, to the merge requests of a specific author in a group or project during a milestone or specific date range. +- Measure velocity over time. Visualize the trends of each metric from the charts above over time in order to observe progress. Zoom in on a particular date range if you notice outliers. ## Accessing metrics and visualizations @@ -40,6 +41,8 @@ The following metrics and visualizations are available on a project or group lev - Number of commits per merge request. - Number of lines of code per commit. - Number of files touched. +- Scatterplot showing all MRs merged on a certain date, together with the days it took to complete the action and a 30 day rolling median. + - Users can zoom in and out on specific days of interest. - Table showing the list of merge requests with their respective time duration metrics. - Users can sort by any of the above metrics. diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 16fca9acccb..89e4da5a42e 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -80,7 +80,7 @@ module API note = create_note(noteable, opts) if note.valid? - present note, with: Entities.const_get(note.class.name) + present note, with: Entities.const_get(note.class.name, false) else bad_request!("Note #{note.errors.messages}") end diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 404675bfaec..e3f3aca27df 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -49,7 +49,7 @@ module API resource :todos do helpers do def issuable_and_awardable?(type) - obj_type = Object.const_get(type) + obj_type = Object.const_get(type, false) (obj_type < Issuable) && (obj_type < Awardable) rescue NameError diff --git a/lib/banzai/filter.rb b/lib/banzai/filter.rb index 7d9766c906c..2438cb3c166 100644 --- a/lib/banzai/filter.rb +++ b/lib/banzai/filter.rb @@ -3,7 +3,7 @@ module Banzai module Filter def self.[](name) - const_get("#{name.to_s.camelize}Filter") + const_get("#{name.to_s.camelize}Filter", false) end end end diff --git a/lib/banzai/filter/audio_link_filter.rb b/lib/banzai/filter/audio_link_filter.rb index 83aa520dc4b..50472c3cf81 100644 --- a/lib/banzai/filter/audio_link_filter.rb +++ b/lib/banzai/filter/audio_link_filter.rb @@ -3,63 +3,15 @@ # Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/audio.js module Banzai module Filter - # Find every image that isn't already wrapped in an `a` tag, and that has - # a `src` attribute ending with an audio extension, add a new audio node and - # a "Download" link in the case the audio cannot be played. - class AudioLinkFilter < HTML::Pipeline::Filter - def call - doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |el| - el.replace(audio_node(doc, el)) if has_audio_extension?(el) - end - - doc - end - + class AudioLinkFilter < PlayableLinkFilter private - def has_audio_extension?(element) - src = element.attr('data-canonical-src').presence || element.attr('src') - - return unless src.present? - - src_ext = File.extname(src).sub('.', '').downcase - Gitlab::FileTypeDetection::SAFE_AUDIO_EXT.include?(src_ext) + def media_type + "audio" end - def audio_node(doc, element) - container = doc.document.create_element( - 'div', - class: 'audio-container' - ) - - audio = doc.document.create_element( - 'audio', - src: element['src'], - controls: true, - 'data-setup' => '{}', - 'data-title' => element['title'] || element['alt']) - - link = doc.document.create_element( - 'a', - element['title'] || element['alt'], - href: element['src'], - target: '_blank', - rel: 'noopener noreferrer', - title: "Download '#{element['title'] || element['alt']}'") - - # make sure the original non-proxied src carries over - if element['data-canonical-src'] - audio['data-canonical-src'] = element['data-canonical-src'] - link['data-canonical-src'] = element['data-canonical-src'] - end - - download_paragraph = doc.document.create_element('p') - download_paragraph.children = link - - container.add_child(audio) - container.add_child(download_paragraph) - - container + def safe_media_ext + Gitlab::FileTypeDetection::SAFE_AUDIO_EXT end end end diff --git a/lib/banzai/filter/playable_link_filter.rb b/lib/banzai/filter/playable_link_filter.rb new file mode 100644 index 00000000000..0a043aa809c --- /dev/null +++ b/lib/banzai/filter/playable_link_filter.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module Banzai + module Filter + # Find every image that isn't already wrapped in an `a` tag, and that has + # a `src` attribute ending with an audio or video extension, add a new audio or video node and + # a "Download" link in the case the media cannot be played. + class PlayableLinkFilter < HTML::Pipeline::Filter + def call + doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |el| + el.replace(media_node(doc, el)) if has_media_extension?(el) + end + + doc + end + + private + + def media_type + raise NotImplementedError + end + + def safe_media_ext + raise NotImplementedError + end + + def extra_element_attrs + {} + end + + def has_media_extension?(element) + src = element.attr('data-canonical-src').presence || element.attr('src') + + return unless src.present? + + src_ext = File.extname(src).sub('.', '').downcase + safe_media_ext.include?(src_ext) + end + + def media_element(doc, element) + media_element_attrs = { + src: element['src'], + controls: true, + 'data-setup': '{}', + 'data-title': element['title'] || element['alt'] + }.merge!(extra_element_attrs) + + if element['data-canonical-src'] + media_element_attrs['data-canonical-src'] = element['data-canonical-src'] + end + + doc.document.create_element(media_type, media_element_attrs) + end + + def download_paragraph(doc, element) + link_content = element['title'] || element['alt'] + + link_element_attrs = { + href: element['src'], + target: '_blank', + rel: 'noopener noreferrer', + title: "Download '#{link_content}'" + } + + # make sure the original non-proxied src carries over + if element['data-canonical-src'] + link_element_attrs['data-canonical-src'] = element['data-canonical-src'] + end + + link = doc.document.create_element('a', link_content, link_element_attrs) + + doc.document.create_element('p').tap do |paragraph| + paragraph.children = link + end + end + + def media_node(doc, element) + container_element_attrs = { class: "#{media_type}-container" } + + doc.document.create_element( "div", container_element_attrs).tap do |container| + container.add_child(media_element(doc, element)) + container.add_child(download_paragraph(doc, element)) + end + end + end + end +end diff --git a/lib/banzai/filter/video_link_filter.rb b/lib/banzai/filter/video_link_filter.rb index 0e329339474..ed82fbc1f94 100644 --- a/lib/banzai/filter/video_link_filter.rb +++ b/lib/banzai/filter/video_link_filter.rb @@ -3,64 +3,19 @@ # Generated HTML is transformed back to GFM by app/assets/javascripts/behaviors/markdown/nodes/video.js module Banzai module Filter - # Find every image that isn't already wrapped in an `a` tag, and that has - # a `src` attribute ending with a video extension, add a new video node and - # a "Download" link in the case the video cannot be played. - class VideoLinkFilter < HTML::Pipeline::Filter - def call - doc.xpath('descendant-or-self::img[not(ancestor::a)]').each do |el| - el.replace(video_node(doc, el)) if has_video_extension?(el) - end - - doc - end - + class VideoLinkFilter < PlayableLinkFilter private - def has_video_extension?(element) - src = element.attr('data-canonical-src').presence || element.attr('src') - - return unless src.present? - - src_ext = File.extname(src).sub('.', '').downcase - Gitlab::FileTypeDetection::SAFE_VIDEO_EXT.include?(src_ext) + def media_type + "video" end - def video_node(doc, element) - container = doc.document.create_element( - 'div', - class: 'video-container' - ) - - video = doc.document.create_element( - 'video', - src: element['src'], - width: '100%', - controls: true, - 'data-setup' => '{}', - 'data-title' => element['title'] || element['alt']) - - link = doc.document.create_element( - 'a', - element['title'] || element['alt'], - href: element['src'], - target: '_blank', - rel: 'noopener noreferrer', - title: "Download '#{element['title'] || element['alt']}'") - - # make sure the original non-proxied src carries over - if element['data-canonical-src'] - video['data-canonical-src'] = element['data-canonical-src'] - link['data-canonical-src'] = element['data-canonical-src'] - end - - download_paragraph = doc.document.create_element('p') - download_paragraph.children = link - - container.add_child(video) - container.add_child(download_paragraph) + def safe_media_ext + Gitlab::FileTypeDetection::SAFE_VIDEO_EXT + end - container + def extra_element_attrs + { width: "100%" } end end end diff --git a/lib/banzai/pipeline.rb b/lib/banzai/pipeline.rb index e8a81bebaa9..497d3f27542 100644 --- a/lib/banzai/pipeline.rb +++ b/lib/banzai/pipeline.rb @@ -4,7 +4,7 @@ module Banzai module Pipeline def self.[](name) name ||= :full - const_get("#{name.to_s.camelize}Pipeline") + const_get("#{name.to_s.camelize}Pipeline", false) end end end diff --git a/lib/banzai/reference_parser.rb b/lib/banzai/reference_parser.rb index efe15096f08..c08d3364a87 100644 --- a/lib/banzai/reference_parser.rb +++ b/lib/banzai/reference_parser.rb @@ -10,7 +10,7 @@ module Banzai # # This would return the `Banzai::ReferenceParser::IssueParser` class. def self.[](name) - const_get("#{name.to_s.camelize}Parser") + const_get("#{name.to_s.camelize}Parser", false) end end end diff --git a/lib/bitbucket/page.rb b/lib/bitbucket/page.rb index 7cc1342ad65..38c689628dd 100644 --- a/lib/bitbucket/page.rb +++ b/lib/bitbucket/page.rb @@ -30,7 +30,7 @@ module Bitbucket end def representation_class(type) - Bitbucket::Representation.const_get(type.to_s.camelize) + Bitbucket::Representation.const_get(type.to_s.camelize, false) end end end diff --git a/lib/bitbucket_server/page.rb b/lib/bitbucket_server/page.rb index 5d9a3168876..304f7cd9d72 100644 --- a/lib/bitbucket_server/page.rb +++ b/lib/bitbucket_server/page.rb @@ -30,7 +30,7 @@ module BitbucketServer end def representation_class(type) - BitbucketServer::Representation.const_get(type.to_s.camelize) + BitbucketServer::Representation.const_get(type.to_s.camelize, false) end end end diff --git a/lib/gitlab/background_migration.rb b/lib/gitlab/background_migration.rb index 2e3a4f3b869..61e0a075018 100644 --- a/lib/gitlab/background_migration.rb +++ b/lib/gitlab/background_migration.rb @@ -78,7 +78,7 @@ module Gitlab end def self.migration_class_for(class_name) - const_get(class_name) + const_get(class_name, false) end def self.enqueued_job?(queues, migration_class) diff --git a/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config.rb b/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config.rb index 29fa0f18448..3c142327e94 100644 --- a/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config.rb +++ b/lib/gitlab/background_migration/backfill_project_fullpath_in_repo_config.rb @@ -171,7 +171,11 @@ module Gitlab end def schedule_retry(project, retry_count) - BackgroundMigrationWorker.perform_in(RETRY_DELAY, self.class::RetryOne.name, [project.id, retry_count]) + # Constants provided to BackgroundMigrationWorker must be within the + # scope of Gitlab::BackgroundMigration + retry_class_name = self.class::RetryOne.name.sub('Gitlab::BackgroundMigration::', '') + + BackgroundMigrationWorker.perform_in(RETRY_DELAY, retry_class_name, [project.id, retry_count]) end end diff --git a/lib/gitlab/cache/request_cache.rb b/lib/gitlab/cache/request_cache.rb index 4c658dc0b8d..6e48ca90054 100644 --- a/lib/gitlab/cache/request_cache.rb +++ b/lib/gitlab/cache/request_cache.rb @@ -23,7 +23,7 @@ module Gitlab end def request_cache(method_name, &method_key_block) - const_get(:RequestCacheExtension).module_eval do + const_get(:RequestCacheExtension, false).module_eval do cache_key_method_name = "#{method_name}_cache_key" define_method(method_name) do |*args| diff --git a/lib/gitlab/ci/build/policy.rb b/lib/gitlab/ci/build/policy.rb index 43c46ad74af..ebeebe7fb5b 100644 --- a/lib/gitlab/ci/build/policy.rb +++ b/lib/gitlab/ci/build/policy.rb @@ -6,7 +6,7 @@ module Gitlab module Policy def self.fabricate(specs) specifications = specs.to_h.map do |spec, value| - self.const_get(spec.to_s.camelize).new(value) + self.const_get(spec.to_s.camelize, false).new(value) end specifications.compact diff --git a/lib/gitlab/ci/status/factory.rb b/lib/gitlab/ci/status/factory.rb index 2a0bf060c9b..c29dc51f076 100644 --- a/lib/gitlab/ci/status/factory.rb +++ b/lib/gitlab/ci/status/factory.rb @@ -20,7 +20,7 @@ module Gitlab def core_status Gitlab::Ci::Status - .const_get(@status.capitalize) + .const_get(@status.capitalize, false) .new(@subject, @user) .extend(self.class.common_helpers) end diff --git a/lib/gitlab/config/entry/simplifiable.rb b/lib/gitlab/config/entry/simplifiable.rb index a56a89adb35..d58aba07d15 100644 --- a/lib/gitlab/config/entry/simplifiable.rb +++ b/lib/gitlab/config/entry/simplifiable.rb @@ -37,7 +37,7 @@ module Gitlab def self.entry_class(strategy) if strategy.present? - self.const_get(strategy.name) + self.const_get(strategy.name, false) else self::UnknownStrategy end diff --git a/lib/gitlab/cycle_analytics/event_fetcher.rb b/lib/gitlab/cycle_analytics/event_fetcher.rb index 98a30a8fc97..04f4b4f053f 100644 --- a/lib/gitlab/cycle_analytics/event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/event_fetcher.rb @@ -4,7 +4,7 @@ module Gitlab module CycleAnalytics module EventFetcher def self.[](stage_name) - CycleAnalytics.const_get("#{stage_name.to_s.camelize}EventFetcher") + CycleAnalytics.const_get("#{stage_name.to_s.camelize}EventFetcher", false) end end end diff --git a/lib/gitlab/cycle_analytics/stage.rb b/lib/gitlab/cycle_analytics/stage.rb index 1bd40a7aa18..5cfd9ea4730 100644 --- a/lib/gitlab/cycle_analytics/stage.rb +++ b/lib/gitlab/cycle_analytics/stage.rb @@ -4,7 +4,7 @@ module Gitlab module CycleAnalytics module Stage def self.[](stage_name) - CycleAnalytics.const_get("#{stage_name.to_s.camelize}Stage") + CycleAnalytics.const_get("#{stage_name.to_s.camelize}Stage", false) end end end diff --git a/lib/gitlab/downtime_check.rb b/lib/gitlab/downtime_check.rb index 31bb6810391..457a3c12206 100644 --- a/lib/gitlab/downtime_check.rb +++ b/lib/gitlab/downtime_check.rb @@ -58,13 +58,13 @@ module Gitlab # Returns true if the given migration can be performed without downtime. def online?(migration) - migration.const_get(DOWNTIME_CONST) == false + migration.const_get(DOWNTIME_CONST, false) == false end # Returns the downtime reason, or nil if none was defined. def downtime_reason(migration) if migration.const_defined?(DOWNTIME_REASON_CONST) - migration.const_get(DOWNTIME_REASON_CONST) + migration.const_get(DOWNTIME_REASON_CONST, false) else nil end diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index befcc004ee4..b0f29d22ad4 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -86,7 +86,7 @@ module Gitlab if name == :health_check Grpc::Health::V1::Health::Stub else - Gitaly.const_get(name.to_s.camelcase.to_sym).const_get(:Stub) + Gitaly.const_get(name.to_s.camelcase.to_sym, false).const_get(:Stub, false) end end diff --git a/lib/gitlab/gitaly_client/attributes_bag.rb b/lib/gitlab/gitaly_client/attributes_bag.rb index 3f1a0ef4888..f935281ac2e 100644 --- a/lib/gitlab/gitaly_client/attributes_bag.rb +++ b/lib/gitlab/gitaly_client/attributes_bag.rb @@ -8,7 +8,7 @@ module Gitlab extend ActiveSupport::Concern included do - attr_accessor(*const_get(:ATTRS)) + attr_accessor(*const_get(:ATTRS, false)) end def initialize(params) @@ -26,7 +26,7 @@ module Gitlab end def attributes - self.class.const_get(:ATTRS) + self.class.const_get(:ATTRS, false) end end end diff --git a/lib/gitlab/patch/prependable.rb b/lib/gitlab/patch/prependable.rb index a9f6cfb19cb..22ece0a6a8b 100644 --- a/lib/gitlab/patch/prependable.rb +++ b/lib/gitlab/patch/prependable.rb @@ -24,7 +24,7 @@ module Gitlab super if const_defined?(:ClassMethods) - klass_methods = const_get(:ClassMethods) + klass_methods = const_get(:ClassMethods, false) base.singleton_class.prepend klass_methods base.instance_variable_set(:@_prepended_class_methods, klass_methods) end @@ -40,7 +40,7 @@ module Gitlab super if instance_variable_defined?(:@_prepended_class_methods) - const_get(:ClassMethods).prepend @_prepended_class_methods + const_get(:ClassMethods, false).prepend @_prepended_class_methods end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 01d4765833e..237b2b7cb0d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7250,6 +7250,9 @@ msgstr "" msgid "Fork project" msgstr "" +msgid "Fork project?" +msgstr "" + msgid "ForkedFromProjectPath|Forked from" msgstr "" diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb index 9b2d0a1a41d..75e48b5785e 100644 --- a/qa/qa/page/validator.rb +++ b/qa/qa/page/validator.rb @@ -17,7 +17,7 @@ module QA def constants @consts ||= @module.constants.map do |const| - @module.const_get(const) + @module.const_get(const, false) end end diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 77d04aa7594..4789b380377 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -65,7 +65,7 @@ module QA # QA::Runtime::Env.browser.capitalize will work for every driver type except PhantomJS. # We will have no use to use PhantomJS so this shouldn't be a problem. - options = Selenium::WebDriver.const_get(QA::Runtime::Env.browser.capitalize)::Options.new + options = Selenium::WebDriver.const_get(QA::Runtime::Env.browser.capitalize, false)::Options.new if QA::Runtime::Env.browser == :chrome options.add_argument("window-size=1480,2200") diff --git a/qa/qa/runtime/release.rb b/qa/qa/runtime/release.rb index 4f96e0cf44b..18a6736afcf 100644 --- a/qa/qa/runtime/release.rb +++ b/qa/qa/runtime/release.rb @@ -19,7 +19,7 @@ module QA end def strategy - QA.const_get("QA::#{version}::Strategy") + Object.const_get("QA::#{version}::Strategy", false) end def self.method_missing(name, *args) diff --git a/rubocop/cop/gitlab/const_get_inherit_false.rb b/rubocop/cop/gitlab/const_get_inherit_false.rb new file mode 100644 index 00000000000..3d3bbc4c8d3 --- /dev/null +++ b/rubocop/cop/gitlab/const_get_inherit_false.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module Gitlab + # Cop that encourages usage of inherit=false for 2nd argument when using const_get. + # + # See https://gitlab.com/gitlab-org/gitlab/issues/27678 + class ConstGetInheritFalse < RuboCop::Cop::Cop + MSG = 'Use inherit=false when using const_get.' + + def_node_matcher :const_get?, <<~PATTERN + (send _ :const_get ...) + PATTERN + + def on_send(node) + return unless const_get?(node) + return if second_argument(node)&.false_type? + + add_offense(node, location: :selector) + end + + def autocorrect(node) + lambda do |corrector| + if arg = second_argument(node) + corrector.replace(arg.source_range, 'false') + else + first_argument = node.arguments[0] + corrector.insert_after(first_argument.source_range, ', false') + end + end + end + + private + + def second_argument(node) + node.arguments[1] + end + end + end + end +end diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb index 8e7df62ea75..70679aa1e78 100644 --- a/rubocop/rubocop.rb +++ b/rubocop/rubocop.rb @@ -1,3 +1,4 @@ +require_relative 'cop/gitlab/const_get_inherit_false' require_relative 'cop/gitlab/module_with_instance_variables' require_relative 'cop/gitlab/predicate_memoization' require_relative 'cop/gitlab/httparty' diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index ef464d3d6e0..a060cd7d6f8 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -15,7 +15,7 @@ FactoryBot.define do end path do - uploader_instance = Object.const_get(uploader.to_s).new(model, mount_point) + uploader_instance = Object.const_get(uploader.to_s, false).new(model, mount_point) File.join(uploader_instance.store_dir, filename) end diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index e5e16e69833..4996e27c2e6 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -270,4 +270,32 @@ describe BlobHelper do end end end + + describe '#ide_fork_and_edit_path' do + let(:project) { create(:project) } + let(:current_user) { create(:user) } + let(:can_push_code) { true } + + before do + allow(helper).to receive(:current_user).and_return(current_user) + allow(helper).to receive(:can?).and_return(can_push_code) + end + + it 'returns path to fork the repo with a redirect param to the full IDE path' do + uri = URI(helper.ide_fork_and_edit_path(project, "master", "")) + params = CGI.unescape(uri.query) + + expect(uri.path).to eq("/#{project.namespace.path}/#{project.path}/-/forks") + expect(params).to include("continue[to]=/-/ide/project/#{project.namespace.path}/#{project.path}/edit/master") + expect(params).to include("namespace_key=#{current_user.namespace.id}") + end + + context 'when user is not logged in' do + let(:current_user) { nil } + + it 'returns nil' do + expect(helper.ide_fork_and_edit_path(project, "master", "")).to be_nil + end + end + end end diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb index 332817d6585..a395b021f32 100644 --- a/spec/lib/banzai/filter/video_link_filter_spec.rb +++ b/spec/lib/banzai/filter/video_link_filter_spec.rb @@ -32,6 +32,7 @@ describe Banzai::Filter::VideoLinkFilter do expect(video.name).to eq 'video' expect(video['src']).to eq src + expect(video['width']).to eq "100%" expect(paragraph.name).to eq 'p' diff --git a/spec/lib/gitlab/ci/status/external/factory_spec.rb b/spec/lib/gitlab/ci/status/external/factory_spec.rb index 3b90fb60cca..9d7dfc42848 100644 --- a/spec/lib/gitlab/ci/status/external/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/external/factory_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::Ci::Status::External::Factory do end let(:expected_status) do - Gitlab::Ci::Status.const_get(simple_status.capitalize) + Gitlab::Ci::Status.const_get(simple_status.capitalize, false) end it "fabricates a core status #{simple_status}" do diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb index b51c0bec47e..c6d7a1ec5d9 100644 --- a/spec/lib/gitlab/ci/status/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/factory_spec.rb @@ -13,7 +13,7 @@ describe Gitlab::Ci::Status::Factory do let(:resource) { double('resource', status: simple_status) } let(:expected_status) do - Gitlab::Ci::Status.const_get(simple_status.capitalize) + Gitlab::Ci::Status.const_get(simple_status.capitalize, false) end it "fabricates a core status #{simple_status}" do diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb index 8a36cd1b658..3acc767ab7a 100644 --- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb @@ -18,7 +18,7 @@ describe Gitlab::Ci::Status::Pipeline::Factory do let(:pipeline) { create(:ci_pipeline, status: simple_status) } let(:expected_status) do - Gitlab::Ci::Status.const_get(simple_status.capitalize) + Gitlab::Ci::Status.const_get(simple_status.capitalize, false) end it "matches correct core status for #{simple_status}" do diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb index 8f5b1ff62a5..dcb53712157 100644 --- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb +++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb @@ -34,7 +34,7 @@ describe Gitlab::Ci::Status::Stage::Factory do it "fabricates a core status #{core_status}" do expect(status).to be_a( - Gitlab::Ci::Status.const_get(core_status.capitalize)) + Gitlab::Ci::Status.const_get(core_status.capitalize, false)) end it 'extends core status with common stage methods' do diff --git a/spec/lib/gitlab/config/entry/simplifiable_spec.rb b/spec/lib/gitlab/config/entry/simplifiable_spec.rb index 65e18fe3f10..5c208cab449 100644 --- a/spec/lib/gitlab/config/entry/simplifiable_spec.rb +++ b/spec/lib/gitlab/config/entry/simplifiable_spec.rb @@ -24,9 +24,9 @@ describe Gitlab::Config::Entry::Simplifiable do let(:unknown) { double('unknown strategy') } before do - stub_const("#{described_class.name}::Something", first) - stub_const("#{described_class.name}::DifferentOne", second) - stub_const("#{described_class.name}::UnknownStrategy", unknown) + entry::Something = first + entry::DifferentOne = second + entry::UnknownStrategy = unknown end context 'when first strategy should be used' do diff --git a/spec/lib/gitlab/patch/prependable_spec.rb b/spec/lib/gitlab/patch/prependable_spec.rb index 725d733d176..255324f89d5 100644 --- a/spec/lib/gitlab/patch/prependable_spec.rb +++ b/spec/lib/gitlab/patch/prependable_spec.rb @@ -72,8 +72,8 @@ describe Gitlab::Patch::Prependable do expect(subject.ancestors.take(3)).to eq([subject, ee, ce]) expect(subject.singleton_class.ancestors.take(3)) .to eq([subject.singleton_class, - ee.const_get(:ClassMethods), - ce.const_get(:ClassMethods)]) + ee.const_get(:ClassMethods, false), + ce.const_get(:ClassMethods, false)]) end it 'prepends only once even if called twice' do @@ -115,8 +115,8 @@ describe Gitlab::Patch::Prependable do it 'has the expected ancestors' do expect(subject.ancestors.take(3)).to eq([ee, ce, subject]) expect(subject.singleton_class.ancestors.take(3)) - .to eq([ee.const_get(:ClassMethods), - ce.const_get(:ClassMethods), + .to eq([ee.const_get(:ClassMethods, false), + ce.const_get(:ClassMethods, false), subject.singleton_class]) end @@ -152,7 +152,7 @@ describe Gitlab::Patch::Prependable do it 'has the expected ancestors' do expect(subject.ancestors.take(2)).to eq([ee, subject]) expect(subject.singleton_class.ancestors.take(2)) - .to eq([ee.const_get(:ClassMethods), + .to eq([ee.const_get(:ClassMethods, false), subject.singleton_class]) end diff --git a/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb b/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb new file mode 100644 index 00000000000..0ff06b431eb --- /dev/null +++ b/spec/rubocop/cop/gitlab/const_get_inherit_false_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'rubocop' +require 'rubocop/rspec/support' +require_relative '../../../../rubocop/cop/gitlab/const_get_inherit_false' + +describe RuboCop::Cop::Gitlab::ConstGetInheritFalse do + include CopHelper + + subject(:cop) { described_class.new } + + context 'Object.const_get' do + it 'registers an offense with no 2nd argument' do + expect_offense(<<~PATTERN.strip_indent) + Object.const_get(:CONSTANT) + ^^^^^^^^^ Use inherit=false when using const_get. + PATTERN + end + + it 'autocorrects' do + expect(autocorrect_source('Object.const_get(:CONSTANT)')).to eq('Object.const_get(:CONSTANT, false)') + end + + context 'inherit=false' do + it 'does not register an offense' do + expect_no_offenses(<<~PATTERN.strip_indent) + Object.const_get(:CONSTANT, false) + PATTERN + end + end + + context 'inherit=true' do + it 'registers an offense' do + expect_offense(<<~PATTERN.strip_indent) + Object.const_get(:CONSTANT, true) + ^^^^^^^^^ Use inherit=false when using const_get. + PATTERN + end + + it 'autocorrects' do + expect(autocorrect_source('Object.const_get(:CONSTANT, true)')).to eq('Object.const_get(:CONSTANT, false)') + end + end + end + + context 'const_get for a nested class' do + it 'registers an offense on reload usage' do + expect_offense(<<~PATTERN.strip_indent) + Nested::Blog.const_get(:CONSTANT) + ^^^^^^^^^ Use inherit=false when using const_get. + PATTERN + end + + it 'autocorrects' do + expect(autocorrect_source('Nested::Blag.const_get(:CONSTANT)')).to eq('Nested::Blag.const_get(:CONSTANT, false)') + end + + context 'inherit=false' do + it 'does not register an offense' do + expect_no_offenses(<<~PATTERN.strip_indent) + Nested::Blog.const_get(:CONSTANT, false) + PATTERN + end + end + + context 'inherit=true' do + it 'registers an offense if inherit is true' do + expect_offense(<<~PATTERN.strip_indent) + Nested::Blog.const_get(:CONSTANT, true) + ^^^^^^^^^ Use inherit=false when using const_get. + PATTERN + end + + it 'autocorrects' do + expect(autocorrect_source('Nested::Blag.const_get(:CONSTANT, true)')).to eq('Nested::Blag.const_get(:CONSTANT, false)') + end + end + end +end diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb index 6f06d323a82..f95b612eb70 100644 --- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb @@ -75,7 +75,7 @@ shared_examples 'cluster application status specs' do |application_name| subject.reload - expect(subject.version).to eq(subject.class.const_get(:VERSION)) + expect(subject.version).to eq(subject.class.const_get(:VERSION, false)) end context 'application is updating' do @@ -104,7 +104,7 @@ shared_examples 'cluster application status specs' do |application_name| subject.reload - expect(subject.version).to eq(subject.class.const_get(:VERSION)) + expect(subject.version).to eq(subject.class.const_get(:VERSION, false)) end end end diff --git a/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb index 181b102e685..ba02da41b53 100644 --- a/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb +++ b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb @@ -12,7 +12,7 @@ shared_examples 'cluster application version specs' do |application_name| context 'version is the same as VERSION' do let(:application) { build(application_name) } - let(:version) { application.class.const_get(:VERSION) } + let(:version) { application.class.const_get(:VERSION, false) } it { is_expected.to be_falsey } end diff --git a/spec/views/projects/tree/_tree_header.html.haml_spec.rb b/spec/views/projects/tree/_tree_header.html.haml_spec.rb new file mode 100644 index 00000000000..4b71ea9ffe3 --- /dev/null +++ b/spec/views/projects/tree/_tree_header.html.haml_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'projects/tree/_tree_header' do + let(:project) { create(:project, :repository) } + let(:current_user) { create(:user) } + let(:repository) { project.repository } + + before do + assign(:project, project) + assign(:repository, repository) + assign(:id, File.join('master', '')) + assign(:ref, 'master') + + allow(view).to receive(:current_user).and_return(current_user) + allow(view).to receive(:can_collaborate_with_project?) { true } + end + + it 'does not render the WebIDE button when user cannot create fork or cannot open MR' do + allow(view).to receive(:can?) { false } + + render + + expect(rendered).not_to have_link('Web IDE') + end + + it 'renders the WebIDE button when user can create fork and can open MR in project' do + allow(view).to receive(:can?) { true } + + render + + expect(rendered).to have_link('Web IDE') + end + + it 'opens a popup confirming a fork if the user can create fork/MR but cannot collaborate with the project' do + allow(view).to receive(:can?) { true } + allow(view).to receive(:can_collaborate_with_project?) { false } + + render + + expect(rendered).to have_link('Web IDE', href: '#modal-confirm-fork') + end +end |