diff options
112 files changed, 2370 insertions, 1037 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 8278119cf10..e7db98858e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 10.7.2 (2018-04-25) + +### Security (2 changes) + +- Serve archive requests with the correct file in all cases. +- Sanitizes user name to avoid XSS attacks. + + ## 10.7.1 (2018-04-23) ### Fixed (11 changes) @@ -237,6 +245,13 @@ entry. - Upgrade Gitaly to upgrade its charlock_holmes. +## 10.6.5 (2018-04-24) + +### Security (1 change) + +- Sanitizes user name to avoid XSS attacks. + + ## 10.6.4 (2018-04-09) ### Fixed (8 changes, 1 of them is from the community) @@ -478,6 +493,13 @@ entry. - Use host URL to build JIRA remote link icon. +## 10.5.8 (2018-04-24) + +### Security (1 change) + +- Sanitizes user name to avoid XSS attacks. + + ## 10.5.7 (2018-04-03) ### Security (2 changes) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 483b7719418..cf22efd819d 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.96.1 +0.96.2 diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index ee74734aa22..6aba2b245a8 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -4.1.0 +4.2.0 @@ -414,7 +414,7 @@ end # Gitaly GRPC client gem 'gitaly-proto', '~> 0.97.0', require: 'gitaly' -gem 'grpc', '~> 1.10.0' +gem 'grpc', '~> 1.11.0' # Locked until https://github.com/google/protobuf/issues/4210 is closed gem 'google-protobuf', '= 3.5.1' diff --git a/Gemfile.lock b/Gemfile.lock index 29c37bc7f1f..9b2c47587ee 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -374,7 +374,7 @@ GEM rake grape_logging (1.7.0) grape - grpc (1.10.0) + grpc (1.11.0) google-protobuf (~> 3.1) googleapis-common-protos-types (~> 1.0.0) googleauth (>= 0.5.1, < 0.7) @@ -1073,7 +1073,7 @@ DEPENDENCIES grape-entity (~> 0.6.0) grape-route-helpers (~> 2.1.0) grape_logging (~> 1.7) - grpc (~> 1.10.0) + grpc (~> 1.11.0) haml_lint (~> 0.26.0) hamlit (~> 2.6.1) hashie-forbidden_attributes diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock index a0330cbdd02..10d5cb6a23f 100644 --- a/Gemfile.rails5.lock +++ b/Gemfile.rails5.lock @@ -304,12 +304,12 @@ GEM flowdock (~> 0.7) gitlab-grit (>= 2.4.1) multi_json - gitlab-gollum-lib (4.2.7.1) + gitlab-gollum-lib (4.2.7.2) gemojione (~> 3.2) github-markup (~> 1.6) gollum-grit_adapter (~> 1.0) nokogiri (>= 1.6.1, < 2.0) - rouge (~> 2.1) + rouge (~> 3.1) sanitize (~> 2.1) stringex (~> 2.6) gitlab-gollum-rugged_adapter (0.4.4) @@ -602,8 +602,6 @@ GEM atomic (>= 1.0.0) mysql2 peek - peek-performance_bar (1.3.1) - peek (>= 0.1.0) peek-pg (1.3.0) concurrent-ruby concurrent-ruby-ext @@ -752,7 +750,7 @@ GEM retriable (3.1.1) rinku (2.0.4) rotp (2.1.2) - rouge (2.2.1) + rouge (3.1.1) rqrcode (0.10.1) chunky_png (~> 1.0) rqrcode-rails3 (0.1.7) @@ -1134,7 +1132,6 @@ DEPENDENCIES peek (~> 1.0.1) peek-gc (~> 0.0.2) peek-mysql2 (~> 1.1.0) - peek-performance_bar (~> 1.3.0) peek-pg (~> 1.3.0) peek-rblineprof (~> 0.2.0) peek-redis (~> 1.2.0) @@ -1166,7 +1163,7 @@ DEPENDENCIES redis-rails (~> 5.0.2) request_store (~> 1.3) responders (~> 2.0) - rouge (~> 2.0) + rouge (~> 3.1) rqrcode-rails3 (~> 0.1.7) rspec-parameterized rspec-rails (~> 3.6.0) diff --git a/app/assets/javascripts/notes/components/discussion_counter.vue b/app/assets/javascripts/notes/components/discussion_counter.vue index d492d1cd001..cbe4774a360 100644 --- a/app/assets/javascripts/notes/components/discussion_counter.vue +++ b/app/assets/javascripts/notes/components/discussion_counter.vue @@ -86,7 +86,7 @@ export default { v-html="resolveSvg" ></span> </span> - <span class=".line-resolve-text"> + <span class="line-resolve-text"> {{ resolvedDiscussionCount }}/{{ discussionCount }} {{ countText }} resolved </span> </div> diff --git a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js index 1eadebc7004..b267422cd97 100644 --- a/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js +++ b/app/assets/javascripts/sidebar/lib/sidebar_move_issue.js @@ -1,4 +1,5 @@ import $ from 'jquery'; +import _ from 'underscore'; function isValidProjectId(id) { return id > 0; @@ -43,7 +44,7 @@ class SidebarMoveIssue { renderRow: project => ` <li> <a href="#" class="js-move-issue-dropdown-item"> - ${project.name_with_namespace} + ${_.escape(project.name_with_namespace)} </a> </li> `, diff --git a/app/assets/javascripts/vue_shared/components/identicon.vue b/app/assets/javascripts/vue_shared/components/identicon.vue index 0a30f467b08..23010f40f26 100644 --- a/app/assets/javascripts/vue_shared/components/identicon.vue +++ b/app/assets/javascripts/vue_shared/components/identicon.vue @@ -17,7 +17,7 @@ export default { }, computed: { /** - * This method is based on app/helpers/application_helper.rb#project_identicon + * This method is based on app/helpers/avatars_helper.rb#project_identicon */ identiconStyles() { const allowedColors = [ diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 81e98f358a8..6d5c6cb136f 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -772,7 +772,3 @@ ul.notes { height: auto; } } - -.line-resolve-text { - vertical-align: middle; -} diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 9137bc92810..40d9fa18a10 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -8,8 +8,8 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController omniauth_flow(Gitlab::Auth::OAuth) end - Gitlab.config.omniauth.providers.each do |provider| - alias_method provider['name'], :handle_omniauth + AuthHelper.providers_for_base_controller.each do |provider| + alias_method provider, :handle_omniauth end # Extend the standard implementation to also increment diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 86c50d88a2a..bc13b8ad7ba 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -33,9 +33,7 @@ class Projects::NotesController < Projects::ApplicationController def resolve return render_404 unless note.resolvable? - note.resolve!(current_user) - - MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(note.noteable) + Notes::ResolveService.new(project, current_user).execute(note) discussion = note.discussion diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb index 0282b378d88..0754123a3cf 100644 --- a/app/finders/groups_finder.rb +++ b/app/finders/groups_finder.rb @@ -39,7 +39,7 @@ class GroupsFinder < UnionFinder def all_groups return [owned_groups] if params[:owned] - return [Group.all] if current_user&.full_private_access? + return [Group.all] if current_user&.full_private_access? && all_available? groups = [] groups << Gitlab::GroupHierarchy.new(groups_for_ancestors, groups_for_descendants).all_groups if current_user @@ -67,6 +67,10 @@ class GroupsFinder < UnionFinder end def include_public_groups? - current_user.nil? || params.fetch(:all_available, true) + current_user.nil? || all_available? + end + + def all_available? + params.fetch(:all_available, true) end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 228c8d2e8f9..6aa307b4db4 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -32,80 +32,6 @@ module ApplicationHelper args.any? { |v| v.to_s.downcase == action_name } end - def project_icon(project_id, options = {}) - project = - if project_id.respond_to?(:avatar_url) - project_id - else - Project.find_by_full_path(project_id) - end - - if project.avatar_url - image_tag project.avatar_url, options - else # generated icon - project_identicon(project, options) - end - end - - def project_identicon(project, options = {}) - allowed_colors = { - red: 'FFEBEE', - purple: 'F3E5F5', - indigo: 'E8EAF6', - blue: 'E3F2FD', - teal: 'E0F2F1', - orange: 'FBE9E7', - gray: 'EEEEEE' - } - - options[:class] ||= '' - options[:class] << ' identicon' - bg_key = project.id % 7 - style = "background-color: ##{allowed_colors.values[bg_key]}; color: #555" - - content_tag(:div, class: options[:class], style: style) do - project.name[0, 1].upcase - end - end - - # Takes both user and email and returns the avatar_icon by - # user (preferred) or email. - def avatar_icon_for(user = nil, email = nil, size = nil, scale = 2, only_path: true) - if user - avatar_icon_for_user(user, size, scale, only_path: only_path) - elsif email - avatar_icon_for_email(email, size, scale, only_path: only_path) - else - default_avatar - end - end - - def avatar_icon_for_email(email = nil, size = nil, scale = 2, only_path: true) - user = User.find_by_any_email(email.try(:downcase)) - if user - avatar_icon_for_user(user, size, scale, only_path: only_path) - else - gravatar_icon(email, size, scale) - end - end - - def avatar_icon_for_user(user = nil, size = nil, scale = 2, only_path: true) - if user - user.avatar_url(size: size, only_path: only_path) || default_avatar - else - gravatar_icon(nil, size, scale) - end - end - - def gravatar_icon(user_email = '', size = nil, scale = 2) - GravatarService.new.execute(user_email, size, scale) || - default_avatar - end - - def default_avatar - asset_path('no_avatar.png') - end - def last_commit(project) if project.repo_exists? time_ago_with_tooltip(project.repository.commit.committed_date) diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index c109954f3a3..d2daee22aba 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -1,6 +1,6 @@ module AuthHelper PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2 authentiq).freeze - FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze + LDAP_PROVIDER = /\Aldap/ def ldap_enabled? Gitlab::Auth::LDAP::Config.enabled? @@ -23,7 +23,7 @@ module AuthHelper end def form_based_provider?(name) - FORM_BASED_PROVIDERS.any? { |pattern| pattern === name.to_s } + [LDAP_PROVIDER, 'crowd'].any? { |pattern| pattern === name.to_s } end def form_based_providers @@ -38,6 +38,10 @@ module AuthHelper auth_providers.reject { |provider| form_based_provider?(provider) } end + def providers_for_base_controller + auth_providers.reject { |provider| LDAP_PROVIDER === provider } + end + def enabled_button_based_providers disabled_providers = Gitlab::CurrentSettings.disabled_oauth_sign_in_sources || [] diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb index 21b6c0a8ad5..d339c01d492 100644 --- a/app/helpers/avatars_helper.rb +++ b/app/helpers/avatars_helper.rb @@ -1,4 +1,78 @@ module AvatarsHelper + def project_icon(project_id, options = {}) + project = + if project_id.respond_to?(:avatar_url) + project_id + else + Project.find_by_full_path(project_id) + end + + if project.avatar_url + image_tag project.avatar_url, options + else # generated icon + project_identicon(project, options) + end + end + + def project_identicon(project, options = {}) + allowed_colors = { + red: 'FFEBEE', + purple: 'F3E5F5', + indigo: 'E8EAF6', + blue: 'E3F2FD', + teal: 'E0F2F1', + orange: 'FBE9E7', + gray: 'EEEEEE' + } + + options[:class] ||= '' + options[:class] << ' identicon' + bg_key = project.id % 7 + style = "background-color: ##{allowed_colors.values[bg_key]}; color: #555" + + content_tag(:div, class: options[:class], style: style) do + project.name[0, 1].upcase + end + end + + # Takes both user and email and returns the avatar_icon by + # user (preferred) or email. + def avatar_icon_for(user = nil, email = nil, size = nil, scale = 2, only_path: true) + if user + avatar_icon_for_user(user, size, scale, only_path: only_path) + elsif email + avatar_icon_for_email(email, size, scale, only_path: only_path) + else + default_avatar + end + end + + def avatar_icon_for_email(email = nil, size = nil, scale = 2, only_path: true) + user = User.find_by_any_email(email.try(:downcase)) + if user + avatar_icon_for_user(user, size, scale, only_path: only_path) + else + gravatar_icon(email, size, scale) + end + end + + def avatar_icon_for_user(user = nil, size = nil, scale = 2, only_path: true) + if user + user.avatar_url(size: size, only_path: only_path) || default_avatar + else + gravatar_icon(nil, size, scale) + end + end + + def gravatar_icon(user_email = '', size = nil, scale = 2) + GravatarService.new.execute(user_email, size, scale) || + default_avatar + end + + def default_avatar + ActionController::Base.helpers.image_path('no_avatar.png') + end + def author_avatar(commit_or_event, options = {}) user_avatar(options.merge({ user: commit_or_event.author, diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb index 5089da519df..5a2360b4661 100644 --- a/app/helpers/dropdowns_helper.rb +++ b/app/helpers/dropdowns_helper.rb @@ -41,7 +41,7 @@ module DropdownsHelper def dropdown_toggle(toggle_text, data_attr, options = {}) default_label = data_attr[:default_label] - content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do + content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}") output << icon('chevron-down') output.html_safe diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 801e624e1de..eb81dc2de43 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -442,7 +442,7 @@ module ProjectsHelper visibilityHelpPath: help_page_path('public_access/public_access'), registryAvailable: Gitlab.config.registry.enabled, registryHelpPath: help_page_path('user/project/container_registry'), - lfsAvailable: Gitlab.config.lfs.enabled && current_user.admin?, + lfsAvailable: Gitlab.config.lfs.enabled, lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') } diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb index 00fe67d6ffb..5b4a141dbcf 100644 --- a/app/helpers/system_note_helper.rb +++ b/app/helpers/system_note_helper.rb @@ -1,14 +1,14 @@ module SystemNoteHelper ICON_NAMES_BY_ACTION = { 'commit' => 'commit', - 'description' => 'pencil', + 'description' => 'pencil-square', 'merge' => 'git-merge', 'merged' => 'git-merge', 'opened' => 'issue-open', 'closed' => 'issue-close', 'time_tracking' => 'timer', 'assignee' => 'user', - 'title' => 'pencil', + 'title' => 'pencil-square', 'task' => 'task-done', 'label' => 'label', 'cross_reference' => 'comment-dots', @@ -18,7 +18,7 @@ module SystemNoteHelper 'milestone' => 'clock', 'discussion' => 'comment', 'moved' => 'arrow-right', - 'outdated' => 'pencil', + 'outdated' => 'pencil-square', 'duplicate' => 'issue-duplicate', 'locked' => 'lock', 'unlocked' => 'lock-open' diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index e4212775956..3646e08a15f 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -16,6 +16,7 @@ class Notify < BaseMailer helper BlobHelper helper EmailsHelper helper MembersHelper + helper AvatarsHelper helper GitlabRoutingHelper def test_email(recipient_email, subject, body) diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 75b8ea2a371..5a1eeb966aa 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -13,14 +13,27 @@ module Ci has_many :statuses, class_name: 'CommitStatus', foreign_key: :stage_id has_many :builds, foreign_key: :stage_id - validates :project, presence: true, unless: :importing? - validates :pipeline, presence: true, unless: :importing? - validates :name, presence: true, unless: :importing? + with_options unless: :importing? do + validates :project, presence: true + validates :pipeline, presence: true + validates :name, presence: true + validates :position, presence: true + end - after_initialize do |stage| + after_initialize do self.status = DEFAULT_STATUS if self.status.nil? end + before_validation unless: :importing? do + next if position.present? + + self.position = statuses.select(:stage_idx) + .where('stage_idx IS NOT NULL') + .group(:stage_idx) + .order('COUNT(*) DESC') + .first&.stage_idx.to_i + end + state_machine :status, initial: :created do event :enqueue do transition created: :pending diff --git a/app/models/commit.rb b/app/models/commit.rb index 9750e9298ec..32f3d90595a 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -105,6 +105,10 @@ class Commit end end end + + def parent_class + ::Project + end end attr_accessor :raw diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 15122cbc693..616a626419b 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -54,7 +54,20 @@ class DiffNote < Note end def diff_file - @diff_file ||= self.original_position.diff_file(self.project.repository) + @diff_file ||= + begin + if created_at_diff?(noteable.diff_refs) + # We're able to use the already persisted diffs (Postgres) if we're + # presenting a "current version" of the MR discussion diff. + # So no need to make an extra Gitaly diff request for it. + # As an extra benefit, the returned `diff_file` already + # has `highlighted_diff_lines` data set from Redis on + # `Diff::FileCollection::MergeRequestDiff`. + noteable.diffs(paths: original_position.paths, expanded: true).diff_files.first + else + original_position.diff_file(self.project.repository) + end + end end def diff_line diff --git a/app/models/user.rb b/app/models/user.rb index b0668148972..4a602ffbb05 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -910,7 +910,7 @@ class User < ActiveRecord::Base def delete_async(deleted_by:, params: {}) block if params[:hard_delete] - DeleteUserWorker.perform_async(deleted_by.id, id, params) + DeleteUserWorker.perform_async(deleted_by.id, id, params.to_h) end def notification_service diff --git a/app/services/ci/ensure_stage_service.rb b/app/services/ci/ensure_stage_service.rb index 87f19b333de..b8c7be2d350 100644 --- a/app/services/ci/ensure_stage_service.rb +++ b/app/services/ci/ensure_stage_service.rb @@ -42,6 +42,7 @@ module Ci def create_stage Ci::Stage.create!(name: @build.stage, + position: @build.stage_idx, pipeline: @build.pipeline, project: @build.project) end diff --git a/app/services/notes/resolve_service.rb b/app/services/notes/resolve_service.rb new file mode 100644 index 00000000000..0db8ee809a9 --- /dev/null +++ b/app/services/notes/resolve_service.rb @@ -0,0 +1,9 @@ +module Notes + class ResolveService < ::BaseService + def execute(note) + note.resolve!(current_user) + + ::MergeRequests::ResolvedDiscussionNotificationService.new(project, current_user).execute(note.noteable) + end + end +end diff --git a/app/services/repository_archive_clean_up_service.rb b/app/services/repository_archive_clean_up_service.rb index 9a88459b511..ba7be4b3f89 100644 --- a/app/services/repository_archive_clean_up_service.rb +++ b/app/services/repository_archive_clean_up_service.rb @@ -20,11 +20,12 @@ class RepositoryArchiveCleanUpService private def clean_up_old_archives - run(%W(find #{path} -not -path #{path} -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -maxdepth 2 -mmin +#{mmin} -delete)) + run(%W(find #{path} -mindepth 1 -maxdepth 3 -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -mmin +#{mmin} -delete)) end def clean_up_empty_directories - run(%W(find #{path} -not -path #{path} -type d -empty -name \*.git -maxdepth 1 -delete)) + run(%W(find #{path} -mindepth 2 -maxdepth 2 -type d -empty -delete)) + run(%W(find #{path} -mindepth 1 -maxdepth 1 -type d -empty -delete)) end def run(cmd) diff --git a/app/views/groups/_group_admin_settings.html.haml b/app/views/groups/_group_admin_settings.html.haml index 2ace1e2dd1e..65e95f3aeef 100644 --- a/app/views/groups/_group_admin_settings.html.haml +++ b/app/views/groups/_group_admin_settings.html.haml @@ -1,28 +1,26 @@ -- if current_user.admin? - .form-group - = f.label :lfs_enabled, 'Large File Storage', class: 'control-label' - .col-sm-10 - .checkbox - = f.label :lfs_enabled do - = f.check_box :lfs_enabled, checked: @group.lfs_enabled? - %strong - Allow projects within this group to use Git LFS - = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') - %br/ - %span.descr This setting can be overridden in each project. +.form-group + = f.label :lfs_enabled, 'Large File Storage', class: 'control-label' + .col-sm-10 + .checkbox + = f.label :lfs_enabled do + = f.check_box :lfs_enabled, checked: @group.lfs_enabled? + %strong + Allow projects within this group to use Git LFS + = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + %br/ + %span.descr This setting can be overridden in each project. -- if can? current_user, :admin_group, @group - .form-group - = f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2' - .col-sm-10 - .checkbox - = f.label :require_two_factor_authentication do - = f.check_box :require_two_factor_authentication - %strong - Require all users in this group to setup Two-factor authentication - = link_to icon('question-circle'), help_page_path('security/two_factor_authentication', anchor: 'enforcing-2fa-for-all-users-in-a-group') - .form-group - .col-sm-offset-2.col-sm-10 - .checkbox - = f.text_field :two_factor_grace_period, class: 'form-control' - .help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication +.form-group + = f.label :require_two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2' + .col-sm-10 + .checkbox + = f.label :require_two_factor_authentication do + = f.check_box :require_two_factor_authentication + %strong + Require all users in this group to setup Two-factor authentication + = link_to icon('question-circle'), help_page_path('security/two_factor_authentication', anchor: 'enforcing-2fa-for-all-users-in-a-group') +.form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.text_field :two_factor_grace_period, class: 'form-control' + .help-block Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index d0c01f95cb7..0e012b5a216 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -8,18 +8,17 @@ %li{ class: "branch-item js-branch-#{branch.name}" } .branch-info .branch-title - = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name' do - = sprite_icon('fork', size: 12) + = sprite_icon('fork', size: 12) + = link_to project_tree_path(@project, branch.name), class: 'item-title str-truncated-100 ref-name prepend-left-8' do = branch.name - - if branch.name == @repository.root_ref - %span.label.label-primary default + %span.label.label-primary.prepend-left-5 default - elsif merged - %span.label.label-info.has-tooltip{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } } + %span.label.label-info.has-tooltip.prepend-left-5{ title: s_('Branches|Merged into %{default_branch}') % { default_branch: @repository.root_ref } } = s_('Branches|merged') - if protected_branch?(@project, branch) - %span.label.label-success + %span.label.label-success.prepend-left-5 = s_('Branches|protected') .block-truncated diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml index 5377d745371..24d2b971472 100644 --- a/app/views/projects/protected_branches/_branches_list.html.haml +++ b/app/views/projects/protected_branches/_branches_list.html.haml @@ -1,4 +1,4 @@ - can_admin_project = can?(current_user, :admin_project, @project) = render layout: 'projects/protected_branches/shared/branches_list', locals: { can_admin_project: can_admin_project } do - = render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches, locals: { can_admin_project: can_admin_project} + = render partial: 'projects/protected_branches/protected_branch', collection: @protected_branches diff --git a/app/views/projects/protected_branches/shared/_protected_branch.html.haml b/app/views/projects/protected_branches/shared/_protected_branch.html.haml index f5b21f0e887..2d3b2af00c2 100644 --- a/app/views/projects/protected_branches/shared/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/shared/_protected_branch.html.haml @@ -21,4 +21,4 @@ - if can_admin_project %td - = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: 'btn btn-warning' + = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], disabled: local_assigns[:disabled], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning" diff --git a/changelogs/unreleased/bvl-fix-openid-redirect.yml b/changelogs/unreleased/bvl-fix-openid-redirect.yml new file mode 100644 index 00000000000..83ee6d953e4 --- /dev/null +++ b/changelogs/unreleased/bvl-fix-openid-redirect.yml @@ -0,0 +1,5 @@ +--- +title: Fix redirection error for applications using OpenID +merge_request: 18599 +author: +type: fixed diff --git a/changelogs/unreleased/dm-commit-trailer-without-gravatar.yml b/changelogs/unreleased/dm-commit-trailer-without-gravatar.yml new file mode 100644 index 00000000000..9f057c67122 --- /dev/null +++ b/changelogs/unreleased/dm-commit-trailer-without-gravatar.yml @@ -0,0 +1,5 @@ +--- +title: Fix commit trailer rendering when Gravatar is disabled +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/feature-show-only-groups-user-is-member-of-in-dashboard.yml b/changelogs/unreleased/feature-show-only-groups-user-is-member-of-in-dashboard.yml new file mode 100644 index 00000000000..6e2273ed9af --- /dev/null +++ b/changelogs/unreleased/feature-show-only-groups-user-is-member-of-in-dashboard.yml @@ -0,0 +1,5 @@ +--- +title: For group dashboard, we no longer show groups which the visitor is not a member of (this applies to admins and auditors) +merge_request: 17884 +author: Roger Rüttimann +type: changed diff --git a/changelogs/unreleased/fix-inconsistent-protected-branch-pill-baseline.yml b/changelogs/unreleased/fix-inconsistent-protected-branch-pill-baseline.yml new file mode 100644 index 00000000000..fb6dffaf226 --- /dev/null +++ b/changelogs/unreleased/fix-inconsistent-protected-branch-pill-baseline.yml @@ -0,0 +1,5 @@ +--- +title: Fixed inconsistent protected branch pill baseline +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/helm-add-alpine-mirrors.yml b/changelogs/unreleased/helm-add-alpine-mirrors.yml new file mode 100644 index 00000000000..656c4f911d0 --- /dev/null +++ b/changelogs/unreleased/helm-add-alpine-mirrors.yml @@ -0,0 +1,5 @@ +--- +title: Increase cluster applications installer availability using alpine linux mirrors +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/jprovazn-commit-notes-api.yml b/changelogs/unreleased/jprovazn-commit-notes-api.yml new file mode 100644 index 00000000000..4665d800ccf --- /dev/null +++ b/changelogs/unreleased/jprovazn-commit-notes-api.yml @@ -0,0 +1,5 @@ +--- +title: Add discussion API for merge requests and commits +merge_request: +author: +type: added diff --git a/changelogs/unreleased/jr-33320-lfs-settings-interface.yml b/changelogs/unreleased/jr-33320-lfs-settings-interface.yml new file mode 100644 index 00000000000..b39308f5474 --- /dev/null +++ b/changelogs/unreleased/jr-33320-lfs-settings-interface.yml @@ -0,0 +1,5 @@ +--- +title: Show group and project LFS settings in the interface to Owners and Masters +merge_request: 18562 +author: +type: changed diff --git a/changelogs/unreleased/osw-use-cached-highlighted-content-for-discussions.yml b/changelogs/unreleased/osw-use-cached-highlighted-content-for-discussions.yml new file mode 100644 index 00000000000..03a11a3038a --- /dev/null +++ b/changelogs/unreleased/osw-use-cached-highlighted-content-for-discussions.yml @@ -0,0 +1,5 @@ +--- +title: Use persisted diff data instead fetching Git on discussions +merge_request: +author: +type: performance diff --git a/changelogs/unreleased/revert-discussion-counter-height.yml b/changelogs/unreleased/revert-discussion-counter-height.yml new file mode 100644 index 00000000000..331ff997009 --- /dev/null +++ b/changelogs/unreleased/revert-discussion-counter-height.yml @@ -0,0 +1,5 @@ +--- +title: Revert discussion counter height +merge_request: 18656 +author: George Tsiolis +type: changed diff --git a/changelogs/unreleased/security-45689-fix-archive-cache-bug.yml b/changelogs/unreleased/security-45689-fix-archive-cache-bug.yml new file mode 100644 index 00000000000..0103a7fc430 --- /dev/null +++ b/changelogs/unreleased/security-45689-fix-archive-cache-bug.yml @@ -0,0 +1,5 @@ +--- +title: Serve archive requests with the correct file in all cases +merge_request: +author: +type: security diff --git a/changelogs/unreleased/security_issue_42029.yml b/changelogs/unreleased/security_issue_42029.yml new file mode 100644 index 00000000000..0772e33f930 --- /dev/null +++ b/changelogs/unreleased/security_issue_42029.yml @@ -0,0 +1,5 @@ +--- +title: Sanitizes user name to avoid XSS attacks +merge_request: +author: +type: security diff --git a/changelogs/unreleased/update-timeline-icon-for-description-edit.yml b/changelogs/unreleased/update-timeline-icon-for-description-edit.yml new file mode 100644 index 00000000000..560db00e503 --- /dev/null +++ b/changelogs/unreleased/update-timeline-icon-for-description-edit.yml @@ -0,0 +1,5 @@ +--- +title: Update timeline icon for description edit +merge_request: 18633 +author: George Tsiolis +type: changed diff --git a/config/initializers/fast_gettext.rb b/config/initializers/9_fast_gettext.rb index fd0167aa476..fd0167aa476 100644 --- a/config/initializers/fast_gettext.rb +++ b/config/initializers/9_fast_gettext.rb diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 2079d3acb72..e3a342590d4 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -104,5 +104,5 @@ Doorkeeper.configure do # set to true if you want this to be allowed # wildcard_redirect_uri false - base_controller 'ApplicationController' + base_controller '::Gitlab::BaseDoorkeeperController' end diff --git a/config/karma.config.js b/config/karma.config.js index 691cda98861..3eb220eed99 100644 --- a/config/karma.config.js +++ b/config/karma.config.js @@ -1,8 +1,16 @@ -var path = require('path'); -var webpack = require('webpack'); -var argumentsParser = require('commander'); -var webpackConfig = require('./webpack.config.js'); -var ROOT_PATH = path.resolve(__dirname, '..'); +const path = require('path'); +const glob = require('glob'); +const chalk = require('chalk'); +const webpack = require('webpack'); +const argumentsParser = require('commander'); +const webpackConfig = require('./webpack.config.js'); + +const ROOT_PATH = path.resolve(__dirname, '..'); + +function fatalError(message) { + console.error(chalk.red(`\nError: ${message}\n`)); + process.exit(1); +} // remove problematic plugins if (webpackConfig.plugins) { @@ -15,33 +23,70 @@ if (webpackConfig.plugins) { }); } -var testFiles = argumentsParser +const specFilters = argumentsParser .option( '-f, --filter-spec [filter]', 'Filter run spec files by path. Multiple filters are like a logical OR.', - (val, memo) => { - memo.push(val); + (filter, memo) => { + memo.push(filter, filter.replace(/\/?$/, '/**/*.js')); return memo; }, [] ) .parse(process.argv).filterSpec; -webpackConfig.plugins.push( - new webpack.DefinePlugin({ - 'process.env.TEST_FILES': JSON.stringify(testFiles), - }) -); +if (specFilters.length) { + const specsPath = /^(?:\.[\\\/])?spec[\\\/]javascripts[\\\/]/; + + // resolve filters + let filteredSpecFiles = specFilters.map(filter => + glob + .sync(filter, { + root: ROOT_PATH, + matchBase: true, + }) + .filter(path => path.endsWith('spec.js')) + ); + + // flatten + filteredSpecFiles = Array.prototype.concat.apply([], filteredSpecFiles); + + // remove duplicates + filteredSpecFiles = [...new Set(filteredSpecFiles)]; + + if (filteredSpecFiles.length < 1) { + fatalError('Your filter did not match any test files.'); + } + + if (!filteredSpecFiles.every(file => specsPath.test(file))) { + fatalError('Test files must be located within /spec/javascripts.'); + } + + const newContext = filteredSpecFiles.reduce((context, file) => { + const relativePath = file.replace(specsPath, ''); + context[file] = `./${relativePath}`; + return context; + }, {}); + + webpackConfig.plugins.push( + new webpack.ContextReplacementPlugin( + /spec[\\\/]javascripts$/, + path.join(ROOT_PATH, 'spec/javascripts'), + newContext + ) + ); +} -webpackConfig.devtool = process.env.BABEL_ENV !== 'coverage' && 'cheap-inline-source-map'; +webpackConfig.entry = undefined; +webpackConfig.devtool = 'cheap-inline-source-map'; // Karma configuration module.exports = function(config) { process.env.TZ = 'Etc/UTC'; - var progressReporter = process.env.CI ? 'mocha' : 'progress'; + const progressReporter = process.env.CI ? 'mocha' : 'progress'; - var karmaConfig = { + const karmaConfig = { basePath: ROOT_PATH, browsers: ['ChromeHeadlessCustom'], customLaunchers: { diff --git a/config/webpack.config.js b/config/webpack.config.js index 39e9fbbd530..b9d098ff9b9 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -69,6 +69,9 @@ const config = { test: /\.js$/, exclude: /(node_modules|vendor\/assets)/, loader: 'babel-loader', + options: { + cacheDirectory: path.join(ROOT_PATH, 'tmp/cache/babel-loader'), + }, }, { test: /\.vue$/, diff --git a/db/migrate/20180417101040_add_tmp_stage_priority_index_to_ci_builds.rb b/db/migrate/20180417101040_add_tmp_stage_priority_index_to_ci_builds.rb new file mode 100644 index 00000000000..ee82c70ecf8 --- /dev/null +++ b/db/migrate/20180417101040_add_tmp_stage_priority_index_to_ci_builds.rb @@ -0,0 +1,16 @@ +class AddTmpStagePriorityIndexToCiBuilds < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index(:ci_builds, [:stage_id, :stage_idx], + where: 'stage_idx IS NOT NULL', name: 'tmp_build_stage_position_index') + end + + def down + remove_concurrent_index_by_name(:ci_builds, 'tmp_build_stage_position_index') + end +end diff --git a/db/migrate/20180417101940_add_index_to_ci_stage.rb b/db/migrate/20180417101940_add_index_to_ci_stage.rb new file mode 100644 index 00000000000..9dac78db774 --- /dev/null +++ b/db/migrate/20180417101940_add_index_to_ci_stage.rb @@ -0,0 +1,9 @@ +class AddIndexToCiStage < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :ci_stages, :position, :integer + end +end diff --git a/db/post_migrate/20180420080616_schedule_stages_index_migration.rb b/db/post_migrate/20180420080616_schedule_stages_index_migration.rb new file mode 100644 index 00000000000..1d0daad002f --- /dev/null +++ b/db/post_migrate/20180420080616_schedule_stages_index_migration.rb @@ -0,0 +1,29 @@ +class ScheduleStagesIndexMigration < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + MIGRATION = 'MigrateStageIndex'.freeze + BATCH_SIZE = 10000 + + disable_ddl_transaction! + + class Stage < ActiveRecord::Base + include EachBatch + self.table_name = 'ci_stages' + end + + def up + disable_statement_timeout + + Stage.all.tap do |relation| + queue_background_migration_jobs_by_range_at_intervals(relation, + MIGRATION, + 5.minutes, + batch_size: BATCH_SIZE) + end + end + + def down + # noop + end +end diff --git a/db/schema.rb b/db/schema.rb index 5853b428430..10cd1bff125 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -322,6 +322,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do add_index "ci_builds", ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id", using: :btree add_index "ci_builds", ["protected"], name: "index_ci_builds_on_protected", using: :btree add_index "ci_builds", ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree + add_index "ci_builds", ["stage_id", "stage_idx"], name: "tmp_build_stage_position_index", where: "(stage_idx IS NOT NULL)", using: :btree add_index "ci_builds", ["stage_id"], name: "index_ci_builds_on_stage_id", using: :btree add_index "ci_builds", ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id", using: :btree add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree @@ -486,6 +487,7 @@ ActiveRecord::Schema.define(version: 20180425131009) do t.string "name" t.integer "status" t.integer "lock_version" + t.integer "position" end add_index "ci_stages", ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true, using: :btree diff --git a/doc/api/discussions.md b/doc/api/discussions.md index c341b7f2009..65e2f9d6cd9 100644 --- a/doc/api/discussions.md +++ b/doc/api/discussions.md @@ -1,6 +1,6 @@ # Discussions API -Discussions are set of related notes on snippets or issues. +Discussions are set of related notes on snippets, issues, merge requests or commits. ## Issues @@ -61,7 +61,8 @@ GET /projects/:id/issues/:issue_iid/discussions "system": false, "noteable_id": 3, "noteable_type": "Issue", - "noteable_iid": null + "noteable_iid": null, + "resolvable": false } ] }, @@ -87,7 +88,8 @@ GET /projects/:id/issues/:issue_iid/discussions "system": false, "noteable_id": 3, "noteable_type": "Issue", - "noteable_iid": null + "noteable_iid": null, + "resolvable": false } ] } @@ -265,7 +267,8 @@ GET /projects/:id/snippets/:snippet_id/discussions "system": false, "noteable_id": 3, "noteable_type": "Snippet", - "noteable_id": null + "noteable_id": null, + "resolvable": false } ] }, @@ -291,7 +294,8 @@ GET /projects/:id/snippets/:snippet_id/discussions "system": false, "noteable_id": 3, "noteable_type": "Snippet", - "noteable_id": null + "noteable_id": null, + "resolvable": false } ] } @@ -409,3 +413,574 @@ Parameters: ```bash curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/snippets/11/discussions/636 ``` + +## Merge requests + +### List project merge request discussions + +Gets a list of all discussions for a single merge request. + +``` +GET /projects/:id/merge_requests/:merge_request_iid/discussions +``` + +| Attribute | Type | Required | Description | +| ------------------- | ---------------- | ---------- | ------------ | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `merge_request_iid` | integer | yes | The IID of a merge request | + +```json +[ + { + "id": "6a9c1750b37d513a43987b574953fceb50b03ce7", + "individual_note": false, + "notes": [ + { + "id": 1126, + "type": "DiscussionNote", + "body": "discussion text", + "attachment": null, + "author": { + "id": 1, + "name": "root", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "created_at": "2018-03-03T21:54:39.668Z", + "updated_at": "2018-03-03T21:54:39.668Z", + "system": false, + "noteable_id": 3, + "noteable_type": "Merge request", + "noteable_iid": null, + "resolved": false, + "resolvable": true, + "resolved_by": null + }, + { + "id": 1129, + "type": "DiscussionNote", + "body": "reply to the discussion", + "attachment": null, + "author": { + "id": 1, + "name": "root", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "created_at": "2018-03-04T13:38:02.127Z", + "updated_at": "2018-03-04T13:38:02.127Z", + "system": false, + "noteable_id": 3, + "noteable_type": "Merge request", + "noteable_iid": null, + "resolved": false, + "resolvable": true, + "resolved_by": null + } + ] + }, + { + "id": "87805b7c09016a7058e91bdbe7b29d1f284a39e6", + "individual_note": true, + "notes": [ + { + "id": 1128, + "type": null, + "body": "a single comment", + "attachment": null, + "author": { + "id": 1, + "name": "root", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "created_at": "2018-03-04T09:17:22.520Z", + "updated_at": "2018-03-04T09:17:22.520Z", + "system": false, + "noteable_id": 3, + "noteable_type": "Merge request", + "noteable_iid": null, + "resolved": false, + "resolvable": true, + "resolved_by": null + } + ] + } +] +``` + +Diff comments contain also position: + +```json +[ + { + "id": "87805b7c09016a7058e91bdbe7b29d1f284a39e6", + "individual_note": false, + "notes": [ + { + "id": 1128, + "type": DiffNote, + "body": "diff comment", + "attachment": null, + "author": { + "id": 1, + "name": "root", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "created_at": "2018-03-04T09:17:22.520Z", + "updated_at": "2018-03-04T09:17:22.520Z", + "system": false, + "noteable_id": 3, + "noteable_type": "Merge request", + "noteable_iid": null, + "position": { + "base_sha": "b5d6e7b1613fca24d250fa8e5bc7bcc3dd6002ef", + "start_sha": "7c9c2ead8a320fb7ba0b4e234bd9529a2614e306", + "head_sha": "4803c71e6b1833ca72b8b26ef2ecd5adc8a38031", + "old_path": "package.json", + "new_path": "package.json", + "position_type": "text", + "old_line": 27, + "new_line": 27 + }, + "resolved": false, + "resolvable": true, + "resolved_by": null + } + ] + } +] +``` + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions +``` + +### Get single merge request discussion + +Returns a single discussion for a specific project merge request + +``` +GET /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| ------------------- | -------------- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `merge_request_iid` | integer | yes | The IID of a merge request | +| `discussion_id` | integer | yes | The ID of a discussion | + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7 +``` + +### Create new merge request discussion + +Creates a new discussion to a single project merge request. This is similar to creating +a note but but another comments (replies) can be added to it later. + +``` +POST /projects/:id/merge_requests/:merge_request_iid/discussions +``` + +Parameters: + +| Attribute | Type | Required | Description | +| ------------------------- | -------------- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `merge_request_iid` | integer | yes | The IID of a merge request | +| `body` | string | yes | The content of a discussion | +| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z | +| `position` | hash | no | Position when creating a diff note | +| `position[base_sha]` | string | yes | Base commit SHA in the source branch | +| `position[start_sha]` | string | yes | SHA referencing commit in target branch | +| `position[head_sha]` | string | yes | SHA referencing HEAD of this merge request | +| `position[position_type]` | string | yes | Type of the position reference', allowed values: 'text' or 'image' | +| `position[new_path]` | string | no | File path after change | +| `position[new_line]` | integer | no | Line number after change (for 'text' diff notes) | +| `position[old_path]` | string | no | File path before change | +| `position[old_line]` | integer | no | Line number before change (for 'text' diff notes) | +| `position[width]` | integer | no | Width of the image (for 'image' diff notes) | +| `position[height]` | integer | no | Height of the image (for 'image' diff notes) | +| `position[x]` | integer | no | X coordinate (for 'image' diff notes) | +| `position[y]` | integer | no | Y coordinate (for 'image' diff notes) | + +```bash +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions?body=comment +``` + +### Resolve a merge request discussion + +Resolve/unresolve whole discussion of a merge request. + +``` +PUT /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| ------------------- | -------------- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `merge_request_iid` | integer | yes | The IID of a merge request | +| `discussion_id` | integer | yes | The ID of a discussion | +| `resolved` | boolean | yes | Resolve/unresolve the discussion | + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7?resolved=true +``` + + +### Add note to existing merge request discussion + +Adds a new note to the discussion. + +``` +POST /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id/notes +``` + +Parameters: + +| Attribute | Type | Required | Description | +| ------------------- | -------------- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `merge_request_iid` | integer | yes | The IID of a merge request | +| `discussion_id` | integer | yes | The ID of a discussion | +| `note_id` | integer | yes | The ID of a discussion note | +| `body` | string | yes | The content of a discussion | +| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z | + +```bash +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment +``` + +### Modify an existing merge request discussion note + +Modify or resolve an existing discussion note of a merge request. + +``` +PUT /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id/notes/:note_id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| ------------------- | -------------- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `merge_request_iid` | integer | yes | The IID of a merge request | +| `discussion_id` | integer | yes | The ID of a discussion | +| `note_id` | integer | yes | The ID of a discussion note | +| `body` | string | no | The content of a discussion (exactly one of `body` or `resolved` must be set | +| `resolved` | boolean | no | Resolve/unresolve the note (exactly one of `body` or `resolved` must be set | + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment +``` + +Resolving a note: + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?resolved=true +``` + +### Delete a merge request discussion note + +Deletes an existing discussion note of a merge request. + +``` +DELETE /projects/:id/merge_requests/:merge_request_iid/discussions/:discussion_id/notes/:note_id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| ------------------- | -------------- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `merge_request_iid` | integer | yes | The IID of a merge request | +| `discussion_id` | integer | yes | The ID of a discussion | +| `note_id` | integer | yes | The ID of a discussion note | + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/merge_requests/11/discussions/636 +``` + +## Commits + +### List project commit discussions + +Gets a list of all discussions for a single commit. + +``` +GET /projects/:id/commits/:commit_id/discussions +``` + +| Attribute | Type | Required | Description | +| ------------------- | ---------------- | ---------- | ------------ | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `commit_id` | integer | yes | The ID of a commit | + +```json +[ + { + "id": "6a9c1750b37d513a43987b574953fceb50b03ce7", + "individual_note": false, + "notes": [ + { + "id": 1126, + "type": "DiscussionNote", + "body": "discussion text", + "attachment": null, + "author": { + "id": 1, + "name": "root", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "created_at": "2018-03-03T21:54:39.668Z", + "updated_at": "2018-03-03T21:54:39.668Z", + "system": false, + "noteable_id": 3, + "noteable_type": "Commit", + "noteable_iid": null, + "resolvable": false + }, + { + "id": 1129, + "type": "DiscussionNote", + "body": "reply to the discussion", + "attachment": null, + "author": { + "id": 1, + "name": "root", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "created_at": "2018-03-04T13:38:02.127Z", + "updated_at": "2018-03-04T13:38:02.127Z", + "system": false, + "noteable_id": 3, + "noteable_type": "Commit", + "noteable_iid": null, + "resolvable": false + } + ] + }, + { + "id": "87805b7c09016a7058e91bdbe7b29d1f284a39e6", + "individual_note": true, + "notes": [ + { + "id": 1128, + "type": null, + "body": "a single comment", + "attachment": null, + "author": { + "id": 1, + "name": "root", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "created_at": "2018-03-04T09:17:22.520Z", + "updated_at": "2018-03-04T09:17:22.520Z", + "system": false, + "noteable_id": 3, + "noteable_type": "Commit", + "noteable_iid": null, + "resolvable": false + } + ] + } +] +``` + +Diff comments contain also position: + +```json +[ + { + "id": "87805b7c09016a7058e91bdbe7b29d1f284a39e6", + "individual_note": false, + "notes": [ + { + "id": 1128, + "type": DiffNote, + "body": "diff comment", + "attachment": null, + "author": { + "id": 1, + "name": "root", + "username": "root", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/00afb8fb6ab07c3ee3e9c1f38777e2f4?s=80&d=identicon", + "web_url": "http://localhost:3000/root" + }, + "created_at": "2018-03-04T09:17:22.520Z", + "updated_at": "2018-03-04T09:17:22.520Z", + "system": false, + "noteable_id": 3, + "noteable_type": "Commit", + "noteable_iid": null, + "position": { + "base_sha": "b5d6e7b1613fca24d250fa8e5bc7bcc3dd6002ef", + "start_sha": "7c9c2ead8a320fb7ba0b4e234bd9529a2614e306", + "head_sha": "4803c71e6b1833ca72b8b26ef2ecd5adc8a38031", + "old_path": "package.json", + "new_path": "package.json", + "position_type": "text", + "old_line": 27, + "new_line": 27 + }, + "resolvable": false + } + ] + } +] +``` + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions +``` + +### Get single commit discussion + +Returns a single discussion for a specific project commit + +``` +GET /projects/:id/commits/:commit_id/discussions/:discussion_id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| ------------------- | -------------- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `commit_id` | integer | yes | The ID of a commit | +| `discussion_id` | integer | yes | The ID of a discussion | + +```bash +curl --request GET --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7 +``` + +### Create new commit discussion + +Creates a new discussion to a single project commit. This is similar to creating +a note but but another comments (replies) can be added to it later. + +``` +POST /projects/:id/commits/:commit_id/discussions +``` + +Parameters: + +| Attribute | Type | Required | Description | +| ------------------------- | -------------- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `commit_id` | integer | yes | The ID of a commit | +| `body` | string | yes | The content of a discussion | +| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z | +| `position` | hash | no | Position when creating a diff note | +| `position[base_sha]` | string | yes | Base commit SHA in the source branch | +| `position[start_sha]` | string | yes | SHA referencing commit in target branch | +| `position[head_sha]` | string | yes | SHA referencing HEAD of this commit | +| `position[position_type]` | string | yes | Type of the position reference', allowed values: 'text' or 'image' | +| `position[new_path]` | string | no | File path after change | +| `position[new_line]` | integer | no | Line number after change | +| `position[old_path]` | string | no | File path before change | +| `position[old_line]` | integer | no | Line number before change | +| `position[width]` | integer | no | Width of the image (for 'image' diff notes) | +| `position[height]` | integer | no | Height of the image (for 'image' diff notes) | +| `position[x]` | integer | no | X coordinate (for 'image' diff notes) | +| `position[y]` | integer | no | Y coordinate (for 'image' diff notes) | + +```bash +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions?body=comment +``` + +### Add note to existing commit discussion + +Adds a new note to the discussion. + +``` +POST /projects/:id/commits/:commit_id/discussions/:discussion_id/notes +``` + +Parameters: + +| Attribute | Type | Required | Description | +| ------------------- | -------------- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `commit_id` | integer | yes | The ID of a commit | +| `discussion_id` | integer | yes | The ID of a discussion | +| `note_id` | integer | yes | The ID of a discussion note | +| `body` | string | yes | The content of a discussion | +| `created_at` | string | no | Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z | + +```bash +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes?body=comment +``` + +### Modify an existing commit discussion note + +Modify or resolve an existing discussion note of a commit. + +``` +PUT /projects/:id/commits/:commit_id/discussions/:discussion_id/notes/:note_id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| ------------------- | -------------- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `commit_id` | integer | yes | The ID of a commit | +| `discussion_id` | integer | yes | The ID of a discussion | +| `note_id` | integer | yes | The ID of a discussion note | +| `body` | string | no | The content of a note | + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?body=comment +``` + +Resolving a note: + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/6a9c1750b37d513a43987b574953fceb50b03ce7/notes/1108?resolved=true +``` + +### Delete a commit discussion note + +Deletes an existing discussion note of a commit. + +``` +DELETE /projects/:id/commits/:commit_id/discussions/:discussion_id/notes/:note_id +``` + +Parameters: + +| Attribute | Type | Required | Description | +| ------------------- | -------------- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) | +| `commit_id` | integer | yes | The ID of a commit | +| `discussion_id` | integer | yes | The ID of a discussion | +| `note_id` | integer | yes | The ID of a discussion note | + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/commits/11/discussions/636 +``` diff --git a/doc/api/groups.md b/doc/api/groups.md index 1aed8aac64e..923fd662a5b 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -10,7 +10,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `skip_groups` | array of integers | no | Skip the group IDs passed | -| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users) | +| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users, `true` for admin) | | `search` | string | no | Return the list of authorized groups matching the search criteria | | `order_by` | string | no | Order groups by `name` or `path`. Default is `name` | | `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` | @@ -94,7 +94,7 @@ Parameters: | --------- | ---- | -------- | ----------- | | `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) of the parent group | | `skip_groups` | array of integers | no | Skip the group IDs passed | -| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users) | +| `all_available` | boolean | no | Show all the groups you have access to (defaults to `false` for authenticated users, `true` for admin) | | `search` | string | no | Return the list of authorized groups matching the search criteria | | `order_by` | string | no | Order groups by `name` or `path`. Default is `name` | | `sort` | string | no | Order groups in `asc` or `desc` order. Default is `asc` | diff --git a/doc/api/notes.md b/doc/api/notes.md index aa38d22845c..d29c5b94915 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -39,7 +39,8 @@ GET /projects/:id/issues/:issue_iid/notes?sort=asc&order_by=updated_at "system": true, "noteable_id": 377, "noteable_type": "Issue", - "noteable_iid": 377 + "noteable_iid": 377, + "resolvable": false }, { "id": 305, @@ -58,7 +59,8 @@ GET /projects/:id/issues/:issue_iid/notes?sort=asc&order_by=updated_at "system": true, "noteable_id": 121, "noteable_type": "Issue", - "noteable_iid": 121 + "noteable_iid": 121, + "resolvable": false } ] ``` @@ -314,7 +316,8 @@ Parameters: "system": false, "noteable_id": 2, "noteable_type": "MergeRequest", - "noteable_iid": 2 + "noteable_iid": 2, + "resolvable": false } ``` diff --git a/doc/development/README.md b/doc/development/README.md index 99c6641e637..3c77e99b8cf 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -41,6 +41,7 @@ comments: false - [Avoid modules with instance variables](module_with_instance_variables.md) if possible - [How to dump production data to staging](db_dump.md) - [Working with the GitHub importer](github_importer.md) +- [Working with Merge Request diffs](diffs.md) ## Performance guides diff --git a/doc/development/diffs.md b/doc/development/diffs.md new file mode 100644 index 00000000000..55fc16e0b33 --- /dev/null +++ b/doc/development/diffs.md @@ -0,0 +1,115 @@ +# Working with Merge Request diffs + +Currently we rely on different sources to present merge request diffs, these include: + +- Rugged gem +- Gitaly service +- Database (through `merge_request_diff_files`) +- Redis (cached highlighted diffs) + +We're constantly moving Rugged calls to Gitaly and the progress can be followed through [Gitaly repo](https://gitlab.com/gitlab-org/gitaly). + +## Architecture overview + +When refreshing a Merge Request (pushing to a source branch, force-pushing to target branch, or if the target branch now contains any commits from the MR) +we fetch the comparison information using `Gitlab::Git::Compare`, which fetches `base` and `head` data using Gitaly and diff between them through +`Gitlab::Git::Diff.between` (which uses _Gitaly_ if it's enabled, otherwise _Rugged_). +The diffs fetching process _limits_ single file diff sizes and the overall size of the whole diff through a series of constant values. Raw diff files are +then persisted on `merge_request_diff_files` table. + +Even though diffs higher than 10kb are collapsed (`Gitlab::Git::Diff::COLLAPSE_LIMIT`), we still keep them on Postgres. However, diff files over _safety limits_ +(see the [Diff limits section](#diff-limits)) are _not_ persisted. + +In order to present diffs information on the Merge Request diffs page, we: + +1. Fetch all diff files from database `merge_request_diff_files` +2. Fetch the _old_ and _new_ file blobs in batch to: + 1. Highlight old and new file content + 2. Know which viewer it should use for each file (text, image, deleted, etc) + 3. Know if the file content changed + 4. Know if it was stored externally + 5. Know if it had storage errors +3. If the diff file is cacheable (text-based), it's cached on Redis +using `Gitlab::Diff::FileCollection::MergeRequestDiff` + +## Diff limits + +As explained above, we limit single diff files and the size of the whole diff. There are scenarios where we collapse the diff file, +and cases where the diff file is not presented at all, and the user is guided to the Blob view. Here we'll go into details about +these limits. + +### Diff collection limits + +Limits that act onto all diff files collection. Files number, lines number and files size are considered. + +```ruby +Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_files] = 100 +``` + +File diffs will be collapsed (but be expandable) if 100 files have already been rendered. + + +```ruby +Gitlab::Git::DiffCollection.collection_limits[:safe_max_lines] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000 +``` + +File diffs will be collapsed (but be expandable) if 5000 lines have already been rendered. + + +```ruby +Gitlab::Git::DiffCollection.collection_limits[:safe_max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] * 5.kilobytes = 500.kilobytes +``` + +File diffs will be collapsed (but be expandable) if 500 kilobytes have already been rendered. + + +```ruby +Gitlab::Git::DiffCollection.collection_limits[:max_files] = Commit::DIFF_HARD_LIMIT_FILES = 1000 +``` + +No more files will be rendered at all if 1000 files have already been rendered. + + +```ruby +Gitlab::Git::DiffCollection.collection_limits[:max_lines] = Commit::DIFF_HARD_LIMIT_LINES = 50000 +``` + +No more files will be rendered at all if 50,000 lines have already been rendered. + +```ruby +Gitlab::Git::DiffCollection.collection_limits[:max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:max_files] * 5.kilobytes = 5000.kilobytes +``` + +No more files will be rendered at all if 5 megabytes have already been rendered. + + +### Individual diff file limits + +Limits that act onto each diff file of a collection. Files number, lines number and files size are considered. + +```ruby +Gitlab::Git::Diff::COLLAPSE_LIMIT = 10.kilobytes +``` + +File diff will be collapsed (but be expandable) if it is larger than 10 kilobytes. + +```ruby +Gitlab::Git::Diff::SIZE_LIMIT = 100.kilobytes +``` + +File diff will not be rendered if it's larger than 100 kilobytes. + + +```ruby +Commit::DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000 +``` + +File diff will be suppressed (technically different from collapsed, but behaves the same, and is expandable) if it has more than 5000 lines. + +## Viewers + +Diff Viewers, which can be found on `models/diff_viewer/*` are classes used to map metadata about each type of Diff File. It has information +whether it's a binary, which partial should be used to render it or which File extensions this class accounts for. + +`DiffViewer::Base` validates _blobs_ (old and new versions) content, extension and file type in order to check if it can be rendered. + diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md index af477f5ab99..0d0d511582b 100644 --- a/doc/development/testing_guide/frontend_testing.md +++ b/doc/development/testing_guide/frontend_testing.md @@ -62,6 +62,7 @@ describe('.methodName', () => { }); }); ``` + #### Testing promises When testing Promises you should always make sure that the test is asynchronous and rejections are handled. @@ -69,9 +70,9 @@ Your Promise chain should therefore end with a call of the `done` callback and ` ```javascript // Good -it('tests a promise', (done) => { +it('tests a promise', done => { promise - .then((data) => { + .then(data => { expect(data).toBe(asExpected); }) .then(done) @@ -79,10 +80,10 @@ it('tests a promise', (done) => { }); // Good -it('tests a promise rejection', (done) => { +it('tests a promise rejection', done => { promise .then(done.fail) - .catch((error) => { + .catch(error => { expect(error).toBe(expectedError); }) .then(done) @@ -91,38 +92,37 @@ it('tests a promise rejection', (done) => { // Bad (missing done callback) it('tests a promise', () => { - promise - .then((data) => { - expect(data).toBe(asExpected); - }) + promise.then(data => { + expect(data).toBe(asExpected); + }); }); // Bad (missing catch) -it('tests a promise', (done) => { +it('tests a promise', done => { promise - .then((data) => { + .then(data => { expect(data).toBe(asExpected); }) - .then(done) + .then(done); }); // Bad (use done.fail in asynchronous tests) -it('tests a promise', (done) => { +it('tests a promise', done => { promise - .then((data) => { + .then(data => { expect(data).toBe(asExpected); }) .then(done) - .catch(fail) + .catch(fail); }); // Bad (missing catch) -it('tests a promise rejection', (done) => { +it('tests a promise rejection', done => { promise - .catch((error) => { + .catch(error => { expect(error).toBe(expectedError); }) - .then(done) + .then(done); }); ``` @@ -139,7 +139,7 @@ documentation for these methods can be found in the [jasmine introduction page]( Sometimes you may need to spy on a method that is directly imported by another module. GitLab has a custom `spyOnDependency` method which utilizes [babel-plugin-rewire](https://github.com/speedskater/babel-plugin-rewire) to -achieve this. It can be used like so: +achieve this. It can be used like so: ```javascript // my_module.js @@ -181,8 +181,8 @@ See this [section][vue-test]. `rake karma` runs the frontend-only (JavaScript) tests. It consists of two subtasks: -- `rake karma:fixtures` (re-)generates fixtures -- `rake karma:tests` actually executes the tests +* `rake karma:fixtures` (re-)generates fixtures +* `rake karma:tests` actually executes the tests As long as the fixtures don't change, `rake karma:tests` (or `yarn karma`) is sufficient (and saves you some time). @@ -217,6 +217,14 @@ yarn karma-start --filter-spec profile/account/components/ yarn karma-start -f vue_shared -f vue_mr_widget ``` +You can also use glob syntax to match files. Remember to put quotes around the +glob otherwise your shell may split it into multiple arguments: + +```bash +# Run all specs named `file_spec` within the IDE subdirectory +yarn karma -f 'spec/javascripts/ide/**/file_spec.js' +``` + ## RSpec feature integration tests Information on setting up and running RSpec integration tests with @@ -231,14 +239,14 @@ supported by the PhantomJS test runner which is used for both Karma and RSpec tests. We polyfill some JavaScript objects for older browsers, but some features are still unavailable: -- Array.from -- Array.first -- Async functions -- Generators -- Array destructuring -- For..Of -- Symbol/Symbol.iterator -- Spread +* Array.from +* Array.first +* Async functions +* Generators +* Array destructuring +* For..Of +* Symbol/Symbol.iterator +* Spread Until these are polyfilled appropriately, they should not be used. Please update this list with additional unsupported features. @@ -295,11 +303,11 @@ Scenario: Developer can approve merge request [jasmine-focus]: https://jasmine.github.io/2.5/focused_specs.html [jasmine-jquery]: https://github.com/velesin/jasmine-jquery [karma]: http://karma-runner.github.io/ -[vue-test]:https://docs.gitlab.com/ce/development/fe_guide/vue.html#testing-vue-components -[RSpec]: https://github.com/rspec/rspec-rails#feature-specs -[Capybara]: https://github.com/teamcapybara/capybara -[Karma]: http://karma-runner.github.io/ -[Jasmine]: https://jasmine.github.io/ +[vue-test]: https://docs.gitlab.com/ce/development/fe_guide/vue.html#testing-vue-components +[rspec]: https://github.com/rspec/rspec-rails#feature-specs +[capybara]: https://github.com/teamcapybara/capybara +[karma]: http://karma-runner.github.io/ +[jasmine]: https://jasmine.github.io/ --- diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md index 0e29740b15f..0d592a6d43e 100644 --- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md +++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md @@ -251,13 +251,4 @@ It is possible to host LFS objects externally by setting a custom LFS url with ` Because GitLab verifies the existence of objects referenced by LFS pointers, push will fail when LFS is enabled for the project. -LFS can be disabled for a project by Owners and Masters using the [Project API](../../api/projects.md#edit-project). - -```bash -curl --request PUT \ - --url https://example.com/api/v4/projects/<PROJECT_ID> \ - --header 'Private-Token: <YOUR_PRIVATE_TOKEN>' \ - --data 'lfs_enabled=false' -``` - -Note, `<PROJECT_ID>` can also be substituted with a [namespaced path](../../api/README.md#namespaced-path-encoding). +LFS can be disabled from the [Project settings](../../user/project/settings/index.md). diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb index 7975f35ab1e..13c34e3473a 100644 --- a/lib/api/discussions.rb +++ b/lib/api/discussions.rb @@ -5,11 +5,12 @@ module API before { authenticate! } - NOTEABLE_TYPES = [Issue, Snippet].freeze + NOTEABLE_TYPES = [Issue, Snippet, MergeRequest, Commit].freeze NOTEABLE_TYPES.each do |noteable_type| parent_type = noteable_type.parent_class.to_s.underscore noteables_str = noteable_type.to_s.underscore.pluralize + noteables_path = noteable_type == Commit ? "repository/#{noteables_str}" : noteables_str params do requires :id, type: String, desc: "The ID of a #{parent_type}" @@ -19,14 +20,12 @@ module API success Entities::Discussion end params do - requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' use :pagination end - get ":id/#{noteables_str}/:noteable_id/discussions" do + get ":id/#{noteables_path}/:noteable_id/discussions" do noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) - break not_found!("Discussions") unless can?(current_user, noteable_read_ability_name(noteable), noteable) - notes = noteable.notes .inc_relations_for_view .includes(:noteable) @@ -43,13 +42,13 @@ module API end params do requires :discussion_id, type: String, desc: 'The ID of a discussion' - requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' end - get ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id" do + get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id" do noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) notes = readable_discussion_notes(noteable, params[:discussion_id]) - if notes.empty? || !can?(current_user, noteable_read_ability_name(noteable), noteable) + if notes.empty? break not_found!("Discussion") end @@ -62,19 +61,36 @@ module API success Entities::Discussion end params do - requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' requires :body, type: String, desc: 'The content of a note' optional :created_at, type: String, desc: 'The creation date of the note' + optional :position, type: Hash do + requires :base_sha, type: String, desc: 'Base commit SHA in the source branch' + requires :start_sha, type: String, desc: 'SHA referencing commit in target branch' + requires :head_sha, type: String, desc: 'SHA referencing HEAD of this merge request' + requires :position_type, type: String, desc: 'Type of the position reference', values: %w(text image) + optional :new_path, type: String, desc: 'File path after change' + optional :new_line, type: Integer, desc: 'Line number after change' + optional :old_path, type: String, desc: 'File path before change' + optional :old_line, type: Integer, desc: 'Line number before change' + optional :width, type: Integer, desc: 'Width of the image' + optional :height, type: Integer, desc: 'Height of the image' + optional :x, type: Integer, desc: 'X coordinate in the image' + optional :y, type: Integer, desc: 'Y coordinate in the image' + end end - post ":id/#{noteables_str}/:noteable_id/discussions" do + post ":id/#{noteables_path}/:noteable_id/discussions" do noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) + type = params[:position] ? 'DiffNote' : 'DiscussionNote' + id_key = noteable.is_a?(Commit) ? :commit_id : :noteable_id opts = { note: params[:body], created_at: params[:created_at], - type: 'DiscussionNote', + type: type, noteable_type: noteables_str.classify, - noteable_id: noteable.id + position: params[:position], + id_key => noteable.id } note = create_note(noteable, opts) @@ -91,13 +107,13 @@ module API end params do requires :discussion_id, type: String, desc: 'The ID of a discussion' - requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' end - get ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes" do + get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes" do noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) notes = readable_discussion_notes(noteable, params[:discussion_id]) - if notes.empty? || !can?(current_user, noteable_read_ability_name(noteable), noteable) + if notes.empty? break not_found!("Notes") end @@ -108,12 +124,12 @@ module API success Entities::Note end params do - requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' requires :discussion_id, type: String, desc: 'The ID of a discussion' requires :body, type: String, desc: 'The content of a note' optional :created_at, type: String, desc: 'The creation date of the note' end - post ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes" do + post ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes" do noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) notes = readable_discussion_notes(noteable, params[:discussion_id]) @@ -139,11 +155,11 @@ module API success Entities::Note end params do - requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' requires :discussion_id, type: String, desc: 'The ID of a discussion' requires :note_id, type: Integer, desc: 'The ID of a note' end - get ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes/:note_id" do + get ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes/:note_id" do noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) get_note(noteable, params[:note_id]) @@ -153,30 +169,52 @@ module API success Entities::Note end params do - requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' requires :discussion_id, type: String, desc: 'The ID of a discussion' requires :note_id, type: Integer, desc: 'The ID of a note' - requires :body, type: String, desc: 'The content of a note' + optional :body, type: String, desc: 'The content of a note' + optional :resolved, type: Boolean, desc: 'Mark note resolved/unresolved' + exactly_one_of :body, :resolved end - put ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes/:note_id" do + put ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes/:note_id" do noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) - update_note(noteable, params[:note_id]) + if params[:resolved].nil? + update_note(noteable, params[:note_id]) + else + resolve_note(noteable, params[:note_id], params[:resolved]) + end end desc "Delete a comment in a #{noteable_type.to_s.downcase} discussion" do success Entities::Note end params do - requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' requires :discussion_id, type: String, desc: 'The ID of a discussion' requires :note_id, type: Integer, desc: 'The ID of a note' end - delete ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes/:note_id" do + delete ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id/notes/:note_id" do noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) delete_note(noteable, params[:note_id]) end + + if Noteable::RESOLVABLE_TYPES.include?(noteable_type.to_s) + desc "Resolve/unresolve an existing #{noteable_type.to_s.downcase} discussion" do + success Entities::Discussion + end + params do + requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable' + requires :discussion_id, type: String, desc: 'The ID of a discussion' + requires :resolved, type: Boolean, desc: 'Mark discussion resolved/unresolved' + end + put ":id/#{noteables_path}/:noteable_id/discussions/:discussion_id" do + noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) + + resolve_discussion(noteable, params[:discussion_id], params[:resolved]) + end + end end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 8aad320e376..75d56b82424 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -286,6 +286,10 @@ module API end end + class DiffRefs < Grape::Entity + expose :base_sha, :head_sha, :start_sha + end + class Commit < Grape::Entity expose :id, :short_id, :title, :created_at expose :parent_ids @@ -601,6 +605,8 @@ module API merge_request.metrics&.pipeline end + expose :diff_refs, using: Entities::DiffRefs + def build_available?(options) options[:project]&.feature_available?(:builds, options[:current_user]) end @@ -642,6 +648,11 @@ module API expose :id, :key, :created_at end + class DiffPosition < Grape::Entity + expose :base_sha, :start_sha, :head_sha, :old_path, :new_path, + :position_type + end + class Note < Grape::Entity # Only Issue and MergeRequest have iid NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze @@ -655,6 +666,14 @@ module API expose :system?, as: :system expose :noteable_id, :noteable_type + expose :position, if: ->(note, options) { note.diff_note? } do |note| + note.position.to_h + end + + expose :resolvable?, as: :resolvable + expose :resolved?, as: :resolved, if: ->(note, options) { note.resolvable? } + expose :resolved_by, using: Entities::UserBasic, if: ->(note, options) { note.resolvable? } + # Avoid N+1 queries as much as possible expose(:noteable_iid) { |note| note.noteable.iid if NOTEABLE_TYPES_WITH_IID.include?(note.noteable_type) } end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index 4a4df1b8b9e..92e3d5cc10a 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -37,13 +37,11 @@ module API use :pagination end - def find_groups(params) - find_params = { - all_available: params[:all_available], - custom_attributes: params[:custom_attributes], - owned: params[:owned] - } - find_params[:parent] = find_group!(params[:id]) if params[:id] + def find_groups(params, parent_id = nil) + find_params = params.slice(:all_available, :custom_attributes, :owned) + find_params[:parent] = find_group!(parent_id) if parent_id + find_params[:all_available] = + find_params.fetch(:all_available, current_user&.full_private_access?) groups = GroupsFinder.new(current_user, find_params).execute groups = groups.search(params[:search]) if params[:search].present? @@ -85,7 +83,7 @@ module API use :with_custom_attributes end get do - groups = find_groups(params) + groups = find_groups(declared_params(include_missing: false), params[:id]) present_groups params, groups end @@ -213,7 +211,7 @@ module API use :with_custom_attributes end get ":id/subgroups" do - groups = find_groups(params) + groups = find_groups(declared_params(include_missing: false), params[:id]) present_groups params, groups end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index b8657cd7ee4..2ed331d4fd2 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -171,6 +171,10 @@ module API MergeRequestsFinder.new(current_user, project_id: user_project.id).find_by!(iid: iid) end + def find_project_commit(id) + user_project.commit_by(oid: id) + end + def find_project_snippet(id) finder_params = { project: user_project } SnippetsFinder.new(current_user, finder_params).find(id) diff --git a/lib/api/helpers/custom_attributes.rb b/lib/api/helpers/custom_attributes.rb index 70e4eda95f8..10d652e33f5 100644 --- a/lib/api/helpers/custom_attributes.rb +++ b/lib/api/helpers/custom_attributes.rb @@ -7,6 +7,9 @@ module API helpers do params :with_custom_attributes do optional :with_custom_attributes, type: Boolean, default: false, desc: 'Include custom attributes in the response' + + optional :custom_attributes, type: Hash, + desc: 'Filter with custom attributes' end def with_custom_attributes(collection_or_resource, options = {}) diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb index b74b8149834..b4bfb677d72 100644 --- a/lib/api/helpers/notes_helpers.rb +++ b/lib/api/helpers/notes_helpers.rb @@ -21,6 +21,23 @@ module API end end + def resolve_note(noteable, note_id, resolved) + note = noteable.notes.find(note_id) + + authorize! :resolve_note, note + + bad_request!("Note is not resolvable") unless note.resolvable? + + if resolved + parent = noteable_parent(noteable) + ::Notes::ResolveService.new(parent, current_user).execute(note) + else + note.unresolve! + end + + present note, with: Entities::Note + end + def delete_note(noteable, note_id) note = noteable.notes.find(note_id) @@ -35,7 +52,7 @@ module API def get_note(noteable, note_id) note = noteable.notes.with_metadata.find(params[:note_id]) - can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user) + can_read_note = !note.cross_reference_not_visible_for?(current_user) if can_read_note present note, with: Entities::Note @@ -49,7 +66,20 @@ module API end def find_noteable(parent, noteables_str, noteable_id) - public_send("find_#{parent}_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend + noteable = public_send("find_#{parent}_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend + + readable = + if noteable.is_a?(Commit) + # for commits there is not :read_commit policy, check if user + # has :read_note permission on the commit's project + can?(current_user, :read_note, user_project) + else + can?(current_user, noteable_read_ability_name(noteable), noteable) + end + + return not_found!(noteables_str) unless readable + + noteable end def noteable_parent(noteable) @@ -57,11 +87,8 @@ module API end def create_note(noteable, opts) - noteables_str = noteable.model_name.to_s.underscore.pluralize - - return not_found!(noteables_str) unless can?(current_user, noteable_read_ability_name(noteable), noteable) - - authorize! :create_note, noteable + policy_object = noteable.is_a?(Commit) ? user_project : noteable + authorize!(:create_note, policy_object) parent = noteable_parent(noteable) @@ -73,6 +100,21 @@ module API project = parent if parent.is_a?(Project) ::Notes::CreateService.new(project, current_user, opts).execute end + + def resolve_discussion(noteable, discussion_id, resolved) + discussion = noteable.find_discussion(discussion_id) + + forbidden! unless discussion.can_resolve?(current_user) + + if resolved + parent = noteable_parent(noteable) + ::Discussions::ResolveService.new(parent, current_user, merge_request: noteable).execute(discussion) + else + discussion.unresolve! + end + + present discussion, with: Entities::Discussion + end end end end diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 69f1df6b341..39923e6d5b5 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -31,23 +31,19 @@ module API get ":id/#{noteables_str}/:noteable_id/notes" do noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) - if can?(current_user, noteable_read_ability_name(noteable), noteable) - # We exclude notes that are cross-references and that cannot be viewed - # by the current user. By doing this exclusion at this level and not - # at the DB query level (which we cannot in that case), the current - # page can have less elements than :per_page even if - # there's more than one page. - raw_notes = noteable.notes.with_metadata.reorder(params[:order_by] => params[:sort]) - notes = - # paginate() only works with a relation. This could lead to a - # mismatch between the pagination headers info and the actual notes - # array returned, but this is really a edge-case. - paginate(raw_notes) - .reject { |n| n.cross_reference_not_visible_for?(current_user) } - present notes, with: Entities::Note - else - not_found!("Notes") - end + # We exclude notes that are cross-references and that cannot be viewed + # by the current user. By doing this exclusion at this level and not + # at the DB query level (which we cannot in that case), the current + # page can have less elements than :per_page even if + # there's more than one page. + raw_notes = noteable.notes.with_metadata.reorder(params[:order_by] => params[:sort]) + notes = + # paginate() only works with a relation. This could lead to a + # mismatch between the pagination headers info and the actual notes + # array returned, but this is really a edge-case. + paginate(raw_notes) + .reject { |n| n.cross_reference_not_visible_for?(current_user) } + present notes, with: Entities::Note end desc "Get a single #{noteable_type.to_s.downcase} note" do diff --git a/lib/banzai/filter/commit_trailers_filter.rb b/lib/banzai/filter/commit_trailers_filter.rb index ef16df1f3ae..7b55e8b36f6 100644 --- a/lib/banzai/filter/commit_trailers_filter.rb +++ b/lib/banzai/filter/commit_trailers_filter.rb @@ -13,7 +13,6 @@ module Banzai # * https://git.wiki.kernel.org/index.php/CommitMessageConventions class CommitTrailersFilter < HTML::Pipeline::Filter include ActionView::Helpers::TagHelper - include ApplicationHelper include AvatarsHelper TRAILER_REGEXP = /(?<label>[[:alpha:]-]+-by:)/i.freeze diff --git a/lib/gitlab/background_migration/migrate_stage_index.rb b/lib/gitlab/background_migration/migrate_stage_index.rb new file mode 100644 index 00000000000..f90f35a913d --- /dev/null +++ b/lib/gitlab/background_migration/migrate_stage_index.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true +# rubocop:disable Style/Documentation + +module Gitlab + module BackgroundMigration + class MigrateStageIndex + def perform(start_id, stop_id) + migrate_stage_index_sql(start_id.to_i, stop_id.to_i).tap do |sql| + ActiveRecord::Base.connection.execute(sql) + end + end + + private + + def migrate_stage_index_sql(start_id, stop_id) + if Gitlab::Database.postgresql? + <<~SQL + WITH freqs AS ( + SELECT stage_id, stage_idx, COUNT(*) AS freq FROM ci_builds + WHERE stage_id BETWEEN #{start_id} AND #{stop_id} + AND stage_idx IS NOT NULL + GROUP BY stage_id, stage_idx + ), indexes AS ( + SELECT DISTINCT stage_id, last_value(stage_idx) + OVER (PARTITION BY stage_id ORDER BY freq ASC) AS index + FROM freqs + ) + + UPDATE ci_stages SET position = indexes.index + FROM indexes WHERE indexes.stage_id = ci_stages.id + AND ci_stages.position IS NULL; + SQL + else + <<~SQL + UPDATE ci_stages + SET position = + (SELECT stage_idx FROM ci_builds + WHERE ci_builds.stage_id = ci_stages.id + GROUP BY ci_builds.stage_idx ORDER BY COUNT(*) DESC LIMIT 1) + WHERE ci_stages.id BETWEEN #{start_id} AND #{stop_id} + AND ci_stages.position IS NULL + SQL + end + end + end + end +end diff --git a/lib/gitlab/base_doorkeeper_controller.rb b/lib/gitlab/base_doorkeeper_controller.rb new file mode 100644 index 00000000000..e4227af25d2 --- /dev/null +++ b/lib/gitlab/base_doorkeeper_controller.rb @@ -0,0 +1,8 @@ +# This is a base controller for doorkeeper. +# It adds the `can?` helper used in the views. +module Gitlab + class BaseDoorkeeperController < ActionController::Base + include Gitlab::Allowable + helper_method :can? + end +end diff --git a/lib/gitlab/ci/pipeline/seed/stage.rb b/lib/gitlab/ci/pipeline/seed/stage.rb index c101f30d6e8..2b58d9863a0 100644 --- a/lib/gitlab/ci/pipeline/seed/stage.rb +++ b/lib/gitlab/ci/pipeline/seed/stage.rb @@ -19,6 +19,7 @@ module Gitlab def attributes { name: @attributes.fetch(:name), + position: @attributes.fetch(:index), pipeline: @pipeline, project: @pipeline.project } end diff --git a/lib/gitlab/diff/file_collection/base.rb b/lib/gitlab/diff/file_collection/base.rb index a6007ebf531..c79d8d3cb21 100644 --- a/lib/gitlab/diff/file_collection/base.rb +++ b/lib/gitlab/diff/file_collection/base.rb @@ -36,6 +36,8 @@ module Gitlab private def decorate_diff!(diff) + return diff if diff.is_a?(File) + Gitlab::Diff::File.new(diff, repository: project.repository, diff_refs: diff_refs, fallback_diff_refs: fallback_diff_refs) end end diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index 690b27cde81..978962ab2eb 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -12,6 +12,10 @@ module Gitlab :head_sha, :old_line, :new_line, + :width, + :height, + :x, + :y, :position_type, to: :formatter # A position can belong to a text line or to an image coordinate diff --git a/lib/gitlab/git/raw_diff_change.rb b/lib/gitlab/git/raw_diff_change.rb index eb3d8819239..92f6c45ce25 100644 --- a/lib/gitlab/git/raw_diff_change.rb +++ b/lib/gitlab/git/raw_diff_change.rb @@ -38,7 +38,9 @@ module Gitlab end def extract_operation - case @raw_operation&.first(1) + return :unknown unless @raw_operation + + case @raw_operation[0] when 'A' :added when 'C' diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 0ad2d385051..de0044fc149 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -391,18 +391,6 @@ module Gitlab nil end - def archive_prefix(ref, sha, append_sha:) - append_sha = (ref != sha) if append_sha.nil? - - project_name = self.name.chomp('.git') - formatted_ref = ref.tr('/', '-') - - prefix_segments = [project_name, formatted_ref] - prefix_segments << sha if append_sha - - prefix_segments.join('-') - end - def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:) ref ||= root_ref commit = Gitlab::Git::Commit.find(self, ref) @@ -413,12 +401,44 @@ module Gitlab { 'RepoPath' => path, 'ArchivePrefix' => prefix, - 'ArchivePath' => archive_file_path(prefix, storage_path, format), + 'ArchivePath' => archive_file_path(storage_path, commit.id, prefix, format), 'CommitId' => commit.id } end - def archive_file_path(name, storage_path, format = "tar.gz") + # This is both the filename of the archive (missing the extension) and the + # name of the top-level member of the archive under which all files go + # + # FIXME: The generated prefix is incorrect for projects with hashed + # storage enabled + def archive_prefix(ref, sha, append_sha:) + append_sha = (ref != sha) if append_sha.nil? + + project_name = self.name.chomp('.git') + formatted_ref = ref.tr('/', '-') + + prefix_segments = [project_name, formatted_ref] + prefix_segments << sha if append_sha + + prefix_segments.join('-') + end + private :archive_prefix + + # The full path on disk where the archive should be stored. This is used + # to cache the archive between requests. + # + # The path is a global namespace, so needs to be globally unique. This is + # achieved by including `gl_repository` in the path. + # + # Archives relating to a particular ref when the SHA is not present in the + # filename must be invalidated when the ref is updated to point to a new + # SHA. This is achieved by including the SHA in the path. + # + # As this is a full path on disk, it is not "cloud native". This should + # be resolved by either removing the cache, or moving the implementation + # into Gitaly and removing the ArchivePath parameter from the git-archive + # senddata response. + def archive_file_path(storage_path, sha, name, format = "tar.gz") # Build file path return nil unless name @@ -436,8 +456,9 @@ module Gitlab end file_name = "#{name}.#{extension}" - File.join(storage_path, self.name, file_name) + File.join(storage_path, self.gl_repository, sha, file_name) end + private :archive_file_path # Return repo size in megabytes def size diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index bf5a491e28d..498187997e1 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -142,7 +142,7 @@ module Gitlab :repository_service, :is_rebase_in_progress, request, - timeout: GitalyClient.default_timeout + timeout: GitalyClient.fast_timeout ) response.in_progress @@ -159,7 +159,7 @@ module Gitlab :repository_service, :is_squash_in_progress, request, - timeout: GitalyClient.default_timeout + timeout: GitalyClient.fast_timeout ) response.in_progress diff --git a/lib/gitlab/kubernetes/helm/base_command.rb b/lib/gitlab/kubernetes/helm/base_command.rb index 6e4df05aa7e..3d778da90c7 100644 --- a/lib/gitlab/kubernetes/helm/base_command.rb +++ b/lib/gitlab/kubernetes/helm/base_command.rb @@ -15,6 +15,9 @@ module Gitlab def generate_script <<~HEREDOC set -eo pipefail + ALPINE_VERSION=$(cat /etc/alpine-release | cut -d '.' -f 1,2) + echo http://mirror.clarkson.edu/alpine/v$ALPINE_VERSION/main >> /etc/apk/repositories + echo http://mirror1.hs-esslingen.de/pub/Mirrors/alpine/v$ALPINE_VERSION/main >> /etc/apk/repositories apk add -U ca-certificates openssl >/dev/null wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v#{Gitlab::Kubernetes::Helm::HELM_VERSION}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null mv /tmp/linux-amd64/helm /usr/bin/ diff --git a/package.json b/package.json index 8d0473069c3..e95add1d7e9 100644 --- a/package.json +++ b/package.json @@ -98,10 +98,11 @@ "devDependencies": { "axios-mock-adapter": "^1.10.0", "babel-eslint": "^8.0.2", - "babel-plugin-istanbul": "^4.1.5", + "babel-plugin-istanbul": "^4.1.6", "babel-plugin-rewire": "^1.1.0", "babel-template": "^6.26.0", "babel-types": "^6.26.0", + "chalk": "^2.4.1", "commander": "^2.15.1", "eslint": "^3.18.0", "eslint-config-airbnb-base": "^10.0.1", @@ -116,13 +117,13 @@ "istanbul": "^0.4.5", "jasmine-core": "^2.9.0", "jasmine-jquery": "^2.1.1", - "karma": "^2.0.0", + "karma": "^2.0.2", "karma-chrome-launcher": "^2.2.0", - "karma-coverage-istanbul-reporter": "^1.4.1", + "karma-coverage-istanbul-reporter": "^1.4.2", "karma-jasmine": "^1.1.1", "karma-mocha-reporter": "^2.2.5", "karma-sourcemap-loader": "^0.3.7", - "karma-webpack": "2.0.7", + "karma-webpack": "3.0.0", "nodemon": "^1.15.1", "prettier": "1.11.1", "webpack-dev-server": "^2.11.2" diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 565adac7499..1bc424335f8 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -47,7 +47,7 @@ GEM mini_portile2 (2.3.0) minitest (5.11.1) netrc (0.11.0) - nokogiri (1.8.1) + nokogiri (1.8.2) mini_portile2 (~> 2.3.0) pry (0.11.3) coderay (~> 1.1.0) diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb index 25309033571..ce61e6bf759 100644 --- a/spec/factories/ci/stages.rb +++ b/spec/factories/ci/stages.rb @@ -21,6 +21,7 @@ FactoryBot.define do pipeline factory: :ci_empty_pipeline name 'test' + position 1 status 'pending' end end diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb index ce5fbc343ee..53368c64e10 100644 --- a/spec/factories/commit_statuses.rb +++ b/spec/factories/commit_statuses.rb @@ -2,6 +2,7 @@ FactoryBot.define do factory :commit_status, class: CommitStatus do name 'default' stage 'test' + stage_idx 0 status 'success' description 'commit status' pipeline factory: :ci_pipeline_with_one_job diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb index a71020002dc..ed47f7ed390 100644 --- a/spec/features/dashboard/groups_list_spec.rb +++ b/spec/features/dashboard/groups_list_spec.rb @@ -40,7 +40,7 @@ feature 'Dashboard Groups page', :js do expect(page).to have_content(nested_group.name) end - describe 'when filtering groups', :nested_groups do + context 'when filtering groups', :nested_groups do before do group.add_owner(user) nested_group.add_owner(user) @@ -75,7 +75,7 @@ feature 'Dashboard Groups page', :js do end end - describe 'group with subgroups', :nested_groups do + context 'with subgroups', :nested_groups do let!(:subgroup) { create(:group, :public, parent: group) } before do @@ -106,7 +106,7 @@ feature 'Dashboard Groups page', :js do end end - describe 'when using pagination' do + context 'when using pagination' do let(:group) { create(:group, created_at: 5.days.ago) } let(:group2) { create(:group, created_at: 2.days.ago) } @@ -141,4 +141,20 @@ feature 'Dashboard Groups page', :js do expect(page).not_to have_selector("#group-#{group2.id}") end end + + context 'when signed in as admin' do + let(:admin) { create(:admin) } + + it 'shows only groups admin is member of' do + group.add_owner(admin) + expect(another_group).to be_persisted + + sign_in(admin) + visit dashboard_groups_path + wait_for_requests + + expect(page).to have_content(group.name) + expect(page).not_to have_content(another_group.name) + end + end end diff --git a/spec/features/projects/issues/user_toggles_subscription_spec.rb b/spec/features/projects/issues/user_toggles_subscription_spec.rb index 117a614b980..c2b2a193682 100644 --- a/spec/features/projects/issues/user_toggles_subscription_spec.rb +++ b/spec/features/projects/issues/user_toggles_subscription_spec.rb @@ -1,9 +1,9 @@ require "spec_helper" describe "User toggles subscription", :js do - set(:project) { create(:project_empty_repo, :public) } - set(:user) { create(:user) } - set(:issue) { create(:issue, project: project, author: user) } + let(:project) { create(:project_empty_repo, :public) } + let(:user) { create(:user) } + let(:issue) { create(:issue, project: project, author: user) } before do project.add_developer(user) @@ -12,7 +12,7 @@ describe "User toggles subscription", :js do visit(project_issue_path(project, issue)) end - it "unsibscribes from issue" do + it "unsubscribes from issue" do subscription_button = find(".js-issuable-subscribe-button") # Check we're subscribed. diff --git a/spec/features/projects/settings/lfs_settings_spec.rb b/spec/features/projects/settings/lfs_settings_spec.rb index 0fd28a5681c..342be1d2a9d 100644 --- a/spec/features/projects/settings/lfs_settings_spec.rb +++ b/spec/features/projects/settings/lfs_settings_spec.rb @@ -1,21 +1,27 @@ require 'rails_helper' describe 'Projects > Settings > LFS settings' do - let(:admin) { create(:admin) } let(:project) { create(:project) } + let(:user) { create(:user) } + let(:role) { :master } context 'LFS enabled setting' do before do allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) - sign_in(admin) + sign_in(user) + project.add_role(user, role) end - it 'displays the correct elements', :js do - visit edit_project_path(project) + context 'for master' do + let(:role) { :master } - expect(page).to have_content('Git Large File Storage') - expect(page).to have_selector('input[name="project[lfs_enabled]"] + button', visible: true) + it 'displays the correct elements', :js do + visit edit_project_path(project) + + expect(page).to have_content('Git Large File Storage') + expect(page).to have_selector('input[name="project[lfs_enabled]"] + button', visible: true) + end end end end diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb index abc470788e1..16c0d418d98 100644 --- a/spec/finders/groups_finder_spec.rb +++ b/spec/finders/groups_finder_spec.rb @@ -2,43 +2,71 @@ require 'spec_helper' describe GroupsFinder do describe '#execute' do - let(:user) { create(:user) } - - context 'root level groups' do - let!(:private_group) { create(:group, :private) } - let!(:internal_group) { create(:group, :internal) } - let!(:public_group) { create(:group, :public) } - - context 'without a user' do - subject { described_class.new.execute } - - it { is_expected.to eq([public_group]) } + let(:user) { create(:user) } + + describe 'root level groups' do + using RSpec::Parameterized::TableSyntax + + where(:user_type, :params, :results) do + nil | { all_available: true } | %i(public_group user_public_group) + nil | { all_available: false } | %i(public_group user_public_group) + nil | {} | %i(public_group user_public_group) + + :regular | { all_available: true } | %i(public_group internal_group user_public_group user_internal_group + user_private_group) + :regular | { all_available: false } | %i(user_public_group user_internal_group user_private_group) + :regular | {} | %i(public_group internal_group user_public_group user_internal_group user_private_group) + + :external | { all_available: true } | %i(public_group user_public_group user_internal_group user_private_group) + :external | { all_available: false } | %i(user_public_group user_internal_group user_private_group) + :external | {} | %i(public_group user_public_group user_internal_group user_private_group) + + :admin | { all_available: true } | %i(public_group internal_group private_group user_public_group + user_internal_group user_private_group) + :admin | { all_available: false } | %i(user_public_group user_internal_group user_private_group) + :admin | {} | %i(public_group internal_group private_group user_public_group user_internal_group + user_private_group) end - context 'with a user' do - subject { described_class.new(user).execute } - - context 'normal user' do - it { is_expected.to contain_exactly(public_group, internal_group) } - end - - context 'external user' do - let(:user) { create(:user, external: true) } - - it { is_expected.to contain_exactly(public_group) } + with_them do + before do + # Fixme: Because of an issue: https://github.com/tomykaira/rspec-parameterized/issues/8#issuecomment-381888428 + # The groups need to be created here, not with let syntax, and also compared by name and not ids + + @groups = { + private_group: create(:group, :private, name: 'private_group'), + internal_group: create(:group, :internal, name: 'internal_group'), + public_group: create(:group, :public, name: 'public_group'), + + user_private_group: create(:group, :private, name: 'user_private_group'), + user_internal_group: create(:group, :internal, name: 'user_internal_group'), + user_public_group: create(:group, :public, name: 'user_public_group') + } + + if user_type + user = + case user_type + when :regular + create(:user) + when :external + create(:user, external: true) + when :admin + create(:user, :admin) + end + @groups.values_at(:user_private_group, :user_internal_group, :user_public_group).each do |group| + group.add_developer(user) + end + end end - context 'user is member of the private group' do - before do - private_group.add_guest(user) - end + subject { described_class.new(User.last, params).execute.to_a } - it { is_expected.to contain_exactly(public_group, internal_group, private_group) } - end + it { is_expected.to match_array(@groups.values_at(*results)) } end end context 'subgroups', :nested_groups do + let(:user) { create(:user) } let!(:parent_group) { create(:group, :public) } let!(:public_subgroup) { create(:group, :public, parent: parent_group) } let!(:internal_subgroup) { create(:group, :internal, parent: parent_group) } diff --git a/spec/fixtures/api/schemas/public_api/v4/notes.json b/spec/fixtures/api/schemas/public_api/v4/notes.json index 4c4ca3b582f..0f9c221fd6d 100644 --- a/spec/fixtures/api/schemas/public_api/v4/notes.json +++ b/spec/fixtures/api/schemas/public_api/v4/notes.json @@ -24,7 +24,10 @@ "system": { "type": "boolean" }, "noteable_id": { "type": "integer" }, "noteable_iid": { "type": "integer" }, - "noteable_type": { "type": "string" } + "noteable_type": { "type": "string" }, + "resolved": { "type": "boolean" }, + "resolvable": { "type": "boolean" }, + "resolved_by": { "type": ["string", "null"] } }, "required": [ "id", "body", "attachment", "author", "created_at", "updated_at", diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 43cb0dfe163..5e454f8b310 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -2,8 +2,6 @@ require 'spec_helper' describe ApplicationHelper do - include UploadHelpers - describe 'current_controller?' do it 'returns true when controller matches argument' do stub_controller_name('foo') @@ -54,143 +52,6 @@ describe ApplicationHelper do end end - describe 'project_icon' do - it 'returns an url for the avatar' do - project = create(:project, :public, avatar: File.open(uploaded_image_temp_path)) - - expect(helper.project_icon(project.full_path).to_s) - .to eq "<img data-src=\"#{project.avatar.url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" - end - end - - describe 'avatar_icon_for' do - let!(:user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: 'bar@example.com') } - let(:email) { 'foo@example.com' } - let!(:another_user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: email) } - - it 'prefers the user to retrieve the avatar_url' do - expect(helper.avatar_icon_for(user, email).to_s) - .to eq(user.avatar.url) - end - - it 'falls back to email lookup if no user given' do - expect(helper.avatar_icon_for(nil, email).to_s) - .to eq(another_user.avatar.url) - end - end - - describe 'avatar_icon_for_email' do - let(:user) { create(:user, avatar: File.open(uploaded_image_temp_path)) } - - context 'using an email' do - context 'when there is a matching user' do - it 'returns a relative URL for the avatar' do - expect(helper.avatar_icon_for_email(user.email).to_s) - .to eq(user.avatar.url) - end - end - - context 'when no user exists for the email' do - it 'calls gravatar_icon' do - expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2) - - helper.avatar_icon_for_email('foo@example.com', 20, 2) - end - end - - context 'without an email passed' do - it 'calls gravatar_icon' do - expect(helper).to receive(:gravatar_icon).with(nil, 20, 2) - - helper.avatar_icon_for_email(nil, 20, 2) - end - end - end - end - - describe 'avatar_icon_for_user' do - let(:user) { create(:user, avatar: File.open(uploaded_image_temp_path)) } - - context 'with a user object passed' do - it 'returns a relative URL for the avatar' do - expect(helper.avatar_icon_for_user(user).to_s) - .to eq(user.avatar.url) - end - end - - context 'without a user object passed' do - it 'calls gravatar_icon' do - expect(helper).to receive(:gravatar_icon).with(nil, 20, 2) - - helper.avatar_icon_for_user(nil, 20, 2) - end - end - end - - describe 'gravatar_icon' do - let(:user_email) { 'user@email.com' } - - context 'with Gravatar disabled' do - before do - stub_application_setting(gravatar_enabled?: false) - end - - it 'returns a generic avatar' do - expect(helper.gravatar_icon(user_email)).to match_asset_path('no_avatar.png') - end - end - - context 'with Gravatar enabled' do - before do - stub_application_setting(gravatar_enabled?: true) - end - - it 'returns a generic avatar when email is blank' do - expect(helper.gravatar_icon('')).to match_asset_path('no_avatar.png') - end - - it 'returns a valid Gravatar URL' do - stub_config_setting(https: false) - - expect(helper.gravatar_icon(user_email)) - .to match('https://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118') - end - - it 'uses HTTPs when configured' do - stub_config_setting(https: true) - - expect(helper.gravatar_icon(user_email)) - .to match('https://secure.gravatar.com') - end - - it 'returns custom gravatar path when gravatar_url is set' do - stub_gravatar_setting(plain_url: 'http://example.local/?s=%{size}&hash=%{hash}') - - expect(gravatar_icon(user_email, 20)) - .to eq('http://example.local/?s=40&hash=b58c6f14d292556214bd64909bcdb118') - end - - it 'accepts a custom size argument' do - expect(helper.gravatar_icon(user_email, 64)).to include '?s=128' - end - - it 'defaults size to 40@2x when given an invalid size' do - expect(helper.gravatar_icon(user_email, nil)).to include '?s=80' - end - - it 'accepts a scaling factor' do - expect(helper.gravatar_icon(user_email, 40, 3)).to include '?s=120' - end - - it 'ignores case and surrounding whitespace' do - normal = helper.gravatar_icon('foo@example.com') - upcase = helper.gravatar_icon(' FOO@EXAMPLE.COM ') - - expect(normal).to eq upcase - end - end - end - describe 'simple_sanitize' do let(:a_tag) { '<a href="#">Foo</a>' } diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb index c94fedd615b..120b23e66ac 100644 --- a/spec/helpers/auth_helper_spec.rb +++ b/spec/helpers/auth_helper_spec.rb @@ -18,6 +18,30 @@ describe AuthHelper do end end + describe "providers_for_base_controller" do + it 'returns all enabled providers from devise' do + allow(helper).to receive(:auth_providers) { [:twitter, :github] } + expect(helper.providers_for_base_controller).to include(*[:twitter, :github]) + end + + it 'excludes ldap providers' do + allow(helper).to receive(:auth_providers) { [:twitter, :ldapmain] } + expect(helper.providers_for_base_controller).not_to include(:ldapmain) + end + end + + describe "form_based_providers" do + it 'includes LDAP providers' do + allow(helper).to receive(:auth_providers) { [:twitter, :ldapmain] } + expect(helper.form_based_providers).to eq %i(ldapmain) + end + + it 'includes crowd provider' do + allow(helper).to receive(:auth_providers) { [:twitter, :crowd] } + expect(helper.form_based_providers).to eq %i(crowd) + end + end + describe 'enabled_button_based_providers' do before do allow(helper).to receive(:auth_providers) { [:twitter, :github] } diff --git a/spec/helpers/avatars_helper_spec.rb b/spec/helpers/avatars_helper_spec.rb index 04c6d259135..5856bccb5b8 100644 --- a/spec/helpers/avatars_helper_spec.rb +++ b/spec/helpers/avatars_helper_spec.rb @@ -1,10 +1,147 @@ require 'rails_helper' describe AvatarsHelper do - include ApplicationHelper + include UploadHelpers let(:user) { create(:user) } + describe '#project_icon' do + it 'returns an url for the avatar' do + project = create(:project, :public, avatar: File.open(uploaded_image_temp_path)) + + expect(helper.project_icon(project.full_path).to_s) + .to eq "<img data-src=\"#{project.avatar.url}\" class=\" lazy\" src=\"#{LazyImageTagHelper.placeholder_image}\" />" + end + end + + describe '#avatar_icon_for' do + let!(:user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: 'bar@example.com') } + let(:email) { 'foo@example.com' } + let!(:another_user) { create(:user, avatar: File.open(uploaded_image_temp_path), email: email) } + + it 'prefers the user to retrieve the avatar_url' do + expect(helper.avatar_icon_for(user, email).to_s) + .to eq(user.avatar.url) + end + + it 'falls back to email lookup if no user given' do + expect(helper.avatar_icon_for(nil, email).to_s) + .to eq(another_user.avatar.url) + end + end + + describe '#avatar_icon_for_email' do + let(:user) { create(:user, avatar: File.open(uploaded_image_temp_path)) } + + context 'using an email' do + context 'when there is a matching user' do + it 'returns a relative URL for the avatar' do + expect(helper.avatar_icon_for_email(user.email).to_s) + .to eq(user.avatar.url) + end + end + + context 'when no user exists for the email' do + it 'calls gravatar_icon' do + expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2) + + helper.avatar_icon_for_email('foo@example.com', 20, 2) + end + end + + context 'without an email passed' do + it 'calls gravatar_icon' do + expect(helper).to receive(:gravatar_icon).with(nil, 20, 2) + + helper.avatar_icon_for_email(nil, 20, 2) + end + end + end + end + + describe '#avatar_icon_for_user' do + let(:user) { create(:user, avatar: File.open(uploaded_image_temp_path)) } + + context 'with a user object passed' do + it 'returns a relative URL for the avatar' do + expect(helper.avatar_icon_for_user(user).to_s) + .to eq(user.avatar.url) + end + end + + context 'without a user object passed' do + it 'calls gravatar_icon' do + expect(helper).to receive(:gravatar_icon).with(nil, 20, 2) + + helper.avatar_icon_for_user(nil, 20, 2) + end + end + end + + describe '#gravatar_icon' do + let(:user_email) { 'user@email.com' } + + context 'with Gravatar disabled' do + before do + stub_application_setting(gravatar_enabled?: false) + end + + it 'returns a generic avatar' do + expect(helper.gravatar_icon(user_email)).to match_asset_path('no_avatar.png') + end + end + + context 'with Gravatar enabled' do + before do + stub_application_setting(gravatar_enabled?: true) + end + + it 'returns a generic avatar when email is blank' do + expect(helper.gravatar_icon('')).to match_asset_path('no_avatar.png') + end + + it 'returns a valid Gravatar URL' do + stub_config_setting(https: false) + + expect(helper.gravatar_icon(user_email)) + .to match('https://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118') + end + + it 'uses HTTPs when configured' do + stub_config_setting(https: true) + + expect(helper.gravatar_icon(user_email)) + .to match('https://secure.gravatar.com') + end + + it 'returns custom gravatar path when gravatar_url is set' do + stub_gravatar_setting(plain_url: 'http://example.local/?s=%{size}&hash=%{hash}') + + expect(gravatar_icon(user_email, 20)) + .to eq('http://example.local/?s=40&hash=b58c6f14d292556214bd64909bcdb118') + end + + it 'accepts a custom size argument' do + expect(helper.gravatar_icon(user_email, 64)).to include '?s=128' + end + + it 'defaults size to 40@2x when given an invalid size' do + expect(helper.gravatar_icon(user_email, nil)).to include '?s=80' + end + + it 'accepts a scaling factor' do + expect(helper.gravatar_icon(user_email, 40, 3)).to include '?s=120' + end + + it 'ignores case and surrounding whitespace' do + normal = helper.gravatar_icon('foo@example.com') + upcase = helper.gravatar_icon(' FOO@EXAMPLE.COM ') + + expect(normal).to eq upcase + end + end + end + describe '#user_avatar' do subject { helper.user_avatar(user: user) } diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js index 8b6e8b24f00..fcd7bea3f6d 100644 --- a/spec/javascripts/sidebar/mock_data.js +++ b/spec/javascripts/sidebar/mock_data.js @@ -138,7 +138,7 @@ const RESPONSE_MAP = { }, { id: 20, - name_with_namespace: 'foo / bar', + name_with_namespace: '<img src=x onerror=alert(document.domain)> foo / bar', }, ], }, diff --git a/spec/javascripts/sidebar/sidebar_move_issue_spec.js b/spec/javascripts/sidebar/sidebar_move_issue_spec.js index a3fb965fbab..00847df4b60 100644 --- a/spec/javascripts/sidebar/sidebar_move_issue_spec.js +++ b/spec/javascripts/sidebar/sidebar_move_issue_spec.js @@ -69,6 +69,15 @@ describe('SidebarMoveIssue', function () { expect($.fn.glDropdown).toHaveBeenCalled(); }); + + it('escapes html from project name', (done) => { + this.$toggleButton.dropdown('toggle'); + + setTimeout(() => { + expect(this.$content.find('.js-move-issue-dropdown-item')[1].innerHTML.trim()).toEqual('<img src=x onerror=alert(document.domain)> foo / bar'); + done(); + }); + }); }); describe('onConfirmClicked', () => { diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js index bcd15f5eae2..2411d33a496 100644 --- a/spec/javascripts/test_bundle.js +++ b/spec/javascripts/test_bundle.js @@ -84,21 +84,11 @@ beforeEach(() => { const axiosDefaultAdapter = getDefaultAdapter(); -let testFiles = process.env.TEST_FILES || []; -if (testFiles.length > 0) { - testFiles = testFiles.map(path => path.replace(/^spec\/javascripts\//, '').replace(/\.js$/, '')); - console.log(`Running only tests matching: ${testFiles}`); -} else { - console.log('Running all tests'); -} - // render all of our tests const testsContext = require.context('.', true, /_spec$/); testsContext.keys().forEach(function(path) { try { - if (testFiles.length === 0 || testFiles.some(p => path.includes(p))) { - testsContext(path); - } + testsContext(path); } catch (err) { console.error('[ERROR] Unable to load spec: ', path); describe('Test bundle', function() { diff --git a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb index 1fd145116df..068cdc85a07 100644 --- a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb @@ -47,16 +47,36 @@ describe Banzai::Filter::CommitTrailersFilter do ) end - it 'non GitLab users and replaces them with mailto links' do - _, message_html = build_commit_message( - trailer: trailer, - name: FFaker::Name.name, - email: email - ) + context 'non GitLab users' do + shared_examples 'mailto links' do + it 'replaces them with mailto links' do + _, message_html = build_commit_message( + trailer: trailer, + name: FFaker::Name.name, + email: email + ) - doc = filter(message_html) + doc = filter(message_html) - expect_to_have_mailto_link(doc, email: email, trailer: trailer) + expect_to_have_mailto_link_with_avatar(doc, email: email, trailer: trailer) + end + end + + context 'when Gravatar is disabled' do + before do + stub_application_setting(gravatar_enabled: false) + end + + it_behaves_like 'mailto links' + end + + context 'when Gravatar is enabled' do + before do + stub_application_setting(gravatar_enabled: true) + end + + it_behaves_like 'mailto links' + end end it 'multiple trailers in the same message' do @@ -69,7 +89,7 @@ describe Banzai::Filter::CommitTrailersFilter do doc = filter(message) expect_to_have_user_link_with_avatar(doc, user: user, trailer: trailer) - expect_to_have_mailto_link(doc, email: email, trailer: different_trailer) + expect_to_have_mailto_link_with_avatar(doc, email: email, trailer: different_trailer) end context 'special names' do @@ -90,7 +110,7 @@ describe Banzai::Filter::CommitTrailersFilter do doc = filter(message_html) - expect_to_have_mailto_link(doc, email: email, trailer: trailer) + expect_to_have_mailto_link_with_avatar(doc, email: email, trailer: trailer) expect(doc.text).to match Regexp.escape(message) end end diff --git a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb new file mode 100644 index 00000000000..f8107dd40b9 --- /dev/null +++ b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe Gitlab::BackgroundMigration::MigrateStageIndex, :migration, schema: 20180420080616 do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:pipelines) { table(:ci_pipelines) } + let(:stages) { table(:ci_stages) } + let(:jobs) { table(:ci_builds) } + + before do + namespaces.create(id: 10, name: 'gitlab-org', path: 'gitlab-org') + projects.create!(id: 11, namespace_id: 10, name: 'gitlab', path: 'gitlab') + pipelines.create!(id: 12, project_id: 11, ref: 'master', sha: 'adf43c3a') + + stages.create(id: 100, project_id: 11, pipeline_id: 12, name: 'build') + stages.create(id: 101, project_id: 11, pipeline_id: 12, name: 'test') + + jobs.create!(id: 121, commit_id: 12, project_id: 11, + stage_idx: 2, stage_id: 100) + jobs.create!(id: 122, commit_id: 12, project_id: 11, + stage_idx: 2, stage_id: 100) + jobs.create!(id: 123, commit_id: 12, project_id: 11, + stage_idx: 10, stage_id: 100) + jobs.create!(id: 124, commit_id: 12, project_id: 11, + stage_idx: 3, stage_id: 101) + end + + it 'correctly migrates stages indices' do + expect(stages.all.pluck(:position)).to all(be_nil) + + described_class.new.perform(100, 101) + + expect(stages.all.pluck(:position)).to eq [2, 3] + end +end diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb index dc12ba076bc..0edc3f315bb 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb @@ -17,7 +17,7 @@ describe Gitlab::Ci::Pipeline::Chain::Create do context 'when pipeline is ready to be saved' do before do - pipeline.stages.build(name: 'test', project: project) + pipeline.stages.build(name: 'test', position: 0, project: project) step.perform! end diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb index eb1b285c7bd..05ce3412fd8 100644 --- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb @@ -24,7 +24,8 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do describe '#attributes' do it 'returns hash attributes of a stage' do expect(subject.attributes).to be_a Hash - expect(subject.attributes).to include(:name, :project) + expect(subject.attributes) + .to include(:name, :position, :pipeline, :project) end end diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index da1a6229ccf..9924641f829 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -234,59 +234,72 @@ describe Gitlab::Git::Repository, seed_helper: true do it_behaves_like 'wrapping gRPC errors', Gitlab::GitalyClient::RefService, :tag_names end - shared_examples 'archive check' do |extenstion| - it { expect(metadata['ArchivePath']).to match(%r{tmp/gitlab-git-test.git/gitlab-git-test-master-#{SeedRepo::LastCommit::ID}}) } - it { expect(metadata['ArchivePath']).to end_with extenstion } - end + describe '#archive_metadata' do + let(:storage_path) { '/tmp' } + let(:cache_key) { File.join(repository.gl_repository, SeedRepo::LastCommit::ID) } - describe '#archive_prefix' do - let(:project_name) { 'project-name'} + let(:append_sha) { true } + let(:ref) { 'master' } + let(:format) { nil } - before do - expect(repository).to receive(:name).once.and_return(project_name) - end + let(:expected_extension) { 'tar.gz' } + let(:expected_filename) { "#{expected_prefix}.#{expected_extension}" } + let(:expected_path) { File.join(storage_path, cache_key, expected_filename) } + let(:expected_prefix) { "gitlab-git-test-#{ref}-#{SeedRepo::LastCommit::ID}" } - it 'returns parameterised string for a ref containing slashes' do - prefix = repository.archive_prefix('test/branch', 'SHA', append_sha: nil) + subject(:metadata) { repository.archive_metadata(ref, storage_path, format, append_sha: append_sha) } - expect(prefix).to eq("#{project_name}-test-branch-SHA") + it 'sets RepoPath to the repository path' do + expect(metadata['RepoPath']).to eq(repository.path) end - it 'returns correct string for a ref containing dots' do - prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: nil) - - expect(prefix).to eq("#{project_name}-test.branch-SHA") + it 'sets CommitId to the commit SHA' do + expect(metadata['CommitId']).to eq(SeedRepo::LastCommit::ID) end - it 'returns string with sha when append_sha is false' do - prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: false) - - expect(prefix).to eq("#{project_name}-test.branch") + it 'sets ArchivePrefix to the expected prefix' do + expect(metadata['ArchivePrefix']).to eq(expected_prefix) end - end - describe '#archive' do - let(:metadata) { repository.archive_metadata('master', '/tmp', append_sha: true) } + it 'sets ArchivePath to the expected globally-unique path' do + # This is really important from a security perspective. Think carefully + # before changing it: https://gitlab.com/gitlab-org/gitlab-ce/issues/45689 + expect(expected_path).to include(File.join(repository.gl_repository, SeedRepo::LastCommit::ID)) - it_should_behave_like 'archive check', '.tar.gz' - end - - describe '#archive_zip' do - let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip', append_sha: true) } + expect(metadata['ArchivePath']).to eq(expected_path) + end - it_should_behave_like 'archive check', '.zip' - end + context 'append_sha varies archive path and filename' do + where(:append_sha, :ref, :expected_prefix) do + sha = SeedRepo::LastCommit::ID - describe '#archive_bz2' do - let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2', append_sha: true) } + true | 'master' | "gitlab-git-test-master-#{sha}" + true | sha | "gitlab-git-test-#{sha}-#{sha}" + false | 'master' | "gitlab-git-test-master" + false | sha | "gitlab-git-test-#{sha}" + nil | 'master' | "gitlab-git-test-master-#{sha}" + nil | sha | "gitlab-git-test-#{sha}" + end - it_should_behave_like 'archive check', '.tar.bz2' - end + with_them do + it { expect(metadata['ArchivePrefix']).to eq(expected_prefix) } + it { expect(metadata['ArchivePath']).to eq(expected_path) } + end + end - describe '#archive_fallback' do - let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup', append_sha: true) } + context 'format varies archive path and filename' do + where(:format, :expected_extension) do + nil | 'tar.gz' + 'madeup' | 'tar.gz' + 'tbz2' | 'tar.bz2' + 'zip' | 'zip' + end - it_should_behave_like 'archive check', '.tar.gz' + with_them do + it { expect(metadata['ArchivePrefix']).to eq(expected_prefix) } + it { expect(metadata['ArchivePath']).to eq(expected_path) } + end + end end describe '#size' do diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 31141807cb2..62da967cf96 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -232,6 +232,7 @@ Ci::Stage: - id - name - status +- position - lock_version - project_id - pipeline_id diff --git a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb index 3cfdae794f6..7be8be54d5e 100644 --- a/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/base_command_spec.rb @@ -4,22 +4,10 @@ describe Gitlab::Kubernetes::Helm::BaseCommand do let(:application) { create(:clusters_applications_helm) } let(:base_command) { described_class.new(application.name) } - describe '#generate_script' do - let(:helm_version) { Gitlab::Kubernetes::Helm::HELM_VERSION } - let(:command) do - <<~HEREDOC - set -eo pipefail - apk add -U ca-certificates openssl >/dev/null - wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v#{helm_version}-linux-amd64.tar.gz | tar zxC /tmp >/dev/null - mv /tmp/linux-amd64/helm /usr/bin/ - HEREDOC - end - - subject { base_command.generate_script } + subject { base_command } - it 'should return a command that prepares the environment for helm-cli' do - expect(subject).to eq(command) - end + it_behaves_like 'helm commands' do + let(:commands) { '' } end describe '#pod_resource' do diff --git a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb index e6920b0a76f..89e36a298f8 100644 --- a/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/init_command_spec.rb @@ -2,23 +2,9 @@ require 'spec_helper' describe Gitlab::Kubernetes::Helm::InitCommand do let(:application) { create(:clusters_applications_helm) } - let(:init_command) { described_class.new(application.name) } + let(:commands) { 'helm init >/dev/null' } - describe '#generate_script' do - let(:command) do - <<~MSG.chomp - set -eo pipefail - apk add -U ca-certificates openssl >/dev/null - wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null - mv /tmp/linux-amd64/helm /usr/bin/ - helm init >/dev/null - MSG - end + subject { described_class.new(application.name) } - subject { init_command.generate_script } - - it 'should return the appropriate command' do - is_expected.to eq(command) - end - end + it_behaves_like 'helm commands' end diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb index 137b8f718de..547f3f1752c 100644 --- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb +++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb @@ -12,50 +12,36 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do ) end - describe '#generate_script' do - let(:command) do - <<~MSG - set -eo pipefail - apk add -U ca-certificates openssl >/dev/null - wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null - mv /tmp/linux-amd64/helm /usr/bin/ - helm init --client-only >/dev/null - helm install #{application.chart} --name #{application.name} --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null - MSG - end - - subject { install_command.generate_script } + subject { install_command } - it 'should return appropriate command' do - is_expected.to eq(command) + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + helm init --client-only >/dev/null + helm install #{application.chart} --name #{application.name} --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null + EOS end + end - context 'with an application with a repository' do - let(:ci_runner) { create(:ci_runner) } - let(:application) { create(:clusters_applications_runner, runner: ci_runner) } - let(:install_command) do - described_class.new( - application.name, - chart: application.chart, - values: application.values, - repository: application.repository - ) - end - - let(:command) do - <<~MSG - set -eo pipefail - apk add -U ca-certificates openssl >/dev/null - wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null - mv /tmp/linux-amd64/helm /usr/bin/ - helm init --client-only >/dev/null - helm repo add #{application.name} #{application.repository} - helm install #{application.chart} --name #{application.name} --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null - MSG - end + context 'with an application with a repository' do + let(:ci_runner) { create(:ci_runner) } + let(:application) { create(:clusters_applications_runner, runner: ci_runner) } + let(:install_command) do + described_class.new( + application.name, + chart: application.chart, + values: application.values, + repository: application.repository + ) + end - it 'should return appropriate command' do - is_expected.to eq(command) + it_behaves_like 'helm commands' do + let(:commands) do + <<~EOS + helm init --client-only >/dev/null + helm repo add #{application.name} #{application.repository} + helm install #{application.chart} --name #{application.name} --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml >/dev/null + EOS end end end diff --git a/spec/migrations/schedule_stages_index_migration_spec.rb b/spec/migrations/schedule_stages_index_migration_spec.rb new file mode 100644 index 00000000000..710264da375 --- /dev/null +++ b/spec/migrations/schedule_stages_index_migration_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20180420080616_schedule_stages_index_migration') + +describe ScheduleStagesIndexMigration, :sidekiq, :migration do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:pipelines) { table(:ci_pipelines) } + let(:stages) { table(:ci_stages) } + + before do + stub_const("#{described_class}::BATCH_SIZE", 1) + + namespaces.create(id: 12, name: 'gitlab-org', path: 'gitlab-org') + projects.create!(id: 123, namespace_id: 12, name: 'gitlab', path: 'gitlab') + pipelines.create!(id: 1, project_id: 123, ref: 'master', sha: 'adf43c3a') + stages.create!(id: 121, project_id: 123, pipeline_id: 1, name: 'build') + stages.create!(id: 122, project_id: 123, pipeline_id: 1, name: 'test') + stages.create!(id: 123, project_id: 123, pipeline_id: 1, name: 'deploy') + end + + it 'schedules delayed background migrations in batches' do + Sidekiq::Testing.fake! do + Timecop.freeze do + expect(stages.all).to all(have_attributes(position: be_nil)) + + migrate! + + expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, 121, 121) + expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, 122, 122) + expect(described_class::MIGRATION).to be_scheduled_delayed_migration(15.minutes, 123, 123) + expect(BackgroundMigrationWorker.jobs.size).to eq 3 + end + end + end +end diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 586d073eb5e..a00db1d2bfc 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -51,7 +51,7 @@ describe Ci::Stage, :models do end end - describe 'update_status' do + describe '#update_status' do context 'when stage objects needs to be updated' do before do create(:ci_build, :success, stage_id: stage.id) @@ -87,4 +87,36 @@ describe Ci::Stage, :models do end end end + + describe '#index' do + context 'when stage has been imported and does not have position index set' do + before do + stage.update_column(:position, nil) + end + + context 'when stage has statuses' do + before do + create(:ci_build, :running, stage_id: stage.id, stage_idx: 10) + end + + it 'recalculates index before updating status' do + expect(stage.reload.position).to be_nil + + stage.update_status + + expect(stage.reload.position).to eq 10 + end + end + + context 'when stage does not have statuses' do + it 'fallbacks to zero' do + expect(stage.reload.position).to be_nil + + stage.update_status + + expect(stage.reload.position).to eq 0 + end + end + end + end end diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 2705421e540..fb51c0172ab 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -85,12 +85,35 @@ describe DiffNote do end describe "#diff_file" do - it "returns the correct diff file" do - diff_file = subject.diff_file + context 'when the discussion was created in the diff' do + it 'returns correct diff file' do + diff_file = subject.diff_file - expect(diff_file.old_path).to eq(position.old_path) - expect(diff_file.new_path).to eq(position.new_path) - expect(diff_file.diff_refs).to eq(position.diff_refs) + expect(diff_file.old_path).to eq(position.old_path) + expect(diff_file.new_path).to eq(position.new_path) + expect(diff_file.diff_refs).to eq(position.diff_refs) + end + end + + context 'when discussion is outdated or not created in the diff' do + let(:diff_refs) { project.commit(sample_commit.id).diff_refs } + let(:position) do + Gitlab::Diff::Position.new( + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + diff_refs: diff_refs + ) + end + + it 'returns the correct diff file' do + diff_file = subject.diff_file + + expect(diff_file.old_path).to eq(position.old_path) + expect(diff_file.new_path).to eq(position.new_path) + expect(diff_file.diff_refs).to eq(position.diff_refs) + end end end diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb index 4a44b219a67..ef34192f888 100644 --- a/spec/requests/api/discussions_spec.rb +++ b/spec/requests/api/discussions_spec.rb @@ -2,32 +2,53 @@ require 'spec_helper' describe API::Discussions do let(:user) { create(:user) } - let!(:project) { create(:project, :public, namespace: user.namespace) } + let!(:project) { create(:project, :public, :repository, namespace: user.namespace) } let(:private_user) { create(:user) } before do - project.add_reporter(user) + project.add_developer(user) end - context "when noteable is an Issue" do + context 'when noteable is an Issue' do let!(:issue) { create(:issue, project: project, author: user) } let!(:issue_note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: user) } - it_behaves_like "discussions API", 'projects', 'issues', 'iid' do + it_behaves_like 'discussions API', 'projects', 'issues', 'iid' do let(:parent) { project } let(:noteable) { issue } let(:note) { issue_note } end end - context "when noteable is a Snippet" do + context 'when noteable is a Snippet' do let!(:snippet) { create(:project_snippet, project: project, author: user) } let!(:snippet_note) { create(:discussion_note_on_snippet, noteable: snippet, project: project, author: user) } - it_behaves_like "discussions API", 'projects', 'snippets', 'id' do + it_behaves_like 'discussions API', 'projects', 'snippets', 'id' do let(:parent) { project } let(:noteable) { snippet } let(:note) { snippet_note } end end + + context 'when noteable is a Merge Request' do + let!(:noteable) { create(:merge_request_with_diffs, source_project: project, target_project: project, author: user) } + let!(:note) { create(:discussion_note_on_merge_request, noteable: noteable, project: project, author: user) } + let!(:diff_note) { create(:diff_note_on_merge_request, noteable: noteable, project: project, author: user) } + let(:parent) { project } + + it_behaves_like 'discussions API', 'projects', 'merge_requests', 'iid' + it_behaves_like 'diff discussions API', 'projects', 'merge_requests', 'iid' + it_behaves_like 'resolvable discussions API', 'projects', 'merge_requests', 'iid' + end + + context 'when noteable is a Commit' do + let!(:noteable) { create(:commit, project: project, author: user) } + let!(:note) { create(:discussion_note_on_commit, commit_id: noteable.id, project: project, author: user) } + let!(:diff_note) { create(:diff_note_on_commit, commit_id: noteable.id, project: project, author: user) } + let(:parent) { project } + + it_behaves_like 'discussions API', 'projects', 'repository/commits', 'id' + it_behaves_like 'diff discussions API', 'projects', 'repository/commits', 'id' + end end diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb index 6bed8e812c0..cd1a6cfc427 100644 --- a/spec/requests/openid_connect_spec.rb +++ b/spec/requests/openid_connect_spec.rb @@ -153,4 +153,13 @@ describe 'OpenID Connect requests' do end end end + + context 'OpenID configuration information' do + it 'correctly returns the configuration' do + get '/.well-known/openid-configuration' + + expect(response).to have_gitlab_http_status(200) + expect(json_response).to have_key('issuer') + end + end end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 8de0bdf92e2..5bc6031388e 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -6,7 +6,9 @@ describe Ci::RetryBuildService do set(:pipeline) { create(:ci_pipeline, project: project) } let(:stage) do - Ci::Stage.create!(project: project, pipeline: pipeline, name: 'test') + create(:ci_stage_entity, project: project, + pipeline: pipeline, + name: 'test') end let(:build) { create(:ci_build, pipeline: pipeline, stage_id: stage.id) } diff --git a/spec/services/notes/resolve_service_spec.rb b/spec/services/notes/resolve_service_spec.rb new file mode 100644 index 00000000000..b54d40a7a5c --- /dev/null +++ b/spec/services/notes/resolve_service_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Notes::ResolveService do + let(:merge_request) { create(:merge_request) } + let(:note) { create(:diff_note_on_merge_request, noteable: merge_request, project: merge_request.project) } + let(:user) { merge_request.author } + + describe '#execute' do + it "resolves the note" do + described_class.new(merge_request.project, user).execute(note) + note.reload + + expect(note.resolved?).to be true + expect(note.resolved_by).to eq(user) + end + + it "sends notifications if all discussions are resolved" do + expect_any_instance_of(MergeRequests::ResolvedDiscussionNotificationService).to receive(:execute).with(merge_request) + + described_class.new(merge_request.project, user).execute(note) + end + end +end diff --git a/spec/services/repository_archive_clean_up_service_spec.rb b/spec/services/repository_archive_clean_up_service_spec.rb index 2d7fa3f80f7..ab1c638fc39 100644 --- a/spec/services/repository_archive_clean_up_service_spec.rb +++ b/spec/services/repository_archive_clean_up_service_spec.rb @@ -1,15 +1,47 @@ require 'spec_helper' describe RepositoryArchiveCleanUpService do - describe '#execute' do - subject(:service) { described_class.new } + subject(:service) { described_class.new } + describe '#execute (new archive locations)' do + let(:sha) { "0" * 40 } + + it 'removes outdated archives and directories in a new-style path' do + in_directory_with_files("project-999/#{sha}", %w[tar tar.bz2 tar.gz zip], 3.hours) do |dirname, files| + service.execute + + files.each { |filename| expect(File.exist?(filename)).to be_falsy } + expect(File.directory?(dirname)).to be_falsy + expect(File.directory?(File.dirname(dirname))).to be_falsy + end + end + + it 'does not remove directories when they contain outdated non-archives' do + in_directory_with_files("project-999/#{sha}", %w[tar conf rb], 2.hours) do |dirname, files| + service.execute + + expect(File.directory?(dirname)).to be_truthy + end + end + + it 'does not remove in-date archives in a new-style path' do + in_directory_with_files("project-999/#{sha}", %w[tar tar.bz2 tar.gz zip], 1.hour) do |dirname, files| + service.execute + + files.each { |filename| expect(File.exist?(filename)).to be_truthy } + end + end + end + + describe '#execute (legacy archive locations)' do context 'when the downloads directory does not exist' do it 'does not remove any archives' do path = '/invalid/path/' stub_repository_downloads_path(path) + allow(File).to receive(:directory?).and_call_original expect(File).to receive(:directory?).with(path).and_return(false) + expect(service).not_to receive(:clean_up_old_archives) expect(service).not_to receive(:clean_up_empty_directories) @@ -19,7 +51,7 @@ describe RepositoryArchiveCleanUpService do context 'when the downloads directory exists' do shared_examples 'invalid archive files' do |dirname, extensions, mtime| - it 'does not remove files and directoy' do + it 'does not remove files and directory' do in_directory_with_files(dirname, extensions, mtime) do |dir, files| service.execute @@ -43,7 +75,7 @@ describe RepositoryArchiveCleanUpService do end context 'with files older than 2 hours inside invalid directories' do - it_behaves_like 'invalid archive files', 'john_doe/sample.git', %w[conf rb tar tar.gz], 2.hours + it_behaves_like 'invalid archive files', 'john/doe/sample.git', %w[conf rb tar tar.gz], 2.hours end context 'with files newer than 2 hours that matches valid archive extensions' do @@ -58,24 +90,24 @@ describe RepositoryArchiveCleanUpService do it_behaves_like 'invalid archive files', 'sample.git', %w[conf rb tar tar.gz], 1.hour end end + end - def in_directory_with_files(dirname, extensions, mtime) - Dir.mktmpdir do |tmpdir| - stub_repository_downloads_path(tmpdir) - dir = File.join(tmpdir, dirname) - files = create_temporary_files(dir, extensions, mtime) + def in_directory_with_files(dirname, extensions, mtime) + Dir.mktmpdir do |tmpdir| + stub_repository_downloads_path(tmpdir) + dir = File.join(tmpdir, dirname) + files = create_temporary_files(dir, extensions, mtime) - yield(dir, files) - end + yield(dir, files) end + end - def stub_repository_downloads_path(path) - allow(Gitlab.config.gitlab).to receive(:repository_downloads_path).and_return(path) - end + def stub_repository_downloads_path(path) + allow(Gitlab.config.gitlab).to receive(:repository_downloads_path).and_return(path) + end - def create_temporary_files(dir, extensions, mtime) - FileUtils.mkdir_p(dir) - FileUtils.touch(extensions.map { |ext| File.join(dir, "sample.#{ext}") }, mtime: Time.now - mtime) - end + def create_temporary_files(dir, extensions, mtime) + FileUtils.mkdir_p(dir) + FileUtils.touch(extensions.map { |ext| File.join(dir, "sample.#{ext}") }, mtime: Time.now - mtime) end end diff --git a/spec/support/commit_trailers_spec_helper.rb b/spec/support/commit_trailers_spec_helper.rb index add359946db..efa317fd2f9 100644 --- a/spec/support/commit_trailers_spec_helper.rb +++ b/spec/support/commit_trailers_spec_helper.rb @@ -8,7 +8,7 @@ module CommitTrailersSpecHelper expect(wrapper.attribute('data-user').value).to eq user.id.to_s end - def expect_to_have_mailto_link(doc, email:, trailer:) + def expect_to_have_mailto_link_with_avatar(doc, email:, trailer:) wrapper = find_user_wrapper(doc, trailer) expect_to_have_links_with_url_and_avatar(wrapper, "mailto:#{CGI.escape_html(email)}", email) diff --git a/spec/support/shared_examples/helm_generated_script.rb b/spec/support/shared_examples/helm_generated_script.rb new file mode 100644 index 00000000000..56e86a87ab9 --- /dev/null +++ b/spec/support/shared_examples/helm_generated_script.rb @@ -0,0 +1,19 @@ +shared_examples 'helm commands' do + describe '#generate_script' do + let(:helm_setup) do + <<~EOS + set -eo pipefail + ALPINE_VERSION=$(cat /etc/alpine-release | cut -d '.' -f 1,2) + echo http://mirror.clarkson.edu/alpine/v$ALPINE_VERSION/main >> /etc/apk/repositories + echo http://mirror1.hs-esslingen.de/pub/Mirrors/alpine/v$ALPINE_VERSION/main >> /etc/apk/repositories + apk add -U ca-certificates openssl >/dev/null + wget -q -O - https://kubernetes-helm.storage.googleapis.com/helm-v2.7.0-linux-amd64.tar.gz | tar zxC /tmp >/dev/null + mv /tmp/linux-amd64/helm /usr/bin/ + EOS + end + + it 'should return appropriate command' do + expect(subject.generate_script).to eq(helm_setup + commands) + end + end +end diff --git a/spec/support/shared_examples/requests/api/diff_discussions.rb b/spec/support/shared_examples/requests/api/diff_discussions.rb new file mode 100644 index 00000000000..85a4bd8ca27 --- /dev/null +++ b/spec/support/shared_examples/requests/api/diff_discussions.rb @@ -0,0 +1,57 @@ +shared_examples 'diff discussions API' do |parent_type, noteable_type, id_name| + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do + it "includes diff discussions" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user) + + discussion = json_response.find { |record| record['id'] == diff_note.discussion_id } + + expect(response).to have_gitlab_http_status(200) + expect(discussion).not_to be_nil + expect(discussion['individual_note']).to eq(false) + expect(discussion['notes'].first['body']).to eq(diff_note.note) + end + end + + describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do + it "returns a discussion by id" do + get api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions/#{diff_note.discussion_id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['id']).to eq(diff_note.discussion_id) + expect(json_response['notes'].first['body']).to eq(diff_note.note) + expect(json_response['notes'].first['position']).to eq(diff_note.position.to_h.stringify_keys) + end + end + + describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do + it "creates a new diff note" do + position = diff_note.position.to_h + + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), body: 'hi!', position: position + + expect(response).to have_gitlab_http_status(201) + expect(json_response['notes'].first['body']).to eq('hi!') + expect(json_response['notes'].first['type']).to eq('DiffNote') + expect(json_response['notes'].first['position']).to eq(position.stringify_keys) + end + + it "returns a 400 bad request error when position is invalid" do + position = diff_note.position.to_h.merge(new_line: '100000') + + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/discussions", user), body: 'hi!', position: position + + expect(response).to have_gitlab_http_status(400) + end + end + + describe "POST /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes" do + it 'adds a new note to the diff discussion' do + post api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{diff_note.discussion_id}/notes", user), body: 'hi!' + + expect(response).to have_gitlab_http_status(201) + expect(json_response['body']).to eq('hi!') + expect(json_response['type']).to eq('DiffNote') + end + end +end diff --git a/spec/support/shared_examples/requests/api/resolvable_discussions.rb b/spec/support/shared_examples/requests/api/resolvable_discussions.rb new file mode 100644 index 00000000000..408ad08cc48 --- /dev/null +++ b/spec/support/shared_examples/requests/api/resolvable_discussions.rb @@ -0,0 +1,87 @@ +shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_name| + describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do + it "resolves discussion if resolved is true" do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}", user), resolved: true + + expect(response).to have_gitlab_http_status(200) + expect(json_response['notes'].size).to eq(1) + expect(json_response['notes'][0]['resolved']).to eq(true) + end + + it "unresolves discussion if resolved is false" do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}", user), resolved: false + + expect(response).to have_gitlab_http_status(200) + expect(json_response['notes'].size).to eq(1) + expect(json_response['notes'][0]['resolved']).to eq(false) + end + + it "returns a 400 bad request error if resolved parameter is not passed" do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}", user) + + expect(response).to have_gitlab_http_status(400) + end + + it "returns a 401 unauthorized error if user is not authenticated" do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}"), resolved: true + + expect(response).to have_gitlab_http_status(401) + end + + it "returns a 403 error if user resolves discussion of someone else" do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}", private_user), resolved: true + + expect(response).to have_gitlab_http_status(403) + end + + context 'when user does not have access to read the discussion' do + before do + parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + end + + it 'responds with 404' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}", private_user), resolved: true + + expect(response).to have_gitlab_http_status(404) + end + end + end + + describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id/notes/:note_id" do + it 'returns resolved note when resolved parameter is true' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user), resolved: true + + expect(response).to have_gitlab_http_status(200) + expect(json_response['resolved']).to eq(true) + end + + it 'returns a 404 error when note id not found' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/12345", user), + body: 'Hello!' + + expect(response).to have_gitlab_http_status(404) + end + + it 'returns a 400 bad request error if neither body nor resolved parameter is given' do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", user) + + expect(response).to have_gitlab_http_status(400) + end + + it "returns a 403 error if user resolves note of someone else" do + put api("/#{parent_type}/#{parent.id}/#{noteable_type}/#{noteable[id_name]}/"\ + "discussions/#{note.discussion_id}/notes/#{note.id}", private_user), resolved: true + + expect(response).to have_gitlab_http_status(403) + end + end +end diff --git a/yarn.lock b/yarn.lock index 395e2cfb0ed..1e6ffa5f524 100644 --- a/yarn.lock +++ b/yarn.lock @@ -66,13 +66,6 @@ version "2.0.48" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-2.0.48.tgz#3e90d8cde2d29015e5583017f7830cb3975b2eef" -JSONStream@^1.0.3: - version "1.3.2" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.2.tgz#c102371b6ec3a7cf3b847ca00c20bb0fce4c6dea" - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -100,13 +93,6 @@ acorn-jsx@^3.0.0: dependencies: acorn "^3.0.4" -acorn-node@^1.2.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.3.0.tgz#5f86d73346743810ef1269b901dbcbded020861b" - dependencies: - acorn "^5.4.1" - xtend "^4.0.1" - acorn@^3.0.4: version "3.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" @@ -115,7 +101,7 @@ acorn@^4.0.3: version "4.0.13" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" -acorn@^5.0.0, acorn@^5.2.1, acorn@^5.3.0, acorn@^5.4.1: +acorn@^5.0.0, acorn@^5.2.1, acorn@^5.3.0: version "5.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102" @@ -226,12 +212,6 @@ ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" -ansi-styles@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.0.tgz#c159b8d5be0f9e5a6f346dab94f16ce022161b88" - dependencies: - color-convert "^1.9.0" - ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -378,7 +358,7 @@ assert-plus@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" -assert@^1.1.1, assert@^1.4.0: +assert@^1.1.1: version "1.4.1" resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" dependencies: @@ -392,12 +372,6 @@ ast-types@0.x.x: version "0.11.1" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.11.1.tgz#5bb3a8d5ba292c3f4ae94d46df37afc30300b990" -astw@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/astw/-/astw-2.2.0.tgz#7bd41784d32493987aeb239b6b4e1c57a873b917" - dependencies: - acorn "^4.0.3" - async-each@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" @@ -410,16 +384,12 @@ async@1.x, async@^1.4.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.1.2, async@^2.1.4, async@^2.4.1: +async@^2.0.0, async@^2.1.2, async@^2.1.4, async@^2.4.1: version "2.6.0" resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" dependencies: lodash "^4.14.0" -async@~0.9.0: - version "0.9.2" - resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" - async@~2.1.2: version "2.1.5" resolved "https://registry.yarnpkg.com/async/-/async-2.1.5.tgz#e587c68580994ac67fc56ff86d3ac56bdbe810bc" @@ -672,13 +642,14 @@ babel-plugin-check-es2015-constants@^6.22.0: dependencies: babel-runtime "^6.22.0" -babel-plugin-istanbul@^4.1.5: - version "4.1.5" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.5.tgz#6760cdd977f411d3e175bb064f2bc327d99b2b6e" +babel-plugin-istanbul@^4.1.6: + version "4.1.6" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz#36c59b2192efce81c5b378321b74175add1c9a45" dependencies: + babel-plugin-syntax-object-rest-spread "^6.13.0" find-up "^2.1.0" - istanbul-lib-instrument "^1.7.5" - test-exclude "^4.1.1" + istanbul-lib-instrument "^1.10.1" + test-exclude "^4.2.1" babel-plugin-rewire@^1.1.0: version "1.1.0" @@ -708,7 +679,7 @@ babel-plugin-syntax-exponentiation-operator@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" -babel-plugin-syntax-object-rest-spread@^6.8.0: +babel-plugin-syntax-object-rest-spread@^6.13.0, babel-plugin-syntax-object-rest-spread@^6.8.0: version "6.13.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" @@ -1035,7 +1006,7 @@ babel-register@^6.26.0: mkdirp "^0.5.1" source-map-support "^0.4.15" -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: +babel-runtime@^6.0.0, babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: @@ -1294,23 +1265,6 @@ brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" -browser-pack@^6.0.1: - version "6.0.4" - resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.0.4.tgz#9a73beb3b48f9e36868be007b64400102c04a99f" - dependencies: - JSONStream "^1.0.3" - combine-source-map "~0.8.0" - defined "^1.0.0" - safe-buffer "^5.1.1" - through2 "^2.0.0" - umd "^3.0.0" - -browser-resolve@^1.11.0, browser-resolve@^1.7.0: - version "1.11.2" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" - dependencies: - resolve "1.1.7" - browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.1.1" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.1.1.tgz#38b7ab55edb806ff2dcda1a7f1620773a477c49f" @@ -1363,64 +1317,6 @@ browserify-zlib@^0.1.4: dependencies: pako "~0.2.0" -browserify-zlib@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - dependencies: - pako "~1.0.5" - -browserify@^14.5.0: - version "14.5.0" - resolved "https://registry.yarnpkg.com/browserify/-/browserify-14.5.0.tgz#0bbbce521acd6e4d1d54d8e9365008efb85a9cc5" - dependencies: - JSONStream "^1.0.3" - assert "^1.4.0" - browser-pack "^6.0.1" - browser-resolve "^1.11.0" - browserify-zlib "~0.2.0" - buffer "^5.0.2" - cached-path-relative "^1.0.0" - concat-stream "~1.5.1" - console-browserify "^1.1.0" - constants-browserify "~1.0.0" - crypto-browserify "^3.0.0" - defined "^1.0.0" - deps-sort "^2.0.0" - domain-browser "~1.1.0" - duplexer2 "~0.1.2" - events "~1.1.0" - glob "^7.1.0" - has "^1.0.0" - htmlescape "^1.1.0" - https-browserify "^1.0.0" - inherits "~2.0.1" - insert-module-globals "^7.0.0" - labeled-stream-splicer "^2.0.0" - module-deps "^4.0.8" - os-browserify "~0.3.0" - parents "^1.0.1" - path-browserify "~0.0.0" - process "~0.11.0" - punycode "^1.3.2" - querystring-es3 "~0.2.0" - read-only-stream "^2.0.0" - readable-stream "^2.0.2" - resolve "^1.1.4" - shasum "^1.0.0" - shell-quote "^1.6.1" - stream-browserify "^2.0.0" - stream-http "^2.0.0" - string_decoder "~1.0.0" - subarg "^1.0.0" - syntax-error "^1.1.1" - through2 "^2.0.0" - timers-browserify "^1.0.1" - tty-browserify "~0.0.0" - url "~0.11.0" - util "~0.10.1" - vm-browserify "~0.0.1" - xtend "^4.0.0" - browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: version "1.7.7" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" @@ -1448,13 +1344,6 @@ buffer@^4.3.0: ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.1.0.tgz#c913e43678c7cb7c8bd16afbcddb6c5505e8f9fe" - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - buildmail@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/buildmail/-/buildmail-4.0.1.tgz#877f7738b78729871c9a105e3b837d2be11a7a72" @@ -1527,10 +1416,6 @@ cacheable-request@^2.1.1: normalize-url "2.0.1" responselike "1.0.2" -cached-path-relative@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.1.tgz#d09c4b52800aa4c078e2dd81a869aac90d2e54e7" - caller-path@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" @@ -1610,17 +1495,9 @@ chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796" - dependencies: - ansi-styles "^3.2.0" - escape-string-regexp "^1.0.5" - supports-color "^5.2.0" - -chalk@^2.3.2: - version "2.4.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.0.tgz#a060a297a6b57e15b61ca63ce84995daa0fe6e52" +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2, chalk@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" @@ -1830,24 +1707,6 @@ combine-lists@^1.0.0: dependencies: lodash "^4.5.0" -combine-source-map@~0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.7.2.tgz#0870312856b307a87cc4ac486f3a9a62aeccc09e" - dependencies: - convert-source-map "~1.1.0" - inline-source-map "~0.6.0" - lodash.memoize "~3.0.3" - source-map "~0.5.3" - -combine-source-map@~0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.8.0.tgz#a58d0df042c186fcf822a8e8015f5450d2d79a8b" - dependencies: - convert-source-map "~1.1.0" - inline-source-map "~0.6.0" - lodash.memoize "~3.0.3" - source-map "~0.5.3" - combined-stream@1.0.6, combined-stream@^1.0.5, combined-stream@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" @@ -1914,14 +1773,6 @@ concat-stream@^1.5.0, concat-stream@^1.5.2: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-stream@~1.5.0, concat-stream@~1.5.1: - version "1.5.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266" - dependencies: - inherits "~2.0.1" - readable-stream "~2.0.0" - typedarray "~0.0.5" - configstore@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.1.tgz#094ee662ab83fad9917678de114faaea8fcdca90" @@ -1962,7 +1813,7 @@ consolidate@^0.14.0: dependencies: bluebird "^3.1.1" -constants-browserify@^1.0.0, constants-browserify@~1.0.0: +constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" @@ -1982,10 +1833,6 @@ convert-source-map@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" -convert-source-map@~1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860" - cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -2105,7 +1952,7 @@ cryptiles@3.x.x: dependencies: boom "5.x.x" -crypto-browserify@^3.0.0, crypto-browserify@^3.11.0: +crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" dependencies: @@ -2511,15 +2358,6 @@ depd@1.1.1, depd@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" -deps-sort@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.0.tgz#091724902e84658260eb910748cccd1af6e21fb5" - dependencies: - JSONStream "^1.0.3" - shasum "^1.0.0" - subarg "^1.0.0" - through2 "^2.0.0" - des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" @@ -2552,13 +2390,6 @@ detect-port-alt@1.1.5: address "^1.0.1" debug "^2.6.0" -detective@^4.0.0: - version "4.7.1" - resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e" - dependencies: - acorn "^5.2.1" - defined "^1.0.0" - di@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" @@ -2633,7 +2464,7 @@ dom-serializer@0: domelementtype "~1.1.1" entities "~1.1.1" -domain-browser@^1.1.1, domain-browser@~1.1.0: +domain-browser@^1.1.1: version "1.1.7" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc" @@ -2672,12 +2503,6 @@ dropzone@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-4.2.0.tgz#fbe7acbb9918e0706489072ef663effeef8a79f3" -duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - dependencies: - readable-stream "^2.0.2" - duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -2877,7 +2702,7 @@ es6-set@~0.1.5: es6-symbol "3.1.1" event-emitter "~0.3.5" -es6-symbol@3, es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@~3.1, es6-symbol@~3.1.1: +es6-symbol@3, es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1, es6-symbol@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" dependencies: @@ -3141,7 +2966,7 @@ eventemitter3@1.x.x: version "1.2.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" -events@^1.0.0, events@~1.1.0: +events@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" @@ -3690,7 +3515,7 @@ glob@^5.0.15: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2: +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: @@ -3909,6 +3734,10 @@ has-symbol-support-x@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.3.0.tgz#588bd6927eaa0e296afae24160659167fc2be4f8" +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + has-to-string-tag-x@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.3.0.tgz#78e3d98c3c0ec9413e970eb8d766249a1e13058f" @@ -3946,7 +3775,7 @@ has-values@^1.0.0: is-number "^3.0.0" kind-of "^4.0.0" -has@^1.0.0, has@^1.0.1: +has@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" dependencies: @@ -4055,10 +3884,6 @@ html-entities@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2" -htmlescape@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351" - htmlparser2@^3.8.2, htmlparser2@^3.9.0: version "3.9.2" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.9.2.tgz#1bdf87acca0f3f9e53fa4fcceb0f4b4cbb00b338" @@ -4142,10 +3967,6 @@ https-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82" -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - https-proxy-agent@1: version "1.0.0" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz#35f7da6c48ce4ddbfa264891ac593ee5ff8671e6" @@ -4255,12 +4076,6 @@ ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" -inline-source-map@~0.6.0: - version "0.6.2" - resolved "https://registry.yarnpkg.com/inline-source-map/-/inline-source-map-0.6.2.tgz#f9393471c18a79d1724f863fa38b586370ade2a5" - dependencies: - source-map "~0.5.3" - inquirer@3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.3.0.tgz#9dd2f2ad765dcab1ff0443b491442a20ba227dc9" @@ -4298,19 +4113,6 @@ inquirer@^0.12.0: strip-ansi "^3.0.0" through "^2.3.6" -insert-module-globals@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.0.1.tgz#c03bf4e01cb086d5b5e5ace8ad0afe7889d638c3" - dependencies: - JSONStream "^1.0.3" - combine-source-map "~0.7.1" - concat-stream "~1.5.1" - is-buffer "^1.1.0" - lexical-scope "^1.2.0" - process "~0.11.0" - through2 "^2.0.0" - xtend "^4.0.0" - internal-ip@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" @@ -4383,7 +4185,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.1.0, is-buffer@^1.1.5: +is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -4666,7 +4468,7 @@ is-wsl@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" -isarray@0.0.1, isarray@~0.0.1: +isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" @@ -4720,13 +4522,29 @@ istanbul-lib-coverage@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" +istanbul-lib-coverage@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz#f7d8f2e42b97e37fe796114cb0f9d68b5e3a4341" + istanbul-lib-hook@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.1.0.tgz#8538d970372cb3716d53e55523dd54b557a8d89b" dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.7.5, istanbul-lib-instrument@^1.9.1: +istanbul-lib-instrument@^1.10.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz#724b4b6caceba8692d3f1f9d0727e279c401af7b" + dependencies: + babel-generator "^6.18.0" + babel-template "^6.16.0" + babel-traverse "^6.18.0" + babel-types "^6.18.0" + babylon "^6.18.0" + istanbul-lib-coverage "^1.2.0" + semver "^5.3.0" + +istanbul-lib-instrument@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.9.1.tgz#250b30b3531e5d3251299fdd64b0b2c9db6b558e" dependencies: @@ -4882,12 +4700,6 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: dependencies: jsonify "~0.0.0" -json-stable-stringify@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45" - dependencies: - jsonify "~0.0.0" - json-stringify-safe@5.0.x, json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -4904,10 +4716,6 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" @@ -4942,9 +4750,9 @@ karma-chrome-launcher@^2.2.0: fs-access "^1.0.0" which "^1.2.1" -karma-coverage-istanbul-reporter@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-1.4.1.tgz#2b42d145ddbb4868d85d52888c495a21c61d873c" +karma-coverage-istanbul-reporter@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-1.4.2.tgz#a8d0c8815c7d6f6cea15a394a7c4b39ef151a939" dependencies: istanbul-api "^1.1.14" minimatch "^3.0.4" @@ -4967,23 +4775,23 @@ karma-sourcemap-loader@^0.3.7: dependencies: graceful-fs "^4.1.2" -karma-webpack@2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-2.0.7.tgz#dc3a492b478f10e8e3ccb9f58171b623f7070a1f" +karma-webpack@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-3.0.0.tgz#bf009c5b73c667c11c015717e9e520f581317c44" dependencies: - async "~0.9.0" - loader-utils "^0.2.5" - lodash "^3.8.0" + async "^2.0.0" + babel-runtime "^6.0.0" + loader-utils "^1.0.0" + lodash "^4.0.0" source-map "^0.5.6" - webpack-dev-middleware "^1.12.0" + webpack-dev-middleware "^2.0.6" -karma@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/karma/-/karma-2.0.0.tgz#a02698dd7f0f05ff5eb66ab8f65582490b512e58" +karma@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/karma/-/karma-2.0.2.tgz#4d2db9402850a66551fa784b0164fb0824ed8c4b" dependencies: bluebird "^3.3.0" body-parser "^1.16.1" - browserify "^14.5.0" chokidar "^1.4.1" colors "^1.1.0" combine-lists "^1.0.0" @@ -5008,7 +4816,7 @@ karma@^2.0.0: socket.io "2.0.4" source-map "^0.6.1" tmp "0.0.33" - useragent "^2.1.12" + useragent "2.2.1" katex@^0.8.3: version "0.8.3" @@ -5046,14 +4854,6 @@ kind-of@^6.0.0, kind-of@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" -labeled-stream-splicer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz#a52e1d138024c00b86b1c0c91f677918b8ae0a59" - dependencies: - inherits "^2.0.1" - isarray "~0.0.1" - stream-splicer "^2.0.0" - latest-version@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15" @@ -5083,12 +4883,6 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -lexical-scope@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/lexical-scope/-/lexical-scope-1.2.0.tgz#fcea5edc704a4b3a8796cdca419c3a0afaf22df4" - dependencies: - astw "^2.0.0" - libbase64@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/libbase64/-/libbase64-0.1.0.tgz#62351a839563ac5ff5bd26f12f60e9830bb751e6" @@ -5134,7 +4928,7 @@ loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" -loader-utils@^0.2.15, loader-utils@^0.2.5: +loader-utils@^0.2.15: version "0.2.16" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.16.tgz#f08632066ed8282835dff88dfb52704765adee6d" dependencies: @@ -5234,10 +5028,6 @@ lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" -lodash.memoize@~3.0.3: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" - lodash.mergewith@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz#150cf0a16791f5903b8891eab154609274bdea55" @@ -5265,10 +5055,6 @@ lodash@4.17.4: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" -lodash@^3.8.0: - version "3.10.1" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" - lodash@^4.0.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0: version "4.17.5" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511" @@ -5310,6 +5096,13 @@ loglevel@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.4.1.tgz#95b383f91a3c2756fd4ab093667e4309161f2bcd" +loglevelnext@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/loglevelnext/-/loglevelnext-1.0.5.tgz#36fc4f5996d6640f539ff203ba819641680d75a2" + dependencies: + es6-symbol "^3.1.1" + object.assign "^4.1.0" + longest@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" @@ -5320,7 +5113,7 @@ loose-envify@^1.0.0: dependencies: js-tokens "^3.0.0" -loud-rejection@^1.0.0: +loud-rejection@^1.0.0, loud-rejection@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" dependencies: @@ -5331,7 +5124,11 @@ lowercase-keys@1.0.0, lowercase-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306" -lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.1: +lru-cache@2.2.x: + version "2.2.4" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d" + +lru-cache@^4.0.1, lru-cache@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" dependencies: @@ -5490,6 +5287,24 @@ micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" +micromatch@^3.1.8: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" @@ -5515,6 +5330,10 @@ mime@^1.3.4, mime@^1.4.1, mime@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" +mime@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369" + mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" @@ -5547,7 +5366,7 @@ minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" -minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: +minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" @@ -5583,26 +5402,6 @@ mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkd dependencies: minimist "0.0.8" -module-deps@^4.0.8: - version "4.1.1" - resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-4.1.1.tgz#23215833f1da13fd606ccb8087b44852dcb821fd" - dependencies: - JSONStream "^1.0.3" - browser-resolve "^1.7.0" - cached-path-relative "^1.0.0" - concat-stream "~1.5.0" - defined "^1.0.0" - detective "^4.0.0" - duplexer2 "^0.1.2" - inherits "^2.0.1" - parents "^1.0.0" - readable-stream "^2.0.2" - resolve "^1.1.3" - stream-combiner2 "^1.1.1" - subarg "^1.0.0" - through2 "^2.0.0" - xtend "^4.0.0" - moment@2.x, moment@^2.18.1: version "2.19.2" resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe" @@ -5935,7 +5734,7 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-keys@^1.0.8: +object-keys@^1.0.11, object-keys@^1.0.8: version "1.0.11" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" @@ -5945,6 +5744,15 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" +object.assign@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" + dependencies: + define-properties "^1.1.2" + function-bind "^1.1.1" + has-symbols "^1.0.0" + object-keys "^1.0.11" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -6026,10 +5834,6 @@ os-browserify@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f" -os-browserify@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -6134,7 +5938,7 @@ pako@~0.2.0: version "0.2.9" resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" -pako@~1.0.2, pako@~1.0.5: +pako@~1.0.2: version "1.0.6" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" @@ -6146,12 +5950,6 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" -parents@^1.0.0, parents@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parents/-/parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751" - dependencies: - path-platform "~0.11.15" - parse-asn1@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712" @@ -6201,7 +5999,7 @@ pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" -path-browserify@0.0.0, path-browserify@~0.0.0: +path-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" @@ -6235,10 +6033,6 @@ path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" -path-platform@~0.11.15: - version "0.11.15" - resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2" - path-proxy@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/path-proxy/-/path-proxy-1.0.0.tgz#18e8a36859fc9d2f1a53b48dee138543c020de5e" @@ -6751,7 +6545,7 @@ punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" -punycode@1.4.1, punycode@^1.2.4, punycode@^1.3.2, punycode@^1.4.1: +punycode@1.4.1, punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" @@ -6794,7 +6588,7 @@ query-string@^5.0.1: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -querystring-es3@^0.2.0, querystring-es3@~0.2.0: +querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -6893,12 +6687,6 @@ react-error-overlay@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.0.tgz#d198408a85b4070937a98667f500c832f86bd5d4" -read-only-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0" - dependencies: - readable-stream "^2.0.2" - read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -6950,7 +6738,7 @@ readable-stream@1.1.x, "readable-stream@1.x >=1.1.9": isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@~2.0.0, readable-stream@~2.0.5, readable-stream@~2.0.6: +readable-stream@~2.0.5, readable-stream@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" dependencies: @@ -7049,7 +6837,7 @@ regex-cache@^0.4.2: dependencies: is-equal-shallow "^0.1.3" -regex-not@^1.0.0: +regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" dependencies: @@ -7258,11 +7046,11 @@ resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" -resolve@1.1.7, resolve@1.1.x: +resolve@1.1.x: version "1.1.7" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" -resolve@^1.1.3, resolve@^1.1.4, resolve@^1.1.6, resolve@^1.2.0: +resolve@^1.1.6, resolve@^1.2.0: version "1.5.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36" dependencies: @@ -7507,20 +7295,13 @@ setprototypeof@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" -sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4: +sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.10" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.10.tgz#b1fde5cd7d11a5626638a07c604ab909cfa31f9b" dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" -shasum@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f" - dependencies: - json-stable-stringify "~0.0.0" - sha.js "~2.4.4" - shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -7531,7 +7312,7 @@ shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" -shell-quote@1.6.1, shell-quote@^1.6.1: +shell-quote@1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" dependencies: @@ -7860,20 +7641,13 @@ statuses@~1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" -stream-browserify@^2.0.0, stream-browserify@^2.0.1: +stream-browserify@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" dependencies: inherits "~2.0.1" readable-stream "^2.0.2" -stream-combiner2@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" - dependencies: - duplexer2 "~0.1.0" - readable-stream "^2.0.2" - stream-combiner@~0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" @@ -7887,7 +7661,7 @@ stream-each@^1.1.0: end-of-stream "^1.1.0" stream-shift "^1.0.0" -stream-http@^2.0.0, stream-http@^2.3.1: +stream-http@^2.3.1: version "2.8.0" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.0.tgz#fd86546dac9b1c91aff8fc5d287b98fafb41bc10" dependencies: @@ -7901,13 +7675,6 @@ stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" -stream-splicer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.0.tgz#1b63be438a133e4b671cc1935197600175910d83" - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.2" - streamroller@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-0.7.0.tgz#a1d1b7cf83d39afb0d63049a5acbf93493bdf64b" @@ -7940,7 +7707,7 @@ string_decoder@^0.10.25, string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" -string_decoder@~1.0.0, string_decoder@~1.0.3: +string_decoder@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" dependencies: @@ -7993,12 +7760,6 @@ style-loader@^0.20.2: loader-utils "^1.1.0" schema-utils "^0.4.3" -subarg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" - dependencies: - minimist "^1.1.0" - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -8043,12 +7804,6 @@ svgo@^0.7.0: sax "~1.2.1" whet.extend "~0.9.9" -syntax-error@^1.1.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c" - dependencies: - acorn-node "^1.2.0" - table@^3.7.8: version "3.8.3" resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" @@ -8095,12 +7850,12 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" -test-exclude@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26" +test-exclude@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.2.1.tgz#dfa222f03480bca69207ca728b37d74b45f724fa" dependencies: arrify "^1.0.1" - micromatch "^2.3.11" + micromatch "^3.1.8" object-assign "^4.1.0" read-pkg-up "^1.0.1" require-main-filename "^1.0.1" @@ -8128,7 +7883,7 @@ through2@^2.0.0: readable-stream "^2.1.5" xtend "~4.0.1" -through@2, "through@>=2.2.7 <3", through@^2.3.6, through@~2.3, through@~2.3.1: +through@2, through@^2.3.6, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -8154,7 +7909,7 @@ timed-out@^4.0.0, timed-out@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" -timers-browserify@^1.0.1, timers-browserify@^1.4.2: +timers-browserify@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" dependencies: @@ -8217,6 +7972,15 @@ to-regex@^3.0.1: extend-shallow "^2.0.1" regex-not "^1.0.0" +to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + touch@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" @@ -8257,10 +8021,6 @@ tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" -tty-browserify@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -8288,7 +8048,7 @@ type-is@~1.6.15: media-typer "0.3.0" mime-types "~2.1.18" -typedarray@^0.0.6, typedarray@~0.0.5: +typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -8321,10 +8081,6 @@ ultron@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" -umd@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.1.tgz#8ae556e11011f63c2596708a8837259f01b3d60e" - unc-path-regex@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" @@ -8426,6 +8182,10 @@ urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" +url-join@^2.0.2: + version "2.0.5" + resolved "https://registry.yarnpkg.com/url-join/-/url-join-2.0.5.tgz#5af22f18c052a000a48d7b82c5e9c2e2feeda728" + url-loader@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.6.2.tgz#a007a7109620e9d988d14bce677a1decb9a993f7" @@ -8464,7 +8224,7 @@ url-to-options@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" -url@^0.11.0, url@~0.11.0: +url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" dependencies: @@ -8485,18 +8245,18 @@ user-home@^2.0.0: dependencies: os-homedir "^1.0.0" -useragent@^2.1.12: - version "2.3.0" - resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.3.0.tgz#217f943ad540cb2128658ab23fc960f6a88c9972" +useragent@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.2.1.tgz#cf593ef4f2d175875e8bb658ea92e18a4fd06d8e" dependencies: - lru-cache "4.1.x" + lru-cache "2.2.x" tmp "0.0.x" util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util@0.10.3, util@^0.10.3, util@~0.10.1: +util@0.10.3, util@^0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" dependencies: @@ -8545,7 +8305,7 @@ visibilityjs@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/visibilityjs/-/visibilityjs-1.2.4.tgz#bff8663da62c8c10ad4ee5ae6a1ae6fac4259d63" -vm-browserify@0.0.4, vm-browserify@~0.0.1: +vm-browserify@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" dependencies: @@ -8659,7 +8419,7 @@ webpack-bundle-analyzer@^2.10.0: opener "^1.4.3" ws "^4.0.0" -webpack-dev-middleware@1.12.2, webpack-dev-middleware@^1.12.0: +webpack-dev-middleware@1.12.2: version "1.12.2" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.2.tgz#f8fc1120ce3b4fc5680ceecb43d777966b21105e" dependencies: @@ -8669,6 +8429,18 @@ webpack-dev-middleware@1.12.2, webpack-dev-middleware@^1.12.0: range-parser "^1.0.3" time-stamp "^2.0.0" +webpack-dev-middleware@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-2.0.6.tgz#a51692801e8310844ef3e3790e1eacfe52326fd4" + dependencies: + loud-rejection "^1.6.0" + memory-fs "~0.4.1" + mime "^2.1.0" + path-is-absolute "^1.0.0" + range-parser "^1.0.3" + url-join "^2.0.2" + webpack-log "^1.0.1" + webpack-dev-server@^2.11.2: version "2.11.2" resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.11.2.tgz#1f4f4c78bf1895378f376815910812daf79a216f" @@ -8701,6 +8473,15 @@ webpack-dev-server@^2.11.2: webpack-dev-middleware "1.12.2" yargs "6.6.0" +webpack-log@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-1.2.0.tgz#a4b34cda6b22b518dbb0ab32e567962d5c72a43d" + dependencies: + chalk "^2.1.0" + log-symbols "^2.1.0" + loglevelnext "^1.0.1" + uuid "^3.1.0" + webpack-sources@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf" @@ -8859,7 +8640,7 @@ xregexp@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: +xtend@^4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" |