diff options
83 files changed, 566 insertions, 199 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index b093d4d25d4..a836b469cc7 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -54,6 +54,11 @@ Style/AlignArray: Style/AlignHash: Enabled: true +# Whether `and` and `or` are banned only in conditionals (conditionals) +# or completely (always). +Style/AndOr: + Enabled: true + # Use `Array#join` instead of `Array#*`. Style/ArrayJoin: Enabled: true diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 648b3fc49d2..a5b4d2f5b02 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -180,13 +180,6 @@ Security/JSONLoad: Style/AlignParameters: Enabled: false -# Offense count: 27 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, SupportedStyles. -# SupportedStyles: always, conditionals -Style/AndOr: - Enabled: false - # Offense count: 54 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, SupportedStyles. @@ -1,6 +1,6 @@ source 'https://rubygems.org' -gem 'rails', '4.2.7.1' +gem 'rails', '4.2.8' gem 'rails-deprecated_sanitizer', '~> 1.0.3' # Responders respond_to and respond_with @@ -333,7 +333,7 @@ gem 'newrelic_rpm', '~> 3.16' gem 'octokit', '~> 4.6.2' -gem 'mail_room', '~> 0.9.0' +gem 'mail_room', '~> 0.9.1' gem 'email_reply_trimmer', '~> 0.1' gem 'html2text' diff --git a/Gemfile.lock b/Gemfile.lock index 4f98dc9d085..2a3be763753 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,40 +3,39 @@ GEM specs: RedCloth (4.3.2) ace-rails-ap (4.1.0) - actionmailer (4.2.7.1) - actionpack (= 4.2.7.1) - actionview (= 4.2.7.1) - activejob (= 4.2.7.1) + actionmailer (4.2.8) + actionpack (= 4.2.8) + actionview (= 4.2.8) + activejob (= 4.2.8) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.7.1) - actionview (= 4.2.7.1) - activesupport (= 4.2.7.1) + actionpack (4.2.8) + actionview (= 4.2.8) + activesupport (= 4.2.8) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.7.1) - activesupport (= 4.2.7.1) + actionview (4.2.8) + activesupport (= 4.2.8) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (4.2.7.1) - activesupport (= 4.2.7.1) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (4.2.8) + activesupport (= 4.2.8) globalid (>= 0.3.0) - activemodel (4.2.7.1) - activesupport (= 4.2.7.1) + activemodel (4.2.8) + activesupport (= 4.2.8) builder (~> 3.1) - activerecord (4.2.7.1) - activemodel (= 4.2.7.1) - activesupport (= 4.2.7.1) + activerecord (4.2.8) + activemodel (= 4.2.8) + activesupport (= 4.2.8) arel (~> 6.0) activerecord_sane_schema_dumper (0.2) rails (>= 4, < 5) - activesupport (4.2.7.1) + activesupport (4.2.8) i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) @@ -47,7 +46,7 @@ GEM activerecord (>= 3.0) akismet (2.0.0) allocations (1.0.5) - arel (6.0.3) + arel (6.0.4) asana (0.4.0) faraday (~> 0.9) faraday_middleware (~> 0.9) @@ -86,7 +85,7 @@ GEM sass (>= 3.3.4) brakeman (3.4.1) browser (2.2.0) - builder (3.2.2) + builder (3.2.3) bullet (5.2.0) activesupport (>= 3.0.0) uniform_notifier (~> 1.10.0) @@ -127,7 +126,7 @@ GEM execjs coffee-script-source (1.10.0) colorize (0.7.7) - concurrent-ruby (1.0.2) + concurrent-ruby (1.0.4) connection_pool (2.2.1) crack (0.4.3) safe_yaml (~> 1.0.0) @@ -354,7 +353,7 @@ GEM json (~> 1.8) multi_xml (>= 0.5.2) httpclient (2.8.2) - i18n (0.7.0) + i18n (0.8.0) ice_nine (0.11.1) influxdb (0.2.3) cause @@ -370,7 +369,7 @@ GEM thor (>= 0.14, < 2.0) jquery-ui-rails (5.0.5) railties (>= 3.2.16) - json (1.8.3) + json (1.8.6) json-schema (2.6.2) addressable (~> 2.3.8) jwt (1.5.6) @@ -409,7 +408,7 @@ GEM nokogiri (>= 1.5.9) mail (2.6.4) mime-types (>= 1.16, < 4) - mail_room (0.9.0) + mail_room (0.9.1) memoist (0.15.0) method_source (0.8.2) mime-types (2.99.3) @@ -429,9 +428,8 @@ GEM net-ssh (3.0.1) netrc (0.11.0) newrelic_rpm (3.16.0.318) - nokogiri (1.6.8) + nokogiri (1.6.8.1) mini_portile2 (~> 2.1.0) - pkg-config (~> 1.1.7) numerizer (0.1.1) oauth (0.5.1) oauth2 (1.2.0) @@ -506,7 +504,6 @@ GEM parser (2.3.1.4) ast (~> 2.2) pg (0.18.4) - pkg-config (1.1.7) poltergeist (1.9.0) capybara (~> 2.1) cliver (~> 0.3.1) @@ -548,28 +545,28 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) - rails (4.2.7.1) - actionmailer (= 4.2.7.1) - actionpack (= 4.2.7.1) - actionview (= 4.2.7.1) - activejob (= 4.2.7.1) - activemodel (= 4.2.7.1) - activerecord (= 4.2.7.1) - activesupport (= 4.2.7.1) + rails (4.2.8) + actionmailer (= 4.2.8) + actionpack (= 4.2.8) + actionview (= 4.2.8) + activejob (= 4.2.8) + activemodel (= 4.2.8) + activerecord (= 4.2.8) + activesupport (= 4.2.8) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.7.1) + railties (= 4.2.8) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.7) + rails-dom-testing (1.0.8) activesupport (>= 4.2.0.beta, < 5.0) - nokogiri (~> 1.6.0) + nokogiri (~> 1.6) rails-deprecated_sanitizer (>= 1.0.1) rails-html-sanitizer (1.0.3) loofah (~> 2.0) - railties (4.2.7.1) - actionpack (= 4.2.7.1) - activesupport (= 4.2.7.1) + railties (4.2.8) + actionpack (= 4.2.8) + activesupport (= 4.2.8) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (2.1.0) @@ -733,10 +730,10 @@ GEM spring (>= 0.9.1) spring-commands-spinach (1.1.0) spring (>= 0.9.1) - sprockets (3.7.0) + sprockets (3.7.1) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.1.1) + sprockets-rails (3.2.0) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) @@ -760,7 +757,7 @@ GEM daemons (~> 1.0, >= 1.0.9) eventmachine (~> 1.0, >= 1.0.4) rack (>= 1, < 3) - thor (0.19.1) + thor (0.19.4) thread_safe (0.3.5) tilt (2.0.5) timecop (0.8.1) @@ -912,7 +909,7 @@ DEPENDENCIES license_finder (~> 2.1.0) licensee (~> 8.0.0) loofah (~> 2.0.3) - mail_room (~> 0.9.0) + mail_room (~> 0.9.1) method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) @@ -949,7 +946,7 @@ DEPENDENCIES rack-cors (~> 0.4.0) rack-oauth2 (~> 1.2.1) rack-proxy (~> 0.6.0) - rails (= 4.2.7.1) + rails (= 4.2.8) rails-deprecated_sanitizer (~> 1.0.3) rainbow (~> 2.1.0) rblineprof (~> 0.3.6) diff --git a/README.md b/README.md index 4f85fac4a56..09e08adbb73 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ We're hiring developers, support people, and production engineers all the time, There are two editions of GitLab: - GitLab Community Edition (CE) is available freely under the MIT Expat license. -- GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/features/#compare) that are more useful for organizations with more than 100 users. To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/). +- GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/products/#compare-options) that are more useful for organizations with more than 100 users. To use EE and get official support please [become a subscriber](https://about.gitlab.com/products/). ## Website diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index bf3da8528f0..a01662e2f9e 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -47,9 +47,10 @@ } // Only filter asynchronously only if option remote is set if (this.options.remote) { - $inputContainer.parent().addClass('is-loading'); clearTimeout(timeout); return timeout = setTimeout(function() { + $inputContainer.parent().addClass('is-loading'); + return this.options.query(this.input.val(), function(data) { $inputContainer.parent().removeClass('is-loading'); return this.options.callback(data); diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js index fa85f9a6c86..a853c3aeb1f 100644 --- a/app/assets/javascripts/header.js +++ b/app/assets/javascripts/header.js @@ -2,7 +2,7 @@ (function() { $(document).on('todo:toggle', function(e, count) { var $todoPendingCount = $('.todos-pending-count'); - $todoPendingCount.text(gl.text.addDelimiter(count)); + $todoPendingCount.text(gl.text.highCountTrim(count)); $todoPendingCount.toggleClass('hidden', count === 0); }); })(); diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index f755d212b3c..579d322e3fb 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -14,6 +14,9 @@ require('vendor/latinise'); gl.text.addDelimiter = function(text) { return text ? text.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",") : text; }; + gl.text.highCountTrim = function(count) { + return count > 99 ? '99+' : count; + }; gl.text.randomString = function() { return Math.random().toString(36).substring(7); }; diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss index 551a66fbf3a..af9ddb9ff80 100644 --- a/app/assets/stylesheets/pages/todos.scss +++ b/app/assets/stylesheets/pages/todos.scss @@ -6,6 +6,8 @@ .navbar-nav { li { .badge.todos-pending-count { + position: inherit; + top: -6px; margin-top: -5px; font-weight: normal; background: $todo-alert-blue; @@ -43,6 +45,12 @@ } } + .todo-avatar, + .todo-actions { + -webkit-flex: 0 0 auto; + flex: 0 0 auto; + } + .todo-actions { display: -webkit-flex; display: flex; @@ -55,8 +63,9 @@ } .todo-item { - -webkit-flex: auto; - flex: auto; + -webkit-flex: 0 1 100%; + flex: 0 1 100%; + min-width: 0; } } @@ -74,8 +83,29 @@ .todo-item { .todo-title { - @include str-truncated(calc(100% - 174px)); - overflow: visible; + display: flex; + + & > .title-item { + -webkit-flex: 0 0 auto; + flex: 0 0 auto; + margin: 0 2px; + + &:first-child { + margin-left: 0; + } + + &:last-child { + margin-right: 0; + } + } + + .todo-label { + -webkit-flex: 0 1 auto; + flex: 0 1 auto; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } .status-box { @@ -154,10 +184,12 @@ .todo-item { .todo-title { - white-space: normal; - overflow: visible; - max-width: 100%; + flex-flow: row wrap; margin-bottom: 10px; + + .todo-label { + white-space: normal; + } } .todo-body { diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index bf6be3d516b..5e7af3bff0d 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -74,7 +74,7 @@ class ApplicationController < ActionController::Base def authenticate_user!(*args) if redirect_to_home_page_url? - redirect_to current_application_settings.home_page_url and return + return redirect_to current_application_settings.home_page_url end super(*args) @@ -131,7 +131,7 @@ class ApplicationController < ActionController::Base headers['X-UA-Compatible'] = 'IE=edge' headers['X-Content-Type-Options'] = 'nosniff' # Enabling HSTS for non-standard ports would send clients to the wrong port - if Gitlab.config.gitlab.https and Gitlab.config.gitlab.port == 443 + if Gitlab.config.gitlab.https && Gitlab.config.gitlab.port == 443 headers['Strict-Transport-Security'] = 'max-age=31536000' end end @@ -152,7 +152,7 @@ class ApplicationController < ActionController::Base def check_password_expiration if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user? - redirect_to new_profile_password_path and return + return redirect_to new_profile_password_path end end @@ -218,7 +218,7 @@ class ApplicationController < ActionController::Base def require_email if current_user && current_user.temp_oauth_email? && session[:impersonator_id].nil? - redirect_to profile_path, notice: 'Please complete your profile with email address' and return + return redirect_to profile_path, notice: 'Please complete your profile with email address' end end diff --git a/app/controllers/dashboard/todos_controller.rb b/app/controllers/dashboard/todos_controller.rb index 4e61b0886d8..ee2358b986d 100644 --- a/app/controllers/dashboard/todos_controller.rb +++ b/app/controllers/dashboard/todos_controller.rb @@ -35,6 +35,11 @@ class Dashboard::TodosController < Dashboard::ApplicationController render json: todos_counts end + # Used in TodosHelper also + def self.todos_count_format(count) + count >= 100 ? '99+' : count + end + private def find_todos diff --git a/app/controllers/import/fogbugz_controller.rb b/app/controllers/import/fogbugz_controller.rb index 99b10b2f9b3..5df6bd34185 100644 --- a/app/controllers/import/fogbugz_controller.rb +++ b/app/controllers/import/fogbugz_controller.rb @@ -29,7 +29,7 @@ class Import::FogbugzController < Import::BaseController unless user_map.is_a?(Hash) && user_map.all? { |k, v| !v[:name].blank? } flash.now[:alert] = 'All users must have a name.' - render 'new_user_map' and return + return render 'new_user_map' end session[:fogbugz_user_map] = user_map diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb index 8d0de158f98..7d7f13ce5d5 100644 --- a/app/controllers/import/google_code_controller.rb +++ b/app/controllers/import/google_code_controller.rb @@ -44,13 +44,13 @@ class Import::GoogleCodeController < Import::BaseController rescue flash.now[:alert] = "The entered user map is not a valid JSON user map." - render "new_user_map" and return + return render "new_user_map" end unless user_map.is_a?(Hash) && user_map.all? { |k, v| k.is_a?(String) && v.is_a?(String) } flash.now[:alert] = "The entered user map is not a valid JSON user map." - render "new_user_map" and return + return render "new_user_map" end # This is the default, so let's not save it into the database. diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb index 58964a0e65d..7625187c7be 100644 --- a/app/controllers/invites_controller.rb +++ b/app/controllers/invites_controller.rb @@ -42,9 +42,7 @@ class InvitesController < ApplicationController @token = params[:id] @member = Member.find_by_invite_token(@token) - unless @member - render_404 and return - end + return render_404 unless @member @member end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 3ab7e6e0658..58d50ad647b 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -122,7 +122,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController else error_message = @user.errors.full_messages.to_sentence - redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return + return redirect_to omniauth_error_path(oauth['provider'], error: error_message) end end diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index 830e0b9591b..c8663a3c38e 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -45,13 +45,13 @@ class Profiles::KeysController < Profiles::ApplicationController if user.present? render text: user.all_ssh_keys.join("\n"), content_type: "text/plain" else - render_404 and return + return render_404 end rescue => e render text: e.message end else - render_404 and return + return render_404 end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index a1db856dcfb..39ba815cfca 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -95,7 +95,7 @@ class Projects::BlobController < Projects::ApplicationController else if tree = @repository.tree(@commit.id, @path) if tree.entries.any? - redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path)) and return + return redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path)) end end diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index cb3ed0f6f9c..4f094146348 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -15,10 +15,10 @@ class Projects::TreeController < Projects::ApplicationController if tree.entries.empty? if @repository.blob_at(@commit.id, @path) - redirect_to( + return redirect_to( namespace_project_blob_path(@project.namespace, @project, File.join(@ref, @path)) - ) and return + ) elsif @path.present? return render_404 end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 366804ab17e..445898a2e9e 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -23,7 +23,7 @@ class SnippetsController < ApplicationController if params[:username].present? @user = User.find_by(username: params[:username]) - render_404 and return unless @user + return render_404 unless @user @snippets = SnippetsFinder.new.execute(current_user, { filter: :by_user, diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index 0676767d910..dc5ae8edbb2 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -1,4 +1,8 @@ module NamespacesHelper + def namespace_id_from(params) + params.dig(:project, :namespace_id) || params[:namespace_id] + end + def namespaces_options(selected = :current_user, display_path: false, extra_group: nil) groups = current_user.owned_groups + current_user.masters_groups diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index 845f1a0e840..c52afd6db1c 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -3,6 +3,10 @@ module TodosHelper @todos_pending_count ||= current_user.todos_pending_count end + def todos_count_format(count) + count > 99 ? '99+' : count + end + def todos_done_count @todos_done_count ||= current_user.todos_done_count end diff --git a/app/models/project.rb b/app/models/project.rb index fc5b1a66910..411299eef63 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -552,7 +552,7 @@ class Project < ActiveRecord::Base end def check_limit - unless creator.can_create_project? or namespace.kind == 'group' + unless creator.can_create_project? || namespace.kind == 'group' projects_limit = creator.projects_limit if projects_limit == 0 diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index 942ec9371e5..1ad9efac196 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -52,7 +52,7 @@ class DroneCiService < CiService response = HTTParty.get(commit_status_path(sha, ref), verify: enable_ssl_verification) status = - if response.code == 200 and response['status'] + if response.code == 200 && response['status'] case response['status'] when 'killed' :canceled diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index 5d93064f9b3..5d6862d9faa 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -96,7 +96,7 @@ class IrkerService < Service rescue URI::InvalidURIError end - unless uri.present? and default_irc_uri.nil? + unless uri.present? && default_irc_uri.nil? begin new_recipient = URI.join(default_irc_uri, '/', recipient).to_s uri = consider_uri(URI.parse(new_recipient)) diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb index 012e82a7704..be34d4fa9b8 100644 --- a/app/services/projects/upload_service.rb +++ b/app/services/projects/upload_service.rb @@ -5,7 +5,7 @@ module Projects end def execute - return nil unless @file and @file.size <= max_attachment_size + return nil unless @file && @file.size <= max_attachment_size uploader = FileUploader.new(@project) uploader.store!(@file) diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb index 2d11305be13..bc0653cb634 100644 --- a/app/services/users/destroy_service.rb +++ b/app/services/users/destroy_service.rb @@ -7,6 +7,10 @@ module Users end def execute(user, options = {}) + unless current_user.admin? || current_user == user + raise Gitlab::Access::AccessDeniedError, "#{current_user} tried to destroy user #{user}!" + end + if !options[:delete_solo_owned_groups] && user.solo_owned_groups.present? user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user' return user diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index dc2d924f212..a3993d5ef16 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -1,28 +1,33 @@ %li{ class: "todo todo-#{todo.done? ? 'done' : 'pending'}", id: dom_id(todo), data: { url: todo_target_path(todo) } } - = author_avatar(todo, size: 40) + .todo-avatar + = author_avatar(todo, size: 40) .todo-item.todo-block .todo-title.title - unless todo.build_failed? || todo.unmergeable? = todo_target_state_pill(todo) - %span.author-name + .title-item.author-name - if todo.author = link_to_author(todo) - else (removed) - %span.action-name + .title-item.action-name = todo_action_name(todo) - %span.todo-label + .title-item.todo-label - if todo.target = todo_target_link(todo) - else (removed) - · #{time_ago_with_tooltip(todo.created_at)} - = todo_due_date(todo) + .title-item + · + + .title-item + #{time_ago_with_tooltip(todo.created_at)} + = todo_due_date(todo) .todo-body .todo-note diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 60b9b8bdbc4..f8986893776 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -35,7 +35,7 @@ = link_to dashboard_todos_path, title: 'Todos', aria: { label: "Todos" }, data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('bell fw') %span.badge.todos-pending-count{ class: ("hidden" if todos_pending_count == 0) } - = todos_pending_count + = todos_count_format(todos_pending_count) - if Gitlab::Sherlock.enabled? %li = link_to sherlock_transactions_path, title: 'Sherlock Transactions', diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index 6abff6aaf95..c2b32a22170 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -3,7 +3,7 @@ .pull-right - if can?(current_user, :update_pipeline, pipeline.project) - if pipeline.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post + = link_to "Retry", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'js-retry-button btn btn-grouped btn-primary', method: :post - if pipeline.builds.running_or_pending.any? = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index a07885537b9..2a98bba05ee 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -22,7 +22,7 @@ - if current_user.can_select_namespace? .input-group-addon = root_url - = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1} + = f.select :namespace_id, namespaces_options(namespace_id_from(params) || :current_user, display_path: true), {}, {class: 'select2 js-select-namespace', tabindex: 1} - else .input-group-addon.static-namespace diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml index e0c972aa2fb..0605af4fcd3 100644 --- a/app/views/projects/pipelines/_info.html.haml +++ b/app/views/projects/pipelines/_info.html.haml @@ -8,7 +8,7 @@ .header-action-buttons - if can?(current_user, :update_pipeline, @pipeline.project) - if @pipeline.retryable? - = link_to "Retry failed", retry_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'btn btn-inverted-secondary', method: :post + = link_to "Retry", retry_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), class: 'js-retry-button btn btn-inverted-secondary', method: :post - if @pipeline.cancelable? = link_to "Cancel running", cancel_namespace_project_pipeline_path(@pipeline.project.namespace, @pipeline.project, @pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post diff --git a/app/views/projects/pipelines_settings/_show.html.haml b/app/views/projects/pipelines_settings/_show.html.haml index 8024fb8979d..132f6372e40 100644 --- a/app/views/projects/pipelines_settings/_show.html.haml +++ b/app/views/projects/pipelines_settings/_show.html.haml @@ -60,7 +60,7 @@ = f.label :build_coverage_regex, "Test coverage parsing", class: 'label-light' .input-group %span.input-group-addon / - = f.text_field :build_coverage_regex, class: 'form-control', placeholder: '\(\d+.\d+\%\) covered' + = f.text_field :build_coverage_regex, class: 'form-control', placeholder: 'Regular expression' %span.input-group-addon / %p.help-block A regular expression that will be used to find the test coverage diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index 7fe2bce3e7c..22004ecacbc 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -11,7 +11,7 @@ .results.prepend-top-10 - if @scope == 'commits' - %ul.list-unstyled + %ul.content-list.commit-list.table-list.table-wide = render partial: "search/results/commit", collection: @search_objects - else .search-results diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index e977c1f1698..f84be600df8 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -12,41 +12,34 @@ %span.light= time_ago_with_tooltip(snippet.created_at) %h4.snippet-title - snippet_path = reliable_snippet_path(snippet) - = link_to snippet_path do - .file-holder - .js-file-title.file-title + .file-holder + .js-file-title.file-title + = link_to snippet_path do %i.fa.fa-file %strong= snippet.file_name - - if markup?(snippet.file_name) - .file-content.wiki + - if markup?(snippet.file_name) + .file-content.wiki + - snippet_chunks.each do |chunk| + - unless chunk[:data].empty? + = render_markup(snippet.file_name, chunk[:data]) + - else + .file-content.code + .nothing-here-block Empty file + - else + .file-content.code.js-syntax-highlight + .line-numbers - snippet_chunks.each do |chunk| - unless chunk[:data].empty? - = render_markup(snippet.file_name, chunk[:data]) + - Gitlab::Git::Util.count_lines(chunk[:data]).times do |index| + - offset = defined?(chunk[:start_line]) ? chunk[:start_line] : 1 + - i = index + offset + = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do + %i.fa.fa-link + = i + .blob-content + - snippet_chunks.each do |chunk| + - unless chunk[:data].empty? + = highlight(snippet.file_name, chunk[:data], repository: nil, plain: snippet.no_highlighting?) - else .file-content.code .nothing-here-block Empty file - - else - .file-content.code.js-syntax-highlight - .line-numbers - - snippet_chunks.each do |chunk| - - unless chunk[:data].empty? - - Gitlab::Git::Util.count_lines(chunk[:data]).times do |index| - - offset = defined?(chunk[:start_line]) ? chunk[:start_line] : 1 - - i = index + offset - = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}", class: "diff-line-num" do - %i.fa.fa-link - = i - - unless snippet == snippet_chunks.last - %a.diff-line-num - = "." - %pre.code - %code - - snippet_chunks.each do |chunk| - - unless chunk[:data].empty? - = chunk[:data] - - unless chunk == snippet_chunks.last - %a - = "..." - - else - .file-content.code - .nothing-here-block Empty file diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml index 239387fc9fa..8e721c9c8dd 100644 --- a/app/views/shared/members/_member.html.haml +++ b/app/views/shared/members/_member.html.haml @@ -61,7 +61,7 @@ = dropdown_title("Change permissions") .dropdown-content %ul - - Gitlab::Access.options.each do |role, role_id| + - member.class.access_level_roles.each do |role, role_id| %li = link_to role, "javascript:void(0)", class: ("is-active" if member.access_level == role_id), diff --git a/app/workers/delete_user_worker.rb b/app/workers/delete_user_worker.rb index 5483bbb210b..3340a7be4fe 100644 --- a/app/workers/delete_user_worker.rb +++ b/app/workers/delete_user_worker.rb @@ -7,5 +7,7 @@ class DeleteUserWorker current_user = User.find(current_user_id) Users::DestroyService.new(current_user).execute(delete_user, options.symbolize_keys) + rescue Gitlab::Access::AccessDeniedError => e + Rails.logger.warn("User could not be destroyed: #{e}") end end diff --git a/changelogs/unreleased/1363-redo-mailroom-support.yml b/changelogs/unreleased/1363-redo-mailroom-support.yml new file mode 100644 index 00000000000..8ed206f4fdb --- /dev/null +++ b/changelogs/unreleased/1363-redo-mailroom-support.yml @@ -0,0 +1,4 @@ +--- +title: Redo internals of Incoming Mail Support +merge_request: 9385 +author: diff --git a/changelogs/unreleased/26703-todos-count.yml b/changelogs/unreleased/26703-todos-count.yml new file mode 100644 index 00000000000..24fd0c406e2 --- /dev/null +++ b/changelogs/unreleased/26703-todos-count.yml @@ -0,0 +1,4 @@ +--- +title: show 99+ for large count in todos notification bell +merge_request: 9171 +author: mhasbini diff --git a/changelogs/unreleased/26881-backup-fails-if-data-changes.yml b/changelogs/unreleased/26881-backup-fails-if-data-changes.yml new file mode 100644 index 00000000000..00bf105560b --- /dev/null +++ b/changelogs/unreleased/26881-backup-fails-if-data-changes.yml @@ -0,0 +1,4 @@ +--- +title: Add `copy` backup strategy to combat file changed errors +merge_request: 8728 +author: diff --git a/changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml b/changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml new file mode 100644 index 00000000000..3bcf0e06d08 --- /dev/null +++ b/changelogs/unreleased/28186-long-group-names-overflow-out-of-todos-view.yml @@ -0,0 +1,4 @@ +--- +title: Truncate long Todo titles for non-mobile screens +merge_request: 9311 +author: diff --git a/changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml b/changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml new file mode 100644 index 00000000000..ed357d86fe3 --- /dev/null +++ b/changelogs/unreleased/28389-ux-problem-with-pipeline-coverage-placeholder.yml @@ -0,0 +1,4 @@ +--- +title: Changed coverage reg expression placeholder text to be more like a placeholder +merge_request: +author: diff --git a/changelogs/unreleased/api-todos-restful.yml b/changelogs/unreleased/api-todos-restful.yml new file mode 100644 index 00000000000..dba1350a495 --- /dev/null +++ b/changelogs/unreleased/api-todos-restful.yml @@ -0,0 +1,4 @@ +--- +title: 'API: Use POST requests to mark todos as done' +merge_request: 9410 +author: Robert Schilling diff --git a/changelogs/unreleased/commit-search-ui-fix.yml b/changelogs/unreleased/commit-search-ui-fix.yml new file mode 100644 index 00000000000..4a5c2cf6090 --- /dev/null +++ b/changelogs/unreleased/commit-search-ui-fix.yml @@ -0,0 +1,4 @@ +--- +title: Fixed commit search UI +merge_request: +author: diff --git a/changelogs/unreleased/group-memebrs-owner-level.yml b/changelogs/unreleased/group-memebrs-owner-level.yml new file mode 100644 index 00000000000..ba77f38eb6d --- /dev/null +++ b/changelogs/unreleased/group-memebrs-owner-level.yml @@ -0,0 +1,4 @@ +--- +title: Added option to update to owner for group members +merge_request: +author: diff --git a/changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml b/changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml new file mode 100644 index 00000000000..b813127b1e6 --- /dev/null +++ b/changelogs/unreleased/rename-retry-failed-pipeline-to-retry.yml @@ -0,0 +1,4 @@ +--- +title: Rename retry failed button on pipeline page to just retry +merge_request: +author: diff --git a/changelogs/unreleased/sh-delete-user-permission-check.yml b/changelogs/unreleased/sh-delete-user-permission-check.yml new file mode 100644 index 00000000000..c0e79aae2a8 --- /dev/null +++ b/changelogs/unreleased/sh-delete-user-permission-check.yml @@ -0,0 +1,4 @@ +--- +title: Add user deletion permission check in `Users::DestroyService` +merge_request: +author: diff --git a/changelogs/unreleased/snippets-search.yml b/changelogs/unreleased/snippets-search.yml new file mode 100644 index 00000000000..00cf34f4a48 --- /dev/null +++ b/changelogs/unreleased/snippets-search.yml @@ -0,0 +1,4 @@ +--- +title: Fix snippets search result spacing +merge_request: +author: diff --git a/config/initializers/mysql_ignore_postgresql_options.rb b/config/initializers/mysql_ignore_postgresql_options.rb index 835f3ec5574..9a569be7674 100644 --- a/config/initializers/mysql_ignore_postgresql_options.rb +++ b/config/initializers/mysql_ignore_postgresql_options.rb @@ -31,7 +31,7 @@ if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) end def add_index_options(table_name, column_name, options = {}) - if options[:using] and options[:using] == :gin + if options[:using] && options[:using] == :gin options = options.dup options.delete(:using) end diff --git a/config/initializers/rack_lineprof.rb b/config/initializers/rack_lineprof.rb index 22e77a32c61..f7172fce9bc 100644 --- a/config/initializers/rack_lineprof.rb +++ b/config/initializers/rack_lineprof.rb @@ -1,7 +1,7 @@ # The default colors of rack-lineprof can be very hard to look at in terminals # with darker backgrounds. This patch tweaks the colors a bit so the output is # actually readable. -if Rails.env.development? and RUBY_ENGINE == 'ruby' and ENV['ENABLE_LINEPROF'] +if Rails.env.development? && RUBY_ENGINE == 'ruby' && ENV['ENABLE_LINEPROF'] Rails.application.config.middleware.use(Rack::Lineprof) module Rack diff --git a/config/mail_room.yml b/config/mail_room.yml index 774c5350a45..88d93d4bc6b 100644 --- a/config/mail_room.yml +++ b/config/mail_room.yml @@ -1,9 +1,6 @@ -# If you change this file in a Merge Request, please also create -# a Merge Request on https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests -# :mailboxes: <% - require_relative "lib/gitlab/mail_room" unless defined?(Gitlab::MailRoom) + require_relative "../lib/gitlab/mail_room" unless defined?(Gitlab::MailRoom) config = Gitlab::MailRoom.config if Gitlab::MailRoom.enabled? diff --git a/config/routes/sidekiq.rb b/config/routes/sidekiq.rb index d3e6bc4c292..0fa23f2b3d0 100644 --- a/config/routes/sidekiq.rb +++ b/config/routes/sidekiq.rb @@ -1,4 +1,4 @@ -constraint = lambda { |request| request.env['warden'].authenticate? and request.env['warden'].user.admin? } +constraint = lambda { |request| request.env['warden'].authenticate? && request.env['warden'].user.admin? } constraints constraint do mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq end diff --git a/doc/api/pipelines.md b/doc/api/pipelines.md index 82351ae688f..f3c9827f742 100644 --- a/doc/api/pipelines.md +++ b/doc/api/pipelines.md @@ -163,7 +163,7 @@ Example of response } ``` -## Retry failed builds in a pipeline +## Retry builds in a pipeline > [Introduced][ce-5837] in GitLab 8.11 diff --git a/doc/api/todos.md b/doc/api/todos.md index a5e81801024..a2fbbc7e1f8 100644 --- a/doc/api/todos.md +++ b/doc/api/todos.md @@ -184,7 +184,7 @@ Marks a single pending todo given by its ID for the current user as done. The todo marked as done is returned in the response. ``` -DELETE /todos/:id +POST /todos/:id/mark_as_done ``` Parameters: @@ -194,7 +194,7 @@ Parameters: | `id` | integer | yes | The ID of a todo | ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/130 +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/130/mark_as_done ``` Example Response: @@ -277,20 +277,15 @@ Example Response: ## Mark all todos as done -Marks all pending todos for the current user as done. It returns the number of marked todos. +Marks all pending todos for the current user as done. It returns the HTTP status code `204` with an empty response. ``` -DELETE /todos +POST /todos/mark_as_done ``` ```bash -curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/todos/donmark_as_donee ``` -Example Response: - -```json -3 -``` [ce-3188]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3188 diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md index 3f58c098b43..1af124c56b1 100644 --- a/doc/api/v3_to_v4.md +++ b/doc/api/v3_to_v4.md @@ -24,9 +24,9 @@ changes are in V4: - `/gitlab_ci_ymls/:key` - `/dockerfiles/:key` - Moved `/projects/fork/:id` to `/projects/:id/fork` +- Moved `DELETE /todos` to `POST /todos/mark_as_done` and `DELETE /todos/:todo_id` to `POST /todos/:todo_id/mark_as_done` - Endpoints `/projects/owned`, `/projects/visible`, `/projects/starred` & `/projects/all` are consolidated into `/projects` using query parameters - Return pagination headers for all endpoints that return an array - Removed `DELETE projects/:id/deploy_keys/:key_id/disable`. Use `DELETE projects/:id/deploy_keys/:key_id` instead - Moved `PUT /users/:id/(block|unblock)` to `POST /users/:id/(block|unblock)` - Labels filter on `projects/:id/issues` and `/issues` now matches only issues containing all labels (i.e.: Logical AND, not OR) - diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 9dee61bfa1f..00787323b6b 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -39,13 +39,15 @@ accessible during the build process. ## What is an image -The `image` keyword is the name of the docker image that is present in the -local Docker Engine (list all images with `docker images`) or any image that -can be found at [Docker Hub][hub]. For more information about images and Docker -Hub please read the [Docker Fundamentals][] documentation. +The `image` keyword is the name of the docker image the docker executor +will run to perform the CI tasks. -In short, with `image` we refer to the docker image, which will be used to -create a container on which your job will run. +By default the executor will only pull images from [Docker Hub][hub], +but this can be configured in the `gitlab-runner/config.toml` by setting +the [docker pull policy][] to allow using local images. + +For more information about images and Docker Hub please read +the [Docker Fundamentals][] documentation. ## What is a service @@ -271,6 +273,7 @@ containers as well as all volumes (`-v`) that were created with the container creation. [Docker Fundamentals]: https://docs.docker.com/engine/understanding-docker/ +[docker pull policy]: https://docs.gitlab.com/runner/executors/docker.html#how-pull-policies-work [hub]: https://hub.docker.com/ [linking-containers]: https://docs.docker.com/engine/userguide/networking/default_network/dockerlinks/ [tutum/wordpress]: https://hub.docker.com/r/tutum/wordpress/ diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index b4e13f5812a..a5b8cd6455c 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -84,6 +84,28 @@ Deleting tmp directories...[DONE] Deleting old backups... [SKIPPING] ``` +## Backup Strategy Option + +> **Note:** Introduced as an option in 8.17 + +The default backup strategy is to essentially stream data from the respective +data locations to the backup using the Linux command `tar` and `gzip`. This works +fine in most cases, but can cause problems when data is rapidly changing. + +When data changes while `tar` is reading it, the error `file changed as we read +it` may occur, and will cause the backup process to fail. To combat this, 8.17 +introduces a new backup strategy called `copy`. The strategy copies data files +to a temporary location before calling `tar` and `gzip`, avoiding the error. + +A side-effect is that the backup process with take up to an additional 1X disk +space. The process does its best to clean up the temporary files at each stage +so the problem doesn't compound, but it could be a considerable change for large +installations. This is why the `copy` strategy is not the default in 8.17. + +To use the `copy` strategy instead of the default streaming strategy, specify +`STRATEGY=copy` in the Rake task command. For example, +`sudo gitlab-rake gitlab:backup:create STRATEGY=copy`. + ## Exclude specific directories from the backup You can choose what should be backed up by adding the environment variable `SKIP`. diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md index 6eb457fde3f..4b0fba842e9 100644 --- a/doc/workflow/todos.md +++ b/doc/workflow/todos.md @@ -16,7 +16,8 @@ in a simple dashboard. You can quickly access the Todos dashboard using the bell icon next to the search bar in the upper right corner. The number in blue is the number of Todos -you still have open. +you still have open if the count is < 100, else it's 99+. The exact number +will still be shown in the body of the _To do_ tab. ![Todos icon](img/todos_icon.png) diff --git a/lib/api/api.rb b/lib/api/api.rb index dbb7271ccbd..e729c07f8c3 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -19,6 +19,7 @@ module API mount ::API::V3::Repositories mount ::API::V3::SystemHooks mount ::API::V3::Tags + mount ::API::V3::Todos mount ::API::V3::Templates mount ::API::V3::Users end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 7b6fae866eb..ae62f1f9580 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -153,7 +153,7 @@ module API params_hash = custom_params || params attrs = {} keys.each do |key| - if params_hash[key].present? or (params_hash.has_key?(key) and params_hash[key] == false) + if params_hash[key].present? || (params_hash.has_key?(key) && params_hash[key] == false) attrs[key] = params_hash[key] end end diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb index b634b1d0222..f59f7959173 100644 --- a/lib/api/pipelines.rb +++ b/lib/api/pipelines.rb @@ -23,7 +23,7 @@ module API pipelines = PipelinesFinder.new(user_project).execute(scope: params[:scope]) present paginate(pipelines), with: Entities::Pipeline end - + desc 'Create a new pipeline' do detail 'This feature was introduced in GitLab 8.14' success Entities::Pipeline @@ -58,7 +58,7 @@ module API present pipeline, with: Entities::Pipeline end - desc 'Retry failed builds in the pipeline' do + desc 'Retry builds in the pipeline' do detail 'This feature was introduced in GitLab 8.11.' success Entities::Pipeline end diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 9bd077263a7..0b9650b296c 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -58,7 +58,7 @@ module API params do requires :id, type: Integer, desc: 'The ID of the todo being marked as done' end - delete ':id' do + post ':id/mark_as_done' do todo = current_user.todos.find(params[:id]) TodoService.new.mark_todos_as_done([todo], current_user) @@ -66,9 +66,11 @@ module API end desc 'Mark all todos as done' - delete do + post '/mark_as_done' do todos = find_todos TodoService.new.mark_todos_as_done(todos, current_user) + + no_content! end end end diff --git a/lib/api/v3/todos.rb b/lib/api/v3/todos.rb new file mode 100644 index 00000000000..4f9b5fe72a6 --- /dev/null +++ b/lib/api/v3/todos.rb @@ -0,0 +1,28 @@ +module API + module V3 + class Todos < Grape::API + before { authenticate! } + + resource :todos do + desc 'Mark a todo as done' do + success ::API::Entities::Todo + end + params do + requires :id, type: Integer, desc: 'The ID of the todo being marked as done' + end + delete ':id' do + todo = current_user.todos.find(params[:id]) + TodoService.new.mark_todos_as_done([todo], current_user) + + present todo.reload, with: ::API::Entities::Todo, current_user: current_user + end + + desc 'Mark all todos as done' + delete do + todos = TodosFinder.new(current_user, params).execute + TodoService.new.mark_todos_as_done(todos, current_user) + end + end + end + end +end diff --git a/lib/backup/files.rb b/lib/backup/files.rb index cedbb289f6a..247c32c1c0a 100644 --- a/lib/backup/files.rb +++ b/lib/backup/files.rb @@ -8,6 +8,7 @@ module Backup @name = name @app_files_dir = File.realpath(app_files_dir) @files_parent_dir = File.realpath(File.join(@app_files_dir, '..')) + @backup_files_dir = File.join(Gitlab.config.backup.path, File.basename(@app_files_dir) ) @backup_tarball = File.join(Gitlab.config.backup.path, name + '.tar.gz') end @@ -15,7 +16,21 @@ module Backup def dump FileUtils.mkdir_p(Gitlab.config.backup.path) FileUtils.rm_f(backup_tarball) - run_pipeline!([%W(tar -C #{app_files_dir} -cf - .), %W(gzip -c -1)], out: [backup_tarball, 'w', 0600]) + + if ENV['STRATEGY'] == 'copy' + cmd = %W(cp -a #{app_files_dir} #{Gitlab.config.backup.path}) + output, status = Gitlab::Popen.popen(cmd) + + unless status.zero? + puts output + abort 'Backup failed' + end + + run_pipeline!([%W(tar -C #{@backup_files_dir} -cf - .), %W(gzip -c -1)], out: [backup_tarball, 'w', 0600]) + FileUtils.rm_rf(@backup_files_dir) + else + run_pipeline!([%W(tar -C #{app_files_dir} -cf - .), %W(gzip -c -1)], out: [backup_tarball, 'w', 0600]) + end end def restore diff --git a/lib/banzai/filter/plantuml_filter.rb b/lib/banzai/filter/plantuml_filter.rb index e194cf59275..b2537117558 100644 --- a/lib/banzai/filter/plantuml_filter.rb +++ b/lib/banzai/filter/plantuml_filter.rb @@ -7,7 +7,7 @@ module Banzai # class PlantumlFilter < HTML::Pipeline::Filter def call - return doc unless doc.at('pre.plantuml') and settings.plantuml_enabled + return doc unless doc.at('pre.plantuml') && settings.plantuml_enabled plantuml_setup diff --git a/lib/ci/ansi2html.rb b/lib/ci/ansi2html.rb index c10d3616f31..158a33f26fe 100644 --- a/lib/ci/ansi2html.rb +++ b/lib/ci/ansi2html.rb @@ -126,7 +126,7 @@ module Ci # We are only interested in color and text style changes - triggered by # sequences starting with '\e[' and ending with 'm'. Any other control # sequence gets stripped (including stuff like "delete last line") - return unless indicator == '[' and terminator == 'm' + return unless indicator == '[' && terminator == 'm' close_open_tags() diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 4800a509b37..fc445ab9483 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -54,7 +54,7 @@ module Gitlab disable_statement_timeout - key_name = "fk_#{source}_#{target}_#{column}" + key_name = concurrent_foreign_key_name(source, column) # Using NOT VALID allows us to create a key without immediately # validating it. This means we keep the ALTER TABLE lock only for a @@ -74,6 +74,15 @@ module Gitlab execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{key_name};") end + # Returns the name for a concurrent foreign key. + # + # PostgreSQL constraint names have a limit of 63 bytes. The logic used + # here is based on Rails' foreign_key_name() method, which unfortunately + # is private so we can't rely on it directly. + def concurrent_foreign_key_name(table, column) + "fk_#{Digest::SHA256.hexdigest("#{table}_#{column}_fk").first(10)}" + end + # Long-running migrations may take more than the timeout allowed by # the database. Disable the session's statement timeout to ensure # migrations don't get killed prematurely. (PostgreSQL only) diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb index 287b7a83547..3aaebb3e9c3 100644 --- a/lib/gitlab/metrics/system.rb +++ b/lib/gitlab/metrics/system.rb @@ -11,7 +11,7 @@ module Gitlab mem = 0 match = File.read('/proc/self/status').match(/VmRSS:\s+(\d+)/) - if match and match[1] + if match && match[1] mem = match[1].to_f * 1024 end diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 35c4194e87c..6102517e730 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -724,8 +724,11 @@ namespace :gitlab do def check_imap_authentication print "IMAP server credentials are correct? ... " - config_path = Rails.root.join('config', 'mail_room.yml') - config_file = YAML.load(ERB.new(File.read(config_path)).result) + config_path = Rails.root.join('config', 'mail_room.yml').to_s + erb = ERB.new(File.read(config_path)) + erb.filename = config_path + config_file = YAML.load(erb.result) + config = config_file[:mailboxes].first if config diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb index 294fae95752..0b8ff006d22 100644 --- a/spec/config/mail_room_spec.rb +++ b/spec/config/mail_room_spec.rb @@ -8,7 +8,7 @@ describe 'mail_room.yml' do context 'when incoming email is disabled' do before do - ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/mail_room_disabled.yml').to_s + ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/config/mail_room_disabled.yml').to_s Gitlab::MailRoom.reset_config! end @@ -26,7 +26,7 @@ describe 'mail_room.yml' do let(:gitlab_redis) { Gitlab::Redis.new(Rails.env) } before do - ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/mail_room_enabled.yml').to_s + ENV['MAIL_ROOM_GITLAB_CONFIG_FILE'] = Rails.root.join('spec/fixtures/config/mail_room_enabled.yml').to_s Gitlab::MailRoom.reset_config! end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index 8f561c8f90b..324ede798fe 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -153,7 +153,7 @@ describe 'Commits' do expect(page).to have_content pipeline.git_author_name expect(page).to have_link('Download artifacts') expect(page).not_to have_link('Cancel running') - expect(page).not_to have_link('Retry failed') + expect(page).not_to have_link('Retry') end end @@ -172,7 +172,7 @@ describe 'Commits' do expect(page).to have_content pipeline.git_author_name expect(page).not_to have_link('Download artifacts') expect(page).not_to have_link('Cancel running') - expect(page).not_to have_link('Retry failed') + expect(page).not_to have_link('Retry') end end end diff --git a/spec/features/groups/members/list_spec.rb b/spec/features/groups/members/list_spec.rb index 109de39b2dd..14c193f7450 100644 --- a/spec/features/groups/members/list_spec.rb +++ b/spec/features/groups/members/list_spec.rb @@ -30,6 +30,21 @@ feature 'Groups members list', feature: true do expect(second_row).to be_blank end + it 'updates user to owner level', :js do + group.add_owner(user1) + group.add_developer(user2) + + visit group_group_members_path(group) + + page.within(second_row) do + click_button('Developer') + + click_link('Owner') + + expect(page).to have_button('Owner') + end + end + def first_row page.all('ul.content-list > li')[0] end diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb index b56e562b2b6..45185f2dd1f 100644 --- a/spec/features/projects/new_project_spec.rb +++ b/spec/features/projects/new_project_spec.rb @@ -19,6 +19,51 @@ feature "New project", feature: true do end end + context "Namespace selector" do + context "with user namespace" do + before do + visit new_project_path + end + + it "selects the user namespace" do + namespace = find("#project_namespace_id") + + expect(namespace.text).to eq user.username + end + end + + context "with group namespace" do + let(:group) { create(:group, :private, owner: user) } + + before do + group.add_owner(user) + visit new_project_path(namespace_id: group.id) + end + + it "selects the group namespace" do + namespace = find("#project_namespace_id option[selected]") + + expect(namespace.text).to eq group.name + end + + context "on validation error" do + before do + fill_in('project_path', with: 'private-group-project') + choose('Internal') + click_button('Create project') + + expect(page).to have_css '.project-edit-errors .alert.alert-danger' + end + + it "selects the group namespace" do + namespace = find("#project_namespace_id option[selected]") + + expect(namespace.text).to eq group.name + end + end + end + end + context 'Import project options' do before do visit new_project_path diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 0b5ccc8c515..9f06e52ab55 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -54,7 +54,7 @@ describe 'Pipeline', :feature, :js do expect(page).to have_content('Build') expect(page).to have_content('Test') expect(page).to have_content('Deploy') - expect(page).to have_content('Retry failed') + expect(page).to have_content('Retry') expect(page).to have_content('Cancel running') end @@ -164,9 +164,9 @@ describe 'Pipeline', :feature, :js do it { expect(page).not_to have_content('retried') } context 'when retrying' do - before { click_on 'Retry failed' } + before { find('.js-retry-button').trigger('click') } - it { expect(page).not_to have_content('Retry failed') } + it { expect(page).not_to have_content('Retry') } end end @@ -198,7 +198,7 @@ describe 'Pipeline', :feature, :js do expect(page).to have_content(build_failed.id) expect(page).to have_content(build_running.id) expect(page).to have_content(build_external.id) - expect(page).to have_content('Retry failed') + expect(page).to have_content('Retry') expect(page).to have_content('Cancel running') expect(page).to have_link('Play') end @@ -226,9 +226,9 @@ describe 'Pipeline', :feature, :js do it { expect(page).not_to have_content('retried') } context 'when retrying' do - before { click_on 'Retry failed' } + before { find('.js-retry-button').trigger('click') } - it { expect(page).not_to have_content('Retry failed') } + it { expect(page).not_to have_content('Retry') } it { expect(page).to have_selector('.retried') } end end diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb index 4eafac1acd8..3b8f0b2d3f8 100644 --- a/spec/features/projects/ref_switcher_spec.rb +++ b/spec/features/projects/ref_switcher_spec.rb @@ -20,6 +20,8 @@ feature 'Ref switcher', feature: true, js: true do input.set 'binary' wait_for_ajax + expect(find('.dropdown-content ul')).to have_selector('li', count: 6) + page.within '.dropdown-content ul' do input.native.send_keys :enter end diff --git a/spec/features/todos/todos_spec.rb b/spec/features/todos/todos_spec.rb index fb19dac1d6a..3495091a0d5 100644 --- a/spec/features/todos/todos_spec.rb +++ b/spec/features/todos/todos_spec.rb @@ -171,6 +171,29 @@ describe 'Dashboard Todos', feature: true do end end + context 'User have large number of todos' do + before do + create_list(:todo, 101, :mentioned, user: user, project: project, target: issue, author: author) + + login_as(user) + visit dashboard_todos_path + end + + it 'shows 99+ for count >= 100 in notification' do + expect(page).to have_selector('.todos-pending-count', text: '99+') + end + + it 'shows exact number in To do tab' do + expect(page).to have_selector('.todos-pending .badge', text: '101') + end + + it 'shows exact number for count < 100' do + 3.times { first('.js-done-todo').click } + + expect(page).to have_selector('.todos-pending-count', text: '98') + end + end + context 'User has a Build Failed todo' do let!(:todo) { create(:todo, :build_failed, user: user, project: project, author: author) } diff --git a/spec/fixtures/mail_room_disabled.yml b/spec/fixtures/config/mail_room_disabled.yml index 97f8cff051f..97f8cff051f 100644 --- a/spec/fixtures/mail_room_disabled.yml +++ b/spec/fixtures/config/mail_room_disabled.yml diff --git a/spec/fixtures/mail_room_enabled.yml b/spec/fixtures/config/mail_room_enabled.yml index 9c94649244d..9c94649244d 100644 --- a/spec/fixtures/mail_room_enabled.yml +++ b/spec/fixtures/config/mail_room_enabled.yml diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js index 576b6cfefdc..46a27b8c98f 100644 --- a/spec/javascripts/header_spec.js +++ b/spec/javascripts/header_spec.js @@ -45,8 +45,8 @@ require('~/lib/utils/text_utility'); expect(isTodosCountHidden()).toEqual(false); }); - it('should add delimiter to todos-pending-count', function() { - expect($(todosPendingCount).text()).toEqual('1,000'); + it('should show 99+ for todos-pending-count', function() { + expect($(todosPendingCount).text()).toEqual('99+'); }); }); }); diff --git a/spec/javascripts/lib/utils/text_utility_spec.js.es6 b/spec/javascripts/lib/utils/text_utility_spec.js.es6 index 86ade66ec29..06b69b8ac17 100644 --- a/spec/javascripts/lib/utils/text_utility_spec.js.es6 +++ b/spec/javascripts/lib/utils/text_utility_spec.js.es6 @@ -35,5 +35,16 @@ require('~/lib/utils/text_utility'); expect(gl.text.pluralize('test', 1)).toBe('test'); }); }); + + describe('gl.text.highCountTrim', () => { + it('returns 99+ for count >= 100', () => { + expect(gl.text.highCountTrim(105)).toBe('99+'); + expect(gl.text.highCountTrim(100)).toBe('99+'); + }); + + it('returns exact number for count < 100', () => { + expect(gl.text.highCountTrim(45)).toBe(45); + }); + }); }); })(); diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index e94ca4fcfd2..e007044868c 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -101,6 +101,16 @@ describe Gitlab::Database::MigrationHelpers, lib: true do end end + describe '#concurrent_foreign_key_name' do + it 'returns the name for a foreign key' do + name = model.concurrent_foreign_key_name(:this_is_a_very_long_table_name, + :with_a_very_long_column_name) + + expect(name).to be_an_instance_of(String) + expect(name.length).to eq(13) + end + end + describe '#disable_statement_timeout' do context 'using PostgreSQL' do it 'disables statement timeouts' do diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 2069d2a7c75..f35e963a14b 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -107,46 +107,47 @@ describe API::Todos, api: true do end end - describe 'DELETE /todos/:id' do + describe 'POST /todos/:id/mark_as_done' do context 'when unauthenticated' do it 'returns authentication error' do - delete api("/todos/#{pending_1.id}") + post api("/todos/#{pending_1.id}/mark_as_done") - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated' do it 'marks a todo as done' do - delete api("/todos/#{pending_1.id}", john_doe) + post api("/todos/#{pending_1.id}/mark_as_done", john_doe) - expect(response.status).to eq(200) + expect(response).to have_http_status(201) + expect(json_response['id']).to eq(pending_1.id) + expect(json_response['state']).to eq('done') expect(pending_1.reload).to be_done end it 'updates todos cache' do expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original - delete api("/todos/#{pending_1.id}", john_doe) + post api("/todos/#{pending_1.id}/mark_as_done", john_doe) end end end - describe 'DELETE /todos' do + describe 'POST /mark_as_done' do context 'when unauthenticated' do it 'returns authentication error' do - delete api('/todos') + post api('/todos/mark_as_done') - expect(response.status).to eq(401) + expect(response).to have_http_status(401) end end context 'when authenticated' do it 'marks all todos as done' do - delete api('/todos', john_doe) + post api('/todos/mark_as_done', john_doe) - expect(response.status).to eq(200) - expect(response.body).to eq('3') + expect(response).to have_http_status(204) expect(pending_1.reload).to be_done expect(pending_2.reload).to be_done expect(pending_3.reload).to be_done @@ -155,7 +156,7 @@ describe API::Todos, api: true do it 'updates todos cache' do expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original - delete api("/todos", john_doe) + post api("/todos/mark_as_done", john_doe) end end end diff --git a/spec/requests/api/v3/todos_spec.rb b/spec/requests/api/v3/todos_spec.rb new file mode 100644 index 00000000000..80fa697e949 --- /dev/null +++ b/spec/requests/api/v3/todos_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +describe API::V3::Todos, api: true do + include ApiHelpers + + let(:project_1) { create(:empty_project) } + let(:project_2) { create(:empty_project) } + let(:author_1) { create(:user) } + let(:author_2) { create(:user) } + let(:john_doe) { create(:user, username: 'john_doe') } + let!(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe) } + let!(:pending_2) { create(:todo, project: project_2, author: author_2, user: john_doe) } + let!(:pending_3) { create(:todo, project: project_1, author: author_2, user: john_doe) } + let!(:done) { create(:todo, :done, project: project_1, author: author_1, user: john_doe) } + + before do + project_1.team << [john_doe, :developer] + project_2.team << [john_doe, :developer] + end + + describe 'DELETE /todos/:id' do + context 'when unauthenticated' do + it 'returns authentication error' do + delete v3_api("/todos/#{pending_1.id}") + + expect(response.status).to eq(401) + end + end + + context 'when authenticated' do + it 'marks a todo as done' do + delete v3_api("/todos/#{pending_1.id}", john_doe) + + expect(response.status).to eq(200) + expect(pending_1.reload).to be_done + end + + it 'updates todos cache' do + expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original + + delete v3_api("/todos/#{pending_1.id}", john_doe) + end + end + end + + describe 'DELETE /todos' do + context 'when unauthenticated' do + it 'returns authentication error' do + delete v3_api('/todos') + + expect(response.status).to eq(401) + end + end + + context 'when authenticated' do + it 'marks all todos as done' do + delete v3_api('/todos', john_doe) + + expect(response.status).to eq(200) + expect(response.body).to eq('3') + expect(pending_1.reload).to be_done + expect(pending_2.reload).to be_done + expect(pending_3.reload).to be_done + end + + it 'updates todos cache' do + expect_any_instance_of(User).to receive(:update_todos_count_cache).and_call_original + + delete v3_api("/todos", john_doe) + end + end + end +end diff --git a/spec/services/users/destroy_spec.rb b/spec/services/users/destroy_spec.rb index 46e58393218..c0bf27c698c 100644 --- a/spec/services/users/destroy_spec.rb +++ b/spec/services/users/destroy_spec.rb @@ -2,11 +2,11 @@ require 'spec_helper' describe Users::DestroyService, services: true do describe "Deletes a user and all their personal projects" do - let!(:user) { create(:user) } - let!(:current_user) { create(:user) } - let!(:namespace) { create(:namespace, owner: user) } - let!(:project) { create(:project, namespace: namespace) } - let(:service) { described_class.new(current_user) } + let!(:user) { create(:user) } + let!(:admin) { create(:admin) } + let!(:namespace) { create(:namespace, owner: user) } + let!(:project) { create(:project, namespace: namespace) } + let(:service) { described_class.new(admin) } context 'no options are given' do it 'deletes the user' do @@ -57,5 +57,26 @@ describe Users::DestroyService, services: true do expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) end end + + context "deletion permission checks" do + it 'does not delete the user when user is not an admin' do + other_user = create(:user) + + expect { described_class.new(other_user).execute(user) }.to raise_error(Gitlab::Access::AccessDeniedError) + expect(User.exists?(user.id)).to be(true) + end + + it 'allows admins to delete anyone' do + described_class.new(admin).execute(user) + + expect(User.exists?(user.id)).to be(false) + end + + it 'allows users to delete their own account' do + described_class.new(user).execute(user) + + expect(User.exists?(user.id)).to be(false) + end + end end end |