diff options
129 files changed, 1075 insertions, 415 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index a1c928aedf3..56ac809b548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 11.6.2 (2019-01-02) + +### Fixed (7 changes) + +- Hide cluster features that don't work yet with Group Clusters. !23935 +- Fix a 500 error that could occur until all migrations are done. !23939 +- Fix missing Git clone button when protocol restriction setting enabled. !24015 +- Fix clone dropdown parent inheritance issues in HAML. !24029 +- Fix content-disposition in blobs and files API endpoint. !24078 +- Fixed markdown toolbar buttons. +- Adjust line-height of blame view line numbers. + + ## 11.6.1 (2018-12-28) ### Security (15 changes) @@ -1,6 +1,6 @@ source 'https://rubygems.org' -gem 'rails', '5.0.7' +gem 'rails', '5.0.7.1' gem 'rails-deprecated_sanitizer', '~> 1.0.3' # Improves copy-on-write performance for MRI @@ -127,7 +127,7 @@ gem 'asciidoctor-plantuml', '0.0.8' gem 'rouge', '~> 3.1' gem 'truncato', '~> 0.7.9' gem 'bootstrap_form', '~> 2.7.0' -gem 'nokogiri', '~> 1.8.4' +gem 'nokogiri', '~> 1.8.5' gem 'escape_utils', '~> 1.1' # Calendar rendering @@ -322,7 +322,7 @@ end group :development, :test do gem 'bootsnap', '~> 1.3' gem 'bullet', '~> 5.5.0', require: !!ENV['ENABLE_BULLET'] - gem 'pry-byebug', '~> 3.4.1', platform: :mri + gem 'pry-byebug', '~> 3.5.1', platform: :mri gem 'pry-rails', '~> 0.3.4' gem 'awesome_print', require: false @@ -342,8 +342,8 @@ group :development, :test do # Generate Fake data gem 'ffaker', '~> 2.10' - gem 'capybara', '~> 2.15' - gem 'capybara-screenshot', '~> 1.0.0' + gem 'capybara', '~> 2.16.1' + gem 'capybara-screenshot', '~> 1.0.18' gem 'selenium-webdriver', '~> 3.12' gem 'spring', '~> 2.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 08684cf7836..78cf4d19078 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,41 +4,41 @@ GEM RedCloth (4.3.2) abstract_type (0.0.7) ace-rails-ap (4.1.2) - actioncable (5.0.7) - actionpack (= 5.0.7) + actioncable (5.0.7.1) + actionpack (= 5.0.7.1) nio4r (>= 1.2, < 3.0) websocket-driver (~> 0.6.1) - actionmailer (5.0.7) - actionpack (= 5.0.7) - actionview (= 5.0.7) - activejob (= 5.0.7) + actionmailer (5.0.7.1) + actionpack (= 5.0.7.1) + actionview (= 5.0.7.1) + activejob (= 5.0.7.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.0.7) - actionview (= 5.0.7) - activesupport (= 5.0.7) + actionpack (5.0.7.1) + actionview (= 5.0.7.1) + activesupport (= 5.0.7.1) rack (~> 2.0) rack-test (~> 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.7) - activesupport (= 5.0.7) + actionview (5.0.7.1) + activesupport (= 5.0.7.1) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.0.7) - activesupport (= 5.0.7) + activejob (5.0.7.1) + activesupport (= 5.0.7.1) globalid (>= 0.3.6) - activemodel (5.0.7) - activesupport (= 5.0.7) - activerecord (5.0.7) - activemodel (= 5.0.7) - activesupport (= 5.0.7) + activemodel (5.0.7.1) + activesupport (= 5.0.7.1) + activerecord (5.0.7.1) + activemodel (= 5.0.7.1) + activesupport (= 5.0.7.1) arel (~> 7.0) activerecord_sane_schema_dumper (1.0) rails (>= 5, < 6) - activesupport (5.0.7) + activesupport (5.0.7.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -97,16 +97,16 @@ GEM bundler-audit (0.5.0) bundler (~> 1.2) thor (~> 0.18) - byebug (9.0.6) - capybara (2.15.1) + byebug (9.1.0) + capybara (2.16.1) addressable mini_mime (>= 0.1.3) nokogiri (>= 1.3.3) rack (>= 1.0.0) rack-test (>= 0.5.4) xpath (~> 2.0) - capybara-screenshot (1.0.14) - capybara (>= 1.0, < 3) + capybara-screenshot (1.0.22) + capybara (>= 1.0, < 4) launchy carrierwave (1.3.0) activemodel (>= 4.0.0) @@ -381,7 +381,7 @@ GEM json (~> 1.8) multi_xml (>= 0.5.2) httpclient (2.8.3) - i18n (1.1.1) + i18n (1.2.0) concurrent-ruby (~> 1.0) icalendar (2.4.1) ice_nine (0.11.2) @@ -449,7 +449,7 @@ GEM loofah (2.2.3) crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.7.0) + mail (2.7.1) mini_mime (>= 0.1.1) mail_room (0.9.1) memoist (0.16.0) @@ -595,8 +595,8 @@ GEM pry (0.11.3) coderay (~> 1.1.0) method_source (~> 0.9.0) - pry-byebug (3.4.3) - byebug (>= 9.0, < 9.1) + pry-byebug (3.5.1) + byebug (~> 9.1) pry (~> 0.10) pry-rails (0.3.6) pry (>= 0.10.4) @@ -624,17 +624,17 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) - rails (5.0.7) - actioncable (= 5.0.7) - actionmailer (= 5.0.7) - actionpack (= 5.0.7) - actionview (= 5.0.7) - activejob (= 5.0.7) - activemodel (= 5.0.7) - activerecord (= 5.0.7) - activesupport (= 5.0.7) + rails (5.0.7.1) + actioncable (= 5.0.7.1) + actionmailer (= 5.0.7.1) + actionpack (= 5.0.7.1) + actionview (= 5.0.7.1) + activejob (= 5.0.7.1) + activemodel (= 5.0.7.1) + activerecord (= 5.0.7.1) + activesupport (= 5.0.7.1) bundler (>= 1.3.0) - railties (= 5.0.7) + railties (= 5.0.7.1) sprockets-rails (>= 2.0.0) rails-controller-testing (1.0.2) actionpack (~> 5.x, >= 5.0.1) @@ -650,15 +650,15 @@ GEM rails-i18n (5.1.1) i18n (>= 0.7, < 2) railties (>= 5.0, < 6) - railties (5.0.7) - actionpack (= 5.0.7) - activesupport (= 5.0.7) + railties (5.0.7.1) + actionpack (= 5.0.7.1) + activesupport (= 5.0.7.1) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (3.0.0) raindrops (0.18.0) - rake (12.3.1) + rake (12.3.2) rb-fsevent (0.10.2) rb-inotify (0.9.10) ffi (>= 0.5.0, < 2) @@ -963,8 +963,8 @@ DEPENDENCIES browser (~> 2.5) bullet (~> 5.5.0) bundler-audit (~> 0.5.0) - capybara (~> 2.15) - capybara-screenshot (~> 1.0.0) + capybara (~> 2.16.1) + capybara-screenshot (~> 1.0.18) carrierwave (~> 1.3) charlock_holmes (~> 0.7.5) chronic (~> 0.10.2) @@ -1059,7 +1059,7 @@ DEPENDENCIES nakayoshi_fork (~> 0.0.4) net-ldap net-ssh (~> 5.0) - nokogiri (~> 1.8.4) + nokogiri (~> 1.8.5) oauth2 (~> 1.4) octokit (~> 4.9) omniauth (~> 1.8) @@ -1087,7 +1087,7 @@ DEPENDENCIES pg (~> 0.18.2) premailer-rails (~> 1.9.7) prometheus-client-mmap (~> 0.9.4) - pry-byebug (~> 3.4.1) + pry-byebug (~> 3.5.1) pry-rails (~> 0.3.4) puma (~> 3.12) puma_worker_killer @@ -1096,7 +1096,7 @@ DEPENDENCIES rack-cors (~> 1.0.0) rack-oauth2 (~> 1.2.1) rack-proxy (~> 0.6.0) - rails (= 5.0.7) + rails (= 5.0.7.1) rails-controller-testing rails-deprecated_sanitizer (~> 1.0.3) rails-i18n (~> 5.1) @@ -1171,4 +1171,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.17.1 + 1.17.3 diff --git a/app/assets/javascripts/boards/components/issue_due_date.vue b/app/assets/javascripts/boards/components/issue_due_date.vue index e038198e6f0..9c4c6632976 100644 --- a/app/assets/javascripts/boards/components/issue_due_date.vue +++ b/app/assets/javascripts/boards/components/issue_due_date.vue @@ -3,7 +3,12 @@ import dateFormat from 'dateformat'; import { GlTooltip } from '@gitlab/ui'; import Icon from '~/vue_shared/components/icon.vue'; import { __ } from '~/locale'; -import { getDayDifference, getTimeago, dateInWords } from '~/lib/utils/datetime_utility'; +import { + getDayDifference, + getTimeago, + dateInWords, + parsePikadayDate, +} from '~/lib/utils/datetime_utility'; export default { components: { @@ -54,7 +59,7 @@ export default { return standardDateFormat; }, issueDueDate() { - return new Date(this.date); + return parsePikadayDate(this.date); }, timeDifference() { const today = new Date(); diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue index 86c114a761a..f5c410211b6 100644 --- a/app/assets/javascripts/notes/components/discussion_filter.vue +++ b/app/assets/javascripts/notes/components/discussion_filter.vue @@ -2,7 +2,11 @@ import $ from 'jquery'; import { mapGetters, mapActions } from 'vuex'; import Icon from '~/vue_shared/components/icon.vue'; -import { DISCUSSION_FILTERS_DEFAULT_VALUE, HISTORY_ONLY_FILTER_VALUE } from '../constants'; +import { + DISCUSSION_FILTERS_DEFAULT_VALUE, + HISTORY_ONLY_FILTER_VALUE, + DISCUSSION_TAB_LABEL, +} from '../constants'; export default { components: { @@ -23,6 +27,7 @@ export default { return { currentValue: this.selectedValue, defaultValue: DISCUSSION_FILTERS_DEFAULT_VALUE, + displayFilters: true, }; }, computed: { @@ -32,6 +37,14 @@ export default { return this.filters.find(filter => filter.value === this.currentValue); }, }, + created() { + if (window.mrTabs) { + const { eventHub, currentTab } = window.mrTabs; + + eventHub.$on('MergeRequestTabChange', this.toggleFilters); + this.toggleFilters(currentTab); + } + }, mounted() { this.toggleCommentsForm(); }, @@ -51,12 +64,15 @@ export default { toggleCommentsForm() { this.setCommentsDisabled(this.currentValue === HISTORY_ONLY_FILTER_VALUE); }, + toggleFilters(tab) { + this.displayFilters = tab === DISCUSSION_TAB_LABEL; + }, }, }; </script> <template> - <div class="discussion-filter-container d-inline-block align-bottom"> + <div v-if="displayFilters" class="discussion-filter-container d-inline-block align-bottom"> <button id="discussion-filter-dropdown" ref="dropdownToggle" diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js index 3147dc64c27..78d365fe94b 100644 --- a/app/assets/javascripts/notes/constants.js +++ b/app/assets/javascripts/notes/constants.js @@ -17,6 +17,7 @@ export const RESOLVE_NOTE_METHOD_NAME = 'post'; export const DESCRIPTION_TYPE = 'changed the description'; export const HISTORY_ONLY_FILTER_VALUE = 2; export const DISCUSSION_FILTERS_DEFAULT_VALUE = 0; +export const DISCUSSION_TAB_LABEL = 'show'; export const NOTEABLE_TYPE_MAPPING = { Issue: ISSUE_NOTEABLE_TYPE, diff --git a/app/assets/javascripts/pages/users/user_overview_block.js b/app/assets/javascripts/pages/users/user_overview_block.js index eec2b5ca8e5..e9ecec717d6 100644 --- a/app/assets/javascripts/pages/users/user_overview_block.js +++ b/app/assets/javascripts/pages/users/user_overview_block.js @@ -29,18 +29,21 @@ export default class UserOverviewBlock { render(data) { const { html, count } = data; - const contentList = document.querySelector(`${this.container} .overview-content-list`); + const containerEl = document.querySelector(this.container); + const contentList = containerEl.querySelector('.overview-content-list'); contentList.innerHTML += html; - const loadingEl = document.querySelector(`${this.container} .loading`); + const loadingEl = containerEl.querySelector('.loading'); if (count && count > 0) { - document.querySelector(`${this.container} .js-view-all`).classList.remove('hide'); + containerEl.querySelector('.js-view-all').classList.remove('hide'); } else { - document - .querySelector(`${this.container} .nothing-here-block`) - .classList.add('text-left', 'p-0'); + const nothingHereBlock = containerEl.querySelector('.nothing-here-block'); + + if (nothingHereBlock) { + nothingHereBlock.classList.add('text-left', 'p-0'); + } } loadingEl.classList.add('hide'); diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index a66604e56ff..e51f230a680 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -45,9 +45,4 @@ &.status-box-upcoming { background: $gl-text-color-secondary; } - - &.status-box-milestone { - color: $gl-text-color; - background: $gray-darker; - } } diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index 1e92582d6d9..94bf32945fc 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -1,3 +1,5 @@ +$status-box-line-height: 26px; + .issues-sortable-list .str-truncated { max-width: 90%; } @@ -38,6 +40,7 @@ font-size: $tooltip-font-size; margin-top: 0; margin-right: $gl-padding-4; + line-height: $status-box-line-height; @include media-breakpoint-down(xs) { line-height: unset; diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 8f683ca06ad..8f267eccc8a 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -77,7 +77,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController def reset_health_check_token @application_setting.reset_health_check_access_token! flash[:notice] = 'New health check access token has been generated!' - redirect_to :back + redirect_back_or_default end def clear_repository_check_states diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 140a625d333..a8fc848c879 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -76,7 +76,7 @@ class ApplicationController < ActionController::Base end def redirect_back_or_default(default: root_path, options: {}) - redirect_to request.referer.present? ? :back : default, options + redirect_back(fallback_location: default, **options) end def not_found diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index b42116b0f36..868deea3f01 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -43,14 +43,7 @@ class Groups::MilestonesController < Groups::ApplicationController def update # Keep this compatible with legacy group milestones where we have to update # all projects milestones states at once. - if @milestone.legacy_group_milestone? - update_params = milestone_params.select { |key| key == "state_event" } - milestones = @milestone.milestones - else - update_params = milestone_params - milestones = [@milestone] - end - + milestones, update_params = get_milestones_for_update milestones.each do |milestone| Milestones::UpdateService.new(milestone.parent, current_user, update_params).execute(milestone) end @@ -71,6 +64,14 @@ class Groups::MilestonesController < Groups::ApplicationController private + def get_milestones_for_update + if @milestone.legacy_group_milestone? + [@milestone.milestones, legacy_milestone_params] + else + [[@milestone], milestone_params] + end + end + def authorize_admin_milestones! return render_404 unless can?(current_user, :admin_milestone, group) end @@ -79,6 +80,10 @@ class Groups::MilestonesController < Groups::ApplicationController params.require(:milestone).permit(:title, :description, :start_date, :due_date, :state_event) end + def legacy_milestone_params + params.require(:milestone).permit(:state_event) + end + def milestone_path if @milestone.legacy_group_milestone? group_milestone_path(group, @milestone.safe_title, title: @milestone.title) diff --git a/app/controllers/sherlock/transactions_controller.rb b/app/controllers/sherlock/transactions_controller.rb index 46e382e594e..8d1847507cc 100644 --- a/app/controllers/sherlock/transactions_controller.rb +++ b/app/controllers/sherlock/transactions_controller.rb @@ -15,7 +15,7 @@ module Sherlock def destroy_all Gitlab::Sherlock.collection.clear - redirect_to :back, status: :found + redirect_back_or_default(options: { status: :found }) end end end diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 327b69e5110..50aec83b867 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -231,12 +231,15 @@ module MilestonesHelper end end - def group_or_dashboard_milestone_path(milestone) - if milestone.group_milestone? - group_milestone_path(milestone.group, milestone.iid, milestone: { title: milestone.title }) - else - dashboard_milestone_path(milestone.safe_title, title: milestone.title) - end + def group_or_project_milestone_path(milestone) + params = + if milestone.group_milestone? + { milestone: { title: milestone.title } } + else + { title: milestone.title } + end + + milestone_path(milestone.milestone, params) end def can_admin_project_milestones? diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 6ac1f42c321..02762897c89 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -234,7 +234,7 @@ module SortingHelper end def sort_title_milestone - s_('SortOptions|Milestone') + s_('SortOptions|Milestone due date') end def sort_title_milestone_later diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 16a72c680fa..aeb35538d67 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -10,6 +10,7 @@ module Ci include Importable include Gitlab::Utils::StrongMemoize include Deployable + include HasRef belongs_to :project, inverse_of: :builds belongs_to :runner @@ -220,6 +221,10 @@ module Ci next unless build.project build.deployment&.drop + end + + after_transition any => [:failed] do |build| + next unless build.project if build.retry_failure? begin @@ -640,11 +645,11 @@ module Ci def secret_group_variables return [] unless project.group - project.group.ci_variables_for(ref, project) + project.group.ci_variables_for(git_ref, project) end def secret_project_variables(environment: persisted_environment) - project.ci_variables_for(ref: ref, environment: environment) + project.ci_variables_for(ref: git_ref, environment: environment) end def steps diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 25937065011..1f5017cc3c3 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -11,6 +11,7 @@ module Ci include Gitlab::Utils::StrongMemoize include AtomicInternalId include EnumWithNil + include HasRef belongs_to :project, inverse_of: :all_pipelines belongs_to :user @@ -380,7 +381,7 @@ module Ci end def branch? - !tag? && !merge_request? + super && !merge_request? end def stuck? @@ -580,7 +581,7 @@ module Ci end def protected_ref? - strong_memoize(:protected_ref) { project.protected_for?(ref) } + strong_memoize(:protected_ref) { project.protected_for?(git_ref) } end def legacy_trigger @@ -712,14 +713,10 @@ module Ci end def git_ref - if branch? + if merge_request? Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s - elsif merge_request? - Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s - elsif tag? - Gitlab::Git::TAG_REF_PREFIX + ref.to_s else - raise ArgumentError, 'Invalid pipeline type!' + super end end diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb index 8f8790585a3..7799f069742 100644 --- a/app/models/clusters/applications/ingress.rb +++ b/app/models/clusters/applications/ingress.rb @@ -23,7 +23,7 @@ module Clusters FETCH_IP_ADDRESS_DELAY = 30.seconds state_machine :status do - before_transition any => [:installed] do |application| + after_transition any => [:installed] do |application| application.run_after_commit do ClusterWaitForIngressIpAddressWorker.perform_in( FETCH_IP_ADDRESS_DELAY, application.name, application.id) diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb index 0c72d7d8340..5ac152278da 100644 --- a/app/models/clusters/applications/knative.rb +++ b/app/models/clusters/applications/knative.rb @@ -20,7 +20,7 @@ module Clusters self.reactive_cache_key = ->(knative) { [knative.class.model_name.singular, knative.id] } state_machine :status do - before_transition any => [:installed] do |application| + after_transition any => [:installed] do |application| application.run_after_commit do ClusterWaitForIngressIpAddressWorker.perform_in( FETCH_IP_ADDRESS_DELAY, application.name, application.id) diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index c931b340b24..0c0247da1fb 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Runner < ActiveRecord::Base - VERSION = '0.1.39'.freeze + VERSION = '0.1.43'.freeze self.table_name = 'clusters_applications_runners' diff --git a/app/models/concerns/has_ref.rb b/app/models/concerns/has_ref.rb new file mode 100644 index 00000000000..d7089294efc --- /dev/null +++ b/app/models/concerns/has_ref.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module HasRef + extend ActiveSupport::Concern + + def branch? + !tag? + end + + def git_ref + if branch? + Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s + elsif tag? + Gitlab::Git::TAG_REF_PREFIX + ref.to_s + end + end +end diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb index e44a069b730..055ffe04646 100644 --- a/app/models/concerns/milestoneish.rb +++ b/app/models/concerns/milestoneish.rb @@ -42,7 +42,7 @@ module Milestoneish def issues_visible_to_user(user) memoize_per_user(user, :issues_visible_to_user) do IssuesFinder.new(user, issues_finder_params) - .execute.preload(:assignees).where(milestone_id: milestoneish_ids) + .execute.preload(:assignees).where(milestone_id: milestoneish_id) end end diff --git a/app/models/dashboard_group_milestone.rb b/app/models/dashboard_group_milestone.rb index ad0bb55f0a7..9bcc95e35a5 100644 --- a/app/models/dashboard_group_milestone.rb +++ b/app/models/dashboard_group_milestone.rb @@ -6,7 +6,7 @@ class DashboardGroupMilestone < GlobalMilestone attr_reader :group_name def initialize(milestone) - super(milestone.title, Array(milestone)) + super @group_name = milestone.group.full_name end @@ -18,22 +18,4 @@ class DashboardGroupMilestone < GlobalMilestone .active .map { |m| new(m) } end - - override :group_milestone? - def group_milestone? - @first_milestone.group_milestone? - end - - override :milestoneish_ids - def milestoneish_ids - milestones.map(&:id) - end - - def group - @first_milestone.group - end - - def iid - @first_milestone.iid - end end diff --git a/app/models/dashboard_milestone.rb b/app/models/dashboard_milestone.rb index 96bc8090b81..9b377b70e5b 100644 --- a/app/models/dashboard_milestone.rb +++ b/app/models/dashboard_milestone.rb @@ -1,11 +1,15 @@ # frozen_string_literal: true class DashboardMilestone < GlobalMilestone - def issues_finder_params - { authorized_only: true } + attr_reader :project_name + + def initialize(milestone) + super + + @project_name = milestone.project.full_name end - def dashboard_milestone? + def project_milestone? true end end diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index 085ffd16c6a..4e82f3fed27 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -3,69 +3,78 @@ class GlobalMilestone include Milestoneish - EPOCH = DateTime.parse('1970-01-01') STATE_COUNT_HASH = { opened: 0, closed: 0, all: 0 }.freeze - attr_accessor :title, :milestones + attr_reader :milestone alias_attribute :name, :title + delegate :title, :state, :due_date, :start_date, :participants, :project, :group, :expires_at, :closed?, :iid, :group_milestone?, :safe_title, :milestoneish_id, to: :milestone + + def to_hash + { + name: title, + title: title, + group_name: group&.full_name, + project_name: project&.full_name + } + end + def for_display - @first_milestone + @milestone end def self.build_collection(projects, params) - params = - { project_ids: projects.map(&:id), state: params[:state] } - - child_milestones = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder - - milestones = child_milestones.select(:id, :title).group_by(&:title).map do |title, grouped| - milestones_relation = Milestone.where(id: grouped.map(&:id)) - new(title, milestones_relation) - end + items = Milestone.of_projects(projects) + .reorder_by_due_date_asc + .order_by_name_asc - milestones.sort_by { |milestone| milestone.due_date || EPOCH } + Milestone.filter_by_state(items, params[:state]).map { |m| new(m) } end + # necessary for legacy milestones def self.build(projects, title) - child_milestones = Milestone.of_projects(projects).where(title: title) - return if child_milestones.blank? + milestones = Milestone.of_projects(projects).where(title: title) + return if milestones.blank? - new(title, child_milestones) + new(milestones.first) end - def self.count_by_state(milestones_by_state_and_title, state) - milestones_by_state_and_title.count do |(milestone_state, _), _| - milestone_state == state + def self.states_count(projects, group = nil) + legacy_group_milestones_count = legacy_group_milestone_states_count(projects) + group_milestones_count = group_milestones_states_count(group) + + legacy_group_milestones_count.merge(group_milestones_count) do |k, legacy_group_milestones_count, group_milestones_count| + legacy_group_milestones_count + group_milestones_count end end - private_class_method :count_by_state - def initialize(title, milestones) - @title = title - @name = title - @milestones = milestones - @first_milestone = milestones.find {|m| m.description.present? } || milestones.first - end + def self.group_milestones_states_count(group) + return STATE_COUNT_HASH unless group - def milestoneish_ids - milestones.select(:id) - end + counts_by_state = Milestone.of_groups(group).count_by_state - def safe_title - @title.to_slug.normalize.to_s + { + opened: counts_by_state['active'] || 0, + closed: counts_by_state['closed'] || 0, + all: counts_by_state.values.sum + } end - def projects - @projects ||= Project.for_milestones(milestoneish_ids) - end + def self.legacy_group_milestone_states_count(projects) + return STATE_COUNT_HASH unless projects - def state - milestones.each do |milestone| - return 'active' if milestone.state != 'closed' - end + # We need to reorder(nil) on the projects, because the controller passes them in sorted. + relation = Milestone.of_projects(projects.reorder(nil)).count_by_state - 'closed' + { + opened: relation['active'] || 0, + closed: relation['closed'] || 0, + all: relation.values.sum + } + end + + def initialize(milestone) + @milestone = milestone end def active? @@ -77,37 +86,14 @@ class GlobalMilestone end def issues - @issues ||= Issue.of_milestones(milestoneish_ids).includes(:project, :assignees, :labels) + @issues ||= Issue.of_milestones(milestone).includes(:project, :assignees, :labels) end def merge_requests - @merge_requests ||= MergeRequest.of_milestones(milestoneish_ids).includes(:target_project, :assignee, :labels) - end - - def participants - @participants ||= milestones.map(&:participants).flatten.uniq + @merge_requests ||= MergeRequest.of_milestones(milestone).includes(:target_project, :assignee, :labels) end def labels - @labels ||= GlobalLabel.build_collection(milestones.includes(:labels).map(&:labels).flatten) - .sort_by!(&:title) - end - - def due_date - return @due_date if defined?(@due_date) - - @due_date = - if @milestones.all? { |x| x.due_date == @milestones.first.due_date } - @milestones.first.due_date - end - end - - def start_date - return @start_date if defined?(@start_date) - - @start_date = - if @milestones.all? { |x| x.start_date == @milestones.first.start_date } - @milestones.first.start_date - end + @labels ||= GlobalLabel.build_collection(milestone.labels).sort_by!(&:title) end end diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb index 9dfaebacc83..a58537de319 100644 --- a/app/models/group_milestone.rb +++ b/app/models/group_milestone.rb @@ -1,18 +1,35 @@ # frozen_string_literal: true # Group Milestones are milestones that can be shared among many projects within the same group class GroupMilestone < GlobalMilestone - attr_accessor :group + attr_reader :group, :milestones def self.build_collection(group, projects, params) - super(projects, params).each do |milestone| - milestone.group = group + params = + { state: params[:state] } + + project_milestones = Milestone.of_projects(projects) + child_milestones = Milestone.filter_by_state(project_milestones, params[:state]) + grouped_milestones = child_milestones.group_by(&:title) + + grouped_milestones.map do |title, grouped| + new(title, grouped, group) end end def self.build(group, projects, title) - super(projects, title).tap do |milestone| - milestone&.group = group - end + child_milestones = Milestone.of_projects(projects).where(title: title) + return if child_milestones.blank? + + new(title, child_milestones, group) + end + + def initialize(title, milestones, group) + @milestones = milestones + @group = group + end + + def milestone + @milestone ||= milestones.find { |m| m.description.present? } || milestones.first end def issues_finder_params diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b937bef100b..6092c56b925 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -105,7 +105,9 @@ class MergeRequest < ActiveRecord::Base before_transition any => :opened do |merge_request| merge_request.merge_jid = nil + end + after_transition any => :opened do |merge_request| merge_request.run_after_commit do UpdateHeadPipelineForMergeRequestWorker.perform_async(merge_request.id) end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 6dc0fca68e6..f55c39d9912 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -94,6 +94,10 @@ class Milestone < ActiveRecord::Base end end + def count_by_state + reorder(nil).group(:state).count + end + def predefined?(milestone) milestone == Any || milestone == None || @@ -215,7 +219,7 @@ class Milestone < ActiveRecord::Base self.class.reference_prefix + self.title end - def milestoneish_ids + def milestoneish_id id end diff --git a/app/models/project.rb b/app/models/project.rb index eab19fe4506..cd558752080 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1733,10 +1733,21 @@ class Project < ActiveRecord::Base end def protected_for?(ref) - if repository.branch_exists?(ref) - ProtectedBranch.protected?(self, ref) - elsif repository.tag_exists?(ref) - ProtectedTag.protected?(self, ref) + raise Repository::AmbiguousRefError if repository.ambiguous_ref?(ref) + + resolved_ref = repository.expand_ref(ref) || ref + return false unless Gitlab::Git.tag_ref?(resolved_ref) || Gitlab::Git.branch_ref?(resolved_ref) + + ref_name = if resolved_ref == ref + Gitlab::Git.ref_name(resolved_ref) + else + ref + end + + if Gitlab::Git.branch_ref?(resolved_ref) + ProtectedBranch.protected?(self, ref_name) + elsif Gitlab::Git.tag_ref?(resolved_ref) + ProtectedTag.protected?(self, ref_name) end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 015a179f374..b19ae2e0e6a 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -25,6 +25,7 @@ class Repository delegate :bundle_to_disk, to: :raw_repository CreateTreeError = Class.new(StandardError) + AmbiguousRefError = Class.new(StandardError) # Methods that cache data from the Git repository. # @@ -181,6 +182,18 @@ class Repository tags.find { |tag| tag.name == name } end + def ambiguous_ref?(ref) + tag_exists?(ref) && branch_exists?(ref) + end + + def expand_ref(ref) + if tag_exists?(ref) + Gitlab::Git::TAG_REF_PREFIX + ref + elsif branch_exists?(ref) + Gitlab::Git::BRANCH_REF_PREFIX + ref + end + end + def add_branch(user, branch_name, ref) branch = raw_repository.add_branch(branch_name, user: user, target: ref) diff --git a/app/models/todo.rb b/app/models/todo.rb index 7b64615f699..d9b86d941b6 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -4,6 +4,11 @@ class Todo < ActiveRecord::Base include Sortable include FromUnion + # Time to wait for todos being removed when not visible for user anymore. + # Prevents TODOs being removed by mistake, for example, removing access from a user + # and giving it back again. + WAIT_FOR_DELETE = 1.hour + ASSIGNED = 1 MENTIONED = 2 BUILD_FAILED = 3 diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 19b5552887f..f8d8ef04001 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -31,7 +31,8 @@ module Ci seeds_block: block, variables_attributes: params[:variables_attributes], project: project, - current_user: current_user) + current_user: current_user, + push_options: params[:push_options]) sequence = Gitlab::Ci::Pipeline::Chain::Sequence .new(pipeline, command, SEQUENCE) diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index f1883877d56..9ecee7c6156 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -174,7 +174,8 @@ class GitPushService < BaseService params[:newrev], params[:ref], @push_commits, - commits_count: commits_count) + commits_count: commits_count, + push_options: params[:push_options] || []) end def push_to_existing_branch? diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index dbadafc0f52..03fcf614c64 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -45,7 +45,8 @@ class GitTagPushService < BaseService params[:newrev], params[:ref], commits, - message) + message, + push_options: params[:push_options] || []) end def build_system_push_data diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb index 31d3c844ad5..de78a3f7b27 100644 --- a/app/services/groups/update_service.rb +++ b/app/services/groups/update_service.rb @@ -31,7 +31,7 @@ module Groups def after_update if group.previous_changes.include?(:visibility_level) && group.private? # don't enqueue immediately to prevent todos removal in case of a mistake - TodosDestroyer::GroupPrivateWorker.perform_in(1.hour, group.id) + TodosDestroyer::GroupPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, group.id) end end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index a1d0cc0e568..e992d682c79 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -44,7 +44,7 @@ module Issues if issue.previous_changes.include?('confidential') # don't enqueue immediately to prevent todos removal in case of a mistake - TodosDestroyer::ConfidentialIssueWorker.perform_in(1.hour, issue.id) if issue.confidential? + TodosDestroyer::ConfidentialIssueWorker.perform_in(Todo::WAIT_FOR_DELETE, issue.id) if issue.confidential? create_confidentiality_note(issue) end diff --git a/app/services/members/base_service.rb b/app/services/members/base_service.rb index d734571f835..e78affff797 100644 --- a/app/services/members/base_service.rb +++ b/app/services/members/base_service.rb @@ -47,5 +47,11 @@ module Members raise "Unknown action '#{action}' on #{member}!" end end + + def enqueue_delete_todos(member) + type = member.is_a?(GroupMember) ? 'Group' : 'Project' + # don't enqueue immediately to prevent todos removal in case of a mistake + TodosDestroyer::EntityLeaveWorker.perform_in(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type) + end end end diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb index c186a5971dc..ae0c644e6c0 100644 --- a/app/services/members/destroy_service.rb +++ b/app/services/members/destroy_service.rb @@ -15,7 +15,7 @@ module Members notification_service.decline_access_request(member) end - enqeue_delete_todos(member) + enqueue_delete_todos(member) after_execute(member: member) @@ -24,12 +24,6 @@ module Members private - def enqeue_delete_todos(member) - type = member.is_a?(GroupMember) ? 'Group' : 'Project' - # don't enqueue immediately to prevent todos removal in case of a mistake - TodosDestroyer::EntityLeaveWorker.perform_in(1.hour, member.user_id, member.source_id, type) - end - def can_destroy_member?(member) can?(current_user, destroy_member_permission(member), member) end diff --git a/app/services/members/update_service.rb b/app/services/members/update_service.rb index 1f5618dae53..ff8d5c1d8c9 100644 --- a/app/services/members/update_service.rb +++ b/app/services/members/update_service.rb @@ -10,9 +10,18 @@ module Members if member.update(params) after_execute(action: permission, old_access_level: old_access_level, member: member) + + # Deletes only confidential issues todos for guests + enqueue_delete_todos(member) if downgrading_to_guest? end member end + + private + + def downgrading_to_guest? + params[:access_level] == Gitlab::Access::GUEST + end end end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 33d8299c8b6..86a04587f79 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -46,11 +46,13 @@ module MergeRequests end if merge_request.previous_changes.include?('assignee_id') + reassigned_merge_request_args = [merge_request, current_user] + old_assignee_id = merge_request.previous_changes['assignee_id'].first - old_assignee = User.find(old_assignee_id) if old_assignee_id + reassigned_merge_request_args << User.find(old_assignee_id) if old_assignee_id create_assignee_note(merge_request) - notification_service.async.reassigned_merge_request(merge_request, current_user, old_assignee) + notification_service.async.reassigned_merge_request(*reassigned_merge_request_args) todo_service.reassigned_merge_request(merge_request, current_user) end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index ff035fea216..e1cf327209b 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -188,7 +188,7 @@ class NotificationService # * merge_request assignee if their notification level is not Disabled # * users with custom level checked with "reassign merge request" # - def reassigned_merge_request(merge_request, current_user, previous_assignee) + def reassigned_merge_request(merge_request, current_user, previous_assignee = nil) recipients = NotificationRecipientService.build_recipients( merge_request, current_user, diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 93e48fc0199..dd1b9680ece 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -61,9 +61,9 @@ module Projects if project.previous_changes.include?(:visibility_level) && project.private? # don't enqueue immediately to prevent todos removal in case of a mistake - TodosDestroyer::ProjectPrivateWorker.perform_in(1.hour, project.id) + TodosDestroyer::ProjectPrivateWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id) elsif (project_changed_feature_keys & todos_features_changes).present? - TodosDestroyer::PrivateFeaturesWorker.perform_in(1.hour, project.id) + TodosDestroyer::PrivateFeaturesWorker.perform_in(Todo::WAIT_FOR_DELETE, project.id) end if project.previous_changes.include?('path') diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml index b876d6fd1f3..89212eb6bf9 100644 --- a/app/views/dashboard/milestones/_milestone.html.haml +++ b/app/views/dashboard/milestones/_milestone.html.haml @@ -1,5 +1,5 @@ = render 'shared/milestones/milestone', - milestone_path: group_or_dashboard_milestone_path(milestone), + milestone_path: group_or_project_milestone_path(milestone), issues_path: issues_dashboard_path(milestone_title: milestone.title), merge_requests_path: merge_requests_dashboard_path(milestone_title: milestone.title), milestone: milestone, diff --git a/app/views/events/_events.html.haml b/app/views/events/_events.html.haml index 68c19df092d..6ae4c334f7f 100644 --- a/app/views/events/_events.html.haml +++ b/app/views/events/_events.html.haml @@ -1 +1,4 @@ -= render partial: 'events/event', collection: @events +- if @events.present? + = render partial: 'events/event', collection: @events +- else + .nothing-here-block= _("No activities found") diff --git a/app/views/projects/services/prometheus/_metrics.html.haml b/app/views/projects/services/prometheus/_metrics.html.haml index 597c029f755..a1d74b91002 100644 --- a/app/views/projects/services/prometheus/_metrics.html.haml +++ b/app/views/projects/services/prometheus/_metrics.html.haml @@ -1,6 +1,6 @@ - project = local_assigns.fetch(:project) -.card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/metrics') } } +.card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/index') } } .card-header = s_('PrometheusService|Common metrics') %span.badge.badge-pill.js-monitored-count 0 diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml index 1d0b0265bb7..9d4574c4590 100644 --- a/app/views/projects/services/prometheus/_show.html.haml +++ b/app/views/projects/services/prometheus/_show.html.haml @@ -4,7 +4,7 @@ = s_('PrometheusService|Metrics') %p = s_('PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters.') - = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus_library/metrics'), target: '_blank', rel: "noopener noreferrer" + = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus_library/index'), target: '_blank', rel: "noopener noreferrer" .col-lg-9 = render 'projects/services/prometheus/metrics', project: @project diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index ed7fefba56d..40b8374848e 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -1,5 +1,5 @@ - dashboard = local_assigns[:dashboard] -- custom_dom_id = dom_id(milestone.try(:milestones) ? milestone.milestones.first : milestone) +- custom_dom_id = dom_id(milestone.try(:milestone) ? milestone.milestone : milestone) - milestone_type = milestone.group_milestone? ? 'Group Milestone' : 'Project Milestone' %li{ class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: custom_dom_id } @@ -21,10 +21,12 @@ = milestone.group.full_name - if milestone.legacy_group_milestone? .projects - - milestone.milestones.each do |milestone| - = link_to milestone_path(milestone) do - %span.label-badge.label-badge-blue.d-inline-block.append-bottom-5 - = dashboard ? milestone.project.full_name : milestone.project.name + - link_to milestone_path(milestone.milestone) do + %span.label-badge.label-badge-blue.d-inline-block.append-bottom-5 + = dashboard ? milestone.project.full_name : milestone.project.name + - if milestone.project + .label-badge.label-badge-gray.d-inline-block + = milestone.project.full_name .col-sm-4.milestone-progress = milestone_progress_bar(milestone) @@ -58,5 +60,5 @@ - else = link_to 'Close Milestone', group_milestone_route(milestone, {state_event: :close }), method: :put, class: "btn btn-sm btn-grouped btn-close" - if dashboard - .status-box.status-box-milestone + .label-badge.label-badge-gray = milestone_type diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml index 0499b04a482..55b1c14022f 100644 --- a/app/views/shared/milestones/_top.html.haml +++ b/app/views/shared/milestones/_top.html.haml @@ -62,20 +62,19 @@ %th Open issues %th State %th Due date - - milestone.milestones.each do |ms| %tr %td - - project_name = group ? ms.project.name : ms.project.full_name - = link_to project_name, project_milestone_path(ms.project, ms) + - project_name = group ? milestone.project.name : milestone.project.full_name + = link_to project_name, milestone_path(milestone.milestone) %td - = ms.issues_visible_to_user(current_user).opened.count + = milestone.milestone.issues_visible_to_user(current_user).opened.count %td - - if ms.closed? + - if milestone.closed? Closed - else Open %td - = ms.expires_at + = milestone.expires_at - elsif milestone.group_milestone? %br View diff --git a/app/workers/mail_scheduler/notification_service_worker.rb b/app/workers/mail_scheduler/notification_service_worker.rb index 4726e416182..c8ccaf0c487 100644 --- a/app/workers/mail_scheduler/notification_service_worker.rb +++ b/app/workers/mail_scheduler/notification_service_worker.rb @@ -8,14 +8,35 @@ module MailScheduler include MailSchedulerQueue def perform(meth, *args) - deserialized_args = ActiveJob::Arguments.deserialize(args) + check_arguments!(args) + deserialized_args = ActiveJob::Arguments.deserialize(args) notification_service.public_send(meth, *deserialized_args) # rubocop:disable GitlabSecurity/PublicSend rescue ActiveJob::DeserializationError + # No-op. + # This exception gets raised when an argument + # is correct (deserializeable), but it still cannot be deserialized. + # This can happen when an object has been deleted after + # rails passes this job to sidekiq, but before + # sidekiq gets it for execution. + # In this case just do nothing. end def self.perform_async(*args) super(*ActiveJob::Arguments.serialize(args)) end + + private + + # If an argument is in the ActiveJob::Arguments::TYPE_WHITELIST list, + # it means the argument cannot be deserialized. + # Which means there's something wrong with our code. + def check_arguments!(args) + args.each do |arg| + if arg.class.in?(ActiveJob::Arguments::TYPE_WHITELIST) + raise(ArgumentError, "Argument `#{arg}` cannot be deserialized because of its type") + end + end + end end end diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index 72a1733a2a8..bbd4ab159e4 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -3,7 +3,7 @@ class PostReceive include ApplicationWorker - def perform(gl_repository, identifier, changes) + def perform(gl_repository, identifier, changes, push_options = []) project, is_wiki = Gitlab::GlRepository.parse(gl_repository) if project.nil? @@ -15,7 +15,7 @@ class PostReceive # Use Sidekiq.logger so arguments can be correlated with execution # time and thread ID's. Sidekiq.logger.info "changes: #{changes.inspect}" if ENV['SIDEKIQ_LOG_ARGUMENTS'] - post_received = Gitlab::GitPostReceive.new(project, identifier, changes) + post_received = Gitlab::GitPostReceive.new(project, identifier, changes, push_options) if is_wiki process_wiki_changes(post_received) @@ -38,9 +38,21 @@ class PostReceive post_received.changes_refs do |oldrev, newrev, ref| if Gitlab::Git.tag_ref?(ref) - GitTagPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute + GitTagPushService.new( + post_received.project, + @user, + oldrev: oldrev, + newrev: newrev, + ref: ref, + push_options: post_received.push_options).execute elsif Gitlab::Git.branch_ref?(ref) - GitPushService.new(post_received.project, @user, oldrev: oldrev, newrev: newrev, ref: ref).execute + GitPushService.new( + post_received.project, + @user, + oldrev: oldrev, + newrev: newrev, + ref: ref, + push_options: post_received.push_options).execute end changes << Gitlab::DataBuilder::Repository.single_change(oldrev, newrev, ref) diff --git a/changelogs/unreleased/18667-handle-push-opts.yml b/changelogs/unreleased/18667-handle-push-opts.yml new file mode 100644 index 00000000000..204293476f6 --- /dev/null +++ b/changelogs/unreleased/18667-handle-push-opts.yml @@ -0,0 +1,5 @@ +--- +title: Handle ci.skip push option +merge_request: 15643 +author: Jonathon Reinhart +type: added diff --git a/changelogs/unreleased/51668-fix-line-numbers.yml b/changelogs/unreleased/51668-fix-line-numbers.yml deleted file mode 100644 index ac6e45e3cc7..00000000000 --- a/changelogs/unreleased/51668-fix-line-numbers.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Adjust line-height of blame view line numbers -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/54206-show-the-activity-filter-dropdown-in-discussion-tab-only.yml b/changelogs/unreleased/54206-show-the-activity-filter-dropdown-in-discussion-tab-only.yml new file mode 100644 index 00000000000..e29987b0935 --- /dev/null +++ b/changelogs/unreleased/54206-show-the-activity-filter-dropdown-in-discussion-tab-only.yml @@ -0,0 +1,5 @@ +--- +title: Discussion filter only displayed in discussions tab for merge requests +merge_request: 24082 +author: +type: changed diff --git a/changelogs/unreleased/54427-label-xss.yml b/changelogs/unreleased/54427-label-xss.yml new file mode 100644 index 00000000000..090d1832af2 --- /dev/null +++ b/changelogs/unreleased/54427-label-xss.yml @@ -0,0 +1,5 @@ +--- +title: Escape html entities in LabelReferenceFilter when no label found +merge_request: +author: +type: security diff --git a/changelogs/unreleased/54953-fix-commit_email_hostname-accessor-in-fake_application_settings.yml b/changelogs/unreleased/54953-fix-commit_email_hostname-accessor-in-fake_application_settings.yml deleted file mode 100644 index 623b3a7319c..00000000000 --- a/changelogs/unreleased/54953-fix-commit_email_hostname-accessor-in-fake_application_settings.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix a 500 error that could occur until all migrations are done -merge_request: 23939 -author: -type: fixed diff --git a/changelogs/unreleased/55103-hide-group-cluster-features.yml b/changelogs/unreleased/55103-hide-group-cluster-features.yml deleted file mode 100644 index fbe780d6f01..00000000000 --- a/changelogs/unreleased/55103-hide-group-cluster-features.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Hide cluster features that don't work yet with Group Clusters -merge_request: 23935 -author: -type: fixed diff --git a/changelogs/unreleased/55266-fix-incorrect-due-date-parsing.yml b/changelogs/unreleased/55266-fix-incorrect-due-date-parsing.yml new file mode 100644 index 00000000000..62a57085192 --- /dev/null +++ b/changelogs/unreleased/55266-fix-incorrect-due-date-parsing.yml @@ -0,0 +1,5 @@ +--- +title: Use 'parsePikadayDate' to parse due date string +merge_request: 24045 +author: +type: fixed diff --git a/changelogs/unreleased/55369-update-milestone-sort-to-say-say-milestone-due-date.yml b/changelogs/unreleased/55369-update-milestone-sort-to-say-say-milestone-due-date.yml new file mode 100644 index 00000000000..7476b9caa93 --- /dev/null +++ b/changelogs/unreleased/55369-update-milestone-sort-to-say-say-milestone-due-date.yml @@ -0,0 +1,5 @@ +--- +title: Renames Milestone sort into Milestone due date +merge_request: 24080 +author: Jacopo Beschi @jacopo-beschi +type: changed diff --git a/changelogs/unreleased/55755-user-activity-is-stuck-loading-when-there-is-none.yml b/changelogs/unreleased/55755-user-activity-is-stuck-loading-when-there-is-none.yml new file mode 100644 index 00000000000..5362a781281 --- /dev/null +++ b/changelogs/unreleased/55755-user-activity-is-stuck-loading-when-there-is-none.yml @@ -0,0 +1,5 @@ +--- +title: Hide spinner on empty activites list on user profile overview +merge_request: 24063 +author: +type: other diff --git a/changelogs/unreleased/55836-docs-fix-navigation-style-in-docs.yml b/changelogs/unreleased/55836-docs-fix-navigation-style-in-docs.yml new file mode 100644 index 00000000000..2ac3599175b --- /dev/null +++ b/changelogs/unreleased/55836-docs-fix-navigation-style-in-docs.yml @@ -0,0 +1,5 @@ +--- +title: Fix navigation style in docs +merge_request: 24090 +author: Takuya Noguchi +type: other diff --git a/changelogs/unreleased/blackst0ne-bump-rails-cve-2018-16476.yml b/changelogs/unreleased/blackst0ne-bump-rails-cve-2018-16476.yml new file mode 100644 index 00000000000..dfa94c69ce0 --- /dev/null +++ b/changelogs/unreleased/blackst0ne-bump-rails-cve-2018-16476.yml @@ -0,0 +1,5 @@ +--- +title: Bump Ruby on Rails to 5.0.7.1 +merge_request: 23396 +author: "@blackst0ne" +type: security diff --git a/changelogs/unreleased/ccr-49289_milestone_link.yml b/changelogs/unreleased/ccr-49289_milestone_link.yml new file mode 100644 index 00000000000..14c09752a24 --- /dev/null +++ b/changelogs/unreleased/ccr-49289_milestone_link.yml @@ -0,0 +1,5 @@ +--- +title: Add project milestone link +merge_request: 22552 +author: +type: added diff --git a/changelogs/unreleased/deprecated-insert-sql.yml b/changelogs/unreleased/deprecated-insert-sql.yml new file mode 100644 index 00000000000..ad21fbd9dde --- /dev/null +++ b/changelogs/unreleased/deprecated-insert-sql.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix deprecation: insert_sql is deprecated and will be removed' +merge_request: 23944 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/deprecated-redirect-back.yml b/changelogs/unreleased/deprecated-redirect-back.yml new file mode 100644 index 00000000000..7fc567fbdb5 --- /dev/null +++ b/changelogs/unreleased/deprecated-redirect-back.yml @@ -0,0 +1,5 @@ +--- +title: 'Fix deprecation: redirect_to :back is deprecated' +merge_request: 23943 +author: Jasper Maes +type: other diff --git a/changelogs/unreleased/fj-55781-fix-api-blob-content-disposition.yml b/changelogs/unreleased/fj-55781-fix-api-blob-content-disposition.yml deleted file mode 100644 index 2e1d9889b22..00000000000 --- a/changelogs/unreleased/fj-55781-fix-api-blob-content-disposition.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix content-disposition in blobs and files API endpoint -merge_request: 24078 -author: -type: fixed diff --git a/changelogs/unreleased/markdown-toolbar-btn-fix.yml b/changelogs/unreleased/markdown-toolbar-btn-fix.yml deleted file mode 100644 index eefb4d19f86..00000000000 --- a/changelogs/unreleased/markdown-toolbar-btn-fix.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fixed markdown toolbar buttons -merge_request: -author: -type: fixed diff --git a/changelogs/unreleased/s3-directories-get.yml b/changelogs/unreleased/s3-directories-get.yml new file mode 100644 index 00000000000..9f76af2bb09 --- /dev/null +++ b/changelogs/unreleased/s3-directories-get.yml @@ -0,0 +1,6 @@ +--- +title: Allow 'rake gitlab:cleanup:remote_upload_files' to read bucket files without + having permissions to see all buckets. +merge_request: 23981 +author: +type: fixed diff --git a/changelogs/unreleased/security-master-secret-ci-variables-exposed.yml b/changelogs/unreleased/security-master-secret-ci-variables-exposed.yml new file mode 100644 index 00000000000..702181065f5 --- /dev/null +++ b/changelogs/unreleased/security-master-secret-ci-variables-exposed.yml @@ -0,0 +1,5 @@ +--- +title: Prevent leaking protected variables for ambiguous refs. +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-master-url-rel.yml b/changelogs/unreleased/security-master-url-rel.yml new file mode 100644 index 00000000000..75f599f6bcd --- /dev/null +++ b/changelogs/unreleased/security-master-url-rel.yml @@ -0,0 +1,5 @@ +--- +title: Set URL rel attribute for broken URLs. +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security-todos_not_redacted_for_guests.yml b/changelogs/unreleased/security-todos_not_redacted_for_guests.yml new file mode 100644 index 00000000000..be0ae9a7193 --- /dev/null +++ b/changelogs/unreleased/security-todos_not_redacted_for_guests.yml @@ -0,0 +1,5 @@ +--- +title: Delete confidential todos for user when downgraded to Guest +merge_request: +author: +type: security diff --git a/changelogs/unreleased/sh-fix-clone-geo-dropdown.yml b/changelogs/unreleased/sh-fix-clone-geo-dropdown.yml deleted file mode 100644 index 1c0cbdc3a2c..00000000000 --- a/changelogs/unreleased/sh-fix-clone-geo-dropdown.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix clone dropdown parent inheritance issues in HAML -merge_request: 24029 -author: -type: fixed diff --git a/changelogs/unreleased/sh-fix-http-clone-panel.yml b/changelogs/unreleased/sh-fix-http-clone-panel.yml deleted file mode 100644 index ab220bd5076..00000000000 --- a/changelogs/unreleased/sh-fix-http-clone-panel.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Fix missing Git clone button when protocol restriction setting enabled -merge_request: 24015 -author: -type: fixed diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-43.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-43.yml new file mode 100644 index 00000000000..24471b028b1 --- /dev/null +++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-1-43.yml @@ -0,0 +1,5 @@ +--- +title: Update GitLab Runner Helm Chart to 0.1.43 +merge_request: 24083 +author: +type: other diff --git a/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb b/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb index 90d869a8c10..8de8b3bcc2e 100644 --- a/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb +++ b/db/migrate/20180413022611_create_missing_namespace_for_internal_users.rb @@ -46,7 +46,7 @@ class CreateMissingNamespaceForInternalUsers < ActiveRecord::Migration[4.2] end insert_query = "INSERT INTO namespaces(owner_id, path, name, created_at, updated_at) VALUES(#{user_id}, '#{path}', '#{path}', NOW(), NOW())" - namespace_id = connection.insert_sql(insert_query) + namespace_id = connection.insert(insert_query) create_route(namespace_id) end diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md index a1ea78b64bd..25ae535d1ec 100644 --- a/doc/administration/job_artifacts.md +++ b/doc/administration/job_artifacts.md @@ -165,12 +165,6 @@ _The artifacts are stored by default in gitlab-rake gitlab:artifacts:migrate ``` - Currently this has to be executed manually and it will allow you to - migrate the existing artifacts to the object storage, but all new - artifacts will still be stored on the local disk. In the future - you will be given an option to define a default storage artifacts for all - new files. - --- **In installations from source:** @@ -201,12 +195,6 @@ _The artifacts are stored by default in sudo -u git -H bundle exec rake gitlab:artifacts:migrate RAILS_ENV=production ``` - Currently this has to be executed manually and it will allow you to - migrate the existing artifacts to the object storage, but all new - artifacts will still be stored on the local disk. In the future - you will be given an option to define a default storage artifacts for all - new files. - ## Expiring artifacts If an expiry date is used for the artifacts, they are marked for deletion diff --git a/doc/administration/monitoring/performance/request_profiling.md b/doc/administration/monitoring/performance/request_profiling.md index dfd9be3d04c..726882fbb87 100644 --- a/doc/administration/monitoring/performance/request_profiling.md +++ b/doc/administration/monitoring/performance/request_profiling.md @@ -2,14 +2,14 @@ ## Procedure -1. Grab the profiling token from `Monitoring > Requests Profiles` admin page +1. Grab the profiling token from **Monitoring > Requests Profiles** admin page (highlighted in a blue in the image below). ![Profile token](img/request_profiling_token.png) 1. Pass the header `X-Profile-Token: <token>` to the request you want to profile. You can use: - Browser extensions. For example, [ModHeader](https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj) Chrome extension. - `curl`. For example, `curl --header 'X-Profile-Token: <token>' https://gitlab.example.com/group/project`. 1. Once request is finished (which will take a little longer than usual), you can - view the profiling output from `Monitoring > Requests Profiles` admin page. + view the profiling output from **Monitoring > Requests Profiles** admin page. ![Profiling output](img/request_profile_result.png) ## Cleaning up diff --git a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md index ec0b5aaed09..b59271e400f 100644 --- a/doc/ci/examples/test-and-deploy-python-application-to-heroku.md +++ b/doc/ci/examples/test-and-deploy-python-application-to-heroku.md @@ -47,7 +47,7 @@ This project has three jobs: ## Store API keys -You'll need to create two variables in `Settings > CI/CD > Variables` on your GitLab project settings: +You'll need to create two variables in **Settings > CI/CD > Variables** in your GitLab project: - `HEROKU_STAGING_API_KEY` - Heroku API key used to deploy staging app. - `HEROKU_PRODUCTION_API_KEY` - Heroku API key used to deploy production app. diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index c74f5e5b3f9..5d87a5c0a1f 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -2200,6 +2200,12 @@ with an API call. If your commit message contains `[ci skip]` or `[skip ci]`, using any capitalization, the commit will be created but the pipeline will be skipped. +Alternatively, one can pass the `ci.skip` [Git push option][push-option] if +using Git 2.10 or newer: +``` +$ git push -o ci.skip +``` + ## Validate the .gitlab-ci.yml Each instance of GitLab CI has an embedded debug tool called Lint, which validates the @@ -2224,3 +2230,4 @@ GitLab CI/CD with various languages. [environment]: ../environments.md "CI/CD environments" [schedules]: ../../user/project/pipelines/schedules.md "Pipelines schedules" [variables]: ../variables/README.md "CI/CD variables" +[push-option]: https://git-scm.com/docs/git-push#git-push--oltoptiongt diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index d9a2eeb32ae..5de8e66e7eb 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -30,7 +30,7 @@ GitLab can seamlessly deploy and manage Prometheus on a [connected Kubernetes cl Once you have a connected Kubernetes cluster with Helm installed, deploying a managed Prometheus is as easy as a single click. -1. Go to the `Operations > Kubernetes` page, to view your connected clusters +1. Go to the **Operations > Kubernetes** page to view your connected clusters 1. Select the cluster you would like to deploy Prometheus to 1. Click the **Install** button to deploy Prometheus to the cluster @@ -88,7 +88,7 @@ to integrate with. Once configured, GitLab will attempt to retrieve performance metrics for any environment which has had a successful deployment. -GitLab will automatically scan the Prometheus server for metrics from known serves like Kubernetes and NGINX, and attempt to identify individual environment. The supported metrics and scan process is detailed in our [Prometheus Metric Library documentation](prometheus_library/metrics.html). +GitLab will automatically scan the Prometheus server for metrics from known serves like Kubernetes and NGINX, and attempt to identify individual environment. The supported metrics and scan process is detailed in our [Prometheus Metric Library documentation](prometheus_library/index.md). You can view the performance dashboard for an environment by [clicking on the monitoring button](../../../ci/environments.md#monitoring-environments). diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md index f94d592d0db..830f17aa7f2 100644 --- a/doc/workflow/todos.md +++ b/doc/workflow/todos.md @@ -35,6 +35,9 @@ A Todo appears in your Todos dashboard when: - the author, or - have set it to automatically merge once pipeline succeeds. +NOTE: **Note:** +When an user no longer has access to a resource related to a Todo like an issue, merge request, project or group the related Todos, for security reasons, gets deleted within the next hour. The delete is delayed to prevent data loss in case user got their access revoked by mistake. + ### Directly addressed Todos > [Introduced][ce-7926] in GitLab 9.0. diff --git a/lib/api/internal.rb b/lib/api/internal.rb index ae40b5f7557..9488b3469d9 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -256,8 +256,9 @@ module API post '/post_receive' do status 200 + PostReceive.perform_async(params[:gl_repository], params[:identifier], - params[:changes]) + params[:changes], params[:push_options].to_a) broadcast_message = BroadcastMessage.current&.last&.message reference_counter_decreased = Gitlab::ReferenceCounter.new(params[:gl_repository]).decrease diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index a0434a66ef1..0add2b3f875 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -195,7 +195,7 @@ module Backup if connection.service == ::Fog::Storage::Local connection.directories.create(key: remote_directory) else - connection.directories.get(remote_directory) + connection.directories.new(key: remote_directory) end end diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb index 2e6d742de27..4f60b6f84c6 100644 --- a/lib/banzai/filter/external_link_filter.rb +++ b/lib/banzai/filter/external_link_filter.rb @@ -9,11 +9,10 @@ module Banzai def call links.each do |node| uri = uri(node['href'].to_s) - next unless uri - node.set_attribute('href', uri.to_s) + node.set_attribute('href', uri.to_s) if uri - if SCHEMES.include?(uri.scheme) && external_url?(uri) + if SCHEMES.include?(uri&.scheme) && !internal_url?(uri) node.set_attribute('rel', 'nofollow noreferrer noopener') node.set_attribute('target', '_blank') end @@ -35,11 +34,12 @@ module Banzai doc.xpath(query) end - def external_url?(uri) + def internal_url?(uri) + return false if uri.nil? # Relative URLs miss a hostname - return false unless uri.hostname + return true unless uri.hostname - uri.hostname != internal_url.hostname + uri.hostname == internal_url.hostname end def internal_url diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index 04ec38209c7..f90a35952e5 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -29,7 +29,7 @@ module Banzai if label yield match, label.id, project, namespace, $~ else - match + escape_html_entities(match) end end end @@ -102,6 +102,10 @@ module Banzai CGI.unescapeHTML(text.to_s) end + def escape_html_entities(text) + CGI.escapeHTML(text.to_s) + end + def object_link_title(object, matches) # use title of wrapped element instead nil diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb index 100b9521412..e62d547d862 100644 --- a/lib/gitlab/ci/pipeline/chain/command.rb +++ b/lib/gitlab/ci/pipeline/chain/command.rb @@ -10,7 +10,7 @@ module Gitlab :origin_ref, :checkout_sha, :after_sha, :before_sha, :trigger_request, :schedule, :merge_request, :ignore_skip_ci, :save_incompleted, - :seeds_block, :variables_attributes + :seeds_block, :variables_attributes, :push_options ) do include Gitlab::Utils::StrongMemoize @@ -54,7 +54,13 @@ module Gitlab def protected_ref? strong_memoize(:protected_ref) do - project.protected_for?(ref) + project.protected_for?(origin_ref) + end + end + + def ambiguous_ref? + strong_memoize(:ambiguous_ref) do + project.repository.ambiguous_ref?(origin_ref) end end end diff --git a/lib/gitlab/ci/pipeline/chain/skip.rb b/lib/gitlab/ci/pipeline/chain/skip.rb index b9707d2f8f5..79bbcc1ed1e 100644 --- a/lib/gitlab/ci/pipeline/chain/skip.rb +++ b/lib/gitlab/ci/pipeline/chain/skip.rb @@ -8,6 +8,7 @@ module Gitlab include ::Gitlab::Utils::StrongMemoize SKIP_PATTERN = /\[(ci[ _-]skip|skip[ _-]ci)\]/i + SKIP_PUSH_OPTION = 'ci.skip' def perform! if skipped? @@ -16,7 +17,7 @@ module Gitlab end def skipped? - !@command.ignore_skip_ci && commit_message_skips_ci? + !@command.ignore_skip_ci && (commit_message_skips_ci? || push_option_skips_ci?) end def break? @@ -32,6 +33,10 @@ module Gitlab !!(@pipeline.git_commit_message =~ SKIP_PATTERN) end end + + def push_option_skips_ci? + !!(@command.push_options&.include?(SKIP_PUSH_OPTION)) + end end end end diff --git a/lib/gitlab/ci/pipeline/chain/validate/repository.rb b/lib/gitlab/ci/pipeline/chain/validate/repository.rb index d88851d8245..9c6c2bc8e25 100644 --- a/lib/gitlab/ci/pipeline/chain/validate/repository.rb +++ b/lib/gitlab/ci/pipeline/chain/validate/repository.rb @@ -16,6 +16,10 @@ module Gitlab unless @command.sha return error('Commit not found') end + + if @command.ambiguous_ref? + return error('Ref is ambiguous') + end end def break? diff --git a/lib/gitlab/cleanup/remote_uploads.rb b/lib/gitlab/cleanup/remote_uploads.rb index eba1faacc3a..03298d960a4 100644 --- a/lib/gitlab/cleanup/remote_uploads.rb +++ b/lib/gitlab/cleanup/remote_uploads.rb @@ -67,7 +67,7 @@ module Gitlab end def remote_directory - connection.directories.get(configuration['remote_directory']) + connection.directories.new(key: configuration['remote_directory']) end def connection diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb index 9bf2f9291a8..862127110b9 100644 --- a/lib/gitlab/data_builder/push.rb +++ b/lib/gitlab/data_builder/push.rb @@ -31,7 +31,11 @@ module Gitlab } } ], - total_commits_count: 1 + total_commits_count: 1, + push_options: [ + "ci.skip", + "custom option" + ] }.freeze # Produce a hash of post-receive data @@ -52,10 +56,12 @@ module Gitlab # homepage: String, # }, # commits: Array, - # total_commits_count: Fixnum + # total_commits_count: Fixnum, + # push_options: Array # } # - def build(project, user, oldrev, newrev, ref, commits = [], message = nil, commits_count: nil) + # rubocop:disable Metrics/ParameterLists + def build(project, user, oldrev, newrev, ref, commits = [], message = nil, commits_count: nil, push_options: []) commits = Array(commits) # Total commits count @@ -93,6 +99,7 @@ module Gitlab project: project.hook_attrs, commits: commit_attrs, total_commits_count: commits_count, + push_options: push_options, # DEPRECATED repository: project.hook_attrs.slice(:name, :url, :description, :homepage, :git_http_url, :git_ssh_url, :visibility_level) diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index c4aac228b2f..44a62586a23 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -54,11 +54,11 @@ module Gitlab end def tag_ref?(ref) - ref.start_with?(TAG_REF_PREFIX) + ref =~ /^#{TAG_REF_PREFIX}.+/ end def branch_ref?(ref) - ref.start_with?(BRANCH_REF_PREFIX) + ref =~ /^#{BRANCH_REF_PREFIX}.+/ end def blank_ref?(ref) diff --git a/lib/gitlab/git_post_receive.rb b/lib/gitlab/git_post_receive.rb index cf2329e489d..426436c2164 100644 --- a/lib/gitlab/git_post_receive.rb +++ b/lib/gitlab/git_post_receive.rb @@ -3,12 +3,13 @@ module Gitlab class GitPostReceive include Gitlab::Identifier - attr_reader :project, :identifier, :changes + attr_reader :project, :identifier, :changes, :push_options - def initialize(project, identifier, changes) + def initialize(project, identifier, changes, push_options) @project = project @identifier = identifier @changes = deserialize_changes(changes) + @push_options = push_options end def identify diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 88eff3b6645..ddfd5599883 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4416,6 +4416,9 @@ msgstr "" msgid "No" msgstr "" +msgid "No activities found" +msgstr "" + msgid "No assignee" msgstr "" @@ -6239,7 +6242,7 @@ msgstr "" msgid "SortOptions|Least popular" msgstr "" -msgid "SortOptions|Milestone" +msgid "SortOptions|Milestone due date" msgstr "" msgid "SortOptions|Milestone due later" diff --git a/qa/Dockerfile b/qa/Dockerfile index 9956ced0ef6..ca7f9accb70 100644 --- a/qa/Dockerfile +++ b/qa/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.4-stretch +FROM ruby:2.5-stretch LABEL maintainer "Grzegorz Bizon <grzegorz@gitlab.com>" ENV DEBIAN_FRONTEND noninteractive diff --git a/qa/Gemfile b/qa/Gemfile index d69c71003ae..75ad7bd07af 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -5,5 +5,6 @@ gem 'capybara', '~> 2.16.1' gem 'capybara-screenshot', '~> 1.0.18' gem 'rake', '~> 12.3.0' gem 'rspec', '~> 3.7' -gem 'selenium-webdriver', '~> 3.8.0' +gem 'selenium-webdriver', '~> 3.12' gem 'airborne', '~> 0.2.13' +gem 'nokogiri', '~> 1.8.5' diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index d61ecf8fbb5..55f3211482b 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -25,7 +25,7 @@ GEM capybara-screenshot (1.0.18) capybara (>= 1.0, < 3) launchy - childprocess (0.8.0) + childprocess (0.9.0) ffi (~> 1.0, >= 1.0.11) coderay (1.1.2) concurrent-ruby (1.0.5) @@ -47,7 +47,7 @@ GEM mini_portile2 (2.3.0) minitest (5.11.1) netrc (0.11.0) - nokogiri (1.8.2) + nokogiri (1.8.5) mini_portile2 (~> 2.3.0) pry (0.11.3) coderay (~> 1.1.0) @@ -78,9 +78,9 @@ GEM rspec-support (~> 3.7.0) rspec-support (3.7.0) rubyzip (1.2.2) - selenium-webdriver (3.8.0) + selenium-webdriver (3.141.0) childprocess (~> 0.5) - rubyzip (~> 1.0) + rubyzip (~> 1.2, >= 1.2.2) thread_safe (0.3.6) tzinfo (1.2.4) thread_safe (~> 0.1) @@ -97,10 +97,11 @@ DEPENDENCIES airborne (~> 0.2.13) capybara (~> 2.16.1) capybara-screenshot (~> 1.0.18) + nokogiri (~> 1.8.5) pry-byebug (~> 3.5.1) rake (~> 12.3.0) rspec (~> 3.7) - selenium-webdriver (~> 3.8.0) + selenium-webdriver (~> 3.12) BUNDLED WITH - 1.17.1 + 1.17.3 diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index 8a8cc14fd4c..c9ccd5f7c55 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -52,7 +52,7 @@ describe Dashboard::MilestonesController do expect(response).to have_gitlab_http_status(200) expect(json_response.size).to eq(2) - expect(json_response.map { |i| i["first_milestone"]["id"] }).to match_array([group_milestone.id, project_milestone.id]) + expect(json_response.map { |i| i["name"] }).to match_array([group_milestone.name, project_milestone.name]) expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name) end diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb index b8e1e08cff7..40d991a669c 100644 --- a/spec/controllers/groups/milestones_controller_spec.rb +++ b/spec/controllers/groups/milestones_controller_spec.rb @@ -64,7 +64,7 @@ describe Groups::MilestonesController do context 'when there is a title parameter' do it 'searches for a legacy group milestone' do - expect(GlobalMilestone).to receive(:build) + expect(GroupMilestone).to receive(:build) expect(Milestone).not_to receive(:find_by_iid) get :show, params: { group_id: group.to_param, id: title, title: milestone1.safe_title } diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index 174840794ed..d57eb87ca77 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -81,7 +81,7 @@ describe 'Group milestones' do description: 'Lorem Ipsum is simply dummy text' ) end - let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.0') } + let!(:active_project_milestone2) { create(:milestone, project: other_project, state: 'active', title: 'v1.1') } let!(:closed_project_milestone1) { create(:milestone, project: project, state: 'closed', title: 'v2.0') } let!(:closed_project_milestone2) { create(:milestone, project: other_project, state: 'closed', title: 'v2.0') } let!(:active_group_milestone) { create(:milestone, group: group, state: 'active', title: 'GL-113') } @@ -104,7 +104,7 @@ describe 'Group milestones' do legacy_milestone = GroupMilestone.build_collection(group, group.projects, { state: 'active' }).first expect(page).to have_selector("#milestone_#{active_group_milestone.id}", count: 1) - expect(page).to have_selector("#milestone_#{legacy_milestone.milestones.first.id}", count: 1) + expect(page).to have_selector("#milestone_#{legacy_milestone.milestone.id}", count: 1) end it 'shows milestone detail and supports its edit' do @@ -121,6 +121,7 @@ describe 'Group milestones' do it 'renders milestones' do expect(page).to have_content('v1.0') + expect(page).to have_content('v1.1') expect(page).to have_content('GL-113') expect(page).to have_link( '1 Issue', diff --git a/spec/features/groups/milestones_sorting_spec.rb b/spec/features/groups/milestones_sorting_spec.rb index bc226ff41c1..7bc015ea28f 100644 --- a/spec/features/groups/milestones_sorting_spec.rb +++ b/spec/features/groups/milestones_sorting_spec.rb @@ -42,6 +42,7 @@ describe 'Milestones sorting', :js do expect(page).to have_button('Due later') + # assert descending sorting within '.milestones' do expect(page.all('ul.content-list > li').first.text).to include('v1.0') expect(page.all('ul.content-list > li')[1].text).to include('v3.0') diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb index 873de85708a..8748230fa0c 100644 --- a/spec/features/users/overview_spec.rb +++ b/spec/features/users/overview_spec.rb @@ -33,6 +33,8 @@ describe 'Overview tab on a user profile', :js do it 'does not show any entries in the list of activities' do page.within('.activities-block') do + expect(page).to have_selector('.loading', visible: false) + expect(page).to have_content('No activities found') expect(page).not_to have_selector('.event-item') end end @@ -93,6 +95,7 @@ describe 'Overview tab on a user profile', :js do it 'it shows an empty project list with an info message' do page.within('.projects-block') do + expect(page).to have_selector('.loading', visible: false) expect(page).to have_content('No projects found') expect(page).not_to have_selector('.project-row') end diff --git a/spec/javascripts/notes/components/discussion_filter_spec.js b/spec/javascripts/notes/components/discussion_filter_spec.js index 9070d968cfd..5efcab436e4 100644 --- a/spec/javascripts/notes/components/discussion_filter_spec.js +++ b/spec/javascripts/notes/components/discussion_filter_spec.js @@ -7,8 +7,9 @@ import { discussionFiltersMock, discussionMock } from '../mock_data'; describe('DiscussionFilter component', () => { let vm; let store; + let eventHub; - beforeEach(() => { + const mountComponent = () => { store = createStore(); const discussions = [ @@ -22,7 +23,7 @@ describe('DiscussionFilter component', () => { const selectedValue = discussionFiltersMock[0].value; store.state.discussions = discussions; - vm = mountComponentWithStore(Component, { + return mountComponentWithStore(Component, { el: null, store, props: { @@ -30,6 +31,11 @@ describe('DiscussionFilter component', () => { selectedValue, }, }); + }; + + beforeEach(() => { + window.mrTabs = undefined; + vm = mountComponent(); }); afterEach(() => { @@ -83,4 +89,30 @@ describe('DiscussionFilter component', () => { expect(defaultFilter.lastChild.classList).toContain('dropdown-divider'); }); + + describe('Merge request tabs', () => { + eventHub = new Vue(); + + beforeEach(() => { + window.mrTabs = { + eventHub, + currentTab: 'show', + }; + + vm = mountComponent(); + }); + + afterEach(() => { + window.mrTabs = undefined; + }); + + it('only renders when discussion tab is active', done => { + eventHub.$emit('MergeRequestTabChange', 'commit'); + + vm.$nextTick(() => { + expect(vm.$el.querySelector).toBeUndefined(); + done(); + }); + }); + }); }); diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb index 2a3c0cd78b8..e6dae8d5382 100644 --- a/spec/lib/banzai/filter/external_link_filter_spec.rb +++ b/spec/lib/banzai/filter/external_link_filter_spec.rb @@ -49,16 +49,16 @@ describe Banzai::Filter::ExternalLinkFilter do end context 'for invalid urls' do - it 'skips broken hrefs' do + it 'adds rel and target attributes to broken hrefs' do doc = filter %q(<p><a href="don't crash on broken urls">Google</a></p>) - expected = %q(<p><a href="don't%20crash%20on%20broken%20urls">Google</a></p>) + expected = %q(<p><a href="don't%20crash%20on%20broken%20urls" rel="nofollow noreferrer noopener" target="_blank">Google</a></p>) expect(doc.to_html).to eq(expected) end - it 'skips improperly formatted mailtos' do + it 'adds rel and target to improperly formatted mailtos' do doc = filter %q(<p><a href="mailto://jblogs@example.com">Email</a></p>) - expected = %q(<p><a href="mailto://jblogs@example.com">Email</a></p>) + expected = %q(<p><a href="mailto://jblogs@example.com" rel="nofollow noreferrer noopener" target="_blank">Email</a></p>) expect(doc.to_html).to eq(expected) end diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb index 00257ed7904..9cfdb9e53a2 100644 --- a/spec/lib/banzai/filter/label_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb @@ -236,6 +236,24 @@ describe Banzai::Filter::LabelReferenceFilter do end end + context 'References with html entities' do + let!(:label) { create(:label, name: '<html>', project: project) } + + it 'links to a valid reference' do + doc = reference_filter('See ~"<html>"') + + expect(doc.css('a').first.attr('href')).to eq urls + .project_issues_url(project, label_name: label.name) + expect(doc.text).to eq 'See <html>' + end + + it 'ignores invalid label names and escapes entities' do + act = %(Label #{Label.reference_prefix}"<non valid>") + + expect(reference_filter(act).to_html).to eq act + end + end + describe 'consecutive references' do let(:bug) { create(:label, name: 'bug', project: project) } let(:feature_proposal) { create(:label, name: 'feature proposal', project: project) } diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb index 75a177d2d1f..6aa802ce6fd 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb @@ -182,4 +182,24 @@ describe Gitlab::Ci::Pipeline::Chain::Command do it { is_expected.to eq(false) } end end + + describe '#ambiguous_ref' do + let(:project) { create(:project, :repository) } + let(:command) { described_class.new(project: project, origin_ref: 'ref') } + + subject { command.ambiguous_ref? } + + context 'when ref is not ambiguous' do + it { is_expected. to eq(false) } + end + + context 'when ref is ambiguous' do + before do + project.repository.add_tag(project.creator, 'ref', 'master') + project.repository.add_branch(project.creator, 'ref', 'master') + end + + it { is_expected. to eq(true) } + end + end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb index 284aed91e29..1b014ecfaa4 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb @@ -14,6 +14,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do Gitlab::Ci::Pipeline::Chain::Command.new( project: project, current_user: user, + origin_ref: 'master', seeds_block: nil) end @@ -106,6 +107,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do Gitlab::Ci::Pipeline::Chain::Command.new( project: project, current_user: user, + origin_ref: 'master', seeds_block: seeds_block) end diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb index fb1b53fc55c..a7cad423d09 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb @@ -42,6 +42,27 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do end end + context 'when ref is ambiguous' do + let(:project) do + create(:project, :repository).tap do |proj| + proj.repository.add_tag(user, 'master', 'master') + end + end + let(:command) do + Gitlab::Ci::Pipeline::Chain::Command.new( + project: project, current_user: user, origin_ref: 'master') + end + + it 'breaks the chain' do + expect(step.break?).to be true + end + + it 'adds an error about missing ref' do + expect(pipeline.errors.to_a) + .to include 'Ref is ambiguous' + end + end + context 'when does not have existing SHA set' do let(:command) do Gitlab::Ci::Pipeline::Chain::Command.new( diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb index fffa727c2ed..2cf812b26dc 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Pipeline::Seed::Build do - let(:pipeline) { create(:ci_empty_pipeline) } + let(:project) { create(:project, :repository) } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:attributes) do { name: 'rspec', diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb index 05ce3412fd8..82f741845db 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Pipeline::Seed::Stage do - let(:pipeline) { create(:ci_empty_pipeline) } + let(:project) { create(:project, :repository) } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } let(:attributes) do { name: 'test', diff --git a/spec/lib/gitlab/cleanup/remote_uploads_spec.rb b/spec/lib/gitlab/cleanup/remote_uploads_spec.rb index 8d03baeb07b..35642cd6e50 100644 --- a/spec/lib/gitlab/cleanup/remote_uploads_spec.rb +++ b/spec/lib/gitlab/cleanup/remote_uploads_spec.rb @@ -25,7 +25,7 @@ describe Gitlab::Cleanup::RemoteUploads do expect(::Fog::Storage).to receive(:new).and_return(connection) - expect(connection).to receive(:directories).and_return(double(get: directory)) + expect(connection).to receive(:directories).and_return(double(new: directory)) expect(directory).to receive(:files).and_return(remote_files) end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index fe7f5f8e1e3..7baf4d93804 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2386,6 +2386,8 @@ describe Ci::Build do end context 'when protected variable is defined' do + let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref } + let(:protected_variable) do { key: 'PROTECTED_KEY', value: 'protected_value', public: false } end @@ -2398,7 +2400,7 @@ describe Ci::Build do context 'when the branch is protected' do before do - allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true) + allow(build.project).to receive(:protected_for?).with(ref).and_return(true) end it { is_expected.to include(protected_variable) } @@ -2406,7 +2408,7 @@ describe Ci::Build do context 'when the tag is protected' do before do - allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true) + allow(build.project).to receive(:protected_for?).with(ref).and_return(true) end it { is_expected.to include(protected_variable) } @@ -2431,6 +2433,8 @@ describe Ci::Build do end context 'when group protected variable is defined' do + let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref } + let(:protected_variable) do { key: 'PROTECTED_KEY', value: 'protected_value', public: false } end @@ -2443,7 +2447,7 @@ describe Ci::Build do context 'when the branch is protected' do before do - allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true) + allow(build.project).to receive(:protected_for?).with(ref).and_return(true) end it { is_expected.to include(protected_variable) } @@ -2451,7 +2455,7 @@ describe Ci::Build do context 'when the tag is protected' do before do - allow(build.project).to receive(:protected_for?).with(build.ref).and_return(true) + allow(build.project).to receive(:protected_for?).with(ref).and_return(true) end it { is_expected.to include(protected_variable) } diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index b67c6a4cffa..17f33785fda 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -397,6 +397,10 @@ describe Ci::Pipeline, :mailer do end describe '#protected_ref?' do + before do + pipeline.project = create(:project, :repository) + end + it 'delegates method to project' do expect(pipeline).not_to be_protected_ref end diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb index 47daa79873e..cae23daac8c 100644 --- a/spec/models/clusters/applications/runner_spec.rb +++ b/spec/models/clusters/applications/runner_spec.rb @@ -18,7 +18,7 @@ describe Clusters::Applications::Runner do let(:application) { create(:clusters_applications_runner, :scheduled, version: '0.1.30') } it 'updates the application version' do - expect(application.reload.version).to eq('0.1.39') + expect(application.reload.version).to eq('0.1.43') end end end @@ -46,7 +46,7 @@ describe Clusters::Applications::Runner do it 'should be initialized with 4 arguments' do expect(subject.name).to eq('runner') expect(subject.chart).to eq('runner/gitlab-runner') - expect(subject.version).to eq('0.1.39') + expect(subject.version).to eq('0.1.43') expect(subject).not_to be_rbac expect(subject.repository).to eq('https://charts.gitlab.io') expect(subject.files).to eq(gitlab_runner.files) @@ -64,7 +64,7 @@ describe Clusters::Applications::Runner do let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') } it 'should be initialized with the locked version' do - expect(subject.version).to eq('0.1.39') + expect(subject.version).to eq('0.1.43') end end end diff --git a/spec/models/concerns/has_ref_spec.rb b/spec/models/concerns/has_ref_spec.rb new file mode 100644 index 00000000000..8aed72d77a4 --- /dev/null +++ b/spec/models/concerns/has_ref_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe HasRef do + describe '#branch?' do + let(:build) { create(:ci_build) } + + subject { build.branch? } + + context 'is not a tag' do + before do + build.tag = false + end + + it 'return true when tag is set to false' do + is_expected.to be_truthy + end + end + + context 'is not a tag' do + before do + build.tag = true + end + + it 'return false when tag is set to true' do + is_expected.to be_falsey + end + end + end + + describe '#git_ref' do + subject { build.git_ref } + + context 'when tag is true' do + let(:build) { create(:ci_build, tag: true) } + + it 'returns a tag ref' do + is_expected.to start_with(Gitlab::Git::TAG_REF_PREFIX) + end + end + + context 'when tag is false' do + let(:build) { create(:ci_build, tag: false) } + + it 'returns a branch ref' do + is_expected.to start_with(Gitlab::Git::BRANCH_REF_PREFIX) + end + end + + context 'when tag is nil' do + let(:build) { create(:ci_build, tag: nil) } + + it 'returns a branch ref' do + is_expected.to start_with(Gitlab::Git::BRANCH_REF_PREFIX) + end + end + end +end diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index b6355455c1d..62699df5611 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -65,56 +65,103 @@ describe GlobalMilestone do ) end - before do - projects = [ + let!(:projects) do + [ project1, project2, project3 ] - - @global_milestones = described_class.build_collection(projects, {}) end - it 'has all project milestones' do - expect(@global_milestones.count).to eq(2) + let!(:global_milestones) { described_class.build_collection(projects, {}) } + + context 'when building a collection of milestones' do + it 'has all project milestones' do + expect(global_milestones.count).to eq(6) + end + + it 'has all project milestones titles' do + expect(global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'Milestone v1.2', 'Milestone v1.2', 'VD-123', 'VD-123', 'VD-123']) + end + + it 'has all project milestones' do + expect(global_milestones.size).to eq(6) + end + + it 'sorts collection by due date' do + expect(global_milestones.map(&:due_date)).to eq [milestone1_due_date, milestone1_due_date, milestone1_due_date, nil, nil, nil] + end end - it 'has all project milestones titles' do - expect(@global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'VD-123']) + context 'when adding new milestones' do + it 'does not add more queries' do + control_count = ActiveRecord::QueryRecorder.new do + described_class.build_collection(projects, {}) + end.count + + create_list(:milestone, 3, project: project3) + + expect do + described_class.build_collection(projects, {}) + end.not_to exceed_all_query_limit(control_count) + end end + end + + describe '.states_count' do + context 'when the projects have milestones' do + before do + create(:closed_milestone, title: 'Active Group Milestone', project: project3) + create(:active_milestone, title: 'Active Group Milestone', project: project1) + create(:active_milestone, title: 'Active Group Milestone', project: project2) + create(:closed_milestone, title: 'Closed Group Milestone', project: project1) + create(:closed_milestone, title: 'Closed Group Milestone', project: project2) + create(:closed_milestone, title: 'Closed Group Milestone', project: project3) + create(:closed_milestone, title: 'Closed Group Milestone 4', group: group) + end + + it 'returns the quantity of global milestones and group milestones in each possible state' do + expected_count = { opened: 2, closed: 5, all: 7 } - it 'has all project milestones' do - expect(@global_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6) + count = described_class.states_count(Project.all, group) + + expect(count).to eq(expected_count) + end + + it 'returns the quantity of global milestones in each possible state' do + expected_count = { opened: 2, closed: 4, all: 6 } + + count = described_class.states_count(Project.all) + + expect(count).to eq(expected_count) + end end - it 'sorts collection by due date' do - expect(@global_milestones.map(&:due_date)).to eq [nil, milestone1_due_date] + context 'when the projects do not have milestones' do + before do + project1 + end + + it 'returns 0 as the quantity of global milestones in each state' do + expected_count = { opened: 0, closed: 0, all: 0 } + + count = described_class.states_count(Project.all) + + expect(count).to eq(expected_count) + end end end describe '#initialize' do let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } - let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } - let(:milestone1_project3) { create(:milestone, title: "Milestone v1.2", project: project3) } - - before do - milestones = - [ - milestone1_project1, - milestone1_project2, - milestone1_project3 - ] - milestones_relation = Milestone.where(id: milestones.map(&:id)) - - @global_milestone = described_class.new(milestone1_project1.title, milestones_relation) - end + subject(:global_milestone) { described_class.new(milestone1_project1) } it 'has exactly one group milestone' do - expect(@global_milestone.title).to eq('Milestone v1.2') + expect(global_milestone.title).to eq('Milestone v1.2') end it 'has all project milestones with the same title' do - expect(@global_milestone.milestones.count).to eq(3) + expect(global_milestone.milestone).to eq(milestone1_project1) end end @@ -122,7 +169,7 @@ describe GlobalMilestone do let(:milestone) { create(:milestone, title: "git / test", project: project1) } it 'strips out slashes and spaces' do - global_milestone = described_class.new(milestone.title, Milestone.where(id: milestone.id)) + global_milestone = described_class.new(milestone) expect(global_milestone.safe_title).to eq('git-test') end @@ -132,11 +179,8 @@ describe GlobalMilestone do context 'when at least one milestone is active' do it 'returns active' do title = 'Active Group Milestone' - milestones = [ - create(:active_milestone, title: title), - create(:closed_milestone, title: title) - ] - global_milestone = described_class.new(title, milestones) + + global_milestone = described_class.new(create(:active_milestone, title: title)) expect(global_milestone.state).to eq('active') end @@ -145,11 +189,8 @@ describe GlobalMilestone do context 'when all milestones are closed' do it 'returns closed' do title = 'Closed Group Milestone' - milestones = [ - create(:closed_milestone, title: title), - create(:closed_milestone, title: title) - ] - global_milestone = described_class.new(title, milestones) + + global_milestone = described_class.new(create(:closed_milestone, title: title)) expect(global_milestone.state).to eq('closed') end diff --git a/spec/models/group_milestone_spec.rb b/spec/models/group_milestone_spec.rb index b60676afc91..fcc33cd95fe 100644 --- a/spec/models/group_milestone_spec.rb +++ b/spec/models/group_milestone_spec.rb @@ -20,13 +20,36 @@ describe GroupMilestone do end describe '.build_collection' do - before do - project_milestone + let(:group) { create(:group) } + let(:project1) { create(:project, group: group) } + let(:project2) { create(:project, path: 'gitlab-ci', group: group) } + let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } + + let!(:projects) do + [ + project1, + project2, + project3 + ] end it 'returns array of milestones, each with group assigned' do milestones = described_class.build_collection(group, [project], {}) expect(milestones).to all(have_attributes(group: group)) end + + context 'when adding new milestones' do + it 'does not add more queries' do + control_count = ActiveRecord::QueryRecorder.new do + described_class.build_collection(group, projects, {}) + end.count + + create(:milestone, title: 'This title', project: project1) + + expect do + described_class.build_collection(group, projects, {}) + end.not_to exceed_all_query_limit(control_count) + end + end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 4b86c6a1836..4b6592020c1 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2550,6 +2550,10 @@ describe Project do end context 'when the ref is not protected' do + before do + allow(project).to receive(:protected_for?).with('ref').and_return(false) + end + it 'contains only the CI variables' do is_expected.to contain_exactly(ci_variable) end @@ -2589,42 +2593,139 @@ describe Project do end describe '#protected_for?' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } - subject { project.protected_for?('ref') } + subject { project.protected_for?(ref) } - context 'when the ref is not protected' do + shared_examples 'ref is not protected' do before do stub_application_setting( default_branch_protection: Gitlab::Access::PROTECTION_NONE) end it 'returns false' do - is_expected.to be_falsey + is_expected.to be false end end - context 'when the ref is a protected branch' do + shared_examples 'ref is protected branch' do before do - allow(project).to receive(:repository).and_call_original - allow(project).to receive_message_chain(:repository, :branch_exists?).and_return(true) - create(:protected_branch, name: 'ref', project: project) + create(:protected_branch, name: 'master', project: project) end it 'returns true' do - is_expected.to be_truthy + is_expected.to be true end end - context 'when the ref is a protected tag' do + shared_examples 'ref is protected tag' do before do - allow(project).to receive_message_chain(:repository, :branch_exists?).and_return(false) - allow(project).to receive_message_chain(:repository, :tag_exists?).and_return(true) - create(:protected_tag, name: 'ref', project: project) + create(:protected_tag, name: 'v1.0.0', project: project) end it 'returns true' do - is_expected.to be_truthy + is_expected.to be true + end + end + + context 'when ref is nil' do + let(:ref) { nil } + + it 'returns false' do + is_expected.to be false + end + end + + context 'when ref is ref name' do + context 'when ref is ambiguous' do + let(:ref) { 'ref' } + + before do + project.repository.add_branch(project.creator, 'ref', 'master') + project.repository.add_tag(project.creator, 'ref', 'master') + end + + it 'raises an error' do + expect { subject }.to raise_error(Repository::AmbiguousRefError) + end + end + + context 'when the ref is not protected' do + let(:ref) { 'master' } + + it_behaves_like 'ref is not protected' + end + + context 'when the ref is a protected branch' do + let(:ref) { 'master' } + + it_behaves_like 'ref is protected branch' + end + + context 'when the ref is a protected tag' do + let(:ref) { 'v1.0.0' } + + it_behaves_like 'ref is protected tag' + end + + context 'when ref does not exist' do + let(:ref) { 'something' } + + it 'returns false' do + is_expected.to be false + end + end + end + + context 'when ref is full ref' do + context 'when the ref is not protected' do + let(:ref) { 'refs/heads/master' } + + it_behaves_like 'ref is not protected' + end + + context 'when the ref is a protected branch' do + let(:ref) { 'refs/heads/master' } + + it_behaves_like 'ref is protected branch' + end + + context 'when the ref is a protected tag' do + let(:ref) { 'refs/tags/v1.0.0' } + + it_behaves_like 'ref is protected tag' + end + + context 'when branch ref name is a full tag ref' do + let(:ref) { 'refs/tags/something' } + + before do + project.repository.add_branch(project.creator, ref, 'master') + end + + context 'when ref is not protected' do + it 'returns false' do + is_expected.to be false + end + end + + context 'when ref is a protected branch' do + before do + create(:protected_branch, name: 'refs/tags/something', project: project) + end + + it 'returns true' do + is_expected.to be true + end + end + end + + context 'when ref does not exist' do + let(:ref) { 'refs/heads/something' } + + it 'returns false' do + is_expected.to be false + end end end end @@ -2844,7 +2945,7 @@ describe Project do it 'shows full error updating an invalid MR' do error_message = 'Failed to replace merge_requests because one or more of the new records could not be saved.'\ - ' Validate fork Source project is not a fork of the target project' + ' Validate fork Source project is not a fork of the target project' expect { project.append_or_update_attribute(:merge_requests, [create(:merge_request)]) } .to raise_error(ActiveRecord::RecordNotSaved, error_message) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index f09b4b67061..2063b4bbe75 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1005,6 +1005,67 @@ describe Repository do end end + describe '#ambiguous_ref?' do + let(:ref) { 'ref' } + + subject { repository.ambiguous_ref?(ref) } + + context 'when ref is ambiguous' do + before do + repository.add_tag(project.creator, ref, 'master') + repository.add_branch(project.creator, ref, 'master') + end + + it 'should be true' do + is_expected.to eq(true) + end + end + + context 'when ref is not ambiguous' do + before do + repository.add_tag(project.creator, ref, 'master') + end + + it 'should be false' do + is_expected.to eq(false) + end + end + end + + describe '#expand_ref' do + let(:ref) { 'ref' } + + subject { repository.expand_ref(ref) } + + context 'when ref is not tag or branch name' do + let(:ref) { 'refs/heads/master' } + + it 'returns nil' do + is_expected.to eq(nil) + end + end + + context 'when ref is tag name' do + before do + repository.add_tag(project.creator, ref, 'master') + end + + it 'returns the tag ref' do + is_expected.to eq("refs/tags/#{ref}") + end + end + + context 'when ref is branch name' do + before do + repository.add_branch(project.creator, ref, 'master') + end + + it 'returns the branch ref' do + is_expected.to eq("refs/heads/#{ref}") + end + end + end + describe '#add_branch' do let(:branch_name) { 'new_feature' } let(:target) { 'master' } diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 589816b5d8f..0fe63e2e517 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -809,7 +809,8 @@ describe API::Internal do gl_repository: gl_repository, secret_token: secret_token, identifier: identifier, - changes: changes + changes: changes, + push_options: push_options } end @@ -817,6 +818,11 @@ describe API::Internal do "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/new_branch" end + let(:push_options) do + ['ci.skip', + 'another push option'] + end + before do project.add_developer(user) allow(described_class).to receive(:identify).and_return(user) @@ -825,7 +831,7 @@ describe API::Internal do it 'enqueues a PostReceive worker job' do expect(PostReceive).to receive(:perform_async) - .with(gl_repository, identifier, changes) + .with(gl_repository, identifier, changes, push_options) post api("/internal/post_receive"), params: valid_params end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index c63621fe7d1..2f322cc7054 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -1320,7 +1320,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do end before do - fog_connection.directories.get('artifacts').files.create( + fog_connection.directories.new(key: 'artifacts').files.create( key: 'tmp/uploads/12312300', body: 'content' ) diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 3cc29a7076d..f1514e90eb2 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -1123,7 +1123,7 @@ describe 'Git LFS API and storage' do context 'with valid remote_id' do before do - fog_connection.directories.get('lfs-objects').files.create( + fog_connection.directories.new(key: 'lfs-objects').files.create( key: 'tmp/uploads/12312300', body: 'content' ) diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index ffa47d527f7..87b60387c52 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -19,12 +19,14 @@ describe Ci::CreatePipelineService do ref: ref_name, trigger_request: nil, variables_attributes: nil, - merge_request: nil) + merge_request: nil, + push_options: nil) params = { ref: ref, before: '00000000', after: after, commits: [{ message: message }], - variables_attributes: variables_attributes } + variables_attributes: variables_attributes, + push_options: push_options } described_class.new(project, user, params).execute( source, trigger_request: trigger_request, merge_request: merge_request) @@ -357,6 +359,22 @@ describe Ci::CreatePipelineService do end end + context 'when push options contain ci.skip' do + let(:push_options) do + ['ci.skip', + 'another push option'] + end + + it 'creates a pipline in the skipped state' do + pipeline = execute_service(push_options: push_options) + + # TODO: DRY these up with "skips builds creation if the commit message" + expect(pipeline).to be_persisted + expect(pipeline.builds.any?).to be false + expect(pipeline.status).to eq("skipped") + end + end + context 'when there are no jobs for this pipeline' do before do config = YAML.dump({ test: { script: 'ls', only: ['feature'] } }) @@ -667,7 +685,7 @@ describe Ci::CreatePipelineService do stub_ci_pipeline_yaml_file(YAML.dump(config)) end - let(:ref_name) { 'feature' } + let(:ref_name) { 'refs/heads/feature' } context 'when source is merge request' do let(:source) { :merge_request } @@ -696,7 +714,7 @@ describe Ci::CreatePipelineService do let(:merge_request) do create(:merge_request, source_project: project, - source_branch: ref_name, + source_branch: Gitlab::Git.ref_name(ref_name), target_project: project, target_branch: 'master') end @@ -709,7 +727,7 @@ describe Ci::CreatePipelineService do end context 'when ref is tag' do - let(:ref_name) { 'v1.1.0' } + let(:ref_name) { 'refs/tags/v1.1.0' } it 'does not create a merge request pipeline' do expect(pipeline).not_to be_persisted @@ -721,7 +739,7 @@ describe Ci::CreatePipelineService do let(:merge_request) do create(:merge_request, source_project: project, - source_branch: ref_name, + source_branch: Gitlab::Git.ref_name(ref_name), target_project: target_project, target_branch: 'master') end @@ -786,7 +804,7 @@ describe Ci::CreatePipelineService do let(:merge_request) do create(:merge_request, source_project: project, - source_branch: ref_name, + source_branch: Gitlab::Git.ref_name(ref_name), target_project: project, target_branch: 'master') end @@ -928,7 +946,7 @@ describe Ci::CreatePipelineService do let(:merge_request) do create(:merge_request, source_project: project, - source_branch: ref_name, + source_branch: Gitlab::Git.ref_name(ref_name), target_project: project, target_branch: 'master') end diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb index 84cfa53ea05..d87a7dd234d 100644 --- a/spec/services/groups/update_service_spec.rb +++ b/spec/services/groups/update_service_spec.rb @@ -56,7 +56,7 @@ describe Groups::UpdateService do create(:project, :private, group: internal_group) expect(TodosDestroyer::GroupPrivateWorker).to receive(:perform_in) - .with(1.hour, internal_group.id) + .with(Todo::WAIT_FOR_DELETE, internal_group.id) end it "changes permission level to private" do diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index bd519e7f077..ce20bf2bef6 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -77,7 +77,7 @@ describe Issues::UpdateService, :mailer do end it 'enqueues ConfidentialIssueWorker when an issue is made confidential' do - expect(TodosDestroyer::ConfidentialIssueWorker).to receive(:perform_in).with(1.hour, issue.id) + expect(TodosDestroyer::ConfidentialIssueWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, issue.id) update_issue(confidential: true) end diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb index 0a5220c7c61..5aa7165e135 100644 --- a/spec/services/members/destroy_service_spec.rb +++ b/spec/services/members/destroy_service_spec.rb @@ -22,7 +22,7 @@ describe Members::DestroyService do shared_examples 'a service destroying a member' do before do type = member.is_a?(GroupMember) ? 'Group' : 'Project' - expect(TodosDestroyer::EntityLeaveWorker).to receive(:perform_in).with(1.hour, member.user_id, member.source_id, type) + expect(TodosDestroyer::EntityLeaveWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type) end it 'destroys the member' do diff --git a/spec/services/members/update_service_spec.rb b/spec/services/members/update_service_spec.rb index 6d19a95ffeb..599ed39ca37 100644 --- a/spec/services/members/update_service_spec.rb +++ b/spec/services/members/update_service_spec.rb @@ -20,11 +20,28 @@ describe Members::UpdateService do shared_examples 'a service updating a member' do it 'updates the member' do + expect(TodosDestroyer::EntityLeaveWorker).not_to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, source.class.name) + updated_member = described_class.new(current_user, params).execute(member, permission: permission) expect(updated_member).to be_valid expect(updated_member.access_level).to eq(Gitlab::Access::MAINTAINER) end + + context 'when member is downgraded to guest' do + let(:params) do + { access_level: Gitlab::Access::GUEST } + end + + it 'schedules to delete confidential todos' do + expect(TodosDestroyer::EntityLeaveWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, source.class.name).once + + updated_member = described_class.new(current_user, params).execute(member, permission: permission) + + expect(updated_member).to be_valid + expect(updated_member.access_level).to eq(Gitlab::Access::GUEST) + end + end end before do diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index d58ff2cedc0..8adfc63222e 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -41,7 +41,7 @@ describe Projects::UpdateService do end it 'updates the project to private' do - expect(TodosDestroyer::ProjectPrivateWorker).to receive(:perform_in).with(1.hour, project.id) + expect(TodosDestroyer::ProjectPrivateWorker).to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, project.id) result = update_project(project, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE) @@ -191,7 +191,7 @@ describe Projects::UpdateService do context 'when changing feature visibility to private' do it 'updates the visibility correctly' do expect(TodosDestroyer::PrivateFeaturesWorker) - .to receive(:perform_in).with(1.hour, project.id) + .to receive(:perform_in).with(Todo::WAIT_FOR_DELETE, project.id) result = update_project(project, user, project_feature_attributes: { issues_access_level: ProjectFeature::PRIVATE } diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb index c74e0bf1955..db9e5eb2ad6 100644 --- a/spec/uploaders/file_uploader_spec.rb +++ b/spec/uploaders/file_uploader_spec.rb @@ -201,7 +201,7 @@ describe FileUploader do end let!(:fog_file) do - fog_connection.directories.get('uploads').files.create( + fog_connection.directories.new(key: 'uploads').files.create( key: 'tmp/uploads/test/123123', body: 'content' ) diff --git a/spec/uploaders/object_storage_spec.rb b/spec/uploaders/object_storage_spec.rb index 7e673681c31..533e9d87ea6 100644 --- a/spec/uploaders/object_storage_spec.rb +++ b/spec/uploaders/object_storage_spec.rb @@ -716,7 +716,7 @@ describe ObjectStorage do end let!(:fog_file) do - fog_connection.directories.get('uploads').files.create( + fog_connection.directories.new(key: 'uploads').files.create( key: 'tmp/uploads/test/123123', body: 'content' ) diff --git a/spec/workers/mail_scheduler/notification_service_worker_spec.rb b/spec/workers/mail_scheduler/notification_service_worker_spec.rb index f725c8763a0..1033557ee88 100644 --- a/spec/workers/mail_scheduler/notification_service_worker_spec.rb +++ b/spec/workers/mail_scheduler/notification_service_worker_spec.rb @@ -17,10 +17,21 @@ describe MailScheduler::NotificationServiceWorker do end context 'when the arguments cannot be deserialized' do - it 'does nothing' do - expect(worker.notification_service).not_to receive(method) + context 'when the arguments are not deserializeable' do + it 'raises exception' do + expect(worker.notification_service).not_to receive(method) + expect { worker.perform(method, key.to_global_id.to_s.succ) }.to raise_exception(ArgumentError) + end + end + + context 'when the arguments are deserializeable' do + it 'does nothing' do + serialized_arguments = *serialize(key) + key.destroy! - worker.perform(method, key.to_global_id.to_s.succ) + expect(worker.notification_service).not_to receive(method) + expect { worker.perform(method, serialized_arguments) }.not_to raise_exception + end end end |