From 3ef71fa4fbea9a5ecc3c094df39cf4ea2f97dc90 Mon Sep 17 00:00:00 2001 From: Jan-Gerd Tenberge Date: Sat, 26 Sep 2015 20:53:16 +0200 Subject: [PATCH 001/410] Add support for HiDPI displays in gravatar service --- app/services/gravatar_service.rb | 4 ++-- spec/helpers/application_helper_spec.rb | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/services/gravatar_service.rb b/app/services/gravatar_service.rb index 4bee0c26a6..433ecc2df3 100644 --- a/app/services/gravatar_service.rb +++ b/app/services/gravatar_service.rb @@ -1,13 +1,13 @@ class GravatarService include Gitlab::CurrentSettings - def execute(email, size = nil) + def execute(email, size = nil, scale = 2) if current_application_settings.gravatar_enabled? && email.present? size = 40 if size.nil? || size <= 0 sprintf gravatar_url, hash: Digest::MD5.hexdigest(email.strip.downcase), - size: size, + size: size * scale, email: email.strip end end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 742420f550..e6e9bc99a1 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -141,15 +141,19 @@ describe ApplicationHelper 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=20&hash=b58c6f14d292556214bd64909bcdb118') + 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=64' + expect(helper.gravatar_icon(user_email, 64)).to include '?s=128' end - it 'defaults size to 40 when given an invalid size' do - expect(helper.gravatar_icon(user_email, nil)).to include '?s=40' + 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 From f93177c6a3bee4c02f6a9a18278db13dd1c30c35 Mon Sep 17 00:00:00 2001 From: Jan-Gerd Tenberge Date: Sat, 26 Sep 2015 21:25:04 +0200 Subject: [PATCH 002/410] Add scale parameter to helper --- app/helpers/application_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 39ab83ccf1..162dae1ed3 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -78,8 +78,8 @@ module ApplicationHelper end end - def gravatar_icon(user_email = '', size = nil) - GravatarService.new.execute(user_email, size) || + def gravatar_icon(user_email = '', size = nil, scale = 2) + GravatarService.new.execute(user_email, size, scale) || default_avatar end From 8d620e39c967739f99bd175449864524f05b915c Mon Sep 17 00:00:00 2001 From: Jan-Gerd Tenberge Date: Sat, 26 Sep 2015 21:51:02 +0200 Subject: [PATCH 003/410] Add scale argument in user class --- app/helpers/application_helper.rb | 4 ++-- app/models/user.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 162dae1ed3..056ffd278f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -68,13 +68,13 @@ module ApplicationHelper end end - def avatar_icon(user_email = '', size = nil) + def avatar_icon(user_email = '', size = nil, scale = 2) user = User.find_by(email: user_email) if user user.avatar_url(size) || default_avatar else - gravatar_icon(user_email, size) + gravatar_icon(user_email, size, scale) end end diff --git a/app/models/user.rb b/app/models/user.rb index 25371f9138..55c095d7d5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -633,11 +633,11 @@ class User < ActiveRecord::Base email.start_with?('temp-email-for-oauth') end - def avatar_url(size = nil) + def avatar_url(size = nil, scale = 2) if avatar.present? [gitlab_config.url, avatar.url].join else - GravatarService.new.execute(email, size) + GravatarService.new.execute(email, size, scale) end end From 5f47b61cef9c110c09eab57a9e5f8f32654f21bd Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 17 Oct 2015 07:19:04 -0700 Subject: [PATCH 004/410] Use a relative link instead of full URL with New Issue button to be consistent Relates to #3095 --- app/views/projects/buttons/_dropdown.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 4580c91269..fa8c2c599a 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -5,7 +5,7 @@ %ul.dropdown-menu.dropdown-menu-right.project-home-dropdown - if can?(current_user, :create_issue, @project) %li - = link_to url_for_new_issue do + = link_to url_for_new_issue(@project, only_path: true) do = icon('exclamation-circle fw') New issue - if can?(current_user, :create_merge_request, @project) From 7d7b6dbb9f2a8c6dbee8ab6eb0ab075705abcfbd Mon Sep 17 00:00:00 2001 From: Jan-Gerd Tenberge Date: Mon, 19 Oct 2015 01:21:21 +0200 Subject: [PATCH 005/410] Update spec --- spec/helpers/application_helper_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 742420f550..b5a29346dc 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -95,9 +95,9 @@ describe ApplicationHelper do end it 'should call gravatar_icon when no User exists with the given email' do - expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20) + expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2) - helper.avatar_icon('foo@example.com', 20) + helper.avatar_icon('foo@example.com', 20, 2) end end From 34d0cd438a7173c35c687ecb37eae1f3293b84b5 Mon Sep 17 00:00:00 2001 From: Jan-Gerd Tenberge Date: Mon, 19 Oct 2015 01:22:20 +0200 Subject: [PATCH 006/410] Update spec --- spec/helpers/application_helper_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index e6e9bc99a1..bed6658ce0 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -95,7 +95,7 @@ describe ApplicationHelper do end it 'should call gravatar_icon when no User exists with the given email' do - expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20) + expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20, 2) helper.avatar_icon('foo@example.com', 20) end From ab6d426a480ef45ba3d13db3049294304b33f6c0 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 19 Oct 2015 21:04:05 +0300 Subject: [PATCH 007/410] Add missing dot in confirm modal --- app/views/shared/_confirm_modal.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml index 5f51b0d450..2a44817e05 100644 --- a/app/views/shared/_confirm_modal.html.haml +++ b/app/views/shared/_confirm_modal.html.haml @@ -14,7 +14,7 @@ %br Please type %code.js-confirm-danger-match #{phrase} - to proceed or close this modal to cancel + to proceed or close this modal to cancel. .form-group = text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input' From 3d50b99d016af91c06a40f7a8bf4c298e241220a Mon Sep 17 00:00:00 2001 From: Dirceu Pereira Tiegs Date: Mon, 19 Oct 2015 20:25:35 -0200 Subject: [PATCH 008/410] Add option to create merge request when editing/creating a file --- .../javascripts/blob/edit_blob.js.coffee | 7 +++++ .../javascripts/blob/new_blob.js.coffee | 7 +++++ app/assets/stylesheets/pages/editor.scss | 4 +++ app/controllers/application_controller.rb | 30 +++++++++++++++++++ app/controllers/projects/blob_controller.rb | 18 +++++++++-- app/helpers/merge_requests_helper.rb | 28 ----------------- app/views/projects/blob/edit.html.haml | 8 +++++ app/views/projects/blob/new.html.haml | 8 +++++ 8 files changed, 80 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee index 050888f9c1..a6f65642d7 100644 --- a/app/assets/javascripts/blob/edit_blob.js.coffee +++ b/app/assets/javascripts/blob/edit_blob.js.coffee @@ -11,6 +11,13 @@ class @EditBlob if ace_mode editor.getSession().setMode "ace/mode/" + ace_mode + $('#new_branch').keyup -> + if $(this).val() != $('#original_branch').val() + $('.form-group.destination').show() + else + $('.form-group.destination').hide() + $('#create_merge_request').prop('checked', false) + $(".js-commit-button").click -> $("#file-content").val editor.getValue() $(".file-editor form").submit() diff --git a/app/assets/javascripts/blob/new_blob.js.coffee b/app/assets/javascripts/blob/new_blob.js.coffee index 1f36a53f19..f4e06ad088 100644 --- a/app/assets/javascripts/blob/new_blob.js.coffee +++ b/app/assets/javascripts/blob/new_blob.js.coffee @@ -11,6 +11,13 @@ class @NewBlob if ace_mode editor.getSession().setMode "ace/mode/" + ace_mode + $('#new_branch').keyup -> + if $(this).val() != $('#original_branch').val() + $('.form-group.destination').show() + else + $('.form-group.destination').hide() + $('#create_merge_request').prop('checked', false) + $(".js-commit-button").click -> $("#file-content").val editor.getValue() $(".file-editor form").submit() diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 1d565477dd..6bc9b1c3ea 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -63,4 +63,8 @@ margin-top: 0; padding: $gl-padding } + + .destination { + display: none; + } } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f0124c6bd6..258e37e98c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -22,6 +22,7 @@ class ApplicationController < ActionController::Base helper_method :abilities, :can?, :current_application_settings helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled? + helper_method :new_mr_from_push_event, :new_mr_path_for_fork_from_push_event, :new_mr_path_from_push_event rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -343,4 +344,33 @@ class ApplicationController < ActionController::Base def git_import_enabled? current_application_settings.import_sources.include?('git') end + + # new merge requests routing helpers + def new_mr_path_from_push_event(event, target_branch=nil) + target_project = event.project.forked_from_project || event.project + new_namespace_project_merge_request_path( + event.project.namespace, + event.project, + new_mr_from_push_event(event, target_project, target_branch) + ) + end + + def new_mr_path_for_fork_from_push_event(event, target_branch=nil) + new_namespace_project_merge_request_path( + event.project.namespace, + event.project, + new_mr_from_push_event(event, event.project.forked_from_project, target_branch) + ) + end + + def new_mr_from_push_event(event, target_project, target_branch) + { + merge_request: { + source_project_id: event.project.id, + target_project_id: target_project.id, + source_branch: event.branch_name, + target_branch: target_branch || target_project.repository.root_ref + } + } + end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 8cc2f21d88..f49c094b59 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -27,7 +27,14 @@ class Projects::BlobController < Projects::ApplicationController if result[:status] == :success flash[:notice] = "The changes have been successfully committed" respond_to do |format| - format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) } + format.html do + url = if params[:create_merge_request] + new_mr_path_from_push_event(current_user.recent_push(@project.id), @ref) + else + namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) + end + redirect_to url + end format.json { render json: { message: "success", filePath: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) } } end else @@ -52,7 +59,14 @@ class Projects::BlobController < Projects::ApplicationController if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" respond_to do |format| - format.html { redirect_to after_edit_path } + format.html do + url = if params[:create_merge_request] + new_mr_path_from_push_event(current_user.recent_push(@project.id), @ref) + else + after_edit_path + end + redirect_to url + end format.json { render json: { message: "success", filePath: after_edit_path } } end else diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 728d877ace..7e8f61fd27 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -1,32 +1,4 @@ module MergeRequestsHelper - def new_mr_path_from_push_event(event) - target_project = event.project.forked_from_project || event.project - new_namespace_project_merge_request_path( - event.project.namespace, - event.project, - new_mr_from_push_event(event, target_project) - ) - end - - def new_mr_path_for_fork_from_push_event(event) - new_namespace_project_merge_request_path( - event.project.namespace, - event.project, - new_mr_from_push_event(event, event.project.forked_from_project) - ) - end - - def new_mr_from_push_event(event, target_project) - { - merge_request: { - source_project_id: event.project.id, - target_project_id: target_project.id, - source_branch: event.branch_name, - target_branch: target_project.repository.root_ref - } - } - end - def mr_css_classes(mr) classes = "merge-request" classes << " closed" if mr.closed? diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index a811adc509..2621646270 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -23,9 +23,17 @@ .col-sm-10 = text_field_tag 'new_branch', @ref, class: "form-control" + .form-group.destination + .col-sm-offset-2.col-sm-10 + .checkbox + = label_tag :create_merge_request do + = check_box_tag :create_merge_request, 1, false + Start a new merge request + = hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] + = hidden_field_tag 'original_branch', @ref = render 'projects/commit_button', ref: @ref, cancel_path: @after_edit_path :javascript diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 7975137c37..0439e55131 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -17,7 +17,15 @@ .col-sm-10 = text_field_tag 'new_branch', @ref, class: "form-control js-quick-submit" + .form-group.destination + .col-sm-offset-2.col-sm-10 + .checkbox + = label_tag :create_merge_request do + = check_box_tag :create_merge_request, 1, false + Start a new merge request + = hidden_field_tag 'content', '', id: 'file-content' + = hidden_field_tag 'original_branch', @ref = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_tree_path(@project.namespace, @project, @id) From c5146ca1517412b0389c483f18c13072e2abc4a3 Mon Sep 17 00:00:00 2001 From: Jan-Gerd Tenberge Date: Fri, 23 Oct 2015 03:00:08 +0200 Subject: [PATCH 009/410] Fix merge error --- app/helpers/application_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 596a47938d..3fa7d2bb1c 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -68,7 +68,7 @@ module ApplicationHelper end end - def avatar_icon(user_email = nil, size = nil, scale = 2) + def avatar_icon(user_or_email = nil, size = nil, scale = 2) if user_or_email.is_a?(User) user = user_or_email else From 3bb626f91cb50bd2eff58681e22db942b7d6a087 Mon Sep 17 00:00:00 2001 From: James Newton Date: Wed, 28 Oct 2015 16:39:23 +0100 Subject: [PATCH 010/410] refactor login as to be impersonation with better login/logout Modifies the existing "login as" feature to be called impersonation, as well as keeping track of who is impersonating to revert back to that user without having to log out. --- app/assets/stylesheets/framework/header.scss | 4 ++ .../admin/application_controller.rb | 6 +++ .../admin/impersonation_controller.rb | 32 ++++++++++++ app/controllers/admin/users_controller.rb | 6 --- app/views/admin/users/_head.html.haml | 2 +- app/views/layouts/header/_default.html.haml | 4 ++ config/routes.rb | 4 +- .../admin/users_controller_spec.rb | 15 ------ spec/features/admin/admin_users_spec.rb | 50 ++++++++++++++----- 9 files changed, 88 insertions(+), 35 deletions(-) create mode 100644 app/controllers/admin/impersonation_controller.rb diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 91e6975e26..02ea91602e 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -118,6 +118,10 @@ header { } } } + + .impersonation i { + color: $red-normal; + } } @mixin collapsed-header { diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb index 56e2438646..9083bfb41c 100644 --- a/app/controllers/admin/application_controller.rb +++ b/app/controllers/admin/application_controller.rb @@ -8,4 +8,10 @@ class Admin::ApplicationController < ApplicationController def authenticate_admin! return render_404 unless current_user.is_admin? end + + def authorize_impersonator! + if session[:impersonator_id] + User.find_by!(username: session[:impersonator_id]).admin? + end + end end diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb new file mode 100644 index 0000000000..0382402afa --- /dev/null +++ b/app/controllers/admin/impersonation_controller.rb @@ -0,0 +1,32 @@ +class Admin::ImpersonationController < Admin::ApplicationController + skip_before_action :authenticate_admin!, only: :destroy + + before_action :user + before_action :authorize_impersonator! + + def create + session[:impersonator_id] = current_user.username + session[:impersonator_return_to] = request.env['HTTP_REFERER'] + + warden.set_user(user, scope: 'user') + + flash[:alert] = "You are impersonating #{user.username}." + + redirect_to root_path + end + + def destroy + redirect = session[:impersonator_return_to] + + warden.set_user(user, scope: 'user') + + session[:impersonator_return_to] = nil + session[:impersonator_id] = nil + + redirect_to redirect || root_path + end + + def user + @user ||= User.find_by!(username: params[:id] || session[:impersonator_id]) + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index c63d0793e3..d7c927d444 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -63,12 +63,6 @@ class Admin::UsersController < Admin::ApplicationController end end - def login_as - sign_in(user) - flash[:alert] = "Logged in as #{user.username}" - redirect_to root_path - end - def disable_two_factor user.disable_two_factor! redirect_to admin_user_path(user), diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml index 4245d0f1ed..8d1cab4137 100644 --- a/app/views/admin/users/_head.html.haml +++ b/app/views/admin/users/_head.html.haml @@ -7,7 +7,7 @@ .pull-right - unless @user == current_user - = link_to 'Log in as this user', login_as_admin_user_path(@user), method: :post, class: "btn btn-grouped btn-info" + = link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-grouped btn-info" = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do %i.fa.fa-pencil-square-o Edit diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index c31b1cbe9a..13eeb0fe20 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -13,6 +13,10 @@ %li.visible-sm.visible-xs = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('search') + - if session[:impersonator_id] + %li.impersonation + = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop impersonation', data: { toggle: 'tooltip', placement: 'bottom' } do + = icon('user-secret fw') - if current_user.is_admin? %li = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do diff --git a/config/routes.rb b/config/routes.rb index f6812c9280..6ebedaefaf 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -212,6 +212,8 @@ Gitlab::Application.routes.draw do resources :keys, only: [:show, :destroy] resources :identities, only: [:index, :edit, :update, :destroy] + delete 'stop_impersonation' => 'impersonation#destroy', on: :collection + member do get :projects get :keys @@ -221,7 +223,7 @@ Gitlab::Application.routes.draw do put :unblock put :unlock put :confirm - post :login_as + post 'impersonate' => 'impersonation#create' patch :disable_two_factor delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' end diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb index fcbe62cace..8b7af4d3a0 100644 --- a/spec/controllers/admin/users_controller_spec.rb +++ b/spec/controllers/admin/users_controller_spec.rb @@ -7,21 +7,6 @@ describe Admin::UsersController do sign_in(admin) end - describe 'POST login_as' do - let(:user) { create(:user) } - - it 'logs admin as another user' do - expect(warden.authenticate(scope: :user)).not_to eq(user) - post :login_as, id: user.username - expect(warden.authenticate(scope: :user)).to eq(user) - end - - it 'redirects user to homepage' do - post :login_as, id: user.username - expect(response).to redirect_to(root_path) - end - end - describe 'DELETE #user with projects' do let(:user) { create(:user) } let(:project) { create(:project, namespace: user.namespace) } diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index c2c7364f6c..4c756a8e73 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -111,24 +111,50 @@ describe "Admin::Users", feature: true do expect(page).to have_content(@user.name) end - describe 'Login as another user' do - it 'should show login button for other users and check that it works' do - another_user = create(:user) + describe 'Impersonation' do + let(:another_user) { create(:user) } + before { visit admin_user_path(another_user) } - visit admin_user_path(another_user) + context 'before impersonating' do + it 'shows impersonate button for other users' do + expect(page).to have_content('Impersonate') + end - click_link 'Log in as this user' + it 'should not show impersonate button for admin itself' do + visit admin_user_path(@user) - expect(page).to have_content("Logged in as #{another_user.username}") - - page.within '.sidebar-user .username' do - expect(page).to have_content(another_user.username) + expect(page).not_to have_content('Impersonate') end end - it 'should not show login button for admin itself' do - visit admin_user_path(@user) - expect(page).not_to have_content('Log in as this user') + context 'when impersonating' do + before { click_link 'Impersonate' } + + it 'logs in as the user when impersonate is clicked' do + page.within '.sidebar-user .username' do + expect(page).to have_content(another_user.username) + end + end + + it 'sees impersonation log out icon' do + icon = first('.fa.fa-user-secret') + + expect(icon).to_not eql nil + end + + it 'can log out of impersonated user back to original user' do + find(:css, 'li.impersonation a').click + + page.within '.sidebar-user .username' do + expect(page).to have_content(@user.username) + end + end + + it 'is redirected back to the impersonated users page in the admin after stopping' do + find(:css, 'li.impersonation a').click + + expect(current_path).to eql "/admin/users/#{another_user.username}" + end end end From 0574166a6117fa5eccc21f632b7f5ab528796fd6 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 29 Oct 2015 13:00:20 +0000 Subject: [PATCH 011/410] Fix grouping of contributors by email in graph. --- .../javascripts/stat_graph_contributors_util.js.coffee | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/stat_graph_contributors_util.js.coffee b/app/assets/javascripts/stat_graph_contributors_util.js.coffee index cfe5508290..f5584bcfe4 100644 --- a/app/assets/javascripts/stat_graph_contributors_util.js.coffee +++ b/app/assets/javascripts/stat_graph_contributors_util.js.coffee @@ -6,7 +6,7 @@ window.ContributorsStatGraphUtil = for entry in log @add_date(entry.date, total) unless total[entry.date]? - data = by_author[entry.author_name] #|| by_email[entry.author_email] + data = by_author[entry.author_name] || by_email[entry.author_email] data ?= @add_author(entry, by_author, by_email) @add_date(entry.date, data) unless data[entry.date] @@ -95,5 +95,4 @@ window.ContributorsStatGraphUtil = if date_range is null || date_range[0] <= new Date(date) <= date_range[1] true else - false - + false \ No newline at end of file From bb8b6f38e7fef0afd41088d54d9dda41695ec1c7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 29 Oct 2015 13:02:11 +0000 Subject: [PATCH 012/410] Update changelog --- CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 0d89fca9fc..ff618ea582 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.2.0 (unreleased) + - Fix grouping of contributors by email in graph. - Improved performance of replacing references in comments - Show last project commit to default branch on project home page - Highlight comment based on anchor in URL @@ -1902,4 +1903,4 @@ v 0.8.0 - stability - security fixes - increased test coverage - - email notification + - email notification \ No newline at end of file From 41734b630ed86c5aa0814cb331f4f8e94e0bf2a2 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Thu, 29 Oct 2015 14:13:30 +0100 Subject: [PATCH 013/410] Ensure variable is evalled --- app/views/notify/project_was_moved_email.text.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb index d8a23dabf4..b2c5f71e46 100644 --- a/app/views/notify/project_was_moved_email.text.erb +++ b/app/views/notify/project_was_moved_email.text.erb @@ -1,4 +1,4 @@ -Project #{@old_path_with_namespace} was moved to another location +Project <%= @old_path_with_namespace %> was moved to another location The project is now located under <%= namespace_project_url(@project.namespace, @project) %> From de990aa15829d0ab182ad5a55b4c527846c0d39c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 29 Oct 2015 16:10:27 +0000 Subject: [PATCH 014/410] fixed last group owner issue and added test --- app/models/member.rb | 9 +++++---- features/groups.feature | 8 ++++++++ features/steps/groups.rb | 18 ++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/app/models/member.rb b/app/models/member.rb index cae8caa23f..bc7e70178e 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -81,11 +81,12 @@ class Member < ActiveRecord::Base member = members.build member.invite_email = user end + if !current_user || current_user.can?(:update_group_member, member) + member.created_by ||= current_user + member.access_level = access_level - member.created_by ||= current_user - member.access_level = access_level - - member.save + member.save + end end end diff --git a/features/groups.feature b/features/groups.feature index db37fa3b37..7777c0298a 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -59,6 +59,14 @@ Feature: Groups When I select "Mike" as "Reporter" Then I should see "Mike" in team list as "Reporter" + @javascript + Scenario: Ignore add user to group when is already Owner + Given gitlab user "Mike" + When I visit group "Owned" members page + And I click link "Add members" + When I select "Mike" as "Reporter" + Then I should see "Mike" in team list as "Owner" + @javascript Scenario: Invite user to group When I visit group "Owned" members page diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 69ddfa42c0..5fd4dea5cd 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -48,6 +48,17 @@ class Spinach::Features::Groups < Spinach::FeatureSteps click_button "Add users to group" end + step 'I select "Mike" as "Master"' do + user = User.find_by(name: "Mike") + + page.within ".users-group-form" do + select2(user.id, from: "#user_ids", multiple: true) + select "Master", from: "access_level" + end + + click_button "Add users to group" + end + step 'I should see "Mike" in team list as "Reporter"' do page.within '.well-list' do expect(page).to have_content('Mike') @@ -55,6 +66,13 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end end + step 'I should see "Mike" in team list as "Owner"' do + page.within '.well-list' do + expect(page).to have_content('Mike') + expect(page).to have_content('Owner') + end + end + step 'I select "sjobs@apple.com" as "Reporter"' do page.within ".users-group-form" do select2("sjobs@apple.com", from: "#user_ids", multiple: true) From c8fe42151291593f0f43509a70235c46fce169a1 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 29 Oct 2015 18:42:29 -0200 Subject: [PATCH 015/410] Improve personal snippet access workflow. Fixes #3258 --- CHANGELOG | 1 + app/controllers/snippets_controller.rb | 9 +- app/models/ability.rb | 65 +++++++--- spec/controllers/snippets_controller_spec.rb | 118 +++++++++++++++++++ spec/factories.rb | 12 ++ 5 files changed, 188 insertions(+), 17 deletions(-) create mode 100644 spec/controllers/snippets_controller_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 0d89fca9fc..325a073b5c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.2.0 (unreleased) - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - Remove deprecated CI events from project settings page - Use issue editor as cross reference comment author when issue is edited with a new mention. + - Improve personal snippet access workflow v 8.1.1 - Fix cloning Wiki repositories via HTTP (Stan Hu) diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 9f9f9a92f1..8498efc89d 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -1,6 +1,9 @@ class SnippetsController < ApplicationController before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] + # Allow read snippet + before_action :authorize_show_snippet!, only: [:show] + # Allow modify snippet before_action :authorize_update_snippet!, only: [:edit, :update] @@ -79,10 +82,14 @@ class SnippetsController < ApplicationController [Snippet::PUBLIC, Snippet::INTERNAL]). find(params[:id]) else - PersonalSnippet.are_public.find(params[:id]) + PersonalSnippet.find(params[:id]) end end + def authorize_show_snippet! + authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet) + end + def authorize_update_snippet! return render_404 unless can?(current_user, :update_personal_snippet, @snippet) end diff --git a/app/models/ability.rb b/app/models/ability.rb index b72178fa12..ee2f7b5f58 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -22,12 +22,17 @@ class Ability # List of possible abilities # for non-authenticated user def not_auth_abilities(user, subject) + return not_auth_personal_snippet_abilities(subject) if subject.kind_of?(PersonalSnippet) + return not_auth_project_abilities(subject) if subject.kind_of?(Project) || subject.respond_to?(:project) + return not_auth_group_abilities(subject) if subject.kind_of?(Group) || subject.respond_to?(:group) + [] + end + + def not_auth_project_abilities(subject) project = if subject.kind_of?(Project) subject - elsif subject.respond_to?(:project) - subject.project else - nil + subject.project end if project && project.public? @@ -47,19 +52,29 @@ class Ability rules - project_disabled_features_rules(project) else - group = if subject.kind_of?(Group) - subject - elsif subject.respond_to?(:group) - subject.group - else - nil - end + [] + end + end - if group && group.public_profile? - [:read_group] - else - [] - end + def not_auth_group_abilities(subject) + group = if subject.kind_of?(Group) + subject + else + subject.group + end + + if group && group.public_profile? + [:read_group] + else + [] + end + end + + def not_auth_personal_snippet_abilities(snippet) + if snippet.public? + [:read_personal_snippet] + else + [] end end @@ -278,7 +293,7 @@ class Ability end end - [:note, :project_snippet, :personal_snippet].each do |name| + [:note, :project_snippet].each do |name| define_method "#{name}_abilities" do |user, subject| rules = [] @@ -298,6 +313,24 @@ class Ability end end + def personal_snippet_abilities(user, snippet) + rules = [] + + if snippet.author == user + rules += [ + :read_personal_snippet, + :update_personal_snippet, + :admin_personal_snippet + ] + end + + if snippet.public? || snippet.internal? + rules.push(:read_snippet) + end + + rules + end + def group_member_abilities(user, subject) rules = [] target_user = subject.user diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb new file mode 100644 index 0000000000..e9b823c523 --- /dev/null +++ b/spec/controllers/snippets_controller_spec.rb @@ -0,0 +1,118 @@ +require 'spec_helper' + +describe SnippetsController do + describe 'GET #show' do + let(:user) { create(:user) } + + context 'when the personal snippet is private' do + let(:personal_snippet) { create(:personal_snippet, :private, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + context 'when signed in user is not the author' do + let(:other_author) { create(:author) } + let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) } + + it 'responds with status 404' do + get :show, id: other_personal_snippet.to_param + + expect(response.status).to eq(404) + end + end + + context 'when signed in user is the author' do + it 'renders the snippet' do + get :show, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + end + + context 'when not signed in' do + it 'redirects to the sign in page' do + get :show, id: personal_snippet.to_param + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context 'when the personal snippet is internal' do + let(:personal_snippet) { create(:personal_snippet, :internal, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + it 'renders the snippet' do + get :show, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + + context 'when not signed in' do + it 'redirects to the sign in page' do + get :show, id: personal_snippet.to_param + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context 'when the personal snippet is public' do + let(:personal_snippet) { create(:personal_snippet, :public, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + it 'renders the snippet' do + get :show, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + + context 'when not signed in' do + it 'renders the snippet' do + get :show, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + end + + context 'when the personal snippet does not exist' do + context 'when signed in' do + before do + sign_in(user) + end + + it 'responds with status 404' do + get :show, id: 'doesntexist' + + expect(response.status).to eq(404) + end + end + + context 'when not signed in' do + it 'responds with status 404' do + get :show, id: 'doesntexist' + + expect(response.status).to eq(404) + end + end + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 200f18f660..4bf93adabe 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -165,6 +165,18 @@ FactoryGirl.define do title content file_name + + trait :public do + visibility_level Gitlab::VisibilityLevel::PUBLIC + end + + trait :internal do + visibility_level Gitlab::VisibilityLevel::INTERNAL + end + + trait :private do + visibility_level Gitlab::VisibilityLevel::PRIVATE + end end factory :snippet do From 6aa9c21ac0e3f4860f9021718900326ea0575151 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 30 Oct 2015 19:55:19 +0000 Subject: [PATCH 016/410] fix issue with adding members to project (spotted by test) --- app/models/member.rb | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/models/member.rb b/app/models/member.rb index bc7e70178e..4651c8fff3 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -73,7 +73,7 @@ class Member < ActiveRecord::Base def add_user(members, user_id, access_level, current_user = nil) user = user_for_id(user_id) - + # `user` can be either a User object or an email to be invited if user.is_a?(User) member = members.find_or_initialize_by(user_id: user.id) @@ -81,13 +81,22 @@ class Member < ActiveRecord::Base member = members.build member.invite_email = user end - if !current_user || current_user.can?(:update_group_member, member) + + project = members.first.respond_to?(:project)? members.first.project : nil + if can_update_member?(current_user, member, project) member.created_by ||= current_user member.access_level = access_level member.save end end + + private + + def can_update_member?(current_user, member, project) + !current_user || current_user.can?(:update_group_member, member) || + (project && current_user.can?(:admin_project_member, project)) + end end def invite? From 3b717c8a8c1e0f10bc06fd8501ce2423c98490d4 Mon Sep 17 00:00:00 2001 From: Jan-Gerd Tenberge Date: Sun, 1 Nov 2015 02:42:36 +0100 Subject: [PATCH 017/410] Fix typo --- app/helpers/application_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 3fa7d2bb1c..3230ff1b00 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -78,7 +78,7 @@ module ApplicationHelper if user user.avatar_url(size) || default_avatar else - gravatar_icon(user_or_email, size, scales) + gravatar_icon(user_or_email, size, scale) end end From 93672c502010787be9102b25a4f93722526968b9 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 2 Nov 2015 14:03:42 -0200 Subject: [PATCH 018/410] Use `read` rather than `show` like the ability name --- app/controllers/snippets_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 8498efc89d..08f2483af3 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -2,7 +2,7 @@ class SnippetsController < ApplicationController before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] # Allow read snippet - before_action :authorize_show_snippet!, only: [:show] + before_action :authorize_read_snippet!, only: [:show] # Allow modify snippet before_action :authorize_update_snippet!, only: [:edit, :update] @@ -86,7 +86,7 @@ class SnippetsController < ApplicationController end end - def authorize_show_snippet! + def authorize_read_snippet! authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet) end From 091979bd1b18f53c246415439d05db9aaf078828 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 2 Nov 2015 14:04:45 -0200 Subject: [PATCH 019/410] Fix ability name for public or internal personal snippets --- app/models/ability.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index ee2f7b5f58..06c199bd8d 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -325,7 +325,7 @@ class Ability end if snippet.public? || snippet.internal? - rules.push(:read_snippet) + rules.push(:read_personal_snippet) end rules From 1b14bc59570a625365fef232f8c57919f76b3e2a Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 3 Nov 2015 11:11:56 +0000 Subject: [PATCH 020/410] refactored permissions and added update_project_member ability logic. Also refactored owner methods to a concern. --- app/models/ability.rb | 18 ++++++++++++++++++ app/models/concerns/has_owners.rb | 31 +++++++++++++++++++++++++++++++ app/models/group.rb | 22 ++-------------------- app/models/member.rb | 8 ++++---- app/models/project.rb | 2 ++ 5 files changed, 57 insertions(+), 24 deletions(-) create mode 100644 app/models/concerns/has_owners.rb diff --git a/app/models/ability.rb b/app/models/ability.rb index b72178fa12..5beead0b75 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -15,6 +15,7 @@ class Ability when "Group" then group_abilities(user, subject) when "Namespace" then namespace_abilities(user, subject) when "GroupMember" then group_member_abilities(user, subject) + when "ProjectMember" then project_member_abilities(user, subject) else [] end.concat(global_abilities(user)) end @@ -316,6 +317,23 @@ class Ability rules end + def project_member_abilities(user, subject) + rules = [] + target_user = subject.user + project = subject.project + can_manage = project_abilities(user, project).include?(:admin_project_member) + + if can_manage && (user != target_user) + rules << :update_project_member + rules << :destroy_project_member + end + + if !project.last_owner?(user) && (can_manage || (user == target_user)) + rules << :destroy_project_member + end + rules + end + def abilities @abilities ||= begin abilities = Six.new diff --git a/app/models/concerns/has_owners.rb b/app/models/concerns/has_owners.rb new file mode 100644 index 0000000000..735b207172 --- /dev/null +++ b/app/models/concerns/has_owners.rb @@ -0,0 +1,31 @@ +# == Owners concern +# +# Contains owners functionality for groups +# +module HasOwners + extend ActiveSupport::Concern + + def owners + @owners ||= my_members.owners.includes(:user).map(&:user) + end + + def my_members + raise NotImplementedError, "Expected my_members to be defined in #{self.class.name}" + end + + def add_owner(user, current_user = nil) + add_user(user, Gitlab::Access::OWNER, current_user) + end + + def has_owner?(user) + owners.include?(user) + end + + def has_master?(user) + members.masters.where(user_id: user).any? + end + + def last_owner?(user) + has_owner?(user) && owners.size == 1 + end +end diff --git a/app/models/group.rb b/app/models/group.rb index 465c22d23a..c9806f6fd6 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -19,8 +19,10 @@ require 'file_size_validator' class Group < Namespace include Gitlab::ConfigHelper include Referable + include HasOwners has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' + alias_method :my_members, :group_members has_many :users, through: :group_members validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } @@ -63,10 +65,6 @@ class Group < Namespace end end - def owners - @owners ||= group_members.owners.includes(:user).map(&:user) - end - def add_users(user_ids, access_level, current_user = nil) user_ids.each do |user_id| Member.add_user(self.group_members, user_id, access_level, current_user) @@ -93,22 +91,6 @@ class Group < Namespace add_user(user, Gitlab::Access::MASTER, current_user) end - def add_owner(user, current_user = nil) - add_user(user, Gitlab::Access::OWNER, current_user) - end - - def has_owner?(user) - owners.include?(user) - end - - def has_master?(user) - members.masters.where(user_id: user).any? - end - - def last_owner?(user) - has_owner?(user) && owners.size == 1 - end - def members group_members end diff --git a/app/models/member.rb b/app/models/member.rb index 4651c8fff3..c565ee6bbc 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -82,8 +82,7 @@ class Member < ActiveRecord::Base member.invite_email = user end - project = members.first.respond_to?(:project)? members.first.project : nil - if can_update_member?(current_user, member, project) + if can_update_member?(current_user, member) member.created_by ||= current_user member.access_level = access_level @@ -93,9 +92,10 @@ class Member < ActiveRecord::Base private - def can_update_member?(current_user, member, project) + def can_update_member?(current_user, member) !current_user || current_user.can?(:update_group_member, member) || - (project && current_user.can?(:admin_project_member, project)) + (member.respond_to?(:project) && + current_user.can?(:update_project_member, member)) end end diff --git a/app/models/project.rb b/app/models/project.rb index 74b89aad49..79b7a6457d 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -41,6 +41,7 @@ class Project < ActiveRecord::Base include Sortable include AfterCommitQueue include CaseSensitivity + include HasOwners extend Gitlab::ConfigHelper extend Enumerize @@ -114,6 +115,7 @@ class Project < ActiveRecord::Base has_many :hooks, dependent: :destroy, class_name: 'ProjectHook' has_many :protected_branches, dependent: :destroy has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember' + alias_method :my_members, :project_members has_many :users, through: :project_members has_many :deploy_keys_projects, dependent: :destroy has_many :deploy_keys, through: :deploy_keys_projects From 798873ca75e656ed0fbd2a3080022eb55a6f3106 Mon Sep 17 00:00:00 2001 From: adamliesko Date: Mon, 9 Nov 2015 17:26:01 +0100 Subject: [PATCH 021/410] Add notification to the former assignee upon unassignment --- CHANGELOG | 1 + app/services/notification_service.rb | 6 ++++-- spec/services/issues/update_service_spec.rb | 10 ++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 217dc4e004..c8db4da1d7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ v 8.2.0 (unreleased) - Add "added", "modified" and "removed" properties to commit object in webhook - Rename "Back to" links to "Go to" because its not always a case it point to place user come from - Allow groups to appear in the search results if the group owner allows it + - Add email notification to former assignee upon unassignment v 8.1.3 - Spread out runner contacted_at updates diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index a6b2234865..16c84f4a05 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -362,7 +362,8 @@ class NotificationService def reassign_resource_email(target, project, current_user, method) assignee_id_was = previous_record(target, "assignee_id") - recipients = build_recipients(target, project, current_user) + previous_assignee = User.find(assignee_id_was) + recipients = build_recipients(target, project, current_user, [previous_assignee]) recipients.each do |recipient| mailer.send(method, recipient.id, target.id, assignee_id_was, current_user.id) @@ -377,8 +378,9 @@ class NotificationService end end - def build_recipients(target, project, current_user) + def build_recipients(target, project, current_user, previous_records = nil ) recipients = target.participants(current_user) + recipients.concat(previous_records).compact.uniq if previous_records recipients = add_project_watchers(recipients, project) recipients = reject_mention_users(recipients, project) diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index a91be3b447..4e79484f26 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Issues::UpdateService do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:issue) { create(:issue, title: 'Old title') } + let(:issue) { create(:issue, title: 'Old title', assignee_id: user.id) } let(:label) { create(:label) } let(:project) { issue.project } @@ -34,9 +34,11 @@ describe Issues::UpdateService do it { expect(@issue.labels.count).to eq(1) } it { expect(@issue.labels.first.title).to eq('Bug') } - it 'should send email to user2 about assign of new issue' do - email = ActionMailer::Base.deliveries.last - expect(email.to.first).to eq(user2.email) + it 'should send email to user2 about assign of new issue and email to user about issue unassignment' do + deliveries = ActionMailer::Base.deliveries + email = deliveries.last + recipients = deliveries.map(&:to).uniq.flatten + expect(recipients.last(2)).to include(user.email,user2.email) expect(email.subject).to include(issue.title) end From d024db0cc816d03063f889a6d3d570f70e8e896c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 10 Nov 2015 12:14:32 +0100 Subject: [PATCH 022/410] Remove deprecated dumped yaml file generated from previous job definitions --- CHANGELOG | 1 + app/controllers/ci/projects_controller.rb | 4 ---- app/views/projects/ci_settings/edit.html.haml | 19 ------------------- config/routes.rb | 1 - 4 files changed, 1 insertion(+), 24 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d11076f484..2d866a5c6c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ v 8.2.0 (unreleased) - Rename "Back to" links to "Go to" because its not always a case it point to place user come from - Allow groups to appear in the search results if the group owner allows it - New design for project graphs page + - Remove deprecated dumped yaml file generated from previous job definitions - Fix incoming email config defaults v 8.1.4 diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb index 809b44387b..8406399fb6 100644 --- a/app/controllers/ci/projects_controller.rb +++ b/app/controllers/ci/projects_controller.rb @@ -26,10 +26,6 @@ module Ci redirect_to namespace_project_runners_path(project.gl_project.namespace, project.gl_project) end - def dumped_yaml - send_data @project.generated_yaml_config, filename: '.gitlab-ci.yml' - end - protected def project diff --git a/app/views/projects/ci_settings/edit.html.haml b/app/views/projects/ci_settings/edit.html.haml index 665556f5c2..acc912d459 100644 --- a/app/views/projects/ci_settings/edit.html.haml +++ b/app/views/projects/ci_settings/edit.html.haml @@ -1,25 +1,6 @@ - page_title "CI Settings" -- if @ci_project.generated_yaml_config - %p.alert.alert-danger - CI Jobs are deprecated now, you can #{link_to "download", dumped_yaml_ci_project_path(@ci_project)} - or - %a.preview-yml{:href => "#yaml-content", "data-toggle" => "modal"} preview - yaml file which is based on your old jobs. - Put this file to the root of your project and name it .gitlab-ci.yml - if no_runners_for_project?(@ci_project) = render 'no_runners' = render 'form' - -- if @ci_project.generated_yaml_config - #yaml-content.modal.fade{"aria-hidden" => "true", "aria-labelledby" => ".gitlab-ci.yml", :role => "dialog", :tabindex => "-1"} - .modal-dialog - .modal-content - .modal-header - %button.close{"aria-hidden" => "true", "data-dismiss" => "modal", :type => "button"} × - %h4.modal-title Content of .gitlab-ci.yml - .modal-body - = text_area_tag :yaml, @ci_project.generated_yaml_config, size: "70x25", class: "form-control" - .modal-footer - %button.btn.btn-default{"data-dismiss" => "modal", :type => "button"} Close diff --git a/config/routes.rb b/config/routes.rb index 2028ea938e..dfc1c571ce 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -32,7 +32,6 @@ Gitlab::Application.routes.draw do get :status, to: 'projects#badge' get :integration post :toggle_shared_runners - get :dumped_yaml end resources :runner_projects, only: [:create, :destroy] From 97380977221865308d5336da0ea0d49e5c45d03a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 10 Nov 2015 07:52:35 -0800 Subject: [PATCH 023/410] Fix avatars not showing in Atom feeds and project issues when Gravatar disabled Fix for https://github.com/gitlabhq/gitlabhq/pull/9783 --- CHANGELOG | 1 + app/helpers/events_helper.rb | 2 +- app/helpers/issues_helper.rb | 2 +- app/views/projects/commits/show.atom.builder | 2 +- app/views/projects/notes/_note.html.haml | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d82cab6f36..9d2af96ede 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.2.0 (unreleased) - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) + - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu) - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) - Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu) - Improved performance of finding users by one of their Email addresses diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 6f69c2a9f3..51b872b7a9 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -198,7 +198,7 @@ module EventsHelper xml.link href: event_link xml.title truncate(event_title, length: 80) xml.updated event.created_at.xmlschema - xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(event.author_email) + xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(event.author_email)) xml.author do |author| xml.name event.author_name xml.email event.author_email diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index fda18e7b31..beb083d82d 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -74,7 +74,7 @@ module IssuesHelper issue.project, issue) xml.title truncate(issue.title, length: 80) xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(issue.author_email) + xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email)) xml.author do |author| xml.name issue.author_name xml.email issue.author_email diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder index 3854ad5d61..268b9b815e 100644 --- a/app/views/projects/commits/show.atom.builder +++ b/app/views/projects/commits/show.atom.builder @@ -12,7 +12,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link href: namespace_project_commit_url(@project.namespace, @project, id: commit.id) xml.title truncate(commit.title, length: 80) xml.updated commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(commit.author_email) + xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(commit.author_email)) xml.author do |author| xml.name commit.author_name xml.email commit.author_email diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 5d18473079..8880830198 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -2,7 +2,7 @@ .timeline-entry-inner .timeline-icon %a{href: user_path(note.author)} - %img.avatar.s40{src: avatar_icon(note.author), alt: ''} + = image_tag avatar_icon(note.author), alt: '', class: 'avatar s40' .timeline-content .note-header - if note_editable?(note) From 88836e2ab6664f0cf7b3dc2fa0d4f3d798aeaaa6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 10 Nov 2015 22:31:16 +0200 Subject: [PATCH 024/410] Fix bottom position of scroll buttons in build log page --- app/assets/stylesheets/pages/builds.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 74dc3e321c..da9965f007 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -21,7 +21,7 @@ .autoscroll-container { position: fixed; - bottom: 10px; + bottom: 20px; right: 20px; z-index: 100; } @@ -34,7 +34,7 @@ a { display: block; - margin-bottom: 5px; + margin-bottom: 10px; } } From 752d528019fc9f9c58d458380a6594d358458b4d Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Mon, 14 Sep 2015 12:07:50 -0500 Subject: [PATCH 025/410] Fix trailing space issue with merge requests and issues. Fixes #2514 --- CHANGELOG | 1 + app/controllers/projects/issues_controller.rb | 4 +++- app/controllers/projects/merge_requests_controller.rb | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9cfcd23a9f..939213a526 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.2.0 (unreleased) - Fix incoming email config defaults - MR target branch is now visible on a list view when it is different from project's default one - Improve Continuous Integration graphs page + - Fix trailing whitespace issue in merge request/issue title v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index e767efbdc0..e74c2905e4 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -158,10 +158,12 @@ class Projects::IssuesController < Projects::ApplicationController end def issue_params - params.require(:issue).permit( + permitted = params.require(:issue).permit( :title, :assignee_id, :position, :description, :milestone_id, :state_event, :task_num, label_ids: [] ) + params[:issue][:title].strip! if params[:issue][:title] + permitted end def bulk_update_params diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index b0788a2d07..188f0cc4ce 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -276,11 +276,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def merge_request_params - params.require(:merge_request).permit( + permitted = params.require(:merge_request).permit( :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :state_event, :description, :task_num, label_ids: [] ) + params[:merge_request][:title].strip! if params[:merge_request][:title] + permitted end # Make sure merge requests created before 8.0 From 73f8763e6a249c4c4be731673556e7e2d625ee42 Mon Sep 17 00:00:00 2001 From: Andy Brandt Date: Tue, 10 Nov 2015 15:42:17 -0600 Subject: [PATCH 026/410] fix build link --- app/views/projects/builds/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 3374d5432a..37f33edd47 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -168,7 +168,7 @@ %td = ci_icon_for_status(build.status) %td - = link_to namespace_project_build_path(@project.namespace, @project, @build) do + = link_to namespace_project_build_path(@project.namespace, @project, build) do - if build.name = build.name - else From 58074ab7da9c24c030054e6303f9020f1d1f6f83 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Tue, 10 Nov 2015 22:48:13 +0100 Subject: [PATCH 027/410] Allow to define cache in `.gitlab-ci.yml` --- CHANGELOG | 1 + doc/ci/yaml/README.md | 66 ++++++++++++++++ lib/ci/gitlab_ci_yaml_processor.rb | 30 ++++++- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 82 ++++++++++++++++++++ 4 files changed, 175 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e54e7329ca..1454f70858 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.2.0 (unreleased) - Show merge request CI status on merge requests index page - Extend yml syntax for only and except to support specifying repository path - Enable shared runners to all new projects + - Allow to define cache in `.gitlab-ci.yml` - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - Remove deprecated CI events from project settings page - [API] Add ability to fetch the commit ID of the last commit that actually touched a file diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 5d35d1da4e..3e6071a2ee 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -56,6 +56,7 @@ There are a few `keywords` that can't be used as job names: | types | optional | Alias for `stages` | | before_script | optional | Define commands prepended for each job's script | | variables | optional | Define build variables | +| cache | optional | Define list of files that should be cached between subsequent runs | ### image and services This allows to specify a custom Docker image and a list of services that can be used for time of the build. @@ -110,6 +111,19 @@ These variables can be later used in all executed commands and scripts. The YAML-defined variables are also set to all created service containers, thus allowing to fine tune them. +### cache +`cache` is used to specify list of files and directories which should be cached between builds. + +**The global setting allows to specify default cached files for all jobs.** + +To cache all git untracked files and files in `binaries`: +``` +cache: + untracked: true + paths: + - binaries/ +``` + ## Jobs `.gitlab-ci.yml` allows you to specify an unlimited number of jobs. Each job has to have a unique `job_name`, which is not one of the keywords mentioned above. @@ -142,6 +156,7 @@ job_name: | allow_failure | optional | Allow build to fail. Failed build doesn't contribute to commit status | | when | optional | Define when to run build. Can be `on_success`, `on_failure` or `always` | | artifacts | optional | Define list build artifacts | +| cache | optional | Define list of files that should be cached between subsequent runs | ### script `script` is a shell script which is executed by runner. The shell script is prepended with `before_script`. @@ -288,6 +303,57 @@ The artifacts will be send after the build success to GitLab and will be accessi This feature requires GitLab Runner v0.7.0 or higher. +### cache +`cache` is used to specify list of files and directories which should be cached between builds. + +1. Cache all files in `binaries` and `.config`: +``` +rspec: + script: test + cache: + paths: + - binaries/ + - .config +``` + +2. Cache all git untracked files: +``` +rspec: + script: test + cache: + untracked: true +``` + +3. Cache all git untracked files and files in `binaries`: +``` +rspec: + script: test + cache: + untracked: true + paths: + - binaries/ +``` + +4. Locally defined cache overwrites globally defined options. This will cache only `binaries/`: + +``` +cache: + paths: + - my/files + +rspec: + script: test + cache: + paths: + - binaries/ +``` + +The cache is provided on best effort basis, so don't expect that cache will be present. +For implementation details please check GitLab Runner. + +This feature requires GitLab Runner v0.7.0 or higher. + + ## Validate the .gitlab-ci.yml Each instance of GitLab CI has an embedded debug tool called Lint. You can find the link to the Lint in the project's settings page or use short url `/lint`. diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 2e2209031e..3beafcad11 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -4,10 +4,10 @@ module Ci DEFAULT_STAGES = %w(build test deploy) DEFAULT_STAGE = 'test' - ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables] - ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when, :artifacts] + ALLOWED_YAML_KEYS = [:before_script, :image, :services, :types, :stages, :variables, :cache] + ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, :allow_failure, :type, :stage, :when, :artifacts, :cache] - attr_reader :before_script, :image, :services, :variables, :path + attr_reader :before_script, :image, :services, :variables, :path, :cache def initialize(config, path = nil) @config = YAML.load(config) @@ -46,6 +46,7 @@ module Ci @services = @config[:services] @stages = @config[:stages] || @config[:types] @variables = @config[:variables] || {} + @cache = @config[:cache] @config.except!(*ALLOWED_YAML_KEYS) # anything that doesn't have script is considered as unknown @@ -78,7 +79,8 @@ module Ci options: { image: job[:image] || @image, services: job[:services] || @services, - artifacts: job[:artifacts] + artifacts: job[:artifacts], + cache: job[:cache] || @cache, }.compact } end @@ -112,6 +114,16 @@ module Ci raise ValidationError, "variables should be a map of key-valued strings" end + if @cache + if @cache[:untracked] && !validate_boolean(@cache[:untracked]) + raise ValidationError, "cache:untracked parameter should be an boolean" + end + + if @cache[:paths] && !validate_array_of_strings(@cache[:paths]) + raise ValidationError, "cache:paths parameter should be an array of strings" + end + end + @jobs.each do |name, job| validate_job!(name, job) end @@ -160,6 +172,16 @@ module Ci raise ValidationError, "#{name} job: except parameter should be an array of strings" end + if job[:cache] + if job[:cache][:untracked] && !validate_boolean(job[:cache][:untracked]) + raise ValidationError, "#{name} job: cache:untracked parameter should be an boolean" + end + + if job[:cache][:paths] && !validate_array_of_strings(job[:cache][:paths]) + raise ValidationError, "#{name} job: cache:paths parameter should be an array of strings" + end + end + if job[:artifacts] if job[:artifacts][:untracked] && !validate_boolean(job[:artifacts][:untracked]) raise ValidationError, "#{name} job: artifacts:untracked parameter should be an boolean" diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 29fc271382..7d90f9877c 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -333,6 +333,60 @@ module Ci end end + describe "Caches" do + it "returns cache when defined globally" do + config = YAML.dump({ + cache: { paths: ["logs/", "binaries/"], untracked: true }, + rspec: { + script: "rspec" + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( + paths: ["logs/", "binaries/"], + untracked: true, + ) + end + + it "returns cache when defined in a job" do + config = YAML.dump({ + rspec: { + cache: { paths: ["logs/", "binaries/"], untracked: true }, + script: "rspec" + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( + paths: ["logs/", "binaries/"], + untracked: true, + ) + end + + it "overwrite cache when defined for a job and globally" do + config = YAML.dump({ + cache: { paths: ["logs/", "binaries/"], untracked: true }, + rspec: { + script: "rspec", + cache: { paths: ["test/"], untracked: false }, + } + }) + + config_processor = GitlabCiYamlProcessor.new(config) + + expect(config_processor.builds_for_stage_and_ref("test", "master").size).to eq(1) + expect(config_processor.builds_for_stage_and_ref("test", "master").first[:options][:cache]).to eq( + paths: ["test/"], + untracked: false, + ) + end + end + describe "Artifacts" do it "returns artifacts when defined" do config = YAML.dump({ @@ -542,6 +596,34 @@ module Ci GitlabCiYamlProcessor.new(config) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:paths parameter should be an array of strings") end + + it "returns errors if cache:untracked is not an array of strings" do + config = YAML.dump({ cache: { untracked: "string" }, rspec: { script: "test" } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:untracked parameter should be an boolean") + end + + it "returns errors if cache:paths is not an array of strings" do + config = YAML.dump({ cache: { paths: "string" }, rspec: { script: "test" } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "cache:paths parameter should be an array of strings") + end + + it "returns errors if job cache:untracked is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { untracked: "string" } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:untracked parameter should be an boolean") + end + + it "returns errors if job cache:paths is not an array of strings" do + config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { paths: "string" } } }) + expect do + GitlabCiYamlProcessor.new(config) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:paths parameter should be an array of strings") + end end end end From c6a0f109cd393e951f4d700b06490d819e6792ba Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 11 Nov 2015 15:42:27 +0000 Subject: [PATCH 028/410] refactored code as projects only have one owner. Kept some refactoring in place (has_owners concern) --- app/models/ability.rb | 52 +++++++++++++++---------------- app/models/concerns/has_owners.rb | 4 +-- app/models/group.rb | 6 +--- app/models/member.rb | 13 ++++---- 4 files changed, 35 insertions(+), 40 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 5beead0b75..b46c4943bf 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -6,17 +6,17 @@ class Ability return [] if user.blocked? case subject.class.name - when "Project" then project_abilities(user, subject) - when "Issue" then issue_abilities(user, subject) - when "Note" then note_abilities(user, subject) - when "ProjectSnippet" then project_snippet_abilities(user, subject) - when "PersonalSnippet" then personal_snippet_abilities(user, subject) - when "MergeRequest" then merge_request_abilities(user, subject) - when "Group" then group_abilities(user, subject) - when "Namespace" then namespace_abilities(user, subject) - when "GroupMember" then group_member_abilities(user, subject) - when "ProjectMember" then project_member_abilities(user, subject) - else [] + when "Project" then project_abilities(user, subject) + when "Issue" then issue_abilities(user, subject) + when "Note" then note_abilities(user, subject) + when "ProjectSnippet" then project_snippet_abilities(user, subject) + when "PersonalSnippet" then personal_snippet_abilities(user, subject) + when "MergeRequest" then merge_request_abilities(user, subject) + when "Group" then group_abilities(user, subject) + when "Namespace" then namespace_abilities(user, subject) + when "GroupMember" then group_member_abilities(user, subject) + when "ProjectMember" then project_member_abilities(user, subject) + else [] end.concat(global_abilities(user)) end @@ -232,17 +232,17 @@ class Ability # Only group masters and group owners can create new projects in group if group.has_master?(user) || group.has_owner?(user) || user.admin? rules.push(*[ - :create_projects, - ]) + :create_projects, + ]) end # Only group owner and administrators can admin group if group.has_owner?(user) || user.admin? rules.push(*[ - :admin_group, - :admin_namespace, - :admin_group_member - ]) + :admin_group, + :admin_namespace, + :admin_group_member + ]) end rules.flatten @@ -254,9 +254,9 @@ class Ability # Only namespace owner and administrators can admin it if namespace.owner == user || user.admin? rules.push(*[ - :create_projects, - :admin_namespace - ]) + :create_projects, + :admin_namespace + ]) end rules.flatten @@ -323,12 +323,12 @@ class Ability project = subject.project can_manage = project_abilities(user, project).include?(:admin_project_member) - if can_manage && (user != target_user) + if can_manage && user != target_user && target_user != project.owner rules << :update_project_member rules << :destroy_project_member end - if !project.last_owner?(user) && (can_manage || (user == target_user)) + if user == target_user && target_user != project.owner rules << :destroy_project_member end rules @@ -336,10 +336,10 @@ class Ability def abilities @abilities ||= begin - abilities = Six.new - abilities << self - abilities - end + abilities = Six.new + abilities << self + abilities + end end private diff --git a/app/models/concerns/has_owners.rb b/app/models/concerns/has_owners.rb index 735b207172..07f3428b48 100644 --- a/app/models/concerns/has_owners.rb +++ b/app/models/concerns/has_owners.rb @@ -6,10 +6,10 @@ module HasOwners extend ActiveSupport::Concern def owners - @owners ||= my_members.owners.includes(:user).map(&:user) + @owners ||= members.owners.includes(:user).map(&:user) end - def my_members + def members raise NotImplementedError, "Expected my_members to be defined in #{self.class.name}" end diff --git a/app/models/group.rb b/app/models/group.rb index c9806f6fd6..9503d8b0f1 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -22,7 +22,7 @@ class Group < Namespace include HasOwners has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' - alias_method :my_members, :group_members + alias_method :members, :group_members has_many :users, through: :group_members validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? } @@ -91,10 +91,6 @@ class Group < Namespace add_user(user, Gitlab::Access::MASTER, current_user) end - def members - group_members - end - def avatar_type unless self.avatar.image? self.errors.add :avatar, "only images allowed" diff --git a/app/models/member.rb b/app/models/member.rb index c565ee6bbc..30160c813e 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -30,13 +30,13 @@ class Member < ActiveRecord::Base validates :user, presence: true, unless: :invite? validates :source, presence: true - validates :user_id, uniqueness: { scope: [:source_type, :source_id], + validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source", allow_nil: true } validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true - validates :invite_email, presence: { if: :invite? }, - email: { strict_mode: true, allow_nil: true }, - uniqueness: { scope: [:source_type, :source_id], allow_nil: true } + validates :invite_email, presence: { if: :invite? }, + email: { strict_mode: true, allow_nil: true }, + uniqueness: { scope: [:source_type, :source_id], allow_nil: true } scope :invite, -> { where(user_id: nil) } scope :non_invite, -> { where("user_id IS NOT NULL") } @@ -94,8 +94,7 @@ class Member < ActiveRecord::Base def can_update_member?(current_user, member) !current_user || current_user.can?(:update_group_member, member) || - (member.respond_to?(:project) && - current_user.can?(:update_project_member, member)) + current_user.can?(:update_project_member, member) end end @@ -105,7 +104,7 @@ class Member < ActiveRecord::Base def accept_invite!(new_user) return false unless invite? - + self.invite_token = nil self.invite_accepted_at = Time.now.utc From fd1b5d6479ca05420dfd9f13ac6af0f53b5b858d Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 11 Nov 2015 15:54:28 +0000 Subject: [PATCH 029/410] updated exception --- app/models/concerns/has_owners.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/concerns/has_owners.rb b/app/models/concerns/has_owners.rb index 07f3428b48..53ef6e939d 100644 --- a/app/models/concerns/has_owners.rb +++ b/app/models/concerns/has_owners.rb @@ -10,7 +10,7 @@ module HasOwners end def members - raise NotImplementedError, "Expected my_members to be defined in #{self.class.name}" + raise NotImplementedError, "Expected members to be defined in #{self.class.name}" end def add_owner(user, current_user = nil) From 1974087114f3f365d16547c8a5c3ec2e03a66104 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 12 Nov 2015 13:16:35 +0800 Subject: [PATCH 030/410] Avoid render edit_form in each notes. Use RJS to render edit note feature. Before: ``` Rendered projects/notes/_note.html.haml (27.9ms) Rendered projects/_zen.html.haml (0.3ms) Rendered projects/notes/_hints.html.haml (0.7ms) Rendered projects/_md_preview.html.haml (3.9ms) Rendered projects/notes/_edit_form.html.haml (6.9ms) Rendered projects/notes/_note.html.haml (17.7ms) Rendered projects/_zen.html.haml (0.3ms) Rendered projects/notes/_hints.html.haml (0.6ms) Rendered projects/_md_preview.html.haml (3.4ms) Rendered projects/notes/_edit_form.html.haml (7.0ms) ``` After: ``` Rendered projects/notes/_note.html.haml (13.8ms) Rendered projects/notes/_note.html.haml (7.1ms) Rendered projects/notes/_note.html.haml (9.5ms) Rendered projects/notes/_note.html.haml (8.5ms) ``` This change reduce at least 6ms * N ('N' - number of notes). --- app/assets/javascripts/notes.js.coffee | 13 ++++++------- app/controllers/projects/notes_controller.rb | 7 ++++++- app/views/projects/notes/_note.html.haml | 5 +---- app/views/projects/notes/_notes_with_form.html.haml | 2 +- app/views/projects/notes/edit.js.erb | 2 ++ config/routes.rb | 2 +- features/steps/project/issues/issues.rb | 2 +- spec/features/notes_on_merge_requests_spec.rb | 6 ------ spec/features/task_lists_spec.rb | 3 --- 9 files changed, 18 insertions(+), 24 deletions(-) create mode 100644 app/views/projects/notes/edit.js.erb diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index ea75c656bc..b0682f1684 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -29,7 +29,6 @@ class @Notes $(document).on "ajax:success", "form.edit_note", @updateNote # Edit note link - $(document).on "click", ".js-note-edit", @showEditForm $(document).on "click", ".note-edit-cancel", @cancelEdit # Reopen and close actions for Issue/MR combined with note form submit @@ -67,7 +66,6 @@ class @Notes $(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-discussion-note-form" $(document).off "ajax:success", "form.edit_note" - $(document).off "click", ".js-note-edit" $(document).off "click", ".note-edit-cancel" $(document).off "click", ".js-note-delete" $(document).off "click", ".js-note-attachment-delete" @@ -287,13 +285,14 @@ class @Notes Adds a hidden div with the original content of the note to fill the edit note form with if the user cancels ### - showEditForm: (e) -> - e.preventDefault() - note = $(this).closest(".note") + showEditForm: (note, formHTML) -> + nodeText = note.find(".note-text"); + nodeText.hide() + note.find('.note-edit-form').remove() + nodeText.after(formHTML) note.find(".note-body > .note-text").hide() note.find(".note-header").hide() - base_form = note.find(".note-edit-form") - form = base_form.clone().insertAfter(base_form) + form = note.find(".note-edit-form") form.addClass('current-note-edit-form gfm-form') form.find('.div-dropzone').remove() diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 41cd08c93c..0c98e2f1bf 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController before_action :authorize_read_note! before_action :authorize_create_note!, only: [:create] before_action :authorize_admin_note!, only: [:update, :destroy] - before_action :find_current_user_notes, except: [:destroy, :delete_attachment] + before_action :find_current_user_notes, except: [:destroy, :edit, :delete_attachment] def index current_fetched_at = Time.now.to_i @@ -29,6 +29,11 @@ class Projects::NotesController < Projects::ApplicationController end end + def edit + @note = note + render layout: false + end + def update @note = Notes::UpdateService.new(project, current_user, note_params).execute(note) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 5d18473079..d0e5286cbd 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -7,7 +7,7 @@ .note-header - if note_editable?(note) .note-actions - = link_to '#', title: 'Edit comment', class: 'js-note-edit' do + = link_to edit_namespace_project_note_path(note.project.namespace, note.project, note), title: 'Edit comment', remote: true, class: 'js-note-edit' do = icon('pencil-square-o') = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'js-note-delete danger' do @@ -59,9 +59,6 @@ .note-text = preserve do = markdown(note.note, {no_header_anchors: true}) - - unless note.system? - -# System notes can't be edited - = render 'projects/notes/edit_form', note: note - if note.attachment.url .note-attachment diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index 04222b8f7c..91cefa6d14 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -7,4 +7,4 @@ = render "projects/notes/form", view: params[:view] :javascript - new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{params[:view]}") + window._notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{params[:view]}") diff --git a/app/views/projects/notes/edit.js.erb b/app/views/projects/notes/edit.js.erb new file mode 100644 index 0000000000..2599bad5d6 --- /dev/null +++ b/app/views/projects/notes/edit.js.erb @@ -0,0 +1,2 @@ +$note = $('.note-row-<%= @note.id %>:visible'); +_notes.showEditForm($note, '<%= escape_javascript(render('edit_form', note: @note)) %>'); diff --git a/config/routes.rb b/config/routes.rb index 2028ea938e..6b053124fe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -658,7 +658,7 @@ Gitlab::Application.routes.draw do end end - resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do + resources :notes, constraints: { id: /\d+/ } do member do delete :delete_attachment end diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index af2da41bad..be134b9c2b 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -219,7 +219,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end step 'The code block should be unchanged' do - expect(page).to have_content("```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```") + expect(page).to have_content("Command [1]: /usr/local/bin/git , see [text](doc/text)") end step 'project \'Shop\' has issue \'Bugfix1\' with description: \'Description for issue1\'' do diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index d7cb3b2e86..16d5a03e88 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -65,12 +65,6 @@ describe 'Comments', feature: true do end describe 'when editing a note', js: true do - it 'should contain the hidden edit form' do - page.within("#note_#{note.id}") do - is_expected.to have_css('.note-edit-form', visible: false) - end - end - describe 'editing the note' do before do find('.note').hover diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index fca3c77fc6..6cbe685a93 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -51,7 +51,6 @@ feature 'Task Lists', feature: true do expect(page).to have_selector(container) expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") - expect(page).to have_selector("#{container} .js-task-list-field") expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('a.btn-close') end @@ -90,7 +89,6 @@ feature 'Task Lists', feature: true do expect(page).to have_selector('.note .js-task-list-container') expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox') - expect(page).to have_selector('.note .js-task-list-container .js-task-list-field') end it 'is only editable by author' do @@ -127,7 +125,6 @@ feature 'Task Lists', feature: true do expect(page).to have_selector(container) expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") - expect(page).to have_selector("#{container} .js-task-list-field") expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('a.btn-close') end From 77dd561796adb94236422fb9da50482271fadbb1 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 12 Nov 2015 08:43:30 +0000 Subject: [PATCH 031/410] fixing rubocop indents --- app/models/ability.rb | 22 +++++++++++----------- app/models/member.rb | 4 +++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index b46c4943bf..6526cc6bc6 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -6,17 +6,17 @@ class Ability return [] if user.blocked? case subject.class.name - when "Project" then project_abilities(user, subject) - when "Issue" then issue_abilities(user, subject) - when "Note" then note_abilities(user, subject) - when "ProjectSnippet" then project_snippet_abilities(user, subject) - when "PersonalSnippet" then personal_snippet_abilities(user, subject) - when "MergeRequest" then merge_request_abilities(user, subject) - when "Group" then group_abilities(user, subject) - when "Namespace" then namespace_abilities(user, subject) - when "GroupMember" then group_member_abilities(user, subject) - when "ProjectMember" then project_member_abilities(user, subject) - else [] + when "Project" then project_abilities(user, subject) + when "Issue" then issue_abilities(user, subject) + when "Note" then note_abilities(user, subject) + when "ProjectSnippet" then project_snippet_abilities(user, subject) + when "PersonalSnippet" then personal_snippet_abilities(user, subject) + when "MergeRequest" then merge_request_abilities(user, subject) + when "Group" then group_abilities(user, subject) + when "Namespace" then namespace_abilities(user, subject) + when "GroupMember" then group_member_abilities(user, subject) + when "ProjectMember" then project_member_abilities(user, subject) + else [] end.concat(global_abilities(user)) end diff --git a/app/models/member.rb b/app/models/member.rb index 30160c813e..ab5a8e6228 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -36,7 +36,9 @@ class Member < ActiveRecord::Base validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true validates :invite_email, presence: { if: :invite? }, email: { strict_mode: true, allow_nil: true }, - uniqueness: { scope: [:source_type, :source_id], allow_nil: true } + uniqueness: { scope: [:source_type, + :source_id], + allow_nil: true } scope :invite, -> { where(user_id: nil) } scope :non_invite, -> { where("user_id IS NOT NULL") } From e11bfa6b86c5b1b838e7e438bcaa599df668f7be Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 12 Nov 2015 08:44:00 +0000 Subject: [PATCH 032/410] fixing rubocop indents --- app/models/member.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/member.rb b/app/models/member.rb index ab5a8e6228..a7c599b359 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -35,7 +35,8 @@ class Member < ActiveRecord::Base allow_nil: true } validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true validates :invite_email, presence: { if: :invite? }, - email: { strict_mode: true, allow_nil: true }, + email: { strict_mode: true, + allow_nil: true }, uniqueness: { scope: [:source_type, :source_id], allow_nil: true } From 2e4a673cbc1a43532e8aa096e4ab5ca034b804f7 Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Thu, 12 Nov 2015 17:19:03 +0800 Subject: [PATCH 033/410] Add caching for ApplicationSetting, Ci::ApplicationSetting. ApplicationSetting.current was called in every pages, cache it and expires it after it updated. This changes will avoid a SQL query in every pages (~0.3 - 0.5ms). ```SQL SELECT "application_settings".* FROM "application_settings" ORDER BY "application_settings"."id" DESC LIMIT 1 ``` --- app/models/application_setting.rb | 8 +++++++- app/models/ci/application_setting.rb | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 266045f7af..5a9e55a95f 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -68,8 +68,14 @@ class ApplicationSetting < ActiveRecord::Base end end + after_commit do + Rails.cache.write('application_setting.last', self) + end + def self.current - ApplicationSetting.last + Rails.cache.fetch('application_setting.last') do + ApplicationSetting.last + end end def self.create_from_defaults diff --git a/app/models/ci/application_setting.rb b/app/models/ci/application_setting.rb index 0cf496f7d8..4ab3e2dcbb 100644 --- a/app/models/ci/application_setting.rb +++ b/app/models/ci/application_setting.rb @@ -12,9 +12,15 @@ module Ci class ApplicationSetting < ActiveRecord::Base extend Ci::Model - + + after_commit do + Rails.cache.write('ci_application_setting.last', self) + end + def self.current - Ci::ApplicationSetting.last + Rails.cache.fetch('ci_application_setting.last') do + Ci::ApplicationSetting.last + end end def self.create_from_defaults From e073b09f1f0d7b37ece6ecb3e7e485eb3f5e2e6f Mon Sep 17 00:00:00 2001 From: Jon Cairns Date: Tue, 3 Nov 2015 09:25:26 +0000 Subject: [PATCH 034/410] Add missing "omniauth" prefix to option in docs [ci skip] Changes block_auto_created_users to omniauth_block_auto_created_users, otherwise the option is ignored. Fixes #3319. --- CHANGELOG | 1 + doc/integration/omniauth.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 588909d257..932b1b2fa1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.2.0 (unreleased) - Remove deprecated CI events from project settings page - Use issue editor as cross reference comment author when issue is edited with a new mention. - [API] Add ability to fetch the commit ID of the last commit that actually touched a file + - Fix omniauth documentation setting for omnibus configuration (Jon Cairns) v 8.1.1 - Fix cloning Wiki repositories via HTTP (Stan Hu) diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index c5cecbc2f2..3348ada015 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -36,7 +36,7 @@ If you want to change these settings: ``` gitlab_rails['omniauth_enabled'] = true gitlab_rails['omniauth_allow_single_sign_on'] = false - gitlab_rails['block_auto_created_users'] = true + gitlab_rails['omniauth_block_auto_created_users'] = true ``` * **For installations from source** From 56ea71a3b19396b01b3b8495337fbf22c534524f Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 12 Nov 2015 12:29:01 +0000 Subject: [PATCH 035/410] fixing rubocop - random code not related to the changes --- app/models/member.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/models/member.rb b/app/models/member.rb index a7c599b359..eed9f2537e 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -35,11 +35,15 @@ class Member < ActiveRecord::Base allow_nil: true } validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true validates :invite_email, presence: { if: :invite? }, - email: { strict_mode: true, - allow_nil: true }, - uniqueness: { scope: [:source_type, - :source_id], - allow_nil: true } + email: { + strict_mode: true, + allow_nil: true + }, + uniqueness: { + scope: [:source_type, + :source_id], + allow_nil: true + } scope :invite, -> { where(user_id: nil) } scope :non_invite, -> { where("user_id IS NOT NULL") } From 3c96ea766a60e15272f9d9ad1d331c9372465d6a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 10 Nov 2015 12:05:27 +0100 Subject: [PATCH 036/410] Fix changelog item Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 62def42d95..bcd4b6a21d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.2.0 (unreleased) - MR target branch is now visible on a list view when it is different from project's default one - Improve Continuous Integration graphs page - Make color of "Accept Merge Request" button consistent with current build status + - Ability to add release notes (markdown text and attachments) to git tags (aka Releases) v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) From 2ac1193720907bccc1f321e9c56fb5ca2b0a3a1c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 10 Nov 2015 12:33:21 +0100 Subject: [PATCH 037/410] Add release notes documentation Signed-off-by: Dmitriy Zaporozhets --- doc/workflow/README.md | 1 + doc/workflow/releases.md | 20 ++++++++++++++++++++ doc/workflow/releases/new_tag.png | Bin 0 -> 154755 bytes doc/workflow/releases/tags.png | Bin 0 -> 165449 bytes 4 files changed, 21 insertions(+) create mode 100644 doc/workflow/releases.md create mode 100644 doc/workflow/releases/new_tag.png create mode 100644 doc/workflow/releases/tags.png diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 5b8d72dfd3..a2653704c3 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -13,5 +13,6 @@ - [Project users](add-user/add-user.md) - [Protected branches](protected_branches.md) - [Web Editor](web_editor.md) +- [Releases](releases.md) - [Merge Requests](merge_requests.md) - ["Work In Progress" Merge Requests](wip_merge_requests.md) diff --git a/doc/workflow/releases.md b/doc/workflow/releases.md new file mode 100644 index 0000000000..0eca220e2c --- /dev/null +++ b/doc/workflow/releases.md @@ -0,0 +1,20 @@ +# Releases + +You can turn any git tag into release by just adding a release notes to it. +Release notes behaves like any other markdown form in GitLab so you can write text and drag-n-drop files to it. +Release notes are stored in GitLab database. + +There are several ways to add release notes: + +* In UI when you create new git tag with GitLab +* In UI when you add release notes to existing git tag +* with GitLab API + +## New tag page with release notes text area + +![new_tag](releases/new_tag.png) + +## Tags page with button to add or edit release notes for existing git tag + +![tags](releases/tags.png) + diff --git a/doc/workflow/releases/new_tag.png b/doc/workflow/releases/new_tag.png new file mode 100644 index 0000000000000000000000000000000000000000..e2b64bfe17fb95f221cbd57ec3da684b5e47ee39 GIT binary patch literal 154755 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 I8T-%w0YIRt+5i9m literal 0 HcmV?d00001 diff --git a/doc/workflow/releases/tags.png b/doc/workflow/releases/tags.png new file mode 100644 index 0000000000000000000000000000000000000000..aca91906c68a439d0987085db345cb82c04a51b4 GIT binary patch literal 165449 zcmdS9Wk8$Twl0iokphL{QYh{o9NJPG3c=mog1ZzcULd$r+})vgad#)U7I(N=Ywdl` zx##YF|G)f7-Zyj3;bY7(o^Oisk{D=2XfQA^7*d}J&eX!%1P12wH*g}dqVfV!|5)38WUS~9To2qIYWv)HVt`QT&lF#A zBq+E9stbRH+7hl@?(8(B z6)P+GENVlx%-8o>k7neXoC!P(A_YmelnL!SFQjjA*8Xf5FnGuVR>nV2P2GsQIK+R5 zePuLF66mKj<1>vCFxy2yZl-#~h7nBe#c_nCc#7I0+_z@=5)$!gK51;@>@|<%yg%uf z_$=kSv7eh;Til#pZw*$Ge2crEv_{@K$l-@G?ljpYzgdk;WcX}o@|3xB)&WkVPSKMf zMRjZ)XS6pJG9I(UnY#^5Ca)Rx&2Ud^$}r{A-Fm|@cBC@*%YCPE*=t85{_UpV7}G{q zODmV@Z$b1qgs{VlxP-f~!+wrllGFn-t}zEetQ|qKe&mo55P;QbL9pOG(jC*V!<*fp z98rfc2XU-3-uDG8mwl$prC@yWqq=d4#zu1a($p5dF|5~P1V5TWtje%6+`dzeHe`oV% z;RQ{@A~g-fiX#1jf0G64s0KO7xSSkxB@!kkj2 zaynlXY6eW24>qFPxzdW@LL?R9VsvNZnP?&j?@vY(TwL%rq4g4;*%IT{PG}zR&Dc7@ z%-bB>{M&H|a$TPT=sUm9(th#svnPYX!TL1bd7^oB(vsk2@cVdRybu_1@Bz%q7+o|D zOjE&8xh~XJBn0La0SeDmZVGi(ynACz_&8(V=0(W&HKxR-B&K{m4%LiajBk$e2=$2a z$i3&p$O*3dl-5TUi5cl22b2>TaZb5PiLOJ@oYg$je5h@%6}CJy53#VDv7IHG_&#ND zV0<`zC^(b12RO*zQ$1Ln=|0dnq&XlRKb#4n$xIIj4`IIQJRP0J{aJ zyIHZ_AP+Ndo7E@w3$`WmS;Onu<)4DHk6CvT=qW*&y{h4u;dTkC3F1RYLrRQtCFUh) zCCIZJv#_&4vvbyLJS;rKHlqt(^>wv!H9{s21?ky3IXdYpzrLjBRmns%lh&#=m@SZ3 zox1AteGnjV*>hC^8S|?G+U+OpH~E147y^gR=QgVr;=z$A z=C!i^^aHX@-JR!UiIu=ZrBn73i;bq)niX`&Hl%Lo5LP*Pz;u?Qzpbow&eQci#yh}k z&`bCB-~r8h=}}+Au4BbVA<=>v!fJJm{4D#j@;nCT1os8bM&0E-OF8J-S^#zUn{UW2(z&}!_B|-d<_1i z`cb~rUa!ivS26yhith9i7{JJ>rmL4%$zU;aHxpZ;QKBGel+&GanA1`Hr~xkh_SRJP z<1pV6xGnlA1=6$7O&D1h%^XDHas^33NFg$t{ch#09IHzR6<;M#$#I`> zOK=O~x?+jq&ITq1%DfmvzOOZXGLhR!5%%||P#DiNOs(cM(cXJkDjoDiEx-Q90L4ze z)(^&eF|L@C7~PC#`gdFqr+laPA~gasB37QW_Vtq23RV+V3swTNMP*B^w)$-s_wR_a zFxZI!JeJnOD}1#BAmtI$&7Gc+j-9u=c)MQnIQ4nVYW%LsC+k=lBGbE4Tc{Ug)^$rB z=ENSvc3NXDHOCJPzGJ&vYjkWZbo95HzgSw&atEUGMC4CHmnM{rHJt2eU1)uGpLC*H zEW`4`Yr>ITAC7GoyXWINcDq++JS!5D5=j;%muVO0^bWKZtMx!HqfdH6n?onymUv!O zI-qsMt(zZc{aUOb5F;cW>kY}hW0LFb#_O?na^OWUjbxSNRCK!_zDMaH?O~>630^T; zaXav$}NX5O%QPq4czORtRaMU6!xqddov%@q-sX4T1I}t=<#K)5Vjr zZ~;JGE7O)Q*ExTa>Dbx_cRGHCYn5bec@1Y`a_(Sdal&-Y<(eQ@uXwB|ol%62Q<#!D_dBN!PB; zz&rOYe|sd~HvFWfL-Wz2zQ4L)KVhaUK{s`^zCF*Iz*b$w|Q3G1HRkt|A3+T-%t;T}>%o0?&mu_F@ap?a>p@4jv4(ZPMKb)89WPrT>z`f_uC z!t?jlX;FtS>7ciiy9P%oKwH2N;AlH-RC_ZMrY2E=SW(b-{v7k1yBT?s%IOPuS`Z== z7QR%yb-CBs@VvX3As-fTl0}de%#_VYyH9$NdR87WI~>!|>uP_cVB*#5&HR*qQ+C`k zc2h7lFf|325W{VSsQCG(g9A)!36dX zoFavUoudf_HwzaF8OA%ZE?@S`PgsL}l*eWDj6vb#--Raph#Ob2MXR z=jZ2VW#eGw;9!RSgBj#*>tx`@Yzun#&nExg=Yt8z$kD>y$->T-;;(%T4DFnqgs7jf`2^)C|bCgSgU`quraX(LC+9o z=iuNL{CmOw^Xk8k{2xm-{(C7uKi~gc`9I$LYo#FTUmW}oj{XtX-%p|P5=Ikb{V&-I zqfIn)K(!$XBlST{#SQi-9n}l(<8{C9mT;p{<0-J%&E+TMC*)X8Z{EBSYyJ39%s@xiV;805D_0c$yN)?d?xKCCn@d^KSSh#O z0%c%@F0k$1g?H*AznCb70^zG4Ha48NC<+QJ<^OMf*q`GRx}IYS8NMUPe=Wg?f&QOq zC-TSD)QkmEQ(%&M`gQeXM8^Mt$+|>8zB#7Rm4PD^KytsW1AXY{Kgk2hKW_4yOEq7Z ze1gMZ8`B@e=l>bI@FJ?F`9QF^1QOQsk;Mk>Ka(gYYaNlfcjs#J)NDlm9vr{$=h{;=x_>NJv>icRH2tecJV`t;Wo?umkJDctNoX*z&H z5jUzNWsehu8Vt0GgA@R)%c6uCG7-Q3L15xprYI^QoF#z!LXNh@b-b|n$nTa7gR~!s z{$xv{7&xKv%8Xx*gAsvT0%24JTGWdwv{bP+CBoGBaDTABWKrsLh41D9I5sKC2<{-huS3T&2smD)O7B(9A2UE3ef62&$u3m9xv@@xxVS>}u>+JHU& z^KqhEC|`$Q-$Vof_iGrO!I2y05_P~lz7!HJU>!~TefIQ(zgk`UYQm48Ke&t{5Dw@J z2>K!t54h(m(bThJ+;ZaLb1$}(V7m&OFe&Uj2=StlePdOu7e^TT=07t{^&u;tM56PW zIKqdmSyjD6bFElzt~$k~PbJT{!MVH4EcmeA8zeOF2a^}4NJ9{J=4dJ-uoWt)YB}yM zwgnq=@2{iQ+3{X=<#pSy)V>*(LUHx%yku_vgBXq}v28=XFI_M11wrp$){PcR#k$Z< zwkuKd^mv`&_EqY%Lr4pmb<`&R^9Fv{g)DHLe93V8soB!w7^EZNglmi=|5&7?xv z*93(b%I<|GXgSK>dAa?a2Zzoy%zvU2*Gbg6h<=F5oJDK)EXZ=Ix)eYP z$i9427^U2#lboIg%MWg%Z}@uj2FY@%s5~AO_CJO)T2}vJzA(5MXNCsGnz#!>M+T6x zt#bl=oduBAT*H~)R(!v|NWRj<<=({+XLi|KQ@G>PVa1 z#H5&E!+SVCFwIwX2~6h4{4}RM$4)LqE6-*lu&)VDHj#H{QI>p_cc*yIFiY74b|7Wc zX=4VvloVXj{4o^B|11Ojk{^>kp@vjDo_1O|)1?kO!*4fbXh_z*DU_Wd3F4_eng* zPGwuB$;!Imsz4@a!PC@fmg(&Rys=%8qtO{2^#VoyiR~J~8oB$Z2mp z41<_=n4Hse__C|3>zzu^D2GeD!+5%i$D+0=xFL>zplO(7_$g&5aFEgLiy|0~uEk~w zDjnN|efO4H_P_`dj{uB$Sx812Qeq<++30!d z#-e`MBdr*0PnV}>XpFk+pQ!d-qk9fk7Eiu6|T?l{L1vvRX! ztb(}VG6NS6(EFHaZ!z>P{aw#BHLkTH`|f3K{6RgS)PMsH_J(|Y0#aKG|wt(#3p$Ney4kb8hsv>zB1cC;IF7L zd|ER$sAP)JlMNQp9duv@>?&C!Ypzg8_ zm(WZ2$P>Zj{PAraf+fUR1;QhFynxsJ593**xG}pG0N*eZzX^$~zD$0J#x<+lk=Ftc zvKKU^%RB8x#ufHH69zk=X@WyWVDavN#*1^Vkq#${1u)&%+PJW@sGLEW{U&?T)-Y4v zz3EwXQc+Im?%)j`-rEWfo7o(~FJHc3<0@Z)o4i<}NCkphz1`^mx(J$M5eEnTBZLuO znQiZ9oJ-&CZ^uUYz}1}r!jMvPUxvei{?WUl8tShLH65WEj&k#N*cfAJovWvH6FZiC zr^3E6e(mu!ve8XwbG$>ta`dD%%W~}u&fZb9yrdfYbbP8N(x!PvP`@pyjcxj-5rUOb zJZ(KT<~hqCLG2>xC$ycHmuJ3K)z*SWX})bc@=;q`yY4D3Mv)yfGPr#3wg%WC&(TX4 zb1ZN3S>p<)Fmg_bYf-z+OwQxq*+o%IG^2w-{j)U6*EmrwVGvUDU!vO(G;^`3GoQ(! z1s~_;CSkg`6a~h90wk%=aTk=GyKp2N`rDwL*KF-&;zZMTFX<{&T9D9E;C3;={iyV?RGj*na$nkHM zhf;HJPnd8vgXeGVwe!lUWA0OG$^K)?6SIfzo&Iv4b@k-GNC=@(F=@&Zj!}%9SgMp} zOa_vC?%S58Re6vf^e{0oSznN$mH&F=h4CwLsbbUzj!=cP9-+EZyZmkPc5JjR9I!3Q z=lRae>t@AuzS;!+`gG0iRjpLq`@Q8RP9!X{2-Px8M4?;JIVGSC<|0KA#lMm!MHni= z{c?oiR4*4L_6$PT)o_#Njje1d4~CkaH}_(#g>oJ{a`RfZ2?!j)1;jM2krMi%v2CSP zbr3>|VoDwn%ipV=c}S1^HDjk*QT&(9*8^%!tmQ#-Mp8z?xtVQ-V>~Tzm24vMT``{r zCMn?S)gdp1*#-XN;{};fM44ut=-g;}%=Zb>NFr_yW80Sxo}QK@t@5df;`b~{K;h8n2%Fv+jk;{ zMu)o!g+iGv?pI*caqU> zF*#Ogk4AXcdTbh2f`c)LG_Q9*z1{P{3K_9ILit1+GtyZLMAX^#RH|ByUm)x=F--<$ zTr~=9(?MF%{;G;5)vAa|^A5u$oMZ2Z3dI93c#vtJ8F)}Ve+Z~MF#Lotd^+C*zvlBm z>NBurXU(=&U)C=(qRi4UrW;kZT;b zp0N6=<9?=s0qy8ZvG+cgyjvY^^Ho^pWubX5bV6um4rWVf(-lgLn*wW|{dwx5<6O*3YeV80X36 z;h)gHih@y9Hl7(+==-cH+JiT`gCX6~!aa9Jqy2{!ZBeKV@}lCc7td4mE-h94kx@3t zqFJYrV%{QKKHBr$jLK9bPQK^`3k)kaIp++fAeBXWQ=vC1Eev`#4nNY20TkSCbxgst zCdHWj(xX+%&y#FZBZ}0!QQ4#ACfsCr4xKrS{LK5SL!+@(A@vRe@9`*(6UlUu7Is7< z^oCB%>JTyylp^|~_V=g0>pqXiXB~IvI|u@IWZsubb6$<@#7iSjpWAm(5Ijk%^2mK3 zt2QVg$wj=&_V6jZ7kvHGbeeV6h{Ey$$6T%V2PMHQb4^>DGV#(s=l20^l@=vA*WtkZ0wu_&z3HO3Dm7e@Bd7hz-m3_X+)S7`F}8X-v?I2s^=_;Prs(v* zsrk6LIQWCNXp7{$^;QS5r3P@Fx{|F0T{mmKcEc1#$jKY=1tohW7400DWTOjanKmTa zeSkPx1)hW7@Zv)W%sP>OX=2t?jWjG4yD$){i96Ib&Jcs=XPj4Z&My+N#CTT+q`kFd z-uCOM-fq^4rL0eTKiBJ>D-TO_7g%raFJ^x6oif!%INuQ3%q9E^61AbIw3vGDb-NL= z&N?xt>&dL^wt)zdG5PW(mQqH5X5=h+WFJ(+6QO5~2RHGVV%ki;TuoagVzteaxckdm z1D~+fmPT;tj~@sQXW30WX(u;|HnVpk&$kdCmqU|B@7O@1INhCim+A%cv0m!e1=l+9 zXXq4Q@?ay(!Orc0yWN>?9Ot_rP3Ft=eeJQV_3qd)XVkyhC(@oa51y2dx{L%q6MqE|blf5d;v!`j&T znNBCwP{e`G<&K*o*^&nP=x%d6PCJBmdsnY^rY_r%N7pXSSv}F0aJTv39-ZrR8%9mJ za04^|ud{DXi}u>s|) zhu#@hkGs2jU-Y1?@n+9Aq_?(qlkeiZES}GjZs^c?_!hQRcBaST>6If+f4KmT7wfve zqT+h>E-SE6#_0GD2JR1iO7v!H&K>?VVjt>2cnDhechtmC&l_PsEY2EKStV(q-vtya zfS#o9^wW*OMD~>Wxn%(w<>xy|ht;x)C9Sdh01u5eeO5kO^P7{lb)jF=y?G7U1$T3W zqyjETkJypd*KWfeoYyyKsTzE66~IfKs^7#VLfzpn9;8SY_P#rrY7 zT)fk5OAvI#`6^0E^E31NV-B%*(d)7I5O3dQq=GTI2Aj|fXs7QPLEL+1xyB6Z`8=_M zf}MS$Lu^p!{MWnS(!#=DiRC}=MAw}dwX&kt^KvAJnBce6dt0n@`$Lg-=Vtzt~ToAWxULXTDn|<|yToX$_r0VS${6>7I z6jM(&raMhWylQ}~LCdtg`}S#WY;(INqhCf(p*L%mIdW`V|C$U~ zFU5pl3GY9W%1bQ6bT!74QJ1}I)y>!QiBgdO^{WT@dunQx=8&uU#y5z+?9)iE*lv1xuKfZ@trE0%J8cLj2_k?PJ;D-aw&L4ZxPU4i3O4sO_T5fu zxZm=vcO*rnR$VU;Pka;hrjm3-Rr)Yn+468Xm)8@XVNZz-qKtBrQw( z>mW&9;(K~}sXfQ*bUHytWNC|JbMc|hxBjjZsm~(&8R}%0NS!`?QlC&h@WW>0O=O9| zOETW^pP%}^2%L1h_{a!BeQgMh3J*@4g2Lb2d&lYTiL~Pbx&Ya}GOkA?D};{Z!i&kd4Ob6+!#~WZ|{%hIm`f(~U|- zkm8Z0#9h~`V$VQ5r3z?{qsc2dxChn_@aam5Em}_ng$DhU`nbMZClYg7ERZt(Lc{Qz zDrQ7d=6j-q$vY%JRav5qo^Q%*o3R%YZ4OMBHg^bWQ)UFdK6yMRp#m24CV^q^Te;~L zkt4|Gv#CxKiTwR_4j!n>kn~_4w=_x@K#zEj7CmbYnyJb^S;L#W&$UUIawTaN$CD(a ziYij5zlx^Q{IvTZ;Tz z(B?wBIz0V?3^d9XC4xiRFMOY*#`V3cg~1{d3QZ?Ag-mZG=1f(# zHI^jyU-Nm? zSH=0xlPRIAEe(h(3@!cS#(vSdKG5cAY3=!wXLbX|qAhJ|Q>N&)saC*D!M&+(M3YoY ztK%r0NOT6&k3visc6hG1cCXe|@(&SZB^}=r67TcuHG8BSf`pSK+_@vw(|_w5J_otl z&x%sh(ipC_d-J&*C7p^xxP}@doYi?1m;NfXeHt(?Zg1TphCwi0@%1Q?4!=52 z%#EM<6~BKu+d+nRgLByVC)*z-7`{|^k-F2%O;o#^#W2Kf&YWhW|L&Z#a!4~xNH!}3 z+%V*cXSuw73viq8(yf*0^(?ahvp-4x#+Rm(9E>`4WVO9Znm<8I{FUOE+@^GwXittm! z=pg~AN#{?d&6zOG1u`^*&7h+1A15qJl_Zq09UOh1Pl^jH(`uMHZzuh<*4m-ICM*<5 zXe>As!79#6xwtgdpJmIWW16VSIe5z)t><%>x6ybo6YgW+`Z{1ggw(jj<(R|tx~d`l z!Fgy^PQ=z}SYmU(>b~7s`@|SmKplP`dfCZTJen+)sZK#f@NCztHTNGLmif5zRM7bOegL37osz(1nABj+wD?rfaMT9mD{;UuRr?^q?59O9DjOpP`dj3NM6WN4=&t@|A=A zT*vC6B3Zj`2Jg!~rKJX~Y9G4#VjsALH-*}Dxtsb|MH%Ovm1}v+scIwpRV%(fnlmN$ zD~HomIo=%FNHUSq(?t*p4*?rwIKI!S-p-6^aUyGBz z8{6CTZW4Yt;{46%u>u$f*pJqW9WPXR_j~D^8@`+zvdi>YaK+9~W#4qTxi|_0C%9b6 z=uf)3WR4&ZyU8G)F~*?y>1x~eRi+h1h|n+93f;)Op#&mK4qa2PlcwDRxzCy?DE^id ziV5QcHPNH_8G{k4L|BbPLs7c&toPZG?VEAmKgbXQe}gB;OdPJ_vdHR*E(sN!Svno$ zo{5Wl)_N;ay{8My3xJ+0<-Wv2t;yB#$_~8ytMaUGbE3Y-e|V^N>lp5D5W$+-&wjjh zuno7~-fRlRbFt)_!5nb?IF(KEjCGj)LG#VWAI?{u|yX;3Jlc3)hLAcIZ(uz#j z@j89`QTDI7ga4w0A1&>V{QN~Cl7m&&Bmq>wiG8K&b2ejCOcVW4_V>+M_?%U09)|mA4$X*$I;*bT8x}I24ZH-R%||Z)E+AK?<7i>?<}{Yg&Ab7_$nVo-W+drD#^-jsUfQS?!15R(f%3X;^l*V zTX;Qos1MvDq;@4*+W!RO&N|}xf)RgK7%qEN&1SLI!HJI^=g1As$&OJ+$mJi)Omn%= zVaL+~iugl)aaW^W)t~p2H)M#VWti#9owN@D!#be}LuGUy!|!+Bn`C`uH#xv_Qyy9_ zjduKdAuLI9J7@d6 z?fKIW)_kGbWIPbdi8RO(G#>mJ@!wOFIzQ2@u@Ox+1a>8q4>bOYnf!L`cRQhxwBt`? z#UrmdlZY^lL0Ro(Bzi}DHL(JC>Gp1PG{xd_cea{#VaTXII(LP+(>IT^V4+jcM(E@;Sm_=WIy{LCkecf4=XZ+45J z&G5e(#NiK|>k}#wa0_JCEji@;e>KE4>&?Gi^rzX`W!=wL&K0WQ>;a04N5B9)|9@da z{554M#&=Db$x@P75G>>Wdtl%dd-q_(wniB^Q@}F+k^{YN+G_NP^`@z=={gY(@!KcI zG79DX>?JQRIM}lw0K%7i=#ny>DIsd*H`Pg?Z_=HU&%e~^tv$|P&4()B^Jm$g*UQIy zGpRi5(Fno-(6X>XU`6>~biXFAK|k?fAcD5GA7oUMs<5Ta27 z>?Ao;;m<#)DT{^l^VsW(zzviRUfrja51dtb*Ktr&Q@`~jJ6rO_Bm;z=YCQ}AD^FB9~{xf%zUq=QS8`pWc*}GIv~mFzvxE#F??jgQ?>VBqvXOvgsr=h@=OCl zJud9f1hY~7lbc0z5O=zjH-7R0a_+fx7!VL6i{XD|Nv-1iUIU2XT5QJxX^qv$14kG= z{g^?SUtFa>4earZwHaLaU^f6D!MZF}o6rHTWsKQ}!Eq@1rHJ6<2U@OhsO0ICs} zj#82$e-jcoGU035EB=oe7MA=?NQMTXTJ7u5U??G{e-o1Cy6tJu5iWcf-m=4y;4s{o zcKw<0Qbji!NZibnj^JHKFi+zCxd-jw48O9R z^ihJSv?JPW=!fw!87MubB;RX$kt0 zUkwj7<^+jy34xm1)~r%T1X&u4Acuy_d60m~0tO#pg44QQj%{pZVIr{E8=Zw2cyKaC~z3yp*drae5YtaQUWkd7j;cLc~|QEzxY)BuSUdJcR6O2pwrx{CfoV*iFPvrctvEalqr%+b?{Pe2GP0Np8!VU zSqunAUaZY&OS^)|LaKm_Sh`0oQqF;4MLZ-%KIweJnsSx~=(LJe*g>4145*CrC%Upu zUh9*8{`y2A?2A4D;X>u$M*eH>H#3B)s z8)Pae5dFX5;Hn+H9NQG1f|BTC(h)#VUA-2e|B+2By^C`99u*gwo z)B+3@HDQ016ZPZ-)kOqIB>VOUx(HJWXfjd*=?`d?4^g6R9#s<&1pawl*}AWN3fX9RQXNme62^#6@6W^|}Eido6#uxF=|VH3p6rzc_yT_tf(L z6;;5ytOxx|u7)mm+GJqzY72u}l`cNrh_iUn?wl=Onz0W?3P&eVAru4Drjbw*xXn;z zSQByaM_2^~Lh&cjJ6*n=tlctWGUk^KX@!{^R_D=u=X7ofy2#o%rnhmI{>8QYJPied z(2>D?_SPTWTogqm-S5P;>U>E3D(iBX{g5FU*k%b1T9U&5=3m6auZk>D9d`%=j$(=X z|B(D|gwjW*LABgLXnb-|kS6o0xaRI!G;K^bzBU!;9!JhAI9^l+FsOQ@&0*0b{@F0I z4iKC=!ohmMo@_&99y89DwHk9p!=+kzUfN(wWW5DJHju8}M3L+qgzGTns?2!Dqx?ed z5DXdr_BOt$d_(?wTYO~E@zZoY!oTaCe>7~*fdg$Dc6|>JheDV#Bf1mW(futcv2k$& zk%|UB%gdK0JpYIT$ow3fGo%5*{k*5O#v3--X_c${9r|vY8X`822ujYFo6^?X=AXp^ z<{PK&W}TCO6su?nHrEgUu5q|GkhV#@Rs~7EZbXNEdwx=05(Nn*w_%I|_sN9++h9Za zU|nMJ#tCljFA;SEpzpp@+Q*-8w$JrQ;S;vgDwPb58D4j=!PUr)fl^3~7)-q}J#-aP zhhCW+M{B4tq6ujzPbIps$&0ZoLl|{M?ffJ+lyTT3 zWb4ruQtG*>!vlAnllnGkkE36kBL_itohC%gHAgtghP9-q$Y~RmkN+MvQ~a-p zFY1OuClY?*xsfn(r|+R`Qw-Q?jg=HEm!eHxSx?j7Gl5>)mNWS6=0dF8Nk+~A%=Lyy z3)sxZ&98}jJGnUJ2nGF2V|$4?v25Z${vm9l`0r6=EfWngj2?0bl{u*#spLBqbC+Y% zYVaYIHFR1CRrt{-GpM-($-XRyn0n-ykCt?m!`qSfAV5% zOgK}hX})~iFMKX_Qb}IoBeCJT29nBi@KB74mJW_^fON@|pm(QJ`pdvls`Hqjt9 zod5XH@g(IHxlA2Wim9sua;t20F{O-_WzpiMN&0fZ|5EgSE8qh?tiW10O9Myx5d6OS zObL%hE&o7(ze#Y(9MIxT2*Ra`V+?m?5_djp+^ZskVqX(flv6OoDCQ? zkg|eE&f(V9DVx+B+W%!Be?ykDcyQ?WTu5m0^0+PUtGcL;MBET0(b24?qcmrmPJAb( z?%4vsht4HJq-w`Ps%;WTBo4opVOhV`qGePYdTAsW14$J7WcZE~fR2QoEG6xS8ls#z z(%FTf8vRu4?6)$phkftgQ{AR01!eU`pn~;)qsdF1!Ir7O2i0rMfzpF8+5eR={eR=| zIcy@d-}x{nl&fR8#5(Lvm(#G23y`3qo^2d6iCVQ>Z$ZGxpr1SrZ(Uaj_h#AxzbA;m zqVnCDH4ejb>vK+ID^76^9O)+ay+YuC3Z%ta%t4kOqXnfOY zP@-Ywi;r$=aWSxarc^n$Yc)rJ0ZpzY z3tap1|5GwqC)jQ`H6L$1&d@RgOAP|?E>9Pl7Dw&FR(~<3N_(x_z$N( zy4f!FCp|7o_OUzf5C{`DJ{F`Mw|imG9z8OgpYD!Jbc@W1CMV0#CIM6GH?~e#A4~s*MT_$j z)o^$n^(K*$h4LF2jn(>PL%)%xLK4h*@&*qqR`5%+_2pu~%&ppQm6fRj#)H z`CQ%JBVp|+#l^?te(1FhC99kKs1C^M~NU9r8I3Xeh9Ju@h+J+FDul`1Gl*T1_ zXr->lMijd@kHuBcw@L%GjBF0RuE|W$Zf^QY!u-m2Q%_^ofTK3{4;nIcJhRv;1E!y{kA;Q zlRd{Q9&esiZsOR9=J#JOWDRS#)Q)X^rmu(sOm7j<6SwsdBllIulWYrxjEXUCY|9#1 zdOrVCn1nLKwt>`Az)?gA<}*U1_FS*cEZZ z>{P>%k8_30`r@et8_b)uUwp`DKa~|J)eQgHV42F!+ji535C?>vdy#;cvg`y*U%& zSX;`P3@x41^3qE z`}|iW*Yn5Prc-WTdq1YGUhw~_woMCtU^yQqg4kl&ri64ndlLKbHrE(@)V}a{yWF08 zVq#cnLv*LM&|6F5-BDAWk@ad_BrlIAlik!lQb>rNBl`{b)vOmrAFcasRu^lgQ!W$Y zdzuo~dVC7`rV;pi+HEYU*fLk=$!6iAhz`-rtiE0aHCVuWOG9lfgi!5J4s3V<$f%n^ z8gS9&(-Hbrd#~^#o@X%v{r8Xp{mAkK-!G1{t;>h;ZUNBA=p(n=^Ty4%nYCK@DiDi{ zaN;95KE?+{6+aCuPg3Lk-OYX9wFs8QqzoT7o?yMC*PYMZYfjEh~NgB*F(Bv681jH;7tiQ}|A&(@6B+Z{c!X@oDzo z^}G`*QxZi^=HMM9F&SifgA6G#SShrRqKB+Jl9xM`2w?WX;z!v2bci@o52%kEstP03 z^gJ#IbGB0qOJ~1|*>fCJW8ZhNQ{0(qqcFeJXi8>OQ-i3NL&r=Jy_)# zHp8nqK{ak)Krs&6eLADv$~U&XFM|B!hihp;Hi^q2nA5}gV;TLBOxpE~kWZT}zQRE_ z7S|+;I{t)l{gmK@rM9~x#_0~T1Ral-a`Tn1LSH`et=hjOYkv?-ftb8O`~9?QWezv5 zepyq?{rO}%sDBjFcu}>MyX4>NW_dtjz3|ifv9yx0{(8gU6WElwc4`rr#3Ff!%%~(_)1W zyR;}L#%FC!)<>V}7w)`>aMopiR=u=u1DUUB(X5Ah`fVk!xFxLUUyNovu(y^V?#OlJvttCZnLU|^~1tJ^jXi^>Tecz`EF-i+KD^s z6T-Bc)$SnQo}kFtP4ZfUvHbt*~XUdCfI$|%6a_F?NVl6r|TV}!zmBd z?hfON4`NN*vhq}m;Su+o!DHyFC5`VJLhzF*|J{nllk=_+)<*&2b9J?`LLj)G?U1%BW#4@&Wn>O>OFNjT2uvo*hQ=7nZJ@D zL9-Ar)$){6t@LJNKU3Z~OYFU8^{+EBG{YT900CT=^-SbPwI8h`HixTChJFvRcC2?I zlYdyMuXPN8W36f3NwKOl8N&Cvno(=q2qw;PL-3cAv0FsZsz1G^AH6k(_2Fj1-F6c3 z0r(Q0o39;atT2%L6z*lCZd@bGe4I;aGMOV`wc3&hN=ka0<~vLAYH#M_H!7kFU7PR( z_x6{0XS|@OYW+u!QexmJd+)+!q}sRK_onup0jhn-$_g{=hJO{WQ{}H1qo}ji*bf&B z&!)Rqr}=6HHYa!)`2P=kZ{5~b^R5jSFWyqzDOTLwOM&90xI=MwcQ0;5f&_=+?rsV0 z?hxGF<>j`$fBSjv{Q;iic=K1XGRe$bHnXld&ucx-#}kI2v0TelVO`S9l?N1f!p@%# zVQ}ybWR36B()M+>BrK`pTQ^x|hU*|#+Ia}t3-yB-aEwUXTnT0rSKh+$Rd$(L2zj|t zQv-UH`>G4dS_dJ&V0d&E3NSw=5pp6I$U)-^1bB$oTvzL_D@BJeatNTpx!v_GGLwAp zi4UP(1^gt&9_8orR@0x7CvnI!a?_Z(>+ABQ&vRQ{JeMB#<9|zuU~lR?FSqoN%H-)) zP-hc5HKE1tyzG@LP1cGN?80wKUpvT_MonQcyI7!0BQnS*MBRGbK2o((PiuO7I^XDe zA-jv&jwP+SecLp=ymlAO`SNhYDOdGzsOiGE91@Z)Yh@b&(yB;zU2!E;6uj5HjU7#m z_7NN$E+7@S!aYYiXK~qw%qcv68ty3O51_48R<*zOHO6G@?CH;djJETj4>OEJkLpPc zJsxBT%sXmG3D3S+v_BCYK4aveZZzL3b8C36!x4y5(tu8jt;;B@wS2rluZLo4^?xsT z61`&XIzf;$!i6tI;)DnfdkMx`AM|;IkH?__8xZ);mFcp>xX^2evK{PdT-7PqmAWm+ zQ@RnCQ>kCVm*t7VsW6f5%-2ZXWKU1c+BL1;T&{SGKI1u{_F~$PVQ@8_=(z0~w`>&4 z@FVZ0F{JtlwTz*$9e=y2UG#8aZfmrQD?r~^i3Z}RjMZ{p>VwA6&pCa4+ON=YyCWm~ocxbWMa4{PnO7}>B|%Dg6|)=EpVDj$42+^Nvj2?{KbTY6%;9^f^6@IcNb*p za80VmQQJOPeV%(TR&6_-8OFdKAuj1MlZsp!ii=tPg#R)Z_wzoT)ozeypJuod-l6?6 zB1XUk%D6nQRfn71GBkX2`19(_m4Mq-(!Q*Z6B$0*Axy~=@jS@Di%@Qu7bXeK%z%5# znf?x%&4m-(;BAzAw#SS4;bPE+C^{ndV~KhVY=+?p1zymtIPO*PI(KXF4URm=HOtQW zVHD6ieT0b1&h|mWlih}=&h19!WXy$>5q|>Lp+M8_wW1E`mUA{oiDo_8>$#)I#rAX5 zR8P+r$NecpKIg-qDwEpty&kc(7RxBBn5yi_XWToYeE3-H)dv+_+Iy?pMqWK3gX`eio{dqBr%sd%}@bj3f$s zqy~^XMPYyOObZP>4X)e_g_bPUXnV`h+IAjA{DyYoIQ)eJ>l{J%@4B??<+@SCdq%XH z7c6AV#E*^}uf-Yuss$iCG5STm8S!hra&B;2C7utVT)P3%O0sJiOVl-vkgvR8N)Y;r z$IMJ+%$yNySxzK@P0B60C$kq4FfT7vUcQfgpULxt?_{V7iN5eCSa0H@Tpu*3c-ntS z&AepGS<-pQ(}*RdLCe^A!OdZ5$|YMw36T?VF@V2dv0To34)`9e*Rh2zXb`bJEQBW3 z*#s9HK<_jbIrZ{o>0)it^-hOlCiFJ&n&aYLum+94zttiUSNfYo`by<@0NGo=W_P8{ zyu-rQa%w(Z&=yE-L2sg_p5}#9r)v2F<9m~3Tbd&*X9g;H9|A>cp-8-wPe5sAT@Otd zfh2{mou>q5(T&kM;HkapT|!ir+}B-1sBHd0=clg(VSGNz(_gnoQo~Yx1c-frk1R7s z$`O$tCo?!3u1*|v_V8VAR=M2OtY6?QB5C1n*ASxv=pnN=8_Rq0lM&)=Dsxc=$>$(e z)d&A%-b-WY%F9w}+g|1RjbS4X2IY;G=&f$?IV}l|Qvd0b*A1sx!&L^;B+3`#2lNPz zkzst9l>65F(z4pJhH$6Qqs>K$`+>>5$Ck$x`VB$2*{dzDuTlDuH~Q%bN=Feaucf_s zxt2zG+QMT9m!k!wZ~6~hk1FXCUT6&$`q2wU>rR|@?$q}FjYfKss*g1u0@h)wIgy^+`?4VMQ0cBqOGyPww` z?G53PDBOVZ5Gfus1XvslBHgqv2M9KzS-P3hh15;Se)p+wKaljdOh02Q@4ug z7)hgXKtWJXjUdKNm97V`8wX+#WF4}AwzReJz-n8eo^$-HRbqx-E~rlf5c+$%H;T! zu~%LiM{m0bCfI!M><8wdMu6ESsl`N_0PDlG$N_~0{zE5%Yc!$Z<9XS5w@0R1KiSa3 zFK4#h|QG$(jm{0pCT9wkq5{`+ZS)di2s=kR`l>ko8*22Xrx`;pi zurHQ8A!1~3knQ?YvC=nbSPQQ0jn+{d{rRvf5d0-e&B6d-(>Uv37N8)dmjhUCX%?Ak zhD?xAQ{@L}`bK)n6YHDc?Pwc$rV!JwSIx7co+a&U30CS!CNhaC+f$x0tH8U9 z)elIg+e9yrWrG3oJr~Fa*I%5+^{-{h_BERd;C7yhCzzi*LsyKY?3jf}>=@+o)W6R6 z(b4Bee}7@Vc4_Z^Q(LHJZnAU^cBnip*VXH-7s_1AzOIv_O$pgtjbTy^%h{(X{Lvw9 z1}dlCND+B76qnxLMboGsV52Lws<&?QPMIt>$Ge3-XdecNEYvLD9|h;B-Sv8!8@+{x zPjOO&J8qE&q)g_lU_%@s1*i=e(P+%UArsl6eL}b)Wi{*$>)D>|x>_g_Qun5hz(kdz z>*k0?VuMw!&(i9%dUyHIj{VY zRSc*FBQDtZ+u5m{aii>x^6}Z0BPQ*4sST)zO@%5D%87r^+i?W9H^3s#?yP+ZRxz*c z!Y@#QvfnnDM4?|h*bmMNh3!D#w)VxFRCjkE9{RLw>>!Sdls9e_75a&@)Dl3Rl$bqX;7u6 zGh+&mH-ik9V++JS$;?9+gk#k(!i!w=Pu3qmjz=Mb)@_)TSBfoc5$>eNYkbM^4{Y|) z0mg!bJIh}p+KdUSK_6T&B$4 zDY5|1sMWiLth%Ir)UemG9dZz|-RYM^bX4L`22a=zer0$m+1Eq3I*Ou-#8;{X=S-<3_Wf{fdGDC8BoNU~5@Bqb`; zG5pq&KQVMcVi>GZGw-*q7qI#;CQw@i6KUngGhbCaB4QUyHAQEbz~dP5LCn!TSdrnd-=Ty* zz#al!-Ux!ij)=K_CP)THjKDSH(e=$rTyRp%y&=r2ubvA+D~Rq^EYJQjmuC90 z?(A!lxEvVu~oEhe)rDF@!Jdb^HB}4Z)Krkf2HfYkSF~K(Xx@TR5|H9oG_)pM1dy7 zU=nQE&YDok(8@MHV&VAOg;0iAfZXB5R1%aTf!zF*qTOUNr|U*_dh*?-SF&Vu!;Y2% zR#}^2!xn~lL4NK(ClSJ9us;aahol)hW4hb*+^(fOTkW|L86WX2yhpmybK)v{Z!Poj z=&DVfgU;S-1baOuYq$@oWzUl~64E8ngW{Am`K7r~VKAP6A~8#7UM*;>DRsdaF6S77 z`P_7q5K^|HSN<&d)*Ts7nV`n+=!Lobnk%^0+7U&s$^=($ zcE+oh_p7(>S>6(8xSWIq)?Vfha}^l87wAFdD`k>9Sjg>yA2zQmSG3rg$d>ue+cZIr z!DMp9pLe>dlm^(d%eGTqX^s@#UL(fx=%uU$|BNt7ZuCY!-+eDQ3<!MPr4B z09jS8*XBe!)O$FrBKbObgLy_DZ>2L*Ix-|*n#l1sG>5mbI>54XQ)n&Yf*&otghh2N zjm_i7&7}|o%YL^J@l5hj+T_hn%J?@zjT>UI)Wzs7%8Vyumx-Zrf4Az2eNa{%Tgu^s zyyz^DsL#{Mhw39snt3Y%DMpBX$QWXiAGQs7 zu_twWFSjR782n=bW0Mdk>Ky@d_Ffg1E)2?x*_K>Wk+bx6Wpa+n!Ay!kc1pVpq729cK2ln<__F zZA_-3*f_9FEKPER;&;<+7q*vZIBuv#rhhxxz2f@MI$+TC*1Lj&b0Ti237GZXY!3kp zTK3|IOzT(N>D;dm@vaQ2?PDs(b4GS`KX{ml2{3WmUIGgA`+SML>lpJWiw0FJPy6RY zIP>+_V%@cghNtkk?QJ&=8u z|0UUF{?vijpmusuk%i1`W!&DD^PXJyF7JIzd5-$+8L$1ZGH=4;DR|c7<3delc%bq2 zmL=a;`Mt}IGU(Qpb&GDYR4up=pgDb4WpC@E`x_qKy@tpY=KDv3=*f}}OPMFv)~#@Y zkCy4|f7eB$M*ibiCow)(9!+R#V#oSBtp-LoOureo*H@~c@ED|e zrq?NYjgmx%H@gERIRR^D#QZ>CsjLR)dA<>iKg$E$VbySi^~VJehF|8 z)?3VuxaQ$~M+9Z8Fz}$5?RmK5rZhl!% zJ^VD#_kMWro57yD^}^hXPdTR5oe3sSU(u#dA!A$TB_w?bUnZfcw0S*AP3uO-%d@iI z{aLa=(J#Z`QAkYDw110OL(UAxTP{k%Ytq}mW4|{Wh9|hR?6+Gg1eGA$La0h9%>j}J zdUqk{ff4Oh14=F`d(GGx(0ON}JcL;W%ziiR3e?7>4ac+o~H$H?v|wmh&A5+(2r8)u)p8bLoVk(4R)&50evQyRlJ%e zwkwL+Akl3_e{q0txF%dzOk`A^`)KVt#c;rv>Npc^Xx!uzfj?~ygXkQ%9;W#kERuR* zN5d>iKF>?3DD3}|nl8_227>cNv2tjKbpr~@nlQN9?gcpBhY*ujMdV`(KGDwH0ZsNI zlV033wMdhqkNVs%ZCYYI4>$7wt)#%dy2FDUn zSJ-U1T+Uo2xUS!+C)b*CsFbbx@{o9LEI$+>^Ttl@3|05Zsx-j8q}*dwIoMYkADPQ! z+(Wf3xT7G_0I6qW>1Omkw3^*7@?@+rj1u^HZcd?_Vr3i(FSg-<(Cz7VhCm^M_OCIQ zLC5D<_R+w)OkuPrs=V_n#qGgBSw`P_4cvS&zfBa%NH9z?hZ_}fYSWu}&6^+K8HTx< zXmP^T6wak`OXi0h74LhN)}MMXGv(TTG>S?1qZ;bu9=E{t;aUuho36L+`Sc@C#EQly z23rfY$yfJU?$8CRC?Pj+!$)jzI}Q@5)#K7Iw`ATz`K|X@ZQ2O|hm_&WX6KHc&0s!v zWX60sbEi7havcx~>npA&iBWqOY;rY5?A87KwFC(hUGVkgcR!Pu&;Xh7)1GD1c{52f zJY%n>w+CED9r;#51PJX{lZ4rQBF3Dha+?9Syc*P?1M_!asr~i!a>WWq6P9D1C+;{1 zo$=4SZ}-AUYM9%<$kkJMu)%{gUb>!7&7m9&wfu5rl^Eva9GyOl_WEW5j|YnsLo1oy z7-u#G`3{A6kh8Jg!PB`AzDmb^3N20!RQ2wrEYnr-lGCIJu)0XG7XTw@X>2^?qi+}6iL%~F@)OZ>4156t=^;x@t0 z135ve+VPif`dDo6v5sC7V#ljTQHGy2JU4#nhIE7F(+Z>eV$6D8=i|rc-TL;(4E^V3 z*OQvOM=@U316G5Dj7PhM3ZMaA7Uk@oi=;Edp*jZmy9Q&7HHT>=o2J4npRd$Zvu<66 z*Cep^gM4j=UB%K>6VS1jKEMA)CR=wIBUbfAi{H`7=l7O>b-_qSAGn-4L@#X?frkYB zX^XjOoJNx+S|{;aqe3|a0lJn%`So#XXf9;YbGGx8k0}J}uKVsG(*0zTSmk8WviTKY zpzy1CqW(r5#F5mjlfUDJVjfjY!jI#yM~Ui?(TX0vgVs6c$582UD%PELW;v#X;;UYN-6+=Gkok)1RmC#?d?v|W?yd1PMyZ3C# zNQ(5>iv*|#@MfBh@}A5o+JXb$C!%S~lV2tQ)rW6>l@I-@m{63CkS%H}xv8A!OFhtB zZYEl=>8@eAU^z;UwjC)4XBwSRwcKQZ8$D;tutuzO5#s*A!M>w{9)?c~*TkqkJM(wC z4ZS9R=wGxpKOk;6o_K}+7*-3KK$;-P%vNV|L(h)&669^~TazjYD+?S!KW7dq^=5fn zuAX`Ak@5__7)#UlZJQ|oh1>1o)9P%#K}Qid_XbKwE2bx?HM-KYHU*2)j+`EAo2!Gz zDxVF!H@J3`872*KRnvZUit_32r7@^6yRh`*XT4@?c0G_Ohn_sW%yy z{O3vBGBi#3bHJ)VFR*eXqAsdprP6B(S^e82tWOD z4H}6xE9N+d1ui2==F!pidjg-=lsX+dfh`&%Q*tz5zM193Vzg2At3|ZRmD@+r7C0O$ z5MW&N=%w2ucG-E+YK<~kHpR6q{LA9+C~Qo856?5ICmbLft^*DOcx2fPgfVF97*lIG zGd1_S{nYinYr^+M72rwPbZ@O)_kF{DcI+CLa@)n;=d{y%{QUwdNSP)G&)Csgo{1_; z-Pi=>7oKY@Khw<1j(8PVW9oW`;6sJ64Iwel11_svp_=?$lLF5SyS#9@0M=x*E6-ME z>l<0x3*Ay(k+9AZbOy zT0}6B@!XSUo5xTcXEktDXK~QNjPPkhl)&G@8O%Wio~8WJk}gjKtXbgof$Ek5^sGDbSCJLNbXn zpD}JPP|ihNyZlwgbAuDCu4gwK0?8|sJsVifpadHV+H-BXYUWoyx>A-t zvpYDtb)1h^ldnEB_7UgZJ5msqtEwwN&EE3oQ>f7AG%jz({oro0_RKfhTPpA4x0Ems zx|2Q<#CD1N)FGssCLc|--yXqjaB(|zEXvGoM%n61y0~bEGy<>{@*a(n3uoRqRvp`} zQFo0k%eaDbw8lAW-0HYG?zA#sE&i8bCXKow;CIEb^GywU+Q6c%UaDJK)~<5 zw=A)dRu(A$m>QT>D@u?#=`!LiyZa`SK7(2Qe1X>@AH~xfS$xxci?-y{ZF>UajBo9F zj5qZH7Gk!-i}V7_;-yO2c(^}WyQ`^M(($Souv3Jh?sl%c0E@<87wQ$zzb2dr%vdax zFgz{qLGowwRzc%aHw>BfUV|Pkk6)IQ1c}Or20NErR!%v%9zmfEUHX7Zc?FDru#c0{$J_qr|I~+|KMcoue~xkdLMPlB?n|f8@&6lq*fycB4Nf_H__m8)`oB7G9xv?VDg!^<_=z>eT8rl2ZxEs{%2n;A#Ea4s^IB@w-bEzX*NlID z5;yx=a#&tb0uFe&CgEBS6$9Ju1&pp0wMFh_`B`OBMm4vCb9~%m31!Wa*}cN*PSgc; zasgkeCu#XcEqTvAgbbwPXcJR_hS(YvdhcknFVwzvZ7RhT+TjpE7gyP*ASc2(%H0h_ z7jS-8Ch%5K9SIbS8nAMs$YXC41>FvqQa7<5@t*(E)HYCIZN7#Xlny1Zq7;5nNcadW zBj>~RY$eJY%q{tKJF!S>Azjhpve!0P&wC%}MgpG&d3yt4K z`fd9TPUfUo(vK3GZMA7DhWu6_`nG2{&i3&aIsh-GjpYe3GFl~K`6fU|&S6agV&=0eK)ldDt7 zgdD3fNH>WR_T#tD1=i;-l4^BqQQky6!BqXgi}3;v9?o&`XKmAY>=0ZXl|AkEzZdw& zeZAZ~kNt-2>~^QSAy=n!{+OQ4tbUVSkj+lz{wf^B;M`ON}LV%oc|q z6h)YjQE7T&9jI%jAK~Vjfb#D6_t#dkHcGj=;9sa<#g-&dX?7*n3Hs$%L?W_j;y6b^ zP!E&RG5>OFrCGOUa6+G)h}5byaS*5EP9mDwI;%&!@DtkuGlyriBt|>Kd!x zZDK)lK&5agXjyzQaH?JohTIWn(FiZ=63UR2MADy(MyFHC%n91<7sO$+*E(oKCEaGV zx!?Pp0JM-~^p%xXH||8u?|j~Vk&R)AI_DTNI=t;XOxQ>DSa_4?-gDRiDnY<4=jQm0 znEA@3WQT@4FX$%D9QL~*oqIAwn=%>LBYoV;Z$yI(7Rf8;H`}_e$lw~11Rqn; zhu^XtwL6A=zhr%wqEI%vU>pHD?R|929bHtYLONk`Xw4BBYT0I@F4CTvz;=KQNwx^@hR01 z6Z3+f9nmTF%?aCd8ruItuoFHaCzY$B1y&nSUY))@VyLq;K|IFQ`e|^muTR2IT_Q0# zoI*V?wgd>4PVCw}zQr+lbb^uLi7U)gA{#q#^RdK~2B_*G6Kdr1(mvUR#y4RUH3 zWPh;S#T!}*^=KAI{IxU5Wucu&-5QN> zJ!13!w_>-XuwAmkX@>e#8d7qVfJRAuYvq*x)aHS{hg_fQyH#8Gj`E;zb*EG{k{UANs_Pi|d%y?$7!*Ib~ovihNt!$MVN zx0?O>%KQ|T`Kb!I3Js<0WoHOw2!*Y?0{xaRW0cus4j9Dq;s)IL<_q0Qo(XNZ zuLTi?LgL2Oiy5j!{o^WT<{P+Aua{omdXcW3oFHFq{PhWQR~~An_E6qyTlUXy|D)lb zZ`ZJ)QQ{hVt@X(M{`vpW;9k*Jr0RLfWaa<3{&#C@vJO&!<{><`|9=kQzuIOB!QoOI z{J+|HN3jswNq7sv{=xl6g8IRnnv$iXfgWgu{~_+b=jwu`C(G)x%tnv%j~@O@2!Ave z&Ol`TI%6_5^z&ck)S>+5;^Z%9C1erj6c&!G_;1FEgdz?F-wzMb z+2Z_n`*M_e^>R5oJN-Wb$p0|hKf;P%=cf+RdA+6LC8~e_UhWU$reS21`PUG`j0<<` zIB0!59U32J`{Eyji@kkI6XT+(VS9Y2PRQRt;!;%H3?Nr6_^%N5pCi~cdmVZc89o6) zh|k^ejkXk-_aQe$1WEMj^JuxY1`zq*XLOnV>M_d|Kt7+gx@*8>L5$sbVD~S6e*TL4 za3;}dUII=_Tv}RMLdHS7|LpC*jmL|rgH)YsD8N71+(LJFQ1resM};ORD1@BHjTP;1 zwLtde5n0!_yqv_*&krR|UGfa(-yDC}=XLb%`Xob5Hk?2oeU3|aFN>HGoGSgefA3X* z;*VGdV3KhdT3G*3|ztUR?Iq<=|?qY^d2DPw1&XdckR9H9(!2=HKEPc9q^l-kA=0htwIX58a?q&+G^{%w;KL+oSDL_$Q4wt+Sk<3sPJBXxZUkW zJ-xbeN{-Vg)48YpW9|JHm8=2(kX?tj!c#{6B0Z{I(#Imxo-tdMlzyv7(~+4QZN?JPTnYjikNCyUe@Zh7?lPh8>u49?k#&j_N>F4ynZ<&3 z;s-QQ`Q)g>K($aei3=8VEX{5G4wVJhIeaT^3s=(zHbt&q92^`P%*9+>3d>8yDMWsL ze%5xA>fTo$(b8M|EnJlJ#*kSML)Eycp~#jy4`n-`(wrro;nB281M2Tm^QUN0P@{xD z{~#1cyK)X-un>8k=Qm`BLB8kyn(yCNTU(pX`=WFzQCcv$cV8O39XTr46H-9CUMz+= zE%?hq;UF`_x(mite|>I-#*1SDOJ2gr0l`GV2;%ssTz3heE>x4+`Xd|vHkW&mj&Fc) z)m0gZJfoHA+PSLAH|h>5YrTN=M298gB}Mb--(>7cCPjM_Kec_dqe~=_&sxRCwHZxx zVf%)H6uByB+p|PNQmC)GJ&;*lJR@XzYE|4+jdRJ_$kEF^FQ6g}GlvH9n@Ngnbh_^b zXI6xXuSq1qEnr|r91GO)aQ5PXbg>>DKc&`!OF&WM)chMjqvN*Tx7Szu)TiP$fS-1= z!*H+Ji??t{0tN-LnNY!Oz9V>2A?to|UZIgY{ZEs;7tdsB$GIHFX2JQ(4SW-Xu{cwp z4aaF5PVXKd>r7DZy^NqZJ#Dc+Yy^}hIv1dmAv&BwY(^_T#f4NF%Q?)ZLKjl$>{C@R zAtYoBsB+*OL}&4PWY7q6qR6*Z!1k}VeMd2~b}?mgR_P^6gQ_B4WdJU4$q)hW79d|Q z<2O-d8-QLY7T4Hory~x@7t`veCudsALL$>WjeI*98mf8YBs&v51usm3?k#vlJ~fL4 zD?K>gO>pv$w@j{P8p(S!CpupEwn`q&&A_OrM*3Gy1sjP79G+S;8%}MEQwV4ys9?vS1_`Y%vppe_ zgJeg4W25l2$rZnuNwr@|H+RWLNG0!9f0Ww8?$MZBNqXQ{Z+L~dRPMBUyecCYWz@5! z{(#?lM%sNI;p~PrU5Q1y2SP$XgaPwmmula&je&D@}c}3N+-xp1$xy2 zhGz>Mx(h`R74Dc4i7%GCtXRZUU15VW+#=fMx-rZ<459!D9NCVy7j+4%J=713WaU+e zO+P>JS6O2Ga6AfdrPbBdUG+AlRSA`M!Rl9W)9($Ziy<^;Oy$DZ5%T>ayKq^Mh2l|1 z0zUIc`VEZy+?`!{+c}?~nWB+j4E8l^%^z3eUh#c;U@`?qMZYjh`j8p-;s%7=r~F9c zP6^6M3ag>&+{aC;_hd<=NQP<5cWl|jjhHl;j6$|=LG8ZK^>_=(9Pm~e&@rNa_zlbd z+*dMVFjf=f^*nPgHaSU-O zUmLRhM!dZin=TbExnDa;ubWxo-l8KqHeAhbw~wXAtoEdgHh=Ox@F7 z$@&@kUltzjdzbp7-Ec&P=C4()mEoJVR;f{?tGEATi8b&Z(`T8o6S;9dDm?P@??93f z6T(iLS(8DAR=eNyg`dy1WLl~@kP|T0e*PjjTsVg=xtm(RFP}P3|bB2Uv#s>4XLeao%Z5X9ov#6$TLMz7my(=J$N7OF_%?7-R8w@job; zXOzObHR}f->(0&hHv3q9LmRHNVdSEU{C2@xnI6!AJL&tB+t-x9WnZ~{B4OKXHT{fd z@sv-)(NAHU1y>RhLe{5*@39EcwTJduj-`QO<-q}qk$=ygO89Yee_faF4SKT0_gVe~ zZi5A9he%=O7eZ^U2q77`inGRe-5+Pv{qTu`?Q8Lmea9y=GW*eT!MWd+xR6yTd{P`S zhDW*^V1=IMBdGsAnUExEeBAyxlE`l?g;uM$1xB}hrY}yi_^4EOB(Lu9C~A7UCEDgW zg9C#U#+X4nu;*>&+L-3;jt~h8!)nA+`!m{e1q}@D%^MXTYt8{iW3ne#czX(54R?u; z8pEom@h5FmP^>;n?dFt;d2jYoXY>}87xSC#)14+HeU^`qBu5DhK*9PlTHG5FUv;g& zhZTj--|jk6#+}G_Dpm(W?(dLYUvkqf(BMtnxYdH<|Fl^>Uu5p*6TS|vh~B?%A%1s= zb~-sZ0BSUtV-duzh*kHnXXB(9AiXNvs0T}PjqCxvO zI6oq$hFIn4TFI5`x=K^2?xUr8Z`ybW+h4kM^d_`CEyll=vR|oAD$pBbNjjX}8G+2U z>Wkj}P)`LZSx<+yO?J}3M-SH`QlXKAmkSbvuaO!I$0~;poE3Q0#;AQPVr7zgM}_fX zq0Bl3L5~CXa?7@LKc<&0*8;96xoX;WKX;3!3m&fR$L91U(SQE1v!+S6Q9CqHjou&d z0ja{kgEQZoO!)w(>^1t@2`~+5oV`(@cx6cF3e=!lu+iF*La@IZ`iyvdHTS-Y_&o3l z(#Dizuo-P1k=Cdc{L2jFeKB-*02=P9SD`XBYH~MaQ@Z^m7uAVVYw#}F$64sPfeRg6 zmZvWkfz)w*mD%<~Wj$S}wH(MsR!gxM)f$p0YswJOdgPY{UwlmBcWZW*uctw|@^qxK zsgt-65e!@U!hPY6=zu-wRuJ_dPUvbQaE+sytt6Mh%#%?zD5%WXG*@^#1CdNo_EKj# zh7})h)qFRvj=$evYuwxsv-(=w+T&CHDC^CD6}khNHUN)50(%QduuBk3;P-bYd!u`o3TFgz?_`q0 zPNL$0yES2f-OSI`1PzW7Ht7|;)Fy6f^dN;lH%Yi(prc+M(6S>p^Z(oncC^xx4qFbT zT9>35JsV!>_&`#ebBd)?jFDmr=R4J^=H>|5F5-|x0q`%dB~z~%KZh8VK`{nCK4rMR zDZLaGU4H`mYHOZchV#wEK=Taew?!w{>mY5&+R&(>c4Gq3*Lsf`gmYhS^s_o)|{H6#uUn9^?p~B zc?gw_9!$dD40Z1Q=5&RRmb zBAlOyC@m$L5@w&M+siTNH9u`2Jfms(I;%_YQ1V9(?7T!J{a9>A6ufv+C{~-L78T#i zoV_`ZakZe5nk80;+WgDXx+Ppv#m=no#-hXTJSM-;rK}Uyw@am_?r^)ShqsSr6L%kk zvMR}$?Mh&uL1JGcxeT6D1;5%9ALYqbEfE9O@IHxjy56cJksVlaIv);v0n}L&6=}+w zs>|_bOh6mbzSr%MAV|$K4=HFcw0M}pp-aSrF*K)nIsJ-~^hV==)Qta_A~MR--YY-m?0N}exa;wL2XNZtT#`RC9?j*JROPyrSLWrhuP%K325=<78g##vKSZ3ibZl4 z1U7|Yt##K+X3^79wBZLiksH*y!KHfpz34D?))t9>0f{Xf9Sl|a5zk7eeDiq5TuC~Z z2P2fPCHB(fLK|MyPB+-z6ISJ+KqPp*yDB04mR!Ksz47Z^l3vAI+;$0+#L{)=zi%i? zV4>d$OyKNIsH%!;ieVR1)}VvT;l6(`51elC{f6aB`>m9e4>v{MG+YyqXl31=BG!@3 zp=I>Gg+;y^#b4dX^4@H)C@DJ&@%FxZ`ZksQW@wl@tM#*iV#WPg{}GV200_HddiqN+ zr)50^*X(>+>zl9DV%WFG4o~{f%AcR6R(e8q2;K>Ci5K(aA8T1K>2&bHLH#^S^3ERF zhAd1px^{HNBd2ej{MK4dKW37hcRW`TYwfhNdY=$-CK*Eh-lDScUG(qus#yECj{FVw zD1EGni2ct~*GB1QYBZyg-~&E;Sy`yoOg2zs#GqQ)4hj5VSs%ZHayr51 z?;l1m39Fwr%=gSR55Nub2Ja5I;U}y;7W4Eqy0Wa$(|tgeDX~u3C{2=z`d*agsxp_a zA*BtIQ!&M6?I`1kdd3K!pk+UIudYU87XAo#T8xYVdamIiTCM$5)s;P8#C*egYC8lT zAlFjC<9Y=ayM2~l1$WlJWVZg;X^+V8J`veVA9A;~TqAoTahE0u?PjK0z}b=C)<5c9 zblpo}|9WjtpRiWy!X_&l8=`*=3r6?};V&8NlK(TM!0ZZZ*=~UXWPl|8Z3kaS)e5ZJ zifnHkS>kq5Ej;GOlKoZhg1!e>$Skr3^BTlUI=t&LE=3c%o)J=Cw3Yw*8Nco3h^7JH zf!KTv6!#XPnmI;JpaP7m7!kha7P?a{GTtNJCaGdX49XLsK0@A%=rl5juwERwqt`s;hc6RK6CJuuuGvlZ+fYKIp~Mvk!Z=*yG_ zr88KY04?eS?4Hd4T+8BhN>efsY+S81iWHN%uMSU$Ba!N`&+z(JPSadx@)@ZCU(=yd z+wP#By@y+V;3f*O5fHq%Vp_aFGAd@+d9L8h%<*PtD7Y;b;?FbV6Z(Xl-rBtEapaO{ z1dvEXf=DvtIjM77`PzVP`uF6>EXy^QC3d`z?Kezoho}OHU#+BF(ZNYNI{x>IlyhhO zJdakB%APLM$#vaRA8}k9>pl@n{22{0abRWIC2u|t$i)4XDq4ew&MBZW8+Hj}L;LuY zSjZcuQii>GxEL+a$nmWF*+)E7w6ayvn2rAb@9GL~|xo;W+HWH4f%(rt%D@LiUR1w;V(C-x{yeh)ovd_tM`xQi4bmG4wA%zOF7N37uPEU z&~_4d1VEQpOSMzwml5q8XzRInb~ z!*J);5SeLvp%)_v*AvKAeUxc%? zR9-N*ma}+Dl19}fIqH2}aQ#CPHXaCzrRTy6U4`d8V;}m2s*rukzl!gFl{|=%qC?Vs zx#q_i*@?0uK=%%-1uXNw$cS-W`)r}E@7wN{JM`OiI^ve4j1G`JoCyhvxUt*blGuGEZbA*#qH6yIsf?=TG0L_cO{+l#M_RZPeN()Ey#SR zEUsqrYOr-mkT2InX39smS;WUXmNXr9e%E*#Ax;puviddcQ5-ov75e zDT76y8f?(pY%t`1$~-F!>Zc=4WS;Z3jP2-uCI_tr%B%6~xVlFb+AyI*hCPB>We%VC ztWvl4%AujJLt^!tuGwUY>vWmku;G9{iUJ#w-8O4VcEYKF>7l;}z_Eb%ASWJBu2N%0 z3f+6C)J`xM0TmUMQ*kS2=i3l~I0_Ykjr8foLUbsXf}eJn_Ntz7qgoSWHgDc$he?A@ zA7h*le>xV(H%BE>1H-z^_Ys~+-bL#`Zflh(V2bKtVm479yiNYKUBS;0G1)v|DUHc< z*W1m$ut+S?*M`3ooSK^3XkG{Vy#}r&rpoob3T4Uz{r){mCM9+4Nj5n_Ol+*t<0n}P zRsL2&h!Xt-6QVrT7DN;=;%Fr=N=O$mzn7ev9KA`{!HFK>PWtsp7p`@1C*GdWLGyrf zRw@!6MzPN@Pkfs@n*jQ}7Y-XDj5v_~o_{}X84=QT9T;XZX{2+HE4tqT7`gspgU}c`3%HA8(d?y5lYB+^-rzYIYQI7yfi~jKG zaQm(xj8W7p!Tpo?r(l3fy2wH+BEb8xHQ38VO)ysX<;_Dj^|>TR@3BnLj35I=t{ZPqe6(*MWa zJ4V;lbq~C08na1bJ56I7ZEQ8RZJSNf#IQ2QQwt|BrW!`{90TdiL0R zu8F1jTXXFU0r#BM5nBI9X!5N>*H5qqwHQ$4&ttPn%b)JVFMPhNzt`}XuE72CZ&jht zqScw9$)VF7lekk;^=Irb39;nYu;x{}u2x+9>pm7?|HV_<9ARW5qPI!ZgL($|H8cEz zMRj~Q{*%X48}eaChLu37W}&r<3vu7*_a=qI>Bp_;FIwaVF+{57eyy1Zp{nkD1It+7 zOtexOIaEr=H)QP;gM-4k*4ro5lE@2Qb7&9($XcfAFMQja+@$T0TU%S9p!QB9LYXR< zPPOjEkUs30<>P17>UAsmCa2oaS(!cF3LU9b;RVtoy^__S!80=(o1aJIMZP<}U_BM4 z?O$yB_}HFe!Im;BgHb^*q&*X>?P-sCMZoKb+R&`O;7udtys7 zPg}xdR3hi&OzRJ(204L6>lKvW`p9G0@|1q}(xsQ zjJa8hUN&T&Ob(mG4YUInu}$rXs1;*YTRUKr{jH&o01GY6L$;pMH2H1oGy(9ofd7b zRa&OB@#>z;(@LqeRRJsYRVF*Y>N%Dr_-ubBflx4(KyBqw_PAs=_cs@Nq*@=PMkdKK zG%!7K{2Y}IObYm7O>Irg7Yx)4&m*x1Mi4&#%2W7YQ=WA&+lvdTU+9c`x*8Ua1erX4 zuHRsywBDc5&KIazdLk^G=wR>8sXF?c$BD?|P}AF`5oyviN6EDS83}k4v;}nO- zcG#AszOAZi!Vd3`{ZxD5_VZElWzpC}xxX^$Yqg+0$T3{t?_dVWQ%H^rX@DfHfi^!tZWPY$-aq z13GP`G9=bqY5gr9G4#ZlW57IvlXHRQ&)mhkSzt!ZZup1zUm0{SaoTi@tXr6%vm4b!{H?eH}>rZpUx z(Bd*nvKN`fH454OkxTb+djp8v?Aq*#{umwB@X%#g7Z38(z$>Rh4WhYd%5!tUolcXQ zocFjQ#N?ONUESQwSd$NSV*O+Z%q2kQ2Fb%As=PUrzLn5j-sdOrsik@C*oks$$bwr! z49cTcPka4u;6mCgx!%NfrFiT|}sf++EZ(Jx5)c+dmP zG4H9pLw&-cdRN{WTa}K@ecWXI`41D;)6nht>3d$-e?s_O0{6}J$=;rTzjhd@O~3p- zlJg1VMy%iOFBkYnzIZJST|43^jTa2+A3yx{csl`R_-`}Jd_HcPZ`yqMK=Av#H4q5k zg802aCoaI;`^i1OH^}cB1s8RW@o)O!KcA`J1uC&=7x5kFUt0Xf2k1lqdjBspwEZv* z{lA-eg5xgMj`{_55Kq1Q3N zH*ukQr~jeIznwH3XhuIHEnD&5|K13saa7RP>#_dT#{YbO`x-EVX?}JjZVCO{-}%tr zLV^fK>Gy~R1{V-wfKT)2r_TJXi|u83E1}PBnj(NTd$^Y$oRg2`Sat45v*<`XDRW(& zi2*MMe~(|PwX01nmG}D>NCO-1D#J@c0=9a$!%M7tXYK6js$>2YR4RRd?_!H$3>@M{ zTT0^RpKIEGL`VktoAiXkbM^G9-o=@j?@rqu!{QDK$t7p^th3*zXl*1zo=RV%dj) z$p9A4)d*AOetpPhyT!OfMb%w3%8dwI1>HS7tl!fX1HNDGMtUB7($(86x1YbT+yW^4 zH8cK)Y_8(KXtwO*c^RX!S@PV-eACb({X;4-3R1c!g+4kkOARN2@@fSRn|LK6wMETtC_{sQ|q=VUw-Sc zW4u7CJ9Kfs9w=BGuc5ciCH?HsZm%%|U)W{aAJUp8ljkqzx|h3F0{kzVLpKI46{p#L zxXTOZSp+<;`m}PND84`wR@@Eb^wM$bW;LN&$ixSJsl;1p0g_(l!RfCO;@8u>Z3tHQ z>3AbP{r?MaJt(cQCZ4XxwIbuKz)@ZS()vnT-Wfct$mzLfvR-H>9fQx2tGIoX`i+aW z|7agpbtK*L&lffvuNTJPjD_;kPu-|&J4-#_yfIRiP+%VVzj&&AkVCRVqTf1$w;MDN({VdSj0^a^pW*|%fI~=oL($*rdrcFl?}phIZIj=J_`?^V zV|elz#r?x!{(AZZ=*<70|G3L3^d2^r?CR9SA@<6=J4cgynH8Uwp|(mKuca2o2g#QuUUd_mC$|^1i|Fg${v=HP>`CfrfXzNsRIh{u zRj`%UB4NGG-3BPhiZ-y6u27mLH%91=mdI&E`7v9)WtNlD#HTF9LgyZ5$ev#KGM5{; zSu3*LRU@42foZD~sGrsC5yKY&Vn=i3m}a*c9Uwok9TLl0&rm#H!zJfblvlTzTrGMW zAG5&GZ!CI?@Q(nT_$S?mfmRDggPN2-Bs*(+BZiOg9_L!zVGyhUr>ku59^Go)Om64M zZsfj>)U{+wxQ*tWme%1~5byqbTvQ1*qnKU zIxd9OhX<-V&Xmtv$0d;x%+wgl_+-khH zGeMW`N!HI|^rokG8)4n#**R>AgLS}QH^DWf9L&&SQoIR%wy-HVT2`5@!D(!XG4ZP3 z7A#+G{q?6u0D`;03@oskrM6J6&_Q2cK#V zQP3Q2xC898t$*Kc>o$Kax(g5Y`oUe+ncq{LmWcBS(Vf$IU3%AG$w|%{k4VU%d$%SZ|aRrkEc&O+75Jph#})4sHN7-4C_e0ju79!ZFuC~>G_uMjNh1)? zFky42-a54x1%CX#V0(*%Geqs@U-eppxPjFFQ1{Wo+ zTG&o@p-QKPZHHQ|F$)>uZ4Hub6rl2hxoY|kdPJ(;xeK^rPQCU*qaES}6uya~odo<{ zB8P3;CG_p!tm5dqyFFBSPvVLTp9YN82Rk4A#c#fn6EWvmF_7`$>$Ko$BASgjmsz2L z6RdHYTdh@d(ee}@-6Jcl-ziGKF^>qqhxOOu^_Rq0#KQ1HQ#k9I=r(CFT}_j+c|PHU znUTqHTFe=9RbKFPA1s`lnHIs=X!n}nG<{|c?`AT%!6_C&n7p7EVEa_#O4L27S&}A{ z@?xJq<(vFHKR@HSb|{TR@@a(Faa@inhn{5#Ky4`C z%X+=nI+a9t?PZhXF>~{+ES}tMI3Ur3+(t4CQDe%PtBU9y_#>iDxXB_KS3SzDU{Pgt zM%Ee(LRuf7XF{k1hrWNZ!nCyziQG@jO8i|^zVO=BiUbOC;Kn&*pvhp(OQgY!mpzPA zXY`rxmL41m*6zXJ<@xyyV_y@o3FTxWV>z1vn7hQbM1;jCca#a0kqk*37aRJ=uAxYg z-N)L!P7`zKHlkf`u&`(T3hi-^dfJ&;LoFPZ7vd3iUtTo-wa9$5PVz-Zt(o9yB%Be7 zXs>5vnP9A7NOWobqp?~&e!CB|BQdOBbE|YYvYtZFW_o3a!z>np0sZosO|DpOw%ZA2 z+_mqmZF@CwnrIA%@0F`cMGSk)debO(OAqf8MhW%qL%g=dL+bF9x*bPf#FK=(6HweO zzn`a6N@3foFKCKSj5!~aFIPS;rWDQa3~YKklXP4zLAxtX1yz$y+FS;~?uLG@ZzM}& zKRipCYfZT+bvb}lhaWNQtGT>$I9|YHHYCDRiv+XV_BwW6q3*T&e(o%l%nB(^zR)DZ zw%8`*)OBn&_q0!;&XkLadJx>0Bs5twy5UN-I{=w?Skjn=W_Z*aci5p!Clr1xd{JWy z7)=R`g?2T+->OeBgJ@{&S??z|Bos>((_ot|z70JxiZzlgS06PizrB;ZmvsgM{cK6$ zV(w;D^#mVfcjyU*>SRqVJ4;qD?qc&QsvcwOI>`RICdR(YuZn|rxkH&3hxz*V5^v1M zn-(wa2Is|W)eRwjumyN`?RCKQQB`h_Eip4hf7h1UXbA~PLhD$s5SeI1F_!#SP>^TT z6uE2WA-{Dl&o~>+$E&3f0q7_fo!Nrf^MD=(aozku4;64Jk#uIptf=$)oW}so66BCm zi8Qj)P&v+H&XO-Jt6P2OM*MzpWUp{s?}D51eUzNzoV8*1#4FFo&)xUBdUg9#&V!6g zyNnhQGWZ04=+~D&$~WxO@VhVAb8#4vSjH=6{aQia6bb~XJmCzy*d}7bLmWyEIy=x! zsn-qLH7hdnO35AnrbzN7lw)b8j)1ODIH`2G8xu8@9{%3Qj)Y(**ciH5L7mONCa^Sv zhG`^0t4GrPYQ@O5pcy;NNEMav0WA(t895vNJxTj1ezp^Rg#0T6xc z0iK|4p1B5I_1hPb@y$5l{zh$E*GR+WS04w-ql9nBOQbTvBj=VB=8gHvPd(B++)_nm z_-oVEB?BJD@w4 z*eOJQp7k(8_j$PX&#k7!r5#mSa5Bo`V*SY3q7O2D{=paeO>eMCcpI>Uez~It+4rs7 z@f2XcH4UlL@nj+r%s_A{QtD5rA6Ag!cB7x=+3h?e85XlCGVt7C^l7@b=e~71j1qby zh6v@h|K;ZwQiK$m8F#hY?@k{!gc3n}U)}{FAjTyn);`K@UqbW5f+WuFRU}aIIN0K( zG$|M%Gq*bAH4{RA+{OqND9EY0Dd8{aZ9Jy`CNCeV$BEeId0xR@>p@S%0jl}gn98RL zNr&(viWZJ0t*LZZujWS{Vx{z9+2+TJP$}FpT*O=@#@yGAn_Zs{OP8&90ZxP47@4qp zb%(7-Ixj zIAS->KSZiAiP*kcunS=?QNP+?}4} z?{p^QooVLo%uZs(Hg8kuH0XSZ$p{5bzSAsQ3iO?UHN0Yl(KV2pl@9a@4*F98JS3Ut z-Ko5&fJJt|aRgs>#@jTN)c+C<5LWcoF*DXdlAeo)R>8LRb3} zM^UWH(w3-5tSW0kzua}f^4J~|yoZh&w$1BYMNluQzSgJifePp-IZ_5Y>~(H3)bX@f z+R9UjBv>)Rf)VL1>_HA zausz!=XU^k`I-eC6CH@=#wxI3+ws|WgLQ9#Z)8;N7)4}QW(uLc_8DGzAPT`k{RRyd z2~TI)7`ueb=haQ6BzFoWUHO8{su2AZ48SeW5mcree}8hnUPf()lqXj58s(CA7#tK;GNxq9vkHKlLbY z^H~^INnSOalC$C7=0!(TEN z@{JgC6*pwKp0^>9%9i9dyxT0h&ySFYuq}77)`ym50(C`+_A|i~Xl8Xnrq?IZow|4_ zR7~d=j!-5Wh=SGND#O#_JxFSB;ce3HuJgo$#8LyNs}$G;EgD_&?l0zh?1s-< z>G7K?`v>uRV5SlHA^6RMx757B2X_b*n@T%#%|s;@hzhtl)hY9h`i0ybo4&fq2vn!) zX$}T?{k;!iEz9Sr-Q1$ln#}9>m|qGQF~2s*dH#UR{!v;GD}R?6r`JtkR)(~UA(4qo z`LoVl|1D$zbM2-O0f4+mM|4dYI$G4jx3cK?qaBMNsLrH49}oa!Q^*v?LnAO5k|0>c zh>$O05eX(_mH=Kdw33jN5t{vi(ptai5crOL^Xt=L*xSa)re=fD07-{;0QQ{6Cv2lK ztYpkW$+Gbx*v$Q%);zJIijSXIB~a@~v%ZzhS_*&$28dEq5a=zEZ3%>QH)B#DQ!!50 z%KlJBK$xIgMof%@6I9Kjy|69K8jJA$6?JI)E(TcPW@|zw?;7&^1ef`e+)qP_w0K3B zM1u%E|Lw7<9Pr{Kg{wisF+rgkmh`Ku>94YdBalO^`z}V46)!)ZkTW~+YLH1HZ;6gP?0X^zuBkeR% z<`+4KMJ9pJSibIa259eE5#=stoVn#Xg$kkQZUI><(jRKI5*X8rniam3uLI z?9x*<-OLhBV^4ychF=4Ug*Yc=xW&9$D(-{G>PK6Tvp5gadFbZB-B6eaj(V~O5lQmd zQkTk5%?q`Heb)@rbJ7sppCODB-&zWg1ZNMbLswt-J@rLY))i=n*S42TiA`8{=9c$d z@+A%w3JFw>aqn9cqs(JKlmh*Vi*6zd*vpOXtSP!k;qa*g7RGdoF^dL_>FrPFV!NA# z^wKE!@}j1beRCib^e6jw2Wm)kbM6l|LdS`>pGHk!aTf7d(Ry}2{k<_r19iug+9^FE z+4WFhSMGY{ppyOUR4+c?)?0UBrR|?jYkHkGfEb{z9!ti;=$o&dxx~z*bkga@D!is9 zdRgwi#8jX{RuXHuYMlvY5iEgi@kBUhbroM>7E^YPM=8SSB=IIIB?fBlf&;-ux~KN>$W$L5C-XO^r*m(w)Cpt2dlkYurkoiZEe3 zD1jZEnw9ku+eZe2>o&LjoiPb1fOyJ&otf57=ayW#i5zyj(U_~z6bUVag=Vc7MfI5Bv=*Dq zPF2GdAIpy&d9Km%gu}GZ%2b*rH4F5I1{T-4(dYVM!`i$&P|2MyO~;PtO&DrSiy1g!JoJx|`+#FK@DPdpbCTPOUTh!9iP=vuTtVW~4S&V-a24 zi*t;Am>ZI=&f#=i@@bMIIvj`YxJ_zav~F^Hf36n;w61Tk=Mu2bTrs&_-KT}kY%21k zitRgfEmUgo!qE^%8YXlQRLfU(oP@CaoR#B6E3MAiif!hE(}`7psTvGo;%j`gTCBlQ z`|;?QIM{B*(dN3rt+$lnjJ~(?h?ExHw^UtRonzu?x3}Q$o#5(^XHh@jeZRLNly6lG z567auv}l_iv|6fRmB}sZ?0G;g*wU8D0#k{-Sk^=j{xN}nQ&UMP_kI)9vft~qLN)ZB zW9!@8VQFV`>v0plBH$N(CbmsUB?fEJMUETVb+k4ZS{N41cQ!v*IiWJ0+s&qnMNfqu zsV6hQX@uEl%MuIQCoTf8lw~!F z8Dw&|Aci{j9>$k6$Z6U2OUpL3wgcganEdEkMOP{q2TsiNNv$)nizyDmQM3Osqyh*% zGDVz^fSs)Q?DbWgno+@ux^D5f;ZksHy$s1mYR3YX>yg&k)n*t}q86*^j}yh;z@4!o zw{?jc$0yHXA^px@4j<3<<0K@Ubxa?LFu8Ibn@G6G%d+66Q!Y5d)C+o%<>96Wr@VXy zDoI}B;;P~WC25*~7n|mDloncw01F;(&w4<-m)eaPydSgi?sRd1XCvO1m`Z9GczxOU zTH;lcpi`m{U449bgkO|B^xdwmYTwDs1#+7{-$cO?y2W9ox8^c0+boN6mP;>Dl04%k z6X{;5(11$LRPTYv(4Us!1WbE{X#c}%vyX;nnQ-FN*|cDfPEOfYA`5(YUpPF{GUvCH zxICw@WkaTmf1PpxI+i_oyEI?2^u< zG1vY2_NKUZhVfJ<{E2ro$}E2~DuCt=tD%Yo3;HeOw_kltbndZ_ba z{(TF*-UpXJ7V*hCZZ@S)sRHrwUute-EewjS#8UEgA`?xRJ`_+)s9arBPKa{SarfK9 z?sJ2RN4+1jeQs+{LA#V;Ynq07kS92R-aGo~I#1GJmM8e}rLWVW>Jz;bKy4=cow^M5VZGNl;%2`$*Phr zz3_M8ZVi&s@R`n#NhSv6u$j!rXoM98)F#CqE7^MdRD_)|JWu0s41HyUV{?6ifq}Kk ze6XA?BP|iEVYf`d@hmB_{_E&HpAXBs%NEJ?&^jBLG!hFdC>$0{allCT^91kQQNkl# z{~ayP{PqNQm}=%WmXvWU0Lxk|uOKtC5hTBwUf64h9eYNy7_F%DTP$R!LvwNZ zCw$t#qUAo~P77All`=A_D64IEEni~Y$xECO$8c~b5s6_r`gzr4 zFNfz$z+9ZG>e&41P$F+LxJ^CDD1FN~eTT(jLw3U11l@~}$~P_`FZ@s$kZh+adUCcD*H#8sYN61)wasc)v-mJmw;d-_qt;CBF7h_R0 z>f&1*`i&4p6APS{DGJE7^Itu4{><4fRe_?HJtI_=saSfVzBt^nQjupoGbmQcDl|e| z@~Y}F+vowp%g5cL4VZf$y0Cjay;-TqG@5S;C0(Y5%Sq*Hs@@5zG4(` zD}I;1PBob@iACY9_i6utKA>%dIcRbN7@nMJy3eg-2v1&}W;I^4xn#Pw^eix#s&6D_ zk!Ea5&R1zIvQgM4W1z5j;k@u1g@1H(pCVqwLD|Y+e8LmeAf50}Qr$0H5q-(rXO-0`u$ zHZe@0m4s zP}^89UBy67{>5|D`?dRqw;>=-$9(56gRT~#4tgK{+@Q9<+Mu^Am#6llf1r!FGd;de z*QI|r{zkm0kRvMzd9Jm{WGCKbMI+e6H0{1>yXm<8p;*(DE?mu%MS2#Ej#|%ZX`bnl zD_)hjhGqx}=BnA771SYaoL(Mb;=aLDWRH{;3weJW6q1?Lpq^|vfR3uX#*5m^h}DTE zZ#>ar!WYBwr&)3U42MH*I0ZGEVRFK-QT%{5LzT+(h;Rdi{f8-f+9eLez=vtE0yHsDb2WdhU13Vvki@Y>cRo_r3BbDqZNv6fu& z+gWvXN6m%UT6MO+daBi~v%Tv{W0EBz&2BlD<3MSp!bs`8{mmb$wpS!fmRZ?Xc7m$H8Ic z_H7sVd&dXJNV!US>#S;K8+0xsN95;Gi{kv0WavVAIo%1ra!IBu_n9nZ zCG>qVPscmhuwUK#&V8bcron<5_}dkSji~W|brNq$r@cyVQ^4H#e?{{JJusRTny~*P znj1uOT^hH6cDydtG`FOHTE=y4YskGsJ}@hi)zrvmHiLnJ6A}>FHG{bpz#0*EIUSm2 z6&>bbZ*iZ}(`O6f3!Xu7UUP=4zCZ}`Gq+A>ofx`vBt4lDnUpNxMn%T@sZB09-qb4W za^$!iX7(4t>niL$OzqY?&7iuj)4cGZO-qe&6-9Pp$!a(WhSYxFz=7g%>@AhAiBE&8y$goC%R$ke`h}aTiT4ew<1l!mD$3* z#<*8S(`}E>UeiFn{L=H%daJ5SL`*8qFs}dZxyZ{0Ca*vzjHzX*ar@(?uKVw6m|4Gn zHv<$J7ig zJNc~Md_quR+TM0IpYyB={}l=n@u*I0TXzH;?Cz16!B*h#O6m2C^J#C@>lbrZNF=$| zpCtBwxA1M1hyRYH?r5Xc*wVCf&SsO&^D7b1!Av9t!G~5{4khpI?=!2bzX1>S8sRMQg4$atZ%O-|-*+e9!_?YcwZ*x+WYA&if4Rr;xnWVqd0-n%r(_-h9?+ z+tw_?ynkX_hL$-d21Tu;3-Rw=jla{*d_F!TbaLoEY7YZG-?rW8kSx!5N z@^?#BqaBsB46#!{=aq>_4?jDzmPn49p}AfXkC)NVShnOK|JR%T)n167?N0J(U4H_K z;O}$$-WR5P{Hl8%D;f^}ivcm9lR;#rr$t#-)<;h`T)UL3WWp9Bo(_-qwTG6Pye6y& z%aF6y%H$b1X8iNr?R+Q+c=Sk<=n^rG%GrlOkKVkk=epAh;#KjqB{{~z~O@-iH0Ue;Oz zlrY@Vl8B4^%kTab8GM{T-aOrLlC7fxXrwR;HeoOTKxq6oote+OPco{CJRsuBUV?^p zgD+J^BmWa2f4tw42#7)^A3>q{{nlU^uW$X=>ro=(zekfdQvpw7WB`v_jB0_|(!8tt zX>+RxEn`2(rumI(^FogQ8%aPPEb$g9Gbq0h9BUYPSZ{>rM-arBN4l@)wBVn{J_2(y z*ZxG4|E8+{5^fiC8@0Y^7v#jVQP@pbF!yqMdTG$5o|5*`dX_6|CGfj$UlcgTMLY5_ zu1*)#u5AiXXukZnux7$k`k%MOgPpAOET_8JI1Mi@aKb^vE83Y5LRT(pw28WPOn&5# zqXk67YmKeY>j#O8S`)qnL>y2u6;tcC_x|aTfBC7az}iV1ZvA8MYsa$P?r}akrVn9N z-h2A&(BV(H?760j17`Y}h z5!KHDT=j4fvk_ZAW-gCCu+YN^FBlNxuDebQH9#bM=&5N7YVsgZW11fCX|XRKczHV` z*sF$$F5k}iddmX`@|25u9=ns~d*y0p3Sf0eRi%M8S5z;zXWEi<_J5o+&>n*k2`!6z zeU@K(KKpnmIkiu>C9dV&tE4$)8mcmS^gvGCN9I#YxcZ7lP&25i>Gjd+^#x5vt%$S` z)W}JR>j$AU4@~&$quwzgh{kB>+dSyj7X%-C;=P+o4?p5&PkgIC008myd_7w|+jzUu zQO#7qG4v0AZYMymf4t$q$fK=Bh!6$$C0u>L;;IVo*xY>`&wf|wm@=U+znys@=hB&V z;R~~1Vi3*n$3V3v0qm-bIB16C-&OXA15aSqmR$G=QkQBDPilgLgVPib3;rvz{xmk6 zwhyP%Smi^J@x9h6xV}2}x2l0rZiG?!z>p4l7Z`(d_^5a^s_13~OjXV^rR^Ao!phpX zs!^C8U^!hMAENf1xaB$d9guk|&ucA=_y}(#Sy{yb7iTyhRl})Ydo%lmwe!N*yaE=I zl7~!T|L6fRtU)Kb$IrG**K{q_#&cik!g-%q1BQ-wJ==u@J2tJn+Lz;Bc-@RZl?-BE z&ha^+pgsk`21V6-2Z;R?KM~hgO-hP2$(ElyiHT)qRGDWumYQtq5vuA}XRxEBR=3L~ z01k?e{dtHaRWYgdYkxdDhO8~kKpkq@{XOzX`AC+8 zG&Q;gSt=c{-c=1x56;T!#P!e}eMfpg0D*}4v&GFb?lAU8bL;a%bZ?oXG+N|<&%eI< zw+PmLA4HmhCVnu2a15+%K`n-_1`dZbCFg-w^CTW`cx7=tp~Z0DBB0=+h_I|Xpe;hjBE4rT?sKYyG9vLsm>{ zzN(qSJKC5O$dYFHf%aOG9%^$r_DH?@_M8b>dQZl23u8bpY`}iDjn=n!ZrxjpJlocN zf&nU}%^B14c##Fuplo}GIQfKn(q>T~%skJRX*hol9^_HtD=3*%<43*T<-GWrl@$bv z{{9EMu)m|>->R^*2Q`qU0kO43VP0C&eq5FQf=1t>N}8K1Im1Whj{@K%)celI0i_`Z z8GWm4Nn^f8Y+tinUHDa2N*fejGNUFLwsc$5YR^*5-3w^h^$&A3cAUU!MGrTJ;pcUN z#?h+o?mY(%b1k<70}gKJY-8x(+QwGyCWqYC4i*Y~E(<<;XZe1OL8JJ&_;)RU1&;Qf z$atjU&x|y^{~5+IZlK$TZNm2_Ee`?at1hTed&RtM>%=+1V+BV)c5^;|Rm!Xyp5nA} zo>qP{c%iiSYJJgYh}k#4cJkMI7;>qXuuBv44^9V8INWG~v~8Nf4dJ}O#YaWc#{I72 zQNCRIyE~Z~g~<~!C==m=E`24o;D^sjNop~{)xnANF^Ph`RO%xGf}XOuHTK{z70lS6+RDt|F4)#0`uu4ifMV0Zu!KL9nfJo z)ADe+gN($13XhwkBzz#zX!jOK+i!gyNAnKwvN*xn_T;m&aGr{}!m299*45^IT~E8g zPo#A_JcQimJ^A?B)Xzkh9m472sIMYifx;#lex$FS*qUj~T7%TAq!8(?mn^NJ!_(Q= zj-#N>FH@iD_vNc2xsIMsA)Rl6wXS}&2)%%xwlaX@QZ2)ix`?oQfMK_)2b8_WBY+Ke zq;20S8*8l=%aZV3W5iiXF^PR7*XR`9=;-i7osj(4jWAv6=$O|ZYONViUC*!FLj2Cf z^t6OidxtxFY`7$AxaAWi^r}Y_KJ>k*4x+#kC*JyT>J(VEo~I&KSSnlSGyQJlF0wy( zxPMpAFR=9g@=GQZp9@qo@0d$5CtcGGNGq0av+ygU(@HN0=|AMZG$D$lem`MBAp{P>wF#>@jFn};`m$57B4RTh z9S1SREt$n1lC@l*SIIEs7hjK?oJ;-i<0N1iMl!1EUwOVjFfiDgbQW;Gc9@Tb&7GUq zlTCl%ZhV5~x~j>O(##~RDx^1AtL*J|I~AZSL03(11Akq#0T~~6E3Z3*3o*7*p3E$i zWD#qapq%>xQMn=YC4s$=^Jqd^k2|$&!5Pjgc^W-T=qsx3ttmqUYF$Tn6J#g;{@r>Y zanREaNFxt3#PQJF6AfPI7yaUqt}}=)x;YI0siBO951%Oc6Hg#5GoyyPyg(blSKDVOe-%n};z;Gb^#AePQ=E;06_eY{2z0#D1{xhTOfQDbHiMyL0b)1qZ`t@3s@ z+i2eDjO#fYNz+t%a{HM3cPWGBYMQ-z)*FnGsz0pikYFbjei=f*ODkFX%JL0;R}q8t zGt~HM%2z3#XH{@W6a%u2rgYs1>%4%A1f^@Q5WA zDo5N=wua?a`|H;?PR4ML(mk0SCu1y5!(;XvMe;!~AW?C+x!iQL-MspP*Fkp7uV8;U z?mHv%t8fvH|AgpM_lhdM;`Y&s7q3{d^vjZ_l^Sv^Z9c_ABU_ZXf+ccXaR|}-L`F+o z9+358u@5c8$ZoVrRVb*NYe|xGq7aqdV=@B%_v`LYNbart=Tn?D7LYgHMwx2Lp8x?5 z1gEyhism8F5LGeHoaL88%vUh@8Aa&gLjw3Si%fEg%n_Mz@yJpHqWZO$kq{i&w)VQa zd02fhDLBFCfO;~{NX*MM#{U?gnV5Vo%8UzhY37DJK#UiKxGh?RCu9!_CuIgR?mrJF z0Y~!;()KicRVrF`@ zl~~nAcO&4jVovhM#LhldAc^aZ5V@t`e?4u)UOw*Alaw+#dcS)VI~Ns4%-sUBEdonJO)k8-3=hrG;C^t>Jg3cq*-su-?z z9)d0MKc|=(U!caqXSWxfC^W_^32VcLEg~$Fzmk8Qosf=yS6y4FPvF_dns`2evZap+ z9Fui!1s^!N}3sYvLUDj(Oe+TeihiOOleparZ?Xk6jRs1XzQ6SO_C3$o&& z`s`iyBAb%(8O;XKFvE4uFk$6JxZY-_v%x_gRchF`0khRyM(UwB@7&iqWDVCFz#buM zS6dDBGk!)TivjTiZ zzF-3FMWSkbQta6FJ9^@r%%R#SL0Z9=jCVRhm5tzt-Oh=&2yRZVA*(r;QP?Lq)6+5| z4e=>o?-#>p&xZ(lstyj7ST}t${Y8sExbZrJ(N3!B6D{2zw9(aiFdy~oP$`jdC-6FS zepAghTgl#BBZrBJIS59D&BpvbV@YFtyzi@MX@P`>?EMax+G*!t=I20DHubd0Vk_fU ze&4Zxlf93ThZRf=Xo;4o^bQYfA@DNxAHBH!IxE(EBB4%qeC*E2%iZryk0e`Z9?`t0 zY6@sGg6A6UiQKidB-*Sv4E^hgxWi`T7~9}dijkz{g}>jbjh%6RI2jJdl}~dY`S+yE zCr=>bDIeF>#@ukJB)l#rNYRKoI4+C#updm7!N*h?LY0ARrll6lKeM>G83$I&YqVH6 zkuIX;X2K4GVGk|3v{8G2n`!%E(cb-e#Vdx3ep=3GO6+MLm#eagnCG>Z8_M2}OxjrI z8#16w{DawTzSR>X-oq6ghcK5P>MI5!>jK1DFKc@H-~rQM#^X6(5Vzb&njhnF(Eeu1 z4^q2bZA{Ef!-)7MJt;rEwz-(p zJZX%TOA~{Jn$yB4JN<>pdolsCIse+)lEUy(lYEm&rCnb@=IvvgV6kfcV z772xk)B6C(wVkmo+*Oo)4W-stY}GtH)-S7rE6>a2;q{R=g%U}}c3F~a3!v*H@UkRo za%%e09(VEdpRJfr5}7dTLK;W4pRkxfxl^jn+av=GhPV2r*@SAVCe1WiVvyLOzd1eN zNF+XED5m!bcFb$#_(L|X3W zAD*2k(f8#~R<)UT6=L*w&y;BJ=pytIKAhQflTG%}QBXG4+p{25*f2?clklVN2Snb1 zbp!GTB_Rd=AG*FVys~cTw!4##ZQHhO+qP}nwylnBTOGS&+wSo0KJRyY?!TSq$zE&E znpL%CRgEzg59q%<(Et@Sv7F2&>Od3LuW)GU>~E~bB_i*S7HtpXODxS=4JhnI4GY># z{8U7V7ECRXCcW#-?&WV*>#c1V%xz?V`Y~F%cSU1%WzOz%5&*TxmqHswdEaAKV~7LC zo7xE7#&J+l2uQO)FA~ar-QK#I;-~e_Nm#es$FlOWXY2(e*(r3mLYgcI7j8d6cg zaU>;-ZSb|-xR=pe8QoNon19i^jFDJV{-|(XcN(xp48l>ZrRKnnxp>_k3)8Wi?D2az zYc@CuAsf_zpQj%hLAewdSwLKgC+qyhMfctp4`!nscD&JX^J^SCEb1p8eOp;_KV;y5 zdH(0maJ2_%q9svOf-IL-U^9!7Kt_*^^Uz&Jrpen1X|?v`D6ek*R@bm=?S>^1M+)Hzx`($;r+;zO3)hjF;JFoUhANmC?3zlhEu#n;o4|X zIDJq#JuQAl7z95%dm^!1gndtcJvK1LV_?9@gaIeS`uD8>!M9^unwi}84{;+LEGY2l zJEq^e8OF3t&yH58AuxAdW+)T*F?6ayq10R?q`p=Ku_&h?=eMp!X_+?a^`hT%Dy>qy z7GW$AgylRh`?V?2l3y>~RC+5S!5#v!)b$&S@^POQx^+fdYX(Wosi;%&@M0m|T>9`L zlf4peK~z}evK_NDce(j$VHjj6##UUde#P<1ZvS*V=9!Qw?p3q%T#Pv(r|)Jga>cU# zlJ`8F&Ar|Hxkc`gN#g9#=GVumYP+S?0=autYI-3vzUdDl2(PtDD&S##tWe6 zTUlcaW(ZVG=2}wmit8~nB-UZwlIHEjDl&WRAq}TV%nQ3{|Jugj^M)t$^uZtJ{SnFz0u9Oe{^Jc+URM2vn%}Tb1)q7`jirqazdg^iA~#o+=ywC z_Va@@iTk>?()w-`Yo+_iL%E|rmRESUI8`V90F~>L>ayzz=giqrb}xG*r++nZH`EA1 zr)BrxDR*GM8$0PP0rL5A67}ZsY5#yUkn}R+6GA7AUC4ceoGsckWJ+Kd@w0EdEngx& zK#hyxo_17mUP5NicBg?)li@gkGyrrxA{3P?5Z=GMMUTbB=aB=2iZc(Tz=x;A2c)-6 zz`J;KK-X%((3|_=U{OqBf#U6=EY?mT6^`yr-s}1WOM@U(r9pf>Qh1uCx7kR$L={cP z>yvqXepyqT?&9Z9T*gDLU{RBp8QV>imN)x}7b1}h8Js{|f_L2c6Wb)kCd7Gqxh9PV z&2k!}@zGAL9|FyHH#y?a5IrFD?qUz;aEf_CTAoPu4~2)xmUBWm9@X3#!$WP)VR^$% zM;7f=d6b4UHq_FSh-hjW~4*hfQDgkqWVE2Kcju$-oJO=o?5m#5JPGshbzUT7I<$pJy7lD9fgaL8`BcThfi`bN$;uvUG171{d839X`k@j z+&Ck)G*>!})9}mN!-MV6x#l+`*x+9=t;LBJdWqt?XdpDBR4bm-{;&|3?8|lT)4tpQZEMAe7k{cGGPn?c)L1IUJ2Aie-n5-P*iGn zezEMy47A3?4NfC14Dq{}rz_|}gn+E+Cmq$6J|sjerT4ecfVEhMRWcIe!wF zJg&vsgF9I{#&*keHAFn( z1FlU^-@5f$De4x>2!>iGX3I3rzkdrT=vyQ<&+N30@$ZCXf zj*kU8)0GF-m%^XiRZpiHlOwF%;ysM?`hJ2h-V)!)V3I%|4@gI=0q4mHRirUJjfoPI z2RrNLwVW0i=vSofp6-0dPltg3Zu^n9tLwQRYxEyg;I*oN$NLm?{jV@7l25oY zfK+)8w_{Ht<)`oVT6HvQS11ti(>H_t>&CTZw+71Lsg_D+O++R7zb2-?_5AZ}z>JJ2 zF2W|8HCQW6V=VA6dCpa=R^PgqSil8;Y1(#$^K=PrZ_C-2#kI??N>K{9_W8M#2aOL( z(Ob`5z_;MjvdUsSjsINyrPrG%%M*jnSYjCo(8o603YJN1*^a6EmG-%7C53}2Q9((W zzkQ30o_@2!6bPEU_JxaS4L4LJt9Pgp;l6a(cpTBPDU45%Efx!Pp0$`A>ihEGIlJC) zKYKe8N8OVUXd*Z#>62}rOpl%hZ<-6PBUtY!JbwAR)RL$T1q_LzQ?FyIBA`+>!u=&j z10y^vCm_4-+hKr**oG~#9@%k?%`!qtm^!sc^H8rao(;PpDN;|of|*&)m=K=ftM^or z-6~dl8#ZMJ%^LsHAABK*uYomT5g1}w|G#{$QS2N0_*9CW6<;8dKZ_|I;v1*)DG{{M z)ReN0e(NPork)(1BbqXavYGp8V}~)h{yJg^gFap+=Mg_8^9B7*6|Au4o=gJMs-R?S~c$XvJuq};Q7|w->S;v}9!v?xr zN4#dID?!EL)sBX18#}Vr@Ch4eh(ds8^aVKr-;_nalS$35+C-ygt7Z zi~hSjG_vl4F-?`_<2ALBmWKV8V=lz4PUlps#hKU@?fIG5)eN?Xm#+<5L#e(n!uCAw zMeJ;tn4?1Y*@dfPd!}a0O>qz*45Hb|lC0xG&GYV+q zNq9v!BORG3`f;A%)>0<0w08y-$fplvQ2GRE7~?B^Dm5!-#rC(j2mdib`Kx%&+W{96 zeh={Net_ZP(m4B}20@Oo!KVU^wB>bIbsvN{%yZ-J)3;@Jxz286syo!#GsQZ-*7+M$ zi}6OP!;T3eyip?VhcDS-_rv!bBpv0jAz=wen>7)M`p1Du2YVm)1Wz#CD!8B5pm|-W z*jd{9N)tuM9d`UEct0wibq`i`t7V3V>$l|@uUB+=dw&j;>D^Mi*(Gu0>uqV&(N(=) za|-hAmp#0mNTt_&*gIc+k!6*Aes&uenU7K)ufl!-OMmo^*dXVKM9fSS== z=n<2KF(@^C*5+x`9Rr58Men>o#yarNLiryPC|10$^h0uPGQU)vdu~@x$f86bb9`Z{ zZK~5maZ~}*V(Ae+611ea{2v*LvXPOIV_{7SuqKBSuXfwL{8H%*e^|peNu3Evxb`Et zsPgK*6K)0|90yoK6g$encF*yNX0|Xq04R?0aF&0s$11*J(5o%0&Js@zooRwU?){tkwaiAykfkA=AIjP1+ao)e4=0Ex>hus{nIqg~Qb4TgIZ-;-&G6n{ zG?X@}@1|d}%qy~YL#fzxVXN&I{&@b@q9MJ5-PoD*l5~_g-d5PbLPt@hYn^#zv$()h zc@alz_Xy)axSCg)6)}}3sW#z%J-YnO?!D3Cz1b@MsF6de3`4av$(|?0sW%6yKj|#? z!Hjge*IN~-nj*@efDtMCwzj6t+KbI+nJt&*UOtVVj|*dGOQbPd&6ZRfo^YcNAgA?a z&njvk6Q7dHvqyIKWOEZZwG%SYNP1epg>AdTT6(quhUWZ0)*lCZt|Qiq70MWlJ{Y*N zp5XvdjEy4MLM1!lbHpeWr>RI`ye}onKo+id#rw5pS|^fJwX*-VYyPG`3}#_k<@Ct- z)Rc77M;onLi1*5%l{X!FkV*i!vXxDeV zUI)m;V6&AkW6V6(ICFDo>yR3+Fvs9L@UK&<6x{o9K+;2zj4rM3`s0k&gs+=G@JaUC zQnf-w9DEgFsahc@U^X&KtpC+QfR7P=)xg=&xY7J@*CKmZ(S?5_`7PnPaB#H&ZXH|$?KMhx06mV%jB49u!UAp8A9m+_q@`g zK^q`Fwc3Lv!C*-@?8r#^Urgne&%vY~(dvZgzGif>cD5fopKmnCKB5Y6s&#*Eu07O) zs2ek=jP|4iD)i`#2cP}IcImmSK8jQWlv-LzEAt$>HX74Zf-S_~+sN=nSUgQx*)Y$= z0b+WTqsrrK^GafORcOepz5<(GZTtMDRk(M^(u!4QN^zOeFDnD{>y6U$B^cYC{b*)> zJd|ezf1Ul+Sr^A`kM_6|DZ(z~f;9`&I_12~FjpW%<4b%N7!ER+;(z2=#+KOI)9?_K z+F!I~&_EuKr$uT?D&7_KiTn1$lguQ6?UUm6cStVnjO?F96R7xHIveSB$b_If=0mYB zJH#vMHSQO+1su*tIK-!9gj+F29~qetP*>PHEkUfUyfxK_J^$*$yt$nF93!nW&pC0Q z9ol<7zQcvjB+ltrtgu{F*~>;Ha2V5|3LmSb5imz-uKX27>q0bYOY9h2;YnRFu&U*| z+Iad{RYi$))Z$wQb9E^*OPra5nOc2c2~kPWEMGVMwqNLGI0mcMPJ@6;`-*IUzA+4m zd{E)4ZZTL#Q$!(jKa5*@nRc^ErfiW8CA*uRoh_+IpW#-z!972iYSLRnWa!{a+3ZV5H9UL31 z@DPo&$aT@OEv)boquEqfW64z9YpW9(tE`ulpp25w`iV6lzCu18r4;a-seb~G!*v*Q zvt-lz(1UmF*+@P11U#jl$9EK^jpN_iIx4J>Sg?p$Ze9UjL3ur0_z;oJKZrB^-+&X@ zHojm2m`WF&TKY)9W?7~SP%(5PYxzn$&*dEbz!{jE47WRHhw_aCqccJ-)?XaoSZGEx z;o-pEuz0vIF>4l~?^wOAk&jiO{59rGUw){dmXRA+`9{f>sqQCNqf#oPT_M0^WMV@R z69?_$C9CnJUxRE?(8~kow%RDqObf1U212mk**?-`=2PP%25qYP6v^Cm3rs)muTu+@C+>oOa!Ww?WLR7qfVHEx$1Kjx4r?qL}yN0 zgUOgempX%0vq>NqnJuGAC0K~juqi=9-CWgvIQY?(0$=>4!*R2lVSsm ze`g557A8Js2xe*%oCWjHP!-1b4l%>R@vY4_ByV9!QZ;-(RT_=$(1)R6i%%|^>nTlt z7@V)z2Sk}z&uH#PYDbf=tj+G`FTyZiEW>p4-~p-IV@h>;;!)?O$Kd*~1&rFl!;;A2hApMa>JMEasx=QlPDjt9bx-L5BtL1O=04g95v)6hs5E_+FDUu+ro0h>u-potRAH(ne5g0P%*YU)!w7kUvoYx|EVTh?rwB)r;&KE+ zY`YfLZ)aW?QDXTV?zi?}z0*M+57^#<2_dTwau8D5METUEKFCX6I9F(xxR4)`_OErj zd?f2wrP!#PY)MG{#cOc43l5@Q6Y^&-B_0N5Kd`J~Ib=JWo(38e1>$6u9Sc3UHDC4a zSMtHGDmMf=pl<5W?V!=i_8hQhmmZ?5lk*qR?iZ?Op7U%s8G zTn>n1kg|Eh;S#SB-amq)iTx1Czi(G=BR-RGLvIo#4cKzs8kzB?cy1%GvSS;5x+i{6PF(fP>~R0_?NQouRqM~O7@|4r+OYlt^;t%2yA$K{-y8-&eOzNPqN zMy(wJZk|ir|MOT>s1VaV#(AWUdaaH({23*8D4}NFjOTl?>=jK18BJ%@F}5f?x+o-d zBB{knYu)`o(2B1%ILej#4b<;PsQzJ>p|b2oC-Un5LcU6Afi zK#v$hYww`6O+jI$vN%9KQezE+FVh!^tPW)CVL5+pA-1uhwZXpb){L5#(R#ORvXf(BXHH$QrqFU60kR+~)NZT)sz zfxkIt@aNH{fdRUd3f@Tf?cr(O5fW(ZRD)!j?~2j=UQ+6QXOGQ|3f_Eq5VO;qY%quV z-cou^E-Dp-5P*sFBB*vg501~rj&r^Ew&dF+dt{^%6==z#2Z=t41r$KXK=sAF3_MI) zap|tyyvN0+++QjlpEOoSLOOs^x=umxhfV*ps-J-IZ%XF$n4#7z_r*61jpF@-z1>i{ za*R98i24*IeheRR&;S7rv(>!G;N3^P6B`DyCVbn@*{<2kTlY$0kkm3I9}$YF+218q zqH&8QKmvlc0-2Pe#YaLmnL26qBVV5Pb%%c6S6oAwfvUFeRW+F|)LjlT_|9=0LBLpT z1ahZl!LKvI`Fy|GFOCl@aN!R_+l1^Ma$jk1XPr7dWKZ7L7F0O}uPjcT^sChYdvF$b zNhe(KR2gZ-1JCtp%7rot27@_^zP6DO3kw%5af%=SQuaYuO7x$w(yqF8v8H!+V_Q9= zMO>ur_8TpYJVwTesFiVH^Cdy~N#HCZ&VWRvjxG#>#V{A~#!}3N-i0gaLo$#86?42Y z26^#r&cAw3$>z&0tD{=?nC|RdLr8go)EwtuX~@cZ-!X>DKJuu?u02ha3WFvY)md$f z=P*Em)mhp`8$m-5Vsx@B8b!S=w67wRAYZKM#&)s@t}YOACUcClO{}}nFn1g0q`crN zgh{)hSt)7hbi5DE${6{fO47oVpuR_3JonKSb75I%7U5tSh0XnC2U`-Z;i}Q5ipD?| zAD;vTb-jIe2@V%2{51_~>$d)Mg;dLBxN4xxrUQZDQOKYsCA7IhV%K?cypi)&XWj;4|wHe5Bw_%2w3dvB=JI<9IoRVc&Tu?~f z1q3~{28~;?p5_4mY|fb>Q_(kjtxp~Xk<{t>MgY{-)cqZMbXZ4dkNvh0+Xfmo2vxc* z`d1yOsnb(-lcx@k;NZgwTrg416v^_4Lv9GYXt{cuE%lkDex(LBtPC2HIT*<-S~(gO zuPwJSPKbRE|2fU?7|jZ*NxQ zuUMe6@pq55gx6m$jMLwMZUVo2mwb{U=XNb;IHYR$apN$%`V{nRvk{bSgm-uPD|6T! zv}oS-Y>xJE^eQ`Ifh`yS)HxLCB`NyhQ}=Ouymjn4yna|>aPSg0rQj-`(9KAcIBsbV z4tTC+eEoXhYcRK=jdAluWgaXV^|teNDh&sUWHX>6Ag6X$V2`~fx4P?aTge) z4pYjrs1u3_AG{ac>&}`}4y`fRH71zrZ3H}i>!LFiiJ+2$2z|SY0<{WG+S@jEqY7Fw z`Gw#9=UVjL(HOx(6QKeOHes~?bw>$aK?NGFH+{^ed2ohlv1>M{`@jZ&P7v~0#K(Q# z6m-`s=!q=yG*`l%e1hU?T>F5}Zw{f65&Ynijub5U7i@NKhG@wIM?2S6uPzu+gV^nJ zIbJ7d@R4`?0`^_kjL!XYAbRIo+Gu6m312fl;+MJB66Qw|5O7=m@=ttiSx{as7FVKG z`SF=coo6$3B$vyu0Y7!`gJ;xvn}*U0p1E<=uzMy30v8OwlfYmZ{@R|Th$i!HG*&sc z200w=F=A~|+Ng8}IB?ucKKW&yv}#P(-(yPBh*)LRXAY6C3kZrw}_od-LTrN(~(=Og%7mM1LPG!hhdeR*-gK*QC$!tom_20=}Inm-HU6^S4Y4c2VVo*L$w4oq`?zbgOEX>^)q3&&L_Q(`%+jBc7x zG%fpnw}qI4YPj!Op*l*(Rjw(5H)@FLotsT+yQC#`RrcVKTyTy1Czvt6zVOte(<2@5 z3eA0bN6O>0L9)zWTG(i4EQ%geIid2`#(knUX#_H+10eb|z>S}%wlo~fX*iIdH{aTU zfw!AjH~S$W4-L2dG^1W}yq1Z$KF{aX-gZQvEruwPlE+w43K=Fp2I~kfvw0AfH#iFn zE$Oq#;3az^`g5#F>FjLMe0EM66-~i{VP7)o=UQ75RG`Dl4dqbrD1eCX6XeQ0f8%h4 z%`ML|;XUVBt4>hh zvr>#r!QOrDLYM3zgr-U690lA5!3ur^-?cajQro9g${b|(QNj%IWk|J(-}s=CK^Wu} zUwsjZt9_zr3fm>rfBJrBlWkmvB__QGyXA9|!oQ7xj4wZH>Di1s3c_yz+5LjfH*c0> zt!iFLAviH?-Kc}zrMAf{_Eezr-5dKGodCwD7{|tg)5WGV#%NKe&Bt7!9jUY;QV0UQ z27+u{sAeymAi6vqsiucX+k9(hmhj6y>UjJ%&26Lqhaf3dSOgMeIEyG^O~iVN8**am z98XprH8%F(!#zxYl)N@wOy>tnyy|v=ECM{wwh0t5P>u@A)?-fl2lmXbSg$&n)mcgo z?K?-N;^_XVs*Y<)X3*EOLAA7ON=PD5X;8+L!bF9#lFA5%I%7vDn)wVv0}1Pj%znDI z9*P!zoVI3yYzW2R$WYew<}tf&cWjxsc)Nu>A0w)}JS4~4@BLG==;}8kbvJUI3|&bQ z5!j82=F~6cl@_HJnktL$_SRec(`%dJ?uVna&lEg@UYunf7B#VRt;Iaa>k^lmlrN8s z%-9voxUk7Qw!yXg#myF!+$|^QjQmdnkp+P_y33Cj_f*MPq|Yy5N2uRTQ3qBk``@kJ z(?u`T+jJ+l81JKM*$#_>LU>hHTAFXU-&RCPWgjNZfaeMi2+cOt6-OEG^sVmNNeh^ zdbT1IZ*Hz_)=&m)uqo~ULQ{9wcV|J%TW%dFT^p^++?=yDpBy(Ao$YzKwnzl#8<+<@ zI|PMj;k&|Z)~arq9;I?N;SA)G&2k#$nY={PgSe|ID-%u(@rr9cd*`$>@W!8fMix+5HjL^!C{po{@8gDQ;$&#c(GRE{bGBjL|I=W*~o_vj&(?AZo}SG=ws3gJiNGaBnBp;E^MbOW(tI5~4_P(Lsikthhv+fz%)K zYJa3d+0f*rTRHX2Kf~Mvd5(-v8X+H)0;v?GGL&58F~emJ;UjFkT_61*A$(jWT!GIP z5^26;Lz2K@>yMP>nvP=o>MK+|^71T@@qraoR@kw9TElBds%_hpHsnL`v(^Hscs%9O zopBoO;-RckGv4;{L;ftgw4|FE`;XH~Hq^$HIQ&Qqw>+J|f4L%3mMJV`PZ>AlLt%VJ zVJMeG-FWsXS;ydMX`FU0v^21vK+jaKs%v@S#z(FXt!UWsgmsFAoUd@+h!(&8GeTx% z`I^aTQ+O4FdW^3daGUMvcUR_@dpS~yso6AMOsZWxRX|iFN~x}-6Dj$pTO|dy@bY;u z86;S>NJdOJ6!mJdw@~KxD}~#g!0Z0TG>!Yrr}O>^mCo|ed!^eOwZNBK>8#tP6a=Dr zS0SErH6Aq`p?dGzq)t%Ru#|$wcT=ZRT9}?8V*;nz z7W*oVM^BT_ST#&nWS@LOdtcvy-Q^dVtc@8N55Uo3-^OErcp5k`HK9An7%06uEN_Og zl9GiuUO#-bHNBmJ7#7>RR#`_1ixWhDw-nF29)mtxgmf;l+Gq@$p(DsC2r~L-9n+-4 zm!V=?apH@z3yavL-o^Wi0YB=fsFfZ#?+ z&;)&5qt)oSz>j~TPso-Xtw23%WfR+_3;#|dXE?BVC?WBb$u$BX2h80diK}r)#-QvT&LBe%G=8tbrkzcB_vxR0PZM(NS0c z0z>tThDjImZwq$!g{}$C)rzMqeAO=7a*`He9hhTw8>oJ$Ns2lJO{eo;ps;hK7pnBs z9N@2r{dSplR*h(3-l7J+(JXFgtiVPp4B-7OGQf)3gKa?6GP?3H6RWij_rrta_X>JQ zRAYJMLi3)2pz|g;fuM8!S7)_%045s*uADqiMmM%J2*Wc>Zznmo5s)c2I2<%JI?=6w z2M$VM=Dh_KpA64I+V2vS#}bUYu3SLPtad%E*i8uh6G`WOdHKd+W-lPpuXANC8K z*DdF5FSpxquYcV>tRTDMn4?Y`F1P3Id(io?ZW&z%;4gn=-;p>fGALKRm+gShw!br+t!Z{la7o-cNE1yj64tCD189X&qzc0|Z^O^prhpsRPB8EL) zqQRvP-PD-z`ev{pzfo|Eyhb&tswK$f^-lksqI0~u1fPytLxJ;! z*=!o&GlIK{oBm&B?pc1iI$%bytSQ6 zuv^t6hcAq&!o1z#uCs=SU|qe|*n);$)tGg5YBMK1?PSzI!KY@pbZDa8?^R_ZE#)`q zH*@{p;{tlReIF1hKHZT~!T*A=ZS8gcl^R#oMlUrD?Y)qboKL-{HpG(*zGZ3#-7B0M z*Eyy>IyJ!;B4pKvIX5d@7o4J1a+VN|*mGX+P@QyAGItbLT?a<$!ySbP4!&~#0DJ3B zK6f8Gg3z8aG}g$@hcT5i*?62xemW`_=&4gx&Z{!_W9YTHx4oN=QKfIF$T>#-om@8z7NWSC z8l0PYz5wCX#WWUY2#-);NkR=tjZCWAV%~yw)%%;cvdpc|E2+hM9%2Eja zAK5R!1CcA|3(~mI5I2G-=wjMVim1V%#qxG`CCfA#I+Co!i*<^+Tr8Iy-FeJgEY_`f zksQs?exY<|$_mS++KGt8<&;{q+5M}~Ka8qX`N$5T6unZh)}Hzj;g@fWiO@-$-XVq^ zFJb(#tLjj)%R32jW4VKxD3n6^xOkc}UKFMwJ}+)Ps%N6}xA9NW#)slt1)R{2ezYMq zA8(63iBMcvyiB?iPI`^a-^|MMNMzttp^Cs<`2~50t2NQU#~7^AHXVg3COtCl2L2$- zn>!=QXq@;d2F6k6cqigUsw#9F%5Nf7m;5}cOB5M3yjs53IL^Ru(v zDR65{4TF{ASGeZjeRsKG9zn`S4Jt_JD;|DE^(3ni6!BHB-RcfQ#;}rQd+^2TlaLS} z7r|JAQ`a@k8B&mD2k{=C4CT8_RJQGj^phIXqt%A=cP_fun;VIx#fEYSC^y|lL z#_Nkhj45I5yCipvo+Vgs-I{%PBS%t7=x1BU7^krUe*_jI)jCZpSx=~&>i&8fyayFE zc%XREQSQ;i=AyGdb(?%ijEH(m{Sz?Ky(VvF8gvbe?p&nv4lV9vo}Zt3uS=AZ8s_tHt?1W_e*7gVPJ@6|I5yUFzw!BxS~-{zn#miTNG1VG!AP%ze`6H$F*bj z@%i3Hv&R1X_?>!EKvTb`KpL$Veazo;<32W$ z)h-#FuQE)^)ACWxAYdNaW*O;`vl{fPQ|9*W8iclzGH^LwelFTnlqH0obEo|Lfq|WitEDu;~TmoXjxg_p|R?7O=5-PQyb%PWMTvS zEjc=Vcqac34noP7z7i99^|#w}QHoizkp{oWTSlqZv9#cDlPJ6@Z$VaAs^wfG0Q=0A z0f5nuCjf@nfX@+lw~Kkvf1nWeF=Q{mF$-A$@Z`WX)`iKh@uBa*!Mnm*;1_GJ5~!aals zfm+EiIHJ}h6r-JcT!VFXbaz3eDsJK3@n_64;Y}W!CT6SfRJT2qv(t%!9ex+NL?6qN znl}0*hkK%5wC%ej8|`KaL<~+5;oUGsMR*lp?gz8|uc)*;S!p~=OWIfZL_aM)!Yoak zrj^keloQuDXr)7us;1Hys>!Eko}gYpwS_C`YxpU>xkr7L7wA7bpeltl!K$VIq4p@%1QtfQher?BDScl{q`J+DO? zMNXG|(m1J&mj$kd*Sm#?(rd>6sM8L?*x36Yn|cbZruGBDmEXkx10|h zVkiKUIK8wBObH}VT+HvkYu6yG=F!5n!F;9J>VP_`xtvI+fDL$G@ zqjh3)7PVdJK_2}2 zNvLr5XezxoS|rcvRx0Ugvm)%`5Trx5ner_81c|)p0k8T{D>bCPq%@Om)4g>bBR9j@ z>%n0z!?HMVWd1{|-!-?{0fPfrwVUXQ9kA+n@G$$ejNz(=HhWFzUvBNj-_;IF^iCnc zfs~E^m8)`J2;iz%d_g?2_`^TAXtMxQoF9^xp2xwQ5L>FwhIP4mhL@3%VRLx{M(^d)FbXRQF#3JpNT*9MQBULI;Mq5=~){v4$u#_|GT|>3+Wma9f6{|81#-;Me|Dbh) zEA$t|mYCl4DUWBI5C<^~Wv4P@pSC<9S)Wd#pSFtI&PjH9`^c!=?DYfDN2S`26alT= zf1`P(@lArq&5k~9*%n$|BGyCeD^YmfAg|C=KVK@|$zXJs%<$Xd6f@Fa44eH=Z=oK% zHoh{Z+SkG&L(5T$$<)?1HUVLkG5f#FDV3KFSL=PIoHUdx^1GX?O#YW6Z68Z7E{mba zx*SHz)oNt@5$dUNGdd5j04vPjXoPhosG%Gry&&}*Amfw&DU34n3~YvPzoz`tPiF&J zeUv`${3BGZ`$=!_r;0FmP^Ey$t%aOL9F4*cl6FQ@IuP3*_!iQ(PWK!VwDzvnYGk#$lGEoCmQQ$(b)9&AP2o88fYLe2tYuCn+w+X?o$S&G@!g?UXECt1V1wlo~QzlDO}Y;yn7| zHfQKhHAnG`zK#`v;8(<<6LJ1^IdXud(h+tX|NdkCwjqE0QHt@^)pe!%8LK(h=ZlY?y0T>jjfA{zf$VHc+*hJVC;^!S`O)&!2AY>W7QiaaQ~>5% zs-z*M{BO*$ncFuO*a^Q4TTea42N}VmMijN*qLOkL3}W;t#6Qq(PdP-aCHK%Ch+h_} zqe+Ys>f@vS66#q3tBs$)z@9SvFS-Doe>wc|Hsl&DZ?N^g$4bxx^TOCq#VBL%kUXn< zqNJWD_;J}RhW%wal2thCa2CljYjdo^?gcy}xO%X3zIp&}39xh}k>2k5Z&}m-5ubli zILd*oLV^YkkEqGE5W;U;Pzv9k#f!_+LVmBo|L!}x`qs@;3*IAK4Qn#rjg4M!)-v(f zT?-FFy;$muk9~08C;Xz-8g?6*`sEgbGWG*nY?_WTK{{7&<;|MMibN!B@<`~9+5LzC zJopBoka@s~=AZudm(%180|&*AAI7Z7b$hdpr`9BiCZ%Ak5)!KZe&QW-+!)sn={y}i zKi6aQVX`{p!Hcb}E3<*r%J~xmUsw`u@c#390MGV+!RH&M$Xpv9g{9Hu|6Schw$&_& zW7(DGi%Gi@g`VFiE81wS$jf0@0kBRZ8sN8C(x+x11A09F5f)4YUl|O3tuqSy$f<&h*|8U)$L zPe7@w4QKW!!a&M8-M&3&{m%gT$G!Np;qmrTq_1@Rp5?a3;+?;ztO0lTrNe-Ow6U3H)DH{rf`rtO34N$#UOH^Z);s2E3W`Wg<%y)w1v8 z@^P(*S5)uc?EtSH%=aBOGz8L&d#t4R-(Q|l`x6Xm`Vj zU`FmiOAM{VK;3m5sJ~~n*v8-ZxjeRr1B!^q#r-AtqE z{HffY)z+Cif8flFttcfOPbZ1xjIrl3{Qqp)4OUC4?j3*N@&Js$>K)ef(x7?sJB5`# zFR($%eMbNZg_v6E>w5ZjnK{c!h zcK@P@pJY_uI3BSBIYom;ACnFx;vDY56sl1N26}h}-X!hJj%1&ycfKXN3iYi0&=+#G zm7YeT9vjT(1~o?S1^?Q|y*)2s??_4GB6Qs==XSb3D#jI*`!pCA#b2 zAkt7a;i4XLZ+GQ4bFE1)#5$xzuIL9zj^2opiOTWZTDJH+`oDr70APgsnn`hQ_%GFD zy8@t^bgTa-k9N&`Zh?zg8gNiSeR&QEg4PrsK|%G2SyvhJ}V;8f0SN zl9+LaT-M>{*9yHFMTv(CP9*$Fco&JY$>){|7q<|7QnHcIPI#E9-KbQ9L;@+#RtGfD zy2H(0hwOH7(+#r3l+GJ=4i5jVB3nCv1Nu%>B7hD&J$Oia)VJ0BB{K)zfBBJI7{HN3 zeR5Kb3PYv@kcaTXv~va!59}5KBh73qXkf0fonu z*fU4h8|9q*`AiVz#cZ~>1h9^|+gz?)5cmSS|2n0Z942&5D7Xrgkp=y`Jae4;iK5>= z?ajP=c8@xL#xUPu>{lS9!Xzs1V?qWa?HN^~BWhwIRi?P`DCDd6ygP%RL#&X{AoX7v zafT8Z`<;@VL#JBukcIbDsAted-|W|(HS;6d4L&5%;QoW2hJmav8}Lu+1paQV`2Y47t1#Qx7~8XvCC!S1>6n-vO7}6yX_S* zhSbM#x+h{)vMoV@9<`v?&eW4(^MUp@h&abb;=uXTmfqRe%(v1YNA?k>7mM$RY9J=g zvRmLIw9|C=Dqzhpl4KgYwY?CuiZqp9P$ zCQ6@YA4HRNPhK9ax9$nV6GG8w{`DI}Hm{Y545b&84BNprLpqQ+kAC(uff9GaB;gB4 zXc1=5T$|$(%MrQmFY*;SBZ-l&7zvGaj;f<#B9=Rr^Fmr%mE7Mvwt%gcwJ=*;T7#ps zNXJH9m56;-jl@jGZ?{{njEXszfTl_W;>Fh&}4Zo>Z%h-G($j*sY zMnB$&8g1V#i<@3W>7{<8OklGK7Nk_Gp&T5tSzk<>C7?(L4Z`3% z;d4VC1vx$}>HG0x)3a*sYxR?aV&;V(Zx=HtB@VG5iox~V`Itor*OaeuL>7U0>o`Fh zsma7~$hSJHd<+PP8Jb@0oQJedxL2E)-epPN;kk<&32+U}3vr8=j$>B&Y~$61!{7g( zRJ%a!FJTSlyrz9;Ma{?v)br^B(XvGkKRxZMQCI{m-PZ|TU8N}mZS-QgXQT$W?3m7K#|2*PyIbuFQRHnrc^ zMpP(RWL#|qsD~kFQFam(e+Gd6^vr*n;55;1tispZW$|_u1@d>K-6@efSkv|&rr8#C zR3FnuyOw%r_WTH&^(ctdxB)GeK2oT)eJ-E9&1b4R(Tqo}C?ixI}yTh4T zYyJD*Uy^loF3uQd-u8T}yQ;gZyPlH1Ws?m#QKt_)-(uNpI{7kg=w1Y}ZPP+}J3&&WyTTq)}>^-6zn;^$7H zr)fjiuo2w%Xrx#;(YdNvxP5(g@maQG(cg@+1ZuijYo*;4f`r>cMUP3Z-(k^HPCb2O zX@Ll&88yIv^eOSdWwQK+&G7v$5{I=-FIwfs4i29&oQ_w^&`nS8^h;(-u_88|bpN%m zMUzSzUUM+@4GlCknnKlekosbR+Pj9B)WwEf=^yh;wcjaD-M6H#uk3n$B5`a+hV)O5 zXY_Z`xZ);Nx<>D~+MS@xL%8?P4It6yXRwu%+?m zqA5wXoYPp@WPuoq$d4fY_V@ZdDpP>90LD-esYb9pMTL#MN^GO3KEl4~2?1e!{h+zc zBking5{`LsBK|e2qwfS(BoM5!%oMtuOi7)6t9aJaocVO}ZLZFcV=d1#4m{*4lWZf$IgsR3of1| zH)OS;N-fY^Lq-ciM@LtHk0RXpvfgsb^`y07i-B_9;eqIJR=1$zQNGT-*z;6YSKsnu zdl$iGf(8%YjQbkK+Gmk@>!{d;zyF1Ax%=fACxhRI5URr!&PU7I4Q+Q@a^B!j&_l1Z zh?r<^&V#|2GffrGCWLLF%!(;J#--qMsR^LWbiTjEC~u5%kS50FB(Wja+8V5j`%-s|Dbt$jowq7c)%*$D+ z!+sCWRj@5K4)32u(f>sjK~zv${ikyf=vW$?aCB!D=c6IH^@05nMpFMuWxns`JWOcz z6W$80ffyO5b0;Qod|iK*7lEb$5iMWlrG`q{M8Q7_kN>Me^~P5S&bZ^RFz+p5oO(BV z4%99Ve9n){_5im&VQg~L;f*Sweuk#{Jt>8;$;lzz-Pv~Siv%3?=0WTy#%cR?CFt3b zf3n=EjG-4(2I(68+4nfhLzwZZiEFXywG@{!Rz%AIq%K&^L*PN{Dy{1GQs}=%H0uwn zKhNgt`EY66TEmDLiOc5Cz55rj0$Gvk0B?EV6&*NG>svSrkw3Zk)k8RN?&dzQ6aANF4q$!w()5>Z4SC>O75^;j{x4p> z0ePtl9TWR6&C4zpARVsh^7C8Jm$_=(T@h}+-l`; z7)~E15fjMEw9=>=0m$n|Rk6ss-^zgh8lT(>Xoj`=%9bbL-{p~s2Bb0W1Vi)b@A6pr zC69W}Y_Y#L?*ZRnD0R^8hW#%L*Z-0S-T2;rY3BMR53S^?kAJZFukPyTfX`4^gJRVC{86@FOf z>yS{W!?Clh(q0#hL@vODz+%EbA-JS``1Sc%%E-jz0}s#2W$gCov1iIoM;iR?j&AyTwtqm}Y^sDz;C+T z-fN&vqRsuqy10BzaiIfuj+u^kuL*w3i%$=h;U|* z(J%s7S328D7b+K8%h}XH4z8SJymVH(wLMi!G||uU{;7ZEkRW~>>SrElCHe@w$Is+m zwJc30r1Ws;vKSm6o}Lak#SVtqd<*HIcO?j!L0Y=hVSoZYT+~06C)QVt7K}usQ*Po> z7rVmSzxm?gjl@vHFEfmgg_?nXx%PIR1Fi}mY>uJ5D{U>Ep6oVepBn`+1m6ZY4rkDF zX6gJmg-zjdJb*~FIMY* zWobR}Flq4VHzhku0TM;p(!Hk8RmpQoOK?2}W=z22+2L%3slUum9#%&8y&^+l@3JUq zD)YhzYDZX=@5QBpyZt*UsNAr5qLClJDA6j|pMa1mI-?pC1Zq}}^-Ry_1YMCB&0mchBaOgO)J;xw~sajBZ+CKsk@IVJLtQzZLKQDmEEPS3ucN37t_NHsAg^C zbbp$VZuRx8?2HCZ7-H)Ae`fsjH&E{mKMBQi1j_QgV6ATkf^(|qW)~qCP~~o8b{y14g}3C zCdK;A>?zabo21e(a&Z$rWRf7Gu|Q>B-I8#*32?)oz*P2YV4jFw6_~~X*g&Al{b~a6 zP-9|Sh>o0pepwh>D*n8f4VN8dReKfB!(2sj*NV^4BmK&+fxGM|+Th*LwUPsCU6OAV zsu>z^(*NNDEp!kL9;3@p1N}S`i~dcn@06c{WK!oYxp?nD`QJMm!;^fUb2d0xH)VD4;$S(8j4I>spVU^ zeGAGgfh6V-3oL|=S`_DO730}7E#{$oE>}NFpi7)L`OZgo=%^}H)>J~b->_@z2Ahiw zRbPm3F}o32K3t(zYbTZb6smicPdoCn7B61xNIw1i*xFuPj#u=^K3Fke#^|7u)`nzuLGBvCn^ zgcl9S+p=na9P=c)5`EH~ptA_U;g0A}-?U&Pd3B<_#0^Yj#-=?Uqu4l0icH!6q$f!F z%Hjh*o9y$cgB_cVA6GYDx_`2bNFoIe+Q)&4{Ps2}Pwu*n3BP7RGn@g?Vk?8{Z?YBc zmRC1rnJaAl@xEtDt?dE2yLrD$F63rZ;*%M3{JaE+j;p;2_T$Kxy3U1aySLi%6izpI zZq8?uMPsaJY#r zZ&@wZ84>pUyOG%dEzJo1tRv>eLb2hDHd0vPOkI38-n9I2ejg%ysYTYM4zdmPtIOIj zFPYcEales;@UyCeidZW|87s4_$8f1(vVFXgl1=##%y+Si1=ug&63(;1>VLY}3>Jot z+>d<7_5=-x0H3YjB#3o-b39)50S$iN!|1RKW0_Kqi+IAf%lc6I6@SEl|+6lhl|->Zj=$8flxhq$>VY3&hXLVMzP6Klgs$v1kvrl z@DoA}IIc~fkW|zY)d~P*np$L@+g-z6Xq)3z%JuIcx#tf1 zVk-_Pfeor-!Y*^-xh}aw34l=D2dZg`y8-F>n=O3l8PspJXFIbZM8vh>Im3K9@`fl_ z9q?uyks>ZTWp_>Z1(jYMqx{e~St=$zTjSH~7#^8!zujkZHsKnd(JD4Mo|fz6q`8il zYOvZ@AIIuSKhg24mkSjXXmDFU-JbM#-L3AWC_NZfW`(-4VxM9$4D>ET=-|fd^7A!C{g^!6HQNo9-v)R=@SXd0EuKzQ#NcDL$!?JEA##!pY>$yPk;?QK?mye<|{T;=e| z@#WIt*GU_?GD*C=m2&<*RjY)T-leWFG6yR(IL7mo+1rQ2Z{nP)fM`bnE3D+aGEm0svJoe5m5^ep~}m zD|ak%EHeN@r+mX99w}$b@?2=KQP45bRk>jF5ljN=tCG1fhm~V$v`2BVSJKmJrit}q zv?n&7amu{ExS}obcRo}!GF%ot>gFX-%K5_PhfSHVAbJ|bDCCOo#d{N(0aIVJX-ncDiaGR4P>egCFD1#&iq@BO+z-g;GgE z5M#{XvE;aE<(5sKo~BSE4yZsFan5V(=}}KLf*5@yV}>o{pg)k89Jr2aUge_Bsy*;U zrIKqDR1NJoh>W{QWZ`Tw22=T9sSNEG`O2@B3-PLtmQ{V$q>yf5cuQfovd*bPRvc%d zOF<>I@OK*;Y#dBp>mSsAY>Se$7mnW0BjyG<>(GT>w_`fLZN$j?-V8IsKhMlRJuz_s z`2@fybwdkrU#?WSEJ6;`WM+~oa8BxQBbI{*W;RMQ{RA$t%YGU;Q$Qw4`xq@Hru=q| zO{S${&$5+acwF&w2X-9pDoQ*G1bbxxP6Vq52sh_3eGlITpN55{v~$xN%nO~|jZA7J zF2|ckQ_DDiZ?wb}$ae^p%?|dRueyKviJB8LV#RahaZfpXTmz1EcvcES0q!Hsh=Ls% zA!$L6vRGwLyMGwAfz|Lh+YzbmyT|*|(30&A0gP)ucAvV4*Rt3%iLk?~gnt@yf_$iy zN0p84vw;?xbE>+pS9Rrrd~T145dE}~wNlTD@w6kr(}sr8#hBh*CQ|fM5f=^0A=>@Oai0d)yq$QX zlB))f4IU%1q4iC-8_rTzjqbEi;j2AJ^Jxe`UP(^{;KhWJ~3Iv#W`#yv@;qp%Ez(TD)L(Ygfp}su9XlHFo;b z%)G1Zj#Pek!C{*p?V`Fy82J31FUber=yU^gVtslAT_iu#S}m{}HP;DFKV8KT>v31Y zPsWua!(@}^I>jKhKGRtgC3;J!Q<)f@B>KkNK!b*?1~{MgQPT4VSRQmEwkyLWxH)#< zz1O+;oN~dLHtGgKV(lb+m=qz#*69cQJ{~?e?GI`;LSJw53npQghy?PA1h?-x z@8hi$r7|s!^4eKl+2`Tn&kmw8*v>x46qrB;h76^-jc?Mww0G(;Nvnl6$qsDfV-bSh zdI#@+GD|26S?CdINM3;I89ta;B9ZV>b$?PFldfd>{pNL;2Q;5mCoKRr9Uc%aU_KdF zyfT^9WnoSt5ZYKA9KnU|Bj3xmcRtX+tVyphoamct${E%7Z-tstb1t`$0#VLcqLytm2rCRLSY{Y9xZnCGrQOR-PKkGM4YCOyW_nY7dO1 zH#ax&Y1r}L1lI`7&I3bcL|E9zTd=Mi zx=}jL@6Ovvw}0Yth|cdeFj@&w8>sYI*tF8#l$n#K7)w;)hAk+yH{_S74KYhA^poNA zFDPkI)A!KM#4voxdu^3`uE2(aB_`goMylmf=~5$}fm+2&c3WktoB>cF$Y0ivVqVUV z3#@I2z~P$lzoK!$T)64q8;N{cvWVxx_~~g7cNQSNY;}I^Jk>GoM#C+sNf>o-)V~pa z&!|N`C^@jXlT^0YC2JLZ?$Nu0}OyU+&ACJX%I z#allMx|rlQ#YmN0E4*wNg$?nCjO6G?FcB~Mmc>%mm%KfB3z-8m^U7?VQ%S;7ha35H z?_<}BLJOQSH!XtX^iMi9ITLzatnK#o@s#_%5YHYOgqd2^&*RX@KNtpn;A$m}5+Uc0M?$xUxmMHL(X%<+^MQINWb(_20dKUu6TlTq=1X(utrdLZ{;6x|Y z&SC}hW7U45L@UaibB{7VWvkj|vwLEB*RFs-1VlA_`lwmosE2j=vDN4wKs7!1Z0*2T zc2zh087+b%pp<=P9j*<%DrNJP_8tM-I@t=vx(kI$hX>}BgT0(P8S69@9m7A`_KM~aRNX3 z*IzrG7&?iUoZc{q|K9$qQ^da${Xd=hLBi{?cYR?^ToH_)f_7-)8g5bxyBuk1(a~YX zbBImN3i|=N%~slR$dXsQ+~z!XI0mKon_EV3^_P#t`VPrr?_&-z2U_Y<69!~M1QdVo zBH)hXeOsAeDhg3>bl>+KGx{tAJB7X-RV`7kVSOo0KaYW6!^_$sXreb+bglSd%}2}Q zX+E|~$C~1I4-XHNlt(mM_vkQRF+Z<8TVNAAmLW#}F5Vmyh#%(loJpd7rL=l71KB-= z`W?I`CeLq-%MBToRUT;N%t3p~;Jq8w2GYEw4+ek{3k-6VZ_ltJMZ!4D3;iE*>ru{3EP(&UzjL=U5}e#TiO3vm;XVY5d?u=9>~csr#6_Wsj2(R5eB;+1dcBrPC9L4 z5!$+>gL(zfqxgH_KLf|c6<StQz=mPim18E!2Ytj1))=Cr?#SxM3P?i zH7*~79+yw^9)|+!0E);M4ZzG~>*cT5fwJm>mLbNY&AivDwBfozMNBVKxxy}iV_6Wo zRGW^u<%YCKaoN@c{TtiH7xxp#jE)DBATI+_{hZM8->`uIOB5^z6rXLcOD0 z&}UwV7gO=&dEwN+utG>w?g!iEg!l!VScmT!LVGb)dXboNO}2+n#}m?hO_H}iC=^*@ zV%>2czfa6?J(2maZYjJlu`z!n=QJVKe`QtI97FYE&L02NKkrJb)=EfHDEfhR5bOyJ z>w~%cS4EycQ2j%9RT5OJ3DKYSBZCy%fHQ!T&xdcO41&%JdJ%9PSYS^#A`Vvc`go2< z(+I1e8k~`o4mNgZ%U|*vg!DRAHmn#8Z!$svw#Lldk?CIt&-IRzxup@Kg94Xp$2=cN znb~{wU%NkDBf@ko3N>&`yE{3(1WVkrDie@TeC1gj?=r2v*ZLS3Agwr5hriFJf9pY^=^=6*dAyS;8vFk>YrzVWN2aP%B^6D!w@B zXnusQiSt?;H7-t}ZiNMv-E0P)AvL`AstN#h(#I{T5m9I>S8*TJq6-(J&de52XvG+d z(o$bKIqgmBK?BKj^%LmzxIl%MSEr_3SIF5Z9I#5k{}6u{hc&1)L`kcgsjy?_)=sdz z0>;Sy!bmQt(ati)O{VzmlMcxKm(ms}$f$j)3I51z25f*!9{l?&gdS)N;u&_Nh*WfP zu_rsy0sc%|AgE{s!kjo(CX)TVe3>ubhf}A+s6EFRx^0hLvmdcXn0T(Y;)SBjtR!pu zJe?WL2t&I8L;Em?HXDxMR4`D~yNZy#Ou=}Eye;4X4|a-W3j)5F zQj|Oo;f+_b;Q6afFXMrs_3tZuQV*OnuB^+^c-3<7e6y#nIB|mSE`$A>6vQAeA7Act zX>D9j)YWOr#qWvvsN`!VbUjbP3me%Zm>;}1Eqf^L%LSJlH=$WJ8-)fOD$hzH}@J zk*Ko)ies;AfADnMZ$s=OGFGPVtiRs(ieX>XLDG93m6=>d!)4e&F$z$=!YvG(ShZ7= zana+v8nRhz-{keM$##;R-93@tj5~B-9zx`yvX%OhJJtELB*SL=(`gSw$Md(7IXC47 z|5fvn{Irkv5cB1lrVpN%)%A;ySoOdFT)YE*R5TP*%ku^%amv#qc!DK)>=*R2 zd1cm}E7U1MK=!wFZ$qj1Y}9QUW-C|jg4C`eU-}^XM>92uoNg82oRuABwwy7ZunB~1 z$UHrRqhiHtf?R#X@m0jN*p3PJ4%*$vq*6;SdawJsKY)xb$`xX97V-9ju5_E&Cq*_= zP`n3BV#S^@l)3h%z9OIgacf(H28NB;G?#d*Dn;U_bJ`iqiN%?yO z4;Q;ISm7GnE7d-^1(tQYsg97)yj$|A;v<6z|A~-b?nP5~u-!b1tJMUK30z z_7m)fK)3D)6kD*kj>fsAC8)ymt%1{VO=x`TH;_&J<*takGQZ5}`tl z-##t*f1DD|_A{d!GINn9)M`WwqcZ!vl74U<{Mn;DT!W9Vc|8(2EXYcmQ+Tbl3G0BW z^kU$^{G}la8n0qu##_}mn)*mlEh}0RiUywBkH9uTS#OV6=u=r&7ATC~*IE!@-xG_2UCLGIp6^BqPBsa5&V8@RmkI}iOI85~Rn0fd_hGw8-Ezc8F#2q1=27nXG3 zpX8j8Wnfz3wMEFEp$y%!!9fc@fu}-V6?1!0L8Ed7};0%ABm~!s;{4t-K-)7!YUE zOy%#i%FR8GgLvla78N3b+E)uHBe1Zv+5AD&AczN2sC@_9v!TDGW~reJe>lX7q>D=cn%m5-i|zG=f9Qn^%< zRrGktyDTzfLz9x7xpPn3)*;}Cmu=~M?ctAmacZ!d7S+*qt690PZNL0zUIShO10j;U zVf_UbDc}gZ3Xt=z?-bJ2gHyy8-`p&C%2f!DY3D@Vz}DHz( zRjt^>dFY40@J1XYlscQZZ&}Q;FS^NCt(TRu4oajvR9*&+Si4EbuCH%x-BEnK+qj>q zz3?S!=H_~a#C8}@wJX||91ov#iDx_FtXwEH2uhrr#NU);a(l;k^Z#k^%0Yo&Uiy?w zY1CS6vk1ZRQe|IV%KO-s{7?952j}-c;D|%h{wxN6Y(opWZTyBR1egae z^)PV`7n;6O> zv;;tbCE&rlPTt#CcslnxoF6St71#26J#DT%0qu-v{@#Ujc24bW7>Hr?n42wvZzv zv!IE4er8tZt!Qt5;8909mDa7S@L+}t7zj+27`1TCI?%>lk$8|qEy!-Td$qr@7t(VO z64UAc<<04eOb2}V;Es^bcA#qW%X}Hm>y5r=IplD~%0behrpg3Ijkrnx2lg<|u30b{ zoZ*lBW{m>9G$RvBY9)RZQolu%0nwI5R5_CBMrFkui-yzlJ0!t*fMNZ+RO%9C3nK7UCMg;= zV9JNj+XmmsJuou2me1#R^Z(zgb(ZcINR%Tm>nKrovf84FC#0ex52bLIcfTca59KJT z=w?C8@f)lE)&_pfWI&(?<63Masl2uYsj$$(gKfk}EXNq&D$|VBF4`1hbUJ((D_J&Dk zBBbA<(*DDfxtK59ovz;%z)-BN#RDADpy#pjyax%YPIl;n&rK;}Bk&PWyT@xYbPSB7 z)=`K5G8Wp-SfEn`gldSVE=P4jr;X_t=xUKPU;6>}cDHZ1Tt}yCW1kul_PZ|pH}JKf z0z@ll&Kc`}vRc^50saoCFLB;*U~9$r#rr1dZeg9r-Va;kjEWeTs-|^ENKirX3m!- z{LipvAPwL+gsO4l#<=W2N8SKRG}4j8ibB=8-Vgb~5~fW5W$cQ;Z%T7Y=c%{&Z|=$eXwVI67{<@eiF)d^f8|Q>1qH~}>otaj#P5pXUjwpD1V$&fNL0}# z{nPpRAL==;zb<+{k6+n-)7bxkFdHR)VV`7bn12a)2a=HU`4#&t;MJF=@Ehp(Z~9ul zB5x~DX~X|cy-46!WOa7Cf%NYe(0@~_i2LQ6gGuW9JM~#2AoW98T|mKqk-hjY*@KT5 z{GIw?G?4mDVWpbl-#0-iDR4t7`x^uPPW?s1E`BnjY71-h)Avar&!0{Mj^%GS;+3){ZRsVM_u&VjFBWHc7x?Pa& zL9BSCNun@Hv_88u4h<$E^f|e)U}i3L3>p@k5;31afQzUqJSvLAdO8S=O3%5er}e3Uu2RbCa| z3}ke)g$dr2v+NnZv@mQo?NFr+?WU*#uGV}Eatgs>e=fb;s8~u0P>1P!SFTWZfPNUyuAn@Yr(;Z%krqe9`o|Zk@YPk z%g$&xKMD~PU~#T^BZ`}Gu-!C3yKcMV!1gYU7B(K{D$B`=a@jRL?ix>c;Y@pr=Y?u4 zmSrTwCJdb(2^aTr@s6yFN7^=}%GsN2$xp0k@Q=v6KRiOupll5-&^fVN+2FDD(DPVG zP5sNx_mu>04XtV=K7f2)|Ncw^gZuhCRJmqD|HIXu-PXOG<%h6Gj$S}(PpAXA zSqf}m(}SMT&kwCSjJOjo4T7kMA5O27;4_em>RKsDP-G+CSP;LMMhP@;-pH=?d{(dX zUhl~2N!Up;%U~#9{tECuJjw{kkLC-SsMCDh-af<3EtQ~`=z|wm-K+m>W<$bImoBYypehQj10Pfjt$<2zo0`WIL zj$G2aLd_&H#UMu{S=m<3JK0~c@{N4^h>j=o+Mp&c$jrOmt;}yu&yN)w)Cu!dHAz~M zM+=)XmI&;xmi72pCtdK#Ak0n=#94T6vX1V)pI#yq?D#5Dy54|uvcp9SpdDvkK+G^HukzOgj=;b@;C$xEQiixF1CPGWIHv3}>e0mBAl_mF*=^xLhJ?mM( zVthK`15pXG7i)2{fSog0H6rk19@}AiOa4Hg_R=-IVs;zer#l9K{!Zay-Gd=-1;@NS ziinu=N{0KV(Y|Mau|5wP4#NYB%B`yhBy+IaGWDiEZ-8<+puiPR{jOyH^4c^@1WJI< z73}Bk?ncYWN|nQai%dg_p!&M^OXjreH}JB&fuH+Qez0*En=NdQ*FplLU*Q223p-&k zgQinMU+W#lRM6d=yPz#L_NAr?Q@=ywFF!`Ig%~$D^bk_bMDU5vGxNOSYU3~P%~S)c zVI90rsvCzVFiucCAWl=vT^7eUEZ%YHIf)+c%j>WXCu*1Oo$j;QnDgBCttLV+aF%tNZ<1P*j&d=i%!ESg24TX}{*9jiO{kmBM zE|hIb-~C6(TGR#g8DXPlwoFmvIk3`qOPOCD3)Sa|k?uQjYNg$fLK9hkc4EstYkVs1 z;IneDA@?7v6Z$6%FGAG~eTP3Li%m^K;Eah>+OtaF(b`}3MXvF9aW`e(aD2j_&@CGF zN&0_YMie45-F;{us|FU( z*Zv9emt%D2S0Wp|>;qQI9VOG7bahLUI;OvU1LO$4m9R457T>+y-Yd9L%vO& zvG*c?mZTCslSxVUVcp$nql(ae%(wkfN$RD7P^d9~*4^l_@AA(2^cK%y8foOwRJpjY ztTw4K*#~wYdKVZ@)7|7cpIz3)^O9EkI-iSOo_(t2JJ=U?|DFUR46bmq7~G3DgH3p? z!PV83k?`S);{Lr@K`O3BuAy(`%{LC-^d`jF!#;;U{IBRazS_P%A}e$yo{q}_ zOuBc`6YXFYhO{S3ICrMJ_^Q-3q$ySA)(OTXhdx*v`VX8|bYdT>_+t-w+PnxB_97OuLyh1Xxp+yr&l6P@s zZHKMXtA{U8NSv4Uvh!{!TSgF^p&uVEzR8Syc+ptnnQX{2o=r9_^tQz}VmGMTb~l1_ zY!&gLF&h$(eiY`}5cP5|icotJPRSx#OwXc8rPiIO(08~v&Wy5o|1MbIfua@NL|wAX zI9t=w3KshfH))(OPqn-Hj1zT&zqqjY5ts3{T-lY(;Jfe&ox0}jJwlQ#itWe`L=ph=rvC-mmF8-lfIcBTeZEz- zI`SN$3ynyFVH;Dhf|^0xXzM%MQYCl>ru#0{@0~6u`XeRLcIQhbTbij}>CL1ivMG4t zWzVxI$kf3xVZ5)Lq-{u_D$7UWt-RPM@^AH&RBUbCw6ZOdB0DNxt<>>6B)?OgP5B1; zGo!&XmT$#%MRYl_n)O$%ICROPK`Q2Qzh?At;_%S#kPCWa)W0FN`FgAEym>KhDxwDq z4C>BY{$%ZA<+RtStLLqlf{KH^sHeDrLoKh_0wSKQqXP{{3qsX_o&1A^+INX3-y^^< za;TfQ{s%~6{uLwys!bMs!^#J}>skTTjbC|S7WJe77gsEr2%k{QI#6B@W^}lOK>m3b zdg#yF$vX(@U-_$4)O?*PhyD__*s$A|+-NlKZGWEYkNU9T=AE`fwb>j6;MugInAG4< zL=lN8Oq+Pt7(0|l5|@5*N)7sR_J9jEClF}5kqMS~q&aMXC%qXKJGTITn50iT$@X9g zb6$UB9sb9hEz$>G63nrcFjTe5I?yht(Z@ghX%qdjAI!A`%TM|u|Ju1P=t3Q^`vs@F znv1{L8or;;Hf-$X|{OV9(g3+b;jte)``))kZTG(4uU9gGK&( z`>#%+{!aA&Rj0Nsrx4J#$`JOlne8PzHMW1;Lj;8}fa%yJx$NaK_2Iy5<*g?^+ zNUlT!1M)vc`O7x-3#J7U7e3Xl|NGSDC;(42<*#9>e;*of=mACuO)g99f2S@1glbf% z$W6ZeVF&*b!^-7koPEz{>=Cs|^h zOK*c%D+$HOyagZXQdMy_=8;G6AINA zHO1IqHr7Gw*LT8sc-BRA9tn*3PPB?y(bcp0vaoC3KLFiE9SG_uT&+rGjM|psu6IbX6VyIw9+?p7zLoa-9{w6V@kVS;-FnJAI1FDl6Z*h=|#ed z_AbOLQI{nTf3_RcX!C6;q=L?#?!~Ahs~fw59GuuIDr1k3iKC9qvOROw^R-T`9-?(+ONZc^!Lilj0d{~Tm5Y4Jl2*}g=TCX^+wv% z)&s^y1YA)J@zDmw;MRl)Eoici>ei)V7dO5pymsDqX5LCaF6R9`W*&GITftKR-0WI?%ai2T%SkX;BRg6 z*^Wk=!qslP*!^1`3|9RzaG?{}x&qBeE@Hq3;i(7Tr#EfTA`)7-_s#5@Ut;BjN!^rX zo-jLG2?XX-P+!%S{4AGpt4l3(?9g4B!4raDhAUMIUC-&dua8zQn3-33Qvvvo{^4R# zM%~bLQQoRHm09gTl1@(|Bw=+Ol=C3R5);_j+wQ&CrmtWf{Z4<kMFb6<=| zWB5;{A}x8MBUf%W%B3mfeft~pG$~q<-QhelV5_rJWC#Y1vI)BnLEY*T?)J|K?|XYM^OseBx_=qqZ>{DMD?KAvUEXX3 z)?@{|TLF}eIMWKOKwGWmMtbu4oT7>b!gsr7XP*~#6dKCyBlGSrfQQtRZ6`*U+-r-m ztVvXRx;&1!?CsdM^VAQV|8b;NGy!$}+_C@ax=-}>(voufQ*|rG)z6{f@@VCq?xPKa z3Gnr=1KTd&qID&;lCb>Oru*R>&W^$g`DH{ylE!k^P;ixHaz+yzpgT0ZwBSlwJ!DzR z9?y`}RNwuWn!)0b;62*w{)o>zg>J&yMaR()7y{eaNzk?|J~<-i{wejK7lpTp3Tl=6 z4fg68Z#(>E;5=CB<(TP82HZSv%IK@nh1uxJ&nTE4(8+5x%p}6m&V)DjjT)%s7G(BK zqBp5N^V-hvyZf!eYk1mNW}*b*)MW6**?M=);M>+gT@3xr(-~KjF*{G5T(lpguB=@2 z7*JFAu_R!&LaQzAP_4@mo%gq@=cR53%u3Id3Yz&>X%`Oy^&84K`31)&b!^)KRs(?7 zMga{1u28Z0pOTJc7&`bHVQkZ{Zf}o}eet3jLS|x9L!JjUGiu4U8mCs#Vs@gy0DvB0 zz`*yfI<5v^cs$!qNOfKoyy4yT-z8kinoj5+JhFD?VVJDWVXm}u1?OAD_3bXC5Q7w7 z)qA^44Z~_VIFoJ1-W28^V}{~SgHo+TzZdy>u=|a2Fr_!0yS_K3b>y5G-LUwY=)3Nj zjkQ&>RKs6eBJsNa!>Srk8-d&$qWCr1Kc@=w$gH=?b)(-HNj><3JWrui<_8#c0+Rh(^z0RLaTbQUQ-w}D;kyu;q zMs@J9H&J^+Ps0*w3H(6>r}(OWd9qprsf^DvKMxTKS3oU-^S#l~Vf!s7*)n2Ef3 zNs+2nX}wu(u=7JXHc*L=ZUY=!kCJxx=d#tyJV+{jyHqC zYznvL{vY<C$Lw>=K3Z?Bbub?8(`{)?zdp}?nZSCM$Y6SR09#w9DDlzR zU5&Q7tfkEvwxl@SmuuIoR)~8;dXw%jjCUzX|IKNK&`G{tZyOXs%pPlkS7MsP9yU^-nMq2``K4!8k%Ur+)U7NvHzV{r46deN}qoIEBeb_}cesFWL1Ua;-?;rB~NT zjGN7=tTk_VkdFVE$*to5#>_@hO&1#;cfVB@zPZr(PN#HVQ;?tY!(C!Yar`aZ@*-ln z8nlr)#*4Q{YIdy{TAZWhmhTE$%&XYY(&WSuTR#L?^G-^HjI_a6$`F`LLs#Y_O)pNX z-syVdlyrF_nNJmV$L|un9ARE~RVgPSkc|uum}q}S07~OHV>cIeV5*iJcd?Zt!IGv= zZLr#F$=Wie>9f+^yh8_)bAA7%H9X@%mSnU0275+B4zplTT@WJdp2p7gy`nVR+dv z1wVM`dL)X~kii$^88cUIA{yjVK+r-+Vo5*Z^+`ch=XQn>&vTmi;-pF@uhv9Xbj#Cu zv+9Uz%LC^xw<^o}ckVgrb$hpyIovvV&_{sraNV4D{`hec@bYk1B)^Bk^Sk;RT(x8P zC7GDASpdHjen?w8J%o?v=UeJgTcq6;yeGD!zLo}n*wOU6OhRs&oH}cf8*?~9#E9JW zV;Hr!V@pWYeV5&(`35c?tzrH}Hq|iSYuHnsH4K;}&8yLT~n>Xi$j_6Cfd}7qy3T0YvL@AT2svRGLbOd+-o9n)SWBBe5*AbpDy~arO3AJ z1e#T)5NBWyK)*zWL)^nxH$b9M_}$H*;t7R&srxnzZbeH4)^u?qE>2UH^7VyJEalr} zqLHBUt5VIGzJv}#z4#1IyaT>DKcL~44IN?x2SlI!aawQS$;J7_8TFr=nWn_+{&u}` zBcA=GYQASm;{EQ@b0}zWm?1kaUit&>Ml*;sS)-09^JJ`VmX|0E@$=JVb)EK9A#dp# znQu5wzeC^dGVe8ph;-u-MPB=C!s^%riS$=;l22p%1%Mvl7^hkLpwlMqR8#PhnR$A( zvMB(_NQleyiqz_^1U?b}Lsmb)MTs}k>H)Pq7WU=k!Ve)b-mBcNJ6-B$vEP>%L}ZsQ z*o|m54eJh+XH+|Ns`G1;SIRB(V5)+rFCs3kh1Hlq@MoQtHwqi%rw)WLd(^h;H^LwoM=?NBO=KZ}$~0+07n@dD)QHk1_|FfyiBb)S7bUDkHV!tgm@NHw^o2G8IY zDvM8~k?xLXnf{s*>=*J2d!KP^bZ#@6-Ys;^l~KCt-p7ArX=lBw(J*7j-9jG9erbiOs96jm^svwHPhB5=&E5n zHuupmy%HG>N$~=KX4VbmGL|slcB#wIBlU`z$%{K8;N=!I)w>EteWVD`$2@x#yNoDy z7jb0c<#TsvX1VN0nCeUzTRZmYB%9WwnGGpuOx%@8>vRuR$Dqd@zV_Cty*R$h@A-5g zv}_=dgV6Lbk7@$|$=!=R1ZPENGQW>^s{aGO$`6>Oe9am1(o@-;K1%j;Xzj;+%In~} z7&JrA&r}kTds~M~0T@86ivvD0)Y}EhxDIGn`*mn`p2@$TZAh%xA%@fQosk( zQ93}BUi0yUbn36&<72|cea=~uC8vV5@bRfoj1+^pkq@Rzf~b+t!A3z``sORCTW*}% zPqb<@=8_~NE+=C_iH^3e&fo0|eS&3<8M#5zSuv#=JFi-_`ZyJ>wE?I8CCqh(PrM#A z+QSiy-S?)X5g~?0Otl~k(`%`Hi||am`IT?u{V8WtMFuY9Ud?2olF&yvt=w4{UXIm@ z9yedPFNCq39y`ZDMAc0R88K$hckhf#E2q@3>gYI@FiYGDQLL}(+s@>5s!Lm10OE-# zi@}8y?%As(51WA4=1Rrl(%04M4Ie62&>wqipy>}~`g5NWjKkWCNj|#oT12V+A|$HG z+ol~v@*$R)ox$O6|0wjz*Ef@Mrxt3Yua+L9*zA(c|#BND}&RI^| zKv?%1HBaGL5N!Ut6n6T^qzF@yPmX^uzI&7 zefXa;+;@dYP-KZdc1jfgrcG3wyRU3hA)ON^{)bdik=U{1Ib{03#JJ|5d?(+hAdtYv znWZ|j(II>@`+oqe5ygVnne3rSFGT#;$@aK&OE}S;TD5XU*DY1pabGgD*&k>;h$n~u z^k1h0zp3>*o%E3&Ae~uHY0VSRAKjIT#+2`_8#R2j8Onlfr^_iEK(05I+SJ@)%;8UG zW9+C~R0aut;mS_1vI4DIb8$yS^U8{bm}qRprS=v|1mK(AO3yJ4@ytB0T7=kN^9B-zY)PSG zU+3VcG@h9M1S>mgF$RWTKq4fIb>ZVMRI*a_l`Tp#?taOvzl27Y&Y2M$3!Y5XNDeXq)eZ*f8s$tAu`qO^AlB$!(#1*IiD!GI9vqD=UF zN{dlZg|)NZAq6D_PFFWY=;46xzZ5NGUFdg=w+zZCwsTkN57E%FRV$J%V^#8+i~XRr zBqxxfCdC~8DtkEEitNPECv`z+?-P_ya4jFSw|EpTd z1lD{VxXro1u3X~vrU5ky)WjyRdz*T8;{6mHgiYvd8pidJ1mCBlYJ&vT)m4=q)(}0@ zvCjOd$m`4qAg0_-Jv#p<{m2KCl8aaG#x}m6aTNT|dVzO_SJ|~WU5r;53D+?gvWUES z@-iy!JfpVd5gbeM=w%MgDU$#it$K-<@#XXmC8biUSKjg>uVG;^VCCMy%gPOo%n=TO zm?jG)n^U%A)!aremuag9hb&noCdWbR>Y%*swCd!TAKwxJKL!_4QogVDJel7)?B=k+ z{Q?tb;@#f8Y*Jb~qyEs&c73*7J3aPgyB(xJNQDK_T7Se%?8 zlh*Zg$hwYD6U+_a;S`_5;vZRZTss7#-EM}zR~+@gqh6c7Cz#qNV>qkBiE^y*Xfa>D zP8>uU!IC`~Tyjg5MA+W#(>STC{F50S21R(@A^P{QV%D$US@<5w|Bt5Dtsm)X9fV_f2xn~&j$hVL4&-xjb=AAb4`;W# zjRMc7uv!H!-&_gk>wlbns)ECLi&>IT9Q@syMUz^;ewN~IAoURRhA5lLV9XI#NabTh zbAvOI1osxb$>AtJ9i01Bq2=-fp}~y>`~zDUlaqLl+HG!Z`YR3k86I+E&cXTlhSDjwP#y;l z>*w*qvO9NE<>j#8b1Y-1|CrQ&&5ytF_i%#O6r#KVec?~h>FMD$HJ>dt+flEtQSZ!) z)oZDn9QFbq?k@w>iXGdl;D#cL*|j@AVCHap3`4)S|wNAz+zRJ+Izh4YC$YRW)U|KI$pxWA+gp_POHrn4B9i@DP z0Iq)@d2>joM+t1EUi+J0B{Q#erb^V1SXrkz?8~-R^#!|7>Ut*szTRq`;B!?tHfnyv zT6Xg*FSPy)eD>?#pSS-C?T#ZzIeG5L)7#bYk11Oh@rxK^(^2RM8!KN+Srz2QFU-wn z1xm2YF3e%83O8wX$0K^6z&fSj)tCCXSr-r6AIJ{ayDw;aaA@QD8EyrS=l?Oa#eJyn zi3)O)V7&dv`;Z3E>QY|ryQwo9K!y8PI}|YCk!-VaIMgu1#x-_uc>9|Z&RXgbVePM9 z{oLq~+^uOOO@KKOHfzrdyT<48e_TskEx(hrbO1^e6%tm9f+ww|!W}rh7)#mT^&O>x z@BUbXFmJule75*6)n4Q)70kyWo8-`TwFtn1(^{rCvWf-Gi?8|HDX16x`!{K6wJ+s;p-DA#(1(SJwTs^Jl4nK|wonIJ=_RF7RC2E)7FMP&$t$A)>ZM z_HKykx4+ph$YhtZUoQtNX!64jsKc>#&)DbwXVDpVd7*Poq`c_$4ma-6EQTz9>4QM(HdzBhv-cJ)SE zTt!x7ON((KgWceLASU%olXQ%`d{3uOk8Q;rA!z{Xl=1AcAAMb@1$GPHA$8g~2$#!0 zmu?fly^@vK)X@9DjBUST89--HI7BAd z!Fy|G7Be1Vh*~3IQhWIYqygJMC2BROrZ)d9;oGev zu)gNk!T`5Dx{jC+a)#F%yk7*+eyvgX&KSwCw3S&D=8#>ll_uSpD(!lN$@2xT1H``aS#NJ2`d3j(i0|us! zD&A7)uMqp7L8_w)uS}D(JEU}5>*0@<1BXG`Qiv(rrkY%U-PazBWn7k9N9<}U%yPw* zVYD=TPR~D4X_qtoq9~fW`^RhqjXhwtpHFeV!C!YH$xH=>x}%sv_b+n2H@&g*e{sYg zym9I7{qbzUUhx#1>#~vD&nf7BBF-ZN<5)=<_ppUuGhr+RrB2$QZO!y|M7#ldh??b2 zDoH*BDjfeFTTNqX(9~*wEAPwjgbpgNlq~b{yNEvF^`IptH}Sg5!WE;Mgj{p`k?b3FXR#Uw2C}jFS&@t9aw_1BWiqYxzBzvL$1@Pea z9ioVs*Z8_&Y z7L)yOK4F=XuSes&SLJQni!MIR9??`>SQ_OuXWYTQ^cc=SIB-*M>pp`$IV%Lo_C!D*v&)m*Yfl{j@HRf zmFQy0<`t@f!7w~psmCC~I}80*#{)_s(-X?TeW0+dMAb5cat+@m0etaSN2Ap`OiE5W zTMaEb`h0Gf#zg;@y}QqjO(({Otq@j*yqj z^pa9xxX&FFI;hpiSz8JICe_A$K%&!j*mC`I0usG%w)tCie9x>j$?m%|7gUc8+el%q ztJ=seFngvdOW*TcJ(^sg&ubr#~a6AAp($ zBY=jS5=QcaT%}r3*#~3wRa!=XcN8*{-eiHE)?|}$Ei^%=f_g+~H8C>nacu?RUvi06 zCrFEmL#LKvEQptC)?v6AJr?_KKLg_yCIq6BHfEU!*21oc)7@0YEUz@oF&g*=Q`<{WR?_kFuv&Hxy3n3cV+F zHPD30dsl3t^e(GI>RpBA)E+r&PZ5=kPWdr@R}I^8y=smmgy_5H8L5<;a5ES;{DM~? zFaDhiexI~dzS45tP-u0;Wji8r>!E)?l)3HJh`3AnIeRNj`vwKI)rOvvwM9iF!ZWW_ zi*={5pP}C+gL*|LUpqfj)Aw%Plm%hv2}p8ZK-?F!hAE7c8J4-shBb{!d<=>dhrn9T zqj;TG79TxMY^@+7hns+4{Rd9MLo3#C$Hf^{8t+sOm31@nv>I3NY8yaVGeX1^X zC*h1l+!NV;I9+xA5IzINrIj|;f-%~;-etasC z(d9HNWQyk+!Y8Es0^zqJcIqkbAr@Ol4e)wDYDu2q&Z6KVMOVPW@=Rg}EO#6rUsX#84Ftj0XXD5WMkLkbG)POBNf@ z4(doc@{imrH^N#PiSyP(&+% zs8Sl5{iM;s48Z1lY(PbtCgd*(Q#@E~r~f9Euuu3OCLx7MP|eOK0+AUv>!WNdmA52w zG{!)C_joe;#b5P@hl@DYduFgl)svIaF)`Y*b&p3`=L%8UkJLzI#T99Xzbq)U?o;LY zgKAs4%IYh4nIfUszOF{-Pg#_Pxu(<_{BAoGX)ew;eM2plc=J!i`%t056&*j1)XIU` zw!0(k+sRct+(o10Lz<=QgC?vzyHVm}b42|;V5--WZ|@3ie?>oBj&o#L-ii;X&ocLu zQAZyb!Ic6pzaV!g7|_wjA*1aS_$WkYFq}3zjqI081{w^lvx#aNvU_7}>;_lFIjhmq zDlb($5;vEjlozS)G2+rn%_BNZU9Hgmx=$*-+*o&h3nVnYlkxAb54Ml!1l}0Sj^Z~b zfspetH}|bvPk+CK76C&RNq#$PX;02j$i9Kx-ONVyb)uuoR{Ttj0(tu-ko$g@G$1qM z9A}y6fm@I?+ycJYv9GwATu;}6zT0)qH`7A6TRf_ zXJnj=CqxNRIJ}|UAOnX2lc=Hsqn7~CQIK9Mh@$L`=?tEXOoorO*Zr$+pDgd!Q6w|k zCl&WQzh{2sI$Di7@eqnGXxEOcEY*_Qy7Eg}8fLh=TDd%} z#8y7nFirw+6^utaNK1{TzRjQ4ZEgESVT>DG{#i8@M&1hNgBBUQie%Al z7nVM2i`!UMM6bm)uS&al285{BM}mD>1@2I7kB{yns#mYz#G*#vVqSjnk1W!e zz#wrS<4{!Dld#jqvil;KFg zOH6e5ks~6YBw3NW>c9{!z zX@8cKbc2ceQESA`Ma(w_K{b*%u7ob*uU(!fD}4~7q`aS`YD_VFoQ>OpW(wMiV2AKo z%>9i=QxY1MtJviL(=X2@bGJJro>8GCGIe(C)v}34HcqMft;t2tMy3 z0iN``pjP8$S5nsdFHz!dV?jcx-lG%eWAvbn#0Mvi`^V*6agmVnT(V;iRE%YrNn z%%m{-tPE8)aG7$Lgp!m6I{;?@&QDw=B|DY%4wJDBOypGIYag$6u`SFT0_aL~)Rz~( z!eiK4AUPF#i}i&dJ?ZAu z=iPHM$?|rewIh7Mq|^*HYKGtFjWF*?Mtlutu+2rEt^9Do%A_0%} zD}Zc5iXJV!0y`R^@+6y5#aB3>mR$9YPm~!c@@uB6-aagCSL3ZO$Ex8{3#@vR`L$J% zKNHI3grziw@D4cvEAvua(CR)TQ5$P{`G)zrE1WFCRd~cF7H^_Jn&sA!>jI88hc$5n zOOl@P7-nc;Zi>UR*k0uIub%r7;!`WECdo^%*e>ix5znHaX2^6O!Odm%s(+J;n?r5C zu8J@AVhZmoQYj}^uY2R~C$x+eC{em6Un=Gun}-iu@Y{QSZU93D`&S^!ed`6_hYn$= z>w}a2_3(K#130et;!G@y<_GDu*^b*(Ma#4KaV;4S9K3X1mWG7TmGqs8S*7XB*EZoX zZ#b)+9rETSF62?83YAL%1FqwQqs;{WH8R781ih;4LPM0^gbMyiPCa!s&v&z0ukWV) zmG7!bs_N7Nxt4xX^2aC~UcI*HlW*h-n)f=5FW<*UPURHuYGHdWrdX}Ea-U#)Ih0X& z9_7A0FTR6VF1jkG;y#T~h7mv2fVs()xgp3L^)ip%p9fAweBW^3AcFOt<7}suX$1FYS z)^LqcU)~XQ<#)1Gq^A)CA|VV`jfcO|l4hr0@%)+Z8}@T3Vj8j+vxXr~wy7epnKf3G z%}qLs+nAk6XFbb$+>H6Dmo^>^5R8n>Gfo-Zg2rx$o{6z}vHVLu<3$@E=;FOs=naDg ze?-fS0#AKvFk=T_PLAPj4SBv480Lu@1gWpJdj)zmfm*zxym%4!sg<%uDnQG(tF7@D zw)GOMzp5-(6NYnesH^Fl7N3mfPXfH}0mf{rJKD0>j@DG6 z9MLxEfOY(<++p08t}hBrLprtlCYoQBUzaZ)9d<4S z$6TFjd46L!PcJ`9!&eHh>5)hgDos)E{28fi7J6Z!BY&+op`>m4R!$+lAymNB+^g_Vw3v23r;2VWq(UqM#QqtPz zM;d|Q8E^JRmrn2;7BTVhsZA|ryj@v%LXphEj;=%)qdvLL_%jyShg)vF9cS*Zm5KIMm9~YEzoLP+mqp%@Et7M7~A;ZSl@478}FBs-T6|kqD47aiAj0?e^-FIENH1Gx=;8aRaY#SrY@kY@2vqL!#dq6ysJ?DJY*wjEb%lKNlD<r;NX9#Kn&=vv zd@k}eYuI2sD<|^Kg^@lijTJZB{KK~z}c7o!!7H%WMbR@I{V zm&q8WKoY-kY+wv;ltnYP@xZrktJgU!ba5}>0z1w7l0OW5Yj1P+EZZA8&nNghH6AYu@-_dr1 zuH4tEImRu3O>uhqxVO*ZhHWgc%--tZbLy{98cycW&Qh)u#Jj18D8~9H<#G(Qs!td- z2oknJ)=s>9z2=LlP>d8+5in)tQ^RHWS{S+CjRUj;VHW~dx2&~)jp3W^D4ChOHEzp> zQYlKm`3V;7f-}d?pO-%AS-;~#=|8W&Z|%N-8GIFKO~1ClcF1Tn&qSN?RHr;$x+EKR z0s!{Lv0;wr+!5zTWYP-o2vwk2cP24ez}A#hbJPUWN{vrW zjzH|#<#;oEJGDl-kEB+KdCPgyQon2yn2?fD!OpQ(jU`s!4U{Y-^kn zW)iGzMXj?k5{V=+c~R35yN85UCexuN66NYLC6dnRNGn~UGEOkw&W<604d@vxq?Ani zjCbYf2^cyJ6IX6*FleFFs)%RJBkXi`pW2mQ-7G(-eWfNFN$k?(jl}*n%6vJ}JK@)xGGxm;5!eB9 z{f;bNT&_Zt7Aty#C1)<%<$d(ZFaC70ubC(uP9~wdAK*Kg4-he9O0mYe=9n?xUtD(k z5Fh<4uz=J>W>KB*-m&qMclHzg{vZ7Pbhr;+&>YH4A~)RYvCS>?0e*gIp^WTaXP9;+ z+?vWULs_Dhe<@>8SNXVp&iZ&$-F0>Ju$Ard9Dq>e(tfoXwyX!i(?#NV7$0?#81~3K z*bh9vh-Ur@Rs80O=v2B8anw&|HVNCBTH8tL3AOe^tI-Wc!DjV_Xts(={oHDGi5(pc z&D5-tasxErd26ijEs z5lAD1b3h1`al{K(YDM8x<_lR$1P{f3pM6VbZjh={>rU- zp)Gt!YIJAy3}y_tHN%CAs{ZRgwLyq(LzJWj84tixEeb6@~fWBNZWbIl9X;;`qGf1o5iAo*EzjYQ=m%EWmGrRO8SVqROHq z7kS1$C1}7Ap;?V5ifN?PIpRVX&#!fqa^4nysVMZSw6W-C1^A*A0H0{ft5hmr+L%X1 zzOFC+?2wzMzq5?1ur|U>QK14wS-Yqu2UbIi!mEl^02GL(NXPim?9dM!P(M!8Nu#by z_pfax_Xy4~C|HAnB6Lqkf+`PW->sJwt|r6-m|=I`mX;u>fS2FBpBxjH@hcLTz8$I0 z(_;5i*l#PH4;`+3LO(q4pJEn0>s^e;d|w=L-PvX{EZ&<>a??>!Vt8!7FvefL zH_&Y2RshQIZY;Wrk&9J-@;l8r?a(ggky?C*fp%h=gld~P$;Wa!UN988?nIvU4D1se3> zBfiGXmmHm8b+O<$-99`jou!Ix)e&DICSqjMmsRN9N!jpC^YFfd1ky_&#}uo{A_=B{ zjzl*Z!wrg;tXW-~%NiYsZ4!Ofre=oJJW$2G@FPx)VQz9NFOwu@?%_^o-AAQwsK)_kDdJ=Vb+lnU(OFyuy>1WXEI3&N>U7H@p7pxS;*^V~A5k0JaHKNFYujbSFZX?Ra z%~nM^)f|;#OrdLgOQwm=nHUKm}nVtv6+JqsuS)C+%;6H39$AY&1?S&e`Wcb`3dPz`S~;4R~hs1)G*pOd-Y8Yc$9;WLam5m zhbK+NpfGOBkZY})e1Ny2WB-7hpYD!j%NVwG1G@>*4}%yV<2rU63V|lKBZgeR_I(!1 zu}lHeGzzzstuj!2XVwFUG5H+rvuk1)%ulf5@yTH_n)we?a=4h#bzkJ4>Vhsltw5CAt~K zEr+z)=F8_pH#fOBh&glfoZCtgJyB}ALhgI+aOpwhrt4(k<$`ef7EF-fHneuwYy@~> z?iBghAKUG5XiD|i*Mz8)>1#zmsQ+#{J}aaTX3>{)5%ZxUz5L?m#Twv7P9BJ)R^xL( z>S3*JTmtpXfhxhMQiX&SN1&V=)n1@y{@L6xqkBZnj?|c$dJvWd#=b7)-6wEibi7`i%iy$6`@6>fUJQE&|2I!)1PO2&ER8x0KfB7Y%b;|5wO z`A9AtPE=NB5^9O0r3elGgqSmsW{DOS!ZN zCbuYiQb_3M^^nmg6XkfSuu zpQLA~8W0lb7lYFYP;)k7uWFc7rV1;*-7- zMZY%TK6Y&XQA#u2Xk=|8#$so=!D;m+!+Yl0^u!uar-g#zVO7D_^nti}SOaNx>Kqt4 z7SVs!%xIeHIDHsY+~Nxc-?kQMK-ZithC8ZM)mADu_F7JO4Q!Z_ZaQJhWi4Ww6`-v% zvv?|tH1N#zqVEok)^anUS(<;ywmcA^>{es7v14eTrk;0HTl935v=hopu)?}6jk4E# zS}-_&dsT5%56zTv_6B>P;2}$3*I1oG%6ybNNZ6irJ&s%8b?4E~_C_Qxr1)queV=Lm02RHB*th*esmP;!TlwVkMX%% zG0#&p+A1LjW=X>KVSDk&5a{K@4?N@CTW$437hhX=vcY|x5*^=UN}EC*?I!G-U_BLd ztX|&+)^p<+;o=z?Ak!66SLtF)HrnCC8)`Aulh;b+xDyu7Fi6_AoKw_^{p@PaDfdwE zC38mzOcQPSqK%L5$B$>upJ*!)9PQXm?(b{^576KNq4`6<@!u8oBMhee5Uw)j*#?Fg>r25At#vyqS9wD#?#z<(99h z?t#2Hoh<-(GJaREO=!wY8LBX)+BdO8;0ZS%x~#~lvW(rNk$>b=3`{x)h2*B+X+Rl9 z;qY;P%a}*PWI``v3R?iv4B9EawQ4!awenU=Ea6a}Vuq0-xE;K}w#D}@s9_6l;Bp%{ z9r;{lKx_)O*!Yi%@5Yaro$_Mo3k)YqM_xZ+7_6f&GjO0exD^~Lib$W=S0wXBbzup3d0ed zCh}|CNW-=A(_Q4)dX1R|^qM=`YX1t^PbHq$ z!$w7)JT!K6pd~FzS4nsQDIIi{nJ|Z&^M3P5`R_9ptBHrZ6fec0bZRHjuSNaCE}P)s zAuNm{d)N!ZqLs$|QASrohW$EAxPsQ^O35;#Tc7QQ8gGh^TP;3t=Dp>#mKvBLr8riG zDCMG2Rre;AO{S4!tz21+h9pas@M}yp!_8Kt8yccYp#i|;bGSj7HYB!|55L;U_0fb^ z{qbL!o&>V`I!e7B>awWJDCT2}ZiWbMg-l>L$n{54Hsl-Nn(7!9n>|dn@q&$T>yyIM z-6L{D6etvn71&m&1kLKS`p(C_MQVUZ{to7}z--B>X*7E~rqF5ey&}vwMGecTmEhw2 zV4?QoFG;g_U5PU`yw_Ed&{raU+oN&{-y-JbRJMBI_W@bpRf?N8=h(kU60Ag|SFo9! zFW)Fs>WK_~3ifoqke?)x=8e`6wq&%)ytZFTvTD6?@tUOunm_V^VG4C&ensP* z+iK5oYaa#=>zn-vr1V9yhUj&umHoNdW;tLp92~bBJw5?EY2)omc+{$)f1vOOMQm3U zfuvWi7y6s7LJCe--xymAn6OV0K+d6tJ#q7fo>keAxYMu~dq3Z{uFvRB9uukaBZ8k8 zu3vi#H6GmUZId1pevOjE9ssV$qit*UW(G}(v3A!v=9EzwfT5N?iIH~-#K#+@rR*OD z$)S;N}AC=*9Y^;@ciNiN!RlCGugdd!-GUoORui53-;Vdl$xt zBAKKTd2pOw(OgYhKB3FbAcattvgwm%l5-tg|~IZku^0 zS;ffB-MBN)Ij|JlQRJ7c-+T|4n7jDoBr?$Ci*e362p{ym@TWcD&z5*LY5&1iUcH8y z-@MeC94|g(sW~t(?7B9>0644d`Z{1S)UEB*Ic$2_0TFLvcS}oQxeh<{=ZU`k1G#7h z6Q<1w58zBQ69Oq8vnxNlC=WXq%@C5p!EUDpTG%qR|EAUdJK4TN3a23l7obXGkYRlE zPdY}*?|uX+2l4;I&R}8x@{fK5ciKc;L;cJQ4Z}=xDosZ-jx^$1DLji{%G_UbOp``? zcHe)_aw+?zHwn4c)i|(>S0WOI3q)|w4Mcm?`m3_QyC1xURm?#)CyI) z20O1rxvnhq7i%mEsoNUO<{hM1Qi?((=W5*6?gzK(>Gzkhz|}SHT+1vGy~@thfh{w0 z=d?}lPHBtmr+9+GOSdklf$!64avlh|uKV70vP4r=4i+iNR~YT#A4*+x_n2_ui|!9r z7nNc;6Fm*h$o-Gd!8hc?zjz=gCxNN9O2Su_g^#Hd6w_*zFx2bJvcIViHkFFm7QZ1^ zM6fvf{5o%yip6zdJN3o!Kb+N`86^~4@0~9^mkqA8emkqBde2POLiI6CNBqWICA(QA z5xBEDMlmbwqgGWv#B)JB@+wM;Hk;q6^Fv`Pe*^BnipsA>1DDm35WQV5&9 zNx!**gD9$*nrYVcwZLlpe}$Lq9Ly9_Et3%fI@dzT-A>+q>YYS;$r4Gtx3DpyB7MQj z*@Ajzl5!(#S#B=Y-J-H}cB=gq)`d=Gs(oomJS0Rrq_8XE6Td7*k`vL9uGvvD8Om11 zh8EYEzDQXof&ZK2;cNHSrg+UimjqaoRpL;Pk2Otx zL7m}~vaBk{dbL=z6QeQr+bFbd*%Ff07Q#R|jj_n8@cxgejo%2pA~BH}T;^!;^1_R1 zhk{yv%c2-)5-2k*O=r>g zv*`KHVG6S#A1nPN=(hf&59q(1*?;IZj7&xo{*Va#Yu%{}AryYB%f1SY|9Hs%TYh0g z0nw|>sa2Exg}wg!E53--kdI*@_Wjs@2EjkZ=b!uSSmrcs`M;R0{l829Kfmj&ST#EJ zn)UcB)?vPWaNT2LW9BOgF$$;M=dwM$>6IztU;OyprpQ~kfM}~x5%cL8U9;L=x!M1H zXaDVfvuXX#3E#ItL}m<|(V}ZJg|^wX>6~~04Vl#TQM9n|Y^7aAEX_8jQ?8>$PSs_& z=RN3`h?l?GOmx0?7NHc^8m!Dmiba*k${YWkc*s}&cMpMgxUHTZHLuj1oLJk`nsC+B z)Ua#e>P){kb2;=RGgnsB){Z6u36r!DZ8*o9@ zHQq@#266xGlfb6*`yy7Ymbqn%$sqkWmU*u(z_VEbLWQr?Az&6a$2!%>i@9|hQ@yhw z^70YYYo_W@Na`-zY3Odj9W?=S?VaOUxJ%Dj+{S_o_66337G z4$4ou+IjJz;`Pg%>%FCNGg10w+7AUZ?Z^)g;Pg&~mR6&8K&|Ll5uSRY=;<~Xc**=r z(>!PV_57v0Le+D@94@aIRjOsX3jk1_Rh7U=`dRPClDK-I)Hc6~(ytj0G3h>@8qQ`$ zuiS6pJ_m@hbTibK&~m{nV!@~w@X>ua>Mqyl&W+@Eyty}B$$^IopDECBiiw-cijO|9 zITMt97~3^X(>JG#ji_eCF$A!(xwx_)6xjZaVJ$x%!X^S+oLq6e9J6EV{txw9^Leav zE}Y1zcNYwiFj7S`KhIGENWRD(u(CT)KC(95uWO1UC$v;fD%uR4JCpGzJj`;qIQDSa z8>QsM101$t{K@;0wrIOsfFio1A&jy#Nk%%eLG>A~sKwH$SaLz3iF(G(XR}Psl$y0A z4ka0vgxerc#+7?E0jpl>97iY)eb1@P%xZ5$Aivsmz>-VdILzVXSnYK;mX5VXq|+Fc z>ceui3)vI{zBK*a+~EwP$Mua%Wol%CgH0!U*dw>(IO`!_CM*ncZ&AR;nn7$t{M#nI zJ}05+IAHx+GS?gVvoua^!_C35-ew|f%NSdV-fNdjV zp}F#UuX$ZlXpA`&&XfhrbT@Qds7`y6 znRD8ZKJyz4k3}HA&6`i|2VMk9KAAPK%h<7${F#Qhi7v2F$%Yr3ik&k3Qm|hI zR)B*`pP;?1*Rq{0-_CS?aRFIb!0{dk1hl3%<9v{LMg~kqot*%Pz-_3Bu`SiY4d?v0 zO2t!rXV!Azi;!`NU!WnLz{}SpSoY9lzt9EIAM-6JE?opx&t(LDln>|+b7a{B{buI$ zF*7hxyTgkoR1?ZNJa6?xmB`;<0w-=y*;rU$5;}F}Nn_`{@uYJw+`X zBiW9*!lzwKhV}-y&f_ZdOpy8t`+yy z)q^sy@Eiv3%Hs@MeHC@wDX5dknhSJom&lsDHfcF8a}d(HK!eo7gDHc-$u~553Mk{s zmWU9q5Ha@)5AVICj3>&?%c?C4iXFFIXEd{%0T zsA(7Y!zJpAbb3D;KUuBalDyr{B{HSTJ(FbGXv$Zk6Z|IoCh)#ctk05?p*6=#93yxL zZ;f-vQ8i>Ch_;VD(p zH*+dOoHrn_e#7o^vbd!8?3EOW!PCYL;n3|tMLSMW!E;&a9(|kQf#O`lf z=BjVnu9J_}^48EdLEEmYWNq zD#pibjA}JF!mELgRe;>%57!(k2GGmr0}qCz#+SoTHaCp2wnwN!`654#)B3JM(AO(? ztDC}F_L!OH!EmFkDfl%h=q|0TMzw&a);^F2B@2@q+J#COj+-hgCd29@0!x$-xubv{ zEkE&~DN0UaoS=~sRN#=Tz2T?0h!_u(f*z+ol`-Hg zfR-b>!JyFgRV>qpA#*BF;ECbl&EHM#ML_2W{T8}!{w9VxD++QoX6Wk!fU1DJa?qGjkIk>zgn z@-i@(Zbu!`dJfT&$eVRp*UfMb|0l5H9Huer_AxUd7a7)pys%@ z?N0$y%^Xi*Fng&9AeZ3aXClclDb85?I`xpA=0UCITnTB6>8(|nJo!z$VkGqyVbs(# zZm$_@V||x0v}8hDed|^>H2vA6!f3@e-=eD!*ii8|Q#N*P1GI~q<-E1cSI@ zC%&$VG@ee1AXv*zSLajbVDmW4`7fehcoUt^a$tmQF07bTZAKia77pse<1_6T#AX?; ztVUcW7DO7u38hX#G`nsNDj0t~tCvfG0a~rt!nu-!s0<}~^KxU@3M0tS&xyc#m9z8RcjYf#;8#+4*|@*<3*bx09DI;NP&`Vq!F!rly40WP@vB_AVSo4;06hPq z{1dVKAN%$8+~^}+yN~4vKX^O_HaUJH3ISd8mD7v8#uTLtS1cwF&=)zK{a>IE*J*6| z=*9DX2cqftkXDWQpBpicq5m+fAJhM?J3@VI_Gc$Mz}Ep}L&PcA(|;+ruqdbb_crj)zTCkF zwC(>v)5Uy|`X!}4o0v4txaZ`3(QvMvX@*LPPobWR4pgl;LSBqX`9&7Z>!qp!SWT*r zxALW2^9V6d2 zsuCL0sUH*V2Dx-AjQ2Xm6G2x9Z5AGQ!=TyWg|bDf@7b1tT5G?!dR|q`t`KZ56Q;9E zQFOk}Av?az7cEQ7IjUBBV0jFY>BRHeVHPG9dICH(+ z4TVBzW8~}0^A>D|364fuxVd~LeBRV_8na}>u_V(wSkfpcyzuR*{6PfdG2vC-n!jJ7 zv5e?Yi!3?%4MQwLMuG32j3=N94u1PV>|eV9Qaqh5*le+~58#?4uvBzLw3aNrG=qG4 zdfFRSgaX6f;EbXX-yqp50o3OaT@aVW(=ELvqjNwqr`wr6aet9N2r6lz96aIUoaJz~ zipIDh@V#dbEPPVeL2GwkySMp3shs@_u+5^ef=74XsldKJQuSp^PRN6*Wa1!}Ce=%P ztIAPSEU7HsUNFGVh2>fxCuO+;4apDvk>2bYL_CEZsLOBUdqvTrv35|NZnZL(s6RiQ z^E`-Iz|7p2D6&{2KkePaEg5d)BpzoVoRX-m>#cjM>nSDtth30uR;s;ijQqeYIP*?_ zBrx<+yKC`hl(M-zCevF2NL4@vC+&rdK~^43?*s^)7*v<-#nOjbY+0tUf$_wY1 zPTL@<7HtA^zu6q(Fwbs41x;{g$`Ag14tk<#G?1ZK?jFpi>_XvQqT_(T&K$-WF=WPv z4`ahh-rt3?Xl-`o7#poXp6+nG9Q;wN3JQ@Y+~um;g7eigSPg@8(*pIlbI(TsgxRUwp6Z({x`UM6%qIcgbYDAGA-=m_&hvOGQBgu_m_K^(n5YoFB}`*OgXimOQ9u7~Ry1L$m#D6&?4;4v1^fIsGmiectLC-Ildl zu2ZeO93Dl>kwTFT<2&<=zxE)p?v0Z@x*}1=6yY=H3%5DrQhL*o&b%%|x%1K*#D%3k zrFm_taw_S_0M=1XFm$RL8Au1c3f1|F(lU<)svi%-yH$g>1lY4f;Ndr5@K?#tRz-Tp z3))KY=5L|z`?04HEq@I)QxOe)yP5+!&sKyklh&h z)t+5E?|!-8JR{9c2bZIIjCFTK=3b>SExU1fuPIrtDu#W1&VWc7IWroH zp}IIpU!`p0D6&_+ZW2FT!hnq<}+r z7uJU(yt!`WfGUoG>OZojpnHF{HNNclB3$Eb;5XRZLrVEZD|oc$)|!AuuMb~oE!QC@ z=r1N$H6@46MryF{{r1`dDch;Kbph0&@+L<%r68jIXm{swqbk0y1Hs7LLC4A9L4qh0 zBCzM~u7u~+6@LtL9v09}T{5Zn>`n^AS=%7`~?G=zGLN-ezM$g z?8iak??vv2P-vf!O1tgO1))BYrqANa%2JKgb@{_*dAz;uiuUl>8+%C;xO6fz$c_$p zM>S>5*r>vKpuZ@H=Qs`~-4ttZ>Ne^G#fd$SkJ zeKjA6=;G~@ad2UpXU%{+Jt75!=J28y8sv7*X#_&Z8Yv?^W;J0C ztg3V3G--_ihEUnTfXUfhe(6Qg`^374&@x77A#@w-N?4$pa6AVLSv?XU`l}L$wAldh1v>md*#YH&UV#uH3n+z_TNW4!ip4m!P<OsDC*oGG1vytXgzce~L-=ouiXHj3- zu|eGq=K3Lt>?A2 zfy}aD_j977_^$^x5mNcHX`PBr67??Ee;`-1)?`J+(V>Uh0klcSoZ{K z@G9_D8I|nS@A9N@lq)kpFSaUxylbRq7wK784-NMRo{-y=C#eC=xdab{GU-%lWNsW@ zVP2c&7Eo3VN=Slr;qpVF7dM^#G4bPg&$q^u2*YMiiE+x$D`CcB%cm4EYN6%~A(UBO zyVZVwLRqMPLEhhYWTe&d9=d#t~Ul8=$ovzMwtDJOh}#z zgXC?(AWJt66LNWR1oO-5H~PcD&$(5SJ@t|rN?aO3k;v0#dO zI;$OLYbOMFE>^<={2v?Y=&;Bc;Z_zF$HEHaok{qsEQxu$Uw67z%sq8_%}YH zZ+xJj%6cwdsP}K9ZfnqI&@>RG5Nl7IWp}GKV{CWHV=c^KZESnX7;OY_6o&Y}LaJ$@ z^h1wBf>(oo*8+H$%|+SG;bj7z%9EM{);9^v!Qivc(5$Oo&V08x+-P!|NBwfSAr9GV z8~H9C=3bc_mafj#hmMMhk34Nv8jVL%qOWR=^GW2Z?t>+(1%^M|thBO|34{!jDz#ZJ zZO%zI3q3ozjuE&if)onF(CEah=v1KyN2$c#v3|)|w5u9)?sXJXhpV36a`wTQcK^GU z;u8tjeO5209#%41L#pmw0opOI?cCyd(*Zp|aU?Qp_S| z?2dN{i^?y%z9PtIU!sjuxB9YlPM7OHFIIP7T(w0Xu?bp}kAjLdtelvb=ap+{PxEySy*{(8M*%~Vk`{APaHfy9! z9gptdK0|(3jV9A@CeG}VVw?`#FH;AOldhpA98<5Tlw?z^tT5@FWxB6Y|m^coU{ZaT%yH3qlF=nAf9(+qQ_^s@yYGbW-e)= zuae+=?T<}YbEvtG*93=&pgxUp%I20nbdp~szoje5c8*$XCWtxf{rD>&&ky017r-)+ z1;0`t(7Hb7m7Au0u6D;tWU>tH4S5gHt3R52Igiu@LUk-M3M}5*A^oH`4lGlsN3AfT ziF>pmyg_u-pVznMh*(5S-QKK!w?6XCqi;HS?jqN<;QV4Imc#cN6F$l{nl`%+htBjF zvbmWQjD`o90F?vJ#0o^=q-wk9J}*%Pjjo=hb~FW2Or9H!?M59YN-J)WefgI5kt)&K z4tm=(%$i%NHGbg>%KDsv(_n+5m5n*!%q0tn%21I^rEjpH?X~PJUu-)Z8mV+#d^Ciu z=}w-$>B5fC!kMpB8@D}lw<+q4$R|CyxhtOv3wxMcku+Dmk1E$xDscBM^IxDWdW=(5 zYIhxPgOvLdpDSG(Y@nh8uQ8;+V&iH``sS_CGBt;Kpr=r@7iA0QDj%)|4I#DAIbgcy zCFy=>5N182h_XC|bgzf+j5~#3`RTfjS(cL;5Py7&cWCQquiP^!`0`X$Z0Q-)ahW4I zB>!H;9-!I6*$K)CE2|_+kA-nTwdrM3A}Jkgo1>%i=5#@n{WM*b1J?ASpuYSJV$$OPAaVLViC6{&-Li{3@1;lx!lCaYlZ~G)}Z#tC)RU_x!R*owf%TD2+TzNa+574GIi- zT`<{}T0{bov7Hn--bPAmZ&tHLPzrgs<$KZwzPGM3Lol+px^v@JONPw3^w{2xAv$DU zn&PsHD8i|C?IvfwB@G(%PTS0^VWiVGg1cCBw2G7x+hJ|Svno(X}>M=srfswEKZqUPCV{qN;AfSE`slLt6ID;QK6RhpgStRS4gX`le zO>C$o2~+rMDFY2?nT3>wpm8p2$&&1-=JW@}`K(R+YDbf$w!SjL7r1Y$q?}Q#MX>jla2V0@UWo z^HYaPgR^=AuIT&uPB@EPh&w<-fAs?31$ z)^<9BV{tdV_C^ORYxHOWmMyUUaM+#;X1xp5jLB>z+`t`wh;spETRJs>{I>U!0=z*5 zYg|lDjKxx|x-ykX3H?osSWOC>4Tiry6}_UO;>ofWZTmvPHU`++zZ8Q1oDFs&o>Npb z=h~FU3hqyu)xke_d3kYH zov&`7n<;|+5YGWH>}qg;s0uaz&8T))&19*C1sbDX{%uu>#r+Dw#X+Q$R(OOS!C8YM zrgW{#oMo-@?+E1YZUYBly*tCl?yM|}!v`#qi8R;XW1GF|M7Zv=zQCQK@WjNxt-jEW z8y3Ti`uSi~KxHJCw|kC8H4m=bF-M73N8{!n995*hUWNbaf%cq_>mrpwZ;kA zZZnsUr7MK*W|7IwyQGdP4x20|HXD!h)GHZ`RvKYlUtDVnVtqk<7gh9$a)?mB8KV3- ztWX~fEXkBVERDUyC=6Ili$^?m6fa6^MGCs?!aE2kn0msS@<}O_i*06Ec7PLy=0nLLVaq?{0=#6rs1(bf~S@X zpe=@(K?=7?4Z#=zaBqKV_9*Kn^rt2eBY zkbZZ|`84>z0B7E_8ZG#Ykmj6P+58)L{g|KdZjP`h0m()^yWI~K(@LCMZ#?reG|VT~ zb8nL?$VeRx3#*p&$vFi)2OoXs)0&yX#M)WVJdc$i&G)A4#O2nnyLBV&Z(xa9wASn< z5;F;5|JWV?>!=VFFn12!9B&XIhLhQl*4*x}l9Q9GTiDz7sTfcgh=)6Az9rG>9)E?w zV*jXX*u5@jf#Dv{pv-~8ZUQKzY#6&juDwGk9$x{%LzxsN9qF+@E*;NlNHj=%T)<8; z){#4pLu(h^$c=nKA3kKei8`FXIjgd#G^~sQZSJb5LYX@l-G-Lr@{UXi-^q|NXzsLV zaFm>a14xV+i_A1^#fuGeD0@YvDSKtgfM$|fwKq!Hn)taM1gY8{wkIEwLGPoy@=y)T zZnV;H8hgj2<9_1hqt)AczLdxCy}7}&U1l6Uq(Fp8~FYt z%AWp@m1+=OroD_!vlfdl?a7c~c5dBd2 z>@Jd|>0ySbaOf7^Du0x9N2igx^IbY|g7oG!d%7>e`O#iD`@U=t8! zNlCa>_b2mUEDnXGBAXa@I@QyQwQ{0wSh*4j0o(m_Xbh~tWU43|8z1g;Z^;RILvaFr z919Mo)PL<%h#g6G2MJKkq;u&|fpM}%?7hC~T$Q(>b8;_^H=&^wiZPB0l84sl%_jQhsYwtJK%^r*JX%>AkI0PcYzdWE2jL5~onLu$ z3#RjYB}-Q;hMj519jY%(4rU;(#B5H9(l6l+7ShCbwwjc@WCg@%dz-7ZK0S9w!>_u5S)K#Xs}#1Zsh zJ~95swX4{d6DkQMwj1|h_15<-eUhD5TUq704`!TABI0^gtTgRI3|SLy&yK;z==h^m z7xKwagHGwjL%dWt-YOYxcSjqBwjH;3X`S9X-_U5?5s5=i{G(fj=q}3E#KOnExtEOS zr0JY=J-7}&vVOy`uMO_Jr+W!6SxxihVfn#yO1b|0TOEQzBtTI7(AM!J6Lk9H&}a(B z(oEK<{NrcyS*9j{9UTILUrltOQ2P1YQw66HOePOn7=q}5d^sjk*4;*Sw*i0ib<<6k z%i1t6xE=Q)O1%V{*Zz(s-o^#Ckx2;yp$R#6DksDtjj-#mUcTc@)D=%>J~Mrd)Zo21 zw%6wg9D-MP0439p0Etk-=WUEIz2kh7gRFs<%rBhyf%`@5u~cWI=q&mDSC#4tJdI0HGAwbtQ@VBF57Uj7ssFpui-X@3$(SHm5ut=I+1 z>7`fevK}rrwz>J^8rt)E&I_tB8mAO&Pf}ZMyPnx8t%pVP30l+D6Ytqp)D@ks9A-lv zWa-msr*~ICW$y~^{etuPZ#DEVvH{u%rv5(n9rlu0+@&hRuoc*uhA(X&RRxQ*OqsAD z!sow=w7CQHj6{BShk5E0nq8Dqz!D!IXrSYrGUn1HBT2CKpsI@(aRe;e_@Kl2h)H{2 z>J`bceE7(LbImo-ZqL!!7K6G5hyie4Ce%Tzi<{{SIjE_Q%sRuRJwTtQvqwYTT#JhH zri)@Q624Yku*ld|xilnSgPdej{SyD7rMl%-*eNRq{ord9qd+DY;7WATsHwO-BO5wt zO}I>OS6ORQkAgd8Z3IL=Bt7z4T%cdE;9X<9@HCl+9#3bA!u>!bZ&8Wd_wjugLRzkwfCM8?nvQJw+E$g{3xHV-?cnm_& z*2sj5E>oZmqM`;8JJxXPH=!&eFVIn&4yM-F!VU%g-)yL+>&0ht!33yqCl?pZ^UW7z zOm!tWY@-Q&mRhg4z=R@BMZBcTky6Q%NH^S`9(HoF6*(AP|0D!o&>stq@8Frl`~pvc zaTeuYDTTah7Ky#QzHXI3?TpdsF1EJ;9L`4uLtWR8WRu!L!5mfQZWS*Whgy!8lojl9 z-YYsPe2Kc9nWWDbr3HRpLeL1DMN2=4BcT_aNXe4B8HE4!>Ll&~UU%{GqhP=7)LiPJ zPj#7Qr&>pK2=Z(*bIjDLFpcSwFjah>=K62DE=Du2`K$cudIf})_W$sLJAf*bP6NPt zsIwSuF*Ndv>@woH!jBx!@!6>O;Dg;G@l5kkY;Y_pd)wx3?jg}3ymPT8;_<FbW2v7sRtwTMPYaHw;pcbWt|ZRYf- zZ-<(Qb@ozK%3ggTBB~7g)L%VjZoEY+@_GB188SeBFS<-L)?O_s(U`$vgxWzu%K0;w z_r)To%oAelLCUSMz2WSBLS7ORh(TZ<6l8eDo}BCXB`%ez>s0zBKJrzZX2qwJxjY@9 z-x#}hpybd1sfIpMt{NB+Q>NK+{Wnx(+$lrB*f6Xja{(gfXe^(--ELfFZa9iPK(aMG z0lm&KS_1Y#eQAG(iHSZ`E*A%M(k4ir0d{y83wsit>_K*)*~| z^9sWyoV-0aHV%cRv^c95!KHrBgRG&JY+!M&WtCIS@p~p4>xb&N^5Zzx?w3lKF^K@O zF>N!SZ@T&>>#Lvdt6V?RbK{`|txuyZjF9+bc!L;?Oqpk6w~BfRhg}o)E(0P;ZB7`BOcT{9B@?q3GbzMD}`sxK)p^p zlD|>+dv|#zk{hkbS7B4C%HdcAX2n73&gwi${f#N4l$tZL#pjtQ+%d5;iB2)3J)Mqf zGL?r8-OO+dUGwa0TeLd|@sS6H)#x%2Ni~ax&(qm==hGpv+Pb~WS?3qmw&Ka?va^zD zk^~>tfvOu$w69eeL61=SdrSUKf}h?%0qF%GUB*l*ke*IhYA6L%sPnzc&uWl23IIf=UJlea3*bA_zI$;{Ci zf@m9j$g-@M&aQiK<&IsJNgH_rW;&?e1pekM^n0MS1Cn(ny)X1~K9`OCFOg$`>OtS5 zMu=XKN_UTAR#RVdYaGojCWQ+L-^qrp7kP~gZ;uM{g`S!C_{Dv*d`x6DyJC2g%wnk@GhIGLXYTwrWSW8CNM z1B??5B{f8&>3)baaHzPcRW^B@mYVl}k7r`w{m8^NfIttcJVA=4*Cc7$zCb&q{A-_@sd%Cb(A1II&2hvwqZ{Mot z!^E1qC+k)uS)k+Il6>;|%w5~y^gH?>Mg5vpQ7@go0ilN)OQW@Bl1hahNC%CQpq2niZvcoT30gbybux z>hgyc0bu_$836VlPH$=H{yF`y)DQ4@MS+yNKj_B+FOV+_AYNt)`Z{|5NmBg#UDRL! zk1zFOKNI~Y;r6eT<*#`F7queUFrSb=roDZv$N0(Xz}l_;c8UG#ZPO)x3JUYE!7&B? z_;@R(pC)Y>hs`8^Boug45CPd0l3Z}ie!s%SzaGDC@^b{4rk$kzJTmM*6|^bNpUhzX z`1tl21b~NyN&Z#pzkB1KePs8u(8K>f@c%PY|Ff6=Umtk1(q0L`xT~20=XTdP9%Yqy zPJCZ?58E8T`=HniUC^YUJsNO$i$dFp;gR({*4?44)H{G;P->rSlc0L+5R^qT^*HA+&N-&GR z0jpiRfOK|RLJ&<==P~b?{jM9i?=#N4GI6$h^g++fRH7hYJ}TTB|2|W9P2}GA^N7ge z4Q6Cu;QAqkpM`pt)_{djW_I2-atA{;L8FkP>g7O#rILf7UqXf3$y+%A9y+M6#lC%C z3)(#McrM+{r_{WZS|V8_2VIwAjZb166n8+0_E^Ci?+K)xoal_tZqv@ELf5`YlJ8IN zDIDZ%Q89euPp@@?;q1W1pr&xr%Kw3ONed!F+!#y z-grC&0pgLqrG&bTo;QOpoj{Z-74R+C!P6EgQT0$#!LJmzriQg1)qP81Jnaa|HSS}3 zG&-G#LyV&bYi1okXm8l_bS|>Sw0sq^!CU8_QYIZxe+lcs?v(mcR$ z^hNmZ5n#Gi%vej2CtsEDW%>GEt~j0xEN{Fj6RTzAjU-n>S+L{m7nZxMv&LwP<^-SX z&-qNlrE&~5hX!a=*;TMLnwNq7a!V$LoTIiG6259zx>0bq+ncHcZFqEyRVl}sET&HWPiVf;#Yc3it*t$1%yrP*p@3@3j zdY;8py4YP=^yD&CIQ_%v%o+wbn)iS;@{@&->8!3;w-E+e!?|)S@ix)A!X3}`x7cxsDCbX_t+EtxLrTOcX^HN z;ZjPeKeVd+r_<}IkD;OpE(g*qo!tI>Lk$Xo&D7HPg`{moh~k4?cgdqIzxnq#9EQF1T4!IU{3O;EaXh4B2Ufj5J-Ew1 zRDRr4CzeLg=7Eow-3ss=X!y)v=Q4jQ%!tiO0~7x*R6 z&axSMDi@YQY`N`Gmy^K)q%q7DnTJFjCxBJ^3G9t*sFpI;#)0-T)fc>|Fm<(+S8?{| zgmHolsCJDM>Ckpa!?I^`Q$&k2x)?k(O-smNwlv%^C*GWM%=D5Qt??ruvo-$mqGLNWfP=w484zrL8L}*BvKR7G#=!aA;mGZ3g*6 zGVn($Kte_W0dlT7v6q^eS_6vdTxq9w!JGdQc8YpyKfqSd$}}E|8~xi*J^0Y++uc)I zSD|`<$Z`!-RBwx7EVqTNkapdu@3^Es;Nxnvu0i{%n{-9g@xq3)!&JZpSC ze9S#keGW57Tg|vBq@f@;cMN(t&gym`QI9Ti?-0(~hkTRib=--ha8(w{XVZdOAF^ zL!Y?1eQm!T(RTv7t`sjY`{pA!wtGM@vke_vXIt~Msk>l1P#GBmA6P8fdtSSU|M0ZK zse7|_J_ocyM^d=EPb0uoU+&5SCa5B`urT!Y{ca6C!EY-+y0Mee4*4|=2wdUOO%3|#Q2h!YS5(Ehka@KA5cC{Gm;w|6ihAB znYtcP7EY#WL}nQ#tk>(jbQ{S8a+sc4J$knABKmS=%?0b1+x<>foRZhy8Wk z1PYC4U)EoT+nw)`aSV!-$;>cjhe9qd#rX+unx1rYV&;<3=L4S|WI?n3AL@RnF#ww| zqOklCKmkE98UgN77}r~ddw&MEKe4SV{I8As8$4n(13rX(MaKOjfJp!OP4NE@eACkl zbjp0pk(r-Zat0gGE_sfy_+M!M#0BZ+HjW)*ckBR ze3j16RtrpjoGxC8KhZt|$?hMk%Y4l5`$r9Skr*i zMJ~W$Sd7i1X8&L08wNk0J{-{S=YBb?0L-_W`;AJ|KR1mD3Gj3S$>IM}K`IOsFf@Je zaq7P&&HYVl0K(%wdQ3Jt)PL?5=bx74T<-P9zZLrWo7f!zfngfzRr^?P<@GY4e9wLh zGMW%Le#GvvyhH#qDk+Jop-m;0cnPg-PuIS5@=T{~qm3t?ue^H2oHZYu+=+;`ysTx* zW_kJroxC=O`6XWt9cd7RyfIcif5Xce4h){fN$j`k54>)OtRBkPbaEPkUqPo z0+J!tifLqfMx0zc0*h|V3!HeYt;I!#00nl-3{)j0u>rBCaHXo_Ii=FU zvmY&{=1sazaJmY4Go@M=Y?RM;LY#Hv1*z@?G#f#Ce||5_Ht9eyCeCsT;< zcjg5*CUSSQ&6Tb!#C^xz$#g3l8_qxi#e=TH^O$L&+2V@HR3P}Ap52-bI=vA>@jSUq z%_R?dxTu{lOif`O>6^WAr1r-;1$`~WEj1kZ^RZVqUAD~VmL84Z6gD?cjykYT{&T3@vHHnqN``mpLJbAs2!zn^l~1*C z+nl$Gc~jr>FN^5Vr#fkHq z)1fumJ@^%^Vgo^Con`Lks#iiC{chR#G!hj@;J(V>_yL=tk zcFKqK% z;`QksPbiN1a;6}Q@^bDv>^6v&9(E8E3_GhbYXt8(Hp5zmY|>b*Kb{q{`Q;S!oL^n_ z1=Fcdu(V$)N>McN59X>2a(`1r<(%v(Yre$5Y0zGda-&-w!SqsTABQi4;SS0_tBSzq z>!wB5H*6y6dkKw+Jnx);MZ?M!Y6jKmpeQk~l4FN6oq!Q&R0U3rp;q`pt9??qqEt=+ zEoxoT676QJ1TOfBawb`G0$M+;O za?^fG>Zl5Vaw=%!05~iY|Gwtz`@#F>3g-u!`47~6Zr$UHW9}^AT0@B-Vmv!5@z!O{ zm>th!m00Az-YC`Zyu=vGj-hH|>l$`JUK#7D6ppzA(%@40M9Ie4`@wNIr!h(BFZELB zQ5_o_v?@Uc#pR2o4Zd)He^N;8PH49pZpy=2+}qW@D&VX(I`9`l92*=P?e&s;_O%X@sXq{Bc_fo!uW9ZIzaidbUJ`9m!*OD5Irg-#t zTe-!vENbpSClB`S%0;0#%ErlrCY7^wZRXzD^s+#Tz(e;zbG_&EAT} zylVU_fbjR9aT1;aDAbr#6bis4K5pl?TCqh z19%KmdIUIt&yUYVhWZK7jrE$)yV$jEy5Y{RYPmJprFYv{X!&E_k~O=&H!w1DEa2^TBcXgwshkE=Rw z!ZJCF_+CoB{&1KaHO2vBt>OX#Uk?aNJqtUU*)Lli@pN$S7H-SYwKd9N3!7Cjt)~$0 z>Xe`-qupn>Eu{?lm0QET}kc~FJX!OS}tSO=2o z%KZpw7(+4s-VV%L3}s)79Sa%wiFW)lLmVB2nI*U1XB1iv zwtdDlb1l!g9Ts-D?&P}KtT2-z#)g@~qB{GrUA=T&8;U)PLt|Uf9CT9g;m%w_NF|zt z`-NNm@@aF*&|*CG#b#Mgse@wMtIl_rIkgo0UrOHDu8lu`hdIGapgp%j(Yl>-K*me z2B~b&{oB)?;7KGlDfHc4=ld3-_HC`J^9Vd~dU{e>Dqtd@h+w&ZV3BjtSeB{L9jT_0 zes=tl!*7y^lmi?-Jw@@Vy?Wfyi-#;-Cf0S$$$qu4AS z7wtLsl?u0@v|;0vFC!i^DtK)yF{2H%gr5qC;zoS>dZK(b1E5oiF`8h0H~&qjCwZ{8 zyjNrA<=Wg^+MT{qytOz{*2$};lK!w=&CK{H(cC2*2mWLJgvXxTG!>RGJ%5F8XeYbx z5{NMDc|}V_VfLhgcMaXrj&h?`FF^!-M9pDILmBgc+z`S9Ge~$rqHhoR*WYaoN~=Bf zSw~`8DbD8BLFCfKLr8;Gok4V+YsP`~i*A!Ag`%;f#(GRC?lPvmz6gdX?Bg9UpqO`a z4ApK}FjeLmGuV#&i^NyzMOxS;lLJ9f7e!xAd1JQ!xa0!*n7*U zxVoiXG|M?o@YN)&G%G2&>Wfh#1Xf7f>eSM8cVkfjZih(+ zYu5Um zWh0JdqPcoEXyt8v+>Ayr?^$ZS$CT=r`a&CIF-SAnd@H7M$8B=Qo5v_w>2SQu5RU6l zRL6UMlFQ}Q6sWeLFEX=g50*~fMqTE4qri&SaZ5@Fc4EJIdx~%UQoo*xW++)&G8Xe- zRcj_%92zto9%Kjvlan&@2UxIs6Mo%HDw3_4N8cS|INklfozT_*dZZ+1!da)U<2yLeQy#JO8rwM zF1#Rttm8VztkP(aEs5#Kz7Fb0Mm5t>hL&1j{6$li9sI@m1PIt{W%oSRh&*M=F0RP*Iazw(qF z%&oNS^k))$Egq;@no^&9Cuo}NVVS$PnYt&a}9_0Yo;B-%ck;u1h*MW#SbrP_6vQ0w@& zVYbf=1@Y+Y*c3e_B~jnO{24(MgZJjj@dW&_-VQ7B7#>hM{zj?uu;t=B&)Q5|Ur+^! z?W~LHW;KM%0yO=4mr%Y*B)pjsNDn9;ImSkh}cfW2ZWt!jDeb z>HrW3o{%~dWoYJSwS>th*w>Sk4e&P60f8-Q@}m0T$$lAG<*0YNH#^WH2X6RjS$ z#@)C4lQ~Fmf_Xa=In=9F<+s4u*#!@kaIbeHS#N)P$o8P^{NNR0%h?rH*+ONx-|2EZ zGR4sWnFeZ7p^}^*L;u<3et#YRiH_JtYv_@ag@#F7qHK)Uw>cu61{Z8W9V;nP?o6iO z0nTPpnHFghKhsfGK8p%2WFril2>cHtZhI-)w*bHY4Oz^3C`$8ORo_C9=Y7FhMG)CT2eLxJ}oA_aU{j| zr9>YYo-+_vk%Q2G7#aE&#+Zt+x*>)gt380|ymc#rN88 z1~h10zgMYu{Ur3lth{U#zW2gm^D2jzsov8Fvup7+kZFGFXgQPKW-&bVhYsNRuwO1- z?hO`PyN_(x=v?;PfEdb<;jh2-oKJAu94zm^0r@jQ7rt3Nbt5N%f}T@st5n^uijo*3K^c)S(wgv|W#&5`9sN(JUO`4z=yTtFcUP-d7s_ie3iF(3l#@~QuVDMA-#wP7PW0Wk7>b|Y`LCp^1?0Zz+yThYb zW2e`J?-mPFby9{333OeH+Qx=x)Iw|743GfSuRqw-uF6g!*FiuTbHSlcm80&@#`m09(DA(l)CMs>Hw6p3_L%WU+vPNc=f+5wG zwFqj79^m5#vy6f*tuZAn66i&261Bl=?@B4uDKwjT$C9L!{lDQFtg@6`qt&IuLGk6f zjDHmXNvRzX><%kL;3x`lkri+0=lv0SO4bh5RZ#JKsmjFr)BEQC?FH~ZRA`nx>uIR} zM8RvogHKDo-*)&Ls+6D3!0X4$eDx<< zV-(e{m!j-rE&kAn1-%I2Y+*ty9V<Gqj8ChzYn%rYRDR*)EJdbi7Nd3VkZnI`Lqg z?Hg(>@5=t26`tJuL$LUoZ;es#Lw}=Xs-^mSaa7tRFP7@2>X+?G)wU}Q{kDeVR$^Jw z6r~nVuP~lq$V#oL6R_C08{{VX_g7M)Smt>f4~0@>xcELM8qZ7f+edjG4aawsSAiXq;k$tKxH?qoJu6V= ziC7L14-D~G4lggZXBp8{KpF{R50%ZI)7NXWG|XW64@R!qr``)PV;tH;YZP^*z(Rhy5L@M2F+T z2_yNtKmXqe4sq=Z)`xk2R&((;EBoJw^^4k!z3o5gJ6^8G)PKRokzr_W{w1yc$M?Kk ziSUn4|BU_r-z)!Xd;gDjs6!YYO_eS>CSGq9p#LA?|Eq6*(IRK>*bL~1AMSPAhO{{T zg9#+cQF5Ie14^0&)kpp$T%8qFEBxmZtNTmzsIEKm?>gAOh!RDLzo>{Pg?R7(fn|w* zv9h`?Bh$Y@F#q*ie)sepK@V)_MaVp;U;E|LCTn!4m%uhbnVE(=pim`E-9Y8Lc)a9j zr^nAeVPXCC2CkI6ymwU4fvx|1uZe%P3`EBx`*=#b!f!{(|Cro0KaE+|{iLL+9Qx8` z_Lk@TM+T5GHSNcga;9U5OM}${84f>FPw?pS*?K&zyS_|^^>mE+KeJJGMSuO(yaPF& zt6sfsiR_L++)f{I^OXTyWJ0>QxG(P+Xb={8kxng%!n?AK@bK_EM|+Xd)5YVG!#1~6 zL$eS(jSu7m>~9C`y>2ULG~2&BvgmXi|9|`u?VoF|P&@w94b6sky3z5*{uPN#@LnYE z1^6`ljoooLFTQ0~&?Vn`e)vE~vA?r#`f=cOH9V2h9(BL`#|GjJX1|)bGOANZDd^|j zeY>00wUFf`n(2Q~9=|-q4KshyRl+m#!(=y?=s_}TaJgweC6-xRC-{3P?vTVjr#ca{ zj+MX0JoPI<>|w9Dh&C_}22bkwbRF$v=h0B-bx9n48veW2TG zO91;UL}7Dg(Hb>)mnyT0$E!tPV{Z>RXA%Jdu|2(tnFPQ^m|SZ4aW64vTfEW*h=Q24 zf=6_5!`nLDE(gsvrw4BM%QgZ3`3{EvQcSz68hDsu@UNsQDeE#?MKyLPCjGh;IXY{P zGNa~Ox2sI+*x5Gohrs>o&nXQ$@mvl_18%RcZ$aXC_dP7c(h>eYn zmn>t?dB+@zh6c#;RdBLgM;jM_b-8PI5C0FxE%HL6mMEoN9wT0!cz$RTAPu`*ar~Gq znXgW)Xf!0Utj?~-Zh`3!AF!z!jh@Oby)jJG*P@I^6kykr8j#whjMwa$R~m%)a*kr6Zn%L#x0`Usc>d9ldg=z+jj>jB2vf>9MeCHYM4i$81286 z8%!ufzeDuh9_odE85oc*)}WJDV`*i@l8k)p7+rm(a7UF7G-gZ6OKome5>NT+Bj4b; zV6#wn#ACf!T^5?~3G~m7;=ea)L-{X~IOre;cJn5c+FWhx*q`9UbGv7dV_Qj64Fv!v-k+$6PtNupg5{#Avj| z>mb){Q*+V{Yy|~2JgMjrG4bEKd-y{d?C2W|a1Hj@tK*bf_~Su?|9uz#djQ zojwS^BWXXVil@h$m`UH3zS+!U|0XM*(fDxNH6D%Ndn1dtpVr&x|E3{OY)DoH`)(;u zsV)3Z@HA1;LZTZoT$?`oXbqds^PL$=neo5n>Hm*k@5uX0q~&*z`ITo|*A`A1;FOpc ze(d1LIN7ER^591)KP-ul@PE6&iC3P7v{2`RTYoGmtSm0L#B=jCgh23fa#A>^z1fB> zeV?3J*xO0QSAwRl4ZC?A!*pcjZDt!MvA?=--3QJZu*`J5O^Wlv!l@h+Vk{v8(Qhq3 zgshKIf&$FCssGbsX2<)NbU%|AZw1jbK~nZY}(>J|1=XRu#s4VQ?I%!l!HxE*0BL*8M?A; zN@g5g3h$#i%ss$?|DL)7p}f5O#oFP#_*1DjcHsj$$%+c5?Wt|qj9el8-|q_ZYNCYB zNAFHQB~XE9koNiE?W`&y>3`P^azpRP%%+s99M|8P>=S0|?7zlqaD#`eeMH31QF24a z<4qGE0#*%-jR+UTGDFx5I-C*=Q^QtD)C$3fgHuek(FK1q(!)q$adp-Hg5)5%VSq}^T;5BDD-InBz0|-`J(kAXkNZ~Td z)@g-KG@VsAS$u_Y6CZE4+@V8^w@E?5z^GJ6jq8VwoVRGZ0k48_?vhQvGmPwgGm(5X zsz`Oy)(57^VC#({sV@W-F6}YG5N1#Ao;TYH1NsR6 zjBDSn*ZEkefgXs`WSKKGmOlKg&Fg4tHV+Z$4ryTUOUQK5;=tID1l;c!=uXh(igH{W zL1*^?t>Y%g8@!o!JS$F239xhfAOd}zf(yY%#NKn*(Ct0 zh{IO%7GA2zb_Q@>i@!AAvC_`cN)N(gFJ#xa0DiMaR`u zF|IQkJZHrD<8;)-(3$jhT-e_6@=n2P+?|&;i_BlBcVPVOX`!DmL@YNo&y=TXZsL7u zO-cKimsjdRT8Xj$m#41?WRc_a^vqAEknNF=vzx_cDf9@We%FsIp3Q{VhK%8GTJ0Ny|hbeiriBFX$E=7@$MlfvXfK!M9 zH4?NVlbpvs#fal~V(Vw%mRq~Ig!Q}VmDc{i_3iwl>)VSf-!q6Oh57iC-Ifynr$4)4 zSaBeZ?N88g;Cqi7i=&5~h^mT5%vgxIv4CssaBna+ESO=EZggwT)go@}?Sfw4AFf)Mg!YQ5VF(3|fKXdTG#W|TjL?qy-4go-Eg=6y^RGN3 zLYafoxn zF)=Av^;?epr+)}j>{Q30hf!~hk+<)K`|s))9dN4bCdWUYOd_+%H*CVg`(qJz{hNzu zFhykbVV&ceo=xVOSzOFS8VC(#Hi&oPiR&6xd=0!S4ope$(#w|OsBLMZqx$098M*NJ zvHs}QTxkydTcBv+T-_lq+bX!+v<6B)P5yvNsg>)`U);c6;Fr*Ck>G z0YC)Oa{0q0_=%zy-BrL0mr{ks@y!$Xmu8Dl&5&ii?`@0T4_Uy<*(m>GlBObnh!W|- zpb~jNwS499TwNt1N5*#a(_PW0r29ZYPhTnE?rSK=ltF3)IlDw6F4} z$uyZXUs)*HUumg~pwcO%b~Tn-Yke4^pXLbrw6h0y`y8S+zyqpv&PnDD6OMe3*Q+mQ zZ`>|u?s?6uJeJ$JQcvdJC9=V0F#6h@JoJ$x#6*f+$Su%G+-Eyoa)_fVl}<$f@U}wv z&T_A=t>~@0lo8jV-x*eOHaq~AwkJ+FL?fF@+{{yja z@A(hQ@xVpT>Au-kFBsaJPk6JvI&PMY;?sSB-Qq{)P!OLIdnzgMa9@jT(zn%mkhApt z_C&#WE%*8J(>9%^-n$@_Wp#m}g9!~-Pa+|H%}94WlxMRafn6H|nbTtGbvgSg$NU0L zR0QNl15|x*psLL+ld1A@I%2_qT8_UK2}(n3_e7?on5Dj4cDiC={p3#QGj|xPWDLh* zH$LEF{zs4e-SDVvS5th|!ecYyd%e~Pys>5v%sfE907N3~z}XfZ!jBqL?R9 zM|CmU=NYJ|coa*7VbJ-W;fq(vwUA(x5V5X)?e3xmn_Ej2|2k{Oa8eiCB_38_*lGl6 z+Q2}CuXh#h{Hi?(a>&uN6wMm~7K%3WRc!31b-jkkQ9_?3(qZ8h4oW0PUs;K!TCRp~ zmyqMORmo`275;l$4D0-*2|R_zqMDrMguJxHJ6xZ8-|IER%HqQ%80?!V4kcXIJ8?Lo z2;%pl?4L7LtG0g3X`d)qW~3P$S=l^jFE3pSJd|EJSC*+;!L%opL(Rij(5U>k!hXI+ z%ckTQAPpWtc%1)oQ=a0jFh+(0k9Z5Y8Pl_>XuZDVrJG;tU3-uWWPTZXy*rft3sLgN zzpjni4-D@m z?r^2=K0%yM3sDHuaP(x!JCrYafva_k4htTPkptKD@+Tbj4bKx&*iYflzs7sC+rP(x zuN(|4E)OeS3QHy}9>iPPy}3+Tw4ham+OYgmQrzXG(E-~GKXO7ZEFjA>Qs2#(xI9$p z9SJZouU3)PrWIP}1m|P7FIKqRO0nAK4d=T4reNzC$7^3Bs|;jJm%3c)dA_zn_)!C1 z8zC&r{;5Hqezg%>hl%}T127#NQ|08@SV}s5Ap7OZZjgdnEa8{SgIOeAxDaSNY>S^+ zS0&U>EU2OMqd`3auq(DkL+TZ4+>{g7Onsf3jRv3WI}SV~oF(mgLstDYf)@C4pZ;by zFTalvUc6W~BENDJp*|9;6Q6+q{YKOpYT-GsIXzl4z;M55*Ued^u&lL&U~3Y|N@>kP zxDc{dmUPxbb&MHB!w|#bSkByUMWaIn;aZ-{G68aZaUB#3ZF$R$M~RM45nA&bx>3G* z&K~~tGuTt09=H~Xxh(xbmnx!psPoFrVbXR7KeG2~e~8H4p`HvEHTu=Ru8uzv!ah&z zjfiuldSPwe=+U#R2>71Z*%2-F)p{XV1D`b`K2#GIv^AmCg}BJP08&^8i3m40WF1Y; zM^!ct+`P3y6x;m~BA#c!c~%L12Q8YA@cA4Ss9%!Ps!6G@E0^F}tAr68=ra}5tnzMU z{)k|r@rJv`fXkTtXU+G(hB&ZL{x5+F5nBCZ@GRMqRFUtAOh)%S^f{}AMO9uWDwbI> zKjcnAJudIMdxv55bjp$M?74kSbP-1KOHM}mTiIo5;HTCciuK~z@oisc)R^3hkev{` z_5EvntF8ndyZ$<$sPx4cjA!KWuFkyWx)clc*o&}Gn!fK#_Vd+ymYJh_d_J&aFW0oz2%R_o;`-aN7U=M`ljbvVFDVqKa)hZQ)jUPl?~s|ZK~tlj$2wgLux`$`L!3Ggq7n`7 z4p$p$HNJ|7e3u)KoaVeeeoKFt;g?-wBBeuZF;n=V9CobsE~}*b(9`e&NMLxRjv8<^ z-W7JA>cZ5s$`f@7?<0m#Z4Whmk%To8$KHz)|EkD!>JAcN|7biav7qco>oFW!bydRk zF*(W+Wnrk=4wq~BOnFc+^Wrsz`DH?|_VLmO$0FS@6klAQceUEu*s>3gdHT(p>Rs8y zY%rT&fp0uFuEQR(x8iwn!ayJ3UfxvyvOe3qwxa<-)!V^`2vFJT@JozbaI9g1b)$8n zc35~^y}n^d1Y#xoOD~$kDvaaBW{Q&~*XR=vnUac1!1ie`$Oe13(OAuV z`TF(H@*dSm_{F5nj`@!3NjL;)m4=?mrl4LzIz(_Sg@Vu3G78T7d=e~Sb~ys2KPebQ zeNQ_6yE{+ky7N#hWUHYjAr%me!w3Xs&bl{o6FH311&W>@-+=wrg6H*8fc4uaXDV0 z>8xTkSDB#L_2kZMJA6}ITpW`C3L3$3pHgVrVX_ld*U8J1;=>^=DlhN*4ddfduhaGU z(K?tV45 z)(lW{`Wpy2SUJ5a>%?6R#)w%FZ)EK-pn$<44=k1FPv^TaP3U_+e0Y3tpL_!9UOMDn z>$EqKxQaReH-sQwgzeAsvf)y*I_6;zbOw4y=D1lM(^n}@@g zPRSFzD-jZ1c@NTbWgB%tNs}w3bHIdQ)lb>n=x40tTI^5T6<%9`8++cT?th+C9Hs+Wq|9aWIHet^p5K ztcwTb%r5p`IKXA9kaXVHAu3oOI5==oOvGg6O+%RQZ?9|uq_v)AWX715>n#MPB2_ur~*AFc2biUdnm zN~o8R@vE|SHcF%Z&fENP0IbjrkV_#RGn2NxESr}>07&cr5N{rL_Wk_WF{7VuAJ%)4 zF2*n9vMI`2mFjh$o#~z*FN6YzoJs&Ur#a0qJ+JM<=Bm%SR>$qWs81#JMo-AR6Ro~* za{?CvD`(B%NwX=Q)14bu2_LB7_i>&|yIvoRclyCLI8Ku@U=_2w6~B;z)yRJ8cdkdi z0-@uP&2Wi89}-YwmdKNg25XNI@T)RUDjl2E!S5Xd#on)aB6Fzwc#^`k^6)y_!A7EN z>)hYS0`u*d>Gam(4i$=U+=8rmm}6cMHAQsImkUfgE@p1WQi4(}cY9hSzcdgH?+Uze z4`?3J(QY<8uhS3>rIMD$LSuW!G;iJ_=t#R)u@)>sKppn^4zCf|q|MU0Pbu4`so{SY zTaGrp6E5bk#?RmU5EF0__QmL`noEK)%a-nY$ zc>K(ibxrEdK5N-V2>hkZ!Ixq-(|?IODaqjkEKQf|Cs>2i1w1LmU_AUU;Yf$nSIWVR zTe@CdZR_X@1FgV|(6MjJADpX|nQ?7QoEJ8mzK|AGh=^%{5F3`HGbM(0B%HldIBVlK z{F&ch_?wu2Xdh?VU8zp?aA(>hVAD*X5t^w{j>`OoMN$#nJ;+g`9K+f-QeQVPdi2BT zt|}b_is+VbF-}a?QrFh@bH5<0Tkz7B5z)})?j#8nQZ$9DmcnJjB0U^nG8x`A=+~)WK6*BhwBJ*4A=yX>I zlD1is-;b9VmkDdoCV&TuPSRx(dU{AM@zRuRY?O+${4kvRFnqaxOINeJr)TbiI z@`uiPL9J*mooY)fwz-pq561d_T1=N|v5PSr`&S#!$!cMzy8TqV62~9)eJzT>L5?c} z8#POFt?VSz8sP4@5{VpOv%~RRQhF&n`lG)cp-J9N4}Q?1n}ZlCdAd%3Li#)|KHvQI z>?}ir{o!Ivyu|ozT8I0asApR^7ngUT%?0>b`vdO>Shf8Ba3pNh4~kSl%jPpv zoIEUH^$x`-{~X75oKa`O6_`ZY z@}?V0zYuBF2z$%x&kExwLhTPWD|X$^zltkU5hXL@-~7~Ho0LX&&JH=NAS5&lL8Pdz z-aUQl<|<6^w&cQLm5g`HJQNeE^cA`uAh|5wdE>h(526C*$c_#OYbUhCC}*2^qh$Po zct2!~gUqre+WqL6O5!@Sc&lq_VwM>U#`vTqP~1NW&&4>9vaSc=Y5C=&+cY_kN{E|! zmaaD-F==pGKYy9z*RJ-(jC&0Z#>sUOJ9Mm(|Fx5Y~y;rX65RzplH!X zk}{hO?iy}rt$q^>VC|&@>X>lb-mOCIC9;|&s!7_MU_d*NXg<4VRP;$&7N<+sPw}sn zjHpS$%Ffi3_}o2jZ65v?XQ7OA8eJn?* zbbV>*tNQ~4Jr?)4frpM%)`jVJCKvPTDl&JnvLw2g#;fLNuGyj1iiDgV(yeNP=6NWN zcEnJo4Kza|>Wb)FOmJ9_aa_y~W~a}0cv(7Z`EQQVZ)a7|tF{ceY2|pA(fj_O(`GAc z=aX);eeWGn)OliS!-)-X>Kfx3K08ohGi+)>s!>%pl}goA)^U#s`e_6GzIKnc&R@Hd zc<$k8R`929aw|rlI|%>Q5MZv8OH44v)R`(a{K<6^ZRA0CJcjQW=@lTuVL-_26{NwG zzB*_;R-xqw@7iI_rv}@fp5w-?J<2aIFomm?MIoWj?|*V+q{i?v2`zpytgWO%Pk+4q znw28$Z$@p~=J@uhRa@l=bf{Afi;H)_IgSIB?b#aow0tU$NGz#|gcC3G#G(@p3Z$19 z!X~3N2!L%Y{4CrO@PlunofS}3qNA+Pr$+OqSWgC=Vu^Dvj!ac767LJkk5orZr>x6W z^$3@pNc1^fng(bKWZ1~VP@W$=3zweYq~|L->Z7nTkL|dNmve_nm;fQU)@0U+X*6ar zfcJW9l3FYVnkUuEn?Emezt(V{`+5k1(pBnxfecvlaf84+;u$s4Z$mM}s*vZ`AMSUv zHy)2-v_5K&@yXoi<5aqPGPHS)E1Y&dlqB<;g1eX;&6*cYkM8uWSrlqgUYCA`#rL|M z{{b~z?U8&u`)%(h$YNX?i|;)vIXY@fddkRj1Fu(fo-kF2|J`BzviS>L!I^Y{X@pe0 zgdj6hT6Ldnk6GxKuVcH39p*#?lEOu~4zD2h=Sc&hOyDOEUa&k5S-zQR;Mm((mY|TG zMJvKFPaO;z3I;g}P2(Kg^&ve0G!Y*REb51^=m>EU(VZ zHp+q!Ewuk}9fs&CbgkWUl-x}I+eiF;jnY%}@ILI8s?Dw5w*RNRPJ(Q|Yx}(wP$DGZ zlI#ZQaf=Qt(p;8mdF}gAZ1>eT3q?@-8k}SQ;aPoFTX7%0J&mL? zoZU9)Dr=9>*JMA&0>KDh(7d{t-0=i27zsbt}0^E3dXwt)UA>G z2VGqnAZO_km@EXFyyNYnb^@(m)fcUE9fNgocthuN+SVr=EIxl#%<#ImDUULK&sKiv zEcs>;br!KN^Mv7q<5Tt2_1dtn;vZrb#p|_(5}KFJ9aK+NgTu*Z&-X?qEYo<_+@1;% zOz668nq~c?J;)y7LQ_}X==d6da7fW*i->S2(U~pM&+J&JMMF7J$0Tcq_;`*cBJ~#N zfoh`r*FW(?Wnv7Iz-3b&@jS)#JbEUfJ2^vX)jx?LY`+yUO{6kL<*NFp3AAiGCV8Uv zuJ`Ar+erFfO-~5Y8Sd)hf*PI_>HaAr`h1P3@9T}~bGA`&+6sH*_PNcWFkWv{9syk_ z$hK~Jgp=}aEnsJY1X{qB_-mf7`0DL2pWGk-FeK8Kh~Mqan7~cy&(^O+U5+;^jBxCR zGjawUd3x6yG3(EmLO##2NiWR0s^lFwf5R*+x36;Wc8m`F^!x+%_8m&Q--#P()C#+< zzjS;xo#>hg#r+LLa(cJCtQbe9)?@*2JCytVa|6Vg|L%tOwr}RiZSTrpV`St@iw9J$ z;Q@+M7)!aT0pTaK%6P83ydceAc*n#VGV7bd_ygMo*7uKe1|{^yc!pUhsN6cGP&k-#Bj%0N$Mt z{E_MNu;+>67a-#xZz17$cHz@ceL^}QCzkPMYlquzHX8_oVo~}=KUfX&(@SMqAo}N^ z$XRX2T|NKtZWjf6+KRNbSX7Jq8$}1)(qILv^2TD=VC58Ne}gwU2T&`!U>dbscONfQ zePE={$XaHjQFPH<4`mRnn~l$+v1qDg;h%6S}%ytIZ^uEq;k)iNNK{kqy>VkW#Bk{S$U74}>SCe3)z%>izk)+irav z(%&j{??*__w;j&0lV@gDZ~0lL-nw_3!fj&N*#yD+ZdW679t?dp9id!oi#QY@5^8#I zad*bITXoL(suqbnK>b{|EB3g69xe6bU_9&9WJJb0KfZonBpC`XQi>I7xN;Mp*%0qw zy+;e#=?-Gs&Yrc5_*qR)JH>k7oQ*r``YF5#&&J)|`ZwTNN1eu|6@d33%em}1mu>q9 zj4?6w&H_8VQ3JuG!!;cgMDj8(>V!bo`^abKW}pXU8it1!?Y%#KyIvF^&|cN;U_Yzp z+kNAEgTvYmeHo~{1=@^oUb2%-aUppbHJ(DPcuOa1;DFF3D6Wxe@w)Q0P%~7=d{sfr zob{34{dFKkd#qVUzh~}^ASa2dp1`}4n%!5vzoN;CN@9A2PfWZG(?m;X+-M(0tpG6H z2+`){L6C8i12at?rlqANwXlomxx+yFOpMB1>#V7>^ZDGcM;RT{uoFpYsdjNmS&syC z_}EaFI1jY&e>`~ zPypnqr`8>-yvbS!azAF1yQ>?>tQiG}rtZ&w=K;GDjkl;f2j7ePs7i zBA^(r5rWMb%aS_0K6{d){~1FK#npps&1wAPdzgue<778D*5PW4H_70mth(O-aHX~_ z>7!3W@Ax+a*5%-Np0F>6DYv|26 z%YMds;(d%vQ-bdO1=Nz)ANBRBSV*a-lPSJ;I$l(X7{;Tg*<5ypf}%7tSYd^|(5RHvxf^3SLWS*oyIb#^xanaSC&vT#xq=eP=9hff8BY z-xHo4OwFHefOmg?oOMn2!r;Lg@6A_}X>GbA%h_Kqm(i#KcAiH7-U+eKc)9q;s#N#d zTp7cH9VyW7j?C6}!A6CMAnsONNP&!#5N7&0_EWOf7c$onT^dJp0%j6&Ub>NsIyJ+b z5V4GhV|=R1E|$q0Y_Y*^A{-;O;nz=qNlQmq7jiV$?F5JIJlM;Fa7+6aBV1i&Y9(mdRpqzUt z%2Ld!E4c)Ry`F?fbA6A*TRjr+o_T3T2f zV$N@(+;}NO?lSa)0#LR~3?72 zfp(qMF9MQVdV8JVRZpy6)<0j7E!4=IpPSzLR6~kwW9L=x-mm(b7o911SE!D0Jv_SO z=z5%UWsbAl4tvkLm2qF@0Ozd_`n6Sxt{&E0gq|-Ozs;x*g>t$dwU*t7-UbmCm-)D+ zrRH#6Pu_iZa)_D6i;gPJ29S6@Ps z7if;71bf2@=A+S5zB?`i!zSg=g{ug@JmZt8)aSq7*8|y} zAJ|R>(joq)PvP63U$4ApIFl!)+<>eg)IOBl(@z5n1b?=X%qTq7?OwRqhDJU0 zUbOQ+PW@2awZ4qDN#$*1Pk3BODw&`nBQx%$cyqBIWq)Jc7W7&a)?2HXQ{ceCw6^}L zRXl@_bU%V0x7A}Wr~LUfBNmyki;E|8G!cIn|6n2O!{Lqr%F<9#07KCqhcVwTQ%f`7 zy)6XRboIi(+?Ij=PE^Vpz!P&eJ?N~9*0;DnTUBorOA`>M%EDD=@@X)IQK!3|7fDXC zfLFrj^*zbv_4$x|Z$&N>TAEm3J!%=v8eX2;`E2Wf`XOtbPI_uVMtB44Wiu;G)R|Z7 z4sNiF(;^+$?Q5w!&Bp>E_pfGz$hycBo) zn3rr%`SSZ4sAK>3j^%O?=lOWO=i8mM1|b+FPjVT@Ov*wzQ@FzuY&lYQTD+zrPp!hp z*{OV%5VYqDezGRg_qHR3thn%na@22R(d`fabU2%4(<8?}(0&@Kabq;LH*d?^Vn0T2 zaE}u{`^_q)6I?+4$aO!!t-^aoVlG5%ki^|Kd01x`v`u9#1|pU2XG!P}avOH-qSWF5 zwcTAa9>lNH5I%X{h<=S?J)riju-Pd~baq(NOz;|{`0e|io~fd%uR3j^<;2*l!H3D^ zB{@#bf{LOWscU&F%Az?~j)(`jamIcmC_+RZnH>DyPmHh~AsJ)hQO0Cy<%x z0PkGL*^z-zpnTew!Mj<^uX!_Ti5q90bDL`!vi;%ff^UkK_SyL_n=3qkO>qFDr1Oh? z#S4S=&hckK6l@PT&W%tu7@N;QYaY8QU_>u!^Wq2^?Kt`zjM56wYHT?1^@(C?)ldoc zYDtU4QJjifdXRqHsVBM}ik&{YEm1i;ITOguAk596kQBN&tI06mkbY>S*!GZF=$Lb} z_Q6yTyiV50ryyf(A0?zlIPdpGAAh7qSZ_4@lJ0fdF0kIB1m3X1-7QPvEpj_`-fqL2ZIh9a-txbOe#!aX4 ztHu??!OG-COpTcMRDyEXP*1sP&3H0}CCgK^Eb!E{sJfIVSUj9%@HQnLG#X!QHaKhJ zHmSK^Ak6M!sy631Akut<0ZUk}L8GG3jOSP)6hx98)gX)n7CY>VO=!svMY zl8Y^8z;Yt$P0t0X|0My~Frlk-?$^h20<; zvo~2Wt4t}jfrDXlL9?=R^NfNsuP>vK(cI9_Te~lK1&@R-Z zFuLOxrJ|O)pUAlb;!}ymA5{Z2zbBMo#l`TxsC?T;_Zg%4NPz`4Mj3zaqPD|V^-y}7 zIX7=PGPQb#3*{BqsqCYpW3N)(v~qk3dwMb%bZb895tm8&itolPuUiv*3*bN9?_F36 zH!Ca3u||u^uV2-LEv!Vbk?2_)us={FHL~A7hVP9Xxy>&>?|pmp*kk8~%l%W-map9g zWmIGUngCaeh}oX(X(0xcdCu6eIsZkUer84Vx$O2l9b&p@Im%jpJWoO#h#VG;Y>HLo z)nVc>{UMt&pQ$DHV7z z&t6Tk85jI9h>SPZUB~TgeM7@P*zq;N=iV*PbK2JYNq#zQb}Y!^Pa4!Nt3#tS z4Y0+!ITOiz%nm&R@__5fxrN5!6Ss4=u-%i70;jzd2BGC|$GhG3DPSF`Ho}`+^WUX? z-O&$!N#EzIOO@@RXQOTxRqzK+4S6qA7N7;W_lJFF;Vyf6w|mY{7N>D_$v;;DP43us z6Zlru$dfjD4JOt+9G%+vY+NlSexS31v*^(JMyE90G2RU?A z0(IS$9F&XNFla9HJ6E_+s30%sK$jU^+9I=0jJUQW>K0YMGZo(>DOi!mI{07YFOkW@ zC3l!!GaTO$=!pEfZ%<)s(XX^5bX{i6F)R%tHDHa1G>(Ku8?R8c2cx>lpv>JX_?r~o z7IGxs6ooqWr`M~GzeRw^z>6UfJlxIB+WW>pd!CRaM8oQ%~_sd}2HGvDro~3Rvot$OyqF){k<#gPlv=4uNKIdC6 z^V|Mo4$zs&t}9{N(WKzErbO|Ih3UqWMMj^dJO5NHrgg(?N)cUm4oHrtQQI0KXQtw7 z$RiClbALEW;eS(>2?Ow@gN679a>&8+kzOSxSE4tx{1RNv^X_ofUwe)17NfBi6wiZq z61RGNT0a6L<=vd<?!p7j?yWPc~%+|D_S_j;xq$}+1ZYKEp-hXHxjUmgLRVo>A zsu+yK5hQ(BKQXM=^SP4l4jDl@WK#fs4T(Nn`?rz|6N;u4+1m~y>ANO$LbH_3{x)_BS^7cU`Z+sy`F zWZ-MF&YaU!1AlX&SKb*f*8V~N&2`r%Q*f{M(Bd^8+@`|*@pxsjVb8y96WX!2GTynS zYFTnKgx28t(REgEP(lc{cJr{l`=Hm>eEHCN;BTbsV>^BTUA;#u)w;$aU^R$J^Pmd8 zm1{W135TvvS{bw}Y>=NcFXff4NmP+#ARE+$E-rOA#ho5=!wUin#hmRy$zQUZ<~^Z>X;Kx#*+eDJhr>e z=Tj9KYT<-mVSN$b?B~DSRdEqkK%x=$U4gN$WEpAkUAwac_^Q5N$RC$kdI_qDyerPy z6?Il|ic&0l{Gs27R?*goW>wE+4YV|;$yG~8Fam0A`_!ohd|qt=_AR^qr|(0R4!j^MLAzV}X(AOwsl1Ed9GN=i#%% zU`1g}wUkpGq6ak8O3eS&+FQpp-FA=TiXbWiDkTP>q@>hn5Q8p3nt{@_4W+vjMJYjY zloEo#$PKqq2I2^(z?ifG8!|DgvD-1s8LfttTJA%n(Ba()`l$6=Msc|7J| z$=ty6#~2lUq9a*W1+Qc%IMYkAMW~R_kw24}_}3RdKR_~s5uQP}&C6M8sg zZT7Jr>?`-o(}IUQU>QN5^ene)1>A~zbJDME@=xm9M*@a$7ZvcjtmeQVlJWAi6nT*Bmwe1Y939I?$CJm{<_q`R#7U zW`1w2JngJ!L0rJ-VW*Y%vL_mDDvkv{X86b#UBt1Pp*fzazL9YF@P`-W>)_ZT8W?NS z;`jJ5)6M>Jo(g@=$?uj|@efeLKesL|i)Y{N!Ej`4-O+1Xb%@S%{kR_L!e|!|AJ)2F z`L6B;??j5>MRDr6CT@L!wqgGhLWyS|Lsv$m!Xh*`RFHDJf!{kH+~Xzj^45iXlPQM> z`Y4dqx6avT?!{f+ANc3C&|hs;H6GveC3j?9=|QDuNi8*h|_mq2fo-i)nD#95#K)58dn~j13rg)2h}F98l8Jy{>sHR}8Wj za5$q0%T|Q%54Z5oqHZLVSxBb(wn!TKeadX?kq#IQ3GRA=cwzJU8@QVFw$e&maypTF zJ#mgnrg~hx_A=Sp^9&<**udrD9^1!_1D1u6t>>*SYFzEGlRvQ3TB9;zamhYcSmQdD zV`34m9U^A;_^|WjSchKCEtH4R9RIR1UY55peD@g4|HLblW75}S)oAwi9hbAyh2Bkj zUFeUZs`?fM>ECCCCy>OTzL}bMOQ*RXpI;?t4oXdY5#*g*unO02J+%0mWmf5X!8-p+ z&3YhHjmHygP6>Z$*qW%4n89LXIIH(=51sd@DBIFFn{5W-XMXI27ouI@yvnB!*W;sR zCCZOK%%>%0Kf;HI6?s0zI34C(92OJq*1c?i(9y%adF0FZpl*lJ>4bpD))=P&w_oX3 zz2&tW&tg{bt4q&JRnI?M+W3({oR2R0k%)7Pvp5Y2?eGfxG01Ns;3Te0nb&E&AlN=U z>xOSuKkINr<7}*QOTxI+&|`*Ea=FUaF9wCIDzmnO8##j~c}GilSX*lN>r#7(KDR;S zomrl5Jo&~yGb6O^G!{e?WrspTdrrkmioU!-7Saf{n)Auk^YLCn9HC+lJy8;?E!vK&3HCc_ z!){l8Qr;k2q5M9?9=Fg+#@nELyPJt!rS?)4M@jbV{%^yRGryT6DwhVk(XkkLJQRm@ zk#(FGBaiu_cvD-$te4pBNrM~QGXw4;ww(B+O*Q$J(1^)ahh$r0cINeGGs*PhmuSsX zRT$=iUI$F&9u+$NA5UA5oe#(Z8kVq1Ed zgtXZ6!QxgUeF5P@p7m-9wTNQEr?%RnFN#H<7oBa^`i@Z10`&KT@tOdRAfH7c?=kR` z-Mp>~loOMYT%{b}p7aR?O<%QFRAg<9@IFe$+djD2n18<6v1c|-^uGtQDQ)G3d@FEL zf4-1g;EN;uL@yKF2+O>2eA?JUhL3V-7l)faa5zpQ7J>{>p!97DbFP2KeR{?M%gJ|% zbuN0n+g$b^;6pb&83sC&MZpg^bWcTXO!WJHMjN4cjU1wg_90wOscPFATBEyMb_up| zC3kL1F`qO4n1OC}+I`hnYlQN+!Mi)oNvm&ex~3xa`V@Cs#Z;$fUW>joWmMv;X{)(K zIeTGEAjl%i*j#C?ti|`dYxmd#n{2lnu2ZQTB4e!nn+#IEM!f2(Kd%ugxNI!_Z#Pz= zKVLB$)SOX9G>G1PW~tNx!+Y_~6@{z7V1;fJr83@{>!^1h=50S*)Q_?dw@*;8fE|NG zrsh2Nc?`P1mQtS)7h-!u(3aPx_ZGQoB`p-Q7WIs^?SXxi!4Ci3Y!fYYtUsQa;xS{! z7Z{3U3yKdy>+xu@W^#B`sGs`a_F_88@RZCUN#%;nA79TGJmlg#sFh2Qy2siFl$B!X zpQ&oFE%}SJwNDrDzo|1h@nwOKzct|-qTLPF&W&%n!TT^a?m370AcNY)gDw3DCVsCk zIds-885a*L>dizP&p41>`fMAzvc9U0p&|*|51$H2KlKslSpAZ0v~YGn5#axmnZ?h7 ze5+SuzB~`q@p6g})?&oP98MW+_PXz1rg~^}#Xlxhrv62%O0Voq3>n;Qj}dCf8}=QF zhpb3OUAUVcJ#$xhS&cBWM5Ks((0;M>Ozt=@|G}cs(B(zF!HCyn!zSg*%*J+Q%$w4Y zbB1pH#0|h%2_i&Pj^F5-@;+)MY!?N+#t=|TLx&<5peTqW8+-@VHV5k!Nr}A zi7c+cJ#t|Jui$+Qhxo5~J@kFDb$hpd_{DP4;)7pw7nxczg-C$@hk`YGnE z79EfM<93CnYqGEKxpZNR6=pRPJ)|tli*Dm;Kv^r33-W7wOZt8LcjX^H9k0&%xl-3` z<;;FVB?>PFqiMg`nnazpKSIuFaua3O^XVQNk4jrFH=OOWlvf^}LJPl<#XOv`Gl_hz zaRHBSmikwPZQaCWW7H#uNL`I{DXV01SM2-_WBpP5m64_LA2Tm@<6dZ=%hTV;>*Z{I zn0txK=lj>^L9|S%+uyg>@4jh>GQYXSdrUjMXq`yTc!Gy zkg00x2f_Llh0Zv~XX4`t)gwxrd3hEr5Ro^r`0Dhfk49OSmrwnCwR@t^1zDPzx+&>5 zlzLfO=H1>C5pm!Vh70@a1=NoGh-Av=S>L3`*RjB=hGO3`$(i(}tigs^{FdTJeAq9k zwZ=B)OiqI997IkDcpZ_Eqle3Uu68@D$I^MBhmB;i94Dk!6_E^Wsd)Dg-f40gD%^0n zqZ3=!MX%Z)U6w)!zLzzzY0e)-YuOK1}}Xx=J?ud%wo4Bf1p`r%FSn4#4hkt_Ve3! z3}38mLv&5rg0ZAe)O)Q0C5N?ao%N?s$xXp_a#St5A^jy=|97txt6iH|(J#YE`G}{P z3g^VNo@%Mvij?@xCckaq2wLh1tT7(*$x+)L`8@ZOK1@uZ<8%~#0MoxpTCH^z*;XxK z@16q>hU6ACgs^HQu&Ax4tDu6UTxW>rJO1_wJgK0LCXMf3Z-jWh8^?cw)GZ+IymRUe z-#G8IHXG<29rkX#2^26N9C8gYD2ac*4e>wwpc))n>Nb=jrfIG)1pmf;BvEB|{n>7~ zoKic@s9dx?qx0O)HTzf3tKwQI&c;P+z1!wDWgZ?}2}FC!s*8$!c=W?^d~4ii<;qE)3Q1@LE0# zg;`u5iV%0G=LDVy@t#S6p7ueBXIj8H)XZhIuL5e^Ep@J8Kt8U2YT?mQ{<>*8$rH;)lzX|oaYl6a1&-Zen@6M5N zXq|NpEDysw{2LNK_Iq30`eiR`_b0d4UE9_CB(%BMS)U$SRG_}OJeoNp>Gm|8-;&+6 zYfx7(C2kF&KaV~Yr zm3Oc%KTX~-eZTnOAiv;=*JQQsV6sbb#ETQ>Ss-sKgW=L1Lr6vb3Box+#v9WCZy%>3 z$1J+KwBp{qI=XQGd8`mJt_gwmcH%?<^dbmS$&8(&^CUB(!LP)*q5VpA}worJ}Mw(M+; zOJnN?`AJv{3&&46yE1bc(pO_1Ea5;p!4aX%zNlbf_r8u2leZq5dj-BoZZZ}4sld7RXbb$6IWuUyYH2bao&DO;nZ-gP zE(jxw_6vC;PJjMI-XqNLBK(r9C+~$bqq|k-4qLpDF69* zvFc0jDU&A`H&DEvWAAjt%2uK>XC*sKd8*G=A>y0dHPFw zoav8@K~Qc1O&RO*lNC^l5cyJebG@27N<0o6o}A~8`3xsIyZ#zTR~?S8=U1l~tF{a@47=F_g~?XUS3ht?kf+eu0tfZMVW-quld@=o zM}tBzs;g@wFa6(7QKXVt#I1x)H3Zvna`{&a1p$72!;s0+R)?lYTaK;0e1moj9(KaL z`oUvbdp%j9zbV${IOHi&_kD|r^;Es-F6X2g+sTf@f@M7GmThX2*trtfvm54@K$ zI;8kN$)1o~{5o#-{etqOr|QgAB*MsI>|E!Lcw4@ndKf(fp{3D)`H) z754lOalUnSpGtG5tq(IxU73AVwepr>^Pa&R_J`mSmWq>qewf;-VHPJ>$<7jZO>^Hd zr_Y&_?rHDsV#DlgQ=p*q01wMjcz=ON#INH!Q(l6+_-#;{t8d914T~K9@uIfxMt;C- zI=bH0`O_wNgza_`0tXz)%_!~8DskV`K&WCz2K}dqtXBa2EL4`BQA;4+;`iG&&gs6vyYtP@{oN_0B5ezY z@Hv}Y;e$zMc6^my^R$M0mV4jHi-H-6{}2@#2Ah*7Rz(G-6>OBd`9A%D7GSMm0@YubQQKyWzk#3h()}s8} zjQxCrKyC?Z2JbN)(go3NcRTj-JwI*4%ze!=8#-2RHGSoZv&hV7yNSSaImxQ4 z5Rze$H19W?bE0XDqovwgawKfcM9p^M$HDt3294b^E4_b_<;3;dl%!^nnX!AVXJcrJ zHnuOMg+w)mcCA~fZ0?EpEB`)nlsMMv6_IIM{3h?-Y$_+X<$30|)GIAhNI*9JWJjq> zMqr5pvB^HiAaK=n{oGr1%@9?1I(jdIxAx|RXs4@JBE1inSgrAme*iHT(>GSMEO(x1 zZLOT-XIjtG4jM4*t5W!nOnHnj6CrzEU^W-186`|5D3xo(csvnA; zOxjaRfA5oePgjmyd?tR@?_Y>@3?Op|C=|#w;62Wzz&Zo(n6->gBxjZE_d0}C4!YI9 z!>My56#(rLlt&kj+bVOdx~V_eWJ!wo-u^mn_|dtGRkjNnYo6Oa-ePBqc88M%U}Cm9 zB;lhCHgwyP-pIzdL%@lr;*Fy-Px3rHDJl5_%8~YSUmcsnI-&6yywA>`JfB444Xe7L zcS=#2jRizkhyyroW_^zq+1hs5(;8K)^?io~1|&1x0OxT8g;YD5+Oe zfz>5wKXm!E-Os7?ue?0(elvN%CE|q(uze-LJm&8`&cc0jdRVK`UeH(Z>*MJYYBppe zMmpL^K*}^o_LDFCI3u*t`#KKg2@*BAMmbJpWJ%XL#TPwwq~QkCo(rA2TFP})K>Nxk zX4L^(@wBr?nsxYefk_Qcv5)@hg8Ei=)cr@fms_}C9}X^e;%RUy{`*u3{%P73%7r$P zVAZs}kHCIf?1zOP0e8&^A5p~$?h7IIH|4sk0arYEsjbKG?+-Hef1#)ee4(QGZBXLB zmU;i-LJF`9A5li!L`!WUb%ZYI{@)Hv|7znhIz#u;yNPs*B{fgZn2Yh>QutdX2=HGY zN8kad0LRVxz(`Iu-doNzyP)H$(C$11z4?zQETtFcaazd= z2oOK3VIAun-zF@okzp_h`tz{4o%E;L!Kcz2^+3Y5DFzo582rq_Y)Q-$g@0l5ZvfDq z|&203{!iMo*#8TJvYas^C5mPk{f0%Z4*_hVfl; z?dz|NYUB^&Qkj#=9V!???Up8_=f$g?ww%5y!-e(}_y1r;3;KYi!O+T_Bm&gTQNBvD zv@Zu`i(=2s@Dv_kewp_VEEY&7!2z_Y1tifT8shk@>n@+#M3YM4s-7m^`zo>bmr|+3cJp>^VSAmzOWuKh;)q9vd=}?~?B|67yo6sDBM;*yG+C~aY1ohy zDad|m?T59s+Y(z}V}?D=7NDb(h(;8jwRn)`?S3MawHIt_a<<`*GysSppxrhhE`-c* z2waV~fjox2KqX{6E9Xz^DeecdV&fRHOV2M zPk*6o1hzU2)~W4u92~{;@2_;lk2u`oU5K1JF1ukV$&mt@{@d!>c^W*Lbs1TY zhU4a-V|-aXorX3_f`3MG%9YilR+a6=sA7x;nq zcvOZk+5{qz&8s_}@?#sK_9xx;6TUE*evU$sZ!wk1)L5GUmANc;?zBL6?^A?jbjVlD zPV9!&`NTk@hFH4HZEek(L4$EK+WVZi{o3ktRvT(zSgyrXEc1h{W4q%SO9Suaj@a$Z z_Yw^$9r4Ff1#@a_7w13=*Fh3QxCJ=EeNV6Ay6!)X=12F0OKM<44{66KP_K2W-92VI zW-hCs^EwFudotT0s>2DB*_BaPnv-NlVl#o_uBg{QISC5VH8U77bDz9L zozA-mdyA;HB@>b7xl%~}+~3|X^H%1LRp1Lp2hZQb>*?c9KLSB8w6nMK|16CY$KG;Wl! ze8uB8`wCPKNAy6AJYXbU9F7S%GD7CNH0iMzckuqSK4Mws>H@w zy_8XxSc=i`!2T^8@|XMwYeWZW@-}w=g8N9n~yGa{u)+n2Sb+d82&TXUa}laG^7_KLNU5j%TYvOlg1+s;5T~M21DD; zlHPft9#^MY>8$=*D*%shIjn;xda!%_KF;a9Kwmsa@be@Mi(%4P2=Ev#g(r|INFlL1 z{{5wzQkHrH`io!9iCtk5yi-mnEA^q*n;ZT;SRBb1S-=?)(Pj6JL2k(1QU7?!Is1(A!K__>p-JSV2dZw8D%2* z(_=SBw$+M+$W#84db&d4JMlAg?7X!J+Dld~zrW5Z^Mhz*H=(CgCS#Nu|8i@(s0CVw z@njNR-8#I9RtFUAaP9WQknKes&c@i&50rzLSkS#Alj&aBU`~U&Fx6Sq`7Yz|5gCBE zjM|6Xb8Zn^Bnn*vswx7`X3n~<>)%Zb(W%U*SJI-g(LhMV=nCT2=n5y^#3SHW&^=2|)1h_niY=j> z)LrYFbdilYe&R9C%9;v~CdptmPWnU6Z}`3R7og!?3g&ayz8P~)^~$sYQM@u|bH8@l zpuy0FcQ+XpP*nu#v6XS?fiGsj3&eWx$ea87{Qh#O0gwCC4yy4fCIW#VWdypYg1hE> ztscyZO2B%N*$9_K(g5tSWKR%o?sYI>CqQgnpISdP=eauFQ$4t1MUQi2)?(ykAJKJg z)U^fo*q-l8dm54^Z+)WMLA$G}+t1Q-Z}(f)&~|2`;X>(?Guhao(J3-`l)6m8(Kad4 z#(%OvK4C}P4fwLBXlTp;GAzXp@~Q(Phq~iZ3crP|fCHNEi6t}w@l5pT?k%D-sWen; z{2`70Uy*H5oxFmM>mk*g+lj+*6<9V??C?X9c@APXBUz9dXLCUOV~2#os1%YHIU95e zy!f@-5|T5rSq2S=%qBYtHYQrBH~i7@$W^3UT-pgL`mu!$ZiNP0zRK?|k!Gf@>JvFD z&R4Ki*ezsqYp){IL|~v*Uy7%-Zz_^7h3R2Vl}GbCFr4=>DeTZV$#juH7t;I9+BRcd zpGcJGgID6{+;c2P(+5y`doI-NfV+v^_|E=P-BswlRm37~gX+AqP0jiv)fCm~<#tmH z?(NutDVShD>*&?PB`mIVA>mL{%Bw~4Xu#yGX^%d6l5B2?6|&5!9=yuKTu?jD%+qnj z3kS#;U-mb{x`;(T<|Kmo?2sdgfKd1vV@-YuGIR}GC0CnHJ88-i7EIwn6>b4(Hl-LL zqHAz9-}apdSAg=1oz7u)k#m1pv;9CvW>mYx1a)pbKC>|^4&@f%D(eA=N!EiwKt1-E z=Rm0=VJ6`E_OwN-{_65egUUFX;C!oKtTWV2`3mz?mS5L4Muj`_M@Od}cCZqX9$;ON zVOfsrp(LUJJ{oPB8_3j)jOu1i@<3C(f{8+sH`Sr`{)S}U&IjtT-cTj~KY1R}Y4E6a z@e%6{NJg?SvRe|;<(IY_l}R!bTB%>*n_ZF;sRkKZXgu@Uki&Lf$AuXPt-wq-YmcRF zGCq$bwk7LIG#*7Cyio6f77~-i>U!y~BA_HuYVTPdDL$e0r&qG(hB6pF7~~jsA=P@Y znZK5q{;N0PLS69V*4GLm;AHvi6tk_#-w_*0kMTuGIYoI0qSk5 zjxT*S;*#V|O_{EaG-&o*Mx|8OmdlHKts1NQDfJgDoDtP7>X>-3Yy_FjW9*r23vjq+ z6Tb*7AZ?CKKakSV>z{8^ic2T+ih4XK=-}5WJt6nn$yE9^hAdy+Q!rAPLXd6+92q+> z450zM3}%RO>@KF3_exS_N2PXLGFVd)J=I=gscUb3>!el7HW)2Dr1phAcKuQ+0cWKyhGlL~{HcFyxa&9qdCjeed zSlzO*?*cpYRjH*T3}J-~9~zh?WPwbJ^|eXFLUxZ)T%OzN@}3wfaA*um=|AfkE!BF(cIoRsRk7^e=MP;9wZnRbheuHf=?gP)VhM<(yZKJo7BQM%;;>j6shl}M3Lv)T2uKS)nORQtJ+>}QZEU>^bu70U0PG~_&D z77FB#(=0T&%NO7U6~cAp)k0L23$sBCIKP-PS61}%8Vtwi3_SasTb|p^^o3Q8baq2c zVJ{e(?8&M>lB9gt<|G!OyRl>!au-a|A zUn@wDP|G*$+I}OY0K@1K$GwHB3W9nyBB@m23RuAEvc8yx&@Wr%N5}d8F4bB1_JIcG zzEKQx*i}Iz%~Jsk%VlW0<#`V>l%HO~^GpFdAU|5{B|DmqK=^N8vWBtp&eT>b1>UdqkhM246d*+)63VtqDUq~ zt&A1v(Ho+P8$!wu@qTIKKD+FiJm(naA+$U@dv3g?tG^RL7K^%rA>8!P?W*Lh&-Pmg zDFVD5l0|6%y+K(|xoYVu)r%N6dCVktRJ_%q}nL3qe33Wrk@Q zymcQ?wttS|+sbdWB|5#9T^*KynT8u$&dyo51~hg9x{v*c{(vjQidQono}6L5GCV>f zv+0A{6)ess&I3X3Kv_|ZrttHF7hC%s>$4H2W|dK<;(QUjH?yW_d=^yR5do%sEG}E3=Z08Q>-^yAuxE?Db0wB9~9tyTx z$9Dm8=~?fLaj@BxPVbP>DP_|GwN2767^AT^0j8?}>$Z$2Lr-)|1THAs@0CDag8=@k zFicZfw+i`K|GkguRb%>RAJqKl`zN8VUE=*D6bkr89bDCGonN$By;vy>7!7rwya@;1 zbZ0M)a)U zbcovihKs6KGve?;F3k4*`;$L^!A2Z(%Z}&%Wprgs!|gP>u%^M7;6C`76ZIjBJHWLd zh8YT@vL7~Tl%*frehRCMbyj{_*p@u#q?`9V9oTv*vRvIzS2J1`(lykFs&uHYkb`Hy zM}Nig+ki<#hsTKnJEt)aOaHucIFrgL)8du5=M#2h`e0}|8MvKb#r3f){7%1C^(y$$N| zlF?}8d1eV0YBc)z*7%R=p)dYQ|14LXLni$-P&rP8hc%!?)Ilt44~E6_D4N8I0*@ z5Vcmx3QQ!={c!!6Fd93>=<%7lPoq_3{!4QT>26;mk0o#6A;L)!rgzY;Ngb-oC<%}+plCg7>^ za53;`7#Kd6Gg6l#_|?^=*4ad*Hq7IX(*ephhwZ?zSCfr^dNveg?)%S3&>eJ(D^n^$ zA|K(BQ#ooPxgpD4pe3ISI*?WtVS)ynJ3v_4(==TWJp_&zavCzbRnO2uDt}R`uSuv8 z`Z0B*9tb-c^;rKXag&CHN^jtW$`Nq5aj`903=Gb8SAQyT6OCju#<)bNiSTxn2Izw% zyM!fv&1kU0$J^S|!#L}zuXT8nevmzNXfmB!r!|>C&FttDuConJX=wS!yh891IpHX_ zilkox{xb+9W;Q#gH_(H{q%#awiUZ!jc8H03hT#IVZuvE;_-7Gwn}DsxM?B1T6*&LL zWsRL1%vFXK;N~JUdc5j(uc6-;7*dT#E~@Pp-M?TPw&>EszA@W7AcX>*#+A~Kr5mDN zt&tbuqr4Sv!HxOAyB@WrG|6%Wg@Gwi$D-1FZr^SChI!(o_e02(Z85 zfV~J8?RXuUgcxIYXyo|^FEs6nS&Xt~ON8w&oiSRRDyznw{ZY8A*$U8MM0psF))1Cr z@IkmN-_0w&(4Y=NzqtunUa@dB6}}x}<|5+{E(pT38m#7c8$7q0#9t>RdVT);6)XR7 zT6~mG=&;Wp6rmjgz|p}bt)x{VGI%6RpTRQ~A`Ia0 zNoJ!=nzB-#;o-r`Zf8~AMsa{am^v$ZQimG{MgEvY)ZsfB9rT7b(z>b1ma`b%Q=HWG z(@N%}@KeJsd29IJCVCsDe;AtTA(oLm&Lg#ykqtE!_Qv3fkZ8b*{AF?;h(E_$1rR$3 z@VTXd5~Dw+RaQ>Ad-@1xM5c@)(HPUZCY+Op@2~|pZ5U=U=6mdiELLm>f_BlRNN_*X zMO9fvM1=lpU5tb5kAdJF%iRH%BhzLL{a?s)5B*D@`z z0kOE{o5ZOWc}$44Xw}BwzbOz(>f9)&yf^vODSork93C3dS{2;Pqx9e5{x$A5Uj_>< z)v6W|`;N}lc*;F3+9?vc3E}%islC@>bX|(JvE)AL$^x+JSyZ_d?Du?ikT8=b4# zRp!dGt!2kYlD5yS>i=z>z`$7?uWgRp*i;@+Nd}&TdP@13?*fik5*c{aqeg;Nv{rJr zv^YfrZ<3jx1qNKQBX?5h&GNe_z?g21q_^x^qm{}zB<~KmA%3C|D^oqI$(;p_>Z!b<5Y3_-(Kn$;5k zzY&^kHopYjQaGcZe3n2a&XDdIZmQTitOZmT)Ou$(cbJEZ@wRI_9li8wpL&4OaD^+%4`3FMLPs5h@?RpleoxgKPt5{7vA7*X=7{fv}{5dcoZ#bzU`zGFR ze((vLumz@4S1Qm6gA}|xX~{c$kdm`synZd@vgl>3>Y4H-LM6F(>t{&l5_X6P=O)`D zKU<@hbnxcDg^<^qa?A%XLVp)c`Xj3Pw_R)f%&~Cer7(H^d)Bk@oZ z)|J?P7YCzbm6DlSTSmkiOX?xXWr1RAEKK>MZdtPh<%Tu&R9oI6_XtAx6Kx;e-v~D% z^5d1u=lO8AIX6%Kq0wD;vd?^8hBOvm*W21_HnF&tS#S40vjs2&a2bm2h^di6e|?Zq zJr2S7P6P+I&i_P2+_xO@rCL*er^ouyCP(WAUwqzJWssv*K9ahHV9GJWQ=z^n^$geF zG*MLBi?(u?er%8RRFJm{@_I;a6EAvla4{=;{O6~^O`eF~B={g5vaP(`TumWwDJ#6A zduH&ZV?oVVG=+O9M}>Nj{eLzvX7CLmH~{ud{t@2$Vul+}O9(x;=@a+6lBH)kqUv;) znh@%37B*)(!-3y<914xRedzy`@PGI=x_~RFJ#~&_eT4S8pk-t8{`B5`w-K6garSR4 z74*1GSUg|-JNLU}sv%P@EF|-X)6rAE({jw@n-iB)38_&sqlMbs37h^O&io-7U2T<+ z9@JWAKucd2Z>{qBAN*a4uC{$lxZK*LN+ShU@9`@B4-V?uh*IxYo@BIg-YEGn4kQEA za(~~~(uob6;8()S`XiWAIe6JTg+9&S*r%JxDxe2G`}-Dt8|d%K=l}nYPaN51Et%t6 V$vjFuegOEVd&B5D;_B^~{|DQ49mfCw literal 0 HcmV?d00001 From a5ab56fd9191e23dfa60707fa42802342c1563f8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 12 Nov 2015 15:41:13 +0100 Subject: [PATCH 038/410] Move git tags API to separate file Signed-off-by: Dmitriy Zaporozhets --- doc/api/repositories.md | 74 --------------------- doc/api/tags.md | 75 ++++++++++++++++++++++ lib/api/api.rb | 1 + lib/api/repositories.rb | 35 ---------- lib/api/tags.rb | 44 +++++++++++++ spec/requests/api/repositories_spec.rb | 75 ---------------------- spec/requests/api/tags_spec.rb | 89 ++++++++++++++++++++++++++ 7 files changed, 209 insertions(+), 184 deletions(-) create mode 100644 doc/api/tags.md create mode 100644 lib/api/tags.rb create mode 100644 spec/requests/api/tags_spec.rb diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 3316745380..b6cca5d4e2 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -1,79 +1,5 @@ # Repositories -## List project repository tags - -Get a list of repository tags from a project, sorted by name in reverse alphabetical order. - -``` -GET /projects/:id/repository/tags -``` - -Parameters: - -- `id` (required) - The ID of a project - -```json -[ - { - "commit": { - "author_name": "John Smith", - "author_email": "john@example.com", - "authored_date": "2012-05-28T04:42:42-07:00", - "committed_date": "2012-05-28T04:42:42-07:00", - "committer_name": "Jack Smith", - "committer_email": "jack@example.com", - "id": "2695effb5807a22ff3d138d593fd856244e155e7", - "message": "Initial commit", - "parents_ids": [ - "2a4b78934375d7f53875269ffd4f45fd83a84ebe" - ] - }, - "name": "v1.0.0", - "message": null - } -] -``` - -## Create a new tag - -Creates new tag in the repository that points to the supplied ref. - -``` -POST /projects/:id/repository/tags -``` - -Parameters: - -- `id` (required) - The ID of a project -- `tag_name` (required) - The name of a tag -- `ref` (required) - Create tag using commit SHA, another tag name, or branch name. -- `message` (optional) - Creates annotated tag. - -```json -{ - "commit": { - "author_name": "John Smith", - "author_email": "john@example.com", - "authored_date": "2012-05-28T04:42:42-07:00", - "committed_date": "2012-05-28T04:42:42-07:00", - "committer_name": "Jack Smith", - "committer_email": "jack@example.com", - "id": "2695effb5807a22ff3d138d593fd856244e155e7", - "message": "Initial commit", - "parents_ids": [ - "2a4b78934375d7f53875269ffd4f45fd83a84ebe" - ] - }, - "name": "v1.0.0", - "message": null -} -``` -The message will be `nil` when creating a lightweight tag otherwise -it will contain the annotation. - -It returns 200 if the operation succeed. In case of an error, -405 with an explaining error message is returned. - ## List repository tree Get a list of repository files and directories in a project. diff --git a/doc/api/tags.md b/doc/api/tags.md new file mode 100644 index 0000000000..dc69a0c6fb --- /dev/null +++ b/doc/api/tags.md @@ -0,0 +1,75 @@ +# Tags + +## List project repository tags + +Get a list of repository tags from a project, sorted by name in reverse alphabetical order. + +``` +GET /projects/:id/repository/tags +``` + +Parameters: + +- `id` (required) - The ID of a project + +```json +[ + { + "commit": { + "author_name": "John Smith", + "author_email": "john@example.com", + "authored_date": "2012-05-28T04:42:42-07:00", + "committed_date": "2012-05-28T04:42:42-07:00", + "committer_name": "Jack Smith", + "committer_email": "jack@example.com", + "id": "2695effb5807a22ff3d138d593fd856244e155e7", + "message": "Initial commit", + "parents_ids": [ + "2a4b78934375d7f53875269ffd4f45fd83a84ebe" + ] + }, + "name": "v1.0.0", + "message": null + } +] +``` + +## Create a new tag + +Creates new tag in the repository that points to the supplied ref. + +``` +POST /projects/:id/repository/tags +``` + +Parameters: + +- `id` (required) - The ID of a project +- `tag_name` (required) - The name of a tag +- `ref` (required) - Create tag using commit SHA, another tag name, or branch name. +- `message` (optional) - Creates annotated tag. + +```json +{ + "commit": { + "author_name": "John Smith", + "author_email": "john@example.com", + "authored_date": "2012-05-28T04:42:42-07:00", + "committed_date": "2012-05-28T04:42:42-07:00", + "committer_name": "Jack Smith", + "committer_email": "jack@example.com", + "id": "2695effb5807a22ff3d138d593fd856244e155e7", + "message": "Initial commit", + "parents_ids": [ + "2a4b78934375d7f53875269ffd4f45fd83a84ebe" + ] + }, + "name": "v1.0.0", + "message": null +} +``` +The message will be `nil` when creating a lightweight tag otherwise +it will contain the annotation. + +It returns 200 if the operation succeed. In case of an error, +405 with an explaining error message is returned. diff --git a/lib/api/api.rb b/lib/api/api.rb index 40671e2517..fe1bf8a481 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -52,5 +52,6 @@ module API mount Labels mount Settings mount Keys + mount Tags end end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 20d568cf46..d7c48639eb 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -16,41 +16,6 @@ module API end end - # Get a project repository tags - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/repository/tags - get ":id/repository/tags" do - present user_project.repo.tags.sort_by(&:name).reverse, - with: Entities::RepoTag, project: user_project - end - - # Create tag - # - # Parameters: - # id (required) - The ID of a project - # tag_name (required) - The name of the tag - # ref (required) - Create tag from commit sha or branch - # message (optional) - Specifying a message creates an annotated tag. - # Example Request: - # POST /projects/:id/repository/tags - post ':id/repository/tags' do - authorize_push_project - message = params[:message] || nil - result = CreateTagService.new(user_project, current_user). - execute(params[:tag_name], params[:ref], message) - - if result[:status] == :success - present result[:tag], - with: Entities::RepoTag, - project: user_project - else - render_api_error!(result[:message], 400) - end - end - # Get a project repository tree # # Parameters: diff --git a/lib/api/tags.rb b/lib/api/tags.rb new file mode 100644 index 0000000000..da962bd402 --- /dev/null +++ b/lib/api/tags.rb @@ -0,0 +1,44 @@ +module API + # Releases API + class Tags < Grape::API + before { authenticate! } + before { authorize! :download_code, user_project } + + resource :projects do + # Get a project repository tags + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/repository/tags + get ":id/repository/tags" do + present user_project.repo.tags.sort_by(&:name).reverse, + with: Entities::RepoTag, project: user_project + end + + # Create tag + # + # Parameters: + # id (required) - The ID of a project + # tag_name (required) - The name of the tag + # ref (required) - Create tag from commit sha or branch + # message (optional) - Specifying a message creates an annotated tag. + # Example Request: + # POST /projects/:id/repository/tags + post ':id/repository/tags' do + authorize_push_project + message = params[:message] || nil + result = CreateTagService.new(user_project, current_user). + execute(params[:tag_name], params[:ref], message) + + if result[:status] == :success + present result[:tag], + with: Entities::RepoTag, + project: user_project + else + render_api_error!(result[:message], 400) + end + end + end + end +end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index faf6b77a46..4911cdd9da 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -11,81 +11,6 @@ describe API::API, api: true do let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } - describe "GET /projects/:id/repository/tags" do - it "should return an array of project tags" do - get api("/projects/#{project.id}/repository/tags", user) - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(project.repo.tags.sort_by(&:name).reverse.first.name) - end - end - - describe 'POST /projects/:id/repository/tags' do - context 'lightweight tags' do - it 'should create a new tag' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v7.0.1', - ref: 'master' - - expect(response.status).to eq(201) - expect(json_response['name']).to eq('v7.0.1') - end - end - - context 'annotated tag' do - it 'should create a new annotated tag' do - # Identity must be set in .gitconfig to create annotated tag. - repo_path = project.repository.path_to_repo - system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name})) - system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email})) - - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v7.1.0', - ref: 'master', - message: 'Release 7.1.0' - - expect(response.status).to eq(201) - expect(json_response['name']).to eq('v7.1.0') - expect(json_response['message']).to eq('Release 7.1.0') - end - end - - it 'should deny for user without push access' do - post api("/projects/#{project.id}/repository/tags", user2), - tag_name: 'v1.9.0', - ref: '621491c677087aa243f165eab467bfdfbee00be1' - expect(response.status).to eq(403) - end - - it 'should return 400 if tag name is invalid' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v 1.0.0', - ref: 'master' - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Tag name invalid') - end - - it 'should return 400 if tag already exists' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v8.0.0', - ref: 'master' - expect(response.status).to eq(201) - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v8.0.0', - ref: 'master' - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Tag already exists') - end - - it 'should return 400 if ref name is invalid' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'mytag', - ref: 'foo' - expect(response.status).to eq(400) - expect(json_response['message']).to eq('Invalid reference name') - end - end - describe "GET /projects/:id/repository/tree" do context "authorized user" do before { project.team << [user2, :reporter] } diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb new file mode 100644 index 0000000000..6ddd7030ce --- /dev/null +++ b/spec/requests/api/tags_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' +require 'mime/types' + +describe API::API, api: true do + include ApiHelpers + include RepoHelpers + + let(:user) { create(:user) } + let(:user2) { create(:user) } + let!(:project) { create(:project, creator_id: user.id) } + let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } + let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } + + + describe "GET /projects/:id/repository/tags" do + it "should return an array of project tags" do + get api("/projects/#{project.id}/repository/tags", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(project.repo.tags.sort_by(&:name).reverse.first.name) + end + end + + describe 'POST /projects/:id/repository/tags' do + context 'lightweight tags' do + it 'should create a new tag' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.0.1', + ref: 'master' + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.0.1') + end + end + + context 'annotated tag' do + it 'should create a new annotated tag' do + # Identity must be set in .gitconfig to create annotated tag. + repo_path = project.repository.path_to_repo + system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.name #{user.name})) + system(*%W(#{Gitlab.config.git.bin_path} --git-dir=#{repo_path} config user.email #{user.email})) + + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.1.0', + ref: 'master', + message: 'Release 7.1.0' + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.1.0') + expect(json_response['message']).to eq('Release 7.1.0') + end + end + + it 'should deny for user without push access' do + post api("/projects/#{project.id}/repository/tags", user2), + tag_name: 'v1.9.0', + ref: '621491c677087aa243f165eab467bfdfbee00be1' + expect(response.status).to eq(403) + end + + it 'should return 400 if tag name is invalid' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v 1.0.0', + ref: 'master' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Tag name invalid') + end + + it 'should return 400 if tag already exists' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v8.0.0', + ref: 'master' + expect(response.status).to eq(201) + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v8.0.0', + ref: 'master' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Tag already exists') + end + + it 'should return 400 if ref name is invalid' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'mytag', + ref: 'foo' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Invalid reference name') + end + end +end From d343d9d8c2f3db0ca5f6fab8ad19e0f79f66e138 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 12 Nov 2015 16:26:23 +0100 Subject: [PATCH 039/410] Add grape routing print Signed-off-by: Dmitriy Zaporozhets --- lib/tasks/grape.rake | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 lib/tasks/grape.rake diff --git a/lib/tasks/grape.rake b/lib/tasks/grape.rake new file mode 100644 index 0000000000..9980e0b798 --- /dev/null +++ b/lib/tasks/grape.rake @@ -0,0 +1,8 @@ +namespace :grape do + desc 'Print compiled grape routes' + task routes: :environment do + API::API.routes.each do |route| + puts route + end + end +end From c119a737935f1e1e8ae7dec814be8accb8980e1d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 12 Nov 2015 16:26:39 +0100 Subject: [PATCH 040/410] Add releases api Signed-off-by: Dmitriy Zaporozhets --- lib/api/entities.rb | 4 ++++ lib/api/tags.rb | 19 ++++++++++++++++++- spec/requests/api/tags_spec.rb | 15 ++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 20cadae229..400900bc40 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -341,5 +341,9 @@ module API expose :user_oauth_applications expose :after_sign_out_path end + + class Release < Grape::Entity + expose :tag, :description + end end end diff --git a/lib/api/tags.rb b/lib/api/tags.rb index da962bd402..06fa1d23fd 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -1,5 +1,5 @@ module API - # Releases API + # Git Tags API class Tags < Grape::API before { authenticate! } before { authorize! :download_code, user_project } @@ -39,6 +39,23 @@ module API render_api_error!(result[:message], 400) end end + + # Add release notes to tag + # + # Parameters: + # id (required) - The ID of a project + # tag (required) - The name of the tag + # description (required) - Release notes with markdown support + # Example Request: + # PUT /projects/:id/repository/tags + put ':id/repository/:tag/release', requirements: { tag: /.*/ } do + authorize_push_project + required_attributes! [:description] + release = user_project.releases.find_or_initialize_by(tag: params[:tag]) + release.update_attributes(description: params[:description]) + + present release, with: Entities::Release + end end end end diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 6ddd7030ce..72df9007ee 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -11,7 +11,6 @@ describe API::API, api: true do let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } - describe "GET /projects/:id/repository/tags" do it "should return an array of project tags" do get api("/projects/#{project.id}/repository/tags", user) @@ -86,4 +85,18 @@ describe API::API, api: true do expect(json_response['message']).to eq('Invalid reference name') end end + + describe 'PUT /projects/:id/repository/:tag/release' do + let(:tag_name) { project.repository.tag_names.first } + let(:description) { 'Awesome release!' } + + it 'should create description for existing git tag' do + put api("/projects/#{project.id}/repository/#{tag_name}/release", user), + description: description + + expect(response.status).to eq(200) + expect(json_response['tag']).to eq(tag_name) + expect(json_response['description']).to eq(description) + end + end end From ba34045f3120fc52ce714d49778a24c647248500 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 12 Nov 2015 17:04:18 +0100 Subject: [PATCH 041/410] Expose release notes to tags api Signed-off-by: Dmitriy Zaporozhets --- lib/api/entities.rb | 6 ++++++ spec/requests/api/tags_spec.rb | 30 +++++++++++++++++++++++++----- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 400900bc40..7528e718c6 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -112,6 +112,12 @@ module API options[:project].repository.commit(repo_obj.target) end end + + expose :release do |repo_obj, options| + if options[:project] + options[:project].releases.find_by(tag: repo_obj.name) + end + end end class RepoObject < Grape::Entity diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 72df9007ee..4362e0ec28 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -12,11 +12,31 @@ describe API::API, api: true do let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } describe "GET /projects/:id/repository/tags" do - it "should return an array of project tags" do - get api("/projects/#{project.id}/repository/tags", user) - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).to eq(project.repo.tags.sort_by(&:name).reverse.first.name) + let(:tag_name) { project.repository.tag_names.sort.reverse.first } + let(:description) { 'Awesome release!' } + + context 'without releases' do + it "should return an array of project tags" do + get api("/projects/#{project.id}/repository/tags", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(tag_name) + end + end + + context 'with releases' do + before do + release = project.releases.find_or_initialize_by(tag: tag_name) + release.update_attributes(description: description) + get api("/projects/#{project.id}/repository/tags", user) + end + + it "should return an array of project tags with release info" do + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(tag_name) + expect(json_response.first['release']['description']).to eq(description) + end end end From 8f53094f0f22fe208f6f6e0d39037188bf14a937 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 12 Nov 2015 23:52:02 +0100 Subject: [PATCH 042/410] Add API docs and correctly expose release api Signed-off-by: Dmitriy Zaporozhets --- doc/api/tags.md | 31 ++++++++++++++++++++++++++++ lib/api/entities.rb | 50 ++++++++++++++++++++++----------------------- lib/api/tags.rb | 2 +- 3 files changed, 57 insertions(+), 26 deletions(-) diff --git a/doc/api/tags.md b/doc/api/tags.md index dc69a0c6fb..0113d4ba04 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -28,6 +28,10 @@ Parameters: "2a4b78934375d7f53875269ffd4f45fd83a84ebe" ] }, + "release": { + "tag": "1.0.0", + "description": "Amazing release. Wow" + }, "name": "v1.0.0", "message": null } @@ -48,6 +52,7 @@ Parameters: - `tag_name` (required) - The name of a tag - `ref` (required) - Create tag using commit SHA, another tag name, or branch name. - `message` (optional) - Creates annotated tag. +- `release_description` (optional) - Add release notes to the git tag and store it in the GitLab database. ```json { @@ -64,6 +69,10 @@ Parameters: "2a4b78934375d7f53875269ffd4f45fd83a84ebe" ] }, + "release": { + "tag": "1.0.0", + "description": "Amazing release. Wow" + }, "name": "v1.0.0", "message": null } @@ -73,3 +82,25 @@ it will contain the annotation. It returns 200 if the operation succeed. In case of an error, 405 with an explaining error message is returned. + + +## New release + +Add release notes to the existing git tag + +``` +PUT /projects/:id/repository/:tag/release +``` + +Parameters: + +- `id` (required) - The ID of a project +- `tag` (required) - The name of a tag +- `description` (required) - Release notes with markdown support + +```json +{ + "tag": "1.0.0", + "description": "Amazing release. Wow" +} +``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 7528e718c6..3ca632dfae 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -95,31 +95,6 @@ module API end end - class RepoTag < Grape::Entity - expose :name - expose :message do |repo_obj, _options| - if repo_obj.respond_to?(:message) - repo_obj.message - else - nil - end - end - - expose :commit do |repo_obj, options| - if repo_obj.respond_to?(:commit) - repo_obj.commit - elsif options[:project] - options[:project].repository.commit(repo_obj.target) - end - end - - expose :release do |repo_obj, options| - if options[:project] - options[:project].releases.find_by(tag: repo_obj.name) - end - end - end - class RepoObject < Grape::Entity expose :name @@ -351,5 +326,30 @@ module API class Release < Grape::Entity expose :tag, :description end + + class RepoTag < Grape::Entity + expose :name + expose :message do |repo_obj, _options| + if repo_obj.respond_to?(:message) + repo_obj.message + else + nil + end + end + + expose :commit do |repo_obj, options| + if repo_obj.respond_to?(:commit) + repo_obj.commit + elsif options[:project] + options[:project].repository.commit(repo_obj.target) + end + end + + expose :release, using: Entities::Release do |repo_obj, options| + if options[:project] + options[:project].releases.find_by(tag: repo_obj.name) + end + end + end end end diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 06fa1d23fd..673342dd44 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -29,7 +29,7 @@ module API authorize_push_project message = params[:message] || nil result = CreateTagService.new(user_project, current_user). - execute(params[:tag_name], params[:ref], message) + execute(params[:tag_name], params[:ref], message, params[:release_description]) if result[:status] == :success present result[:tag], From d6db451732a5ed9f31f3a345dd936abc1c07c6cf Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 12 Nov 2015 23:54:22 +0100 Subject: [PATCH 043/410] Add api test for creating tag with release info Signed-off-by: Dmitriy Zaporozhets --- spec/requests/api/tags_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 4362e0ec28..cc9a5f4758 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -52,6 +52,19 @@ describe API::API, api: true do end end + context 'lightweight tags with release notes' do + it 'should create a new tag' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.0.1', + ref: 'master', + release_description: 'Wow' + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.0.1') + expect(json_response['release']['description']).to eq('Wow') + end + end + context 'annotated tag' do it 'should create a new annotated tag' do # Identity must be set in .gitconfig to create annotated tag. From e5cc197a993fd0fbaafd7eeac6253fe38b8a96ad Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 00:06:42 +0100 Subject: [PATCH 044/410] Split huge method MergeRequests::UpdateService#execute Signed-off-by: Dmitriy Zaporozhets --- app/services/merge_requests/update_service.rb | 51 ++++++++++--------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 61f7d2bbe8..d2849e5193 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -35,35 +35,38 @@ module MergeRequests ) end - if merge_request.previous_changes.include?('target_branch') - create_branch_change_note(merge_request, 'target', - merge_request.previous_changes['target_branch'].first, - merge_request.target_branch) - end - - if merge_request.previous_changes.include?('milestone_id') - create_milestone_note(merge_request) - end - - if merge_request.previous_changes.include?('assignee_id') - create_assignee_note(merge_request) - notification_service.reassigned_merge_request(merge_request, current_user) - end - - if merge_request.previous_changes.include?('title') - create_title_change_note(merge_request, merge_request.previous_changes['title'].first) - end - - if merge_request.previous_changes.include?('target_branch') || - merge_request.previous_changes.include?('source_branch') - merge_request.mark_as_unchecked - end - + handle_changes(merge_request) merge_request.create_new_cross_references!(current_user) execute_hooks(merge_request, 'update') end merge_request end + + def handle_changes(merge_request) + if merge_request.previous_changes.include?('target_branch') + create_branch_change_note(merge_request, 'target', + merge_request.previous_changes['target_branch'].first, + merge_request.target_branch) + end + + if merge_request.previous_changes.include?('milestone_id') + create_milestone_note(merge_request) + end + + if merge_request.previous_changes.include?('assignee_id') + create_assignee_note(merge_request) + notification_service.reassigned_merge_request(merge_request, current_user) + end + + if merge_request.previous_changes.include?('title') + create_title_change_note(merge_request, merge_request.previous_changes['title'].first) + end + + if merge_request.previous_changes.include?('target_branch') || + merge_request.previous_changes.include?('source_branch') + merge_request.mark_as_unchecked + end + end end end From 7f0b4ce16f14129f9c3f94b4a83fa8a6426cd4ba Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 00:12:18 +0100 Subject: [PATCH 045/410] Split complex method SystemHooksService#build_event_data Signed-off-by: Dmitriy Zaporozhets --- app/services/system_hooks_service.rb | 72 ++++++++++++++++------------ 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 9a5fe4af9d..8b5143e1eb 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -33,17 +33,7 @@ class SystemHooksService ) end when Project - owner = model.owner - - data.merge!({ - name: model.name, - path: model.path, - path_with_namespace: model.path_with_namespace, - project_id: model.id, - owner_name: owner.name, - owner_email: owner.respond_to?(:email) ? owner.email : "", - project_visibility: Project.visibility_levels.key(model.visibility_level_field).downcase - }) + data.merge!(project_data(model)) when User data.merge!({ name: model.name, @@ -51,16 +41,7 @@ class SystemHooksService user_id: model.id }) when ProjectMember - data.merge!({ - project_name: model.project.name, - project_path: model.project.path, - project_path_with_namespace: model.project.path_with_namespace, - project_id: model.project.id, - user_name: model.user.name, - user_email: model.user.email, - access_level: model.human_access, - project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase - }) + data.merge!(project_member_data(model)) when Group owner = model.owner @@ -72,15 +53,7 @@ class SystemHooksService owner_email: owner.respond_to?(:email) ? owner.email : nil, ) when GroupMember - data.merge!( - group_name: model.group.name, - group_path: model.group.path, - group_id: model.group.id, - user_name: model.user.name, - user_email: model.user.email, - user_id: model.user.id, - group_access: model.human_access, - ) + data.merge!(group_member_data(model)) end end @@ -96,4 +69,43 @@ class SystemHooksService "#{model.class.name.downcase}_#{event.to_s}" end end + + def project_data(model) + owner = model.owner + + { + name: model.name, + path: model.path, + path_with_namespace: model.path_with_namespace, + project_id: model.id, + owner_name: owner.name, + owner_email: owner.respond_to?(:email) ? owner.email : "", + project_visibility: Project.visibility_levels.key(model.visibility_level_field).downcase + } + end + + def project_member_data(model) + { + project_name: model.project.name, + project_path: model.project.path, + project_path_with_namespace: model.project.path_with_namespace, + project_id: model.project.id, + user_name: model.user.name, + user_email: model.user.email, + access_level: model.human_access, + project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase + } + end + + def group_member_data(model) + { + group_name: model.group.name, + group_path: model.group.path, + group_id: model.group.id, + user_name: model.user.name, + user_email: model.user.email, + user_id: model.user.id, + group_access: model.human_access, + } + end end From 857710634c516cea8516e73132027a361364211c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 00:13:45 +0100 Subject: [PATCH 046/410] Split complex method Issues::UpdateService#execute Signed-off-by: Dmitriy Zaporozhets --- app/services/issues/update_service.rb | 29 +++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 551325e4ca..aa1fd79d22 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -22,24 +22,27 @@ module Issues issue, issue.labels - old_labels, old_labels - issue.labels) end - if issue.previous_changes.include?('milestone_id') - create_milestone_note(issue) - end - - if issue.previous_changes.include?('assignee_id') - create_assignee_note(issue) - notification_service.reassigned_issue(issue, current_user) - end - - if issue.previous_changes.include?('title') - create_title_change_note(issue, issue.previous_changes['title'].first) - end - + handle_changes(issue) issue.create_new_cross_references!(current_user) execute_hooks(issue, 'update') end issue end + + def handle_changes(issue) + if issue.previous_changes.include?('milestone_id') + create_milestone_note(issue) + end + + if issue.previous_changes.include?('assignee_id') + create_assignee_note(issue) + notification_service.reassigned_issue(issue, current_user) + end + + if issue.previous_changes.include?('title') + create_title_change_note(issue, issue.previous_changes['title'].first) + end + end end end From bcc82511966aba863a0a912302e8cfe3368ebc32 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 00:15:33 +0100 Subject: [PATCH 047/410] Split complex method EventsHelper#event_feed_url. Signed-off-by: Dmitriy Zaporozhets --- app/helpers/events_helper.rb | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index 51b872b7a9..dde83ff36b 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -108,19 +108,23 @@ module EventsHelper end end elsif event.push? - if event.push_with_commits? && event.md_ref? - if event.commits_count > 1 - namespace_project_compare_url(event.project.namespace, event.project, - from: event.commit_from, to: - event.commit_to) - else - namespace_project_commit_url(event.project.namespace, event.project, - id: event.commit_to) - end + push_event_feed_url(event) + end + end + + def push_event_feed_url(event) + if event.push_with_commits? && event.md_ref? + if event.commits_count > 1 + namespace_project_compare_url(event.project.namespace, event.project, + from: event.commit_from, to: + event.commit_to) else - namespace_project_commits_url(event.project.namespace, event.project, - event.ref_name) + namespace_project_commit_url(event.project.namespace, event.project, + id: event.commit_to) end + else + namespace_project_commits_url(event.project.namespace, event.project, + event.ref_name) end end From ea6a78ef5cb665d6f3b06d71fa693ffa895b4b1a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 00:44:16 +0100 Subject: [PATCH 048/410] Split complex Gitlab::InlineDiff::processing method Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/inline_diff.rb | 55 +++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb index 99e7b529ba..420c3099ce 100644 --- a/lib/gitlab/inline_diff.rb +++ b/lib/gitlab/inline_diff.rb @@ -16,15 +16,7 @@ module Gitlab # Skip inline diff if empty line was replaced with content next if first_line == "-\n" - first_the_same_symbols = 0 - (0..max_length + 1).each do |i| - first_the_same_symbols = i - 1 - if first_line[i] != second_line[i] && i > 0 - break - end - end - - first_token = first_line[0..first_the_same_symbols][1..-1] + first_token = find_first_token(first_line, second_line) start = first_token + START if first_token.empty? @@ -36,25 +28,50 @@ module Gitlab diff_arr[index+2].sub!(first_token, first_token => start) end - last_the_same_symbols = 0 - (1..max_length + 1).each do |i| - last_the_same_symbols = -i - shortest_line = second_line.size > first_line.size ? first_line : second_line - if ( first_line[-i] != second_line[-i] ) || "#{first_token}#{START}".size == shortest_line[1..-i].size - break - end - end - last_the_same_symbols += 1 - last_token = first_line[last_the_same_symbols..-1] + last_token = find_last_token(first_line, second_line, first_token) + # This is tricky: escape backslashes so that `sub` doesn't interpret them # as backreferences. Regexp.escape does NOT do the right thing. replace_token = FINISH + last_token.gsub(/\\/, '\&\&') diff_arr[index+1].sub!(/#{Regexp.escape(last_token)}$/, replace_token) diff_arr[index+2].sub!(/#{Regexp.escape(last_token)}$/, replace_token) end + diff_arr end + def find_first_token(first_line, second_line) + max_length = [first_line.size, second_line.size].max + first_the_same_symbols = 0 + + (0..max_length + 1).each do |i| + first_the_same_symbols = i - 1 + + if first_line[i] != second_line[i] && i > 0 + break + end + end + + first_line[0..first_the_same_symbols][1..-1] + end + + def find_last_token(first_line, second_line, first_token) + max_length = [first_line.size, second_line.size].max + last_the_same_symbols = 0 + + (1..max_length + 1).each do |i| + last_the_same_symbols = -i + shortest_line = second_line.size > first_line.size ? first_line : second_line + + if (first_line[-i] != second_line[-i]) || "#{first_token}#{START}".size == shortest_line[1..-i].size + break + end + end + + last_the_same_symbols += 1 + first_line[last_the_same_symbols..-1] + end + def _indexes_of_changed_lines(diff_arr) chain_of_first_symbols = "" diff_arr.each_with_index do |line, i| From c903f23f3c0b5654c2883cdf63593e53a8898a79 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 00:55:20 +0100 Subject: [PATCH 049/410] Split complex methods in GoogleCodeImport::Importer Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/google_code_import/importer.rb | 93 ++++++++++++----------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb index 03c410726a..87fee28dc0 100644 --- a/lib/gitlab/google_code_import/importer.rb +++ b/lib/gitlab/google_code_import/importer.rb @@ -30,7 +30,7 @@ module Gitlab def user_map @user_map ||= begin - user_map = Hash.new do |hash, user| + user_map = Hash.new do |hash, user| # Replace ... by \.\.\., so `johnsm...@gmail.com` isn't autolinked. Client.mask_email(user).sub("...", "\\.\\.\\.") end @@ -76,18 +76,7 @@ module Gitlab attachments = format_attachments(raw_issue["id"], 0, issue_comment["attachments"]) body = format_issue_body(author, date, content, attachments) - - labels = [] - raw_issue["labels"].each do |label| - name = nice_label_name(label) - labels << name - - unless @known_labels.include?(name) - create_label(name) - @known_labels << name - end - end - labels << nice_status_name(raw_issue["status"]) + labels = import_issue_labels(raw_issue) assignee_id = nil if raw_issue.has_key?("owner") @@ -110,6 +99,7 @@ module Gitlab assignee_id: assignee_id, state: raw_issue["state"] == "closed" ? "closed" : "opened" ) + issue.add_labels_by_names(labels) if issue.iid != raw_issue["id"] @@ -120,6 +110,23 @@ module Gitlab end end + def import_issue_labels(raw_issue) + labels = [] + + raw_issue["labels"].each do |label| + name = nice_label_name(label) + labels << name + + unless @known_labels.include?(name) + create_label(name) + @known_labels << name + end + end + + labels << nice_status_name(raw_issue["status"]) + labels + end + def import_issue_comments(issue, comments) Note.transaction do while raw_comment = comments.shift @@ -172,7 +179,7 @@ module Gitlab "#5cb85c" when "Status: Started" "#8e44ad" - + when "Priority: Critical" "#ffcfcf" when "Priority: High" @@ -181,7 +188,7 @@ module Gitlab "#fff5cc" when "Priority: Low" "#cfe9ff" - + when "Type: Defect" "#d9534f" when "Type: Enhancement" @@ -249,8 +256,8 @@ module Gitlab end if raw_updates.has_key?("cc") - cc = raw_updates["cc"].map do |l| - deleted = l.start_with?("-") + cc = raw_updates["cc"].map do |l| + deleted = l.start_with?("-") l = l[1..-1] if deleted l = user_map[l] l = "~~#{l}~~" if deleted @@ -261,8 +268,8 @@ module Gitlab end if raw_updates.has_key?("labels") - labels = raw_updates["labels"].map do |l| - deleted = l.start_with?("-") + labels = raw_updates["labels"].map do |l| + deleted = l.start_with?("-") l = l[1..-1] if deleted l = nice_label_name(l) l = "~~#{l}~~" if deleted @@ -278,45 +285,39 @@ module Gitlab if raw_updates.has_key?("blockedOn") blocked_ons = raw_updates["blockedOn"].map do |raw_blocked_on| - name, id = raw_blocked_on.split(":", 2) - - deleted = name.start_with?("-") - name = name[1..-1] if deleted - - text = - if name == project.import_source - "##{id}" - else - "#{project.namespace.path}/#{name}##{id}" - end - text = "~~#{text}~~" if deleted - text + format_blocking_updates(raw_blocked_on) end + updates << "*Blocked on: #{blocked_ons.join(", ")}*" end if raw_updates.has_key?("blocking") blockings = raw_updates["blocking"].map do |raw_blocked_on| - name, id = raw_blocked_on.split(":", 2) - - deleted = name.start_with?("-") - name = name[1..-1] if deleted - - text = - if name == project.import_source - "##{id}" - else - "#{project.namespace.path}/#{name}##{id}" - end - text = "~~#{text}~~" if deleted - text + format_blocking_updates(raw_blocked_on) end + updates << "*Blocking: #{blockings.join(", ")}*" end updates end + def format_blocking_updates(raw_blocked_on) + name, id = raw_blocked_on.split(":", 2) + + deleted = name.start_with?("-") + name = name[1..-1] if deleted + + text = + if name == project.import_source + "##{id}" + else + "#{project.namespace.path}/#{name}##{id}" + end + text = "~~#{text}~~" if deleted + text + end + def format_attachments(issue_id, comment_id, raw_attachments) return [] unless raw_attachments @@ -325,7 +326,7 @@ module Gitlab filename = attachment["fileName"] link = "https://storage.googleapis.com/google-code-attachments/#{@repo.name}/issue-#{issue_id}/comment-#{comment_id}/#{filename}" - + text = "[#{filename}](#{link})" text = "!#{text}" if filename =~ /\.(png|jpg|jpeg|gif|bmp|tiff)\z/i text From c71892e1a87ece19edf17f1d61c71d4b99953bc7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 01:07:33 +0100 Subject: [PATCH 050/410] Even more refactoring to inline_diff.rb Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/inline_diff.rb | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb index 420c3099ce..72d0b493be 100644 --- a/lib/gitlab/inline_diff.rb +++ b/lib/gitlab/inline_diff.rb @@ -17,29 +17,36 @@ module Gitlab next if first_line == "-\n" first_token = find_first_token(first_line, second_line) - start = first_token + START - - if first_token.empty? - # In case if we remove string of spaces in commit - diff_arr[index+1].sub!("-", "-" => "-#{START}") - diff_arr[index+2].sub!("+", "+" => "+#{START}") - else - diff_arr[index+1].sub!(first_token, first_token => start) - diff_arr[index+2].sub!(first_token, first_token => start) - end + apply_first_token(diff_arr, index, first_token) last_token = find_last_token(first_line, second_line, first_token) - - # This is tricky: escape backslashes so that `sub` doesn't interpret them - # as backreferences. Regexp.escape does NOT do the right thing. - replace_token = FINISH + last_token.gsub(/\\/, '\&\&') - diff_arr[index+1].sub!(/#{Regexp.escape(last_token)}$/, replace_token) - diff_arr[index+2].sub!(/#{Regexp.escape(last_token)}$/, replace_token) + apply_last_token(diff_arr, index, last_token) end diff_arr end + def apply_first_token(diff_arr, index, first_token) + start = first_token + START + + if first_token.empty? + # In case if we remove string of spaces in commit + diff_arr[index+1].sub!("-", "-" => "-#{START}") + diff_arr[index+2].sub!("+", "+" => "+#{START}") + else + diff_arr[index+1].sub!(first_token, first_token => start) + diff_arr[index+2].sub!(first_token, first_token => start) + end + end + + def apply_last_token(diff_arr, index, last_token) + # This is tricky: escape backslashes so that `sub` doesn't interpret them + # as backreferences. Regexp.escape does NOT do the right thing. + replace_token = FINISH + last_token.gsub(/\\/, '\&\&') + diff_arr[index+1].sub!(/#{Regexp.escape(last_token)}$/, replace_token) + diff_arr[index+2].sub!(/#{Regexp.escape(last_token)}$/, replace_token) + end + def find_first_token(first_line, second_line) max_length = [first_line.size, second_line.size].max first_the_same_symbols = 0 From c613f2d9a85c60bdf34fdafdda41dd5ee1e32904 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 01:08:10 +0100 Subject: [PATCH 051/410] Don't allow flog failure any more Signed-off-by: Dmitriy Zaporozhets --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 128faa07db..141e7ba41d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -80,7 +80,6 @@ flog: tags: - ruby - mysql - allow_failure: true flay: script: From 3d0efa8e0a359c84485a0fd7a3317745bf5648b8 Mon Sep 17 00:00:00 2001 From: Minsik Yoon Date: Thu, 22 Oct 2015 09:55:35 +0900 Subject: [PATCH 052/410] Add ignore white space option in merge request diff fix this issue(https://gitlab.com/gitlab-org/gitlab-ce/issues/1393). Add ignore whitespace optoin to Commits Compare view --- CHANGELOG | 1 + .../projects/compare_controller.rb | 3 +- app/models/merge_request.rb | 2 +- app/models/merge_request_diff.rb | 16 +++++++++- app/services/compare_service.rb | 4 +-- .../merge_requests/show/_diffs.html.haml | 2 +- doc/workflow/merge_requests.md | 12 ++++++++ .../merge_requests/commit_compare.png | Bin 0 -> 89631 bytes .../merge_requests/merge_request_diff.png | Bin 0 -> 120422 bytes .../merge_request_diff_without_whitespace.png | Bin 0 -> 98887 bytes lib/gitlab/compare_result.rb | 4 +-- .../projects/compare_controller_spec.rb | 16 ++++++++++ .../merge_requests_controller_spec.rb | 28 ++++++++++++++++++ 13 files changed, 80 insertions(+), 8 deletions(-) create mode 100644 doc/workflow/merge_requests/commit_compare.png create mode 100644 doc/workflow/merge_requests/merge_request_diff.png create mode 100644 doc/workflow/merge_requests/merge_request_diff_without_whitespace.png diff --git a/CHANGELOG b/CHANGELOG index 62def42d95..b8fe3b1c2f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.2.0 (unreleased) - MR target branch is now visible on a list view when it is different from project's default one - Improve Continuous Integration graphs page - Make color of "Accept Merge Request" button consistent with current build status + - Add ignore white space option in merge request diff and commit and compare view v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 71aaad1fad..55134e11d1 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -12,9 +12,10 @@ class Projects::CompareController < Projects::ApplicationController def show base_ref = Addressable::URI.unescape(params[:from]) @ref = head_ref = Addressable::URI.unescape(params[:to]) + diff_options = { ignore_whitespace_change: true } if params[:w] == '1' compare_result = CompareService.new. - execute(@project, head_ref, @project, base_ref) + execute(@project, head_ref, @project, base_ref, diff_options) if compare_result @commits = Commit.decorate(compare_result.commits, @project) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 85f37e49e6..e81d65c233 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -40,7 +40,7 @@ class MergeRequest < ActiveRecord::Base after_create :create_merge_request_diff after_update :update_merge_request_diff - delegate :commits, :diffs, to: :merge_request_diff, prefix: nil + delegate :commits, :diffs, :diffs_no_whitespace, to: :merge_request_diff, prefix: nil # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 6575d0bc81..c499a4b5b4 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -19,7 +19,7 @@ class MergeRequestDiff < ActiveRecord::Base # Prevent store of diff if commits amount more then 500 COMMITS_SAFE_SIZE = 500 - attr_reader :commits, :diffs + attr_reader :commits, :diffs, :diffs_no_whitespace belongs_to :merge_request @@ -47,6 +47,20 @@ class MergeRequestDiff < ActiveRecord::Base @diffs ||= (load_diffs(st_diffs) || []) end + def diffs_no_whitespace + # Get latest sha of branch from source project + source_sha = merge_request.source_project.commit(source_branch).sha + + compare_result = Gitlab::CompareResult.new( + Gitlab::Git::Compare.new( + merge_request.target_project.repository.raw_repository, + merge_request.target_branch, + source_sha, + ), { ignore_whitespace_change: true } + ) + @diffs_no_whitespace ||= load_diffs(dump_commits(compare_result.diffs)) + end + def commits @commits ||= load_commits(st_commits || []) end diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index bfe6a3dc4b..ec581658fc 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,7 +3,7 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::CompareResult object that responds to commits and diffs class CompareService - def execute(source_project, source_branch, target_project, target_branch) + def execute(source_project, source_branch, target_project, target_branch, diff_options = {}) source_commit = source_project.commit(source_branch) return unless source_commit @@ -25,7 +25,7 @@ class CompareService target_project.repository.raw_repository, target_branch, source_sha, - ) + ), diff_options ) end end diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index 626970f39b..d9cfc3d7ae 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,5 +1,5 @@ - if @merge_request_diff.collected? - = render "projects/diffs/diffs", diffs: @merge_request.diffs, project: @merge_request.project + = render "projects/diffs/diffs", diffs: params[:w] == '1' ? @merge_request.diffs_no_whitespace : @merge_request.diffs, project: @merge_request.project - elsif @merge_request_diff.empty? .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} - else diff --git a/doc/workflow/merge_requests.md b/doc/workflow/merge_requests.md index 751e19da7f..6d57b5d98c 100644 --- a/doc/workflow/merge_requests.md +++ b/doc/workflow/merge_requests.md @@ -38,3 +38,15 @@ To check out a particular merge request: ``` $ git checkout origin/merge-requests/1 ``` + +## Ignore whitespace changes in Merge Request diff view + +![MR diff](merge_requests/merge_request_diff.png) + +It you add `w=1` option to URL, you can see diff without whitespace changes. + +![MR diff without whitespace](merge_requests/merge_request_diff_without_whitespace.png) + +It is also working on commits compare view. + +![Commit Compare](merge_requests/commit_compare.png) diff --git a/doc/workflow/merge_requests/commit_compare.png b/doc/workflow/merge_requests/commit_compare.png new file mode 100644 index 0000000000000000000000000000000000000000..46b3a56a59b9cdf0317d7ae8c1903a65eca2c5af GIT binary patch literal 89631 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∓5Wdx0(-)x_xiuIh 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/merge_requests/merge_request_diff.png b/doc/workflow/merge_requests/merge_request_diff.png new file mode 100644 index 0000000000000000000000000000000000000000..ed08ae91bec73772c3cd51521a46edfea9aace93 GIT binary patch literal 120422 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png b/doc/workflow/merge_requests/merge_request_diff_without_whitespace.png new file mode 100644 index 0000000000000000000000000000000000000000..67d67a64d122ffa00c76a34a095f56b6f48adb01 GIT binary patch literal 98887 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

!Zn}kl#=#>N^LaqhdtCvCcje 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 literal 0 HcmV?d00001 diff --git a/lib/gitlab/compare_result.rb b/lib/gitlab/compare_result.rb index d72391dade..0d696a1ee2 100644 --- a/lib/gitlab/compare_result.rb +++ b/lib/gitlab/compare_result.rb @@ -2,8 +2,8 @@ module Gitlab class CompareResult attr_reader :commits, :diffs - def initialize(compare) - @commits, @diffs = compare.commits, compare.diffs + def initialize(compare, diff_options = {}) + @commits, @diffs = compare.commits, compare.diffs(nil, diff_options) end end end diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 2a447248b7..be19f1abc5 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -23,6 +23,22 @@ describe Projects::CompareController do expect(assigns(:commits).length).to be >= 1 end + it 'compare should show some diffs with ignore whitespace change option' do + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + from: '08f22f25', + to: '66eceea0', + w: 1) + + expect(response).to be_success + expect(assigns(:diffs).length).to be >= 1 + expect(assigns(:commits).length).to be >= 1 + # without whitespace option, there are more than 2 diff_splits + diff_splits = assigns(:diffs)[0].diff.split("\n") + expect(diff_splits.length).to be <= 2 + end + describe 'non-existent refs' do it 'invalid source ref' do get(:show, diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index b8db859170..3e5e1fa87a 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -147,6 +147,34 @@ describe Projects::MergeRequestsController do end end + describe 'GET diffs with ignore_whitespace_change' do + def go(format: 'html') + get :diffs, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: merge_request.iid, + format: format, + w: 1 + end + + context 'as html' do + it 'renders the diff template' do + go + + expect(response).to render_template('diffs') + end + end + + context 'as json' do + it 'renders the diffs template to a string' do + go format: 'json' + + expect(response).to render_template('projects/merge_requests/show/_diffs') + expect(JSON.parse(response.body)).to have_key('html') + end + end + end + describe 'GET commits' do def go(format: 'html') get :commits, From 3c16fb93fcfb7dc50a46a39cbdd545ddfdab0bc1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 09:45:21 +0100 Subject: [PATCH 053/410] Move spec to proper place and fix unused variable Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/inline_diff.rb | 1 - spec/lib/gitlab/{diff => }/inline_diff_spec.rb | 0 2 files changed, 1 deletion(-) rename spec/lib/gitlab/{diff => }/inline_diff_spec.rb (100%) diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb index 72d0b493be..44507bde25 100644 --- a/lib/gitlab/inline_diff.rb +++ b/lib/gitlab/inline_diff.rb @@ -11,7 +11,6 @@ module Gitlab indexes.each do |index| first_line = diff_arr[index+1] second_line = diff_arr[index+2] - max_length = [first_line.size, second_line.size].max # Skip inline diff if empty line was replaced with content next if first_line == "-\n" diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/inline_diff_spec.rb similarity index 100% rename from spec/lib/gitlab/diff/inline_diff_spec.rb rename to spec/lib/gitlab/inline_diff_spec.rb From 6384c757b7ce6d1c0c3e2a3828b0cfac26dfb7f9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 Nov 2015 16:48:03 +0100 Subject: [PATCH 054/410] Expose CI enable option in project features - Enable CI by default for all new projects --- .../projects/application_controller.rb | 2 +- app/controllers/projects_controller.rb | 3 ++- app/helpers/projects_helper.rb | 2 +- app/models/project.rb | 23 ++++++++++++++----- app/services/git_push_service.rb | 2 +- app/services/projects/fork_service.rb | 2 +- .../layouts/nav/_project_settings.html.haml | 2 +- app/views/projects/edit.html.haml | 11 ++++++++- app/views/projects/graphs/_head.html.haml | 2 +- config/gitlab.yml.example | 1 + config/initializers/1_settings.rb | 1 + doc/api/projects.md | 6 +++++ lib/api/entities.rb | 2 +- lib/api/projects.rb | 6 +++++ spec/factories/ci/projects.rb | 12 ++++++---- spec/models/project_spec.rb | 9 +++++--- spec/requests/api/projects_spec.rb | 7 ++++-- spec/requests/api/services_spec.rb | 1 + spec/services/projects/create_service_spec.rb | 22 ++++++++++++++++++ spec/services/projects/fork_service_spec.rb | 2 +- 20 files changed, 93 insertions(+), 25 deletions(-) diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 519d6d6127..d3f926b62b 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -29,7 +29,7 @@ class Projects::ApplicationController < ApplicationController private def ci_enabled - return render_404 unless @project.gitlab_ci? + return render_404 unless @project.builds_enabled? end def ci_project diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 00d13a83ce..30b166334a 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -213,7 +213,8 @@ class ProjectsController < ApplicationController params.require(:project).permit( :name, :path, :description, :issues_tracker, :tag_list, :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch, - :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar + :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar, + :builds_enabled ) end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 5301c2ccf7..690ae2090d 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -117,7 +117,7 @@ module ProjectsHelper nav_tabs << :merge_requests end - if project.gitlab_ci? && can?(current_user, :read_build, project) + if project.builds_enabled? && can?(current_user, :read_build, project) nav_tabs << :builds end diff --git a/app/models/project.rb b/app/models/project.rb index bdb22e49bb..3e72a9a46a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -52,6 +52,7 @@ class Project < ActiveRecord::Base default_value_for :visibility_level, gitlab_config_features.visibility_level default_value_for :issues_enabled, gitlab_config_features.issues default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests + default_value_for :builds_enabled, gitlab_config_features.builds default_value_for :wiki_enabled, gitlab_config_features.wiki default_value_for :wall_enabled, false default_value_for :snippets_enabled, gitlab_config_features.snippets @@ -457,10 +458,6 @@ class Project < ActiveRecord::Base list.find { |service| service.to_param == name } end - def gitlab_ci? - gitlab_ci_service && gitlab_ci_service.active && gitlab_ci_project.present? - end - def ci_services services.select { |service| service.category == :ci } end @@ -782,9 +779,23 @@ class Project < ActiveRecord::Base ) end - def enable_ci + # TODO: this should be migrated to Project table, + # the same as issues_enabled + def builds_enabled + gitlab_ci_service && gitlab_ci_service.active + end + + def builds_enabled? + builds_enabled + end + + def builds_enabled=(value) service = gitlab_ci_service || create_gitlab_ci_service - service.active = true + service.active = value service.save end + + def enable_ci + self.builds_enabled = true + end end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 3de7bb9dca..ccb6b97858 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -60,7 +60,7 @@ class GitPushService # If CI was disabled but .gitlab-ci.yml file was pushed # we enable CI automatically - if !project.gitlab_ci? && gitlab_ci_yaml?(newrev) + if !project.builds_enabled? && gitlab_ci_yaml?(newrev) project.enable_ci end diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index 46374a3909..5da1c7afd9 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -17,7 +17,7 @@ module Projects new_project = CreateService.new(current_user, new_params).execute if new_project.persisted? - if @project.gitlab_ci? + if @project.builds_enabled? new_project.enable_ci settings = @project.gitlab_ci_project.attributes.select do |attr_name, value| diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index a59939ccd3..377a99e719 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -34,7 +34,7 @@ %span Protected Branches - - if @project.gitlab_ci? + - if @project.builds_enabled? = nav_link(controller: :runners) do = link_to namespace_project_runners_path(@project.namespace, @project), title: 'Runners', data: {placement: 'right'} do = icon('cog fw') diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index afbf88b550..3ebc175648 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -57,7 +57,16 @@ = f.check_box :merge_requests_enabled %strong Merge Requests %br - %span.descr Submit changes to be merged upstream. + %span.descr Submit changes to be merged upstream + + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :builds_enabled do + = f.check_box :builds_enabled + %strong Builds + %br + %span.descr Test and deploy your changes before merge .form-group .col-sm-offset-2.col-sm-10 diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml index e0d06a14bf..03d0733f91 100644 --- a/app/views/projects/graphs/_head.html.haml +++ b/app/views/projects/graphs/_head.html.haml @@ -3,7 +3,7 @@ = link_to 'Contributors', namespace_project_graph_path = nav_link(action: :commits) do = link_to 'Commits', commits_namespace_project_graph_path - - if @project.gitlab_ci? + - if @project.builds_enabled? = nav_link(action: :ci) do = link_to ci_namespace_project_graph_path do Continuous Integration diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 20894ebcdc..5bd98e3e42 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -84,6 +84,7 @@ production: &base merge_requests: true wiki: true snippets: false + builds: true ## Webhook settings # Number of seconds to wait for HTTP response after sending webhook HTTP POST request (default: 10) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 302124bd97..847d9f2189 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -171,6 +171,7 @@ Settings.gitlab.default_projects_features['issues'] = true if Settings.g Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.gitlab.default_projects_features['merge_requests'].nil? Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil? Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil? +Settings.gitlab.default_projects_features['builds'] = true if Settings.gitlab.default_projects_features['builds'].nil? Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') if Settings.gitlab['repository_downloads_path'].nil? Settings.gitlab['restricted_signup_domains'] ||= [] diff --git a/doc/api/projects.md b/doc/api/projects.md index 9648585703..755cc6525c 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -60,6 +60,7 @@ Parameters: "path_with_namespace": "diaspora/diaspora-client", "issues_enabled": true, "merge_requests_enabled": true, + "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, "created_at": "2013-09-30T13: 46: 02Z", @@ -101,6 +102,7 @@ Parameters: "path_with_namespace": "brightbox/puppet", "issues_enabled": true, "merge_requests_enabled": true, + "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, "created_at": "2013-09-30T13:46:02Z", @@ -191,6 +193,7 @@ Parameters: "path_with_namespace": "diaspora/diaspora-project-site", "issues_enabled": true, "merge_requests_enabled": true, + "builds_enabled": true, "wiki_enabled": true, "snippets_enabled": false, "created_at": "2013-09-30T13: 46: 02Z", @@ -312,6 +315,7 @@ Parameters: - `description` (optional) - short project description - `issues_enabled` (optional) - `merge_requests_enabled` (optional) +- `builds_enabled` (optional) - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 @@ -334,6 +338,7 @@ Parameters: - `default_branch` (optional) - 'master' by default - `issues_enabled` (optional) - `merge_requests_enabled` (optional) +- `builds_enabled` (optional) - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 @@ -357,6 +362,7 @@ Parameters: - `default_branch` (optional) - `issues_enabled` (optional) - `merge_requests_enabled` (optional) +- `builds_enabled` (optional) - `wiki_enabled` (optional) - `snippets_enabled` (optional) - `public` (optional) - if `true` same as setting visibility_level = 20 diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 20cadae229..73acf66935 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -62,7 +62,7 @@ module API expose :owner, using: Entities::UserBasic, unless: ->(project, options) { project.group } expose :name, :name_with_namespace expose :path, :path_with_namespace - expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at + expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :builds_enabled, :snippets_enabled, :created_at, :last_activity_at expose :creator_id expose :namespace expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? } diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 67ee66a205..2b4ada6e2e 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -75,6 +75,7 @@ module API # description (optional) - short project description # issues_enabled (optional) # merge_requests_enabled (optional) + # builds_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) # namespace_id (optional) - defaults to user namespace @@ -90,6 +91,7 @@ module API :description, :issues_enabled, :merge_requests_enabled, + :builds_enabled, :wiki_enabled, :snippets_enabled, :namespace_id, @@ -117,6 +119,7 @@ module API # default_branch (optional) - 'master' by default # issues_enabled (optional) # merge_requests_enabled (optional) + # builds_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) # public (optional) - if true same as setting visibility_level = 20 @@ -132,6 +135,7 @@ module API :default_branch, :issues_enabled, :merge_requests_enabled, + :builds_enabled, :wiki_enabled, :snippets_enabled, :public, @@ -172,6 +176,7 @@ module API # description (optional) - short project description # issues_enabled (optional) # merge_requests_enabled (optional) + # builds_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) # public (optional) - if true same as setting visibility_level = 20 @@ -185,6 +190,7 @@ module API :default_branch, :issues_enabled, :merge_requests_enabled, + :builds_enabled, :wiki_enabled, :snippets_enabled, :public, diff --git a/spec/factories/ci/projects.rb b/spec/factories/ci/projects.rb index 1183a19035..11cb8c9eea 100644 --- a/spec/factories/ci/projects.rb +++ b/spec/factories/ci/projects.rb @@ -31,16 +31,20 @@ FactoryGirl.define do factory :ci_project_without_token, class: Ci::Project do default_ref 'master' - gl_project factory: :empty_project - shared_runners_enabled false factory :ci_project do token 'iPWx6WM4lhHNedGfBpPJNP' end - factory :ci_public_project do - public true + initialize_with do + # TODO: + # this is required, because builds_enabled is initialized when Project is created + # and this create gitlab_ci_project if builds is set to true + # here we take created gitlab_ci_project and update it's attributes + ci_project = create(:empty_project).ensure_gitlab_ci_project + ci_project.update_attributes(attributes) + ci_project end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f93935ebe3..8d7e6e7676 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -415,12 +415,15 @@ describe Project do it { expect(project.ci_commit(commit.sha)).to eq(commit) } end - describe :enable_ci do + describe :builds_enabled do let(:project) { create :project } - before { project.enable_ci } + before { project.builds_enabled = true } - it { expect(project.gitlab_ci?).to be_truthy } + subject { project.builds_enabled } + + it { is_expected.to eq(project.gitlab_ci_service.active) } + it { expect(project.builds_enabled?).to be_truthy } it { expect(project.gitlab_ci_project).to be_a(Ci::Project) } end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index e9de9e0826..9fc294118a 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -88,8 +88,11 @@ describe API::API, api: true do end it 'returns projects in the correct order when ci_enabled_first parameter is passed' do - [project, project2, project3].each{ |project| project.build_missing_services } - project2.gitlab_ci_service.update(active: true) + [project, project2, project3].each do |project| + project.builds_enabled = false + project.build_missing_services + end + project2.builds_enabled = true get api('/projects', user), { ci_enabled_first: 'true' } expect(response.status).to eq(200) expect(json_response).to be_an Array diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index c0226605a2..b180d2fec7 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -46,6 +46,7 @@ describe API::API, api: true do delete api("/projects/#{project.id}/services/#{dashed_service}", user) expect(response.status).to eq(200) + project.send(service_method).reload expect(project.send(service_method).activated?).to be_falsey end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 25277f0748..e81c4edb7d 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -70,6 +70,28 @@ describe Projects::CreateService do end end + context 'builds_enabled global setting' do + let(:project) { create_project(@user, @opts) } + + subject { project.builds_enabled? } + + context 'global builds_enabled false does not enable CI by default' do + before do + @opts.merge!(builds_enabled: false) + end + + it { is_expected.to be_falsey } + end + + context 'global builds_enabled true does enable CI by default' do + before do + @opts.merge!(builds_enabled: true) + end + + it { is_expected.to be_truthy } + end + end + context 'restricted visibility level' do before do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 65a8c81204..e397b2b9b4 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -46,7 +46,7 @@ describe Projects::ForkService do it "fork and enable CI for fork" do @from_project.enable_ci @to_project = fork_project(@from_project, @to_user) - expect(@to_project.gitlab_ci?).to be_truthy + expect(@to_project.builds_enabled?).to be_truthy end end end From e53a56aceae6e79412a853ac3cfd1e47570135eb Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 12 Nov 2015 16:52:22 +0100 Subject: [PATCH 055/410] Fix broken tests --- app/models/ci/project.rb | 24 ---- spec/models/ci/project_spec.rb | 16 +-- spec/requests/ci/api/builds_spec.rb | 4 +- spec/requests/ci/api/commits_spec.rb | 2 +- spec/requests/ci/api/projects_spec.rb | 116 +++++++----------- spec/requests/ci/api/triggers_spec.rb | 2 +- .../ci/create_trigger_request_service_spec.rb | 2 +- 7 files changed, 48 insertions(+), 118 deletions(-) diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb index 4e806ca1a6..f81417ba27 100644 --- a/app/models/ci/project.rb +++ b/app/models/ci/project.rb @@ -66,30 +66,6 @@ module Ci class << self include Ci::CurrentSettings - def base_build_script - <<-eos - git submodule update --init - ls -la - eos - end - - def parse(project) - params = { - gitlab_id: project.id, - default_ref: project.default_branch || 'master', - email_add_pusher: current_application_settings.add_pusher, - email_only_broken_builds: current_application_settings.all_broken_builds, - } - - project = Ci::Project.new(params) - project.build_missing_services - project - end - - def already_added?(project) - where(gitlab_id: project.id).any? - end - def unassigned(runner) joins("LEFT JOIN #{Ci::RunnerProject.table_name} ON #{Ci::RunnerProject.table_name}.project_id = #{Ci::Project.table_name}.id " \ "AND #{Ci::RunnerProject.table_name}.runner_id = #{runner.id}"). diff --git a/spec/models/ci/project_spec.rb b/spec/models/ci/project_spec.rb index 490c6a6798..4546cbfad1 100644 --- a/spec/models/ci/project_spec.rb +++ b/spec/models/ci/project_spec.rb @@ -28,8 +28,8 @@ require 'spec_helper' describe Ci::Project do - let(:gl_project) { FactoryGirl.create :empty_project } - let(:project) { FactoryGirl.create :ci_project, gl_project: gl_project } + let(:project) { FactoryGirl.create :ci_project } + let(:gl_project) { project.gl_project } subject { project } it { is_expected.to have_many(:runner_projects) } @@ -194,18 +194,6 @@ describe Ci::Project do end end - describe 'Project.parse' do - let(:project) { FactoryGirl.create :project } - - subject { Ci::Project.parse(project) } - - it { is_expected.to be_valid } - it { is_expected.to be_kind_of(Ci::Project) } - it { expect(subject.name).to eq(project.name_with_namespace) } - it { expect(subject.gitlab_id).to eq(project.id) } - it { expect(subject.gitlab_url).to eq(project.web_url) } - end - describe :repo_url_with_auth do let(:project) { FactoryGirl.create :ci_project } subject { project.repo_url_with_auth } diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 7886a6feca..392dc2021f 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -5,7 +5,7 @@ describe Ci::API::API do let(:runner) { FactoryGirl.create(:ci_runner, tag_list: ["mysql", "ruby"]) } let(:project) { FactoryGirl.create(:ci_project) } - let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) } + let(:gl_project) { project.gl_project } before do stub_ci_commit_to_return_yaml_file @@ -14,7 +14,7 @@ describe Ci::API::API do describe "Builds API for runners" do let(:shared_runner) { FactoryGirl.create(:ci_runner, token: "SharedRunner") } let(:shared_project) { FactoryGirl.create(:ci_project, name: "SharedProject") } - let(:shared_gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: shared_project) } + let(:shared_gl_project) { shared_project.gl_project } before do FactoryGirl.create :ci_runner_project, project_id: project.id, runner_id: runner.id diff --git a/spec/requests/ci/api/commits_spec.rb b/spec/requests/ci/api/commits_spec.rb index 6049135fd1..aa51ba95bc 100644 --- a/spec/requests/ci/api/commits_spec.rb +++ b/spec/requests/ci/api/commits_spec.rb @@ -4,7 +4,7 @@ describe Ci::API::API, 'Commits' do include ApiHelpers let(:project) { FactoryGirl.create(:ci_project) } - let(:gl_project) { FactoryGirl.create(:empty_project, gitlab_ci_project: project) } + let(:gl_project) { project.gl_project } let(:commit) { FactoryGirl.create(:ci_commit, gl_project: gl_project) } let(:options) do diff --git a/spec/requests/ci/api/projects_spec.rb b/spec/requests/ci/api/projects_spec.rb index 53f7f91cc1..893fd168d3 100644 --- a/spec/requests/ci/api/projects_spec.rb +++ b/spec/requests/ci/api/projects_spec.rb @@ -41,8 +41,8 @@ describe Ci::API::API do describe "GET /projects/owned" do let!(:gl_project1) {FactoryGirl.create(:empty_project, namespace: user.namespace)} let!(:gl_project2) {FactoryGirl.create(:empty_project, namespace: user.namespace)} - let!(:project1) { FactoryGirl.create(:ci_project, gl_project: gl_project1) } - let!(:project2) { FactoryGirl.create(:ci_project, gl_project: gl_project2) } + let!(:project1) { gl_project1.ensure_gitlab_ci_project } + let!(:project2) { gl_project2.ensure_gitlab_ci_project } before do project1.gl_project.team << [user, :developer] @@ -180,87 +180,53 @@ describe Ci::API::API do end end - describe "POST /projects" do - let(:gl_project) { FactoryGirl.create :empty_project } - let(:project_info) do - { - gitlab_id: gl_project.id - } + describe "POST /projects/:id/runners/:id" do + let(:project) { FactoryGirl.create(:ci_project) } + let(:runner) { FactoryGirl.create(:ci_runner) } + + it "should add the project to the runner" do + project.gl_project.team << [user, :master] + post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(201) + + project.reload + expect(project.runners.first.id).to eq(runner.id) end - let(:invalid_project_info) { {} } + it "should fail if it tries to link a non-existing project or runner" do + post ci_api("/projects/#{project.id}/runners/non-existing"), options + expect(response.status).to eq(404) - context "with valid project info" do - before do - options.merge!(project_info) - end - - it "should create a project with valid data" do - post ci_api("/projects"), options - expect(response.status).to eq(201) - expect(json_response['name']).to eq(gl_project.name_with_namespace) - end + post ci_api("/projects/non-existing/runners/#{runner.id}"), options + expect(response.status).to eq(404) end - context "with invalid project info" do - before do - options.merge!(invalid_project_info) - end + it "non-manager is not authorized" do + allow_any_instance_of(User).to receive(:can_manage_project?).and_return(false) + post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(401) + end + end - it "should error with invalid data" do - post ci_api("/projects"), options - expect(response.status).to eq(400) - end + describe "DELETE /projects/:id/runners/:id" do + let(:project) { FactoryGirl.create(:ci_project) } + let(:runner) { FactoryGirl.create(:ci_runner) } + + it "should remove the project from the runner" do + project.gl_project.team << [user, :master] + post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + + expect(project.runners).to be_present + delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(200) + + project.reload + expect(project.runners).to be_empty end - describe "POST /projects/:id/runners/:id" do - let(:project) { FactoryGirl.create(:ci_project) } - let(:runner) { FactoryGirl.create(:ci_runner) } - - it "should add the project to the runner" do - project.gl_project.team << [user, :master] - post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(201) - - project.reload - expect(project.runners.first.id).to eq(runner.id) - end - - it "should fail if it tries to link a non-existing project or runner" do - post ci_api("/projects/#{project.id}/runners/non-existing"), options - expect(response.status).to eq(404) - - post ci_api("/projects/non-existing/runners/#{runner.id}"), options - expect(response.status).to eq(404) - end - - it "non-manager is not authorized" do - allow_any_instance_of(User).to receive(:can_manage_project?).and_return(false) - post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(401) - end - end - - describe "DELETE /projects/:id/runners/:id" do - let(:project) { FactoryGirl.create(:ci_project) } - let(:runner) { FactoryGirl.create(:ci_runner) } - - it "should remove the project from the runner" do - project.gl_project.team << [user, :master] - post ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - - expect(project.runners).to be_present - delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(200) - - project.reload - expect(project.runners).to be_empty - end - - it "non-manager is not authorized" do - delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options - expect(response.status).to eq(401) - end + it "non-manager is not authorized" do + delete ci_api("/projects/#{project.id}/runners/#{runner.id}"), options + expect(response.status).to eq(401) end end end diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb index 93617fc4b3..a2b436d581 100644 --- a/spec/requests/ci/api/triggers_spec.rb +++ b/spec/requests/ci/api/triggers_spec.rb @@ -6,7 +6,7 @@ describe Ci::API::API do describe 'POST /projects/:project_id/refs/:ref/trigger' do let!(:trigger_token) { 'secure token' } let!(:gl_project) { FactoryGirl.create(:project) } - let!(:project) { FactoryGirl.create(:ci_project, gl_project: gl_project) } + let!(:project) { gl_project.ensure_gitlab_ci_project } let!(:project2) { FactoryGirl.create(:ci_project) } let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) } let(:options) do diff --git a/spec/services/ci/create_trigger_request_service_spec.rb b/spec/services/ci/create_trigger_request_service_spec.rb index fcafae3864..2ef4bb50a5 100644 --- a/spec/services/ci/create_trigger_request_service_spec.rb +++ b/spec/services/ci/create_trigger_request_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Ci::CreateTriggerRequestService do let(:service) { Ci::CreateTriggerRequestService.new } let(:gl_project) { create(:project) } - let(:project) { create(:ci_project, gl_project: gl_project) } + let(:project) { gl_project.ensure_gitlab_ci_project } let(:trigger) { create(:ci_trigger, project: project) } before do From f36698663cc3a87cc5deec07c693020d97e174f7 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 13 Nov 2015 11:15:17 +0100 Subject: [PATCH 056/410] Bump the GITLAB_WORKHORSE version to 0.4.1 [ci skip] --- CHANGELOG | 1 + GITLAB_WORKHORSE | 1 + 2 files changed, 2 insertions(+) create mode 100644 GITLAB_WORKHORSE diff --git a/CHANGELOG b/CHANGELOG index c766e8ea98..1bd60728e6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -21,6 +21,7 @@ v 8.2.0 (unreleased) - Send build name and stage in CI notification e-mail - Extend yml syntax for only and except to support specifying repository path - Enable shared runners to all new projects + - Bump GitLab-Workhorse to 0.4.1 - Allow to define cache in `.gitlab-ci.yml` - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - Remove deprecated CI events from project settings page diff --git a/GITLAB_WORKHORSE b/GITLAB_WORKHORSE new file mode 100644 index 0000000000..267577d47e --- /dev/null +++ b/GITLAB_WORKHORSE @@ -0,0 +1 @@ +0.4.1 From fea2f214370420cdb86336881bfbcb24a994f6c6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 13 Nov 2015 14:41:19 +0100 Subject: [PATCH 057/410] Fix caching breaking test of build artifacts --- spec/requests/ci/api/builds_spec.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 392dc2021f..c2be045099 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -160,15 +160,13 @@ describe Ci::API::API do end it "using token as parameter" do - settings = Gitlab::CurrentSettings::current_application_settings - settings.update_attributes(max_artifacts_size: 0) + stub_application_setting(max_artifacts_size: 0) post authorize_url, { token: build.project.token, filesize: 100 }, headers expect(response.status).to eq(413) end it "using token as header" do - settings = Gitlab::CurrentSettings::current_application_settings - settings.update_attributes(max_artifacts_size: 0) + stub_application_setting(max_artifacts_size: 0) post authorize_url, { filesize: 100 }, headers_with_token expect(response.status).to eq(413) end @@ -220,8 +218,7 @@ describe Ci::API::API do end it do - settings = Gitlab::CurrentSettings::current_application_settings - settings.update_attributes(max_artifacts_size: 0) + stub_application_setting(max_artifacts_size: 0) upload_artifacts(file_upload, headers_with_token) expect(response.status).to eq(413) end From 2c13723150b9095bb5b1482c62bab7817fd9a49e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 13 Nov 2015 12:58:43 -0500 Subject: [PATCH 058/410] Update doc/install/installation.md for 8-2-stable [ci skip] --- doc/install/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 8028e51dbc..698f54cabc 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -211,9 +211,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-1-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-2-stable gitlab -**Note:** You can change `8-1-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `8-2-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It From b8ab012544e584210b8b41219f85babdab091f59 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 13 Nov 2015 12:59:03 -0500 Subject: [PATCH 059/410] Update doc/install/installation.md for gitlab-workhorse [ci skip] --- doc/install/installation.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 698f54cabc..1479bdcb76 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -128,11 +128,10 @@ Install the Bundler Gem: ## 3. Go -Since GitLab 8.0, Git HTTP requests are handled by gitlab-git-http-server. -This is a small daemon written in Go. -To install gitlab-git-http-server we need a Go compiler. -The instructions below assume you use 64-bit Linux. You can find -downloads for other platforms at the [Go download +Since GitLab 8.0, Git HTTP requests are handled by gitlab-workhorse (formerly +gitlab-git-http-server). This is a small daemon written in Go. To install +gitlab-workhorse we need a Go compiler. The instructions below assume you +use 64-bit Linux. You can find downloads for other platforms at the [Go download page](https://golang.org/dl). curl -O --progress https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz @@ -323,16 +322,16 @@ GitLab Shell is an SSH access and repository management software developed speci **Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up gitlab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)". -### Install gitlab-git-http-server +### Install gitlab-workhorse cd /home/git - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-git-http-server.git - cd gitlab-git-http-server - sudo -u git -H git checkout 0.3.0 + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git + cd gitlab-workhorse + sudo -u git -H git checkout 0.4.1 sudo -u git -H make ### Initialize Database and Activate Advanced Features - + # Go to Gitlab installation folder cd /home/git/gitlab From a060a8dad5b44d3da20485123937ddf89169f8c7 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 13 Nov 2015 13:06:52 -0500 Subject: [PATCH 060/410] Update doc/update/8.1-to-8.2.md [ci skip] --- doc/update/8.1-to-8.2.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/doc/update/8.1-to-8.2.md b/doc/update/8.1-to-8.2.md index 3772f624e9..73d899f9c2 100644 --- a/doc/update/8.1-to-8.2.md +++ b/doc/update/8.1-to-8.2.md @@ -2,7 +2,8 @@ **NOTE:** GitLab 8.0 introduced several significant changes related to installation and configuration which *are not duplicated here*. Be sure you're -already running a working version of 8.0 before proceeding with this guide. +already running a working version of at least 8.0 before proceeding with this +guide. ### 0. Double-check your Git version @@ -67,7 +68,7 @@ sudo -u git -H git checkout 8-2-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v2.6.5 +sudo -u git -H git checkout v2.6.6 ``` ### 5. Replace gitlab-git-http-server with gitlab-workhorse @@ -80,7 +81,7 @@ from GitLab 8.1. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse -sudo -u git -H git checkout 0.3.1 +sudo -u git -H git checkout 0.4.1 sudo -u git -H make ``` @@ -165,12 +166,12 @@ To make sure you didn't miss anything run a more thorough check: If all items are green, then congratulations, the upgrade is complete! -## Things went south? Revert to previous version (8.0) +## Things went south? Revert to previous version (8.1) ### 1. Revert the code to the previous version -Follow the [upgrade guide from 7.14 to 8.0](7.14-to-8.0.md), except for the database migration -(The backup is already migrated to the previous version) +Follow the [upgrade guide from 8.0 to 8.1](8.0-to-8.1.md), except for the +database migration (the backup is already migrated to the previous version). ### 2. Restore from the backup From 47b4b91ecd26066a04cc4b506dd62ccaed67bbdf Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 13 Nov 2015 13:10:34 -0500 Subject: [PATCH 061/410] Reorder monthly release tasks for RC1 day It didn't make sense to merge CE master into EE master, and *then* do the RC1 tasks (e.g., install/update docs) because then the RC1 won't have the updated docs. --- doc/release/monthly.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 4925816daa..d347f58ba0 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -37,9 +37,9 @@ template are explained below: ### Xth: (6 working days before the 22nd) -- [ ] Merge CE `master` into EE `master` via merge request (#LINK) - [ ] Determine QA person and notify this person - [ ] Check the tasks in [how to rc1 guide](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/release/howto_rc1.md) and delegate tasks if necessary +- [ ] Merge CE `master` into EE `master` via merge request (#LINK) - [ ] Create CE and EE RC1 versions (#LINK) - [ ] Build RC1 packages From a237999f000526b3db5b0b5a72a665adcff29522 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 19:22:46 +0100 Subject: [PATCH 062/410] Annotate models Signed-off-by: Dmitriy Zaporozhets --- app/models/application_setting.rb | 4 +++ app/models/ci/application_setting.rb | 2 +- app/models/ci/build.rb | 14 ++++++-- app/models/ci/commit.rb | 25 +++++++------- app/models/ci/event.rb | 2 +- app/models/ci/project.rb | 4 +-- app/models/ci/runner.rb | 2 +- app/models/ci/runner_project.rb | 2 +- app/models/ci/service.rb | 2 +- app/models/ci/trigger.rb | 2 +- app/models/ci/trigger_request.rb | 2 +- app/models/ci/variable.rb | 2 +- app/models/ci/web_hook.rb | 2 +- app/models/commit_status.rb | 33 +++++++++++++++++++ app/models/generic_commit_status.rb | 33 +++++++++++++++++++ app/models/group.rb | 1 + app/models/hooks/project_hook.rb | 25 +++++++------- app/models/hooks/service_hook.rb | 25 +++++++------- app/models/hooks/system_hook.rb | 25 +++++++------- app/models/hooks/web_hook.rb | 25 +++++++------- app/models/label.rb | 1 + app/models/merge_request.rb | 1 + app/models/namespace.rb | 1 + .../project_services/ci/hip_chat_service.rb | 2 +- .../project_services/ci/mail_service.rb | 2 +- .../project_services/ci/slack_service.rb | 2 +- app/models/release.rb | 12 +++++++ app/models/user.rb | 1 + spec/factories/labels.rb | 1 + spec/factories/merge_requests.rb | 1 + spec/factories/releases.rb | 12 +++++++ spec/models/application_setting_spec.rb | 4 +++ spec/models/ci/commit_spec.rb | 25 +++++++------- spec/models/ci/project_spec.rb | 4 +-- spec/models/ci/runner_project_spec.rb | 2 +- spec/models/ci/runner_spec.rb | 2 +- spec/models/ci/service_spec.rb | 2 +- spec/models/ci/trigger_spec.rb | 12 +++++++ spec/models/ci/variable_spec.rb | 2 +- spec/models/ci/web_hook_spec.rb | 2 +- spec/models/commit_status_spec.rb | 33 +++++++++++++++++++ spec/models/generic_commit_status_spec.rb | 33 +++++++++++++++++++ spec/models/group_spec.rb | 1 + spec/models/label_spec.rb | 1 + spec/models/merge_request_spec.rb | 1 + spec/models/namespace_spec.rb | 1 + spec/models/release_spec.rb | 12 +++++++ spec/models/user_spec.rb | 2 ++ 48 files changed, 311 insertions(+), 96 deletions(-) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 6d2711268e..9e70247ef5 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -23,6 +23,10 @@ # after_sign_out_path :string(255) # session_expire_delay :integer default(10080), not null # import_sources :text +# help_page_text :text +# admin_notification_email :string(255) +# shared_runners_enabled :boolean default(TRUE), not null +# max_artifacts_size :integer default(100), not null # class ApplicationSetting < ActiveRecord::Base diff --git a/app/models/ci/application_setting.rb b/app/models/ci/application_setting.rb index 4ab3e2dcbb..1307fa0b47 100644 --- a/app/models/ci/application_setting.rb +++ b/app/models/ci/application_setting.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: application_settings +# Table name: ci_application_settings # # id :integer not null, primary key # all_broken_builds :boolean diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 0ec7e21032..e78b154084 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: builds +# Table name: ci_builds # # id :integer not null, primary key # project_id :integer @@ -11,16 +11,24 @@ # updated_at :datetime # started_at :datetime # runner_id :integer -# commit_id :integer # coverage :float +# commit_id :integer # commands :text # job_id :integer # name :string(255) +# deploy :boolean default(FALSE) # options :text # allow_failure :boolean default(FALSE), not null # stage :string(255) -# deploy :boolean default(FALSE) # trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text # module Ci diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index e58420d82d..33b5717392 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -1,18 +1,19 @@ # == Schema Information # -# Table name: commits +# Table name: ci_commits # -# id :integer not null, primary key -# project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) -# push_data :text -# created_at :datetime -# updated_at :datetime -# tag :boolean default(FALSE) -# yaml_errors :text -# committed_at :datetime +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime +# tag :boolean default(FALSE) +# yaml_errors :text +# committed_at :datetime +# gl_project_id :integer # module Ci diff --git a/app/models/ci/event.rb b/app/models/ci/event.rb index cac3a7a49c..8c39be4267 100644 --- a/app/models/ci/event.rb +++ b/app/models/ci/event.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: events +# Table name: ci_events # # id :integer not null, primary key # project_id :integer diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb index 4e806ca1a6..b3e05e42e9 100644 --- a/app/models/ci/project.rb +++ b/app/models/ci/project.rb @@ -1,9 +1,9 @@ # == Schema Information # -# Table name: projects +# Table name: ci_projects # # id :integer not null, primary key -# name :string(255) not null +# name :string(255) # timeout :integer default(3600), not null # created_at :datetime # updated_at :datetime diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index b719ad3c87..8971048581 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: runners +# Table name: ci_runners # # id :integer not null, primary key # token :string(255) diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb index 44453ee4b4..3f4fc43873 100644 --- a/app/models/ci/runner_project.rb +++ b/app/models/ci/runner_project.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: runner_projects +# Table name: ci_runner_projects # # id :integer not null, primary key # runner_id :integer not null diff --git a/app/models/ci/service.rb b/app/models/ci/service.rb index ed5e3f940b..8063c51e82 100644 --- a/app/models/ci/service.rb +++ b/app/models/ci/service.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: services +# Table name: ci_services # # id :integer not null, primary key # type :string(255) diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index fe224b7dc7..b73c35d5ae 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: triggers +# Table name: ci_triggers # # id :integer not null, primary key # token :string(255) diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb index 29cd955339..9973d2e5ad 100644 --- a/app/models/ci/trigger_request.rb +++ b/app/models/ci/trigger_request.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: trigger_requests +# Table name: ci_trigger_requests # # id :integer not null, primary key # trigger_id :integer not null diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index 7a542802fa..b3d2b809e0 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: variables +# Table name: ci_variables # # id :integer not null, primary key # project_id :integer not null diff --git a/app/models/ci/web_hook.rb b/app/models/ci/web_hook.rb index 8f03b0625d..7ca16a1bde 100644 --- a/app/models/ci/web_hook.rb +++ b/app/models/ci/web_hook.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: web_hooks +# Table name: ci_web_hooks # # id :integer not null, primary key # url :string(255) not null diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index d346c5d35d..e70f4d3718 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -1,3 +1,36 @@ +# == Schema Information +# +# Table name: ci_builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# coverage :float +# commit_id :integer +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text +# + class CommitStatus < ActiveRecord::Base self.table_name = 'ci_builds' diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb index fa54e3540d..12c934e249 100644 --- a/app/models/generic_commit_status.rb +++ b/app/models/generic_commit_status.rb @@ -1,3 +1,36 @@ +# == Schema Information +# +# Table name: ci_builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# coverage :float +# commit_id :integer +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text +# + class GenericCommitStatus < CommitStatus before_validation :set_default_values diff --git a/app/models/group.rb b/app/models/group.rb index 34904af3b5..793a3b5ef2 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -11,6 +11,7 @@ # type :string(255) # description :string(255) default(""), not null # avatar :string(255) +# public :boolean default(FALSE) # require 'carrierwave/orm/activerecord' diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb index ca7066b959..337b309712 100644 --- a/app/models/hooks/project_hook.rb +++ b/app/models/hooks/project_hook.rb @@ -2,18 +2,19 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# note_events :boolean default(FALSE), not null +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null +# tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null +# enable_ssl_verification :boolean default(TRUE) # class ProjectHook < WebHook diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb index b55e217975..09bb3ee52a 100644 --- a/app/models/hooks/service_hook.rb +++ b/app/models/hooks/service_hook.rb @@ -2,18 +2,19 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# note_events :boolean default(FALSE), not null +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null +# tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null +# enable_ssl_verification :boolean default(TRUE) # class ServiceHook < WebHook diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb index 6fb2d42102..2f63c59b07 100644 --- a/app/models/hooks/system_hook.rb +++ b/app/models/hooks/system_hook.rb @@ -2,18 +2,19 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# note_events :boolean default(FALSE), not null +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null +# tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null +# enable_ssl_verification :boolean default(TRUE) # class SystemHook < WebHook diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index a078accbdb..d6c6f415c4 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -2,18 +2,19 @@ # # Table name: web_hooks # -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) default("ProjectHook") -# service_id :integer -# push_events :boolean default(TRUE), not null -# issues_events :boolean default(FALSE), not null -# merge_requests_events :boolean default(FALSE), not null -# tag_push_events :boolean default(FALSE) -# note_events :boolean default(FALSE), not null +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# type :string(255) default("ProjectHook") +# service_id :integer +# push_events :boolean default(TRUE), not null +# issues_events :boolean default(FALSE), not null +# merge_requests_events :boolean default(FALSE), not null +# tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null +# enable_ssl_verification :boolean default(TRUE) # class WebHook < ActiveRecord::Base diff --git a/app/models/label.rb b/app/models/label.rb index 1bb4b5f55c..b306aecbac 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -8,6 +8,7 @@ # project_id :integer # created_at :datetime # updated_at :datetime +# template :boolean default(FALSE) # class Label < ActiveRecord::Base diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index e81d65c233..2eb03b8ba5 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -20,6 +20,7 @@ # position :integer default(0) # locked_at :datetime # updated_by_id :integer +# merge_error :string(255) # require Rails.root.join("app/models/commit") diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 5782e649f8..20b92e68d6 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -11,6 +11,7 @@ # type :string(255) # description :string(255) default(""), not null # avatar :string(255) +# public :boolean default(FALSE) # class Namespace < ActiveRecord::Base diff --git a/app/models/project_services/ci/hip_chat_service.rb b/app/models/project_services/ci/hip_chat_service.rb index f17993d9f3..0df03890ef 100644 --- a/app/models/project_services/ci/hip_chat_service.rb +++ b/app/models/project_services/ci/hip_chat_service.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: services +# Table name: ci_services # # id :integer not null, primary key # type :string(255) diff --git a/app/models/project_services/ci/mail_service.rb b/app/models/project_services/ci/mail_service.rb index fd19330100..d31dd6899c 100644 --- a/app/models/project_services/ci/mail_service.rb +++ b/app/models/project_services/ci/mail_service.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: services +# Table name: ci_services # # id :integer not null, primary key # type :string(255) diff --git a/app/models/project_services/ci/slack_service.rb b/app/models/project_services/ci/slack_service.rb index ee8e498882..7064bfe78d 100644 --- a/app/models/project_services/ci/slack_service.rb +++ b/app/models/project_services/ci/slack_service.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: services +# Table name: ci_services # # id :integer not null, primary key # type :string(255) diff --git a/app/models/release.rb b/app/models/release.rb index e196b84eb1..89f70278af 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: releases +# +# id :integer not null, primary key +# tag :string(255) +# description :text +# project_id :integer +# created_at :datetime +# updated_at :datetime +# + class Release < ActiveRecord::Base belongs_to :project diff --git a/app/models/user.rb b/app/models/user.rb index 67fef1c1e6..9ffadcf446 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -54,6 +54,7 @@ # public_email :string(255) default(""), not null # dashboard :integer default(0) # project_view :integer default(0) +# consumed_timestep :integer # layout :integer default(0) # diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index 6829387c66..8b12ee11af 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -8,6 +8,7 @@ # project_id :integer # created_at :datetime # updated_at :datetime +# template :boolean default(FALSE) # # Read about factories at https://github.com/thoughtbot/factory_girl diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 6080d0ccde..729a49c9f7 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -20,6 +20,7 @@ # position :integer default(0) # locked_at :datetime # updated_by_id :integer +# merge_error :string(255) # FactoryGirl.define do diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb index 80d6bbee6c..43d09b1753 100644 --- a/spec/factories/releases.rb +++ b/spec/factories/releases.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: releases +# +# id :integer not null, primary key +# tag :string(255) +# description :text +# project_id :integer +# created_at :datetime +# updated_at :datetime +# + # Read about factories at https://github.com/thoughtbot/factory_girl FactoryGirl.define do diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index f01fe8bd39..dfbac7b400 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -23,6 +23,10 @@ # after_sign_out_path :string(255) # session_expire_delay :integer default(10080), not null # import_sources :text +# help_page_text :text +# admin_notification_email :string(255) +# shared_runners_enabled :boolean default(TRUE), not null +# max_artifacts_size :integer default(100), not null # require 'spec_helper' diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index 44dbd083f0..a13f6458ca 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -1,18 +1,19 @@ # == Schema Information # -# Table name: commits +# Table name: ci_commits # -# id :integer not null, primary key -# project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) -# push_data :text -# created_at :datetime -# updated_at :datetime -# tag :boolean default(FALSE) -# yaml_errors :text -# committed_at :datetime +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime +# tag :boolean default(FALSE) +# yaml_errors :text +# committed_at :datetime +# gl_project_id :integer # require 'spec_helper' diff --git a/spec/models/ci/project_spec.rb b/spec/models/ci/project_spec.rb index 490c6a6798..a0864442d9 100644 --- a/spec/models/ci/project_spec.rb +++ b/spec/models/ci/project_spec.rb @@ -1,9 +1,9 @@ # == Schema Information # -# Table name: projects +# Table name: ci_projects # # id :integer not null, primary key -# name :string(255) not null +# name :string(255) # timeout :integer default(3600), not null # created_at :datetime # updated_at :datetime diff --git a/spec/models/ci/runner_project_spec.rb b/spec/models/ci/runner_project_spec.rb index 0218d48413..37682c6ea0 100644 --- a/spec/models/ci/runner_project_spec.rb +++ b/spec/models/ci/runner_project_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: runner_projects +# Table name: ci_runner_projects # # id :integer not null, primary key # runner_id :integer not null diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index f8a51c29dc..9a1233b909 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: runners +# Table name: ci_runners # # id :integer not null, primary key # token :string(255) diff --git a/spec/models/ci/service_spec.rb b/spec/models/ci/service_spec.rb index 2df70e8888..36cda988eb 100644 --- a/spec/models/ci/service_spec.rb +++ b/spec/models/ci/service_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: services +# Table name: ci_services # # id :integer not null, primary key # type :string(255) diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index 19c14ef2da..b8aa3c1e77 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: ci_triggers +# +# id :integer not null, primary key +# token :string(255) +# project_id :integer not null +# deleted_at :datetime +# created_at :datetime +# updated_at :datetime +# + require 'spec_helper' describe Ci::Trigger do diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index d034a6c7b9..a515f5881f 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: variables +# Table name: ci_variables # # id :integer not null, primary key # project_id :integer not null diff --git a/spec/models/ci/web_hook_spec.rb b/spec/models/ci/web_hook_spec.rb index bf9481ab81..2865482a21 100644 --- a/spec/models/ci/web_hook_spec.rb +++ b/spec/models/ci/web_hook_spec.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: web_hooks +# Table name: ci_web_hooks # # id :integer not null, primary key # url :string(255) not null diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index c96a606fda..dca0715eed 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -1,3 +1,36 @@ +# == Schema Information +# +# Table name: ci_builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# coverage :float +# commit_id :integer +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text +# + require 'spec_helper' describe CommitStatus do diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index f442fa5fbe..c86314c454 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -1,3 +1,36 @@ +# == Schema Information +# +# Table name: ci_builds +# +# id :integer not null, primary key +# project_id :integer +# status :string(255) +# finished_at :datetime +# trace :text +# created_at :datetime +# updated_at :datetime +# started_at :datetime +# runner_id :integer +# coverage :float +# commit_id :integer +# commands :text +# job_id :integer +# name :string(255) +# deploy :boolean default(FALSE) +# options :text +# allow_failure :boolean default(FALSE), not null +# stage :string(255) +# trigger_request_id :integer +# stage_idx :integer +# tag :boolean +# ref :string(255) +# user_id :integer +# type :string(255) +# target_url :string(255) +# description :string(255) +# artifacts_file :text +# + require 'spec_helper' describe GenericCommitStatus do diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 0f23e81ace..bbfc5535ee 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -11,6 +11,7 @@ # type :string(255) # description :string(255) default(""), not null # avatar :string(255) +# public :boolean default(FALSE) # require 'spec_helper' diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 6518213d71..511ee8cbd9 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -8,6 +8,7 @@ # project_id :integer # created_at :datetime # updated_at :datetime +# template :boolean default(FALSE) # require 'spec_helper' diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index eed2cbc541..90af75ff0e 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -20,6 +20,7 @@ # position :integer default(0) # locked_at :datetime # updated_by_id :integer +# merge_error :string(255) # require 'spec_helper' diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 1d72a9503a..a98b9cb732 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -11,6 +11,7 @@ # type :string(255) # description :string(255) default(""), not null # avatar :string(255) +# public :boolean default(FALSE) # require 'spec_helper' diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index 527005b2b6..72ecb442a3 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -1,3 +1,15 @@ +# == Schema Information +# +# Table name: releases +# +# id :integer not null, primary key +# tag :string(255) +# description :text +# project_id :integer +# created_at :datetime +# updated_at :datetime +# + require 'rails_helper' RSpec.describe Release, type: :model do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 49e0bfdd2e..7d716c2312 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -54,6 +54,8 @@ # public_email :string(255) default(""), not null # dashboard :integer default(0) # project_view :integer default(0) +# consumed_timestep :integer +# layout :integer default(0) # require 'spec_helper' From 4bb99677b1ddee84ab0433efe081de8025e5aa53 Mon Sep 17 00:00:00 2001 From: Alec Cooper Date: Thu, 12 Nov 2015 21:38:26 -0500 Subject: [PATCH 063/410] Relative links in the README file shown on the repository homepage should point to the default branch, not to master --- CHANGELOG | 1 + lib/gitlab/markdown/relative_link_filter.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 1bd60728e6..5d22e95091 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -39,6 +39,7 @@ v 8.2.0 (unreleased) - Improve Continuous Integration graphs page - Make color of "Accept Merge Request" button consistent with current build status - Add ignore white space option in merge request diff and commit and compare view + - Relative links from a repositories README.md now link to the default branch v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) diff --git a/lib/gitlab/markdown/relative_link_filter.rb b/lib/gitlab/markdown/relative_link_filter.rb index 6ee3d1ce03..632be4d754 100644 --- a/lib/gitlab/markdown/relative_link_filter.rb +++ b/lib/gitlab/markdown/relative_link_filter.rb @@ -51,7 +51,7 @@ module Gitlab relative_url_root, context[:project].path_with_namespace, path_type(file_path), - ref || 'master', # assume that if no ref exists we can point to master + ref || context[:project].default_branch, # if no ref exists, point to the default branch file_path ].compact.join('/').squeeze('/').chomp('/') From 9337da7864756116f3865e14d46227ffe857d0e5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 15 Nov 2015 21:37:13 -0500 Subject: [PATCH 064/410] Revert "Merge pull request #9812 from chrspeich/hide-tabs-lone-auth-provider" This reverts commit 84999611d8f7894219eb4ebc76555c79b1f14794, reversing changes made to 0d9fb211f3f842d10e1c57dcb9d3d42a9c11cd0b. --- app/views/devise/shared/_signin_box.html.haml | 46 ++++++++----------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml index 9a1331b254..41ad2c231d 100644 --- a/app/views/devise/shared/_signin_box.html.haml +++ b/app/views/devise/shared/_signin_box.html.haml @@ -7,34 +7,26 @@ %h3 Sign in .login-body - if form_based_providers.any? - - if form_based_providers.count >= 2 || signin_enabled? - %ul.nav.nav-tabs - - if crowd_enabled? - %li.active - = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' - - @ldap_servers.each_with_index do |server, i| - %li{class: (:active if i.zero? && !crowd_enabled?)} - = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' - - if signin_enabled? - %li - = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' - .tab-content - - if crowd_enabled? - %div.tab-pane.active{id: "tab-crowd"} - = render 'devise/sessions/new_crowd' - - @ldap_servers.each_with_index do |server, i| - %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} - = render 'devise/sessions/new_ldap', server: server - - if signin_enabled? - %div#tab-signin.tab-pane - = render 'devise/sessions/new_base' - - else + %ul.nav.nav-tabs - if crowd_enabled? - = render 'devise/sessions/new_crowd' - - elsif @ldap_servers.any? - = render 'devise/sessions/new_ldap', server: @ldap_servers.first - - elsif signin_enabled? - = render 'devise/sessions/new_base' + %li.active + = link_to "Crowd", "#tab-crowd", 'data-toggle' => 'tab' + - @ldap_servers.each_with_index do |server, i| + %li{class: (:active if i.zero? && !crowd_enabled?)} + = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' + - if signin_enabled? + %li + = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' + .tab-content + - if crowd_enabled? + %div.tab-pane.active{id: "tab-crowd"} + = render 'devise/sessions/new_crowd' + - @ldap_servers.each_with_index do |server, i| + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero? && !crowd_enabled?)} + = render 'devise/sessions/new_ldap', server: server + - if signin_enabled? + %div#tab-signin.tab-pane + = render 'devise/sessions/new_base' - elsif signin_enabled? = render 'devise/sessions/new_base' From 743d66e4da5c11f638d12a7f7ec6354631ee2b90 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 10:11:16 +0100 Subject: [PATCH 065/410] Improve english text Signed-off-by: Dmitriy Zaporozhets --- doc/api/tags.md | 2 +- doc/workflow/releases.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/api/tags.md b/doc/api/tags.md index 0113d4ba04..b5b90cf6b8 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -40,7 +40,7 @@ Parameters: ## Create a new tag -Creates new tag in the repository that points to the supplied ref. +Creates a new tag in the repository that points to the supplied ref. ``` POST /projects/:id/repository/tags diff --git a/doc/workflow/releases.md b/doc/workflow/releases.md index 0eca220e2c..6176784fc5 100644 --- a/doc/workflow/releases.md +++ b/doc/workflow/releases.md @@ -1,14 +1,14 @@ # Releases -You can turn any git tag into release by just adding a release notes to it. -Release notes behaves like any other markdown form in GitLab so you can write text and drag-n-drop files to it. -Release notes are stored in GitLab database. +You can turn any git tag into a release, by adding a note to it. +Release notes behave like any other markdown form in GitLab so you can write text and drag-n-drop files to it. +Release notes are stored in the database of GitLab. There are several ways to add release notes: -* In UI when you create new git tag with GitLab -* In UI when you add release notes to existing git tag -* with GitLab API +* In the interface, when you create a new git tag with GitLab +* In the interface, by adding a note to an existing git tag +* with the GitLab API ## New tag page with release notes text area From 9e0d443f9e420a347cb88508c9afebf17454fac1 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 16 Nov 2015 12:18:31 +0200 Subject: [PATCH 066/410] Disabling cache for test environment --- config/environments/test.rb | 2 ++ config/initializers/session_store.rb | 20 +++++++++++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/config/environments/test.rb b/config/environments/test.rb index 2d5e7addcd..955540837d 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -30,4 +30,6 @@ Gitlab::Application.configure do config.active_support.deprecation = :stderr config.eager_load = false + + config.cache_store = :null_store end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 04ed9e90df..d7c5432da7 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -9,12 +9,14 @@ begin rescue end -Gitlab::Application.config.session_store( - :redis_store, # Using the cookie_store would enable session replay attacks. - servers: Gitlab::Application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store - key: '_gitlab_session', - secure: Gitlab.config.gitlab.https, - httponly: true, - expire_after: Settings.gitlab['session_expire_delay'] * 60, - path: (Gitlab::Application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root -) +unless Rails.env.test? + Gitlab::Application.config.session_store( + :redis_store, # Using the cookie_store would enable session replay attacks. + servers: Gitlab::Application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store + key: '_gitlab_session', + secure: Gitlab.config.gitlab.https, + httponly: true, + expire_after: Settings.gitlab['session_expire_delay'] * 60, + path: (Gitlab::Application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root + ) +end From 14032d8eb1a60ae5920286249c1044be2fa27278 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 12 Oct 2015 16:42:14 +0200 Subject: [PATCH 067/410] Add support for git lfs. --- .gitignore | 1 + app/controllers/projects_controller.rb | 5 +- app/models/lfs_object.rb | 8 + app/models/lfs_objects_project.rb | 8 + app/models/project.rb | 12 + app/uploaders/lfs_object_uploader.rb | 29 + config/gitlab.yml.example | 10 +- config/initializers/1_settings.rb | 7 + config/routes.rb | 2 +- .../20151103134857_create_lfs_objects.rb | 10 + ...51103134958_create_lfs_objects_projects.rb | 12 + .../20151104105513_add_file_to_lfs_objects.rb | 5 + ...14113410_add_index_for_lfs_oid_and_size.rb | 6 + db/schema.rb | 22 +- lib/gitlab/backend/grack_auth.rb | 5 +- lib/gitlab/git_access.rb | 6 +- lib/gitlab/lfs/response.rb | 308 +++++++++ lib/gitlab/lfs/router.rb | 95 +++ lib/support/nginx/gitlab | 9 +- lib/support/nginx/gitlab-ssl | 9 +- spec/factories/lfs_objects.rb | 12 + spec/factories/lfs_objects_projects.rb | 8 + spec/lib/gitlab/lfs/lfs_router_spec.rb | 650 ++++++++++++++++++ 23 files changed, 1226 insertions(+), 13 deletions(-) create mode 100644 app/models/lfs_object.rb create mode 100644 app/models/lfs_objects_project.rb create mode 100644 app/uploaders/lfs_object_uploader.rb create mode 100644 db/migrate/20151103134857_create_lfs_objects.rb create mode 100644 db/migrate/20151103134958_create_lfs_objects_projects.rb create mode 100644 db/migrate/20151104105513_add_file_to_lfs_objects.rb create mode 100644 db/migrate/20151114113410_add_index_for_lfs_oid_and_size.rb create mode 100644 lib/gitlab/lfs/response.rb create mode 100644 lib/gitlab/lfs/router.rb create mode 100644 spec/factories/lfs_objects.rb create mode 100644 spec/factories/lfs_objects_projects.rb create mode 100644 spec/lib/gitlab/lfs/lfs_router_spec.rb diff --git a/.gitignore b/.gitignore index 39ff95c50e..f5b6427ca0 100644 --- a/.gitignore +++ b/.gitignore @@ -43,3 +43,4 @@ rails_best_practices_output.html tmp/ vendor/bundle/* builds/* +shared/* diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 30b166334a..23453195e8 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -72,8 +72,7 @@ class ProjectsController < ApplicationController def remove_fork return access_denied! unless can?(current_user, :remove_fork_project, @project) - if @project.forked? - @project.forked_project_link.destroy + if @project.unlink_fork flash[:notice] = 'The fork relationship has been removed.' end end @@ -243,7 +242,7 @@ class ProjectsController < ApplicationController project.repository_exists? && !project.empty_repo? end - # Override get_id from ExtractsPath, which returns the branch and file path + # Override get_id from ExtractsPath, which returns the branch and file path # for the blob/tree, which in this case is just the root of the default branch. def get_id project.repository.root_ref diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb new file mode 100644 index 0000000000..3c1426f59d --- /dev/null +++ b/app/models/lfs_object.rb @@ -0,0 +1,8 @@ +class LfsObject < ActiveRecord::Base + has_many :lfs_objects_projects, dependent: :destroy + has_many :projects, through: :lfs_objects_projects + + validates :oid, presence: true, uniqueness: true + + mount_uploader :file, LfsObjectUploader +end diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb new file mode 100644 index 0000000000..0fd5f089db --- /dev/null +++ b/app/models/lfs_objects_project.rb @@ -0,0 +1,8 @@ +class LfsObjectsProject < ActiveRecord::Base + belongs_to :project + belongs_to :lfs_object + + validates :lfs_object_id, presence: true + validates :lfs_object_id, uniqueness: { scope: [:project_id], message: "already exists in project" } + validates :project_id, presence: true +end diff --git a/app/models/project.rb b/app/models/project.rb index 3e72a9a46a..9ea0d15497 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -124,6 +124,8 @@ class Project < ActiveRecord::Base has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build' has_many :releases, dependent: :destroy + has_many :lfs_objects_projects, dependent: :destroy + has_many :lfs_objects, through: :lfs_objects_projects has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :gitlab_ci_project, dependent: :destroy, class_name: "Ci::Project", foreign_key: :gitlab_id @@ -798,4 +800,14 @@ class Project < ActiveRecord::Base def enable_ci self.builds_enabled = true end + + def unlink_fork + if forked? + forked_from_project.lfs_objects.find_each do |lfs_object| + lfs_object.projects << self + end + + forked_project_link.destroy + end + end end diff --git a/app/uploaders/lfs_object_uploader.rb b/app/uploaders/lfs_object_uploader.rb new file mode 100644 index 0000000000..28085b3108 --- /dev/null +++ b/app/uploaders/lfs_object_uploader.rb @@ -0,0 +1,29 @@ +# encoding: utf-8 + +class LfsObjectUploader < CarrierWave::Uploader::Base + storage :file + + def store_dir + "#{Gitlab.config.lfs.storage_path}/#{model.oid[0,2]}/#{model.oid[2,2]}" + end + + def cache_dir + "#{Gitlab.config.lfs.storage_path}/tmp/cache" + end + + def move_to_cache + true + end + + def move_to_store + true + end + + def exists? + file.try(:exists?) + end + + def filename + model.oid[4..-1] + end +end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 5bd98e3e42..8fdb2603ce 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -124,6 +124,12 @@ production: &base # The mailbox where incoming mail will end up. Usually "inbox". mailbox: "inbox" + ## Git LFS + lfs: + enabled: false + # The location where LFS objects are stored (default: shared/lfs-objects). + # storage_path: shared/lfs-objects + ## Gravatar ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html gravatar: @@ -317,8 +323,6 @@ production: &base # path: /mnt/gitlab # Default: shared - - # # 4. Advanced settings # ========================== @@ -419,6 +423,8 @@ test: <<: *base gravatar: enabled: true + lfs: + enabled: false gitlab: host: localhost port: 80 diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 847d9f2189..6b7990c0ab 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -199,6 +199,13 @@ Settings.incoming_email['ssl'] = false if Settings.incoming_email['ssl']. Settings.incoming_email['start_tls'] = false if Settings.incoming_email['start_tls'].nil? Settings.incoming_email['mailbox'] = "inbox" if Settings.incoming_email['mailbox'].nil? +# +# Git LFS +# +Settings['lfs'] ||= Settingslogic.new({}) +Settings.lfs['enabled'] = false if Settings.lfs['enabled'].nil? +Settings.lfs['storage_path'] = File.expand_path(Settings.lfs['storage_path'] || File.join(Settings.shared['path'], "lfs-objects"), Rails.root) + # # Gravatar # diff --git a/config/routes.rb b/config/routes.rb index 095c562be8..bd85f4e3c6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -93,7 +93,7 @@ Gitlab::Application.routes.draw do end # Enable Grack support - mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post] + mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post, :put] # Help get 'help' => 'help#index' diff --git a/db/migrate/20151103134857_create_lfs_objects.rb b/db/migrate/20151103134857_create_lfs_objects.rb new file mode 100644 index 0000000000..2d04c170a8 --- /dev/null +++ b/db/migrate/20151103134857_create_lfs_objects.rb @@ -0,0 +1,10 @@ +class CreateLfsObjects < ActiveRecord::Migration + def change + create_table :lfs_objects do |t| + t.string :oid, null: false, unique: true + t.integer :size, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20151103134958_create_lfs_objects_projects.rb b/db/migrate/20151103134958_create_lfs_objects_projects.rb new file mode 100644 index 0000000000..f3f58b931e --- /dev/null +++ b/db/migrate/20151103134958_create_lfs_objects_projects.rb @@ -0,0 +1,12 @@ +class CreateLfsObjectsProjects < ActiveRecord::Migration + def change + create_table :lfs_objects_projects do |t| + t.integer :lfs_object_id, null: false + t.integer :project_id, null: false + + t.timestamps + end + + add_index :lfs_objects_projects, :project_id + end +end diff --git a/db/migrate/20151104105513_add_file_to_lfs_objects.rb b/db/migrate/20151104105513_add_file_to_lfs_objects.rb new file mode 100644 index 0000000000..7c57f3f0df --- /dev/null +++ b/db/migrate/20151104105513_add_file_to_lfs_objects.rb @@ -0,0 +1,5 @@ +class AddFileToLfsObjects < ActiveRecord::Migration + def change + add_column :lfs_objects, :file, :string + end +end diff --git a/db/migrate/20151114113410_add_index_for_lfs_oid_and_size.rb b/db/migrate/20151114113410_add_index_for_lfs_oid_and_size.rb new file mode 100644 index 0000000000..d10f1f6e60 --- /dev/null +++ b/db/migrate/20151114113410_add_index_for_lfs_oid_and_size.rb @@ -0,0 +1,6 @@ +class AddIndexForLfsOidAndSize < ActiveRecord::Migration + def change + add_index :lfs_objects, :oid + add_index :lfs_objects, [:oid, :size] + end +end diff --git a/db/schema.rb b/db/schema.rb index f631d73f33..a8e8dfe6bb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151109100728) do +ActiveRecord::Schema.define(version: 20151114113410) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -422,6 +422,26 @@ ActiveRecord::Schema.define(version: 20151109100728) do add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree + create_table "lfs_objects", force: true do |t| + t.string "oid", null: false, unique: true + t.integer "size", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.string "file" + end + + add_index "lfs_objects", ["oid", "size"], name: "index_lfs_objects_on_oid_and_size", using: :btree + add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", using: :btree + + create_table "lfs_objects_projects", force: true do |t| + t.integer "lfs_object_id", null: false + t.integer "project_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree + create_table "members", force: true do |t| t.integer "access_level", null: false t.integer "source_id", null: false diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 440ef5a3cb..0d156047ff 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -33,6 +33,9 @@ module Grack auth! + lfs_response = Gitlab::Lfs::Router.new(project, @user, @request).try_call + return lfs_response unless lfs_response.nil? + if project && authorized_request? # Tell gitlab-workhorse the request is OK, and what the GL_ID is render_grack_auth_ok @@ -72,7 +75,7 @@ module Grack matched_login = /(?^[a-zA-Z]*-ci)-token$/.match(login) if project && matched_login.present? && git_cmd == 'git-upload-pack' - underscored_service = matched_login['s'].underscore + underscored_service = matched_login['s'].underscore if Service.available_services_names.include?(underscored_service) service_method = "#{underscored_service}_service" diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index c90184d31c..3ed1eec517 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -13,7 +13,7 @@ module Gitlab def user return @user if defined?(@user) - @user = + @user = case actor when User actor @@ -125,7 +125,7 @@ module Gitlab def change_access_check(change) oldrev, newrev, ref = change.split(' ') - action = + action = if project.protected_branch?(branch_name(ref)) protected_branch_action(oldrev, newrev, branch_name(ref)) elsif protected_tag?(tag_name(ref)) @@ -148,7 +148,7 @@ module Gitlab build_status_object(false, "You are not allowed to change existing tags on this project.") else # :push_code build_status_object(false, "You are not allowed to push code to this project.") - end + end return status end diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb new file mode 100644 index 0000000000..4202c78646 --- /dev/null +++ b/lib/gitlab/lfs/response.rb @@ -0,0 +1,308 @@ +module Gitlab + module Lfs + class Response + + def initialize(project, user, request) + @origin_project = project + @project = storage_project(project) + @user = user + @env = request.env + @request = request + end + + # Return a response for a download request + # Can be a response to: + # Request from a user to get the file + # Request from gitlab-workhorse which file to serve to the user + def render_download_hypermedia_response(oid) + render_response_to_download do + if check_download_accept_header? + render_lfs_download_hypermedia(oid) + else + render_not_found + end + end + end + + def render_download_object_response(oid) + render_response_to_download do + if check_download_sendfile_header? && check_download_accept_header? + render_lfs_sendfile(oid) + else + render_not_found + end + end + end + + def render_lfs_api_auth + render_response_to_push do + request_body = JSON.parse(@request.body.read) + return render_not_found if request_body.empty? || request_body['objects'].empty? + + response = build_response(request_body['objects']) + [ + 200, + { + "Content-Type" => "application/json; charset=utf-8", + "Cache-Control" => "private", + }, + [JSON.dump(response)] + ] + end + end + + def render_storage_upload_authorize_response(oid, size) + render_response_to_push do + [ + 200, + { "Content-Type" => "application/json; charset=utf-8" }, + [JSON.dump({ + 'StoreLFSPath' => "#{Gitlab.config.lfs.storage_path}/tmp/upload", + 'LfsOid' => oid, + 'LfsSize' => size + })] + ] + end + end + + def render_storage_upload_store_response(oid, size, tmp_file_name) + render_response_to_push do + render_lfs_upload_ok(oid, size, tmp_file_name) + end + end + + private + + def render_not_enabled + [ + 501, + { + "Content-Type" => "application/vnd.git-lfs+json", + }, + [JSON.dump({ + 'message' => 'Git LFS is not enabled on this GitLab server, contact your admin.', + 'documentation_url' => "#{Gitlab.config.gitlab.url}/help", + })] + ] + end + + def render_unauthorized + [ + 401, + { + 'Content-Type' => 'text/plain' + }, + ['Unauthorized'] + ] + end + + def render_not_found + [ + 404, + { + "Content-Type" => "application/vnd.git-lfs+json" + }, + [JSON.dump({ + 'message' => 'Not found.', + 'documentation_url' => "#{Gitlab.config.gitlab.url}/help", + })] + ] + end + + def render_forbidden + [ + 403, + { + "Content-Type" => "application/vnd.git-lfs+json" + }, + [JSON.dump({ + 'message' => 'Access forbidden. Check your access level.', + 'documentation_url' => "#{Gitlab.config.gitlab.url}/help", + })] + ] + end + + def render_lfs_sendfile(oid) + return render_not_found unless oid.present? + + lfs_object = object_for_download(oid) + + if lfs_object && lfs_object.file.exists? + [ + 200, + { + # GitLab-workhorse will forward Content-Type header + "Content-Type" => "application/octet-stream", + "X-Sendfile" => lfs_object.file.path + }, + [] + ] + else + render_not_found + end + end + + def render_lfs_download_hypermedia(oid) + return render_not_found unless oid.present? + + lfs_object = object_for_download(oid) + if lfs_object + [ + 200, + { "Content-Type" => "application/vnd.git-lfs+json" }, + [JSON.dump(download_hypermedia(oid))] + ] + else + render_not_found + end + end + + def render_lfs_upload_ok(oid, size, tmp_file) + if store_file(oid, size, tmp_file) + [ + 200, + { + 'Content-Type' => 'text/plain', + 'Content-Length' => 0 + }, + [] + ] + else + [ + 422, + { 'Content-Type' => 'text/plain' }, + ["Unprocessable entity"] + ] + end + end + + def render_response_to_download + return render_not_enabled unless Gitlab.config.lfs.enabled + + unless @project.public? + return render_unauthorized unless @user + return render_forbidden unless user_can_fetch? + end + + yield + end + + def render_response_to_push + return render_not_enabled unless Gitlab.config.lfs.enabled + return render_unauthorized unless @user + return render_forbidden unless user_can_push? + + yield + end + + def check_download_sendfile_header? + @env['HTTP_X_SENDFILE_TYPE'].to_s == "X-Sendfile" + end + + def check_download_accept_header? + @env['HTTP_ACCEPT'].to_s == "application/vnd.git-lfs+json; charset=utf-8" + end + + def user_can_fetch? + # Check user access against the project they used to initiate the pull + @user.can?(:download_code, @origin_project) + end + + def user_can_push? + # Check user access against the project they used to initiate the push + @user.can?(:push_code, @origin_project) + end + + def storage_project(project) + if project.forked? + project.forked_from_project + else + project + end + end + + def store_file(oid, size, tmp_file) + tmp_file_path = File.join("#{Gitlab.config.lfs.storage_path}/tmp/upload", tmp_file) + + object = LfsObject.find_or_create_by(oid: oid, size: size) + if object.file.exists? + success = true + else + success = move_tmp_file_to_storage(object, tmp_file_path) + end + + if success + success = link_to_project(object) + end + + success + ensure + # Ensure that the tmp file is removed + FileUtils.rm_f(tmp_file_path) + end + + def object_for_download(oid) + @project.lfs_objects.find_by(oid: oid) + end + + def move_tmp_file_to_storage(object, path) + File.open(path) do |f| + object.file = f + end + + object.file.store! + object.save + end + + def link_to_project(object) + if object && !object.projects.exists?(@project) + object.projects << @project + object.save + end + end + + def select_existing_objects(objects) + objects_oids = objects.map { |o| o['oid'] } + @project.lfs_objects.where(oid: objects_oids).pluck(:oid).to_set + end + + def build_response(objects) + selected_objects = select_existing_objects(objects) + + upload_hypermedia(objects, selected_objects) + end + + def download_hypermedia(oid) + { + '_links' => { + 'download' => + { + 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{oid}", + 'header' => { + 'Accept' => "application/vnd.git-lfs+json; charset=utf-8", + 'Authorization' => @env['HTTP_AUTHORIZATION'] + }.compact + } + } + } + end + + def upload_hypermedia(all_objects, existing_objects) + all_objects.each do |object| + object['_links'] = hypermedia_links(object) unless existing_objects.include?(object['oid']) + end + + { 'objects' => all_objects } + end + + def hypermedia_links(object) + { + "upload" => { + 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}/#{object['size']}", + 'header' => { 'Authorization' => @env['HTTP_AUTHORIZATION'] } + }.compact + } + end + end + end +end diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb new file mode 100644 index 0000000000..4809e83498 --- /dev/null +++ b/lib/gitlab/lfs/router.rb @@ -0,0 +1,95 @@ +module Gitlab + module Lfs + class Router + def initialize(project, user, request) + @project = project + @user = user + @env = request.env + @request = request + end + + def try_call + return unless @request && @request.path.present? + + case @request.request_method + when 'GET' + get_response + when 'POST' + post_response + when 'PUT' + put_response + else + nil + end + end + + private + + def get_response + path_match = @request.path.match(/\/(info\/lfs|gitlab-lfs)\/objects\/([0-9a-f]{64})$/) + return nil unless path_match + + oid = path_match[2] + return nil unless oid + + case path_match[1] + when "info/lfs" + lfs.render_download_hypermedia_response(oid) + when "gitlab-lfs" + lfs.render_download_object_response(oid) + else + nil + end + end + + def post_response + post_path = @request.path.match(/\/info\/lfs\/objects(\/batch)?$/) + return nil unless post_path + + # Check for Batch API + if post_path[0].ends_with?("/info/lfs/objects/batch") + lfs.render_lfs_api_auth + else + nil + end + end + + def put_response + object_match = @request.path.match(/\/gitlab-lfs\/objects\/([0-9a-f]{64})\/([0-9]+)(|\/authorize){1}$/) + return nil if object_match.nil? + + oid = object_match[1] + size = object_match[2].try(:to_i) + return nil if oid.nil? || size.nil? + + # GitLab-workhorse requests + # 1. Try to authorize the request + # 2. send a request with a header containing the name of the temporary file + if object_match[3] && object_match[3] == '/authorize' + lfs.render_storage_upload_authorize_response(oid, size) + else + tmp_file_name = sanitize_tmp_filename(@request.env['HTTP_X_GITLAB_LFS_TMP']) + return nil unless tmp_file_name + + lfs.render_storage_upload_store_response(oid, size, tmp_file_name) + end + end + + def lfs + return unless @project + + Gitlab::Lfs::Response.new(@project, @user, @request) + end + + def sanitize_tmp_filename(name) + if name.present? + name.gsub!(/^.*(\\|\/)/, '') + name = name.match(/[0-9a-f]{73}/) + name[0] if name + else + nil + end + end + end + end +end diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 0a7a411807..93f2ad07ae 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -44,7 +44,7 @@ upstream gitlab-workhorse { ## Normal HTTP host server { - ## Either remove "default_server" from the listen line below, + ## Either remove "default_server" from the listen line below, ## or delete the /etc/nginx/sites-enabled/default file. This will cause gitlab ## to be served if you visit any address that your server responds to, eg. ## the ip address of the server (http://x.x.x.x/)n 0.0.0.0:80 default_server; @@ -113,6 +113,13 @@ server { proxy_pass http://gitlab; } + location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects { + client_max_body_size 0; + # 'Error' 418 is a hack to re-use the @gitlab-workhorse block + error_page 418 = @gitlab-workhorse; + return 418; + } + location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ { # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index b463d5b6aa..90749947fa 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -48,7 +48,7 @@ upstream gitlab-workhorse { ## Redirects all HTTP traffic to the HTTPS host server { - ## Either remove "default_server" from the listen line below, + ## Either remove "default_server" from the listen line below, ## or delete the /etc/nginx/sites-enabled/default file. This will cause gitlab ## to be served if you visit any address that your server responds to, eg. ## the ip address of the server (http://x.x.x.x/) @@ -160,6 +160,13 @@ server { proxy_pass http://gitlab; } + location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects { + client_max_body_size 0; + # 'Error' 418 is a hack to re-use the @gitlab-workhorse block + error_page 418 = @gitlab-workhorse; + return 418; + } + location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ { # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb new file mode 100644 index 0000000000..7fb2d77ca3 --- /dev/null +++ b/spec/factories/lfs_objects.rb @@ -0,0 +1,12 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :lfs_object do + oid "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" + size 499013 + end + + trait :with_file do + file { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") } + end +end diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb new file mode 100644 index 0000000000..93de6607df --- /dev/null +++ b/spec/factories/lfs_objects_projects.rb @@ -0,0 +1,8 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :lfs_objects_project do + lfs_object + project + end +end diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb new file mode 100644 index 0000000000..cebcb5bc88 --- /dev/null +++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb @@ -0,0 +1,650 @@ +require 'spec_helper' + +describe Gitlab::Lfs::Router do + let(:project) { create(:project) } + let(:public_project) { create(:project, :public) } + let(:forked_project) { fork_project(public_project, user) } + + let(:user) { create(:user) } + let(:user_two) { create(:user) } + let!(:lfs_object) { create(:lfs_object, :with_file) } + + let(:request) { Rack::Request.new(env) } + let(:env) do + { + 'rack.input' => '', + 'REQUEST_METHOD' => 'GET', + } + end + + let(:lfs_router_auth) { new_lfs_router(project, user) } + let(:lfs_router_noauth) { new_lfs_router(project, nil) } + let(:lfs_router_public_auth) { new_lfs_router(public_project, user) } + let(:lfs_router_public_noauth) { new_lfs_router(public_project, nil) } + let(:lfs_router_forked_noauth) { new_lfs_router(forked_project, nil) } + let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user_two) } + + let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" } + let(:sample_size) { 499013 } + + describe 'when lfs is disabled' do + before do + allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" + end + + it 'responds with 501' do + respond_with_disabled = [ 501, + { "Content-Type"=>"application/vnd.git-lfs+json" }, + ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"] + ] + expect(lfs_router_auth.try_call).to match_array(respond_with_disabled) + end + end + + describe 'when fetching lfs object' do + before do + enable_lfs + env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8" + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" + end + + describe 'when user is authenticated' do + context 'and user has project download access' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.lfs_objects << lfs_object + project.team << [user, :master] + end + + it "responds with status 200" do + expect(lfs_router_auth.try_call.first).to eq(200) + end + + it "responds with download hypermedia" do + json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first) + + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") + expect(json_response['_links']['download']['header']).to eq("Authorization" => @auth, "Accept" => "application/vnd.git-lfs+json; charset=utf-8") + end + end + + context 'and user does not have project access' do + it "responds with status 403" do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + describe 'when user is unauthenticated' do + context 'and user does not have download access' do + it "responds with status 401" do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and user has download access' do + before do + project.team << [user, :master] + end + + it "responds with status 401" do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + end + + describe 'and project is public' do + context 'and project has access to the lfs object' do + before do + public_project.lfs_objects << lfs_object + end + + context 'and user is authenticated' do + it "responds with status 200 and sends download hypermedia" do + expect(lfs_router_public_auth.try_call.first).to eq(200) + json_response = ActiveSupport::JSON.decode(lfs_router_public_auth.try_call.last.first) + + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") + expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8") + end + end + + context 'and user is unauthenticated' do + it "responds with status 200 and sends download hypermedia" do + expect(lfs_router_public_noauth.try_call.first).to eq(200) + json_response = ActiveSupport::JSON.decode(lfs_router_public_noauth.try_call.last.first) + + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") + expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8") + end + end + end + + context 'and project does not have access to the lfs object' do + it "responds with status 404" do + expect(lfs_router_public_auth.try_call.first).to eq(404) + end + end + end + + describe 'and request comes from gitlab-workhorse' do + before do + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}" + end + context 'without user being authorized' do + it "responds with status 401" do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'with required headers' do + before do + env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile" + end + + context 'when user does not have project access' do + it "responds with status 403" do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + + context 'when user has project access' do + before do + project.lfs_objects << lfs_object + project.team << [user, :master] + end + + it "responds with status 200" do + expect(lfs_router_auth.try_call.first).to eq(200) + end + + it "responds with the file location" do + expect(lfs_router_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") + expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) + end + end + end + + context 'without required headers' do + it "responds with status 403" do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + describe 'from a forked public project' do + before do + env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8" + env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" + end + + context "when fetching a lfs object" do + context "and user has project download access" do + before do + public_project.lfs_objects << lfs_object + end + + it "can download the lfs object" do + expect(lfs_router_forked_auth.try_call.first).to eq(200) + json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) + + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") + expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8") + end + end + + context "and user is not authenticated but project is public" do + before do + public_project.lfs_objects << lfs_object + end + + it "can download the lfs object" do + expect(lfs_router_forked_auth.try_call.first).to eq(200) + end + end + + context "and user has project download access" do + before do + env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897" + @auth = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) + env["HTTP_AUTHORIZATION"] = @auth + lfs_object_two = create(:lfs_object, :with_file, oid: "91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", size: 1575078) + public_project.lfs_objects << lfs_object_two + end + + it "can get a lfs object that is not in the forked project" do + expect(lfs_router_forked_auth.try_call.first).to eq(200) + + json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) + expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8", "Authorization" => @auth) + end + end + + context "and user has project download access" do + before do + env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/267c8b1d876743971e3a9978405818ff5ca731c4c870b06507619cd9b1847b6b" + lfs_object_three = create(:lfs_object, :with_file, oid: "267c8b1d876743971e3a9978405818ff5ca731c4c870b06507619cd9b1847b6b", size: 127192524) + project.lfs_objects << lfs_object_three + end + + it "cannot get a lfs object that is not in the project" do + expect(lfs_router_forked_auth.try_call.first).to eq(404) + end + end + end + end + end + + describe 'when initiating pushing of the lfs object' do + before do + enable_lfs + env['REQUEST_METHOD'] = 'POST' + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" + end + + describe 'when user is authenticated' do + before do + body = { 'objects' => [{ + 'oid' => sample_oid, + 'size' => sample_size + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end + + describe 'when user has project push access' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.team << [user, :master] + end + + context 'when pushing an lfs object that already exists' do + before do + public_project.lfs_objects << lfs_object + end + + it "responds with status 200 and links the object to the project" do + response_body = lfs_router_auth.try_call.last + response = ActiveSupport::JSON.decode(response_body.first) + + expect(response['objects']).to be_kind_of(Array) + expect(response['objects'].first['oid']).to eq(sample_oid) + expect(response['objects'].first['size']).to eq(sample_size) + expect(lfs_object.projects.pluck(:id)).to_not include(project.id) + expect(lfs_object.projects.pluck(:id)).to include(public_project.id) + expect(response['objects'].first).to have_key('_links') + end + end + + context 'when pushing a lfs object that does not exist' do + before do + body = { + 'objects' => [{ + 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }] + }.to_json + env['rack.input'] = StringIO.new(body) + end + + it "responds with status 200 and upload hypermedia link" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + + response_body = ActiveSupport::JSON.decode(response.last.first) + expect(response_body['objects']).to be_kind_of(Array) + expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(response_body['objects'].first['size']).to eq(1575078) + expect(lfs_object.projects.pluck(:id)).not_to include(project.id) + expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth) + end + end + + context 'when pushing one new and one existing lfs object' do + before do + body = { + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ] + }.to_json + env['rack.input'] = StringIO.new(body) + public_project.lfs_objects << lfs_object + end + + it "responds with status 200 with upload hypermedia link for the new object" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + + response_body = ActiveSupport::JSON.decode(response.last.first) + expect(response_body['objects']).to be_kind_of(Array) + + + expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(response_body['objects'].first['size']).to eq(1575078) + expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth) + + expect(response_body['objects'].last['oid']).to eq(sample_oid) + expect(response_body['objects'].last['size']).to eq(sample_size) + expect(lfs_object.projects.pluck(:id)).to_not include(project.id) + expect(lfs_object.projects.pluck(:id)).to include(public_project.id) + expect(response_body['objects'].last).to have_key('_links') + end + end + end + + context 'when user does not have push access' do + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + context 'when user is not authenticated' do + context 'when user has push access' do + before do + project.team << [user, :master] + end + + it "responds with status 401" do + expect(lfs_router_public_noauth.try_call.first).to eq(401) + end + end + + context 'when user does not have push access' do + it "responds with status 401" do + expect(lfs_router_public_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'when pushing a lfs object' do + before do + enable_lfs + env['REQUEST_METHOD'] = 'PUT' + end + + describe 'to one project' do + describe 'when user has push access to the project' do + before do + project.team << [user, :master] + end + + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with status 200, location of lfs store and object details' do + json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first) + + expect(lfs_router_auth.try_call.first).to eq(200) + expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") + expect(json_response['LfsOid']).to eq(sample_oid) + expect(json_response['LfsSize']).to eq(sample_size) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with status 200 and lfs object is linked to the project' do + expect(lfs_router_auth.try_call.first).to eq(200) + expect(lfs_object.projects.pluck(:id)).to include(project.id) + end + end + end + + describe 'when user is unauthenticated' do + let(:lfs_router_noauth) { new_lfs_router(project, nil) } + + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with status 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with status 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent with a malformed headers' do + before do + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" + env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd" + end + + it 'does not recognize it as a valid lfs command' do + expect(lfs_router_noauth.try_call).to eq(nil) + end + end + end + end + + describe 'and user does not have push access' do + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + describe 'when user is unauthenticated' do + let(:lfs_router_noauth) { new_lfs_router(project, nil) } + + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(project) + end + + it 'responds with 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(project) + end + + it 'responds with 401' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + end + end + end + + describe "to a forked project" do + let(:forked_project) { fork_project(public_project, user) } + + describe 'when user has push access to the project' do + before do + forked_project.team << [user_two, :master] + end + + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with status 200, location of lfs store and object details' do + json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) + + expect(lfs_router_forked_auth.try_call.first).to eq(200) + expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") + expect(json_response['LfsOid']).to eq(sample_oid) + expect(json_response['LfsSize']).to eq(sample_size) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with status 200 and lfs object is linked to the source project' do + expect(lfs_router_forked_auth.try_call.first).to eq(200) + expect(lfs_object.projects.pluck(:id)).to include(public_project.id) + end + end + end + + describe 'when user is unauthenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with status 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with status 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'and user does not have push access' do + describe 'when user is authenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with 403' do + expect(lfs_router_forked_auth.try_call.first).to eq(403) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with 403' do + expect(lfs_router_forked_auth.try_call.first).to eq(403) + end + end + end + + describe 'when user is unauthenticated' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + header_for_upload_authorize(forked_project) + end + + it 'responds with 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + headers_for_upload_finalize(forked_project) + end + + it 'responds with 401' do + expect(lfs_router_forked_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'and second project not related to fork or a source project' do + let(:second_project) { create(:project) } + let(:lfs_router_second_project) { new_lfs_router(second_project, user) } + + before do + public_project.lfs_objects << lfs_object + headers_for_upload_finalize(second_project) + end + + context 'when pushing the same lfs object to the second project' do + before do + second_project.team << [user, :master] + end + + it 'responds with 200 and links the lfs object to the project' do + expect(lfs_router_second_project.try_call.first).to eq(200) + expect(lfs_object.projects.pluck(:id)).to include(second_project.id, public_project.id) + end + end + end + end + end + + def enable_lfs + allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) + end + + def authorize(user) + ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) + end + + def new_lfs_router(project, user) + Gitlab::Lfs::Router.new(project, user, request) + end + + def header_for_upload_authorize(project) + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize" + end + + def headers_for_upload_finalize(project) + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" + env["HTTP_X_GITLAB_LFS_TMP"] = "#{sample_oid}6e561c9d4" + end + + def fork_project(project, user, object = nil) + allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) + Projects::ForkService.new(project, user, {}).execute + end +end From 796bb651700c26ce1a5693ba6d6c8b2353cb6e34 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 14 Nov 2015 19:29:58 +0100 Subject: [PATCH 068/410] Remove duplicate methods in uploaders Signed-off-by: Dmitriy Zaporozhets --- app/uploaders/attachment_uploader.rb | 19 ++----------------- app/uploaders/avatar_uploader.rb | 19 ++----------------- app/uploaders/file_uploader.rb | 19 ++----------------- app/uploaders/uploader_helper.rb | 19 +++++++++++++++++++ 4 files changed, 25 insertions(+), 51 deletions(-) create mode 100644 app/uploaders/uploader_helper.rb diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index a9691bee46..a65a896e41 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -1,26 +1,11 @@ # encoding: utf-8 class AttachmentUploader < CarrierWave::Uploader::Base + include UploaderHelper + storage :file def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end - - def image? - img_ext = %w(png jpg jpeg gif bmp tiff) - if file.respond_to?(:extension) - img_ext.include?(file.extension.downcase) - else - # Not all CarrierWave storages respond to :extension - ext = file.path.split('.').last.downcase - img_ext.include?(ext) - end - rescue - false - end - - def file_storage? - self.class.storage == CarrierWave::Storage::File - end end diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb index 7cad044555..6135c3ad96 100644 --- a/app/uploaders/avatar_uploader.rb +++ b/app/uploaders/avatar_uploader.rb @@ -1,6 +1,8 @@ # encoding: utf-8 class AvatarUploader < CarrierWave::Uploader::Base + include UploaderHelper + storage :file after :store, :reset_events_cache @@ -9,23 +11,6 @@ class AvatarUploader < CarrierWave::Uploader::Base "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end - def image? - img_ext = %w(png jpg jpeg gif bmp tiff) - if file.respond_to?(:extension) - img_ext.include?(file.extension.downcase) - else - # Not all CarrierWave storages respond to :extension - ext = file.path.split('.').last.downcase - img_ext.include?(ext) - end - rescue - false - end - - def file_storage? - self.class.storage == CarrierWave::Storage::File - end - def reset_events_cache(file) model.reset_events_cache if model.is_a?(User) end diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index e821158583..ac920119a8 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -1,5 +1,7 @@ # encoding: utf-8 class FileUploader < CarrierWave::Uploader::Base + include UploaderHelper + storage :file attr_accessor :project, :secret @@ -28,21 +30,4 @@ class FileUploader < CarrierWave::Uploader::Base def secure_url File.join("/uploads", @secret, file.filename) end - - def file_storage? - self.class.storage == CarrierWave::Storage::File - end - - def image? - img_ext = %w(png jpg jpeg gif bmp tiff) - if file.respond_to?(:extension) - img_ext.include?(file.extension.downcase) - else - # Not all CarrierWave storages respond to :extension - ext = file.path.split('.').last.downcase - img_ext.include?(ext) - end - rescue - false - end end diff --git a/app/uploaders/uploader_helper.rb b/app/uploaders/uploader_helper.rb new file mode 100644 index 0000000000..5ef440f336 --- /dev/null +++ b/app/uploaders/uploader_helper.rb @@ -0,0 +1,19 @@ +# Extra methods for uploader +module UploaderHelper + def image? + img_ext = %w(png jpg jpeg gif bmp tiff) + if file.respond_to?(:extension) + img_ext.include?(file.extension.downcase) + else + # Not all CarrierWave storages respond to :extension + ext = file.path.split('.').last.downcase + img_ext.include?(ext) + end + rescue + false + end + + def file_storage? + self.class.storage == CarrierWave::Storage::File + end +end From cd513034e66a3eccb9022968af3a09bc7203a9c9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 14 Nov 2015 19:31:14 +0100 Subject: [PATCH 069/410] Set less strict flay option for now Signed-off-by: Dmitriy Zaporozhets --- lib/tasks/flay.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/flay.rake b/lib/tasks/flay.rake index 5efffc2cda..dfb9df4772 100644 --- a/lib/tasks/flay.rake +++ b/lib/tasks/flay.rake @@ -1,6 +1,6 @@ desc 'Code duplication analyze via flay' task :flay do - output = %x(bundle exec flay app/ lib/gitlab/) + output = %x(bundle exec flay --mass 30 app/ lib/gitlab/) if output.include? "Similar code found" puts output From 433e4a80efa50bdc3b588ae34c488b6982c2985d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 14 Nov 2015 19:38:05 +0100 Subject: [PATCH 070/410] Fix code duplication in NotificationsHelper Signed-off-by: Dmitriy Zaporozhets --- app/helpers/notifications_helper.rb | 38 ++++++++++------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index cf11f8e532..ba07278614 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -16,40 +16,28 @@ module NotificationsHelper def notification_list_item(notification_level, user_membership) case notification_level when Notification::N_DISABLED - content_tag(:li, class: active_level_for(user_membership, Notification::N_DISABLED)) do - link_to '#', class: 'update-notification', data: { notification_level: Notification::N_DISABLED } do - icon('microphone-slash fw', text: 'Disabled') - end - end + update_notification_link(Notification::N_DISABLED, user_membership, 'Disabled', 'microphone-slash') when Notification::N_PARTICIPATING - content_tag(:li, class: active_level_for(user_membership, Notification::N_PARTICIPATING)) do - link_to '#', class: 'update-notification', data: { notification_level: Notification::N_PARTICIPATING } do - icon('volume-up fw', text: 'Participate') - end - end + update_notification_link(Notification::N_PARTICIPATING, user_membership, 'Participate', 'volume-up') when Notification::N_WATCH - content_tag(:li, class: active_level_for(user_membership, Notification::N_WATCH)) do - link_to '#', class: 'update-notification', data: { notification_level: Notification::N_WATCH } do - icon('eye fw', text: 'Watch') - end - end + update_notification_link(Notification::N_WATCH, user_membership, 'Watch', 'eye') when Notification::N_MENTION - content_tag(:li, class: active_level_for(user_membership, Notification::N_MENTION)) do - link_to '#', class: 'update-notification', data: { notification_level: Notification::N_MENTION } do - icon('at fw', text: 'On mention') - end - end + update_notification_link(Notification::N_MENTION, user_membership, 'On mention', 'at') when Notification::N_GLOBAL - content_tag(:li, class: active_level_for(user_membership, Notification::N_GLOBAL)) do - link_to '#', class: 'update-notification', data: { notification_level: Notification::N_GLOBAL } do - icon('globe fw', text: 'Global') - end - end + update_notification_link(Notification::N_GLOBAL, user_membership, 'Global', 'globe') else # do nothing end end + def update_notification_link(notification_label, user_membership, title, icon) + content_tag(:li, class: active_level_for(user_membership, notification_level)) do + link_to '#', class: 'update-notification', data: { notification_level: notification_label } do + icon("#{icon} fw", text: title) + end + end + end + def notification_label(user_membership) Notification.new(user_membership).to_s end From 0698c96d7dc472a0e7e74c290047a50eeddf27bb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Sat, 14 Nov 2015 19:43:48 +0100 Subject: [PATCH 071/410] Remove duplicate code in Repository#*_names_contains Signed-off-by: Dmitriy Zaporozhets --- app/models/repository.rb | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index f8c4cb1387..f76b770e86 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -346,8 +346,8 @@ class Repository end end - def branch_names_contains(sha) - args = %W(#{Gitlab.config.git.bin_path} branch --contains #{sha}) + def refs_contains_sha(ref_type, sha) + args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha}) names = Gitlab::Popen.popen(args, path_to_repo).first if names.respond_to?(:split) @@ -363,21 +363,12 @@ class Repository end end + def branch_names_contains(sha) + refs_contains_sha('branch', sha) + end + def tag_names_contains(sha) - args = %W(#{Gitlab.config.git.bin_path} tag --contains #{sha}) - names = Gitlab::Popen.popen(args, path_to_repo).first - - if names.respond_to?(:split) - names = names.split("\n").map(&:strip) - - names.each do |name| - name.slice! '* ' - end - - names - else - [] - end + refs_contains_sha('tag', sha) end def branches From c9f2f2a4838f9aa49782acdb7cfad75d06f31ac2 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 09:53:17 +0100 Subject: [PATCH 072/410] Fix wrong variable name Signed-off-by: Dmitriy Zaporozhets --- app/helpers/notifications_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index ba07278614..499c655d2b 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -30,9 +30,9 @@ module NotificationsHelper end end - def update_notification_link(notification_label, user_membership, title, icon) + def update_notification_link(notification_level, user_membership, title, icon) content_tag(:li, class: active_level_for(user_membership, notification_level)) do - link_to '#', class: 'update-notification', data: { notification_level: notification_label } do + link_to '#', class: 'update-notification', data: { notification_level: notification_level } do icon("#{icon} fw", text: title) end end From 03f5ff750b107b30a6d306aafb6699a9c9ecff0d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 16 Nov 2015 13:24:36 +0100 Subject: [PATCH 073/410] Show specific runners from projects where user is master or owner --- CHANGELOG | 1 + app/models/user.rb | 15 ++++++++++----- spec/features/runners_spec.rb | 12 +++++++++++- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9806666833..45ef22e7e8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -35,6 +35,7 @@ v 8.2.0 (unreleased) - New design for project graphs page - Remove deprecated dumped yaml file generated from previous job definitions - Fix incoming email config defaults + - Show specific runners from projects where user is master or owner - MR target branch is now visible on a list view when it is different from project's default one - Improve Continuous Integration graphs page - Make color of "Accept Merge Request" button consistent with current build status diff --git a/app/models/user.rb b/app/models/user.rb index 9ffadcf446..61abea1f6e 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -405,6 +405,15 @@ class User < ActiveRecord::Base end end + def master_or_owner_projects_id + @master_or_owner_projects_id ||= begin + scope = { access_level: [ Gitlab::Access::MASTER, Gitlab::Access::OWNER ] } + project_ids = personal_projects.pluck(:id) + project_ids.push(*groups_projects.where(members: scope).pluck(:id)) + project_ids.push(*projects.where(members: scope).pluck(:id).uniq) + end + end + # Projects user has access to def authorized_projects @authorized_projects ||= Project.where(id: authorized_projects_id) @@ -765,14 +774,10 @@ class User < ActiveRecord::Base !solo_owned_groups.present? end - def ci_authorized_projects - @ci_authorized_projects ||= Ci::Project.where(gitlab_id: authorized_projects_id) - end - def ci_authorized_runners @ci_authorized_runners ||= begin runner_ids = Ci::RunnerProject.joins(:project). - where(ci_projects: { gitlab_id: authorized_projects_id }).select(:runner_id) + where(ci_projects: { gitlab_id: master_or_owner_projects_id }).select(:runner_id) Ci::Runner.specific.where(id: runner_ids) end end diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb index 06adb7633b..b025902663 100644 --- a/spec/features/runners_spec.rb +++ b/spec/features/runners_spec.rb @@ -14,15 +14,25 @@ describe "Runners" do @project2 = FactoryGirl.create :ci_project @project2.gl_project.team << [user, :master] + @project3 = FactoryGirl.create :ci_project + @project3.gl_project.team << [user, :developer] + @shared_runner = FactoryGirl.create :ci_shared_runner @specific_runner = FactoryGirl.create :ci_specific_runner @specific_runner2 = FactoryGirl.create :ci_specific_runner + @specific_runner3 = FactoryGirl.create :ci_specific_runner @project.runners << @specific_runner @project2.runners << @specific_runner2 + @project3.runners << @specific_runner3 visit runners_path(@project.gl_project) end + before do + expect(page).to_not have_content(@specific_runner3.display_name) + expect(page).to_not have_content(@specific_runner3.display_name) + end + it "places runners in right places" do expect(page.find(".available-specific-runners")).to have_content(@specific_runner2.display_name) expect(page.find(".activated-specific-runners")).to have_content(@specific_runner.display_name) @@ -76,10 +86,10 @@ describe "Runners" do @project.gl_project.team << [user, :master] @specific_runner = FactoryGirl.create :ci_specific_runner @project.runners << @specific_runner - visit runners_path(@project.gl_project) end it "shows runner information" do + visit runners_path(@project.gl_project) click_on @specific_runner.short_sha expect(page).to have_content(@specific_runner.platform) end From 05335a3c8584c48a9317bd0919eccee6948de742 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 16:07:27 +0100 Subject: [PATCH 074/410] Create milestones in the group Signed-off-by: Dmitriy Zaporozhets --- .../groups/milestones_controller.rb | 36 +++++++++++++-- app/views/groups/milestones/index.html.haml | 12 +++-- app/views/groups/milestones/new.html.haml | 46 +++++++++++++++++++ config/routes.rb | 2 +- features/groups.feature | 9 +++- features/steps/groups.rb | 22 +++++++++ features/steps/shared/paths.rb | 4 ++ 7 files changed, 121 insertions(+), 10 deletions(-) create mode 100644 app/views/groups/milestones/new.html.haml diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 669f7f3126..8779376d93 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -1,16 +1,34 @@ class Groups::MilestonesController < Groups::ApplicationController before_action :authorize_group_milestone!, only: :update + before_action :group def index - project_milestones = case params[:state] - when 'all'; state - when 'closed'; state('closed') - else state('active') - end + project_milestones = + case params[:state] + when 'all'; state + when 'closed'; state('closed') + else state('active') + end + @group_milestones = Milestones::GroupService.new(project_milestones).execute @group_milestones = Kaminari.paginate_array(@group_milestones).page(params[:page]).per(PER_PAGE) end + def new + @group_milestone = OpenStruct.new(title: nil, description: nil) + end + + def create + project_ids = params[:milestone][:project_ids] + title = milestone_params[:title] + + @group.projects.where(id: project_ids).each do |project| + Milestones::CreateService.new(project, current_user, milestone_params).execute + end + + redirect_to group_milestone_path(@group, title.parameterize, title: title) + end + def show project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC") @group_milestone = Milestones::GroupService.new(project_milestones).milestone(title) @@ -51,4 +69,12 @@ class Groups::MilestonesController < Groups::ApplicationController def authorize_group_milestone! return render_404 unless can?(current_user, :admin_group, group) end + + def milestone_params + params.require(:milestone).permit( + :title, + :description, + :due_date + ) + end end diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index 2bbcad5fdf..ded4f3713f 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -3,9 +3,15 @@ = render 'shared/milestones_filter' .gray-content-block - Only milestones from - %strong #{@group.name} - group are listed here. + .pull-right + %span.pull-right.hidden-xs + = link_to new_group_milestone_path(@group), class: "btn btn-new" do + New Milestone + + .oneline + Only milestones from + %strong #{@group.name} + group are listed here. .milestones %ul.content-list - if @group_milestones.blank? diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml new file mode 100644 index 0000000000..287f89a707 --- /dev/null +++ b/app/views/groups/milestones/new.html.haml @@ -0,0 +1,46 @@ +%h3.page-title + New Milestone + +%p.light + This will create milestone in every selected project +%hr + += form_for @group_milestone, as: :milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-requires-input' } do |f| + .row + .col-md-6 + .form-group + = f.label :title, "Title", class: "control-label" + .col-sm-10 + = f.text_field :title, maxlength: 255, class: "form-control js-quick-submit", required: true + %p.hint Required + .form-group.milestone-description + = f.label :description, "Description", class: "control-label" + .col-sm-10 + = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do + = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit' + = render 'projects/notes/hints' + .clearfix + .error-alert + .form-group + = f.label :projects, "Projects", class: "control-label" + .col-sm-10 + = f.collection_select :project_ids, @group.projects, :id, :name, + { selected: @group.projects.map(&:id) }, multiple: true, class: 'select2' + + .col-md-6 + .form-group + = f.label :due_date, "Due Date", class: "control-label" + .col-sm-10= f.hidden_field :due_date + .col-sm-10 + .datepicker + + .form-actions + = f.submit 'Create Milestone', class: "btn-create btn" + = link_to "Cancel", group_milestones_path(@group), class: "btn btn-cancel" + + +:javascript + $( ".datepicker" ).datepicker({ + dateFormat: "yy-mm-dd", + onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } + }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); diff --git a/config/routes.rb b/config/routes.rb index bd85f4e3c6..c0077ab1a9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -368,7 +368,7 @@ Gitlab::Application.routes.draw do end resource :avatar, only: [:destroy] - resources :milestones, only: [:index, :show, :update] + resources :milestones, only: [:index, :show, :update, :new, :create] end end diff --git a/features/groups.feature b/features/groups.feature index db37fa3b37..615eff6a33 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -153,6 +153,13 @@ Feature: Groups Then I should see group milestone with descriptions and expiry date And I should see group milestone with all issues and MRs assigned to that milestone + Scenario: Create multiple milestones with one form + Given I visit group "Owned" milestones page + And I click new milestone button + And I fill milestone name + When I press create mileston button + Then milestone in each project should be created + # Group projects in settings Scenario: I should see all projects in the project list in settings Given Group "Owned" has archived project @@ -169,4 +176,4 @@ Feature: Groups When I visit group "Owned" page Then I should see group "Owned" Then I should see project "Public-project" - + diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 70388c18fc..a8fba2406a 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -255,6 +255,28 @@ class Spinach::Features::Groups < Spinach::FeatureSteps expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived') end + step 'I fill milestone name' do + fill_in 'milestone_title', with: 'v2.9.0' + end + + step 'I click new milestone button' do + click_link "New Milestone" + end + + step 'I press create mileston button' do + click_button "Create Milestone" + end + + step 'milestone in each project should be created' do + group = Group.find_by(name: 'Owned') + expect(page).to have_content "Milestone v2.9.0" + expect(group.projects).to be_present + + group.projects.each do |project| + expect(page).to have_content project.name + end + end + protected def assigned_to_me(key) diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index eb978620da..c74a5fd3bc 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -31,6 +31,10 @@ module SharedPaths visit merge_requests_group_path(Group.find_by(name: "Owned")) end + step 'I visit group "Owned" milestones page' do + visit group_milestones_path(Group.find_by(name: "Owned")) + end + step 'I visit group "Owned" members page' do visit group_group_members_path(Group.find_by(name: "Owned")) end From 986695e136a8f6afa326048b30be77a9265d3bf7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 19:20:48 +0100 Subject: [PATCH 075/410] Refactor global and group milestones logic Signed-off-by: Dmitriy Zaporozhets --- app/controllers/concerns/global_milestones.rb | 19 ++++++ .../dashboard/milestones_controller.rb | 29 ++------- .../groups/application_controller.rb | 11 +++- app/controllers/groups/avatars_controller.rb | 2 - .../groups/group_members_controller.rb | 5 -- .../groups/milestones_controller.rb | 65 ++++++------------- app/finders/milestones_finder.rb | 13 ++++ app/helpers/labels_helper.rb | 2 +- app/helpers/milestones_helper.rb | 2 +- app/models/global_label.rb | 17 +++++ ...group_milestone.rb => global_milestone.rb} | 18 +++-- app/models/group_label.rb | 9 --- app/services/labels/group_service.rb | 26 -------- ...group_service.rb => collection_service.rb} | 2 +- .../dashboard/milestones/index.html.haml | 6 +- app/views/dashboard/milestones/show.html.haml | 36 +++++----- app/views/groups/milestones/index.html.haml | 6 +- app/views/groups/milestones/new.html.haml | 2 +- app/views/groups/milestones/show.html.haml | 42 ++++++------ 19 files changed, 146 insertions(+), 166 deletions(-) create mode 100644 app/controllers/concerns/global_milestones.rb create mode 100644 app/finders/milestones_finder.rb create mode 100644 app/models/global_label.rb rename app/models/{group_milestone.rb => global_milestone.rb} (78%) delete mode 100644 app/models/group_label.rb delete mode 100644 app/services/labels/group_service.rb rename app/services/milestones/{group_service.rb => collection_service.rb} (90%) diff --git a/app/controllers/concerns/global_milestones.rb b/app/controllers/concerns/global_milestones.rb new file mode 100644 index 0000000000..b428249acd --- /dev/null +++ b/app/controllers/concerns/global_milestones.rb @@ -0,0 +1,19 @@ +module GlobalMilestones + extend ActiveSupport::Concern + + def milestones + @milestones = MilestonesFinder.new.execute(@projects, params) + @milestones = GlobalMilestone.build_collection(@milestones) + @milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE) + end + + def milestone + milestones = Milestone.of_projects(@projects).where(title: params[:title]) + + if milestones.present? + @milestone = GlobalMilestone.new(params[:title], milestones) + else + render_404 + end + end +end diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb index 53896d4f2c..2bdce0f8a0 100644 --- a/app/controllers/dashboard/milestones_controller.rb +++ b/app/controllers/dashboard/milestones_controller.rb @@ -1,34 +1,19 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController - before_action :load_projects + include GlobalMilestones + + before_action :projects + before_action :milestones, only: [:index] + before_action :milestone, only: [:show] def index - project_milestones = case params[:state] - when 'all'; state - when 'closed'; state('closed') - else state('active') - end - @dashboard_milestones = Milestones::GroupService.new(project_milestones).execute - @dashboard_milestones = Kaminari.paginate_array(@dashboard_milestones).page(params[:page]).per(PER_PAGE) end def show - project_milestones = Milestone.where(project_id: @projects).order("due_date ASC") - @dashboard_milestone = Milestones::GroupService.new(project_milestones).milestone(title) end private - def load_projects - @projects = current_user.authorized_projects.sorted_by_activity.non_archived - end - - def title - params[:title] - end - - def state(state = nil) - conditions = { project_id: @projects } - conditions.reverse_merge!(state: state) if state - Milestone.where(conditions).order("title ASC") + def projects + @projects ||= current_user.authorized_projects.sorted_by_activity.non_archived end end diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb index 6878d4bc07..be801858ea 100644 --- a/app/controllers/groups/application_controller.rb +++ b/app/controllers/groups/application_controller.rb @@ -1,8 +1,13 @@ class Groups::ApplicationController < ApplicationController layout 'group' + before_action :group private - + + def group + @group ||= Group.find_by(path: params[:group_id]) + end + def authorize_read_group! unless @group and can?(current_user, :read_group, @group) if current_user.nil? @@ -12,13 +17,13 @@ class Groups::ApplicationController < ApplicationController end end end - + def authorize_admin_group! unless can?(current_user, :admin_group, group) return render_404 end end - + def authorize_admin_group_member! unless can?(current_user, :admin_group_member, group) return render_403 diff --git a/app/controllers/groups/avatars_controller.rb b/app/controllers/groups/avatars_controller.rb index 6aa64222f7..f390705dc6 100644 --- a/app/controllers/groups/avatars_controller.rb +++ b/app/controllers/groups/avatars_controller.rb @@ -1,8 +1,6 @@ class Groups::AvatarsController < ApplicationController def destroy - @group = Group.find_by(path: params[:group_id]) @group.remove_avatar! - @group.save redirect_to edit_group_path(@group) diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index 91518c44a9..b25957a06e 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -1,6 +1,5 @@ class Groups::GroupMembersController < Groups::ApplicationController skip_before_action :authenticate_user!, only: [:index] - before_action :group # Authorize before_action :authorize_read_group! @@ -80,10 +79,6 @@ class Groups::GroupMembersController < Groups::ApplicationController protected - def group - @group ||= Group.find_by(path: params[:group_id]) - end - def member_params params.require(:group_member).permit(:access_level, :user_id) end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 8779376d93..6833a550c9 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -1,21 +1,16 @@ class Groups::MilestonesController < Groups::ApplicationController - before_action :authorize_group_milestone!, only: :update - before_action :group + include GlobalMilestones + + before_action :projects + before_action :milestones, only: [:index] + before_action :milestone, only: [:show, :update] + before_action :authorize_group_milestone!, only: [:create, :update] def index - project_milestones = - case params[:state] - when 'all'; state - when 'closed'; state('closed') - else state('active') - end - - @group_milestones = Milestones::GroupService.new(project_milestones).execute - @group_milestones = Kaminari.paginate_array(@group_milestones).page(params[:page]).per(PER_PAGE) end def new - @group_milestone = OpenStruct.new(title: nil, description: nil) + @milestone = Milestone.new end def create @@ -26,55 +21,35 @@ class Groups::MilestonesController < Groups::ApplicationController Milestones::CreateService.new(project, current_user, milestone_params).execute end - redirect_to group_milestone_path(@group, title.parameterize, title: title) + redirect_to milestone_path(title) end def show - project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC") - @group_milestone = Milestones::GroupService.new(project_milestones).milestone(title) end def update - project_milestones = Milestone.where(project_id: group.projects).order("due_date ASC") - @group_milestones = Milestones::GroupService.new(project_milestones).milestone(title) - - @group_milestones.milestones.each do |milestone| - Milestones::UpdateService.new(milestone.project, current_user, params[:milestone]).execute(milestone) + @milestone.milestones.each do |milestone| + Milestones::UpdateService.new(milestone.project, current_user, milestone_params).execute(milestone) end - respond_to do |format| - format.js - format.html do - redirect_to group_milestones_path(group) - end - end + redirect_back_or_default(default: milestone_path(@milestone.title)) end private - def group - @group ||= Group.find_by(path: params[:group_id]) - end - - def title - params[:title] - end - - def state(state = nil) - conditions = { project_id: group.projects } - conditions.reverse_merge!(state: state) if state - Milestone.where(conditions).order("title ASC") - end - def authorize_group_milestone! return render_404 unless can?(current_user, :admin_group, group) end def milestone_params - params.require(:milestone).permit( - :title, - :description, - :due_date - ) + params.require(:milestone).permit(:title, :description, :due_date, :state_event) + end + + def milestone_path(title) + group_milestone_path(@group, title.parameterize, title: title) + end + + def projects + @projects ||= @group.projects end end diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb new file mode 100644 index 0000000000..71f207ca03 --- /dev/null +++ b/app/finders/milestones_finder.rb @@ -0,0 +1,13 @@ +class MilestonesFinder + def execute(projects, params) + milestones = Milestone.of_projects(projects) + milestones = milestones.order("due_date ASC") + + case params[:state] + when 'closed' then milestones.closed + when 'all' then milestones + else milestones.active + end + end +end + diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index ee04ace35d..795fb439f2 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -100,7 +100,7 @@ module LabelsHelper Label.where(project_id: @projects) end - grouped_labels = Labels::GroupService.new(labels).execute + grouped_labels = GlobalLabel.build_collection(labels) grouped_labels.unshift(Label::None) grouped_labels.unshift(Label::Any) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 37a5b58cce..ad43892b63 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -28,7 +28,7 @@ module MilestonesHelper Milestone.where(project_id: @projects) end.active - grouped_milestones = Milestones::GroupService.new(milestones).execute + grouped_milestones = GlobalMilestone.build_collection(milestones) grouped_milestones.unshift(Milestone::None) grouped_milestones.unshift(Milestone::Any) diff --git a/app/models/global_label.rb b/app/models/global_label.rb new file mode 100644 index 0000000000..0171f7d54b --- /dev/null +++ b/app/models/global_label.rb @@ -0,0 +1,17 @@ +class GlobalLabel + attr_accessor :title, :labels + alias_attribute :name, :title + + def self.build_collection(labels) + labels = labels.group_by(&:title) + + labels.map do |title, label| + new(title, label) + end + end + + def initialize(title, labels) + @title = title + @labels = labels + end +end diff --git a/app/models/group_milestone.rb b/app/models/global_milestone.rb similarity index 78% rename from app/models/group_milestone.rb rename to app/models/global_milestone.rb index 91844da62e..f96e9d41c9 100644 --- a/app/models/group_milestone.rb +++ b/app/models/global_milestone.rb @@ -1,7 +1,15 @@ -class GroupMilestone +class GlobalMilestone attr_accessor :title, :milestones alias_attribute :name, :title + def self.build_collection(milestones) + milestones = milestones.group_by(&:title) + + milestones.map do |title, milestones| + new(title, milestones) + end + end + def initialize(title, milestones) @title = title @milestones = milestones @@ -10,7 +18,7 @@ class GroupMilestone def safe_title @title.parameterize end - + def projects milestones.map { |milestone| milestone.project } end @@ -60,15 +68,15 @@ class GroupMilestone end def issues - @group_issues ||= milestones.map(&:issues).flatten.group_by(&:state) + @issues ||= milestones.map(&:issues).flatten.group_by(&:state) end def merge_requests - @group_merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state) + @merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state) end def participants - @group_participants ||= milestones.map(&:participants).flatten.compact.uniq + @participants ||= milestones.map(&:participants).flatten.compact.uniq end def opened_issues diff --git a/app/models/group_label.rb b/app/models/group_label.rb deleted file mode 100644 index 0fc39cb877..0000000000 --- a/app/models/group_label.rb +++ /dev/null @@ -1,9 +0,0 @@ -class GroupLabel - attr_accessor :title, :labels - alias_attribute :name, :title - - def initialize(title, labels) - @title = title - @labels = labels - end -end diff --git a/app/services/labels/group_service.rb b/app/services/labels/group_service.rb deleted file mode 100644 index b26cee24d5..0000000000 --- a/app/services/labels/group_service.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Labels - class GroupService < ::BaseService - def initialize(project_labels) - @project_labels = project_labels.group_by(&:title) - end - - def execute - build(@project_labels) - end - - def label(title) - if title - group_label = @project_labels[title].group_by(&:title) - build(group_label).first - else - nil - end - end - - private - - def build(label) - label.map { |title, labels| GroupLabel.new(title, labels) } - end - end -end diff --git a/app/services/milestones/group_service.rb b/app/services/milestones/collection_service.rb similarity index 90% rename from app/services/milestones/group_service.rb rename to app/services/milestones/collection_service.rb index 11d702f1e7..2eefec99e7 100644 --- a/app/services/milestones/group_service.rb +++ b/app/services/milestones/collection_service.rb @@ -1,5 +1,5 @@ module Milestones - class GroupService < Milestones::BaseService + class CollectionService < Milestones::BaseService def initialize(project_milestones) @project_milestones = project_milestones.group_by(&:title) end diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml index 21b25c3986..635251e237 100644 --- a/app/views/dashboard/milestones/index.html.haml +++ b/app/views/dashboard/milestones/index.html.haml @@ -10,10 +10,10 @@ .milestones %ul.content-list - - if @dashboard_milestones.blank? + - if @milestones.blank? %li .nothing-here-block No milestones to show - else - - @dashboard_milestones.each do |milestone| + - @milestones.each do |milestone| = render 'milestone', milestone: milestone - = paginate @dashboard_milestones, theme: "gitlab" + = paginate @milestones, theme: "gitlab" diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml index 2fe14c6388..580db613ed 100644 --- a/app/views/dashboard/milestones/show.html.haml +++ b/app/views/dashboard/milestones/show.html.haml @@ -1,14 +1,14 @@ -- page_title @dashboard_milestone.title, "Milestones" +- page_title @milestone.title, "Milestones" %h4.page-title - .issue-box{ class: "issue-box-#{@dashboard_milestone.closed? ? 'closed' : 'open'}" } - - if @dashboard_milestone.closed? + .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" } + - if @milestone.closed? Closed - else Open - Milestone #{@dashboard_milestone.title} + Milestone #{@milestone.title} %hr -- if (@dashboard_milestone.total_items_count == @dashboard_milestone.closed_items_count) && @dashboard_milestone.active? +- if (@milestone.total_items_count == @milestone.closed_items_count) && @milestone.active? .alert.alert-success %span All issues for this milestone are closed. You may close the milestone now. @@ -22,7 +22,7 @@ %th Open issues %th State %th Due date - - @dashboard_milestone.milestones.each do |milestone| + - @milestone.milestones.each do |milestone| %tr %td = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) @@ -39,46 +39,46 @@ .context %p.lead Progress: - #{@dashboard_milestone.closed_items_count} closed + #{@milestone.closed_items_count} closed – - #{@dashboard_milestone.open_items_count} open - = milestone_progress_bar(@dashboard_milestone) + #{@milestone.open_items_count} open + = milestone_progress_bar(@milestone) %ul.nav.nav-tabs %li.active = link_to '#tab-issues', 'data-toggle' => 'tab' do Issues - %span.badge= @dashboard_milestone.issue_count + %span.badge= @milestone.issue_count %li = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do Merge Requests - %span.badge= @dashboard_milestone.merge_requests_count + %span.badge= @milestone.merge_requests_count %li = link_to '#tab-participants', 'data-toggle' => 'tab' do Participants - %span.badge= @dashboard_milestone.participants.count + %span.badge= @milestone.participants.count .pull-right - = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @dashboard_milestone.title), class: "btn edit-milestone-link btn-grouped" + = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped" .tab-content .tab-pane.active#tab-issues .row .col-md-6 - = render 'issues', title: "Open", issues: @dashboard_milestone.opened_issues + = render 'issues', title: "Open", issues: @milestone.opened_issues .col-md-6 - = render 'issues', title: "Closed", issues: @dashboard_milestone.closed_issues + = render 'issues', title: "Closed", issues: @milestone.closed_issues .tab-pane#tab-merge-requests .row .col-md-6 - = render 'merge_requests', title: "Open", merge_requests: @dashboard_milestone.opened_merge_requests + = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests .col-md-6 - = render 'merge_requests', title: "Closed", merge_requests: @dashboard_milestone.closed_merge_requests + = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests .tab-pane#tab-participants %ul.bordered-list - - @dashboard_milestone.participants.each do |user| + - @milestone.participants.each do |user| %li = link_to user, title: user.name, class: "darken" do = image_tag avatar_icon(user, 32), class: "avatar s32" diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index ded4f3713f..ffd7dd3fc0 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -14,10 +14,10 @@ group are listed here. .milestones %ul.content-list - - if @group_milestones.blank? + - if @milestones.blank? %li .nothing-here-block No milestones to show - else - - @group_milestones.each do |milestone| + - @milestones.each do |milestone| = render 'milestone', milestone: milestone - = paginate @group_milestones, theme: "gitlab" + = paginate @milestones, theme: "gitlab" diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index 287f89a707..4c490d8ccb 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -5,7 +5,7 @@ This will create milestone in every selected project %hr -= form_for @group_milestone, as: :milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-requires-input' } do |f| += form_for @milestone, url: group_milestones_path(@group), html: { class: 'form-horizontal milestone-form gfm-form js-requires-input' } do |f| .row .col-md-6 .form-group diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index a92ad5d751..e609abca08 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -1,22 +1,22 @@ -- page_title @group_milestone.title, "Milestones" +- page_title @milestone.title, "Milestones" = render "header_title" %h4.page-title - .issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" } - - if @group_milestone.closed? + .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" } + - if @milestone.closed? Closed - else Open - Milestone #{@group_milestone.title} + Milestone #{@milestone.title} .pull-right - if can?(current_user, :admin_group, @group) - - if @group_milestone.active? - = link_to 'Close Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" + - if @milestone.active? + = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" - else - = link_to 'Reopen Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" + = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" %hr -- if (@group_milestone.total_items_count == @group_milestone.closed_items_count) && @group_milestone.active? +- if (@milestone.total_items_count == @milestone.closed_items_count) && @milestone.active? .alert.alert-success %span All issues for this milestone are closed. You may close the milestone now. @@ -30,7 +30,7 @@ %th Open issues %th State %th Due date - - @group_milestone.milestones.each do |milestone| + - @milestone.milestones.each do |milestone| %tr %td = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) @@ -47,46 +47,46 @@ .context %p.lead Progress: - #{@group_milestone.closed_items_count} closed + #{@milestone.closed_items_count} closed – - #{@group_milestone.open_items_count} open - = milestone_progress_bar(@group_milestone) + #{@milestone.open_items_count} open + = milestone_progress_bar(@milestone) %ul.nav.nav-tabs %li.active = link_to '#tab-issues', 'data-toggle' => 'tab' do Issues - %span.badge= @group_milestone.issue_count + %span.badge= @milestone.issue_count %li = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do Merge Requests - %span.badge= @group_milestone.merge_requests_count + %span.badge= @milestone.merge_requests_count %li = link_to '#tab-participants', 'data-toggle' => 'tab' do Participants - %span.badge= @group_milestone.participants.count + %span.badge= @milestone.participants.count .pull-right - = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @group_milestone.title), class: "btn edit-milestone-link btn-grouped" + = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped" .tab-content .tab-pane.active#tab-issues .row .col-md-6 - = render 'issues', title: "Open", issues: @group_milestone.opened_issues + = render 'issues', title: "Open", issues: @milestone.opened_issues .col-md-6 - = render 'issues', title: "Closed", issues: @group_milestone.closed_issues + = render 'issues', title: "Closed", issues: @milestone.closed_issues .tab-pane#tab-merge-requests .row .col-md-6 - = render 'merge_requests', title: "Open", merge_requests: @group_milestone.opened_merge_requests + = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests .col-md-6 - = render 'merge_requests', title: "Closed", merge_requests: @group_milestone.closed_merge_requests + = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests .tab-pane#tab-participants %ul.bordered-list - - @group_milestone.participants.each do |user| + - @milestone.participants.each do |user| %li = link_to user, title: user.name, class: "darken" do = image_tag avatar_icon(user, 32), class: "avatar s32" From c79d801bf58c58ec21e64cb782176d6dc879a60f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 Nov 2015 19:31:02 +0100 Subject: [PATCH 076/410] Fix a bug when milestone/label filter was empty for dashboard issues page Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + app/controllers/dashboard_controller.rb | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 9806666833..dd4306ed0c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -42,6 +42,7 @@ v 8.2.0 (unreleased) - Ability to add release notes (markdown text and attachments) to git tags (aka Releases) - Relative links from a repositories README.md now link to the default branch - Fix trailing whitespace issue in merge request/issue title + - Fix bug when milestone/label filter was empty for dashboard issues page v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 4ebb3d7276..b2c1fa4230 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,5 +1,6 @@ class DashboardController < Dashboard::ApplicationController before_action :event_filter, only: :activity + before_action :projects, only: [:issues, :merge_requests] respond_to :html @@ -47,4 +48,8 @@ class DashboardController < Dashboard::ApplicationController @events = @event_filter.apply_filter(@events).with_associations @events = @events.limit(20).offset(params[:offset] || 0) end + + def projects + @projects ||= current_user.authorized_projects.sorted_by_activity.non_archived + end end From 98d6d491b5187945b2d86db20af411240716980a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 14:38:34 +0100 Subject: [PATCH 077/410] Move global milestone specs Signed-off-by: Dmitriy Zaporozhets --- app/services/milestones/collection_service.rb | 26 ------- spec/models/global_milestone_spec.rb | 65 +++++++++++++++++ .../services/milestones/group_service_spec.rb | 70 ------------------- 3 files changed, 65 insertions(+), 96 deletions(-) delete mode 100644 app/services/milestones/collection_service.rb create mode 100644 spec/models/global_milestone_spec.rb delete mode 100644 spec/services/milestones/group_service_spec.rb diff --git a/app/services/milestones/collection_service.rb b/app/services/milestones/collection_service.rb deleted file mode 100644 index 2eefec99e7..0000000000 --- a/app/services/milestones/collection_service.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Milestones - class CollectionService < Milestones::BaseService - def initialize(project_milestones) - @project_milestones = project_milestones.group_by(&:title) - end - - def execute - build(@project_milestones) - end - - def milestone(title) - if title - group_milestone = @project_milestones[title].group_by(&:title) - build(group_milestone).first - else - nil - end - end - - private - - def build(milestone) - milestone.map{ |title, milestones| GroupMilestone.new(title, milestones) } - end - end -end diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb new file mode 100644 index 0000000000..6eeff30b20 --- /dev/null +++ b/spec/models/global_milestone_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe GlobalMilestone do + let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:group) { create(:group) } + let(:project1) { create(:project, group: group) } + let(:project2) { create(:project, path: 'gitlab-ci', group: group) } + let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } + let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } + let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } + let(:milestone1_project3) { create(:milestone, title: "Milestone v1.2", project: project3) } + let(:milestone2_project1) { create(:milestone, title: "VD-123", project: project1) } + let(:milestone2_project2) { create(:milestone, title: "VD-123", project: project2) } + let(:milestone2_project3) { create(:milestone, title: "VD-123", project: project3) } + + describe :build_collection do + before do + milestones = + [ + milestone1_project1, + milestone1_project2, + milestone1_project3, + milestone2_project1, + milestone2_project2, + milestone2_project3 + ] + + @global_milestones = GlobalMilestone.build_collection(milestones) + end + + it 'should have all project milestones' do + expect(@global_milestones.count).to eq(2) + end + + it 'should have all project milestones titles' do + expect(@global_milestones.map(&:title)).to match_array(['Milestone v1.2', 'VD-123']) + end + + it 'should have all project milestones' do + expect(@global_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6) + end + end + + describe :initialize do + before do + milestones = + [ + milestone1_project1, + milestone1_project2, + milestone1_project3, + ] + + @global_milestone = GlobalMilestone.new(milestone1_project1.title, milestones) + end + + it 'should have exactly one group milestone' do + expect(@global_milestone.title).to eq('Milestone v1.2') + end + + it 'should have all project milestones with the same title' do + expect(@global_milestone.milestones.count).to eq(3) + end + end +end diff --git a/spec/services/milestones/group_service_spec.rb b/spec/services/milestones/group_service_spec.rb deleted file mode 100644 index 74eb0f99e0..0000000000 --- a/spec/services/milestones/group_service_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -require 'spec_helper' - -describe Milestones::GroupService do - let(:user) { create(:user) } - let(:user2) { create(:user) } - let(:group) { create(:group) } - let(:project1) { create(:project, group: group) } - let(:project2) { create(:project, path: 'gitlab-ci', group: group) } - let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } - let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } - let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } - let(:milestone1_project3) { create(:milestone, title: "Milestone v1.2", project: project3) } - let(:milestone2_project1) { create(:milestone, title: "VD-123", project: project1) } - let(:milestone2_project2) { create(:milestone, title: "VD-123", project: project2) } - let(:milestone2_project3) { create(:milestone, title: "VD-123", project: project3) } - - describe 'execute' do - context 'with valid projects' do - before do - milestones = - [ - milestone1_project1, - milestone1_project2, - milestone1_project3, - milestone2_project1, - milestone2_project2, - milestone2_project3 - ] - @group_milestones = Milestones::GroupService.new(milestones).execute - end - - it 'should have all project milestones' do - expect(@group_milestones.count).to eq(2) - end - - it 'should have all project milestones titles' do - expect(@group_milestones.map { |group_milestone| group_milestone.title }).to match_array(['Milestone v1.2', 'VD-123']) - end - - it 'should have all project milestones' do - expect(@group_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6) - end - end - end - - describe 'milestone' do - context 'with valid title' do - before do - milestones = - [ - milestone1_project1, - milestone1_project2, - milestone1_project3, - milestone2_project1, - milestone2_project2, - milestone2_project3 - ] - @group_milestones = Milestones::GroupService.new(milestones).milestone('Milestone v1.2') - end - - it 'should have exactly one group milestone' do - expect(@group_milestones.title).to eq('Milestone v1.2') - end - - it 'should have all project milestones with the same title' do - expect(@group_milestones.milestones.count).to eq(3) - end - end - end -end From 8a03cb87442a42c8fc1630ab356dfd35dbb476f7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 14:39:19 +0100 Subject: [PATCH 078/410] Lets add more tests to Milestones services Signed-off-by: Dmitriy Zaporozhets --- app/finders/milestones_finder.rb | 1 - .../services/milestones/close_service_spec.rb | 28 +++++++++++++++++++ .../milestones/create_service_spec.rb | 24 ++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 spec/services/milestones/close_service_spec.rb create mode 100644 spec/services/milestones/create_service_spec.rb diff --git a/app/finders/milestones_finder.rb b/app/finders/milestones_finder.rb index 71f207ca03..b704e87890 100644 --- a/app/finders/milestones_finder.rb +++ b/app/finders/milestones_finder.rb @@ -10,4 +10,3 @@ class MilestonesFinder end end end - diff --git a/spec/services/milestones/close_service_spec.rb b/spec/services/milestones/close_service_spec.rb new file mode 100644 index 0000000000..034c0f22e1 --- /dev/null +++ b/spec/services/milestones/close_service_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Milestones::CloseService do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:milestone) { create(:milestone, title: "Milestone v1.2", project: project) } + + before do + project.team << [user, :master] + end + + describe :execute do + before do + Milestones::CloseService.new(project, user, {}).execute(milestone) + end + + it { expect(milestone).to be_valid } + it { expect(milestone).to be_closed } + + describe :event do + let(:event) { Event.first } + + it { expect(event.milestone).to be_truthy } + it { expect(event.target).to eq(milestone) } + it { expect(event.action_name).to eq('closed') } + end + end +end diff --git a/spec/services/milestones/create_service_spec.rb b/spec/services/milestones/create_service_spec.rb new file mode 100644 index 0000000000..757c9a226d --- /dev/null +++ b/spec/services/milestones/create_service_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe Milestones::CreateService do + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + + describe :execute do + context "valid params" do + before do + project.team << [user, :master] + + opts = { + title: 'v2.1.9', + description: 'Patch release to fix security issue' + } + + @milestone = Milestones::CreateService.new(project, user, opts).execute + end + + it { expect(@milestone).to be_valid } + it { expect(@milestone.title).to eq('v2.1.9') } + end + end +end From 8630d476e419ba524cb5f23e25772bcf2223195d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 14:45:20 +0100 Subject: [PATCH 079/410] Add header and page title to new milestone page Signed-off-by: Dmitriy Zaporozhets --- app/views/groups/milestones/new.html.haml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index 4c490d8ccb..d4284af8a0 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -1,3 +1,6 @@ +- page_title "Milestones" +- header_title group_title(@group, "Milestones", group_milestones_path(@group)) + %h3.page-title New Milestone From f16f315115e732493d976a9c866af351253ecf61 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 15:07:16 +0100 Subject: [PATCH 080/410] Few changes to Group Milestone feature: * Group attachments are not supported so I removed attach file link * Added changelog item * Add markdown hint to project milestone form Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + app/assets/javascripts/dispatcher.js.coffee | 2 ++ app/views/groups/milestones/new.html.haml | 3 +-- app/views/projects/milestones/_form.html.haml | 6 ++---- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dd4306ed0c..27ce7f3668 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -43,6 +43,7 @@ v 8.2.0 (unreleased) - Relative links from a repositories README.md now link to the default branch - Fix trailing whitespace issue in merge request/issue title - Fix bug when milestone/label filter was empty for dashboard issues page + - Add ability to create milestone in group projects from single form v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 951173af5d..4059fc39c6 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -28,6 +28,8 @@ class Dispatcher when 'projects:milestones:new', 'projects:milestones:edit' new ZenMode() new DropzoneInput($('.milestone-form')) + when 'groups:milestones:new' + new ZenMode() when 'projects:compare:show' new Diff() when 'projects:issues:new','projects:issues:edit' diff --git a/app/views/groups/milestones/new.html.haml b/app/views/groups/milestones/new.html.haml index d4284af8a0..800bac4ef0 100644 --- a/app/views/groups/milestones/new.html.haml +++ b/app/views/groups/milestones/new.html.haml @@ -21,7 +21,6 @@ .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit' - = render 'projects/notes/hints' .clearfix .error-alert .form-group @@ -43,7 +42,7 @@ :javascript - $( ".datepicker" ).datepicker({ + $(".datepicker").datepicker({ dateFormat: "yy-mm-dd", onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index 255ddab479..24879b19d2 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -23,9 +23,7 @@ .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do = render 'projects/zen', f: f, attr: :description, classes: 'description form-control js-quick-submit' - .hint - .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. - .pull-left Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + = render 'projects/notes/hints' .clearfix .error-alert .col-md-6 @@ -45,7 +43,7 @@ :javascript - $( ".datepicker" ).datepicker({ + $(".datepicker").datepicker({ dateFormat: "yy-mm-dd", onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); From f445d3d1f317d8addbe60cf31fa15ca592ba0d19 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 16 Nov 2015 15:36:14 +0100 Subject: [PATCH 081/410] unique is for indexes. --- db/schema.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index a8e8dfe6bb..4870fe3fc4 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -423,7 +423,7 @@ ActiveRecord::Schema.define(version: 20151114113410) do add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree create_table "lfs_objects", force: true do |t| - t.string "oid", null: false, unique: true + t.string "oid", null: false t.integer "size", null: false t.datetime "created_at" t.datetime "updated_at" From 78d542fcd811b6e18209fb97f126fd6291bd411b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 15:56:50 +0100 Subject: [PATCH 082/410] Add milestones documentation to workflow Signed-off-by: Dmitriy Zaporozhets --- doc/workflow/README.md | 1 + doc/workflow/milestones.md | 13 +++++++++++++ doc/workflow/milestones/form.png | Bin 0 -> 88591 bytes doc/workflow/milestones/group_form.png | Bin 0 -> 77087 bytes 4 files changed, 14 insertions(+) create mode 100644 doc/workflow/milestones.md create mode 100644 doc/workflow/milestones/form.png create mode 100644 doc/workflow/milestones/group_form.png diff --git a/doc/workflow/README.md b/doc/workflow/README.md index a2653704c3..c1a4f64981 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -14,5 +14,6 @@ - [Protected branches](protected_branches.md) - [Web Editor](web_editor.md) - [Releases](releases.md) +- [Milestones](milestones.md) - [Merge Requests](merge_requests.md) - ["Work In Progress" Merge Requests](wip_merge_requests.md) diff --git a/doc/workflow/milestones.md b/doc/workflow/milestones.md new file mode 100644 index 0000000000..1cd1f0f2fc --- /dev/null +++ b/doc/workflow/milestones.md @@ -0,0 +1,13 @@ +# Milestones + +Milestone allows you to group issues and set due date for it. +Milestone is created per project. + +![milestone form](milestones/form.png) + +## Groups and milestones + +You can create milestone with single form for several projects that belongs to the same group. +On the group milestones page you will be able to see this milestones grouped together by name. + +![group milestone form](milestones/group_form.png) diff --git a/doc/workflow/milestones/form.png b/doc/workflow/milestones/form.png new file mode 100644 index 0000000000000000000000000000000000000000..de44c1ffc1a44c21e7d6eff77ff8752d84b6ea8f GIT binary patch literal 88591 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

7chsg28F&v6Qs`*;tr3Ve?{d?nB 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/milestones/group_form.png b/doc/workflow/milestones/group_form.png new file mode 100644 index 0000000000000000000000000000000000000000..38862dcca6895c28a09c6173fc8a2d7b1552b528 GIT binary patch literal 77087 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

hub?d7kLqUFo_P#LClu2 znc|Z~!I%7tuc@j3Owewbar*E4=UyZUH?)Yb9Ks$dU8pD&-@h}P78TGRux_wij&rYm zKeu^(ir9xD4j3=z~$QPNrAE z)t-OTRE76ajm{SGD1QsRbJqw(b?U#Euzx-@>W7XA4#i~=CqzLkT_cYg8b|^Lg9Y{8~z)70Y(M|dRc-KigtK< z{lB3eo&XS5vLQ}Oi7%OQe|PUs;n+WBt|uTxuKzo*1qm?rPe6W4^S|Hb`n$2qj7CsM zg&Zd|qq09!0rFEN?OPcnN<#M~l!6}&L<^)E?4o?n9{j3Xbo)7juaeQgOYHDa8M5!_ zkv0>n2-Zg8y(2c-@QSwu!k8W_!fD5sXU|~7pmsN?=J>A$y1z32r`_$3wa#!l5_@a4ERndk>d32Pf@3*S4|-;P1}1f!~C2M^YA_G z-s~Bu=HQ$_?)w;GBF;cBzLU&kdmHkq!d@F5K@8>?;P@gRxvN*URX!Lq()gk$423dq zmwa0x&M^jI>#4*vbZiTF^L0@YeMJGORi*zzHVggoldsvpR_@!UWKxy~s)rT2Ek5st zj*q1KCK+2Pg@cne4^((mnFK;V?|!*w+`HMG`u`DT!K%K{v?>|`cvF#r<#oSfUKJ20 zRR7or$9I-Y_vrP>S~noXg??$z3d}AfR~~cFsLAPx?3G(IEN#L`e$EX_f+>Z~g-6Q$ zpS2&v4o&;6VAnMZSTf^1Iy~BLzWZ5K-L^%wKD!n!l$G!CqRzA+%F5{742oo*7fJ}9 z&26PZwD4{0o;ObW)kSNRy3z({42mb(u&!LEzO50W@M)O3T1FB%pKoaP zcCAJpJi$ISnm8lG*izYJNN4Rl<=OhRy7G}b(=vCEgV{KJcjm=Z5U#7m>*{p#GX&9G z_Km?$>T>m#AOY0wOO4HtEl)ch-xt~spcAjZ?w74Ny^YjG8uh)>V*7c@)@?ydPv=P z!r52bt&BAXbKXxMlcl$Xbue^5b*R67;ec>}A2yI_=@Di{Yt^RKX6&v{RD#vJl>|86 z;Bn)EjD!(+$qOQs1`okl1hHLOVJuIYXA!lib_TVXf>qe=GzhV1Fdd2$A`LZ#NJ``u z1Rv$}5d&px&(1734X=}wtrzR3V|QS+OPesVROO2IfXph--tm2f@Ayf*d)wVf4CC+s zKZ}*gD|*a!P6Q+b{xy$+pJtLVOm26gK|EDjiU>`Vfhebs0owze?9HCtxM$SCVpyBq zqJQPl%!6kHz>6kqB^;w&hAtyM+mJ&9zHG&g8B}D))?mf1h?X$nKXFN^wL#lkBLIsm z!qVak4o2He{`|?#@YuaRAaqmnv%PI?1S3^G@IO1P{5+QA$5nw|wAv6Gx`O%pDg17zFFquaz zf8&O*@&Sp@Of#bR-Tgj>R7lQkuaDuBQvce_flP=VMGiu{-8zM^ISU$znBk9)kl`+b zpV~>g50h}D7Bs1kxjVykxl*sWC|L?A9&Xe(=&)=#xR2Sc|8s|POiKu_}Lf&t{8HQpJooYWkORTh5=?~#cS z@O&015@cZ>X*ws_q~AR)2S0jFI?wJ=bh9!Z6H3p49c9SpiT&(z`(#W=6pcDh=bENz z*hgzTx!4shBNNC6z^E2LTe{CN8%G3?-UvsaVT0P$M4eQw1p^BPcyx^X?=geJ2D)`g zO+}D+8+m$?Ni6yK6X*Wa!vnhY(Lik!?*;NE)7&OKx_TGx>mpxodAu@^hLMIxy2za( zB0{9CjeIR0EWg5c!XVK?$s~ww#@K zeBctci&SQ})Eco_D|)gspw1+BpDA;0aCxYe??V|1yFG=hdHJQznQ8-2$9+8`$jFO< z(!mLE6&SPeWntB_81|V{z%E;a#wE8k$hAt{ai5cQdC>n_e#Dk~@nfr$XQL%*V!b8A zcFa5fE#xkKWa09}*u70XF9O2ot4(p220uLpGvBLf*GK|T)yI`GLCC}y*ulv zTqT09$!g`E!{$ppQ++K!w=lm)GZ0VpNK=mRY|oyo@zYlisOwWm5{@nG5^-9Fgt zTz|DFSPd3>ty0v%5Pm3J+4-H-HsZIy>w$ z7Ci9hw@NtGB80*)Wc6&g9}*obu#L;l=Q|Oi?D3H>Ps6XUiaD^~`CD4IQn&`A#Gnxz zX5MF{!DNA-5~aTyEV>31jeCoN7!K3U5BK+F70~BwR2Udt(T>m9Fk9$zMJ>1TY}!N{#weEPFTn z@gQ>Fhge!DwE79Dln+k@#b4k2;#0$Yj@L&OnzP+!Vw|=fH0z}?F*_X6T=2E<#S50r zaUpJWjdjzA7>+{}y-3xR0!}4mW5wc^+^4Wm0FALgAzvtNPuJ+0%iBk9yC^kdcEU;t z*y{^Tg?E29?Fkx{4R&SC-85E0iz)!_4Zj|Ou-3ebuM(Kmnz%JGAg*dzIEEbXvjKHK zl+Lz1yeXw%n0P58`(vWYv!&I$ppU3_Pu}II!kRHB8LDG~6sq;`=nu_7vxn#A&hDcI zsZ4ervFQ(LzDBh6VR-6kNPlozPXMUyv+!2W%}uuV zy|Y{BHzd@qs9O$Z=m&Pd0L(_N_^(#`iE_ilJuNugOxY|yXN_LQ&F!_ZS!P!<_pt+D zwx0cs{!sx95tOumG(r2{cQYzEbBZfDJ1-`R{GWZpM+L?wmb|~(A6KK^-7nVd512Y* zPQ6XICJ=qi|Ngbc+>NZh%kb4wvDS>5^|x#miw|YEuC>hk8>xDV$C?hteTc+8Cl9=D zovlfiY|bR>z8h<7DLv6jPQCn#ri&VP;#c=r-q2=TuN24pmvy^iJ((K%UhYS;DZ3_T zwWndZzS%c#H%0A6w&#rGYmaAbP&I0m+nDD~xHUD3qDTX7s-y#eIuy7rm5A7$NPpalk$nf2k$ZzJI04>+UIO_ZXw9QS+S@K!9edzYm5@AC3<8JFvS@pM-$hwc~H(D?HXMdrXQ8aAe;hbs7 zC3tH_ijKKPGM@2hnJRf#BbKu!Yr5d(@me8Mhv7`~M7c+jC(Em^we zX+5pQ92U%5QhNjg-n;_6ZtX{+A2@h%6xr4>ZQ?O`Md;_b@Jpmv=Z(;pQ{t0iHPE+7 z>U^Tp!(tT1iJv^-GszrR*@^Wh&6n<%n(-fKBW4Hix-jHEJi#2HZ7q6%a$ka;nR@v} zO#<>_gZ_?#aIepNV}vWcejy&$oUp<_eW~-^JagZAQ{!^}odOC;C*v9y(JSPYrDqa< zaFvAN0+DuJTS(%Zt2ygZpM*dmZn1!RH};IA_J+;gZFtSI;R&T;SIqQ7@kMNLs*!N} z!I#%@2$3PKUtL7tsv>Y{IYyID$#7n`TH0^a9(gnYsgS9jz>d7p@ZW}(T(ajnGEGU( z8o(A25d-E)U4#>A{eq8=t_iFU1LnCq+;F$6_KJ+E?)SF}GG?4aLLS#xQ{{SR(w3Pu z*b+j+XR}b(8vj4o!j6UyD>#57IRzDvE&?OfFRBKQu{8uGtkr+NbF0iE(Ng@*kpYf&jQF+aJAemw(;O0@CVIku zhQ1a8j&3!F#xYJk+vd}5ebvNlqG(6#`eiHYeYk<08g;soyz%*SiO4dNP=x*ceK}lQ zAyoHjO!ok4R(b@B7`huzDI7#rVhxt%EETa6urtDG!W2GKK>k5(qRYUQ#~xQCWPx4n z_>w%A69A~OfRGoYu{s|66Osw1(%-v1XGB;G-kF%z$j)M}DJ|hTYk1OXI66J}jQF_n z({pA6Y`T8FKzRKsBwi1Ji!pY^bv^~l;cL_B^uf}(RKIm;e(@5@`prQYltuETvS{t& zAQ`0F!HQx_A60%F_DW4~EF_IHF$3YP&`x+Bg@pOOB76)y*`Eh!m)wm8ZXo(?1)?L? zHtAa?;w5nRR&X&o94)rA(^`h)-mOo&Wk-{pF21o~aB3isRIJ8SgiDrZ2Fi4v5@VBp zU(HcL5oaE{K8gjV(I-p^XNGkgs^xR_t9_V6frm)VA_ zxh1<>O*Jn=qw#5*sFC9%ClV4la6d*_h8oYzi}Q>I&|O`81XinvFsg>CjPKPm&PSft zooibubjV#&|HzyHWJhfk{ ztRZrx_KnuV6oCb#=i~gg!g8i5$4SxKSv&NQ==M0-h3oSxlxBtp_meXb5K3nr>J5ea z*45bLr{0rCQ{I-{m(n2qSquC+UyRo!`yBNSJY^i}DqOOPd13$#dJ7g!i%n@OmRYfH z@ap(+T7mo+Oc|u=;Zw_WNyVDjVbcM2r2}yuYhg|+VMiY1$tDyShJj`SzDKfd0iKHn zeSM;l=U%RvK~NeVWkQ9>O6t2ezHqD>F@42sp+!@w^uU+!dfb-S!<**|XuY+^bfQcr(P?s6HMA>Ciz#5p47(}Y6s}-OmouX&dQ>4+d|h&snxa&VdPCg> z%rpZaYz=_|=AWD(a$f+wAcJ0EuGjsKV!PnT`WGL^zIxl`fVchFPJbos4eT47v@+Q^ zOcIEDXhhO9Hv6xfDe-%qKtwAEz5Nf%IFw!4&xFEGO4Qi$55h6I2KS;J-h92bKKCVE zGM%SkL(x_OrQO4=-kRGL4e~iVWuIn1Bh`KK&~~1q?V_gIrR_mW6I5apZzy`=!(+%R z2HZ?7{csL|4O$4$n~2PbY6)H-L8v~9M01(Ph<3xi?RStAgMI!qxc_R&`L zgT^fm z9^NRHpQn{Vsxm~~%PkM{RF|xy*=unm(SB_fS%#vb-|G0Hq$$FE9Votc0N(CgFx6wo zXa89u+D3h3xvgmcEFEK5e;}_TI+$bgXF56&D8GWLVXVc_*+tdt4c$Q-LW4E5t@z~~ zKtsW@z8fUYu^qFjl|z|G49qfJVc&RD6kvZE;Ad|aVEa808k*k|D;NETz2tPciGY8gs<3x%jQG#QEL?D)k~Sy^9BIu4%3d zd9#2n0bPnXLyFp(` zRKQIuLEvji(0E8BURv267esfnlq4~vPDiA$`&A5PG}(*Wr5;Ao67r}pFrSSrbR_;| zZtNn%!$#mM=116X=lR1CDb_J)<@xh_`=Ral26|}UY9NSV2<#kW2h)DQ8G67IhH6Lm zy@;r$>McX&{1u355)Mc)Q!n4a9)3Gke6(fX63O?EY5rCRN0T{XFswu{tqryVDQt};jA}B&4+fiW*Nwz(|6K6QV6EMo_y*$bJ5>h^wMjrm7{PmVj*D=wAh2Ts44v7mP37ibQ ze5qz7(6w;|E$#Su7cy+rRs<)Hn^C<(R5PswH{+)R6knm74U zl}ex*%wY}>oRu6@s6yX)M-SZ_osLWSmejEu$cZh)0&s||W8@VG`|^v(^Uh4i`#VbH z(y_mN$@p>bfQLk9Ou~>V!C}hM$XcE97NlT}xwP&m&e8jUCZ3HqV~iH4ixdrT)c-}d z`!UI1ic-&FdPy~&sOP zAqF=eU)6pd{$}EtS*EdAg3x@H@;oknMCH`3-s+=^fn0yN2;Db2-Ph0#OSnGL>W0(a z$-Y6(2ZRC-mLI}^M7j5mLd<16W@lS9YLgYOs*n%Fa4Z<`!W<(_u^@kM0FA&di%u&u z-(3P-ujSc8d6kMPGexWb0LWbw)U2Sy^9sbrfj?q=*owIj-za!{)PER2kCS);=^LrM zHifq>B(tF(7GLj}suqMj{E`^&19@G~0W;i#oB)8dozk68DIbLla$8U(I-cTBJ+%*FH@~ zJ;6zr%Z%Qp7M^}5sNx}w_axb_lskK%TXk=wr~MHaP{sl*ZKQ(?E|N9#JnwZNg;>Gk zVI!PSUwd!<^h07ph~h^UbR}`cLG`7?L(PqmbCspsw;5Dk`b_DnB9mIaaMqsKVK|5( zk9xzh5JymP^zhs%l@_$`lmjn&)p7s}b9@_;8uNJ%=8wmPGt$-a=vNc?5Jz2AU%!UN zfl-Lu2Ajx_p`raH=8Dv{J^BVBlm?8yPFInZx(eB%uz}wlq@m}Z_FMpaGMGQU70U7o zr{LfIA+|EG<#2z>J1<>c-;{jml;T(7&v-gNr}yG=Y}D!SuPLU>m;zn0{W}5bRc`?#;5|z9Gf{pQRk`AEgJ<>_XqCXdb`z! zLb}U*opDu-uh+kjVA@r4rK)DEbN0Szm1%BR{n(d!GrUs1*XwjkpbGn^dt9o%a}D3p zWHmIr%UEL!VY}+XPF&9hOQxf2Jj%^gH9lUKA;3W5NWwo`A^z!+Sq^+q;@Vns=zC}E zAMD*fuSU!9Ps*7x@31@-Xtqf2#-#PUAa2-m(60JnUk@yjeQgNycOdnCWb5ec?3Agj zti&q7V(}LX{fq_u3;^=_@i8)l2y4Z~o8skObr0UudczSCx${RlGyDX`%^zf#XixDN(nGB*5A_7aVB388k@ho=G>jhcQ z67nm#tdW#el804UC^BU9p>uewn)+ z1zvvg>1uIXZ>mI7V$nBU@SLmhh5k}6fq8(g?b_EPYr4fa=t(>aJFMaGKh9Fr;PyUO zeYLkR(TT++AZh%$k64>-ENXkyXfTIy&LzbRD%%1tqGoRcZxr=8TiFiuTRgSG%D>n= z&rfu&xP#z#75(5GcV}1yk}VPR9~@}Q&S)yUdQq{kS3ns&{^^V0ZG9YuBDsAA6io4- zJwFu+p?r-iYk;I{pnc=Xod2%v4OmH9^}c*wB4GbA$aZEj!9weNGVx@p1?tw0_CmL$ zYfpdr%yz8!hYI~`o8o3bN_E#@mu{x{Qvrz{BpfHuUL>uQEj#Ss)tO~Yw%U&U67)E4%X(XcvmCK6{BbC6@h@aKqk-4=HT zYNTD=!a7PK;Sd~N*iqP-`t)6Vku>PRPgx(9h;eBw9<6w|F+IBA;dW-TyY@!-Zv;-K zU-|ad>^%!^ux??*QOQFY;={V2wDVziR(+$!2LWZ{!|zu-@@7Qf13RA5yN`};z4PCk z7>om%s^RJh9zKLuczM)D*6Z?^4gc2x+;Y`bduzdN#aj~II&7lVmukJ2*PwDmdO>=~ z1pV}YcblovmZI*g{z6Ng*eWUD+|+uir*`b8{^g~&fHL)%lW;_pQmOJPal}|r`4=!g zFB;>$indM?c$eHS<^7#IpE7jXR<ar+h?MtKprq z=j$ZR6wTx+hr&9G$K>yvA)a>yPVR5*9wfQP+&rdsgizS#ZW)^_Ck6zHL7jFc{kyhD zTze_$v$49LNEwEX{u_5d!tmLSyr-(arNQmjyB6u%@rZA<*R6ld56`=q&O8PfV=d71xhk7i%u7j3?3XD8y| z;g1=Xg%-y$SQ%t%`&||pEgY)c9ijhi4mN||!2$FHLw)m~1SXcmVI`~yKsI=lP!H?$ zF*eIAnH?QRpSgof@eOq(b$3FC_B zE3L${9jQ^~J-m43eoEFA3vN9$Y+s;99a48($7CliMahh7Y!N4@Y=wieB286%+#)JR z#h=Mh1Zs~R^Fg-krH__4aO)lQw26!e0>+AGe%^|30bh0|{jC+F1R5oFKmP`eP4K(j z$TLT3IV3qRYuZa`qU&Wq7WrG6&6JvJ2j8CSA%jcG zN|&{L%Fzf-F$bmvaL|j0J?as-gvr4Gj$KK>AY^Uq?rQOgj91f8)O|76gM~G%=P-UdDjiE3y#TLB zpDN2|A)lO)2otLdB20*gEf+5v`7oN$BZty7BVNx;HIV|KeSsPH(+I5RiYI@N%`nBm z>QPw#i2piK29UG+8VkhmZrpbZoNe{qE(-;2;s?e*)?6l6Pg@j{Os~tb)9b1=*Y)5p zj^)lo%8)}$nzvoq1D2#2Y2P48STsrI@K!@y0e2Kxa(|9`f0ITTmvD`)yS$C5o{8Jv zfk61abh}K@%ezCPA-M0lA+{>N_5YQ*-6<%5cluI~ZkAIq1gyiN&BNS-3Aoh#6)>tAqI1gi2Q3 zy{WVtOm@In1t`E*3M5Le)O)n`2n5w~)PoWpr9I%YA{;ABI#2pofW{1{^NG&F?m}@Z zIh%wa(sj80&Sl`Ea4Gp>8@2l2J(_vfn*?HY}^+8~*wrd`YFiQnRiU59h{6U?aZxyb5 zA4udw0zNz@m|gjS`98nVEYH7d2^||2Qp!y3BXK%&yK(nGN}s>eF%86R8+SC1ot!Fg}!3aAn&wW+(IP>mlf=RvQs%w`QdGDH0)nd(cC} zk--}fi34z!noV(krJ!N!!W7czpZoxqQ%}HY&}H}%>EuN;z&3ilf>{l;4MdAH>8iGV zDd?C8{<$KLEA#78wd$-qreYLfcqq(>B z?TC}28ViJ4jrSay<+diDPU1;)?NmUerhD6_oAhw;nyB_S|)>4;x z)X?hDoB9clJqiS^x!GPF`of?#&3yz$j*zc9JHmV}j@#FnkmSWMJbKYqyG_6IhqBeF zCtFQa%h{bJjbPU?Ma`}v?fbl!xOsHMxf9!=CbVRUutc2CCDX*Qh!>}?R``(KDqVI7U zPU9C*+FTX78J7Jsus)>tcTHHITF|IU$m0Nt1rb}YuPYpr)g-z*>%OSiuRpNnyx6$G z#lTm!s@Z^fhpkqe!KhmMF~)eRFBC{n8FpzBP>*@RpUefUo`JJA=bT=g(k@{IzH2G| zf3&@IR8~zFHcq#IH{IPxN|$s?mvl>mNT+n?O?QWcG)N;MEr^sf(kMWkK}=l|Ag>VOX(gfRIDE9OO8Y&mH6mx9?AE+LLu(WXDAtRrTvCJLUT z72dtz7Ll^JrMb2BR5iSDzKp^P@Q3t#97f*M6xcQ@fG}Jh0@e)cdz=}PTQuzUN`HzY z7DO=X68fB!g+H?0^Ae`rrp2PRIe5y%tM^La`{mIfk=dX1L}tAsd8e6&y)6vcJmxI> zwLm7c(BWHEG?QBt4*W-wuir)tUr+{8L%NGddJ^}IW*%NTaJZ>pY&6lB&iWue2@ba2`ajQnb&CaF%_$%kE}F~ML+I9-Dvb$Yio`RaF-gjZ znatf7V$Xcw&wp3vovxB!Ymv4nnXk)gb3u>m;{vffo*!^@V_6aY4tt!Pw2a^uw9s=~ z-8HQzCR={lVBK{>RW5IKvnw(cyQ)mCj73K_%AqpNY&un|N~J>H&>nP$z4kIP@r(?d zuEoTzdv`uLfm*r11pq@o{UF3x?>=49MBErJ&$049{=h7FHykJ(I+0FNf2|ZkX@6nL zF0hFnY9Ixxf8%d0Hrl5;)U9tweTEb|ockKL)^=Bx`>}To4XCS7AGuV#30+)t?Wjyr z&c-Z8$~zd`Mn=5voCM>S`6jm4M2>)_BC<$k63)!ecKWvTgv*?AdX$WcK59`wTM*lF zg)S9q5VzN{+^bR{XXmR6-_efyt)mySQO`l_O|yw;WALPynCBOvfc^=1t+eTv%nz

AZ-_IvsYe~ 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 IAzhvEA5O_fNB{r; literal 0 HcmV?d00001 From 84c71e5d827f1a0b225b7f6e9eeabe00a9c3eadd Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Mon, 16 Nov 2015 15:59:15 +0100 Subject: [PATCH 083/410] Add unique to oid index for lfs object. --- db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb | 7 +++++++ db/schema.rb | 5 ++--- 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb diff --git a/db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb b/db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb new file mode 100644 index 0000000000..41b93da0a8 --- /dev/null +++ b/db/migrate/20151116144118_add_unique_for_lfs_oid_index.rb @@ -0,0 +1,7 @@ +class AddUniqueForLfsOidIndex < ActiveRecord::Migration + def change + remove_index :lfs_objects, :oid + remove_index :lfs_objects, [:oid, :size] + add_index :lfs_objects, :oid, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 4870fe3fc4..aa76cef9fe 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151114113410) do +ActiveRecord::Schema.define(version: 20151116144118) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -430,8 +430,7 @@ ActiveRecord::Schema.define(version: 20151114113410) do t.string "file" end - add_index "lfs_objects", ["oid", "size"], name: "index_lfs_objects_on_oid_and_size", using: :btree - add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", using: :btree + add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree create_table "lfs_objects_projects", force: true do |t| t.integer "lfs_object_id", null: false From 929ab909c88e9ac5d87acacb376a39dcfa6a639c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 16:14:19 +0100 Subject: [PATCH 084/410] Group masters should be able to create/close milestones Signed-off-by: Dmitriy Zaporozhets --- app/controllers/groups/milestones_controller.rb | 2 +- app/models/ability.rb | 1 + app/views/groups/milestones/_milestone.html.haml | 2 +- app/views/groups/milestones/index.html.haml | 9 +++++---- app/views/groups/milestones/show.html.haml | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 6833a550c9..10233222ee 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -38,7 +38,7 @@ class Groups::MilestonesController < Groups::ApplicationController private def authorize_group_milestone! - return render_404 unless can?(current_user, :admin_group, group) + return render_404 unless can?(current_user, :admin_milestones, group) end def milestone_params diff --git a/app/models/ability.rb b/app/models/ability.rb index 5ae28d5133..d01b3ae6f0 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -233,6 +233,7 @@ class Ability if group.has_master?(user) || group.has_owner?(user) || user.admin? rules.push(*[ :create_projects, + :admin_milestones ]) end diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml index 41dffdd2fb..a20bf75bc3 100644 --- a/app/views/groups/milestones/_milestone.html.haml +++ b/app/views/groups/milestones/_milestone.html.haml @@ -22,7 +22,7 @@ %span.label.label-gray = milestone.project.name .col-sm-6 - - if can?(current_user, :admin_group, @group) + - if can?(current_user, :admin_milestones, @group) - if milestone.closed? = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-xs btn-grouped btn-reopen" - else diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index ffd7dd3fc0..84ec77c618 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -3,10 +3,11 @@ = render 'shared/milestones_filter' .gray-content-block - .pull-right - %span.pull-right.hidden-xs - = link_to new_group_milestone_path(@group), class: "btn btn-new" do - New Milestone + - if can?(current_user, :admin_milestones, @group) + .pull-right + %span.pull-right.hidden-xs + = link_to new_group_milestone_path(@group), class: "btn btn-new" do + New Milestone .oneline Only milestones from diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index e609abca08..716e93f558 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -9,7 +9,7 @@ Open Milestone #{@milestone.title} .pull-right - - if can?(current_user, :admin_group, @group) + - if can?(current_user, :admin_milestones, @group) - if @milestone.active? = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" - else From 32f1a7196817b1073327c607905ee40b9140e6df Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 17:24:14 +0100 Subject: [PATCH 085/410] Fix removing avatar for group Signed-off-by: Dmitriy Zaporozhets --- app/controllers/groups/avatars_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/groups/avatars_controller.rb b/app/controllers/groups/avatars_controller.rb index f390705dc6..76c87366ba 100644 --- a/app/controllers/groups/avatars_controller.rb +++ b/app/controllers/groups/avatars_controller.rb @@ -1,4 +1,4 @@ -class Groups::AvatarsController < ApplicationController +class Groups::AvatarsController < Groups::ApplicationController def destroy @group.remove_avatar! @group.save From c8e53d4467e1e8cce4db04aafba00d55f014e283 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 15 Nov 2015 15:30:05 -0500 Subject: [PATCH 086/410] Revert "Merge pull request #9820 from huacnlee/avoid-render-form-in-notes-list" This reverts commit 63144cd062f6d259f1f30b6e06eb92a16caa8dec, reversing changes made to 8ab5df9d872414b2cca3ebd16d57b89e2f19e06a. --- app/assets/javascripts/notes.js.coffee | 13 +++++++------ app/controllers/projects/notes_controller.rb | 7 +------ app/views/projects/notes/_note.html.haml | 5 ++++- app/views/projects/notes/_notes_with_form.html.haml | 2 +- app/views/projects/notes/edit.js.erb | 2 -- config/routes.rb | 2 +- features/steps/project/issues/issues.rb | 2 +- spec/features/notes_on_merge_requests_spec.rb | 6 ++++++ spec/features/task_lists_spec.rb | 3 +++ 9 files changed, 24 insertions(+), 18 deletions(-) delete mode 100644 app/views/projects/notes/edit.js.erb diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index b0682f1684..ea75c656bc 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -29,6 +29,7 @@ class @Notes $(document).on "ajax:success", "form.edit_note", @updateNote # Edit note link + $(document).on "click", ".js-note-edit", @showEditForm $(document).on "click", ".note-edit-cancel", @cancelEdit # Reopen and close actions for Issue/MR combined with note form submit @@ -66,6 +67,7 @@ class @Notes $(document).off "ajax:success", ".js-main-target-form" $(document).off "ajax:success", ".js-discussion-note-form" $(document).off "ajax:success", "form.edit_note" + $(document).off "click", ".js-note-edit" $(document).off "click", ".note-edit-cancel" $(document).off "click", ".js-note-delete" $(document).off "click", ".js-note-attachment-delete" @@ -285,14 +287,13 @@ class @Notes Adds a hidden div with the original content of the note to fill the edit note form with if the user cancels ### - showEditForm: (note, formHTML) -> - nodeText = note.find(".note-text"); - nodeText.hide() - note.find('.note-edit-form').remove() - nodeText.after(formHTML) + showEditForm: (e) -> + e.preventDefault() + note = $(this).closest(".note") note.find(".note-body > .note-text").hide() note.find(".note-header").hide() - form = note.find(".note-edit-form") + base_form = note.find(".note-edit-form") + form = base_form.clone().insertAfter(base_form) form.addClass('current-note-edit-form gfm-form') form.find('.div-dropzone').remove() diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 0c98e2f1bf..41cd08c93c 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController before_action :authorize_read_note! before_action :authorize_create_note!, only: [:create] before_action :authorize_admin_note!, only: [:update, :destroy] - before_action :find_current_user_notes, except: [:destroy, :edit, :delete_attachment] + before_action :find_current_user_notes, except: [:destroy, :delete_attachment] def index current_fetched_at = Time.now.to_i @@ -29,11 +29,6 @@ class Projects::NotesController < Projects::ApplicationController end end - def edit - @note = note - render layout: false - end - def update @note = Notes::UpdateService.new(project, current_user, note_params).execute(note) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 8a3292f7da..8880830198 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -7,7 +7,7 @@ .note-header - if note_editable?(note) .note-actions - = link_to edit_namespace_project_note_path(note.project.namespace, note.project, note), title: 'Edit comment', remote: true, class: 'js-note-edit' do + = link_to '#', title: 'Edit comment', class: 'js-note-edit' do = icon('pencil-square-o') = link_to namespace_project_note_path(note.project.namespace, note.project, note), title: 'Remove comment', method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: 'js-note-delete danger' do @@ -59,6 +59,9 @@ .note-text = preserve do = markdown(note.note, {no_header_anchors: true}) + - unless note.system? + -# System notes can't be edited + = render 'projects/notes/edit_form', note: note - if note.attachment.url .note-attachment diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index 91cefa6d14..04222b8f7c 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -7,4 +7,4 @@ = render "projects/notes/form", view: params[:view] :javascript - window._notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{params[:view]}") + new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{params[:view]}") diff --git a/app/views/projects/notes/edit.js.erb b/app/views/projects/notes/edit.js.erb deleted file mode 100644 index 2599bad5d6..0000000000 --- a/app/views/projects/notes/edit.js.erb +++ /dev/null @@ -1,2 +0,0 @@ -$note = $('.note-row-<%= @note.id %>:visible'); -_notes.showEditForm($note, '<%= escape_javascript(render('edit_form', note: @note)) %>'); diff --git a/config/routes.rb b/config/routes.rb index bd85f4e3c6..696136d964 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -660,7 +660,7 @@ Gitlab::Application.routes.draw do end end - resources :notes, constraints: { id: /\d+/ } do + resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do member do delete :delete_attachment end diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index be134b9c2b..af2da41bad 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -219,7 +219,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps end step 'The code block should be unchanged' do - expect(page).to have_content("Command [1]: /usr/local/bin/git , see [text](doc/text)") + expect(page).to have_content("```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```") end step 'project \'Shop\' has issue \'Bugfix1\' with description: \'Description for issue1\'' do diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 16d5a03e88..d7cb3b2e86 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -65,6 +65,12 @@ describe 'Comments', feature: true do end describe 'when editing a note', js: true do + it 'should contain the hidden edit form' do + page.within("#note_#{note.id}") do + is_expected.to have_css('.note-edit-form', visible: false) + end + end + describe 'editing the note' do before do find('.note').hover diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb index 6cbe685a93..fca3c77fc6 100644 --- a/spec/features/task_lists_spec.rb +++ b/spec/features/task_lists_spec.rb @@ -51,6 +51,7 @@ feature 'Task Lists', feature: true do expect(page).to have_selector(container) expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector("#{container} .js-task-list-field") expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('a.btn-close') end @@ -89,6 +90,7 @@ feature 'Task Lists', feature: true do expect(page).to have_selector('.note .js-task-list-container') expect(page).to have_selector('.note .js-task-list-container .task-list .task-list-item .task-list-item-checkbox') + expect(page).to have_selector('.note .js-task-list-container .js-task-list-field') end it 'is only editable by author' do @@ -125,6 +127,7 @@ feature 'Task Lists', feature: true do expect(page).to have_selector(container) expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox") + expect(page).to have_selector("#{container} .js-task-list-field") expect(page).to have_selector('form.js-issuable-update') expect(page).to have_selector('a.btn-close') end From ccb0c40c54d913fe140231c88f4adcd2d41c5b87 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 15 Nov 2015 14:32:28 -0500 Subject: [PATCH 087/410] Make ProjectWiki touch Project#last_activity_at after wiki actions Closes #3026 --- app/models/project_wiki.rb | 10 ++++++++++ spec/models/project_wiki_spec.rb | 18 ++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 231973fa54..b5fec38378 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -86,6 +86,8 @@ class ProjectWiki commit = commit_details(:created, message, title) wiki.write_page(title, format, content, commit) + + update_project_activity rescue Gollum::DuplicatePageError => e @error_message = "Duplicate page: #{e.message}" return false @@ -95,10 +97,14 @@ class ProjectWiki commit = commit_details(:updated, message, page.title) wiki.update_page(page, page.name, format, content, commit) + + update_project_activity end def delete_page(page, message = nil) wiki.delete_page(page, commit_details(:deleted, message, page.title)) + + update_project_activity end def page_title_and_dir(title) @@ -146,4 +152,8 @@ class ProjectWiki def path_to_repo @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git") end + + def update_project_activity + @project.touch(:last_activity_at) + end end diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 9f6cdeeaa9..3b88914444 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -184,6 +184,12 @@ describe ProjectWiki do subject.create_page("test page", "some content", :markdown, "commit message") expect(subject.pages.first.page.version.message).to eq("commit message") end + + it 'updates project activity' do + expect(subject).to receive(:update_project_activity) + + subject.create_page('Test Page', 'This is content') + end end describe "#update_page" do @@ -205,6 +211,12 @@ describe ProjectWiki do it "sets the correct commit message" do expect(@page.version.message).to eq("updated page") end + + it 'updates project activity' do + expect(subject).to receive(:update_project_activity) + + subject.update_page(@gollum_page, 'Yet more content', :markdown, 'Updated page again') + end end describe "#delete_page" do @@ -217,6 +229,12 @@ describe ProjectWiki do subject.delete_page(@page) expect(subject.pages.count).to eq(0) end + + it 'updates project activity' do + expect(subject).to receive(:update_project_activity) + + subject.delete_page(@page) + end end private From b093f50986b6dcd0e4caf33d3c96831155e71db8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 19:55:58 +0100 Subject: [PATCH 088/410] Some code and doc improvements Signed-off-by: Dmitriy Zaporozhets --- app/models/global_milestone.rb | 4 ++++ app/views/dashboard/milestones/show.html.haml | 2 +- app/views/groups/milestones/show.html.haml | 2 +- doc/workflow/milestones.md | 8 ++++---- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index f96e9d41c9..1321ccd963 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -94,4 +94,8 @@ class GlobalMilestone def closed_merge_requests merge_requests.values_at("closed", "merged", "locked").compact.flatten end + + def complete? + total_items_count == closed_items_count + end end diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml index 580db613ed..83077a398b 100644 --- a/app/views/dashboard/milestones/show.html.haml +++ b/app/views/dashboard/milestones/show.html.haml @@ -8,7 +8,7 @@ Milestone #{@milestone.title} %hr -- if (@milestone.total_items_count == @milestone.closed_items_count) && @milestone.active? +- if @milestone.complete? && @milestone.active? .alert.alert-success %span All issues for this milestone are closed. You may close the milestone now. diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index 716e93f558..d161259e4a 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -16,7 +16,7 @@ = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" %hr -- if (@milestone.total_items_count == @milestone.closed_items_count) && @milestone.active? +- if @milestone.complete? && @milestone.active? .alert.alert-success %span All issues for this milestone are closed. You may close the milestone now. diff --git a/doc/workflow/milestones.md b/doc/workflow/milestones.md index 1cd1f0f2fc..dff36899ae 100644 --- a/doc/workflow/milestones.md +++ b/doc/workflow/milestones.md @@ -1,13 +1,13 @@ # Milestones -Milestone allows you to group issues and set due date for it. -Milestone is created per project. +Milestones allow you to organize issues and merge requests into a cohesive group, optionally setting a due date. +A common use is keeping track of an upcoming software version. Milestones are created per-project. ![milestone form](milestones/form.png) ## Groups and milestones -You can create milestone with single form for several projects that belongs to the same group. -On the group milestones page you will be able to see this milestones grouped together by name. +You can create a milestone for several projects in the same group simultaneously. +On the group's milestones page, you will be able to see the status of that milestone across all of the selected projects. ![group milestone form](milestones/group_form.png) From da26fd3fc2673b65a75b7cfac2925d1abb0d5f9a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 20:22:14 +0100 Subject: [PATCH 089/410] Dont allow code duplication check to fail Signed-off-by: Dmitriy Zaporozhets --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 141e7ba41d..9475309354 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -87,4 +87,3 @@ flay: tags: - ruby - mysql - allow_failure: true From adec8c7768152c81a40dd3a9fff77ea8525438c4 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 20:40:57 +0100 Subject: [PATCH 090/410] Refactor select2 tags Signed-off-by: Dmitriy Zaporozhets --- app/helpers/namespaces_helper.rb | 9 --------- app/helpers/selects_helper.rb | 16 ++++++++++++++-- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index b3132a1f3b..e7f3cb2103 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -17,15 +17,6 @@ module NamespacesHelper grouped_options_for_select(options, selected) end - def namespace_select_tag(id, opts = {}) - css_class = "ajax-namespace-select " - css_class << "multiselect " if opts[:multiple] - css_class << (opts[:class] || '') - value = opts[:selected] || '' - - hidden_field_tag(id, value, class: css_class) - end - def namespace_icon(namespace, size = 40) if namespace.kind_of?(Group) group_icon(namespace) diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index 12fce8db70..7e54d4d1b5 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -35,8 +35,20 @@ module SelectsHelper end def groups_select_tag(id, opts = {}) - css_class = "ajax-groups-select " - css_class << "multiselect " if opts[:multiple] + opts[:class] ||= '' + opts[:class] << ' ajax-groups-select' + select2_tag(id, opts) + end + + def namespace_select_tag(id, opts = {}) + opts[:class] ||= '' + opts[:class] << ' ajax-namespace-select' + select2_tag(id, opts) + end + + def select2_tag(id, opts = {}) + css_class = '' + css_class << 'multiselect ' if opts[:multiple] css_class << (opts[:class] || '') value = opts[:selected] || '' From 79fa6c646cecf887e6de8f2739d958ae49bb6eb3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 21:56:28 +0100 Subject: [PATCH 091/410] Remove duplication in reference filters Signed-off-by: Dmitriy Zaporozhets --- .../markdown/abstract_reference_filter.rb | 100 ++++++++++++++++++ lib/gitlab/markdown/issue_reference_filter.rb | 63 ++--------- .../merge_request_reference_filter.rb | 61 ++--------- .../markdown/snippet_reference_filter.rb | 61 ++--------- 4 files changed, 119 insertions(+), 166 deletions(-) create mode 100644 lib/gitlab/markdown/abstract_reference_filter.rb diff --git a/lib/gitlab/markdown/abstract_reference_filter.rb b/lib/gitlab/markdown/abstract_reference_filter.rb new file mode 100644 index 0000000000..fd5b7eb933 --- /dev/null +++ b/lib/gitlab/markdown/abstract_reference_filter.rb @@ -0,0 +1,100 @@ +require 'gitlab/markdown' + +module Gitlab + module Markdown + # Issues, Snippets and Merge Requests shares similar functionality in refernce filtering. + # All this functionality moved to this class + class AbstractReferenceFilter < ReferenceFilter + include CrossProjectReference + + def self.object_class + # Implement in child class + # Example: MergeRequest + end + + def self.object_name + object_class.name.underscore + end + + def self.object_sym + object_name.to_sym + end + + def self.data_reference + "data-#{object_name.dasherize}" + end + + # Public: Find references in text (like `!123` for merge requests) + # + # AnyReferenceFilter.references_in(text) do |match, object| + # "PREFIX#{object}" + # end + # + # PREFIX - symbol that detects reference (like ! for merge requests) + # object - reference object (snippet, merget request etc) + # text - String text to search. + # + # Yields the String match, the Integer referenced object ID, and an optional String + # of the external project reference. + # + # Returns a String replaced with the return of the block. + def self.references_in(text) + text.gsub(object_class.reference_pattern) do |match| + yield match, $~[object_sym].to_i, $~[:project] + end + end + + def self.referenced_by(node) + { object_sym => LazyReference.new(object_class, node.attr(data_reference)) } + end + + delegate :object_class, :object_sym, :references_in, to: :class + + def find_object(project, id) + # Implement in child class + # Example: project.merge_requests.find + end + + def url_for_object(object, project) + # Implement in child class + # Example: project_merge_request_url + end + + def call + replace_text_nodes_matching(object_class.reference_pattern) do |content| + object_link_filter(content) + end + end + + # Replace references (like `!123` for merge requests) in text with links + # to the referenced object's details page. + # + # text - String text to replace references in. + # + # Returns a String with references replaced with links. All links + # have `gfm` and `gfm-OBJECT_NAME` class names attached for styling. + def object_link_filter(text) + references_in(text) do |match, id, project_ref| + project = project_from_ref(project_ref) + + if project && object = find_object(project, id) + title = escape_once("#{object_title}: #{object.title}") + klass = reference_class(object_sym) + data = data_attribute(project: project.id, object_sym => object.id) + url = url_for_object(object, project) + + %(#{match}) + else + match + end + end + end + + def object_title + object_class.name.titleize + end + end + end +end diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb index 481d282f7b..1ed69e2f43 100644 --- a/lib/gitlab/markdown/issue_reference_filter.rb +++ b/lib/gitlab/markdown/issue_reference_filter.rb @@ -6,66 +6,17 @@ module Gitlab # issues that do not exist are ignored. # # This filter supports cross-project references. - class IssueReferenceFilter < ReferenceFilter - include CrossProjectReference - - # Public: Find `#123` issue references in text - # - # IssueReferenceFilter.references_in(text) do |match, issue, project_ref| - # "##{issue}" - # end - # - # text - String text to search. - # - # Yields the String match, the Integer issue ID, and an optional String of - # the external project reference. - # - # Returns a String replaced with the return of the block. - def self.references_in(text) - text.gsub(Issue.reference_pattern) do |match| - yield match, $~[:issue].to_i, $~[:project] - end + class IssueReferenceFilter < AbstractReferenceFilter + def self.object_class + Issue end - def self.referenced_by(node) - { issue: LazyReference.new(Issue, node.attr("data-issue")) } + def find_object(project, id) + project.get_issue(id) end - def call - replace_text_nodes_matching(Issue.reference_pattern) do |content| - issue_link_filter(content) - end - end - - # Replace `#123` issue references in text with links to the referenced - # issue's details page. - # - # text - String text to replace references in. - # - # Returns a String with `#123` references replaced with links. All links - # have `gfm` and `gfm-issue` class names attached for styling. - def issue_link_filter(text) - self.class.references_in(text) do |match, id, project_ref| - project = self.project_from_ref(project_ref) - - if project && issue = project.get_issue(id) - url = url_for_issue(id, project, only_path: context[:only_path]) - - title = escape_once("Issue: #{issue.title}") - klass = reference_class(:issue) - data = data_attribute(project: project.id, issue: issue.id) - - %(#{match}) - else - match - end - end - end - - def url_for_issue(*args) - IssuesHelper.url_for_issue(*args) + def url_for_object(issue, project) + IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path]) end end end diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb index 5bc6326980..1f47f03c94 100644 --- a/lib/gitlab/markdown/merge_request_reference_filter.rb +++ b/lib/gitlab/markdown/merge_request_reference_filter.rb @@ -6,65 +6,16 @@ module Gitlab # to merge requests that do not exist are ignored. # # This filter supports cross-project references. - class MergeRequestReferenceFilter < ReferenceFilter - include CrossProjectReference - - # Public: Find `!123` merge request references in text - # - # MergeRequestReferenceFilter.references_in(text) do |match, merge_request, project_ref| - # "##{merge_request}" - # end - # - # text - String text to search. - # - # Yields the String match, the Integer merge request ID, and an optional - # String of the external project reference. - # - # Returns a String replaced with the return of the block. - def self.references_in(text) - text.gsub(MergeRequest.reference_pattern) do |match| - yield match, $~[:merge_request].to_i, $~[:project] - end + class MergeRequestReferenceFilter < AbstractReferenceFilter + def self.object_class + MergeRequest end - def self.referenced_by(node) - { merge_request: LazyReference.new(MergeRequest, node.attr("data-merge-request")) } + def find_object(project, id) + project.merge_requests.find_by(iid: id) end - def call - replace_text_nodes_matching(MergeRequest.reference_pattern) do |content| - merge_request_link_filter(content) - end - end - - # Replace `!123` merge request references in text with links to the - # referenced merge request's details page. - # - # text - String text to replace references in. - # - # Returns a String with `!123` references replaced with links. All links - # have `gfm` and `gfm-merge_request` class names attached for styling. - def merge_request_link_filter(text) - self.class.references_in(text) do |match, id, project_ref| - project = self.project_from_ref(project_ref) - - if project && merge_request = project.merge_requests.find_by(iid: id) - title = escape_once("Merge Request: #{merge_request.title}") - klass = reference_class(:merge_request) - data = data_attribute(project: project.id, merge_request: merge_request.id) - - url = url_for_merge_request(merge_request, project) - - %(#{match}) - else - match - end - end - end - - def url_for_merge_request(mr, project) + def url_for_object(mr, project) h = Gitlab::Application.routes.url_helpers h.namespace_project_merge_request_url(project.namespace, project, mr, only_path: context[:only_path]) diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb index f783f95171..f7bd07c2a3 100644 --- a/lib/gitlab/markdown/snippet_reference_filter.rb +++ b/lib/gitlab/markdown/snippet_reference_filter.rb @@ -6,65 +6,16 @@ module Gitlab # snippets that do not exist are ignored. # # This filter supports cross-project references. - class SnippetReferenceFilter < ReferenceFilter - include CrossProjectReference - - # Public: Find `$123` snippet references in text - # - # SnippetReferenceFilter.references_in(text) do |match, snippet| - # "$#{snippet}" - # end - # - # text - String text to search. - # - # Yields the String match, the Integer snippet ID, and an optional String - # of the external project reference. - # - # Returns a String replaced with the return of the block. - def self.references_in(text) - text.gsub(Snippet.reference_pattern) do |match| - yield match, $~[:snippet].to_i, $~[:project] - end + class SnippetReferenceFilter < AbstractReferenceFilter + def self.object_class + Snippet end - def self.referenced_by(node) - { snippet: LazyReference.new(Snippet, node.attr("data-snippet")) } + def find_object(project, id) + project.snippets.find_by(id: id) end - def call - replace_text_nodes_matching(Snippet.reference_pattern) do |content| - snippet_link_filter(content) - end - end - - # Replace `$123` snippet references in text with links to the referenced - # snippets's details page. - # - # text - String text to replace references in. - # - # Returns a String with `$123` references replaced with links. All links - # have `gfm` and `gfm-snippet` class names attached for styling. - def snippet_link_filter(text) - self.class.references_in(text) do |match, id, project_ref| - project = self.project_from_ref(project_ref) - - if project && snippet = project.snippets.find_by(id: id) - title = escape_once("Snippet: #{snippet.title}") - klass = reference_class(:snippet) - data = data_attribute(project: project.id, snippet: snippet.id) - - url = url_for_snippet(snippet, project) - - %(#{match}) - else - match - end - end - end - - def url_for_snippet(snippet, project) + def url_for_object(snippet, project) h = Gitlab::Application.routes.url_helpers h.namespace_project_snippet_url(project.namespace, project, snippet, only_path: context[:only_path]) From 9573e06e265f7768923bfb7eff1e5fdc56d5b9bb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 23:06:28 +0100 Subject: [PATCH 092/410] Allow flay to fail for now since there is still a lot of refactoring todo Signed-off-by: Dmitriy Zaporozhets --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9475309354..141e7ba41d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -87,3 +87,4 @@ flay: tags: - ruby - mysql + allow_failure: true From bfbfa3b5f050405180b2024ff6a790bb71915606 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 16 Nov 2015 23:53:21 +0100 Subject: [PATCH 093/410] Remove duplication in issue emails Signed-off-by: Dmitriy Zaporozhets --- app/mailers/emails/issues.rb | 66 ++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 2c035fbb70..11533bc53c 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -1,53 +1,47 @@ module Emails module Issues def new_issue_email(recipient_id, issue_id) - @issue = Issue.find(issue_id) - @project = @issue.project - @target_url = namespace_project_issue_url(@project.namespace, @project, @issue) - mail_new_thread(@issue, - from: sender(@issue.author_id), - to: recipient(recipient_id), - subject: subject("#{@issue.title} (##{@issue.iid})")) - - SentNotification.record(@issue, recipient_id, reply_key) + mail_with_notification(issue_id, recipient_id) do + mail_new_thread(@issue, thread_options(@issue.author_id, recipient_id)) + end end def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id) - @issue = Issue.find(issue_id) - @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id - @project = @issue.project - @target_url = namespace_project_issue_url(@project.namespace, @project, @issue) - mail_answer_thread(@issue, - from: sender(updated_by_user_id), - to: recipient(recipient_id), - subject: subject("#{@issue.title} (##{@issue.iid})")) - - SentNotification.record(@issue, recipient_id, reply_key) + mail_with_notification(issue_id, recipient_id) do + @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id + mail_answer_thread(@issue, thread_options(updated_by_user_id, recipient_id)) + end end def closed_issue_email(recipient_id, issue_id, updated_by_user_id) - @issue = Issue.find issue_id - @project = @issue.project - @updated_by = User.find updated_by_user_id - @target_url = namespace_project_issue_url(@project.namespace, @project, @issue) - mail_answer_thread(@issue, - from: sender(updated_by_user_id), - to: recipient(recipient_id), - subject: subject("#{@issue.title} (##{@issue.iid})")) - - SentNotification.record(@issue, recipient_id, reply_key) + mail_with_notification(issue_id, recipient_id) do + @updated_by = User.find updated_by_user_id + mail_answer_thread(@issue, thread_options(updated_by_user_id, recipient_id)) + end end def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) - @issue = Issue.find issue_id - @issue_status = status + mail_with_notification(issue_id, recipient_id) do + @issue_status = status + @updated_by = User.find updated_by_user_id + mail_answer_thread(@issue, thread_options(updated_by_user_id, recipient_id)) + end + end + + def thread_options(sender_id, recipient_id) + { + from: sender(sender_id), + to: recipient(recipient_id), + subject: subject("#{@issue.title} (##{@issue.iid})") + } + end + + def mail_with_notification(issue_id, recipient_id) + @issue = Issue.find(issue_id) @project = @issue.project - @updated_by = User.find updated_by_user_id @target_url = namespace_project_issue_url(@project.namespace, @project, @issue) - mail_answer_thread(@issue, - from: sender(updated_by_user_id), - to: recipient(recipient_id), - subject: subject("#{@issue.title} (##{@issue.iid})")) + + yield SentNotification.record(@issue, recipient_id, reply_key) end From 3300db70ff53699732672824859186cd083623fa Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Mon, 16 Nov 2015 02:01:26 -0800 Subject: [PATCH 094/410] Rewrite HTTP links to force TLS, where possible --- CONTRIBUTING.md | 8 ++++---- app/views/admin/users/_profile.html.haml | 4 ++-- app/views/users/show.html.haml | 4 ++-- doc/ci/docker/using_docker_build.md | 4 ++-- doc/ci/quick_start/README.md | 6 +++--- doc/customization/libravatar.md | 6 +++--- doc/development/architecture.md | 2 +- doc/hooks/custom_hooks.md | 2 +- doc/install/database_mysql.md | 2 +- doc/install/installation.md | 4 ++-- doc/integration/ldap.md | 4 ++-- doc/legal/corporate_contributor_license_agreement.md | 2 +- .../individual_contributor_license_agreement.md | 2 +- doc/markdown/markdown.md | 12 ++++++------ doc/operations/unicorn.md | 4 ++-- doc/release/security.md | 4 ++-- doc/ssh/README.md | 2 +- doc/update/6.x-or-7.x-to-7.14.md | 2 +- doc/workflow/gitlab_flow.md | 12 ++++++------ doc/workflow/importing/migrating_from_svn.md | 4 ++-- 20 files changed, 45 insertions(+), 45 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9f79ff413a..5d85c9f3fc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic ## Security vulnerability disclosure -Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. +Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](https://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. ## Closing policy for issues and merge requests @@ -35,7 +35,7 @@ The [GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab Do not use the issue tracker for feature requests. We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose. Please keep feature requests as small and simple as possible, complex ones might be edited to make them small and simple. -Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](http://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there. +Please send a merge request with a tested solution or a merge request with a failing test instead of opening an issue if you can. If you're unsure where to post, post to the [mailing list](https://groups.google.com/forum/#!forum/gitlabhq) or [Stack Overflow](https://stackoverflow.com/questions/tagged/gitlab) first. There are a lot of helpful GitLab users there who may be able to help you quickly. If your particular issue turns out to be a bug, it will find its way from there. ### Issue tracker guidelines @@ -72,7 +72,7 @@ If you can, please submit a merge request with the fix or improvements including 1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code 1. Add your changes to the [CHANGELOG](CHANGELOG) 1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message -1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) +1. If you have multiple commits please combine them into one commit by [squashing them](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) 1. Push the commit to your fork 1. Submit a merge request (MR) to the master branch 1. The MR title should describe the change you want to make @@ -181,4 +181,4 @@ This code of conduct applies both within project spaces and in public spaces whe Instances of abusive, harassing, or otherwise unacceptable behavior can be reported by emailing contact@gitlab.com -This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/) +This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/) diff --git a/app/views/admin/users/_profile.html.haml b/app/views/admin/users/_profile.html.haml index 90d9980c85..7d11edc79e 100644 --- a/app/views/admin/users/_profile.html.haml +++ b/app/views/admin/users/_profile.html.haml @@ -16,11 +16,11 @@ - unless user.linkedin.blank? %li %span.light LinkedIn: - %strong= link_to user.linkedin, "http://www.linkedin.com/in/#{user.linkedin}" + %strong= link_to user.linkedin, "https://www.linkedin.com/in/#{user.linkedin}" - unless user.twitter.blank? %li %span.light Twitter: - %strong= link_to user.twitter, "http://www.twitter.com/#{user.twitter}" + %strong= link_to user.twitter, "https://twitter.com/#{user.twitter}" - unless user.website_url.blank? %li %span.light Website: diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 3099241218..d5a92cb816 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -32,11 +32,11 @@ = icon('skype') - unless @user.linkedin.blank? .profile-link-holder - = link_to "http://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do + = link_to "https://www.linkedin.com/in/#{@user.linkedin}", title: "LinkedIn" do = icon('linkedin-square') - unless @user.twitter.blank? .profile-link-holder - = link_to "http://www.twitter.com/#{@user.twitter}", title: "Twitter" do + = link_to "https://twitter.com/#{@user.twitter}", title: "Twitter" do = icon('twitter-square') - unless @user.website_url.blank? .profile-link-holder diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md index 5af27470d2..4b1788a9af 100644 --- a/doc/ci/docker/using_docker_build.md +++ b/doc/ci/docker/using_docker_build.md @@ -35,7 +35,7 @@ GitLab Runner then executes build scripts as `gitlab-runner` user. ```bash $ sudo gitlab-runner register -n \ - --url http://gitlab.com/ci \ + --url https://gitlab.com/ci \ --token RUNNER_TOKEN \ --executor shell --description "My Runner" @@ -84,7 +84,7 @@ In order to do that follow the steps: ```bash $ sudo gitlab-runner register -n \ - --url http://gitlab.com/ci \ + --url https://gitlab.com/ci \ --token RUNNER_TOKEN \ --executor docker \ --description "My Docker Runner" \ diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index a87a1f806f..d69064a91f 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -6,7 +6,7 @@ To start building projects with GitLab CI a few steps needs to be done. First you need to have a working GitLab and GitLab CI instance. -You can omit this step if you use [GitLab.com](http://GitLab.com/). +You can omit this step if you use [GitLab.com](https://GitLab.com/). ## 2. Create repository on GitLab @@ -16,7 +16,7 @@ Push your application to that repository. ## 3. Add project to CI The next part is to login to GitLab CI. -Point your browser to the URL you have set GitLab or use [gitlab.com/ci](http://gitlab.com/ci/). +Point your browser to the URL you have set GitLab or use [gitlab.com/ci](https://gitlab.com/ci/). On the first screen you will see a list of GitLab's projects that you have access to: @@ -97,7 +97,7 @@ If you do it correctly your runner should be shown under **Runners activated for ### Shared runners -If you use [gitlab.com/ci](http://gitlab.com/ci/) you can use **Shared runners** provided by GitLab Inc. +If you use [gitlab.com/ci](https://gitlab.com/ci/) you can use **Shared runners** provided by GitLab Inc. These are special virtual machines that are run on GitLab's infrastructure that can build any project. To enable **Shared runners** you have to go to **Runners** and click **Enable shared runners** for this project. diff --git a/doc/customization/libravatar.md b/doc/customization/libravatar.md index 54c1780c3a..bd2c242afc 100644 --- a/doc/customization/libravatar.md +++ b/doc/customization/libravatar.md @@ -2,7 +2,7 @@ GitLab by default supports [Gravatar](https://gravatar.com) avatar service. Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is -[heavily based on gravatar](http://wiki.libravatar.org/api/). +[heavily based on gravatar](https://wiki.libravatar.org/api/). This means that it is not complicated to switch to Libravatar avatar service or even self hosted Libravatar server. @@ -31,7 +31,7 @@ the configuration options as follows: ## Self-hosted -If you are [running your own libravatar service](http://wiki.libravatar.org/running_your_own/) the URL will be different in the configuration +If you are [running your own libravatar service](https://wiki.libravatar.org/running_your_own/) the URL will be different in the configuration but the important part is to provide the same placeholders so GitLab can parse the URL correctly. For example, you host a service on `http://libravatar.example.com` the `plain_url` you need to supply in `gitlab.yml` is @@ -63,7 +63,7 @@ Run `sudo gitlab-ctl reconfigure` for changes to take effect. ## Default URL for missing images -[Libravatar supports different sets](http://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service. +[Libravatar supports different sets](https://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service. In order to use a different set other than `identicon`, replace `&d=identicon` portion of the URL with another supported set. For example, you can use `retro` set in which case the URL would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"` diff --git a/doc/development/architecture.md b/doc/development/architecture.md index c00d290371..6101a71a8d 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -146,7 +146,7 @@ nginx Apache httpd -- [Explanation of Apache logs](http://httpd.apache.org/docs/2.2/logs.html). +- [Explanation of Apache logs](https://httpd.apache.org/docs/2.2/logs.html). - `/var/log/apache2/` contains error and output logs (on Ubuntu). - `/var/log/httpd/` contains error and output logs (on RHEL). diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md index 548c484bc0..0f2665a3bf 100644 --- a/doc/hooks/custom_hooks.md +++ b/doc/hooks/custom_hooks.md @@ -7,7 +7,7 @@ Please explore webhooks as an option if you do not have filesystem access. For a Git natively supports hooks that are executed on different actions. Examples of server-side git hooks include pre-receive, post-receive, and update. See -[Git SCM Server-Side Hooks](http://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks) +[Git SCM Server-Side Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks) for more information about each hook type. As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md index c565e90da2..513ad69ec2 100644 --- a/doc/install/database_mysql.md +++ b/doc/install/database_mysql.md @@ -2,7 +2,7 @@ ## Note -We do not recommend using MySQL due to various issues. For example, case [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html) and [problems](http://bugs.mysql.com/bug.php?id=65830) that [suggested](http://bugs.mysql.com/bug.php?id=50909) [fixes](http://bugs.mysql.com/bug.php?id=65830) [have](http://bugs.mysql.com/bug.php?id=63164). +We do not recommend using MySQL due to various issues. For example, case [(in)sensitivity](https://dev.mysql.com/doc/refman/5.0/en/case-sensitivity.html) and [problems](https://bugs.mysql.com/bug.php?id=65830) that [suggested](https://bugs.mysql.com/bug.php?id=50909) [fixes](https://bugs.mysql.com/bug.php?id=65830) [have](https://bugs.mysql.com/bug.php?id=63164). ## MySQL diff --git a/doc/install/installation.md b/doc/install/installation.md index 8028e51dbc..193db10e4d 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -106,7 +106,7 @@ Then select 'Internet Site' and press enter to confirm the hostname. ## 2. Ruby -The use of Ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby. +The use of Ruby version managers such as [RVM](https://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby. Remove the old Ruby 1.8 if present @@ -298,7 +298,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ### Install Gems -**Note:** As of bundler 1.5.2, you can invoke `bundle install -jN` (where `N` the number of your processor cores) and enjoy the parallel gems installation with measurable difference in completion time (~60% faster). Check the number of your cores with `nproc`. For more information check this [post](http://robots.thoughtbot.com/parallel-gem-installing-using-bundler). First make sure you have bundler >= 1.5.2 (run `bundle -v`) as it addresses some [issues](https://devcenter.heroku.com/changelog-items/411) that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2. +**Note:** As of bundler 1.5.2, you can invoke `bundle install -jN` (where `N` the number of your processor cores) and enjoy the parallel gems installation with measurable difference in completion time (~60% faster). Check the number of your cores with `nproc`. For more information check this [post](https://robots.thoughtbot.com/parallel-gem-installing-using-bundler). First make sure you have bundler >= 1.5.2 (run `bundle -v`) as it addresses some [issues](https://devcenter.heroku.com/changelog-items/411) that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2. # For PostgreSQL (note, the option says "without ... mysql") sudo -u git -H bundle install --deployment --without development test mysql aws kerberos diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md index 9b7d8fa396..7e2920b886 100644 --- a/doc/integration/ldap.md +++ b/doc/integration/ldap.md @@ -71,7 +71,7 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server # Filter LDAP users # - # Format: RFC 4515 http://tools.ietf.org/search/rfc4515 + # Format: RFC 4515 https://tools.ietf.org/search/rfc4515 # Ex. (employeeType=developer) # # Note: GitLab does not support omniauth-ldap's custom filter syntax. @@ -145,7 +145,7 @@ If multiple LDAP email attributes are present, e.g. `mail: foo@bar.com` and `ema ## Using an LDAP filter to limit access to your GitLab server If you want to limit all GitLab access to a subset of the LDAP users on your LDAP server you can set up an LDAP user filter. -The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515). +The filter must comply with [RFC 4515](https://tools.ietf.org/search/rfc4515). ```ruby # For omnibus packages; new LDAP server syntax diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md index 13bf15fcf4..7b94506c29 100644 --- a/doc/legal/corporate_contributor_license_agreement.md +++ b/doc/legal/corporate_contributor_license_agreement.md @@ -22,4 +22,4 @@ You accept and agree to the following terms and conditions for Your present and 8. It is your responsibility to notify GitLab B.V. when any change is required to the list of designated employees authorized to submit Contributions on behalf of the Corporation, or to the Corporation's Point of Contact with GitLab B.V.. -This text is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. +This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. diff --git a/doc/legal/individual_contributor_license_agreement.md b/doc/legal/individual_contributor_license_agreement.md index 72b01433dd..f97c252fd7 100644 --- a/doc/legal/individual_contributor_license_agreement.md +++ b/doc/legal/individual_contributor_license_agreement.md @@ -22,4 +22,4 @@ You accept and agree to the following terms and conditions for Your present and 8. You agree to notify GitLab B.V. of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. -This text is licensed under the [Creative Commons Attribution 3.0 License](http://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. +This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index ac3851f8c9..bc8e7d155e 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -43,7 +43,7 @@ You can also use other rich text files in GitLab. You might have to install a de ## Newlines -GFM honors the markdown specification in how [paragraphs and line breaks are handled](http://daringfireball.net/projects/markdown/syntax#p). +GFM honors the markdown specification in how [paragraphs and line breaks are handled](https://daringfireball.net/projects/markdown/syntax#p). A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. Line-breaks, or softreturns, are rendered if you end a line with two or more spaces @@ -72,14 +72,14 @@ do_this_and_do_that_and_another_thing GFM will autolink almost any URL you copy and paste into your text. - * http://www.google.com + * https://www.google.com * https://google.com/ * ftp://ftp.us.debian.org/debian/ * smb://foo/bar/baz * irc://irc.freenode.net/gitlab * http://localhost:3000 -* http://www.google.com +* https://www.google.com * https://google.com/ * ftp://ftp.us.debian.org/debian/ * smb://foo/bar/baz @@ -390,7 +390,7 @@ There are two ways to create links, inline-style and reference-style. [arbitrary case-insensitive reference text]: https://www.mozilla.org [1]: http://slashdot.org - [link text itself]: http://www.reddit.com + [link text itself]: https://www.reddit.com [I'm an inline-style link](https://www.google.com) @@ -406,7 +406,7 @@ Some text to show that the reference links can follow later. [arbitrary case-insensitive reference text]: https://www.mozilla.org [1]: http://slashdot.org -[link text itself]: http://www.reddit.com +[link text itself]: https://www.reddit.com **Note** @@ -583,5 +583,5 @@ By including colons in the header row, you can align the text within that column ## References - This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). -- The [Markdown Syntax Guide](http://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown. +- The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown. - [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown. diff --git a/doc/operations/unicorn.md b/doc/operations/unicorn.md index 3998da01f0..bad61151bd 100644 --- a/doc/operations/unicorn.md +++ b/doc/operations/unicorn.md @@ -52,7 +52,7 @@ leak memory, probably because it does not handle user requests.) To make these memory leaks manageable, GitLab comes with the [unicorn-worker-killer gem](https://github.com/kzk/unicorn-worker-killer). This -gem [monkey-patches](http://en.wikipedia.org/wiki/Monkey_patch) the Unicorn +gem [monkey-patches](https://en.wikipedia.org/wiki/Monkey_patch) the Unicorn workers to do a memory self-check after every 16 requests. If the memory of the Unicorn worker exceeds a pre-set limit then the worker process exits. The Unicorn master then automatically replaces the worker process. @@ -83,4 +83,4 @@ is a normal value for our current GitLab.com setup and traffic. The high frequency of Unicorn memory restarts on some GitLab sites can be a source of confusion for administrators. Usually they are a [red -herring](http://en.wikipedia.org/wiki/Red_herring). +herring](https://en.wikipedia.org/wiki/Red_herring). diff --git a/doc/release/security.md b/doc/release/security.md index 60bcfbb6da..b1a62b333e 100644 --- a/doc/release/security.md +++ b/doc/release/security.md @@ -8,7 +8,7 @@ Do a security release when there is a critical issue that needs to be addresses ## Security vulnerability disclosure -Please report suspected security vulnerabilities in private to , also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. +Please report suspected security vulnerabilities in private to , also see the [disclosure section on the GitLab.com website](https://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. ## Release Procedure @@ -25,7 +25,7 @@ Please report suspected security vulnerabilities in private to Date: Tue, 17 Nov 2015 10:40:29 +0100 Subject: [PATCH 095/410] Remove code duplication in gitlab_markdown_helper.rb Signed-off-by: Dmitriy Zaporozhets --- app/helpers/gitlab_markdown_helper.rb | 52 +++++++++++++-------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 6581348212..a0f6b80e9e 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -46,39 +46,13 @@ module GitlabMarkdownHelper end def markdown(text, context = {}) - return "" unless text.present? - - context.reverse_merge!( - path: @path, - pipeline: :default, - project: @project, - project_wiki: @project_wiki, - ref: @ref - ) - - user = current_user if defined?(current_user) - - html = Gitlab::Markdown.render(text, context) - Gitlab::Markdown.post_process(html, pipeline: context[:pipeline], project: @project, user: user) + process_markdown(text, options) end # TODO (rspeicher): Remove all usages of this helper and just call `markdown` # with a custom pipeline depending on the content being rendered def gfm(text, options = {}) - return "" unless text.present? - - options.reverse_merge!( - path: @path, - pipeline: :default, - project: @project, - project_wiki: @project_wiki, - ref: @ref - ) - - user = current_user if defined?(current_user) - - html = Gitlab::Markdown.gfm(text, options) - Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], project: @project, user: user) + process_markdown(text, options, :gfm) end def asciidoc(text) @@ -204,4 +178,26 @@ module GitlabMarkdownHelper '' end end + + def process_markdown(text, options, method = :markdown) + return "" unless text.present? + + options.reverse_merge!( + path: @path, + pipeline: :default, + project: @project, + project_wiki: @project_wiki, + ref: @ref + ) + + user = current_user if defined?(current_user) + + html = if method == :gfm + Gitlab::Markdown.gfm(text, options) + else + Gitlab::Markdown.render(text, options) + end + + Gitlab::Markdown.post_process(html, pipeline: options[:pipeline], project: @project, user: user) + end end From 437d4c76ece4bdf89e3702b05d13027a9a6f63a1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 17 Nov 2015 10:49:23 +0100 Subject: [PATCH 096/410] Remove duplication in diff_helper.rb Signed-off-by: Dmitriy Zaporozhets --- app/helpers/diff_helper.rb | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index b889fb2897..30e829cdd4 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -132,25 +132,11 @@ module DiffHelper end def inline_diff_btn - params_copy = params.dup - params_copy[:view] = 'inline' - # Always use HTML to handle case where JSON diff rendered this button - params_copy.delete(:format) - - link_to url_for(params_copy), id: "inline-diff-btn", class: (params[:view] != 'parallel' ? 'btn active' : 'btn') do - 'Inline' - end + diff_btn('Inline', 'inline', params[:view] != 'parallel') end def parallel_diff_btn - params_copy = params.dup - params_copy[:view] = 'parallel' - # Always use HTML to handle case where JSON diff rendered this button - params_copy.delete(:format) - - link_to url_for(params_copy), id: "parallel-diff-btn", class: (params[:view] == 'parallel' ? 'btn active' : 'btn') do - 'Side-by-side' - end + diff_btn('Side-by-side', 'parallel', params[:view] == 'parallel') end def submodule_link(blob, ref, repository = @repository) @@ -187,4 +173,18 @@ module DiffHelper def editable_diff?(diff) !diff.deleted_file && @merge_request && @merge_request.source_project end + + private + + def diff_btn(title, name, selected) + params_copy = params.dup + params_copy[:view] = name + + # Always use HTML to handle case where JSON diff rendered this button + params_copy.delete(:format) + + link_to url_for(params_copy), id: "#{name}-diff-btn", class: (selected ? 'btn active' : 'btn') do + title + end + end end From 3cebe9e78064030553e62939ec3612993c63ad76 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 17 Nov 2015 11:03:18 +0100 Subject: [PATCH 097/410] Refactor duplciate code for groups_controller.rb and slack_service/note_message.rb Signed-off-by: Dmitriy Zaporozhets --- app/controllers/concerns/issues_action.rb | 14 +++++++++ .../concerns/merge_requests_action.rb | 9 ++++++ app/controllers/dashboard_controller.rb | 20 ++---------- app/controllers/groups_controller.rb | 20 ++---------- app/helpers/gitlab_markdown_helper.rb | 2 +- .../slack_service/note_message.rb | 31 ++++++++++--------- 6 files changed, 46 insertions(+), 50 deletions(-) create mode 100644 app/controllers/concerns/issues_action.rb create mode 100644 app/controllers/concerns/merge_requests_action.rb diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb new file mode 100644 index 0000000000..effd472194 --- /dev/null +++ b/app/controllers/concerns/issues_action.rb @@ -0,0 +1,14 @@ +module IssuesAction + extend ActiveSupport::Concern + + def issues + @issues = get_issues_collection + @issues = @issues.page(params[:page]).per(ApplicationController::PER_PAGE) + @issues = @issues.preload(:author, :project) + + respond_to do |format| + format.html + format.atom { render layout: false } + end + end +end diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb new file mode 100644 index 0000000000..f7a25111db --- /dev/null +++ b/app/controllers/concerns/merge_requests_action.rb @@ -0,0 +1,9 @@ +module MergeRequestsAction + extend ActiveSupport::Concern + + def merge_requests + @merge_requests = get_merge_requests_collection + @merge_requests = @merge_requests.page(params[:page]).per(ApplicationController::PER_PAGE) + @merge_requests = @merge_requests.preload(:author, :target_project) + end +end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index b2c1fa4230..087da93508 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -1,26 +1,12 @@ class DashboardController < Dashboard::ApplicationController + include IssuesAction + include MergeRequestsAction + before_action :event_filter, only: :activity before_action :projects, only: [:issues, :merge_requests] respond_to :html - def merge_requests - @merge_requests = get_merge_requests_collection - @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) - @merge_requests = @merge_requests.preload(:author, :target_project) - end - - def issues - @issues = get_issues_collection - @issues = @issues.page(params[:page]).per(PER_PAGE) - @issues = @issues.preload(:author, :project) - - respond_to do |format| - format.html - format.atom { render layout: false } - end - end - def activity @last_push = current_user.recent_push diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index fb4eb094f2..fb26a4e6fc 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -1,4 +1,7 @@ class GroupsController < Groups::ApplicationController + include IssuesAction + include MergeRequestsAction + skip_before_action :authenticate_user!, only: [:show, :issues, :merge_requests] respond_to :html before_action :group, except: [:new, :create] @@ -53,23 +56,6 @@ class GroupsController < Groups::ApplicationController end end - def merge_requests - @merge_requests = get_merge_requests_collection - @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) - @merge_requests = @merge_requests.preload(:author, :target_project) - end - - def issues - @issues = get_issues_collection - @issues = @issues.page(params[:page]).per(PER_PAGE) - @issues = @issues.preload(:author, :project) - - respond_to do |format| - format.html - format.atom { render layout: false } - end - end - def edit end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index a0f6b80e9e..98c6d9d5d2 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -46,7 +46,7 @@ module GitlabMarkdownHelper end def markdown(text, context = {}) - process_markdown(text, options) + process_markdown(text, context) end # TODO (rspeicher): Remove all usages of this helper and just call `markdown` diff --git a/app/models/project_services/slack_service/note_message.rb b/app/models/project_services/slack_service/note_message.rb index 074478b292..b15d9a1467 100644 --- a/app/models/project_services/slack_service/note_message.rb +++ b/app/models/project_services/slack_service/note_message.rb @@ -45,30 +45,27 @@ class SlackService def create_commit_note(commit) commit_sha = commit[:id] commit_sha = Commit.truncate_sha(commit_sha) - commit_link = "[commit #{commit_sha}](#{@note_url})" - title = format_title(commit[:message]) - @message = "#{@user_name} commented on #{commit_link} in #{project_link}: *#{title}*" + commented_on_message( + "[commit #{commit_sha}](#{@note_url})", + format_title(commit[:message])) end def create_issue_note(issue) - issue_iid = issue[:iid] - note_link = "[issue ##{issue_iid}](#{@note_url})" - title = format_title(issue[:title]) - @message = "#{@user_name} commented on #{note_link} in #{project_link}: *#{title}*" + commented_on_message( + "[issue ##{issue[:iid]}](#{@note_url})", + format_title(issue[:title])) end def create_merge_note(merge_request) - merge_request_id = merge_request[:iid] - merge_request_link = "[merge request ##{merge_request_id}](#{@note_url})" - title = format_title(merge_request[:title]) - @message = "#{@user_name} commented on #{merge_request_link} in #{project_link}: *#{title}*" + commented_on_message( + "[merge request ##{merge_request[:iid]}](#{@note_url})", + format_title(merge_request[:title])) end def create_snippet_note(snippet) - snippet_id = snippet[:id] - snippet_link = "[snippet ##{snippet_id}](#{@note_url})" - title = format_title(snippet[:title]) - @message = "#{@user_name} commented on #{snippet_link} in #{project_link}: *#{title}*" + commented_on_message( + "[snippet ##{snippet[:id]}](#{@note_url})", + format_title(snippet[:title])) end def description_message @@ -78,5 +75,9 @@ class SlackService def project_link "[#{@project_name}](#{@project_url})" end + + def commented_on_message(target_link, title) + @message = "#{@user_name} commented on #{target_link} in #{project_link}: *#{title}*" + end end end From 4747412a49baf2d4776833e962e82b8cc893a06a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 17 Nov 2015 11:18:01 +0100 Subject: [PATCH 098/410] Set higher flay value to avoid unnecessary refactoring for now Signed-off-by: Dmitriy Zaporozhets --- lib/tasks/flay.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/flay.rake b/lib/tasks/flay.rake index dfb9df4772..e9587595fe 100644 --- a/lib/tasks/flay.rake +++ b/lib/tasks/flay.rake @@ -1,6 +1,6 @@ desc 'Code duplication analyze via flay' task :flay do - output = %x(bundle exec flay --mass 30 app/ lib/gitlab/) + output = %x(bundle exec flay --mass 35 app/ lib/gitlab/) if output.include? "Similar code found" puts output From 616675b4a6ef63abed2d133333fe5f5fbe1d73c6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 17 Nov 2015 11:55:43 +0100 Subject: [PATCH 099/410] Remove duplication in mailers/emails/notes.rb Signed-off-by: Dmitriy Zaporozhets --- app/mailers/emails/issues.rb | 22 ++++++----- app/mailers/emails/notes.rb | 75 +++++++++++++++++++----------------- 2 files changed, 52 insertions(+), 45 deletions(-) diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index 11533bc53c..abdeefed5e 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -1,34 +1,36 @@ module Emails module Issues def new_issue_email(recipient_id, issue_id) - mail_with_notification(issue_id, recipient_id) do - mail_new_thread(@issue, thread_options(@issue.author_id, recipient_id)) + issue_mail_with_notification(issue_id, recipient_id) do + mail_new_thread(@issue, issue_thread_options(@issue.author_id, recipient_id)) end end def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id) - mail_with_notification(issue_id, recipient_id) do + issue_mail_with_notification(issue_id, recipient_id) do @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id - mail_answer_thread(@issue, thread_options(updated_by_user_id, recipient_id)) + mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id)) end end def closed_issue_email(recipient_id, issue_id, updated_by_user_id) - mail_with_notification(issue_id, recipient_id) do + issue_mail_with_notification(issue_id, recipient_id) do @updated_by = User.find updated_by_user_id - mail_answer_thread(@issue, thread_options(updated_by_user_id, recipient_id)) + mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id)) end end def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id) - mail_with_notification(issue_id, recipient_id) do + issue_mail_with_notification(issue_id, recipient_id) do @issue_status = status @updated_by = User.find updated_by_user_id - mail_answer_thread(@issue, thread_options(updated_by_user_id, recipient_id)) + mail_answer_thread(@issue, issue_thread_options(updated_by_user_id, recipient_id)) end end - def thread_options(sender_id, recipient_id) + private + + def issue_thread_options(sender_id, recipient_id) { from: sender(sender_id), to: recipient(recipient_id), @@ -36,7 +38,7 @@ module Emails } end - def mail_with_notification(issue_id, recipient_id) + def issue_mail_with_notification(issue_id, recipient_id) @issue = Issue.find(issue_id) @project = @issue.project @target_url = namespace_project_issue_url(@project.namespace, @project, @issue) diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index 87ba94a583..65f37e9267 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -1,49 +1,54 @@ module Emails module Notes def note_commit_email(recipient_id, note_id) - @note = Note.find(note_id) - @commit = @note.noteable - @project = @note.project - @target_url = namespace_project_commit_url(@project.namespace, @project, - @commit, anchor: - "note_#{@note.id}") - mail_answer_thread(@commit, - from: sender(@note.author_id), - to: recipient(recipient_id), - subject: subject("#{@commit.title} (#{@commit.short_id})")) + note_mail_with_notification(note_id, recipient_id) do + @commit = @note.noteable + @target_url = namespace_project_commit_url(*note_target_url_options) - SentNotification.record_note(@note, recipient_id, reply_key) + mail_answer_thread(@commit, + from: sender(@note.author_id), + to: recipient(recipient_id), + subject: subject("#{@commit.title} (#{@commit.short_id})")) + end end def note_issue_email(recipient_id, note_id) - @note = Note.find(note_id) - @issue = @note.noteable - @project = @note.project - @target_url = namespace_project_issue_url(@project.namespace, @project, - @issue, anchor: - "note_#{@note.id}") - mail_answer_thread(@issue, - from: sender(@note.author_id), - to: recipient(recipient_id), - subject: subject("#{@issue.title} (##{@issue.iid})")) - - SentNotification.record_note(@note, recipient_id, reply_key) + note_mail_with_notification(note_id, recipient_id) do + @issue = @note.noteable + @target_url = namespace_project_issue_url(*note_target_url_options) + mail_answer_thread(@issue, note_thread_options(recipient_id)) + end end def note_merge_request_email(recipient_id, note_id) - @note = Note.find(note_id) - @merge_request = @note.noteable - @project = @note.project - @target_url = namespace_project_merge_request_url(@project.namespace, - @project, - @merge_request, anchor: - "note_#{@note.id}") - mail_answer_thread(@merge_request, - from: sender(@note.author_id), - to: recipient(recipient_id), - subject: subject("#{@merge_request.title} (##{@merge_request.iid})")) + note_mail_with_notification(note_id, recipient_id) do + @merge_request = @note.noteable + @target_url = namespace_project_merge_request_url(*note_target_url_options) + mail_answer_thread(@merge_request, note_thread_options(recipient_id)) + end + end - SentNotification.record_note(@note, recipient_id, reply_key) + private + + def note_target_url_options + [@project.namespace, @project, @note.noteable, anchor: "note_#{@note.id}"] + end + + def note_thread_options(recipient_id) + { + from: sender(@note.author_id), + to: recipient(recipient_id), + subject: subject("#{@note.noteable.title} (##{@note.noteable.iid})") + } + end + + def note_mail_with_notification(note_id, recipient_id) + @note = Note.find(note_id) + @project = @note.project + + yield + + SentNotification.record(@note, recipient_id, reply_key) end end end From 9b0efdb71ea6e4f3a33db9959c63125cd50afebe Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 17 Nov 2015 12:05:35 +0100 Subject: [PATCH 100/410] Remove code duplication in notification_service.rb Signed-off-by: Dmitriy Zaporozhets --- app/services/notification_service.rb | 46 +++++++++++----------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index a6b2234865..4b871f072d 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -113,7 +113,7 @@ class NotificationService end # Add all users participating in the thread (author, assignee, comment authors) - participants = + participants = if target.respond_to?(:participants) target.participants(note.author) else @@ -276,35 +276,25 @@ class NotificationService # Remove users with disabled notifications from array # Also remove duplications and nil recipients def reject_muted_users(users, project = nil) - users = users.to_a.compact.uniq - users = users.reject(&:blocked?) - - users.reject do |user| - next user.notification.disabled? unless project - - member = project.project_members.find_by(user_id: user.id) - - if !member && project.group - member = project.group.group_members.find_by(user_id: user.id) - end - - # reject users who globally disabled notification and has no membership - next user.notification.disabled? unless member - - # reject users who disabled notification in project - next true if member.notification.disabled? - - # reject users who have N_GLOBAL in project and disabled in global settings - member.notification.global? && user.notification.disabled? - end + reject_users(users, :disabled?, project) end # Remove users with notification level 'Mentioned' def reject_mention_users(users, project = nil) + reject_users(users, :mention?, project) + end + + # Reject users which method_name from notification object returns true. + # + # Example: + # reject_users(users, :watch?, project) + # + def reject_users(users, method_name, project = nil) users = users.to_a.compact.uniq + users = users.reject(&:blocked?) users.reject do |user| - next user.notification.mention? unless project + next user.notification.send(method_name) unless project member = project.project_members.find_by(user_id: user.id) @@ -313,19 +303,19 @@ class NotificationService end # reject users who globally set mention notification and has no membership - next user.notification.mention? unless member + next user.notification.send(method_name) unless member # reject users who set mention notification in project - next true if member.notification.mention? + next true if member.notification.send(method_name) # reject users who have N_MENTION in project and disabled in global settings - member.notification.global? && user.notification.mention? + member.notification.global? && user.notification.send(method_name) end end def reject_unsubscribed_users(recipients, target) return recipients unless target.respond_to? :subscriptions - + recipients.reject do |user| subscription = target.subscriptions.find_by_user_id(user.id) subscription && !subscription.subscribed @@ -343,7 +333,7 @@ class NotificationService recipients end end - + def new_resource_email(target, project, method) recipients = build_recipients(target, project, target.author) From 4de50a866e89d83fce0264c1e0b689b638d9be95 Mon Sep 17 00:00:00 2001 From: Den Girnyk Date: Tue, 17 Nov 2015 13:08:11 +0200 Subject: [PATCH 101/410] Fix md syntax in doc/api/commits.md --- doc/api/commits.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/commits.md b/doc/api/commits.md index 8e4a0ee1b8..93d62b751e 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -188,7 +188,7 @@ Parameters: "target_url": "http://jenkins/project/url", "description": "Jenkins success", "created_at": "2015-10-12T09:47:16.250Z", - "started_at": "2015-10-12T09:47:16.250Z"", + "started_at": "2015-10-12T09:47:16.250Z", "finished_at": "2015-10-12T09:47:16.262Z", "author": { "id": 1, @@ -228,7 +228,7 @@ POST /projects/:id/statuses/:sha "target_url": "http://jenkins/project/url", "description": "Jenkins success", "created_at": "2015-10-12T09:47:16.250Z", - "started_at": "2015-10-12T09:47:16.250Z"", + "started_at": "2015-10-12T09:47:16.250Z", "finished_at": "2015-10-12T09:47:16.262Z", "author": { "id": 1, From d27e400cb9ecf4423a0c713f4c3b084c154640b9 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 17 Nov 2015 12:39:06 +0100 Subject: [PATCH 102/410] LFS doc, first draft --- doc/workflow/git_lfs.md | 127 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 doc/workflow/git_lfs.md diff --git a/doc/workflow/git_lfs.md b/doc/workflow/git_lfs.md new file mode 100644 index 0000000000..6202822ea2 --- /dev/null +++ b/doc/workflow/git_lfs.md @@ -0,0 +1,127 @@ +# Git LFS + +Managing large files such as audio, video and graphics files has always been one of the shortcomings of Git. +The general recommendation is to not have Git repositories larger than 1GB to preserve performance. + +GitLab already supports [managing large files with git annex](http://doc.gitlab.com/ee/workflow/git_annex.html) (EE only), however in certain +environments it is not always convenient to use different commands to differentiate between the large files and regular ones. + +Git LFS makes this simpler for the end user by removing the requirement to learn new commands + + +## How it works + +Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication to authorize client requests. +Once the request is authorized, Git LFS client receives instructions from where to fetch/where to push the large file. + +## Requirements + +* Git LFS is supported in GitLab starting with version 8.2 +* Git LFS client version 0.6.0 and up + +## GitLab and Git LFS + +### Configuration + +Git LFS objects can be large in size and they are stored on GitLab server storage. + +There are two configuration options to help GitLab server administrators: + +* Enabling/disabling Git LFS support +* Changing the location of LFS object storage + +#### Omnibus packages + +In `/etc/gitlab/gitlab.rb`: + +```ruby +gitlab_rails['lfs_enabled'] = false +gitlab_rails['lfs_storage_path'] = "/mnt/storage/lfs-objects" +``` + +#### Installations from source + +In `config/gitlab.yml`: + +```yaml + lfs: + enabled: false + storage_path: /mnt/storage/lfs-objects +``` + +### Known limitations + +* Git LFS v1 original API is not supported since it was deprecated early in LFS development, starting with Git LFS version 0.6.0 +* When SSH is set as a remote, Git LFS objects still go through HTTPS +* Any Git LFS request will ask for HTTPS credentials to be provided so good Git credentials store is recommended +* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported +* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the url to Git config manually (see #troubleshooting-tips) + +## Using Git LFS + +Lets take a look at the workflow when you need to check large files into your Git repository with Git LFS: +For example, if you want to upload a very large file and check it into your Git repository: + +```bash +git clone git@gitlab.example.com:group/project.git +git lfs init # initialize the Git LFS project project +git lfs track "*.iso" # select the file extensions that you want to treat as large files +cp ~/tmp/debian.iso ./ # copy a large file into the current directory +git add . # add the large file to git annex +git commit -am "Added Debian iso" # commit the file meta data +git push origin master # sync the git repo and large file to the GitLab server +``` + +Downloading a single large file is also very simple: + +```bash +git clone git@gitlab.example.com:group/project.git +git lfs fetch debian.iso # download the large file +``` + + +## Troubleshooting tips + +### error: Repository or object not found + +Few reasons why this error can occur: + +1. Check the version of Git LFS on the client machine, `git lfs version`. Only version 0.6.0 and up are supported. +1. Check the Git config for traces of deprecated API, `git lfs -l`. If `batch = false` remove the line and try using Git LFS client > 0.6.0 + +### Invalid status for : 501 + +When attempting to push a LFS object to a GitLab server that doesn't have Git LFS support enabled, server will return status `error 501`. Check with your GitLab administrator why Git LFS is not enabled on the server + +### getsockopt: connection refused + +When pushing a LFS object and you receive an error similar to: `Post /info/lfs/objects/batch: dial tcp IP: getsockopt: connection refused`, +LFS client is trying to reach GitLab through HTTPS but your GitLab is being served on HTTP. +This behaviour is caused by Git LFS using HTTPS connections by default when it doesn't have a `lfsurl` set in the Git config. + +To go around this issue set the lfs url in git config: + +```bash + +git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs/objects/batch" +``` + +### Credentials are always required when pushing an object + +Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing the LFS object on every push for every object, user HTTPS credentials are required. + +By default, Git has support for remembering the credentials for each repository you use. This is described in [Git credentials man pages](https://git-scm.com/docs/gitcredentials). + +For example, you can tell Git to remember the password for a period of time in which you expect to push the objects: + +```bash +git config --global credential.helper 'cache --timeout=3600' +``` + +This will remember the credentials for an hour after which Git operations will require re-authentication. + +If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, `wincred` is available. + +More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage) + + From 84b5d0356a3364393aacb4a4b22c0635f7e4b4b6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 17 Nov 2015 12:42:43 +0100 Subject: [PATCH 103/410] Refactor similar code for Issue and MR update service Signed-off-by: Dmitriy Zaporozhets --- app/services/issuable_base_service.rb | 35 +++++++++++++++++ app/services/issues/update_service.rb | 36 +++++------------ app/services/merge_requests/update_service.rb | 39 +++++-------------- 3 files changed, 53 insertions(+), 57 deletions(-) diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 15b3825f96..11d2b08bba 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -28,6 +28,9 @@ class IssuableBaseService < BaseService end def filter_params(issuable_ability_name = :issue) + params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE + params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE + ability = :"admin_#{issuable_ability_name}" unless can?(current_user, ability, project) @@ -36,4 +39,36 @@ class IssuableBaseService < BaseService params.delete(:assignee_id) end end + + def update(issuable) + change_state(issuable) + filter_params + old_labels = issuable.labels.to_a + + if params.present? && issuable.update_attributes(params.merge(updated_by: current_user)) + issuable.reset_events_cache + + if issuable.labels != old_labels + create_labels_note( + issuable, + issuable.labels - old_labels, + old_labels - issuable.labels) + end + + handle_changes(issuable) + issuable.create_new_cross_references!(current_user) + execute_hooks(issuable, 'update') + end + + issuable + end + + def change_state(issuable) + case params.delete(:state_event) + when 'reopen' + reopen_service.new(project, current_user, {}).execute(issuable) + when 'close' + close_service.new(project, current_user, {}).execute(issuable) + end + end end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index aa1fd79d22..7c112f731a 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -1,33 +1,7 @@ module Issues class UpdateService < Issues::BaseService def execute(issue) - case params.delete(:state_event) - when 'reopen' - Issues::ReopenService.new(project, current_user, {}).execute(issue) - when 'close' - Issues::CloseService.new(project, current_user, {}).execute(issue) - end - - params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE - params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE - - filter_params - old_labels = issue.labels.to_a - - if params.present? && issue.update_attributes(params.merge(updated_by: current_user)) - issue.reset_events_cache - - if issue.labels != old_labels - create_labels_note( - issue, issue.labels - old_labels, old_labels - issue.labels) - end - - handle_changes(issue) - issue.create_new_cross_references!(current_user) - execute_hooks(issue, 'update') - end - - issue + update(issue) end def handle_changes(issue) @@ -44,5 +18,13 @@ module Issues create_title_change_note(issue, issue.previous_changes['title'].first) end end + + def reopen_service + Issues::ReopenService + end + + def close_service + Issues::CloseService + end end end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index d2849e5193..a5db3776eb 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -11,36 +11,7 @@ module MergeRequests params.except!(:target_project_id) params.except!(:source_branch) - case params.delete(:state_event) - when 'reopen' - MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request) - when 'close' - MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request) - end - - params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE - params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE - - filter_params - old_labels = merge_request.labels.to_a - - if params.present? && merge_request.update_attributes(params.merge(updated_by: current_user)) - merge_request.reset_events_cache - - if merge_request.labels != old_labels - create_labels_note( - merge_request, - merge_request.labels - old_labels, - old_labels - merge_request.labels - ) - end - - handle_changes(merge_request) - merge_request.create_new_cross_references!(current_user) - execute_hooks(merge_request, 'update') - end - - merge_request + update(merge_request) end def handle_changes(merge_request) @@ -68,5 +39,13 @@ module MergeRequests merge_request.mark_as_unchecked end end + + def reopen_service + MergeRequests::ReopenService + end + + def close_service + MergeRequests::CloseService + end end end From 49e32cb59fbb5412630426152aa4a7f38f02a443 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 17 Nov 2015 12:55:16 +0100 Subject: [PATCH 104/410] Remove small code duplication in user_reference_filter.rb Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/markdown/user_reference_filter.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb index 2a594e1662..ab5e1f6fe9 100644 --- a/lib/gitlab/markdown/user_reference_filter.rb +++ b/lib/gitlab/markdown/user_reference_filter.rb @@ -85,13 +85,12 @@ module Gitlab def link_to_all project = context[:project] - url = urls.namespace_project_url(project.namespace, project, only_path: context[:only_path]) data = data_attribute(project: project.id) - text = User.reference_prefix + 'all' - %(#{text}) + + link_tag(url, data, text) end def link_to_namespace(namespace) @@ -105,16 +104,20 @@ module Gitlab def link_to_group(group, namespace) url = urls.group_url(group, only_path: context[:only_path]) data = data_attribute(group: namespace.id) - text = Group.reference_prefix + group - %(#{text}) + + link_tag(url, data, text) end def link_to_user(user, namespace) url = urls.user_url(user, only_path: context[:only_path]) data = data_attribute(user: namespace.owner_id) - text = User.reference_prefix + user + + link_tag(url, data, text) + end + + def link_tag(url, data, text) %(#{text}) end end From fab04fac13c9f9c116e1e4ed744244c5041e198d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 17 Nov 2015 12:55:48 +0100 Subject: [PATCH 105/410] Code duplication check should be enabled now Signed-off-by: Dmitriy Zaporozhets --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 141e7ba41d..9475309354 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -87,4 +87,3 @@ flay: tags: - ruby - mysql - allow_failure: true From 24cf6865d3c0d47615a814c091cdb40bf513307e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 13:10:01 +0100 Subject: [PATCH 106/410] Correctly set comparison first commit when range includes a merge commit --- app/controllers/projects/compare_controller.rb | 4 ++-- app/helpers/diff_helper.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 71aaad1fad..3517b2bece 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -19,8 +19,8 @@ class Projects::CompareController < Projects::ApplicationController if compare_result @commits = Commit.decorate(compare_result.commits, @project) @diffs = compare_result.diffs - @commit = @commits.last - @first_commit = @commits.first + @commit = @project.commit(head_ref) + @first_commit = @project.commit(base_ref) @line_notes = [] end end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index b889fb2897..f7bf2a66eb 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -171,7 +171,7 @@ module DiffHelper def commit_for_diff(diff) if diff.deleted_file first_commit = @first_commit || @commit - first_commit.parent + first_commit.parent || @first_commit else @commit end From ecb83afabcb69d80995b56323bb89b1ee1176225 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 15:49:37 +0100 Subject: [PATCH 107/410] Refactor ability changes --- app/models/ability.rb | 54 +++++++++++++++++-------------- app/models/concerns/has_owners.rb | 31 ------------------ app/models/group.rb | 23 +++++++++++-- app/models/member.rb | 26 ++++++++------- app/models/project.rb | 4 +-- 5 files changed, 67 insertions(+), 71 deletions(-) delete mode 100644 app/models/concerns/has_owners.rb diff --git a/app/models/ability.rb b/app/models/ability.rb index eef481c8f8..500af08d20 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -240,11 +240,11 @@ class Ability # Only group owner and administrators can admin group if group.has_owner?(user) || user.admin? - rules.push(*[ - :admin_group, - :admin_namespace, - :admin_group_member - ]) + rules += [ + :admin_group, + :admin_namespace, + :admin_group_member + ] end rules.flatten @@ -255,16 +255,15 @@ class Ability # Only namespace owner and administrators can admin it if namespace.owner == user || user.admin? - rules.push(*[ - :create_projects, - :admin_namespace - ]) + rules += [ + :create_projects, + :admin_namespace + ] end rules.flatten end - [:issue, :merge_request].each do |name| define_method "#{name}_abilities" do |user, subject| rules = [] @@ -305,15 +304,18 @@ class Ability rules = [] target_user = subject.user group = subject.group - can_manage = group_abilities(user, group).include?(:admin_group_member) - if can_manage && (user != target_user) - rules << :update_group_member - rules << :destroy_group_member - end + unless group.last_owner?(target_user) + can_manage = group_abilities(user, group).include?(:admin_group_member) - if !group.last_owner?(user) && (can_manage || (user == target_user)) - rules << :destroy_group_member + if can_manage && user != target_user + rules << :update_group_member + rules << :destroy_group_member + end + + if user == target_user + rules << :destroy_group_member + end end rules @@ -323,16 +325,20 @@ class Ability rules = [] target_user = subject.user project = subject.project - can_manage = project_abilities(user, project).include?(:admin_project_member) - if can_manage && user != target_user && target_user != project.owner - rules << :update_project_member - rules << :destroy_project_member + unless target_user == project.owner + can_manage = project_abilities(user, project).include?(:admin_project_member) + + if can_manage && user != target_user + rules << :update_project_member + rules << :destroy_project_member + end + + if user == target_user + rules << :destroy_project_member + end end - if user == target_user && target_user != project.owner - rules << :destroy_project_member - end rules end diff --git a/app/models/concerns/has_owners.rb b/app/models/concerns/has_owners.rb deleted file mode 100644 index 53ef6e939d..0000000000 --- a/app/models/concerns/has_owners.rb +++ /dev/null @@ -1,31 +0,0 @@ -# == Owners concern -# -# Contains owners functionality for groups -# -module HasOwners - extend ActiveSupport::Concern - - def owners - @owners ||= members.owners.includes(:user).map(&:user) - end - - def members - raise NotImplementedError, "Expected members to be defined in #{self.class.name}" - end - - def add_owner(user, current_user = nil) - add_user(user, Gitlab::Access::OWNER, current_user) - end - - def has_owner?(user) - owners.include?(user) - end - - def has_master?(user) - members.masters.where(user_id: user).any? - end - - def last_owner?(user) - has_owner?(user) && owners.size == 1 - end -end diff --git a/app/models/group.rb b/app/models/group.rb index 11fde7ba6c..2c9e75496b 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -20,8 +20,7 @@ require 'file_size_validator' class Group < Namespace include Gitlab::ConfigHelper include Referable - include HasOwners - + has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' alias_method :members, :group_members has_many :users, through: :group_members @@ -66,6 +65,10 @@ class Group < Namespace end end + def owners + @owners ||= group_members.owners.includes(:user).map(&:user) + end + def add_users(user_ids, access_level, current_user = nil) user_ids.each do |user_id| Member.add_user(self.group_members, user_id, access_level, current_user) @@ -92,6 +95,22 @@ class Group < Namespace add_user(user, Gitlab::Access::MASTER, current_user) end + def add_owner(user, current_user = nil) + add_user(user, Gitlab::Access::OWNER, current_user) + end + + def has_owner?(user) + owners.include?(user) + end + + def has_master?(user) + members.masters.where(user_id: user).any? + end + + def last_owner?(user) + has_owner?(user) && owners.size == 1 + end + def avatar_type unless self.avatar.image? self.errors.add :avatar, "only images allowed" diff --git a/app/models/member.rb b/app/models/member.rb index eed9f2537e..28aee2e379 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -34,16 +34,18 @@ class Member < ActiveRecord::Base message: "already exists in source", allow_nil: true } validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true - validates :invite_email, presence: { if: :invite? }, - email: { - strict_mode: true, - allow_nil: true - }, - uniqueness: { - scope: [:source_type, - :source_id], - allow_nil: true - } + validates :invite_email, + presence: { + if: :invite? + }, + email: { + strict_mode: true, + allow_nil: true + }, + uniqueness: { + scope: [:source_type, :source_id], + allow_nil: true + } scope :invite, -> { where(user_id: nil) } scope :non_invite, -> { where("user_id IS NOT NULL") } @@ -100,7 +102,9 @@ class Member < ActiveRecord::Base private def can_update_member?(current_user, member) - !current_user || current_user.can?(:update_group_member, member) || + # There is no current user for bulk actions, in which case anything is allowed + !current_user || + current_user.can?(:update_group_member, member) || current_user.can?(:update_project_member, member) end end diff --git a/app/models/project.rb b/app/models/project.rb index 0946577578..a099a67cf6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -42,8 +42,7 @@ class Project < ActiveRecord::Base include Sortable include AfterCommitQueue include CaseSensitivity - include HasOwners - + extend Gitlab::ConfigHelper extend Enumerize @@ -117,7 +116,6 @@ class Project < ActiveRecord::Base has_many :hooks, dependent: :destroy, class_name: 'ProjectHook' has_many :protected_branches, dependent: :destroy has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember' - alias_method :my_members, :project_members has_many :users, through: :project_members has_many :deploy_keys_projects, dependent: :destroy has_many :deploy_keys, through: :deploy_keys_projects From e3fe3da63d23981f5a0f3bd629046cbe0533a132 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 15:51:40 +0100 Subject: [PATCH 108/410] Use project member abilities more extensively --- .../groups/group_members_controller.rb | 30 ++++++++-------- .../projects/project_members_controller.rb | 34 ++++++++++++------- .../group_members/_group_member.html.haml | 6 ++-- .../groups/group_members/index.html.haml | 6 ++-- .../project_members/_project_member.html.haml | 11 +++--- .../projects/project_members/_team.html.haml | 4 +-- .../projects/project_members/update.js.haml | 3 +- 7 files changed, 49 insertions(+), 45 deletions(-) diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index b25957a06e..0e902c4bb4 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -3,8 +3,7 @@ class Groups::GroupMembersController < Groups::ApplicationController # Authorize before_action :authorize_read_group! - before_action :authorize_admin_group!, except: [:index, :leave] - before_action :authorize_admin_group_member!, only: [:create, :resend_invite] + before_action :authorize_admin_group_member!, except: [:index, :leave] def index @project = @group.projects.find(params[:project_id]) if params[:project_id] @@ -17,7 +16,8 @@ class Groups::GroupMembersController < Groups::ApplicationController end @members = @members.order('access_level DESC').page(params[:page]).per(50) - @group_member = GroupMember.new + + @group_member = @group.group_members.new end def create @@ -27,24 +27,23 @@ class Groups::GroupMembersController < Groups::ApplicationController end def update - @member = @group.group_members.find(params[:id]) + @group_member = @group.group_members.find(params[:id]) - return render_403 unless can?(current_user, :update_group_member, @member) + return render_403 unless can?(current_user, :update_group_member, @group_member) - @member.update_attributes(member_params) + @group_member.update_attributes(member_params) end def destroy @group_member = @group.group_members.find(params[:id]) - if can?(current_user, :destroy_group_member, @group_member) # May fail if last owner. - @group_member.destroy - respond_to do |format| - format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } - format.js { render nothing: true } - end - else - return render_403 + return render_403 unless can?(current_user, :destroy_group_member, @group_member) + + @group_member.destroy + + respond_to do |format| + format.html { redirect_to group_group_members_path(@group), notice: 'User was successfully removed from group.' } + format.js { render nothing: true } end end @@ -63,10 +62,11 @@ class Groups::GroupMembersController < Groups::ApplicationController end def leave - @group_member = @group.group_members.where(user_id: current_user.id).first + @group_member = @group.group_members.find_by(user_id: current_user) if can?(current_user, :destroy_group_member, @group_member) @group_member.destroy + redirect_to(dashboard_groups_path, notice: "You left #{group.name} group.") else if @group.last_owner?(current_user) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index 9de5269cd2..07eb94e4f4 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -1,6 +1,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController # Authorize - before_action :authorize_admin_project!, except: :leave + before_action :authorize_admin_project_member!, except: :leave def index @project_members = @project.project_members @@ -29,10 +29,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController @project_member = @project.project_members.new end - def new - @project_member = @project.project_members.new - end - def create @project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user) @@ -41,11 +37,17 @@ class Projects::ProjectMembersController < Projects::ApplicationController def update @project_member = @project.project_members.find(params[:id]) + + return render_403 unless can?(current_user, :update_project_member, @project_member) + @project_member.update_attributes(member_params) end def destroy @project_member = @project.project_members.find(params[:id]) + + return render_403 unless can?(current_user, :destroy_project_member, @project_member) + @project_member.destroy respond_to do |format| @@ -71,16 +73,22 @@ class Projects::ProjectMembersController < Projects::ApplicationController end def leave - if @project.namespace == current_user.namespace - message = 'You can not leave your own project. Transfer or delete the project.' - return redirect_back_or_default(default: { action: 'index' }, options: { alert: message }) - end + @project_member = @project.project_members.find_by(user_id: current_user) - @project.project_members.find_by(user_id: current_user).destroy + if can?(current_user, :destroy_project_member, @project_member) + @project_member.destroy - respond_to do |format| - format.html { redirect_to dashboard_projects_path } - format.js { render nothing: true } + respond_to do |format| + format.html { redirect_to dashboard_projects_path, notice: "You left the project." } + format.js { render nothing: true } + end + else + if current_user == @project.owner + message = 'You can not leave your own project. Transfer or delete the project.' + redirect_back_or_default(default: { action: 'index' }, options: { alert: message }) + else + render_403 + end end end diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml index 3c19381321..be94b1abc1 100644 --- a/app/views/groups/group_members/_group_member.html.haml +++ b/app/views/groups/group_members/_group_member.html.haml @@ -1,6 +1,5 @@ - user = member.user - return unless user || member.invite? -- show_roles = true if show_roles.nil? %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} %span{class: ("list-item-name" if show_controls)} @@ -25,11 +24,11 @@ = link_to member.created_by.name, user_path(member.created_by) = time_ago_with_tooltip(member.created_at) - - if show_controls && can?(current_user, :admin_group_member, member) + - if show_controls && can?(current_user, :admin_group_member, @group) = link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do Resend invite - - if show_roles + - if should_user_see_group_roles?(current_user, @group) %span.pull-right %strong= member.human_access - if show_controls @@ -37,6 +36,7 @@ = button_tag class: "btn-xs btn js-toggle-button", title: 'Edit access level', type: 'button' do %i.fa.fa-pencil-square-o + - if can?(current_user, :destroy_group_member, member)   - if current_user == user diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index 15d289471c..d4ad33a8bf 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,8 +1,6 @@ - page_title "Members" - header_title group_title(@group, "Members", group_group_members_path(@group)) -- show_roles = should_user_see_group_roles?(current_user, @group) - -- if show_roles +- if should_user_see_group_roles?(current_user, @group) %p.light Members of group have access to all group projects. Read more about permissions @@ -32,7 +30,7 @@ (#{@members.total_count}) %ul.well-list - @members.each do |member| - = render 'groups/group_members/group_member', member: member, show_roles: show_roles, show_controls: true + = render 'groups/group_members/group_member', member: member, show_controls: true = paginate @members, theme: 'gitlab' diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml index 76c46d1d80..f07cd97e63 100644 --- a/app/views/projects/project_members/_project_member.html.haml +++ b/app/views/projects/project_members/_project_member.html.haml @@ -24,18 +24,19 @@ = link_to member.created_by.name, user_path(member.created_by) = time_ago_with_tooltip(member.created_at) - - if current_user_can_admin_project + - if can?(current_user, :admin_project_member, @project) = link_to resend_invite_namespace_project_project_member_path(@project.namespace, @project, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do Resend invite - - if current_user_can_admin_project - - unless @project.personal? && user == current_user - .pull-right - %strong= member.human_access + - if can?(current_user, :admin_project_member, @project) + .pull-right + %strong= member.human_access + - if can?(current_user, :update_project_member, member) = button_tag class: "btn-xs btn js-toggle-button", title: 'Edit access level', type: 'button' do %i.fa.fa-pencil-square-o + - if can?(current_user, :destroy_project_member, member)   - if current_user == user = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: leave_project_message(@project) }, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 615c425e59..b807fb2cc9 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -1,5 +1,3 @@ -- can_admin_project = can?(current_user, :admin_project, @project) - .panel.panel-default.prepend-top-20 .panel-heading %strong #{@project.name} @@ -8,4 +6,4 @@ (#{members.count}) %ul.well-list - members.each do |project_member| - = render 'project_member', member: project_member, current_user_can_admin_project: can_admin_project + = render 'project_member', member: project_member diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml index 811b185882..2fb3a41d54 100644 --- a/app/views/projects/project_members/update.js.haml +++ b/app/views/projects/project_members/update.js.haml @@ -1,3 +1,2 @@ -- can_admin_project = can?(current_user, :admin_project, @project) :plain - $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render("project_member", member: @project_member, current_user_can_admin_project: can_admin_project))}'); + $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render("project_member", member: @project_member))}'); From d60a23b718abc365651f146de678f20367ef25b1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 15:53:53 +0100 Subject: [PATCH 109/410] Add changelog item --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 3c22df7c9a..f7c6b09e7b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -45,6 +45,7 @@ v 8.2.0 (unreleased) - Fix trailing whitespace issue in merge request/issue title - Fix bug when milestone/label filter was empty for dashboard issues page - Add ability to create milestone in group projects from single form + - Prevent the last owner of a group from being able to delete themselves by 'adding' themselves as a master (James Lopez) v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) From 3ae1dee475dbeb9bdf62ed139292cba09b6c98bb Mon Sep 17 00:00:00 2001 From: Jason Lee Date: Mon, 16 Nov 2015 12:00:37 +0800 Subject: [PATCH 110/410] Avoid render edit_form when visitor can't edit them. Reverted #9820, github/task_list need a form, textarea for update. --- app/views/projects/notes/_note.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 8880830198..efa7dd01cc 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -59,8 +59,7 @@ .note-text = preserve do = markdown(note.note, {no_header_anchors: true}) - - unless note.system? - -# System notes can't be edited + - if note_editable?(note) = render 'projects/notes/edit_form', note: note - if note.attachment.url From 756d61562bb8c1a2ebcb29eb0f62548d2338db52 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 16:24:02 +0100 Subject: [PATCH 111/410] Minor refactoring --- app/models/ability.rb | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 95dd53bf42..f5cd14a89c 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -2,7 +2,7 @@ class Ability class << self def allowed(user, subject) return not_auth_abilities(user, subject) if user.nil? - return [] unless user.kind_of?(User) + return [] unless user.is_a?(User) return [] if user.blocked? case subject.class.name @@ -22,14 +22,20 @@ class Ability # List of possible abilities # for non-authenticated user def not_auth_abilities(user, subject) - return not_auth_personal_snippet_abilities(subject) if subject.kind_of?(PersonalSnippet) - return not_auth_project_abilities(subject) if subject.kind_of?(Project) || subject.respond_to?(:project) - return not_auth_group_abilities(subject) if subject.kind_of?(Group) || subject.respond_to?(:group) - [] + case true + when subject.is_a?(PersonalSnippet) + not_auth_personal_snippet_abilities(subject) + when subject.is_a?(Project) || subject.respond_to?(:project) + not_auth_project_abilities(subject) + when subject.is_a?(Group) || subject.respond_to?(:group) + not_auth_group_abilities(subject) + else + [] + end end def not_auth_project_abilities(subject) - project = if subject.kind_of?(Project) + project = if subject.is_a?(Project) subject else subject.project @@ -57,7 +63,7 @@ class Ability end def not_auth_group_abilities(subject) - group = if subject.kind_of?(Group) + group = if subject.is_a?(Group) subject else subject.group @@ -327,7 +333,7 @@ class Ability end if snippet.public? || snippet.internal? - rules.push(:read_personal_snippet) + rules << :read_personal_snippet end rules From 1b3475653f253c62ab493fa62665d8a3e105aa88 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 16:25:02 +0100 Subject: [PATCH 112/410] Fix changelog --- CHANGELOG | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b555a05044..ee0da5598a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -25,8 +25,7 @@ v 8.2.0 (unreleased) - Allow to define cache in `.gitlab-ci.yml` - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - Remove deprecated CI events from project settings page - - Use issue editor as cross reference comment author when issue is edited with a new mention. - - Improve personal snippet access workflow + - Improve personal snippet access workflow (Douglas Alexandre) - [API] Add ability to fetch the commit ID of the last commit that actually touched a file - Fix omniauth documentation setting for omnibus configuration (Jon Cairns) - Add "New file" link to dropdown on project page From e49190cad933abc38271b46cded8150fd9b15568 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 16:08:58 +0100 Subject: [PATCH 113/410] Don't use params[:view] directly. --- app/helpers/diff_helper.rb | 8 ++++++-- app/views/projects/commit/show.html.haml | 2 +- app/views/projects/diffs/_diffs.html.haml | 2 +- app/views/projects/diffs/_file.html.haml | 3 +-- app/views/projects/notes/_form.html.haml | 2 +- app/views/projects/notes/_notes_with_form.html.haml | 4 ++-- 6 files changed, 12 insertions(+), 9 deletions(-) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index b889fb2897..f47d5c4c74 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -1,4 +1,8 @@ module DiffHelper + def diff_view + params[:view] == 'parallel' ? 'parallel' : 'inline' + end + def allowed_diff_size if diff_hard_limit_enabled? Commit::DIFF_HARD_LIMIT_FILES @@ -137,7 +141,7 @@ module DiffHelper # Always use HTML to handle case where JSON diff rendered this button params_copy.delete(:format) - link_to url_for(params_copy), id: "inline-diff-btn", class: (params[:view] != 'parallel' ? 'btn active' : 'btn') do + link_to url_for(params_copy), id: "inline-diff-btn", class: (diff_view == 'inline' ? 'btn active' : 'btn') do 'Inline' end end @@ -148,7 +152,7 @@ module DiffHelper # Always use HTML to handle case where JSON diff rendered this button params_copy.delete(:format) - link_to url_for(params_copy), id: "parallel-diff-btn", class: (params[:view] == 'parallel' ? 'btn active' : 'btn') do + link_to url_for(params_copy), id: "parallel-diff-btn", class: (diff_view == 'parallel' ? 'btn active' : 'btn') do 'Side-by-side' end end diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 30a3973828..85e203cbe5 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -3,4 +3,4 @@ = render "commit_box" = render "ci_menu" if @ci_commit = render "projects/diffs/diffs", diffs: @diffs, project: @project -= render "projects/notes/notes_with_form", view: params[:view] += render "projects/notes/notes_with_form" diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index e46bf1ab1e..416fb4da07 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -1,4 +1,4 @@ -- if params[:view] == 'parallel' +- if diff_view == 'parallel' - fluid_layout true - diff_files = safe_diff_files(diffs) diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 410ff6abb4..c745b4e69b 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -33,7 +33,7 @@ -# Skipp all non non-supported blobs - return unless blob.respond_to?('text?') - if blob.text? - - if params[:view] == 'parallel' + - if diff_view == 'parallel' = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i - else = render "projects/diffs/text_file", diff_file: diff_file, index: i @@ -42,4 +42,3 @@ = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i - else .nothing-here-block No preview for this file type - diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 13dfa0a1bb..5dd84317e3 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -1,5 +1,5 @@ = form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f| - = hidden_field_tag :view, params[:view] + = hidden_field_tag :view, diff_view = hidden_field_tag :line_type = note_target_fields(@note) = f.hidden_field :commit_id diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index 91cefa6d14..066a1986f6 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -4,7 +4,7 @@ .js-main-target-form - if can? current_user, :create_note, @project - = render "projects/notes/form", view: params[:view] + = render "projects/notes/form", view: diff_view :javascript - window._notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{params[:view]}") + window._notes = new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}, "#{diff_view}") From 8c308e3df6b2c9b259f66172d13cc81009c1ae89 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 16:51:35 +0100 Subject: [PATCH 114/410] Don't fail when there was no previous assignee --- app/services/notification_service.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 16c84f4a05..27928c0945 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -113,7 +113,7 @@ class NotificationService end # Add all users participating in the thread (author, assignee, comment authors) - participants = + participants = if target.respond_to?(:participants) target.participants(note.author) else @@ -325,7 +325,7 @@ class NotificationService def reject_unsubscribed_users(recipients, target) return recipients unless target.respond_to? :subscriptions - + recipients.reject do |user| subscription = target.subscriptions.find_by_user_id(user.id) subscription && !subscription.subscribed @@ -343,7 +343,7 @@ class NotificationService recipients end end - + def new_resource_email(target, project, method) recipients = build_recipients(target, project, target.author) @@ -361,12 +361,13 @@ class NotificationService end def reassign_resource_email(target, project, current_user, method) - assignee_id_was = previous_record(target, "assignee_id") - previous_assignee = User.find(assignee_id_was) + previous_assignee_id = previous_record(target, "assignee_id") + previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id + recipients = build_recipients(target, project, current_user, [previous_assignee]) recipients.each do |recipient| - mailer.send(method, recipient.id, target.id, assignee_id_was, current_user.id) + mailer.send(method, recipient.id, target.id, previous_assignee_id, current_user.id) end end @@ -378,9 +379,10 @@ class NotificationService end end - def build_recipients(target, project, current_user, previous_records = nil ) + def build_recipients(target, project, current_user, extra_recipients = nil ) recipients = target.participants(current_user) - recipients.concat(previous_records).compact.uniq if previous_records + + recipients = recipients.concat(extra_recipients).compact.uniq if extra_recipients recipients = add_project_watchers(recipients, project) recipients = reject_mention_users(recipients, project) From 80a2eb5d50102ff8e0dcfb67994acc3037b0270e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 16:51:39 +0100 Subject: [PATCH 115/410] Fix spec --- spec/services/issues/update_service_spec.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 4e79484f26..6a9053753c 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -3,13 +3,15 @@ require 'spec_helper' describe Issues::UpdateService do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:issue) { create(:issue, title: 'Old title', assignee_id: user.id) } + let(:user3) { create(:user) } + let(:issue) { create(:issue, title: 'Old title', assignee_id: user3.id) } let(:label) { create(:label) } let(:project) { issue.project } before do project.team << [user, :master] project.team << [user2, :developer] + project.team << [user3, :developer] end describe 'execute' do @@ -35,10 +37,10 @@ describe Issues::UpdateService do it { expect(@issue.labels.first.title).to eq('Bug') } it 'should send email to user2 about assign of new issue and email to user about issue unassignment' do - deliveries = ActionMailer::Base.deliveries + deliveries = ActionMailer::Base.deliveries email = deliveries.last - recipients = deliveries.map(&:to).uniq.flatten - expect(recipients.last(2)).to include(user.email,user2.email) + recipients = deliveries.last(2).map(&:to).flatten + expect(recipients).to include(user2.email, user3.email) expect(email.subject).to include(issue.title) end From 0fbf47a219c7c284b5728a4a6d066f1d0700f985 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 16:51:49 +0100 Subject: [PATCH 116/410] Add credits to changelog --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 56c0daa61a..119185b8ce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,7 +32,7 @@ v 8.2.0 (unreleased) - Add "added", "modified" and "removed" properties to commit object in webhook - Rename "Back to" links to "Go to" because its not always a case it point to place user come from - Allow groups to appear in the search results if the group owner allows it - - Add email notification to former assignee upon unassignment + - Add email notification to former assignee upon unassignment (Adam Lieskovský) - New design for project graphs page - Remove deprecated dumped yaml file generated from previous job definitions - Fix incoming email config defaults From 931c56f822c21e5f77743297603cfff5065eb772 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 16:53:15 +0100 Subject: [PATCH 117/410] Add tests for merge request update. --- spec/services/issues/update_service_spec.rb | 2 +- spec/services/merge_requests/update_service_spec.rb | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 6a9053753c..f55527ee9a 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -36,7 +36,7 @@ describe Issues::UpdateService do it { expect(@issue.labels.count).to eq(1) } it { expect(@issue.labels.first.title).to eq('Bug') } - it 'should send email to user2 about assign of new issue and email to user about issue unassignment' do + it 'should send email to user2 about assign of new issue and email to user3 about issue unassignment' do deliveries = ActionMailer::Base.deliveries email = deliveries.last recipients = deliveries.last(2).map(&:to).flatten diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index c75173c145..2ed51d223b 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' describe MergeRequests::UpdateService do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:merge_request) { create(:merge_request, :simple, title: 'Old title') } + let(:user3) { create(:user) } + let(:merge_request) { create(:merge_request, :simple, title: 'Old title', assignee_id: user3.id) } let(:project) { merge_request.project } let(:label) { create(:label) } @@ -47,9 +48,11 @@ describe MergeRequests::UpdateService do with(@merge_request, 'update') end - it 'should send email to user2 about assign of new merge_request' do - email = ActionMailer::Base.deliveries.last - expect(email.to.first).to eq(user2.email) + it 'should send email to user2 about assign of new merge request and email to user3 about merge request unassignment' do + deliveries = ActionMailer::Base.deliveries + email = deliveries.last + recipients = deliveries.last(2).map(&:to).flatten + expect(recipients).to include(user2.email, user3.email) expect(email.subject).to include(merge_request.title) end From 4597f8ef209b832538c3aa8524c197ef505d625e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 17 Nov 2015 10:44:01 -0500 Subject: [PATCH 118/410] Update monthly release template [ci skip] --- doc/release/monthly.md | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index d347f58ba0..c9ab87671d 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -54,21 +54,25 @@ template are explained below: - [ ] Update GitLab.com with RC1 - [ ] Create the regression issue in the CE issue tracker: - > This is a meta issue to index possible regressions in this monthly release - > and any patch versions. - > - > Please do not raise or discuss issues directly in this issue but link to - > issues that might warrant a patch release. If there is a Merge Request - > that fixes the issue, please link to that as well. - > - > Please only post one regression issue and/or merge request per comment. - > Comments will be updated by the release manager as they are addressed. + ``` + This is a meta issue to index possible regressions in this monthly release + and any patch versions. + + Please do not raise or discuss issues directly in this issue but link to + issues that might warrant a patch release. If there is a Merge Request + that fixes the issue, please link to that as well. + + Please only post one regression issue and/or merge request per comment. + Comments will be updated by the release manager as they are addressed. + ``` - [ ] Tweet about RC1 release: - > GitLab x.y.0.rc1 is available: https://packages.gitlab.com/gitlab/unstable - > Use at your own risk. Please link regressions issues from - > LINK_TO_REGRESSION_ISSUE + ``` + GitLab x.y.0.rc1 is available: https://packages.gitlab.com/gitlab/unstable + Use at your own risk. Please link regressions issues from + LINK_TO_REGRESSION_ISSUE + ``` ### Xth: (3 working days before the 22nd) From 85102d094f44741e6fbf53910df1bed363a020bb Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 17 Nov 2015 11:02:11 -0500 Subject: [PATCH 119/410] Update CHANGELOG [ci skip] --- CHANGELOG | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 3c22df7c9a..ca09d9c09f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ Please view this file on the master branch, on stable branches it's out of date. -v 8.2.0 (unreleased) +v 8.3.0 (unreleased) + +v 8.2.0 - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) - Fix Drone CI service template not saving properly (Stan Hu) - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu) From f008b2d2dfd1b8030ec0634c223ca9f11d304e91 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 17:19:31 +0100 Subject: [PATCH 120/410] Remove extra space --- app/services/notification_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 27928c0945..d9aa6bc98c 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -379,7 +379,7 @@ class NotificationService end end - def build_recipients(target, project, current_user, extra_recipients = nil ) + def build_recipients(target, project, current_user, extra_recipients = nil) recipients = target.participants(current_user) recipients = recipients.concat(extra_recipients).compact.uniq if extra_recipients From 6f6c75cb85195984c6e610ad109464dcb952a5fe Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 17 Nov 2015 11:59:22 -0500 Subject: [PATCH 121/410] Bump VERSION [ci skip] --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index a2264f05f5..8d0676ff07 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.1.0.pre +8.3.0.pre From e945ec02804bb28dbd228d8002a159c8da0fcc38 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 18:53:56 +0100 Subject: [PATCH 122/410] Add "Start a new merge request" option to every commit form --- .../blob/blob_file_dropzone.js.coffee | 17 ++---- .../javascripts/blob/edit_blob.js.coffee | 7 --- .../javascripts/blob/new_blob.js.coffee | 7 --- .../javascripts/new_commit_form.js.coffee | 21 ++++++++ app/assets/stylesheets/pages/editor.scss | 4 -- app/controllers/application_controller.rb | 30 ----------- .../creates_merge_request_for_commit.rb | 28 ++++++++++ app/controllers/projects/blob_controller.rb | 53 ++++++++++--------- app/controllers/projects/tree_controller.rb | 13 ++++- app/helpers/merge_requests_helper.rb | 20 +++++++ app/views/projects/blob/_actions.html.haml | 2 +- app/views/projects/blob/_new_dir.html.haml | 14 +++-- app/views/projects/blob/_remove.html.haml | 16 +++--- app/views/projects/blob/_upload.html.haml | 18 +++---- app/views/projects/blob/edit.html.haml | 19 ++----- app/views/projects/blob/new.html.haml | 24 ++------- app/views/projects/blob/show.html.haml | 4 +- .../projects/tree/_tree_content.html.haml | 2 +- .../_commit_message_container.html.haml | 8 +-- app/views/shared/_new_commit_form.html.haml | 18 +++++++ 20 files changed, 168 insertions(+), 157 deletions(-) create mode 100644 app/assets/javascripts/new_commit_form.js.coffee create mode 100644 app/controllers/concerns/creates_merge_request_for_commit.rb create mode 100644 app/views/shared/_new_commit_form.html.haml diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee index 5b604adbbb..195f8b11e5 100644 --- a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee +++ b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee @@ -23,18 +23,6 @@ class @BlobFileDropzone init: -> this.on 'addedfile', (file) -> $('.dropzone-alerts').html('').hide() - commit_message = form.find('#commit_message')[0] - - if /^Upload/.test(commit_message.placeholder) - commit_message.placeholder = 'Upload ' + file.name - - return - - this.on 'removedfile', (file) -> - commit_message = form.find('#commit_message')[0] - - if /^Upload/.test(commit_message.placeholder) - commit_message.placeholder = 'Upload new file' return @@ -47,8 +35,9 @@ class @BlobFileDropzone return this.on 'sending', (file, xhr, formData) -> - formData.append('new_branch', form.find('#new_branch').val()) - formData.append('commit_message', form.find('#commit_message').val()) + formData.append('new_branch', form.find('.js-new-branch').val()) + formData.append('create_merge_request', form.find('.js-create-merge-request').val()) + formData.append('commit_message', form.find('.js-commit-message').val()) return # Override behavior of adding error underneath preview diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee index bf4cd85aee..f6bf836f19 100644 --- a/app/assets/javascripts/blob/edit_blob.js.coffee +++ b/app/assets/javascripts/blob/edit_blob.js.coffee @@ -11,13 +11,6 @@ class @EditBlob if ace_mode editor.getSession().setMode "ace/mode/" + ace_mode - $('#new_branch').keyup -> - if $(this).val() != $('#original_branch').val() - $('.form-group.destination').show() - else - $('.form-group.destination').hide() - $('#create_merge_request').prop('checked', false) - # Before a form submission, move the content from the Ace editor into the # submitted textarea $('form').submit -> diff --git a/app/assets/javascripts/blob/new_blob.js.coffee b/app/assets/javascripts/blob/new_blob.js.coffee index c2d6c7acfe..68c5e5195e 100644 --- a/app/assets/javascripts/blob/new_blob.js.coffee +++ b/app/assets/javascripts/blob/new_blob.js.coffee @@ -11,13 +11,6 @@ class @NewBlob if ace_mode editor.getSession().setMode "ace/mode/" + ace_mode - $('#new_branch').keyup -> - if $(this).val() != $('#original_branch').val() - $('.form-group.destination').show() - else - $('.form-group.destination').hide() - $('#create_merge_request').prop('checked', false) - # Before a form submission, move the content from the Ace editor into the # submitted textarea $('form').submit -> diff --git a/app/assets/javascripts/new_commit_form.js.coffee b/app/assets/javascripts/new_commit_form.js.coffee new file mode 100644 index 0000000000..2e561dea3e --- /dev/null +++ b/app/assets/javascripts/new_commit_form.js.coffee @@ -0,0 +1,21 @@ +class @NewCommitForm + constructor: (form) -> + @newBranch = form.find('.js-new-branch') + @originalBranch = form.find('.js-original-branch') + @createMergeRequest = form.find('.js-create-merge-request') + @createMergeRequestFormGroup = form.find('.js-create-merge-request-form-group') + + @renderDestination() + @newBranch.keyup @renderDestination + + renderDestination: => + different = @newBranch.val() != @originalBranch.val() + + if different + @createMergeRequestFormGroup.show() + @createMergeRequest.prop('checked', true) unless @wasDifferent + else + @createMergeRequestFormGroup.hide() + @createMergeRequest.prop('checked', false) + + @wasDifferent = different diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index c0defa82bf..e2c521af91 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -63,8 +63,4 @@ margin-top: 0; padding: $gl-padding } - - .destination { - display: none; - } } diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6f87ee08b2..0d182e8eb0 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -22,7 +22,6 @@ class ApplicationController < ActionController::Base helper_method :abilities, :can?, :current_application_settings helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled? - helper_method :new_mr_from_push_event, :new_mr_path_for_fork_from_push_event, :new_mr_path_from_push_event rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -343,35 +342,6 @@ class ApplicationController < ActionController::Base current_application_settings.import_sources.include?('git') end - # new merge requests routing helpers - def new_mr_path_from_push_event(event, target_branch=nil) - target_project = event.project.forked_from_project || event.project - new_namespace_project_merge_request_path( - event.project.namespace, - event.project, - new_mr_from_push_event(event, target_project, target_branch) - ) - end - - def new_mr_path_for_fork_from_push_event(event, target_branch=nil) - new_namespace_project_merge_request_path( - event.project.namespace, - event.project, - new_mr_from_push_event(event, event.project.forked_from_project, target_branch) - ) - end - - def new_mr_from_push_event(event, target_project, target_branch) - { - merge_request: { - source_project_id: event.project.id, - target_project_id: target_project.id, - source_branch: event.branch_name, - target_branch: target_branch || target_project.repository.root_ref - } - } - end - def redirect_to_home_page_url? # If user is not signed-in and tries to access root_path - redirect him to landing page # Don't redirect to the default URL to prevent endless redirections diff --git a/app/controllers/concerns/creates_merge_request_for_commit.rb b/app/controllers/concerns/creates_merge_request_for_commit.rb new file mode 100644 index 0000000000..c752782215 --- /dev/null +++ b/app/controllers/concerns/creates_merge_request_for_commit.rb @@ -0,0 +1,28 @@ +module CreatesMergeRequestForCommit + extend ActiveSupport::Concern + + def new_merge_request_path + if @project.forked? + target_project = @project.forked_from_project || @project + target_branch = target_project.repository.root_ref + else + target_project = @project + target_branch = @ref + end + + new_namespace_project_merge_request_path( + @project.namespace, + @project, + merge_request: { + source_project_id: @project.id, + target_project_id: target_project.id, + source_branch: @new_branch, + target_branch: target_branch + } + ) + end + + def create_merge_request? + params[:create_merge_request] && @new_branch != @ref + end +end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index d7fae64fcd..41ec7bde45 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -1,6 +1,7 @@ # Controller for viewing a file's blame class Projects::BlobController < Projects::ApplicationController include ExtractsPath + include CreatesMergeRequestForCommit include ActionView::Helpers::SanitizeHelper # Raised when given an invalid file path @@ -27,15 +28,8 @@ class Projects::BlobController < Projects::ApplicationController if result[:status] == :success flash[:notice] = "The changes have been successfully committed" respond_to do |format| - format.html do - url = if params[:create_merge_request] - new_mr_path_from_push_event(current_user.recent_push(@project.id), @ref) - else - namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) - end - redirect_to url - end - format.json { render json: { message: "success", filePath: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) } } + format.html { redirect_to after_create_path } + format.json { render json: { message: "success", filePath: after_create_path } } end else flash[:alert] = result[:message] @@ -59,14 +53,7 @@ class Projects::BlobController < Projects::ApplicationController if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" respond_to do |format| - format.html do - url = if params[:create_merge_request] - new_mr_path_from_push_event(current_user.recent_push(@project.id), @ref) - else - after_edit_path - end - redirect_to url - end + format.html { redirect_to after_edit_path } format.json { render json: { message: "success", filePath: after_edit_path } } end else @@ -91,7 +78,7 @@ class Projects::BlobController < Projects::ApplicationController if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - redirect_to namespace_project_tree_path(@project.namespace, @project, @target_branch) + redirect_to after_destroy_path else flash[:alert] = result[:message] render :show @@ -145,15 +132,33 @@ class Projects::BlobController < Projects::ApplicationController render_404 end + def after_create_path + @after_create_path ||= + if create_merge_request? + new_merge_request_path + else + namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @file_path)) + end + end + def after_edit_path @after_edit_path ||= - if from_merge_request + if create_merge_request? + new_merge_request_path + elsif from_merge_request && @new_branch == @ref diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + "#file-path-#{hexdigest(@path)}" - elsif @target_branch.present? - namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) else - namespace_project_blob_path(@project.namespace, @project, @id) + namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @path)) + end + end + + def after_destroy_path + @after_destroy_path ||= + if create_merge_request? + new_merge_request_path + else + namespace_project_tree_path(@project.namespace, @project, @new_branch) end end @@ -168,7 +173,7 @@ class Projects::BlobController < Projects::ApplicationController def editor_variables @current_branch = @ref - @target_branch = params[:new_branch].present? ? sanitized_new_branch_name : @ref + @new_branch = params[:new_branch].present? ? sanitized_new_branch_name : @ref @file_path = if action_name.to_s == 'create' @@ -188,7 +193,7 @@ class Projects::BlobController < Projects::ApplicationController @commit_params = { file_path: @file_path, current_branch: @current_branch, - target_branch: @target_branch, + target_branch: @new_branch, commit_message: params[:commit_message], file_content: params[:content], file_content_encoding: params[:encoding] diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index bdcb1a3e29..8f272ad128 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -1,6 +1,7 @@ # Controller for viewing a repository's file structure class Projects::TreeController < Projects::ApplicationController include ExtractsPath + include CreatesMergeRequestForCommit include ActionView::Helpers::SanitizeHelper before_action :require_non_empty_project, except: [:new, :create] @@ -43,7 +44,7 @@ class Projects::TreeController < Projects::ApplicationController if result && result[:status] == :success flash[:notice] = "The directory has been successfully created" respond_to do |format| - format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name)) } + format.html { redirect_to after_create_dir_path } end else flash[:alert] = message @@ -53,6 +54,8 @@ class Projects::TreeController < Projects::ApplicationController end end + private + def assign_dir_vars @new_branch = params[:new_branch].present? ? sanitize(strip_tags(params[:new_branch])) : @ref @dir_name = File.join(@path, params[:dir_name]) @@ -63,4 +66,12 @@ class Projects::TreeController < Projects::ApplicationController commit_message: params[:commit_message], } end + + def after_create_dir_path + if create_merge_request? + new_merge_request_path + else + namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name)) + end + end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 7e8f61fd27..b804d4f4e3 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -1,4 +1,24 @@ module MergeRequestsHelper + def new_mr_path_from_push_event(event) + target_project = event.project.forked_from_project || event.project + new_namespace_project_merge_request_path( + event.project.namespace, + event.project, + new_mr_from_push_event(event, target_project) + ) + end + + def new_mr_from_push_event(event, target_project) + { + merge_request: { + source_project_id: event.project.id, + target_project_id: target_project.id, + source_branch: event.branch_name, + target_branch: target_project.repository.root_ref + } + } + end + def mr_css_classes(mr) classes = "merge-request" classes << " closed" if mr.closed? diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml index 373b3a0c5b..ba3e0c3c59 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -19,4 +19,4 @@ - if allowed_tree_edit? .btn-group{ role: "group" } %button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace - %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Remove + %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml index a0fc8bbd75..13b5ffd17f 100644 --- a/app/views/projects/blob/_new_dir.html.haml +++ b/app/views/projects/blob/_new_dir.html.haml @@ -5,21 +5,19 @@ %a.close{href: "#", "data-dismiss" => "modal"} × %h3.page-title Create New Directory .modal-body - = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, id: 'dir-create-form', class: 'form-horizontal' do + = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form' do .form-group = label_tag :dir_name, 'Directory Name', class: 'control-label' .col-sm-10 = text_field_tag :dir_name, params[:dir_name], placeholder: "Directory name", required: true, class: 'form-control' - = render 'shared/commit_message_container', params: params, placeholder: '' - - unless @project.empty_repo? - .form-group - = label_tag :branch_name, 'Branch', class: 'control-label' - .col-sm-10 - = text_field_tag 'new_branch', @ref, class: "form-control" + + = render 'shared/new_commit_form', placeholder: "Add new directory" + .form-group .col-sm-offset-2.col-sm-10 = submit_tag "Create directory", class: 'btn btn-primary btn-create' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" :javascript - disableButtonIfAnyEmptyField($("#dir-create-form"), ".form-control", ".btn-create"); + disableButtonIfAnyEmptyField($(".js-create-dir-form"), ".form-control", ".btn-create"); + new NewCommitForm($('.js-create-dir-form')) diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml index cae5ff0109..1cf19a7d3d 100644 --- a/app/views/projects/blob/_remove.html.haml +++ b/app/views/projects/blob/_remove.html.haml @@ -3,16 +3,16 @@ .modal-content .modal-header %a.close{href: "#", "data-dismiss" => "modal"} × - %h3.page-title Remove #{@blob.name} - %p.light - From branch - %strong= @ref + %h3.page-title Delete #{@blob.name} .modal-body - = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-requires-input' do - = render 'shared/commit_message_container', params: params, - placeholder: 'Removed this file because...' + = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-replace-blob-form js-requires-input' do + = render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}" + .form-group .col-sm-offset-2.col-sm-10 - = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file' + = button_tag 'Delete file', class: 'btn btn-remove btn-remove-file' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" + +:javascript + new NewCommitForm($('.js-replace-blob-form')) diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml index a1c54e731f..3bb61f0c94 100644 --- a/app/views/projects/blob/_upload.html.haml +++ b/app/views/projects/blob/_upload.html.haml @@ -5,7 +5,7 @@ %a.close{href: "#", "data-dismiss" => "modal"} × %h3.page-title #{title} .modal-body - = form_tag form_path, method: method, class: 'blob-file-upload-form-js form-horizontal' do + = form_tag form_path, method: method, class: 'js-upload-blob-form form-horizontal' do .dropzone .dropzone-previews.blob-upload-dropzone-previews %p.dz-message.light @@ -13,19 +13,15 @@ = link_to 'click to upload', '#', class: "markdown-selector" %br .dropzone-alerts{class: "alert alert-danger data", style: "display:none"} - = render 'shared/commit_message_container', params: params, - placeholder: placeholder - - unless @project.empty_repo? - .form-group.branch - = label_tag 'branch', class: 'control-label' do - Branch - .col-sm-10 - = text_field_tag 'new_branch', @ref, class: "form-control" + + = render 'shared/new_commit_form', placeholder: placeholder + .form-group .col-sm-offset-2.col-sm-10 = button_tag button_title, class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" :javascript - disableButtonIfEmptyField($('.blob-file-upload-form-js').find('#commit_message'), '.btn-upload-file'); - new BlobFileDropzone($('.blob-file-upload-form-js'), '#{method}'); + disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file'); + new BlobFileDropzone($('.js-upload-blob-form'), '#{method}'); + new NewCommitForm($('.js-upload-blob-form')) diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 2621646270..5674516525 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -13,28 +13,15 @@ %i.fa.fa-eye = editing_preview_title(@blob.name) - = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input') do + = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input js-edit-blob-form') do = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data - = render 'shared/commit_message_container', params: params, placeholder: "Update #{@blob.name}" - - .form-group.branch - = label_tag 'branch', class: 'control-label' do - Branch - .col-sm-10 - = text_field_tag 'new_branch', @ref, class: "form-control" - - .form-group.destination - .col-sm-offset-2.col-sm-10 - .checkbox - = label_tag :create_merge_request do - = check_box_tag :create_merge_request, 1, false - Start a new merge request + = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}" = hidden_field_tag 'last_commit', @last_commit = hidden_field_tag 'content', '', id: "file-content" = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] - = hidden_field_tag 'original_branch', @ref = render 'projects/commit_button', ref: @ref, cancel_path: @after_edit_path :javascript blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}") + new NewCommitForm($('.js-edit-blob-form')) diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml index 0439e55131..1ff6800545 100644 --- a/app/views/projects/blob/new.html.haml +++ b/app/views/projects/blob/new.html.haml @@ -2,32 +2,18 @@ = render "header_title" .gray-content-block.top-block - Create a new file + %h3.page-title + Create New File .file-editor - = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file js-requires-input') do + = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-requires-input') do = render 'projects/blob/editor', ref: @ref - = render 'shared/commit_message_container', params: params, - placeholder: 'Add new file' - - - unless @project.empty_repo? - .form-group.branch - = label_tag 'branch', class: 'control-label' do - Branch - .col-sm-10 - = text_field_tag 'new_branch', @ref, class: "form-control js-quick-submit" - - .form-group.destination - .col-sm-offset-2.col-sm-10 - .checkbox - = label_tag :create_merge_request do - = check_box_tag :create_merge_request, 1, false - Start a new merge request + = render 'shared/new_commit_form', placeholder: "Add new file" = hidden_field_tag 'content', '', id: 'file-content' - = hidden_field_tag 'original_branch', @ref = render 'projects/commit_button', ref: @ref, cancel_path: namespace_project_tree_path(@project.namespace, @project, @id) :javascript blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null) + new NewCommitForm($('.js-new-blob-form')) diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml index f52b89f692..b7276868ce 100644 --- a/app/views/projects/blob/show.html.haml +++ b/app/views/projects/blob/show.html.haml @@ -10,6 +10,4 @@ = render 'projects/blob/remove' - title = "Replace #{@blob.name}" - = render 'projects/blob/upload', title: title, placeholder: title, - button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), - method: :put + = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index ee4c9d1693..c64e684df2 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -30,7 +30,7 @@ = render "projects/tree/readme", readme: tree.readme - if allowed_tree_edit? - = render 'projects/blob/upload', title: 'Upload', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post + = render 'projects/blob/upload', title: 'Upload New File', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post = render 'projects/blob/new_dir' :javascript diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml index cc3f1268f8..7c57924277 100644 --- a/app/views/shared/_commit_message_container.html.haml +++ b/app/views/shared/_commit_message_container.html.haml @@ -1,13 +1,15 @@ .form-group.commit_message-group - = label_tag 'commit_message', class: 'control-label' do + - nonce = SecureRandom.hex + = label_tag "commit_message-#{nonce}", class: 'control-label' do Commit message .col-sm-10 .commit-message-container .max-width-marker = text_area_tag 'commit_message', (params[:commit_message] || local_assigns[:text]), - class: 'form-control js-quick-submit', placeholder: local_assigns[:placeholder], - required: true, rows: (local_assigns[:rows] || 3) + class: 'form-control js-commit-message js-quick-submit', placeholder: local_assigns[:placeholder], + required: true, rows: (local_assigns[:rows] || 3), + id: "commit_message-#{nonce}" - if local_assigns[:hint] %p.hint Try to keep the first line under 52 characters diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml new file mode 100644 index 0000000000..8636341c60 --- /dev/null +++ b/app/views/shared/_new_commit_form.html.haml @@ -0,0 +1,18 @@ += render 'shared/commit_message_container', placeholder: placeholder + +- unless @project.empty_repo? + .form-group.branch + = label_tag 'branch', class: 'control-label' do + Branch + .col-sm-10 + = text_field_tag 'new_branch', @new_branch || @ref, class: "form-control js-new-branch" + + .form-group.js-create-merge-request-form-group + .col-sm-offset-2.col-sm-10 + .checkbox + - nonce = SecureRandom.hex + = label_tag "create_merge_request-#{nonce}" do + = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}" + Start a new merge request with this commit + + = hidden_field_tag 'original_branch', @ref, class: 'js-original-branch' From 95139a6508ee2ac958c898704eb5a0421ad7487e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 17 Nov 2015 18:54:45 +0100 Subject: [PATCH 123/410] Add changelog item --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 3c22df7c9a..1009b1d168 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -45,6 +45,7 @@ v 8.2.0 (unreleased) - Fix trailing whitespace issue in merge request/issue title - Fix bug when milestone/label filter was empty for dashboard issues page - Add ability to create milestone in group projects from single form + - Add option to create merge request when editing/creating a file (Dirceu Tiegs) v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) From 632f60cc76c1b2cd91a079ca8538d59fa28c9818 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 17 Nov 2015 13:48:45 -0500 Subject: [PATCH 124/410] Fix CHANGELOG [ci skip] --- CHANGELOG | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 56072e10ef..94f07a3168 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,7 +3,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) v 8.2.0 -v 8.2.0 (unreleased) - Fix grouping of contributors by email in graph. - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) - Fix Drone CI service template not saving properly (Stan Hu) @@ -1957,4 +1956,4 @@ v 0.8.0 - stability - security fixes - increased test coverage - - email notification \ No newline at end of file + - email notification From 08dc38223e0c18233052e04ac95a4f6942fcb1b5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 17 Nov 2015 15:00:14 -0500 Subject: [PATCH 125/410] Rename `not_auth_*` ability methods to `anonymous_*` --- app/models/ability.rb | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index f5cd14a89c..c93139e903 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -1,7 +1,7 @@ class Ability class << self def allowed(user, subject) - return not_auth_abilities(user, subject) if user.nil? + return anonymous_abilities(user, subject) if user.nil? return [] unless user.is_a?(User) return [] if user.blocked? @@ -19,22 +19,21 @@ class Ability end.concat(global_abilities(user)) end - # List of possible abilities - # for non-authenticated user - def not_auth_abilities(user, subject) + # List of possible abilities for anonymous user + def anonymous_abilities(user, subject) case true when subject.is_a?(PersonalSnippet) - not_auth_personal_snippet_abilities(subject) + anonymous_personal_snippet_abilities(subject) when subject.is_a?(Project) || subject.respond_to?(:project) - not_auth_project_abilities(subject) + anonymous_project_abilities(subject) when subject.is_a?(Group) || subject.respond_to?(:group) - not_auth_group_abilities(subject) + anonymous_group_abilities(subject) else [] end end - def not_auth_project_abilities(subject) + def anonymous_project_abilities(subject) project = if subject.is_a?(Project) subject else @@ -62,7 +61,7 @@ class Ability end end - def not_auth_group_abilities(subject) + def anonymous_group_abilities(subject) group = if subject.is_a?(Group) subject else @@ -76,7 +75,7 @@ class Ability end end - def not_auth_personal_snippet_abilities(snippet) + def anonymous_personal_snippet_abilities(snippet) if snippet.public? [:read_personal_snippet] else From 3d762c2e92f42a2c35cfbc90c87caedc8d60518a Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 17 Nov 2015 12:19:04 -0800 Subject: [PATCH 126/410] More labels since we get more specializations. --- PROCESS.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/PROCESS.md b/PROCESS.md index a4b0c83644..1408c8302e 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -34,13 +34,16 @@ The most important thing is making sure valid issues receive feedback from the d ## Workflow labels -Workflow labels are purposely not very detailed since that would be hard to keep updated as you would need to re-evaluate them after every comment. We optionally use functional labels on demand when want to group related issues to get an overview (for example all issues related to RVM, to tackle them in one go) and to add details to the issue. +Workflow labels are purposely not very detailed since that would be hard to keep updated as you would need to re-evaluate them after every comment. We optionally use functional labels on demand when want to group related issues to get an overview (for example all issues related to RVM, to tackle them in one go) and to add details to the issue. - *Awaiting feedback*: Feedback pending from the reporter - *Awaiting confirmation of fix*: The issue should already be solved in **master** (generally you can avoid this workflow item and just close the issue right away) - *Attached MR*: There is a MR attached and the discussion should happen there - We need to let issues stay in sync with the MR's. We can do this with a "Closing #XXXX" or "Fixes #XXXX" comment in the MR. We can't close the issue when there is a merge request because sometimes a MR is not good and we just close the MR, then the issue must stay. -- *Awaiting developer action/feedback*: Issue needs to be fixed or clarified by a developer +- *Developer*: needs help from a developer +- *UX* needs needs help from a UX designer +- *Frontend* needs help from a Front-end engineer +- *Graphics* needs help from a Graphics designer ## Functional labels From 1e74a12c2f1ed024bb7bd0d1963011833b7404ca Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 17 Nov 2015 14:43:51 -0800 Subject: [PATCH 127/410] Example workflow. --- PROCESS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/PROCESS.md b/PROCESS.md index 1408c8302e..482ad5fe9e 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -45,6 +45,8 @@ Workflow labels are purposely not very detailed since that would be hard to keep - *Frontend* needs help from a Front-end engineer - *Graphics* needs help from a Graphics designer +Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label. + ## Functional labels These labels describe what development specialities are involved such as: PostgreSQL, UX, LDAP. From 6c62f182c61e9f5f2c1cd3e98c647f246bf6880b Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 18 Nov 2015 10:32:29 +0100 Subject: [PATCH 128/410] Updates to the lfs doc. --- doc/workflow/git_lfs.md | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/doc/workflow/git_lfs.md b/doc/workflow/git_lfs.md index 6202822ea2..7304735f57 100644 --- a/doc/workflow/git_lfs.md +++ b/doc/workflow/git_lfs.md @@ -12,7 +12,7 @@ Git LFS makes this simpler for the end user by removing the requirement to learn ## How it works Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication to authorize client requests. -Once the request is authorized, Git LFS client receives instructions from where to fetch/where to push the large file. +Once the request is authorized, Git LFS client receives instructions from where to fetch or where to push the large file. ## Requirements @@ -23,7 +23,7 @@ Once the request is authorized, Git LFS client receives instructions from where ### Configuration -Git LFS objects can be large in size and they are stored on GitLab server storage. +Git LFS objects can be large in size. By default, they are stored on the server GitLab is installed on. There are two configuration options to help GitLab server administrators: @@ -49,7 +49,7 @@ In `config/gitlab.yml`: storage_path: /mnt/storage/lfs-objects ``` -### Known limitations +## Known limitations * Git LFS v1 original API is not supported since it was deprecated early in LFS development, starting with Git LFS version 0.6.0 * When SSH is set as a remote, Git LFS objects still go through HTTPS @@ -66,8 +66,13 @@ For example, if you want to upload a very large file and check it into your Git git clone git@gitlab.example.com:group/project.git git lfs init # initialize the Git LFS project project git lfs track "*.iso" # select the file extensions that you want to treat as large files +``` + +Once a certain file extension is marked for tracking as a LFS object you can use Git as usual without having to redo the command to track a file with the same extension: + +```bash cp ~/tmp/debian.iso ./ # copy a large file into the current directory -git add . # add the large file to git annex +git add . # add the large file to the project git commit -am "Added Debian iso" # commit the file meta data git push origin master # sync the git repo and large file to the GitLab server ``` @@ -80,26 +85,32 @@ git lfs fetch debian.iso # download the large file ``` -## Troubleshooting tips +## Troubleshooting ### error: Repository or object not found -Few reasons why this error can occur: +There are a couple of reasons why this error can occur: -1. Check the version of Git LFS on the client machine, `git lfs version`. Only version 0.6.0 and up are supported. -1. Check the Git config for traces of deprecated API, `git lfs -l`. If `batch = false` remove the line and try using Git LFS client > 0.6.0 +* Wrong version of LFS client used: + +Check the version of Git LFS on the client machine with `git lfs version`. Only version 0.6.0 and newer are supported. + +* Project is using deprecated LFS API + +Check the Git config of the project for traces of deprecated API with `git lfs -l`. If `batch = false` is set in the config, remove the line and try using Git LFS client newer than 0.6.0. ### Invalid status for : 501 -When attempting to push a LFS object to a GitLab server that doesn't have Git LFS support enabled, server will return status `error 501`. Check with your GitLab administrator why Git LFS is not enabled on the server +When attempting to push a LFS object to a GitLab server that doesn't have Git LFS support enabled, server will return status `error 501`. Check with your GitLab administrator why Git LFS is not enabled on the server. See [Configuration section](#configuration) for instructions on how to enable LFS support. ### getsockopt: connection refused -When pushing a LFS object and you receive an error similar to: `Post /info/lfs/objects/batch: dial tcp IP: getsockopt: connection refused`, -LFS client is trying to reach GitLab through HTTPS but your GitLab is being served on HTTP. -This behaviour is caused by Git LFS using HTTPS connections by default when it doesn't have a `lfsurl` set in the Git config. +If you push a LFS object to a project and you receive an error similar to: `Post /info/lfs/objects/batch: dial tcp IP: getsockopt: connection refused`, +the LFS client is trying to reach GitLab through HTTPS. However, your GitLab instance is being served on HTTP. -To go around this issue set the lfs url in git config: +This behaviour is caused by Git LFS using HTTPS connections by default when a `lfsurl` is not set in the Git config. + +To prevent this from happening, set the lfs url in project Git config: ```bash From 60e45651425369d7e55b85984ac8f5045d8bdef6 Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Wed, 18 Nov 2015 10:03:08 +0000 Subject: [PATCH 129/410] missing . --- doc/workflow/git_lfs.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/workflow/git_lfs.md b/doc/workflow/git_lfs.md index 7304735f57..4990a9b5aa 100644 --- a/doc/workflow/git_lfs.md +++ b/doc/workflow/git_lfs.md @@ -6,7 +6,7 @@ The general recommendation is to not have Git repositories larger than 1GB to pr GitLab already supports [managing large files with git annex](http://doc.gitlab.com/ee/workflow/git_annex.html) (EE only), however in certain environments it is not always convenient to use different commands to differentiate between the large files and regular ones. -Git LFS makes this simpler for the end user by removing the requirement to learn new commands +Git LFS makes this simpler for the end user by removing the requirement to learn new commands. ## How it works @@ -133,6 +133,4 @@ This will remember the credentials for an hour after which Git operations will r If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, `wincred` is available. -More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage) - - +More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage) \ No newline at end of file From b6251af40fa8ffdc9e85f5ed4c279774fe0ecaaf Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 18 Nov 2015 11:15:49 +0100 Subject: [PATCH 130/410] Fix feature spec. --- features/project/source/browse_files.feature | 14 ++++----- features/steps/project/source/browse_files.rb | 30 +++++-------------- 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index 69aa79f2d2..e545ea63ca 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -42,7 +42,7 @@ Feature: Project Source Browse Files And I fill the new branch name And I click on "Upload file" Then I can see the new text file - And I am redirected to the uploaded file on new branch + And I am redirected to the new merge request page And I can see the new commit message @javascript @@ -64,7 +64,7 @@ Feature: Project Source Browse Files And I fill the commit message And I fill the new branch name And I click on "Commit Changes" - Then I am redirected to the new file on new branch + Then I am redirected to the new merge request page And I should see its new content @javascript @@ -134,7 +134,7 @@ Feature: Project Source Browse Files And I fill the commit message And I fill the new branch name And I click on "Commit Changes" - Then I am redirected to the ".gitignore" on new branch + Then I am redirected to the new merge request page And I should see its new content @javascript @wip @@ -154,7 +154,7 @@ Feature: Project Source Browse Files And I fill the commit message And I fill the new branch name And I click on "Create directory" - Then I am redirected to the new directory + Then I am redirected to the new merge request page @javascript Scenario: I attempt to create an existing directory @@ -174,12 +174,12 @@ Feature: Project Source Browse Files Then I see diff @javascript - Scenario: I can remove file and commit + Scenario: I can delete file and commit Given I click on ".gitignore" file in repo And I see the ".gitignore" - And I click on "Remove" + And I click on "Delete" And I fill the commit message - And I click on "Remove file" + And I click on "Delete file" Then I am redirected to the files URL And I don't see the ".gitignore" diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 84725b9b58..f40e0f0d52 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -98,12 +98,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps click_button 'Create directory' end - step 'I click on "Remove"' do - click_button 'Remove' + step 'I click on "Delete"' do + click_button 'Delete' end - step 'I click on "Remove file"' do - click_button 'Remove file' + step 'I click on "Delete file"' do + click_button 'Delete file' end step 'I click on "Replace"' do @@ -142,7 +142,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I can see new file page' do - expect(page).to have_content "new file" + expect(page).to have_content "Create New File" expect(page).to have_content "Commit message" end @@ -225,10 +225,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'master/.gitignore')) end - step 'I am redirected to the ".gitignore" on new branch' do - expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/.gitignore')) - end - step 'I am redirected to the permalink URL' do expect(current_path).to( eq(namespace_project_blob_path(@project.namespace, @project, @@ -247,20 +243,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps @project.namespace, @project, 'master/' + new_file_name_with_directory)) end - step 'I am redirected to the new file on new branch' do - expect(current_path).to eq(namespace_project_blob_path( - @project.namespace, @project, 'new_branch_name/' + new_file_name)) - end - - step 'I am redirected to the uploaded file on new branch' do - expect(current_path).to eq(namespace_project_blob_path( - @project.namespace, @project, - 'new_branch_name/' + File.basename(test_text_file))) - end - - step 'I am redirected to the new directory' do - expect(current_path).to eq(namespace_project_tree_path( - @project.namespace, @project, 'new_branch_name/' + new_dir_name)) + step 'I am redirected to the new merge request page' do + expect(current_path).to eq(new_namespace_project_merge_request_path(@project.namespace, @project)) end step 'I am redirected to the root directory' do From 531177757eef772cc7ce5dd3898c3e6803187ed6 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 11 Nov 2015 16:19:06 +0100 Subject: [PATCH 131/410] Add import_error to project. --- db/migrate/20151110125604_add_import_error_to_project.rb | 5 +++++ db/schema.rb | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20151110125604_add_import_error_to_project.rb diff --git a/db/migrate/20151110125604_add_import_error_to_project.rb b/db/migrate/20151110125604_add_import_error_to_project.rb new file mode 100644 index 0000000000..7fc990f8d0 --- /dev/null +++ b/db/migrate/20151110125604_add_import_error_to_project.rb @@ -0,0 +1,5 @@ +class AddImportErrorToProject < ActiveRecord::Migration + def change + add_column :projects, :import_error, :text + end +end diff --git a/db/schema.rb b/db/schema.rb index aa76cef9fe..440a33e200 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -640,7 +640,10 @@ ActiveRecord::Schema.define(version: 20151116144118) do t.integer "star_count", default: 0, null: false t.string "import_type" t.string "import_source" - t.integer "commit_count", default: 0 + t.integer "commit_count", default: 0 + t.boolean "merge_requests_ff_only_enabled", default: false + t.text "issues_template" + t.text "import_error" end add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree From 841a7c6897b23957286056498cc3f05ec4330d15 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 11 Nov 2015 16:22:51 +0100 Subject: [PATCH 132/410] Store and show reason why import failed. --- app/models/project.rb | 10 ++-- app/views/projects/imports/new.html.haml | 12 +++-- app/workers/repository_fork_worker.rb | 14 +++-- app/workers/repository_import_worker.rb | 65 +++++++++++++++--------- lib/gitlab/backend/shell.rb | 7 +-- 5 files changed, 64 insertions(+), 44 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index a099a67cf6..c2ff103759 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -310,15 +310,17 @@ class Project < ActiveRecord::Base def add_import_job if forked? - unless RepositoryForkWorker.perform_async(id, forked_from_project.path_with_namespace, self.namespace.path) - import_fail - end + RepositoryForkWorker.perform_async(self.id, forked_from_project.path_with_namespace, self.namespace.path) else - RepositoryImportWorker.perform_async(id) + RepositoryImportWorker.perform_async(self.id) end end def clear_import_data + update(import_error: nil) + + ProjectCacheWorker.perform_async(self.id) + self.import_data.destroy if self.import_data end diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index 92a87690c5..f7b4416d87 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -1,12 +1,16 @@ - page_title "Import repository" %h3.page-title - - if @project.import_failed? - Import failed. Retry? - - else - Import repository + Import repository %hr +- if @project.import_failed? + .alert.alert-danger + %p The repository could not be imported. + %pre.prepend-top-10 + :preserve + #{@project.import_error.try(:strip)} + = form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f| .form-group.import-url-data = f.label :import_url, class: 'control-label' do diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb index acd1c43f06..2f991c5233 100644 --- a/app/workers/repository_fork_worker.rb +++ b/app/workers/repository_fork_worker.rb @@ -13,22 +13,20 @@ class RepositoryForkWorker end result = gitlab_shell.fork_repository(source_path, target_path) - unless result logger.error("Unable to fork project #{project_id} for repository #{source_path} -> #{target_path}") + project.update(import_error: "The project could not be forked.") project.import_fail - project.save return end - if project.valid_repo? - ProjectCacheWorker.perform_async(project.id) - project.import_finish - else - project.import_fail + unless project.valid_repo? logger.error("Project #{id} had an invalid repository after fork") + project.update(import_error: "The forked repository is invalid.") + project.import_fail + return end - project.save + project.import_finish end end diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index ea2808045e..5be2245df2 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -7,37 +7,52 @@ class RepositoryImportWorker def perform(project_id) project = Project.find(project_id) - unless project.import_url == Project::UNKNOWN_IMPORT_URL - import_result = gitlab_shell.send(:import_repository, - project.path_with_namespace, - project.import_url) - return project.import_fail unless import_result - else + if project.import_url == Project::UNKNOWN_IMPORT_URL + # In this case, we only want to import issues, not a repository. unless project.create_repository - return project.import_fail + project.update(import_error: "The repository could not be created.") + project.import_fail + return + end + else + begin + import_result = gitlab_shell.import_repository(project.path_with_namespace, project.import_url) + rescue Gitlab::Shell::Error => e + project.update(import_error: e.message) + project.import_fail + return end end - data_import_result = case project.import_type - when 'github' - Gitlab::GithubImport::Importer.new(project).execute - when 'gitlab' - Gitlab::GitlabImport::Importer.new(project).execute - when 'bitbucket' - Gitlab::BitbucketImport::Importer.new(project).execute - when 'google_code' - Gitlab::GoogleCodeImport::Importer.new(project).execute - when 'fogbugz' - Gitlab::FogbugzImport::Importer.new(project).execute - else - true - end - return project.import_fail unless data_import_result + data_import_result = + case project.import_type + when 'github' + Gitlab::GithubImport::Importer.new(project).execute + when 'gitlab' + Gitlab::GitlabImport::Importer.new(project).execute + when 'bitbucket' + Gitlab::BitbucketImport::Importer.new(project).execute + when 'google_code' + Gitlab::GoogleCodeImport::Importer.new(project).execute + when 'fogbugz' + Gitlab::FogbugzImport::Importer.new(project).execute + else + true + end - Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket' + unless data_import_result + project.update(import_error: "The remote issue data could not be imported.") + project.import_fail + return + end + + if project.import_type == 'bitbucket' + Gitlab::BitbucketImport::KeyDeleter.new(project).execute + end project.import_finish - project.save - ProjectCacheWorker.perform_async(project.id) + + # Explicitly update mirror so that upstream remote is created and fetched + project.update_mirror end end diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 01b8bda05c..87ac30b5ff 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -1,6 +1,6 @@ module Gitlab class Shell - class AccessDenied < StandardError; end + class Error < StandardError; end class KeyAdder < Struct.new(:io) def add_key(id, key) @@ -36,8 +36,9 @@ module Gitlab # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") # def import_repository(name, url) - Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'import-project', - "#{name}.git", url, '240']) + output, status = Popen::popen([gitlab_shell_projects_path, 'import-project', "#{name}.git", url, '240']) + raise Error, output unless status.zero? + true end # Move repository From 40470975e863b271f09fe147eb2eb545211e1b08 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 11 Nov 2015 16:23:51 +0100 Subject: [PATCH 133/410] Add Project#safe_import_url helper. --- app/helpers/projects_helper.rb | 8 -------- app/models/project.rb | 8 ++++++++ app/views/projects/imports/show.html.haml | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 690ae2090d..c9cd4a0d54 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -253,14 +253,6 @@ module ProjectsHelper filename_path(project, :version) end - def hidden_pass_url(original_url) - result = URI(original_url) - result.password = '*****' unless result.password.nil? - result - rescue - original_url - end - def project_wiki_path_with_version(proj, page, version, is_newest) url_params = is_newest ? {} : { version_id: version } namespace_project_wiki_path(proj.namespace, proj, page, url_params) diff --git a/app/models/project.rb b/app/models/project.rb index c2ff103759..70a648e68a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -348,6 +348,14 @@ class Project < ActiveRecord::Base import_status == 'finished' end + def safe_import_url + result = URI.parse(self.import_url) + result.password = '*****' unless result.password.nil? + result.to_s + rescue + original_url + end + def check_limit unless creator.can_create_project? or namespace.kind == 'group' errors[:limit_reached] << ("Your project limit is #{creator.projects_limit} projects! Please contact your administrator to increase it") diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml index 06886d215a..c0d1ce0d12 100644 --- a/app/views/projects/imports/show.html.haml +++ b/app/views/projects/imports/show.html.haml @@ -8,7 +8,7 @@ - else Import in progress. - unless @project.forked? - %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)} + %p.monospace git clone --bare #{@project.safe_import_url} %p Please wait while we import the repository for you. Refresh at will. :javascript new ProjectImport(); From 01d2b1943f009720a3f8a51e441dcc18f1706c9f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 11 Nov 2015 16:26:12 +0100 Subject: [PATCH 134/410] Move import form to partial. --- app/assets/stylesheets/framework/common.scss | 4 ++++ app/views/projects/imports/new.html.haml | 12 ++---------- app/views/projects/new.html.haml | 15 +-------------- app/views/shared/_import_form.html.haml | 16 ++++++++++++++++ 4 files changed, 23 insertions(+), 24 deletions(-) create mode 100644 app/views/shared/_import_form.html.haml diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index ddbacd7fd4..40f4beb196 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -328,6 +328,10 @@ table { } } +.well { + margin-bottom: 0; +} + .search_box { @extend .well; text-align: center; diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index f7b4416d87..01b247336e 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -12,15 +12,7 @@ #{@project.import_error.try(:strip)} = form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f| - .form-group.import-url-data - = f.label :import_url, class: 'control-label' do - %span Import existing git repo - .col-sm-10 - = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git' - .well.prepend-top-20 - This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git. - %br - The import will time out after 4 minutes. For big repositories, use a clone/push combination. - For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"} + = render "shared/import_form", f: f + .form-actions = f.submit 'Start import', class: "btn btn-create", tabindex: 4 diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index a02c12f06a..c9d1fc3da2 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -23,7 +23,6 @@ = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2} - if import_sources_enabled? - .project-import.js-toggle-container .form-group %label.control-label Import project from @@ -82,19 +81,7 @@ %span Any repo by URL .js-toggle-content.hide - .form-group.import-url-data - = f.label :import_url, class: 'control-label' do - %span Git repository URL - .col-sm-10 - = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git' - .well.prepend-top-20 - %ul - %li - The repository must be accessible over HTTP(S). If it is not publicly accessible, you can add authentication information to the URL: https://username:password@gitlab.company.com/group/project.git. - %li - The import will time out after 4 minutes. For big repositories, use a clone/push combination. - %li - To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}. + = render "shared/import_form", f: f .prepend-botton-10 diff --git a/app/views/shared/_import_form.html.haml b/app/views/shared/_import_form.html.haml new file mode 100644 index 0000000000..285af56ad7 --- /dev/null +++ b/app/views/shared/_import_form.html.haml @@ -0,0 +1,16 @@ +.form-group.import-url-data + = f.label :import_url, class: 'control-label' do + %span Git repository URL + .col-sm-10 + = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git' + + .well.prepend-top-20 + %ul + %li + The repository must be accessible over http://, https:// or git://. + %li + If your HTTP repository is not publicly accessible, add authentication information to the URL: https://username:password@gitlab.company.com/group/project.git. + %li + The import will time out after 4 minutes. For big repositories, use a clone/push combination. + %li + To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}. From 7b405d306431448f384591de792497e719d71caa Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 11 Nov 2015 16:25:01 +0100 Subject: [PATCH 135/410] Fix redirect after import fails. --- app/controllers/projects/imports_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb index 066b66014f..fb8788f081 100644 --- a/app/controllers/projects/imports_controller.rb +++ b/app/controllers/projects/imports_controller.rb @@ -28,8 +28,8 @@ class Projects::ImportsController < Projects::ApplicationController if @project.import_finished? redirect_to(project_path(@project)) and return else - redirect_to new_namespace_project_import_path(@project.namespace, - @project) && return + redirect_to(new_namespace_project_import_path(@project.namespace, + @project)) and return end end end From a0519818cb4ff90d6a1d66b6360ec94f05bc79ec Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 11 Nov 2015 16:26:43 +0100 Subject: [PATCH 136/410] Tweak code formatting. --- app/services/projects/create_service.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 5b84527ecc..700a1db04d 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -55,7 +55,9 @@ module Projects @project.save if @project.persisted? && !@project.import? - raise 'Failed to create repository' unless @project.create_repository + unless @project.create_repository + raise 'Failed to create repository' + end end end @@ -94,9 +96,7 @@ module Projects @project.team << [current_user, :master, current_user] end - if @project.import? - @project.import_start - end + @project.import_start if @project.import? end end end From 153085a93bf7f2350d15009fe7924cea190cd7f2 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 11 Nov 2015 16:27:55 +0100 Subject: [PATCH 137/410] Add Repository#is_ancestor? convenience method. --- app/models/repository.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index f76b770e86..c5b6ee80dc 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -484,7 +484,7 @@ class Repository root_ref_commit = commit(root_ref) if branch_commit - rugged.merge_base(root_ref_commit.id, branch_commit.id) == branch_commit.id + is_ancestor?(branch_commit.id, root_ref_commit.id) else nil end @@ -494,6 +494,11 @@ class Repository rugged.merge_base(first_commit_id, second_commit_id) end + def is_ancestor?(ancestor_id, descendant_id) + merge_base(ancestor_id, descendant_id) == ancestor_id + end + + def search_files(query, ref) offset = 2 args = %W(#{Gitlab.config.git.bin_path} grep -i -n --before-context #{offset} --after-context #{offset} -e #{query} #{ref || root_ref}) From b1f4d14f7d521b1ceb167a52a188eb93e9384db4 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 11 Nov 2015 16:28:31 +0100 Subject: [PATCH 138/410] Clean up Repository cache code. --- app/models/repository.rb | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index c5b6ee80dc..fd60c7edbb 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -105,29 +105,25 @@ class Repository end def add_branch(branch_name, ref) - cache.expire(:branch_names) - @branches = nil + expire_branches_cache gitlab_shell.add_branch(path_with_namespace, branch_name, ref) end def add_tag(tag_name, ref, message = nil) - cache.expire(:tag_names) - @tags = nil + expire_tags_cache gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message) end def rm_branch(branch_name) - cache.expire(:branch_names) - @branches = nil + expire_branches_cache gitlab_shell.rm_branch(path_with_namespace, branch_name) end def rm_tag(tag_name) - cache.expire(:tag_names) - @tags = nil + expire_tags_cache gitlab_shell.rm_tag(path_with_namespace, tag_name) end @@ -169,6 +165,16 @@ class Repository end end + def expire_tags_cache + cache.expire(:tag_names) + @tags = nil + end + + def expire_branches_cache + cache.expire(:branch_names) + @branches = nil + end + def expire_cache cache_keys.each do |key| cache.expire(key) From 91fdbdcde7f2d58f6f5987640eed538588822b93 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 12 Nov 2015 12:44:33 +0100 Subject: [PATCH 139/410] Add tooltips to home panel buttons. --- app/views/projects/_home_panel.html.haml | 17 +++++-------- .../projects/buttons/_download.html.haml | 4 +++ .../projects/buttons/_dropdown.html.haml | 2 -- app/views/projects/buttons/_fork.html.haml | 25 ++++++++++--------- app/views/projects/buttons/_star.html.haml | 2 +- app/views/shared/_clone_panel.html.haml | 5 ++-- 6 files changed, 26 insertions(+), 29 deletions(-) create mode 100644 app/views/projects/buttons/_download.html.haml diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 8c0980369f..88d54bf6f2 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -18,17 +18,12 @@ .project-repo-buttons .split-one = render 'projects/buttons/star' + = render 'projects/buttons/fork' - - unless empty_repo - = render 'projects/buttons/fork' - = render "shared/clone_panel" - .split-repo-buttons - - unless empty_repo - - if can? current_user, :download_code, @project - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do - = icon('download fw') - - = render 'projects/buttons/dropdown' - = render 'projects/buttons/notifications' + .split-repo-buttons + = render "projects/buttons/download" + = render 'projects/buttons/dropdown' + + = render 'projects/buttons/notifications' diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml new file mode 100644 index 0000000000..14ee2263b7 --- /dev/null +++ b/app/views/projects/buttons/_download.html.haml @@ -0,0 +1,4 @@ +- unless @project.empty_repo? + - if can? current_user, :download_code, @project + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn has_tooltip', rel: 'nofollow', title: "Download ZIP" do + = icon('download') diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 18cae8ef6d..b277b765b6 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -32,5 +32,3 @@ = link_to new_namespace_project_tag_path(@project.namespace, @project) do = icon('tags fw') New tag - - diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml index 8f2f631eb7..2d3abf0905 100644 --- a/app/views/projects/buttons/_fork.html.haml +++ b/app/views/projects/buttons/_fork.html.haml @@ -1,12 +1,13 @@ -- if current_user && can?(current_user, :fork_project, @project) - - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn' do - = icon('code-fork fw') - Fork - %span.count - = @project.forks_count - - else - = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn' do - = icon('code-fork fw') - %span.count - = @project.forks_count +- unless @project.empty_repo? + - if current_user && can?(current_user, :fork_project, @project) + - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 + = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn has_tooltip' do + = icon('code-fork fw') + Fork + %span.count + = @project.forks_count + - else + = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn has_tooltip' do + = icon('code-fork fw') + %span.count + = @project.forks_count diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml index 0658390203..41a3ec6d90 100644 --- a/app/views/projects/buttons/_star.html.haml +++ b/app/views/projects/buttons/_star.html.haml @@ -1,5 +1,5 @@ - if current_user - = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star', method: :post, remote: true do + = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star has_tooltip', method: :post, remote: true, title: "Star project" do = icon('star fw') %span.count = @project.star_count diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 2e4aab3630..8bcb24ae9d 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -21,7 +21,6 @@ = gitlab_config.protocol.upcase = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true - if project.kind_of?(Project) - .input-group-addon - .visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" } + .input-group-addon.has_tooltip{title: "#{visibility_level_label(project.visibility_level)} project", data: { container: "body" } } + .visibility-level-label = visibility_level_icon(project.visibility_level) - From 5036d8d5d55a8b22e423fb440d75f3d3949038d6 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 12 Nov 2015 12:45:49 +0100 Subject: [PATCH 140/410] Fix styling of import error. --- app/assets/stylesheets/framework/tw_bootstrap.scss | 2 +- app/views/projects/imports/new.html.haml | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index 99d028d122..50c0cf61f4 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -172,7 +172,7 @@ } .panel-body { - form { + form, pre { margin: 0; } diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml index 01b247336e..6027fb2336 100644 --- a/app/views/projects/imports/new.html.haml +++ b/app/views/projects/imports/new.html.haml @@ -5,11 +5,12 @@ %hr - if @project.import_failed? - .alert.alert-danger - %p The repository could not be imported. - %pre.prepend-top-10 - :preserve - #{@project.import_error.try(:strip)} + .panel.panel-danger + .panel-heading The repository could not be imported. + .panel-body + %pre + :preserve + #{@project.import_error.try(:strip)} = form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f| = render "shared/import_form", f: f From 5e8ddd3c2d898367f6bb377df02e40754850d4f3 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 12 Nov 2015 12:53:00 +0100 Subject: [PATCH 141/410] Remove unused variable in repository import --- app/workers/repository_import_worker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index 5be2245df2..1de4916199 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -16,7 +16,7 @@ class RepositoryImportWorker end else begin - import_result = gitlab_shell.import_repository(project.path_with_namespace, project.import_url) + gitlab_shell.import_repository(project.path_with_namespace, project.import_url) rescue Gitlab::Shell::Error => e project.update(import_error: e.message) project.import_fail From d0ec28827d33d00ca637b1962b370313701ac3a0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 12 Nov 2015 13:46:50 +0100 Subject: [PATCH 142/410] Fix specs --- features/steps/dashboard/new_project.rb | 1 - spec/services/projects/fork_service_spec.rb | 17 +++++------------ spec/workers/repository_fork_worker_spec.rb | 1 - 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb index 44a4aa9844..a0aad66184 100644 --- a/features/steps/dashboard/new_project.rb +++ b/features/steps/dashboard/new_project.rb @@ -44,7 +44,6 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps git_import_instructions = first('.js-toggle-content') expect(git_import_instructions).to be_visible expect(git_import_instructions).to have_content "Git repository URL" - expect(git_import_instructions).to have_content "The repository must be accessible over HTTP(S). If it is not publicly accessible, you can add authentication information to the URL:" end step 'I click on "Google Code"' do diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index e397b2b9b4..1feba6ce04 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -25,13 +25,6 @@ describe Projects::ForkService do end end - context 'fork project failure' do - it "fails due to transaction failure" do - @to_project = fork_project(@from_project, @to_user, false) - expect(@to_project.import_failed?) - end - end - context 'project already exists' do it "should fail due to validation, not transaction failure" do @existing_project = create(:project, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace) @@ -66,7 +59,7 @@ describe Projects::ForkService do context 'fork project for group' do it 'group owner successfully forks project into the group' do - to_project = fork_project(@project, @group_owner, true, @opts) + to_project = fork_project(@project, @group_owner, @opts) expect(to_project.owner).to eq(@group) expect(to_project.namespace).to eq(@group) expect(to_project.name).to eq(@project.name) @@ -78,7 +71,7 @@ describe Projects::ForkService do context 'fork project for group when user not owner' do it 'group developer should fail to fork project into the group' do - to_project = fork_project(@project, @developer, true, @opts) + to_project = fork_project(@project, @developer, @opts) expect(to_project.errors[:namespace]).to eq(['is not valid']) end end @@ -87,7 +80,7 @@ describe Projects::ForkService do it 'should fail due to validation, not transaction failure' do existing_project = create(:project, name: @project.name, namespace: @group) - to_project = fork_project(@project, @group_owner, true, @opts) + to_project = fork_project(@project, @group_owner, @opts) expect(existing_project.persisted?).to be_truthy expect(to_project.errors[:name]).to eq(['has already been taken']) expect(to_project.errors[:path]).to eq(['has already been taken']) @@ -95,8 +88,8 @@ describe Projects::ForkService do end end - def fork_project(from_project, user, fork_success = true, params = {}) - allow(RepositoryForkWorker).to receive(:perform_async).and_return(fork_success) + def fork_project(from_project, user, params = {}) + allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) Projects::ForkService.new(from_project, user, params).execute end end diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb index aa03110696..245f066df1 100644 --- a/spec/workers/repository_fork_worker_spec.rb +++ b/spec/workers/repository_fork_worker_spec.rb @@ -12,7 +12,6 @@ describe RepositoryForkWorker do project.path_with_namespace, fork_project.namespace.path). and_return(true) - expect(ProjectCacheWorker).to receive(:perform_async) subject.perform(project.id, project.path_with_namespace, From 8937c5ff7adb309f7b52a3ecf7dfe3181e2ceb1b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 12 Nov 2015 13:47:48 +0100 Subject: [PATCH 143/410] Load raw repository lazily to recover from failed read --- app/models/repository.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index fd60c7edbb..c183610346 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -6,7 +6,7 @@ class Repository include Gitlab::ShellAdapter - attr_accessor :raw_repository, :path_with_namespace, :project + attr_accessor :path_with_namespace, :project def self.clean_old_archives repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path @@ -19,14 +19,18 @@ class Repository def initialize(path_with_namespace, default_branch = nil, project = nil) @path_with_namespace = path_with_namespace @project = project + end - if path_with_namespace - @raw_repository = Gitlab::Git::Repository.new(path_to_repo) - @raw_repository.autocrlf = :input + def raw_repository + return nil unless path_with_namespace + + @raw_repository ||= begin + repo = Gitlab::Git::Repository.new(path_to_repo) + repo.autocrlf = :input + repo + rescue Gitlab::Git::Repository::NoRepository + nil end - - rescue Gitlab::Git::Repository::NoRepository - nil end # Return absolute path to repository From e809669383d528e80d6408c4838566cf9f0bb8ac Mon Sep 17 00:00:00 2001 From: Arseny Zarechnev Date: Tue, 17 Nov 2015 17:47:39 +0000 Subject: [PATCH 144/410] Fix github importer to handle empty issues --- lib/gitlab/github_import/importer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index bd7340a80f..b5720f6e2c 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -19,7 +19,7 @@ module Gitlab if issue.pull_request.nil? body = @formatter.author_line(issue.user.login) - body += issue.body + body += issue.body || "" if issue.comments > 0 body += @formatter.comments_header From 7763e61ae59b9b610cb87b30ef5ca90c94a011ea Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 18 Nov 2015 12:09:33 +0100 Subject: [PATCH 145/410] Use gitlab-shell 2.6.7 --- GITLAB_SHELL_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 338a5b5d8f..e261122d5c 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.6.6 +2.6.7 From f5e3d93c28ff9e1a7e0ba9bddbbc242709f0fa8b Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 18 Nov 2015 13:01:35 +0100 Subject: [PATCH 146/410] Check which folders and archives should be packed before passing to tar command. --- lib/backup/manager.rb | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 9e15d5411a..69922eb66e 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -150,17 +150,15 @@ module Backup private def backup_contents - folders_to_backup + ["uploads.tar.gz", "builds.tar.gz", "artifacts.tar.gz", "backup_information.yml"] + folders_to_backup + archives_to_backup + ["backup_information.yml"] + end + + def archives_to_backup + %w{uploads builds artifacts}.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact end def folders_to_backup - folders = %w{repositories db} - - if ENV["SKIP"] - return folders.reject{ |folder| ENV["SKIP"].include?(folder) } - end - - folders + %w{repositories db}.map{ |name| name unless skipped?(name) }.compact end def settings From 054f2f98eda60682fd796a1f66681accc6f7ce1c Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 11 Nov 2015 17:14:47 +0100 Subject: [PATCH 147/410] Faster way of obtaining latest event update time Instead of using MAX(events.updated_at) we can simply sort the events in descending order by the "id" column and grab the first row. In other words, instead of this: SELECT max(events.updated_at) AS max_id FROM events LEFT OUTER JOIN projects ON projects.id = events.project_id LEFT OUTER JOIN namespaces ON namespaces.id = projects.namespace_id WHERE events.author_id IS NOT NULL AND events.project_id IN (13083); we can use this: SELECT events.updated_at AS max_id FROM events LEFT OUTER JOIN projects ON projects.id = events.project_id LEFT OUTER JOIN namespaces ON namespaces.id = projects.namespace_id WHERE events.author_id IS NOT NULL AND events.project_id IN (13083) ORDER BY events.id DESC LIMIT 1; This has the benefit that on PostgreSQL a backwards index scan can be used, which due to the "LIMIT 1" will at most process only a single row. This in turn greatly speeds up the process of grabbing the latest update time. This can be confirmed by looking at the query plans. The first query produces the following plan: Aggregate (cost=43779.84..43779.85 rows=1 width=12) (actual time=2142.462..2142.462 rows=1 loops=1) -> Index Scan using index_events_on_project_id on events (cost=0.43..43704.69 rows=30060 width=12) (actual time=0.033..2138.086 rows=32769 loops=1) Index Cond: (project_id = 13083) Filter: (author_id IS NOT NULL) Planning time: 1.248 ms Execution time: 2142.548 ms The second query in turn produces the following plan: Limit (cost=0.43..41.65 rows=1 width=16) (actual time=1.394..1.394 rows=1 loops=1) -> Index Scan Backward using events_pkey on events (cost=0.43..1238907.96 rows=30060 width=16) (actual time=1.394..1.394 rows=1 loops=1) Filter: ((author_id IS NOT NULL) AND (project_id = 13083)) Rows Removed by Filter: 2104 Planning time: 0.166 ms Execution time: 1.408 ms According to the above plans the 2nd query is around 1500 times faster. However, re-running the first query produces timings of around 80 ms, making the 2nd query "only" around 55 times faster. --- app/models/event.rb | 6 ++++++ .../dashboard/projects/index.atom.builder | 2 +- app/views/groups/show.atom.builder | 2 +- app/views/projects/show.atom.builder | 2 +- app/views/users/show.atom.builder | 2 +- spec/models/event_spec.rb | 21 +++++++++++++++++++ 6 files changed, 31 insertions(+), 4 deletions(-) diff --git a/app/models/event.rb b/app/models/event.rb index bf64ac29d3..7618b503d8 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -63,6 +63,12 @@ class Event < ActiveRecord::Base Event::PUSHED, ["MergeRequest", "Issue"], [Event::CREATED, Event::CLOSED, Event::MERGED]) end + + def latest_update_time + row = select(:updated_at, :project_id).reorder(id: :desc).take + + row ? row.updated_at : nil + end end def proper? diff --git a/app/views/dashboard/projects/index.atom.builder b/app/views/dashboard/projects/index.atom.builder index d2c5148684..c8c219f4cc 100644 --- a/app/views/dashboard/projects/index.atom.builder +++ b/app/views/dashboard/projects/index.atom.builder @@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link href: dashboard_projects_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: dashboard_projects_url, rel: "alternate", type: "text/html" xml.id dashboard_projects_url - xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? + xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| event_to_atom(xml, event) diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder index a91d1a6e94..7ea574434c 100644 --- a/app/views/groups/show.atom.builder +++ b/app/views/groups/show.atom.builder @@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link href: group_url(@group, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: group_url(@group), rel: "alternate", type: "text/html" xml.id group_url(@group) - xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? + xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| event_to_atom(xml, event) diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder index 242684e5c7..15c4976755 100644 --- a/app/views/projects/show.atom.builder +++ b/app/views/projects/show.atom.builder @@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html" xml.id namespace_project_url(@project.namespace, @project) - xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? + xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| event_to_atom(xml, event) diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder index 50232dc718..2fe5b7fac8 100644 --- a/app/views/users/show.atom.builder +++ b/app/views/users/show.atom.builder @@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml" xml.link href: user_url(@user), rel: "alternate", type: "text/html" xml.id user_url(@user) - xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? + xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| event_to_atom(xml, event) diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 0f32f162a1..f46c50ed6f 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -64,4 +64,25 @@ describe Event do it { expect(@event.branch_name).to eq("master") } it { expect(@event.author).to eq(@user) } end + + describe '.latest_update_time' do + describe 'when events are present' do + let(:time) { Time.utc(2015, 1, 1) } + + before do + create(:closed_issue_event, updated_at: time) + create(:closed_issue_event, updated_at: time + 5) + end + + it 'returns the latest update time' do + expect(Event.latest_update_time).to eq(time + 5) + end + end + + describe 'when no events exist' do + it 'returns nil' do + expect(Event.latest_update_time).to be_nil + end + end + end end From d769596aec7daa2ff43f86c8fad4211fbc4f607d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 12 Nov 2015 16:16:49 +0100 Subject: [PATCH 148/410] Added Gitlab::SQL::Union class This class can be used to join multiple AcitveRecord::Relation objects together using a SQL UNION statement. ActiveRecord < 5.0 sadly doesn't support UNION and existing Gems out there don't handle prepared statements (e.g. they never incremented the variable bindings). --- lib/gitlab/sql/union.rb | 34 +++++++++++++++++++++++++++++++ spec/lib/gitlab/sql/union_spec.rb | 16 +++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 lib/gitlab/sql/union.rb create mode 100644 spec/lib/gitlab/sql/union_spec.rb diff --git a/lib/gitlab/sql/union.rb b/lib/gitlab/sql/union.rb new file mode 100644 index 0000000000..1a62eff0b3 --- /dev/null +++ b/lib/gitlab/sql/union.rb @@ -0,0 +1,34 @@ +module Gitlab + module SQL + # Class for building SQL UNION statements. + # + # ORDER BYs are dropped from the relations as the final sort order is not + # guaranteed any way. + # + # Example usage: + # + # union = Gitlab::SQL::Union.new(user.personal_projects, user.projects) + # sql = union.to_sql + # + # Project.where("id IN (#{sql})") + class Union + def initialize(relations) + @relations = relations + end + + def to_sql + # Some relations may include placeholders for prepared statements, these + # aren't incremented properly when joining relations together this way. + # By using "unprepared_statements" we remove the usage of placeholders + # (thus fixing this problem), at a slight performance cost. + fragments = ActiveRecord::Base.connection.unprepared_statement do + @relations.map do |rel| + "(#{rel.reorder(nil).to_sql})" + end + end + + fragments.join(' UNION ') + end + end + end +end diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb new file mode 100644 index 0000000000..976360af9b --- /dev/null +++ b/spec/lib/gitlab/sql/union_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe Gitlab::SQL::Union do + describe '#to_sql' do + it 'returns a String joining relations together using a UNION' do + rel1 = User.where(email: 'alice@example.com') + rel2 = User.where(email: 'bob@example.com') + union = described_class.new([rel1, rel2]) + + sql1 = rel1.reorder(nil).to_sql + sql2 = rel2.reorder(nil).to_sql + + expect(union.to_sql).to eq("(#{sql1}) UNION (#{sql2})") + end + end +end From 028bd227fb1915edca181331542c433fd171d31a Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 12 Nov 2015 16:29:40 +0100 Subject: [PATCH 149/410] Use SQL::Union for User#authorized_projects This allows retrieving of the list of authorized projects using a single query, without having to load any IDs into Ruby. This in turn also means we can remove the method User#authorized_projects_id. --- app/models/user.rb | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 61abea1f6e..8f34f52be1 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -397,26 +397,9 @@ class User < ActiveRecord::Base end end - def authorized_projects_id - @authorized_projects_id ||= begin - project_ids = personal_projects.pluck(:id) - project_ids.push(*groups_projects.pluck(:id)) - project_ids.push(*projects.pluck(:id).uniq) - end - end - - def master_or_owner_projects_id - @master_or_owner_projects_id ||= begin - scope = { access_level: [ Gitlab::Access::MASTER, Gitlab::Access::OWNER ] } - project_ids = personal_projects.pluck(:id) - project_ids.push(*groups_projects.where(members: scope).pluck(:id)) - project_ids.push(*projects.where(members: scope).pluck(:id).uniq) - end - end - # Projects user has access to def authorized_projects - @authorized_projects ||= Project.where(id: authorized_projects_id) + @authorized_projects ||= Project.where("id IN (#{projects_union.to_sql})") end def owned_projects @@ -743,8 +726,8 @@ class User < ActiveRecord::Base Event.contributions.where(author_id: self). where("created_at > ?", Time.now - 1.year). reorder(project_id: :desc). - select(:project_id). - uniq.map(&:project_id) + uniq. + pluck(:project_id) end def restricted_signup_domains @@ -774,11 +757,26 @@ class User < ActiveRecord::Base !solo_owned_groups.present? end + def ci_authorized_projects + @ci_authorized_projects ||= + Ci::Project.where("gitlab_id IN (#{projects_union.to_sql})") + end + def ci_authorized_runners @ci_authorized_runners ||= begin runner_ids = Ci::RunnerProject.joins(:project). - where(ci_projects: { gitlab_id: master_or_owner_projects_id }).select(:runner_id) + where("ci_projects.gitlab_id IN (#{projects_union.to_sql})"). + select(:runner_id) + Ci::Runner.specific.where(id: runner_ids) end end + + private + + def projects_union + Gitlab::SQL::Union.new([personal_projects.select(:id), + groups_projects.select(:id), + projects.select(:id)]) + end end From 656d9ff69b372be8e0aebb94418c487e76e04256 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 12 Nov 2015 16:30:39 +0100 Subject: [PATCH 150/410] Make it easier to re-apply default sort orders By moving the default sort order into a separate scope (and calling this from the default scope) we can more easily re-apply a default order without having to specify the exact column/ordering all over the place. --- app/models/concerns/sortable.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb index 913c747a1c..7391a77383 100644 --- a/app/models/concerns/sortable.rb +++ b/app/models/concerns/sortable.rb @@ -8,8 +8,9 @@ module Sortable included do # By default all models should be ordered # by created_at field starting from newest - default_scope { order(id: :desc) } + default_scope { order_id_desc } + scope :order_id_desc, -> { reorder(id: :desc) } scope :order_created_desc, -> { reorder(created_at: :desc) } scope :order_created_asc, -> { reorder(created_at: :asc) } scope :order_updated_desc, -> { reorder(updated_at: :desc) } From 189c40c33d18df08dd40e9f009f6658f89e3af0e Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 12 Nov 2015 17:38:20 +0100 Subject: [PATCH 151/410] Use SQL::Union for User#authorized_groups This removes the need for plucking any IDs into Ruby. --- app/models/user.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 8f34f52be1..ddb3158e0f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -391,10 +391,13 @@ class User < ActiveRecord::Base # Groups user has access to def authorized_groups - @authorized_groups ||= begin - group_ids = (groups.pluck(:id) + authorized_projects.pluck(:namespace_id)) - Group.where(id: group_ids) - end + @authorized_groups ||= + begin + union = Gitlab::SQL::Union. + new([groups.select(:id), authorized_projects.select(:namespace_id)]) + + Group.where("id IN (#{union.to_sql})") + end end # Projects user has access to From bfd9855a2b2a09e8f5bff89e84891d3ad598fe0d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 12 Nov 2015 17:50:43 +0100 Subject: [PATCH 152/410] Prefix table names for User UNIONs --- app/models/user.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index ddb3158e0f..a2258967e2 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -396,13 +396,14 @@ class User < ActiveRecord::Base union = Gitlab::SQL::Union. new([groups.select(:id), authorized_projects.select(:namespace_id)]) - Group.where("id IN (#{union.to_sql})") + Group.where("namespaces.id IN (#{union.to_sql})") end end # Projects user has access to def authorized_projects - @authorized_projects ||= Project.where("id IN (#{projects_union.to_sql})") + @authorized_projects ||= + Project.where("projects.id IN (#{projects_union.to_sql})") end def owned_projects From 5fcd9986b86812786c90a0b8d3461db79ce71051 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 16 Nov 2015 14:28:02 +0100 Subject: [PATCH 153/410] Refactor getting user groups/projects/contributions This new setup no longer loads any IDs into memory using "pluck", instead using SQL UNIONs to merge the various datasets together. This results in greatly improved query performance as well as a reduction of memory usage. The old setup was in particular problematic when requesting the authorized projects _including_ public/internal projects as this would result in roughly 65000 project IDs being loaded into memory. These IDs would in turn be passed to other queries. --- app/models/user.rb | 64 ++++++++++++++++++++++++++++++---------- spec/models/user_spec.rb | 52 +++++++++++++++++++++++++++++--- 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index a2258967e2..d523b3f049 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -389,21 +389,40 @@ class User < ActiveRecord::Base end end - # Groups user has access to - def authorized_groups - @authorized_groups ||= - begin - union = Gitlab::SQL::Union. - new([groups.select(:id), authorized_projects.select(:namespace_id)]) + # Returns the groups a user has access to, optionally including any public + # groups. + # + # public_internal - When set to "true" all public groups and groups of public + # projects are also included. + # + # Returns an ActiveRecord::Relation + def authorized_groups(public_internal = false) + union = Gitlab::SQL::Union. + new([groups.select(:id), authorized_projects(public_internal). + select(:namespace_id)]) - Group.where("namespaces.id IN (#{union.to_sql})") - end + sql = "namespaces.id IN (#{union.to_sql})" + + if public_internal + sql << ' OR public IS TRUE' + end + + Group.where(sql) end - # Projects user has access to - def authorized_projects - @authorized_projects ||= - Project.where("projects.id IN (#{projects_union.to_sql})") + # Returns the groups a user is authorized to access. + # + # public_internal - When set to "true" all public/internal projects will also + # be included. + def authorized_projects(public_internal = false) + base = "projects.id IN (#{projects_union.to_sql})" + + if public_internal + Project.where("#{base} OR projects.visibility_level IN (?)", + Project.public_and_internal_levels) + else + Project.where(base) + end end def owned_projects @@ -726,12 +745,25 @@ class User < ActiveRecord::Base Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil) end - def contributed_projects_ids - Event.contributions.where(author_id: self). + # Returns the projects a user contributed to in the last year. + # + # This method relies on a subquery as this performs significantly better + # compared to a JOIN when coupled with, for example, + # `Project.visible_to_user`. That is, consider the following code: + # + # some_user.contributed_projects.visible_to_user(other_user) + # + # If this method were to use a JOIN the resulting query would take roughly 200 + # ms on a database with a similar size to gitlab.com's database. On the other + # hand, using a subquery means we can get the exact same data in about 40 ms. + def contributed_projects + events = Event.select(:project_id). + contributions.where(author_id: self). where("created_at > ?", Time.now - 1.year). - reorder(project_id: :desc). uniq. - pluck(:project_id) + reorder(nil) + + Project.where(id: events) end def restricted_signup_domains diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 7d716c2312..71160f8dfe 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -686,7 +686,7 @@ describe User do end end - describe "#contributed_projects_ids" do + describe "#contributed_projects" do subject { create(:user) } let!(:project1) { create(:project) } let!(:project2) { create(:project, forked_from_project: project3) } @@ -701,15 +701,15 @@ describe User do end it "includes IDs for projects the user has pushed to" do - expect(subject.contributed_projects_ids).to include(project1.id) + expect(subject.contributed_projects).to include(project1) end it "includes IDs for projects the user has had merge requests merged into" do - expect(subject.contributed_projects_ids).to include(project3.id) + expect(subject.contributed_projects).to include(project3) end it "doesn't include IDs for unrelated projects" do - expect(subject.contributed_projects_ids).not_to include(project2.id) + expect(subject.contributed_projects).not_to include(project2) end end @@ -758,4 +758,48 @@ describe User do expect(subject.recent_push).to eq(nil) end end + + describe '#authorized_groups' do + let!(:user) { create(:user) } + let!(:private_group) { create(:group) } + let!(:public_group) { create(:group, public: true) } + + before do + private_group.add_user(user, Gitlab::Access::MASTER) + end + + describe 'excluding public groups' do + subject { user.authorized_groups } + + it { is_expected.to eq([private_group]) } + end + + describe 'including public groups' do + subject { user.authorized_groups(true) } + + it { is_expected.to eq([public_group, private_group]) } + end + end + + describe '#authorized_projects' do + let!(:user) { create(:user) } + let!(:private_project) { create(:project, :private) } + let!(:public_project) { create(:project, :public) } + + before do + private_project.team << [user, Gitlab::Access::MASTER] + end + + describe 'excluding public projects' do + subject { user.authorized_projects } + + it { is_expected.to eq([private_project]) } + end + + describe 'including public projects' do + subject { user.authorized_projects(true) } + + it { is_expected.to eq([public_project, private_project]) } + end + end end From b4646391eeb23b9f9828f0913e06ae78d298726e Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 12:02:14 +0100 Subject: [PATCH 154/410] Renamed GroupsFinder spec file so the name matches --- spec/finders/{group_finder_spec.rb => groups_finder_spec.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/finders/{group_finder_spec.rb => groups_finder_spec.rb} (100%) diff --git a/spec/finders/group_finder_spec.rb b/spec/finders/groups_finder_spec.rb similarity index 100% rename from spec/finders/group_finder_spec.rb rename to spec/finders/groups_finder_spec.rb From 2110247f83440f4a1044b999ff0f2630bd36e969 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 12:03:27 +0100 Subject: [PATCH 155/410] Refactoed GroupsFinder into two separate classes In the previous setup the GroupsFinder class had two distinct tasks: 1. Finding the projects user A could see 2. Finding the projects of user A that user B could see Task two was actually handled outside of the GroupsFinder (in the UsersController) by restricting the returned list of groups to those the viewed user was a member of. Moving all this logic into a single finder proved to be far too complex and confusing, hence there are now two finders: * GroupsFinder: for finding groups a user can see * JoinedGroupsFinder: for finding groups that user A is a member of, restricted to either public groups or groups user B can also see. --- app/finders/groups_finder.rb | 67 ++++++++++++----------- app/finders/joined_groups_finder.rb | 49 +++++++++++++++++ spec/finders/groups_finder_spec.rb | 51 ++++++++++++++--- spec/finders/joined_groups_finder_spec.rb | 49 +++++++++++++++++ 4 files changed, 176 insertions(+), 40 deletions(-) create mode 100644 app/finders/joined_groups_finder.rb create mode 100644 spec/finders/joined_groups_finder_spec.rb diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb index b5f3176461..91cb0f228f 100644 --- a/app/finders/groups_finder.rb +++ b/app/finders/groups_finder.rb @@ -1,39 +1,44 @@ class GroupsFinder - def execute(current_user, options = {}) - all_groups(current_user) + # Finds the groups available to the given user. + # + # current_user - The user to find the groups for. + # + # Returns an ActiveRecord::Relation. + def execute(current_user = nil) + if current_user + relation = groups_visible_to_user(current_user) + else + relation = public_groups + end + + relation.order_id_desc end private - def all_groups(current_user) - group_ids = if current_user - if current_user.authorized_groups.any? - # User has access to groups - # - # Return only: - # groups with public projects - # groups with internal projects - # groups with joined projects - # - Project.public_and_internal_only.pluck(:namespace_id) + - current_user.authorized_groups.pluck(:id) - else - # User has no group membership - # - # Return only: - # groups with public projects - # groups with internal projects - # - Project.public_and_internal_only.pluck(:namespace_id) - end - else - # Not authenticated - # - # Return only: - # groups with public projects - Project.public_only.pluck(:namespace_id) - end + # This method returns the groups "current_user" can see. + def groups_visible_to_user(current_user) + base = groups_for_projects(public_and_internal_projects) - Group.where("public IS TRUE OR id IN(?)", group_ids) + union = Gitlab::SQL::Union. + new([base.select(:id), current_user.authorized_groups.select(:id)]) + + Group.where("namespaces.id IN (#{union.to_sql})") + end + + def public_groups + groups_for_projects(public_projects) + end + + def groups_for_projects(projects) + Group.public_and_given_groups(projects.select(:namespace_id)) + end + + def public_projects + Project.unscoped.public_only + end + + def public_and_internal_projects + Project.unscoped.public_and_internal_only end end diff --git a/app/finders/joined_groups_finder.rb b/app/finders/joined_groups_finder.rb new file mode 100644 index 0000000000..e7523136fe --- /dev/null +++ b/app/finders/joined_groups_finder.rb @@ -0,0 +1,49 @@ +# Class for finding the groups a user is a member of. +class JoinedGroupsFinder + def initialize(user = nil) + @user = user + end + + # Finds the groups of the source user, optionally limited to those visible to + # the current user. + # + # current_user - If given the groups of "@user" will only include the groups + # "current_user" can also see. + # + # Returns an ActiveRecord::Relation. + def execute(current_user = nil) + if current_user + relation = groups_visible_to_user(current_user) + else + relation = public_groups + end + + relation.order_id_desc + end + + private + + # Returns the groups the user in "current_user" can see. + # + # This list includes all public/internal projects as well as the projects of + # "@user" that "current_user" also has access to. + def groups_visible_to_user(current_user) + base = @user.authorized_groups.visible_to_user(current_user) + extra = public_and_internal_groups + union = Gitlab::SQL::Union.new([base.select(:id), extra.select(:id)]) + + Group.where("namespaces.id IN (#{union.to_sql})") + end + + def public_groups + groups_for_projects(@user.authorized_projects.public_only) + end + + def public_and_internal_groups + groups_for_projects(@user.authorized_projects.public_and_internal_only) + end + + def groups_for_projects(projects) + @user.groups.public_and_given_groups(projects.select(:namespace_id)) + end +end diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb index 78dc027837..4f6a000822 100644 --- a/spec/finders/groups_finder_spec.rb +++ b/spec/finders/groups_finder_spec.rb @@ -1,15 +1,48 @@ require 'spec_helper' describe GroupsFinder do - let(:user) { create :user } - let!(:group) { create :group } - let!(:public_group) { create :group, public: true } - - describe :execute do - it 'finds public group' do - groups = GroupsFinder.new.execute(user) - expect(groups.size).to eq(1) - expect(groups.first).to eq(public_group) + describe '#execute' do + let(:user) { create(:user) } + + let(:group1) { create(:group) } + let(:group2) { create(:group) } + let(:group3) { create(:group) } + let(:group4) { create(:group, public: true) } + + let!(:public_project) { create(:project, :public, group: group1) } + let!(:internal_project) { create(:project, :internal, group: group2) } + let!(:private_project) { create(:project, :private, group: group3) } + + let(:finder) { described_class.new } + + describe 'with a user' do + subject { finder.execute(user) } + + describe 'when the user is not a member of any groups' do + it { is_expected.to eq([group4, group2, group1]) } + end + + describe 'when the user is a member of a group' do + before do + group3.add_user(user, Gitlab::Access::DEVELOPER) + end + + it { is_expected.to eq([group4, group3, group2, group1]) } + end + + describe 'when the user is a member of a private project' do + before do + private_project.team.add_user(user, Gitlab::Access::DEVELOPER) + end + + it { is_expected.to eq([group4, group3, group2, group1]) } + end + end + + describe 'without a user' do + subject { finder.execute } + + it { is_expected.to eq([group4, group1]) } end end end diff --git a/spec/finders/joined_groups_finder_spec.rb b/spec/finders/joined_groups_finder_spec.rb new file mode 100644 index 0000000000..2d9068cc72 --- /dev/null +++ b/spec/finders/joined_groups_finder_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe JoinedGroupsFinder do + describe '#execute' do + let(:source_user) { create(:user) } + let(:current_user) { create(:user) } + + let(:group1) { create(:group) } + let(:group2) { create(:group) } + let(:group3) { create(:group) } + let(:group4) { create(:group, public: true) } + + let!(:public_project) { create(:project, :public, group: group1) } + let!(:internal_project) { create(:project, :internal, group: group2) } + let!(:private_project) { create(:project, :private, group: group3) } + + let(:finder) { described_class.new(source_user) } + + before do + [group1, group2, group3, group4].each do |group| + group.add_user(source_user, Gitlab::Access::MASTER) + end + end + + describe 'with a current user' do + describe 'when the current user has access to the projects of the source user' do + before do + private_project.team.add_user(current_user, Gitlab::Access::DEVELOPER) + end + + subject { finder.execute(current_user) } + + it { is_expected.to eq([group4, group3, group2, group1]) } + end + + describe 'when the current user does not have access to the projects of the source user' do + subject { finder.execute(current_user) } + + it { is_expected.to eq([group4, group2, group1]) } + end + end + + describe 'without a current user' do + subject { finder.execute } + + it { is_expected.to eq([group4, group1]) } + end + end +end From fbcf3bd3fc94a39982e2bb11aa61ac014326e53a Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 12:21:06 +0100 Subject: [PATCH 156/410] Refactor ProjectsFinder to not pluck IDs This class now uses a UNION (when needed) instead of plucking tens of thousands of project IDs into memory. The tests have also been re-written to ensure all different use cases are tested properly (assuming I didn't forget any cases). The finder has also been broken up into 3 different finder classes: * ContributedProjectsFinder: class for getting the projects a user contributed to. * PersonalProjectsFinder: class for getting the personal projects of a user. * ProjectsFinder: class for getting generic projects visible to a given user. Previously a lot of the logic of these finders was handled directly in the users controller. --- app/finders/contributed_projects_finder.rb | 37 ++++++ app/finders/personal_projects_finder.rb | 37 ++++++ app/finders/projects_finder.rb | 121 ++++++++---------- .../contributed_projects_finder_spec.rb | 35 +++++ spec/finders/personal_projects_finder_spec.rb | 34 +++++ spec/finders/projects_finder_spec.rb | 75 ++++++----- 6 files changed, 237 insertions(+), 102 deletions(-) create mode 100644 app/finders/contributed_projects_finder.rb create mode 100644 app/finders/personal_projects_finder.rb create mode 100644 spec/finders/contributed_projects_finder_spec.rb create mode 100644 spec/finders/personal_projects_finder_spec.rb diff --git a/app/finders/contributed_projects_finder.rb b/app/finders/contributed_projects_finder.rb new file mode 100644 index 0000000000..0209649b01 --- /dev/null +++ b/app/finders/contributed_projects_finder.rb @@ -0,0 +1,37 @@ +class ContributedProjectsFinder + def initialize(user) + @user = user + end + + # Finds the projects "@user" contributed to, limited to either public projects + # or projects visible to the given user. + # + # current_user - When given the list of the projects is limited to those only + # visible by this user. + # + # Returns an ActiveRecord::Relation. + def execute(current_user = nil) + if current_user + relation = projects_visible_to_user(current_user) + else + relation = public_projects + end + + relation.includes(:namespace).order_id_desc + end + + private + + def projects_visible_to_user(current_user) + authorized = @user.contributed_projects.visible_to_user(current_user) + + union = Gitlab::SQL::Union. + new([authorized.select(:id), public_projects.select(:id)]) + + Project.where("projects.id IN (#{union.to_sql})") + end + + def public_projects + @user.contributed_projects.public_only + end +end diff --git a/app/finders/personal_projects_finder.rb b/app/finders/personal_projects_finder.rb new file mode 100644 index 0000000000..7c03957361 --- /dev/null +++ b/app/finders/personal_projects_finder.rb @@ -0,0 +1,37 @@ +class PersonalProjectsFinder + def initialize(user) + @user = user + end + + # Finds the projects belonging to the user in "@user", limited to either + # public projects or projects visible to the given user. + # + # current_user - When given the list of projects is limited to those only + # visible by this user. + # + # Returns an ActiveRecord::Relation. + def execute(current_user = nil) + if current_user + relation = projects_visible_to_user(current_user) + else + relation = public_projects + end + + relation.includes(:namespace).order_id_desc + end + + private + + def projects_visible_to_user(current_user) + authorized = @user.personal_projects.visible_to_user(current_user) + + union = Gitlab::SQL::Union. + new([authorized.select(:id), public_projects.select(:id)]) + + Project.where("projects.id IN (#{union.to_sql})") + end + + def public_projects + @user.personal_projects.public_only + end +end diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index c81bb51583..dd35c215c5 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -1,11 +1,39 @@ class ProjectsFinder - def execute(current_user, options = {}) + # Returns all projects, optionally including group projects a user has access + # to. + # + # ## Examples + # + # Retrieving all public projects: + # + # ProjectsFinder.new.execute + # + # Retrieving all public/internal projects and those the given user has access + # to: + # + # ProjectsFinder.new.execute(some_user) + # + # Retrieving all public/internal projects as well as the group's projects the + # user has access to: + # + # ProjectsFinder.new.execute(some_user, group: some_group) + # + # Returns an ActiveRecord::Relation. + def execute(current_user = nil, options = {}) group = options[:group] if group - group_projects(current_user, group) + base, extra = group_projects(current_user, group) else - all_projects(current_user) + base, extra = all_projects(current_user) + end + + if base and extra + union = Gitlab::SQL::Union.new([base.select(:id), extra.select(:id)]) + + Project.where("projects.id IN (#{union.to_sql})") + else + base end end @@ -13,77 +41,36 @@ class ProjectsFinder def group_projects(current_user, group) if current_user - if group.users.include?(current_user) - # User is group member - # - # Return ALL group projects - group.projects - else - projects_members = ProjectMember.in_projects(group.projects). - with_user(current_user) - - if projects_members.any? - # User is a project member - # - # Return only: - # public projects - # internal projects - # joined projects - # - group.projects.where( - "projects.id IN (?) OR projects.visibility_level IN (?)", - projects_members.pluck(:source_id), - Project.public_and_internal_levels - ) - else - # User has no access to group or group projects - # - # Return only: - # public projects - # internal projects - # - group.projects.public_and_internal_only - end - end + [ + group_projects_for_user(current_user, group), + group.projects.public_and_internal_only + ] else - # Not authenticated - # - # Return only: - # public projects - group.projects.public_only + [group.projects.public_only] end end def all_projects(current_user) if current_user - if current_user.authorized_projects.any? - # User has access to private projects - # - # Return only: - # public projects - # internal projects - # joined projects - # - Project.where( - "projects.id IN (?) OR projects.visibility_level IN (?)", - current_user.authorized_projects.pluck(:id), - Project.public_and_internal_levels - ) - else - # User has no access to private projects - # - # Return only: - # public projects - # internal projects - # - Project.public_and_internal_only - end + [current_user.authorized_projects, public_and_internal_projects] else - # Not authenticated - # - # Return only: - # public projects - Project.public_only + [Project.public_only] end end + + def group_projects_for_user(current_user, group) + if group.users.include?(current_user) + group.projects + else + group.projects.visible_to_user(current_user) + end + end + + def public_projects + Project.unscoped.public_only + end + + def public_and_internal_projects + Project.unscoped.public_and_internal_only + end end diff --git a/spec/finders/contributed_projects_finder_spec.rb b/spec/finders/contributed_projects_finder_spec.rb new file mode 100644 index 0000000000..8c1e46fe9f --- /dev/null +++ b/spec/finders/contributed_projects_finder_spec.rb @@ -0,0 +1,35 @@ +require 'spec_helper' + +describe ContributedProjectsFinder do + let(:source_user) { create(:user) } + let(:current_user) { create(:user) } + + let(:finder) { described_class.new(source_user) } + + let!(:public_project) { create(:project, :public) } + let!(:private_project) { create(:project, :private) } + + before do + private_project.team << [source_user, Gitlab::Access::MASTER] + private_project.team << [current_user, Gitlab::Access::DEVELOPER] + public_project.team << [source_user, Gitlab::Access::MASTER] + + create(:event, action: Event::PUSHED, project: public_project, + target: public_project, author: source_user) + + create(:event, action: Event::PUSHED, project: private_project, + target: private_project, author: source_user) + end + + describe 'without a current user' do + subject { finder.execute } + + it { is_expected.to eq([public_project]) } + end + + describe 'with a current user' do + subject { finder.execute(current_user) } + + it { is_expected.to eq([private_project, public_project]) } + end +end diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb new file mode 100644 index 0000000000..6bd4e6a3f3 --- /dev/null +++ b/spec/finders/personal_projects_finder_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe PersonalProjectsFinder do + let(:source_user) { create(:user) } + let(:current_user) { create(:user) } + + let(:finder) { described_class.new(source_user) } + + let!(:public_project) do + create(:project, :public, namespace: source_user.namespace, name: 'A', + path: 'A') + end + + let!(:private_project) do + create(:project, :private, namespace: source_user.namespace, name: 'B', + path: 'B') + end + + before do + private_project.team << [current_user, Gitlab::Access::DEVELOPER] + end + + describe 'without a current user' do + subject { finder.execute } + + it { is_expected.to eq([public_project]) } + end + + describe 'with a current user' do + subject { finder.execute(current_user) } + + it { is_expected.to eq([private_project, public_project]) } + end +end diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index de9d4cd612..d1dede78f7 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -1,51 +1,56 @@ require 'spec_helper' describe ProjectsFinder do - let(:user) { create :user } - let(:group) { create :group } + describe '#execute' do + let(:user) { create(:user) } - let(:project1) { create(:empty_project, :public, group: group) } - let(:project2) { create(:empty_project, :internal, group: group) } - let(:project3) { create(:empty_project, :private, group: group) } - let(:project4) { create(:empty_project, :private, group: group) } + let!(:private_project) { create(:project, :private) } + let!(:internal_project) { create(:project, :internal) } + let!(:public_project) { create(:project, :public) } - context 'non authenticated' do - subject { ProjectsFinder.new.execute(nil, group: group) } + let(:finder) { described_class.new } - it { is_expected.to include(project1) } - it { is_expected.not_to include(project2) } - it { is_expected.not_to include(project3) } - it { is_expected.not_to include(project4) } - end + describe 'without a group' do + describe 'without a user' do + subject { finder.execute } - context 'authenticated' do - subject { ProjectsFinder.new.execute(user, group: group) } + it { is_expected.to eq([public_project]) } + end - it { is_expected.to include(project1) } - it { is_expected.to include(project2) } - it { is_expected.not_to include(project3) } - it { is_expected.not_to include(project4) } - end + describe 'with a user' do + subject { finder.execute(user) } - context 'authenticated, project member' do - before { project3.team << [user, :developer] } + describe 'without private projects' do + it { is_expected.to eq([public_project, internal_project]) } + end - subject { ProjectsFinder.new.execute(user, group: group) } + describe 'with private projects' do + before do + private_project.team.add_user(user, Gitlab::Access::MASTER) + end - it { is_expected.to include(project1) } - it { is_expected.to include(project2) } - it { is_expected.to include(project3) } - it { is_expected.not_to include(project4) } - end + it do + is_expected.to eq([public_project, internal_project, + private_project]) + end + end + end + end - context 'authenticated, group member' do - before { group.add_developer(user) } + describe 'with a group' do + let(:group) { public_project.group } - subject { ProjectsFinder.new.execute(user, group: group) } + describe 'without a user' do + subject { finder.execute(nil, group: group) } - it { is_expected.to include(project1) } - it { is_expected.to include(project2) } - it { is_expected.to include(project3) } - it { is_expected.to include(project4) } + it { is_expected.to eq([public_project]) } + end + + describe 'with a user' do + subject { finder.execute(user, group: group) } + + it { is_expected.to eq([public_project, internal_project]) } + end + end end end From 01620dd7e7f3015e31ac0288ef71fcfc4f268a14 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 12:25:37 +0100 Subject: [PATCH 157/410] Added Event.limit_recent This will be used to move some querying logic from the users controller to the Event model (where it belongs). --- app/models/event.rb | 4 ++++ spec/models/event_spec.rb | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/app/models/event.rb b/app/models/event.rb index 7618b503d8..9afd223bce 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -69,6 +69,10 @@ class Event < ActiveRecord::Base row ? row.updated_at : nil end + + def limit_recent(limit = 20, offset = nil) + recent.limit(limit).offset(offset) + end end def proper? diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index f46c50ed6f..ae53f7a536 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -85,4 +85,21 @@ describe Event do end end end + + describe '.limit_recent' do + let!(:event1) { create(:closed_issue_event) } + let!(:event2) { create(:closed_issue_event) } + + describe 'without an explicit limit' do + subject { Event.limit_recent } + + it { is_expected.to eq([event2, event1]) } + end + + describe 'with an explicit limit' do + subject { Event.limit_recent(1) } + + it { is_expected.to eq([event2]) } + end + end end From a74d6d204366c862657a545d999cb33dfde300dd Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 12:27:21 +0100 Subject: [PATCH 158/410] Group methods for filtering public/visible groups These methods will be used to get a list of groups, optionally restricted to only those visible to a given user. --- app/models/group.rb | 8 ++++++++ spec/models/group_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/app/models/group.rb b/app/models/group.rb index 2c9e75496b..1b5b875a19 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -49,6 +49,14 @@ class Group < Namespace def reference_pattern User.reference_pattern end + + def public_and_given_groups(ids) + where('public IS TRUE OR namespaces.id IN (?)', ids) + end + + def visible_to_user(user) + where(id: user.authorized_groups.select(:id).reorder(nil)) + end end def to_reference(_from_project = nil) diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index bbfc5535ee..6f166b5ab7 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -38,6 +38,33 @@ describe Group do it { is_expected.not_to validate_presence_of :owner } end + describe '.public_and_given_groups' do + let!(:public_group) { create(:group, public: true) } + + subject { described_class.public_and_given_groups([group.id]) } + + it { is_expected.to eq([public_group, group]) } + end + + describe '.visible_to_user' do + let!(:group) { create(:group) } + let!(:user) { create(:user) } + + subject { described_class.visible_to_user(user) } + + describe 'when the user has access to a group' do + before do + group.add_user(user, Gitlab::Access::MASTER) + end + + it { is_expected.to eq([group]) } + end + + describe 'when the user does not have access to any groups' do + it { is_expected.to eq([]) } + end + end + describe '#to_reference' do it 'returns a String reference to the object' do expect(group.to_reference).to eq "@#{group.name}" From a4fc8112df3cf6cb344cfba65f5df46c7a99bef7 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 12:29:45 +0100 Subject: [PATCH 159/410] Added Project.visible_to_user This method can be used to filter projects to those visible to a given user. --- app/models/project.rb | 4 ++++ spec/models/project_spec.rb | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/app/models/project.rb b/app/models/project.rb index a099a67cf6..750df0f1ae 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -286,6 +286,10 @@ class Project < ActiveRecord::Base joins(join_body).reorder('join_note_counts.amount DESC') end + + def visible_to_user(user) + where(id: user.authorized_projects.select(:id).reorder(nil)) + end end def team diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8d7e6e7676..c42e8870f8 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -464,4 +464,23 @@ describe Project do end end end + + describe '.visible_to_user' do + let!(:project) { create(:project, :private) } + let!(:user) { create(:user) } + + subject { described_class.visible_to_user(user) } + + describe 'when a user has access to a project' do + before do + project.team.add_user(user, Gitlab::Access::MASTER) + end + + it { is_expected.to eq([project]) } + end + + describe 'when a user does not have access to any projects' do + it { is_expected.to eq([]) } + end + end end From e116a356b8ac07bd3a935c40ceb274d67d808c83 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 12:30:24 +0100 Subject: [PATCH 160/410] Refactor User#authorized_groups/projects These methods no longer include public groups/projects (that don't belong to the actual user) as this is handled by the various finder classes now. This also removes the need for passing extra arguments. Note that memoizing was removed _explicitly_. For whatever reason doing so messes up the users controller to a point where it claims a certain user does _not_ have access to certain groups/projects when it does have access. Existing code shouldn't be affected as these methods are only called in ways that they'd run queries anyway (e.g. a combination of "any?" and "each" which would run 2 queries regardless of memoizing). --- app/models/user.rb | 35 ++++++----------------------------- spec/models/user_spec.rb | 26 ++++---------------------- 2 files changed, 10 insertions(+), 51 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index d523b3f049..20a2457eec 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -389,40 +389,17 @@ class User < ActiveRecord::Base end end - # Returns the groups a user has access to, optionally including any public - # groups. - # - # public_internal - When set to "true" all public groups and groups of public - # projects are also included. - # - # Returns an ActiveRecord::Relation - def authorized_groups(public_internal = false) + # Returns the groups a user has access to + def authorized_groups union = Gitlab::SQL::Union. - new([groups.select(:id), authorized_projects(public_internal). - select(:namespace_id)]) + new([groups.select(:id), authorized_projects.select(:namespace_id)]) - sql = "namespaces.id IN (#{union.to_sql})" - - if public_internal - sql << ' OR public IS TRUE' - end - - Group.where(sql) + Group.where("namespaces.id IN (#{union.to_sql})") end # Returns the groups a user is authorized to access. - # - # public_internal - When set to "true" all public/internal projects will also - # be included. - def authorized_projects(public_internal = false) - base = "projects.id IN (#{projects_union.to_sql})" - - if public_internal - Project.where("#{base} OR projects.visibility_level IN (?)", - Project.public_and_internal_levels) - else - Project.where(base) - end + def authorized_projects + Project.where("projects.id IN (#{projects_union.to_sql})") end def owned_projects diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 71160f8dfe..4631b12faf 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -762,44 +762,26 @@ describe User do describe '#authorized_groups' do let!(:user) { create(:user) } let!(:private_group) { create(:group) } - let!(:public_group) { create(:group, public: true) } before do private_group.add_user(user, Gitlab::Access::MASTER) end - describe 'excluding public groups' do - subject { user.authorized_groups } + subject { user.authorized_groups } - it { is_expected.to eq([private_group]) } - end - - describe 'including public groups' do - subject { user.authorized_groups(true) } - - it { is_expected.to eq([public_group, private_group]) } - end + it { is_expected.to eq([private_group]) } end describe '#authorized_projects' do let!(:user) { create(:user) } let!(:private_project) { create(:project, :private) } - let!(:public_project) { create(:project, :public) } before do private_project.team << [user, Gitlab::Access::MASTER] end - describe 'excluding public projects' do - subject { user.authorized_projects } + subject { user.authorized_projects } - it { is_expected.to eq([private_project]) } - end - - describe 'including public projects' do - subject { user.authorized_projects(true) } - - it { is_expected.to eq([public_project, private_project]) } - end + it { is_expected.to eq([private_project]) } end end From fbdf3767495cd60b002f24ab4e9aa4d0c019de95 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 12:32:35 +0100 Subject: [PATCH 161/410] Refactor UsersController to not kill the database Previously this controller would in multiple places load tons (read: around 65000) project and/or group IDs into memory. These changes in combination with the previous commits significantly cut down loading times of user profile pages and the Atom feeds of users. --- app/controllers/users_controller.rb | 29 +++++++++-------------- spec/controllers/users_controller_spec.rb | 23 ++++++++++++++---- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 1484356a7f..30cb869eb2 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -3,14 +3,11 @@ class UsersController < ApplicationController before_action :set_user def show - @contributed_projects = contributed_projects.joined(@user). - reject(&:forked?) + @contributed_projects = contributed_projects.joined(@user).reject(&:forked?) - @projects = @user.personal_projects. - where(id: authorized_projects_ids).includes(:namespace) + @projects = PersonalProjectsFinder.new(@user).execute(current_user) - # Collect only groups common for both users - @groups = @user.groups & GroupsFinder.new.execute(current_user) + @groups = JoinedGroupsFinder.new(@user).execute(current_user) respond_to do |format| format.html @@ -53,16 +50,8 @@ class UsersController < ApplicationController @user = User.find_by_username!(params[:username]) end - def authorized_projects_ids - # Projects user can view - @authorized_projects_ids ||= - ProjectsFinder.new.execute(current_user).pluck(:id) - end - def contributed_projects - @contributed_projects = Project. - where(id: authorized_projects_ids & @user.contributed_projects_ids). - includes(:namespace) + ContributedProjectsFinder.new(@user).execute(current_user) end def contributions_calendar @@ -73,9 +62,13 @@ class UsersController < ApplicationController def load_events # Get user activity feed for projects common for both users @events = @user.recent_events. - where(project_id: authorized_projects_ids). - with_associations + merge(projects_for_current_user). + references(:project). + with_associations. + limit_recent(20, params[:offset]) + end - @events = @events.limit(20).offset(params[:offset] || 0) + def projects_for_current_user + ProjectsFinder.new.execute(current_user) end end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 9f89101d7f..104a5f5014 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -16,13 +16,26 @@ describe UsersController do context 'with rendered views' do render_views - it 'renders the show template' do - sign_in(user) + describe 'when logged in' do + before do + sign_in(user) + end - get :show, username: user.username + it 'renders the show template' do + get :show, username: user.username - expect(response).to be_success - expect(response).to render_template('show') + expect(response).to be_success + expect(response).to render_template('show') + end + end + + describe 'when logged out' do + it 'renders the show template' do + get :show, username: user.username + + expect(response).to be_success + expect(response).to render_template('show') + end end end end From 73f302edf98d6c292c048c913f76282058d6d81c Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 13:02:03 +0100 Subject: [PATCH 162/410] Apply CI scope changes to the User model These changes are based on those from commit 03f5ff750b107b30a6d306aafb6699a9c9ecff0d, except they use a UNION instead of plucking IDs into memory. --- app/models/user.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 20a2457eec..47439ce4b0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -770,15 +770,10 @@ class User < ActiveRecord::Base !solo_owned_groups.present? end - def ci_authorized_projects - @ci_authorized_projects ||= - Ci::Project.where("gitlab_id IN (#{projects_union.to_sql})") - end - def ci_authorized_runners @ci_authorized_runners ||= begin runner_ids = Ci::RunnerProject.joins(:project). - where("ci_projects.gitlab_id IN (#{projects_union.to_sql})"). + where("ci_projects.gitlab_id IN (#{ci_projects_union.to_sql})"). select(:runner_id) Ci::Runner.specific.where(id: runner_ids) @@ -792,4 +787,13 @@ class User < ActiveRecord::Base groups_projects.select(:id), projects.select(:id)]) end + + def ci_projects_union + scope = { access_level: [Gitlab::Access::MASTER, Gitlab::Access::OWNER] } + groups = groups_projects.where(members: scope) + other = projects.where(members: scope) + + Gitlab::SQL::Union.new([personal_projects.select(:id), groups.select(:id), + other.select(:id)]) + end end From 26482bddb091a085e2368ff20c3e3e797da74ea3 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 13:12:44 +0100 Subject: [PATCH 163/410] Don't pluck project IDs in User#owned_projects This won't work efficiently if you happen to have a lot of projects. --- app/models/user.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/models/user.rb b/app/models/user.rb index 47439ce4b0..9d75bb3aeb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -404,10 +404,8 @@ class User < ActiveRecord::Base def owned_projects @owned_projects ||= - begin - namespace_ids = owned_groups.pluck(:id).push(namespace.id) - Project.in_namespace(namespace_ids).joins(:namespace) - end + Project.where('namespace_id IN (?) OR namespace_id = ?', + owned_groups.select(:id), namespace.id).joins(:namespace) end # Team membership in authorized projects From d9c4625c6d5264756f6ca43f7a8f5eb984d753bc Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 18 Nov 2015 13:16:07 +0100 Subject: [PATCH 164/410] Specs that failed before the fix. --- spec/tasks/gitlab/backup_rake_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 06559c3925..fb5e74af64 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -149,7 +149,7 @@ describe 'gitlab:app namespace rake task' do # Redirect STDOUT and run the rake task orig_stdout = $stdout $stdout = StringIO.new - ENV["SKIP"] = "repositories" + ENV["SKIP"] = "repositories,uploads" run_rake_task('gitlab:backup:create') $stdout = orig_stdout @@ -180,6 +180,7 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task["gitlab:backup:db:restore"]).to receive :invoke expect(Rake::Task["gitlab:backup:repo:restore"]).not_to receive :invoke + expect(Rake::Task["gitlab:backup:uploads:restore"]).not_to receive :invoke expect(Rake::Task["gitlab:backup:builds:restore"]).to receive :invoke expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive :invoke expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke From cc11c44ba997eb32dfa48ea225ae4c6942bc8610 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 13:27:03 +0100 Subject: [PATCH 165/410] Align hash literals to keep Rubocop happy --- spec/finders/contributed_projects_finder_spec.rb | 4 ++-- spec/finders/personal_projects_finder_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/finders/contributed_projects_finder_spec.rb b/spec/finders/contributed_projects_finder_spec.rb index 8c1e46fe9f..65d7f14c72 100644 --- a/spec/finders/contributed_projects_finder_spec.rb +++ b/spec/finders/contributed_projects_finder_spec.rb @@ -15,10 +15,10 @@ describe ContributedProjectsFinder do public_project.team << [source_user, Gitlab::Access::MASTER] create(:event, action: Event::PUSHED, project: public_project, - target: public_project, author: source_user) + target: public_project, author: source_user) create(:event, action: Event::PUSHED, project: private_project, - target: private_project, author: source_user) + target: private_project, author: source_user) end describe 'without a current user' do diff --git a/spec/finders/personal_projects_finder_spec.rb b/spec/finders/personal_projects_finder_spec.rb index 6bd4e6a3f3..38817add45 100644 --- a/spec/finders/personal_projects_finder_spec.rb +++ b/spec/finders/personal_projects_finder_spec.rb @@ -8,12 +8,12 @@ describe PersonalProjectsFinder do let!(:public_project) do create(:project, :public, namespace: source_user.namespace, name: 'A', - path: 'A') + path: 'A') end let!(:private_project) do create(:project, :private, namespace: source_user.namespace, name: 'B', - path: 'B') + path: 'B') end before do From 9eefae69171ba199d34bccf504902500a980fcb3 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 13:31:18 +0100 Subject: [PATCH 166/410] Fix UNION syntax for MySQL Apparently MySQL doesn't support this syntax: (...) UNION (...) instead it only supports: ... UNION ... --- lib/gitlab/sql/union.rb | 4 ++-- spec/lib/gitlab/sql/union_spec.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/sql/union.rb b/lib/gitlab/sql/union.rb index 1a62eff0b3..1cd89b3a9c 100644 --- a/lib/gitlab/sql/union.rb +++ b/lib/gitlab/sql/union.rb @@ -23,11 +23,11 @@ module Gitlab # (thus fixing this problem), at a slight performance cost. fragments = ActiveRecord::Base.connection.unprepared_statement do @relations.map do |rel| - "(#{rel.reorder(nil).to_sql})" + rel.reorder(nil).to_sql end end - fragments.join(' UNION ') + fragments.join("\nUNION\n") end end end diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb index 976360af9b..9e1cd4419e 100644 --- a/spec/lib/gitlab/sql/union_spec.rb +++ b/spec/lib/gitlab/sql/union_spec.rb @@ -10,7 +10,7 @@ describe Gitlab::SQL::Union do sql1 = rel1.reorder(nil).to_sql sql2 = rel2.reorder(nil).to_sql - expect(union.to_sql).to eq("(#{sql1}) UNION (#{sql2})") + expect(union.to_sql).to eq("#{sql1}\nUNION\n#{sql2}") end end end From f486b06c4de26e7eb6468cfc4f864b50e645d5c7 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 15:08:28 +0100 Subject: [PATCH 167/410] Return internal projects in PersonalProjectsFinder When getting the projects of a user we should get the public _and_ internal projects, not just the public ones. --- app/finders/personal_projects_finder.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/finders/personal_projects_finder.rb b/app/finders/personal_projects_finder.rb index 7c03957361..a61ffa2299 100644 --- a/app/finders/personal_projects_finder.rb +++ b/app/finders/personal_projects_finder.rb @@ -26,7 +26,7 @@ class PersonalProjectsFinder authorized = @user.personal_projects.visible_to_user(current_user) union = Gitlab::SQL::Union. - new([authorized.select(:id), public_projects.select(:id)]) + new([authorized.select(:id), public_and_internal_projects.select(:id)]) Project.where("projects.id IN (#{union.to_sql})") end @@ -34,4 +34,8 @@ class PersonalProjectsFinder def public_projects @user.personal_projects.public_only end + + def public_and_internal_projects + @user.personal_projects.public_and_internal_only + end end From e6c6c2234b7d82211928cc2549e5c53f60330d05 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 18 Nov 2015 15:15:47 +0100 Subject: [PATCH 168/410] Fix huge line height for diff files list Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/commit.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index fbd7c363de..a0e5f7554e 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -56,6 +56,7 @@ li { padding: 3px 0px; + line-height: 20px; } } .new-file { From cab6efa53fe8c2d9d44d6762fd71f97bd8d4e3d4 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Wed, 18 Nov 2015 09:53:08 -0600 Subject: [PATCH 169/410] Deploy page should be shown for all pages not just root --- lib/support/nginx/gitlab | 2 +- lib/support/nginx/gitlab-ssl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 93f2ad07ae..acc547afcc 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -67,7 +67,7 @@ server { location / { ## Serve static files from defined root folder. ## @gitlab is a named location for the upstream fallback, see below. - try_files $uri $uri/index.html $uri.html @gitlab; + try_files $uri /index.html $uri.html @gitlab; } ## We route uploads through GitLab to prevent XSS and enforce access control. diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 90749947fa..a8f8d48eb1 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -112,7 +112,7 @@ server { location / { ## Serve static files from defined root folder. ## @gitlab is a named location for the upstream fallback, see below. - try_files $uri $uri/index.html $uri.html @gitlab; + try_files $uri /index.html $uri.html @gitlab; } ## We route uploads through GitLab to prevent XSS and enforce access control. From f3cfd20952411dc7302c78933346a9a11d8e58af Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 18 Nov 2015 17:10:06 +0100 Subject: [PATCH 170/410] DRY up code --- app/controllers/projects/blob_controller.rb | 54 +++++++++------------ 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 41ec7bde45..31a33bfd23 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -23,21 +23,9 @@ class Projects::BlobController < Projects::ApplicationController end def create - result = Files::CreateService.new(@project, current_user, @commit_params).execute - - if result[:status] == :success - flash[:notice] = "The changes have been successfully committed" - respond_to do |format| - format.html { redirect_to after_create_path } - format.json { render json: { message: "success", filePath: after_create_path } } - end - else - flash[:alert] = result[:message] - respond_to do |format| - format.html { render :new } - format.json { render json: { message: "failed", filePath: namespace_project_blob_path(@project.namespace, @project, @id) } } - end - end + create_commit(Files::CreateService, success_path: after_create_path, + failure_view: :new, + failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref)) end def show @@ -48,21 +36,9 @@ class Projects::BlobController < Projects::ApplicationController end def update - result = Files::UpdateService.new(@project, current_user, @commit_params).execute - - if result[:status] == :success - flash[:notice] = "Your changes have been successfully committed" - respond_to do |format| - format.html { redirect_to after_edit_path } - format.json { render json: { message: "success", filePath: after_edit_path } } - end - else - flash[:alert] = result[:message] - respond_to do |format| - format.html { render :edit } - format.json { render json: { message: "failed", filePath: namespace_project_new_blob_path(@project.namespace, @project, @id) } } - end - end + create_commit(Files::UpdateService, success_path: after_edit_path, + failure_view: :edit, + failure_path: namespace_project_blob_path(@project.namespace, @project, @id)) end def preview @@ -132,6 +108,24 @@ class Projects::BlobController < Projects::ApplicationController render_404 end + def create_commit(service, success_path:, failure_view:, failure_path:) + result = service.new(@project, current_user, @commit_params).execute + + if result[:status] == :success + flash[:notice] = "Your changes have been successfully committed" + respond_to do |format| + format.html { redirect_to success_path } + format.json { render json: { message: "success", filePath: success_path } } + end + else + flash[:alert] = result[:message] + respond_to do |format| + format.html { render failure_view } + format.json { render json: { message: "failed", filePath: failure_path } } + end + end + end + def after_create_path @after_create_path ||= if create_merge_request? From ee2739e60682153895ffc4ec274d22acb509c3b3 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 17:24:51 +0100 Subject: [PATCH 171/410] Added an index on namespaces.public --- db/migrate/20151118162244_add_projects_public_index.rb | 5 +++++ db/schema.rb | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20151118162244_add_projects_public_index.rb diff --git a/db/migrate/20151118162244_add_projects_public_index.rb b/db/migrate/20151118162244_add_projects_public_index.rb new file mode 100644 index 0000000000..fded70e3c0 --- /dev/null +++ b/db/migrate/20151118162244_add_projects_public_index.rb @@ -0,0 +1,5 @@ +class AddProjectsPublicIndex < ActiveRecord::Migration + def change + add_index :namespaces, :public + end +end diff --git a/db/schema.rb b/db/schema.rb index aa76cef9fe..7f95b4cbdb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20151116144118) do +ActiveRecord::Schema.define(version: 20151118162244) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -538,6 +538,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree + add_index "namespaces", ["public"], name: "index_namespaces_on_public", using: :btree add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree create_table "notes", force: true do |t| From bf3c0aafaa17c2e322d225180fd9708ec2827ac4 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 18 Nov 2015 18:14:13 +0100 Subject: [PATCH 172/410] USe reject. --- lib/backup/manager.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 69922eb66e..e7eda7c6f4 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -158,7 +158,7 @@ module Backup end def folders_to_backup - %w{repositories db}.map{ |name| name unless skipped?(name) }.compact + %w{repositories db}.reject{ |name| skipped?(name) } end def settings From efd5d93745cede13c3a720fe17388b146a8998ed Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 18 Nov 2015 20:20:55 +0100 Subject: [PATCH 173/410] Use "GitLab.com" instead of "gitlab.com" --- app/models/user.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/user.rb b/app/models/user.rb index 9d75bb3aeb..9374f01f99 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -729,7 +729,7 @@ class User < ActiveRecord::Base # some_user.contributed_projects.visible_to_user(other_user) # # If this method were to use a JOIN the resulting query would take roughly 200 - # ms on a database with a similar size to gitlab.com's database. On the other + # ms on a database with a similar size to GitLab.com's database. On the other # hand, using a subquery means we can get the exact same data in about 40 ms. def contributed_projects events = Event.select(:project_id). From fd2c0fe446c7f761b845c91307ef8110d869e8e8 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 11 Nov 2015 15:12:51 +0200 Subject: [PATCH 174/410] award emoji --- app/assets/javascripts/awards_handler.coffee | 55 +++++ app/assets/javascripts/notes.js.coffee | 9 +- app/assets/stylesheets/pages/issuable.scss | 42 ++++ app/controllers/projects/issues_controller.rb | 2 +- .../projects/merge_requests_controller.rb | 2 +- app/controllers/projects/notes_controller.rb | 25 ++- app/helpers/issues_helper.rb | 5 + app/models/concerns/issuable.rb | 49 ----- app/models/note.rb | 46 +---- app/services/notes/create_service.rb | 17 +- app/services/notification_service.rb | 1 + app/views/projects/issues/_issue.html.haml | 2 - .../merge_requests/_merge_request.html.haml | 2 - app/views/projects/notes/_note.html.haml | 20 -- app/views/votes/_votes_block.html.haml | 43 +++- app/views/votes/_votes_inline.html.haml | 9 - config/routes.rb | 4 + .../20151106000015_add_is_award_to_notes.rb | 5 + db/schema.rb | 3 +- doc/api/merge_requests.md | 12 -- doc/api/notes.md | 8 +- lib/api/entities.rb | 4 +- spec/lib/votes_spec.rb | 188 ------------------ spec/models/project_spec.rb | 11 - 24 files changed, 203 insertions(+), 361 deletions(-) create mode 100644 app/assets/javascripts/awards_handler.coffee delete mode 100644 app/views/votes/_votes_inline.html.haml create mode 100644 db/migrate/20151106000015_add_is_award_to_notes.rb delete mode 100644 spec/lib/votes_spec.rb diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee new file mode 100644 index 0000000000..aab3179f10 --- /dev/null +++ b/app/assets/javascripts/awards_handler.coffee @@ -0,0 +1,55 @@ +class @AwardsHandler + constructor: (@post_emoji_url, @noteable_type, @noteable_id) -> + + addAward: (emoji) -> + @postEmoji emoji, => + if @exist(emoji) + if @isActive(emoji) + @decrementCounter(emoji) + else + counter = $(".icon." + emoji).siblings(".counter") + counter.text(parseInt(counter.text()) + 1) + counter.parent().addClass("active") + else + @createEmoji(emoji) + + + exist: (emoji) -> + $(".icon").hasClass(emoji) + + isActive: (emoji) -> + $(".icon." + emoji).parent().hasClass("active") + + decrementCounter: (emoji) -> + counter = $(".icon." + emoji).siblings(".counter") + + if parseInt(counter.text()) > 1 + counter.text(parseInt(counter.text()) - 1) + counter.parent().removeClass("active") + else + counter.parent().remove() + + + createEmoji: (emoji) -> + nodes = [] + nodes.push("

") + nodes.push("
") + nodes.push(@getImage(emoji)) + nodes.push("
") + nodes.push("
1") + nodes.push("
") + + $(".awards").append(nodes.join("\n")) + + getImage: (emoji) -> + $("li." + emoji).html() + + postEmoji: (emoji, callback) -> + emoji = emoji.replace("emoji-", "") + $.post @post_emoji_url, { + emoji: emoji + noteable_type: @noteable_type + noteable_id: @noteable_id + },(data) -> + if data.ok + callback.call() \ No newline at end of file diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index ea75c656bc..cd27b20dd7 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -113,13 +113,16 @@ class @Notes renderNote: (note) -> # render note if it not present in loaded list # or skip if rendered - if @isNewNote(note) + if @isNewNote(note) && !note.award @note_ids.push(note.id) $('ul.main-notes-list'). append(note.html). syntaxHighlight() @initTaskList() + if note.award + awards_handler.addAward("emoji-" + note.note) + ### Check if note does not exists on page ### @@ -255,7 +258,6 @@ class @Notes ### addNote: (xhr, note, status) => @renderNote(note) - @updateVotes() ### Called in response to the new note form being submitted @@ -473,9 +475,6 @@ class @Notes form = $(e.target).closest(".js-discussion-note-form") @removeDiscussionNoteForm(form) - updateVotes: -> - true - ### Called after an attachment file has been selected. diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index abc27a19e3..efeb239316 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -101,3 +101,45 @@ background-color: $background-color; } } + +.awards { + .award { + border: 1px solid; + padding: 1px 3px; + width: 50px; + border-radius: 5px; + float:left; + margin: 0 3px; + border-color: #ccc; + cursor: pointer; + + &.active { + border-color: rgba(79,176,252,.4); + background-color: rgba(79,176,252,.08); + + .counter { + font-weight: bold; + } + } + + .icon { + float: left; + margin-right: 10px; + } + } + + #add-award { + font-size: 20px; + border-radius: 5px; + float: left; + width: 50px; + font-weight: bold; + } + + .awards-menu{ + li { + float: left; + margin: 3px; + } + } +} diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index e74c2905e4..5250a0f5e6 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -60,7 +60,7 @@ class Projects::IssuesController < Projects::ApplicationController def show @participants = @issue.participants(current_user) @note = @project.notes.new(noteable: @issue) - @notes = @issue.notes.with_associations.fresh + @notes = @issue.notes.nonawards.with_associations.fresh @noteable = @issue respond_with(@issue) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 188f0cc4ce..a0468c65d5 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -254,7 +254,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController # Build a note object for comment form @note = @project.notes.new(noteable: @merge_request) - @notes = @merge_request.mr_and_commit_notes.inc_author.fresh + @notes = @merge_request.nonawards.mr_and_commit_notes.inc_author.fresh @discussions = Note.discussions_from_notes(@notes) @noteable = @merge_request diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 41cd08c93c..357b292980 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController before_action :authorize_read_note! before_action :authorize_create_note!, only: [:create] before_action :authorize_admin_note!, only: [:update, :destroy] - before_action :find_current_user_notes, except: [:destroy, :delete_attachment] + before_action :find_current_user_notes, except: [:destroy, :delete_attachment, :award_toggle]] def index current_fetched_at = Time.now.to_i @@ -58,6 +58,27 @@ class Projects::NotesController < Projects::ApplicationController end end + def award_toggle + noteable = params[:noteable_type] == "Issue" ? Issue : MergeRequest + noteable = noteable.find(params[:noteable_id]) + data = { + noteable: noteable, + author: current_user, + is_award: true, + note: params[:emoji] + } + + note = project.notes.find_by(data) + + if note + note.destroy + else + project.notes.create(data) + end + + render json: {ok: true} + end + private def note @@ -111,6 +132,8 @@ class Projects::NotesController < Projects::ApplicationController id: note.id, discussion_id: note.discussion_id, html: note_to_html(note), + award: note.is_award, + note: note.note, discussion_html: note_to_discussion_html(note), discussion_with_diff_html: note_to_discussion_with_diff_html(note) } diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index beb083d82d..ff3e091195 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -87,6 +87,11 @@ module IssuesHelper merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ') end + def url_to_emoji(name) + emoji_path = "emoji/#{Emoji.emoji_filename(name)}.png" + url_to_image(emoji_path) + end + # Required for Gitlab::Markdown::IssueReferenceFilter module_function :url_for_issue end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 492a026add..91da6797df 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -89,41 +89,6 @@ module Issuable opened? || reopened? end - # - # Votes - # - - # Return the number of -1 comments (downvotes) - def downvotes - filter_superceded_votes(notes.select(&:downvote?), notes).size - end - - def downvotes_in_percent - if votes_count.zero? - 0 - else - 100.0 - upvotes_in_percent - end - end - - # Return the number of +1 comments (upvotes) - def upvotes - filter_superceded_votes(notes.select(&:upvote?), notes).size - end - - def upvotes_in_percent - if votes_count.zero? - 0 - else - 100.0 / votes_count * upvotes - end - end - - # Return the total number of votes - def votes_count - upvotes + downvotes - end - def subscribed?(user) subscription = subscriptions.find_by_user_id(user.id) @@ -183,18 +148,4 @@ module Issuable def notes_with_associations notes.includes(:author, :project) end - - private - - def filter_superceded_votes(votes, notes) - filteredvotes = [] + votes - - votes.each do |vote| - if vote.superceded?(notes) - filteredvotes.delete(vote) - end - end - - filteredvotes - end end diff --git a/app/models/note.rb b/app/models/note.rb index 0b3aa30abd..458d433211 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -50,6 +50,8 @@ class Note < ActiveRecord::Base mount_uploader :attachment, AttachmentUploader # Scopes + scope :awards, ->{ where("is_award IS TRUE") } + scope :nonawards, ->{ where("is_award IS FALSE") } scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) } scope :inline, ->{ where("line_code IS NOT NULL") } scope :not_inline, ->{ where(line_code: [nil, '']) } @@ -97,6 +99,12 @@ class Note < ActiveRecord::Base def search(query) where("LOWER(note) like :query", query: "%#{query.downcase}%") end + + def grouped_awards + select(:note).distinct.map do |note| + [ note.note, where(note: note.note) ] + end + end end def cross_reference? @@ -288,44 +296,6 @@ class Note < ActiveRecord::Base nil end - DOWNVOTES = %w(-1 :-1: :thumbsdown: :thumbs_down_sign:) - - # Check if the note is a downvote - def downvote? - votable? && note.start_with?(*DOWNVOTES) - end - - UPVOTES = %w(+1 :+1: :thumbsup: :thumbs_up_sign:) - - # Check if the note is an upvote - def upvote? - votable? && note.start_with?(*UPVOTES) - end - - def superceded?(notes) - return false unless vote? - - notes.each do |note| - next if note == self - - if note.vote? && - self[:author_id] == note[:author_id] && - self[:created_at] <= note[:created_at] - return true - end - end - - false - end - - def vote? - upvote? || downvote? - end - - def votable? - for_issue? || (for_merge_request? && !for_diff_line?) - end - # Mentionable override. def gfm_reference(from_project = nil) noteable.gfm_reference(from_project) diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 2001dc89c3..f448f61cc8 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -5,11 +5,16 @@ module Notes note.author = current_user note.system = false + if contains_emoji_only?(params[:note]) + note.is_award = true + note.note = emoji_name(params[:note]) + end + if note.save notification_service.new_note(note) - # Skip system notes, like status changes and cross-references. - unless note.system + # Skip system notes, like status changes and cross-references and awards + unless note.system || note.is_award event_service.leave_note(note, note.author) note.create_cross_references! execute_hooks(note) @@ -28,5 +33,13 @@ module Notes note.project.execute_hooks(note_data, :note_hooks) note.project.execute_services(note_data, :note_hooks) end + + def contains_emoji_only?(note) + note =~ /^:[-_+[:alnum:]]*:\s?/ + end + + def emoji_name(note) + note.match(/\A:([-_+[:alnum:]]*):\s?/)[1] + end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index bbfe755f44..d6550fbb55 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -102,6 +102,7 @@ class NotificationService # ignore gitlab service messages return true if note.note.start_with?('Status changed to closed') return true if note.cross_reference? && note.system == true + return true if note.is_award target = note.noteable diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 55ce912829..d7657ee7e4 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -29,8 +29,6 @@ .issue-info = "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe - - if issue.votes_count > 0 - = render 'votes/votes_inline', votable: issue - if issue.milestone   %span diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index c5234c0618..83e8ad1198 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -34,8 +34,6 @@ .merge-request-info = "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe - - if merge_request.votes_count > 0 - = render 'votes/votes_inline', votable: merge_request - if merge_request.milestone_id?   %span diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index efa7dd01cc..dd0abc8c74 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -35,26 +35,6 @@ - if note.updated_by && note.updated_by != note.author by #{link_to_member(note.project, note.updated_by, avatar: false, author_class: nil)} - - if note.superceded?(@notes) - - if note.upvote? - %span.vote.upvote.label.label-gray.strikethrough - = icon('thumbs-up') - \+1 - - if note.downvote? - %span.vote.downvote.label.label-gray.strikethrough - = icon('thumbs-down') - \-1 - - else - - if note.upvote? - %span.vote.upvote.label.label-success - = icon('thumbs-up') - \+1 - - if note.downvote? - %span.vote.downvote.label.label-danger - = icon('thumbs-down') - \-1 - - .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} .note-text = preserve do diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 36ea674206..3c3ae9dd0b 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,10 +1,33 @@ -.votes.votes-block - .btn-group - - unless votable.upvotes.zero? - .btn.btn-sm.disabled.cgreen - %i.fa.fa-thumbs-up - = votable.upvotes - - unless votable.downvotes.zero? - .btn.btn-sm.disabled.cred - %i.fa.fa-thumbs-down - = votable.downvotes +.awards.votes-block + - votable.notes.awards.grouped_awards.each do | vote | + .award{class: ("active" if vote.last.pluck(:author_id).include?(current_user.id))} + .icon{class: "emoji-#{vote.first}"} + = image_tag url_to_emoji(vote.first), height: "20px", width: "20px" + .counter + = vote.last.count + + %button.dropdown + %a#add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"} + + %ul.dropdown-menu.awards-menu + - ["100", "blush", "heart", "smile", "rage", "beers", "thumbsup", "disappointed", "ok_hand", "helicopter"].each do |emoji| + %li{class: "emoji-#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px" + + +:coffeescript + post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}" + noteable_type = "Issue" + noteable_id = #{@issue.id} + awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id) + + $ -> + $(".awards-menu li").click (e)-> + emoji = $(this).attr("class") + awards_handler.addAward(emoji) + + $(".awards").on "click", ".award", (e)-> + emoji = /(emoji-\S*)/.exec($(this).find(".icon").attr("class"))[0] + awards_handler.addAward(emoji) + + + + \ No newline at end of file diff --git a/app/views/votes/_votes_inline.html.haml b/app/views/votes/_votes_inline.html.haml deleted file mode 100644 index 2cb3ae04e1..0000000000 --- a/app/views/votes/_votes_inline.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -.votes.votes-inline - - unless votable.upvotes.zero? - %span.upvotes.cgreen - + #{votable.upvotes} - - unless votable.downvotes.zero? - \/ - - unless votable.downvotes.zero? - %span.downvotes.cred - \- #{votable.downvotes} diff --git a/config/routes.rb b/config/routes.rb index 0bc2c17345..ac81a2aac7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -664,6 +664,10 @@ Gitlab::Application.routes.draw do member do delete :delete_attachment end + + collection do + post :award_toggle + end end resources :uploads, only: [:create] do diff --git a/db/migrate/20151106000015_add_is_award_to_notes.rb b/db/migrate/20151106000015_add_is_award_to_notes.rb new file mode 100644 index 0000000000..bffe85df3d --- /dev/null +++ b/db/migrate/20151106000015_add_is_award_to_notes.rb @@ -0,0 +1,5 @@ +class AddIsAwardToNotes < ActiveRecord::Migration + def change + add_column :notes, :is_award, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 440a33e200..6c322d3368 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -531,7 +531,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do t.string "type" t.string "description", default: "", null: false t.string "avatar" - t.boolean "public", default: false + t.boolean "visible", default: false end add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree @@ -554,6 +554,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do t.boolean "system", default: false, null: false t.text "st_diff" t.integer "updated_by_id" + t.boolean "is_award", default: false end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index ffa7f2cdf1..2f17d4ae06 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -31,8 +31,6 @@ Parameters: "project_id": 3, "title": "test1", "state": "opened", - "upvotes": 0, - "downvotes": 0, "author": { "id": 1, "username": "admin", @@ -77,8 +75,6 @@ Parameters: "project_id": 3, "title": "test1", "state": "merged", - "upvotes": 0, - "downvotes": 0, "author": { "id": 1, "username": "admin", @@ -126,8 +122,6 @@ Parameters: "updated_at": "2015-02-02T20:08:49.959Z", "target_branch": "secret_token", "source_branch": "version-1-9", - "upvotes": 0, - "downvotes": 0, "author": { "name": "Chad Hamill", "username": "jarrett", @@ -198,8 +192,6 @@ Parameters: "project_id": 3, "title": "test1", "state": "opened", - "upvotes": 0, - "downvotes": 0, "author": { "id": 1, "username": "admin", @@ -250,8 +242,6 @@ Parameters: "title": "test1", "description": "description1", "state": "opened", - "upvotes": 0, - "downvotes": 0, "author": { "id": 1, "username": "admin", @@ -304,8 +294,6 @@ Parameters: "project_id": 3, "title": "test1", "state": "merged", - "upvotes": 0, - "downvotes": 0, "author": { "id": 1, "username": "admin", diff --git a/doc/api/notes.md b/doc/api/notes.md index c683cb883d..bcba1f6215 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -32,9 +32,7 @@ Parameters: "created_at": "2013-09-30T13:46:01Z" }, "created_at": "2013-10-02T09:22:45Z", - "system": true, - "upvote": false, - "downvote": false + "system": true }, { "id": 305, @@ -49,9 +47,7 @@ Parameters: "created_at": "2013-09-30T13:46:01Z" }, "created_at": "2013-10-02T09:56:03Z", - "system": false, - "upvote": false, - "downvote": false + "system": false } ] ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index d6aec03d7f..3da6bc415d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -162,7 +162,7 @@ module API end class MergeRequest < ProjectEntity - expose :target_branch, :source_branch, :upvotes, :downvotes + expose :target_branch, :source_branch expose :author, :assignee, using: Entities::UserBasic expose :source_project_id, :target_project_id expose :label_names, as: :labels @@ -192,8 +192,6 @@ module API expose :author, using: Entities::UserBasic expose :created_at expose :system?, as: :system - expose :upvote?, as: :upvote - expose :downvote?, as: :downvote end class MRNote < Grape::Entity diff --git a/spec/lib/votes_spec.rb b/spec/lib/votes_spec.rb deleted file mode 100644 index 39e5d054e6..0000000000 --- a/spec/lib/votes_spec.rb +++ /dev/null @@ -1,188 +0,0 @@ -require 'spec_helper' - -describe Issue, 'Votes' do - let(:issue) { create(:issue) } - - describe "#upvotes" do - it "with no notes has a 0/0 score" do - expect(issue.upvotes).to eq(0) - end - - it "should recognize non-+1 notes" do - add_note "No +1 here" - expect(issue.notes.size).to eq(1) - expect(issue.notes.first.upvote?).to be_falsey - expect(issue.upvotes).to eq(0) - end - - it "should recognize a single +1 note" do - add_note "+1 This is awesome" - expect(issue.upvotes).to eq(1) - end - - it 'should recognize multiple +1 notes' do - add_note '+1 This is awesome', create(:user) - add_note '+1 I want this', create(:user) - expect(issue.upvotes).to eq(2) - end - - it 'should not count 2 +1 votes from the same user' do - add_note '+1 This is awesome' - add_note '+1 I want this' - expect(issue.upvotes).to eq(1) - end - end - - describe "#downvotes" do - it "with no notes has a 0/0 score" do - expect(issue.downvotes).to eq(0) - end - - it "should recognize non--1 notes" do - add_note "Almost got a -1" - expect(issue.notes.size).to eq(1) - expect(issue.notes.first.downvote?).to be_falsey - expect(issue.downvotes).to eq(0) - end - - it "should recognize a single -1 note" do - add_note "-1 This is bad" - expect(issue.downvotes).to eq(1) - end - - it "should recognize multiple -1 notes" do - add_note('-1 This is bad', create(:user)) - add_note('-1 Away with this', create(:user)) - expect(issue.downvotes).to eq(2) - end - end - - describe "#votes_count" do - it "with no notes has a 0/0 score" do - expect(issue.votes_count).to eq(0) - end - - it "should recognize non notes" do - add_note "No +1 here" - expect(issue.notes.size).to eq(1) - expect(issue.votes_count).to eq(0) - end - - it "should recognize a single +1 note" do - add_note "+1 This is awesome" - expect(issue.votes_count).to eq(1) - end - - it "should recognize a single -1 note" do - add_note "-1 This is bad" - expect(issue.votes_count).to eq(1) - end - - it "should recognize multiple notes" do - add_note('+1 This is awesome', create(:user)) - add_note('-1 This is bad', create(:user)) - add_note('+1 I want this', create(:user)) - expect(issue.votes_count).to eq(3) - end - - it 'should not count 2 -1 votes from the same user' do - add_note '-1 This is suspicious' - add_note '-1 This is bad' - expect(issue.votes_count).to eq(1) - end - end - - describe "#upvotes_in_percent" do - it "with no notes has a 0% score" do - expect(issue.upvotes_in_percent).to eq(0) - end - - it "should count a single 1 note as 100%" do - add_note "+1 This is awesome" - expect(issue.upvotes_in_percent).to eq(100) - end - - it 'should count multiple +1 notes as 100%' do - add_note('+1 This is awesome', create(:user)) - add_note('+1 I want this', create(:user)) - expect(issue.upvotes_in_percent).to eq(100) - end - - it 'should count fractions for multiple +1 and -1 notes correctly' do - add_note('+1 This is awesome', create(:user)) - add_note('+1 I want this', create(:user)) - add_note('-1 This is bad', create(:user)) - add_note('+1 me too', create(:user)) - expect(issue.upvotes_in_percent).to eq(75) - end - end - - describe "#downvotes_in_percent" do - it "with no notes has a 0% score" do - expect(issue.downvotes_in_percent).to eq(0) - end - - it "should count a single -1 note as 100%" do - add_note "-1 This is bad" - expect(issue.downvotes_in_percent).to eq(100) - end - - it 'should count multiple -1 notes as 100%' do - add_note('-1 This is bad', create(:user)) - add_note('-1 Away with this', create(:user)) - expect(issue.downvotes_in_percent).to eq(100) - end - - it 'should count fractions for multiple +1 and -1 notes correctly' do - add_note('+1 This is awesome', create(:user)) - add_note('+1 I want this', create(:user)) - add_note('-1 This is bad', create(:user)) - add_note('+1 me too', create(:user)) - expect(issue.downvotes_in_percent).to eq(25) - end - end - - describe '#filter_superceded_votes' do - - it 'should count a users vote only once amongst multiple votes' do - add_note('-1 This needs work before I will accept it') - add_note('+1 I want this', create(:user)) - add_note('+1 This is is awesome', create(:user)) - add_note('+1 this looks good now') - add_note('+1 This is awesome', create(:user)) - add_note('+1 me too', create(:user)) - expect(issue.downvotes).to eq(0) - expect(issue.upvotes).to eq(5) - end - - it 'should count each users vote only once' do - add_note '-1 This needs work before it will be accepted' - add_note '+1 I like this' - add_note '+1 I still like this' - add_note '+1 I really like this' - add_note '+1 Give me this now!!!!' - expect(issue.downvotes).to eq(0) - expect(issue.upvotes).to eq(1) - end - - it 'should count a users vote only once without caring about comments' do - add_note '-1 This needs work before it will be accepted' - add_note 'Comment 1' - add_note 'Another comment' - add_note '+1 vote' - add_note 'final comment' - expect(issue.downvotes).to eq(0) - expect(issue.upvotes).to eq(1) - end - - end - - def add_note(text, author = issue.author) - created_at = Time.now - 1.hour + Note.count.seconds - issue.notes << create(:note, - note: text, - project: issue.project, - author_id: author.id, - created_at: created_at) - end -end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8d7e6e7676..3c53722010 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -345,17 +345,6 @@ describe Project do expect(project1.star_count).to eq(0) expect(project2.star_count).to eq(0) end - - it 'is decremented when an upvoter account is deleted' do - user = create :user - project = create :project, :public - user.toggle_star(project) - project.reload - expect(project.star_count).to eq(1) - user.destroy - project.reload - expect(project.star_count).to eq(0) - end end describe :avatar_type do From 06a4fd1035c58d89251fb979dafa8610ba8c5157 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 17 Nov 2015 13:16:16 +0200 Subject: [PATCH 175/410] css improvements --- app/assets/javascripts/awards_handler.coffee | 2 +- app/assets/stylesheets/pages/issuable.scss | 20 +++++++++++++++----- app/finders/notes_finder.rb | 4 ++-- app/views/votes/_votes_block.html.haml | 2 +- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index aab3179f10..1ede7c317c 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -39,7 +39,7 @@ class @AwardsHandler nodes.push("
1") nodes.push("
") - $(".awards").append(nodes.join("\n")) + $(".awards-controls").before(nodes.join("\n")) getImage: (emoji) -> $("li." + emoji).html() diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index efeb239316..3f79d0d496 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -128,12 +128,22 @@ } } - #add-award { - font-size: 20px; - border-radius: 5px; + .awards-controls { + height: 25px; + width: 28px; float: left; - width: 50px; - font-weight: bold; + padding: 0 0 5px 5px; + line-height: 1; + + #add-award { + font-size: 27px; + &:hover { + text-decoration: none; + } + &:link { + text-decoration: none; + } + } } .awards-menu{ diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index ab252821b5..fa4c635f55 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -12,9 +12,9 @@ class NotesFinder when "commit" project.notes.for_commit_id(target_id).not_inline when "issue" - project.issues.find(target_id).notes.inc_author + project.issues.find(target_id).notes.nonawards.inc_author when "merge_request" - project.merge_requests.find(target_id).mr_and_commit_notes.inc_author + project.merge_requests.find(target_id).mr_and_commit_notes.nonawards.inc_author when "snippet", "project_snippet" project.snippets.find(target_id).notes else diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 3c3ae9dd0b..7f988160ad 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -6,7 +6,7 @@ .counter = vote.last.count - %button.dropdown + .dropdown.awards-controls %a#add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"} + %ul.dropdown-menu.awards-menu - ["100", "blush", "heart", "smile", "rage", "beers", "thumbsup", "disappointed", "ok_hand", "helicopter"].each do |emoji| From 36d0442e837cd520dec780590040c83108bc14e6 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 17 Nov 2015 16:44:58 +0200 Subject: [PATCH 176/410] replace emoji references from class name to data [ci skip] --- app/assets/javascripts/awards_handler.coffee | 36 +++++++++++--------- app/assets/javascripts/notes.js.coffee | 2 +- app/views/votes/_votes_block.html.haml | 10 +++--- 3 files changed, 26 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 1ede7c317c..8803c0cca2 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -3,25 +3,27 @@ class @AwardsHandler addAward: (emoji) -> @postEmoji emoji, => - if @exist(emoji) - if @isActive(emoji) - @decrementCounter(emoji) - else - counter = $(".icon." + emoji).siblings(".counter") - counter.text(parseInt(counter.text()) + 1) - counter.parent().addClass("active") - else - @createEmoji(emoji) + @addAwardToEmojiBar(emoji) + addAwardToEmojiBar: (emoji) -> + if @exist(emoji) + if @isActive(emoji) + @decrementCounter(emoji) + else + counter = @findEmojiIcon(emoji).siblings(".counter") + counter.text(parseInt(counter.text()) + 1) + counter.parent().addClass("active") + else + @createEmoji(emoji) exist: (emoji) -> - $(".icon").hasClass(emoji) + @findEmojiIcon(emoji).length > 0 isActive: (emoji) -> - $(".icon." + emoji).parent().hasClass("active") + @findEmojiIcon(emoji).parent().hasClass("active") decrementCounter: (emoji) -> - counter = $(".icon." + emoji).siblings(".counter") + counter = @findEmojiIcon(emoji).siblings(".counter") if parseInt(counter.text()) > 1 counter.text(parseInt(counter.text()) - 1) @@ -33,7 +35,7 @@ class @AwardsHandler createEmoji: (emoji) -> nodes = [] nodes.push("
") - nodes.push("
") + nodes.push("
") nodes.push(@getImage(emoji)) nodes.push("
") nodes.push("
1") @@ -42,14 +44,16 @@ class @AwardsHandler $(".awards-controls").before(nodes.join("\n")) getImage: (emoji) -> - $("li." + emoji).html() + $("li[data-emoji='" + emoji + "'").html() postEmoji: (emoji, callback) -> - emoji = emoji.replace("emoji-", "") $.post @post_emoji_url, { emoji: emoji noteable_type: @noteable_type noteable_id: @noteable_id },(data) -> if data.ok - callback.call() \ No newline at end of file + callback.call() + + findEmojiIcon: (emoji) -> + $(".icon[data-emoji='" + emoji + "'") \ No newline at end of file diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index cd27b20dd7..73a95c455e 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -121,7 +121,7 @@ class @Notes @initTaskList() if note.award - awards_handler.addAward("emoji-" + note.note) + awards_handler.addAwardToEmojiBar(note.note) ### Check if note does not exists on page diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 7f988160ad..118a095181 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,7 +1,7 @@ .awards.votes-block - votable.notes.awards.grouped_awards.each do | vote | .award{class: ("active" if vote.last.pluck(:author_id).include?(current_user.id))} - .icon{class: "emoji-#{vote.first}"} + .icon{"data-emoji" => "#{vote.first}"} = image_tag url_to_emoji(vote.first), height: "20px", width: "20px" .counter = vote.last.count @@ -10,22 +10,22 @@ %a#add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"} + %ul.dropdown-menu.awards-menu - ["100", "blush", "heart", "smile", "rage", "beers", "thumbsup", "disappointed", "ok_hand", "helicopter"].each do |emoji| - %li{class: "emoji-#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px" + %li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px" :coffeescript post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}" noteable_type = "Issue" noteable_id = #{@issue.id} - awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id) + window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id) $ -> $(".awards-menu li").click (e)-> - emoji = $(this).attr("class") + emoji = $(this).data("emoji") awards_handler.addAward(emoji) $(".awards").on "click", ".award", (e)-> - emoji = /(emoji-\S*)/.exec($(this).find(".icon").attr("class"))[0] + emoji = $(this).find(".icon").data("emoji") awards_handler.addAward(emoji) From f021bc5c6aa79147940ee31e800f519962f4d806 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 18 Nov 2015 15:43:53 +0200 Subject: [PATCH 177/410] add stats on hover --- app/assets/javascripts/awards_handler.coffee | 32 ++++++++++++++++++-- app/helpers/issues_helper.rb | 8 +++++ app/views/votes/_votes_block.html.haml | 4 ++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 8803c0cca2..29b11b0cc5 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -13,6 +13,7 @@ class @AwardsHandler counter = @findEmojiIcon(emoji).siblings(".counter") counter.text(parseInt(counter.text()) + 1) counter.parent().addClass("active") + @addMeToAuthorList(emoji) else @createEmoji(emoji) @@ -28,13 +29,38 @@ class @AwardsHandler if parseInt(counter.text()) > 1 counter.text(parseInt(counter.text()) - 1) counter.parent().removeClass("active") + @removeMeFromAuthorList(emoji) else - counter.parent().remove() + award = counter.parent() + award.tooltip("destroy") + award.remove() + removeMeFromAuthorList: (emoji) -> + award_block = @findEmojiIcon(emoji).parent() + authors = award_block.attr("data-original-title").split(", ") + authors = _.without(authors, "me").join(", ") + award_block.attr("title", authors) + @resetTooltip(award_block) + + addMeToAuthorList: (emoji) -> + award_block = @findEmojiIcon(emoji).parent() + authors = award_block.attr("data-original-title").split(", ") + authors.push("me") + award_block.attr("title", authors.join(", ")) + @resetTooltip(award_block) + + resetTooltip: (award) -> + award.tooltip("destroy") + + # "destroy" call is asynchronous, this is why we need to set timeout. + setTimeout (-> + award.tooltip() + ), 200 + createEmoji: (emoji) -> nodes = [] - nodes.push("
") + nodes.push("
") nodes.push("
") nodes.push(@getImage(emoji)) nodes.push("
") @@ -43,6 +69,8 @@ class @AwardsHandler $(".awards-controls").before(nodes.join("\n")) + $(".award").tooltip() + getImage: (emoji) -> $("li[data-emoji='" + emoji + "'").html() diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index ff3e091195..3aa16b6694 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -92,6 +92,14 @@ module IssuesHelper url_to_image(emoji_path) end + def emoji_author_list(notes, current_user) + list = notes.map do |note| + note.author == current_user ? "me" : note.author.username + end + + list.join(", ") + end + # Required for Gitlab::Markdown::IssueReferenceFilter module_function :url_for_issue end diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 118a095181..a2298f1813 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,6 +1,6 @@ .awards.votes-block - votable.notes.awards.grouped_awards.each do | vote | - .award{class: ("active" if vote.last.pluck(:author_id).include?(current_user.id))} + .award{class: ("active" if vote.last.pluck(:author_id).include?(current_user.id)), title: emoji_author_list(vote.last, current_user)} .icon{"data-emoji" => "#{vote.first}"} = image_tag url_to_emoji(vote.first), height: "20px", width: "20px" .counter @@ -28,6 +28,8 @@ emoji = $(this).find(".icon").data("emoji") awards_handler.addAward(emoji) + $(".award").tooltip() + \ No newline at end of file From 2f6f99d300675b0794b2e96be564db9d405fac36 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 18 Nov 2015 16:48:37 +0200 Subject: [PATCH 178/410] award for merge requests[ci skip] --- app/assets/stylesheets/pages/issuable.scss | 4 ++-- app/controllers/projects/merge_requests_controller.rb | 2 +- app/views/votes/_votes_block.html.haml | 10 +++------- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 3f79d0d496..5b73e20df7 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -114,8 +114,8 @@ cursor: pointer; &.active { - border-color: rgba(79,176,252,.4); - background-color: rgba(79,176,252,.08); + border-color: rgba(79,176,252,0.4); + background-color: rgba(79,176,252,0.1); .counter { font-weight: bold; diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index a0468c65d5..6378a1f56b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -254,7 +254,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController # Build a note object for comment form @note = @project.notes.new(noteable: @merge_request) - @notes = @merge_request.nonawards.mr_and_commit_notes.inc_author.fresh + @notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh @discussions = Note.discussions_from_notes(@notes) @noteable = @merge_request diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index a2298f1813..2ae832c31f 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -15,8 +15,8 @@ :coffeescript post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}" - noteable_type = "Issue" - noteable_id = #{@issue.id} + noteable_type = #{votable} + noteable_id = #{votable.id} window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id) $ -> @@ -28,8 +28,4 @@ emoji = $(this).find(".icon").data("emoji") awards_handler.addAward(emoji) - $(".award").tooltip() - - - - \ No newline at end of file + $(".award").tooltip() \ No newline at end of file From 2d1fcd802a291cc8e26fbbe5874e20316a5f93af Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 18 Nov 2015 17:38:15 +0200 Subject: [PATCH 179/410] Emoji: refactoring --- app/helpers/issues_helper.rb | 4 ++++ app/views/votes/_votes_block.html.haml | 2 +- lib/award_emoji.rb | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 lib/award_emoji.rb diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 3aa16b6694..bca3232209 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -100,6 +100,10 @@ module IssuesHelper list.join(", ") end + def emoji_list + ::AwardEmoji::EMOJI_LIST + end + # Required for Gitlab::Markdown::IssueReferenceFilter module_function :url_for_issue end diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 2ae832c31f..02947f2979 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -9,7 +9,7 @@ .dropdown.awards-controls %a#add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"} + %ul.dropdown-menu.awards-menu - - ["100", "blush", "heart", "smile", "rage", "beers", "thumbsup", "disappointed", "ok_hand", "helicopter"].each do |emoji| + - emoji_list.each do |emoji| %li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px" diff --git a/lib/award_emoji.rb b/lib/award_emoji.rb new file mode 100644 index 0000000000..95b9c8f921 --- /dev/null +++ b/lib/award_emoji.rb @@ -0,0 +1,6 @@ +class AwardEmoji + EMOJI_LIST = ["+1", "-1", "100", "blush", "heart", "smile", "rage", + "beers", "disappointed", "ok_hand", + "helicopter", "shit", "airplane", "alarm_clock", + "ambulance", "anguished", "two_hearts", "wink"] +end \ No newline at end of file From d8676f18fcbd6b78af472b8b00c29f20820a74a9 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 18 Nov 2015 21:44:48 +0200 Subject: [PATCH 180/410] fix --- app/views/votes/_votes_block.html.haml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 02947f2979..afd1c2745a 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -15,17 +15,16 @@ :coffeescript post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}" - noteable_type = #{votable} - noteable_id = #{votable.id} + noteable_type = "#{votable.class}" + noteable_id = "#{votable.id}" window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id) - $ -> - $(".awards-menu li").click (e)-> - emoji = $(this).data("emoji") - awards_handler.addAward(emoji) + $(".awards-menu li").click (e)-> + emoji = $(this).data("emoji") + awards_handler.addAward(emoji) - $(".awards").on "click", ".award", (e)-> - emoji = $(this).find(".icon").data("emoji") - awards_handler.addAward(emoji) + $(".awards").on "click", ".award", (e)-> + emoji = $(this).find(".icon").data("emoji") + awards_handler.addAward(emoji) - $(".award").tooltip() \ No newline at end of file + $(".award").tooltip() \ No newline at end of file From 23df515fd09661a690c0c0a651e131bc3a6d0191 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 18 Nov 2015 23:59:58 +0200 Subject: [PATCH 181/410] Emoji: fix image of emoji when it is submitted via comment --- app/assets/javascripts/awards_handler.coffee | 16 ++++++++++------ app/assets/javascripts/notes.js.coffee | 2 +- app/controllers/projects/notes_controller.rb | 1 + app/helpers/issues_helper.rb | 2 +- app/views/votes/_votes_block.html.haml | 1 - lib/award_emoji.rb | 4 ++++ 6 files changed, 17 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 29b11b0cc5..cac9c10332 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -5,7 +5,7 @@ class @AwardsHandler @postEmoji emoji, => @addAwardToEmojiBar(emoji) - addAwardToEmojiBar: (emoji) -> + addAwardToEmojiBar: (emoji, custom_path = '') -> if @exist(emoji) if @isActive(emoji) @decrementCounter(emoji) @@ -15,7 +15,7 @@ class @AwardsHandler counter.parent().addClass("active") @addMeToAuthorList(emoji) else - @createEmoji(emoji) + @createEmoji(emoji, custom_path) exist: (emoji) -> @findEmojiIcon(emoji).length > 0 @@ -58,11 +58,11 @@ class @AwardsHandler ), 200 - createEmoji: (emoji) -> + createEmoji: (emoji, custom_path) -> nodes = [] nodes.push("
") nodes.push("
") - nodes.push(@getImage(emoji)) + nodes.push(@getImage(emoji, custom_path)) nodes.push("
") nodes.push("
1") nodes.push("
") @@ -71,8 +71,12 @@ class @AwardsHandler $(".award").tooltip() - getImage: (emoji) -> - $("li[data-emoji='" + emoji + "'").html() + getImage: (emoji, custom_path) -> + if custom_path + $(".awards-menu li").first().html().replace(/emoji\/.*\.png/, custom_path) + else + $("li[data-emoji='" + emoji + "'").html() + postEmoji: (emoji, callback) -> $.post @post_emoji_url, { diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 73a95c455e..7de7632201 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -121,7 +121,7 @@ class @Notes @initTaskList() if note.award - awards_handler.addAwardToEmojiBar(note.note) + awards_handler.addAwardToEmojiBar(note.note, note.emoji_path) ### Check if note does not exists on page diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 357b292980..98bf056a60 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -133,6 +133,7 @@ class Projects::NotesController < Projects::ApplicationController discussion_id: note.discussion_id, html: note_to_html(note), award: note.is_award, + emoji_path: note.is_award ? ::AwardEmoji.path_to_emoji_image(note.note) : "", note: note.note, discussion_html: note_to_discussion_html(note), discussion_with_diff_html: note_to_discussion_with_diff_html(note) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index bca3232209..bf289c6db1 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -88,7 +88,7 @@ module IssuesHelper end def url_to_emoji(name) - emoji_path = "emoji/#{Emoji.emoji_filename(name)}.png" + emoji_path = ::AwardEmoji.path_to_emoji_image(name) url_to_image(emoji_path) end diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index afd1c2745a..5392915b4d 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -12,7 +12,6 @@ - emoji_list.each do |emoji| %li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px" - :coffeescript post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}" noteable_type = "#{votable.class}" diff --git a/lib/award_emoji.rb b/lib/award_emoji.rb index 95b9c8f921..9e296f0bc3 100644 --- a/lib/award_emoji.rb +++ b/lib/award_emoji.rb @@ -3,4 +3,8 @@ class AwardEmoji "beers", "disappointed", "ok_hand", "helicopter", "shit", "airplane", "alarm_clock", "ambulance", "anguished", "two_hearts", "wink"] + + def self.path_to_emoji_image(name) + "emoji/#{Emoji.emoji_filename(name)}.png" + end end \ No newline at end of file From db91ef3a2a46511da23dff780791a70594e57312 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 19 Nov 2015 00:03:45 +0200 Subject: [PATCH 182/410] better regexp --- app/services/notes/create_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index f448f61cc8..dbff58dfb9 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -35,7 +35,7 @@ module Notes end def contains_emoji_only?(note) - note =~ /^:[-_+[:alnum:]]*:\s?/ + note =~ /\A:[-_+[:alnum:]]*:\s?\z/ end def emoji_name(note) From 92943580cb1647930dbfdd8d2957213326c134d9 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 19 Nov 2015 00:10:58 +0200 Subject: [PATCH 183/410] improve style --- app/assets/stylesheets/pages/issuable.scss | 4 +++- app/views/votes/_votes_block.html.haml | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 5b73e20df7..7dd4f239c7 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -135,8 +135,10 @@ padding: 0 0 5px 5px; line-height: 1; - #add-award { + .add-award { font-size: 27px; + color: #ccc; + &:hover { text-decoration: none; } diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 5392915b4d..fff7474591 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -7,7 +7,8 @@ = vote.last.count .dropdown.awards-controls - %a#add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"} + + %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"} + = icon('plus-circle') %ul.dropdown-menu.awards-menu - emoji_list.each do |emoji| %li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px" From fdd5a8f2e16cc210f24d93334877f1ca7ce92ce9 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 19 Nov 2015 00:59:07 +0200 Subject: [PATCH 184/410] addressing comments --- app/helpers/issues_helper.rb | 4 ++++ app/models/note.rb | 4 ++-- app/views/votes/_votes_block.html.haml | 10 +++++----- db/migrate/20151106000015_add_is_award_to_notes.rb | 3 ++- db/schema.rb | 3 ++- 5 files changed, 15 insertions(+), 9 deletions(-) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index bf289c6db1..3a238824c0 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -104,6 +104,10 @@ module IssuesHelper ::AwardEmoji::EMOJI_LIST end + def note_active_class(notes, current_user) + notes.pluck(:author_id).include?(current_user.id) ? "active" : "" + end + # Required for Gitlab::Markdown::IssueReferenceFilter module_function :url_for_issue end diff --git a/app/models/note.rb b/app/models/note.rb index 458d433211..d53f568a67 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -50,8 +50,8 @@ class Note < ActiveRecord::Base mount_uploader :attachment, AttachmentUploader # Scopes - scope :awards, ->{ where("is_award IS TRUE") } - scope :nonawards, ->{ where("is_award IS FALSE") } + scope :awards, ->{ where(is_award: true) } + scope :nonawards, ->{ where(is_award: false) } scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) } scope :inline, ->{ where("line_code IS NOT NULL") } scope :not_inline, ->{ where(line_code: [nil, '']) } diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index fff7474591..3eadf209a5 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,10 +1,10 @@ .awards.votes-block - - votable.notes.awards.grouped_awards.each do | vote | - .award{class: ("active" if vote.last.pluck(:author_id).include?(current_user.id)), title: emoji_author_list(vote.last, current_user)} - .icon{"data-emoji" => "#{vote.first}"} - = image_tag url_to_emoji(vote.first), height: "20px", width: "20px" + - votable.notes.awards.grouped_awards.each do | note | + .award{class: (note_active_class(note.last, current_user)), title: emoji_author_list(note.last, current_user)} + .icon{"data-emoji" => "#{note.first}"} + = image_tag url_to_emoji(note.first), height: "20px", width: "20px" .counter - = vote.last.count + = note.last.count .dropdown.awards-controls %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"} diff --git a/db/migrate/20151106000015_add_is_award_to_notes.rb b/db/migrate/20151106000015_add_is_award_to_notes.rb index bffe85df3d..02b271637e 100644 --- a/db/migrate/20151106000015_add_is_award_to_notes.rb +++ b/db/migrate/20151106000015_add_is_award_to_notes.rb @@ -1,5 +1,6 @@ class AddIsAwardToNotes < ActiveRecord::Migration def change - add_column :notes, :is_award, :boolean, default: false + add_column :notes, :is_award, :boolean, default: false, null: false + add_index :notes, :is_award end end diff --git a/db/schema.rb b/db/schema.rb index 6c322d3368..f5511ac189 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -554,13 +554,14 @@ ActiveRecord::Schema.define(version: 20151116144118) do t.boolean "system", default: false, null: false t.text "st_diff" t.integer "updated_by_id" - t.boolean "is_award", default: false + t.boolean "is_award", default: false, null: false end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree + add_index "notes", ["is_award"], name: "index_notes_on_is_award", using: :btree add_index "notes", ["line_code"], name: "index_notes_on_line_code", using: :btree add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree From 73bc9edc4410d228db0258c7f0c6fa84dd1d2c49 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 19 Nov 2015 01:00:56 +0200 Subject: [PATCH 185/410] add changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 94f07a3168..6bc0d07b1c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -50,6 +50,7 @@ v 8.2.0 - Fix bug when milestone/label filter was empty for dashboard issues page - Add ability to create milestone in group projects from single form - Prevent the last owner of a group from being able to delete themselves by 'adding' themselves as a master (James Lopez) + - Add Award Emoji to issue and merge request pages v 8.1.4 - Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu) From fa2ed94fe9aa9f4d43c2aec7d99103982976063f Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 19 Nov 2015 01:19:32 +0200 Subject: [PATCH 186/410] fix schema --- db/schema.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/schema.rb b/db/schema.rb index f5511ac189..f77f6dfc66 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -531,7 +531,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do t.string "type" t.string "description", default: "", null: false t.string "avatar" - t.boolean "visible", default: false + t.boolean "public", default: false end add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree From a2912074be67deb6345a37787c14b7e640be26f8 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 19 Nov 2015 01:31:15 +0200 Subject: [PATCH 187/410] satisfy rubocop --- app/controllers/projects/notes_controller.rb | 4 ++-- db/schema.rb | 4 +--- lib/award_emoji.rb | 8 +++++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 98bf056a60..8159cc5083 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController before_action :authorize_read_note! before_action :authorize_create_note!, only: [:create] before_action :authorize_admin_note!, only: [:update, :destroy] - before_action :find_current_user_notes, except: [:destroy, :delete_attachment, :award_toggle]] + before_action :find_current_user_notes, except: [:destroy, :delete_attachment, :award_toggle] def index current_fetched_at = Time.now.to_i @@ -76,7 +76,7 @@ class Projects::NotesController < Projects::ApplicationController project.notes.create(data) end - render json: {ok: true} + render json: { ok: true } end private diff --git a/db/schema.rb b/db/schema.rb index f77f6dfc66..3062bf4d41 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -642,9 +642,7 @@ ActiveRecord::Schema.define(version: 20151116144118) do t.integer "star_count", default: 0, null: false t.string "import_type" t.string "import_source" - t.integer "commit_count", default: 0 - t.boolean "merge_requests_ff_only_enabled", default: false - t.text "issues_template" + t.integer "commit_count", default: 0 t.text "import_error" end diff --git a/lib/award_emoji.rb b/lib/award_emoji.rb index 9e296f0bc3..d58a196c4e 100644 --- a/lib/award_emoji.rb +++ b/lib/award_emoji.rb @@ -1,10 +1,12 @@ class AwardEmoji - EMOJI_LIST = ["+1", "-1", "100", "blush", "heart", "smile", "rage", + EMOJI_LIST = [ + "+1", "-1", "100", "blush", "heart", "smile", "rage", "beers", "disappointed", "ok_hand", "helicopter", "shit", "airplane", "alarm_clock", - "ambulance", "anguished", "two_hearts", "wink"] + "ambulance", "anguished", "two_hearts", "wink" + ] def self.path_to_emoji_image(name) "emoji/#{Emoji.emoji_filename(name)}.png" end -end \ No newline at end of file +end From 060e59f05436f29640f6890c69426be1cd79e5cd Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 18 Nov 2015 11:06:12 +0100 Subject: [PATCH 188/410] Lfs on by default. --- config/gitlab.yml.example | 2 +- config/initializers/1_settings.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 8fdb2603ce..1788d4c2c7 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -126,7 +126,7 @@ production: &base ## Git LFS lfs: - enabled: false + enabled: true # The location where LFS objects are stored (default: shared/lfs-objects). # storage_path: shared/lfs-objects diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 6b7990c0ab..b498fb1e1d 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -203,7 +203,7 @@ Settings.incoming_email['mailbox'] = "inbox" if Settings.incoming_email['mail # Git LFS # Settings['lfs'] ||= Settingslogic.new({}) -Settings.lfs['enabled'] = false if Settings.lfs['enabled'].nil? +Settings.lfs['enabled'] = true if Settings.lfs['enabled'].nil? Settings.lfs['storage_path'] = File.expand_path(Settings.lfs['storage_path'] || File.join(Settings.shared['path'], "lfs-objects"), Rails.root) # From 3cc2b48a82aae8b535ddf11e9057a29f2242524c Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 18 Nov 2015 11:39:26 +0100 Subject: [PATCH 189/410] Backup LFS objects same as any upload. --- doc/raketasks/backup_restore.md | 2 +- lib/backup/lfs.rb | 13 +++++++++++++ lib/backup/manager.rb | 2 +- lib/tasks/gitlab/backup.rake | 21 +++++++++++++++++++++ 4 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 lib/backup/lfs.rb diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 1a5442cdac..4e645b21a8 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -29,7 +29,7 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` Also you can choose what should be backed up by adding environment variable SKIP. Available options: db, -uploads (attachments), repositories, builds(CI build output logs), artifacts (CI build artifacts). +uploads (attachments), repositories, builds(CI build output logs), artifacts (CI build artifacts), lfs (LFS objects). Use a comma to specify several options at the same time. ``` diff --git a/lib/backup/lfs.rb b/lib/backup/lfs.rb new file mode 100644 index 0000000000..4153467fbe --- /dev/null +++ b/lib/backup/lfs.rb @@ -0,0 +1,13 @@ +require 'backup/files' + +module Backup + class Lfs < Files + def initialize + super('lfs', Settings.lfs.storage_path) + end + + def create_files_dir + Dir.mkdir(app_files_dir, 0700) + end + end +end diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index e7eda7c6f4..099062eeb8 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -154,7 +154,7 @@ module Backup end def archives_to_backup - %w{uploads builds artifacts}.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact + %w{uploads builds artifacts lfs}.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact end def folders_to_backup diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index 3c46bcea40..cb4abe1379 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -13,6 +13,7 @@ namespace :gitlab do Rake::Task["gitlab:backup:uploads:create"].invoke Rake::Task["gitlab:backup:builds:create"].invoke Rake::Task["gitlab:backup:artifacts:create"].invoke + Rake::Task["gitlab:backup:lfs:create"].invoke backup = Backup::Manager.new backup.pack @@ -34,6 +35,7 @@ namespace :gitlab do Rake::Task["gitlab:backup:uploads:restore"].invoke unless backup.skipped?("uploads") Rake::Task["gitlab:backup:builds:restore"].invoke unless backup.skipped?("builds") Rake::Task["gitlab:backup:artifacts:restore"].invoke unless backup.skipped?("artifacts") + Rake::Task["gitlab:backup:lfs:restore"].invoke unless backup.skipped?("lfs") Rake::Task["gitlab:shell:setup"].invoke backup.cleanup @@ -134,6 +136,25 @@ namespace :gitlab do end end + namespace :lfs do + task create: :environment do + $progress.puts "Dumping lfs objects ... ".blue + + if ENV["SKIP"] && ENV["SKIP"].include?("lfs") + $progress.puts "[SKIPPED]".cyan + else + Backup::Lfs.new.dump + $progress.puts "done".green + end + end + + task restore: :environment do + $progress.puts "Restoring lfs objects ... ".blue + Backup::Lfs.new.restore + $progress.puts "done".green + end + end + def configure_cron_mode if ENV['CRON'] # We need an object we can say 'puts' and 'print' to; let's use a From 2219743d5c6bffd80eaab55db76c0f7d19a8ae61 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 18 Nov 2015 13:21:57 +0100 Subject: [PATCH 190/410] Add lfs to backup specs. --- spec/tasks/gitlab/backup_rake_spec.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index fb5e74af64..63bed2414d 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -16,7 +16,7 @@ describe 'gitlab:app namespace rake task' do end def reenable_backup_sub_tasks - %w{db repo uploads builds artifacts}.each do |subtask| + %w{db repo uploads builds artifacts lfs}.each do |subtask| Rake::Task["gitlab:backup:#{subtask}:create"].reenable end end @@ -49,7 +49,7 @@ describe 'gitlab:app namespace rake task' do to raise_error(SystemExit) end - it 'should invoke restoration on mach' do + it 'should invoke restoration on match' do allow(YAML).to receive(:load_file). and_return({ gitlab_version: gitlab_version }) expect(Rake::Task["gitlab:backup:db:restore"]).to receive(:invoke) @@ -57,6 +57,7 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task["gitlab:backup:builds:restore"]).to receive(:invoke) expect(Rake::Task["gitlab:backup:uploads:restore"]).to receive(:invoke) expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive(:invoke) + expect(Rake::Task["gitlab:backup:lfs:restore"]).to receive(:invoke) expect(Rake::Task["gitlab:shell:setup"]).to receive(:invoke) expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error end @@ -114,7 +115,7 @@ describe 'gitlab:app namespace rake task' do it 'should set correct permissions on the tar contents' do tar_contents, exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz} + %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz} ) expect(exit_status).to eq(0) expect(tar_contents).to match('db/') @@ -122,12 +123,13 @@ describe 'gitlab:app namespace rake task' do expect(tar_contents).to match('repositories/') expect(tar_contents).to match('builds.tar.gz') expect(tar_contents).to match('artifacts.tar.gz') + expect(tar_contents).to match('lfs.tar.gz') expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|artifacts.tar.gz)\/$/) end it 'should delete temp directories' do temp_dirs = Dir.glob( - File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts}') + File.join(Gitlab.config.backup.path, '{db,repositories,uploads,builds,artifacts,lfs}') ) expect(temp_dirs).to be_empty @@ -163,13 +165,14 @@ describe 'gitlab:app namespace rake task' do it "does not contain skipped item" do tar_contents, _exit_status = Gitlab::Popen.popen( - %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz} + %W{tar -tvf #{@backup_tar} db uploads.tar.gz repositories builds.tar.gz artifacts.tar.gz lfs.tar.gz} ) expect(tar_contents).to match('db/') expect(tar_contents).to match('uploads.tar.gz') expect(tar_contents).to match('builds.tar.gz') expect(tar_contents).to match('artifacts.tar.gz') + expect(tar_contents).to match('lfs.tar.gz') expect(tar_contents).not_to match('repositories/') end @@ -183,6 +186,7 @@ describe 'gitlab:app namespace rake task' do expect(Rake::Task["gitlab:backup:uploads:restore"]).not_to receive :invoke expect(Rake::Task["gitlab:backup:builds:restore"]).to receive :invoke expect(Rake::Task["gitlab:backup:artifacts:restore"]).to receive :invoke + expect(Rake::Task["gitlab:backup:lfs:restore"]).to receive :invoke expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke expect { run_rake_task('gitlab:backup:restore') }.not_to raise_error end From 9b724baa7f03e02ca847d80eff4df529e63e79de Mon Sep 17 00:00:00 2001 From: Ferdinand Rosario Date: Thu, 19 Nov 2015 15:17:12 +0530 Subject: [PATCH 191/410] Updated rails patch --- Gemfile | 2 +- Gemfile.lock | 54 ++++++++++++++++++++++++++-------------------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Gemfile b/Gemfile index 8a19885bcb..9889010856 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,6 @@ source "https://rubygems.org" -gem 'rails', '4.1.12' +gem 'rails', '4.1.14' # Specify a sprockets version due to security issue # See https://groups.google.com/forum/#!topic/rubyonrails-security/doAVp0YaTqY diff --git a/Gemfile.lock b/Gemfile.lock index 99cdc2a50a..8dd49bcf96 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -4,25 +4,25 @@ GEM CFPropertyList (2.3.1) RedCloth (4.2.9) ace-rails-ap (2.0.1) - actionmailer (4.1.12) - actionpack (= 4.1.12) - actionview (= 4.1.12) + actionmailer (4.1.14) + actionpack (= 4.1.14) + actionview (= 4.1.14) mail (~> 2.5, >= 2.5.4) - actionpack (4.1.12) - actionview (= 4.1.12) - activesupport (= 4.1.12) + actionpack (4.1.14) + actionview (= 4.1.14) + activesupport (= 4.1.14) rack (~> 1.5.2) rack-test (~> 0.6.2) - actionview (4.1.12) - activesupport (= 4.1.12) + actionview (4.1.14) + activesupport (= 4.1.14) builder (~> 3.1) erubis (~> 2.7.0) - activemodel (4.1.12) - activesupport (= 4.1.12) + activemodel (4.1.14) + activesupport (= 4.1.14) builder (~> 3.1) - activerecord (4.1.12) - activemodel (= 4.1.12) - activesupport (= 4.1.12) + activerecord (4.1.14) + activemodel (= 4.1.14) + activesupport (= 4.1.14) arel (~> 5.0.0) activerecord-deprecated_finders (1.0.4) activerecord-session_store (0.1.1) @@ -33,7 +33,7 @@ GEM activemodel (~> 4.0) activesupport (~> 4.0) rails-observers (~> 0.1.1) - activesupport (4.1.12) + activesupport (4.1.14) i18n (~> 0.6, >= 0.6.9) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) @@ -509,21 +509,21 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) - rails (4.1.12) - actionmailer (= 4.1.12) - actionpack (= 4.1.12) - actionview (= 4.1.12) - activemodel (= 4.1.12) - activerecord (= 4.1.12) - activesupport (= 4.1.12) + rails (4.1.14) + actionmailer (= 4.1.14) + actionpack (= 4.1.14) + actionview (= 4.1.14) + activemodel (= 4.1.14) + activerecord (= 4.1.14) + activesupport (= 4.1.14) bundler (>= 1.3.0, < 2.0) - railties (= 4.1.12) + railties (= 4.1.14) sprockets-rails (~> 2.0) rails-observers (0.1.2) activemodel (~> 4.0) - railties (4.1.12) - actionpack (= 4.1.12) - activesupport (= 4.1.12) + railties (4.1.14) + actionpack (= 4.1.14) + activesupport (= 4.1.14) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (2.0.0) @@ -687,7 +687,7 @@ GEM multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.3.2) + sprockets-rails (2.3.3) actionpack (>= 3.0) activesupport (>= 3.0) sprockets (>= 2.8, < 4.0) @@ -886,7 +886,7 @@ DEPENDENCIES rack-attack (~> 4.3.0) rack-cors (~> 0.4.0) rack-oauth2 (~> 1.0.5) - rails (= 4.1.12) + rails (= 4.1.14) raphael-rails (~> 2.1.2) rblineprof rdoc (~> 3.6) From 618b54910ef3183cd1a3bf71ffe6301e029973fb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 19 Nov 2015 10:50:38 +0100 Subject: [PATCH 192/410] Improve UI for emoji awards Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/issuable.scss | 51 ++++++++++++------- .../projects/issues/_discussion.html.haml | 2 +- app/views/votes/_votes_block.html.haml | 4 +- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 7dd4f239c7..affa34a5f8 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -103,19 +103,23 @@ } .awards { + @include clearfix; + line-height: 32px; + margin: 5px 0; + .award { + @include border-radius(5px); + border: 1px solid; - padding: 1px 3px; - width: 50px; - border-radius: 5px; - float:left; - margin: 0 3px; - border-color: #ccc; + padding: 0px 10px; + float: left; + margin: 0 5px; + border-color: $border-color; cursor: pointer; &.active { - border-color: rgba(79,176,252,0.4); - background-color: rgba(79,176,252,0.1); + border-color: $border-gray-light; + background-color: $gray-light; .counter { font-weight: bold; @@ -126,28 +130,39 @@ float: left; margin-right: 10px; } + + .counter { + float: left; + } } .awards-controls { - height: 25px; - width: 28px; + line-height: 32px; + margin-left: 10px; float: left; - padding: 0 0 5px 5px; - line-height: 1; .add-award { - font-size: 27px; - color: #ccc; + font-size: 24px; + color: $gl-gray; + position: relative; + top: 2px; - &:hover { - text-decoration: none; - } + &:hover, &:link { text-decoration: none; } } + + .awards-menu { + padding: $gl-padding; + min-width: 214px; + + > li { + margin: 5px; + } + } } - + .awards-menu{ li { float: left; diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index c5fd863ae9..020952dd00 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -7,7 +7,7 @@ = render 'shared/show_aside' -.gray-content-block.second-block +.gray-content-block.second-block.oneline-block .row .col-md-9 .votes-holder.pull-right diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 3eadf209a5..f32e5193d1 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -8,7 +8,7 @@ .dropdown.awards-controls %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"} - = icon('plus-circle') + = icon('plus-square-o') %ul.dropdown-menu.awards-menu - emoji_list.each do |emoji| %li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px" @@ -27,4 +27,4 @@ emoji = $(this).find(".icon").data("emoji") awards_handler.addAward(emoji) - $(".award").tooltip() \ No newline at end of file + $(".award").tooltip() From caf1c9d85bb9df05b8e7f90e1a844fe8ecb5a857 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 19 Nov 2015 10:53:40 +0100 Subject: [PATCH 193/410] Fix CHANGELOG --- CHANGELOG | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 94f07a3168..3d0cd3550e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,9 @@ v 8.2.0 - Upgrade gitlab_git to 7.2.20 and rugged to 0.23.3 (Stan Hu) - Improved performance of finding users by one of their Email addresses - Add allow_failure field to commit status API (Stan Hu) + - Commits without .gitlab-ci.yml are marked as skipped + - Save detailed error when YAML syntax is invalid + - Added build artifacts - Improved performance of replacing references in comments - Show last project commit to default branch on project home page - Highlight comment based on anchor in URL @@ -119,7 +122,6 @@ v 8.1.0 - Show CI status on Your projects page and Starred projects page - Remove "Continuous Integration" page from dashboard - Add notes and SSL verification entries to hook APIs (Ben Boeckel) - - Added build artifacts - Fix grammar in admin area "labels" .nothing-here-block when no labels exist. - Move CI runners page to project settings area - Move CI variables page to project settings area From 2c7d8678623a9e207d54e2e39d7eef9e2f77cb47 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 19 Nov 2015 10:59:58 +0100 Subject: [PATCH 194/410] Few minor improvements to emoji awards UI Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/issuable.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index affa34a5f8..3a08ee70bc 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -104,8 +104,8 @@ .awards { @include clearfix; - line-height: 32px; - margin: 5px 0; + line-height: 34px; + margin: 2px 0; .award { @include border-radius(5px); @@ -137,7 +137,6 @@ } .awards-controls { - line-height: 32px; margin-left: 10px; float: left; From 96cdacd4eae3fb939f2da4ba0240f7dfa10b63da Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 19 Nov 2015 11:05:21 +0100 Subject: [PATCH 195/410] Changelog entries for atom/profile improvements --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 94f07a3168..c979c23e06 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,8 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) v 8.2.0 + - Improved performance of finding projects and groups in various places + - Improved performance of rendering user profile pages and Atom feeds - Fix grouping of contributors by email in graph. - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) - Fix Drone CI service template not saving properly (Stan Hu) From 50f654161036ac0f97d9f931dc11678a9509d083 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 9 Nov 2015 17:17:40 +0100 Subject: [PATCH 196/410] Added index on issues.state This field is queried when filtering issues and due to the lack of an index would end up triggering a sequence scan. --- db/migrate/20151109134526_add_issues_state_index.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20151109134526_add_issues_state_index.rb diff --git a/db/migrate/20151109134526_add_issues_state_index.rb b/db/migrate/20151109134526_add_issues_state_index.rb new file mode 100644 index 0000000000..1c4d2e3017 --- /dev/null +++ b/db/migrate/20151109134526_add_issues_state_index.rb @@ -0,0 +1,5 @@ +class AddIssuesStateIndex < ActiveRecord::Migration + def change + add_index :issues, :state + end +end From d63da890943c57694fab0dc250655a2bcd49fabc Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Mon, 9 Nov 2015 17:18:26 +0100 Subject: [PATCH 197/410] Added index on projects.visibility_level --- .../20151109134916_add_projects_visibility_level_index.rb | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 db/migrate/20151109134916_add_projects_visibility_level_index.rb diff --git a/db/migrate/20151109134916_add_projects_visibility_level_index.rb b/db/migrate/20151109134916_add_projects_visibility_level_index.rb new file mode 100644 index 0000000000..600b4bafd9 --- /dev/null +++ b/db/migrate/20151109134916_add_projects_visibility_level_index.rb @@ -0,0 +1,5 @@ +class AddProjectsVisibilityLevelIndex < ActiveRecord::Migration + def change + add_index :projects, :visibility_level + end +end From e4ae8b1385eecce2e4438f83017799644a8b92ce Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 11 Nov 2015 12:48:27 +0100 Subject: [PATCH 198/410] Updated DB schema with new issues/projects indexes --- db/schema.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 462d5ed3b2..d687e68d09 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -384,6 +384,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree + add_index "issues", ["state"], name: "index_issues_on_state", using: :btree add_index "issues", ["title"], name: "index_issues_on_title", using: :btree create_table "keys", force: true do |t| @@ -641,9 +642,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do t.integer "star_count", default: 0, null: false t.string "import_type" t.string "import_source" - t.integer "commit_count", default: 0 - t.boolean "merge_requests_ff_only_enabled", default: false - t.text "issues_template" + t.integer "commit_count", default: 0 t.text "import_error" end @@ -653,6 +652,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree add_index "projects", ["path"], name: "index_projects_on_path", using: :btree add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree + add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree create_table "protected_branches", force: true do |t| t.integer "project_id", null: false From 45840426a79f5ab251caf71400d3e4ed5f5eedbf Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 11 Nov 2015 12:48:56 +0100 Subject: [PATCH 199/410] Added benchmark for IssuesFinder --- spec/benchmarks/finders/issues_finder_spec.rb | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 spec/benchmarks/finders/issues_finder_spec.rb diff --git a/spec/benchmarks/finders/issues_finder_spec.rb b/spec/benchmarks/finders/issues_finder_spec.rb new file mode 100644 index 0000000000..c233fa82cf --- /dev/null +++ b/spec/benchmarks/finders/issues_finder_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe IssuesFinder, benchmark: true do + describe '#execute' do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + + let(:label1) { create(:label, project: project, title: 'A') } + let(:label2) { create(:label, project: project, title: 'B') } + + before do + 10.times do |n| + issue = create(:issue, author: user, project: project) + + if n > 4 + create(:label_link, label: label1, target: issue) + create(:label_link, label: label2, target: issue) + end + end + end + + describe 'retrieving issues without labels' do + let(:finder) do + IssuesFinder.new(user, scope: 'all', label_name: Label::None.title, + state: 'opened') + end + + benchmark_subject { finder.execute } + + it { is_expected.to iterate_per_second(2000) } + end + + describe 'retrieving issues with labels' do + let(:finder) do + IssuesFinder.new(user, scope: 'all', label_name: label1.title, + state: 'opened') + end + + benchmark_subject { finder.execute } + + it { is_expected.to iterate_per_second(1000) } + end + + describe 'retrieving issues for a single project' do + let(:finder) do + IssuesFinder.new(user, scope: 'all', label_name: Label::None.title, + state: 'opened', project_id: project.id) + end + + benchmark_subject { finder.execute } + + it { is_expected.to iterate_per_second(2000) } + end + end +end From c232a0f97f9b724a142c728ebeceba25ef15ab32 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 11 Nov 2015 12:49:16 +0100 Subject: [PATCH 200/410] Removed trailing whitespace from IssuableFinder --- app/finders/issuable_finder.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index c407dfc163..30ec8f6009 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -62,10 +62,10 @@ class IssuableFinder if project? @project = Project.find(params[:project_id]) - + unless Ability.abilities.allowed?(current_user, :read_project, @project) @project = nil - end + end else @project = nil end From e9cd58f5d50a7b5cfc14e08cd9526505e24f1071 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 11 Nov 2015 12:49:31 +0100 Subject: [PATCH 201/410] Memoize IssuableFinder#projects Since this method's returned data doesn't change between calls on the same IssuableFinder instance we can just memoize this similar to the "project" method. --- app/finders/issuable_finder.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 30ec8f6009..15b5d6ab34 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -77,11 +77,11 @@ class IssuableFinder return @projects if defined?(@projects) if project? - project + @projects = project elsif current_user && params[:authorized_only].presence && !current_user_related? - current_user.authorized_projects + @projects = current_user.authorized_projects else - ProjectsFinder.new.execute(current_user) + @projects = ProjectsFinder.new.execute(current_user) end end From 8591cc02be6b12ed60f763a5e0147f2cbbca99e1 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 11 Nov 2015 12:50:36 +0100 Subject: [PATCH 202/410] Use a JOIN in IssuableFinder#by_project When using IssuableFinder/IssuesFinder to find issues for multiple projects it's more efficient to use a JOIN + a "WHERE project_id IN" condition opposed to running a sub-query. This change means that when finding issues without labels we're now using the following SQL: SELECT issues.* FROM issues JOIN projects ON projects.id = issues.project_id LEFT JOIN label_links ON label_links.target_type = 'Issue' AND label_links.target_id = issues.id WHERE ( projects.id IN (...) OR projects.visibility_level IN (20, 10) ) AND issues.state IN ('opened','reopened') AND label_links.id IS NULL ORDER BY issues.id DESC; instead of: SELECT issues.* FROM issues LEFT JOIN label_links ON label_links.target_type = 'Issue' AND label_links.target_id = issues.id WHERE issues.project_id IN ( SELECT id FROM projects WHERE id IN (...) OR visibility_level IN (20,10) ) AND issues.state IN ('opened','reopened') AND label_links.id IS NULL ORDER BY issues.id DESC; The big benefit here is that in the last case PostgreSQL can't properly use all available indexes. In particular it ends up performing a sequence scan on the "label_links" table (processing around 290 000 rows). The new query is roughly 2x as fast as the old query. --- app/finders/issuable_finder.rb | 10 +++++++--- app/models/concerns/issuable.rb | 3 +++ app/models/merge_request.rb | 3 +++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 15b5d6ab34..3d5e8b6fbe 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -190,8 +190,10 @@ class IssuableFinder def by_project(items) items = - if projects - items.of_projects(projects).references(:project) + if project? + items.of_projects(projects).references_project + elsif projects + items.merge(projects.reorder(nil)).join_project else items.none end @@ -206,7 +208,9 @@ class IssuableFinder end def sort(items) - items.sort(params[:sort]) + # Ensure we always have an explicit sort order (instead of inheriting + # multiple orders when combining ActiveRecord::Relation objects). + params[:sort] ? items.sort(params[:sort]) : items.reorder(id: :desc) end def by_assignee(items) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 492a026add..97d3cb18f4 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -35,6 +35,9 @@ module Issuable scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') } scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') } + scope :join_project, -> { joins(:project) } + scope :references_project, -> { references(:project) } + delegate :name, :email, to: :author, diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 2eb03b8ba5..1e8d9908f0 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -134,6 +134,9 @@ class MergeRequest < ActiveRecord::Base scope :closed, -> { with_state(:closed) } scope :closed_and_merged, -> { with_states(:closed, :merged) } + scope :join_project, -> { joins(:target_project) } + scope :references_project, -> { references(:target_project) } + def self.reference_prefix '!' end From 901d3dee0fdd0f67b429dd3741fe047f69e96d4a Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 11 Nov 2015 13:00:41 +0100 Subject: [PATCH 203/410] Changelog entry for finding issues performance --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index ea8d5d6000..baf7c8e5ad 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.2.0 - Improved performance of finding projects and groups in various places - Improved performance of rendering user profile pages and Atom feeds - Fix grouping of contributors by email in graph. + - Improved performance of finding issues with/without labels - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) - Fix Drone CI service template not saving properly (Stan Hu) - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu) From 2b907f61ff5db3ff68b27a9d3bb164745ab7703b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Wed, 18 Nov 2015 16:32:00 +0100 Subject: [PATCH 204/410] Commits without .gitlab-ci.yml are marked as skipped - Save detailed error when YAML syntax --- app/models/ci/commit.rb | 5 ++++- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 6 +++++- .../services/ci/create_commit_service_spec.rb | 20 ++++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 33b5717392..73c1570c21 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -188,12 +188,15 @@ module Ci end def config_processor + return nil unless ci_yaml_file @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, gl_project.path_with_namespace) rescue Ci::GitlabCiYamlProcessor::ValidationError => e save_yaml_error(e.message) nil + rescue Psych::SyntaxError => e + save_yaml_error(e.message) + nil rescue Exception => e - logger.error e.message + "\n" + e.backtrace.join("\n") save_yaml_error("Undefined yaml error") nil end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 7d90f9877c..6f287719ba 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -425,8 +425,12 @@ module Ci end describe "Error handling" do + it "fails to parse YAML" do + expect{GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError) + end + it "indicates that object is invalid" do - expect{GitlabCiYamlProcessor.new("invalid_yaml\n!ccdvlf%612334@@@@")}.to raise_error(GitlabCiYamlProcessor::ValidationError) + expect{GitlabCiYamlProcessor.new("invalid_yaml")}.to raise_error(GitlabCiYamlProcessor::ValidationError) end it "returns errors if tags parameter is invalid" do diff --git a/spec/services/ci/create_commit_service_spec.rb b/spec/services/ci/create_commit_service_spec.rb index e3a8fe9681..392f5fce35 100644 --- a/spec/services/ci/create_commit_service_spec.rb +++ b/spec/services/ci/create_commit_service_spec.rb @@ -100,7 +100,7 @@ module Ci end it "skips builds creation if there is [ci skip] tag in commit message and yaml is invalid" do - stub_ci_commit_yaml_file('invalid: file') + stub_ci_commit_yaml_file('invalid: file: fiile') commits = [{ message: message }] commit = service.execute(project, user, ref: 'refs/tags/0_1', @@ -110,6 +110,24 @@ module Ci ) expect(commit.builds.any?).to be false expect(commit.status).to eq("skipped") + expect(commit.yaml_errors).to be_nil + end + end + + describe :config_processor do + it "skips builds creation if yaml is invalid" do + allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { "message" } + stub_ci_commit_yaml_file('invalid: file: file') + commits = [{ message: message }] + commit = service.execute(project, user, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits + ) + expect(commit.builds.any?).to be false + expect(commit.status).to eq("skipped") + expect(commit.yaml_errors).to_not be_nil end end From 0df7a32ea50baf251f03e6bfc5b91c5ccb68aad0 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 19 Nov 2015 12:08:30 +0100 Subject: [PATCH 205/410] Fix tests --- app/models/ci/commit.rb | 2 +- .../services/ci/create_commit_service_spec.rb | 38 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 73c1570c21..b0c78499e4 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -196,7 +196,7 @@ module Ci rescue Psych::SyntaxError => e save_yaml_error(e.message) nil - rescue Exception => e + rescue Exception save_yaml_error("Undefined yaml error") nil end diff --git a/spec/services/ci/create_commit_service_spec.rb b/spec/services/ci/create_commit_service_spec.rb index 392f5fce35..58d81c46a6 100644 --- a/spec/services/ci/create_commit_service_spec.rb +++ b/spec/services/ci/create_commit_service_spec.rb @@ -53,7 +53,7 @@ module Ci end end - it 'fails commits without .gitlab-ci.yml' do + it 'skips commits without .gitlab-ci.yml' do stub_ci_commit_yaml_file(nil) result = service.execute(project, user, ref: 'refs/heads/0_1', @@ -63,7 +63,24 @@ module Ci ) expect(result).to be_persisted expect(result.builds.any?).to be_falsey - expect(result.status).to eq('failed') + expect(result.status).to eq('skipped') + expect(commit.yaml_errors).to_not be_nil + end + + it 'skips commits if yaml is invalid' do + message = 'message' + allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message } + stub_ci_commit_yaml_file('invalid: file: file') + commits = [{ message: message }] + commit = service.execute(project, user, + ref: 'refs/tags/0_1', + before: '00000000', + after: '31das312', + commits: commits + ) + expect(commit.builds.any?).to be false + expect(commit.status).to eq('skipped') + expect(commit.yaml_errors).to_not be_nil end describe :ci_skip? do @@ -114,23 +131,6 @@ module Ci end end - describe :config_processor do - it "skips builds creation if yaml is invalid" do - allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { "message" } - stub_ci_commit_yaml_file('invalid: file: file') - commits = [{ message: message }] - commit = service.execute(project, user, - ref: 'refs/tags/0_1', - before: '00000000', - after: '31das312', - commits: commits - ) - expect(commit.builds.any?).to be false - expect(commit.status).to eq("skipped") - expect(commit.yaml_errors).to_not be_nil - end - end - it "skips build creation if there are already builds" do allow_any_instance_of(Ci::Commit).to receive(:ci_yaml_file) { gitlab_ci_yaml } From fd6b58949652c5fb9925c061fae823da7e468ca2 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 19 Nov 2015 12:09:24 +0100 Subject: [PATCH 206/410] Since GitLab CI is enabled by default, remove enabling it by pushing .gitlab-ci.yml --- CHANGELOG | 1 + app/services/git_push_service.rb | 12 ------------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ea8d5d6000..01f18dea23 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.2.0 - Add allow_failure field to commit status API (Stan Hu) - Commits without .gitlab-ci.yml are marked as skipped - Save detailed error when YAML syntax is invalid + - Since GitLab CI is enabled by default, remove enabling it by pushing .gitlab-ci.yml - Added build artifacts - Improved performance of replacing references in comments - Show last project commit to default branch on project home page diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index ccb6b97858..f11690aa3f 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -58,12 +58,6 @@ class GitPushService @push_data = build_push_data(oldrev, newrev, ref) - # If CI was disabled but .gitlab-ci.yml file was pushed - # we enable CI automatically - if !project.builds_enabled? && gitlab_ci_yaml?(newrev) - project.enable_ci - end - EventCreateService.new.push(project, user, @push_data) project.execute_hooks(@push_data.dup, :push_hooks) project.execute_services(@push_data.dup, :push_hooks) @@ -134,10 +128,4 @@ class GitPushService def commit_user(commit) commit.author || user end - - def gitlab_ci_yaml?(sha) - @project.repository.blob_at(sha, '.gitlab-ci.yml') - rescue Rugged::ReferenceError - nil - end end From 1f9a0bd764ed2935c4438dbc81001b0d69df26ea Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 19 Nov 2015 12:15:54 +0100 Subject: [PATCH 207/410] Bump GitLab Workhorse version to 0.4.2 --- GITLAB_WORKHORSE | 1 - GITLAB_WORKHORSE_VERSION | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 GITLAB_WORKHORSE diff --git a/GITLAB_WORKHORSE b/GITLAB_WORKHORSE deleted file mode 100644 index 267577d47e..0000000000 --- a/GITLAB_WORKHORSE +++ /dev/null @@ -1 +0,0 @@ -0.4.1 diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index 9e11b32fca..2b7c5ae018 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -0.3.1 +0.4.2 From 23c5473cc0bd9b9034c5671fbea1356b0fb531e5 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 19 Nov 2015 13:20:09 +0200 Subject: [PATCH 208/410] added spinach tests --- app/assets/javascripts/awards_handler.coffee | 4 +- app/helpers/issues_helper.rb | 6 +- app/models/note.rb | 1 + app/views/votes/_votes_block.html.haml | 38 +++++----- features/project/issues/award_emoji.feature | 14 ++++ features/steps/project/issues/award_emoji.rb | 41 +++++++++++ spec/models/note_spec.rb | 75 -------------------- 7 files changed, 83 insertions(+), 96 deletions(-) create mode 100644 features/project/issues/award_emoji.feature create mode 100644 features/steps/project/issues/award_emoji.rb diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index cac9c10332..f5b9adbe9e 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -75,7 +75,7 @@ class @AwardsHandler if custom_path $(".awards-menu li").first().html().replace(/emoji\/.*\.png/, custom_path) else - $("li[data-emoji='" + emoji + "'").html() + $("li[data-emoji='" + emoji + "']").html() postEmoji: (emoji, callback) -> @@ -88,4 +88,4 @@ class @AwardsHandler callback.call() findEmojiIcon: (emoji) -> - $(".icon[data-emoji='" + emoji + "'") \ No newline at end of file + $(".icon[data-emoji='" + emoji + "']") \ No newline at end of file diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 3a238824c0..2c791aa568 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -105,7 +105,11 @@ module IssuesHelper end def note_active_class(notes, current_user) - notes.pluck(:author_id).include?(current_user.id) ? "active" : "" + if current_user && notes.pluck(:author_id).include?(current_user.id) + "active" + else + "" + end end # Required for Gitlab::Markdown::IssueReferenceFilter diff --git a/app/models/note.rb b/app/models/note.rb index d53f568a67..39645f8f1a 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -46,6 +46,7 @@ class Note < ActiveRecord::Base validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' } validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' } + validates :author, presence: true, if: ->(n) { n.is_award } mount_uploader :attachment, AttachmentUploader diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index f32e5193d1..04e3b5a381 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -6,25 +6,27 @@ .counter = note.last.count - .dropdown.awards-controls - %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"} - = icon('plus-square-o') - %ul.dropdown-menu.awards-menu - - emoji_list.each do |emoji| - %li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px" + - if current_user + .dropdown.awards-controls + %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"} + = icon('plus-square-o') + %ul.dropdown-menu.awards-menu + - emoji_list.each do |emoji| + %li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px" -:coffeescript - post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}" - noteable_type = "#{votable.class}" - noteable_id = "#{votable.id}" - window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id) +- if current_user + :coffeescript + post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}" + noteable_type = "#{votable.class}" + noteable_id = "#{votable.id}" + window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id) - $(".awards-menu li").click (e)-> - emoji = $(this).data("emoji") - awards_handler.addAward(emoji) + $(".awards-menu li").click (e)-> + emoji = $(this).data("emoji") + awards_handler.addAward(emoji) - $(".awards").on "click", ".award", (e)-> - emoji = $(this).find(".icon").data("emoji") - awards_handler.addAward(emoji) + $(".awards").on "click", ".award", (e)-> + emoji = $(this).find(".icon").data("emoji") + awards_handler.addAward(emoji) - $(".award").tooltip() + $(".award").tooltip() diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature new file mode 100644 index 0000000000..a9bc8ffb9b --- /dev/null +++ b/features/project/issues/award_emoji.feature @@ -0,0 +1,14 @@ +Feature: Award Emoji + Background: + Given I sign in as a user + And I own project "Shop" + And project "Shop" has issue "Bugfix" + And I visit "Bugfix" issue page + + @javascript + Scenario: I add and remove award in the issue + Given I click to emoji-picker + And I click to emoji in the picker + Then I have award added + And I can remove it by clicking to icon + \ No newline at end of file diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb new file mode 100644 index 0000000000..8f7a45dec0 --- /dev/null +++ b/features/steps/project/issues/award_emoji.rb @@ -0,0 +1,41 @@ +class Spinach::Features::AwardEmoji < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + include Select2Helper + + step 'I visit "Bugfix" issue page' do + visit namespace_project_issue_path(@project.namespace, @project, @issue) + end + + step 'I click to emoji-picker' do + page.within ".awards-controls" do + page.find(".add-award").click + end + end + + step 'I click to emoji in the picker' do + page.within ".awards-menu" do + page.first("img").click + end + end + + step 'I can remove it by clicking to icon' do + page.within ".awards" do + page.first(".award").click + expect(page).to_not have_selector ".award" + end + end + + step 'I have award added' do + page.within ".awards" do + expect(page).to have_selector ".award" + expect(page.find(".award .counter")).to have_content "1" + end + end + + step 'project "Shop" has issue "Bugfix"' do + @project = Project.find_by(name: "Shop") + @issue = create(:issue, title: "Bugfix", project: project) + end +end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 75564839dc..6e37dae07d 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -32,77 +32,6 @@ describe Note do it { is_expected.to validate_presence_of(:project) } end - describe '#votable?' do - it 'is true for issue notes' do - note = build(:note_on_issue) - expect(note).to be_votable - end - - it 'is true for merge request notes' do - note = build(:note_on_merge_request) - expect(note).to be_votable - end - - it 'is false for merge request diff notes' do - note = build(:note_on_merge_request_diff) - expect(note).not_to be_votable - end - - it 'is false for commit notes' do - note = build(:note_on_commit) - expect(note).not_to be_votable - end - - it 'is false for commit diff notes' do - note = build(:note_on_commit_diff) - expect(note).not_to be_votable - end - end - - describe 'voting score' do - it 'recognizes a neutral note' do - note = build(:votable_note, note: 'This is not a +1 note') - expect(note).not_to be_upvote - expect(note).not_to be_downvote - end - - it 'recognizes a neutral emoji note' do - note = build(:votable_note, note: "I would :+1: this, but I don't want to") - expect(note).not_to be_upvote - expect(note).not_to be_downvote - end - - it 'recognizes a +1 note' do - note = build(:votable_note, note: '+1 for this') - expect(note).to be_upvote - end - - it 'recognizes a +1 emoji as a vote' do - note = build(:votable_note, note: ':+1: for this') - expect(note).to be_upvote - end - - it 'recognizes a thumbsup emoji as a vote' do - note = build(:votable_note, note: ':thumbsup: for this') - expect(note).to be_upvote - end - - it 'recognizes a -1 note' do - note = build(:votable_note, note: '-1 for this') - expect(note).to be_downvote - end - - it 'recognizes a -1 emoji as a vote' do - note = build(:votable_note, note: ':-1: for this') - expect(note).to be_downvote - end - - it 'recognizes a thumbsdown emoji as a vote' do - note = build(:votable_note, note: ':thumbsdown: for this') - expect(note).to be_downvote - end - end - describe "Commit notes" do let!(:note) { create(:note_on_commit, note: "+1 from me") } let!(:commit) { note.noteable } @@ -139,10 +68,6 @@ describe Note do it "should be recognized by #for_commit_diff_line?" do expect(note).to be_for_commit_diff_line end - - it "should not be votable" do - expect(note).not_to be_votable - end end describe 'authorization' do From 671a49cfd53230b57acf579a609bab958e066982 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 19 Nov 2015 14:41:05 +0200 Subject: [PATCH 209/410] added specs --- spec/helpers/issues_helper_spec.rb | 26 +++++++++++++++++ spec/models/note_spec.rb | 13 +++++++++ spec/services/notes/create_service_spec.rb | 34 ++++++++++++++++++++++ 3 files changed, 73 insertions(+) diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 78a6b631eb..1f2c4ee77b 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -127,4 +127,30 @@ describe IssuesHelper do it { is_expected.to eq("!1, !2, or !3") } end + describe "#url_to_emoji" do + it "returns url" do + expect(url_to_emoji("smile")).to include("emoji/1F604.png") + end + end + + describe "#emoji_list" do + it "returns url" do + expect(emoji_list).to be_kind_of(Array) + end + end + + describe "#note_active_class" do + before do + @note = create :note + @note1 = create :note + end + + it "returns empty string for unauthenticated user" do + expect(note_active_class(Note.all, nil)).to eq("") + end + + it "returns active string for author" do + expect(note_active_class(Note.all, @note.author)).to eq("active") + end + end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 6e37dae07d..86b6c27a18 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -129,4 +129,17 @@ describe Note do it { expect(Note.search('wow')).to include(note) } end + + describe :grouped_awards do + before do + create :note, note: "smile" + create :note, note: "smile" + end + + it "returns grouped array of notes" do + grouped_array = Note.grouped_awards + expect(Note.grouped_awards.first.first).to eq("smile") + expect(Note.grouped_awards.first.last).to match_array(Note.all) + end + end end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index f2ea0805b2..066461f5a1 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -24,4 +24,38 @@ describe Notes::CreateService do it { expect(@note.note).to eq('Awesome comment') } end end + + describe "award emoji" do + before do + project.team << [user, :master] + end + + it "creates emoji note" do + opts = { + note: ':smile: ', + noteable_type: 'Issue', + noteable_id: issue.id + } + + @note = Notes::CreateService.new(project, user, opts).execute + + expect(@note).to be_valid + expect(@note.note).to eq('smile') + expect(@note.is_award).to be_truthy + end + + it "creates regular note if emoji name is invalid" do + opts = { + note: ':smile: moretext: ', + noteable_type: 'Issue', + noteable_id: issue.id + } + + @note = Notes::CreateService.new(project, user, opts).execute + + expect(@note).to be_valid + expect(@note.note).to eq(opts[:note]) + expect(@note.is_award).to be_falsy + end + end end From 372dcc217eeb379f603c10c81115dd7579762b59 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 19 Nov 2015 13:51:18 +0100 Subject: [PATCH 210/410] Do not limit workhorse POST/PUT size in NGINX Limiting, if any, should happen in gitlab-workhorse. --- lib/support/nginx/gitlab | 4 +--- lib/support/nginx/gitlab-ssl | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 93f2ad07ae..f38e2e6209 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -114,7 +114,6 @@ server { } location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects { - client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; @@ -140,7 +139,6 @@ server { # Build artifacts should be submitted to this location location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { - client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; @@ -148,13 +146,13 @@ server { # Build artifacts should be submitted to this location location ~ /ci/api/v1/builds/[0-9]+/artifacts { - client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; } location @gitlab-workhorse { + client_max_body_size 0; ## If you use HTTPS make sure you disable gzip compression ## to be safe against BREACH attack. # gzip off; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 90749947fa..4cc6a17db0 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -161,7 +161,6 @@ server { } location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects { - client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; @@ -187,7 +186,6 @@ server { # Build artifacts should be submitted to this location location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { - client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; @@ -195,13 +193,13 @@ server { # Build artifacts should be submitted to this location location ~ /ci/api/v1/builds/[0-9]+/artifacts { - client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; } location @gitlab-workhorse { + client_max_body_size 0; ## If you use HTTPS make sure you disable gzip compression ## to be safe against BREACH attack. gzip off; From d70da301f61f3333b65a5b92de79eea4b7e032f6 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 19 Nov 2015 14:13:17 +0100 Subject: [PATCH 211/410] Add link to git-lfs client [ci skip] --- doc/workflow/git_lfs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/workflow/git_lfs.md b/doc/workflow/git_lfs.md index 4990a9b5aa..e1064051fe 100644 --- a/doc/workflow/git_lfs.md +++ b/doc/workflow/git_lfs.md @@ -17,7 +17,7 @@ Once the request is authorized, Git LFS client receives instructions from where ## Requirements * Git LFS is supported in GitLab starting with version 8.2 -* Git LFS client version 0.6.0 and up +* Git LFS [client](https://git-lfs.github.com) version 0.6.0 and up ## GitLab and Git LFS From 97842cea7409d80a3bf22f3ab8993a2ce3b02195 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 19 Nov 2015 15:37:20 +0100 Subject: [PATCH 212/410] Fix 'Attach a file' link in new tag form --- app/views/projects/_zen.html.haml | 9 ++++++--- app/views/projects/releases/edit.html.haml | 3 +-- app/views/projects/tags/new.html.haml | 13 ++----------- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml index 63ebfc9381..7e6301abde 100644 --- a/app/views/projects/_zen.html.haml +++ b/app/views/projects/_zen.html.haml @@ -2,9 +2,12 @@ %input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox") .zen-backdrop - classes << ' js-gfm-input markdown-area' - = f.text_area attr, class: classes, placeholder: '' + - if defined?(f) && f + = f.text_area attr, class: classes, placeholder: '' + - else + = text_area_tag attr, nil, class: classes, placeholder: '' %a.zen-enter-link(tabindex="-1" href="#") - %i.fa.fa-expand + = icon('expand') Edit in fullscreen %a.zen-leave-link(href="#") - %i.fa.fa-compress + = icon('compress') diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index e7db09cdaa..f516b65ecd 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -11,10 +11,9 @@ .prepend-top-default = form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form' }) do |f| = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - = render 'projects/zen', f: f, attr: :description, classes: 'description js-quick-submit' + = render 'projects/zen', f: f, attr: :description, classes: 'description js-quick-submit form-control' = render 'projects/notes/hints' .error-alert .prepend-top-default = f.submit 'Save changes', class: 'btn btn-save' = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel" - diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index e106be794f..86aa15dc5b 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -10,7 +10,7 @@ New git tag %hr -= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal tag-form" do += form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form" do .form-group = label_tag :tag_name, 'Name for new tag', class: 'control-label' .col-sm-10 @@ -30,16 +30,7 @@ = label_tag :release_description, 'Release notes', class: 'control-label' .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do - .zennable - %input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox") - .zen-backdrop - = text_area_tag :release_description, nil, class: 'js-gfm-input markdown-area description js-quick-submit form-control', placeholder: '' - %a.zen-enter-link(tabindex="-1" href="#") - = icon('expand') - Edit in fullscreen - %a.zen-leave-link(href="#") - = icon('compress') - + = render 'projects/zen', attr: :release_description, classes: 'description js-quick-submit form-control' = render 'projects/notes/hints' .help-block (Optional) You can add release notes to your tag. It will be stored in the GitLab database and shown on the tags page .form-actions From a5b10196e649b57cf7cc698fe6fb34f73eec47ea Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 19 Nov 2015 15:56:03 +0100 Subject: [PATCH 213/410] Fix tests --- spec/services/ci/create_commit_service_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/services/ci/create_commit_service_spec.rb b/spec/services/ci/create_commit_service_spec.rb index 58d81c46a6..e0ede1d58b 100644 --- a/spec/services/ci/create_commit_service_spec.rb +++ b/spec/services/ci/create_commit_service_spec.rb @@ -64,7 +64,7 @@ module Ci expect(result).to be_persisted expect(result.builds.any?).to be_falsey expect(result.status).to eq('skipped') - expect(commit.yaml_errors).to_not be_nil + expect(result.yaml_errors).to be_nil end it 'skips commits if yaml is invalid' do @@ -79,7 +79,7 @@ module Ci commits: commits ) expect(commit.builds.any?).to be false - expect(commit.status).to eq('skipped') + expect(commit.status).to eq('failed') expect(commit.yaml_errors).to_not be_nil end From cf61b8e22c9c78165ce55c7c2f78ae0b3c7af7aa Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 19 Nov 2015 16:01:55 +0100 Subject: [PATCH 214/410] Update gitlab-workhorse docs to 0.4.2 --- doc/install/installation.md | 4 ++-- doc/update/8.1-to-8.2.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 7ef46b0406..71b0ef3ebb 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -327,12 +327,12 @@ GitLab Shell is an SSH access and repository management software developed speci cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse - sudo -u git -H git checkout 0.4.1 + sudo -u git -H git checkout 0.4.2 sudo -u git -H make ### Initialize Database and Activate Advanced Features - # Go to Gitlab installation folder + # Go to GitLab installation folder cd /home/git/gitlab diff --git a/doc/update/8.1-to-8.2.md b/doc/update/8.1-to-8.2.md index 73d899f9c2..81e13f714b 100644 --- a/doc/update/8.1-to-8.2.md +++ b/doc/update/8.1-to-8.2.md @@ -81,7 +81,7 @@ from GitLab 8.1. cd /home/git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git cd gitlab-workhorse -sudo -u git -H git checkout 0.4.1 +sudo -u git -H git checkout 0.4.2 sudo -u git -H make ``` From 094e1cc01b4f98ea4b8cd664344f3b8b583af471 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 19 Nov 2015 16:02:21 +0100 Subject: [PATCH 215/410] Align hash literals in IssuesFinder spec --- spec/benchmarks/finders/issues_finder_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/benchmarks/finders/issues_finder_spec.rb b/spec/benchmarks/finders/issues_finder_spec.rb index c233fa82cf..b57a33004a 100644 --- a/spec/benchmarks/finders/issues_finder_spec.rb +++ b/spec/benchmarks/finders/issues_finder_spec.rb @@ -22,7 +22,7 @@ describe IssuesFinder, benchmark: true do describe 'retrieving issues without labels' do let(:finder) do IssuesFinder.new(user, scope: 'all', label_name: Label::None.title, - state: 'opened') + state: 'opened') end benchmark_subject { finder.execute } @@ -33,7 +33,7 @@ describe IssuesFinder, benchmark: true do describe 'retrieving issues with labels' do let(:finder) do IssuesFinder.new(user, scope: 'all', label_name: label1.title, - state: 'opened') + state: 'opened') end benchmark_subject { finder.execute } @@ -44,7 +44,7 @@ describe IssuesFinder, benchmark: true do describe 'retrieving issues for a single project' do let(:finder) do IssuesFinder.new(user, scope: 'all', label_name: Label::None.title, - state: 'opened', project_id: project.id) + state: 'opened', project_id: project.id) end benchmark_subject { finder.execute } From aec4aac0c2825befe8baff392107dcf7702d3d04 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 19 Nov 2015 16:06:13 +0100 Subject: [PATCH 216/410] Update gitlab-shell documentation [ci skip] --- doc/install/installation.md | 2 +- doc/update/8.1-to-8.2.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 7ef46b0406..52ae30af80 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -312,7 +312,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da GitLab Shell is an SSH access and repository management software developed specially for GitLab. # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed): - sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.6] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production + sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.7] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production # By default, the gitlab-shell config is generated from your main GitLab config. # You can review (and modify) the gitlab-shell config as follows: diff --git a/doc/update/8.1-to-8.2.md b/doc/update/8.1-to-8.2.md index 73d899f9c2..261031442a 100644 --- a/doc/update/8.1-to-8.2.md +++ b/doc/update/8.1-to-8.2.md @@ -68,7 +68,7 @@ sudo -u git -H git checkout 8-2-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v2.6.6 +sudo -u git -H git checkout v2.6.7 ``` ### 5. Replace gitlab-git-http-server with gitlab-workhorse From 52bcfd037fdc17bd73a674c248045dc750cc55c6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 19 Nov 2015 17:18:07 +0200 Subject: [PATCH 217/410] Update public access documentation [ci skip] --- doc/public_access/public_access.md | 51 +++++++++++++++++++----------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md index bd439f7c6f..6e22ea7b72 100644 --- a/doc/public_access/public_access.md +++ b/doc/public_access/public_access.md @@ -1,44 +1,59 @@ # Public access -GitLab allows you to open selected projects to be accessed **publicly** or **internally**. +GitLab allows you to change your projects' visibility in order be accessed +**publicly** or **internally**. -Projects with either of these visibility levels will be listed in the [public access directory](/public). +Projects with either of these visibility levels will be listed in the +public access directory (`/public` under your GitLab instance). +Here is the [GitLab.com example](https://gitlab.com/public). Internal projects will only be available to authenticated users. -## Public projects +## Visibility of projects + +### Public projects Public projects can be cloned **without any** authentication. -It will also be listed on the [public access directory](/public). +They will also be listed on the public access directory (`/public`). -**Any logged in user** will have [Guest](../permissions/permissions) permissions on the repository. +**Any logged in user** will have [Guest](../permissions/permissions) +permissions on the repository. -## Internal projects +### Internal projects Internal projects can be cloned by any logged in user. -It will also be listed on the [public access directory](/public) for logged in users. +They will also be listed on the public access directory (`/public`) for logged +in users. -Any logged in user will have [Guest](../permissions/permissions) permissions on the repository. +Any logged in user will have [Guest](../permissions/permissions) permissions on +the repository. -## How to change project visibility +### How to change project visibility -1. Go to your project dashboard -1. Click on the "Edit" tab -1. Change "Visibility Level" +1. Go to your project's **Settings** +1. Change "Visibility Level" to either Public, Internal or Private ## Visibility of users -The public page of users, located at `/u/username` is visible if either: +The public page of a user, located at `/u/username`, is always visible whether +you are logged in or not. -- You are logged in. -- You are logged out, and the target user is authorized to (is Guest, Reporter, etc.) at least one public project. +When visiting the public page of a user, you can only see the projects which +you are privileged to. -Otherwise, you will be redirected to the sign in page. +## Visibility of groups -When visiting the public page of an user, you will only see listed projects which you can view yourself. +The public page of a group, located at `/groups/groupname`, is always visible +to everyone. + +Logged out users will be able to see the description and the avatar of the +group as well as all public projects belonging to that group. ## Restricting the use of public or internal projects -In the Admin area under Settings you can disable public projects or public and internal projects for the entire GitLab installation to prevent people making code public by accident. The restricted visibility settings do not apply to admin users. +In the Admin area under **Settings** (`/admin/application_settings`), you can +restrict the use of visibility levels for users when they create a project or a +snippet. This is useful to prevent people exposing their repositories to public +by accident. The restricted visibility settings do not apply to admin users. From de440ee5e3fd03d0e3de33c474b1889e03aaf04a Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 19 Nov 2015 16:25:17 +0100 Subject: [PATCH 218/410] More GitLab spellings. No dedicated CI in omnibus [ci skip] --- CONTRIBUTING.md | 10 +++++----- README.md | 2 -- doc/install/installation.md | 2 +- doc/release/monthly.md | 2 +- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5d85c9f3fc..007e410b67 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -23,8 +23,8 @@ Issues and merge requests should be in English and contain appropriate language ## Helping others Please help other GitLab users when you can. -The channnels people will reach out on can be found on the [getting help page](https://about.gitlab.com/getting-help/). -Sign up for the mailinglist, answer GitLab questions on StackOverflow or respond in the irc channel. +The channels people will reach out on can be found on the [getting help page](https://about.gitlab.com/getting-help/). +Sign up for the mailinglist, answer GitLab questions on StackOverflow or respond in the IRC channel. You can also sign up on [CodeTriage](http://www.codetriage.com/gitlabhq/gitlabhq) to help with one issue every day. ## Issue tracker @@ -59,7 +59,7 @@ We welcome merge requests with fixes and improvements to GitLab code, tests, and Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests) or [github.com](https://github.com/gitlabhq/gitlabhq/pulls). -If you are new to GitLab development (or web development in general), search for the label `easyfix` ([gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [github](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint. +If you are new to GitLab development (or web development in general), search for the label `easyfix` ([GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [GitHub](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint. To start with GitLab download the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit) and see [Development section](doc/development/README.md) in the help file. @@ -99,7 +99,7 @@ If you contribute to GitLab please know that changes involve more than just code We have the following [definition of done](http://guide.agilealliance.org/guide/definition-of-done.html). Please ensure you support the feature you contribute through all of these steps. -1. Description explaning the relevancy (see following item) +1. Description explaining the relevancy (see following item) 1. Working and clean code that is commented where needed 1. Unit and integration tests that pass on the CI server 1. Documented in the /doc directory @@ -163,7 +163,7 @@ If you add a dependency in GitLab (such as an operating system package) please c 1. [Markdown](http://www.cirosantilli.com/markdown-styleguide) 1. [Database Migrations](doc/development/migration_style_guide.md) 1. [Documentation styleguide](doc_styleguide.md) -1. Interface text should be written subjectively instead of objectively. It should be the gitlab core team addressing a person. It should be written in present time and never use past tense (has been/was). For example instead of "prohibited this user from being saved due to the following errors:" the text should be "sorry, we could not create your account because:". Also these [excellent writing guidelines](https://github.com/NARKOZ/guides#writing). +1. Interface text should be written subjectively instead of objectively. It should be the GitLab core team addressing a person. It should be written in present time and never use past tense (has been/was). For example instead of "prohibited this user from being saved due to the following errors:" the text should be "sorry, we could not create your account because:". Also these [excellent writing guidelines](https://github.com/NARKOZ/guides#writing). This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com). diff --git a/README.md b/README.md index 52e2d97762..c59c8593eb 100644 --- a/README.md +++ b/README.md @@ -27,8 +27,6 @@ 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/). -Included with the GitLab Omnibus Packages is [GitLab CI](https://about.gitlab.com/gitlab-ci/) that can easily build, test and deploy code. - ## Website On [about.gitlab.com](https://about.gitlab.com/) you can find more information about: diff --git a/doc/install/installation.md b/doc/install/installation.md index 52ae30af80..a710407d4f 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -320,7 +320,7 @@ GitLab Shell is an SSH access and repository management software developed speci **Note:** If you want to use HTTPS, see [Using HTTPS](#using-https) for the additional steps. -**Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up gitlab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)". +**Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up GitLab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)". ### Install gitlab-workhorse diff --git a/doc/release/monthly.md b/doc/release/monthly.md index c9ab87671d..aff3f066b2 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -159,7 +159,7 @@ Please do not raise issues directly in this issue but link to issues that might The decision to create a patch release or not is with the release manager who is assigned to this issue. The release manager will comment here about the plans for patch releases. -Assign the issue to the release manager and at mention all members of gitlab core team. If there are any known bugs in the release add them immediately. +Assign the issue to the release manager and at mention all members of GitLab core team. If there are any known bugs in the release add them immediately. ## Tweet about RC1 From 91a76957e3d18e3cb89bc8320f8513e1002e551e Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 19 Nov 2015 17:05:30 +0100 Subject: [PATCH 219/410] Update LFS docs for cloning [ci skip] --- doc/workflow/git_lfs.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/workflow/git_lfs.md b/doc/workflow/git_lfs.md index e1064051fe..616a71522a 100644 --- a/doc/workflow/git_lfs.md +++ b/doc/workflow/git_lfs.md @@ -55,7 +55,7 @@ In `config/gitlab.yml`: * When SSH is set as a remote, Git LFS objects still go through HTTPS * Any Git LFS request will ask for HTTPS credentials to be provided so good Git credentials store is recommended * Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported -* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the url to Git config manually (see #troubleshooting-tips) +* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the URL to Git config manually (see #troubleshooting-tips) ## Using Git LFS @@ -77,11 +77,10 @@ git commit -am "Added Debian iso" # commit the file meta data git push origin master # sync the git repo and large file to the GitLab server ``` -Downloading a single large file is also very simple: +Cloning the repository works the same as before. Git automatically detects the LFS-tracked files and clones them via HTTP. If you performed the git clone command with a SSH URL, you have to enter your GitLab credentials for HTTP authentication. ```bash git clone git@gitlab.example.com:group/project.git -git lfs fetch debian.iso # download the large file ``` From bdf4007cb7b18ed6892455d0a9adf78476188563 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 19 Nov 2015 18:12:17 +0200 Subject: [PATCH 220/410] adressing comments --- app/assets/javascripts/awards_handler.coffee | 6 +++--- app/controllers/projects/notes_controller.rb | 12 ++++++------ app/models/note.rb | 5 +++-- app/services/notes/create_service.rb | 4 ++-- app/views/votes/_votes_block.html.haml | 14 +++++++------- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index f5b9adbe9e..ae42e390c4 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -79,11 +79,11 @@ class @AwardsHandler postEmoji: (emoji, callback) -> - $.post @post_emoji_url, { - emoji: emoji + $.post @post_emoji_url, { note: { + note: emoji noteable_type: @noteable_type noteable_id: @noteable_id - },(data) -> + }},(data) -> if data.ok callback.call() diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 8159cc5083..263b8b8d94 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -59,21 +59,21 @@ class Projects::NotesController < Projects::ApplicationController end def award_toggle - noteable = params[:noteable_type] == "Issue" ? Issue : MergeRequest - noteable = noteable.find(params[:noteable_id]) + noteable = note_params[:noteable_type] == "issue" ? Issue : MergeRequest + noteable = noteable.find_by!(id: note_params[:noteable_id], project: project) + data = { - noteable: noteable, author: current_user, is_award: true, - note: params[:emoji] + note: note_params[:note] } - note = project.notes.find_by(data) + note = noteable.notes.find_by(data) if note note.destroy else - project.notes.create(data) + Notes::CreateService.new(project, current_user, note_params).execute end render json: { ok: true } diff --git a/app/models/note.rb b/app/models/note.rb index 39645f8f1a..e30be444eb 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -40,13 +40,14 @@ class Note < ActiveRecord::Base delegate :name, :email, to: :author, prefix: true validates :note, :project, presence: true + validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award } validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true # Attachments are deprecated and are handled by Markdown uploader validates :attachment, file_size: { maximum: :max_attachment_size } validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' } validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' } - validates :author, presence: true, if: ->(n) { n.is_award } + validates :author, presence: true mount_uploader :attachment, AttachmentUploader @@ -102,7 +103,7 @@ class Note < ActiveRecord::Base end def grouped_awards - select(:note).distinct.map do |note| + awards.select(:note).distinct.map do |note| [ note.note, where(note: note.note) ] end end diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index dbff58dfb9..25a985df4d 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -35,11 +35,11 @@ module Notes end def contains_emoji_only?(note) - note =~ /\A:[-_+[:alnum:]]*:\s?\z/ + note =~ /\A:?[-_+[:alnum:]]*:?\s?\z/ end def emoji_name(note) - note.match(/\A:([-_+[:alnum:]]*):\s?/)[1] + note.match(/\A:?([-_+[:alnum:]]*):?\s?/)[1] end end end diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 04e3b5a381..7eb27c12d3 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,15 +1,15 @@ .awards.votes-block - - votable.notes.awards.grouped_awards.each do | note | - .award{class: (note_active_class(note.last, current_user)), title: emoji_author_list(note.last, current_user)} - .icon{"data-emoji" => "#{note.first}"} - = image_tag url_to_emoji(note.first), height: "20px", width: "20px" + - votable.notes.awards.grouped_awards.each do |emoji, notes| + .award{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user)} + .icon{"data-emoji" => "#{emoji}"} + = image_tag url_to_emoji(emoji), height: "20px", width: "20px" .counter - = note.last.count + = notes.count - if current_user .dropdown.awards-controls %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"} - = icon('plus-square-o') + = icon('smile-o') %ul.dropdown-menu.awards-menu - emoji_list.each do |emoji| %li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px" @@ -17,7 +17,7 @@ - if current_user :coffeescript post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}" - noteable_type = "#{votable.class}" + noteable_type = "#{votable.class.name.underscore}" noteable_id = "#{votable.id}" window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id) From 888821f9ffb56c6fdf762f28dd42cf3b7226652f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 19 Nov 2015 19:22:46 +0100 Subject: [PATCH 221/410] Add support for batch download operation --- lib/gitlab/lfs/response.rb | 105 ++++++++++++++++++------- lib/gitlab/lfs/router.rb | 2 +- spec/lib/gitlab/lfs/lfs_router_spec.rb | 25 ++++-- 3 files changed, 96 insertions(+), 36 deletions(-) diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb index 4202c78646..ddadc07ebb 100644 --- a/lib/gitlab/lfs/response.rb +++ b/lib/gitlab/lfs/response.rb @@ -26,7 +26,7 @@ module Gitlab def render_download_object_response(oid) render_response_to_download do - if check_download_sendfile_header? && check_download_accept_header? + if check_download_sendfile_header? render_lfs_sendfile(oid) else render_not_found @@ -34,20 +34,15 @@ module Gitlab end end - def render_lfs_api_auth - render_response_to_push do - request_body = JSON.parse(@request.body.read) - return render_not_found if request_body.empty? || request_body['objects'].empty? - - response = build_response(request_body['objects']) - [ - 200, - { - "Content-Type" => "application/json; charset=utf-8", - "Cache-Control" => "private", - }, - [JSON.dump(response)] - ] + def render_batch_operation_response + request_body = JSON.parse(@request.body.read) + case request_body["operation"] + when "download" + render_batch_download(request_body) + when "upload" + render_batch_upload(request_body) + else + render_forbidden end end @@ -142,6 +137,38 @@ module Gitlab end end + def render_batch_upload(body) + return render_not_found if body.empty? || body['objects'].nil? + + render_response_to_push do + response = build_upload_batch_response(body['objects']) + [ + 200, + { + "Content-Type" => "application/json; charset=utf-8", + "Cache-Control" => "private", + }, + [JSON.dump(response)] + ] + end + end + + def render_batch_download(body) + return render_not_found if body.empty? || body['objects'].nil? + + render_response_to_download do + response = build_download_batch_response(body['objects']) + [ + 200, + { + "Content-Type" => "application/json; charset=utf-8", + "Cache-Control" => "private", + }, + [JSON.dump(response)] + ] + end + end + def render_lfs_download_hypermedia(oid) return render_not_found unless oid.present? @@ -266,10 +293,16 @@ module Gitlab @project.lfs_objects.where(oid: objects_oids).pluck(:oid).to_set end - def build_response(objects) + def build_upload_batch_response(objects) selected_objects = select_existing_objects(objects) - upload_hypermedia(objects, selected_objects) + upload_hypermedia_links(objects, selected_objects) + end + + def build_download_batch_response(objects) + selected_objects = select_existing_objects(objects) + + download_hypermedia_links(objects, selected_objects) end def download_hypermedia(oid) @@ -279,7 +312,6 @@ module Gitlab { 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{oid}", 'header' => { - 'Accept' => "application/vnd.git-lfs+json; charset=utf-8", 'Authorization' => @env['HTTP_AUTHORIZATION'] }.compact } @@ -287,21 +319,40 @@ module Gitlab } end - def upload_hypermedia(all_objects, existing_objects) + def download_hypermedia_links(all_objects, existing_objects) all_objects.each do |object| - object['_links'] = hypermedia_links(object) unless existing_objects.include?(object['oid']) + # generate links only for existing objects + next unless existing_objects.include?(object['oid']) + + object['_links'] = { + 'download' => { + 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}", + 'header' => { + 'Authorization' => @env['HTTP_AUTHORIZATION'] + }.compact + } + } end { 'objects' => all_objects } end - def hypermedia_links(object) - { - "upload" => { - 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}/#{object['size']}", - 'header' => { 'Authorization' => @env['HTTP_AUTHORIZATION'] } - }.compact - } + def upload_hypermedia_links(all_objects, existing_objects) + all_objects.each do |object| + # generate links only for non-existing objects + next if existing_objects.include?(object['oid']) + + object['_links'] = { + 'upload' => { + 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}/#{object['size']}", + 'header' => { + 'Authorization' => @env['HTTP_AUTHORIZATION'] + }.compact + } + } + end + + { 'objects' => all_objects } end end end diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb index 4809e83498..2b3f2e8e95 100644 --- a/lib/gitlab/lfs/router.rb +++ b/lib/gitlab/lfs/router.rb @@ -48,7 +48,7 @@ module Gitlab # Check for Batch API if post_path[0].ends_with?("/info/lfs/objects/batch") - lfs.render_lfs_api_auth + lfs.render_batch_operation_response else nil end diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb index cebcb5bc88..5eafaad79c 100644 --- a/spec/lib/gitlab/lfs/lfs_router_spec.rb +++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb @@ -66,7 +66,7 @@ describe Gitlab::Lfs::Router do json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first) expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") - expect(json_response['_links']['download']['header']).to eq("Authorization" => @auth, "Accept" => "application/vnd.git-lfs+json; charset=utf-8") + expect(json_response['_links']['download']['header']).to eq("Authorization" => @auth) end end @@ -107,7 +107,7 @@ describe Gitlab::Lfs::Router do json_response = ActiveSupport::JSON.decode(lfs_router_public_auth.try_call.last.first) expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") - expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8") + expect(json_response['_links']['download']['header']).to eq({}) end end @@ -117,7 +117,7 @@ describe Gitlab::Lfs::Router do json_response = ActiveSupport::JSON.decode(lfs_router_public_noauth.try_call.last.first) expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") - expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8") + expect(json_response['_links']['download']['header']).to eq({}) end end end @@ -191,7 +191,7 @@ describe Gitlab::Lfs::Router do json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") - expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8") + expect(json_response['_links']['download']['header']).to eq({}) end end @@ -219,7 +219,7 @@ describe Gitlab::Lfs::Router do json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") - expect(json_response['_links']['download']['header']).to eq("Accept" => "application/vnd.git-lfs+json; charset=utf-8", "Authorization" => @auth) + expect(json_response['_links']['download']['header']).to eq("Authorization" => @auth) end end @@ -250,7 +250,8 @@ describe Gitlab::Lfs::Router do body = { 'objects' => [{ 'oid' => sample_oid, 'size' => sample_size - }] + }], + 'operation' => 'upload' }.to_json env['rack.input'] = StringIO.new(body) end @@ -286,7 +287,8 @@ describe Gitlab::Lfs::Router do 'objects' => [{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', 'size' => 1575078 - }] + }], + 'operation' => 'upload' }.to_json env['rack.input'] = StringIO.new(body) end @@ -315,7 +317,8 @@ describe Gitlab::Lfs::Router do { 'oid' => sample_oid, 'size' => sample_size } - ] + ], + 'operation' => 'upload' }.to_json env['rack.input'] = StringIO.new(body) public_project.lfs_objects << lfs_object @@ -351,6 +354,12 @@ describe Gitlab::Lfs::Router do end context 'when user is not authenticated' do + before do + env['rack.input'] = StringIO.new( + { 'objects' => [], 'operation' => 'upload' }.to_json + ) + end + context 'when user has push access' do before do project.team << [user, :master] From 8248314bc9256d3a0252ad6322df098edca7385a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 19 Nov 2015 20:16:56 +0100 Subject: [PATCH 222/410] Don't rescue Exception, but StandardError --- app/controllers/ci/lints_controller.rb | 4 ++-- app/models/ci/commit.rb | 9 +++------ 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index 24dd1b5c93..a4f6aff49b 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -15,10 +15,10 @@ module Ci @builds = @config_processor.builds @status = true end - rescue Ci::GitlabCiYamlProcessor::ValidationError => e + rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e @error = e.message @status = false - rescue Exception + rescue @error = "Undefined error" @status = false end diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index b0c78499e4..971e899de8 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -190,14 +190,11 @@ module Ci def config_processor return nil unless ci_yaml_file @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, gl_project.path_with_namespace) - rescue Ci::GitlabCiYamlProcessor::ValidationError => e + rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e save_yaml_error(e.message) nil - rescue Psych::SyntaxError => e - save_yaml_error(e.message) - nil - rescue Exception - save_yaml_error("Undefined yaml error") + rescue + save_yaml_error("Undefined error") nil end From 22bbb379ae976d94d7ddd7addb7384e0b79ddfd9 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 19 Nov 2015 23:00:30 +0200 Subject: [PATCH 223/410] fox tests --- spec/models/note_spec.rb | 5 +-- spec/services/notes/create_service_spec.rb | 50 +++++++++++----------- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 86b6c27a18..f347f53755 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -132,12 +132,11 @@ describe Note do describe :grouped_awards do before do - create :note, note: "smile" - create :note, note: "smile" + create :note, note: "smile", is_award: true + create :note, note: "smile", is_award: true end it "returns grouped array of notes" do - grouped_array = Note.grouped_awards expect(Note.grouped_awards.first.first).to eq("smile") expect(Note.grouped_awards.first.last).to match_array(Note.all) end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 066461f5a1..cc38d25779 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -26,36 +26,36 @@ describe Notes::CreateService do end describe "award emoji" do - before do - project.team << [user, :master] - end + before do + project.team << [user, :master] + end - it "creates emoji note" do - opts = { - note: ':smile: ', - noteable_type: 'Issue', - noteable_id: issue.id - } + it "creates emoji note" do + opts = { + note: ':smile: ', + noteable_type: 'Issue', + noteable_id: issue.id + } - @note = Notes::CreateService.new(project, user, opts).execute + @note = Notes::CreateService.new(project, user, opts).execute - expect(@note).to be_valid - expect(@note.note).to eq('smile') - expect(@note.is_award).to be_truthy - end + expect(@note).to be_valid + expect(@note.note).to eq('smile') + expect(@note.is_award).to be_truthy + end - it "creates regular note if emoji name is invalid" do - opts = { - note: ':smile: moretext: ', - noteable_type: 'Issue', - noteable_id: issue.id - } + it "creates regular note if emoji name is invalid" do + opts = { + note: ':smile: moretext: ', + noteable_type: 'Issue', + noteable_id: issue.id + } - @note = Notes::CreateService.new(project, user, opts).execute + @note = Notes::CreateService.new(project, user, opts).execute - expect(@note).to be_valid - expect(@note.note).to eq(opts[:note]) - expect(@note.is_award).to be_falsy - end + expect(@note).to be_valid + expect(@note.note).to eq(opts[:note]) + expect(@note.is_award).to be_falsy + end end end From 2a3680219bb5a4d35aa9b98ba2a2c43855aea27a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 19 Nov 2015 15:46:05 -0500 Subject: [PATCH 224/410] Add a fallback for Safari copy-to-clipboard Also, hide the tooltip in a less stupid way. Closes #3547 --- .../javascripts/copy_to_clipboard.js.coffee | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/copy_to_clipboard.js.coffee b/app/assets/javascripts/copy_to_clipboard.js.coffee index ec4b80cca6..9c68c5cc1b 100644 --- a/app/assets/javascripts/copy_to_clipboard.js.coffee +++ b/app/assets/javascripts/copy_to_clipboard.js.coffee @@ -9,13 +9,24 @@ $ -> clipboard.on 'success', (e) -> $(e.trigger). tooltip(trigger: 'manual', placement: 'auto bottom', title: 'Copied!'). - tooltip('show') + tooltip('show'). + one('mouseleave', -> $(this).tooltip('hide')) # Clear the selection and blur the trigger so it loses its border e.clearSelection() $(e.trigger).blur() - # Manually hide the tooltip after 1 second - setTimeout(-> - $(e.trigger).tooltip('hide') - , 1000) + # Safari doesn't support `execCommand`, so instead we inform the user to + # copy manually. + # + # See http://clipboardjs.com/#browser-support + clipboard.on 'error', (e) -> + if /Mac/i.test(navigator.userAgent) + title = "Press ⌘-C to copy" + else + title = "Press Ctrl-C to copy" + + $(e.trigger). + tooltip(trigger: 'manual', placement: 'auto bottom', html: true, title: title). + tooltip('show'). + one('mouseleave', -> $(this).tooltip('hide')) From 55c2a69f5fcde26299a5a829a146808a77c56267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Thu, 19 Nov 2015 16:22:41 -0500 Subject: [PATCH 225/410] Update copy used in alert message when deleting branches or tags. #2993 --- app/views/projects/branches/_branch.html.haml | 2 +- app/views/projects/tags/show.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index cc0ec9483d..3f95e2a1bf 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -26,7 +26,7 @@ Compare - if can_remove_branch?(@project, branch.name) - = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do + = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row', method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?" }, remote: true do = icon("trash-o") - if commit diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml index ebe3718afc..879c6c7d31 100644 --- a/app/views/projects/tags/show.html.haml +++ b/app/views/projects/tags/show.html.haml @@ -15,7 +15,7 @@ = render 'projects/tags/download', ref: @tag.name, project: @project - if can?(current_user, :admin_project, @project) .pull-right - = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'} do + = link_to namespace_project_tag_path(@project.namespace, @project, @tag.name), class: 'btn btn-remove remove-row grouped', method: :delete, data: { confirm: "Deleting the '#{@tag.name}' tag cannot be undone. Are you sure?" } do %i.fa.fa-trash-o .title %strong= @tag.name From 2df492fd2f5bfb6f9aa9c3f800fa4a6a39bdeeb1 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 19 Nov 2015 19:25:22 -0500 Subject: [PATCH 226/410] Add clipboard button to merge request cross-project reference --- app/views/projects/merge_requests/_discussion.html.haml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index 7e60782ff5..cb75bd8c5b 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -14,8 +14,10 @@ #votes= render 'votes/votes_block', votable: @merge_request = render "projects/merge_requests/show/participants" .col-md-3 - %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'} - = cross_project_reference(@project, @merge_request) + .input-group.cross-project-reference + %span.slead.has_tooltip{title: 'Cross-project reference'} + = cross_project_reference(@project, @merge_request) + = clipboard_button .row %section.col-md-9 From 97afb84b310cfdaa9305e8b79fc00bdbb866bbca Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Thu, 22 Oct 2015 10:18:59 -0500 Subject: [PATCH 227/410] Generate system note after Task item has been updated on Issue or Merge Request. #2296 Everytime the User check or uncheck a Task Item from the Issue or Merge Request description, a new update is going to be added to the activity logs of the Issue or Merge Request. Note that when using the edit form, you can only update the Task item status or add/delete/modify existing ones. Doing both actions is not fully supported. --- app/models/concerns/issuable.rb | 5 ++ app/models/concerns/taskable.rb | 31 +++++++-- app/services/issuable_base_service.rb | 17 +++++ app/services/issues/update_service.rb | 4 -- app/services/merge_requests/update_service.rb | 4 -- app/services/system_note_service.rb | 17 +++++ config/initializers/task_list_ext.rb | 12 ++++ spec/services/issues/update_service_spec.rb | 64 +++++++++++++++++-- .../merge_requests/update_service_spec.rb | 51 +++++++++++++-- 9 files changed, 181 insertions(+), 24 deletions(-) create mode 100644 config/initializers/task_list_ext.rb diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 2dafb5e752..62e8e66b1e 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -151,4 +151,9 @@ module Issuable def notes_with_associations notes.includes(:author, :project) end + + def updated_tasks + Taskable.get_updated_tasks(old_content: previous_changes['description'].first, + new_content: description) + end end diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index 660e58b876..3daa4dbe24 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -7,14 +7,37 @@ require 'task_list/filter' # # Used by MergeRequest and Issue module Taskable + ITEM_PATTERN = / + ^ + (?:\s*[-+*]|(?:\d+\.))? # optional list prefix + \s* # optional whitespace prefix + (\[\s\]|\[[xX]\]) # checkbox + (\s.+) # followed by whitespace and some text. + /x + + def self.get_tasks(content) + content.to_s.scan(ITEM_PATTERN).map do |checkbox, label| + # ITEM_PATTERN strips out the hyphen, but Item requires it. Rabble rabble. + TaskList::Item.new("- #{checkbox}", label.strip) + end + end + + def self.get_updated_tasks(old_content:, new_content:) + old_tasks, new_tasks = get_tasks(old_content), get_tasks(new_content) + + new_tasks.select.with_index do |new_task, i| + old_task = old_tasks[i] + next unless old_task + + new_task.source == new_task.source && new_task.complete? != old_task.complete? + end + end + # Called by `TaskList::Summary` def task_list_items return [] if description.blank? - @task_list_items ||= description.scan(TaskList::Filter::ItemPattern).collect do |item| - # ItemPattern strips out the hyphen, but Item requires it. Rabble rabble. - TaskList::Item.new("- #{item}") - end + @task_list_items ||= Taskable.get_tasks(description) end def tasks diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 11d2b08bba..19055fb67f 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -27,6 +27,12 @@ class IssuableBaseService < BaseService old_branch, new_branch) end + def create_task_status_note(issuable) + issuable.updated_tasks.each do |task| + SystemNoteService.change_task_status(issuable, issuable.project, current_user, task) + end + end + def filter_params(issuable_ability_name = :issue) params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE @@ -55,6 +61,7 @@ class IssuableBaseService < BaseService old_labels - issuable.labels) end + handle_common_system_notes(issuable) handle_changes(issuable) issuable.create_new_cross_references!(current_user) execute_hooks(issuable, 'update') @@ -71,4 +78,14 @@ class IssuableBaseService < BaseService close_service.new(project, current_user, {}).execute(issuable) end end + + def handle_common_system_notes(issuable) + if issuable.previous_changes.include?('title') + create_title_change_note(issuable, issuable.previous_changes['title'].first) + end + + if issuable.previous_changes.include?('description') && issuable.tasks? + create_task_status_note(issuable) + end + end end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 7c112f731a..a55a04dd5e 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -13,10 +13,6 @@ module Issues create_assignee_note(issue) notification_service.reassigned_issue(issue, current_user) end - - if issue.previous_changes.include?('title') - create_title_change_note(issue, issue.previous_changes['title'].first) - end end def reopen_service diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index a5db3776eb..5ff2cc03dd 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -30,10 +30,6 @@ module MergeRequests notification_service.reassigned_merge_request(merge_request, current_user) end - if merge_request.previous_changes.include?('title') - create_title_change_note(merge_request, merge_request.previous_changes['title'].first) - end - if merge_request.previous_changes.include?('target_branch') || merge_request.previous_changes.include?('source_branch') merge_request.mark_as_unchecked diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 708c2f0048..7c5d523ef3 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -341,4 +341,21 @@ class SystemNoteService "* #{commit_ids} - #{commits_text} from branch `#{branch}`\n" end + + # Called when the status of a Task has changed + # + # noteable - Noteable object. + # project - Project owning noteable + # author - User performing the change + # new_task - TaskList::Item object. + # + # Example Note text: + # + # "Soandso marked the task Whatever as completed." + # + # Returns the created Note object + def self.change_task_status(noteable, project, author, new_task) + body = "Marked the task **#{new_task.source}** as #{new_task.status_label}" + create_note(noteable: noteable, project: project, author: author, note: body) + end end diff --git a/config/initializers/task_list_ext.rb b/config/initializers/task_list_ext.rb new file mode 100644 index 0000000000..c05b683b5b --- /dev/null +++ b/config/initializers/task_list_ext.rb @@ -0,0 +1,12 @@ +require 'task_list' + +class TaskList + class Item + COMPLETED = 'completed'.freeze + INCOMPLETE = 'incomplete'.freeze + + def status_label + complete? ? COMPLETED : INCOMPLETE + end + end +end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index f55527ee9a..adb3aa143a 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -15,6 +15,17 @@ describe Issues::UpdateService do end describe 'execute' do + def find_note(starting_with) + @issue.notes.find do |note| + note && note.note.start_with?(starting_with) + end + end + + def update_issue(opts) + @issue = Issues::UpdateService.new(project, user, opts).execute(issue) + @issue.reload + end + context "valid params" do before do opts = { @@ -44,12 +55,6 @@ describe Issues::UpdateService do expect(email.subject).to include(issue.title) end - def find_note(starting_with) - @issue.notes.find do |note| - note && note.note.start_with?(starting_with) - end - end - it 'should create system note about issue reassign' do note = find_note('Reassigned to') @@ -71,5 +76,52 @@ describe Issues::UpdateService do expect(note.note).to eq 'Title changed from **Old title** to **New title**' end end + + context 'when Issue has tasks' do + before { update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) } + + it { expect(@issue.tasks?).to eq(true) } + + context 'when tasks are marked as completed' do + before { update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) } + + it 'creates system note about task status change' do + note1 = find_note('Marked the task **Task 1** as completed') + note2 = find_note('Marked the task **Task 2** as completed') + + expect(note1).not_to be_nil + expect(note2).not_to be_nil + end + end + + context 'when tasks are marked as incomplete' do + before do + update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) + update_issue({ description: "- [ ] Task 1\n- [ ] Task 2" }) + end + + it 'creates system note about task status change' do + note1 = find_note('Marked the task **Task 1** as incomplete') + note2 = find_note('Marked the task **Task 2** as incomplete') + + expect(note1).not_to be_nil + expect(note2).not_to be_nil + end + end + + context 'when tasks position has been modified' do + before do + update_issue({ description: "- [x] Task 1\n- [X] Task 2" }) + update_issue({ description: "- [x] Task 1\n- [ ] Task 3\n- [ ] Task 2" }) + end + + it 'does not create a system note' do + note = find_note('Marked the task **Task 2** as incomplete') + + expect(note).to be_nil + end + end + end + end end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 2ed51d223b..97f5c009ae 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -14,6 +14,17 @@ describe MergeRequests::UpdateService do end describe 'execute' do + def find_note(starting_with) + @merge_request.notes.find do |note| + note && note.note.start_with?(starting_with) + end + end + + def update_merge_request(opts) + @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request) + @merge_request.reload + end + context 'valid params' do let(:opts) do { @@ -56,12 +67,6 @@ describe MergeRequests::UpdateService do expect(email.subject).to include(merge_request.title) end - def find_note(starting_with) - @merge_request.notes.find do |note| - note && note.note.start_with?(starting_with) - end - end - it 'should create system note about merge_request reassign' do note = find_note('Reassigned to') @@ -90,5 +95,39 @@ describe MergeRequests::UpdateService do expect(note.note).to eq 'Target branch changed from `master` to `target`' end end + + context 'when MergeRequest has tasks' do + before { update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) } + + it { expect(@merge_request.tasks?).to eq(true) } + + context 'when tasks are marked as completed' do + before { update_merge_request({ description: "- [x] Task 1\n- [X] Task 2" }) } + + it 'creates system note about task status change' do + note1 = find_note('Marked the task **Task 1** as completed') + note2 = find_note('Marked the task **Task 2** as completed') + + expect(note1).not_to be_nil + expect(note2).not_to be_nil + end + end + + context 'when tasks are marked as incomplete' do + before do + update_merge_request({ description: "- [x] Task 1\n- [X] Task 2" }) + update_merge_request({ description: "- [ ] Task 1\n- [ ] Task 2" }) + end + + it 'creates system note about task status change' do + note1 = find_note('Marked the task **Task 1** as incomplete') + note2 = find_note('Marked the task **Task 2** as incomplete') + + expect(note1).not_to be_nil + expect(note2).not_to be_nil + end + end + end + end end From f31ee525070d335aba8a189b304e3c446aedf1fb Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Fri, 20 Nov 2015 11:13:43 +0200 Subject: [PATCH 228/410] Fix for Emoji --- app/assets/javascripts/awards_handler.coffee | 2 +- app/controllers/projects/notes_controller.rb | 2 +- app/helpers/issues_helper.rb | 2 ++ app/services/notes/create_service.rb | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index ae42e390c4..635c9b4f8d 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -80,7 +80,7 @@ class @AwardsHandler postEmoji: (emoji, callback) -> $.post @post_emoji_url, { note: { - note: emoji + note: ":" + emoji + ":" noteable_type: @noteable_type noteable_id: @noteable_id }},(data) -> diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 263b8b8d94..1e3f1d8fd2 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -65,7 +65,7 @@ class Projects::NotesController < Projects::ApplicationController data = { author: current_user, is_award: true, - note: note_params[:note] + note: note_params[:note].gsub(":", '') } note = noteable.notes.find_by(data) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 2c791aa568..493f370d9a 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -90,6 +90,8 @@ module IssuesHelper def url_to_emoji(name) emoji_path = ::AwardEmoji.path_to_emoji_image(name) url_to_image(emoji_path) + rescue StandardError + "" end def emoji_author_list(notes, current_user) diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index 25a985df4d..dbff58dfb9 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -35,11 +35,11 @@ module Notes end def contains_emoji_only?(note) - note =~ /\A:?[-_+[:alnum:]]*:?\s?\z/ + note =~ /\A:[-_+[:alnum:]]*:\s?\z/ end def emoji_name(note) - note.match(/\A:?([-_+[:alnum:]]*):?\s?/)[1] + note.match(/\A:([-_+[:alnum:]]*):\s?/)[1] end end end From 14d95b0529eacb8f42c5184fb71bff3f326d1670 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 20 Nov 2015 11:59:32 +0100 Subject: [PATCH 229/410] Part of tests done [ci skip] --- lib/gitlab/lfs/response.rb | 27 +- spec/lib/gitlab/lfs/lfs_router_spec.rb | 481 ++++++++++++++++++------- 2 files changed, 371 insertions(+), 137 deletions(-) diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb index ddadc07ebb..0371e1a710 100644 --- a/lib/gitlab/lfs/response.rb +++ b/lib/gitlab/lfs/response.rb @@ -42,7 +42,7 @@ module Gitlab when "upload" render_batch_upload(request_body) else - render_forbidden + render_not_found end end @@ -322,16 +322,21 @@ module Gitlab def download_hypermedia_links(all_objects, existing_objects) all_objects.each do |object| # generate links only for existing objects - next unless existing_objects.include?(object['oid']) - - object['_links'] = { - 'download' => { - 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}", - 'header' => { - 'Authorization' => @env['HTTP_AUTHORIZATION'] - }.compact + if existing_objects.include?(object['oid']) + object['actions'] = { + 'download' => { + 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}", + 'header' => { + 'Authorization' => @env['HTTP_AUTHORIZATION'] + }.compact + } } - } + else + object['error'] = { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + end end { 'objects' => all_objects } @@ -342,7 +347,7 @@ module Gitlab # generate links only for non-existing objects next if existing_objects.include?(object['oid']) - object['_links'] = { + object['actions'] = { 'upload' => { 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{object['oid']}/#{object['size']}", 'header' => { diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb index 5eafaad79c..b0cf38e225 100644 --- a/spec/lib/gitlab/lfs/lfs_router_spec.rb +++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb @@ -238,142 +238,371 @@ describe Gitlab::Lfs::Router do end end - describe 'when initiating pushing of the lfs object' do + describe 'when handling lfs batch request' do before do enable_lfs env['REQUEST_METHOD'] = 'POST' - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" + env['PATH_INFO'] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" end - describe 'when user is authenticated' do + describe 'download' do + describe 'when user is authenticated' do + before do + body = { 'objects' => [{ + 'oid' => sample_oid, + 'size' => sample_size + }], + 'operation' => 'download' + }.to_json + env['rack.input'] = StringIO.new(body) + end + + describe 'when user has download access' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.team << [user, :reporter] + end + + context 'when downloading an lfs object that is assigned to our project' do + before do + project.lfs_objects << lfs_object + end + + it 'responds with status 200 and href to download' do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) + + expect(response_body).to eq( + 'objects' => [{ + 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => {'Authorization' => @auth} + } + } + }]) + end + end + + context 'when downloading an lfs object that is assigned to other project' do + before do + public_project.lfs_objects << lfs_object + end + + it 'responds with status 200 and error message' do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) + + expect(response_body).to eq( + 'objects' => [{ + 'oid' => sample_oid, + 'size' => sample_size, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }]) + end + end + + context 'when downloading a lfs object that does not exist' do + before do + body = { + 'objects' => [{ + 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }], + 'operation' => 'download' + }.to_json + env['rack.input'] = StringIO.new(body) + end + + it "responds with status 200 and error message" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) + + expect(response_body).to eq( + 'objects' => [{ + 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }]) + end + end + + context 'when downloading one new and one existing lfs object' do + before do + body = { + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ], + 'operation' => 'download' + }.to_json + env['rack.input'] = StringIO.new(body) + project.lfs_objects << lfs_object + end + + it "responds with status 200 with upload hypermedia link for the new object" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) + + expect(response_body).to eq( + 'objects' => [{ + 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }, + { + 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => {'Authorization' => @auth} + } + } + }]) + end + end + end + + context 'when user does is not member of the project' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.team << [user, :guest] + end + + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + + context 'when user does not have download access' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.team << [user, :guest] + end + + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + context 'when user is not authenticated' do + before do + body = { 'objects' => [{ + 'oid' => sample_oid, + 'size' => sample_size + }], + 'operation' => 'download' + }.to_json + env['rack.input'] = StringIO.new(body) + end + + describe 'is accessing public project' do + before do + public_project.lfs_objects << lfs_object + end + + it 'responds with status 200 and href to download' do + response = lfs_router_public_noauth.try_call + expect(response.first).to eq(200) + response_body = ActiveSupport::JSON.decode(response.last.first) + + expect(response_body).to eq( + 'objects' => [{ + 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{public_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => {} + } + } + }]) + end + end + + describe 'is accessing non-public project' do + before do + project.lfs_objects << lfs_object + end + + it 'responds with authorization required' do + expect(lfs_router_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'upload' do + describe 'when user is authenticated' do + before do + body = { 'objects' => [{ + 'oid' => sample_oid, + 'size' => sample_size + }], + 'operation' => 'upload' + }.to_json + env['rack.input'] = StringIO.new(body) + end + + describe 'when user has project push access' do + before do + @auth = authorize(user) + env["HTTP_AUTHORIZATION"] = @auth + project.team << [user, :master] + end + + context 'when pushing an lfs object that already exists' do + before do + public_project.lfs_objects << lfs_object + end + + it "responds with status 200 and links the object to the project" do + response_body = lfs_router_auth.try_call.last + response = ActiveSupport::JSON.decode(response_body.first) + + expect(response['objects']).to be_kind_of(Array) + expect(response['objects'].first['oid']).to eq(sample_oid) + expect(response['objects'].first['size']).to eq(sample_size) + expect(lfs_object.projects.pluck(:id)).to_not include(project.id) + expect(lfs_object.projects.pluck(:id)).to include(public_project.id) + expect(response['objects'].first).to have_key('_links') + end + end + + context 'when pushing a lfs object that does not exist' do + before do + body = { + 'objects' => [{ + 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }], + 'operation' => 'upload' + }.to_json + env['rack.input'] = StringIO.new(body) + end + + it "responds with status 200 and upload hypermedia link" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + + response_body = ActiveSupport::JSON.decode(response.last.first) + expect(response_body['objects']).to be_kind_of(Array) + expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(response_body['objects'].first['size']).to eq(1575078) + expect(lfs_object.projects.pluck(:id)).not_to include(project.id) + expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth) + end + end + + context 'when pushing one new and one existing lfs object' do + before do + body = { + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ], + 'operation' => 'upload' + }.to_json + env['rack.input'] = StringIO.new(body) + public_project.lfs_objects << lfs_object + end + + it "responds with status 200 with upload hypermedia link for the new object" do + response = lfs_router_auth.try_call + expect(response.first).to eq(200) + + response_body = ActiveSupport::JSON.decode(response.last.first) + expect(response_body['objects']).to be_kind_of(Array) + + + expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") + expect(response_body['objects'].first['size']).to eq(1575078) + expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth) + + expect(response_body['objects'].last['oid']).to eq(sample_oid) + expect(response_body['objects'].last['size']).to eq(sample_size) + expect(lfs_object.projects.pluck(:id)).to_not include(project.id) + expect(lfs_object.projects.pluck(:id)).to include(public_project.id) + expect(response_body['objects'].last).to have_key('_links') + end + end + end + + context 'when user does not have push access' do + it 'responds with 403' do + expect(lfs_router_auth.try_call.first).to eq(403) + end + end + end + + context 'when user is not authenticated' do + before do + env['rack.input'] = StringIO.new( + { 'objects' => [], 'operation' => 'upload' }.to_json + ) + end + + context 'when user has push access' do + before do + project.team << [user, :master] + end + + it "responds with status 401" do + expect(lfs_router_public_noauth.try_call.first).to eq(401) + end + end + + context 'when user does not have push access' do + it "responds with status 401" do + expect(lfs_router_public_noauth.try_call.first).to eq(401) + end + end + end + end + + describe 'unsupported' do before do body = { 'objects' => [{ - 'oid' => sample_oid, - 'size' => sample_size - }], - 'operation' => 'upload' - }.to_json + 'oid' => sample_oid, + 'size' => sample_size + }], + 'operation' => 'other' + }.to_json env['rack.input'] = StringIO.new(body) end - describe 'when user has project push access' do - before do - @auth = authorize(user) - env["HTTP_AUTHORIZATION"] = @auth - project.team << [user, :master] - end - - context 'when pushing an lfs object that already exists' do - before do - public_project.lfs_objects << lfs_object - end - - it "responds with status 200 and links the object to the project" do - response_body = lfs_router_auth.try_call.last - response = ActiveSupport::JSON.decode(response_body.first) - - expect(response['objects']).to be_kind_of(Array) - expect(response['objects'].first['oid']).to eq(sample_oid) - expect(response['objects'].first['size']).to eq(sample_size) - expect(lfs_object.projects.pluck(:id)).to_not include(project.id) - expect(lfs_object.projects.pluck(:id)).to include(public_project.id) - expect(response['objects'].first).to have_key('_links') - end - end - - context 'when pushing a lfs object that does not exist' do - before do - body = { - 'objects' => [{ - 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }], - 'operation' => 'upload' - }.to_json - env['rack.input'] = StringIO.new(body) - end - - it "responds with status 200 and upload hypermedia link" do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - - response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body['objects']).to be_kind_of(Array) - expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") - expect(response_body['objects'].first['size']).to eq(1575078) - expect(lfs_object.projects.pluck(:id)).not_to include(project.id) - expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") - expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth) - end - end - - context 'when pushing one new and one existing lfs object' do - before do - body = { - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }, - { 'oid' => sample_oid, - 'size' => sample_size - } - ], - 'operation' => 'upload' - }.to_json - env['rack.input'] = StringIO.new(body) - public_project.lfs_objects << lfs_object - end - - it "responds with status 200 with upload hypermedia link for the new object" do - response = lfs_router_auth.try_call - expect(response.first).to eq(200) - - response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body['objects']).to be_kind_of(Array) - - - expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") - expect(response_body['objects'].first['size']).to eq(1575078) - expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") - expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth) - - expect(response_body['objects'].last['oid']).to eq(sample_oid) - expect(response_body['objects'].last['size']).to eq(sample_size) - expect(lfs_object.projects.pluck(:id)).to_not include(project.id) - expect(lfs_object.projects.pluck(:id)).to include(public_project.id) - expect(response_body['objects'].last).to have_key('_links') - end - end - end - - context 'when user does not have push access' do - it 'responds with 403' do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - end - - context 'when user is not authenticated' do - before do - env['rack.input'] = StringIO.new( - { 'objects' => [], 'operation' => 'upload' }.to_json - ) - end - - context 'when user has push access' do - before do - project.team << [user, :master] - end - - it "responds with status 401" do - expect(lfs_router_public_noauth.try_call.first).to eq(401) - end - end - - context 'when user does not have push access' do - it "responds with status 401" do - expect(lfs_router_public_noauth.try_call.first).to eq(401) - end + it 'responds with status 404' do + expect(lfs_router_public_noauth.try_call.first).to eq(404) end end end From b8d8292bef996a5d074b21a82d4faec0edb2e2bf Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 20 Nov 2015 12:21:41 +0100 Subject: [PATCH 230/410] Fix upload tests, reformat code and make rubocop happy --- lib/gitlab/lfs/response.rb | 3 +- spec/lib/gitlab/lfs/lfs_router_spec.rb | 229 ++++++++++++------------- 2 files changed, 110 insertions(+), 122 deletions(-) diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb index 0371e1a710..ada518f9e0 100644 --- a/lib/gitlab/lfs/response.rb +++ b/lib/gitlab/lfs/response.rb @@ -321,7 +321,6 @@ module Gitlab def download_hypermedia_links(all_objects, existing_objects) all_objects.each do |object| - # generate links only for existing objects if existing_objects.include?(object['oid']) object['actions'] = { 'download' => { @@ -344,7 +343,7 @@ module Gitlab def upload_hypermedia_links(all_objects, existing_objects) all_objects.each do |object| - # generate links only for non-existing objects + # generate actions only for non-existing objects next if existing_objects.include?(object['oid']) object['actions'] = { diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb index b0cf38e225..f898b76eb5 100644 --- a/spec/lib/gitlab/lfs/lfs_router_spec.rb +++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb @@ -248,11 +248,11 @@ describe Gitlab::Lfs::Router do describe 'download' do describe 'when user is authenticated' do before do - body = { 'objects' => [{ - 'oid' => sample_oid, - 'size' => sample_size - }], - 'operation' => 'download' + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }] }.to_json env['rack.input'] = StringIO.new(body) end @@ -274,17 +274,16 @@ describe Gitlab::Lfs::Router do expect(response.first).to eq(200) response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body).to eq( - 'objects' => [{ - 'oid' => sample_oid, + expect(response_body).to eq('objects' => [ + { 'oid' => sample_oid, 'size' => sample_size, 'actions' => { 'download' => { - 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", - 'header' => {'Authorization' => @auth} + 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => { 'Authorization' => @auth } + } } - } - }]) + }]) end end @@ -298,26 +297,24 @@ describe Gitlab::Lfs::Router do expect(response.first).to eq(200) response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body).to eq( - 'objects' => [{ - 'oid' => sample_oid, - 'size' => sample_size, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }]) + expect(response_body).to eq('objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }]) end end context 'when downloading a lfs object that does not exist' do before do - body = { - 'objects' => [{ - 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }], - 'operation' => 'download' + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }] }.to_json env['rack.input'] = StringIO.new(body) end @@ -327,30 +324,28 @@ describe Gitlab::Lfs::Router do expect(response.first).to eq(200) response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body).to eq( - 'objects' => [{ - 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }]) + expect(response_body).to eq('objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }]) end end context 'when downloading one new and one existing lfs object' do before do - body = { - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }, - { 'oid' => sample_oid, - 'size' => sample_size - } - ], - 'operation' => 'download' + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ] }.to_json env['rack.input'] = StringIO.new(body) project.lfs_objects << lfs_object @@ -361,25 +356,23 @@ describe Gitlab::Lfs::Router do expect(response.first).to eq(200) response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body).to eq( - 'objects' => [{ - 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it", - } - }, - { - 'oid' => sample_oid, - 'size' => sample_size, - 'actions' => { - 'download' => { - 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", - 'header' => {'Authorization' => @auth} - } - } - }]) + expect(response_body).to eq('objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it", + } + }, + { 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => { 'Authorization' => @auth } + } + } + }]) end end end @@ -411,11 +404,12 @@ describe Gitlab::Lfs::Router do context 'when user is not authenticated' do before do - body = { 'objects' => [{ - 'oid' => sample_oid, - 'size' => sample_size - }], - 'operation' => 'download' + body = { 'operation' => 'download', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }], + }.to_json env['rack.input'] = StringIO.new(body) end @@ -430,17 +424,16 @@ describe Gitlab::Lfs::Router do expect(response.first).to eq(200) response_body = ActiveSupport::JSON.decode(response.last.first) - expect(response_body).to eq( - 'objects' => [{ - 'oid' => sample_oid, - 'size' => sample_size, - 'actions' => { - 'download' => { - 'href' => "#{public_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", - 'header' => {} - } - } - }]) + expect(response_body).to eq('objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size, + 'actions' => { + 'download' => { + 'href' => "#{public_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", + 'header' => {} + } + } + }]) end end @@ -459,12 +452,12 @@ describe Gitlab::Lfs::Router do describe 'upload' do describe 'when user is authenticated' do before do - body = { 'objects' => [{ - 'oid' => sample_oid, - 'size' => sample_size - }], - 'operation' => 'upload' - }.to_json + body = { 'operation' => 'upload', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }] + }.to_json env['rack.input'] = StringIO.new(body) end @@ -472,7 +465,7 @@ describe Gitlab::Lfs::Router do before do @auth = authorize(user) env["HTTP_AUTHORIZATION"] = @auth - project.team << [user, :master] + project.team << [user, :developer] end context 'when pushing an lfs object that already exists' do @@ -489,19 +482,19 @@ describe Gitlab::Lfs::Router do expect(response['objects'].first['size']).to eq(sample_size) expect(lfs_object.projects.pluck(:id)).to_not include(project.id) expect(lfs_object.projects.pluck(:id)).to include(public_project.id) - expect(response['objects'].first).to have_key('_links') + expect(response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}") + expect(response['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth) end end context 'when pushing a lfs object that does not exist' do before do - body = { - 'objects' => [{ - 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }], - 'operation' => 'upload' - }.to_json + body = { 'operation' => 'upload', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }] + }.to_json env['rack.input'] = StringIO.new(body) end @@ -514,26 +507,25 @@ describe Gitlab::Lfs::Router do expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") expect(response_body['objects'].first['size']).to eq(1575078) expect(lfs_object.projects.pluck(:id)).not_to include(project.id) - expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") - expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth) + expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(response_body['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth) end end context 'when pushing one new and one existing lfs object' do before do - body = { - 'objects' => [ - { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', - 'size' => 1575078 - }, - { 'oid' => sample_oid, - 'size' => sample_size - } - ], - 'operation' => 'upload' + body = { 'operation' => 'upload', + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ] }.to_json env['rack.input'] = StringIO.new(body) - public_project.lfs_objects << lfs_object + project.lfs_objects << lfs_object end it "responds with status 200 with upload hypermedia link for the new object" do @@ -543,17 +535,14 @@ describe Gitlab::Lfs::Router do response_body = ActiveSupport::JSON.decode(response.last.first) expect(response_body['objects']).to be_kind_of(Array) - expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") expect(response_body['objects'].first['size']).to eq(1575078) - expect(response_body['objects'].first['_links']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") - expect(response_body['objects'].first['_links']['upload']['header']).to eq("Authorization" => @auth) + expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") + expect(response_body['objects'].first['actions']['upload']['header']).to eq("Authorization" => @auth) expect(response_body['objects'].last['oid']).to eq(sample_oid) expect(response_body['objects'].last['size']).to eq(sample_size) - expect(lfs_object.projects.pluck(:id)).to_not include(project.id) - expect(lfs_object.projects.pluck(:id)).to include(public_project.id) - expect(response_body['objects'].last).to have_key('_links') + expect(response_body['objects'].last).to_not have_key('actions') end end end @@ -592,11 +581,11 @@ describe Gitlab::Lfs::Router do describe 'unsupported' do before do - body = { 'objects' => [{ - 'oid' => sample_oid, - 'size' => sample_size - }], - 'operation' => 'other' + body = { 'operation' => 'other', + 'objects' => [ + { 'oid' => sample_oid, + 'size' => sample_size + }] }.to_json env['rack.input'] = StringIO.new(body) end From dbc0be1b275e92af5432cf7c5cf44de3dc8c9ca5 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Fri, 20 Nov 2015 12:45:30 +0100 Subject: [PATCH 231/410] Error 501 when client is using deprecated API. --- lib/gitlab/lfs/response.rb | 60 ++------ lib/gitlab/lfs/router.rb | 4 +- spec/lib/gitlab/lfs/lfs_router_spec.rb | 186 +++++-------------------- 3 files changed, 52 insertions(+), 198 deletions(-) diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb index ada518f9e0..aceef53ba6 100644 --- a/lib/gitlab/lfs/response.rb +++ b/lib/gitlab/lfs/response.rb @@ -10,20 +10,6 @@ module Gitlab @request = request end - # Return a response for a download request - # Can be a response to: - # Request from a user to get the file - # Request from gitlab-workhorse which file to serve to the user - def render_download_hypermedia_response(oid) - render_response_to_download do - if check_download_accept_header? - render_lfs_download_hypermedia(oid) - else - render_not_found - end - end - end - def render_download_object_response(oid) render_response_to_download do if check_download_sendfile_header? @@ -66,13 +52,24 @@ module Gitlab end end + def render_unsupported_deprecated_api + [ + 501, + { "Content-Type" => "application/json; charset=utf-8" }, + [JSON.dump({ + 'message' => 'Server supports batch API only, please update your Git LFS client to version 0.6.0 and up.', + 'documentation_url' => "#{Gitlab.config.gitlab.url}/help", + })] + ] + end + private def render_not_enabled [ 501, { - "Content-Type" => "application/vnd.git-lfs+json", + "Content-Type" => "application/json; charset=utf-8", }, [JSON.dump({ 'message' => 'Git LFS is not enabled on this GitLab server, contact your admin.', @@ -169,21 +166,6 @@ module Gitlab end end - def render_lfs_download_hypermedia(oid) - return render_not_found unless oid.present? - - lfs_object = object_for_download(oid) - if lfs_object - [ - 200, - { "Content-Type" => "application/vnd.git-lfs+json" }, - [JSON.dump(download_hypermedia(oid))] - ] - else - render_not_found - end - end - def render_lfs_upload_ok(oid, size, tmp_file) if store_file(oid, size, tmp_file) [ @@ -226,10 +208,6 @@ module Gitlab @env['HTTP_X_SENDFILE_TYPE'].to_s == "X-Sendfile" end - def check_download_accept_header? - @env['HTTP_ACCEPT'].to_s == "application/vnd.git-lfs+json; charset=utf-8" - end - def user_can_fetch? # Check user access against the project they used to initiate the pull @user.can?(:download_code, @origin_project) @@ -305,20 +283,6 @@ module Gitlab download_hypermedia_links(objects, selected_objects) end - def download_hypermedia(oid) - { - '_links' => { - 'download' => - { - 'href' => "#{@origin_project.http_url_to_repo}/gitlab-lfs/objects/#{oid}", - 'header' => { - 'Authorization' => @env['HTTP_AUTHORIZATION'] - }.compact - } - } - } - end - def download_hypermedia_links(all_objects, existing_objects) all_objects.each do |object| if existing_objects.include?(object['oid']) diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb index 2b3f2e8e95..78d0289110 100644 --- a/lib/gitlab/lfs/router.rb +++ b/lib/gitlab/lfs/router.rb @@ -34,7 +34,7 @@ module Gitlab case path_match[1] when "info/lfs" - lfs.render_download_hypermedia_response(oid) + lfs.render_unsupported_deprecated_api when "gitlab-lfs" lfs.render_download_object_response(oid) else @@ -49,6 +49,8 @@ module Gitlab # Check for Batch API if post_path[0].ends_with?("/info/lfs/objects/batch") lfs.render_batch_operation_response + elsif post_path[0].ends_with?("/info/lfs/objects") + lfs.render_unsupported_deprecated_api else nil end diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb index f898b76eb5..92598559ae 100644 --- a/spec/lib/gitlab/lfs/lfs_router_spec.rb +++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb @@ -26,19 +26,41 @@ describe Gitlab::Lfs::Router do let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" } let(:sample_size) { 499013 } + let(:respond_with_deprecated) {[ 501, { "Content-Type"=>"application/json; charset=utf-8" }, ["{\"message\":\"Server supports batch API only, please update your Git LFS client to version 0.6.0 and up.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]} + let(:respond_with_disabled) {[ 501, { "Content-Type"=>"application/json; charset=utf-8" }, ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]} describe 'when lfs is disabled' do before do allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) + env['REQUEST_METHOD'] = 'POST' + body = { + 'objects' => [ + { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', + 'size' => 1575078 + }, + { 'oid' => sample_oid, + 'size' => sample_size + } + ], + 'operation' => 'upload' + }.to_json + env['rack.input'] = StringIO.new(body) + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" + end + + it 'responds with 501' do + expect(lfs_router_auth.try_call).to match_array(respond_with_disabled) + end + end + + describe 'when fetching lfs object using deprecated API' do + before do + enable_lfs env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" end it 'responds with 501' do - respond_with_disabled = [ 501, - { "Content-Type"=>"application/vnd.git-lfs+json" }, - ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"] - ] - expect(lfs_router_auth.try_call).to match_array(respond_with_disabled) + expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated) end end @@ -46,93 +68,10 @@ describe Gitlab::Lfs::Router do before do enable_lfs env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8" - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" - end - - describe 'when user is authenticated' do - context 'and user has project download access' do - before do - @auth = authorize(user) - env["HTTP_AUTHORIZATION"] = @auth - project.lfs_objects << lfs_object - project.team << [user, :master] - end - - it "responds with status 200" do - expect(lfs_router_auth.try_call.first).to eq(200) - end - - it "responds with download hypermedia" do - json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first) - - expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") - expect(json_response['_links']['download']['header']).to eq("Authorization" => @auth) - end - end - - context 'and user does not have project access' do - it "responds with status 403" do - expect(lfs_router_auth.try_call.first).to eq(403) - end - end - end - - describe 'when user is unauthenticated' do - context 'and user does not have download access' do - it "responds with status 401" do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end - - context 'and user has download access' do - before do - project.team << [user, :master] - end - - it "responds with status 401" do - expect(lfs_router_noauth.try_call.first).to eq(401) - end - end - end - - describe 'and project is public' do - context 'and project has access to the lfs object' do - before do - public_project.lfs_objects << lfs_object - end - - context 'and user is authenticated' do - it "responds with status 200 and sends download hypermedia" do - expect(lfs_router_public_auth.try_call.first).to eq(200) - json_response = ActiveSupport::JSON.decode(lfs_router_public_auth.try_call.last.first) - - expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") - expect(json_response['_links']['download']['header']).to eq({}) - end - end - - context 'and user is unauthenticated' do - it "responds with status 200 and sends download hypermedia" do - expect(lfs_router_public_noauth.try_call.first).to eq(200) - json_response = ActiveSupport::JSON.decode(lfs_router_public_noauth.try_call.last.first) - - expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{public_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") - expect(json_response['_links']['download']['header']).to eq({}) - end - end - end - - context 'and project does not have access to the lfs object' do - it "responds with status 404" do - expect(lfs_router_public_auth.try_call.first).to eq(404) - end - end + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}" end describe 'and request comes from gitlab-workhorse' do - before do - env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}" - end context 'without user being authorized' do it "responds with status 401" do expect(lfs_router_noauth.try_call.first).to eq(401) @@ -173,68 +112,17 @@ describe Gitlab::Lfs::Router do end end end + end - describe 'from a forked public project' do - before do - env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8" - env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}" - end + describe 'when handling lfs request using deprecated API' do + before do + enable_lfs + env['REQUEST_METHOD'] = 'POST' + env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects" + end - context "when fetching a lfs object" do - context "and user has project download access" do - before do - public_project.lfs_objects << lfs_object - end - - it "can download the lfs object" do - expect(lfs_router_forked_auth.try_call.first).to eq(200) - json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) - - expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}") - expect(json_response['_links']['download']['header']).to eq({}) - end - end - - context "and user is not authenticated but project is public" do - before do - public_project.lfs_objects << lfs_object - end - - it "can download the lfs object" do - expect(lfs_router_forked_auth.try_call.first).to eq(200) - end - end - - context "and user has project download access" do - before do - env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897" - @auth = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) - env["HTTP_AUTHORIZATION"] = @auth - lfs_object_two = create(:lfs_object, :with_file, oid: "91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897", size: 1575078) - public_project.lfs_objects << lfs_object_two - end - - it "can get a lfs object that is not in the forked project" do - expect(lfs_router_forked_auth.try_call.first).to eq(200) - - json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) - expect(json_response['_links']['download']['href']).to eq("#{Gitlab.config.gitlab.url}/#{forked_project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") - expect(json_response['_links']['download']['header']).to eq("Authorization" => @auth) - end - end - - context "and user has project download access" do - before do - env["PATH_INFO"] = "#{forked_project.repository.path_with_namespace}.git/info/lfs/objects/267c8b1d876743971e3a9978405818ff5ca731c4c870b06507619cd9b1847b6b" - lfs_object_three = create(:lfs_object, :with_file, oid: "267c8b1d876743971e3a9978405818ff5ca731c4c870b06507619cd9b1847b6b", size: 127192524) - project.lfs_objects << lfs_object_three - end - - it "cannot get a lfs object that is not in the project" do - expect(lfs_router_forked_auth.try_call.first).to eq(404) - end - end - end + it 'responds with 501' do + expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated) end end From 5a4c56c38dd7aef414582edb880b343bf67b65b8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 19 Nov 2015 14:49:35 +0100 Subject: [PATCH 232/410] Reduce method complexity in AutocompleteController --- app/controllers/autocomplete_controller.rb | 49 +++++++++---------- .../autocomplete_controller_spec.rb | 9 ++-- 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 202e9da9ee..aa0268b8d6 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -1,34 +1,8 @@ class AutocompleteController < ApplicationController skip_before_action :authenticate_user!, only: [:users] + before_action :find_users, only: [:users] def users - begin - @users = - if params[:project_id].present? - project = Project.find(params[:project_id]) - - if can?(current_user, :read_project, project) - project.team.users - end - elsif params[:group_id] - group = Group.find(params[:group_id]) - - if can?(current_user, :read_group, group) - group.users - end - elsif current_user - User.all - end - rescue ActiveRecord::RecordNotFound - if current_user - return render json: {}, status: 404 - end - end - - if @users.nil? && current_user.nil? - authenticate_user! - end - @users ||= User.none @users = @users.search(params[:search]) if params[:search].present? @users = @users.active @@ -49,4 +23,25 @@ class AutocompleteController < ApplicationController @user = User.find(params[:id]) render json: @user, only: [:name, :username, :id], methods: [:avatar_url] end + + private + + def find_users + @users = + if params[:project_id].present? + project = Project.find(params[:project_id]) + return render_404 unless can?(current_user, :read_project, project) + + project.team.users + elsif params[:group_id].present? + group = Group.find(params[:group_id]) + return render_404 unless can?(current_user, :read_group, group) + + group.users + elsif current_user + User.all + else + User.none + end + end end diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index aa8d6cb807..85379a8e98 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -114,7 +114,7 @@ describe AutocompleteController do get(:users, project_id: project.id) end - it { expect(response.status).to eq(302) } + it { expect(response.status).to eq(404) } end describe 'GET #users with unknown project' do @@ -122,7 +122,7 @@ describe AutocompleteController do get(:users, project_id: 'unknown') end - it { expect(response.status).to eq(302) } + it { expect(response.status).to eq(404) } end describe 'GET #users with inaccessible group' do @@ -131,7 +131,7 @@ describe AutocompleteController do get(:users, group_id: user.namespace.id) end - it { expect(response.status).to eq(302) } + it { expect(response.status).to eq(404) } end describe 'GET #users with no project' do @@ -139,7 +139,8 @@ describe AutocompleteController do get(:users) end - it { expect(response.status).to eq(302) } + it { expect(body).to be_kind_of(Array) } + it { expect(body.size).to eq 0 } end end end From fed059a12ddde628a7d19d008c199da00990ce19 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 20 Nov 2015 15:48:05 +0100 Subject: [PATCH 233/410] Port GitLab EE ProjectsFinder changes These changes were added in GitLab EE commit d39de0ea91b26b8840195e5674b92c353cc16661. The tests were a bit bugged (they used a non existing group, thus not testing a crucial part) which I only noticed when porting CE changes to EE. --- app/finders/projects_finder.rb | 10 +++++----- spec/finders/projects_finder_spec.rb | 17 ++++++++++++----- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index dd35c215c5..3b4e0362e0 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -23,17 +23,17 @@ class ProjectsFinder group = options[:group] if group - base, extra = group_projects(current_user, group) + segments = group_projects(current_user, group) else - base, extra = all_projects(current_user) + segments = all_projects(current_user) end - if base and extra - union = Gitlab::SQL::Union.new([base.select(:id), extra.select(:id)]) + if segments.length > 1 + union = Gitlab::SQL::Union.new(segments.map { |s| s.select(:id) }) Project.where("projects.id IN (#{union.to_sql})") else - base + segments.first end end diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index d1dede78f7..f32641ef0f 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -3,10 +3,19 @@ require 'spec_helper' describe ProjectsFinder do describe '#execute' do let(:user) { create(:user) } + let(:group) { create(:group) } - let!(:private_project) { create(:project, :private) } - let!(:internal_project) { create(:project, :internal) } - let!(:public_project) { create(:project, :public) } + let!(:private_project) do + create(:project, :private, name: 'A', path: 'A') + end + + let!(:internal_project) do + create(:project, :internal, group: group, name: 'B', path: 'B') + end + + let!(:public_project) do + create(:project, :public, group: group, name: 'C', path: 'C') + end let(:finder) { described_class.new } @@ -38,8 +47,6 @@ describe ProjectsFinder do end describe 'with a group' do - let(:group) { public_project.group } - describe 'without a user' do subject { finder.execute(nil, group: group) } From 899807f604f7923cc40a64cdd0f61c4248b8870d Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Fri, 20 Nov 2015 16:10:08 +0100 Subject: [PATCH 234/410] Update required version of lfs client and separate the docs for users and admins. --- doc/README.md | 1 + doc/workflow/README.md | 1 + doc/workflow/lfs/lfs_administration.md | 41 +++++++++++ .../manage_large_binaries_with_git_lfs.md} | 72 ++++++++----------- lib/gitlab/lfs/response.rb | 2 +- spec/lib/gitlab/lfs/lfs_router_spec.rb | 2 +- 6 files changed, 76 insertions(+), 43 deletions(-) create mode 100644 doc/workflow/lfs/lfs_administration.md rename doc/workflow/{git_lfs.md => lfs/manage_large_binaries_with_git_lfs.md} (73%) diff --git a/doc/README.md b/doc/README.md index 0f6866475f..58ab5dd08e 100644 --- a/doc/README.md +++ b/doc/README.md @@ -50,6 +50,7 @@ - [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. - [Reply by email](incoming_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails. - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. +- [Git LFS configuration](workflow/lfs/lfs_administration.md) ## Contributor documentation diff --git a/doc/workflow/README.md b/doc/workflow/README.md index c1a4f64981..a6b4d95118 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -17,3 +17,4 @@ - [Milestones](milestones.md) - [Merge Requests](merge_requests.md) - ["Work In Progress" Merge Requests](wip_merge_requests.md) +- [Manage large binaries with Git LFS](lfs/manage_large_binaries_with_git_lfs.md) diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md new file mode 100644 index 0000000000..9fa307a9d5 --- /dev/null +++ b/doc/workflow/lfs/lfs_administration.md @@ -0,0 +1,41 @@ +## GitLab Git LFS Administration + +Documentation on how to use Git LFS are under [Managing large binary files with Git LFS doc](manage_large_binaries_with_git_lfs.md). + +## Requirements + +* Git LFS is supported in GitLab starting with version 8.2. +* Users need to install [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up + +## Configuration + +Git LFS objects can be large in size. By default, they are stored on the server GitLab is installed on. + +There are two configuration options to help GitLab server administrators: + +* Enabling/disabling Git LFS support +* Changing the location of LFS object storage + +### Omnibus packages + +In `/etc/gitlab/gitlab.rb`: + +```ruby +gitlab_rails['lfs_enabled'] = false +gitlab_rails['lfs_storage_path'] = "/mnt/storage/lfs-objects" +``` + +### Installations from source + +In `config/gitlab.yml`: + +```yaml + lfs: + enabled: false + storage_path: /mnt/storage/lfs-objects +``` + +## Known limitations + +* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported +* Currently, removing LFS objects from GitLab Git LFS storage is not supported diff --git a/doc/workflow/git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md similarity index 73% rename from doc/workflow/git_lfs.md rename to doc/workflow/lfs/manage_large_binaries_with_git_lfs.md index 616a71522a..a93fd3dfb1 100644 --- a/doc/workflow/git_lfs.md +++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md @@ -7,55 +7,27 @@ GitLab already supports [managing large files with git annex](http://doc.gitlab. environments it is not always convenient to use different commands to differentiate between the large files and regular ones. Git LFS makes this simpler for the end user by removing the requirement to learn new commands. - ## How it works Git LFS client talks with the GitLab server over HTTPS. It uses HTTP Basic Authentication to authorize client requests. Once the request is authorized, Git LFS client receives instructions from where to fetch or where to push the large file. +## GitLab server configuration + +Documentation for GitLab instance administrators is under [LFS administration doc](lfs_administration.md). + ## Requirements * Git LFS is supported in GitLab starting with version 8.2 -* Git LFS [client](https://git-lfs.github.com) version 0.6.0 and up - -## GitLab and Git LFS - -### Configuration - -Git LFS objects can be large in size. By default, they are stored on the server GitLab is installed on. - -There are two configuration options to help GitLab server administrators: - -* Enabling/disabling Git LFS support -* Changing the location of LFS object storage - -#### Omnibus packages - -In `/etc/gitlab/gitlab.rb`: - -```ruby -gitlab_rails['lfs_enabled'] = false -gitlab_rails['lfs_storage_path'] = "/mnt/storage/lfs-objects" -``` - -#### Installations from source - -In `config/gitlab.yml`: - -```yaml - lfs: - enabled: false - storage_path: /mnt/storage/lfs-objects -``` +* [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up ## Known limitations -* Git LFS v1 original API is not supported since it was deprecated early in LFS development, starting with Git LFS version 0.6.0 +* Git LFS v1 original API is not supported since it was deprecated early in LFS development * When SSH is set as a remote, Git LFS objects still go through HTTPS * Any Git LFS request will ask for HTTPS credentials to be provided so good Git credentials store is recommended -* Currently, storing GitLab Git LFS objects on a non-local storage (like S3 buckets) is not supported -* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the URL to Git config manually (see #troubleshooting-tips) +* Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have to add the URL to Git config manually (see #troubleshooting) ## Using Git LFS @@ -83,6 +55,11 @@ Cloning the repository works the same as before. Git automatically detects the L git clone git@gitlab.example.com:group/project.git ``` +If you already cloned the repository and you want to get the latest LFS object that are on the remote repository, eg. from branch `master`: + +```bash +git lfs fetch master +``` ## Troubleshooting @@ -90,17 +67,30 @@ git clone git@gitlab.example.com:group/project.git There are a couple of reasons why this error can occur: -* Wrong version of LFS client used: +* You don't have permissions to access certain LFS object -Check the version of Git LFS on the client machine with `git lfs version`. Only version 0.6.0 and newer are supported. +Check if you have permissions to push to the project or fetch from the project. + +* Project is not allowed to access the LFS object + +Check if the LFS object you are trying to push to the project or fetch from the project is available to the project. * Project is using deprecated LFS API -Check the Git config of the project for traces of deprecated API with `git lfs -l`. If `batch = false` is set in the config, remove the line and try using Git LFS client newer than 0.6.0. - ### Invalid status for : 501 -When attempting to push a LFS object to a GitLab server that doesn't have Git LFS support enabled, server will return status `error 501`. Check with your GitLab administrator why Git LFS is not enabled on the server. See [Configuration section](#configuration) for instructions on how to enable LFS support. +Git LFS will log the failures into a log file. +To view this log file, while in project directory: + +```bash +git lfs logs last +``` + +If the status `error 501` is shown, it is because: + +* Git LFS support is not enabled on the GitLab server. Check with your GitLab administrator why Git LFS is not enabled on the server. See [LFS administration documentation](lfs_administration.md) for instructions on how to enable LFS support. + +* Git LFS client version is not supported by GitLab server. Check your Git LFS version with `git lfs version`. Check the Git config of the project for traces of deprecated API with `git lfs -l`. If `batch = false` is set in the config, remove the line and try to update your Git LFS client. Only version 1.0.1 and newer are supported. ### getsockopt: connection refused @@ -132,4 +122,4 @@ This will remember the credentials for an hour after which Git operations will r If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, `wincred` is available. -More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage) \ No newline at end of file +More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb index aceef53ba6..c18dfbd485 100644 --- a/lib/gitlab/lfs/response.rb +++ b/lib/gitlab/lfs/response.rb @@ -57,7 +57,7 @@ module Gitlab 501, { "Content-Type" => "application/json; charset=utf-8" }, [JSON.dump({ - 'message' => 'Server supports batch API only, please update your Git LFS client to version 0.6.0 and up.', + 'message' => 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.', 'documentation_url' => "#{Gitlab.config.gitlab.url}/help", })] ] diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb index 92598559ae..5b13d65c7c 100644 --- a/spec/lib/gitlab/lfs/lfs_router_spec.rb +++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::Lfs::Router do let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" } let(:sample_size) { 499013 } - let(:respond_with_deprecated) {[ 501, { "Content-Type"=>"application/json; charset=utf-8" }, ["{\"message\":\"Server supports batch API only, please update your Git LFS client to version 0.6.0 and up.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]} + let(:respond_with_deprecated) {[ 501, { "Content-Type"=>"application/json; charset=utf-8" }, ["{\"message\":\"Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]} let(:respond_with_disabled) {[ 501, { "Content-Type"=>"application/json; charset=utf-8" }, ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]} describe 'when lfs is disabled' do From fc18e96db38f5d5ab5e102fe630682f6779203ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Fri, 20 Nov 2015 10:36:12 -0500 Subject: [PATCH 235/410] Refactor creation of system notes for Issue/MR labels. #2296 --- app/services/issuable_base_service.rb | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 19055fb67f..2556f06e2d 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -53,15 +53,7 @@ class IssuableBaseService < BaseService if params.present? && issuable.update_attributes(params.merge(updated_by: current_user)) issuable.reset_events_cache - - if issuable.labels != old_labels - create_labels_note( - issuable, - issuable.labels - old_labels, - old_labels - issuable.labels) - end - - handle_common_system_notes(issuable) + handle_common_system_notes(issuable, old_labels: old_labels) handle_changes(issuable) issuable.create_new_cross_references!(current_user) execute_hooks(issuable, 'update') @@ -79,7 +71,7 @@ class IssuableBaseService < BaseService end end - def handle_common_system_notes(issuable) + def handle_common_system_notes(issuable, options = {}) if issuable.previous_changes.include?('title') create_title_change_note(issuable, issuable.previous_changes['title'].first) end @@ -87,5 +79,10 @@ class IssuableBaseService < BaseService if issuable.previous_changes.include?('description') && issuable.tasks? create_task_status_note(issuable) end + + old_labels = options[:old_labels] + if old_labels && (issuable.labels != old_labels) + create_labels_note(issuable, issuable.labels - old_labels, old_labels - issuable.labels) + end end end From fa9f2dec0e07ff3ae3a2acd6ee0586e317bdb7b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Fri, 20 Nov 2015 10:49:12 -0500 Subject: [PATCH 236/410] Monkey patching TaskList::Item is no longer required. #2296 --- app/models/concerns/taskable.rb | 2 ++ app/services/system_note_service.rb | 3 ++- config/initializers/task_list_ext.rb | 12 ------------ 3 files changed, 4 insertions(+), 13 deletions(-) delete mode 100644 config/initializers/task_list_ext.rb diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index 3daa4dbe24..de7588fea8 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -7,6 +7,8 @@ require 'task_list/filter' # # Used by MergeRequest and Issue module Taskable + COMPLETED = 'completed'.freeze + INCOMPLETE = 'incomplete'.freeze ITEM_PATTERN = / ^ (?:\s*[-+*]|(?:\d+\.))? # optional list prefix diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 7c5d523ef3..7e2bc83417 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -355,7 +355,8 @@ class SystemNoteService # # Returns the created Note object def self.change_task_status(noteable, project, author, new_task) - body = "Marked the task **#{new_task.source}** as #{new_task.status_label}" + status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE + body = "Marked the task **#{new_task.source}** as #{status_label}" create_note(noteable: noteable, project: project, author: author, note: body) end end diff --git a/config/initializers/task_list_ext.rb b/config/initializers/task_list_ext.rb deleted file mode 100644 index c05b683b5b..0000000000 --- a/config/initializers/task_list_ext.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'task_list' - -class TaskList - class Item - COMPLETED = 'completed'.freeze - INCOMPLETE = 'incomplete'.freeze - - def status_label - complete? ? COMPLETED : INCOMPLETE - end - end -end From ad1f451f249692f9ed27d3f27ee712178cb30742 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 20 Nov 2015 08:13:25 -0800 Subject: [PATCH 237/410] Fix Drone web hook URL not being updated --- app/models/project_services/drone_ci_service.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index c240213200..127684bd27 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -32,6 +32,8 @@ class DroneCiService < CiService def compose_service_hook hook = service_hook || build_service_hook + # If using a service template, project may not be available + hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join if project hook.enable_ssl_verification = enable_ssl_verification hook.save end From 3aabed3456506d1a917e6daba29cd46ce6a25dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Fri, 20 Nov 2015 13:58:45 -0500 Subject: [PATCH 238/410] Fix bug that happened when replacing the Task list. #2296 REF: https://gitlab.com/gitlab-org/gitlab-ce/issues/2296#note_2724697 --- app/models/concerns/taskable.rb | 2 +- spec/services/issues/update_service_spec.rb | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index de7588fea8..df2a9e3e84 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -31,7 +31,7 @@ module Taskable old_task = old_tasks[i] next unless old_task - new_task.source == new_task.source && new_task.complete? != old_task.complete? + new_task.source == old_task.source && new_task.complete? != old_task.complete? end end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index adb3aa143a..bc6a26416a 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -121,6 +121,25 @@ describe Issues::UpdateService do expect(note).to be_nil end end + + context 'when a Task list with a completed item is totally replaced' do + before do + update_issue({ description: "- [ ] Task 1\n- [X] Task 2" }) + update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" }) + end + + it 'does not create a system note referencing the position the old item' do + note = find_note('Marked the task **Two** as incomplete') + + expect(note).to be_nil + end + + it 'should not generate a new note at all' do + expect { + update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" }) + }.not_to change { Note.count } + end + end end end From 976cebe45681766764509c3b4df630ced6cc6e03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Fri, 20 Nov 2015 14:27:31 -0500 Subject: [PATCH 239/410] Little fix for Rubocop's complaints. #2296 --- spec/services/issues/update_service_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index bc6a26416a..cc3e648326 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -135,9 +135,9 @@ describe Issues::UpdateService do end it 'should not generate a new note at all' do - expect { + expect do update_issue({ description: "- [ ] One\n- [ ] Two\n- [ ] Three" }) - }.not_to change { Note.count } + end.not_to change { Note.count } end end end From d22435f3ffe005bddcd0ca24137d55cec97c84fb Mon Sep 17 00:00:00 2001 From: Greg Smethells Date: Fri, 20 Nov 2015 14:15:01 -0600 Subject: [PATCH 240/410] fix syntax error in common.scss --- app/assets/stylesheets/framework/common.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 40f4beb196..61689aff57 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -64,7 +64,7 @@ pre { .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus { background: $gl-primary; - color: #FFF + color: #FFF; } .str-truncated { From 2ed48df79af82313539fc0bf5dceac2785350262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Fri, 20 Nov 2015 16:50:48 -0500 Subject: [PATCH 241/410] Remove accidentally added line. #3598 It should exist in EE only. --- app/workers/repository_import_worker.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index 1de4916199..d18c0706b3 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -51,8 +51,5 @@ class RepositoryImportWorker end project.import_finish - - # Explicitly update mirror so that upstream remote is created and fetched - project.update_mirror end end From d496a6b919abd32252f54e59d5607956005cfb15 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 20 Nov 2015 23:43:10 +0100 Subject: [PATCH 242/410] Handle removed source projects in MR CI commits When calling MergeRequest#ci_commit the code would previously raise an error if the source project no longer existed (e.g. because the user removed their fork). See #3599 for more information. --- app/models/merge_request.rb | 2 +- spec/models/merge_request_spec.rb | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 1e8d9908f0..1b3d6079d2 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -476,7 +476,7 @@ class MergeRequest < ActiveRecord::Base end def ci_commit - if last_commit + if last_commit and source_project source_project.ci_commit(last_commit.id) end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 90af75ff0e..567c911425 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -193,4 +193,29 @@ describe MergeRequest do it_behaves_like 'a Taskable' do subject { create :merge_request, :simple } end + + describe '#ci_commit' do + describe 'when the source project exists' do + it 'returns the latest commit' do + commit = double(:commit, id: '123abc') + ci_commit = double(:ci_commit) + + allow(subject).to receive(:last_commit).and_return(commit) + + expect(subject.source_project).to receive(:ci_commit). + with('123abc'). + and_return(ci_commit) + + expect(subject.ci_commit).to eq(ci_commit) + end + end + + describe 'when the source project does not exist' do + it 'returns nil' do + allow(subject).to receive(:source_project).and_return(nil) + + expect(subject.ci_commit).to be_nil + end + end + end end From be045553ea4115853847cfcb8a0a40f2a3d7c4a2 Mon Sep 17 00:00:00 2001 From: Jose Corcuera Date: Fri, 20 Nov 2015 23:55:17 -0500 Subject: [PATCH 243/410] Fix Assignee selector when 'Unassigned' #2831 --- CHANGELOG | 1 + app/assets/javascripts/users_select.js.coffee | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 75e5b7585f..fc7b6e75b1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) + - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) v 8.2.0 - Improved performance of finding projects and groups in various places diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 9157562a5c..f5db74d84e 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -58,11 +58,8 @@ class @UsersSelect query.callback(data) - initSelection: (element, callback) => - id = $(element).val() - if id != "" && id != "0" - @user(id, callback) - + initSelection: (args...) => + @initSelection(args...) formatResult: (args...) => @formatResult(args...) formatSelection: (args...) => @@ -71,6 +68,14 @@ class @UsersSelect escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results m + initSelection: (element, callback) -> + id = $(element).val() + if id == "0" + nullUser = { name: 'Unassigned' } + callback(nullUser) + else if id != "" + @user(id, callback) + formatResult: (user) -> if user.avatar_url avatar = user.avatar_url From ca735446efe46e9309fa59673ecea97fb11ddcd5 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sat, 21 Nov 2015 12:13:30 +0100 Subject: [PATCH 244/410] Add tags page to readme [ci skip] --- doc/api/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/api/README.md b/doc/api/README.md index 6b8528de50..25a31b235c 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -10,6 +10,7 @@ - [Repositories](repositories.md) - [Repository Files](repository_files.md) - [Commits](commits.md) +- [Tags](tags.md) - [Branches](branches.md) - [Merge Requests](merge_requests.md) - [Issues](issues.md) From c113e23489fe4ee0a0d0be837090597595800553 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sat, 21 Nov 2015 16:50:37 +0100 Subject: [PATCH 245/410] Use award emoticons in GitLab workflow [ci skip] --- doc/workflow/gitlab_flow.md | 9 ++++----- doc/workflow/voting_slider.png | Bin 5329 -> 0 bytes 2 files changed, 4 insertions(+), 5 deletions(-) delete mode 100644 doc/workflow/voting_slider.png diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md index 31495bce76..8965e5b365 100644 --- a/doc/workflow/gitlab_flow.md +++ b/doc/workflow/gitlab_flow.md @@ -244,13 +244,12 @@ Developing software happen in small messy steps and it is OK to have your histor You can use tools to view the network graphs of commits and understand the messy history that created your code. If you rebase code the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers. -## Voting on merge requests +## Award emojis on issues and merge requests -![Voting slider in GitLab](voting_slider.png) +![Emoji bar in GitLab](award_emoji.png) -It is common to voice approval or disapproval by using +1 or -1 emoticons. -In GitLab the +1 and -1 are aggregated and shown at the top of the merge request. -As a rule of thumb anything that doesn't have two times more +1's than -1's is suspect and should not be merged yet. +It is common to voice approval or disapproval by using +1 or -1. In GitLab you +can use emojis to give a virtual high five on issues and merge requests. ## Pushing and removing branches diff --git a/doc/workflow/voting_slider.png b/doc/workflow/voting_slider.png deleted file mode 100644 index 4c660ef95937c66924a2f01373cc4efed4eae831..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5329 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 From 2cba93a0d2d12ee36bf98569e5c6c14ac7ea40e0 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sat, 21 Nov 2015 17:29:26 +0100 Subject: [PATCH 246/410] Make tag API consistent for release feature --- doc/api/tags.md | 10 +++++----- lib/api/entities.rb | 3 ++- lib/api/tags.rb | 8 ++++---- spec/requests/api/tags_spec.rb | 6 +++--- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/doc/api/tags.md b/doc/api/tags.md index b5b90cf6b8..cf95f87dc3 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -29,7 +29,7 @@ Parameters: ] }, "release": { - "tag": "1.0.0", + "tag_name": "1.0.0", "description": "Amazing release. Wow" }, "name": "v1.0.0", @@ -70,7 +70,7 @@ Parameters: ] }, "release": { - "tag": "1.0.0", + "tag_name": "1.0.0", "description": "Amazing release. Wow" }, "name": "v1.0.0", @@ -89,18 +89,18 @@ It returns 200 if the operation succeed. In case of an error, Add release notes to the existing git tag ``` -PUT /projects/:id/repository/:tag/release +PUT /projects/:id/repository/tags/:tag_name/release ``` Parameters: - `id` (required) - The ID of a project -- `tag` (required) - The name of a tag +- `tag_name` (required) - The name of a tag - `description` (required) - Release notes with markdown support ```json { - "tag": "1.0.0", + "tag_name": "1.0.0", "description": "Amazing release. Wow" } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 3da6bc415d..5dea74db29 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -322,7 +322,8 @@ module API end class Release < Grape::Entity - expose :tag, :description + expose :tag, as: :tag_name + expose :description end class RepoTag < Grape::Entity diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 673342dd44..2c6c73da08 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -44,14 +44,14 @@ module API # # Parameters: # id (required) - The ID of a project - # tag (required) - The name of the tag + # tag_name (required) - The name of the tag # description (required) - Release notes with markdown support # Example Request: - # PUT /projects/:id/repository/tags - put ':id/repository/:tag/release', requirements: { tag: /.*/ } do + # PUT /projects/:id/repository/tags/:tag_name/release + put ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.*/ } do authorize_push_project required_attributes! [:description] - release = user_project.releases.find_or_initialize_by(tag: params[:tag]) + release = user_project.releases.find_or_initialize_by(tag: params[:tag_name]) release.update_attributes(description: params[:description]) present release, with: Entities::Release diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index cc9a5f4758..e3fd2d547c 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -119,16 +119,16 @@ describe API::API, api: true do end end - describe 'PUT /projects/:id/repository/:tag/release' do + describe 'PUT /projects/:id/repository/tags/:tag_name/release' do let(:tag_name) { project.repository.tag_names.first } let(:description) { 'Awesome release!' } it 'should create description for existing git tag' do - put api("/projects/#{project.id}/repository/#{tag_name}/release", user), + put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), description: description expect(response.status).to eq(200) - expect(json_response['tag']).to eq(tag_name) + expect(json_response['tag_name']).to eq(tag_name) expect(json_response['description']).to eq(description) end end From faef95af1a07bdcfd02eead36d144f332b428f1f Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sat, 21 Nov 2015 18:08:45 +0100 Subject: [PATCH 247/410] API: Return 404 if the tag for a release does not exist --- app/services/create_release_service.rb | 25 +++++++++++++++++++++++++ app/services/create_tag_service.rb | 10 +++++----- doc/api/tags.md | 3 ++- lib/api/tags.rb | 10 +++++++--- spec/requests/api/tags_spec.rb | 8 ++++++++ 5 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 app/services/create_release_service.rb diff --git a/app/services/create_release_service.rb b/app/services/create_release_service.rb new file mode 100644 index 0000000000..355374ce25 --- /dev/null +++ b/app/services/create_release_service.rb @@ -0,0 +1,25 @@ +require_relative 'base_service' + +class CreateReleaseService < BaseService + def execute(tag_name, release_description) + + repository = project.repository + existing_tag = repository.find_tag(tag_name) + + # Only create a release if the tag exists + if existing_tag + release = project.releases.find_or_initialize_by(tag: tag_name) + release.update_attributes(description: release_description) + + success(release) + else + error('Tag does not exist') + end + end + + def success(release) + out = super() + out[:release] = release + out + end +end diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb index 9917119fce..2452999382 100644 --- a/app/services/create_tag_service.rb +++ b/app/services/create_tag_service.rb @@ -19,16 +19,16 @@ class CreateTagService < BaseService new_tag = repository.find_tag(tag_name) if new_tag - if release_description - release = project.releases.find_or_initialize_by(tag: tag_name) - release.update_attributes(description: release_description) - end - push_data = create_push_data(project, current_user, new_tag) EventCreateService.new.push(project, current_user, push_data) project.execute_hooks(push_data.dup, :tag_push_hooks) project.execute_services(push_data.dup, :tag_push_hooks) + if release_description + CreateReleaseService.new(@project, @current_user). + execute(tag_name, release_description) + end + success(new_tag) else error('Invalid reference name') diff --git a/doc/api/tags.md b/doc/api/tags.md index cf95f87dc3..ca9e2cef65 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -86,7 +86,8 @@ It returns 200 if the operation succeed. In case of an error, ## New release -Add release notes to the existing git tag +Add release notes to the existing git tag. It returns 200 if the release is +created successfully. If the tag does not exist, 404 is returned. ``` PUT /projects/:id/repository/tags/:tag_name/release diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 2c6c73da08..0721b9cc84 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -51,10 +51,14 @@ module API put ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.*/ } do authorize_push_project required_attributes! [:description] - release = user_project.releases.find_or_initialize_by(tag: params[:tag_name]) - release.update_attributes(description: params[:description]) + result = CreateReleaseService.new(user_project, current_user). + execute(params[:tag_name], params[:description]) - present release, with: Entities::Release + if result[:status] == :success + present result[:release], with: Entities::Release + else + render_api_error!(result[:message], 404) + end end end end diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index e3fd2d547c..4dac3eec84 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -131,5 +131,13 @@ describe API::API, api: true do expect(json_response['tag_name']).to eq(tag_name) expect(json_response['description']).to eq(description) end + + it 'should return 404 if the tag does not exist' do + put api("/projects/#{project.id}/repository/tags/foobar/release", user), + description: description + + expect(response.status).to eq(404) + expect(json_response['message']).to eq('Tag does not exist') + end end end From fbac9e106dd6acf35ba679b106b438a3bbfd191f Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Sat, 21 Nov 2015 18:32:59 +0200 Subject: [PATCH 248/410] Award: merge request fix --- app/controllers/projects/notes_controller.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 1e3f1d8fd2..ead940aea6 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -59,8 +59,11 @@ class Projects::NotesController < Projects::ApplicationController end def award_toggle - noteable = note_params[:noteable_type] == "issue" ? Issue : MergeRequest - noteable = noteable.find_by!(id: note_params[:noteable_id], project: project) + noteable = if note_params[:noteable_type] == "issue" + project.issues.find(note_params[:noteable_id]) + else + project.merge_requests.find(note_params[:noteable_id]) + end data = { author: current_user, From 6f7e90f6dba300591281aba08ffbe30ce3cc5c90 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sat, 21 Nov 2015 18:51:41 +0100 Subject: [PATCH 249/410] Use POST to create a new release instead of PUT --- doc/api/tags.md | 6 +++--- lib/api/tags.rb | 2 +- spec/requests/api/tags_spec.rb | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/api/tags.md b/doc/api/tags.md index ca9e2cef65..ab13511725 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -84,13 +84,13 @@ It returns 200 if the operation succeed. In case of an error, 405 with an explaining error message is returned. -## New release +## Create a new release -Add release notes to the existing git tag. It returns 200 if the release is +Add release notes to the existing git tag. It returns 201 if the release is created successfully. If the tag does not exist, 404 is returned. ``` -PUT /projects/:id/repository/tags/:tag_name/release +POST /projects/:id/repository/tags/:tag_name/release ``` Parameters: diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 0721b9cc84..48f630d58e 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -48,7 +48,7 @@ module API # description (required) - Release notes with markdown support # Example Request: # PUT /projects/:id/repository/tags/:tag_name/release - put ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.*/ } do + post ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.*/ } do authorize_push_project required_attributes! [:description] result = CreateReleaseService.new(user_project, current_user). diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 4dac3eec84..0ee819ae44 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -119,21 +119,21 @@ describe API::API, api: true do end end - describe 'PUT /projects/:id/repository/tags/:tag_name/release' do + describe 'POST /projects/:id/repository/tags/:tag_name/release' do let(:tag_name) { project.repository.tag_names.first } let(:description) { 'Awesome release!' } it 'should create description for existing git tag' do - put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), + post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), description: description - expect(response.status).to eq(200) + expect(response.status).to eq(201) expect(json_response['tag_name']).to eq(tag_name) expect(json_response['description']).to eq(description) end it 'should return 404 if the tag does not exist' do - put api("/projects/#{project.id}/repository/tags/foobar/release", user), + post api("/projects/#{project.id}/repository/tags/foobar/release", user), description: description expect(response.status).to eq(404) From c502695fdb9b01b4ff8721845e1d39f3d3369008 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 21 Nov 2015 13:15:51 -0500 Subject: [PATCH 250/410] Add award_emoji.png doc image [ci skip] --- doc/workflow/award_emoji.png | Bin 0 -> 6620 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/workflow/award_emoji.png diff --git a/doc/workflow/award_emoji.png b/doc/workflow/award_emoji.png new file mode 100644 index 0000000000000000000000000000000000000000..fb26ee043930b2a06f4b93dd3918c041e2f32033 GIT binary patch literal 6620 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 literal 0 HcmV?d00001 From f2318dfc9e80555bae99f543d4e07ec089ab3935 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sat, 21 Nov 2015 20:25:07 +0100 Subject: [PATCH 251/410] Fix CI yaml syntax documentation [ci skip] --- doc/ci/yaml/README.md | 90 ++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 3e6071a2ee..3dbf1afc7a 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -278,26 +278,23 @@ The above script will: `artifacts` is used to specify list of files and directories which should be attached to build after success. 1. Send all files in `binaries` and `.config`: -``` -artifacts: - paths: - - binaries/ - - .config -``` + + artifacts: + paths: + - binaries/ + - .config 2. Send all git untracked files: -``` -artifacts: - untracked: true -``` + + artifacts: + untracked: true 3. Send all git untracked files and files in `binaries`: -``` -artifacts: - untracked: true - paths: - - binaries/ -``` + + artifacts: + untracked: true + paths: + - binaries/ The artifacts will be send after the build success to GitLab and will be accessible in GitLab interface to download. @@ -307,46 +304,41 @@ This feature requires GitLab Runner v0.7.0 or higher. `cache` is used to specify list of files and directories which should be cached between builds. 1. Cache all files in `binaries` and `.config`: -``` -rspec: - script: test - cache: - paths: - - binaries/ - - .config -``` + + rspec: + script: test + cache: + paths: + - binaries/ + - .config 2. Cache all git untracked files: -``` -rspec: - script: test - cache: - untracked: true -``` + rspec: + script: test + cache: + untracked: true + 3. Cache all git untracked files and files in `binaries`: -``` -rspec: - script: test - cache: - untracked: true - paths: - - binaries/ -``` + + rspec: + script: test + cache: + untracked: true + paths: + - binaries/ 4. Locally defined cache overwrites globally defined options. This will cache only `binaries/`: -``` -cache: - paths: - - my/files - -rspec: - script: test - cache: - paths: - - binaries/ -``` + cache: + paths: + - my/files + + rspec: + script: test + cache: + paths: + - binaries/ The cache is provided on best effort basis, so don't expect that cache will be present. For implementation details please check GitLab Runner. From 26b12e2c374c8f07abda06a8b19bd116448325f4 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sat, 21 Nov 2015 21:36:31 +0100 Subject: [PATCH 252/410] Add upvote/downvote fields to merge request and note API to preserve compatibility --- app/models/concerns/issuable.rb | 10 ++++++++++ app/models/note.rb | 10 ++++++++++ doc/api/merge_requests.md | 32 +++++++++++++++++++++++++------- doc/api/notes.md | 11 ++++++++--- lib/api/entities.rb | 5 +++++ 5 files changed, 58 insertions(+), 10 deletions(-) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 2dafb5e752..98ad5e76da 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -92,6 +92,16 @@ module Issuable opened? || reopened? end + # Deprecated. Still exists to preserve API compatibility. + def downvotes + 0 + end + + # Deprecated. Still exists to preserve API compatibility. + def upvotes + 0 + end + def subscribed?(user) subscription = subscriptions.find_by_user_id(user.id) diff --git a/app/models/note.rb b/app/models/note.rb index e30be444eb..1c6345e735 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -335,6 +335,16 @@ class Note < ActiveRecord::Base read_attribute(:system) end + # Deprecated. Still exists to preserve API compatibility. + def downvote? + false + end + + # Deprecated. Still exists to preserve API compatibility. + def upvote? + false + end + def editable? !system? end diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 2f17d4ae06..0cef09d5b2 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -3,8 +3,9 @@ ## List merge requests Get all merge requests for this project. -The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`). -The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests. +The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`). +The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests. With GitLab 8.2 the return fields `upvotes` and +`downvotes` are deprecated and always return `0`. ``` GET /projects/:id/merge_requests @@ -31,6 +32,8 @@ Parameters: "project_id": 3, "title": "test1", "state": "opened", + "upvotes": 0, + "downvotes": 0, "author": { "id": 1, "username": "admin", @@ -55,7 +58,7 @@ Parameters: ## Get single MR -Shows information about a single merge request. +Shows information about a single merge request. With GitLab 8.2 the return fields `upvotes` and `downvotes` are deprecated and always return `0`. ``` GET /projects/:id/merge_request/:merge_request_id @@ -75,6 +78,8 @@ Parameters: "project_id": 3, "title": "test1", "state": "merged", + "upvotes": 0, + "downvotes": 0, "author": { "id": 1, "username": "admin", @@ -98,7 +103,9 @@ Parameters: ## Get single MR changes -Shows information about the merge request including its files and changes +Shows information about the merge request including its files and changes. +With GitLab 8.2 the return fields `upvotes` and `downvotes` are deprecated and +always return `0`. ``` GET /projects/:id/merge_request/:merge_request_id/changes @@ -122,6 +129,8 @@ Parameters: "updated_at": "2015-02-02T20:08:49.959Z", "target_branch": "secret_token", "source_branch": "version-1-9", + "upvotes": 0, + "downvotes": 0, "author": { "name": "Chad Hamill", "username": "jarrett", @@ -167,7 +176,8 @@ Parameters: ## Create MR -Creates a new merge request. +Creates a new merge request. With GitLab 8.2 the return fields `upvotes` and ` +downvotes` are deprecated and always return `0`. ``` POST /projects/:id/merge_requests @@ -192,6 +202,8 @@ Parameters: "project_id": 3, "title": "test1", "state": "opened", + "upvotes": 0, + "downvotes": 0, "author": { "id": 1, "username": "admin", @@ -217,7 +229,8 @@ If an error occurs, an error number and a message explaining the reason is retur ## Update MR -Updates an existing merge request. You can change the target branch, title, or even close the MR. +Updates an existing merge request. You can change the target branch, title, or even close the MR. With GitLab 8.2 the return fields `upvotes` and `downvotes` +are deprecated and always return `0`. ``` PUT /projects/:id/merge_request/:merge_request_id @@ -242,6 +255,8 @@ Parameters: "title": "test1", "description": "description1", "state": "opened", + "upvotes": 0, + "downvotes": 0, "author": { "id": 1, "username": "admin", @@ -266,7 +281,8 @@ If an error occurs, an error number and a message explaining the reason is retur ## Accept MR -Merge changes submitted with MR using this API. +Merge changes submitted with MR using this API. With GitLab 8.2 the return +fields `upvotes` and `downvotes` are deprecated and always return `0`. If merge success you get `200 OK`. @@ -294,6 +310,8 @@ Parameters: "project_id": 3, "title": "test1", "state": "merged", + "upvotes": 0, + "downvotes": 0, "author": { "id": 1, "username": "admin", diff --git a/doc/api/notes.md b/doc/api/notes.md index bcba1f6215..e7f299c099 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -6,7 +6,8 @@ Notes are comments on snippets, issues or merge requests. ### List project issue notes -Gets a list of all notes for a single issue. +Gets a list of all notes for a single issue. With GitLab 8.2 the return fields +`upvote` and `downvote` are deprecated and always return `false`. ``` GET /projects/:id/issues/:issue_id/notes @@ -32,7 +33,9 @@ Parameters: "created_at": "2013-09-30T13:46:01Z" }, "created_at": "2013-10-02T09:22:45Z", - "system": true + "system": true, + "upvote": false, + "downvote": false }, { "id": 305, @@ -47,7 +50,9 @@ Parameters: "created_at": "2013-09-30T13:46:01Z" }, "created_at": "2013-10-02T09:56:03Z", - "system": false + "system": true, + "upvote": false, + "downvote": false } ] ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 3da6bc415d..7f9dba4b6a 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -163,6 +163,8 @@ module API class MergeRequest < ProjectEntity expose :target_branch, :source_branch + # deprecated, always returns 0 + expose :upvotes, :downvotes expose :author, :assignee, using: Entities::UserBasic expose :source_project_id, :target_project_id expose :label_names, as: :labels @@ -192,6 +194,9 @@ module API expose :author, using: Entities::UserBasic expose :created_at expose :system?, as: :system + # upvote? and downvote? are deprecated, always return false + expose :upvote?, as: :upvote + expose :downvote?, as: :downvote end class MRNote < Grape::Entity From 3ea05c5b5b253de33d8bf8d615c66e2935b940ef Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sat, 21 Nov 2015 22:24:34 +0100 Subject: [PATCH 253/410] Only allow to create a release if it does not exist yet --- app/services/create_release_service.rb | 14 ++++++++++---- doc/api/tags.md | 3 ++- lib/api/tags.rb | 4 ++-- spec/requests/api/tags_spec.rb | 17 ++++++++++++++++- 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/app/services/create_release_service.rb b/app/services/create_release_service.rb index 355374ce25..54e87478e6 100644 --- a/app/services/create_release_service.rb +++ b/app/services/create_release_service.rb @@ -8,12 +8,18 @@ class CreateReleaseService < BaseService # Only create a release if the tag exists if existing_tag - release = project.releases.find_or_initialize_by(tag: tag_name) - release.update_attributes(description: release_description) + release = project.releases.find_by(tag: tag_name) - success(release) + if(release) + error('Release already exists', 409) + else + release = project.releases.new({ tag: tag_name, description: release_description }) + release.save + + success(release) + end else - error('Tag does not exist') + error('Tag does not exist', 404) end end diff --git a/doc/api/tags.md b/doc/api/tags.md index ab13511725..bc61aa1118 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -87,7 +87,8 @@ It returns 200 if the operation succeed. In case of an error, ## Create a new release Add release notes to the existing git tag. It returns 201 if the release is -created successfully. If the tag does not exist, 404 is returned. +created successfully. If the tag does not exist, 404 is returned. If there +already exists a release for the given tag, 409 is returned. ``` POST /projects/:id/repository/tags/:tag_name/release diff --git a/lib/api/tags.rb b/lib/api/tags.rb index 48f630d58e..e46d9bb96f 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -47,7 +47,7 @@ module API # tag_name (required) - The name of the tag # description (required) - Release notes with markdown support # Example Request: - # PUT /projects/:id/repository/tags/:tag_name/release + # POST /projects/:id/repository/tags/:tag_name/release post ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.*/ } do authorize_push_project required_attributes! [:description] @@ -57,7 +57,7 @@ module API if result[:status] == :success present result[:release], with: Entities::Release else - render_api_error!(result[:message], 404) + render_api_error!(result[:message], result[:http_status]) end end end diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 0ee819ae44..0a456fc3b5 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -28,10 +28,10 @@ describe API::API, api: true do before do release = project.releases.find_or_initialize_by(tag: tag_name) release.update_attributes(description: description) - get api("/projects/#{project.id}/repository/tags", user) end it "should return an array of project tags with release info" do + get api("/projects/#{project.id}/repository/tags", user) expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.first['name']).to eq(tag_name) @@ -139,5 +139,20 @@ describe API::API, api: true do expect(response.status).to eq(404) expect(json_response['message']).to eq('Tag does not exist') end + + context 'on tag with existing release' do + before do + release = project.releases.find_or_initialize_by(tag: tag_name) + release.update_attributes(description: description) + end + + it 'should return 409 if there is already a release' do + post api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), + description: description + + expect(response.status).to eq(409) + expect(json_response['message']).to eq('Release already exists') + end + end end end From 04a3d27eaba0312d99e8d88a3a9ee4b5c83ecce1 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sat, 21 Nov 2015 22:34:53 +0100 Subject: [PATCH 254/410] Allow editing a release in API via PUT method --- app/services/create_release_service.rb | 2 +- app/services/update_release_service.rb | 29 ++++++++++++++++++++ doc/api/tags.md | 23 ++++++++++++++++ lib/api/tags.rb | 21 ++++++++++++++ spec/requests/api/tags_spec.rb | 38 ++++++++++++++++++++++++++ 5 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 app/services/update_release_service.rb diff --git a/app/services/create_release_service.rb b/app/services/create_release_service.rb index 54e87478e6..e06a6f2f47 100644 --- a/app/services/create_release_service.rb +++ b/app/services/create_release_service.rb @@ -10,7 +10,7 @@ class CreateReleaseService < BaseService if existing_tag release = project.releases.find_by(tag: tag_name) - if(release) + if release error('Release already exists', 409) else release = project.releases.new({ tag: tag_name, description: release_description }) diff --git a/app/services/update_release_service.rb b/app/services/update_release_service.rb new file mode 100644 index 0000000000..25eb13ef09 --- /dev/null +++ b/app/services/update_release_service.rb @@ -0,0 +1,29 @@ +require_relative 'base_service' + +class UpdateReleaseService < BaseService + def execute(tag_name, release_description) + + repository = project.repository + existing_tag = repository.find_tag(tag_name) + + if existing_tag + release = project.releases.find_by(tag: tag_name) + + if release + release.update_attributes(description: release_description) + + success(release) + else + error('Release does not exist', 404) + end + else + error('Tag does not exist', 404) + end + end + + def success(release) + out = super() + out[:release] = release + out + end +end diff --git a/doc/api/tags.md b/doc/api/tags.md index bc61aa1118..085d387e82 100644 --- a/doc/api/tags.md +++ b/doc/api/tags.md @@ -106,3 +106,26 @@ Parameters: "description": "Amazing release. Wow" } ``` + +## Update a release + +Updates the release notes of a given release. It returns 200 if the release is +successfully updated. If the tag or the release does not exist, it returns 404 +with a proper error message. + +``` +PUT /projects/:id/repository/tags/:tag_name/release +``` + +Parameters: + +- `id` (required) - The ID of a project +- `tag_name` (required) - The name of a tag +- `description` (required) - Release notes with markdown support + +```json +{ + "tag_name": "1.0.0", + "description": "Amazing release. Wow" +} +``` \ No newline at end of file diff --git a/lib/api/tags.rb b/lib/api/tags.rb index e46d9bb96f..47621f443e 100644 --- a/lib/api/tags.rb +++ b/lib/api/tags.rb @@ -60,6 +60,27 @@ module API render_api_error!(result[:message], result[:http_status]) end end + + # Updates a release notes of a tag + # + # Parameters: + # id (required) - The ID of a project + # tag_name (required) - The name of the tag + # description (required) - Release notes with markdown support + # Example Request: + # PUT /projects/:id/repository/tags/:tag_name/release + put ':id/repository/tags/:tag_name/release', requirements: { tag_name: /.*/ } do + authorize_push_project + required_attributes! [:description] + result = UpdateReleaseService.new(user_project, current_user). + execute(params[:tag_name], params[:description]) + + if result[:status] == :success + present result[:release], with: Entities::Release + else + render_api_error!(result[:message], result[:http_status]) + end + end end end end diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index 0a456fc3b5..17f2643fd4 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -155,4 +155,42 @@ describe API::API, api: true do end end end + + describe 'PUT id/repository/tags/:tag_name/release' do + let(:tag_name) { project.repository.tag_names.first } + let(:description) { 'Awesome release!' } + let(:new_description) { 'The best release!' } + + context 'on tag with existing release' do + before do + release = project.releases.find_or_initialize_by(tag: tag_name) + release.update_attributes(description: description) + end + + it 'should update the release description' do + put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), + description: new_description + + expect(response.status).to eq(200) + expect(json_response['tag_name']).to eq(tag_name) + expect(json_response['description']).to eq(new_description) + end + end + + it 'should return 404 if the tag does not exist' do + put api("/projects/#{project.id}/repository/tags/foobar/release", user), + description: new_description + + expect(response.status).to eq(404) + expect(json_response['message']).to eq('Tag does not exist') + end + + it 'should return 404 if the release does not exist' do + put api("/projects/#{project.id}/repository/tags/#{tag_name}/release", user), + description: new_description + + expect(response.status).to eq(404) + expect(json_response['message']).to eq('Release does not exist') + end + end end From 3fc10d46f180d5b1904496e4f4e528d215057dbd Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Sun, 22 Nov 2015 01:51:00 +0200 Subject: [PATCH 255/410] Emoji bug: Invalid url to image --- app/assets/javascripts/awards_handler.coffee | 2 +- app/controllers/projects/notes_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee index 635c9b4f8d..09b48fe557 100644 --- a/app/assets/javascripts/awards_handler.coffee +++ b/app/assets/javascripts/awards_handler.coffee @@ -73,7 +73,7 @@ class @AwardsHandler getImage: (emoji, custom_path) -> if custom_path - $(".awards-menu li").first().html().replace(/emoji\/.*\.png/, custom_path) + $("").attr({src: custom_path, width: 20, height: 20}).wrap("
").parent().html() else $("li[data-emoji='" + emoji + "']").html() diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index ead940aea6..5ac18446aa 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -136,7 +136,7 @@ class Projects::NotesController < Projects::ApplicationController discussion_id: note.discussion_id, html: note_to_html(note), award: note.is_award, - emoji_path: note.is_award ? ::AwardEmoji.path_to_emoji_image(note.note) : "", + emoji_path: note.is_award ? view_context.image_url(::AwardEmoji.path_to_emoji_image(note.note)) : "", note: note.note, discussion_html: note_to_discussion_html(note), discussion_with_diff_html: note_to_discussion_with_diff_html(note) From 075e3661c534a06753065e9e3323168b786cdbe5 Mon Sep 17 00:00:00 2001 From: Felipe Orlando Date: Sun, 22 Nov 2015 06:04:20 -0200 Subject: [PATCH 256/410] Update autocomplete_controller to be more readable --- app/controllers/autocomplete_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index aa0268b8d6..77c8dafc01 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -9,7 +9,7 @@ class AutocompleteController < ApplicationController @users = @users.reorder(:name) @users = @users.page(params[:page]).per(PER_PAGE) - unless params[:search].present? + if params[:search].blank? # Include current user if available to filter by "Me" if params[:current_user] && current_user @users = [*@users, current_user].uniq From ab238a767713aeb2ae019e22831fc0049873870c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 22 Nov 2015 07:23:56 -0800 Subject: [PATCH 257/410] Fix CSS styling for clipboard icon in new MR page Closes #3602 --- app/assets/stylesheets/framework/tw_bootstrap.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/framework/tw_bootstrap.scss b/app/assets/stylesheets/framework/tw_bootstrap.scss index 50c0cf61f4..94f0ed761d 100644 --- a/app/assets/stylesheets/framework/tw_bootstrap.scss +++ b/app/assets/stylesheets/framework/tw_bootstrap.scss @@ -190,6 +190,10 @@ .btn { min-width: 124px; } + + .btn-clipboard { + min-width: 0px; + } } &.panel-small { From 732c2d15d235ca112db4b1d15c888da52660a1d5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 22 Nov 2015 18:24:53 -0500 Subject: [PATCH 258/410] Update doc/release/patch.md with more current patch procedures [ci skip] --- doc/release/patch.md | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/doc/release/patch.md b/doc/release/patch.md index 6aa11b283d..3022e375ac 100644 --- a/doc/release/patch.md +++ b/doc/release/patch.md @@ -1,21 +1,46 @@ # Things to do when doing a patch release -NOTE: This is a guide for GitLab developers. If you are trying to install GitLab see the latest stable [installation guide](install/installation.md) and if you are trying to upgrade, see the [upgrade guides](update). +NOTE: This is a guide for GitLab developers. If you are trying to install GitLab +see the latest stable [installation guide](install/installation.md) and if you +are trying to upgrade, see the [upgrade guides](update). ## When to do a patch release -Do a patch release when there is a critical regression that needs to be addresses before the next monthly release. - -Otherwise include it in the monthly release and note there was a regression fix in the release announcement. +Patch releases are done as-needed in order to fix regressions in the current +major release that cannot or should not wait until the next major release. +What's included and when to release is at the discretion of the release manager. ## Release Procedure +### Create a patch issue + +Create an issue in the GitLab CE project. Name it "Release x.y.z", tag it with +the `release` label, and assign it to the milestone of the corresponding major +release. + +Use the following template: + +``` +- Picked into respective `stable` branches: +- [ ] Merge `x-y-stable` into `x-y-stable-ee` +- [ ] release-tools: `x.y.z` +- gitlab-omnibus + - [ ] `x.y.z+ee.0` + - [ ] `x.y.z+ce.0` +- [ ] Deploy +- [ ] Add patch notice to [x.y regressions]() +- [ ] [Blog post]() +- [ ] [Tweet]() +- [ ] Add entry to version.gitlab.com +``` + +Update the issue with links to merge requests that need to be/have been picked +into the `stable` branches. + ### Preparation 1. Verify that the issue can be reproduced 1. Note in the 'GitLab X.X regressions' that you will create a patch -1. Create an issue on private GitLab development server -1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier 1. Fix the issue on a feature branch, do this on the private GitLab development server 1. If it is a security issue, then assign it to the release manager and apply a 'security' label 1. Consider creating and testing workarounds @@ -25,7 +50,6 @@ Otherwise include it in the monthly release and note there was a regression fix 1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X" 1. Merge CE stable branch into EE stable branch - ### Bump version Get release tools @@ -54,4 +78,4 @@ bundle exec rake release["x.x.x"] 1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only) 1. Create the 'x.y.0' version on version.gitlab.com 1. [Create new AMIs](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md) -1. Create a new patch release issue for the next potential release \ No newline at end of file +1. Create a new patch release issue for the next potential release From 11728b50f96a296c760d9e69273d304f92e51f8f Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 22 Nov 2015 14:27:30 +0100 Subject: [PATCH 259/410] Expose artifacts path --- CHANGELOG | 1 + app/models/application_setting.rb | 2 +- app/uploaders/artifact_uploader.rb | 6 +++--- config/gitlab.yml.example | 6 ++++++ config/initializers/1_settings.rb | 9 ++++++++- lib/ci/api/builds.rb | 2 ++ lib/gitlab/current_settings.rb | 2 +- 7 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 75e5b7585f..4b7218b594 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ v 8.3.0 (unreleased) v 8.2.0 - Improved performance of finding projects and groups in various places - Improved performance of rendering user profile pages and Atom feeds + - Expose build artifacts path as config option - Fix grouping of contributors by email in graph. - Improved performance of finding issues with/without labels - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 9e70247ef5..b2d5fe1558 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -99,7 +99,7 @@ class ApplicationSetting < ActiveRecord::Base restricted_signup_domains: Settings.gitlab['restricted_signup_domains'], import_sources: ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], - max_artifacts_size: Settings.gitlab_ci['max_artifacts_size'], + max_artifacts_size: Settings.artifacts['max_size'], ) end diff --git a/app/uploaders/artifact_uploader.rb b/app/uploaders/artifact_uploader.rb index b4e0fc5772..1dccc39e7e 100644 --- a/app/uploaders/artifact_uploader.rb +++ b/app/uploaders/artifact_uploader.rb @@ -5,15 +5,15 @@ class ArtifactUploader < CarrierWave::Uploader::Base attr_accessor :build, :field def self.artifacts_path - File.expand_path('shared/artifacts/', Rails.root) + Gitlab.config.artifacts.path end def self.artifacts_upload_path - File.expand_path('shared/artifacts/tmp/uploads/', Rails.root) + File.join(self.artifacts_path, 'tmp/uploads/') end def self.artifacts_cache_path - File.expand_path('shared/artifacts/tmp/cache/', Rails.root) + File.join(self.artifacts_path, 'tmp/cache/') end def initialize(build, field) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 1788d4c2c7..1da42ab38f 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -124,6 +124,12 @@ production: &base # The mailbox where incoming mail will end up. Usually "inbox". mailbox: "inbox" + ## Build Artifacts + artifacts: + enabled: true + # The location where build artifacts are stored (default: shared/artifacts). + # path: shared/artifacts + ## Git LFS lfs: enabled: true diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index b498fb1e1d..b162b8a83f 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -187,7 +187,6 @@ Settings.gitlab_ci['all_broken_builds'] = true if Settings.gitlab_ci['all_br Settings.gitlab_ci['add_pusher'] = false if Settings.gitlab_ci['add_pusher'].nil? Settings.gitlab_ci['url'] ||= Settings.send(:build_gitlab_ci_url) Settings.gitlab_ci['builds_path'] = File.expand_path(Settings.gitlab_ci['builds_path'] || "builds/", Rails.root) -Settings.gitlab_ci['max_artifacts_size'] ||= 100 # in megabytes # # Reply by email @@ -199,6 +198,14 @@ Settings.incoming_email['ssl'] = false if Settings.incoming_email['ssl']. Settings.incoming_email['start_tls'] = false if Settings.incoming_email['start_tls'].nil? Settings.incoming_email['mailbox'] = "inbox" if Settings.incoming_email['mailbox'].nil? +# +# Build Artifacts +# +Settings['artifacts'] ||= Settingslogic.new({}) +Settings.artifacts['enabled'] = true if Settings.artifacts['enabled'].nil? +Settings.artifacts['path'] = File.expand_path(Settings.artifacts['path'] || File.join(Settings.shared['path'], "artifacts"), Rails.root) +Settings.artifacts['max_size'] ||= 100 # in megabytes + # # Git LFS # diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 0a58667280..15faa6edd8 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -58,6 +58,7 @@ module Ci # POST /builds/:id/artifacts/authorize post ":id/artifacts/authorize" do require_gitlab_workhorse! + not_allowed! unless Gitlab.config.artifacts.enabled build = Ci::Build.find_by_id(params[:id]) not_found! unless build authenticate_build_token!(build) @@ -91,6 +92,7 @@ module Ci # POST /builds/:id/artifacts post ":id/artifacts" do require_gitlab_workhorse! + not_allowed! unless Gitlab.config.artifacts.enabled build = Ci::Build.find_by_id(params[:id]) not_found! unless build authenticate_build_token!(build) diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 2d3e32d953..46a4ef0e31 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -25,7 +25,7 @@ module Gitlab session_expire_delay: Settings.gitlab['session_expire_delay'], import_sources: Settings.gitlab['import_sources'], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], - max_artifacts_size: Ci::Settings.gitlab_ci['max_artifacts_size'], + max_artifacts_size: Settings.artifacts['max_size'], ) end From 57e974c03b693c37b6ca7e57ce86477e32b770f1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 23 Nov 2015 13:49:40 +0100 Subject: [PATCH 260/410] Fix 500 when using CI - Fix for Ci::Build state machine, allowing to process builds without the project - Forcefully update builds that didn't want to update with state machine - Fix saving GitLabCiService as Admin Template --- CHANGELOG | 2 ++ app/models/ci/build.rb | 2 ++ app/models/project_services/gitlab_ci_service.rb | 1 + app/workers/stuck_ci_builds_worker.rb | 3 +++ 4 files changed, 8 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index fc7b6e75b1..1e39a9da59 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,8 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) + - Forcefully update builds that didn't want to update with state machine + - Fix: saving GitLabCiService as Admin Template v 8.2.0 - Improved performance of finding projects and groups in various places diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index e78b154084..52ce1b920f 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -97,6 +97,8 @@ module Ci state_machine :status, initial: :pending do after_transition any => [:success, :failed, :canceled] do |build, transition| + return unless build.gl_project + project = build.project if project.web_hooks? diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index 095d04e0df..c5657b5070 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -30,6 +30,7 @@ class GitlabCiService < CiService end def ensure_gitlab_ci_project + return unless project project.ensure_gitlab_ci_project end diff --git a/app/workers/stuck_ci_builds_worker.rb b/app/workers/stuck_ci_builds_worker.rb index ad02a3b16d..4e5eddbaba 100644 --- a/app/workers/stuck_ci_builds_worker.rb +++ b/app/workers/stuck_ci_builds_worker.rb @@ -14,5 +14,8 @@ class StuckCiBuildsWorker Rails.logger.debug "Dropping stuck #{build.status} build #{build.id} for runner #{build.runner_id}" build.drop end + + # Update builds that failed to drop + builds.update_all(status: 'failed') end end From 93f32b25d23525138ec910f725ebd545ce7ef125 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 23 Nov 2015 14:57:41 +0100 Subject: [PATCH 261/410] Add bundler-audit to CI Signed-off-by: Dmitriy Zaporozhets --- .gitlab-ci.yml | 8 ++++++++ Gemfile | 1 + Gemfile.lock | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9475309354..acba37039a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -87,3 +87,11 @@ flay: tags: - ruby - mysql + +bundler:audit + script: + - bundle exec bundle-audit update + - bundle exec bundle-audit check + tags: + - ruby + - mysql diff --git a/Gemfile b/Gemfile index 8a19885bcb..56f341aac3 100644 --- a/Gemfile +++ b/Gemfile @@ -261,6 +261,7 @@ group :development, :test do gem 'simplecov', '~> 0.10.0', require: false gem 'flog', require: false gem 'flay', require: false + gem 'bundler-audit', require: false gem 'benchmark-ips', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 99cdc2a50a..83d4e6927d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -90,6 +90,9 @@ GEM bullet (4.14.9) activesupport (>= 3.0.0) uniform_notifier (~> 1.9.0) + bundler-audit (0.4.0) + bundler (~> 1.2) + thor (~> 0.18) byebug (6.0.2) cal-heatmap-rails (0.0.1) capybara (2.4.4) @@ -802,6 +805,7 @@ DEPENDENCIES brakeman (= 3.0.1) browser (~> 1.0.0) bullet + bundler-audit byebug cal-heatmap-rails (~> 0.0.1) capybara (~> 2.4.0) From 7ac112d8e814a8e3bb08e24f5af0d8828e38d4e9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 23 Nov 2015 15:18:59 +0100 Subject: [PATCH 262/410] Add few fixes to documentation based on comments from review Signed-off-by: Dmitriy Zaporozhets --- doc/workflow/lfs/lfs_administration.md | 4 ++-- doc/workflow/lfs/manage_large_binaries_with_git_lfs.md | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md index 9fa307a9d5..5076b2697a 100644 --- a/doc/workflow/lfs/lfs_administration.md +++ b/doc/workflow/lfs/lfs_administration.md @@ -1,11 +1,11 @@ -## GitLab Git LFS Administration +# GitLab Git LFS Administration Documentation on how to use Git LFS are under [Managing large binary files with Git LFS doc](manage_large_binaries_with_git_lfs.md). ## Requirements * Git LFS is supported in GitLab starting with version 8.2. -* Users need to install [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up +* Users need to install [Git LFS client](https://git-lfs.github.com) version 1.0.1 and up. ## Configuration 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 a93fd3dfb1..210a8f71c3 100644 --- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md +++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md @@ -73,9 +73,10 @@ Check if you have permissions to push to the project or fetch from the project. * Project is not allowed to access the LFS object -Check if the LFS object you are trying to push to the project or fetch from the project is available to the project. +LFS object you are trying to push to the project or fetch from the project is not available to the project anymore. +Probably the object was removed from the server. -* Project is using deprecated LFS API +* Local git repository is using deprecated LFS API ### Invalid status for : 501 From 200c82adade27225caf2035721161a99296050f3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 23 Nov 2015 15:21:38 +0100 Subject: [PATCH 263/410] Fix gitlab-ci.yml syntax Signed-off-by: Dmitriy Zaporozhets --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index acba37039a..38fd7b9ac1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -88,10 +88,10 @@ flay: - ruby - mysql -bundler:audit +bundler:audit: script: - - bundle exec bundle-audit update - - bundle exec bundle-audit check + - "bundle exec bundle-audit update" + - "bundle exec bundle-audit check" tags: - ruby - mysql From b6ed935dcddef1b458b3431dae7a1e8e250e081f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 23 Nov 2015 15:45:16 +0100 Subject: [PATCH 264/410] Allow bundler:audit to fail Signed-off-by: Dmitriy Zaporozhets --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 38fd7b9ac1..e8290fb36b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -95,3 +95,4 @@ bundler:audit: tags: - ruby - mysql + allow_failure: true From e4f3d05c74292ae2dbc3563db38325d982cd6f99 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 23 Nov 2015 18:35:16 +0100 Subject: [PATCH 265/410] Add tests for (Create|Update)ReleaseService --- spec/services/create_release_service_spec.rb | 34 ++++++++++++++++++++ spec/services/update_release_service_spec.rb | 34 ++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 spec/services/create_release_service_spec.rb create mode 100644 spec/services/update_release_service_spec.rb diff --git a/spec/services/create_release_service_spec.rb b/spec/services/create_release_service_spec.rb new file mode 100644 index 0000000000..26d7f365bb --- /dev/null +++ b/spec/services/create_release_service_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe CreateReleaseService do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:tag_name) { project.repository.tag_names.first } + let(:description) { 'Awesome release!' } + let(:service) { CreateReleaseService.new(project, user) } + + it 'creates a new release' do + result = service.execute(tag_name, description) + expect(result[:status]).to eq(:success) + release = project.releases.find_by(tag: tag_name) + expect(release).not_to be_nil + expect(release.description).to eq(description) + end + + it 'raises an error if the tag does not exist' do + result = service.execute("foobar", description) + expect(result[:status]).to eq(:error) + end + + context 'there already exists a release on a tag' do + before do + service.execute(tag_name, description) + end + + it 'raises an error and does not update the release' do + result = service.execute(tag_name, 'The best release!') + expect(result[:status]).to eq(:error) + expect(project.releases.find_by(tag: tag_name).description).to eq(description) + end + end +end diff --git a/spec/services/update_release_service_spec.rb b/spec/services/update_release_service_spec.rb new file mode 100644 index 0000000000..93368c45b8 --- /dev/null +++ b/spec/services/update_release_service_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe UpdateReleaseService do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:tag_name) { project.repository.tag_names.first } + let(:description) { 'Awesome release!' } + let(:new_description) { 'The best release!' } + let(:service) { UpdateReleaseService.new(project, user) } + + context 'with an existing release' do + let(:create_service) { CreateReleaseService.new(project, user) } + + before do + create_service.execute(tag_name, description) + end + + it 'successfully updates an existing release' do + result = service.execute(tag_name, new_description) + expect(result[:status]).to eq(:success) + expect(project.releases.find_by(tag: tag_name).description).to eq(new_description) + end + end + + it 'raises an error if the tag does not exist' do + result = service.execute("foobar", description) + expect(result[:status]).to eq(:error) + end + + it 'raises an error if the release does not exist' do + result = service.execute(tag_name, description) + expect(result[:status]).to eq(:error) + end +end From 62303bfad1268dbb466f0bfa68fef40958c6d287 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 23 Nov 2015 16:51:16 -0500 Subject: [PATCH 266/410] Update CHANGELOG [ci skip] --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 1e39a9da59..eb5badf6a9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,8 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) + +v 8.2.1 - Forcefully update builds that didn't want to update with state machine - Fix: saving GitLabCiService as Admin Template From 97f8c6279fc39c4bad87bb880eba04802f6d351d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 19 Nov 2015 12:33:58 +0100 Subject: [PATCH 267/410] Added total query time to Sherlock This makes it easier to see if a problem is caused by slow queries or slow Ruby code (unrelated to any SQL queries that might be used). --- app/views/sherlock/transactions/_general.html.haml | 6 ++++++ config/locales/sherlock.en.yml | 1 + lib/gitlab/sherlock/transaction.rb | 5 +++++ spec/lib/gitlab/sherlock/transaction_spec.rb | 13 +++++++++++++ 4 files changed, 25 insertions(+) diff --git a/app/views/sherlock/transactions/_general.html.haml b/app/views/sherlock/transactions/_general.html.haml index 4287a0c320..8533b130da 100644 --- a/app/views/sherlock/transactions/_general.html.haml +++ b/app/views/sherlock/transactions/_general.html.haml @@ -25,6 +25,12 @@ %strong = @transaction.duration.round(2) = t('sherlock.seconds') + %li + %span.light + #{t('sherlock.query_time')} + %strong + = @transaction.query_duration.round(2) + = t('sherlock.seconds') %li %span.light #{t('sherlock.finished_at')}: diff --git a/config/locales/sherlock.en.yml b/config/locales/sherlock.en.yml index 683b09dc32..f24b825f58 100644 --- a/config/locales/sherlock.en.yml +++ b/config/locales/sherlock.en.yml @@ -35,3 +35,4 @@ en: events: Events percent: '%' count: Count + query_time: Query Time diff --git a/lib/gitlab/sherlock/transaction.rb b/lib/gitlab/sherlock/transaction.rb index d87a4c9bb4..3489fb251b 100644 --- a/lib/gitlab/sherlock/transaction.rb +++ b/lib/gitlab/sherlock/transaction.rb @@ -36,6 +36,11 @@ module Gitlab @duration ||= started_at && finished_at ? finished_at - started_at : 0 end + # Returns the total query duration in seconds. + def query_duration + @query_duration ||= @queries.map { |q| q.duration }.inject(:+) / 1000.0 + end + def to_param @id end diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb index bb49fb65cf..fb80c62c79 100644 --- a/spec/lib/gitlab/sherlock/transaction_spec.rb +++ b/spec/lib/gitlab/sherlock/transaction_spec.rb @@ -84,6 +84,19 @@ describe Gitlab::Sherlock::Transaction do end end + describe '#query_duration' do + it 'returns the total query duration in seconds' do + time = Time.now + query1 = Gitlab::Sherlock::Query.new('SELECT 1', time, time + 5) + query2 = Gitlab::Sherlock::Query.new('SELECT 2', time, time + 2) + + transaction.queries << query1 + transaction.queries << query2 + + expect(transaction.query_duration).to be_within(0.1).of(7.0) + end + end + describe '#to_param' do it 'returns the transaction ID' do expect(transaction.to_param).to eq(transaction.id) From d1a73563985a2fdaf7a99fc469c38e957d238fde Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Nov 2015 11:36:53 +0100 Subject: [PATCH 268/410] Fix ultra-light color for small text Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/typography.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index ba0312ba0d..2c4a58c8db 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -256,3 +256,9 @@ textarea.js-gfm-input { .strikethrough { text-decoration: line-through; } + +h1, h2, h3, h4 { + small { + color: $gl-gray; + } +} From 9595ec036a0f6f0d2e18e47094431b5a1b6d427f Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 24 Nov 2015 12:11:31 +0100 Subject: [PATCH 269/410] Maybe rescue session_expire_delay by setting a default value. --- config/initializers/session_store.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index d7c5432da7..eb534625e3 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -7,6 +7,7 @@ include Gitlab::CurrentSettings begin Settings.gitlab['session_expire_delay'] = current_application_settings.session_expire_delay rescue + Settings.gitlab['session_expire_delay'] ||= 10080 end unless Rails.env.test? From 01bd7baf4fbf23507f6adcdaebf3946b892eb001 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 24 Nov 2015 12:21:11 +0100 Subject: [PATCH 270/410] Spell out commands for omnibus-gitlab packages. --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 007e410b67..446eb1189a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,10 +47,10 @@ Please send a merge request with a tested solution or a merge request with a fai 1. **Observed behavior** 1. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise. 1. **Output of checks** - * Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`); we will only investigate if the tests are passing + * Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (For installations with omnibus-gitlab package: `sudo gitlab-rake gitlab:check SANITIZE=true`); For installations from source: `sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`); we will only investigate if the tests are passing * Version of GitLab you are running; we will only investigate issues in the latest stable and development releases as per the [maintenance policy](MAINTENANCE.md) * Add the last commit SHA-1 of the GitLab version you used to replicate the issue (obtainable from the help page) - * Describe your setup (use relevant parts from `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`) + * Describe your setup (use relevant parts from the env info: For installations with omnibus-gitlab package: `sudo gitlab-rake gitlab:env:info`; For installations from source: `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`) 1. **Possible fixes**: If you can, link to the line of code that might be responsible for the problem ## Merge requests From 0985032132a2dfa02f2968a3e959df8629b77b12 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 24 Nov 2015 15:57:28 +0100 Subject: [PATCH 271/410] Also fallback to a default value if none is set. --- config/initializers/session_store.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index eb534625e3..f30178ff71 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -3,9 +3,9 @@ require 'gitlab/current_settings' include Gitlab::CurrentSettings -# allow it to fail: it may to do so when create_from_defaults is executed before migrations are actually done +# allow it to fail: it may do so when create_from_defaults is executed before migrations are actually done begin - Settings.gitlab['session_expire_delay'] = current_application_settings.session_expire_delay + Settings.gitlab['session_expire_delay'] = current_application_settings.session_expire_delay || 10080 rescue Settings.gitlab['session_expire_delay'] ||= 10080 end From 4caef63d82143518e6f23ec6183021657d7ed1ae Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Nov 2015 16:13:21 +0100 Subject: [PATCH 272/410] Fix 500 error when update group member permission Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + .../group_members/_group_member.html.haml | 2 +- app/views/groups/group_members/update.js.haml | 2 +- features/groups.feature | 7 +++ features/steps/groups.rb | 51 +++++++++++++++---- 5 files changed, 51 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 90ade156ca..8916b9f040 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) + - Fix 500 error when update group member permission v 8.2.1 - Forcefully update builds that didn't want to update with state machine diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml index be94b1abc1..bae67552a1 100644 --- a/app/views/groups/group_members/_group_member.html.haml +++ b/app/views/groups/group_members/_group_member.html.haml @@ -30,7 +30,7 @@ - if should_user_see_group_roles?(current_user, @group) %span.pull-right - %strong= member.human_access + %strong.member-access-level= member.human_access - if show_controls - if can?(current_user, :update_group_member, member) = button_tag class: "btn-xs btn js-toggle-button", diff --git a/app/views/groups/group_members/update.js.haml b/app/views/groups/group_members/update.js.haml index 5bad48abaf..df726e2b2b 100644 --- a/app/views/groups/group_members/update.js.haml +++ b/app/views/groups/group_members/update.js.haml @@ -1,2 +1,2 @@ :plain - $("##{dom_id(@member)}").replaceWith('#{escape_javascript(render(@member, member: @member, show_controls: true))}'); + $("##{dom_id(@group_member)}").replaceWith('#{escape_javascript(render(@group_member, member: @group_member, show_controls: true))}'); diff --git a/features/groups.feature b/features/groups.feature index abf3769a84..938e658f2a 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -74,6 +74,13 @@ Feature: Groups When I select "sjobs@apple.com" as "Reporter" Then I should see "sjobs@apple.com" in team list as invited "Reporter" + @javascript + Scenario: Edit group member permissions + Given "Mary Jane" is guest of group "Owned" + And I visit group "Owned" members page + When I change the "Mary Jane" role to "Developer" + Then I should see "Mary Jane" as "Developer" + # Leave @javascript diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 9c0313537b..1d530a2528 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -26,7 +26,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end step 'Group "Owned" has a public project "Public-project"' do - group = Group.find_by(name: "Owned") + group = owned_group @project = create :empty_project, :public, group: group, @@ -91,7 +91,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end step 'I should see group "Owned" projects list' do - Group.find_by(name: "Owned").projects.each do |project| + owned_group.projects.each do |project| expect(page).to have_link project.name end end @@ -172,12 +172,12 @@ class Spinach::Features::Groups < Spinach::FeatureSteps step 'I change group "Owned" avatar' do attach_file(:group_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')) click_button "Save group" - Group.find_by(name: "Owned").reload + owned_group.reload end step 'I should see new group "Owned" avatar' do - expect(Group.find_by(name: "Owned").avatar).to be_instance_of AvatarUploader - expect(Group.find_by(name: "Owned").avatar.url).to eq "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/banana_sample.gif" + expect(owned_group.avatar).to be_instance_of AvatarUploader + expect(owned_group.avatar.url).to eq "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/banana_sample.gif" end step 'I should see the "Remove avatar" button' do @@ -187,16 +187,16 @@ class Spinach::Features::Groups < Spinach::FeatureSteps step 'I have group "Owned" avatar' do attach_file(:group_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')) click_button "Save group" - Group.find_by(name: "Owned").reload + owned_group.reload end step 'I remove group "Owned" avatar' do click_link "Remove avatar" - Group.find_by(name: "Owned").reload + owned_group.reload end step 'I should not see group "Owned" avatar' do - expect(Group.find_by(name: "Owned").avatar?).to eq false + expect(owned_group.avatar?).to eq false end step 'I should not see the "Remove avatar" button' do @@ -295,18 +295,49 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end end + step 'I change the "Mary Jane" role to "Developer"' do + member = mary_jane_member + + page.within "#group_member_#{member.id}" do + find(".js-toggle-button").click + page.within "#edit_group_member_#{member.id}" do + select 'Developer', from: 'group_member_access_level' + click_on 'Save' + end + end + end + + step 'I should see "Mary Jane" as "Developer"' do + member = mary_jane_member + + page.within "#group_member_#{member.id}" do + page.within '.member-access-level' do + expect(page).to have_content "Developer" + end + end + end + protected + def owned_group + @owned_group ||= Group.find_by(name: "Owned") + end + + def mary_jane_member + user = User.find_by(name: "Mary Jane") + member = owned_group.members.where(user_id: user.id).first + end + def assigned_to_me(key) project.send(key).where(assignee_id: current_user.id) end def project - Group.find_by(name: "Owned").projects.first + owned_group.projects.first end def group_milestone - group = Group.find_by(name: "Owned") + group = owned_group @project1 = create :project, group: group From cb9f573818098b2fdc556db867bd9a32f469d471 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Nov 2015 16:17:11 +0100 Subject: [PATCH 273/410] Fix rubocop complain Signed-off-by: Dmitriy Zaporozhets --- features/steps/groups.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 1d530a2528..5086a9e0b8 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -325,7 +325,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps def mary_jane_member user = User.find_by(name: "Mary Jane") - member = owned_group.members.where(user_id: user.id).first + owned_group.members.where(user_id: user.id).first end def assigned_to_me(key) From cbaa33db8a77efaf6446ed294db2e334ff7de8e7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Nov 2015 17:40:41 +0100 Subject: [PATCH 274/410] Small code improvement Signed-off-by: Dmitriy Zaporozhets --- features/steps/groups.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 5086a9e0b8..7c991af4c2 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -325,7 +325,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps def mary_jane_member user = User.find_by(name: "Mary Jane") - owned_group.members.where(user_id: user.id).first + owned_group.members.find_by(user_id: user.id) end def assigned_to_me(key) From 776027e4c7a4e7ff5df18e92cd79461a7d5de73e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 24 Nov 2015 15:36:36 -0500 Subject: [PATCH 275/410] Bump gon to ~> 6.0.1 Closes #2856 --- Gemfile | 2 +- Gemfile.lock | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index bd8e9bd8fa..3bb9448e40 100644 --- a/Gemfile +++ b/Gemfile @@ -193,7 +193,7 @@ gem 'addressable', '~> 2.3.8' gem 'bootstrap-sass', '~> 3.0' gem 'font-awesome-rails', '~> 4.2' gem 'gitlab_emoji', '~> 0.1' -gem 'gon', '~> 5.0.0' +gem 'gon', '~> 6.0.1' gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-rails', '~> 3.1.3' gem 'jquery-scrollto-rails', '~> 1.4.3' diff --git a/Gemfile.lock b/Gemfile.lock index 448941cfc8..70c532f05d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -312,9 +312,11 @@ GEM rouge (~> 1.10.1) sanitize (~> 2.1.0) stringex (~> 2.5.1) - gon (5.0.4) - actionpack (>= 2.3.0) + gon (6.0.1) + actionpack (>= 3.0) json + multi_json + request_store (>= 1.0) grape (0.13.0) activesupport builder @@ -846,7 +848,7 @@ DEPENDENCIES gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.0.2) - gon (~> 5.0.0) + gon (~> 6.0.1) grape (~> 0.13.0) grape-entity (~> 0.4.2) haml-rails (~> 0.9.0) From 26f9dbf8a95d79eccf6a3dd3b779b7a7bc51c03b Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 24 Nov 2015 15:42:42 -0500 Subject: [PATCH 276/410] Bump creole to ~> 0.5.0 Closes #2815 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index bd8e9bd8fa..bcbdabb5d9 100644 --- a/Gemfile +++ b/Gemfile @@ -95,7 +95,7 @@ gem 'redcarpet', '~> 3.3.3' gem 'RedCloth', '~> 4.2.9' gem 'rdoc', '~>3.6' gem 'org-ruby', '~> 0.9.12' -gem 'creole', '~>0.3.6' +gem 'creole', '~> 0.5.0' gem 'wikicloth', '0.8.1' gem 'asciidoctor', '~> 1.5.2' diff --git a/Gemfile.lock b/Gemfile.lock index 448941cfc8..4d60d8fadc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -134,7 +134,7 @@ GEM thor (~> 0.19.1) crack (0.4.2) safe_yaml (~> 1.0.0) - creole (0.3.8) + creole (0.5.0) d3_rails (3.5.6) railties (>= 3.1.0) daemons (1.2.3) @@ -816,7 +816,7 @@ DEPENDENCIES colored (~> 1.2) colorize (~> 0.5.8) coveralls (~> 0.8.2) - creole (~> 0.3.6) + creole (~> 0.5.0) d3_rails (~> 3.5.5) database_cleaner (~> 1.4.0) default_value_for (~> 3.0.0) From 446a95d3079e8927dd40c8d41a58d66192721cfc Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 24 Nov 2015 15:48:49 -0500 Subject: [PATCH 277/410] Bump rack-oauth2 to ~> 1.2.1 --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index bd8e9bd8fa..0436e93065 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,7 @@ gem 'omniauth-saml', '~> 1.4.0' gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth_crowd' -gem 'rack-oauth2', '~> 1.0.5' +gem 'rack-oauth2', '~> 1.2.1' # Two-factor authentication gem 'devise-two-factor', '~> 2.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 448941cfc8..ecd83b6979 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -357,7 +357,7 @@ GEM httparty (0.13.5) json (~> 1.8) multi_xml (>= 0.5.2) - httpclient (2.6.0.1) + httpclient (2.7.0.1) i18n (0.7.0) ice_cube (0.11.1) ice_nine (0.11.1) @@ -502,7 +502,7 @@ GEM rack-cors (0.4.0) rack-mount (0.8.3) rack (>= 1.0.0) - rack-oauth2 (1.0.10) + rack-oauth2 (1.2.1) activesupport (>= 2.3) attr_required (>= 0.0.5) httpclient (>= 2.4) @@ -889,7 +889,7 @@ DEPENDENCIES quiet_assets (~> 1.0.2) rack-attack (~> 4.3.0) rack-cors (~> 0.4.0) - rack-oauth2 (~> 1.0.5) + rack-oauth2 (~> 1.2.1) rails (= 4.1.14) raphael-rails (~> 2.1.2) rblineprof From b9828278127724ff5eac60eb71e36f675b9985cd Mon Sep 17 00:00:00 2001 From: Eirik Lygre Date: Tue, 24 Nov 2015 20:55:18 +0000 Subject: [PATCH 278/410] Add reference to Microsoft's Git Credential Manager for Windows. --- doc/workflow/lfs/manage_large_binaries_with_git_lfs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 210a8f71c3..b59e92cb31 100644 --- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md +++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md @@ -121,6 +121,6 @@ git config --global credential.helper 'cache --timeout=3600' This will remember the credentials for an hour after which Git operations will require re-authentication. -If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, `wincred` is available. +If you are using OS X you can use `osxkeychain` to store and encrypt your credentials. For Windows, you can use `wincred` or Microsoft's [Git Credential Manager for Windows](https://github.com/Microsoft/Git-Credential-Manager-for-Windows/releases). -More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). +More details about various methods of storing the user credentials can be found on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). \ No newline at end of file From a2915aeb662e30537f3f226e3db6d85fd252905f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 24 Nov 2015 16:12:25 -0500 Subject: [PATCH 279/410] Rescue StandardError, not Exception --- app/services/merge_requests/merge_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 7963af127e..d619b72e3c 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -38,7 +38,7 @@ module MergeRequests } repository.merge(current_user, merge_request.source_sha, merge_request.target_branch, options) - rescue Exception => e + rescue StandardError => e merge_request.update(merge_error: "Something went wrong during merge") Rails.logger.error(e.message) return false From 28de6770fae44f65aa81538ff660fc7050072070 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 24 Nov 2015 16:29:21 -0500 Subject: [PATCH 280/410] Bump colorize to ~> 0.7.0 Also removes `colored` which came in during the CI merge and is redundant. Closes #2822 --- Gemfile | 3 +-- Gemfile.lock | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Gemfile b/Gemfile index bd8e9bd8fa..b7ec16198c 100644 --- a/Gemfile +++ b/Gemfile @@ -125,8 +125,7 @@ gem 'sidetiq', '~> 0.6.3' gem "httparty", '~> 0.13.3' # Colored output to console -gem "colored", '~> 1.2' -gem "colorize", '~> 0.5.8' +gem "colorize", '~> 0.7.0' # GitLab settings gem 'settingslogic', '~> 2.0.9' diff --git a/Gemfile.lock b/Gemfile.lock index 448941cfc8..20fc885481 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -123,8 +123,7 @@ GEM coffee-script-source execjs coffee-script-source (1.9.1.1) - colored (1.2) - colorize (0.5.8) + colorize (0.7.7) connection_pool (2.2.0) coveralls (0.8.2) json (~> 1.8) @@ -813,8 +812,7 @@ DEPENDENCIES carrierwave (~> 0.9.0) charlock_holmes (~> 0.7.3) coffee-rails (~> 4.1.0) - colored (~> 1.2) - colorize (~> 0.5.8) + colorize (~> 0.7.0) coveralls (~> 0.8.2) creole (~> 0.3.6) d3_rails (~> 3.5.5) From af65046c5fd9f214124d39a5582b06ee2fe39933 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 22 Nov 2015 23:52:02 -0500 Subject: [PATCH 281/410] Move HTTP/SSH clone button logic to helpers --- app/helpers/button_helper.rb | 35 +++++++++++++++++++++++++ app/views/shared/_clone_panel.html.haml | 18 ++----------- 2 files changed, 37 insertions(+), 16 deletions(-) create mode 100644 app/helpers/button_helper.rb diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb new file mode 100644 index 0000000000..8963e96ea1 --- /dev/null +++ b/app/helpers/button_helper.rb @@ -0,0 +1,35 @@ +module ButtonHelper + def http_clone_button(project) + klass = 'btn' + klass << ' active' if default_clone_protocol == 'http' + klass << ' has_tooltip' if current_user.try(:require_password?) + + protocol = gitlab_config.protocol.upcase + + content_tag :button, protocol, + class: klass, + data: { + clone: project.http_url_to_repo, + container: 'body', + html: 'true', + title: "Set a password on your account
to pull or push via #{protocol}" + }, + type: :button + end + + def ssh_clone_button(project) + klass = 'btn' + klass << ' active' if default_clone_protocol == 'ssh' + klass << ' has_tooltip' if current_user.try(:require_ssh_key?) + + content_tag :button, 'SSH', + class: klass, + data: { + clone: project.ssh_url_to_repo, + container: 'body', + html: 'true', + title: 'Add an SSH key to your profile
to pull or push via SSH.' + }, + type: :button + end +end diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 8bcb24ae9d..a82971157d 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -2,23 +2,9 @@ .git-clone-holder.input-group .input-group-addon.git-protocols .input-group-btn - %button{ | - type: 'button', | - class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", | - :"data-clone" => project.ssh_url_to_repo, | - :"data-title" => "Add an SSH key to your profile
to pull or push via SSH.", - :"data-html" => "true", - :"data-container" => "body"} - SSH + = ssh_clone_button(project) .input-group-btn - %button{ | - type: 'button', | - class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", | - :"data-clone" => project.http_url_to_repo, | - :"data-title" => "Set a password on your account
to pull or push via #{gitlab_config.protocol.upcase}.", - :"data-html" => "true", - :"data-container" => "body"} - = gitlab_config.protocol.upcase + = http_clone_button(project) = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true - if project.kind_of?(Project) .input-group-addon.has_tooltip{title: "#{visibility_level_label(project.visibility_level)} project", data: { container: "body" } } From acc0f162c864d2a061461467473fca8761b6611f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 22 Nov 2015 23:53:55 -0500 Subject: [PATCH 282/410] Move clipboard_button helper to ButtonHelper module --- app/helpers/button_helper.rb | 7 +++++++ app/helpers/clipboard_helper.rb | 8 -------- 2 files changed, 7 insertions(+), 8 deletions(-) delete mode 100644 app/helpers/clipboard_helper.rb diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 8963e96ea1..bbb35afca8 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -1,4 +1,11 @@ module ButtonHelper + def clipboard_button + content_tag :button, + icon('clipboard'), + class: 'btn btn-xs btn-clipboard js-clipboard-trigger', + type: :button + end + def http_clone_button(project) klass = 'btn' klass << ' active' if default_clone_protocol == 'http' diff --git a/app/helpers/clipboard_helper.rb b/app/helpers/clipboard_helper.rb deleted file mode 100644 index 3c1d7569fa..0000000000 --- a/app/helpers/clipboard_helper.rb +++ /dev/null @@ -1,8 +0,0 @@ -module ClipboardHelper - def clipboard_button - content_tag :button, - icon('clipboard'), - class: 'btn btn-xs btn-clipboard js-clipboard-trigger', - type: :button - end -end From 7dab8ed739359bc579d8bc4d3de61816993ca57d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 24 Nov 2015 18:35:24 -0500 Subject: [PATCH 283/410] Rework the copy_to_clipboard logic It needed to be more flexible in how we set the target text or element. --- .../javascripts/copy_to_clipboard.js.coffee | 63 ++++++++++--------- app/helpers/button_helper.rb | 20 +++++- app/views/projects/commits/_commit.html.haml | 4 +- .../projects/issues/_discussion.html.haml | 4 +- .../merge_requests/_discussion.html.haml | 4 +- 5 files changed, 58 insertions(+), 37 deletions(-) diff --git a/app/assets/javascripts/copy_to_clipboard.js.coffee b/app/assets/javascripts/copy_to_clipboard.js.coffee index 9c68c5cc1b..24301e01b1 100644 --- a/app/assets/javascripts/copy_to_clipboard.js.coffee +++ b/app/assets/javascripts/copy_to_clipboard.js.coffee @@ -1,32 +1,37 @@ #= require clipboard +genericSuccess = (e) -> + showTooltip(e.trigger, 'Copied!') + + # Clear the selection and blur the trigger so it loses its border + e.clearSelection() + $(e.trigger).blur() + +# Safari doesn't support `execCommand`, so instead we inform the user to +# copy manually. +# +# See http://clipboardjs.com/#browser-support +genericError = (e) -> + if /Mac/i.test(navigator.userAgent) + key = '⌘' # Command + else + key = 'Ctrl' + + showTooltip(e.trigger, "Press #{key}-C to copy") + +showTooltip = (target, title) -> + $(target). + tooltip( + container: 'body' + html: 'true' + placement: 'auto bottom' + title: title + trigger: 'manual' + ). + tooltip('show'). + one('mouseleave', -> $(this).tooltip('hide')) + $ -> - clipboard = new Clipboard '.js-clipboard-trigger', - text: (trigger) -> - $target = $(trigger.nextElementSibling || trigger.previousElementSibling) - $target.data('clipboard-text') || $target.text().trim() - - clipboard.on 'success', (e) -> - $(e.trigger). - tooltip(trigger: 'manual', placement: 'auto bottom', title: 'Copied!'). - tooltip('show'). - one('mouseleave', -> $(this).tooltip('hide')) - - # Clear the selection and blur the trigger so it loses its border - e.clearSelection() - $(e.trigger).blur() - - # Safari doesn't support `execCommand`, so instead we inform the user to - # copy manually. - # - # See http://clipboardjs.com/#browser-support - clipboard.on 'error', (e) -> - if /Mac/i.test(navigator.userAgent) - title = "Press ⌘-C to copy" - else - title = "Press Ctrl-C to copy" - - $(e.trigger). - tooltip(trigger: 'manual', placement: 'auto bottom', html: true, title: title). - tooltip('show'). - one('mouseleave', -> $(this).tooltip('hide')) + clipboard = new Clipboard '[data-clipboard-target], [data-clipboard-text]' + clipboard.on 'success', genericSuccess + clipboard.on 'error', genericError diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index bbb35afca8..b9bb1ac8d8 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -1,8 +1,24 @@ module ButtonHelper - def clipboard_button + # Output a "Copy to Clipboard" button + # + # data - Data attributes passed to `content_tag` + # + # Examples: + # + # # Define the clipboard's text + # clipboard_button(clipboard_text: "Foo") + # # => "" + # + # # Define the target element + # clipboard_button(clipboard_target: "#foo") + # # => "" + # + # See http://clipboardjs.com/#usage + def clipboard_button(data = {}) content_tag :button, icon('clipboard'), - class: 'btn btn-xs btn-clipboard js-clipboard-trigger', + class: 'btn btn-xs btn-clipboard', + data: data, type: :button end diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 805be332e6..2e489a0a4d 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -20,8 +20,8 @@ - if ci_commit = render_ci_status(ci_commit)   - = clipboard_button - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id", data: {clipboard_text: commit.id} + = clipboard_button(clipboard_text: commit.id) + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" .notes_count - if note_count > 0 diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 020952dd00..8f0a1ed9be 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -18,9 +18,9 @@ = link_to_member(@project, participant, name: false, size: 24) .col-md-3 .input-group.cross-project-reference - %span.slead.has_tooltip{title: 'Cross-project reference'} + %span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'} = cross_project_reference(@project, @issue) - = clipboard_button + = clipboard_button(clipboard_target: '#cross-project-reference') .row %section.col-md-9 diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index cb75bd8c5b..2b3c3eff5e 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -15,9 +15,9 @@ = render "projects/merge_requests/show/participants" .col-md-3 .input-group.cross-project-reference - %span.slead.has_tooltip{title: 'Cross-project reference'} + %span#cross-project-reference.slead.has_tooltip{title: 'Cross-project reference'} = cross_project_reference(@project, @merge_request) - = clipboard_button + = clipboard_button(clipboard_target: '#cross-project-reference') .row %section.col-md-9 From ec002e802677a691d758ebc6f94f23020b2b063a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 24 Nov 2015 19:20:23 -0500 Subject: [PATCH 284/410] Remove usage of Colored --- lib/tasks/gitlab/task_helpers.rake | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake index c95b6540eb..efb863a876 100644 --- a/lib/tasks/gitlab/task_helpers.rake +++ b/lib/tasks/gitlab/task_helpers.rake @@ -2,16 +2,6 @@ module Gitlab class TaskAbortedByUserError < StandardError; end end -unless STDOUT.isatty - module Colored - extend self - - def colorize(string, options={}) - string - end - end -end - namespace :gitlab do # Ask if the user wants to continue @@ -103,7 +93,7 @@ namespace :gitlab do gitlab_user = Gitlab.config.gitlab.user current_user = run(%W(whoami)).chomp unless current_user == gitlab_user - puts "#{Colored.color(:black)+Colored.color(:on_yellow)} Warning #{Colored.extra(:clear)}" + puts " Warning ".colorize(:black).on_yellow puts " You are running as user #{current_user.magenta}, we hope you know what you are doing." puts " Things may work\/fail for the wrong reasons." puts " For correct results you should run this as user #{gitlab_user.magenta}." From 1d80cd315e403e02f85a99c35eb9e83f4d829f8d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 24 Nov 2015 19:32:06 -0500 Subject: [PATCH 285/410] Add clipboard button to project clone panel Closes #3585 --- app/assets/javascripts/project.js.coffee | 10 ++++------ app/assets/stylesheets/pages/projects.scss | 7 ++++++- app/helpers/button_helper.rb | 4 ++-- app/helpers/projects_helper.rb | 3 +-- app/views/shared/_clone_panel.html.haml | 2 ++ 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index 0ea8fffce0..d37a00bded 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -3,11 +3,9 @@ class @Project # Git clone panel switcher cloneHolder = $('.git-clone-holder') if cloneHolder.length - $('a, button', cloneHolder).click -> - $('a, button', cloneHolder).removeClass 'active' - $(@).addClass 'active' - $('#project_clone', cloneHolder).val $(@).data 'clone' - $(".clone").text("").append $(@).data 'clone' + $('.js-protocol-switch', cloneHolder).click -> + $('.js-protocol-switch', cloneHolder).toggleClass('active') + $('#project_clone').val($(@).data('clone')) # Ref switcher $('.project-refs-select').on 'change', -> @@ -39,4 +37,4 @@ class @Project when 4 then label = ' On Mention ' $('#notifications-button').empty().append("" + label + "") $(@).parents('ul').find('li.active').removeClass 'active' - $(@).parent().addClass 'active' \ No newline at end of file + $(@).parent().addClass 'active' diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index d3b1004002..ad607ead2b 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -178,6 +178,11 @@ &:active { outline: none; } + + &.btn-clipboard { + padding-left: 15px; + padding-right: 15px; + } } .active { @@ -552,4 +557,4 @@ pre.light-well { z-index: 100; position: relative; } -} \ No newline at end of file +} diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index b9bb1ac8d8..313b6dde91 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -23,7 +23,7 @@ module ButtonHelper end def http_clone_button(project) - klass = 'btn' + klass = 'btn js-protocol-switch' klass << ' active' if default_clone_protocol == 'http' klass << ' has_tooltip' if current_user.try(:require_password?) @@ -41,7 +41,7 @@ module ButtonHelper end def ssh_clone_button(project) - klass = 'btn' + klass = 'btn js-protocol-switch' klass << ' active' if default_clone_protocol == 'ssh' klass << ' has_tooltip' if current_user.try(:require_ssh_key?) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index c9cd4a0d54..c0c51aae03 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -173,8 +173,7 @@ module ProjectsHelper 'unknown' end - def default_url_to_repo(project = nil) - project = project || @project + def default_url_to_repo(project = @project) current_user ? project.url_to_repo : project.http_url_to_repo end diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index a82971157d..408a46aaaf 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -6,6 +6,8 @@ .input-group-btn = http_clone_button(project) = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true + .input-group-btn + = clipboard_button(clipboard_target: '#project_clone') - if project.kind_of?(Project) .input-group-addon.has_tooltip{title: "#{visibility_level_label(project.visibility_level)} project", data: { container: "body" } } .visibility-level-label From 637e64c24472a06b32091cb45f1b81260c5e6ec0 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 24 Nov 2015 20:38:36 -0500 Subject: [PATCH 286/410] Clean up the Git protocol switcher JS Also re-adds the `.clone` updating that was removed accidentally. Thanks, tests! --- app/assets/javascripts/project.js.coffee | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index d37a00bded..ec919f0cd6 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -1,11 +1,19 @@ class @Project constructor: -> - # Git clone panel switcher - cloneHolder = $('.git-clone-holder') - if cloneHolder.length - $('.js-protocol-switch', cloneHolder).click -> - $('.js-protocol-switch', cloneHolder).toggleClass('active') - $('#project_clone').val($(@).data('clone')) + # Git protocol switcher + $('.js-protocol-switch').click -> + return if $(@).hasClass('active') + + # Toggle 'active' for both buttons + $('.js-protocol-switch').toggleClass('active') + + url = $(@).data('clone') + + # Update the input field + $('#project_clone').val(url) + + # Update the command line instructions + $('.clone').text(url) # Ref switcher $('.project-refs-select').on 'change', -> From 767bd6f8825661c2cd170172f2b0d5ce34c67a93 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 24 Nov 2015 16:18:14 -0500 Subject: [PATCH 287/410] Enable the Lint/RescueException cop --- .rubocop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.rubocop.yml b/.rubocop.yml index 11e4502849..d59edbc8b1 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -888,7 +888,7 @@ Lint/RequireParentheses: Lint/RescueException: Description: 'Avoid rescuing the Exception class.' StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues' - Enabled: false + Enabled: true Lint/ShadowingOuterLocalVariable: Description: >- From d9749c8d36750136cbd8989918b1fec8ff2c4b49 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Nov 2015 16:51:01 +0100 Subject: [PATCH 288/410] Improve UI for group members page Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/lists.scss | 5 ++ .../stylesheets/framework/pagination.scss | 4 ++ app/assets/stylesheets/pages/groups.scss | 4 +- .../group_members/_group_member.html.haml | 4 +- .../groups/group_members/index.html.haml | 58 ++++++++++--------- 5 files changed, 43 insertions(+), 32 deletions(-) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 45f3b5849b..a798ae812e 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -127,3 +127,8 @@ ul.content-list { } } +.panel > .content-list { + li { + margin: 0; + } +} diff --git a/app/assets/stylesheets/framework/pagination.scss b/app/assets/stylesheets/framework/pagination.scss index 6677f94daf..2cd30491bf 100644 --- a/app/assets/stylesheets/framework/pagination.scss +++ b/app/assets/stylesheets/framework/pagination.scss @@ -32,3 +32,7 @@ } } } + +.panel > .gl-pagination { + margin: 0; +} diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 07a38a19fa..bcb73a9acd 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -1,5 +1,5 @@ .new-group-member-holder { - margin-top: 50px; + margin-top: 10px; padding-top: 20px; } @@ -15,4 +15,4 @@ .form-control { height: 42px; } -} \ No newline at end of file +} diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml index bae67552a1..a79a0fcdc8 100644 --- a/app/views/groups/group_members/_group_member.html.haml +++ b/app/views/groups/group_members/_group_member.html.haml @@ -4,7 +4,7 @@ %li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} %span{class: ("list-item-name" if show_controls)} - if member.user - = image_tag avatar_icon(user, 16), class: "avatar s16", alt: '' + = image_tag avatar_icon(user, 24), class: "avatar s24", alt: '' %strong = link_to user.name, user_path(user) %span.cgray= user.username @@ -14,7 +14,7 @@ %label.label.label-danger %strong Blocked - else - = image_tag avatar_icon(member.invite_email, 16), class: "avatar s16", alt: '' + = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: '' %strong = member.invite_email %span.cgray diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index d4ad33a8bf..b3a811b266 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,38 +1,40 @@ +- @blank_container = true - page_title "Members" - header_title group_title(@group, "Members", group_group_members_path(@group)) -- if should_user_see_group_roles?(current_user, @group) - %p.light - Members of group have access to all group projects. - Read more about permissions - %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" -.clearfix.js-toggle-container - = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do - .form-group - = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input', spellcheck: false } - = button_tag 'Search', class: 'btn' + +.group-members-page - if current_user && current_user.can?(:admin_group_member, @group) - .pull-right - = button_tag class: 'btn btn-new js-toggle-button', type: 'button' do - Add members - %i.fa.fa-chevron-down + .panel.panel-default + .panel-heading + Add new user to group + .panel-body + - if should_user_see_group_roles?(current_user, @group) + %p.light + Members of group have access to all group projects. + Read more about permissions + %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" + .new-group-member-holder + = render "new_group_member" - .js-toggle-content.hide.new-group-member-holder - = render "new_group_member" - -.panel.panel-default.prepend-top-20 - .panel-heading - %strong #{@group.name} - group members - %small - (#{@members.total_count}) - %ul.well-list - - @members.each do |member| - = render 'groups/group_members/group_member', member: member, show_controls: true - -= paginate @members, theme: 'gitlab' + .panel.panel-default + .panel-heading + %strong #{@group.name} + group members + %small + (#{@members.total_count}) + .pull-right + = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do + .form-group + = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false } + = button_tag class: 'btn' do + = icon("search") + %ul.content-list + - @members.each do |member| + = render 'groups/group_members/group_member', member: member, show_controls: true + = paginate @members, theme: 'gitlab' :javascript $('form.member-search-form').on('submit', function(event) { From 34cc8f4a60f25bfa2503f0ad006b047fd2c2f81c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Nov 2015 17:11:45 +0100 Subject: [PATCH 289/410] Improve project members page UI Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/groups.scss | 5 --- .../groups/group_members/index.html.haml | 7 +-- .../project_members/_group_members.html.haml | 8 ++-- .../project_members/_project_member.html.haml | 4 +- .../projects/project_members/_team.html.haml | 16 ++++++- .../projects/project_members/index.html.haml | 45 +++++++------------ 6 files changed, 36 insertions(+), 49 deletions(-) diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index bcb73a9acd..263993f59a 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -1,8 +1,3 @@ -.new-group-member-holder { - margin-top: 10px; - padding-top: 20px; -} - .member-search-form { float: left; } diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index b3a811b266..c83766e729 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,9 +1,6 @@ -- @blank_container = true - page_title "Members" - header_title group_title(@group, "Members", group_group_members_path(@group)) - - - +- @blank_container = true .group-members-page - if current_user && current_user.can?(:admin_group_member, @group) @@ -14,8 +11,6 @@ - if should_user_see_group_roles?(current_user, @group) %p.light Members of group have access to all group projects. - Read more about permissions - %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" .new-group-member-holder = render "new_group_member" diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml index 43e92437cf..0c73d7e34a 100644 --- a/app/views/projects/project_members/_group_members.html.haml +++ b/app/views/projects/project_members/_group_members.html.haml @@ -4,11 +4,11 @@ group members %small (#{members.count}) - .panel-head-actions - = link_to group_group_members_path(@group), class: 'btn btn-sm' do - %i.fa.fa-pencil-square-o + .pull-right + = link_to group_group_members_path(@group), class: 'btn' do + = icon('pencil-square-o') Edit group members - %ul.well-list + %ul.content-list - members.each do |member| = render 'groups/group_members/group_member', member: member, show_controls: false - if members.count > 20 diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml index f07cd97e63..05bf3a7ef6 100644 --- a/app/views/projects/project_members/_project_member.html.haml +++ b/app/views/projects/project_members/_project_member.html.haml @@ -4,7 +4,7 @@ %li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)} %span.list-item-name - if member.user - = image_tag avatar_icon(user, 16), class: "avatar s16", alt: '' + = image_tag avatar_icon(user, 24), class: "avatar s24", alt: '' %strong = link_to user.name, user_path(user) %span.cgray= user.username @@ -14,7 +14,7 @@ %label.label.label-danger %strong Blocked - else - = image_tag avatar_icon(member.invite_email, 16), class: "avatar s16", alt: '' + = image_tag avatar_icon(member.invite_email, 24), class: "avatar s24", alt: '' %strong = member.invite_email %span.cgray diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index b807fb2cc9..8f4c613426 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -1,9 +1,21 @@ -.panel.panel-default.prepend-top-20 +.panel.panel-default .panel-heading %strong #{@project.name} project members %small (#{members.count}) - %ul.well-list + .pull-right + = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do + .form-group + = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false } + = button_tag class: 'btn' do + = icon("search") + %ul.content-list - members.each do |project_member| = render 'project_member', member: project_member + +:javascript + $('form.member-search-form').on('submit', function (event) { + event.preventDefault(); + Turbolinks.visit(this.action + '?' + $(this).serialize()); + }); diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml index 9fc4be583c..29225a3636 100644 --- a/app/views/projects/project_members/index.html.haml +++ b/app/views/projects/project_members/index.html.haml @@ -1,36 +1,21 @@ - page_title "Members" = render "header_title" +- @blank_container = true -.gray-content-block.top-block - .clearfix.js-toggle-container - = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do - .form-group - = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input', spellcheck: false } - = button_tag 'Search', class: 'btn' - - - if can?(current_user, :admin_project_member, @project) - %span.pull-right - = button_tag class: 'btn btn-new btn-grouped js-toggle-button', type: 'button' do - Add members - %i.fa.fa-chevron-down - = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do - Import members - - .js-toggle-content.hide.new-group-member-holder +.project-members-page + - if can?(current_user, :admin_project_member, @project) + .panel.panel-default + .panel-heading + Add new user to project + .pull-right + = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do + Import members + .panel-body + %p.light + Users with access to this project are listed below. = render "new_project_member" -%p.prepend-top-default.light - Users with access to this project are listed below. - Read more about project permissions - %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" + = render "team", members: @project_members -= render "team", members: @project_members - -- if @group - = render "group_members", members: @group_members - -:javascript - $('form.member-search-form').on('submit', function (event) { - event.preventDefault(); - Turbolinks.visit(this.action + '?' + $(this).serialize()); - }); + - if @group + = render "group_members", members: @group_members From ea7467d2be0e367ed1ec6df656cab059a9db6da0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 24 Nov 2015 22:27:01 +0100 Subject: [PATCH 290/410] Refactor group members tests a bit Signed-off-by: Dmitriy Zaporozhets --- features/groups.feature | 3 --- features/project/team_management.feature | 6 ++--- features/steps/groups.rb | 29 ++++++++++------------- features/steps/project/team_management.rb | 4 ---- 4 files changed, 15 insertions(+), 27 deletions(-) diff --git a/features/groups.feature b/features/groups.feature index 938e658f2a..65ced5c529 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -55,7 +55,6 @@ Feature: Groups Scenario: Add user to group Given gitlab user "Mike" When I visit group "Owned" members page - And I click link "Add members" When I select "Mike" as "Reporter" Then I should see "Mike" in team list as "Reporter" @@ -63,14 +62,12 @@ Feature: Groups Scenario: Ignore add user to group when is already Owner Given gitlab user "Mike" When I visit group "Owned" members page - And I click link "Add members" When I select "Mike" as "Reporter" Then I should see "Mike" in team list as "Owner" @javascript Scenario: Invite user to group When I visit group "Owned" members page - And I click link "Add members" When I select "sjobs@apple.com" as "Reporter" Then I should see "sjobs@apple.com" in team list as invited "Reporter" diff --git a/features/project/team_management.feature b/features/project/team_management.feature index 09a7df59df..06fb45c8bd 100644 --- a/features/project/team_management.feature +++ b/features/project/team_management.feature @@ -13,14 +13,12 @@ Feature: Project Team Management @javascript Scenario: Add user to project - Given I click link "Add members" - And I select "Mike" as "Reporter" + When I select "Mike" as "Reporter" Then I should see "Mike" in team list as "Reporter" @javascript Scenario: Invite user to project - Given I click link "Add members" - And I select "sjobs@apple.com" as "Reporter" + When I select "sjobs@apple.com" as "Reporter" Then I should see "sjobs@apple.com" in team list as invited "Reporter" @javascript diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 7c991af4c2..5de54d9b1e 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -13,10 +13,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps create(:user, name: "Mike") end - step 'I click link "Add members"' do - find(:css, 'button.btn-new').click - end - step 'I should see group "Owned"' do expect(page).to have_content '@owned' end @@ -60,14 +56,14 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end step 'I should see "Mike" in team list as "Reporter"' do - page.within '.well-list' do + page.within '.content-list' do expect(page).to have_content('Mike') expect(page).to have_content('Reporter') end end step 'I should see "Mike" in team list as "Owner"' do - page.within '.well-list' do + page.within '.content-list' do expect(page).to have_content('Mike') expect(page).to have_content('Owner') end @@ -83,7 +79,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do - page.within '.well-list' do + page.within '.content-list' do expect(page).to have_content('sjobs@apple.com') expect(page).to have_content('invited') expect(page).to have_content('Reporter') @@ -114,32 +110,29 @@ class Spinach::Features::Groups < Spinach::FeatureSteps step 'I select user "Mary Jane" from list with role "Reporter"' do user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane") - click_button 'Add members' + page.within ".users-group-form" do select2(user.id, from: "#user_ids", multiple: true) select "Reporter", from: "access_level" end + click_button "Add users to group" end step 'I should see user "John Doe" in team list' do - projects_with_access = find(".panel .well-list") - expect(projects_with_access).to have_content("John Doe") + expect(group_members_list).to have_content("John Doe") end step 'I should not see user "John Doe" in team list' do - projects_with_access = find(".panel .well-list") - expect(projects_with_access).not_to have_content("John Doe") + expect(group_members_list).not_to have_content("John Doe") end step 'I should see user "Mary Jane" in team list' do - projects_with_access = find(".panel .well-list") - expect(projects_with_access).to have_content("Mary Jane") + expect(group_members_list).to have_content("Mary Jane") end step 'I should not see user "Mary Jane" in team list' do - projects_with_access = find(".panel .well-list") - expect(projects_with_access).not_to have_content("Mary Jane") + expect(group_members_list).not_to have_content("Mary Jane") end step 'project from group "Owned" has issues assigned to me' do @@ -401,4 +394,8 @@ class Spinach::Features::Groups < Spinach::FeatureSteps author: current_user, milestone: milestone2_project3 end + + def group_members_list + find(".panel .content-list") + end end diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb index 97d6301645..caad52def7 100644 --- a/features/steps/project/team_management.rb +++ b/features/steps/project/team_management.rb @@ -15,10 +15,6 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps expect(page).to have_content(user.username) end - step 'I click link "Add members"' do - find(:css, 'button.btn-new').click - end - step 'I select "Mike" as "Reporter"' do user = User.find_by(name: "Mike") From 561634c78f3ab7967709196e8cca39a256d27196 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 25 Nov 2015 10:24:08 +0100 Subject: [PATCH 291/410] Refactor group steps Signed-off-by: Dmitriy Zaporozhets --- features/steps/groups.rb | 77 +++++++++------------------------------- 1 file changed, 16 insertions(+), 61 deletions(-) diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 5de54d9b1e..d99417d178 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -332,67 +332,22 @@ class Spinach::Features::Groups < Spinach::FeatureSteps def group_milestone group = owned_group - @project1 = create :project, - group: group - project2 = create :project, - path: 'gitlab-ci', - group: group - @project3 = create :project, - path: 'cookbook-gitlab', - group: group - milestone1_project1 = create :milestone, - title: "Version 7.2", - project: @project1 - milestone1_project2 = create :milestone, - title: "Version 7.2", - project: project2 - create :milestone, - title: "Version 7.2", - project: @project3 - milestone2_project1 = create :milestone, - title: "GL-113", - project: @project1 - milestone2_project2 = create :milestone, - title: "GL-113", - project: project2 - milestone2_project3 = create :milestone, - title: "GL-113", - project: @project3, - due_date: '2114-08-20', - description: 'Lorem Ipsum is simply dummy text of the printing and typesetting industry' - @issue1 = create :issue, - project: @project1, - assignee: current_user, - author: current_user, - milestone: milestone2_project1 - create :issue, - project: project2, - assignee: current_user, - author: current_user, - milestone: milestone1_project2 - create :issue, - project: @project3, - assignee: current_user, - author: current_user, - milestone: milestone1_project1 - create :merge_request, - source_project: @project1, - target_project: @project1, - assignee: current_user, - author: current_user, - milestone: milestone2_project1 - create :merge_request, - source_project: project2, - target_project: project2, - assignee: current_user, - author: current_user, - milestone: milestone2_project2 - @mr3 = create :merge_request, - source_project: @project3, - target_project: @project3, - assignee: current_user, - author: current_user, - milestone: milestone2_project3 + %w(gitlabhq gitlab-ci cookbook-gitlab).each do |path| + project = create :project, path: path, group: group + milestone = create :milestone, title: "Version 7.2", project: project + create :issue, + project: project, + assignee: current_user, + author: current_user, + milestone: milestone + + milestone = create :milestone, title: "GL-113", project: project + create :issue, + project: project, + assignee: current_user, + author: current_user, + milestone: milestone + end end def group_members_list From d262daa4f0f425ddda189ae9e2eb21bbf287f21a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 25 Nov 2015 11:44:39 +0100 Subject: [PATCH 292/410] Split group feature tests Signed-off-by: Dmitriy Zaporozhets --- .../groups/group_members/index.html.haml | 2 +- .../projects/project_members/_team.html.haml | 2 +- features/group/members.feature | 105 ++++++++ features/group/milestones.feature | 30 +++ features/groups.feature | 129 ---------- features/steps/group/members.rb | 147 ++++++++++++ features/steps/group/milestones.rb | 87 +++++++ features/steps/groups.rb | 226 +----------------- features/steps/shared/group.rb | 4 + features/steps/shared/user.rb | 4 + 10 files changed, 380 insertions(+), 356 deletions(-) create mode 100644 features/group/members.feature create mode 100644 features/group/milestones.feature create mode 100644 features/steps/group/members.rb create mode 100644 features/steps/group/milestones.rb diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml index c83766e729..335bf03607 100644 --- a/app/views/groups/group_members/index.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -24,7 +24,7 @@ = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false } - = button_tag class: 'btn' do + = button_tag class: 'btn', title: 'Search' do = icon("search") %ul.content-list - @members.each do |member| diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml index 8f4c613426..ccddab13aa 100644 --- a/app/views/projects/project_members/_team.html.haml +++ b/app/views/projects/project_members/_team.html.haml @@ -8,7 +8,7 @@ = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control', spellcheck: false } - = button_tag class: 'btn' do + = button_tag class: 'btn', title: 'Search' do = icon("search") %ul.content-list - members.each do |project_member| diff --git a/features/group/members.feature b/features/group/members.feature new file mode 100644 index 0000000000..1f9514bac3 --- /dev/null +++ b/features/group/members.feature @@ -0,0 +1,105 @@ +Feature: Group Members + Background: + Given I sign in as "John Doe" + And "John Doe" is owner of group "Owned" + And "John Doe" is guest of group "Guest" + + @javascript + Scenario: I should add user to group "Owned" + Given User "Mary Jane" exists + When I visit group "Owned" members page + And I select user "Mary Jane" from list with role "Reporter" + Then I should see user "Mary Jane" in team list + + @javascript + Scenario: Add user to group + Given gitlab user "Mike" + When I visit group "Owned" members page + When I select "Mike" as "Reporter" + Then I should see "Mike" in team list as "Reporter" + + @javascript + Scenario: Ignore add user to group when is already Owner + Given gitlab user "Mike" + When I visit group "Owned" members page + When I select "Mike" as "Reporter" + Then I should see "Mike" in team list as "Owner" + + @javascript + Scenario: Invite user to group + When I visit group "Owned" members page + When I select "sjobs@apple.com" as "Reporter" + Then I should see "sjobs@apple.com" in team list as invited "Reporter" + + @javascript + Scenario: Edit group member permissions + Given "Mary Jane" is guest of group "Owned" + And I visit group "Owned" members page + When I change the "Mary Jane" role to "Developer" + Then I should see "Mary Jane" as "Developer" + + # Leave + + @javascript + Scenario: Owner should be able to remove himself from group if he is not the last owner + Given "Mary Jane" is owner of group "Owned" + When I visit group "Owned" members page + Then I should see user "John Doe" in team list + Then I should see user "Mary Jane" in team list + When I click on the "Remove User From Group" button for "John Doe" + And I visit group "Owned" members page + Then I should not see user "John Doe" in team list + Then I should see user "Mary Jane" in team list + + @javascript + Scenario: Owner should not be able to remove himself from group if he is the last owner + Given "Mary Jane" is guest of group "Owned" + When I visit group "Owned" members page + Then I should see user "John Doe" in team list + Then I should see user "Mary Jane" in team list + Then I should not see the "Remove User From Group" button for "John Doe" + + @javascript + Scenario: Guest should be able to remove himself from group + Given "Mary Jane" is guest of group "Guest" + When I visit group "Guest" members page + Then I should see user "John Doe" in team list + Then I should see user "Mary Jane" in team list + When I click on the "Remove User From Group" button for "John Doe" + When I visit group "Guest" members page + Then I should not see user "John Doe" in team list + Then I should see user "Mary Jane" in team list + + @javascript + Scenario: Guest should be able to remove himself from group even if he is the only user in the group + When I visit group "Guest" members page + Then I should see user "John Doe" in team list + When I click on the "Remove User From Group" button for "John Doe" + When I visit group "Guest" members page + Then I should not see user "John Doe" in team list + + # Remove others + + Scenario: Owner should be able to remove other users from group + Given "Mary Jane" is owner of group "Owned" + When I visit group "Owned" members page + Then I should see user "John Doe" in team list + Then I should see user "Mary Jane" in team list + When I click on the "Remove User From Group" button for "Mary Jane" + When I visit group "Owned" members page + Then I should see user "John Doe" in team list + Then I should not see user "Mary Jane" in team list + + Scenario: Guest should not be able to remove other users from group + Given "Mary Jane" is guest of group "Guest" + When I visit group "Guest" members page + Then I should see user "John Doe" in team list + Then I should see user "Mary Jane" in team list + Then I should not see the "Remove User From Group" button for "Mary Jane" + + Scenario: Search member by name + Given "Mary Jane" is guest of group "Guest" + And I visit group "Guest" members page + When I search for 'Mary' member + Then I should see user "Mary Jane" in team list + Then I should not see user "John Doe" in team list diff --git a/features/group/milestones.feature b/features/group/milestones.feature new file mode 100644 index 0000000000..62ea66a783 --- /dev/null +++ b/features/group/milestones.feature @@ -0,0 +1,30 @@ +Feature: Group Milestones + Background: + Given I sign in as "John Doe" + And "John Doe" is owner of group "Owned" + + Scenario: I should see group "Owned" milestone index page with no milestones + When I visit group "Owned" page + And I click on group milestones + Then I should see group milestones index page has no milestones + + Scenario: I should see group "Owned" milestone index page with milestones + Given Group has projects with milestones + When I visit group "Owned" page + And I click on group milestones + Then I should see group milestones index page with milestones + + Scenario: I should see group "Owned" milestone show page + Given Group has projects with milestones + When I visit group "Owned" page + And I click on group milestones + And I click on one group milestone + Then I should see group milestone with descriptions and expiry date + And I should see group milestone with all issues and MRs assigned to that milestone + + Scenario: Create multiple milestones with one form + Given I visit group "Owned" milestones page + And I click new milestone button + And I fill milestone name + When I press create mileston button + Then milestone in each project should be created diff --git a/features/groups.feature b/features/groups.feature index 65ced5c529..c803e95298 100644 --- a/features/groups.feature +++ b/features/groups.feature @@ -2,7 +2,6 @@ Feature: Groups Background: Given I sign in as "John Doe" And "John Doe" is owner of group "Owned" - And "John Doe" is guest of group "Guest" Scenario: I should have back to group button When I visit group "Owned" page @@ -24,13 +23,6 @@ Feature: Groups When I visit group "Owned" merge requests page Then I should see merge requests from group "Owned" assigned to me - @javascript - Scenario: I should add user to projects in group "Owned" - Given User "Mary Jane" exists - When I visit group "Owned" members page - And I select user "Mary Jane" from list with role "Reporter" - Then I should see user "Mary Jane" in team list - Scenario: I should see edit group "Owned" page When I visit group "Owned" settings page And I change group "Owned" name to "new-name" @@ -51,127 +43,6 @@ Feature: Groups Then I should not see group "Owned" avatar And I should not see the "Remove avatar" button - @javascript - Scenario: Add user to group - Given gitlab user "Mike" - When I visit group "Owned" members page - When I select "Mike" as "Reporter" - Then I should see "Mike" in team list as "Reporter" - - @javascript - Scenario: Ignore add user to group when is already Owner - Given gitlab user "Mike" - When I visit group "Owned" members page - When I select "Mike" as "Reporter" - Then I should see "Mike" in team list as "Owner" - - @javascript - Scenario: Invite user to group - When I visit group "Owned" members page - When I select "sjobs@apple.com" as "Reporter" - Then I should see "sjobs@apple.com" in team list as invited "Reporter" - - @javascript - Scenario: Edit group member permissions - Given "Mary Jane" is guest of group "Owned" - And I visit group "Owned" members page - When I change the "Mary Jane" role to "Developer" - Then I should see "Mary Jane" as "Developer" - - # Leave - - @javascript - Scenario: Owner should be able to remove himself from group if he is not the last owner - Given "Mary Jane" is owner of group "Owned" - When I visit group "Owned" members page - Then I should see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - When I click on the "Remove User From Group" button for "John Doe" - And I visit group "Owned" members page - Then I should not see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - - @javascript - Scenario: Owner should not be able to remove himself from group if he is the last owner - Given "Mary Jane" is guest of group "Owned" - When I visit group "Owned" members page - Then I should see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - Then I should not see the "Remove User From Group" button for "John Doe" - - @javascript - Scenario: Guest should be able to remove himself from group - Given "Mary Jane" is guest of group "Guest" - When I visit group "Guest" members page - Then I should see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - When I click on the "Remove User From Group" button for "John Doe" - When I visit group "Guest" members page - Then I should not see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - - @javascript - Scenario: Guest should be able to remove himself from group even if he is the only user in the group - When I visit group "Guest" members page - Then I should see user "John Doe" in team list - When I click on the "Remove User From Group" button for "John Doe" - When I visit group "Guest" members page - Then I should not see user "John Doe" in team list - - # Remove others - - Scenario: Owner should be able to remove other users from group - Given "Mary Jane" is owner of group "Owned" - When I visit group "Owned" members page - Then I should see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - When I click on the "Remove User From Group" button for "Mary Jane" - When I visit group "Owned" members page - Then I should see user "John Doe" in team list - Then I should not see user "Mary Jane" in team list - - Scenario: Guest should not be able to remove other users from group - Given "Mary Jane" is guest of group "Guest" - When I visit group "Guest" members page - Then I should see user "John Doe" in team list - Then I should see user "Mary Jane" in team list - Then I should not see the "Remove User From Group" button for "Mary Jane" - - Scenario: Search member by name - Given "Mary Jane" is guest of group "Guest" - And I visit group "Guest" members page - When I search for 'Mary' member - Then I should see user "Mary Jane" in team list - Then I should not see user "John Doe" in team list - - # Group milestones - - Scenario: I should see group "Owned" milestone index page with no milestones - When I visit group "Owned" page - And I click on group milestones - Then I should see group milestones index page has no milestones - - Scenario: I should see group "Owned" milestone index page with milestones - Given Group has projects with milestones - When I visit group "Owned" page - And I click on group milestones - Then I should see group milestones index page with milestones - - Scenario: I should see group "Owned" milestone show page - Given Group has projects with milestones - When I visit group "Owned" page - And I click on group milestones - And I click on one group milestone - Then I should see group milestone with descriptions and expiry date - And I should see group milestone with all issues and MRs assigned to that milestone - - Scenario: Create multiple milestones with one form - Given I visit group "Owned" milestones page - And I click new milestone button - And I fill milestone name - When I press create mileston button - Then milestone in each project should be created - # Group projects in settings Scenario: I should see all projects in the project list in settings Given Group "Owned" has archived project diff --git a/features/steps/group/members.rb b/features/steps/group/members.rb new file mode 100644 index 0000000000..0706df3aec --- /dev/null +++ b/features/steps/group/members.rb @@ -0,0 +1,147 @@ +class Spinach::Features::GroupMembers < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedGroup + include SharedUser + include Select2Helper + + step 'I select "Mike" as "Reporter"' do + user = User.find_by(name: "Mike") + + page.within ".users-group-form" do + select2(user.id, from: "#user_ids", multiple: true) + select "Reporter", from: "access_level" + end + + click_button "Add users to group" + end + + step 'I select "Mike" as "Master"' do + user = User.find_by(name: "Mike") + + page.within ".users-group-form" do + select2(user.id, from: "#user_ids", multiple: true) + select "Master", from: "access_level" + end + + click_button "Add users to group" + end + + step 'I should see "Mike" in team list as "Reporter"' do + page.within '.content-list' do + expect(page).to have_content('Mike') + expect(page).to have_content('Reporter') + end + end + + step 'I should see "Mike" in team list as "Owner"' do + page.within '.content-list' do + expect(page).to have_content('Mike') + expect(page).to have_content('Owner') + end + end + + step 'I select "sjobs@apple.com" as "Reporter"' do + page.within ".users-group-form" do + select2("sjobs@apple.com", from: "#user_ids", multiple: true) + select "Reporter", from: "access_level" + end + + click_button "Add users to group" + end + + step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do + page.within '.content-list' do + expect(page).to have_content('sjobs@apple.com') + expect(page).to have_content('invited') + expect(page).to have_content('Reporter') + end + end + + step 'I select user "Mary Jane" from list with role "Reporter"' do + user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane") + + page.within ".users-group-form" do + select2(user.id, from: "#user_ids", multiple: true) + select "Reporter", from: "access_level" + end + + click_button "Add users to group" + end + + step 'I should see user "John Doe" in team list' do + expect(group_members_list).to have_content("John Doe") + end + + step 'I should not see user "John Doe" in team list' do + expect(group_members_list).not_to have_content("John Doe") + end + + step 'I should see user "Mary Jane" in team list' do + expect(group_members_list).to have_content("Mary Jane") + end + + step 'I should not see user "Mary Jane" in team list' do + expect(group_members_list).not_to have_content("Mary Jane") + end + + step 'I click on the "Remove User From Group" button for "John Doe"' do + find(:css, 'li', text: "John Doe").find(:css, 'a.btn-remove').click + # poltergeist always confirms popups. + end + + step 'I click on the "Remove User From Group" button for "Mary Jane"' do + find(:css, 'li', text: "Mary Jane").find(:css, 'a.btn-remove').click + # poltergeist always confirms popups. + end + + step 'I should not see the "Remove User From Group" button for "John Doe"' do + expect(find(:css, 'li', text: "John Doe")).not_to have_selector(:css, 'a.btn-remove') + # poltergeist always confirms popups. + end + + step 'I should not see the "Remove User From Group" button for "Mary Jane"' do + expect(find(:css, 'li', text: "Mary Jane")).not_to have_selector(:css, 'a.btn-remove') + # poltergeist always confirms popups. + end + + step 'I search for \'Mary\' member' do + page.within '.member-search-form' do + fill_in 'search', with: 'Mary' + click_button 'Search' + end + end + + step 'I change the "Mary Jane" role to "Developer"' do + member = mary_jane_member + + page.within "#group_member_#{member.id}" do + find(".js-toggle-button").click + page.within "#edit_group_member_#{member.id}" do + select 'Developer', from: 'group_member_access_level' + click_on 'Save' + end + end + end + + step 'I should see "Mary Jane" as "Developer"' do + member = mary_jane_member + + page.within "#group_member_#{member.id}" do + page.within '.member-access-level' do + expect(page).to have_content "Developer" + end + end + end + + private + + def mary_jane_member + user = User.find_by(name: "Mary Jane") + owned_group.members.find_by(user_id: user.id) + end + + def group_members_list + find(".panel .content-list") + end +end diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb new file mode 100644 index 0000000000..4c4038d44c --- /dev/null +++ b/features/steps/group/milestones.rb @@ -0,0 +1,87 @@ +class Spinach::Features::GroupMilestones < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedGroup + include SharedUser + + step 'I click on group milestones' do + click_link 'Milestones' + end + + step 'I should see group milestones index page has no milestones' do + expect(page).to have_content('No milestones to show') + end + + step 'Group has projects with milestones' do + group_milestone + end + + step 'I should see group milestones index page with milestones' do + expect(page).to have_content('Version 7.2') + expect(page).to have_content('GL-113') + expect(page).to have_link('3 Issues', href: issues_group_path("owned", milestone_title: "Version 7.2")) + expect(page).to have_link('0 Merge Requests', href: merge_requests_group_path("owned", milestone_title: "GL-113")) + end + + step 'I click on one group milestone' do + click_link 'GL-113' + end + + step 'I should see group milestone with descriptions and expiry date' do + expect(page).to have_content('expires at Aug 20, 2114') + end + + step 'I should see group milestone with all issues and MRs assigned to that milestone' do + expect(page).to have_content('Milestone GL-113') + expect(page).to have_content('Progress: 0 closed – 3 open') + issue = Milestone.find_by(name: 'GL-113').issues.first + expect(page).to have_link(issue.title, href: namespace_project_issue_path(issue.project.namespace, issue.project, issue)) + end + + step 'I fill milestone name' do + fill_in 'milestone_title', with: 'v2.9.0' + end + + step 'I click new milestone button' do + click_link "New Milestone" + end + + step 'I press create mileston button' do + click_button "Create Milestone" + end + + step 'milestone in each project should be created' do + group = Group.find_by(name: 'Owned') + expect(page).to have_content "Milestone v2.9.0" + expect(group.projects).to be_present + + group.projects.each do |project| + expect(page).to have_content project.name + end + end + + private + + def group_milestone + group = owned_group + + %w(gitlabhq gitlab-ci cookbook-gitlab).each do |path| + project = create :project, path: path, group: group + milestone = create :milestone, title: "Version 7.2", project: project + create :issue, + project: project, + assignee: current_user, + author: current_user, + milestone: milestone + + milestone = create :milestone, title: "GL-113", project: project, + due_date: '2114-08-20', description: 'Lorem Ipsum is simply dummy text' + + create :issue, + project: project, + assignee: current_user, + author: current_user, + milestone: milestone + end + end +end diff --git a/features/steps/groups.rb b/features/steps/groups.rb index d99417d178..f5e3fee61c 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -3,16 +3,11 @@ class Spinach::Features::Groups < Spinach::FeatureSteps include SharedPaths include SharedGroup include SharedUser - include Select2Helper step 'I should see back to dashboard button' do expect(page).to have_content 'Go to dashboard' end - step 'gitlab user "Mike"' do - create(:user, name: "Mike") - end - step 'I should see group "Owned"' do expect(page).to have_content '@owned' end @@ -33,59 +28,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps expect(page).to have_content 'Public-project' end - step 'I select "Mike" as "Reporter"' do - user = User.find_by(name: "Mike") - - page.within ".users-group-form" do - select2(user.id, from: "#user_ids", multiple: true) - select "Reporter", from: "access_level" - end - - click_button "Add users to group" - end - - step 'I select "Mike" as "Master"' do - user = User.find_by(name: "Mike") - - page.within ".users-group-form" do - select2(user.id, from: "#user_ids", multiple: true) - select "Master", from: "access_level" - end - - click_button "Add users to group" - end - - step 'I should see "Mike" in team list as "Reporter"' do - page.within '.content-list' do - expect(page).to have_content('Mike') - expect(page).to have_content('Reporter') - end - end - - step 'I should see "Mike" in team list as "Owner"' do - page.within '.content-list' do - expect(page).to have_content('Mike') - expect(page).to have_content('Owner') - end - end - - step 'I select "sjobs@apple.com" as "Reporter"' do - page.within ".users-group-form" do - select2("sjobs@apple.com", from: "#user_ids", multiple: true) - select "Reporter", from: "access_level" - end - - click_button "Add users to group" - end - - step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do - page.within '.content-list' do - expect(page).to have_content('sjobs@apple.com') - expect(page).to have_content('invited') - expect(page).to have_content('Reporter') - end - end - step 'I should see group "Owned" projects list' do owned_group.projects.each do |project| expect(page).to have_link project.name @@ -108,33 +50,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps end end - step 'I select user "Mary Jane" from list with role "Reporter"' do - user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane") - - page.within ".users-group-form" do - select2(user.id, from: "#user_ids", multiple: true) - select "Reporter", from: "access_level" - end - - click_button "Add users to group" - end - - step 'I should see user "John Doe" in team list' do - expect(group_members_list).to have_content("John Doe") - end - - step 'I should not see user "John Doe" in team list' do - expect(group_members_list).not_to have_content("John Doe") - end - - step 'I should see user "Mary Jane" in team list' do - expect(group_members_list).to have_content("Mary Jane") - end - - step 'I should not see user "Mary Jane" in team list' do - expect(group_members_list).not_to have_content("Mary Jane") - end - step 'project from group "Owned" has issues assigned to me' do create :issue, project: project, @@ -196,67 +111,6 @@ class Spinach::Features::Groups < Spinach::FeatureSteps expect(page).not_to have_link("Remove avatar") end - step 'I click on the "Remove User From Group" button for "John Doe"' do - find(:css, 'li', text: "John Doe").find(:css, 'a.btn-remove').click - # poltergeist always confirms popups. - end - - step 'I click on the "Remove User From Group" button for "Mary Jane"' do - find(:css, 'li', text: "Mary Jane").find(:css, 'a.btn-remove').click - # poltergeist always confirms popups. - end - - step 'I should not see the "Remove User From Group" button for "John Doe"' do - expect(find(:css, 'li', text: "John Doe")).not_to have_selector(:css, 'a.btn-remove') - # poltergeist always confirms popups. - end - - step 'I should not see the "Remove User From Group" button for "Mary Jane"' do - expect(find(:css, 'li', text: "Mary Jane")).not_to have_selector(:css, 'a.btn-remove') - # poltergeist always confirms popups. - end - - step 'I search for \'Mary\' member' do - page.within '.member-search-form' do - fill_in 'search', with: 'Mary' - click_button 'Search' - end - end - - step 'I click on group milestones' do - click_link 'Milestones' - end - - step 'I should see group milestones index page has no milestones' do - expect(page).to have_content('No milestones to show') - end - - step 'Group has projects with milestones' do - group_milestone - end - - step 'I should see group milestones index page with milestones' do - expect(page).to have_content('Version 7.2') - expect(page).to have_content('GL-113') - expect(page).to have_link('2 Issues', href: issues_group_path("owned", milestone_title: "Version 7.2")) - expect(page).to have_link('3 Merge Requests', href: merge_requests_group_path("owned", milestone_title: "GL-113")) - end - - step 'I click on one group milestone' do - click_link 'GL-113' - end - - step 'I should see group milestone with descriptions and expiry date' do - expect(page).to have_content('expires at Aug 20, 2114') - end - - step 'I should see group milestone with all issues and MRs assigned to that milestone' do - expect(page).to have_content('Milestone GL-113') - expect(page).to have_content('Progress: 0 closed – 4 open') - expect(page).to have_link(@issue1.title, href: namespace_project_issue_path(@project1.namespace, @project1, @issue1)) - expect(page).to have_link(@mr3.title, href: namespace_project_merge_request_path(@project3.namespace, @project3, @mr3)) - end - step 'Group "Owned" has archived project' do group = Group.find_by(name: 'Owned') create(:project, namespace: group, archived: true, path: "archived-project") @@ -266,60 +120,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps expect(page).to have_xpath("//span[@class='label label-warning']", text: 'archived') end - step 'I fill milestone name' do - fill_in 'milestone_title', with: 'v2.9.0' - end - - step 'I click new milestone button' do - click_link "New Milestone" - end - - step 'I press create mileston button' do - click_button "Create Milestone" - end - - step 'milestone in each project should be created' do - group = Group.find_by(name: 'Owned') - expect(page).to have_content "Milestone v2.9.0" - expect(group.projects).to be_present - - group.projects.each do |project| - expect(page).to have_content project.name - end - end - - step 'I change the "Mary Jane" role to "Developer"' do - member = mary_jane_member - - page.within "#group_member_#{member.id}" do - find(".js-toggle-button").click - page.within "#edit_group_member_#{member.id}" do - select 'Developer', from: 'group_member_access_level' - click_on 'Save' - end - end - end - - step 'I should see "Mary Jane" as "Developer"' do - member = mary_jane_member - - page.within "#group_member_#{member.id}" do - page.within '.member-access-level' do - expect(page).to have_content "Developer" - end - end - end - - protected - - def owned_group - @owned_group ||= Group.find_by(name: "Owned") - end - - def mary_jane_member - user = User.find_by(name: "Mary Jane") - owned_group.members.find_by(user_id: user.id) - end + private def assigned_to_me(key) project.send(key).where(assignee_id: current_user.id) @@ -328,29 +129,4 @@ class Spinach::Features::Groups < Spinach::FeatureSteps def project owned_group.projects.first end - - def group_milestone - group = owned_group - - %w(gitlabhq gitlab-ci cookbook-gitlab).each do |path| - project = create :project, path: path, group: group - milestone = create :milestone, title: "Version 7.2", project: project - create :issue, - project: project, - assignee: current_user, - author: current_user, - milestone: milestone - - milestone = create :milestone, title: "GL-113", project: project - create :issue, - project: project, - assignee: current_user, - author: current_user, - milestone: milestone - end - end - - def group_members_list - find(".panel .content-list") - end end diff --git a/features/steps/shared/group.rb b/features/steps/shared/group.rb index 83a0457697..58581653f2 100644 --- a/features/steps/shared/group.rb +++ b/features/steps/shared/group.rb @@ -41,4 +41,8 @@ module SharedGroup project.team << [user, :master] @project_count += 1 end + + def owned_group + @owned_group ||= Group.find_by(name: "Owned") + end end diff --git a/features/steps/shared/user.rb b/features/steps/shared/user.rb index fc1e8d6e88..250cc5b94f 100644 --- a/features/steps/shared/user.rb +++ b/features/steps/shared/user.rb @@ -9,6 +9,10 @@ module SharedUser user_exists("Mary Jane", { username: "mary_jane" }) end + step 'gitlab user "Mike"' do + create(:user, name: "Mike") + end + protected def user_exists(name, options = {}) From acad9d67c7af881c1e4cce1e20ffd9c8f65f3029 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 25 Nov 2015 12:27:34 +0100 Subject: [PATCH 293/410] Fix rubocop complain Signed-off-by: Dmitriy Zaporozhets --- features/steps/group/milestones.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb index 4c4038d44c..6e57b16ccb 100644 --- a/features/steps/group/milestones.rb +++ b/features/steps/group/milestones.rb @@ -74,8 +74,11 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps author: current_user, milestone: milestone - milestone = create :milestone, title: "GL-113", project: project, - due_date: '2114-08-20', description: 'Lorem Ipsum is simply dummy text' + milestone = create :milestone, + title: "GL-113", + project: project, + due_date: '2114-08-20', + description: 'Lorem Ipsum is simply dummy text' create :issue, project: project, From 18a5f91ed63322af7f2fed29a6cc9a4c1e96978d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 25 Nov 2015 13:08:47 +0100 Subject: [PATCH 294/410] Bump gitlab-shell to 2.6.8 Signed-off-by: Dmitriy Zaporozhets --- GITLAB_SHELL_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index e261122d5c..743af5e125 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -2.6.7 +2.6.8 From 7da750cc30308173e6f21f570ecd5902dfd3d89d Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 25 Nov 2015 13:27:31 +0100 Subject: [PATCH 295/410] Specs for links in email notifications for Gmail Actions. --- spec/mailers/notify_spec.rb | 94 ++++++++++++++++++++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 47863d5457..52bad36ae1 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -77,6 +77,14 @@ describe Notify do end end + shared_examples 'it should have Gmail Actions links' do + it { is_expected.to have_body_text /ViewAction/ } + end + + shared_examples 'it should not have Gmail Actions links' do + it { is_expected.to_not have_body_text /ViewAction/ } + end + describe 'for new users, the email' do let(:example_site_path) { root_path } let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) } @@ -87,6 +95,7 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'a new user email', new_user_address + it_behaves_like 'it should not have Gmail Actions links' it 'contains the password text' do is_expected.to have_body_text /Click here to set your password/ @@ -115,6 +124,7 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it_behaves_like 'a new user email', new_user_address + it_behaves_like 'it should not have Gmail Actions links' it 'should not contain the new user\'s password' do is_expected.not_to have_body_text /password/ @@ -127,6 +137,7 @@ describe Notify do subject { Notify.new_ssh_key_email(key.id) } it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' it 'is sent to the new user' do is_expected.to deliver_to key.user.email @@ -150,6 +161,8 @@ describe Notify do subject { Notify.new_email_email(email.id) } + it_behaves_like 'it should not have Gmail Actions links' + it 'is sent to the new user' do is_expected.to deliver_to email.user.email end @@ -194,6 +207,7 @@ describe Notify do it_behaves_like 'an assignee email' it_behaves_like 'an email starting a new thread', 'issue' + it_behaves_like 'it should have Gmail Actions links' it 'has the correct subject' do is_expected.to have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/ @@ -202,6 +216,10 @@ describe Notify do it 'contains a link to the new issue' do is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/ end + + it 'Gmail Actions contain correct action name' do + is_expected.to have_body_text /View Issue/ + end end describe 'that are new with a description' do @@ -217,6 +235,7 @@ describe Notify do it_behaves_like 'a multiple recipients email' it_behaves_like 'an answer to an existing thread', 'issue' + it_behaves_like 'it should have Gmail Actions links' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -239,6 +258,10 @@ describe Notify do it 'contains a link to the issue' do is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/ end + + it 'Gmail Actions contain correct action name' do + is_expected.to have_body_text /View Issue/ + end end describe 'status changed' do @@ -246,6 +269,7 @@ describe Notify do subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) } it_behaves_like 'an answer to an existing thread', 'issue' + it_behaves_like 'it should have Gmail Actions links' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -268,8 +292,11 @@ describe Notify do it 'contains a link to the issue' do is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/ end - end + it 'Gmail Actions contain correct action name' do + is_expected.to have_body_text /View Issue/ + end + end end context 'for merge requests' do @@ -282,6 +309,7 @@ describe Notify do it_behaves_like 'an assignee email' it_behaves_like 'an email starting a new thread', 'merge_request' + it_behaves_like 'it should have Gmail Actions links' it 'has the correct subject' do is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ @@ -302,14 +330,24 @@ describe Notify do it 'has the correct message-id set' do is_expected.to have_header 'Message-ID', "" end + + it 'Gmail Actions contain correct action name' do + is_expected.to have_body_text /View Merge request/ + end end describe 'that are new with a description' do subject { Notify.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) } + it_behaves_like 'it should have Gmail Actions links' + it 'contains the description' do is_expected.to have_body_text /#{merge_request_with_description.description}/ end + + it 'Gmail Actions contain correct action name' do + is_expected.to have_body_text /View Merge request/ + end end describe 'that are reassigned' do @@ -317,6 +355,7 @@ describe Notify do it_behaves_like 'a multiple recipients email' it_behaves_like 'an answer to an existing thread', 'merge_request' + it_behaves_like 'it should have Gmail Actions links' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -339,6 +378,10 @@ describe Notify do it 'contains a link to the merge request' do is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/ end + + it 'Gmail Actions contain correct action name' do + is_expected.to have_body_text /View Merge request/ + end end describe 'status changed' do @@ -346,6 +389,8 @@ describe Notify do subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user) } it_behaves_like 'an answer to an existing thread', 'merge_request' + it_behaves_like 'it should have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -368,6 +413,10 @@ describe Notify do it 'contains a link to the merge request' do is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/ end + + it 'Gmail Actions contain correct action name' do + is_expected.to have_body_text /View Merge request/ + end end describe 'that are merged' do @@ -375,6 +424,7 @@ describe Notify do it_behaves_like 'a multiple recipients email' it_behaves_like 'an answer to an existing thread', 'merge_request' + it_behaves_like 'it should have Gmail Actions links' it 'is sent as the merge author' do sender = subject.header[:from].addrs[0] @@ -393,6 +443,10 @@ describe Notify do it 'contains a link to the merge request' do is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/ end + + it 'Gmail Actions contain correct action name' do + is_expected.to have_body_text /View Merge request/ + end end end end @@ -403,6 +457,7 @@ describe Notify do subject { Notify.project_was_moved_email(project.id, user.id, "gitlab/gitlab") } it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' it 'has the correct subject' do is_expected.to have_subject /Project was moved/ @@ -424,13 +479,16 @@ describe Notify do subject { Notify.project_access_granted_email(project_member.id) } it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' it 'has the correct subject' do is_expected.to have_subject /Access to project was granted/ end + it 'contains name of project' do is_expected.to have_body_text /#{project.name}/ end + it 'contains new user role' do is_expected.to have_body_text /#{project_member.human_access}/ end @@ -445,6 +503,8 @@ describe Notify do end shared_examples 'a note email' do + it_behaves_like 'it should have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(note_author.name) @@ -469,6 +529,7 @@ describe Notify do it_behaves_like 'a note email' it_behaves_like 'an answer to an existing thread', 'commit' + it_behaves_like 'it should have Gmail Actions links' it 'has the correct subject' do is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/ @@ -477,6 +538,10 @@ describe Notify do it 'contains a link to the commit' do is_expected.to have_body_text commit.short_id end + + it 'Gmail Actions contain correct action name' do + is_expected.to have_body_text /View Commit/ + end end describe 'on a merge request' do @@ -488,6 +553,7 @@ describe Notify do it_behaves_like 'a note email' it_behaves_like 'an answer to an existing thread', 'merge_request' + it_behaves_like 'it should have Gmail Actions links' it 'has the correct subject' do is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ @@ -496,6 +562,10 @@ describe Notify do it 'contains a link to the merge request note' do is_expected.to have_body_text /#{note_on_merge_request_path}/ end + + it 'Gmail Actions contain correct action name' do + is_expected.to have_body_text /View Merge request/ + end end describe 'on an issue' do @@ -507,6 +577,7 @@ describe Notify do it_behaves_like 'a note email' it_behaves_like 'an answer to an existing thread', 'issue' + it_behaves_like 'it should have Gmail Actions links' it 'has the correct subject' do is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/ @@ -515,6 +586,10 @@ describe Notify do it 'contains a link to the issue note' do is_expected.to have_body_text /#{note_on_issue_path}/ end + + it 'Gmail Actions contain correct action name' do + is_expected.to have_body_text /View Issue/ + end end end end @@ -527,6 +602,7 @@ describe Notify do subject { Notify.group_access_granted_email(membership.id) } it_behaves_like 'an email sent from GitLab' + it_behaves_like 'it should not have Gmail Actions links' it 'has the correct subject' do is_expected.to have_subject /Access to group was granted/ @@ -574,6 +650,8 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :create) } + it_behaves_like 'it should not have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(user.name) @@ -600,6 +678,8 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :create) } + it_behaves_like 'it should not have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(user.name) @@ -625,6 +705,8 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :delete) } + it_behaves_like 'it should not have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(user.name) @@ -646,6 +728,8 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) } + it_behaves_like 'it should not have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(user.name) @@ -671,6 +755,8 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare, reverse_compare: false, send_from_committer_email: send_from_committer_email) } + it_behaves_like 'it should not have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(user.name) @@ -774,6 +860,8 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) } + it_behaves_like 'it should have Gmail Actions links' + it 'is sent as the author' do sender = subject.header[:from].addrs[0] expect(sender.display_name).to eq(user.name) @@ -799,5 +887,9 @@ describe Notify do it 'contains a link to the diff' do is_expected.to have_body_text /#{diff_path}/ end + + it 'Gmail Actions contain correct action name' do + is_expected.to have_body_text /View Commit/ + end end end From 56135927273cfd722872e893abde3728f3b21d38 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 25 Nov 2015 13:53:02 +0100 Subject: [PATCH 296/410] If action link is not found, return nil. --- app/helpers/emails_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 45788ba95a..41b5bd7be9 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -28,6 +28,8 @@ module EmailsHelper return "View #{action.humanize.singularize}" end end + + nil end def color_email_diff(diffcontent) From 5c0be319cc6d568cefd339be8bc5f80e157836b4 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 25 Nov 2015 13:59:03 +0100 Subject: [PATCH 297/410] Remove some repetition in notify spec. --- spec/mailers/notify_spec.rb | 93 +++++++++++++------------------------ 1 file changed, 32 insertions(+), 61 deletions(-) diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 52bad36ae1..d6796b07a5 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -85,6 +85,24 @@ describe Notify do it { is_expected.to_not have_body_text /ViewAction/ } end + shared_examples 'it should show Gmail Actions View Issue link' do + it_behaves_like 'it should have Gmail Actions links' + + it { is_expected.to have_body_text /View Issue/ } + end + + shared_examples 'it should show Gmail Actions View Merge request link' do + it_behaves_like 'it should have Gmail Actions links' + + it { is_expected.to have_body_text /View Merge request/ } + end + + shared_examples 'it should show Gmail Actions View Commit link' do + it_behaves_like 'it should have Gmail Actions links' + + it { is_expected.to have_body_text /View Commit/ } + end + describe 'for new users, the email' do let(:example_site_path) { root_path } let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) } @@ -207,7 +225,7 @@ describe Notify do it_behaves_like 'an assignee email' it_behaves_like 'an email starting a new thread', 'issue' - it_behaves_like 'it should have Gmail Actions links' + it_behaves_like 'it should show Gmail Actions View Issue link' it 'has the correct subject' do is_expected.to have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/ @@ -216,15 +234,13 @@ describe Notify do it 'contains a link to the new issue' do is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/ end - - it 'Gmail Actions contain correct action name' do - is_expected.to have_body_text /View Issue/ - end end describe 'that are new with a description' do subject { Notify.new_issue_email(issue_with_description.assignee_id, issue_with_description.id) } + it_behaves_like 'it should show Gmail Actions View Issue link' + it 'contains the description' do is_expected.to have_body_text /#{issue_with_description.description}/ end @@ -235,7 +251,7 @@ describe Notify do it_behaves_like 'a multiple recipients email' it_behaves_like 'an answer to an existing thread', 'issue' - it_behaves_like 'it should have Gmail Actions links' + it_behaves_like 'it should show Gmail Actions View Issue link' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -258,10 +274,6 @@ describe Notify do it 'contains a link to the issue' do is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/ end - - it 'Gmail Actions contain correct action name' do - is_expected.to have_body_text /View Issue/ - end end describe 'status changed' do @@ -269,7 +281,7 @@ describe Notify do subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) } it_behaves_like 'an answer to an existing thread', 'issue' - it_behaves_like 'it should have Gmail Actions links' + it_behaves_like 'it should show Gmail Actions View Issue link' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -292,10 +304,6 @@ describe Notify do it 'contains a link to the issue' do is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/ end - - it 'Gmail Actions contain correct action name' do - is_expected.to have_body_text /View Issue/ - end end end @@ -309,7 +317,7 @@ describe Notify do it_behaves_like 'an assignee email' it_behaves_like 'an email starting a new thread', 'merge_request' - it_behaves_like 'it should have Gmail Actions links' + it_behaves_like 'it should show Gmail Actions View Merge request link' it 'has the correct subject' do is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ @@ -330,24 +338,16 @@ describe Notify do it 'has the correct message-id set' do is_expected.to have_header 'Message-ID', "" end - - it 'Gmail Actions contain correct action name' do - is_expected.to have_body_text /View Merge request/ - end end describe 'that are new with a description' do subject { Notify.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) } - it_behaves_like 'it should have Gmail Actions links' + it_behaves_like 'it should show Gmail Actions View Merge request link' it 'contains the description' do is_expected.to have_body_text /#{merge_request_with_description.description}/ end - - it 'Gmail Actions contain correct action name' do - is_expected.to have_body_text /View Merge request/ - end end describe 'that are reassigned' do @@ -355,7 +355,7 @@ describe Notify do it_behaves_like 'a multiple recipients email' it_behaves_like 'an answer to an existing thread', 'merge_request' - it_behaves_like 'it should have Gmail Actions links' + it_behaves_like 'it should show Gmail Actions View Merge request link' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -378,10 +378,6 @@ describe Notify do it 'contains a link to the merge request' do is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/ end - - it 'Gmail Actions contain correct action name' do - is_expected.to have_body_text /View Merge request/ - end end describe 'status changed' do @@ -389,8 +385,7 @@ describe Notify do subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user) } it_behaves_like 'an answer to an existing thread', 'merge_request' - it_behaves_like 'it should have Gmail Actions links' - + it_behaves_like 'it should show Gmail Actions View Merge request link' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -413,10 +408,6 @@ describe Notify do it 'contains a link to the merge request' do is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/ end - - it 'Gmail Actions contain correct action name' do - is_expected.to have_body_text /View Merge request/ - end end describe 'that are merged' do @@ -424,7 +415,7 @@ describe Notify do it_behaves_like 'a multiple recipients email' it_behaves_like 'an answer to an existing thread', 'merge_request' - it_behaves_like 'it should have Gmail Actions links' + it_behaves_like 'it should show Gmail Actions View Merge request link' it 'is sent as the merge author' do sender = subject.header[:from].addrs[0] @@ -443,10 +434,6 @@ describe Notify do it 'contains a link to the merge request' do is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/ end - - it 'Gmail Actions contain correct action name' do - is_expected.to have_body_text /View Merge request/ - end end end end @@ -529,7 +516,7 @@ describe Notify do it_behaves_like 'a note email' it_behaves_like 'an answer to an existing thread', 'commit' - it_behaves_like 'it should have Gmail Actions links' + it_behaves_like 'it should show Gmail Actions View Commit link' it 'has the correct subject' do is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/ @@ -538,10 +525,6 @@ describe Notify do it 'contains a link to the commit' do is_expected.to have_body_text commit.short_id end - - it 'Gmail Actions contain correct action name' do - is_expected.to have_body_text /View Commit/ - end end describe 'on a merge request' do @@ -553,7 +536,7 @@ describe Notify do it_behaves_like 'a note email' it_behaves_like 'an answer to an existing thread', 'merge_request' - it_behaves_like 'it should have Gmail Actions links' + it_behaves_like 'it should show Gmail Actions View Merge request link' it 'has the correct subject' do is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ @@ -562,10 +545,6 @@ describe Notify do it 'contains a link to the merge request note' do is_expected.to have_body_text /#{note_on_merge_request_path}/ end - - it 'Gmail Actions contain correct action name' do - is_expected.to have_body_text /View Merge request/ - end end describe 'on an issue' do @@ -577,7 +556,7 @@ describe Notify do it_behaves_like 'a note email' it_behaves_like 'an answer to an existing thread', 'issue' - it_behaves_like 'it should have Gmail Actions links' + it_behaves_like 'it should show Gmail Actions View Issue link' it 'has the correct subject' do is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/ @@ -586,10 +565,6 @@ describe Notify do it 'contains a link to the issue note' do is_expected.to have_body_text /#{note_on_issue_path}/ end - - it 'Gmail Actions contain correct action name' do - is_expected.to have_body_text /View Issue/ - end end end end @@ -860,7 +835,7 @@ describe Notify do subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) } - it_behaves_like 'it should have Gmail Actions links' + it_behaves_like 'it should show Gmail Actions View Commit link' it 'is sent as the author' do sender = subject.header[:from].addrs[0] @@ -887,9 +862,5 @@ describe Notify do it 'contains a link to the diff' do is_expected.to have_body_text /#{diff_path}/ end - - it 'Gmail Actions contain correct action name' do - is_expected.to have_body_text /View Commit/ - end end end From 40ff1318d29884e4d17e7e450d8a7633e5ac36a9 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 25 Nov 2015 18:18:44 +0200 Subject: [PATCH 298/410] Rails update to 4.2.4 --- Gemfile | 7 +- Gemfile.lock | 246 ++++++++++++-------- app/models/sent_notification.rb | 5 +- app/workers/emails_on_push_worker.rb | 2 +- bin/ci/upgrade.rb | 0 bin/rails | 6 +- bin/rake | 9 +- bin/setup | 29 +++ bin/upgrade.rb | 0 config/environment.rb | 2 +- config/environments/development.rb | 2 +- config/environments/production.rb | 2 +- config/environments/test.rb | 2 +- config/initializers/cookies_serializer.rb | 2 +- config/initializers/default_url_options.rb | 2 +- config/initializers/rack_attack.rb.example | 14 +- config/initializers/rack_lineprof.rb | 2 +- config/initializers/secret_token.rb | 8 +- config/initializers/session_store.rb | 4 +- config/initializers/sherlock.rb | 2 +- config/initializers/smtp_settings.rb.sample | 2 +- config/initializers/static_files.rb | 2 +- config/routes.rb | 2 +- db/schema.rb | 106 ++++----- 24 files changed, 264 insertions(+), 194 deletions(-) mode change 100644 => 100755 bin/ci/upgrade.rb create mode 100755 bin/setup mode change 100644 => 100755 bin/upgrade.rb diff --git a/Gemfile b/Gemfile index 4762e2e223..4a9f70a129 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,9 @@ source "https://rubygems.org" -gem 'rails', '4.1.14' +gem 'rails', '4.2.4' + +# Responders respond_to and respond_with +gem 'responders', '~> 2.0' # Specify a sprockets version due to security issue # See https://groups.google.com/forum/#!topic/rubyonrails-security/doAVp0YaTqY @@ -98,6 +101,7 @@ gem 'org-ruby', '~> 0.9.12' gem 'creole', '~> 0.5.0' gem 'wikicloth', '0.8.1' gem 'asciidoctor', '~> 1.5.2' +gem 'net-ssh', '~> 3.0.1' # Diffs gem 'diffy', '~> 3.0.3' @@ -213,6 +217,7 @@ group :development do gem 'rerun', '~> 0.10.0' gem 'bullet', require: false gem 'rblineprof', platform: :mri, require: false + gem 'web-console', '~> 2.0' # Better errors handler gem 'better_errors', '~> 1.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index 46247dd88e..e946051505 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,31 +1,40 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (2.3.1) + CFPropertyList (2.3.2) RedCloth (4.2.9) ace-rails-ap (2.0.1) - actionmailer (4.1.14) - actionpack (= 4.1.14) - actionview (= 4.1.14) + actionmailer (4.2.4) + actionpack (= 4.2.4) + actionview (= 4.2.4) + activejob (= 4.2.4) mail (~> 2.5, >= 2.5.4) - actionpack (4.1.14) - actionview (= 4.1.14) - activesupport (= 4.1.14) - rack (~> 1.5.2) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.4) + actionview (= 4.2.4) + activesupport (= 4.2.4) + rack (~> 1.6) rack-test (~> 0.6.2) - actionview (4.1.14) - activesupport (= 4.1.14) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (4.2.4) + activesupport (= 4.2.4) builder (~> 3.1) erubis (~> 2.7.0) - activemodel (4.1.14) - activesupport (= 4.1.14) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + activejob (4.2.4) + activesupport (= 4.2.4) + globalid (>= 0.3.0) + activemodel (4.2.4) + activesupport (= 4.2.4) builder (~> 3.1) - activerecord (4.1.14) - activemodel (= 4.1.14) - activesupport (= 4.1.14) - arel (~> 5.0.0) + activerecord (4.2.4) + activemodel (= 4.2.4) + activesupport (= 4.2.4) + arel (~> 6.0) activerecord-deprecated_finders (1.0.4) - activerecord-session_store (0.1.1) + activerecord-session_store (0.1.2) actionpack (>= 4.0.0, < 5) activerecord (>= 4.0.0, < 5) railties (>= 4.0.0, < 5) @@ -33,31 +42,31 @@ GEM activemodel (~> 4.0) activesupport (~> 4.0) rails-observers (~> 0.1.1) - activesupport (4.1.14) - i18n (~> 0.6, >= 0.6.9) + activesupport (4.2.4) + i18n (~> 0.7) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) - thread_safe (~> 0.1) + thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) acts-as-taggable-on (3.5.0) activerecord (>= 3.2, < 5) addressable (2.3.8) - after_commit_queue (1.1.0) - rails (>= 3.0) + after_commit_queue (1.3.0) + activerecord (>= 3.0) annotate (2.6.10) activerecord (>= 3.2, <= 4.3) rake (~> 10.4) - arel (5.0.1.20140414130214) + arel (6.0.3) asana (0.0.6) activeresource (>= 3.2.3) - asciidoctor (1.5.2) + asciidoctor (1.5.3) ast (2.1.0) astrolabe (1.3.1) parser (~> 2.2) attr_encrypted (1.3.4) encryptor (>= 1.3.0) attr_required (1.0.0) - autoprefixer-rails (5.2.1.2) + autoprefixer-rails (6.1.1) execjs json awesome_print (1.2.0) @@ -85,15 +94,15 @@ GEM ruby_parser (~> 3.5.0) sass (~> 3.0) terminal-table (~> 1.4) - browser (1.0.0) + browser (1.0.1) builder (3.2.2) - bullet (4.14.9) + bullet (4.14.10) activesupport (>= 3.0.0) uniform_notifier (~> 1.9.0) bundler-audit (0.4.0) bundler (~> 1.2) thor (~> 0.18) - byebug (6.0.2) + byebug (8.2.0) cal-heatmap-rails (0.0.1) capybara (2.4.4) mime-types (>= 1.16) @@ -108,10 +117,25 @@ GEM activemodel (>= 3.2.0) activesupport (>= 3.2.0) json (>= 1.7) - celluloid (0.16.0) - timers (~> 4.0.0) + celluloid (0.17.2) + celluloid-essentials + celluloid-extras + celluloid-fsm + celluloid-pool + celluloid-supervision + timers (>= 4.1.1) + celluloid-essentials (0.20.5) + timers (>= 4.1.1) + celluloid-extras (0.20.5) + timers (>= 4.1.1) + celluloid-fsm (0.20.5) + timers (>= 4.1.1) + celluloid-pool (0.20.5) + timers (>= 4.1.1) + celluloid-supervision (0.20.5) + timers (>= 4.1.1) charlock_holmes (0.7.3) - chunky_png (1.3.4) + chunky_png (1.3.5) cliver (0.3.2) coderay (1.1.0) coercible (1.0.0) @@ -122,15 +146,16 @@ GEM coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.9.1.1) + coffee-script-source (1.10.0) colorize (0.7.7) connection_pool (2.2.0) - coveralls (0.8.2) + coveralls (0.8.9) json (~> 1.8) rest-client (>= 1.6.8, < 2) simplecov (~> 0.10.0) term-ansicolor (~> 1.3) thor (~> 0.19.1) + tins (~> 1.6.0) crack (0.4.2) safe_yaml (~> 1.0.0) creole (0.5.0) @@ -153,7 +178,7 @@ GEM warden (~> 1.2.3) devise-async (0.9.0) devise (~> 3.2) - devise-two-factor (2.0.0) + devise-two-factor (2.0.1) activesupport attr_encrypted (~> 1.3.2) devise (~> 3.5.0) @@ -162,11 +187,11 @@ GEM diff-lcs (1.2.5) diffy (3.0.7) docile (1.1.5) - domain_name (0.5.24) + domain_name (0.5.25) unf (>= 0.0.5, < 1.0.0) doorkeeper (2.1.4) railties (>= 3.2) - dropzonejs-rails (0.7.1) + dropzonejs-rails (0.7.2) rails (> 3.1) email_reply_parser (0.5.8) email_spec (1.6.0) @@ -202,7 +227,7 @@ GEM flog (4.3.2) ruby_parser (~> 3.1, > 3.1.0) sexp_processor (~> 4.4) - flowdock (0.7.0) + flowdock (0.7.1) httparty (~> 0.7) multi_json fog (1.25.0) @@ -224,13 +249,10 @@ GEM fog-core (~> 1.22) fog-json inflecto (~> 0.0.2) - fog-core (1.32.1) + fog-core (1.35.0) builder excon (~> 0.45) formatador (~> 0.2) - mime-types - net-scp (~> 1.1) - net-ssh (>= 2.1.3) fog-json (1.0.2) fog-core (~> 1.0) multi_json (~> 1.10) @@ -242,10 +264,10 @@ GEM fog-core (>= 1.21.0) fog-json fog-xml (>= 0.0.1) - fog-sakuracloud (1.0.1) + fog-sakuracloud (1.4.0) fog-core fog-json - fog-softlayer (0.4.7) + fog-softlayer (1.0.2) fog-core fog-json fog-terremark (0.1.0) @@ -260,7 +282,7 @@ GEM fog-xml (0.1.2) fog-core nokogiri (~> 1.5, >= 1.5.11) - font-awesome-rails (4.4.0.0) + font-awesome-rails (4.5.0.0) railties (>= 3.2, < 5.0) foreman (0.78.0) thor (~> 0.19.1) @@ -270,11 +292,11 @@ GEM ruby-progressbar (~> 1.4) gemnasium-gitlab-service (0.2.6) rugged (~> 0.21) - gemojione (2.0.1) + gemojione (2.1.0) json get_process_mem (0.2.0) gherkin-ruby (0.3.2) - github-linguist (4.7.0) + github-linguist (4.7.2) charlock_holmes (~> 0.7.3) escape_utils (~> 1.1.0) mime-types (>= 1.19) @@ -289,8 +311,8 @@ GEM diff-lcs (~> 1.1) mime-types (~> 1.15) posix-spawn (~> 0.3) - gitlab_emoji (0.1.1) - gemojione (~> 2.0) + gitlab_emoji (0.2.0) + gemojione (~> 2.1) gitlab_git (7.2.20) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) @@ -302,13 +324,15 @@ GEM omniauth (~> 1.0) pyu-ruby-sasl (~> 0.0.3.1) rubyntlm (~> 0.3) + globalid (0.3.6) + activesupport (>= 4.1.0) gollum-grit_adapter (1.0.0) gitlab-grit (~> 2.7, >= 2.7.1) gollum-lib (4.0.3) github-markup (~> 1.3.3) gollum-grit_adapter (~> 1.0) nokogiri (~> 1.6.4) - rouge (~> 1.10.1) + rouge (~> 1.7.4) sanitize (~> 2.1.0) stringex (~> 2.5.1) gon (6.0.1) @@ -337,7 +361,7 @@ GEM haml (>= 4.0.6, < 5.0) html2haml (>= 1.0.1) railties (>= 4.0.1) - hashie (3.4.2) + hashie (3.4.3) highline (1.6.21) hike (1.2.3) hipchat (1.5.2) @@ -355,7 +379,7 @@ GEM http-cookie (1.0.2) domain_name (~> 0.5) http_parser.rb (0.5.3) - httparty (0.13.5) + httparty (0.13.7) json (~> 1.8) multi_xml (>= 0.5.2) httpclient (2.7.0.1) @@ -365,7 +389,7 @@ GEM inflecto (0.0.2) ipaddress (0.8.0) jquery-atwho-rails (1.3.2) - jquery-rails (3.1.3) + jquery-rails (3.1.4) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) jquery-scrollto-rails (1.4.3) @@ -376,19 +400,21 @@ GEM jquery-ui-rails (4.2.1) railties (>= 3.2.16) json (1.8.3) - jwt (1.5.1) + jwt (1.5.2) kaminari (0.16.3) actionpack (>= 3.0.0) activesupport (>= 3.0.0) - kgio (2.9.3) + kgio (2.10.0) launchy (2.4.3) addressable (~> 2.3) letter_opener (1.1.2) launchy (~> 2.2) - listen (2.10.1) - celluloid (~> 0.16.0) + listen (2.9.0) + celluloid (>= 0.15.2) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) + loofah (2.0.3) + nokogiri (>= 1.5.9) macaddr (1.7.1) systemu (~> 2.6.2) mail (2.6.3) @@ -405,16 +431,14 @@ GEM multipart-post (2.0.0) mysql2 (0.3.20) nested_form (0.3.2) - net-ldap (0.11) - net-scp (1.2.1) - net-ssh (>= 2.6.5) - net-ssh (2.9.2) - netrc (0.10.3) + net-ldap (0.12.1) + net-ssh (3.0.1) + netrc (0.11.0) newrelic-grape (2.0.0) grape newrelic_rpm newrelic_rpm (3.9.4.245) - nokogiri (1.6.6.2) + nokogiri (1.6.6.4) mini_portile (~> 0.6.0) nprogress-rails (0.1.6.7) oauth (0.4.7) @@ -438,12 +462,15 @@ GEM omniauth-github (1.1.2) omniauth (~> 1.0) omniauth-oauth2 (~> 1.1) - omniauth-gitlab (1.0.0) + omniauth-gitlab (1.0.1) omniauth (~> 1.0) omniauth-oauth2 (~> 1.0) - omniauth-google-oauth2 (0.2.6) - omniauth (> 1.0) - omniauth-oauth2 (~> 1.1) + omniauth-google-oauth2 (0.2.10) + addressable (~> 2.3) + jwt (~> 1.0) + multi_json (~> 1.3) + omniauth (>= 1.1.1) + omniauth-oauth2 (~> 1.3.1) omniauth-kerberos (0.3.0) omniauth-multipassword timfel-krb5-auth (~> 0.8) @@ -467,18 +494,18 @@ GEM activesupport nokogiri (>= 1.4.4) omniauth (~> 1.0) - opennebula (4.12.1) + opennebula (4.14.2) json nokogiri rbvmomi org-ruby (0.9.12) rubypants (~> 0.2) orm_adapter (0.5.0) - paranoia (2.1.3) + paranoia (2.1.4) activerecord (~> 4.0) - parser (2.2.2.6) + parser (2.2.3.0) ast (>= 1.1, < 3.0) - pg (0.18.2) + pg (0.18.4) poltergeist (1.6.0) capybara (~> 2.1) cliver (~> 0.3.1) @@ -486,7 +513,7 @@ GEM websocket-driver (>= 0.2.0) posix-spawn (0.3.11) powerpack (0.0.9) - pry (0.10.1) + pry (0.10.3) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) @@ -495,7 +522,7 @@ GEM pyu-ruby-sasl (0.0.3.3) quiet_assets (1.0.3) railties (>= 3.1, < 5.0) - rack (1.5.5) + rack (1.6.4) rack-accept (0.4.5) rack (>= 0.4) rack-attack (4.3.0) @@ -513,28 +540,37 @@ GEM rack rack-test (0.6.3) rack (>= 1.0) - rails (4.1.14) - actionmailer (= 4.1.14) - actionpack (= 4.1.14) - actionview (= 4.1.14) - activemodel (= 4.1.14) - activerecord (= 4.1.14) - activesupport (= 4.1.14) + rails (4.2.4) + actionmailer (= 4.2.4) + actionpack (= 4.2.4) + actionview (= 4.2.4) + activejob (= 4.2.4) + activemodel (= 4.2.4) + activerecord (= 4.2.4) + activesupport (= 4.2.4) bundler (>= 1.3.0, < 2.0) - railties (= 4.1.14) - sprockets-rails (~> 2.0) + railties (= 4.2.4) + sprockets-rails + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.7) + activesupport (>= 4.2.0.beta, < 5.0) + nokogiri (~> 1.6.0) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.2) + loofah (~> 2.0) rails-observers (0.1.2) activemodel (~> 4.0) - railties (4.1.14) - actionpack (= 4.1.14) - activesupport (= 4.1.14) + railties (4.2.4) + actionpack (= 4.2.4) + activesupport (= 4.2.4) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (2.0.0) raindrops (0.15.0) rake (10.4.2) raphael-rails (2.1.2) - rb-fsevent (0.9.5) + rb-fsevent (0.9.6) rb-inotify (0.9.5) ffi (>= 0.5.0) rblineprof (0.3.6) @@ -546,13 +582,13 @@ GEM rdoc (3.12.2) json (~> 1.4) redcarpet (3.3.3) - redis (3.2.1) - redis-actionpack (4.0.0) + redis (3.2.2) + redis-actionpack (4.0.1) actionpack (~> 4) redis-rack (~> 1.5.0) redis-store (~> 1.1.0) - redis-activesupport (4.1.1) - activesupport (~> 4) + redis-activesupport (4.1.5) + activesupport (>= 3, < 5) redis-store (~> 1.1.0) redis-namespace (1.5.2) redis (~> 3.0, >= 3.0.4) @@ -563,20 +599,20 @@ GEM redis-actionpack (~> 4) redis-activesupport (~> 4) redis-store (~> 1.1.0) - redis-store (1.1.6) + redis-store (1.1.7) redis (>= 2.2) - request_store (1.2.0) + request_store (1.2.1) rerun (0.10.0) listen (~> 2.7, >= 2.7.3) - responders (1.1.2) - railties (>= 3.2, < 4.2) + responders (2.1.0) + railties (>= 4.2.0, < 5) rest-client (1.8.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 3.0) netrc (~> 0.7) rinku (1.7.3) rotp (2.1.1) - rouge (1.10.1) + rouge (1.7.7) rqrcode (0.7.0) chunky_png rqrcode-rails3 (0.1.7) @@ -716,14 +752,14 @@ GEM terminal-table (1.5.2) test_after_commit (0.2.7) activerecord (>= 3.2) - thin (1.6.3) + thin (1.6.4) daemons (~> 1.0, >= 1.0.9) - eventmachine (~> 1.0) + eventmachine (~> 1.0, >= 1.0.4) rack (~> 1.0) thor (0.19.1) thread_safe (0.3.5) tilt (1.4.1) - timers (4.0.4) + timers (4.1.1) hitimes timfel-krb5-auth (0.8.3) tinder (1.10.1) @@ -756,9 +792,9 @@ GEM kgio (~> 2.6) rack raindrops (~> 0.7) - unicorn-worker-killer (0.4.3) + unicorn-worker-killer (0.4.4) get_process_mem (~> 0) - unicorn (~> 4) + unicorn (>= 4, < 6) uniform_notifier (1.9.0) uuid (2.3.8) macaddr (~> 1.0) @@ -770,10 +806,15 @@ GEM equalizer (~> 0.0, >= 0.0.9) warden (1.2.3) rack (>= 1.0) + web-console (2.2.1) + activemodel (>= 4.0) + binding_of_caller (>= 0.7.2) + railties (>= 4.0) + sprockets-rails (>= 2.0, < 4.0) webmock (1.21.0) addressable (>= 2.3.6) crack (>= 0.3.2) - websocket-driver (0.6.2) + websocket-driver (0.6.3) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.2) wikicloth (0.8.1) @@ -865,6 +906,7 @@ DEPENDENCIES mousetrap-rails (~> 1.4.6) mysql2 (~> 0.3.16) nested_form (~> 0.3.2) + net-ssh (~> 3.0.1) newrelic-grape newrelic_rpm (~> 3.9.4.245) nprogress-rails (~> 0.1.6.7) @@ -890,7 +932,7 @@ DEPENDENCIES rack-attack (~> 4.3.0) rack-cors (~> 0.4.0) rack-oauth2 (~> 1.2.1) - rails (= 4.1.14) + rails (= 4.2.4) raphael-rails (~> 2.1.2) rblineprof rdoc (~> 3.6) @@ -898,6 +940,7 @@ DEPENDENCIES redis-rails (~> 4.0.0) request_store (~> 1.2.0) rerun (~> 0.10.0) + responders (~> 2.0) rqrcode-rails3 (~> 0.1.7) rspec-rails (~> 3.3.0) rubocop (~> 0.28.0) @@ -938,6 +981,7 @@ DEPENDENCIES unicorn-worker-killer (~> 0.4.2) version_sorter (~> 2.0.0) virtus (~> 1.0.1) + web-console (~> 2.0) webmock (~> 1.21.0) wikicloth (= 0.8.1) diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 3eed5c16e4..d8fe65b06f 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -17,9 +17,8 @@ class SentNotification < ActiveRecord::Base belongs_to :noteable, polymorphic: true belongs_to :recipient, class_name: "User" - validate :project, :recipient, :reply_key, presence: true - validate :reply_key, uniqueness: true - + validates :project, :recipient, :reply_key, presence: true + validates :reply_key, uniqueness: true validates :noteable_id, presence: true, unless: :for_commit? validates :commit_id, presence: true, if: :for_commit? validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 916a99bb27..c4d8595d45 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -53,7 +53,7 @@ class EmailsOnPushWorker reverse_compare: reverse_compare, send_from_committer_email: send_from_committer_email, disable_diffs: disable_diffs - ).deliver + ).deliver_now # These are input errors and won't be corrected even if Sidekiq retries rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e logger.info("Failed to send e-mail for project '#{project.name_with_namespace}' to #{recipient}: #{e}") diff --git a/bin/ci/upgrade.rb b/bin/ci/upgrade.rb old mode 100644 new mode 100755 diff --git a/bin/rails b/bin/rails index 7feb6a30e6..5191e6927a 100755 --- a/bin/rails +++ b/bin/rails @@ -1,8 +1,4 @@ #!/usr/bin/env ruby -begin - load File.expand_path("../spring", __FILE__) -rescue LoadError -end -APP_PATH = File.expand_path('../../config/application', __FILE__) +APP_PATH = File.expand_path('../../config/application', __FILE__) require_relative '../config/boot' require 'rails/commands' diff --git a/bin/rake b/bin/rake index 0fb4e07e13..17240489f6 100755 --- a/bin/rake +++ b/bin/rake @@ -1,7 +1,4 @@ #!/usr/bin/env ruby -begin - load File.expand_path("../spring", __FILE__) -rescue LoadError -end -require 'bundler/setup' -load Gem.bin_path('rake', 'rake') +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/bin/setup b/bin/setup new file mode 100755 index 0000000000..acdb2c1389 --- /dev/null +++ b/bin/setup @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +require 'pathname' + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) + +Dir.chdir APP_ROOT do + # This script is a starting point to setup your application. + # Add necessary setup steps to this file: + + puts "== Installing dependencies ==" + system "gem install bundler --conservative" + system "bundle check || bundle install" + + # puts "\n== Copying sample files ==" + # unless File.exist?("config/database.yml") + # system "cp config/database.yml.sample config/database.yml" + # end + + puts "\n== Preparing database ==" + system "bin/rake db:setup" + + puts "\n== Removing old logs and tempfiles ==" + system "rm -f log/*" + system "rm -rf tmp/cache" + + puts "\n== Restarting application server ==" + system "touch tmp/restart.txt" +end diff --git a/bin/upgrade.rb b/bin/upgrade.rb old mode 100644 new mode 100755 diff --git a/config/environment.rb b/config/environment.rb index 3b186a9d57..df3006d349 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -2,4 +2,4 @@ require File.expand_path('../application', __FILE__) # Initialize the rails application -Gitlab::Application.initialize! +Rails.application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index 827a110c24..c22722c606 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,4 +1,4 @@ -Gitlab::Application.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb # In the development environment your application's code is reloaded on diff --git a/config/environments/production.rb b/config/environments/production.rb index 3316ece387..e8250d6645 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,4 +1,4 @@ -Gitlab::Application.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb # Code is not reloaded between requests diff --git a/config/environments/test.rb b/config/environments/test.rb index 955540837d..46982be286 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,4 +1,4 @@ -Gitlab::Application.configure do +Rails.application.configure do # Settings specified here will take precedence over those in config/application.rb # The test environment is used exclusively to run your application's diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb index 43adac8b2c..54516e3f23 100644 --- a/config/initializers/cookies_serializer.rb +++ b/config/initializers/cookies_serializer.rb @@ -1,3 +1,3 @@ # Be sure to restart your server when you modify this file. -Gitlab::Application.config.action_dispatch.cookies_serializer = :hybrid +Rails.application.config.action_dispatch.cookies_serializer = :hybrid diff --git a/config/initializers/default_url_options.rb b/config/initializers/default_url_options.rb index f9f88f95db..8fd27b1d88 100644 --- a/config/initializers/default_url_options.rb +++ b/config/initializers/default_url_options.rb @@ -8,4 +8,4 @@ unless Gitlab.config.gitlab_on_standard_port? default_url_options[:port] = Gitlab.config.gitlab.port end -Gitlab::Application.routes.default_url_options = default_url_options +Rails.application.routes.default_url_options = default_url_options diff --git a/config/initializers/rack_attack.rb.example b/config/initializers/rack_attack.rb.example index 2155ea1456..b1bbcca1d6 100644 --- a/config/initializers/rack_attack.rb.example +++ b/config/initializers/rack_attack.rb.example @@ -4,13 +4,13 @@ # 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 paths_to_be_protected = [ - "#{Gitlab::Application.config.relative_url_root}/users/password", - "#{Gitlab::Application.config.relative_url_root}/users/sign_in", - "#{Gitlab::Application.config.relative_url_root}/api/#{API::API.version}/session.json", - "#{Gitlab::Application.config.relative_url_root}/api/#{API::API.version}/session", - "#{Gitlab::Application.config.relative_url_root}/users", - "#{Gitlab::Application.config.relative_url_root}/users/confirmation", - "#{Gitlab::Application.config.relative_url_root}/unsubscribes/" + "#{Rails.application.config.relative_url_root}/users/password", + "#{Rails.application.config.relative_url_root}/users/sign_in", + "#{Rails.application.config.relative_url_root}/api/#{API::API.version}/session.json", + "#{Rails.application.config.relative_url_root}/api/#{API::API.version}/session", + "#{Rails.application.config.relative_url_root}/users", + "#{Rails.application.config.relative_url_root}/users/confirmation", + "#{Rails.application.config.relative_url_root}/unsubscribes/" ] diff --git a/config/initializers/rack_lineprof.rb b/config/initializers/rack_lineprof.rb index f0c006d811..22e77a32c6 100644 --- a/config/initializers/rack_lineprof.rb +++ b/config/initializers/rack_lineprof.rb @@ -2,7 +2,7 @@ # 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'] - Gitlab::Application.config.middleware.use(Rack::Lineprof) + Rails.application.config.middleware.use(Rack::Lineprof) module Rack class Lineprof diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb index 1b518c3bec..dae3a4a9a9 100644 --- a/config/initializers/secret_token.rb +++ b/config/initializers/secret_token.rb @@ -22,15 +22,15 @@ def find_secure_token end end -Gitlab::Application.config.secret_token = find_secure_token -Gitlab::Application.config.secret_key_base = find_secure_token +Rails.application.config.secret_token = find_secure_token +Rails.application.config.secret_key_base = find_secure_token # CI def generate_new_secure_token SecureRandom.hex(64) end -if Gitlab::Application.secrets.db_key_base.blank? +if Rails.application.secrets.db_key_base.blank? warn "Missing `db_key_base` for '#{Rails.env}' environment. The secrets will be generated and stored in `config/secrets.yml`" all_secrets = YAML.load_file('config/secrets.yml') if File.exist?('config/secrets.yml') @@ -46,5 +46,5 @@ if Gitlab::Application.secrets.db_key_base.blank? file.write(YAML.dump(all_secrets)) end - Gitlab::Application.secrets.db_key_base = env_secrets['db_key_base'] + Rails.application.secrets.db_key_base = env_secrets['db_key_base'] end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index f30178ff71..d5208b8c93 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -13,11 +13,11 @@ end unless Rails.env.test? Gitlab::Application.config.session_store( :redis_store, # Using the cookie_store would enable session replay attacks. - servers: Gitlab::Application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store + servers: Rails.application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store key: '_gitlab_session', secure: Gitlab.config.gitlab.https, httponly: true, expire_after: Settings.gitlab['session_expire_delay'] * 60, - path: (Gitlab::Application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root + path: (Rails.application.config.relative_url_root.nil?) ? '/' : Gitlab::Application.config.relative_url_root ) end diff --git a/config/initializers/sherlock.rb b/config/initializers/sherlock.rb index 42b0d78c85..8f2ababb71 100644 --- a/config/initializers/sherlock.rb +++ b/config/initializers/sherlock.rb @@ -1,5 +1,5 @@ if Gitlab::Sherlock.enabled? - Gitlab::Application.configure do |config| + Rails.application.configure do |config| config.middleware.use(Gitlab::Sherlock::Middleware) end end diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample index 25ec247a09..ec182502d4 100644 --- a/config/initializers/smtp_settings.rb.sample +++ b/config/initializers/smtp_settings.rb.sample @@ -8,7 +8,7 @@ # 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 if Rails.env.production? - Gitlab::Application.config.action_mailer.delivery_method = :smtp + Rails.application.config.action_mailer.delivery_method = :smtp ActionMailer::Base.smtp_settings = { address: "email.server.com", diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb index e6d5600edb..d9042c652b 100644 --- a/config/initializers/static_files.rb +++ b/config/initializers/static_files.rb @@ -1,4 +1,4 @@ -app = Gitlab::Application +app = Rails.application if app.config.serve_static_assets # The `ActionDispatch::Static` middleware intercepts requests for static files diff --git a/config/routes.rb b/config/routes.rb index ac81a2aac7..5c114452a3 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,7 +1,7 @@ require 'sidekiq/web' require 'api/api' -Gitlab::Application.routes.draw do +Rails.application.routes.draw do if Gitlab::Sherlock.enabled? namespace :sherlock do resources :transactions, only: [:index, :show] do diff --git a/db/schema.rb b/db/schema.rb index 5bbe0c908e..fbcb711e56 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -16,7 +16,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" - create_table "abuse_reports", force: true do |t| + create_table "abuse_reports", force: :cascade do |t| t.integer "reporter_id" t.integer "user_id" t.text "message" @@ -24,7 +24,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do t.datetime "updated_at" end - create_table "application_settings", force: true do |t| + create_table "application_settings", force: :cascade do |t| t.integer "default_projects_limit" t.boolean "signup_enabled" t.boolean "signin_enabled" @@ -51,7 +51,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do t.integer "max_artifacts_size", default: 100, null: false end - create_table "audit_events", force: true do |t| + create_table "audit_events", force: :cascade do |t| t.integer "author_id", null: false t.string "type", null: false t.integer "entity_id", null: false @@ -65,7 +65,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree - create_table "broadcast_messages", force: true do |t| + create_table "broadcast_messages", force: :cascade do |t| t.text "message", null: false t.datetime "starts_at" t.datetime "ends_at" @@ -76,14 +76,14 @@ ActiveRecord::Schema.define(version: 20151118162244) do t.string "font" end - create_table "ci_application_settings", force: true do |t| + create_table "ci_application_settings", force: :cascade do |t| t.boolean "all_broken_builds" t.boolean "add_pusher" t.datetime "created_at" t.datetime "updated_at" end - create_table "ci_builds", force: true do |t| + create_table "ci_builds", force: :cascade do |t| t.integer "project_id" t.string "status" t.datetime "finished_at" @@ -123,7 +123,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "ci_builds", ["status"], name: "index_ci_builds_on_status", using: :btree add_index "ci_builds", ["type"], name: "index_ci_builds_on_type", using: :btree - create_table "ci_commits", force: true do |t| + create_table "ci_commits", force: :cascade do |t| t.integer "project_id" t.string "ref" t.string "sha" @@ -144,7 +144,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "ci_commits", ["project_id"], name: "index_ci_commits_on_project_id", using: :btree add_index "ci_commits", ["sha"], name: "index_ci_commits_on_sha", using: :btree - create_table "ci_events", force: true do |t| + create_table "ci_events", force: :cascade do |t| t.integer "project_id" t.integer "user_id" t.integer "is_admin" @@ -157,7 +157,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "ci_events", ["is_admin"], name: "index_ci_events_on_is_admin", using: :btree add_index "ci_events", ["project_id"], name: "index_ci_events_on_project_id", using: :btree - create_table "ci_jobs", force: true do |t| + create_table "ci_jobs", force: :cascade do |t| t.integer "project_id", null: false t.text "commands" t.boolean "active", default: true, null: false @@ -174,7 +174,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "ci_jobs", ["deleted_at"], name: "index_ci_jobs_on_deleted_at", using: :btree add_index "ci_jobs", ["project_id"], name: "index_ci_jobs_on_project_id", using: :btree - create_table "ci_projects", force: true do |t| + create_table "ci_projects", force: :cascade do |t| t.string "name" t.integer "timeout", default: 3600, null: false t.datetime "created_at" @@ -200,7 +200,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "ci_projects", ["gitlab_id"], name: "index_ci_projects_on_gitlab_id", using: :btree add_index "ci_projects", ["shared_runners_enabled"], name: "index_ci_projects_on_shared_runners_enabled", using: :btree - create_table "ci_runner_projects", force: true do |t| + create_table "ci_runner_projects", force: :cascade do |t| t.integer "runner_id", null: false t.integer "project_id", null: false t.datetime "created_at" @@ -210,7 +210,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "ci_runner_projects", ["project_id"], name: "index_ci_runner_projects_on_project_id", using: :btree add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree - create_table "ci_runners", force: true do |t| + create_table "ci_runners", force: :cascade do |t| t.string "token" t.datetime "created_at" t.datetime "updated_at" @@ -225,7 +225,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do t.string "architecture" end - create_table "ci_services", force: true do |t| + create_table "ci_services", force: :cascade do |t| t.string "type" t.string "title" t.integer "project_id", null: false @@ -237,7 +237,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "ci_services", ["project_id"], name: "index_ci_services_on_project_id", using: :btree - create_table "ci_sessions", force: true do |t| + create_table "ci_sessions", force: :cascade do |t| t.string "session_id", null: false t.text "data" t.datetime "created_at" @@ -247,7 +247,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "ci_sessions", ["session_id"], name: "index_ci_sessions_on_session_id", using: :btree add_index "ci_sessions", ["updated_at"], name: "index_ci_sessions_on_updated_at", using: :btree - create_table "ci_taggings", force: true do |t| + create_table "ci_taggings", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" t.string "taggable_type" @@ -260,14 +260,14 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "ci_taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "ci_taggings_idx", unique: true, using: :btree add_index "ci_taggings", ["taggable_id", "taggable_type", "context"], name: "index_ci_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree - create_table "ci_tags", force: true do |t| + create_table "ci_tags", force: :cascade do |t| t.string "name" t.integer "taggings_count", default: 0 end add_index "ci_tags", ["name"], name: "index_ci_tags_on_name", unique: true, using: :btree - create_table "ci_trigger_requests", force: true do |t| + create_table "ci_trigger_requests", force: :cascade do |t| t.integer "trigger_id", null: false t.text "variables" t.datetime "created_at" @@ -275,7 +275,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do t.integer "commit_id" end - create_table "ci_triggers", force: true do |t| + create_table "ci_triggers", force: :cascade do |t| t.string "token" t.integer "project_id", null: false t.datetime "deleted_at" @@ -285,7 +285,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "ci_triggers", ["deleted_at"], name: "index_ci_triggers_on_deleted_at", using: :btree - create_table "ci_variables", force: true do |t| + create_table "ci_variables", force: :cascade do |t| t.integer "project_id", null: false t.string "key" t.text "value" @@ -296,14 +296,14 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "ci_variables", ["project_id"], name: "index_ci_variables_on_project_id", using: :btree - create_table "ci_web_hooks", force: true do |t| + create_table "ci_web_hooks", force: :cascade do |t| t.string "url", null: false t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" end - create_table "deploy_keys_projects", force: true do |t| + create_table "deploy_keys_projects", force: :cascade do |t| t.integer "deploy_key_id", null: false t.integer "project_id", null: false t.datetime "created_at" @@ -312,7 +312,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree - create_table "emails", force: true do |t| + create_table "emails", force: :cascade do |t| t.integer "user_id", null: false t.string "email", null: false t.datetime "created_at" @@ -322,7 +322,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "emails", ["email"], name: "index_emails_on_email", unique: true, using: :btree add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree - create_table "events", force: true do |t| + create_table "events", force: :cascade do |t| t.string "target_type" t.integer "target_id" t.string "title" @@ -341,7 +341,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "events", ["target_id"], name: "index_events_on_target_id", using: :btree add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree - create_table "forked_project_links", force: true do |t| + create_table "forked_project_links", force: :cascade do |t| t.integer "forked_to_project_id", null: false t.integer "forked_from_project_id", null: false t.datetime "created_at" @@ -350,7 +350,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree - create_table "identities", force: true do |t| + create_table "identities", force: :cascade do |t| t.string "extern_uid" t.string "provider" t.integer "user_id" @@ -361,7 +361,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "identities", ["created_at", "id"], name: "index_identities_on_created_at_and_id", using: :btree add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree - create_table "issues", force: true do |t| + create_table "issues", force: :cascade do |t| t.string "title" t.integer "assignee_id" t.integer "author_id" @@ -387,7 +387,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "issues", ["state"], name: "index_issues_on_state", using: :btree add_index "issues", ["title"], name: "index_issues_on_title", using: :btree - create_table "keys", force: true do |t| + create_table "keys", force: :cascade do |t| t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" @@ -401,7 +401,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree - create_table "label_links", force: true do |t| + create_table "label_links", force: :cascade do |t| t.integer "label_id" t.integer "target_id" t.string "target_type" @@ -412,7 +412,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "label_links", ["label_id"], name: "index_label_links_on_label_id", using: :btree add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree - create_table "labels", force: true do |t| + create_table "labels", force: :cascade do |t| t.string "title" t.string "color" t.integer "project_id" @@ -423,7 +423,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree - create_table "lfs_objects", force: true do |t| + create_table "lfs_objects", force: :cascade do |t| t.string "oid", null: false t.integer "size", null: false t.datetime "created_at" @@ -433,7 +433,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree - create_table "lfs_objects_projects", force: true do |t| + create_table "lfs_objects_projects", force: :cascade do |t| t.integer "lfs_object_id", null: false t.integer "project_id", null: false t.datetime "created_at" @@ -442,7 +442,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree - create_table "members", force: true do |t| + create_table "members", force: :cascade do |t| t.integer "access_level", null: false t.integer "source_id", null: false t.string "source_type", null: false @@ -464,7 +464,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "members", ["type"], name: "index_members_on_type", using: :btree add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree - create_table "merge_request_diffs", force: true do |t| + create_table "merge_request_diffs", force: :cascade do |t| t.string "state" t.text "st_commits" t.text "st_diffs" @@ -475,7 +475,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree - create_table "merge_requests", force: true do |t| + create_table "merge_requests", force: :cascade do |t| t.string "target_branch", null: false t.string "source_branch", null: false t.integer "source_project_id", null: false @@ -507,7 +507,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "merge_requests", ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree - create_table "milestones", force: true do |t| + create_table "milestones", force: :cascade do |t| t.string "title", null: false t.integer "project_id", null: false t.text "description" @@ -523,7 +523,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree - create_table "namespaces", force: true do |t| + create_table "namespaces", force: :cascade do |t| t.string "name", null: false t.string "path", null: false t.integer "owner_id" @@ -542,7 +542,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "namespaces", ["public"], name: "index_namespaces_on_public", using: :btree add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree - create_table "notes", force: true do |t| + create_table "notes", force: :cascade do |t| t.text "note" t.string "noteable_type" t.integer "author_id" @@ -571,7 +571,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree - create_table "oauth_access_grants", force: true do |t| + create_table "oauth_access_grants", force: :cascade do |t| t.integer "resource_owner_id", null: false t.integer "application_id", null: false t.string "token", null: false @@ -584,7 +584,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree - create_table "oauth_access_tokens", force: true do |t| + create_table "oauth_access_tokens", force: :cascade do |t| t.integer "resource_owner_id" t.integer "application_id" t.string "token", null: false @@ -599,7 +599,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "oauth_access_tokens", ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id", using: :btree add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree - create_table "oauth_applications", force: true do |t| + create_table "oauth_applications", force: :cascade do |t| t.string "name", null: false t.string "uid", null: false t.string "secret", null: false @@ -614,12 +614,12 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree - create_table "project_import_data", force: true do |t| + create_table "project_import_data", force: :cascade do |t| t.integer "project_id" t.text "data" end - create_table "projects", force: true do |t| + create_table "projects", force: :cascade do |t| t.string "name" t.string "path" t.text "description" @@ -656,7 +656,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree - create_table "protected_branches", force: true do |t| + create_table "protected_branches", force: :cascade do |t| t.integer "project_id", null: false t.string "name", null: false t.datetime "created_at" @@ -666,7 +666,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree - create_table "releases", force: true do |t| + create_table "releases", force: :cascade do |t| t.string "tag" t.text "description" t.integer "project_id" @@ -677,7 +677,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "releases", ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree add_index "releases", ["project_id"], name: "index_releases_on_project_id", using: :btree - create_table "sent_notifications", force: true do |t| + create_table "sent_notifications", force: :cascade do |t| t.integer "project_id" t.integer "noteable_id" t.string "noteable_type" @@ -689,7 +689,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree - create_table "services", force: true do |t| + create_table "services", force: :cascade do |t| t.string "type" t.string "title" t.integer "project_id" @@ -709,7 +709,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree add_index "services", ["template"], name: "index_services_on_template", using: :btree - create_table "snippets", force: true do |t| + create_table "snippets", force: :cascade do |t| t.string "title" t.text "content" t.integer "author_id", null: false @@ -729,7 +729,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree - create_table "subscriptions", force: true do |t| + create_table "subscriptions", force: :cascade do |t| t.integer "user_id" t.integer "subscribable_id" t.string "subscribable_type" @@ -740,7 +740,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "subscriptions", ["subscribable_id", "subscribable_type", "user_id"], name: "subscriptions_user_id_and_ref_fields", unique: true, using: :btree - create_table "taggings", force: true do |t| + create_table "taggings", force: :cascade do |t| t.integer "tag_id" t.integer "taggable_id" t.string "taggable_type" @@ -753,14 +753,14 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "taggings", ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true, using: :btree add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree - create_table "tags", force: true do |t| + create_table "tags", force: :cascade do |t| t.string "name" t.integer "taggings_count", default: 0 end add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree - create_table "users", force: true do |t| + create_table "users", force: :cascade do |t| t.string "email", default: "", null: false t.string "encrypted_password", default: "", null: false t.string "reset_password_token" @@ -826,7 +826,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["username"], name: "index_users_on_username", using: :btree - create_table "users_star_projects", force: true do |t| + create_table "users_star_projects", force: :cascade do |t| t.integer "project_id", null: false t.integer "user_id", null: false t.datetime "created_at" @@ -837,7 +837,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do add_index "users_star_projects", ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true, using: :btree add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree - create_table "web_hooks", force: true do |t| + create_table "web_hooks", force: :cascade do |t| t.string "url" t.integer "project_id" t.datetime "created_at" From e55473ad6880a68a86f355b7825dbdaf67e1f375 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 25 Nov 2015 11:30:33 -0800 Subject: [PATCH 299/410] Expire application settings from cache at startup If a DB migration occurs, there's a chance that the application settings are loaded from the cache and provide stale values, causing Error 500s. This ensures that at startup the settings are always refreshed. Closes #3643 --- CHANGELOG | 1 + app/models/application_setting.rb | 12 ++++++++++-- app/models/ci/application_setting.rb | 12 ++++++++++-- config/initializers/1_settings.rb | 4 ++++ 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8916b9f040..a6bef82693 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) + - Ensure cached application settings are refreshed at startup (Stan Hu) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - Fix 500 error when update group member permission diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index b2d5fe1558..3df8135acf 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -73,15 +73,23 @@ class ApplicationSetting < ActiveRecord::Base end after_commit do - Rails.cache.write('application_setting.last', self) + Rails.cache.write(cache_key, self) end def self.current - Rails.cache.fetch('application_setting.last') do + Rails.cache.fetch(cache_key) do ApplicationSetting.last end end + def self.expire + Rails.cache.delete(cache_key) + end + + def self.cache_key + 'application_setting.last' + end + def self.create_from_defaults create( default_projects_limit: Settings.gitlab['default_projects_limit'], diff --git a/app/models/ci/application_setting.rb b/app/models/ci/application_setting.rb index 1307fa0b47..4e512d290e 100644 --- a/app/models/ci/application_setting.rb +++ b/app/models/ci/application_setting.rb @@ -14,11 +14,15 @@ module Ci extend Ci::Model after_commit do - Rails.cache.write('ci_application_setting.last', self) + Rails.cache.write(cache_key, self) + end + + def self.expire + Rails.cache.delete(cache_key) end def self.current - Rails.cache.fetch('ci_application_setting.last') do + Rails.cache.fetch(cache_key) do Ci::ApplicationSetting.last end end @@ -29,5 +33,9 @@ module Ci add_pusher: Settings.gitlab_ci['add_pusher'], ) end + + def self.cache_key + 'ci_application_setting.last' + end end end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index b162b8a83f..80b480eac3 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -293,3 +293,7 @@ if Rails.env.test? Settings.gitlab['default_can_create_group'] = true Settings.gitlab['default_can_create_team'] = false end + +# Force a refresh of application settings at startup +ApplicationSetting.expire +Ci::ApplicationSetting.expire From 8dcef120cd94717b4f82db864191698826ca02a5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 25 Nov 2015 17:24:07 -0200 Subject: [PATCH 300/410] Fix raw private snippets access workflow --- CHANGELOG | 1 + app/controllers/snippets_controller.rb | 2 +- spec/controllers/snippets_controller_spec.rb | 115 +++++++++++++++++++ 3 files changed, 117 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 8916b9f040..d61d85e405 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - Fix 500 error when update group member permission + - Fix: Raw private snippets access workflow v 8.2.1 - Forcefully update builds that didn't want to update with state machine diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 08f2483af3..c72df73af4 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -2,7 +2,7 @@ class SnippetsController < ApplicationController before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] # Allow read snippet - before_action :authorize_read_snippet!, only: [:show] + before_action :authorize_read_snippet!, only: [:show, :raw] # Allow modify snippet before_action :authorize_update_snippet!, only: [:edit, :update] diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb index e9b823c523..b3dcb52c50 100644 --- a/spec/controllers/snippets_controller_spec.rb +++ b/spec/controllers/snippets_controller_spec.rb @@ -115,4 +115,119 @@ describe SnippetsController do end end end + + describe 'GET #raw' do + let(:user) { create(:user) } + + context 'when the personal snippet is private' do + let(:personal_snippet) { create(:personal_snippet, :private, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + context 'when signed in user is not the author' do + let(:other_author) { create(:author) } + let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) } + + it 'responds with status 404' do + get :raw, id: other_personal_snippet.to_param + + expect(response.status).to eq(404) + end + end + + context 'when signed in user is the author' do + it 'renders the raw snippet' do + get :raw, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + end + + context 'when not signed in' do + it 'redirects to the sign in page' do + get :raw, id: personal_snippet.to_param + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context 'when the personal snippet is internal' do + let(:personal_snippet) { create(:personal_snippet, :internal, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + it 'renders the raw snippet' do + get :raw, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + + context 'when not signed in' do + it 'redirects to the sign in page' do + get :raw, id: personal_snippet.to_param + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context 'when the personal snippet is public' do + let(:personal_snippet) { create(:personal_snippet, :public, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + it 'renders the raw snippet' do + get :raw, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + + context 'when not signed in' do + it 'renders the raw snippet' do + get :raw, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + end + + context 'when the personal snippet does not exist' do + context 'when signed in' do + before do + sign_in(user) + end + + it 'responds with status 404' do + get :raw, id: 'doesntexist' + + expect(response.status).to eq(404) + end + end + + context 'when not signed in' do + it 'responds with status 404' do + get :raw, id: 'doesntexist' + + expect(response.status).to eq(404) + end + end + end + end end From 73eca2a95ef401a8e3061a14949ee1dbf34f99b9 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 25 Nov 2015 16:26:15 -0500 Subject: [PATCH 301/410] Decouple Markdown previews from DropzoneInput --- .../javascripts/dropzone_input.js.coffee | 80 +---------------- .../javascripts/markdown_preview.js.coffee | 87 +++++++++++++++++++ app/views/projects/_md_preview.html.haml | 2 +- 3 files changed, 92 insertions(+), 77 deletions(-) create mode 100644 app/assets/javascripts/markdown_preview.js.coffee diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee index 6f789e668a..30a35a0433 100644 --- a/app/assets/javascripts/dropzone_input.js.coffee +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -1,3 +1,5 @@ +#= require markdown_preview + class @DropzoneInput constructor: (form) -> Dropzone.autoDiscover = false @@ -11,17 +13,14 @@ class @DropzoneInput uploadProgress = $("
") btnAlert = "" project_uploads_path = window.project_uploads_path or null - markdown_preview_path = window.markdown_preview_path or null max_file_size = gon.max_file_size or 10 form_textarea = $(form).find("textarea.markdown-area") form_textarea.wrap "
" form_textarea.on 'paste', (event) => handlePaste(event) - form_textarea.on "input", -> - hideReferencedUsers() - form_textarea.on "blur", -> - renderMarkdown() + + $(form).setupMarkdownPreview() form_dropzone = $(form).find('.div-dropzone') form_dropzone.parent().addClass "div-dropzone-wrapper" @@ -34,42 +33,6 @@ class @DropzoneInput "opacity": 0 "display": "none" - # Preview button - $(document).off "click", ".js-md-preview-button" - $(document).on "click", ".js-md-preview-button", (e) -> - ### - Shows the Markdown preview. - - Lets the server render GFM into Html and displays it. - ### - e.preventDefault() - form = $(this).closest("form") - # toggle tabs - form.find(".js-md-write-button").parent().removeClass "active" - form.find(".js-md-preview-button").parent().addClass "active" - - # toggle content - form.find(".md-write-holder").hide() - form.find(".md-preview-holder").show() - - renderMarkdown() - - # Write button - $(document).off "click", ".js-md-write-button" - $(document).on "click", ".js-md-write-button", (e) -> - ### - Shows the Markdown textarea. - ### - e.preventDefault() - form = $(this).closest("form") - # toggle tabs - form.find(".js-md-write-button").parent().addClass "active" - form.find(".js-md-preview-button").parent().removeClass "active" - - # toggle content - form.find(".md-write-holder").show() - form.find(".md-preview-holder").hide() - dropzone = form_dropzone.dropzone( url: project_uploads_path dictDefaultMessage: "" @@ -136,41 +99,6 @@ class @DropzoneInput child = $(dropzone[0]).children("textarea") - hideReferencedUsers = -> - referencedUsers = form.find(".referenced-users") - referencedUsers.hide() - - renderReferencedUsers = (users) -> - referencedUsers = form.find(".referenced-users") - - if referencedUsers.length - if users.length >= 10 - referencedUsers.show() - referencedUsers.find(".js-referenced-users-count").text users.length - else - referencedUsers.hide() - - renderMarkdown = -> - preview = form.find(".js-md-preview") - mdText = form.find(".markdown-area").val() - if mdText.trim().length is 0 - preview.text "Nothing to preview." - hideReferencedUsers() - else - preview.text "Loading..." - $.ajax( - type: "POST", - url: markdown_preview_path, - data: { - text: mdText - }, - dataType: "json" - ).success (data) -> - preview.html data.body - preview.syntaxHighlight() - - renderReferencedUsers data.references.users - formatLink = (link) -> text = "[#{link.alt}](#{link.url})" text = "!#{text}" if link.is_image diff --git a/app/assets/javascripts/markdown_preview.js.coffee b/app/assets/javascripts/markdown_preview.js.coffee new file mode 100644 index 0000000000..98fc8f1734 --- /dev/null +++ b/app/assets/javascripts/markdown_preview.js.coffee @@ -0,0 +1,87 @@ +# MarkdownPreview +# +# Handles toggling the "Write" and "Preview" tab clicks, rendering the preview, +# and showing a warning when more than `x` users are referenced. +# +class @MarkdownPreview + # Minimum number of users referenced before triggering a warning + referenceThreshold: 10 + + showPreview: (form) -> + preview = form.find('.js-md-preview') + mdText = form.find('textarea.markdown-area').val() + + if mdText.trim().length == 0 + preview.text('Nothing to preview.') + @hideReferencedUsers(form) + else + preview.text('Loading...') + @renderMarkdown mdText, (response) => + preview.html(response.body) + preview.syntaxHighlight() + @renderReferencedUsers(response.references.users, form) + + renderMarkdown: (text, success) -> + return unless window.markdown_preview_path + + $.ajax + type: 'POST' + url: window.markdown_preview_path + data: { text: text } + dataType: 'json' + success: success + + hideReferencedUsers: (form) -> + referencedUsers = form.find('.referenced-users') + referencedUsers.hide() + + renderReferencedUsers: (users, form) -> + referencedUsers = form.find('.referenced-users') + + if referencedUsers.length + if users.length >= @referenceThreshold + referencedUsers.show() + referencedUsers.find('.js-referenced-users-count').text(users.length) + else + referencedUsers.hide() + +markdownPreview = new MarkdownPreview() + +previewButtonSelector = '.js-md-preview-button' +writeButtonSelector = '.js-md-write-button' + +$.fn.setupMarkdownPreview = -> + $form = $(this) + + form_textarea = $form.find('textarea.markdown-area') + + form_textarea.on 'input', -> markdownPreview.hideReferencedUsers($form) + form_textarea.on 'blur', -> markdownPreview.showPreview($form) + +$(document).on 'click', previewButtonSelector, (e) -> + e.preventDefault() + + $form = $(this).closest('form') + + # toggle tabs + $form.find(writeButtonSelector).parent().removeClass('active') + $form.find(previewButtonSelector).parent().addClass('active') + + # toggle content + $form.find('.md-write-holder').hide() + $form.find('.md-preview-holder').show() + + markdownPreview.showPreview($form) + +$(document).on 'click', writeButtonSelector, (e) -> + e.preventDefault() + + $form = $(this).closest('form') + + # toggle tabs + $form.find(writeButtonSelector).parent().addClass('active') + $form.find(previewButtonSelector).parent().removeClass('active') + + # toggle content + $form.find('.md-write-holder').show() + $form.find('.md-preview-holder').hide() diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml index 7b21095ea3..8218cf1120 100644 --- a/app/views/projects/_md_preview.html.haml +++ b/app/views/projects/_md_preview.html.haml @@ -5,7 +5,7 @@ %a.js-md-write-button(href="#md-write-holder" tabindex="-1") Write %li - %a.js-md-preview-button(href="md-preview-holder" tabindex="-1") + %a.js-md-preview-button(href="#md-preview-holder" tabindex="-1") Preview - if defined?(referenced_users) && referenced_users From 36bde0fcb197aa7ca93bb615cda070f55f5e268f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 25 Nov 2015 14:00:35 -0800 Subject: [PATCH 302/410] Fix Error 500 when viewing user's personal projects from admin page This is a regression introduced in 4d7f00f. Closes #3680 --- CHANGELOG | 1 + app/views/admin/users/_projects.html.haml | 13 +++++++++++++ app/views/admin/users/projects.html.haml | 2 +- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 app/views/admin/users/_projects.html.haml diff --git a/CHANGELOG b/CHANGELOG index 8916b9f040..6c78cc70b7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) + - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - Fix 500 error when update group member permission diff --git a/app/views/admin/users/_projects.html.haml b/app/views/admin/users/_projects.html.haml new file mode 100644 index 0000000000..a126a858ea --- /dev/null +++ b/app/views/admin/users/_projects.html.haml @@ -0,0 +1,13 @@ +- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present? + .panel.panel-default.contributed-projects + .panel-heading Projects contributed to + = render 'shared/projects/list', + projects: contributed_projects.sort_by(&:star_count).reverse, + projects_limit: 5, stars: true, avatar: false + +- if local_assigns.has_key?(:projects) && projects.present? + .panel.panel-default + .panel-heading Personal projects + = render 'shared/projects/list', + projects: projects.sort_by(&:star_count).reverse, + projects_limit: 10, stars: true, avatar: false diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml index 0d7a1a25a8..b655b2a15f 100644 --- a/app/views/admin/users/projects.html.haml +++ b/app/views/admin/users/projects.html.haml @@ -14,7 +14,7 @@ .row .col-md-6 - if @personal_projects.present? - = render 'users/projects', projects: @personal_projects + = render 'admin/users/projects', projects: @personal_projects - else .nothing-here-block This user has no personal projects. From 59cc61e246dd059d041793cf4fd29ee540c7d7d2 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 25 Nov 2015 17:01:02 -0500 Subject: [PATCH 303/410] Bump doorkeeper to ~> 2.2.0 Closes #2746 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 4762e2e223..491a6acd43 100644 --- a/Gemfile +++ b/Gemfile @@ -16,7 +16,7 @@ gem "pg", '~> 0.18.2', group: :postgres # Authentication libraries gem 'devise', '~> 3.5.2' gem 'devise-async', '~> 0.9.0' -gem 'doorkeeper', '~> 2.1.3' +gem 'doorkeeper', '~> 2.2.0' gem 'omniauth', '~> 1.2.2' gem 'omniauth-bitbucket', '~> 0.0.2' gem 'omniauth-facebook', '~> 3.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 46247dd88e..3cb986b39d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -164,7 +164,7 @@ GEM docile (1.1.5) domain_name (0.5.24) unf (>= 0.0.5, < 1.0.0) - doorkeeper (2.1.4) + doorkeeper (2.2.2) railties (>= 3.2) dropzonejs-rails (0.7.1) rails (> 3.1) @@ -824,7 +824,7 @@ DEPENDENCIES devise-async (~> 0.9.0) devise-two-factor (~> 2.0.0) diffy (~> 3.0.3) - doorkeeper (~> 2.1.3) + doorkeeper (~> 2.2.0) dropzonejs-rails (~> 0.7.1) email_reply_parser (~> 0.5.8) email_spec (~> 1.6.0) From 91c0aa5e982ee5e3299e6b8c899bfc396fe51279 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 25 Nov 2015 17:03:30 -0500 Subject: [PATCH 304/410] Bump asana to ~> 0.4.0 Closes #2830 --- Gemfile | 2 +- Gemfile.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Gemfile b/Gemfile index 4762e2e223..8325c4d8e6 100644 --- a/Gemfile +++ b/Gemfile @@ -153,7 +153,7 @@ gem "gemnasium-gitlab-service", "~> 0.2" gem "slack-notifier", "~> 1.2.0" # Asana integration -gem 'asana', '~> 0.0.6' +gem 'asana', '~> 0.4.0' # FogBugz integration gem 'ruby-fogbugz', '~> 0.2.1' diff --git a/Gemfile.lock b/Gemfile.lock index 46247dd88e..24aadef5c7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -29,10 +29,6 @@ GEM actionpack (>= 4.0.0, < 5) activerecord (>= 4.0.0, < 5) railties (>= 4.0.0, < 5) - activeresource (4.0.0) - activemodel (~> 4.0) - activesupport (~> 4.0) - rails-observers (~> 0.1.1) activesupport (4.1.14) i18n (~> 0.6, >= 0.6.9) json (~> 1.7, >= 1.7.7) @@ -48,8 +44,11 @@ GEM activerecord (>= 3.2, <= 4.3) rake (~> 10.4) arel (5.0.1.20140414130214) - asana (0.0.6) - activeresource (>= 3.2.3) + asana (0.4.0) + faraday (~> 0.9) + faraday_middleware (~> 0.9) + faraday_middleware-multi_json (~> 0.0) + oauth2 (~> 1.0) asciidoctor (1.5.2) ast (2.1.0) astrolabe (1.3.1) @@ -191,6 +190,9 @@ GEM multipart-post (>= 1.2, < 3) faraday_middleware (0.10.0) faraday (>= 0.7.4, < 0.10) + faraday_middleware-multi_json (0.0.6) + faraday_middleware + multi_json fastercsv (1.5.5) ffaker (2.0.0) ffi (1.9.10) @@ -523,8 +525,6 @@ GEM bundler (>= 1.3.0, < 2.0) railties (= 4.1.14) sprockets-rails (~> 2.0) - rails-observers (0.1.2) - activemodel (~> 4.0) railties (4.1.14) actionpack (= 4.1.14) activesupport (= 4.1.14) @@ -795,7 +795,7 @@ DEPENDENCIES addressable (~> 2.3.8) after_commit_queue annotate (~> 2.6.0) - asana (~> 0.0.6) + asana (~> 0.4.0) asciidoctor (~> 1.5.2) attr_encrypted (~> 1.3.4) awesome_print (~> 1.2.0) From 5f0d1f30b0f0e31d31a2eb0db9f92776bf551f26 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 25 Nov 2015 17:06:04 -0500 Subject: [PATCH 305/410] Remove enumerize gem --- Gemfile | 3 --- Gemfile.lock | 3 --- app/models/project.rb | 3 +-- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/Gemfile b/Gemfile index 4762e2e223..1beeabd44d 100644 --- a/Gemfile +++ b/Gemfile @@ -62,9 +62,6 @@ gem 'rack-cors', '~> 0.4.0', require: 'rack/cors' # based on human-friendly examples gem "stamp", '~> 0.6.0' -# Enumeration fields -gem 'enumerize', '~> 0.7.0' - # Pagination gem "kaminari", "~> 0.16.3" diff --git a/Gemfile.lock b/Gemfile.lock index 46247dd88e..ef083754f0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -173,8 +173,6 @@ GEM launchy (~> 2.1) mail (~> 2.2) encryptor (1.3.0) - enumerize (0.7.0) - activesupport (>= 3.2) equalizer (0.0.11) erubis (2.7.0) escape_utils (1.1.0) @@ -828,7 +826,6 @@ DEPENDENCIES dropzonejs-rails (~> 0.7.1) email_reply_parser (~> 0.5.8) email_spec (~> 1.6.0) - enumerize (~> 0.7.0) factory_girl_rails (~> 4.3.0) ffaker (~> 2.0.0) flay diff --git a/app/models/project.rb b/app/models/project.rb index f0a4b6aae7..6010770a5f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -42,9 +42,8 @@ class Project < ActiveRecord::Base include Sortable include AfterCommitQueue include CaseSensitivity - + extend Gitlab::ConfigHelper - extend Enumerize UNKNOWN_IMPORT_URL = 'http://unknown.git' From 7adfb30c349396f19b71a703419a475bd1d4cd5b Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 25 Nov 2015 17:06:36 -0800 Subject: [PATCH 306/410] Remove duplicate entry shipped in 8.1.4 --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 8916b9f040..1838198417 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,7 +14,6 @@ v 8.2.0 - Expose build artifacts path as config option - Fix grouping of contributors by email in graph. - Improved performance of finding issues with/without labels - - Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu) - Fix Drone CI service template not saving properly (Stan Hu) - Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu) - Added a GitLab specific profiling tool called "Sherlock" (see GitLab CE merge request #1749) From 2497d3d550dc0d4e095c8c3fe75d4452fb163252 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 25 Nov 2015 23:11:35 -0800 Subject: [PATCH 307/410] Fix 404 in redirection after removing a project Closes https://github.com/gitlabhq/gitlabhq/issues/9844 Closes #3559 --- CHANGELOG | 1 + app/controllers/projects_controller.rb | 2 +- spec/controllers/projects_controller_spec.rb | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 8916b9f040..3dd96e34dc 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) + - Fix 404 in redirection after removing a project (Stan Hu) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - Fix 500 error when update group member permission diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 23453195e8..10c75370d7 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -123,7 +123,7 @@ class ProjectsController < ApplicationController ::Projects::DestroyService.new(@project, current_user, {}).execute flash[:alert] = "Project '#{@project.name}' was deleted." - redirect_back_or_default(default: dashboard_projects_path, options: {}) + redirect_to dashboard_projects_path rescue Projects::DestroyService::DestroyError => ex redirect_to edit_project_path(@project), alert: ex.message end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 4bb47c6b02..665526fde9 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -88,6 +88,22 @@ describe ProjectsController do end end + describe "#destroy" do + let(:admin) { create(:admin) } + + it "redirects to the dashboard" do + controller.instance_variable_set(:@project, project) + sign_in(admin) + + orig_id = project.id + delete :destroy, namespace_id: project.namespace.path, id: project.path + + expect { Project.find(orig_id) }.to raise_error(ActiveRecord::RecordNotFound) + expect(response.status).to eq(302) + expect(response).to redirect_to(dashboard_projects_path) + end + end + describe "POST #toggle_star" do it "toggles star if user is signed in" do sign_in(user) From 38489d8d7d0a352789c23d3bf9cadb169f765d5b Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 26 Nov 2015 11:57:04 +0200 Subject: [PATCH 308/410] update test_after_commit gem to 0.4.2 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 4a9f70a129..dc801d60b2 100644 --- a/Gemfile +++ b/Gemfile @@ -274,7 +274,7 @@ group :test do gem 'shoulda-matchers', '~> 2.8.0', require: false gem 'email_spec', '~> 1.6.0' gem 'webmock', '~> 1.21.0' - gem 'test_after_commit', '~> 0.2.2' + gem 'test_after_commit', '~> 0.4.2' gem 'sham_rack' end diff --git a/Gemfile.lock b/Gemfile.lock index e946051505..bb4f94aeef 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -750,7 +750,7 @@ GEM term-ansicolor (1.3.2) tins (~> 1.0) terminal-table (1.5.2) - test_after_commit (0.2.7) + test_after_commit (0.4.2) activerecord (>= 3.2) thin (1.6.4) daemons (~> 1.0, >= 1.0.9) @@ -970,7 +970,7 @@ DEPENDENCIES task_list (~> 1.0.2) teaspoon (~> 1.0.0) teaspoon-jasmine (~> 2.2.0) - test_after_commit (~> 0.2.2) + test_after_commit (~> 0.4.2) thin (~> 1.6.1) tinder (~> 1.10.0) turbolinks (~> 2.5.0) From 7f214cee74796ceaf7b01bd6e133d4d54c5123db Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 26 Nov 2015 15:48:01 +0200 Subject: [PATCH 309/410] Migrate mailers to ActiveJob --- Gemfile | 1 + Gemfile.lock | 1 + Procfile | 2 +- app/controllers/abuse_reports_controller.rb | 2 +- app/mailers/base_mailer.rb | 4 - .../project_services/ci/mail_service.rb | 2 +- app/services/notification_service.rb | 74 +++++++++++++------ app/workers/email_receiver_worker.rb | 2 +- config/application.rb | 4 + config/environments/production.rb | 2 +- config/environments/test.rb | 2 +- config/initializers/static_files.rb | 2 +- lib/gitlab/markdown/label_reference_filter.rb | 3 +- lib/gitlab/seeder.rb | 2 +- 14 files changed, 67 insertions(+), 36 deletions(-) diff --git a/Gemfile b/Gemfile index 808c5df7ca..91c909a2e7 100644 --- a/Gemfile +++ b/Gemfile @@ -1,6 +1,7 @@ source "https://rubygems.org" gem 'rails', '4.2.4' +gem 'rails-deprecated_sanitizer', '~> 1.0.3' # Responders respond_to and respond_with gem 'responders', '~> 2.0' diff --git a/Gemfile.lock b/Gemfile.lock index 1671edbc6f..0800137991 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -930,6 +930,7 @@ DEPENDENCIES rack-cors (~> 0.4.0) rack-oauth2 (~> 1.2.1) rails (= 4.2.4) + rails-deprecated_sanitizer (~> 1.0.3) raphael-rails (~> 2.1.2) rblineprof rdoc (~> 3.6) diff --git a/Procfile b/Procfile index 08880b9c42..fd5f7ecb94 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,3 @@ web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} -worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q default +worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q mailers -q default # mail_room: bundle exec mail_room -q -c config/mail_room.yml diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb index 2f4054eaa1..d8e9059433 100644 --- a/app/controllers/abuse_reports_controller.rb +++ b/app/controllers/abuse_reports_controller.rb @@ -10,7 +10,7 @@ class AbuseReportsController < ApplicationController if @abuse_report.save if current_application_settings.admin_notification_email.present? - AbuseReportMailer.delay.notify(@abuse_report.id) + AbuseReportMailer.deliver_later.notify(@abuse_report.id) end message = "Thank you for your report. A GitLab administrator will look into it shortly." diff --git a/app/mailers/base_mailer.rb b/app/mailers/base_mailer.rb index aedb088918..8b83bbd93b 100644 --- a/app/mailers/base_mailer.rb +++ b/app/mailers/base_mailer.rb @@ -8,10 +8,6 @@ class BaseMailer < ActionMailer::Base default from: Proc.new { default_sender_address.format } default reply_to: Proc.new { default_reply_to_address.format } - def self.delay - delay_for(2.seconds) - end - def can? Ability.abilities.allowed?(current_user, action, subject) end diff --git a/app/models/project_services/ci/mail_service.rb b/app/models/project_services/ci/mail_service.rb index d31dd6899c..bdc85667e9 100644 --- a/app/models/project_services/ci/mail_service.rb +++ b/app/models/project_services/ci/mail_service.rb @@ -78,7 +78,7 @@ module Ci end def mailer - Ci::Notify.delay + Ci::Notify.deliver_later end end end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index d6550fbb55..03fe8c8fe1 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -13,14 +13,14 @@ class NotificationService # even if user disabled notifications def new_key(key) if key.user - mailer.new_ssh_key_email(key.id) + mailer.new_ssh_key_email(key.id).deliver_later end end # Always notify user about email added to profile def new_email(email) if email.user - mailer.new_email_email(email.id) + mailer.new_email_email(email.id).deliver_later end end @@ -79,17 +79,27 @@ class NotificationService end def merge_mr(merge_request, current_user) - close_resource_email(merge_request, merge_request.target_project, current_user, 'merged_merge_request_email') + close_resource_email( + merge_request, + merge_request.target_project, + current_user, + 'merged_merge_request_email' + ) end def reopen_mr(merge_request, current_user) - reopen_resource_email(merge_request, merge_request.target_project, current_user, 'merge_request_status_email', 'reopened') + reopen_resource_email( + merge_request, + merge_request.target_project, + current_user, 'merge_request_status_email', + 'reopened' + ) end # Notify new user with email after creation def new_user(user, token = nil) # Don't email omniauth created users - mailer.new_user_email(user.id, token) unless user.identities.any? + mailer.new_user_email(user.id, token).deliver_later unless user.identities.any? end # Notify users on new note in system @@ -140,48 +150,58 @@ class NotificationService notify_method = "note_#{note.noteable_type.underscore}_email".to_sym recipients.each do |recipient| - mailer.send(notify_method, recipient.id, note.id) + mailer.send(notify_method, recipient.id, note.id).deliver_later end end def invite_project_member(project_member, token) - mailer.project_member_invited_email(project_member.id, token) + mailer.project_member_invited_email(project_member.id, token).deliver_later end def accept_project_invite(project_member) - mailer.project_invite_accepted_email(project_member.id) + mailer.project_invite_accepted_email(project_member.id).deliver_later end def decline_project_invite(project_member) - mailer.project_invite_declined_email(project_member.project.id, project_member.invite_email, project_member.access_level, project_member.created_by_id) + mailer.project_invite_declined_email( + project_member.project.id, + project_member.invite_email, + project_member.access_level, + project_member.created_by_id + ).deliver_later end def new_project_member(project_member) - mailer.project_access_granted_email(project_member.id) + mailer.project_access_granted_email(project_member.id).deliver_later end def update_project_member(project_member) - mailer.project_access_granted_email(project_member.id) + mailer.project_access_granted_email(project_member.id).deliver_later end def invite_group_member(group_member, token) - mailer.group_member_invited_email(group_member.id, token) + mailer.group_member_invited_email(group_member.id, token).deliver_later end def accept_group_invite(group_member) - mailer.group_invite_accepted_email(group_member.id) + mailer.group_invite_accepted_email(group_member.id).deliver_later end def decline_group_invite(group_member) - mailer.group_invite_declined_email(group_member.group.id, group_member.invite_email, group_member.access_level, group_member.created_by_id) + mailer.group_invite_declined_email( + group_member.group.id, + group_member.invite_email, + group_member.access_level, + group_member.created_by_id + ).deliver_later end def new_group_member(group_member) - mailer.group_access_granted_email(group_member.id) + mailer.group_access_granted_email(group_member.id).deliver_later end def update_group_member(group_member) - mailer.group_access_granted_email(group_member.id) + mailer.group_access_granted_email(group_member.id).deliver_later end def project_was_moved(project, old_path_with_namespace) @@ -189,7 +209,11 @@ class NotificationService recipients = reject_muted_users(recipients, project) recipients.each do |recipient| - mailer.project_was_moved_email(project.id, recipient.id, old_path_with_namespace) + mailer.project_was_moved_email( + project.id, + recipient.id, + old_path_with_namespace + ).deliver_later end end @@ -339,7 +363,7 @@ class NotificationService recipients = build_recipients(target, project, target.author) recipients.each do |recipient| - mailer.send(method, recipient.id, target.id) + mailer.send(method, recipient.id, target.id).deliver_later end end @@ -347,7 +371,7 @@ class NotificationService recipients = build_recipients(target, project, current_user) recipients.each do |recipient| - mailer.send(method, recipient.id, target.id, current_user.id) + mailer.send(method, recipient.id, target.id, current_user.id).deliver end end @@ -358,7 +382,13 @@ class NotificationService recipients = build_recipients(target, project, current_user, [previous_assignee]) recipients.each do |recipient| - mailer.send(method, recipient.id, target.id, previous_assignee_id, current_user.id) + mailer.send( + method, + recipient.id, + target.id, + previous_assignee_id, + current_user.id + ).deliver_later end end @@ -366,7 +396,7 @@ class NotificationService recipients = build_recipients(target, project, current_user) recipients.each do |recipient| - mailer.send(method, recipient.id, target.id, status, current_user.id) + mailer.send(method, recipient.id, target.id, status, current_user.id).deliver end end @@ -388,7 +418,7 @@ class NotificationService end def mailer - Notify.delay + Notify end def previous_record(object, attribute) diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 5a921a73fe..1df8de1db7 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -46,6 +46,6 @@ class EmailReceiverWorker return end - EmailRejectionMailer.delay.rejection(reason, raw, can_retry) + EmailRejectionMailer.deliver_later.rejection(reason, raw, can_retry) end end diff --git a/config/application.rb b/config/application.rb index bfa2a809dd..d255ff0719 100644 --- a/config/application.rb +++ b/config/application.rb @@ -99,6 +99,10 @@ module Gitlab redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever config.cache_store = :redis_store, redis_config_hash + config.active_record.raise_in_transactional_callbacks = true + + config.active_job.queue_adapter = :sidekiq + # This is needed for gitlab-shell ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH'] end diff --git a/config/environments/production.rb b/config/environments/production.rb index e8250d6645..317b113e10 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -9,7 +9,7 @@ Rails.application.configure do config.action_controller.perform_caching = true # Disable Rails's static asset server (Apache or nginx will already do this) - config.serve_static_assets = false + config.serve_static_files = false # Compress JavaScripts and CSS. config.assets.js_compressor = :uglifier diff --git a/config/environments/test.rb b/config/environments/test.rb index 46982be286..2eddf0605d 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -8,7 +8,7 @@ Rails.application.configure do config.cache_classes = false # Configure static asset server for tests with Cache-Control for performance - config.serve_static_assets = true + config.serve_static_files = true config.static_cache_control = "public, max-age=3600" # Show full error reports and disable caching diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb index d9042c652b..d6dbf8b9fb 100644 --- a/config/initializers/static_files.rb +++ b/config/initializers/static_files.rb @@ -1,6 +1,6 @@ app = Rails.application -if app.config.serve_static_assets +if app.config.serve_static_files # The `ActionDispatch::Static` middleware intercepts requests for static files # by checking if they exist in the `/public` directory. # We're replacing it with our `Gitlab::Middleware::Static` that does the same, diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb index 618acb7a57..13581b8fb1 100644 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ b/lib/gitlab/markdown/label_reference_filter.rb @@ -60,8 +60,7 @@ module Gitlab def url_for_label(project, label) h = Gitlab::Application.routes.url_helpers h.namespace_project_issues_path(project.namespace, project, - label_name: label.name, - only_path: context[:only_path]) + label_name: label.name) end def render_colored_label(label) diff --git a/lib/gitlab/seeder.rb b/lib/gitlab/seeder.rb index 31aa3528c4..2ef0e98225 100644 --- a/lib/gitlab/seeder.rb +++ b/lib/gitlab/seeder.rb @@ -14,7 +14,7 @@ module Gitlab def self.mute_mailer code = <<-eos -def Notify.delay +def Notify.deliver_later self end eos From b9df1a63550c78396d43b661bd24d2745604f6fc Mon Sep 17 00:00:00 2001 From: Jose Corcuera Date: Thu, 26 Nov 2015 10:16:50 -0500 Subject: [PATCH 310/410] Strip attributes for Milestone and Issuable. #3428 --- CHANGELOG | 1 + app/controllers/projects/issues_controller.rb | 4 +-- .../projects/merge_requests_controller.rb | 4 +-- app/models/concerns/issuable.rb | 2 ++ app/models/concerns/strip_attribute.rb | 34 +++++++++++++++++++ app/models/milestone.rb | 3 ++ spec/models/concerns/strip_attribute_spec.rb | 20 +++++++++++ 7 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 app/models/concerns/strip_attribute.rb create mode 100644 spec/models/concerns/strip_attribute_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 0e1e1a3671..39a57231d9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.3.0 (unreleased) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - Fix 500 error when update group member permission - Fix: Raw private snippets access workflow + - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) v 8.2.1 - Forcefully update builds that didn't want to update with state machine diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 5250a0f5e6..ae474cf8d6 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -158,12 +158,10 @@ class Projects::IssuesController < Projects::ApplicationController end def issue_params - permitted = params.require(:issue).permit( + params.require(:issue).permit( :title, :assignee_id, :position, :description, :milestone_id, :state_event, :task_num, label_ids: [] ) - params[:issue][:title].strip! if params[:issue][:title] - permitted end def bulk_update_params diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 6378a1f56b..3f47f2ddb2 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -276,13 +276,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def merge_request_params - permitted = params.require(:merge_request).permit( + params.require(:merge_request).permit( :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, :state_event, :description, :task_num, label_ids: [] ) - params[:merge_request][:title].strip! if params[:merge_request][:title] - permitted end # Make sure merge requests created before 8.0 diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 68138688aa..badeadfa41 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -8,6 +8,7 @@ module Issuable extend ActiveSupport::Concern include Participable include Mentionable + include StripAttribute included do belongs_to :author, class_name: "User" @@ -51,6 +52,7 @@ module Issuable attr_mentionable :title, :description participant :author, :assignee, :notes_with_associations + strip_attributes :title end module ClassMethods diff --git a/app/models/concerns/strip_attribute.rb b/app/models/concerns/strip_attribute.rb new file mode 100644 index 0000000000..8806ebe897 --- /dev/null +++ b/app/models/concerns/strip_attribute.rb @@ -0,0 +1,34 @@ +# == Strip Attribute module +# +# Contains functionality to clean attributes before validation +# +# Usage: +# +# class Milestone < ActiveRecord::Base +# strip_attributes :title +# end +# +# +module StripAttribute + extend ActiveSupport::Concern + + module ClassMethods + def strip_attributes(*attrs) + strip_attrs.concat(attrs) + end + + def strip_attrs + @strip_attrs ||= [] + end + end + + included do + before_validation :strip_attributes + end + + def strip_attributes + self.class.strip_attrs.each do |attr| + self[attr].strip! if self[attr] && self[attr].respond_to?(:strip!) + end + end +end diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 2ff16e2825..c2642b75b8 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -22,6 +22,7 @@ class Milestone < ActiveRecord::Base include InternalId include Sortable + include StripAttribute belongs_to :project has_many :issues @@ -35,6 +36,8 @@ class Milestone < ActiveRecord::Base validates :title, presence: true validates :project, presence: true + strip_attributes :title + state_machine :state, initial: :active do event :close do transition active: :closed diff --git a/spec/models/concerns/strip_attribute_spec.rb b/spec/models/concerns/strip_attribute_spec.rb new file mode 100644 index 0000000000..6445e29c3e --- /dev/null +++ b/spec/models/concerns/strip_attribute_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Milestone, "StripAttribute" do + let(:milestone) { create(:milestone) } + + describe ".strip_attributes" do + it { expect(Milestone).to respond_to(:strip_attributes) } + it { expect(Milestone.strip_attrs).to include(:title) } + end + + describe "#strip_attributes" do + before do + milestone.title = ' 8.3 ' + milestone.valid? + end + + it { expect(milestone.title).to eq('8.3') } + end + +end From 78c1ab40e20f2c412719a2140d9de61ada26d1b8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 26 Nov 2015 07:55:21 -0800 Subject: [PATCH 311/410] Gracefully handle when Redis is not available --- config/initializers/1_settings.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 80b480eac3..444e91109d 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -295,5 +295,10 @@ if Rails.env.test? end # Force a refresh of application settings at startup -ApplicationSetting.expire -Ci::ApplicationSetting.expire +begin + ApplicationSetting.expire + Ci::ApplicationSetting.expire +rescue + # Gracefully handle when Redis is not available. For example, + # omnibus may fail here during assets:precompile. +end From 295d378e9ab1a8e052e35c6e6fedf8d6890b74d0 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 27 Nov 2015 13:56:26 +0100 Subject: [PATCH 312/410] Repeat "client_max_body_size 0" everywhere It turns out that if we do not the declaration from "location /" wins. --- lib/support/nginx/gitlab | 6 ++++++ lib/support/nginx/gitlab-ssl | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 0cf5292b29..1dfcac91cd 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -114,24 +114,28 @@ server { } location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects { + client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; } location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ { + client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; } location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive { + client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; } location ~ ^/api/v3/projects/.*/repository/archive { + client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; @@ -139,6 +143,7 @@ server { # Build artifacts should be submitted to this location location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { + client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; @@ -146,6 +151,7 @@ server { # Build artifacts should be submitted to this location location ~ /ci/api/v1/builds/[0-9]+/artifacts { + client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 31a651c87f..7cd32de073 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -161,24 +161,28 @@ server { } location ~ ^/[\w\.-]+/[\w\.-]+/gitlab-lfs/objects { + client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; } location ~ ^/[\w\.-]+/[\w\.-]+/(info/refs|git-upload-pack|git-receive-pack)$ { + client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; } location ~ ^/[\w\.-]+/[\w\.-]+/repository/archive { + client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; } location ~ ^/api/v3/projects/.*/repository/archive { + client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; @@ -186,6 +190,7 @@ server { # Build artifacts should be submitted to this location location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { + client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; @@ -193,6 +198,7 @@ server { # Build artifacts should be submitted to this location location ~ /ci/api/v1/builds/[0-9]+/artifacts { + client_max_body_size 0; # 'Error' 418 is a hack to re-use the @gitlab-workhorse block error_page 418 = @gitlab-workhorse; return 418; From 04049b6b17c9354555115b5d2f049daffa311c16 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 27 Nov 2015 13:57:53 +0100 Subject: [PATCH 313/410] Fix indentation in NGINX config --- lib/support/nginx/gitlab-ssl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 7cd32de073..016f7a536f 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -191,17 +191,17 @@ server { # Build artifacts should be submitted to this location location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { client_max_body_size 0; - # 'Error' 418 is a hack to re-use the @gitlab-workhorse block - error_page 418 = @gitlab-workhorse; - return 418; + # 'Error' 418 is a hack to re-use the @gitlab-workhorse block + error_page 418 = @gitlab-workhorse; + return 418; } # Build artifacts should be submitted to this location location ~ /ci/api/v1/builds/[0-9]+/artifacts { client_max_body_size 0; - # 'Error' 418 is a hack to re-use the @gitlab-workhorse block - error_page 418 = @gitlab-workhorse; - return 418; + # 'Error' 418 is a hack to re-use the @gitlab-workhorse block + error_page 418 = @gitlab-workhorse; + return 418; } location @gitlab-workhorse { From f1710073b46c940245d38b46e5436c0745223aee Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 27 Nov 2015 14:39:55 -0500 Subject: [PATCH 314/410] Fix alignment [ci skip] --- lib/support/nginx/gitlab | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 1dfcac91cd..2a79fbdcf9 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -144,17 +144,17 @@ server { # Build artifacts should be submitted to this location location ~ ^/[\w\.-]+/[\w\.-]+/builds/download { client_max_body_size 0; - # 'Error' 418 is a hack to re-use the @gitlab-workhorse block - error_page 418 = @gitlab-workhorse; - return 418; + # 'Error' 418 is a hack to re-use the @gitlab-workhorse block + error_page 418 = @gitlab-workhorse; + return 418; } # Build artifacts should be submitted to this location location ~ /ci/api/v1/builds/[0-9]+/artifacts { client_max_body_size 0; - # 'Error' 418 is a hack to re-use the @gitlab-workhorse block - error_page 418 = @gitlab-workhorse; - return 418; + # 'Error' 418 is a hack to re-use the @gitlab-workhorse block + error_page 418 = @gitlab-workhorse; + return 418; } location @gitlab-workhorse { From 3723182a8831595502570c90211610abb4770781 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 27 Nov 2015 16:15:06 -0500 Subject: [PATCH 315/410] Move project visibility level off of the clone panel --- app/assets/stylesheets/pages/projects.scss | 5 +++++ app/views/projects/_home_panel.html.haml | 10 ++++++---- app/views/shared/_clone_panel.html.haml | 4 ---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index ad607ead2b..4a0fe54684 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -90,7 +90,12 @@ } .visibility-level-label { + @extend .btn; + @extend .btn-gray; + color: $gray; + cursor: auto; + i { color: inherit; } diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 88d54bf6f2..b30036966a 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,5 +1,5 @@ - empty_repo = @project.empty_repo? -.project-home-panel.clearfix{:class => ("empty-project" if empty_repo)} +.project-home-panel.cover-block.clearfix{:class => ("empty-project" if empty_repo)} .project-identicon-holder = project_icon(@project, alt: '', class: 'project-avatar avatar s90') .project-home-desc @@ -12,8 +12,10 @@ Forked from = link_to project_path(forked_from_project) do = forked_from_project.namespace.try(:name) - - + .cover-controls + .visibility-level-label + = visibility_level_icon(@project.visibility_level) + = visibility_level_label(@project.visibility_level) .project-repo-buttons .split-one @@ -21,7 +23,7 @@ = render 'projects/buttons/fork' = render "shared/clone_panel" - + .split-repo-buttons = render "projects/buttons/download" = render 'projects/buttons/dropdown' diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 408a46aaaf..edb5778f42 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -8,7 +8,3 @@ = text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control", readonly: true .input-group-btn = clipboard_button(clipboard_target: '#project_clone') - - if project.kind_of?(Project) - .input-group-addon.has_tooltip{title: "#{visibility_level_label(project.visibility_level)} project", data: { container: "body" } } - .visibility-level-label - = visibility_level_icon(project.visibility_level) From 344cb89718d9424e239a24dbc888d7b7a98cb474 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 27 Nov 2015 16:34:59 -0500 Subject: [PATCH 316/410] Bump jquery-turbolinks to ~> 2.1.0 See #2857 --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 685b126a79..51ce6c9ed7 100644 --- a/Gemfile +++ b/Gemfile @@ -183,7 +183,7 @@ gem "sass-rails", '~> 4.0.5' gem "coffee-rails", '~> 4.1.0' gem "uglifier", '~> 2.7.2' gem 'turbolinks', '~> 2.5.0' -gem 'jquery-turbolinks', '~> 2.0.1' +gem 'jquery-turbolinks', '~> 2.1.0' gem 'addressable', '~> 2.3.8' gem 'bootstrap-sass', '~> 3.0' diff --git a/Gemfile.lock b/Gemfile.lock index 8fb2ef778e..54a63ea22d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -121,7 +121,7 @@ GEM coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.9.1.1) + coffee-script-source (1.10.0) colorize (0.7.7) connection_pool (2.2.0) coveralls (0.8.2) @@ -370,7 +370,7 @@ GEM thor (>= 0.14, < 2.0) jquery-scrollto-rails (1.4.3) railties (> 3.1, < 5.0) - jquery-turbolinks (2.0.2) + jquery-turbolinks (2.1.0) railties (>= 3.1.0) turbolinks jquery-ui-rails (4.2.1) @@ -853,7 +853,7 @@ DEPENDENCIES jquery-atwho-rails (~> 1.3.2) jquery-rails (~> 3.1.3) jquery-scrollto-rails (~> 1.4.3) - jquery-turbolinks (~> 2.0.1) + jquery-turbolinks (~> 2.1.0) jquery-ui-rails (~> 4.2.1) kaminari (~> 0.16.3) letter_opener (~> 1.1.2) From 85ad2140fa4ef2209b9d217403e183e33ce4dd37 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Sat, 28 Nov 2015 20:53:17 +0100 Subject: [PATCH 317/410] Remove reverence to satellites [ci skip] --- doc/raketasks/backup_restore.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 4e645b21a8..b4d2786bd7 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -274,9 +274,6 @@ sudo gitlab-rake gitlab:backup:restore BACKUP=1393513186 # Start GitLab sudo gitlab-ctl start -# Create satellites -sudo gitlab-rake gitlab:satellites:create - # Check GitLab sudo gitlab-rake gitlab:check SANITIZE=true ``` From 7d6323a9d4ada6be6c00f7d160b366219952f77f Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 29 Nov 2015 11:51:02 +0200 Subject: [PATCH 318/410] Update phantomjs to 2.0.0 * Version 1.9 was removed from upstream Debian repos --- scripts/prepare_build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh index dca5e1c5db..5ff4d7fa76 100755 --- a/scripts/prepare_build.sh +++ b/scripts/prepare_build.sh @@ -1,7 +1,7 @@ #!/bin/bash if [ -f /.dockerinit ]; then - wget -q http://ftp.de.debian.org/debian/pool/main/p/phantomjs/phantomjs_1.9.0-1+b1_amd64.deb - dpkg -i phantomjs_1.9.0-1+b1_amd64.deb + wget -q http://ftp.de.debian.org/debian/pool/main/p/phantomjs/phantomjs_2.0.0+dfsg-1_amd64.deb + dpkg -i phantomjs_2.0.0+dfsg-1_amd64.deb apt-get update -qq apt-get install -y -qq libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client From bb6ce2bb10734cfc45e488378acaa17000fdacc2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 29 Nov 2015 13:44:19 +0200 Subject: [PATCH 319/410] Test using a 1.9.8 phantomjs version built with fpm https://gitlab.com/axil/phantomjs-debian --- scripts/prepare_build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh index 5ff4d7fa76..119cc90fc1 100755 --- a/scripts/prepare_build.sh +++ b/scripts/prepare_build.sh @@ -1,7 +1,7 @@ #!/bin/bash if [ -f /.dockerinit ]; then - wget -q http://ftp.de.debian.org/debian/pool/main/p/phantomjs/phantomjs_2.0.0+dfsg-1_amd64.deb - dpkg -i phantomjs_2.0.0+dfsg-1_amd64.deb + wget -q https://gitlab.com/axil/phantomjs-debian/raw/master/phantomjs_1.9.8-0jessie_amd64.deb + dpkg -i phantomjs_1.9.8-0jessie_amd64.deb apt-get update -qq apt-get install -y -qq libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client From e1522ec85582962c12f875a46a5853d6ae570d0b Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 29 Nov 2015 17:52:40 -0500 Subject: [PATCH 320/410] Simplify `build_gitlab_shell_ssh_path_prefix` --- config/initializers/1_settings.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index b162b8a83f..abe08d5718 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -33,13 +33,15 @@ class Settings < Settingslogic end def build_gitlab_shell_ssh_path_prefix + user_host = "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}" + if gitlab_shell.ssh_port != 22 - "ssh://#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:#{gitlab_shell.ssh_port}/" + "ssh://#{user_host}:#{gitlab_shell.ssh_port}/" else if gitlab_shell.ssh_host.include? ':' - "[#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}]:" + "[#{user_host}]:" else - "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:" + "#{user_host}:" end end end From 461731f0769a826d00c4d5846ff6d2f55fd4b829 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 30 Nov 2015 11:21:10 +0200 Subject: [PATCH 321/410] fix notification_service specs --- app/services/notification_service.rb | 5 +- config/environments/test.rb | 2 + spec/services/notification_service_spec.rb | 345 +++++++++------------ spec/spec_helper.rb | 1 + 4 files changed, 144 insertions(+), 209 deletions(-) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 03fe8c8fe1..388a4defb2 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -148,7 +148,6 @@ class NotificationService # build notify method like 'note_commit_email' notify_method = "note_#{note.noteable_type.underscore}_email".to_sym - recipients.each do |recipient| mailer.send(notify_method, recipient.id, note.id).deliver_later end @@ -371,7 +370,7 @@ class NotificationService recipients = build_recipients(target, project, current_user) recipients.each do |recipient| - mailer.send(method, recipient.id, target.id, current_user.id).deliver + mailer.send(method, recipient.id, target.id, current_user.id).deliver_later end end @@ -396,7 +395,7 @@ class NotificationService recipients = build_recipients(target, project, current_user) recipients.each do |recipient| - mailer.send(method, recipient.id, target.id, status, current_user.id).deliver + mailer.send(method, recipient.id, target.id, status, current_user.id).deliver_later end end diff --git a/config/environments/test.rb b/config/environments/test.rb index 2eddf0605d..f96ac6f975 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -32,4 +32,6 @@ Rails.application.configure do config.eager_load = false config.cache_store = :null_store + + config.active_job.queue_adapter = :test end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 520140917a..a4e2b2953c 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -3,6 +3,12 @@ require 'spec_helper' describe NotificationService do let(:notification) { NotificationService.new } + around(:each) do |example| + perform_enqueued_jobs do + example.run + end + end + describe 'Keys' do describe :new_key do let!(:key) { create(:personal_key) } @@ -10,8 +16,7 @@ describe NotificationService do it { expect(notification.new_key(key)).to be_truthy } it 'should sent email to key owner' do - expect(Notify).to receive(:new_ssh_key_email).with(key.id) - notification.new_key(key) + expect{ notification.new_key(key) }.to change{ ActionMailer::Base.deliveries.size }.by(1) end end end @@ -23,8 +28,7 @@ describe NotificationService do it { expect(notification.new_email(email)).to be_truthy } it 'should send email to email owner' do - expect(Notify).to receive(:new_email_email).with(email.id) - notification.new_email(email) + expect{ notification.new_email(email) }.to change{ ActionMailer::Base.deliveries.size }.by(1) end end end @@ -47,18 +51,20 @@ describe NotificationService do it do add_users_with_subscription(note.project, issue) - should_email(@u_watcher.id) - should_email(note.noteable.author_id) - should_email(note.noteable.assignee_id) - should_email(@u_mentioned.id) - should_email(@subscriber.id) - should_not_email(note.author_id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_outsider_mentioned) + ActionMailer::Base.deliveries.clear notification.new_note(note) + + should_email(@u_watcher) + should_email(note.noteable.author) + should_email(note.noteable.assignee) + should_email(@u_mentioned) + should_email(@subscriber) + should_not_email(note.author) + should_not_email(@u_participating) + should_not_email(@u_disabled) + should_not_email(@unsubscriber) + should_not_email(@u_outsider_mentioned) end it 'filters out "mentioned in" notes' do @@ -82,27 +88,21 @@ describe NotificationService do group_member = note.project.group.group_members.find_by_user_id(@u_watcher.id) group_member.notification_level = Notification::N_GLOBAL group_member.save + ActionMailer::Base.deliveries.clear end it do - should_email(note.noteable.author_id) - should_email(note.noteable.assignee_id) - should_email(@u_mentioned.id) - should_not_email(@u_watcher.id) - should_not_email(note.author_id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.new_note(note) + + should_email(note.noteable.author) + should_email(note.noteable.assignee) + should_email(@u_mentioned) + should_not_email(@u_watcher) + should_not_email(note.author) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end - - def should_email(user_id) - expect(Notify).to receive(:note_issue_email).with(user_id, note.id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:note_issue_email).with(user_id, note.id) - end end context 'issue note mention' do @@ -113,24 +113,26 @@ describe NotificationService do before do build_team(note.project) + ActionMailer::Base.deliveries.clear end describe :new_note do it do + notification.new_note(note) + # Notify all team members note.project.team.members.each do |member| # User with disabled notification should not be notified next if member.id == @u_disabled.id - should_email(member.id) + should_email(member) end - should_email(note.noteable.author_id) - should_email(note.noteable.assignee_id) - should_not_email(note.author_id) - should_not_email(@u_mentioned.id) - should_not_email(@u_disabled.id) - should_not_email(@u_not_mentioned.id) - notification.new_note(note) + should_email(note.noteable.author) + should_email(note.noteable.assignee) + should_not_email(note.author) + should_email(@u_mentioned) + should_not_email(@u_disabled) + should_email(@u_not_mentioned) end it 'filters out "mentioned in" notes' do @@ -140,14 +142,6 @@ describe NotificationService do notification.new_note(mentioned_note) end end - - def should_email(user_id) - expect(Notify).to receive(:note_issue_email).with(user_id, note.id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:note_issue_email).with(user_id, note.id) - end end context 'commit note' do @@ -156,43 +150,38 @@ describe NotificationService do before do build_team(note.project) + ActionMailer::Base.deliveries.clear allow_any_instance_of(Commit).to receive(:author).and_return(@u_committer) end - describe :new_note do + describe :new_note, :perform_enqueued_jobs do it do - should_email(@u_committer.id, note) - should_email(@u_watcher.id, note) - should_not_email(@u_mentioned.id, note) - should_not_email(note.author_id, note) - should_not_email(@u_participating.id, note) - should_not_email(@u_disabled.id, note) notification.new_note(note) + + should_email(@u_committer) + should_email(@u_watcher) + should_not_email(@u_mentioned) + should_not_email(note.author) + should_not_email(@u_participating) + should_not_email(@u_disabled) end it do note.update_attribute(:note, '@mention referenced') - should_email(@u_committer.id, note) - should_email(@u_watcher.id, note) - should_email(@u_mentioned.id, note) - should_not_email(note.author_id, note) - should_not_email(@u_participating.id, note) - should_not_email(@u_disabled.id, note) notification.new_note(note) + + should_email(@u_committer) + should_email(@u_watcher) + should_email(@u_mentioned) + should_not_email(note.author) + should_not_email(@u_participating) + should_not_email(@u_disabled) end it do @u_committer.update_attributes(notification_level: Notification::N_MENTION) - should_not_email(@u_committer.id, note) notification.new_note(note) - end - - def should_email(user_id, n) - expect(Notify).to receive(:note_commit_email).with(user_id, n.id) - end - - def should_not_email(user_id, n) - expect(Notify).not_to receive(:note_commit_email).with(user_id, n.id) + should_not_email(@u_committer) end end end @@ -205,99 +194,69 @@ describe NotificationService do before do build_team(issue.project) add_users_with_subscription(issue.project, issue) + ActionMailer::Base.deliveries.clear end describe :new_issue do it do - should_email(issue.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_not_email(@u_mentioned.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.new_issue(issue, @u_disabled) + + should_email(issue.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_not_email(@u_mentioned) + should_not_email(@u_participating) + should_not_email(@u_disabled) end it do issue.assignee.update_attributes(notification_level: Notification::N_MENTION) - should_not_email(issue.assignee_id) notification.new_issue(issue, @u_disabled) - end - def should_email(user_id) - expect(Notify).to receive(:new_issue_email).with(user_id, issue.id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:new_issue_email).with(user_id, issue.id) + should_not_email(issue.assignee) end end describe :reassigned_issue do it 'should email new assignee' do - should_email(issue.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) - notification.reassigned_issue(issue, @u_disabled) - end - def should_email(user_id) - expect(Notify).to receive(:reassigned_issue_email).with(user_id, issue.id, nil, @u_disabled.id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id) + should_email(issue.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end describe :close_issue do it 'should sent email to issue assignee and issue author' do - should_email(issue.assignee_id) - should_email(issue.author_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) - notification.close_issue(issue, @u_disabled) - end - def should_email(user_id) - expect(Notify).to receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id) + should_email(issue.assignee) + should_email(issue.author) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end describe :reopen_issue do it 'should send email to issue assignee and issue author' do - should_email(issue.assignee_id) - should_email(issue.author_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) - notification.reopen_issue(issue, @u_disabled) - end - def should_email(user_id) - expect(Notify).to receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id) + should_email(issue.assignee) + should_email(issue.author) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) end end end @@ -309,108 +268,74 @@ describe NotificationService do before do build_team(merge_request.target_project) add_users_with_subscription(merge_request.target_project, merge_request) + ActionMailer::Base.deliveries.clear end describe :new_merge_request do it do - should_email(merge_request.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.new_merge_request(merge_request, @u_disabled) - end - def should_email(user_id) - expect(Notify).to receive(:new_merge_request_email).with(user_id, merge_request.id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:new_merge_request_email).with(user_id, merge_request.id) + should_email(merge_request.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end describe :reassigned_merge_request do it do - should_email(merge_request.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.reassigned_merge_request(merge_request, merge_request.author) - end - def should_email(user_id) - expect(Notify).to receive(:reassigned_merge_request_email).with(user_id, merge_request.id, nil, merge_request.author_id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id) + should_email(merge_request.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end describe :closed_merge_request do it do - should_email(merge_request.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.close_mr(merge_request, @u_disabled) - end - def should_email(user_id) - expect(Notify).to receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) + should_email(merge_request.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end describe :merged_merge_request do it do - should_email(merge_request.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.merge_mr(merge_request, @u_disabled) - end - def should_email(user_id) - expect(Notify).to receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) + should_email(merge_request.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end describe :reopen_merge_request do it do - should_email(merge_request.assignee_id) - should_email(@u_watcher.id) - should_email(@u_participant_mentioned.id) - should_email(@subscriber.id) - should_not_email(@unsubscriber.id) - should_not_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.reopen_mr(merge_request, @u_disabled) - end - def should_email(user_id) - expect(Notify).to receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id) - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id) + should_email(merge_request.assignee) + should_email(@u_watcher) + should_email(@u_participant_mentioned) + should_email(@subscriber) + should_not_email(@unsubscriber) + should_not_email(@u_participating) + should_not_email(@u_disabled) end end end @@ -420,22 +345,16 @@ describe NotificationService do before do build_team(project) + ActionMailer::Base.deliveries.clear end describe :project_was_moved do it do - should_email(@u_watcher.id) - should_email(@u_participating.id) - should_not_email(@u_disabled.id) notification.project_was_moved(project, "gitlab/gitlab") - end - def should_email(user_id) - expect(Notify).to receive(:project_was_moved_email).with(project.id, user_id, "gitlab/gitlab") - end - - def should_not_email(user_id) - expect(Notify).not_to receive(:project_was_moved_email).with(project.id, user_id, "gitlab/gitlab") + should_email(@u_watcher) + should_email(@u_participating) + should_not_email(@u_disabled) end end end @@ -469,4 +388,18 @@ describe NotificationService do issuable.subscriptions.create(user: @subscriber, subscribed: true) issuable.subscriptions.create(user: @unsubscriber, subscribed: false) end + + def sent_to_user?(user) + ActionMailer::Base.deliveries.any? do |message| + message.to.include?(user.email) + end + end + + def should_email(user) + expect(sent_to_user?(user)).to be_truthy + end + + def should_not_email(user) + expect(sent_to_user?(user)).to be_falsey + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2be13bb3e6..0225a0ee53 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -31,6 +31,7 @@ RSpec.configure do |config| config.include StubConfiguration config.include RelativeUrl, type: feature config.include TestEnv + config.include ActiveJob::TestHelper config.include StubGitlabCalls config.include StubGitlabData config.include BenchmarkMatchers, benchmark: true From e92ceb7b57139e985674a44cfe75534c52ed4acd Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 30 Nov 2015 16:12:31 +0200 Subject: [PATCH 322/410] fix specs --- Gemfile.lock | 4 +- app/controllers/abuse_reports_controller.rb | 2 +- .../project_services/ci/mail_service.rb | 6 +- app/views/ci/admin/runners/show.html.haml | 2 +- .../ci/notify/build_success_email.html.haml | 2 +- app/views/projects/edit.html.haml | 2 +- app/views/projects/runners/edit.html.haml | 2 +- app/workers/email_receiver_worker.rb | 2 +- lib/gitlab/github_import/client.rb | 2 +- lib/gitlab/gitlab_import/client.rb | 2 +- .../abuse_reports_controller_spec.rb | 36 ++++++----- spec/features/admin/admin_users_spec.rb | 2 +- spec/helpers/application_helper_spec.rb | 2 +- .../markdown/label_reference_filter_spec.rb | 6 +- .../ci/project_services/mail_service_spec.rb | 62 +++++++------------ .../project_services/jira_service_spec.rb | 6 +- spec/workers/email_receiver_worker_spec.rb | 12 ++-- 17 files changed, 72 insertions(+), 80 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 0800137991..f06c4a4165 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -332,7 +332,7 @@ GEM github-markup (~> 1.3.3) gollum-grit_adapter (~> 1.0) nokogiri (~> 1.6.4) - rouge (~> 1.7.4) + rouge (~> 1.10.1) sanitize (~> 2.1.0) stringex (~> 2.5.1) gon (6.0.1) @@ -610,7 +610,7 @@ GEM netrc (~> 0.7) rinku (1.7.3) rotp (2.1.1) - rouge (1.7.7) + rouge (1.10.1) rqrcode (0.7.0) chunky_png rqrcode-rails3 (0.1.7) diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb index d8e9059433..20bc5173f1 100644 --- a/app/controllers/abuse_reports_controller.rb +++ b/app/controllers/abuse_reports_controller.rb @@ -10,7 +10,7 @@ class AbuseReportsController < ApplicationController if @abuse_report.save if current_application_settings.admin_notification_email.present? - AbuseReportMailer.deliver_later.notify(@abuse_report.id) + AbuseReportMailer.notify(@abuse_report.id).deliver_later end message = "Thank you for your report. A GitLab administrator will look into it shortly." diff --git a/app/models/project_services/ci/mail_service.rb b/app/models/project_services/ci/mail_service.rb index bdc85667e9..bb961d0697 100644 --- a/app/models/project_services/ci/mail_service.rb +++ b/app/models/project_services/ci/mail_service.rb @@ -64,9 +64,9 @@ module Ci build.project_recipients.each do |recipient| case build.status.to_sym when :success - mailer.build_success_email(build.id, recipient) + mailer.build_success_email(build.id, recipient).deliver_later when :failed - mailer.build_fail_email(build.id, recipient) + mailer.build_fail_email(build.id, recipient).deliver_later end end end @@ -78,7 +78,7 @@ module Ci end def mailer - Ci::Notify.deliver_later + Ci::Notify end end end diff --git a/app/views/ci/admin/runners/show.html.haml b/app/views/ci/admin/runners/show.html.haml index 1498db46a8..fd3d33d657 100644 --- a/app/views/ci/admin/runners/show.html.haml +++ b/app/views/ci/admin/runners/show.html.haml @@ -37,7 +37,7 @@ = label_tag :tag_list, class: 'control-label' do Tags .col-sm-10 - = f.text_field :tag_list, class: 'form-control' + = f.text_field :tag_list, value: @runner.tag_list.to_s, class: 'form-control' .help-block You can setup builds to only use runners with specific tags .form-actions = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/ci/notify/build_success_email.html.haml b/app/views/ci/notify/build_success_email.html.haml index 24c439e50e..6ef1fd67d8 100644 --- a/app/views/ci/notify/build_success_email.html.haml +++ b/app/views/ci/notify/build_success_email.html.haml @@ -8,7 +8,7 @@ = @project.name %p - Commit: #{link_to @build.short_sha, namespace_project_commit_path(@build.gl_project.namespace, @build.gl_project, @build.sha)} + Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.gl_project.namespace, @build.gl_project, @build.sha)} %p Author: #{@build.commit.git_author_name} %p diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 3ebc175648..0c10de1604 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -35,7 +35,7 @@ .form-group = f.label :tag_list, "Tags", class: 'control-label' .col-sm-10 - = f.text_field :tag_list, maxlength: 2000, class: "form-control" + = f.text_field :tag_list, value: @project.tag_list.to_s, maxlength: 2000, class: "form-control" %p.help-block Separate tags with commas. %fieldset.features diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml index dde9e448cb..a032470169 100644 --- a/app/views/projects/runners/edit.html.haml +++ b/app/views/projects/runners/edit.html.haml @@ -23,7 +23,7 @@ = label_tag :tag_list, class: 'control-label' do Tags .col-sm-10 - = f.text_field :tag_list, class: 'form-control' + = f.text_field :tag_list, value: @runner.tag_list.to_s, class: 'form-control' .help-block You can setup jobs to only use runners with specific tags .form-actions = f.submit 'Save', class: 'btn btn-save' diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 1df8de1db7..f2649e38eb 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -46,6 +46,6 @@ class EmailReceiverWorker return end - EmailRejectionMailer.deliver_later.rejection(reason, raw, can_retry) + EmailRejectionMailer.rejection(reason, raw, can_retry).deliver_later end end diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb index 270cbcd9cc..74d1529e1f 100644 --- a/lib/gitlab/github_import/client.rb +++ b/lib/gitlab/github_import/client.rb @@ -46,7 +46,7 @@ module Gitlab end def github_options - OmniAuth::Strategies::GitHub.default_options[:client_options].symbolize_keys + OmniAuth::Strategies::GitHub.default_options[:client_options].to_h.symbolize_keys end end end diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb index 9c00896c91..86fb6c5176 100644 --- a/lib/gitlab/gitlab_import/client.rb +++ b/lib/gitlab/gitlab_import/client.rb @@ -75,7 +75,7 @@ module Gitlab end def gitlab_options - OmniAuth::Strategies::GitLab.default_options[:client_options].symbolize_keys + OmniAuth::Strategies::GitLab.default_options[:client_options].to_h.symbolize_keys end end end diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb index 0faab8d7ff..15824a1c67 100644 --- a/spec/controllers/abuse_reports_controller_spec.rb +++ b/spec/controllers/abuse_reports_controller_spec.rb @@ -18,27 +18,31 @@ describe AbuseReportsController do end it "sends a notification email" do - post :create, - abuse_report: { - user_id: user.id, - message: message - } - - email = ActionMailer::Base.deliveries.last - - expect(email.to).to eq([admin_email]) - expect(email.subject).to include(user.username) - expect(email.text_part.body).to include(message) - end - - it "saves the abuse report" do - expect do + perform_enqueued_jobs do post :create, abuse_report: { user_id: user.id, message: message } - end.to change { AbuseReport.count }.by(1) + + email = ActionMailer::Base.deliveries.last + + expect(email.to).to eq([admin_email]) + expect(email.subject).to include(user.username) + expect(email.text_part.body).to include(message) + end + end + + it "saves the abuse report" do + perform_enqueued_jobs do + expect do + post :create, + abuse_report: { + user_id: user.id, + message: message + } + end.to change { AbuseReport.count }.by(1) + end end end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 4c756a8e73..c6b6ac58ac 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -87,7 +87,7 @@ describe "Admin::Users", feature: true do end it "should call send mail" do - expect(Notify).to receive(:new_user_email) + expect_any_instance_of(NotificationService).to receive(:new_user) click_button "Create user" end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 1dfae0fbd3..4b8000ecc4 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -59,7 +59,7 @@ describe ApplicationHelper do avatar_url = "http://localhost/uploads/project/avatar/#{project.id}/banana_sample.gif" expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s). - to eq "\"Banana" + to eq "\"Banana" end it 'should give uploaded icon when present' do diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb index fc21b65a84..ae286c8be2 100644 --- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb @@ -71,7 +71,7 @@ module Gitlab::Markdown doc = filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls. - namespace_project_issues_url(project.namespace, project, label_name: label.name) + namespace_project_issues_path(project.namespace, project, label_name: label.name) end it 'links with adjacent text' do @@ -94,7 +94,7 @@ module Gitlab::Markdown doc = filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls. - namespace_project_issues_url(project.namespace, project, label_name: label.name) + namespace_project_issues_path(project.namespace, project, label_name: label.name) expect(doc.text).to eq 'See gfm' end @@ -118,7 +118,7 @@ module Gitlab::Markdown doc = filter("See #{reference}") expect(doc.css('a').first.attr('href')).to eq urls. - namespace_project_issues_url(project.namespace, project, label_name: label.name) + namespace_project_issues_path(project.namespace, project, label_name: label.name) expect(doc.text).to eq 'See gfm references' end diff --git a/spec/models/ci/project_services/mail_service_spec.rb b/spec/models/ci/project_services/mail_service_spec.rb index d9b3d34ff1..c03be3ef75 100644 --- a/spec/models/ci/project_services/mail_service_spec.rb +++ b/spec/models/ci/project_services/mail_service_spec.rb @@ -44,13 +44,10 @@ describe Ci::MailService do end it do - should_email("git@example.com") - mail.execute(build) - end - - def should_email(email) - expect(Ci::Notify).to receive(:build_fail_email).with(build.id, email) - expect(Ci::Notify).not_to receive(:build_success_email).with(build.id, email) + perform_enqueued_jobs do + expect{ mail.execute(build) }.to change{ ActionMailer::Base.deliveries.size }.by(1) + expect(ActionMailer::Base.deliveries.last.to).to eq(["git@example.com"]) + end end end @@ -67,13 +64,10 @@ describe Ci::MailService do end it do - should_email("git@example.com") - mail.execute(build) - end - - def should_email(email) - expect(Ci::Notify).to receive(:build_success_email).with(build.id, email) - expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email) + perform_enqueued_jobs do + expect{ mail.execute(build) }.to change{ ActionMailer::Base.deliveries.size }.by(1) + expect(ActionMailer::Base.deliveries.last.to).to eq(["git@example.com"]) + end end end @@ -95,14 +89,12 @@ describe Ci::MailService do end it do - should_email("git@example.com") - should_email("jeroen@example.com") - mail.execute(build) - end - - def should_email(email) - expect(Ci::Notify).to receive(:build_success_email).with(build.id, email) - expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email) + perform_enqueued_jobs do + expect{ mail.execute(build) }.to change{ ActionMailer::Base.deliveries.size }.by(2) + expect( + ActionMailer::Base.deliveries.map(&:to).flatten + ).to include("git@example.com", "jeroen@example.com") + end end end @@ -124,14 +116,11 @@ describe Ci::MailService do end it do - should_email(commit.git_author_email) - should_email("jeroen@example.com") - mail.execute(build) if mail.can_execute?(build) - end - - def should_email(email) - expect(Ci::Notify).not_to receive(:build_success_email).with(build.id, email) - expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email) + perform_enqueued_jobs do + expect do + mail.execute(build) if mail.can_execute?(build) + end.to_not change{ ActionMailer::Base.deliveries.size } + end end end @@ -177,14 +166,11 @@ describe Ci::MailService do it do Ci::Build.retry(build) - should_email(commit.git_author_email) - should_email("jeroen@example.com") - mail.execute(build) if mail.can_execute?(build) - end - - def should_email(email) - expect(Ci::Notify).not_to receive(:build_success_email).with(build.id, email) - expect(Ci::Notify).not_to receive(:build_fail_email).with(build.id, email) + perform_enqueued_jobs do + expect do + mail.execute(build) if mail.can_execute?(build) + end.to_not change{ ActionMailer::Base.deliveries.size } + end end end end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index ddd2cce212..576f5fc79e 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -94,9 +94,9 @@ describe JiraService do end it 'should be prepopulated with the settings' do - expect(@service.properties[:project_url]).to eq('http://jira.sample/projects/project_a') - expect(@service.properties[:issues_url]).to eq("http://jira.sample/issues/:id") - expect(@service.properties[:new_issue_url]).to eq("http://jira.sample/projects/project_a/issues/new") + expect(@service.properties["project_url"]).to eq('http://jira.sample/projects/project_a') + expect(@service.properties["issues_url"]).to eq("http://jira.sample/issues/:id") + expect(@service.properties["new_issue_url"]).to eq("http://jira.sample/projects/project_a/issues/new") end end end diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb index 65a8d7d919..de40a6f78a 100644 --- a/spec/workers/email_receiver_worker_spec.rb +++ b/spec/workers/email_receiver_worker_spec.rb @@ -21,12 +21,14 @@ describe EmailReceiverWorker do end it "sends out a rejection email" do - described_class.new.perform(raw_message) + perform_enqueued_jobs do + described_class.new.perform(raw_message) - email = ActionMailer::Base.deliveries.last - expect(email).not_to be_nil - expect(email.to).to eq(["jake@adventuretime.ooo"]) - expect(email.subject).to include("Rejected") + email = ActionMailer::Base.deliveries.last + expect(email).not_to be_nil + expect(email.to).to eq(["jake@adventuretime.ooo"]) + expect(email.subject).to include("Rejected") + end end end end From f1504e1ad52df7aec241b93d0d015da13ddce5a9 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 30 Nov 2015 18:03:07 +0200 Subject: [PATCH 323/410] test fix --- app/models/project_services/gitlab_ci_service.rb | 2 +- spec/features/admin/admin_users_spec.rb | 5 ++++- spec/services/issues/close_service_spec.rb | 4 +++- spec/services/issues/update_service_spec.rb | 5 ++++- spec/services/merge_requests/close_service_spec.rb | 4 +++- spec/services/merge_requests/merge_service_spec.rb | 5 +++-- spec/services/merge_requests/reopen_service_spec.rb | 4 +++- spec/services/merge_requests/update_service_spec.rb | 6 ++++-- 8 files changed, 25 insertions(+), 10 deletions(-) diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index c5657b5070..234e8e8b58 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -55,7 +55,7 @@ class GitlabCiService < CiService end def get_ci_commit(sha, ref) - Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha!(sha) + Ci::Project.find(project.gitlab_ci_project.id).commits.find_by_sha!(sha) end def commit_status(sha, ref) diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index c6b6ac58ac..86f01faffb 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -93,7 +93,10 @@ describe "Admin::Users", feature: true do end it "should send valid email to user with email & password" do - click_button "Create user" + perform_enqueued_jobs do + click_button "Create user" + end + user = User.find_by(username: 'bang') email = ActionMailer::Base.deliveries.last expect(email.subject).to have_content('Account was created') diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index db547ce0d5..d711e3da10 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -14,7 +14,9 @@ describe Issues::CloseService do describe :execute do context "valid params" do before do - @issue = Issues::CloseService.new(project, user, {}).execute(issue) + perform_enqueued_jobs do + @issue = Issues::CloseService.new(project, user, {}).execute(issue) + end end it { expect(@issue).to be_valid } diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index cc3e648326..73d0b7f7ab 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -36,7 +36,10 @@ describe Issues::UpdateService do label_ids: [label.id] } - @issue = Issues::UpdateService.new(project, user, opts).execute(issue) + perform_enqueued_jobs do + @issue = Issues::UpdateService.new(project, user, opts).execute(issue) + end + @issue.reload end diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index b3cbfd4b5b..272f389793 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -18,7 +18,9 @@ describe MergeRequests::CloseService do before do allow(service).to receive(:execute_hooks) - @merge_request = service.execute(merge_request) + perform_enqueued_jobs do + @merge_request = service.execute(merge_request) + end end it { expect(@merge_request).to be_valid } diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 7483f51de0..c0961ceb11 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -17,8 +17,9 @@ describe MergeRequests::MergeService do before do allow(service).to receive(:execute_hooks) - - service.execute(merge_request, 'Awesome message') + perform_enqueued_jobs do + service.execute(merge_request, 'Awesome message') + end end it { expect(merge_request).to be_valid } diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb index 9401bc3b55..05146bf43f 100644 --- a/spec/services/merge_requests/reopen_service_spec.rb +++ b/spec/services/merge_requests/reopen_service_spec.rb @@ -19,7 +19,9 @@ describe MergeRequests::ReopenService do allow(service).to receive(:execute_hooks) merge_request.state = :closed - service.execute(merge_request) + perform_enqueued_jobs do + service.execute(merge_request) + end end it { expect(merge_request).to be_valid } diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 97f5c009ae..d899b1f01d 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -42,8 +42,10 @@ describe MergeRequests::UpdateService do before do allow(service).to receive(:execute_hooks) - @merge_request = service.execute(merge_request) - @merge_request.reload + perform_enqueued_jobs do + @merge_request = service.execute(merge_request) + @merge_request.reload + end end it { expect(@merge_request).to be_valid } From ae18ba16327abd79cf6207b83209d4ef96d3e158 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 24 Nov 2015 13:35:07 +0200 Subject: [PATCH 324/410] Fire update hook from GitLab --- CHANGELOG | 1 + app/models/repository.rb | 10 +++++++--- lib/gitlab/git/hook.rb | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 090b54f41a..4a18ac821f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ v 8.3.0 (unreleased) - Fix 500 error when update group member permission - Fix: Raw private snippets access workflow - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) + - Fire update hook from GitLab v 8.2.1 - Forcefully update builds that didn't want to update with state machine diff --git a/app/models/repository.rb b/app/models/repository.rb index c183610346..d247b0f501 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -571,9 +571,13 @@ class Repository # Run GitLab pre-receive hook pre_receive_hook = Gitlab::Git::Hook.new('pre-receive', path_to_repo) - status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref) + pre_receive_hook_status = pre_receive_hook.trigger(gl_id, oldrev, newrev, ref) - if status + # Run GitLab update hook + update_hook = Gitlab::Git::Hook.new('update', path_to_repo) + update_hook_status = update_hook.trigger(gl_id, oldrev, newrev, ref) + + if pre_receive_hook_status && update_hook_status if was_empty # Create branch rugged.references.create(ref, newrev) @@ -596,7 +600,7 @@ class Repository # Remove tmp ref and return error to user rugged.references.delete(tmp_ref) - raise PreReceiveError.new('Commit was rejected by pre-receive hook') + raise PreReceiveError.new('Commit was rejected by git hook') end end diff --git a/lib/gitlab/git/hook.rb b/lib/gitlab/git/hook.rb index dd393fe09d..07b856ca64 100644 --- a/lib/gitlab/git/hook.rb +++ b/lib/gitlab/git/hook.rb @@ -16,6 +16,17 @@ module Gitlab def trigger(gl_id, oldrev, newrev, ref) return true unless exists? + case name + when "pre-receive", "post-receive" + call_receive_hook(gl_id, oldrev, newrev, ref) + when "update" + call_update_hook(gl_id, oldrev, newrev, ref) + end + end + + private + + def call_receive_hook(gl_id, oldrev, newrev, ref) changes = [oldrev, newrev, ref].join(" ") # function will return true if succesful @@ -54,6 +65,12 @@ module Gitlab exit_status end + + def call_update_hook(gl_id, oldrev, newrev, ref) + Dir.chdir(repo_path) do + system({ 'GL_ID' => gl_id }, path, ref, oldrev, newrev) + end + end end end end From ceeb93fa77783a3fa9a60529195cd187af191bba Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 30 Nov 2015 12:02:44 -0500 Subject: [PATCH 325/410] Update CHANGELOG [ci skip] --- CHANGELOG | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 259a81f42d..1e8fb67032 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,14 +1,17 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.3.0 (unreleased) - - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu) - - Ensure cached application settings are refreshed at startup (Stan Hu) - - Fix 404 in redirection after removing a project (Stan Hu) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - Fix 500 error when update group member permission - - Fix: Raw private snippets access workflow - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) +v 8.2.2 + - Fix 404 in redirection after removing a project (Stan Hu) + - Ensure cached application settings are refreshed at startup (Stan Hu) + - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu) + - Fix: Raw private snippets access workflow + - Prevent "413 Request entity too large" errors when pushing large files with LFS + v 8.2.1 - Forcefully update builds that didn't want to update with state machine - Fix: saving GitLabCiService as Admin Template From 7ba00b50827d8d30ea4c54acb2623d6fb387dcf1 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 30 Nov 2015 19:04:44 +0100 Subject: [PATCH 326/410] Use gitlab-shell 2.6.8 in update guide --- doc/update/8.1-to-8.2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/update/8.1-to-8.2.md b/doc/update/8.1-to-8.2.md index b57e999cfb..7b228d6a22 100644 --- a/doc/update/8.1-to-8.2.md +++ b/doc/update/8.1-to-8.2.md @@ -68,7 +68,7 @@ sudo -u git -H git checkout 8-2-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v2.6.7 +sudo -u git -H git checkout v2.6.8 ``` ### 5. Replace gitlab-git-http-server with gitlab-workhorse From a9642ba046db9aa4490d3361b32cdba48b3fbb9c Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 30 Nov 2015 19:05:28 +0100 Subject: [PATCH 327/410] Install gitlab-shell 2.6.8 --- doc/install/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 3f5c03a890..6164778060 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -312,7 +312,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da GitLab Shell is an SSH access and repository management software developed specially for GitLab. # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed): - sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.7] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production + sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.8] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production # By default, the gitlab-shell config is generated from your main GitLab config. # You can review (and modify) the gitlab-shell config as follows: From 8c4a3c77d87e89bf3fd237fef49fc87fb6170d86 Mon Sep 17 00:00:00 2001 From: Minsik Yoon Date: Mon, 30 Nov 2015 18:30:44 +0900 Subject: [PATCH 328/410] Add ignore whitespace change option to commit view --- CHANGELOG | 1 + app/controllers/projects/commit_controller.rb | 7 ++++++- spec/controllers/commit_controller_spec.rb | 15 +++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 090b54f41a..35bb9951d8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ v 8.3.0 (unreleased) - Fix 500 error when update group member permission - Fix: Raw private snippets access workflow - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) + - Add ignore whitespace change option to commit view v 8.2.1 - Forcefully update builds that didn't want to update with state machine diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index deefdd7666..3f137440e2 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -67,7 +67,12 @@ class Projects::CommitController < Projects::ApplicationController end def define_show_vars - @diffs = commit.diffs + if params[:w].to_i == 1 + @diffs = commit.diffs({ ignore_whitespace_change: true }) + else + @diffs = commit.diffs + end + @notes_count = commit.notes.count @builds = ci_commit.builds if ci_commit diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb index bb3d87f384..5337a69e84 100644 --- a/spec/controllers/commit_controller_spec.rb +++ b/spec/controllers/commit_controller_spec.rb @@ -69,6 +69,21 @@ describe Projects::CommitController do expect(response.body).to start_with("diff --git") end + + it "should really only be a git diff without whitespace changes" do + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: '66eceea0db202bb39c4e445e8ca28689645366c5', + # id: commit.id, + format: format, + w: 1) + + expect(response.body).to start_with("diff --git") + # without whitespace option, there are more than 2 diff_splits + diff_splits = assigns(:diffs)[0].diff.split("\n") + expect(diff_splits.length).to be <= 2 + end end describe "as patch" do From 5f6117c0aa391a6e9c96493ca01a8a23eb40f0cd Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Thu, 5 Nov 2015 16:53:05 +0100 Subject: [PATCH 329/410] update ci docs with yml reason and better quickstart --- doc/ci/quick_start/README.md | 202 ++++++++++++++++++++++++----------- doc/ci/yaml/README.md | 30 +++--- 2 files changed, 156 insertions(+), 76 deletions(-) diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index d69064a91f..74626c8f8a 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -1,44 +1,62 @@ # Quick Start -To start building projects with GitLab CI a few steps needs to be done. +GitLab Continuous Integration (CI) is fully integrated into GitLab itself. You +only need to enable it in the Services settings of your project. -## 1. Install GitLab and CI +This guide assumes that you: -First you need to have a working GitLab and GitLab CI instance. +- have a working GitLab instance of version 8.0 or higher or are using + [GitLab.com](https://gitlab.com/users/sign_in) +- have a project in GitLab that you would like to use CI for -You can omit this step if you use [GitLab.com](https://GitLab.com/). +In brief, the steps needed to have a working CI can be summed up to: -## 2. Create repository on GitLab +1. Create a new project +1. Add `.gitlab-ci.yml` to the git repository and push to GitLab +1. Configure a Runner -Once you login on your GitLab add a new repository where you will store your source code. -Push your application to that repository. +From there on, on every push to your git repository the build will be +automagically started by the Runner and will appear under the project's +`/builds` page. -## 3. Add project to CI +Now, let's break it down to pieces and work on solving the GitLab CI puzzle. -The next part is to login to GitLab CI. -Point your browser to the URL you have set GitLab or use [gitlab.com/ci](https://gitlab.com/ci/). +## 1. Creating a `.gitlab-ci.yml` file -On the first screen you will see a list of GitLab's projects that you have access to: + **GitLab CI** service is enabled automatically on the first push of a + `.gitlab-ci.yml` file in your repository and this is the recommended way. -![Projects](projects.png) +For other methods read [how to enable the GitLab CI service](../enable_ci.md). -Click **Add Project to CI**. -This will create project in CI and authorize GitLab CI to fetch sources from GitLab. +### What is `.gitlab-ci.yml` -> GitLab CI creates unique token that is used to configure GitLab CI service in GitLab. -> This token allows to access GitLab's repository and configures GitLab to trigger GitLab CI webhook on **Push events** and **Tag push events**. -> You can see that token by going to Project's Settings > Services > GitLab CI. -> You will see there token, the same token is assigned in GitLab CI settings of project. +The `.gitlab-ci.yml` file is where you configure what CI does with your project. +It lives in the root of your repository. -## 4. Create project's configuration - .gitlab-ci.yml +On any push to your repository, GitLab will look for the `.gitlab-ci.yml` +file and start builds on _Runners_ according to the contents of the file, +for that commit. -The next: You have to define how your project will be built. -GitLab CI uses [YAML](https://en.wikipedia.org/wiki/YAML) file to store build configuration. -You need to create `.gitlab-ci.yml` in root directory of your repository: +Because `.gitlab-ci.yml` is in the repository, it is version controlled, +old versions still build succesfully, forks can easily make use of CI, +branches can have separate builds and you have a single source of truth for CI. +You can read more about the reasons why we are using `.gitlab-ci.yml` +[in our blog about it][blog-ci]. + +`.gitlab-ci.yml` is a [YAML](https://en.wikipedia.org/wiki/YAML) file. + +### Creating a simple `.gitlab-ci.yml` file + +You need to create a file named `.gitlab-ci.yml` in the root directory of your +repository. Below is an example for a Ruby on Rails project. ```yaml before_script: - - bundle install + - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs + - ruby -v + - which ruby + - gem install bundler --no-ri --no-rdoc + - bundle install --jobs $(nproc) "${FLAGS[@]}" rspec: script: @@ -49,71 +67,129 @@ rubocop: - bundle exec rubocop ``` -This is the simplest possible build configuration that will work for most Ruby applications: -1. Define two jobs `rspec` and `rubocop` with two different commands to be executed. -1. Before every job execute commands defined by `before_script`. +This is the simplest possible build configuration that will work for most Ruby +applications: -The `.gitlab-ci.yml` defines set of jobs with constrains how and when they should be run. -The jobs are defined as top-level elements with name and always have to contain the `script`. -Jobs are used to create builds, which are then picked by [runners](../runners/README.md) and executed within environment of the runner. -What is important that each job is run independently from each other. +1. Define two jobs `rspec` and `rubocop` with different commands to be executed. +1. Before every job, the commands defined by `before_script` are executed. -For more information and complete `.gitlab-ci.yml` syntax, please check the [Configuring project (.gitlab-ci.yml)](../yaml/README.md). +The `.gitlab-ci.yml` file defines sets of jobs with constraints of how and when +they should be run. The jobs are defined as top-level elements with a name and +always have to contain the `script` name. Jobs are used to create builds, +which are then picked by [Runners](../runners/README.md) and executed within +the environment of the Runner. -## 5. Add file and push .gitlab-ci.yml to repository +What is important is that each job is run independently from each other. -Once you created `.gitlab-ci.yml` you should add it to git repository and push it to GitLab. +If you want to check whether your `.gitlab-ci.yml` file is valid, there is a +Lint tool under the page `/ci/lint` of your GitLab instance. You can also find +the link under **Settings** -> **CI settings** in your project. + +For more information and a complete `.gitlab-ci.yml` syntax, please check +[the documentation on .gitlab-ci.yml](../yaml/README.md). + +### Push `.gitlab-ci.yml` to GitLab + +Once you've created `.gitlab-ci.yml`, you should add it to your git repository +and push it to GitLab. ```bash git add .gitlab-ci.yml -git commit +git commit -m "Add .gitlab-ci.yml" git push origin master ``` -If you refresh the project's page on GitLab CI you will notice a one new commit: +Now if you go to the **Builds** page you will see that the builds are pending. -![](new_commit.png) +You can also go to the **Commits** page and notice the little clock icon next +to the commit SHA. -However the commit has status **pending** which means that commit was not yet picked by runner. +![New commit pending](img/new_commit.png) -## 6. Configure runner +Clicking on the clock icon you will be directed to the builds page for that +specific commit. -In GitLab CI, Runners run your builds. -A runner is a machine (can be virtual, bare-metal or VPS) that picks up builds through the coordinator API of GitLab CI. +![Single commit builds page](img/single_commit_status_pending.png) -A runner can be specific to a certain project or serve any project in GitLab CI. -A runner that serves all projects is called a shared runner. -More information about different runner types can be found in [Configuring runner](../runners/README.md). +Notice that there are two jobs pending which are named after what we wrote in +`.gitlab-ci.yml`. The red triangle indicates that there is no Runner configured +yet for these builds. -To check if you have runners assigned to your project go to **Runners**. You will find there information how to setup project specific runner: +The next step is to configure a Runner so that it picks the pending jobs. -1. Install GitLab Runner software. Checkout the [GitLab Runner](https://about.gitlab.com/gitlab-ci/#gitlab-runner) section to install it. -1. Specify following URL during runner setup: https://gitlab.com/ci/ -1. Use the following registration token during setup: TOKEN +## 2. Configuring a Runner -If you do it correctly your runner should be shown under **Runners activated for this project**: +In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`. +A Runner can be a virtual machine, a VPS, a bare-metal machine, a docker +container or even a cluster of containers. GitLab and the Runners communicate +through an API, so the only needed requirement is that the machine on which the +Runner is configured to have Internet access. -![](runners_activated.png) +A Runner can be specific to a certain project or serve multiple projects in +GitLab. If it serves all projects it's called a _Shared Runner_. -### Shared runners +Find more information about different Runners in the +[Runners](../runners/README.md) documentation. -If you use [gitlab.com/ci](https://gitlab.com/ci/) you can use **Shared runners** provided by GitLab Inc. -These are special virtual machines that are run on GitLab's infrastructure that can build any project. -To enable **Shared runners** you have to go to **Runners** and click **Enable shared runners** for this project. +You can find whether any Runners are assigned to your project by going to +**Settings** -> **Runners**. -## 7. Check status of commit +Setting up a Runner is easy and straightforward. The official Runner supported +by GitLab is written in Go and can be found at +. -If everything went OK and you go to commit, the status of the commit should change from **pending** to either **running**, **success** or **failed**. +In order to have a functional Runner you need to: -![](commit_status.png) +1. [Install it][runner-install] +2. [Configure it](../runners/README.md#registering-a-specific-runner) -You can click **Build ID** to view build log for specific job. +For other types of unofficial Runners written in other languages, see the +[instructions for the various GitLab Runners](https://about.gitlab.com/gitlab-ci/#gitlab-runner). -## 8. Congratulations! +Once the Runner has been set up, you should see it on the Runners page of your +project, following **Settings** -> **Runners**. -You managed to build your first project using GitLab CI. -You may need to tune your `.gitlab-ci.yml` file to implement build plan for your project. -A few examples how it can be done you can find on [Examples](../examples/README.md) page. +![Activated runners](img/runners_activated.png) -GitLab CI also offers **the Lint** tool to verify validity of your `.gitlab-ci.yml` which can be useful to troubleshoot potential problems. -The Lint is available from project's settings or by adding `/lint` to GitLab CI url. +### Shared Runners + +If you use [GitLab.com](https://gitlab.com/) you can use **Shared Runners** +provided by GitLab Inc. + +These are special virtual machines that are run on GitLab's infrastructure that +can build any project. + +To enable **Shared Runners** you have to go to your project's +**Settings** -> **Runners** and click **Enable shared runners**. + +[Read more on Shared Runners](../runners/README.md). + +## 3. Seeing the status of your build + +After configuring the Runner succesfully, you should see the status of your +last commit change from _pending_ to either _running_, _success_ or _failed_. + +You can view all builds, by going to the **Builds** page in your project. + +![Commit status](img/builds_status.png) + +By clicking on a Build ID, you will be able to see the log of that build. +This is important to diagnose why a build failed or acted differently than +you expected. + +![Build log](img/build_log.png) + +You are also able to view the status of any commit in the various pages in +GitLab, such as **Commits** and **Merge Requests**. + +## Next steps + +Awesome! You started using CI in GitLab! + +Next you can look into doing more with the CI. Many people are using GitLab +to package, containerize, test and deploy software. + +We have a number of [examples](../examples/README.md) available. + +[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation +[blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/ diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 3dbf1afc7a..9ee26c41e6 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -20,6 +20,22 @@ Of course a command can execute code directly (`./configure;make;make install`) Jobs are used to create builds, which are then picked up by [runners](../runners/README.md) and executed within the environment of the runner. What is important, is that each job is run independently from each other. +## Why `.gitlab-ci.yml` + +By placing a single configuration file in the root of your repository, +it is version controlled and you get all the advantages of git. + +In addition, builds for older versions of the repository will work just fine, +as GitLab look at the `.gitlab-ci.yml` of the pushed commit. +This means that forks also build without any problem. + +You can even set up different builds for different branches. This allows you +to only deploy the `production` branch, for instance. + +By having a single source of truth, everyone can view and contribute to the +stability of your CI builds, eventually improving the quality of your development +cycle. + ## .gitlab-ci.yml The YAML syntax allows for using more complex job specifications than in the above example: @@ -185,7 +201,7 @@ This are two parameters that allow for setting a refs policy to limit when jobs There are a few rules that apply to usage of refs policy: -1. `only` and `except` are inclusive. If both `only` and `except` are defined in job specification the ref is filtered by `only` and `except`. +1. `only` and `except` are exclusive. If both `only` and `except` are defined in job specification only `only` is taken into account. 1. `only` and `except` allow for using the regexp expressions. 1. `only` and `except` allow for using special keywords: `branches` and `tags`. These names can be used for example to exclude all tags and all branches. @@ -198,18 +214,6 @@ job: - branches # use special keyword ``` -1. `only` and `except` allow for specify repository path to filter jobs for forks. -The repository path can be used to have jobs executed only for parent repository. - -```yaml -job: - only: - - branches@gitlab-org/gitlab-ce - except: - - master@gitlab-org/gitlab-ce -``` -The above will run `job` for all branches on `gitlab-org/gitlab-ce`, except master . - ### tags `tags` is used to select specific runners from the list of all runners that are allowed to run this project. From 2c30d11e80a4bb4112d01be7e1fbe51a321b3beb Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 5 Nov 2015 22:49:57 +0200 Subject: [PATCH 330/410] Clean up intro --- doc/ci/quick_start/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index 74626c8f8a..92dad2be3e 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -16,8 +16,8 @@ In brief, the steps needed to have a working CI can be summed up to: 1. Configure a Runner From there on, on every push to your git repository the build will be -automagically started by the Runner and will appear under the project's -`/builds` page. +automagically started by the runner and will appear under the project's `/builds` +page. Now, let's break it down to pieces and work on solving the GitLab CI puzzle. From 1058652a920d1106d29452aadfef953937a188e5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 6 Nov 2015 10:43:28 +0200 Subject: [PATCH 331/410] Add section of enabling GitLab CI --- doc/ci/quick_start/README.md | 7 +++++ doc/ci/quick_start/builds_tab.png | Bin 0 -> 3845 bytes doc/ci/quick_start/ci_service_disabled.png | Bin 0 -> 3598 bytes doc/ci/quick_start/ci_service_enabled.png | Bin 0 -> 3545 bytes doc/ci/quick_start/ci_service_mark_active.png | Bin 0 -> 17193 bytes doc/ci/quick_start/enable_ci.md | 24 ++++++++++++++++++ 6 files changed, 31 insertions(+) create mode 100644 doc/ci/quick_start/builds_tab.png create mode 100644 doc/ci/quick_start/ci_service_disabled.png create mode 100644 doc/ci/quick_start/ci_service_enabled.png create mode 100644 doc/ci/quick_start/ci_service_mark_active.png create mode 100644 doc/ci/quick_start/enable_ci.md diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index 92dad2be3e..d45ad3e5e2 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -21,6 +21,13 @@ page. Now, let's break it down to pieces and work on solving the GitLab CI puzzle. +## 1. Enable GitLab CI + +After creating a new project, the first thing to do is enable the **GitLab CI** +service in your project's settings if it isn't already enabled. + +Read [how to enable the GitLab CI service](enable_ci.md). + ## 1. Creating a `.gitlab-ci.yml` file **GitLab CI** service is enabled automatically on the first push of a diff --git a/doc/ci/quick_start/builds_tab.png b/doc/ci/quick_start/builds_tab.png new file mode 100644 index 0000000000000000000000000000000000000000..d088b8b329dc5dca75513e4fbe27d2323203d770 GIT binary patch literal 3845 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 literal 0 HcmV?d00001 diff --git a/doc/ci/quick_start/ci_service_enabled.png b/doc/ci/quick_start/ci_service_enabled.png new file mode 100644 index 0000000000000000000000000000000000000000..dfe7488c1ba50d0bbf43523a20ab64f6dceb0eff GIT binary patch literal 3545 zcmb_f_d6Tf8&_(!8l#G&g4%m@q>Y%>gxaFG#^qjB)M|@}5d^hKmtA5LT03T~+S-a) zwTf7k+A1-MFZX``g!{|+eBSpt=Xsy!^PKaZ_Y-Ywh+t;}veD7evFqw+o6^zI15UXH z3)AVEQT;~wG@qrbt!ajSyOyz!q_EXtI;ZKn)9LJ zG0qo(Ig(LA+ML>qoF@FaWG*qRq;xdbQ<11TkhTpSdrl77e+g4IOe$CzE8Cb?ZQVSw zER3Nw9DeuOSo0;(+`;NN zGWHalZ=@ypZ~(@~$4$-6|A~R|;(mU9$DMa|b)z}7ScJnvrW<{curu1BdKnOYN(Mv8 z*t6Km_@A`)g&5|ujn0}|8mGV^CkZ}-9L7$DQ_!l;LJOTj)Rq6@aE@L_d;Zs*SD1&H z9jjnz5xws(T-2ZIwN>cXe?{HY={&2XW5H>+CI+G@jJ?BGYwb|?Afjlpq0XCWKf@BCKOZ|$ijLHSzwqv%IMlS$u5@( z$FJ?>z7gSApKg@cLGtztl(YQN;O6Js=%80L>C|OW_3)GLq*hY1rcUSGpf6h1gZ9Ks zaiO#_qE26^tl!V}xoukV%qxpSjO@I4XWvQ{ky0lNKB(gW2wvnTga(`F5^4moF_AJB z!O7tZ&xdH!!rLEPW?s^mFxRegn=bZe)EFLWXR68DdAz9h9CFm|C{%uE8U8u*wdgLd zJd@lJ0tfC5V+KrMXg8P?ZSiY;)1$vlC~B5(5|3o@3H%Cja$}22U(%<^?TIoLjwbLV z?F67qSY(9W=Ml?s>t<5G)Px$!J*CFpq?No(-~|+>;qK#igHd8&MTS9Sk|Ge8{>B-- zMWj6(Gg^E$mirKQRaIW^+4kaGJ3_5YX39pC)G}g}w0u%KO|z8)%lp$aYPaiQ)L(4J zgbhvj>)rB{OL`^XsJRslv$bjP{^O%Mznq_pg`SsJRW*^QYhqI2Hu2kbU|UjdT5RXl zaA?x^VJ#OIT5_Yr-oR_irbq6zUlB;;Ys8JX*J7EixE3XD(|y-OS<95UXhDE`nUvhx zw}>VynCJ$ceE&9I&5|XYC8m%Rp29C(zO=yOY+EaIeUjQ>AIXBZes13@i55)dna1mxvv-ZIK@{xdti5@iWB10rh@};-?fo&48#PxL zz29FF<_7$APr)^s*)3<)BdyeCm+9vPUWG##2`a+DBk=Rc&9W2QPO%BbLRDSsFY>9u z|6D)JPLoSf^oxLada3CW_=h&a6!$Igw9y%NcV7l7o}BIbANV^X`E|NP zE*zH})uZ&P*}0;+x*Cnky)M1~G%~Lszl6A;G_UrWff+z>&`VnC>1K%RMKDB2-z{{q zaR49P&Og@$$H+ILR8s|1&_BvA0t6Q)5W!=-$!^Jm4(iC^o*z?dN$%c()CcP-zOW#9 z((7_gk;F1SRuFQC7v^`(Q&xa~tgSe~EMVu*&!}EhRJ`Oj5R|^K(2IQa=56s%Y}D2F zLtQs&4X1qzFz@$@5rxmJKk?9Z-Fka$hU9I}A3a}*B@Sx|@LzssJ65W8`kvean$r(7 z?g)P{>EFWNE2(Zi$?otnc=I?iJ{>Ag1hv%Ojg)c!+5fhAP_21lKo@pzkWXrSZ@#;zhhXHb6u;osJPfX=cYn1k@Di=z?L<_5QY6F#U9)?gU1<; zL2lZb2;87=U7M_REL?`=Ioum_YJ5gAvX<@B^n#(=We*V5!jfesUF>+DrAAn zYun-`XVQt7)iXi&8YNx?jfCqy9tc56PuE*VryU$zNC;ZtQr=QTPpT6;7{K$Z9_5Hu zmgc2hLRe&pmN@wH+oQFqlbsC-+Qv-n7pKF1{D{+Mt-9SAw$8g7S=6cd{?9OIRp=$g zWgEr#4$tWZv9O4^&4jFX8Plm%Edah4U0IglzbnDk1^?yv%GWSXTxQf|lBmXW~Nle?2{-Rk_O+rAS!nqMQ#fW656eB@~rm{ZNIoKH|6js&M~$< zu?4kxVT@Vt>>jLASgIsa?y!oVHnGI`mbxpKFk|X6$<}vi&F2L^!VQhFKD!fI8@?VL z?YwqkMWj#W?_82Iky*_YPL^-WA>En~w+V3x!;GzhfYC_ww}T_p?*25Ev*s8O8@PH> z`@GqA7zz6Y9%!>)8;%+D17hio9Qo{vxkbcty?*i9m@m9HbuEA7&7c(8j+?|!;Viu% zp)DZ6j8X`L=JNy~a^BnHa!g4V>?JMoNQW%*x*4~j ztZZ_i-SS0cvX&b>d}us*Ra$x{JE7@oaF)NkUlT%Oj|=d}oqfs?H>ZO*fn;4X zUfWy2T{vUV*}{+T;6M0|6R^afC;1(00{`U8+ESqZzXU3rkdN z>K5x`0}!Zt(=du)r|(&-`$ej5mhY>j=TzOw+Akjlr9h4D*g+6IOJ+QTIXGAiYKt>e z@x9kzCp(OgPGEy6ikZzz9%k3C{Q6yy9MJUPSNMI%tkZlYik>kGkB z&3b97pqXaRRmrIljML;;D*>F*w4|yrC2%>X5pLo1Y?Vh6$SmwJQN`O5Apo=S2ON2u4&A$sORdK$xK36lTWZU2Vsl89KW>|P` zF_YxW6s{LI1feXNJi?#;?93b-KNKreKq~!Cof5@9?hM|RKLWuUqJp`oCI^vJY|HGk z`7ZW-Y570nzsK3b{X6(J~@DsSz$VjfT ztsl%53HgiR8YN)q-FYH`eb$9;q(zAbaH1F*o>lb;aQTXdIY?&fqwohAIz7fEjk$t# zmC5F%4}oXR9K#qWdtF%YgKx)~!gZX0hTUutaBF95vRUKbF3bT^PwcK>>{fZ=zy4aA z;2^g(c!$vtbiuUqL)}eG=dk$|{F&9lqM-)Kzh|yQf@po8n;b^P`yi0t_f)%5D}#&Q zHk)(j{e3zBlNePtCE!Nx5CI&t3wJuDG?VA4^NqwFf4v42|j5TiAl-n z0>^W5=H_K*XE#oZXoJp1`4#%j`%m7vJ4l0Y$8#8l>=Z-sJemJ;ivLgjg9TYwMn*3t zHrC}|XCtbjGTDO8`oG~(76a!jkasS}Ha;9GyY_tUv8{=#LI zR+aA`AE_ll&TRoc{_J!dxAwQ+j+E=D;)E=UwyeDJk?&XXE;`jmhWsfWu7ahm16J-l z_1EnUNf6-Eg}Z{SGBUS5+kk=VKOJaQ0j?c7FWx94bJpuPAgav69TU5Of1bZYuLoq+ z`f<~Pe}9v&EY#ICXlA(9bBfE=CLagB@1Khf*Noe#q=gdkPWyIbwamQ&uI9xH2G-T1 z#adGMUdY5`S8g_SgN#h2ZxSRGho|VQvNebqP8Dq5sH-Qj&ZbB~-ari;r>db*gE^W? zef8>4MFih1RJx25erF!7a^6ZCE`4>Gm0E0)4B1OPY@YD3jWs<~ zGtF$75Wc^^eYV*eYj|V=L5_^Sg9L1MRT~_ zDU{;AD#E90M|iwvwxtlYPB@kXX^p76MD&+0iuW@r{lo`Lu7aODv1p$=5ZZ3U-}#0s z2BOI(Q;`&_&$dVi4pu-8T~IYJUl9)N?mQ zGRth9)j}?Q^APd|Di%ZC=;*>*KEA#~&0*WJjmFl=oJIBG0r5VV z#FNJs?VqnQ+wsLEFH?37$N)V^&?GhvwC($<(Wnr3%Dr~V zV09prm6>l!)Pyfg`D*O$eB)Sp-Qx7bCBxp*5XoFLV_%@DSzE~s&cPQ6#F_$%fUooy^H!{5UCQ;~1Ihu5;{pR@D}oB`=cG`Tj20rT*~yNK za(eDDs%h_=g>BnxJI-%%`4BXuTwii0Wys)uDRia7DI#V!TET6AFsb`=&6GCyEzm*N4(J3FnaAcL7HoWCyn_vwd#sy*MZPK z*O%@;6}2<5<0gB2{Ns~6YeL*rTic&ODjFIZJ!gO?T!qnSGip!OoYY(rER(1f$W)4p z$x>vtHOMIV1%KI_UHXY8B0c@p&$T9a==sgGH*F>S;E2b0;AZWI4P%!Vj$c2f3Ayq!m9E zS9Wb=OL^x66cj~lZv15~=nJV~e`e|_gUb#sE{ajLVG0a``O;xBN`C@vZ>-_oa1bO~ zoMoa%j>*XQ)eKgmN69FSm>SX*AGguHsGwJ+`NL!tBRetRMXoGso6W;VbU`AameSIj z2)T&4{;enqApt*SEwY<{MtsDbdG5KLS;kbErd*0bPm|)M+@-bxw5xjtF19(P^VO>x zC}vL_C^ond7I03Zra_OUn)J8iOvohaF6cnsq^jR3)$~*`jUObR${Ydy297;RADDhB@6RP3VNG%_tzl||Go9L?_ zhAI}69`5fPpTy@&?qA&6rev1^A0Z{%oK0SlEDRAxdX)Gx&GyFj*VdG-;&Ou<6m&K3 z1_3UlpF>DX{!YHz8UD;)xu7d->iXE%Gn^oQG|^{EA}pP#qnew5_`dX)`90 zG=tFnUQr>&Rr6f!MG45Nk5v++7cFzPna-N1c>DIFWrlI@+>|gIS!52^$TYNiR=|sX( zO~OZ3whFee7puFL%T&e92g|p0 zW}m!X-;*6q-9z5@;Q_`Pc92MalRptEObMbW?3d}(t#lF>my~Faj86Jv>Nm>i$Bx~g z5m)2z5XJ(f$6wp(;>C;0NOJo0Aen9n=#!hpf>aii2_;bb**?^CxEqQbhpcjmP$c^s zGT&kv7Ol>2-m(0V$B5OG3OU-olp(U@zT2mq*@&4z&n5ZA{_M^62H!}$U$NfSRij~H zWfRxm^w06;OI;n59)(&LjAAHIVPC#{M`SAy&X__Y^my6)*8F(N_=>77LM z()8|*xvH2B0e9XweEA-`QbxuVnwd;Bir^3G>tLlq?cn!b+Fp}eptgd)l9H2$4x78j z8Xbwruz~xi9b{A8b>`IWhSVF0YP5d(cs~?2Ts`V)0iqZy9Y(;(a}mVLtSukBAshH>Ma?Z zOsaf+rl1FF6#Lt>|4|FDfTN2}n?C)=7s4@(>MXS+!_b_c_2JS(Yl$9_(G%WTg$?$I zWxQ|)5!d!(^Yrcv>0cOW@M)i)&At;tI6v+x$)l{eK@fn=AP7Cq!1&r^m(IJeZd5>Sh*K>2A$e#3&< zd$ipp?BTwhM25Z``(UpfN0bpmn$UI2f*7B3GrOIx750G4jGdZu zVznfySXKWJ&DnZ^gHQoQ3u>d?J^%?>r*fHX2xm5&aWB``E#FMo+Bz8UApV!2?HSc~ ztt$-gd>1R&ZRoTgAQo&sV_u}wIN4JH&CbwP*Cz@%$_)lX%+Fn(qMcFt(=LO}!&$T$ z-%zTq)2Vod^Mtk^?%Uuc!>FReH8L`nGe`f-(?9MiNp439!yZ4Fc^olmhfU}|11~k3 z@ap=Co7VLU=Ik>_elmh8XHvNt=LyCSoB%Z)vHLuKi3qK0>2_s}&o`hJvP}XA$jGj+ zWVP)i7PO5Ewx7~9b!EREt1LD=>gML=@dQ`rw=qc47a2`{TjSXog-ZTSNvVf(L;Xpq zi(NsLF!)pD@SRrQ&}#F3fhwsQ(KCtHUlb=ph;&VR@s0|}5eUPeyItv0Sdwq+h zmRh#&*Jy&bUiI(1S~i{=T)-mGhBI)Vx}vb{20<22>-_Ew{7<`r?!AP)Lca=8f8r8~ zpV*>?qYNl>k|64YY?_>@Vd3<3W+CHUeVd~(fj_T+SHpRTwpzK<-;8!pYltrs)VQAt zEu&{tWpY;hh4Sf+>v|PSXA*jq8cla9dK0;!lX4KaTYQ9|0nRtbF;QWmPL!Xp1vg>0Nj1PmvTzACch&x9pZp;Z__9D;#pwc^@cA zX=s>1<_{*saIuA&Q(9Xo<~Sp6UzWeY`y?u&#@uiMx^4Rt9=Fh9H$uHY)D|=p@o6>p zFmlO___EbkkT1KmY_oMxMJIY1rj|ph^!zh$Pz@*#&lpb7VTYd2+>*{!m~AB_h7szv zeSDN?0QLk*&&pG+zWe{2n9@ZZu>>)4F~4-HI=JQz7C}s*wze-gO+X##x@Dr`NvC7R z)F2r9xYvAPQh3Zj2`JrPUHTX6*?%eR_%ADGf5(YX@syko=W*Ec_96qf2DJYuPk4JE z;+r3SKTwe7oPS)5c5twl-KiZi6{zj|j;fx$A4t3g33?hhL+3LA?{&0MQd`DY%t76j zygZRcREcBAnTmL`$pls}bwonLmb8dorlY4j+OzJYH?Q*)ONM%mQcjXRr-jQFy@3>mMd%{rN2 zy85+gyAh@S1fBT-WEk~cTKDSfBeBQ$Mxm)RLSLv6i_~rij1gK?Jq6b4YL=No-LK)6 z0WybZ?T*O^vhe#U*>d4IBwpBYiyC-Z9kZAG1nEctt*(?@J8jbMHJn)^pzx*Ba8`^* znR+p|qIzgHxdDU@K4z5(D=aLsnh{&DAhFZIg+(x4q*`K>8?#`GvN3vixdhd?h=AZs z;8JFM#8*?J2`FuPb=Hw3gvaQ}Uje0~Hr=0XhEgf zjV*qHWHn01H1O4>r=hq)Z?10?=R02lfJ*5r zN-H}<&gCnilln04NYhkM&Fh23{9P=3sM` z>xzZYK2mUbX(?FRUl``ZDgkeZCgeiDsKpx&M$VkeHnGP|%~d;!jl)=3CZvoAaR%9k z28&eBOhEVb^c*t=>6j9|fjoXvZE!h*I_8N_n}zz^CskjKsEb{mSf~!1va9>VA6bPO z#j8Nnrmaj3^U2ZlhGWcZ3K@)>P}c#8FXw{pnk=?W_~;D<1mXTm|+A*A%Zf7aAZ`u$N;!>BPg zs;JiM5MC{Q9c|*3M80b79W=j0$z~R|7i^J=+@%40FkQ~nddz-+78?7|L<(Q+8X1k| zUk^<$lnFp%Ptmn%Yvbtgm)v9YhfihV>uyjkF~nl^!vHF9{AqxVp6Y-DdW#5}o>EKz zK6KVz{qGR4|Fa(BKVxk`K7R7ViNikt-)}C}fv0iNBByg_zLPk;cm5H85EuRzF86r-N%5!#2?7sW~08|uavp2rFTCeAug7pWOF>q4i zjptx~ewr7Xnnag%DKh0qsz6>s}XSSU|w}{JgxLlVaq3)l>c_u?p_Abuu|8a?$I5 zoBZ?=2>cm<4@;Z6R<@%xxBMoJOs``(HcI22>Jha{5GKqJP#Etd$glVA37lM+nT3S` z!huvq|5q{Fku*S?USp#QSHZV`8lI@QvWcd$`xvC6NmvuRm|GjZf;aj33J`|Ygx&I6 zEqdR+)U=BhDe3QDeZBX!_Qt%ki*f8qCL=x3fH1AZkIDj$_3YtRm7mU?6Bid&(?bfq z8oH;|p*A*tAfw31%f$$3MKpTl(@AL2*8`~QYrEX}Q1Iw8+F;VCBCx5@QsYv^+csbC zV#|~yV@+a1*I~D_@*f|w;jybk5xYR!M>nS4Ht>wf-%H*#Swxi09OcjHQ*oy9CJA8Y zfcptjH1~as{&AXdBeo+hAS2pR;%kknPz3{Gf*?=$=QDt ztOGYMQe43}JBNYSc7?a3)AKXcks=3lhV5LrFH>=VOP;Qg6 zdfq`CfZ8x9*_o)0J^S{iPi17>TMuxEV24sSSZiyWNgH7%oF1uw<44NMAi|O$i%UzH ze9G)@1J+a(7pP1>LizLa^FEyNy*_{a1!)H$SK9B1gp9&#;tUht2ITa-GOIUuBX;tvXN*FYAQiS0Y?pvFVL}lyL^Qu4@Ia4^VOYV@2?wj0K8I zO?$(N#LB7|U1&qv{yH?C;5gg!4OOu)Fsoe)aNSDTBuHK!bOv7E+t$<9HwZh!Kt`N> zU$F2f5uX91qP{c3WSE-gMObsk2>#{EcVNPybo4mQ6|6Dgss~?~*$;A~@#(QS1`*Ob zE6DeGxc0ZRiIz@I=@j|%=4nbs;pT7p3heFd(wNPVG>S|0AhGqNhK4*~MuxC|7{rAu zh7;hxkCTg3K%F1qqBM@bg<}GMymH~7{%dq$V9d8M0&BblDq^n{jJvE&-2QCrte3Ke zL_>EDs*%s%*rbz)81eDvoKH*)Z7p_b>}q#;qpA7bBLB9zwMWck8Ag1~iZTt=qh~uq z>|ft$B^0|=j4GS##TKq22d+rqRz}E%R&yMB^b$-VyEVkyJ71jvfaUFLMz0_*BVz-8 ze#g4D=sGxR?u;~Trl+q8Yp7caomRW2=IFLhT{`gefd1*RvT=dNVGeKGePb9tRFl)JU+W3k^clCJ32aM0t~5N@jhZ>s33{Mnq_JkWm)USntG)nD^PIl znD=`mX^!HaImE`Wp=#__47W%%Y|0IHy3lAePI{&r=mAv3_w56G_5FX}L-~KuSO4e> z-PR$a#oax0Q{Cj`9}mfrUE#WsD-kG+E?{UWh<=!-MZZ!-eUF90oy4&+uVsk`|^i0d6~ zI=^x*dUL2Jcv?i4#?!PX>t+0ap4hMVe5u*#ZH}e=f@@HyqT11;`98lFU*4OllVTlD z1%4b5P()=}b5Uw#Xjy5}u zO~t7sQPo{)GBIHBFoHkO{Pcj)=?U)*y}i9YGxhiJj<^Nho88B(PkVb$#JA~1r(pYD z^nY?x!cXkUY zWO6ROLNpf{R(x&q#MR@Ugh1Jddv?|DRtXJ!XIooDB+A=^0URC_+gJ4pMi16ZPPU)rpj?Hu2q&iM zbPE)(@6@afw2?xsoszERHV-u;B>u6f_8J>7ZQlfYiYU%17!)>TuE5 z33>X#0mhFf?L{f-?%uJ=M!~j;VZ+tYzKPJ$+1c3I4er8TYF+sBX+=Gsfmrxxxq8o{ zM{08sdZ|Q3CdU&9#lIa%rN00AbA=y(b+6fF*%&((yTP8YiLpYXP%U;Pndyp&Gnt-o zfI@d`zyM-gjzK0vx>UnPluCatVuOs2Th*aiS%xE#M}$Ru+u%4wefWUK+;f}r4-(!8 zLm!890?+gPnpYq?tu$uIs>+tRl>j)Cd(UjZMTOI0S!(G-1gdS%o;`!jc87qZBDNYs zWTbfq*c*FdP5^e*9s5^v_%GNObS3V}lDy?{9O+4n^iI=vC)w(*zcl$er}T+s4SK3) zmCo4t2sF@B4ulW=h?!$CGp~cMcUNU)PywJCr)A;CZptsmw1fK)?9~;Wni-#DiMJvdur%v%8;vU70-$UEj z1H+-BV~Ts#qjw})o7A>!p0#O>=N!L`_qLFD^!OeiF+b`A5EW zrqwrKDOfZ-9J*CS9%x7Zm<7PUdHvar-g9J+P4vU1)?S#E6SCG<+%qzx0AZq{n~i{S zdF#aP5VwiYZwsawAIuX5xN`GBI;6U581JA_1<)n^+dCA&gY~_+7`8rbqY2R{Eo{kM zzig8E0HKs4r0BW{sTGwY|WGdcFgy33=y=x}tyb6J_r-mBnUsfFm6 z#aUk#fP-gq!+}<~P^01W_I;{n zZbq5lF)isv@RutO4`Q$I0sOfb_e$0zmK9ja#)_6sw%7@&N1|hE!J*|jUm9work(%L z$$CJK1R|ASYbq-R65W?u+S{XxRf}(ij33#-UsxWgTi`v1xbElVlanEFt$7Jko9}l6 z$@SVg88L@@hkvKCR*nfI&VHJldzKuC9xV(K|j? zSv%v!AA;Xn0Sj{2K#!gd7|E0CwNaF{UQnbTSpaJ?k6G%~2!-cOffS0g^FN>0@FVOi9`#PhZpadd2ij`V zP0Vjpepy>fTYVS|+P~ea}T9 z2rv)}H&jozmEC`LLNP+A;)4y?CHBDFMOmtQ{biKXkanvt)We;P_{6syvVE~In-Z-3 zs}gX}hw^ZcKCDJ5SP+RqR=|oTK3@vwc@i+2()Y)aAp99%hQeV9ZYdEWb!u; zYP706^GKq85m)wU9W+R$b4LFFB|F-J6D#M!L<)`CK^7X=*@z9(`zLmP)@&3YzkJ+; zQivI!v-jySl&Nv(q#q$hBcL{h>&j zI&MgW)jClw${eFP3X|mv9{7rEA6(UT7(&h%p!H4;kU`K zzHnM~r|6&&s>G?0`MRL6V7p07=kf>M-3~-Il6<$?w=+r{1n~vIXY?%G_FI|q!1qhz z1zWw++{Fj=eX+IK?tTi{CfY{w*=6v^88%iQBT(t}@#9(L{_61a+=CENw-wakYOhy@ zM=Klhq`ZCPeRsbxPCp$pwDQ8-ryp{Hr$}(c^ed*79yW>*JzjBw-;6pRB^pK<0#g^N z+x62GBOZU{E_m`N-N?hP zOS#jiCZG0VUDogRR+H?N6e>&_^MN~yJQ0y$?hUaF3&IX4oluLJ(C!UCaZ0_yySB99 z-Oi%I+!c!t$Bd$9AE+O2Zj>}(0>!O$AvG<3&L9gTVUZMQ6YE{>O-VfiJXr&hT>-yE zk~@m34?YcQ9tn>c#23{Z)CzT+(bO=$f+$@12$y;+yhFJ2bEY{pZe~L?GDhDw^C;26 zP_l=lx2CNz6HvnYgZInI2`|R%p9OGngSp2P8cvD$b^rlCucD#nw3BH2T7Ms#at(j7 z-|LiXooCGGGa$RkWS3h>Y^@3Tgb{m0*pVw2uN>_sk49C{GJVSJ^54U;IMY~k25E`(FL$TaR)?zK!r0rVyM5bt{n|stR zJ4TYjh&ebX`I5412+KSMn30)+Uf4zS>b#<&@XZ~Wmuc)1JWk1JZF-uV5oR#=MhhU>xV~Yd?Q7^WKw+xk zc0$MLN^>6_6Y_7|%N>t~i&P1;ZeSv!GR!?8`taOR&#i(d`ugeCg|@pYI~05^M8DU? z7_QGh9J@EU_hq)cbHg}NEEg*be!fy8K?IW~0Ks16riVK49>NusD8g6KGoLxe6^Q6+ zlTAgr{zEb8mq@}LgR;=+X0t~5;1*&;fK=m z%|x!u+#4XstSKTeh$h^Ag{K=w(z7sUATHR_VMl54DkOum@Iz5F8fPp=P@u)x)8^&8 zZ)D?3MLEPs>OpX_FQkSm*A0(;LE!3x;)`IE$I3|q$f+b^@U7P+sHL_jr8nkx?OG8g zqa~=OZ2jy0Qc_GQ9>B{!AUwKIlXKaoFfq=IDpAMLm!WdRuP^DARE~nNE3F3Xun|bE z8R3b%Ts9AU+LPlz^`;lNSh;fvE38%u%I9mnT*CykOFFUQoIfhNB4PqNMCTTZPr235 zgSAI=DvpVa2&;bzNUF!%V_q1qzLET4NQ~&8P7fe=4`fpYd@cFaj>W0}jdM9J z1xk3i(x-wX#R$9JMxBQ~?^X?3DLyx0^StZ1;z)0i_todU@uyoVv6b~1kV1VAVzUl> z#CRg>=UzS9t|M%pqMU#Fr5QhR!{AQRz0i%3`VWCg#E4GOoC^_A z4YWd?3IQv{oujWcu|S4X+R-g3_h_%8LhX0ep_y)9gwxm*dxGsq1iR)Q0|2S+=EK<~ zWXMY)kfYv`ceT#R)D>)3?#IM;`~U7*amG;8&wO0?7klW0QbFG?eOKAH9_%uQ%wY4A z7Sm4VgV{k26t_Dbo7)r-Y<=#kG0a&U_mH z8)uuKe1L(-8*QFeRza_ml9Doxmfe9~@4jZJ+kj7OFG8PkX=q(*>xyc#$)mc5FU7}O z@y(XSFgiT#H`5*Z!2Dg}pRF!uL%UOq$`J-`l>ql3=cxp$p4homW!h3>RZ*I7-O9j`QQiM66 z9e$&=ZFgZ&5oH*;-HHh24|r4!1GGX^TR`(2@K<*t8H1O3wcB}~u)b3HgAAu^dGK-% zZ@8*i8Y>X&0G-CSOzdQ9T%U6V%0zC?e}>$z?V3_P5#P8p4{!f^?NN6mfaiZ_*@Gcj z?Wci}jmOFT3IN+ylhn83jgf+n3AsP7p+S?I1h((TE4Eq@r+c0^w6>~g2edDK!ljnH z`EXAaw`pNJ1x8$Wm`wjpI6_nCCYXPZH@t0kfsDPt^u_*3d4G)vADUBgo?Ji!tZ=x_rT>a~q zIe39@+V3^!cG1#&JCOGNAR{IX!2AX|gvLq|c6x7ELh|1JZk6H>VYW!S^|kfZJ6_8&!{ zGhVUEi(S4V-()A>v^aUtKr_I6CX+}rJLv`fCPYDIu(;hIj5K?h<;ch%9@P&XE&uRT z(X?g90mJtlRMk&r!1}TQBtO`UxTl>?2Vl`ZkJ5gKd&XYNog+$7J$}3rqhyRY;$XNs z0B(r!OewIReQHvg2L)qkll*$4zp_QWU8WJ@9vwu_&His)+4`EbwNKJYBYIpH7(0~% zVmn47eg$V)zvG^Eg#kmthUHVIa0>8$kQV>j!E;?#&0P1V2a9|1H+-KC^MZ17??kg_ zM(&Lvm@lrpWihYw6?~T8v?V52^@8!RKXm1+=xbc91en^t_vI8WxlF6Ti0U4lYhHg# z`QctFLn?Kj24->F1C}H6`~S4=Hkob!rf;v!a(s!=S=te|)o;uk#+{FguQUf^4~LSt zuJ?Js>>S`O_McXwtc_2jnkB#p;_e4VKo4frEBBS|&7L!;sR36o72Ej7+JH#{5gENZ zGu4`*LOpCL-jf`VY^?qy^xwi5!}SNlfi*)xsCWdnxVjM^GBtOAF~3o!5-3-HI(Jjt zQ!rCwR8Zf^X4q%W#J=*#VAa|be;F*wc4n!qaeWG6Q_QDG;o{2@SY-?tXPBI60O>trUUfDA_gVU-Ww{%={w!jcR? zJJv{5=MJ^RrCSR*HiqkDE?@XpCtNzM)vuA;4w^(8Ob9Dd;o7K#aUZn7`zvmv(((%*{+Q+1{$&P z<7tnXs8DWj8C!sN0oP0$c{_Mb>wh4WR1ST$4U9=3$WeLuJ92%BtA)d%i^a&xIrw(Trp#b`{z%8PYgLlRw=p)Yq zKwF(zIdoGbwtIyf_NhxtXJ%Egy{RbTD^+&IjgVn(;U;_|@hA)`oSio+;Ur+meZ`EB z9yg=-+F# z6VVBo6&=^98ai@7V+AWq?Kgb`paxTi2}mKOvmaD&r#~}KdKODY(30%!bklv(1Y+8& zlc6&7;Y%`es`8&Pu0Rq&+h4qgKJbQGDR&z_$hV%+I7gTL12??WbV z!0(>brp)$ySVTKjv3#1k3;Bi2^p~m@bQR>UBzG*=zQ*fsa zF;~Peyki8^)f9>BaI$YBjy zxUE5p*k9PhL4V9fzo@7UozL&?u62Y0EM@|_=?`whK}j5)3*CS}H9G8NSTOqWPM;Gq zum$W-hnO29#*1E+8}E4|XYoz|p3;+K9`B)=NAO-i00T0J_sVAyM%Q5Q`ckx#5Cz?TH2p=<9; z51SG4?|y=}l*iRl^~Vj zVoH@yAg=|-Ec+vudtQK6W_zog*KkH!sgWJ0Q?>Xz%{caNXvQ3Kjb@x~)L%)d2hXgi zkAz(Y-n(t4T|B8f0O(t(rV+(OJ~CroattT zXJr3*)bwvN^h4OSqt2+H)Qi-W(m|%aSatFeX3s*`lqC?k5A-QaXgB73bPL79ZX@O{ znnxp|ZL~(-PlLJF<%-dz_LNN-%}2U^CUNKq1A`m3Y_vc|r1 zL&AcfU3ayNAXq`qriH37NURfagJ|;Z=#H=z`ghn7>?b9;6|KPz7GO716jyU)TWs;R zs8P7Mo`nmDBzJLiX`v*R1^rnGX75Wo_>wv@`o%&GqVG3*agV>bNoi1i^tZoJ2Sp8M z)|S*ksyoU*?2%$HIS;9__K1b+CcI)e@mEaezgxj~Js`*ME8CyYr>mG#_;lT}#@kDn zO~lMQ@0UmM74W<$lY?fGs7}~(fPx^`w#arR&Pvo38oruVhJYoqwZ79kSQ0g3FscH{ z--=QxEmd7I5XF7sR^p_8OvyhRO$w7s9M!zrN=P{9AGr`4QpYL1L7|B^yk0HuyE&zk z$5=W7p~Jxnj8MhkN<00$J*5+MRn%59 zhs*i4a&p;kh046W<>f^K>G4_?x$`JI>ix#U@T=%)A+ z`7d@??Zw$8iR+6yeqkT!=Onq-RKn862bMrVdP^Q#-b-00-Xza67f$M=l@b+|P`r?f z_FsgP!>=M&jBbo^iVeGAFAqx%9~Z$a^m)c9oTlG_D0fSSn?pB+qC~dgC_CE5xXZLuc74#iTQ`~zt;O{RmU(UQI_frkRROO zW^dt}3eI_)c zZEHeYh5;Gtx*u!2?7MZMV#5!4pLP7)>MOhDhsfBYNz|P`{gB$w;y6y}*B^o-Qf$CR zXMeoR%{loJ9e(SF_}xzD#5|cFJCIlL-+BO|Rv>O2!v zwkSMIT?tH$g^Y3T&0gWQT7{3L-)jzMn0n7RrVhqB8kYo=%aQmXvmXZ#lHUd}tzP*+ z|7E@lScmAn`p3TOLq1Y|v2>P=Nx7~gPHw_4Ibp?NeUjN`{b2*IY@MF8)%pV5iWsLS z()OND_L(T*MT)Pd#@;&o;{|pw$96)vFa)k@)013DVGGsrJGKYy+pL0jcC{pu%I}b%BYM4e;XR`u?^5 z7eE)6vG2!eA35z>iZ<+uH#fg>YS3HhxuilphlCFuA~Qweko^vW>|uSmc#Mg|YW=(A z#!T6?!UxXU#u5JK#N(g8+r`6JQlQulalWf!Kb0cLM75*Lk42U?*B7}kKdgE$)QHMd zQ~L&2S8K5Dwm*!l`*NxpZWTs%WX*km;a8tO5;;>a7)VvyQp>ErPZNbKdd-R*GS&ny zCqJ%ZEhXN$=RaJaR(4V7+(f?Dtoq|D-mRU5{LpG}%5cB`V;E>CGtTZvSgR|Q<4ldd z=)3y?F1zMkY1?W-vID!!TR&o`ndLszmxn$k5saVwq1z8nxE7+rd;X6r@&A1*0ZI`F zjB&bguv4FTQB&{{sLB4lUwFD*F>ovG>R+XMN`yQMrvr?C;}m{4sszygKW^ Services** and search for **GitLab CI**. Its state +should be disabled. + +![CI service disabled](ci_service_disabled.png) + +Click on **GitLab CI** to enter its settings, mark it as active and hit +**Save**. + +![Mark CI service as active](ci_service_mark_active.png) + +Do you see that green dot? Then good, the service is now enabled! You can also +check its status under **Services**. + +![CI service enabled](ci_service_enabled.png) From 3b4806bad95e55a40d3efe02e329d271995d8a70 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sat, 7 Nov 2015 09:11:37 +0200 Subject: [PATCH 332/410] Add option to enable GitLab CI via .gitlab-ci.yml --- doc/ci/quick_start/enable_ci.md | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/doc/ci/quick_start/enable_ci.md b/doc/ci/quick_start/enable_ci.md index 6f539c752d..42635e35d5 100644 --- a/doc/ci/quick_start/enable_ci.md +++ b/doc/ci/quick_start/enable_ci.md @@ -1,15 +1,30 @@ # Enable GitLab CI -GitLab Continuous Integration (CI) is fully integrated into GitLab itself. You -only need to enable it in the **Services** settings of your project. +GitLab Continuous Integration (CI) is fully integrated into GitLab itself as +of [version 8.0](https://about.gitlab.com/2015/09/22/gitlab-8-0-released/). -First, head over your project's page that you would like to enable CI for. -If you can see the **Builds** tab in the sidebar, then CI is enabled. +First, head over your project's page that you would like to enable GitLab CI +for. If you can see the **Builds** tab in the sidebar, then GitLab CI is +already enabled and you can skip reading the rest of this guide. ![Builds tab](builds_tab.png) -If not, go to **Settings > Services** and search for **GitLab CI**. Its state -should be disabled. +If not, there are two ways to enable it in your project. + +## Use .gitlab-ci.yml to enable GitLab CI + +GitLab CI will be automatically enabled if you just push a +[`.gitlab-ci.yml`](../yaml/README.md) in your git repository. GitLab will +pick up the change immediately and GitLab CI will be enabled for this project. +This is the recommended way. + +## Manually enable GitLab CI + +The second way is to manually enable it in the project's **Services** settings +and this is also the way to disable it if needed. + +Go to **Settings > Services** and search for **GitLab CI**. Its state should +be disabled. ![CI service disabled](ci_service_disabled.png) From 32a3c3e068e957716eac56a755f44268c07c5957 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 10 Nov 2015 03:26:51 +0200 Subject: [PATCH 333/410] Place images in their own dir --- doc/ci/{quick_start => }/enable_ci.md | 10 +++++----- doc/ci/{quick_start => img}/builds_tab.png | Bin .../ci_service_disabled.png | Bin .../{quick_start => img}/ci_service_enabled.png | Bin .../ci_service_mark_active.png | Bin doc/ci/quick_start/README.md | 16 +++++++--------- doc/ci/quick_start/{ => img}/build_status.png | Bin doc/ci/quick_start/{ => img}/commit_status.png | Bin doc/ci/quick_start/{ => img}/new_commit.png | Bin doc/ci/quick_start/{ => img}/projects.png | Bin doc/ci/quick_start/{ => img}/runners.png | Bin .../quick_start/{ => img}/runners_activated.png | Bin 12 files changed, 12 insertions(+), 14 deletions(-) rename doc/ci/{quick_start => }/enable_ci.md (80%) rename doc/ci/{quick_start => img}/builds_tab.png (100%) rename doc/ci/{quick_start => img}/ci_service_disabled.png (100%) rename doc/ci/{quick_start => img}/ci_service_enabled.png (100%) rename doc/ci/{quick_start => img}/ci_service_mark_active.png (100%) rename doc/ci/quick_start/{ => img}/build_status.png (100%) rename doc/ci/quick_start/{ => img}/commit_status.png (100%) rename doc/ci/quick_start/{ => img}/new_commit.png (100%) rename doc/ci/quick_start/{ => img}/projects.png (100%) rename doc/ci/quick_start/{ => img}/runners.png (100%) rename doc/ci/quick_start/{ => img}/runners_activated.png (100%) diff --git a/doc/ci/quick_start/enable_ci.md b/doc/ci/enable_ci.md similarity index 80% rename from doc/ci/quick_start/enable_ci.md rename to doc/ci/enable_ci.md index 42635e35d5..01c684dabe 100644 --- a/doc/ci/quick_start/enable_ci.md +++ b/doc/ci/enable_ci.md @@ -7,14 +7,14 @@ First, head over your project's page that you would like to enable GitLab CI for. If you can see the **Builds** tab in the sidebar, then GitLab CI is already enabled and you can skip reading the rest of this guide. -![Builds tab](builds_tab.png) +![Builds tab](img/builds_tab.png) If not, there are two ways to enable it in your project. ## Use .gitlab-ci.yml to enable GitLab CI GitLab CI will be automatically enabled if you just push a -[`.gitlab-ci.yml`](../yaml/README.md) in your git repository. GitLab will +[`.gitlab-ci.yml`](yaml/README.md) in your git repository. GitLab will pick up the change immediately and GitLab CI will be enabled for this project. This is the recommended way. @@ -26,14 +26,14 @@ and this is also the way to disable it if needed. Go to **Settings > Services** and search for **GitLab CI**. Its state should be disabled. -![CI service disabled](ci_service_disabled.png) +![CI service disabled](img/ci_service_disabled.png) Click on **GitLab CI** to enter its settings, mark it as active and hit **Save**. -![Mark CI service as active](ci_service_mark_active.png) +![Mark CI service as active](img/ci_service_mark_active.png) Do you see that green dot? Then good, the service is now enabled! You can also check its status under **Services**. -![CI service enabled](ci_service_enabled.png) +![CI service enabled](img/ci_service_enabled.png) diff --git a/doc/ci/quick_start/builds_tab.png b/doc/ci/img/builds_tab.png similarity index 100% rename from doc/ci/quick_start/builds_tab.png rename to doc/ci/img/builds_tab.png diff --git a/doc/ci/quick_start/ci_service_disabled.png b/doc/ci/img/ci_service_disabled.png similarity index 100% rename from doc/ci/quick_start/ci_service_disabled.png rename to doc/ci/img/ci_service_disabled.png diff --git a/doc/ci/quick_start/ci_service_enabled.png b/doc/ci/img/ci_service_enabled.png similarity index 100% rename from doc/ci/quick_start/ci_service_enabled.png rename to doc/ci/img/ci_service_enabled.png diff --git a/doc/ci/quick_start/ci_service_mark_active.png b/doc/ci/img/ci_service_mark_active.png similarity index 100% rename from doc/ci/quick_start/ci_service_mark_active.png rename to doc/ci/img/ci_service_mark_active.png diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index d45ad3e5e2..ce05a6f417 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -16,23 +16,21 @@ In brief, the steps needed to have a working CI can be summed up to: 1. Configure a Runner From there on, on every push to your git repository the build will be -automagically started by the runner and will appear under the project's `/builds` -page. +automagically started by the runner and will appear under the project's +`/builds` page. Now, let's break it down to pieces and work on solving the GitLab CI puzzle. -## 1. Enable GitLab CI - -After creating a new project, the first thing to do is enable the **GitLab CI** -service in your project's settings if it isn't already enabled. - -Read [how to enable the GitLab CI service](enable_ci.md). - ## 1. Creating a `.gitlab-ci.yml` file **GitLab CI** service is enabled automatically on the first push of a `.gitlab-ci.yml` file in your repository and this is the recommended way. +For other methods read [how to enable the GitLab CI service](../enable_ci.md). + + **GitLab CI** service is enabled automatically on the first push of a + `.gitlab-ci.yml` file in your repository and this is the recommended way. + For other methods read [how to enable the GitLab CI service](../enable_ci.md). ### What is `.gitlab-ci.yml` diff --git a/doc/ci/quick_start/build_status.png b/doc/ci/quick_start/img/build_status.png similarity index 100% rename from doc/ci/quick_start/build_status.png rename to doc/ci/quick_start/img/build_status.png diff --git a/doc/ci/quick_start/commit_status.png b/doc/ci/quick_start/img/commit_status.png similarity index 100% rename from doc/ci/quick_start/commit_status.png rename to doc/ci/quick_start/img/commit_status.png diff --git a/doc/ci/quick_start/new_commit.png b/doc/ci/quick_start/img/new_commit.png similarity index 100% rename from doc/ci/quick_start/new_commit.png rename to doc/ci/quick_start/img/new_commit.png diff --git a/doc/ci/quick_start/projects.png b/doc/ci/quick_start/img/projects.png similarity index 100% rename from doc/ci/quick_start/projects.png rename to doc/ci/quick_start/img/projects.png diff --git a/doc/ci/quick_start/runners.png b/doc/ci/quick_start/img/runners.png similarity index 100% rename from doc/ci/quick_start/runners.png rename to doc/ci/quick_start/img/runners.png diff --git a/doc/ci/quick_start/runners_activated.png b/doc/ci/quick_start/img/runners_activated.png similarity index 100% rename from doc/ci/quick_start/runners_activated.png rename to doc/ci/quick_start/img/runners_activated.png From e70fb9cf75d08e8439d94637726dbee7e2324ab6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 10 Nov 2015 03:49:32 +0200 Subject: [PATCH 334/410] New images for the commit build status --- doc/ci/quick_start/README.md | 2 -- doc/ci/quick_start/img/new_commit.png | Bin 47527 -> 9033 bytes .../img/single_commit_status_pending.png | Bin 0 -> 36431 bytes doc/ci/quick_start/img/status_pending.png | Bin 0 -> 19782 bytes 4 files changed, 2 deletions(-) create mode 100644 doc/ci/quick_start/img/single_commit_status_pending.png create mode 100644 doc/ci/quick_start/img/status_pending.png diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index ce05a6f417..073146b9d1 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -120,8 +120,6 @@ Notice that there are two jobs pending which are named after what we wrote in `.gitlab-ci.yml`. The red triangle indicates that there is no Runner configured yet for these builds. -The next step is to configure a Runner so that it picks the pending jobs. - ## 2. Configuring a Runner In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`. diff --git a/doc/ci/quick_start/img/new_commit.png b/doc/ci/quick_start/img/new_commit.png index 3839e893c177ee81865b6791d2b81b664a4dcfc0..3d3c9d5c0bd078333a7bc1e670b9a7dc736a2fcc 100644 GIT binary patch literal 9033 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 A-T(jq literal 47527 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 Ot1Ig$RVrA#`hNgeb)0Ab literal 0 HcmV?d00001 diff --git a/doc/ci/quick_start/img/status_pending.png b/doc/ci/quick_start/img/status_pending.png new file mode 100644 index 0000000000000000000000000000000000000000..a049ec2a5ba560c222078da1c45b3e358ce15dd3 GIT binary patch literal 19782 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 n<4{~;*@nc!x6vFp0QR3*pmN!P_v Date: Tue, 10 Nov 2015 21:16:12 +0200 Subject: [PATCH 335/410] Final touches for the quick start quide --- doc/ci/quick_start/README.md | 6 ++++-- doc/ci/quick_start/img/build_log.png | Bin 0 -> 63272 bytes doc/ci/quick_start/img/builds_status.png | Bin 0 -> 49121 bytes doc/ci/quick_start/img/runners_activated.png | Bin 60769 -> 27597 bytes 4 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 doc/ci/quick_start/img/build_log.png create mode 100644 doc/ci/quick_start/img/builds_status.png diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index 073146b9d1..7771d78d91 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -16,7 +16,7 @@ In brief, the steps needed to have a working CI can be summed up to: 1. Configure a Runner From there on, on every push to your git repository the build will be -automagically started by the runner and will appear under the project's +automagically started by the Runner and will appear under the project's `/builds` page. Now, let's break it down to pieces and work on solving the GitLab CI puzzle. @@ -120,6 +120,8 @@ Notice that there are two jobs pending which are named after what we wrote in `.gitlab-ci.yml`. The red triangle indicates that there is no Runner configured yet for these builds. +The next step is to configure a Runner so that it picks the pending jobs. + ## 2. Configuring a Runner In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`. @@ -136,8 +138,8 @@ Find more information about different Runners in the You can find whether any Runners are assigned to your project by going to **Settings** -> **Runners**. - Setting up a Runner is easy and straightforward. The official Runner supported + by GitLab is written in Go and can be found at . diff --git a/doc/ci/quick_start/img/build_log.png b/doc/ci/quick_start/img/build_log.png new file mode 100644 index 0000000000000000000000000000000000000000..89e6cd40cb61d890b7ee1ce0137c738a18526e74 GIT binary patch literal 63272 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 o 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 E1r+N0pa1{> literal 0 HcmV?d00001 diff --git a/doc/ci/quick_start/img/runners_activated.png b/doc/ci/quick_start/img/runners_activated.png index c934bd12f41dc17d10eb131b233f630b18fc1761..eafcfd6ecd5daa3ebdea96496b2126f756ace99e 100644 GIT binary patch literal 27597 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 DV4kVw From aea5b72faafa6911377cb0c9fc0801269ac59e1e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 11 Nov 2015 09:57:21 +0200 Subject: [PATCH 336/410] Remove old images --- doc/ci/quick_start/img/build_status.png | Bin 62140 -> 0 bytes doc/ci/quick_start/img/commit_status.png | Bin 33492 -> 0 bytes doc/ci/quick_start/img/projects.png | Bin 37014 -> 0 bytes 3 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 doc/ci/quick_start/img/build_status.png delete mode 100644 doc/ci/quick_start/img/commit_status.png delete mode 100644 doc/ci/quick_start/img/projects.png diff --git a/doc/ci/quick_start/img/build_status.png b/doc/ci/quick_start/img/build_status.png deleted file mode 100644 index 333259e6acd301b579ebb7b7682b1df1e635a61a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 62140 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�n!5^ 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 diff --git a/doc/ci/quick_start/img/commit_status.png b/doc/ci/quick_start/img/commit_status.png deleted file mode 100644 index 725b79e6f91d1de8e8240c28f911b2753d92222a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33492 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 diff --git a/doc/ci/quick_start/img/projects.png b/doc/ci/quick_start/img/projects.png deleted file mode 100644 index 0b3430a69dbfafbe71d780c13025226499e82407..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37014 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 Date: Tue, 1 Dec 2015 10:05:24 +0200 Subject: [PATCH 337/410] Clean up quick start quide [ci skip] * Remove references to enabling CI since it it will be deprecated * Revert changes in yaml/README.md, refine it in a separate MR --- doc/ci/enable_ci.md | 39 ---------------- doc/ci/img/ci_service_disabled.png | Bin 3598 -> 0 bytes doc/ci/img/ci_service_enabled.png | Bin 3545 -> 0 bytes doc/ci/img/ci_service_mark_active.png | Bin 17193 -> 0 bytes doc/ci/quick_start/README.md | 61 ++++++++++++-------------- doc/ci/quick_start/img/runners.png | Bin 123048 -> 0 bytes doc/ci/yaml/README.md | 30 ++++++------- 7 files changed, 42 insertions(+), 88 deletions(-) delete mode 100644 doc/ci/enable_ci.md delete mode 100644 doc/ci/img/ci_service_disabled.png delete mode 100644 doc/ci/img/ci_service_enabled.png delete mode 100644 doc/ci/img/ci_service_mark_active.png delete mode 100644 doc/ci/quick_start/img/runners.png diff --git a/doc/ci/enable_ci.md b/doc/ci/enable_ci.md deleted file mode 100644 index 01c684dabe..0000000000 --- a/doc/ci/enable_ci.md +++ /dev/null @@ -1,39 +0,0 @@ -# Enable GitLab CI - -GitLab Continuous Integration (CI) is fully integrated into GitLab itself as -of [version 8.0](https://about.gitlab.com/2015/09/22/gitlab-8-0-released/). - -First, head over your project's page that you would like to enable GitLab CI -for. If you can see the **Builds** tab in the sidebar, then GitLab CI is -already enabled and you can skip reading the rest of this guide. - -![Builds tab](img/builds_tab.png) - -If not, there are two ways to enable it in your project. - -## Use .gitlab-ci.yml to enable GitLab CI - -GitLab CI will be automatically enabled if you just push a -[`.gitlab-ci.yml`](yaml/README.md) in your git repository. GitLab will -pick up the change immediately and GitLab CI will be enabled for this project. -This is the recommended way. - -## Manually enable GitLab CI - -The second way is to manually enable it in the project's **Services** settings -and this is also the way to disable it if needed. - -Go to **Settings > Services** and search for **GitLab CI**. Its state should -be disabled. - -![CI service disabled](img/ci_service_disabled.png) - -Click on **GitLab CI** to enter its settings, mark it as active and hit -**Save**. - -![Mark CI service as active](img/ci_service_mark_active.png) - -Do you see that green dot? Then good, the service is now enabled! You can also -check its status under **Services**. - -![CI service enabled](img/ci_service_enabled.png) diff --git a/doc/ci/img/ci_service_disabled.png b/doc/ci/img/ci_service_disabled.png deleted file mode 100644 index 351de4267a4a32318a065cf3797e7b2ec223135b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3598 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 diff --git a/doc/ci/img/ci_service_enabled.png b/doc/ci/img/ci_service_enabled.png deleted file mode 100644 index dfe7488c1ba50d0bbf43523a20ab64f6dceb0eff..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3545 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 **CI settings** in your project. +the link under **Settings > CI settings** in your project. For more information and a complete `.gitlab-ci.yml` syntax, please check [the documentation on .gitlab-ci.yml](../yaml/README.md). @@ -122,13 +118,13 @@ yet for these builds. The next step is to configure a Runner so that it picks the pending jobs. -## 2. Configuring a Runner +## Configuring a Runner In GitLab, Runners run the builds that you define in `.gitlab-ci.yml`. A Runner can be a virtual machine, a VPS, a bare-metal machine, a docker container or even a cluster of containers. GitLab and the Runners communicate through an API, so the only needed requirement is that the machine on which the -Runner is configured to have Internet access. +Runner is configured to has Internet access. A Runner can be specific to a certain project or serve multiple projects in GitLab. If it serves all projects it's called a _Shared Runner_. @@ -137,22 +133,23 @@ Find more information about different Runners in the [Runners](../runners/README.md) documentation. You can find whether any Runners are assigned to your project by going to -**Settings** -> **Runners**. -Setting up a Runner is easy and straightforward. The official Runner supported - -by GitLab is written in Go and can be found at +**Settings > Runners**. Setting up a Runner is easy and straightforward. The +official Runner supported by GitLab is written in Go and can be found at . -In order to have a functional Runner you need to: +In order to have a functional Runner you need to follow two steps: 1. [Install it][runner-install] 2. [Configure it](../runners/README.md#registering-a-specific-runner) +Follow the links above to set up your own Runner or use a Shared Runner as +described in the next section. + For other types of unofficial Runners written in other languages, see the [instructions for the various GitLab Runners](https://about.gitlab.com/gitlab-ci/#gitlab-runner). Once the Runner has been set up, you should see it on the Runners page of your -project, following **Settings** -> **Runners**. +project, following **Settings > Runners**. ![Activated runners](img/runners_activated.png) @@ -161,15 +158,15 @@ project, following **Settings** -> **Runners**. If you use [GitLab.com](https://gitlab.com/) you can use **Shared Runners** provided by GitLab Inc. -These are special virtual machines that are run on GitLab's infrastructure that -can build any project. +These are special virtual machines that run on GitLab's infrastructure and can +build any project. To enable **Shared Runners** you have to go to your project's -**Settings** -> **Runners** and click **Enable shared runners**. +**Settings > Runners** and click **Enable shared runners**. [Read more on Shared Runners](../runners/README.md). -## 3. Seeing the status of your build +## Seeing the status of your build After configuring the Runner succesfully, you should see the status of your last commit change from _pending_ to either _running_, _success_ or _failed_. @@ -194,7 +191,7 @@ Awesome! You started using CI in GitLab! Next you can look into doing more with the CI. Many people are using GitLab to package, containerize, test and deploy software. -We have a number of [examples](../examples/README.md) available. +Visit our various languages examples at . [runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation [blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/ diff --git a/doc/ci/quick_start/img/runners.png b/doc/ci/quick_start/img/runners.png deleted file mode 100644 index 25b4046bc00a8843d2dc68125ce8a9864e541df3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 123048 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

&I#>0_{^-L0SDa6;E-#{Wt}}|6@wPdcqQ{^ zA>~-*wZ7GV_Cj<^h={@KUc3lpaALi$Kx-9=%MLwvlDPz;Kx8CH$dLZ^7mux_7Km`* zG!RxAh!GzIf$epJLZjA+0qImWaI{|fv==L5LyEbuh+fbhLSViL)<`XJ;t{?QLVwlk zWeW@>7I*|5L3H>^dGsC*T+wB`Y|&01l_N6Yc`7ylXFN3+7?Bym7m+1ZF3HDxzj=9{ zOo$WCD*fEPUX890H$9)Es`A`@rMtuapi#evfIsuXfj6oCOeEO48>e|YUm;B6UNn26 zRv3K5U5eCH;wvPy&#>%krvBwoP>|5s>P4@qHgh28iB~$eKesqLcmW`D{y&1w#IKtn z93jBwv4@L(aMm9Ir#8vO?GmlvM`O90J7b&nQir1QYNaRoZNCg|>2dd+NO(57nh2wr z8vXc}dQ2<+shv`~PVR#!amCtr4`-_*;IhJL3zbt#ieRfm`7{yn5#p z;HI9gOg*p98QONyir;cU@HB65K_jv(PNeCbBJ88eL8)$!+tZJ5nNKp`nj%d1kzEst zquS$AyRFuwc?`DG8zuAM^fY(-6$-Ld2y;{T<6(X2eS$1+cYJ@eW6S3$7~F1nsO zjM?T=gx2b9G>bi+lt{!@NrpI2v<^{7{<6 zjO^~S)zPcuB&%ey>o*(RM%qy1qI4{HiBtl!s#yYu=oiHuF70YeLa(Cpu z^17Fsvq~MU*#RvQovw!y(kpCSoLWLX9$(O8*Wp@1nq-OUQ-sZema6pe=%}3P_I(q= z`u)VTt##MJEK6N0B;V#fFO#bHjWV{rd`+pzEgw~9*j1O9vhZxlp$;17hhhW_R%7Lk zpze?={n}Na5x#boKsBi{%LHxukl>c8jfKYN=2 z=Ho&hA>wu3E3&TFW>1GbMS+Yll}zq@3DzW+R05~vi)}pvD~*Xxo}c~_0iql|<&+|bKuBq= zT>CcGUUnBnf=TA!1G!mr6@d~yQ}o{}>))&B841H>-}OB`o$1WyhaP)}tjLeTpESHG zI9?%{bi*S1-Hr8@^ZqfhVV#RUOyQVY~Kw@RT zNS&5F-Z-+*JnNt{`=d3aRX>JITCbRL!XUJOwAm54LEq666$FrwRWhahqlJWvvJoF% z;PBEiPu{}@*LwPaMprvFRY`}9fpYSD-egas10_WDW;Zawl~K+NYv$GI-q*+Z*_hL| zLJP$G9!3eMmbSz$f~0jxBali zrfG+#p>@+wq2OC2{CW>Yx$!hL2ZM2}wjPb1V!kZF_71(!msZ>8go3;~3-^1MFA|xs`^)2Ukpw9w69jHgWG;$7~4FQuBxWKRwQ-uc6?KV(b8BB zY7J%G{yiUYEOb7o0f7JST?VM$K>al}2IY|3OGvJZ&Q@??u1|{lO;n5MNTL&l12iG6 z)&0)V(v(t7`eX1;8!!7?Yls`fk4F~)E-Pqv+H9exPP12`9+&#(O3S!s{3sEcvZQ%$ zIH_0n{+60gLxJNCICttBasI)*Rho{)(yg{27!$19mMhR_cHt_6CBpjchD)^`w4R<1 z(AbgpB?DWUpAfbc+Eb_A4g*b*6d$G03j*g$?+H`Dy( z*{JA`zN*>*CJrvxd)E9g*xI+Ns5WgXfs;w_b~#vtVc##o>V=U=Et>{rxigUSg>_+J z)vhn8<^c_VEiJmzh@w(b=?HEN`9 z4SKT;RLQ@T0xlqa#+b4GiHMP6TLX(rKlXDobl)AR*5*xruU3xX{A67fpUAz=C;F9n zRgp|~dF`0QgyjQ8$1sDr$Gw0UgFgnH?3heiWF$#xYhauGXkucAS zx_w?8;fU$w$vRO7u^~D|bu*BkCb-TqT7~|l=F!vm=&GPV(6m1;K6g&TVbc~=+Zr>q zw6<=qHqWGRpYJ-KaeFdb5SQ_RZhrR}(uE9x$LV>(VU|z;HnKJesD;wthf77+aV8gE(dmOs}?XIVjqF*nF_FJatNITnHt^w~9excl+rn9NMRKCHvk z!g|{rWz;l&t$D~6iGQZ4Fk$O*Pu{7R9r|zeF>)cdzptcF$(_;lwK$y09y#XLG{ALR z*Lm+hZo}EL#OKP6lxwI2?c9)oL+o~Fh?C(yII!fBn08jFzBnESBY+b-p+{~&H6!_|Mt*VOnfK4eXd3!7D3V*st;`y2gWUkb#JbVXZZXMx83pvmvG z#XjSr@kv4Z6laj!wh}{X|3EdUle8YMiIuPqoEfu?#5{!B#)KhESET%j!d$}7t1Iv( zCWaAESb(IGBX|L}n-P6$#7=J|e6{g?#Z*Ecpg;t`B*4z~ikC{Xwqo#J(a-fdgtfk>7yIM`e=~fMF97R5ds3YaD8p;-HoW zljLXn*!Jl<}j zVuFcs5Uo7IZZcosBXO4HYbI6xXt4xLIX2x0lELN_iu6^vyGAtRs27+9zP?>aq<6GE zR%vJu+@f6w4Igkbl+g%jcbj3xHLOCGOl=_Zo$iaTE7vJ&UE_NssmOlce}j5Dl{>TQ zo?cK!HCoN-r|CqppGV(*N3ZX|Rz0!Z*8A?Afc?yzxYK4u{)09j5cuQX{ZOM{*iTDa zBq@;`Wy;N!;X4Al`U>^baur%NrehX-f4{f>R!jysgsZHQrXP}>hkcU_>++fsAPHUL ztNtiK{k$D&VkwN!od0$H9p_f351$8AKw}JTiA_~0y3l3g(Mv==9St5-*QPu%0WUTO z&X&AH)#X=Nk`L^A+?3t$gkB2PlKvK!>0eJ1iAP9eKgC@Uu^BY*al3n$mV}T8E~Hb# zDkZrY3XlqSA|}~34=Pb^XB;zRqZltuiutF*k@WThxEFY_R$Y}OlUne_Tfu%8Qw@Y8 z0i;^)*ov=18q|o+0CXZ3IVOj^{FcVE2+hR)wK^B1{U>2%{YAkw&&y+;p)f<`uo za&&2t2OjQ)2(0FIc@G+bffhw^FI zy!lD~3_{~K*ik-G8!V=cVOL6OC=5o1>UMk|w{Fk5A4<)>l- ziKK1{pd&WywFpT-PxdUE&C*o)B@_th!V#DD?Q(y#i6*D7b%0F45OR9a?Hi9ys@eyk za9A#7C)1*{joO)gqpNNrWsPq)kLJl$wv@=H;Da!7xjFlVP=x$F$8vT3PSc&R{3QEY z5N7iNFwAJ;i@nLO4ncZIBv=T-ik|vbzQfnadr68Cy+!5wxLb=0TV^Nw?8j~Ww4)Y47{FWEX8FEMZM0|F4~(hM)a2>w6S;I=IA9UL3~E6?c`8`QZ0O+)|I z?7G5R2LxPXEcoCj=n|7oA|9oY+ZMoVlzZKj%@WTsxXz4B#FbCf=^Ugb$PLHXi6y8FY!B01T*x(_HWuuecTWy z)#XK=e^wqDkiYFnSgd?q#Ab25HW<||CY}QWc!XgD<26Iu4%h@~NuG3x+x{U(D+urV zDB*~?D6ZTal!#ZR+^k<>o7$9CtYhtxBb`l~pL^zbPEx`9n$M#C*)@xHH?C_OFo~!vK1f zZVgG65&q$#^T9~#YF`r8w5IWwAd0QFr>6jw-OJxd8ucxw`jJM7#53)sMD(MD?rYRa znV=EZ4s0<0C1u4-&YS`vj@y1GM4*4}a*&xb_JuXkw_TRotGGz3lEemZmvqf~)BiPzV7E~h?LPXmP!t&>_ z(8=bBIBDLRNBeq;ey$hhFKNzV|l#7%Mtts!>iYhKmF_z10q$P|_*5F2np{TM#o~amB!Sd>h=(d21QR{v! zahg6R;EKa8P({=Jc z+!}t>s?p>5IhJARB`fkCRC?`JT}~s2UJ-5J?24GuAp?o$-B z)VXltdw%m8mNEXED15Yll8HX>>2MapN_{w@MV@>gh{$kkivx?!n#8Pi*lz;V_EIXe z!Z_gz`ZhzY-LO$~>$o+bu(PwPCfCY)&XYq*Cm(Rj*;-okJl`GyGAAaa(`03$NnE6d zR*cyT88U)!Z$))I5q0;RJ;R9Ie*JTm=SK+wh6KU(?Lt>;<+#{Q z8iY$3OBp@O6+OpI#yRQcqW+~+fkjJYxLURdel?`B`}V~Q%2SO1L)wSz3Cx8duh>L) zEfSCVXGpvc;>n|VFWj{r+#lq0mah$QR+cxr_alFt6`ixb3Z+_hX}oaoa}$gz8kTcy z7jFo)G}CTu_wj%@B(z zrv`;aTIO$aR!nXs1cw8ld;O-CNJOTyQ2`3ZGPY9#j%P2M0VPeuE`UiUPPKEG+PWv%x&@kM| z(rp9dfBKNLMM06<ZRvt2LyFhEx&nWp2rjWA`-HP6OTd}v}uH{~n+uV-kr z<5F@Q_%>dNnf#+oO-1Uv6@7@mpAU0P7cua-LO=pKlD`7HS6OB=|*Gmb`q(TIQU|Lz*#2aRkW4HPwae>c4YoUQ!j`NsJ4e;pJ+ zW7qpHV;2*=<^TAoNv_<#$>Q?re3%1==08p${^5E+|La`kt1lnM|K9Ni&!7kCIDGn# z+OFonL4CRu77c~|)t;nFa80_~bXPIQxK<_dZz28vDn$1O=vuk!j~`>?|2*CDX%^V} Z1Sz8|r}y)&xA_A6i3v#yR`Bco`G1c#sD1zd diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 9ee26c41e6..3dbf1afc7a 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -20,22 +20,6 @@ Of course a command can execute code directly (`./configure;make;make install`) Jobs are used to create builds, which are then picked up by [runners](../runners/README.md) and executed within the environment of the runner. What is important, is that each job is run independently from each other. -## Why `.gitlab-ci.yml` - -By placing a single configuration file in the root of your repository, -it is version controlled and you get all the advantages of git. - -In addition, builds for older versions of the repository will work just fine, -as GitLab look at the `.gitlab-ci.yml` of the pushed commit. -This means that forks also build without any problem. - -You can even set up different builds for different branches. This allows you -to only deploy the `production` branch, for instance. - -By having a single source of truth, everyone can view and contribute to the -stability of your CI builds, eventually improving the quality of your development -cycle. - ## .gitlab-ci.yml The YAML syntax allows for using more complex job specifications than in the above example: @@ -201,7 +185,7 @@ This are two parameters that allow for setting a refs policy to limit when jobs There are a few rules that apply to usage of refs policy: -1. `only` and `except` are exclusive. If both `only` and `except` are defined in job specification only `only` is taken into account. +1. `only` and `except` are inclusive. If both `only` and `except` are defined in job specification the ref is filtered by `only` and `except`. 1. `only` and `except` allow for using the regexp expressions. 1. `only` and `except` allow for using special keywords: `branches` and `tags`. These names can be used for example to exclude all tags and all branches. @@ -214,6 +198,18 @@ job: - branches # use special keyword ``` +1. `only` and `except` allow for specify repository path to filter jobs for forks. +The repository path can be used to have jobs executed only for parent repository. + +```yaml +job: + only: + - branches@gitlab-org/gitlab-ce + except: + - master@gitlab-org/gitlab-ce +``` +The above will run `job` for all branches on `gitlab-org/gitlab-ce`, except master . + ### tags `tags` is used to select specific runners from the list of all runners that are allowed to run this project. From 55d0e04ffd3f178f41ae3ef13790eb6882b1ef8e Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 1 Dec 2015 14:26:57 +0100 Subject: [PATCH 338/410] Auto-detect the required gitlab-shell version --- doc/install/installation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 6164778060..618391e16d 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -312,7 +312,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da GitLab Shell is an SSH access and repository management software developed specially for GitLab. # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed): - sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.8] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production + sudo -u git -H bundle exec rake gitlab:shell:install REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production # By default, the gitlab-shell config is generated from your main GitLab config. # You can review (and modify) the gitlab-shell config as follows: From b0ef603f71e3a55981f15944f16595470091c344 Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Tue, 1 Dec 2015 16:21:53 +0100 Subject: [PATCH 339/410] up for grabs label --- CONTRIBUTING.md | 10 ++++++++++ PROCESS.md | 1 + 2 files changed, 11 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 446eb1189a..7f5da063fd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,16 @@ The channels people will reach out on can be found on the [getting help page](ht Sign up for the mailinglist, answer GitLab questions on StackOverflow or respond in the IRC channel. You can also sign up on [CodeTriage](http://www.codetriage.com/gitlabhq/gitlabhq) to help with one issue every day. +## I want to contribute! + +If you want to contribute to GitLab, but are not sure where to start, +look for [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues?milestone_id=&scope=all&sort=created_desc&state=opened&utf8=%E2%9C%93&assignee_id=&author_id=&milestone_title=&label_name=up-for-grabs) +with the label `up-for-grabs`. +These issues will be of reasonable size and challenge, for anyone to start +contributing to GitLab. + +This was inspired by [an article by Kent C. Dodds](https://medium.com/@kentcdodds/first-timers-only-78281ea47455#.i2f363mx4). + ## Issue tracker To get support for your particular problem please use the [getting help channels](https://about.gitlab.com/getting-help/). diff --git a/PROCESS.md b/PROCESS.md index 482ad5fe9e..72fc348144 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -44,6 +44,7 @@ Workflow labels are purposely not very detailed since that would be hard to keep - *UX* needs needs help from a UX designer - *Frontend* needs help from a Front-end engineer - *Graphics* needs help from a Graphics designer +- *up-for-grabs* is an issue suitable for first-time contributors, of reasonable difficulty and size. Not exclusive with other labels. Example workflow: when a UX designer provided a design but it needs frontend work they remove the UX label and add the frontend label. From 9f30f5c63515709adce95eef166e96ea91811e36 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 1 Dec 2015 18:19:33 -0500 Subject: [PATCH 340/410] Make icon-link image transparent --- app/assets/images/icon-link.png | Bin 726 -> 1128 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png index 60021d5ac47686cce70570d22bad6670082caf56..7d89da97e11a0030fbab56b3d172f547ac9f896e 100644 GIT binary patch delta 1118 zcmV-k1fl!Z1?UKn8Gi%-0001Bxm5rF00d`2O+f$vv5yPND(xlykug~ z(jlQpiNYu_qFZ%}@X|F5QIH}KvO07LD$zq3Y#4a4E`1Po34aqMjXVU4xn>Gw5F!0L zxUTNJGdnZy&i2m9WgdUO_nn=2o@ZY{_`jw&iV4hN6{V;{6PmGAXeZP2h%rhbjdFHy zndiGiVO2)_+URAD1nlrCh5w8XDtK9Y7rd;H zHGY;g3Pc{;IDg1z>}0t?At(4k4x|8a)+-hI=s`e-f0VY$^Gbz$#+apm9bT_c&Ut1b zV2{@;w385FlhGR{@Dp)N;x)b|KP$ih&T7(5f-RVmKHG-4k8SvuW4y?S_UDvMv4^C;7hS&tPq;6z zjzzr8gz@pOjWH>9mkT_>q^L{w_?nFPwb7GN zVUiVrh#{Z&%nB-e8Grf3>lIXZ!7iIz;D%Q#sPVO2F$?I*jWZnLD6a=jHkE=JAK{oe z9jo#Ae1Dh%EU;0T6Q`INzlFo*f<%Y!PrJXxfWqdaq{GL|C7B&QsNjaz3fSY*60xo!+ zf-8QLfGb|5;EqoTnDO6~J#dtZsTmU?@JWFMWq-9JZ@f;$9KoHu$B!Im4~s+KlLD<4 zc)fytnDGRNrw+%Ci-HZF01>suyC~S<2~0ZSZ53?s1a1a`9~eWs4fsf^+^`V1AO*Ks zh4u$3{LTB~tu>y&Q7L@TGWZM6u`mEH;Z^={z!L?lQoC4e*=AOYe%xRTuP}}8$U_D8 zqJJ4D9U9f-by~WIPAC8DM_!#IbQf{suQsz>^7ooyB(RS4oHzD(rM$w=UY?cs%_=NO zQhV?MJ2Y!K#TFz000Ip-j0RMr1O;exUHkQm=um7W-BSpYa1Gm#SXTa1m4bhKG8>LC z-$Ef5_*6l1M|q~zb1utr!&-poRVq}nkALs^j@`sQ@w0c18m5GInvrbGpv(#$<`Z zc!_B&LZAZmID!+1giSfx>mM(EC1m0-Z@jK~#7Foz+1|R8bhk@$b%HFpDX2Hkw2Z zx|A?z5W+>c5W+>ERL~-&MTnbRrXdPa5Q0{;3pB}~G$s^gQ|*G72sFe9MHi9CXo`>) zE}|32#c%lDJMzY}z4QOA=H4fWyoKS8=BBou+#ZS-8OUawUw^81yL4cq#TBh0S1PcS z_Nia|QE9+pm9rZn0PU$DrS>XB1(r;&LNs9I%s~-rotWuM89}1=WzPX%wN8Z*0+9YC z_E%6Lo+SY7t&l1Zfc94CIvn`$dTj98DeZ$oVoL$GfB9Pp?X55>h_KanFk7qiK)c{* z8}UE`2j(7Jzkhh4rF&`zVVm_p4FjgmIltuXbp)69r?uh0`XyJi@>d9Kz@IgS0xRbI zh~0zPe9|9v3i Date: Wed, 2 Dec 2015 08:50:00 +0200 Subject: [PATCH 341/410] git rid of deprecated warnings --- app/models/user.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index e1144ca77b..15aab31564 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -794,4 +794,9 @@ class User < ActiveRecord::Base Gitlab::SQL::Union.new([personal_projects.select(:id), groups.select(:id), other.select(:id)]) end + + # Added according to https://github.com/plataformatec/devise/blob/7df57d5081f9884849ca15e4fde179ef164a575f/README.md#activejob-integration + def send_devise_notification(notification, *args) + devise_mailer.send(notification, self, *args).deliver_later + end end From a8e463c8aca571ede3691c98f7f3990d3d880d0b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 10:56:05 +0100 Subject: [PATCH 342/410] Don't show project fork event as imported --- CHANGELOG | 1 + app/models/event.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index db812796b6..8fbe1e6ff1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.3.0 (unreleased) - Trim leading and trailing whitespace of milestone and issueable titles (Jose Corcuera) - Add ignore whitespace change option to commit view - Fire update hook from GitLab + - Don't show project fork event as "imported" v 8.2.2 - Fix 404 in redirection after removing a project (Stan Hu) diff --git a/app/models/event.rb b/app/models/event.rb index 9afd223bce..01d008035a 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -201,7 +201,7 @@ class Event < ActiveRecord::Base elsif commented? "commented on" elsif created_project? - if project.import? + if project.external_import? "imported" else "created" From 927a4576c6e0bff2c9a41e538efcd2c7691a6a74 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 2 Dec 2015 13:26:49 +0100 Subject: [PATCH 343/410] The Procfile is for development only --- Procfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Procfile b/Procfile index fd5f7ecb94..2e41485677 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,7 @@ +# For DEVELOPMENT only. Production uses Runit in +# https://gitlab.com/gitlab-org/omnibus-gitlab or the init scripts in +# lib/support/init.d, which call scripts in bin/ . +# web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q incoming_email -q runner -q common -q mailers -q default # mail_room: bundle exec mail_room -q -c config/mail_room.yml From 4d67a2909f46d807c3586a74938d6f7619429808 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 13:59:15 +0100 Subject: [PATCH 344/410] Use pointer cursor in award emoji selector --- app/assets/stylesheets/pages/issuable.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 3a08ee70bc..848ad7dac8 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -157,6 +157,7 @@ min-width: 214px; > li { + cursor: pointer; margin: 5px; } } From b7cac5a8dae03a1c870f58d22f51e156e62b0965 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 13:59:21 +0100 Subject: [PATCH 345/410] Use full names in emoji tooltip --- app/helpers/issues_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 493f370d9a..25befd654d 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -96,7 +96,7 @@ module IssuesHelper def emoji_author_list(notes, current_user) list = notes.map do |note| - note.author == current_user ? "me" : note.author.username + note.author == current_user ? "me" : note.author.name end list.join(", ") From 275c2a3161ac4d5638b8fa39a7355bbd9fc3cf46 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 13:59:42 +0100 Subject: [PATCH 346/410] Use new style for wiki --- app/assets/stylesheets/pages/wiki.scss | 5 ++++ app/views/projects/wikis/_form.html.haml | 16 ++++++------ .../projects/wikis/_main_links.html.haml | 11 +++----- app/views/projects/wikis/_nav.html.haml | 25 +++++++++++++------ app/views/projects/wikis/_new.html.haml | 4 +-- app/views/projects/wikis/edit.html.haml | 24 +++++++++--------- app/views/projects/wikis/git_access.html.haml | 2 +- app/views/projects/wikis/pages.html.haml | 7 +++--- app/views/projects/wikis/show.html.haml | 12 +++------ .../steps/project/source/markdown_render.rb | 2 +- features/steps/project/wiki.rb | 12 ++++----- 11 files changed, 62 insertions(+), 58 deletions(-) diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss index dfaeba41cf..cdf514197c 100644 --- a/app/assets/stylesheets/pages/wiki.scss +++ b/app/assets/stylesheets/pages/wiki.scss @@ -4,3 +4,8 @@ margin-right: auto; padding-right: 7px; } + +.wiki-last-edit-by { + font-size: 80%; + font-weight: normal; +} diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 9c94c43e74..1d257818dc 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form prepend-top-default' } do |f| -if @page.errors.any? #error_explanation .alert.alert-danger @@ -11,14 +11,7 @@ .col-sm-10 = f.select :format, options_for_select(ProjectWiki::MARKUPS, {selected: @page.format}), {}, class: "form-control" - .row - .col-sm-offset-2.col-sm-10 - %p.cgray - To link to a (new) page you can just type - %code [Link Title](page-slug) - \. - - .form-group.wiki-content + .form-group = f.label :content, class: 'control-label' .col-sm-10 = render layout: 'projects/md_preview', locals: { preview_class: "md-preview" } do @@ -27,6 +20,11 @@ .clearfix .error-alert + + .help-block + To link to a (new) page, simply type + %code [Link Title](page-slug) + \. .form-group = f.label :commit_message, class: 'control-label' .col-sm-10= f.text_field :message, class: 'form-control', rows: 18 diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml index 14f2582225..29bf5d62ab 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -1,9 +1,4 @@ %span.pull-right - - if can?(current_user, :create_wiki, @project) - = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new btn-grouped", "data-toggle" => "modal" do - %i.fa.fa-plus - New Page - - if (@page && @page.persisted?) = link_to namespace_project_wiki_history_path(@project.namespace, @project, @page), class: "btn btn-grouped" do Page History @@ -11,5 +6,7 @@ = link_to namespace_project_wiki_edit_path(@project.namespace, @project, @page), class: "btn btn-grouped" do %i.fa.fa-pencil-square-o Edit - -= render 'projects/wikis/new' + - if can?(current_user, :admin_wiki, @project) + = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-remove" do + = icon('trash') + Delete diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index fffb4eb31a..e6e6ad5bc4 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -1,10 +1,19 @@ -%ul.center-top-menu - = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do - = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) +.project-issuable-filter + .controls + - if can?(current_user, :create_wiki, @project) + = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do + %i.fa.fa-plus + New Page - = nav_link(path: 'wikis#pages') do - = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project) + = render 'projects/wikis/new' - = nav_link(path: 'wikis#git_access') do - = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do - Git Access + %ul.center-top-menu + = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do + = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) + + = nav_link(path: 'wikis#pages') do + = link_to 'Pages', namespace_project_wiki_pages_path(@project.namespace, @project) + + = nav_link(path: 'wikis#git_access') do + = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do + Git Access diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml index dace172438..f0547e9c05 100644 --- a/app/views/projects/wikis/_new.html.haml +++ b/app/views/projects/wikis/_new.html.haml @@ -12,5 +12,5 @@ The page slug is invalid. Please don't use characters other then: a-z 0-9 _ - and / %p.hint Please don't use spaces. - .modal-footer - = link_to 'Build', '#', class: 'build-new-wiki btn btn-create' + .form-actions + = link_to 'Create Page', '#', class: 'build-new-wiki btn btn-create' diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index 0b709c3695..23f64fbbd1 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -1,16 +1,16 @@ -- page_title "Edit", @page.title, "Wiki" +- page_title "Edit", @page.title.capitalize, "Wiki" = render "header_title" = render 'nav' -.pull-right - = render 'main_links' -%h3.page-title - Editing - - %span.light #{@page.title} -%hr -= render 'form' +.gray-content-block + .pull-right + = render 'main_links' -.pull-right - - if @page.persisted? && can?(current_user, :admin_wiki, @project) - = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-sm btn-remove" do - Delete this page + %h3.page-title.oneline + %span.light Edit Page + - if @page.persisted? + = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page) + - else + = @page.title + += render 'form' diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml index 6417ef4a38..11c8c4f0eb 100644 --- a/app/views/projects/wikis/git_access.html.haml +++ b/app/views/projects/wikis/git_access.html.haml @@ -5,7 +5,7 @@ .gray-content-block .row .col-sm-6 - %h3.page-title + %h3.page-title.oneline Git access for %strong= @project_wiki.path_with_namespace diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml index d179a1abec..aae1ad69ad 100644 --- a/app/views/projects/wikis/pages.html.haml +++ b/app/views/projects/wikis/pages.html.haml @@ -1,11 +1,10 @@ -- page_title "All Pages", "Wiki" +- page_title "Pages", "Wiki" = render "header_title" = render 'nav' .gray-content-block - = render 'main_links' - %h3.page-title - All Pages + All pages in this wiki are listed below. + %ul.content-list - @wiki_pages.each do |wiki_page| %li diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index 55fbf5a8b6..309d40f52b 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -5,11 +5,12 @@ .gray-content-block = render 'main_links' - %h3.page-title + %h3.page-title.oneline = @page.title.capitalize - .wiki-last-edit-by - Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} + %span.wiki-last-edit-by + · + last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} - if @page.historical? .warning_message @@ -21,8 +22,3 @@ .wiki = preserve do = render_wiki_content(@page) - -.gray-content-block.footer-block - .wiki-last-edit-by - Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} - diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb index c78e86fa1a..ec88c8c20c 100644 --- a/features/steps/project/source/markdown_render.rb +++ b/features/steps/project/source/markdown_render.rb @@ -238,7 +238,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps step 'I see new wiki page named test' do expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "test") - expect(page).to have_content "Editing" + expect(page).to have_content "Edit Page test" end When 'I go back to wiki page home' do diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb index 02207dbffa..935c1ca8a2 100644 --- a/features/steps/project/wiki.rb +++ b/features/steps/project/wiki.rb @@ -5,7 +5,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps include SharedPaths step 'I click on the Cancel button' do - page.within(:css, ".form-actions") do + page.within(:css, ".wiki-form .form-actions") do click_on "Cancel" end end @@ -24,7 +24,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps expect(page).to have_content "link test" click_link "link test" - expect(page).to have_content "Editing" + expect(page).to have_content "Edit Page" end step 'I have an existing Wiki page' do @@ -68,7 +68,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps end step 'I click on the "Delete this page" button' do - click_on "Delete this page" + click_on "Delete" end step 'The page should be deleted' do @@ -120,13 +120,13 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps step 'I should see the new wiki page form' do expect(current_path).to match('wikis/image.jpg') expect(page).to have_content('New Wiki Page') - expect(page).to have_content('Editing - image.jpg') + expect(page).to have_content('Edit Page image.jpg') end step 'I create a New page with paths' do click_on 'New Page' fill_in 'Page slug', with: 'one/two/three' - click_on 'Build' + click_on 'Create Page' fill_in "wiki_content", with: 'wiki content' click_on "Create page" expect(current_path).to include 'one/two/three' @@ -135,7 +135,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps step 'I create a New page with an invalid name' do click_on 'New Page' fill_in 'Page slug', with: 'invalid name' - click_on 'Build' + click_on 'Create Page' end step 'I should see an error message' do From b66694d236f71474054c63b1e8a683abacf7fdc3 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:00:54 +0100 Subject: [PATCH 347/410] Add "New X" link to dashboard/group milestone project-specific issue/MR panels --- app/views/shared/_issues.html.haml | 7 ++++--- app/views/shared/_merge_requests.html.haml | 7 +++++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/views/shared/_issues.html.haml b/app/views/shared/_issues.html.haml index 0dbb6a0439..4b4c9e9eab 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -3,8 +3,10 @@ .panel.panel-default.panel-small - project = group[0] .panel-heading - = link_to_project project - = link_to 'show all', namespace_project_issues_path(project.namespace, project), class: 'pull-right' + = link_to project.name_with_namespace, namespace_project_issues_path(project.namespace, project) + - if can?(current_user, :create_issue, project) + .pull-right + = link_to 'New issue', new_namespace_project_issue_path(project.namespace, project) %ul.well-list.issues-list - group[1].each do |issue| @@ -12,4 +14,3 @@ = paginate @issues, theme: "gitlab" - else .nothing-here-block No issues to show - diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index c02c5af008..be17a511b2 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -3,8 +3,11 @@ .panel.panel-default.panel-small - project = group[0] .panel-heading - = link_to_project project - = link_to 'show all', namespace_project_merge_requests_path(project.namespace, project), class: 'pull-right' + = link_to project.name_with_namespace, namespace_project_merge_requests_path(project.namespace, project) + - if can?(current_user, :create_merge_request, project) + .pull-right + = link_to 'New merge request', new_namespace_project_merge_request_path(project.namespace, project) + %ul.well-list.mr-list - group[1].each do |merge_request| = render 'projects/merge_requests/merge_request', merge_request: merge_request From 0833057494dcf84c789fee8a1dd2c3f3f541d036 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:01:20 +0100 Subject: [PATCH 348/410] Use new style for milestone detail page --- app/views/dashboard/milestones/show.html.haml | 52 +++++--- app/views/groups/milestones/show.html.haml | 61 ++++++---- app/views/projects/milestones/show.html.haml | 113 ++++++++++-------- 3 files changed, 140 insertions(+), 86 deletions(-) diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml index 83077a398b..3536bbeaf4 100644 --- a/app/views/dashboard/milestones/show.html.haml +++ b/app/views/dashboard/milestones/show.html.haml @@ -1,19 +1,23 @@ - page_title @milestone.title, "Milestones" -%h4.page-title - .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" } - - if @milestone.closed? - Closed - - else - Open - Milestone #{@milestone.title} +- header_title "Milestones", dashboard_milestones_path + +.issuable-details + .page-title + .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" } + - if @milestone.closed? + Closed + - else + Open + Milestone #{@milestone.title} + + .gray-content-block.middle-block + %h2.issue-title + = gfm escape_once(@milestone.title) -%hr - if @milestone.complete? && @milestone.active? - .alert.alert-success + .alert.alert-success.prepend-top-default %span All issues for this milestone are closed. You may close the milestone now. -.description - .table-holder %table.table %thead @@ -44,7 +48,7 @@ #{@milestone.open_items_count} open = milestone_progress_bar(@milestone) -%ul.nav.nav-tabs +%ul.center-top-menu.no-top.no-bottom %li.active = link_to '#tab-issues', 'data-toggle' => 'tab' do Issues @@ -58,25 +62,39 @@ Participants %span.badge= @milestone.participants.count - .pull-right - = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped" - .tab-content .tab-pane.active#tab-issues - .row + .gray-content-block.middle-block + .pull-right + = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped" + + .oneline + All issues in this milestone + + .row.prepend-top-default .col-md-6 = render 'issues', title: "Open", issues: @milestone.opened_issues .col-md-6 = render 'issues', title: "Closed", issues: @milestone.closed_issues .tab-pane#tab-merge-requests - .row + .gray-content-block.middle-block + .pull-right + = link_to 'Browse Merge Requests', merge_requests_dashboard_path(milestone_title: @milestone.title), class: "btn btn-grouped" + + .oneline + All merge requests in this milestone + + .row.prepend-top-default .col-md-6 = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests .col-md-6 = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests .tab-pane#tab-participants + .gray-content-block.middle-block + .oneline + All participants to this milestone %ul.bordered-list - @milestone.participants.each do |user| %li diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index d161259e4a..3c1d881501 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -1,27 +1,29 @@ - page_title @milestone.title, "Milestones" = render "header_title" -%h4.page-title - .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" } - - if @milestone.closed? - Closed - - else - Open - Milestone #{@milestone.title} - .pull-right - - if can?(current_user, :admin_milestones, @group) - - if @milestone.active? - = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" +.issuable-details + .page-title + .issue-box{ class: "issue-box-#{@milestone.closed? ? 'closed' : 'open'}" } + - if @milestone.closed? + Closed - else - = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" + Open + Milestone #{@milestone.title} + .pull-right + - if can?(current_user, :admin_milestones, @group) + - if @milestone.active? + = link_to 'Close Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close" + - else + = link_to 'Reopen Milestone', group_milestone_path(@group, @milestone.safe_title, title: @milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-grouped btn-reopen" + + .gray-content-block.middle-block + %h2.issue-title + = gfm escape_once(@milestone.title) -%hr - if @milestone.complete? && @milestone.active? - .alert.alert-success + .alert.alert-success.prepend-top-default %span All issues for this milestone are closed. You may close the milestone now. -.description - .table-holder %table.table %thead @@ -52,7 +54,7 @@ #{@milestone.open_items_count} open = milestone_progress_bar(@milestone) -%ul.nav.nav-tabs +%ul.center-top-menu.no-top.no-bottom %li.active = link_to '#tab-issues', 'data-toggle' => 'tab' do Issues @@ -66,25 +68,40 @@ Participants %span.badge= @milestone.participants.count - .pull-right - = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped" - .tab-content .tab-pane.active#tab-issues - .row + .gray-content-block.middle-block + .pull-right + = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped" + + .oneline + All issues in this milestone + + .row.prepend-top-default .col-md-6 = render 'issues', title: "Open", issues: @milestone.opened_issues .col-md-6 = render 'issues', title: "Closed", issues: @milestone.closed_issues .tab-pane#tab-merge-requests - .row + .gray-content-block.middle-block + .pull-right + = link_to 'Browse Merge Requests', merge_requests_group_path(@group, milestone_title: @milestone.title), class: "btn btn-grouped" + + .oneline + All merge requests in this milestone + + .row.prepend-top-default .col-md-6 = render 'merge_requests', title: "Open", merge_requests: @milestone.opened_merge_requests .col-md-6 = render 'merge_requests', title: "Closed", merge_requests: @milestone.closed_merge_requests .tab-pane#tab-participants + .gray-content-block.middle-block + .oneline + All participants to this milestone + %ul.bordered-list - @milestone.participants.each do |user| %li diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 3a898dfbcf..c3bda794c6 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -1,46 +1,50 @@ - page_title @milestone.title, "Milestones" = render "header_title" -%h4.page-title - .issue-box{ class: issue_box_class(@milestone) } - - if @milestone.closed? - Closed - - elsif @milestone.expired? - Expired - - else - Open - Milestone ##{@milestone.iid} - %small.creator - = @milestone.expires_at - .pull-right - - if can?(current_user, :admin_milestone, @project) - = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do - %i.fa.fa-pencil-square-o - Edit - - if @milestone.active? - = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped" +.issuable-details + .page-title + .issue-box{ class: issue_box_class(@milestone) } + - if @milestone.closed? + Closed + - elsif @milestone.expired? + Expired - else - = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped" - = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do - %i.fa.fa-trash-o - Remove + Open + Milestone ##{@milestone.iid} + - if @milestone.expires_at + %span.creator + · + = @milestone.expires_at + .pull-right + - if can?(current_user, :admin_milestone, @project) + = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do + %i.fa.fa-pencil-square-o + Edit + + - if @milestone.active? + = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped" + - else + = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped" + + = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do + %i.fa.fa-trash-o + Delete + + .gray-content-block.middle-block + %h2.issue-title + = gfm escape_once(@milestone.title) + %div + - if @milestone.description.present? + .description + .wiki + = preserve do + = markdown @milestone.description -%hr - if @milestone.issues.any? && @milestone.can_be_closed? - .alert.alert-success + .alert.alert-success.prepend-top-default %span All issues for this milestone are closed. You may close milestone now. -%h3.issue-title - = gfm escape_once(@milestone.title) -%div - - if @milestone.description.present? - .description - .wiki - = preserve do - = markdown @milestone.description - -%hr -.context +.context.prepend-top-default %p.lead Progress: #{@milestone.closed_items_count} closed @@ -51,8 +55,7 @@ %span.pull-right= @milestone.expires_at = milestone_progress_bar(@milestone) - -%ul.nav.nav-tabs +%ul.center-top-menu.no-top.no-bottom %li.active = link_to '#tab-issues', 'data-toggle' => 'tab' do Issues @@ -66,17 +69,21 @@ Participants %span.badge= @users.count - .pull-right - - if can?(current_user, :create_issue, @project) - = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do - %i.fa.fa-plus - New Issue - - if can?(current_user, :read_issue, @project) - = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped" - .tab-content .tab-pane.active#tab-issues - .row + .gray-content-block.middle-block + .pull-right + - if can?(current_user, :create_issue, @project) + = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do + %i.fa.fa-plus + New Issue + - if can?(current_user, :read_issue, @project) + = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped" + + .oneline + All issues in this milestone + + .row.prepend-top-default .col-md-4 = render('issues', title: 'Unstarted Issues (open and unassigned)', issues: @issues.opened.unassigned, id: 'unassigned') .col-md-4 @@ -85,7 +92,15 @@ = render('issues', title: 'Completed Issues (closed)', issues: @issues.closed, id: 'closed') .tab-pane#tab-merge-requests - .row + .gray-content-block.middle-block + .pull-right + - if can?(current_user, :read_merge_request, @project) + = link_to 'Browse Merge Requests', namespace_project_merge_requests_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn btn-grouped" + + .oneline + All merge requests in this milestone + + .row.prepend-top-default .col-md-3 = render('merge_requests', title: 'Work in progress (open and unassigned)', merge_requests: @merge_requests.opened.unassigned, id: 'unassigned') .col-md-3 @@ -100,6 +115,10 @@ = render 'merge_request', merge_request: merge_request .tab-pane#tab-participants + .gray-content-block.middle-block + .oneline + All participants to this milestone + %ul.bordered-list - @users.each do |user| %li From 112cca872bd7982bd2452f2f0903d21342d5ea5f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:07:21 +0100 Subject: [PATCH 349/410] Move project visibility status to the left --- app/assets/stylesheets/framework/blocks.scss | 5 +++++ app/views/projects/_home_panel.html.haml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 1635df9c97..9e30d3b9c8 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -112,5 +112,10 @@ position: absolute; top: 10px; right: 10px; + + &.left { + left: 10px; + right: auto; + } } } diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index b30036966a..d94a279eaf 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -12,9 +12,9 @@ Forked from = link_to project_path(forked_from_project) do = forked_from_project.namespace.try(:name) - .cover-controls .visibility-level-label = visibility_level_icon(@project.visibility_level) + .cover-controls.left = visibility_level_label(@project.visibility_level) .project-repo-buttons From daca985a6e75d6f43c5cc5b487a0942d5bf93f68 Mon Sep 17 00:00:00 2001 From: Andrew Tomaka Date: Tue, 1 Dec 2015 23:40:24 -0500 Subject: [PATCH 350/410] Prevent impersonation if blocked --- .../admin/impersonation_controller.rb | 16 +++++++++++----- app/views/admin/users/_head.html.haml | 2 +- .../admin/impersonation_controller_spec.rb | 19 +++++++++++++++++++ spec/features/admin/admin_users_spec.rb | 10 ++++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 spec/controllers/admin/impersonation_controller_spec.rb diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb index 0382402afa..102dd43740 100644 --- a/app/controllers/admin/impersonation_controller.rb +++ b/app/controllers/admin/impersonation_controller.rb @@ -5,14 +5,20 @@ class Admin::ImpersonationController < Admin::ApplicationController before_action :authorize_impersonator! def create - session[:impersonator_id] = current_user.username - session[:impersonator_return_to] = request.env['HTTP_REFERER'] + if @user.blocked? + flash[:alert] = "You cannot impersonate a blocked user" - warden.set_user(user, scope: 'user') + redirect_to admin_user_path(@user) + else + session[:impersonator_id] = current_user.username + session[:impersonator_return_to] = request.env['HTTP_REFERER'] - flash[:alert] = "You are impersonating #{user.username}." + warden.set_user(user, scope: 'user') - redirect_to root_path + flash[:alert] = "You are impersonating #{user.username}." + + redirect_to root_path + end end def destroy diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml index 8d1cab4137..5e17b01816 100644 --- a/app/views/admin/users/_head.html.haml +++ b/app/views/admin/users/_head.html.haml @@ -6,7 +6,7 @@ %span.cred (Admin) .pull-right - - unless @user == current_user + - unless @user == current_user || @user.blocked? = link_to 'Impersonate', impersonate_admin_user_path(@user), method: :post, class: "btn btn-grouped btn-info" = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do %i.fa.fa-pencil-square-o diff --git a/spec/controllers/admin/impersonation_controller_spec.rb b/spec/controllers/admin/impersonation_controller_spec.rb new file mode 100644 index 0000000000..d7a7ba1c5b --- /dev/null +++ b/spec/controllers/admin/impersonation_controller_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe Admin::ImpersonationController do + let(:admin) { create(:admin) } + + before do + sign_in(admin) + end + + describe 'CREATE #impersonation when blocked' do + let(:blocked_user) { create(:user, state: :blocked) } + + it 'does not allow impersonation' do + post :create, id: blocked_user.username + + expect(flash[:alert]).to eq 'You cannot impersonate a blocked user' + end + end +end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 86f01faffb..4570e40912 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -128,6 +128,16 @@ describe "Admin::Users", feature: true do expect(page).not_to have_content('Impersonate') end + + it 'should not show impersonate button for blocked user' do + another_user.block + + visit admin_user_path(another_user) + + expect(page).not_to have_content('Impersonate') + + another_user.activate + end end context 'when impersonating' do From 085e45a81cbdac73f41702764a1b53a56a298653 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:08:25 +0100 Subject: [PATCH 351/410] Add edit and RSS buttons to project home panel --- app/views/projects/_home_panel.html.haml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index d94a279eaf..695da7f07d 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -17,6 +17,15 @@ .cover-controls.left = visibility_level_label(@project.visibility_level) + .cover-controls + - if can?(current_user, :admin_project, @project) + = link_to edit_project_path(@project), class: 'btn btn-gray' do + = icon('pencil') + - if current_user +   + = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), class: 'btn btn-gray' do + = icon('rss') + .project-repo-buttons .split-one = render 'projects/buttons/star' From d65647e90c25a1cf28353b3d0476aec0a4c6ebb7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:08:59 +0100 Subject: [PATCH 352/410] Add visibility description tooltip to snippet and project visibility labels --- app/helpers/icons_helper.rb | 22 ++++++----- app/helpers/visibility_level_helper.rb | 44 +++++---------------- app/views/projects/_home_panel.html.haml | 4 +- app/views/shared/snippets/_header.html.haml | 4 +- 4 files changed, 27 insertions(+), 47 deletions(-) diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index 1cf5b96481..5724d3aabe 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -27,16 +27,20 @@ module IconsHelper end end - def public_icon - icon('globe fw') - end + def visibility_level_icon(level, fw: true) + name = + case level + when Gitlab::VisibilityLevel::PRIVATE + 'lock' + when Gitlab::VisibilityLevel::INTERNAL + 'shield' + else # Gitlab::VisibilityLevel::PUBLIC + 'globe' + end + + name << " fw" if fw - def internal_icon - icon('shield fw') - end - - def private_icon - icon('lock fw') + icon(name) end def file_type_icon_class(type, mode, name) diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index b52cd23aba..72c65030f9 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -25,48 +25,24 @@ module VisibilityLevelHelper end def project_visibility_level_description(level) - capture_haml do - haml_tag :span do - case level - when Gitlab::VisibilityLevel::PRIVATE - haml_concat "Project access must be granted explicitly for each user." - when Gitlab::VisibilityLevel::INTERNAL - haml_concat "The project can be cloned by" - haml_concat "any logged in user." - when Gitlab::VisibilityLevel::PUBLIC - haml_concat "The project can be cloned" - haml_concat "without any" - haml_concat "authentication." - end - end + case level + when Gitlab::VisibilityLevel::PRIVATE + "Project access must be granted explicitly for each user." + when Gitlab::VisibilityLevel::INTERNAL + "The project can be cloned by any logged in user." + when Gitlab::VisibilityLevel::PUBLIC + "The project can be cloned without any authentication." end end def snippet_visibility_level_description(level) - capture_haml do - haml_tag :span do - case level - when Gitlab::VisibilityLevel::PRIVATE - haml_concat "The snippet is visible only for me." - when Gitlab::VisibilityLevel::INTERNAL - haml_concat "The snippet is visible for any logged in user." - when Gitlab::VisibilityLevel::PUBLIC - haml_concat "The snippet can be accessed" - haml_concat "without any" - haml_concat "authentication." - end - end - end - end - - def visibility_level_icon(level) case level when Gitlab::VisibilityLevel::PRIVATE - private_icon + "The snippet is visible only for me." when Gitlab::VisibilityLevel::INTERNAL - internal_icon + "The snippet is visible for any logged in user." when Gitlab::VisibilityLevel::PUBLIC - public_icon + "The snippet can be accessed without any authentication." end end diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 695da7f07d..c1669ac046 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -12,9 +12,9 @@ Forked from = link_to project_path(forked_from_project) do = forked_from_project.namespace.try(:name) - .visibility-level-label - = visibility_level_icon(@project.visibility_level) .cover-controls.left + .visibility-level-label.has_tooltip{title: project_visibility_level_description(@project.visibility_level), data: { container: 'body' } } + = visibility_level_icon(@project.visibility_level, fw: false) = visibility_level_label(@project.visibility_level) .cover-controls diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index 0a4a790ec5..b6c9872f08 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -1,7 +1,7 @@ .snippet-details .page-title - .snippet-box{class: visibility_level_color(@snippet.visibility_level)} - = visibility_level_icon(@snippet.visibility_level) + .snippet-box.has_tooltip{class: visibility_level_color(@snippet.visibility_level), title: snippet_visibility_level_description(@snippet.visibility_level), data: { container: 'body' }} + = visibility_level_icon(@snippet.visibility_level, fw: false) = visibility_level_label(@snippet.visibility_level) %span.snippet-id Snippet ##{@snippet.id} %span.creator From de5e28cdcfe8554f2f45f3247d8304aa94f5c3cb Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:09:13 +0100 Subject: [PATCH 353/410] Use default cursor for project visibility label. --- app/assets/stylesheets/pages/projects.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 4a0fe54684..68a3617512 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -94,7 +94,7 @@ @extend .btn-gray; color: $gray; - cursor: auto; + cursor: default; i { color: inherit; From 762e759bb3546db7f058d8055ed7b62590d80217 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:09:18 +0100 Subject: [PATCH 354/410] Remove duplicate styles. --- app/assets/stylesheets/pages/projects.scss | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 68a3617512..25d4d0a2c4 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -30,12 +30,6 @@ } .project-home-panel { - text-align: center; - background: #f7f8fa; - margin: -$gl-padding; - padding: $gl-padding; - padding: 44px 0 17px 0; - .project-identicon-holder { margin-bottom: 16px; @@ -105,7 +99,6 @@ display: inline-table; position: relative; top: 17px; - margin-bottom: 44px; } .project-repo-buttons { From 6252fe4510376c917a690e655f8f4257132dde3e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:09:50 +0100 Subject: [PATCH 355/410] Unify new project namespace select and path input --- app/assets/stylesheets/framework/forms.scss | 8 ++++++++ app/helpers/namespaces_helper.rb | 6 +++--- app/views/projects/new.html.haml | 20 ++++++++++---------- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 0edfe24f19..95674c5812 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -88,7 +88,15 @@ label { } .input-group { + .select2-container { + display: table-cell; + width: 200px !important; + } .input-group-addon { background-color: #f7f8fa; } + .input-group-addon:not(:first-child):not(:last-child) { + border-left: 0; + border-right: 0; + } } diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index e7f3cb2103..faba418c4d 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -1,10 +1,10 @@ module NamespacesHelper - def namespaces_options(selected = :current_user, scope = :default) + def namespaces_options(selected = :current_user, display_path: false) groups = current_user.owned_groups + current_user.masters_groups users = [current_user.namespace] - group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [g.human_name, g.id]} ] - users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [u.human_name, u.id]} ] + group_opts = ["Groups", groups.sort_by(&:human_name).map {|g| [display_path ? g.path : g.human_name, g.id]} ] + users_opts = [ "Users", users.sort_by(&:human_name).map {|u| [display_path ? u.path : u.human_name, u.id]} ] options = [] options << group_opts diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index c9d1fc3da2..f6355ea670 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -11,16 +11,16 @@ Project path .col-sm-10 .input-group - = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true, required: true - .input-group-addon - \.git - - - if current_user.can_select_namespace? - .form-group - = f.label :namespace_id, class: 'control-label' do - %span Namespace - .col-sm-10 - = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2} + - 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', tabindex: 1} + .input-group-addon + \/ + - else + .input-group-addon + #{root_url}#{current_user.username}/ + = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true - if import_sources_enabled? .project-import.js-toggle-container From 37ab1120c8a252aa29702ef141ff4ea1163e0e75 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:10:03 +0100 Subject: [PATCH 356/410] Move "Add Group" button higher up in new project form --- app/views/projects/new.html.haml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index f6355ea670..a4bd8d54af 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -22,6 +22,11 @@ #{root_url}#{current_user.username}/ = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 2, autofocus: true, required: true + - if current_user.can_create_group? + .help-block + Want to house several dependent projects under the same namespace? + = link_to "Create a group", new_group_path + - if import_sources_enabled? .project-import.js-toggle-container .form-group @@ -96,14 +101,6 @@ .form-actions = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 - - if current_user.can_create_group? - .pull-right - .light.inline - .space-right - Need a group for several dependent projects? - = link_to new_group_path, class: "btn" do - Create a group - .save-project-loader.hide .center %h2 From a895d5d3b2252cd962b8ea19d2c1092cbee7fca6 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:10:32 +0100 Subject: [PATCH 357/410] Use select2 for bulk issue edit status --- app/assets/javascripts/issues.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index 40bb9e9cb0..ac9e022e72 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -29,7 +29,7 @@ $('#filter_issue_search').val($('#issue_search').val()) initSelects: -> - $("select#update_status").select2(width: 'resolve', dropdownAutoWidth: true) + $("select#update_state_event").select2(width: 'resolve', dropdownAutoWidth: true) $("select#update_assignee_id").select2(width: 'resolve', dropdownAutoWidth: true) $("select#update_milestone_id").select2(width: 'resolve', dropdownAutoWidth: true) $("select#label_name").select2(width: 'resolve', dropdownAutoWidth: true) From f385988d1648e54218d68e02a25eaad048b04d0b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:10:43 +0100 Subject: [PATCH 358/410] Use "Any Label" and "Any Milestone" in selects rather than the ambiguous "Any" option --- app/models/label.rb | 2 +- app/models/milestone.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/models/label.rb b/app/models/label.rb index b306aecbac..bef6063fe8 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -17,7 +17,7 @@ class Label < ActiveRecord::Base # Requests that have no label assigned. LabelStruct = Struct.new(:title, :name) None = LabelStruct.new('No Label', 'No Label') - Any = LabelStruct.new('Any', '') + Any = LabelStruct.new('Any Label', '') DEFAULT_COLOR = '#428BCA' diff --git a/app/models/milestone.rb b/app/models/milestone.rb index c2642b75b8..d8c7536cd3 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -16,9 +16,9 @@ class Milestone < ActiveRecord::Base # Represents a "No Milestone" state used for filtering Issues and Merge # Requests that have no milestone assigned. - MilestoneStruct = Struct.new(:title, :name) - None = MilestoneStruct.new('No Milestone', 'No Milestone') - Any = MilestoneStruct.new('Any', '') + MilestoneStruct = Struct.new(:title, :name, :id) + None = MilestoneStruct.new('No Milestone', 'No Milestone', 0) + Any = MilestoneStruct.new('Any Milestone', '', -1) include InternalId include Sortable From af541515fa49756d4d23719029648276ed6c2f5b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:11:32 +0100 Subject: [PATCH 359/410] Remove "none" username for "Unassigned" and "Any User" select options --- app/assets/javascripts/users_select.js.coffee | 13 +++++-------- app/assets/stylesheets/framework/selects.scss | 8 +++++++- app/helpers/issues_helper.rb | 7 ++++--- app/helpers/selects_helper.rb | 14 ++++++++------ app/views/shared/issuable/_filter.html.haml | 4 ++-- 5 files changed, 26 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index f5db74d84e..12abf806bf 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -32,17 +32,15 @@ class @UsersSelect if showNullUser nullUser = { name: 'Unassigned', - avatar: null, - username: 'none', id: 0 } data.results.unshift(nullUser) if showAnyUser + name = showAnyUser + name = 'Any User' if name == true anyUser = { - name: 'Any', - avatar: null, - username: 'none', + name: name, id: null } data.results.unshift(anyUser) @@ -50,7 +48,6 @@ class @UsersSelect if showEmailUser && data.results.length == 0 && query.term.match(/^[^@]+@[^@]+$/) emailUser = { name: "Invite \"#{query.term}\"", - avatar: null, username: query.term, id: query.term } @@ -82,10 +79,10 @@ class @UsersSelect else avatar = gon.default_avatar_url - "

+ "
#{user.name}
-
#{user.username}
+
#{user.username || ""}
" formatSelection: (user) -> diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 78fff58d23..5781983c48 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -123,10 +123,16 @@ } .user-result { + min-height: 24px; + .user-image { float: left; } - .user-name { + + &.no-username { + .user-name { + line-height: 24px; + } } } diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 493f370d9a..bfaae223b1 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -44,9 +44,10 @@ module IssuesHelper end def bulk_update_milestone_options - options_for_select([['None (backlog)', -1]]) + - options_from_collection_for_select(project_active_milestones, 'id', - 'title', params[:milestone_id]) + milestones = project_active_milestones.to_a + milestones.unshift(Milestone::None) + + options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id]) end def milestone_options(object) diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index 7e54d4d1b5..7e175d0de8 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -15,12 +15,14 @@ module SelectsHelper html = { class: css_class, - 'data-placeholder' => placeholder, - 'data-null-user' => null_user, - 'data-any-user' => any_user, - 'data-email-user' => email_user, - 'data-first-user' => first_user, - 'data-current-user' => current_user + data: { + placeholder: placeholder, + null_user: null_user, + any_user: any_user, + email_user: email_user, + first_user: first_user, + current_user: current_user + } } unless opts[:scope] == :all diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index d1231438ee..c159e85ef2 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -31,11 +31,11 @@ .issues-other-filters .filter-item.inline = users_select_tag(:assignee_id, selected: params[:assignee_id], - placeholder: 'Assignee', class: 'trigger-submit', any_user: true, null_user: true, first_user: true, current_user: true) + placeholder: 'Assignee', class: 'trigger-submit', any_user: "Any Assignee", null_user: true, first_user: true, current_user: true) .filter-item.inline = users_select_tag(:author_id, selected: params[:author_id], - placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true, current_user: true) + placeholder: 'Author', class: 'trigger-submit', any_user: "Any Author", first_user: true, current_user: true) .filter-item.inline.milestone-filter = select_tag('milestone_title', projects_milestones_options, From 5d0ab2b9e9854733ba174cd3d403600f45c7dbd1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:12:16 +0100 Subject: [PATCH 360/410] Make style of select2 box more consistent with controls --- app/assets/stylesheets/framework/selects.scss | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index 5781983c48..5e5ae3d0af 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -15,6 +15,16 @@ border-left: none; padding-top: 5px; } + + .select2-chosen { + color: $gl-text-color; + } + + &.select2-default { + .select2-chosen { + color: #999; + } + } } } @@ -23,6 +33,7 @@ border: 1px solid #e7e9ed; } + .select2-drop { @include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px); @include border-radius (0px); From 68ee01cfa0de7656b09eb743aabccf7ca8746d57 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:12:46 +0100 Subject: [PATCH 361/410] Use placeholders rather than blank options in bulk issue edit selects. --- app/views/shared/issuable/_filter.html.haml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml index c159e85ef2..ac6c248ccf 100644 --- a/app/views/shared/issuable/_filter.html.haml +++ b/app/views/shared/issuable/_filter.html.haml @@ -53,12 +53,16 @@ - if controller.controller_name == 'issues' .issues_bulk_update.hide = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do - = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), prompt: "Status", class: 'form-control') - = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, first_user: true, current_user: true) - = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone") + .filter-item.inline + = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), include_blank: true, data: { placeholder: "Status" }) + .filter-item.inline + = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true, first_user: true, current_user: true) + .filter-item.inline + = select_tag('update[milestone_id]', bulk_update_milestone_options, include_blank: true, data: { placeholder: "Milestone" }) = hidden_field_tag 'update[issues_ids]', [] = hidden_field_tag :state_event, params[:state_event] - = button_tag "Update issues", class: "btn update_selected_issues btn-save" + .filter-item.inline + = button_tag "Update issues", class: "btn update_selected_issues btn-save" :javascript new UsersSelect(); From d399ffa244511b0bfe0b4de746b6775a3a456465 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:18:46 +0100 Subject: [PATCH 362/410] Use "Assigned to USER" tooltip in issue list item --- app/helpers/projects_helper.rb | 5 +++-- app/views/projects/issues/_issue.html.haml | 2 +- app/views/projects/merge_requests/_merge_request.html.haml | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index c0c51aae03..48729e5260 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -21,7 +21,7 @@ module ProjectsHelper end def link_to_member(project, author, opts = {}) - default_opts = { avatar: true, name: true, size: 16, author_class: 'author' } + default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" } opts = default_opts.merge(opts) return "(deleted)" unless author @@ -39,7 +39,8 @@ module ProjectsHelper if opts[:name] link_to(author_html, user_path(author), class: "author_link").html_safe else - link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => sanitize(author.name) } ).html_safe + title = opts[:title].sub(":name", sanitize(author.name)) + link_to(author_html, user_path(author), class: "author_link has_tooltip", data: { :'original-title' => title, container: 'body' } ).html_safe end end diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index d7657ee7e4..1d452dc601 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -14,7 +14,7 @@ %span CLOSED - if issue.assignee - = link_to_member(@project, issue.assignee, name: false) + = link_to_member(@project, issue.assignee, name: false, title: "Assigned to :name") - note_count = issue.notes.user.count - if note_count > 0   diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 83e8ad1198..5842d7ff16 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -20,7 +20,7 @@ - note_count = merge_request.mr_and_commit_notes.user.count - if merge_request.assignee   - = link_to_member(merge_request.source_project, merge_request.assignee, name: false) + = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name") - if note_count > 0   %span From dc809983a4d5c9f61af9ca4392010d243271f7d3 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:18:59 +0100 Subject: [PATCH 363/410] Link issue/MR list item comments counter to comments --- app/views/projects/issues/_issue.html.haml | 8 ++++---- .../projects/merge_requests/_merge_request.html.haml | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 1d452dc601..4f6ff20caf 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -18,13 +18,13 @@ - note_count = issue.notes.user.count - if note_count > 0   - %span - %i.fa.fa-comments + = link_to issue_path(issue) + "#notes" do + = icon('comments') = note_count - else   - %span.issue-no-comments - %i.fa.fa-comments + = link_to issue_path(issue) + "#notes", class: "issue-no-comments" do + = icon('comments') = 0 .issue-info diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 5842d7ff16..2f2ebc0894 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -23,13 +23,13 @@ = link_to_member(merge_request.source_project, merge_request.assignee, name: false, title: "Assigned to :name") - if note_count > 0   - %span - %i.fa.fa-comments + = link_to merge_request_path(merge_request) + "#notes" do + = icon('comments') = note_count - else   - %span.merge-request-no-comments - %i.fa.fa-comments + = link_to merge_request_path(merge_request) + "#notes", class: "merge-request-no-comments" do + = icon('comments') = 0 .merge-request-info From 08ee38a1c5d55d66070aa6430afa991a395a7da0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:19:25 +0100 Subject: [PATCH 364/410] Link milestone item to issues with that milestone --- app/views/projects/issues/_issue.html.haml | 8 +++++--- .../projects/merge_requests/_merge_request.html.haml | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 4f6ff20caf..2e50b60331 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -28,11 +28,13 @@ = 0 .issue-info - = "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe + #{issue.to_reference} · + opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} + by #{link_to_member(@project, issue.author, avatar: false)} - if issue.milestone   - %span - %i.fa.fa-clock-o + = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do + = icon('clock-o') = issue.milestone.title - if issue.tasks? %span.task-status diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 2f2ebc0894..4940bc7843 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -33,11 +33,13 @@ = 0 .merge-request-info - = "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe + \##{merge_request.iid} · + opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} + by #{link_to_member(@project, merge_request.author, avatar: false)} - if merge_request.milestone_id?   - %span - %i.fa.fa-clock-o + = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do + = icon('clock-o') = merge_request.milestone.title - if merge_request.target_project.default_branch != merge_request.target_branch   From fbe6432934973021acddcff1d3b0fd3ed5844d2b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:20:03 +0100 Subject: [PATCH 365/410] Move labels to second line of issue/MR list item --- app/views/projects/issues/_issue.html.haml | 8 +++++--- .../projects/merge_requests/_merge_request.html.haml | 12 +++++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 2e50b60331..1eb71990e5 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -6,9 +6,6 @@ .issue-title %span.issue-title-text = link_to_gfm issue.title, issue_path(issue), class: "row_title" - .issue-labels - - issue.labels.each do |label| - = link_to_label(label, project: issue.project) .pull-right.light - if issue.closed? %span @@ -36,7 +33,12 @@ = link_to namespace_project_issues_path(issue.project.namespace, issue.project, milestone_title: issue.milestone.title) do = icon('clock-o') = issue.milestone.title + - if issue.labels.any? +   + - issue.labels.each do |label| + = link_to_label(label, project: issue.project) - if issue.tasks? +   %span.task-status = issue.task_status diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 4940bc7843..8246a432c7 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -3,19 +3,16 @@ .merge-request-title %span.merge-request-title-text = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title" - .merge-request-labels - - merge_request.labels.each do |label| - = link_to_label(label, project: merge_request.project) .pull-right.light - if ci_commit = render_ci_status(ci_commit) - if merge_request.merged? %span - %i.fa.fa-check + = icon('check') MERGED - elsif merge_request.closed? %span - %i.fa.fa-ban + = icon('ban') CLOSED - note_count = merge_request.mr_and_commit_notes.user.count - if merge_request.assignee @@ -41,12 +38,17 @@ = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do = icon('clock-o') = merge_request.milestone.title + - if merge_request.labels.any? +   + - merge_request.labels.each do |label| + = link_to_label(label, project: merge_request.project) - if merge_request.target_project.default_branch != merge_request.target_branch   %span %i.fa.fa-code-fork = merge_request.target_branch - if merge_request.tasks? +   %span.task-status = merge_request.task_status From 58dad2a9dadea647b5665e1de6a6e997484b96b1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:43:23 +0100 Subject: [PATCH 366/410] Remove bottom margin from page-titles --- app/assets/stylesheets/framework/typography.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 2c4a58c8db..aef338cfa5 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -181,6 +181,10 @@ body { line-height: 1.3; font-size: 1.25em; font-weight: 600; + + &:last-child { + margin-bottom: 0; + } } .page-title-empty { From 3ae7dba931b880b8090edffb247ebfe32d26648e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:46:15 +0100 Subject: [PATCH 367/410] Use gl-padding instead of 15px/20px where appropriate --- app/assets/stylesheets/framework.scss | 3 ++- app/assets/stylesheets/framework/common.scss | 10 +++++++++- app/assets/stylesheets/framework/files.scss | 4 +--- app/assets/stylesheets/framework/panels.scss | 15 +++++++++++++++ app/assets/stylesheets/framework/tables.scss | 2 ++ app/assets/stylesheets/pages/merge_requests.scss | 2 +- app/assets/stylesheets/pages/note_form.scss | 7 +++---- app/assets/stylesheets/pages/projects.scss | 2 +- app/views/profiles/notifications/show.html.haml | 4 +--- app/views/projects/compare/show.html.haml | 4 ++-- .../merge_requests/_new_compare.html.haml | 3 +-- app/views/projects/merge_requests/_show.html.haml | 2 +- app/views/shared/issuable/_context.html.haml | 6 +++--- 13 files changed, 42 insertions(+), 22 deletions(-) create mode 100644 app/assets/stylesheets/framework/panels.scss diff --git a/app/assets/stylesheets/framework.scss b/app/assets/stylesheets/framework.scss index 1ec9d2fd84..48a4971c8f 100644 --- a/app/assets/stylesheets/framework.scss +++ b/app/assets/stylesheets/framework.scss @@ -1,9 +1,9 @@ @import "framework/fonts"; @import "framework/variables"; @import "framework/mixins"; -@import "framework/layout"; @import 'framework/tw_bootstrap_variables'; @import 'framework/tw_bootstrap'; +@import "framework/layout"; @import "framework/avatar.scss"; @import "framework/blocks.scss"; @@ -25,6 +25,7 @@ @import "framework/markdown_area.scss"; @import "framework/mobile.scss"; @import "framework/pagination.scss"; +@import "framework/panels.scss"; @import "framework/selects.scss"; @import "framework/sidebar.scss"; @import "framework/tables.scss"; diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 61689aff57..61ecd58e6c 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -7,7 +7,7 @@ /** COMMON CLASSES **/ .prepend-top-10 { margin-top:10px } -.prepend-top-default { margin-top: $gl-padding; } +.prepend-top-default { margin-top: $gl-padding !important; } .prepend-top-20 { margin-top:20px } .prepend-left-10 { margin-left:10px } .prepend-left-20 { margin-left:20px } @@ -52,6 +52,10 @@ pre { } } +hr { + margin: $gl-padding 0; +} + .dropdown-menu > li > a { text-shadow: none; } @@ -433,3 +437,7 @@ table { .space-right { margin-right: 10px; } + +.alert, .progress { + margin-bottom: $gl-padding; +} diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss index 35db00281e..6bf2857e83 100644 --- a/app/assets/stylesheets/framework/files.scss +++ b/app/assets/stylesheets/framework/files.scss @@ -8,7 +8,6 @@ border: none; border-top: 1px solid #E7E9EE; border-bottom: 1px solid #E7E9EE; - margin-bottom: 1em; &.readme-holder { border-bottom: 0; @@ -25,7 +24,7 @@ text-shadow: 0 1px 1px #fff; margin: 0; text-align: left; - padding: 10px 15px; + padding: 10px $gl-padding; .file-actions { float: right; @@ -171,4 +170,3 @@ } } } - diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss new file mode 100644 index 0000000000..406aff3d72 --- /dev/null +++ b/app/assets/stylesheets/framework/panels.scss @@ -0,0 +1,15 @@ +.panel { + margin-bottom: $gl-padding; + + .panel-heading { + padding: 10px $gl-padding; + } + .panel-body { + padding: $gl-padding; + + .form-actions { + margin: -$gl-padding; + margin-top: $gl-padding; + } + } +} diff --git a/app/assets/stylesheets/framework/tables.scss b/app/assets/stylesheets/framework/tables.scss index 66e16e8df7..793ab3d9bb 100644 --- a/app/assets/stylesheets/framework/tables.scss +++ b/app/assets/stylesheets/framework/tables.scss @@ -6,6 +6,8 @@ table { &.table { + margin-bottom: $gl-padding; + .dropdown-menu a { text-decoration: none; } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 08e4bcdf52..177cf6ca45 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -87,7 +87,7 @@ .mr-widget-body, .ci_widget, .mr-widget-footer { - padding: 15px; + padding: $gl-padding; } .normal { diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 268fc995aa..02db44c8eb 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -7,6 +7,7 @@ } .reply-btn { @extend .btn-primary; + margin: 10px $gl-padding; } .diff-file .diff-content { tr.line_holder:hover { @@ -38,9 +39,8 @@ } .new_note, .edit_note { - .buttons { - margin-top: 8px; - margin-bottom: 3px; + .note-form-actions { + margin-top: $gl-padding; } .note-preview-holder { @@ -150,7 +150,6 @@ .discussion-reply-holder { background: $background-color; - padding: 10px 15px; border-top: 1px solid $border-color; } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 4a0fe54684..b3054e3624 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -376,7 +376,7 @@ table.table.protected-branches-list tr.no-border { .project-stats { text-align: center; - margin-top: 15px; + margin-top: $gl-padding; margin-bottom: 0; padding-top: 10px; padding-bottom: 4px; diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index 8eebd96b67..d4d9f24627 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -53,9 +53,7 @@ .form-actions = f.submit 'Save changes', class: "btn btn-create" -.clearfix - %hr -.row.all-notifications +.row.all-notifications.prepend-top-default .col-md-6 %p You can also specify notification level per group or per project. diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 39755efd2f..51088a7dea 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -7,11 +7,11 @@ = render "form" - if @commits.present? - .prepend-top-20 + .prepend-top-default = render "projects/commits/commit_list" = render "projects/diffs/diffs", diffs: @diffs, project: @project - else - .light-well.prepend-top-20 + .light-well.prepend-top-default .center %h4 There isn't anything to compare. diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index d9eff1f932..6def9d6266 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -37,7 +37,7 @@ %h4 Compare failed %p We can't compare selected branches. It may be because of huge diff. Please try again or select different branches. - else - .light-well.append-bottom-10 + .light-well.append-bottom-default .center %h4 There isn't anything to merge. @@ -86,4 +86,3 @@ return; } }); - diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index eeaa72ed21..94bd154aeb 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -8,7 +8,7 @@ .merge-request-details.issuable-details = render "projects/merge_requests/show/mr_title" = render "projects/merge_requests/show/mr_box" - .append-bottom-20.mr-source-target.prepend-top-default + .append-bottom-default.mr-source-target.prepend-top-default - if @merge_request.open? .pull-right - if @merge_request.source_branch_exists? diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml index be66256c7b..f1646b4ce6 100644 --- a/app/views/shared/issuable/_context.html.haml +++ b/app/views/shared/issuable/_context.html.haml @@ -1,5 +1,5 @@ = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| - %div.prepend-top-20 + %div.prepend-top-default .issuable-context-title %label Assignee: @@ -11,7 +11,7 @@ - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true, current_user: true) - %div.prepend-top-20.clearfix + %div.prepend-top-default.clearfix .issuable-context-title %label Milestone: @@ -31,7 +31,7 @@ - if current_user - subscribed = issuable.subscribed?(current_user) - %div.prepend-top-20.clearfix + %div.prepend-top-default.clearfix .issuable-context-title %label Subscription: From c95b64d1cad8aa8f0861feba0c919ae3d1722df0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:46:55 +0100 Subject: [PATCH 368/410] Fix alignment of buttons in issue header --- app/assets/stylesheets/framework/issue_box.scss | 5 +++-- app/assets/stylesheets/pages/issuable.scss | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index 93377e45e7..f12d68b5a1 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -7,8 +7,9 @@ .issue-box { @include border-radius(2px); - display: inline-block; - padding: 10px $gl-padding; + display: block; + float: left; + padding: 0 $gl-padding; font-weight: normal; margin-right: 10px; font-size: $gl-font-size; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 3a08ee70bc..51d8e5b465 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -51,11 +51,12 @@ .issuable-details { .page-title { - margin-top: -15px; - padding: 10px 0; + margin-top: -$gl-padding; + padding: 7px 0; margin-bottom: 0; color: #5c5d5e; font-size: 16px; + line-height: 42px; .author { color: #5c5d5e; From aa01b23937d05d4110cf24683bbe96dcc77c730c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:47:19 +0100 Subject: [PATCH 369/410] Fix padding around editor path, input and select box. --- app/assets/stylesheets/pages/editor.scss | 48 +++++++++-------------- app/views/projects/blob/_editor.html.haml | 22 +++++------ 2 files changed, 30 insertions(+), 40 deletions(-) diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index e2c521af91..39d916cd33 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -19,48 +19,38 @@ color: #B94A48; } } - .commit-button-annotation { - display: inline-block; - margin: 0; - padding: 2px; - - > * { - float: left; - } - - .message { - display: inline-block; - margin: 5px 8px 0 8px; - } - } .file-title { @extend .monospace; + + line-height: 42px; + padding-top: 7px; + padding-bottom: 7px; } .editor-ref { background: $background-color; - padding: 11px 15px; + padding-right: $gl-padding; border-right: 1px solid $border-color; - display: inline-block; - margin: -5px -5px; + display: block; + float: left; margin-right: 10px; } .editor-file-name { - .new-file-name { - display: inline-block; - width: 450px; - } - - .form-control { - margin-top: -3px; - } + @extend .monospace; + + float: left; + margin-right: 10px; } - .form-actions { - margin: -$gl-padding; - margin-top: 0; - padding: $gl-padding + .new-file-name { + display: inline-block; + width: 450px; + float: left; + } + + .select2 { + float: right; } } diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index f1ad0c3c40..333f5d470e 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -1,19 +1,19 @@ -.file-holder.file - .file-title +.file-holder.file.append-bottom-default + .file-title.clearfix .editor-ref - %i.fa.fa-code-fork + = icon('code-fork') = ref %span.editor-file-name - - if @path - %span.monospace - = @path + = @path - - if current_action?(:new) || current_action?(:create) + - if current_action?(:new) || current_action?(:create) + %span.editor-file-name \/ - = text_field_tag 'file_name', params[:file_name], placeholder: "File name", - required: true, class: 'form-control new-file-name js-quick-submit' - .pull-right - = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' + = text_field_tag 'file_name', params[:file_name], placeholder: "File name", + required: true, class: 'form-control new-file-name js-quick-submit' + + .pull-right + = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' .file-content.code %pre.js-edit-mode-pane#editor From 0159d78a3b7dbc58f74c90c41adf8ef3049ed0aa Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:48:06 +0100 Subject: [PATCH 370/410] Remove padding around form-actions --- app/assets/stylesheets/framework/forms.scss | 7 ++++--- app/views/profiles/notifications/show.html.haml | 2 +- app/views/projects/releases/edit.html.haml | 2 +- app/views/shared/issuable/_form.html.haml | 3 ++- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 0edfe24f19..ddb522bb63 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -22,9 +22,10 @@ input[type='text'].danger { } .form-actions { - padding: 17px 20px 18px; - margin-top: 18px; - margin-bottom: 18px; + margin: -$gl-padding; + margin-top: 0; + margin-bottom: -$gl-padding; + padding: $gl-padding; background-color: $background-color; border-top: 1px solid $border-color; } diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index d4d9f24627..0bcadc965f 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -50,7 +50,7 @@ Watch %p You will receive notifications for any activity - .form-actions + .gray-content-block = f.submit 'Save changes', class: "btn btn-create" .row.all-notifications.prepend-top-default diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml index f516b65ecd..bc80f2f29a 100644 --- a/app/views/projects/releases/edit.html.haml +++ b/app/views/projects/releases/edit.html.haml @@ -14,6 +14,6 @@ = render 'projects/zen', f: f, attr: :description, classes: 'description js-quick-submit form-control' = render 'projects/notes/hints' .error-alert - .prepend-top-default + .form-actions.prepend-top-default = f.submit 'Save changes', class: 'btn btn-save' = link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel" diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index 0fc74d7d2b..7558b37f83 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -93,7 +93,8 @@ %p.help-block = link_to 'Change branches', mr_change_branches_path(@merge_request) -.form-actions +- is_footer = !(issuable.is_a?(MergeRequest) && issuable.new_record?) +.gray-content-block{class: (is_footer ? "footer-block" : "middle-block")} - if !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project)) && !issuable.persisted? %p Please review the From 3ad1d320ef3f3aa5a0896a9b30a2fba791696f58 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:48:51 +0100 Subject: [PATCH 371/410] Remove duplicated styling for center top menus --- app/assets/stylesheets/pages/commit.scss | 13 ----------- .../stylesheets/pages/merge_requests.scss | 23 ++----------------- app/views/projects/commit/_ci_menu.html.haml | 2 +- app/views/projects/diffs/_diffs.html.haml | 2 +- .../merge_requests/_discussion.html.haml | 2 +- .../merge_requests/_new_submit.html.haml | 5 ++-- .../projects/merge_requests/_show.html.haml | 2 +- .../merge_requests/show/_commits.html.haml | 2 +- 8 files changed, 9 insertions(+), 42 deletions(-) diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index a0e5f7554e..74bbe0880f 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -108,16 +108,3 @@ z-index: 2; } } - -.commit-ci-menu { - padding: 0; - margin: 0; - list-style: none; - margin-top: 5px; - height: 56px; - margin: -16px; - padding: 16px; - text-align: center; - margin-top: 0px; - margin-bottom: 2px; -} diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 177cf6ca45..017a86bcd9 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -4,7 +4,6 @@ */ .mr-state-widget { background: #F7F8FA; - margin-bottom: 20px; color: $gl-gray; border: 1px solid #dce0e6; @include border-radius(2px); @@ -116,26 +115,8 @@ } } -.merge-request .merge-request-tabs { - @include nav-menu; - margin: -$gl-padding; - padding: $gl-padding; - text-align: center; - margin-bottom: 1px; -} - -// Mobile -@media (max-width: 480px) { - .merge-request .merge-request-tabs { - margin: 0; - padding: 0; - - li { - a { - padding: 0; - } - } - } +.merge-request-details { + margin-bottom: $gl-padding; } .mr_source_commit, diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml index c73ba74f5e..76dc87a882 100644 --- a/app/views/projects/commit/_ci_menu.html.haml +++ b/app/views/projects/commit/_ci_menu.html.haml @@ -1,4 +1,4 @@ -%ul.center-top-menu.commit-ci-menu +%ul.center-top-menu.no-top.no-bottom.commit-ci-menu = nav_link(path: 'commit#show') do = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do Changes diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 416fb4da07..f9d661d59d 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -3,7 +3,7 @@ - diff_files = safe_diff_files(diffs) -.gray-content-block.second-block.oneline-block +.gray-content-block.middle-block.oneline-block .inline-parallel-buttons .btn-group = inline_diff_btn diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index 2b3c3eff5e..4a192aeb2c 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -7,7 +7,7 @@ = render 'shared/show_aside' -.gray-content-block.second-block.oneline-block +.gray-content-block.middle-block.oneline-block .row .col-md-9 .votes-holder.pull-right diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 6244d3ba0b..72132344c8 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -19,7 +19,7 @@ = f.hidden_field :target_branch .mr-compare.merge-request - %ul.merge-request-tabs + %ul.merge-request-tabs.center-top-menu.no-top.no-bottom %li.commits-tab = link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do Commits @@ -31,7 +31,7 @@ .tab-content #commits.commits.tab-pane - = render "projects/commits/commits", project: @project + = render "projects/merge_requests/show/commits" #diffs.diffs.tab-pane.active - if @diffs.present? = render "projects/diffs/diffs", diffs: @diffs, project: @project @@ -57,4 +57,3 @@ diffs_loaded: true, commits_loaded: true }); - diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 94bd154aeb..e7eb006659 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -40,7 +40,7 @@ = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" - if @commits.present? - %ul.merge-request-tabs + %ul.merge-request-tabs.center-top-menu.no-top.no-bottom %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do Discussion diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml index 478054db51..7f904ec42a 100644 --- a/app/views/projects/merge_requests/show/_commits.html.haml +++ b/app/views/projects/merge_requests/show/_commits.html.haml @@ -1,4 +1,4 @@ -.gray-content-block.second-block.oneline-block +.gray-content-block.middle-block.oneline-block = icon("sort-amount-desc") Most recent commits displayed first From 1c595681e5b60cfdf402abc6fd43c282897b6260 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:49:29 +0100 Subject: [PATCH 372/410] Remove double border between gray blocks --- app/assets/stylesheets/framework/blocks.scss | 4 ++++ app/views/projects/commit/show.html.haml | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index 1635df9c97..dec50a0e0f 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -114,3 +114,7 @@ right: 10px; } } + +.block-connector { + margin-top: -1px; +} diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 85e203cbe5..069b8b1f16 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -1,6 +1,9 @@ - page_title "#{@commit.title} (#{@commit.short_id})", "Commits" = render "projects/commits/header_title" = render "commit_box" -= render "ci_menu" if @ci_commit +- if @ci_commit + = render "ci_menu" +- else + %div.block-connector = render "projects/diffs/diffs", diffs: @diffs, project: @project = render "projects/notes/notes_with_form" From 9ea72430921efaa4a1a79ea21c54763bda8bba78 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:49:46 +0100 Subject: [PATCH 373/410] Fix padding of no readme and empty repo messages --- app/views/projects/_readme.html.haml | 25 +++++++++++++------------ app/views/projects/empty.html.haml | 13 ++++++------- app/views/projects/show.html.haml | 2 +- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml index b5ef0aca54..d1191928d4 100644 --- a/app/views/projects/_readme.html.haml +++ b/app/views/projects/_readme.html.haml @@ -7,15 +7,16 @@ = cache(readme_cache_key) do = render_readme(readme) - else - %h3.page-title - This project does not have README yet - - if can?(current_user, :push_code, @project) - %p.slead - A - %code README - file contains information about other files in a repository and is commonly - distributed with computer software, forming part of its documentation. - %br - We recommend you to - = link_to "add README", new_readme_path, class: 'underlined-link' - file to the repository and GitLab will render it here instead of this message. + .gray-content-block.second-block.center + %h3.page-title + This project does not have README yet + - if can?(current_user, :push_code, @project) + %p + A + %code README + file contains information about other files in a repository and is commonly + distributed with computer software, forming part of its documentation. + %p + We recommend you to + = link_to "add README", new_readme_path, class: 'underlined-link' + file to the repository and GitLab will render it here instead of this message. diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index c3858e78ca..950ab33825 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -5,17 +5,16 @@ = render "home_panel" -.gray-content-block.center +.gray-content-block.second-block.center %h3.page-title The repository for this project is empty - - if can?(current_user, :download_code, @project) + - if can?(current_user, :push_code, @project) %p If you already have files you can push them using command line instructions below. - %br - - if can?(current_user, :push_code, @project) - Otherwise you can start with - = link_to "adding README", new_readme_path, class: 'underlined-link' - file to this project. + %p + Otherwise you can start with + = link_to "adding README", new_readme_path, class: 'underlined-link' + file to this project. - if can?(current_user, :download_code, @project) .prepend-top-20 diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 585caf674c..9c7a5584da 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -11,7 +11,7 @@ = render "home_panel" -.project-stats.gray-content-block +.project-stats.gray-content-block.second-block %ul.nav.nav-pills %li = link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do From ba4bf27fe486af1790844ac40bc188c6462720b8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:49:59 +0100 Subject: [PATCH 374/410] Use table-holder where appropriate --- app/views/profiles/applications.html.haml | 84 ++++++++++---------- app/views/profiles/keys/_key_table.html.haml | 16 ++-- 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml index 2342936a5d..0436c2213d 100644 --- a/app/views/profiles/applications.html.haml +++ b/app/views/profiles/applications.html.haml @@ -15,24 +15,25 @@ .pull-right = link_to 'New Application', new_oauth_application_path, class: 'btn btn-success' - if @applications.any? - %table.table.table-striped - %thead - %tr - %th Name - %th Callback URL - %th Clients - %th - %th - %tbody - - @applications.each do |application| - %tr{:id => "application_#{application.id}"} - %td= link_to application.name, oauth_application_path(application) - %td - - application.redirect_uri.split.each do |uri| - %div= uri - %td= application.access_tokens.count - %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm' - %td= render 'doorkeeper/applications/delete_form', application: application + .table-holder + %table.table.table-striped + %thead + %tr + %th Name + %th Callback URL + %th Clients + %th + %th + %tbody + - @applications.each do |application| + %tr{:id => "application_#{application.id}"} + %td= link_to application.name, oauth_application_path(application) + %td + - application.redirect_uri.split.each do |uri| + %div= uri + %td= application.access_tokens.count + %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm' + %td= render 'doorkeeper/applications/delete_form', application: application .oauth-authorized-applications.prepend-top-20 - if user_oauth_applications? @@ -40,29 +41,30 @@ Authorized applications - if @authorized_tokens.any? - %table.table.table-striped - %thead - %tr - %th Name - %th Authorized At - %th Scope - %th - %tbody - - @authorized_apps.each do |app| - - token = app.authorized_tokens.order('created_at desc').first - %tr{:id => "application_#{app.id}"} - %td= app.name - %td= token.created_at - %td= token.scopes - %td= render 'doorkeeper/authorized_applications/delete_form', application: app - - @authorized_anonymous_tokens.each do |token| + .table-holder + %table.table.table-striped + %thead %tr - %td - Anonymous - %div.help-block - %em Authorization was granted by entering your username and password in the application. - %td= token.created_at - %td= token.scopes - %td= render 'doorkeeper/authorized_applications/delete_form', token: token + %th Name + %th Authorized At + %th Scope + %th + %tbody + - @authorized_apps.each do |app| + - token = app.authorized_tokens.order('created_at desc').first + %tr{:id => "application_#{app.id}"} + %td= app.name + %td= token.created_at + %td= token.scopes + %td= render 'doorkeeper/authorized_applications/delete_form', application: app + - @authorized_anonymous_tokens.each do |token| + %tr + %td + Anonymous + %div.help-block + %em Authorization was granted by entering your username and password in the application. + %td= token.created_at + %td= token.scopes + %td= render 'doorkeeper/authorized_applications/delete_form', token: token - else %p.light You don't have any authorized applications diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml index ef0075aad3..8c9d546af4 100644 --- a/app/views/profiles/keys/_key_table.html.haml +++ b/app/views/profiles/keys/_key_table.html.haml @@ -1,6 +1,6 @@ - is_admin = defined?(admin) ? true : false -.panel.panel-default - - if @keys.any? +- if @keys.any? + .table-holder %table.table %thead.panel-heading %tr @@ -11,9 +11,9 @@ %tbody - @keys.each do |key| = render 'profiles/keys/key', key: key, is_admin: is_admin - - else - .nothing-here-block - - if is_admin - User has no ssh keys - - else - There are no SSH keys with access to your account. +- else + .nothing-here-block + - if is_admin + User has no ssh keys + - else + There are no SSH keys with access to your account. From d5fd9b26b13678a9e09a56890af10babe46362f0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:50:12 +0100 Subject: [PATCH 375/410] Remove style duplication between snippet and issue detail pages --- app/assets/stylesheets/pages/snippets.scss | 44 +++------------------ app/views/shared/snippets/_header.html.haml | 7 ++-- 2 files changed, 9 insertions(+), 42 deletions(-) diff --git a/app/assets/stylesheets/pages/snippets.scss b/app/assets/stylesheets/pages/snippets.scss index 242783a7b7..bb74e50151 100644 --- a/app/assets/stylesheets/pages/snippets.scss +++ b/app/assets/stylesheets/pages/snippets.scss @@ -27,56 +27,22 @@ } .snippet-holder { - .snippet-details { - .page-title { - margin-top: -15px; - padding: 10px 0; - margin-bottom: 0; - color: #5c5d5e; - font-size: 16px; - - .author { - color: #5c5d5e; - } - - .snippet-id { - color: #5c5d5e; - } - } - - .snippet-title { - margin: 0; - font-size: 23px; - color: #313236; - } - - @media (max-width: $screen-md-max) { - .new-snippet-link { - display: none; - } - } - - @media (max-width: $screen-sm-max) { - .creator, - .page-title .btn-close { - display: none; - } - } - } + margin-bottom: -$gl-padding; .file-holder { border-top: 0; } } - .snippet-box { @include border-radius(2px); - display: inline-block; - padding: 10px $gl-padding; + display: block; + float: left; + padding: 0 $gl-padding; font-weight: normal; margin-right: 10px; font-size: $gl-font-size; border: 1px solid; + line-height: 40px; } diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml index 0a4a790ec5..3524102928 100644 --- a/app/views/shared/snippets/_header.html.haml +++ b/app/views/shared/snippets/_header.html.haml @@ -1,9 +1,9 @@ -.snippet-details +.issuable-details .page-title .snippet-box{class: visibility_level_color(@snippet.visibility_level)} = visibility_level_icon(@snippet.visibility_level) = visibility_level_label(@snippet.visibility_level) - %span.snippet-id Snippet ##{@snippet.id} + Snippet ##{@snippet.id} %span.creator · created by #{link_to_member(@project, @snippet.author, size: 24)} · @@ -19,6 +19,7 @@ = render "projects/snippets/actions" - else = render "snippets/actions" + .gray-content-block.middle-block - %h2.snippet-title + %h2.issue-title = gfm escape_once(@snippet.title) From 09396b2eaa529a7c9049e482142c3885c4510b90 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:50:41 +0100 Subject: [PATCH 376/410] Render all form controls at the same height --- app/assets/stylesheets/framework/forms.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index ddb522bb63..80283a56ec 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -74,6 +74,8 @@ label { .form-control { @include box-shadow(none); + height: 42px; + padding: 8px $gl-padding; } .wiki-content { From 8196d269ebcc15925bf532a9edcccf75248bfffb Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:50:50 +0100 Subject: [PATCH 377/410] No bottom margin for form help blocks --- app/assets/stylesheets/framework/forms.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 80283a56ec..cc92966c45 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -95,3 +95,7 @@ label { background-color: #f7f8fa; } } + +.help-block { + margin-bottom: 0; +} From ac389c6a3ec7f54174edb243b54b279a4916c30a Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:51:10 +0100 Subject: [PATCH 378/410] Consistent styling for dashboard groups gray block --- app/views/dashboard/groups/index.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index f3f3f58111..d5b7e729e7 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -8,8 +8,8 @@ = link_to new_group_path, class: "btn btn-new" do %i.fa.fa-plus New Group - .title Welcome to the groups! - Group members have access to all group projects. + .oneline + Group members have access to all group projects. %ul.content-list - @group_members.each do |group_member| From 6ec9999a8e49e5374fe9e10dc60dc98a7ede1075 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:51:25 +0100 Subject: [PATCH 379/410] Use consistent border color --- app/assets/stylesheets/pages/note_form.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss index 02db44c8eb..e1a72af001 100644 --- a/app/assets/stylesheets/pages/note_form.scss +++ b/app/assets/stylesheets/pages/note_form.scss @@ -79,8 +79,8 @@ padding: $gl-padding; margin-left: -$gl-padding; margin-right: -$gl-padding; - border-right: 1px solid #ECEEF1; - border-top: 1px solid #ECEEF1; + border-right: 1px solid $border-color; + border-top: 1px solid $border-color; margin-bottom: -$gl-padding; } From aec4b290fd154ac382b49aefed4e34386785630f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:51:37 +0100 Subject: [PATCH 380/410] Remove unused style --- app/assets/stylesheets/pages/projects.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index b3054e3624..ac2d50f9c1 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -18,10 +18,6 @@ } } -.project-edit-content { - padding: 7px; -} - .project-name-holder { .help-inline { vertical-align: top; From 9517b6211782619ba4d4f9f1322230bd833c0a2c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:20:11 +0100 Subject: [PATCH 381/410] Link MR list item branch name to branch --- app/assets/stylesheets/framework/common.scss | 4 ---- app/views/projects/merge_requests/_merge_request.html.haml | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss index 61689aff57..1c7c31b02a 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -337,10 +337,6 @@ table { text-align: center; } -.task-status { - margin-left: 10px; -} - #nprogress .spinner { top: 15px !important; right: 10px !important; diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 8246a432c7..60dd4f4f87 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -44,8 +44,8 @@ = link_to_label(label, project: merge_request.project) - if merge_request.target_project.default_branch != merge_request.target_branch   - %span - %i.fa.fa-code-fork + = link_to namespace_project_commits_path(merge_request.project.namespace, merge_request.project, merge_request.target_branch) do + = icon('code-fork') = merge_request.target_branch - if merge_request.tasks?   From 080a0c026bbd2f651eab68c816991e31e57e11ef Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:52:36 +0100 Subject: [PATCH 382/410] Use same text color for commit box as issue title --- app/assets/stylesheets/pages/commit.scss | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss index a0e5f7554e..39cde517c1 100644 --- a/app/assets/stylesheets/pages/commit.scss +++ b/app/assets/stylesheets/pages/commit.scss @@ -2,10 +2,6 @@ display: block; } -.commit-title{ - margin-bottom: 10px; -} - .commit-author, .commit-committer{ display: block; color: #999; @@ -41,6 +37,8 @@ .commit-box { .commit-title { margin: 0; + font-size: 23px; + color: #313236; } .commit-description { From 67ff47b39cfa47a6904bd5e4e84499a098a158cf Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:53:11 +0100 Subject: [PATCH 383/410] Capitalize tab titles --- app/helpers/blob_helper.rb | 2 +- app/views/projects/blob/edit.html.haml | 6 +++--- features/steps/project/source/browse_files.rb | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 77d99140c4..df5f5fae23 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -60,7 +60,7 @@ module BlobHelper if Gitlab::MarkupHelper.previewable?(filename) 'Preview' else - 'Preview changes' + 'Preview Changes' end end diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml index 5674516525..a47fe7ede8 100644 --- a/app/views/projects/blob/edit.html.haml +++ b/app/views/projects/blob/edit.html.haml @@ -5,12 +5,12 @@ %ul.center-top-menu.no-bottom.js-edit-mode %li.active = link_to '#editor' do - %i.fa.fa-edit - Edit file + = icon('edit') + Edit File %li = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do - %i.fa.fa-eye + = icon('eye') = editing_preview_title(@blob.name) = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input js-edit-blob-form') do diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index f40e0f0d52..bee3150677 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -87,7 +87,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I click link "Diff"' do - click_link 'Preview changes' + click_link 'Preview Changes' end step 'I click on "Commit Changes"' do From e9e9f537e84c1b3a1df7b4920d3b696029ba6dcc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:53:20 +0100 Subject: [PATCH 384/410] Truncate submodule commit SHAs to 7 characters --- app/helpers/diff_helper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index bfd3622a6a..24134310fc 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -146,9 +146,9 @@ module DiffHelper def submodule_link(blob, ref, repository = @repository) tree, commit = submodule_links(blob, ref, repository) commit_id = if commit.nil? - blob.id[0..10] + Commit.truncate_sha(blob.id) else - link_to "#{blob.id[0..10]}", commit + link_to Commit.truncate_sha(blob.id), commit end [ From b8a926fab0acc85dd22456366390a93f4c4b6ad9 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:53:28 +0100 Subject: [PATCH 385/410] Don't use success state for explore projects search button --- app/views/explore/projects/_filter.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml index 2761272aa8..28b12c8dca 100644 --- a/app/views/explore/projects/_filter.html.haml +++ b/app/views/explore/projects/_filter.html.haml @@ -3,7 +3,7 @@ .form-group = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false .form-group - = button_tag 'Search', class: "btn btn-success" + = button_tag 'Search', class: "btn" .pull-right.hidden-sm.hidden-xs - if current_user From 12dd4c7c8a393cea2f264832e38df4286591f48c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:53:56 +0100 Subject: [PATCH 386/410] Use "Delete" in milestone and label delete buttons instead of "Remove" --- app/views/projects/labels/_label.html.haml | 2 +- app/views/projects/milestones/_milestone.html.haml | 2 +- features/steps/admin/labels.rb | 2 +- features/steps/project/issues/labels.rb | 2 +- features/steps/project/issues/milestones.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index c6ebfa281a..b70a9fc9fe 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -7,4 +7,4 @@ - if can? current_user, :admin_label, @project = link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm' - = link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} + = link_to 'Delete', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index 5e93d55b1f..334172b976 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -31,4 +31,4 @@ = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-xs btn-close" = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-xs btn-remove" do %i.fa.fa-trash-o - Remove + Delete diff --git a/features/steps/admin/labels.rb b/features/steps/admin/labels.rb index b45d98658b..2d5db8f739 100644 --- a/features/steps/admin/labels.rb +++ b/features/steps/admin/labels.rb @@ -17,7 +17,7 @@ class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps step 'I remove label \'bug\'' do page.within "#label_#{bug_label.id}" do - click_link 'Remove' + click_link 'Delete' end end diff --git a/features/steps/project/issues/labels.rb b/features/steps/project/issues/labels.rb index d656acf422..047cf701bb 100644 --- a/features/steps/project/issues/labels.rb +++ b/features/steps/project/issues/labels.rb @@ -9,7 +9,7 @@ class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps step 'I remove label \'bug\'' do page.within "#label_#{bug_label.id}" do - click_link 'Remove' + click_link 'Delete' end end diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb index c8708572ec..e2eda51149 100644 --- a/features/steps/project/issues/milestones.rb +++ b/features/steps/project/issues/milestones.rb @@ -63,7 +63,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps end step 'I click link to remove milestone' do - click_link 'Remove' + click_link 'Delete' end step 'I should see no milestones' do From f8e577591b29b4cde211f275db6f870f98f58eb3 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:54:12 +0100 Subject: [PATCH 387/410] Don't use success state for "Download zip" button --- app/views/projects/repositories/_download_archive.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index 07c24950ee..b9486a9b49 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -3,10 +3,10 @@ - split_button = split_button || false - if split_button == true %span.btn-group{class: btn_class} - = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn btn-success col-xs-10', rel: 'nofollow' do + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do %i.fa.fa-download %span Download zip - %a.col-xs-2.btn.btn-success.dropdown-toggle{ 'data-toggle' => 'dropdown' } + %a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } %span.caret %span.sr-only Select Archive Format From 40760d3f7bc1b5fbed17bff2f185e92538590484 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:54:24 +0100 Subject: [PATCH 388/410] Add header title to import pages --- app/views/import/bitbucket/status.html.haml | 1 + app/views/import/fogbugz/new.html.haml | 1 + app/views/import/fogbugz/new_user_map.html.haml | 1 + app/views/import/fogbugz/status.html.haml | 1 + app/views/import/github/status.html.haml | 1 + app/views/import/gitlab/status.html.haml | 1 + app/views/import/gitorious/status.html.haml | 1 + app/views/import/google_code/new.html.haml | 3 ++- .../import/google_code/new_user_map.html.haml | 17 +++++++++-------- app/views/import/google_code/status.html.haml | 1 + 10 files changed, 19 insertions(+), 9 deletions(-) diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml index 1f09a27e2d..aec2e836c9 100644 --- a/app/views/import/bitbucket/status.html.haml +++ b/app/views/import/bitbucket/status.html.haml @@ -1,4 +1,5 @@ - page_title "Bitbucket import" +- header_title "Projects", root_path %h3.page-title %i.fa.fa-bitbucket Import projects from Bitbucket diff --git a/app/views/import/fogbugz/new.html.haml b/app/views/import/fogbugz/new.html.haml index e1bb88ca4e..5515fad6f4 100644 --- a/app/views/import/fogbugz/new.html.haml +++ b/app/views/import/fogbugz/new.html.haml @@ -1,4 +1,5 @@ - page_title "FogBugz Import" +- header_title "Projects", root_path %h3.page-title %i.fa.fa-bug Import projects from FogBugz diff --git a/app/views/import/fogbugz/new_user_map.html.haml b/app/views/import/fogbugz/new_user_map.html.haml index bc3c90294e..07338736ba 100644 --- a/app/views/import/fogbugz/new_user_map.html.haml +++ b/app/views/import/fogbugz/new_user_map.html.haml @@ -1,4 +1,5 @@ - page_title 'User map', 'FogBugz import' +- header_title "Projects", root_path %h3.page-title %i.fa.fa-bug Import projects from FogBugz diff --git a/app/views/import/fogbugz/status.html.haml b/app/views/import/fogbugz/status.html.haml index b902006597..6ee16c8be4 100644 --- a/app/views/import/fogbugz/status.html.haml +++ b/app/views/import/fogbugz/status.html.haml @@ -1,4 +1,5 @@ - page_title "FogBugz import" +- header_title "Projects", root_path %h3.page-title %i.fa.fa-bug Import projects from FogBugz diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml index 0699321c8c..1416ee5bd5 100644 --- a/app/views/import/github/status.html.haml +++ b/app/views/import/github/status.html.haml @@ -1,4 +1,5 @@ - page_title "GitHub import" +- header_title "Projects", root_path %h3.page-title %i.fa.fa-github Import projects from GitHub diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml index f4a2b33af2..911a55eb85 100644 --- a/app/views/import/gitlab/status.html.haml +++ b/app/views/import/gitlab/status.html.haml @@ -1,4 +1,5 @@ - page_title "GitLab.com import" +- header_title "Projects", root_path %h3.page-title %i.fa.fa-heart Import projects from GitLab.com diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml index 71752d21ef..6b0fa1edf8 100644 --- a/app/views/import/gitorious/status.html.haml +++ b/app/views/import/gitorious/status.html.haml @@ -1,4 +1,5 @@ - page_title "Gitorious import" +- header_title "Projects", root_path %h3.page-title %i.icon-gitorious.icon-gitorious-big Import projects from Gitorious.org diff --git a/app/views/import/google_code/new.html.haml b/app/views/import/google_code/new.html.haml index 9c64e0a009..5d2f149cd5 100644 --- a/app/views/import/google_code/new.html.haml +++ b/app/views/import/google_code/new.html.haml @@ -1,4 +1,5 @@ - page_title "Google Code import" +- header_title "Projects", root_path %h3.page-title %i.fa.fa-google Import projects from Google Code @@ -6,7 +7,7 @@ = form_tag callback_import_google_code_path, class: 'form-horizontal', multipart: true do %p - Follow the steps below to export your Google Code project data. + Follow the steps below to export your Google Code project data. In the next step, you'll be able to select the projects you want to import. %ol %li diff --git a/app/views/import/google_code/new_user_map.html.haml b/app/views/import/google_code/new_user_map.html.haml index e53ebda7dc..0738b3db1e 100644 --- a/app/views/import/google_code/new_user_map.html.haml +++ b/app/views/import/google_code/new_user_map.html.haml @@ -1,4 +1,5 @@ - page_title "User map", "Google Code import" +- header_title "Projects", root_path %h3.page-title %i.fa.fa-google Import projects from Google Code @@ -8,31 +9,31 @@ %p Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import. - %p + %p The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of :. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side. %ul %li %strong Default: Directly import the Google Code email address or username %p - "johnsmith@example.com": "johnsm...@example.com" - will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com. + "johnsmith@example.com": "johnsm...@example.com" + will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com. The email address or username is masked to ensure the user's privacy. %li %strong Map a Google Code user to a GitLab user %p - "johnsmith@example.com": "@johnsmith" - will add "By
@johnsmith" to all issues and comments originally created by johnsmith@example.com, + "johnsmith@example.com": "@johnsmith" + will add "By @johnsmith" to all issues and comments originally created by johnsmith@example.com, and will set @johnsmith as the assignee on all issues originally assigned to johnsmith@example.com. %li %strong Map a Google Code user to a full name %p - "johnsmith@example.com": "John Smith" + "johnsmith@example.com": "John Smith" will add "By John Smith" to all issues and comments originally created by johnsmith@example.com. %li %strong Map a Google Code user to a full email address %p - "johnsmith@example.com": "johnsmith@example.com" - will add "By johnsmith@example.com" to all issues and comments originally created by johnsmith@example.com. + "johnsmith@example.com": "johnsmith@example.com" + will add "By johnsmith@example.com" to all issues and comments originally created by johnsmith@example.com. By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address. .form-group diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml index 8c64fd27e6..175ef6921c 100644 --- a/app/views/import/google_code/status.html.haml +++ b/app/views/import/google_code/status.html.haml @@ -1,4 +1,5 @@ - page_title "Google Code import" +- header_title "Projects", root_path %h3.page-title %i.fa.fa-google Import projects from Google Code From 0ec0c93824096fc6b26de369e170ffc729967bd3 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 2 Dec 2015 15:54:44 +0200 Subject: [PATCH 389/410] Also update gitlab-workhorse in patch updates [ci skip] --- doc/update/patch_versions.md | 45 +++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index 593722eb01..957354decb 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -6,7 +6,8 @@ For example from 7.14.0 to 7.14.3, also see the [semantic versioning specificati ### 0. Backup It's useful to make a backup just in case things go south: -(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab +user on the database version) ```bash cd /home/git/gitlab @@ -15,19 +16,23 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ### 1. Stop server - sudo service gitlab stop +```bash +sudo service gitlab stop +``` ### 2. Get latest code for the stable branch +In the commands below, replace `LATEST_TAG` with the latest GitLab tag you want +to update to, for example `v8.0.3`. Use `git tag -l 'v*.[0-9]' --sort='v:refname'` +to see a list of all tags. Make sure to update patch versions only (check your +current version with `cat VERSION`). + ```bash cd /home/git/gitlab sudo -u git -H git fetch --all sudo -u git -H git checkout -- Gemfile.lock db/schema.rb sudo -u git -H git checkout LATEST_TAG -b LATEST_TAG ``` -Replace `LATEST_TAG` with the latest GitLab tag you want to update to, for example `v8.0.3`. -Use `git tag -l 'v*.[0-9]' --sort='v:refname'` to see a list of all tags. -Make sure to update patch versions only (check your current version with `cat VERSION`) ### 3. Update gitlab-shell to the corresponding version @@ -37,12 +42,20 @@ sudo -u git -H git fetch sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` -b v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` ``` -### 4. Install libs, migrations, etc. +### 4. Update gitlab-workhorse to the corresponding version + +```bash +cd /home/git/gitlab-workhorse +sudo -u git -H git fetch +sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` -b v`cat /home/git/gitlab/GITLAB_WORKHORSE_VERSION` +``` + +### 5. Install libs, migrations, etc. ```bash cd /home/git/gitlab -#PostgreSQL +# PostgreSQL sudo -u git -H bundle install --without development test mysql --deployment # MySQL @@ -52,19 +65,25 @@ sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production ``` -### 5. Start application +### 6. Start application - sudo service gitlab start - sudo service nginx restart +```bash +sudo service gitlab start +sudo service nginx restart +``` -### 6. Check application status +### 7. Check application status Check if GitLab and its environment are configured correctly: - sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production +```bash +sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production +``` To make sure you didn't miss anything run a more thorough check with: - sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production +```bash +sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production +``` If all items are green, then congratulations upgrade complete! From 90df3701e753257a7b71d97c8eec590cc148f409 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:54:50 +0100 Subject: [PATCH 390/410] Add plus icon to all "Add X" buttons --- app/views/profiles/keys/index.html.haml | 4 +++- app/views/projects/branches/index.html.haml | 2 +- app/views/projects/labels/index.html.haml | 1 + app/views/projects/tags/index.html.haml | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index 14adba1c79..17a4195030 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -3,7 +3,9 @@ .gray-content-block.top-block .pull-right - = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new" + = link_to new_profile_key_path, class: "btn btn-new" do + = icon('plus') + Add SSH Key .oneline Before you can add an SSH key you need to = link_to "generate it.", help_page_path("ssh", "README") diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 03ade02a0c..204def6079 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -5,7 +5,7 @@ .pull-right - if can? current_user, :push_code, @project = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do - %i.fa.fa-add-sign + = icon('plus') New branch   .dropdown.inline diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index fb784ee5f4..9081bcfe9b 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -4,6 +4,7 @@ .gray-content-block.top-block - if can? current_user, :admin_label, @project = link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do + = icon('plus') New label .oneline Labels can be applied to issues and merge requests. diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index 85d76eae3b..760347de0a 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -6,7 +6,7 @@ - if can? current_user, :push_code, @project .pull-right = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do - %i.fa.fa-add-sign + = icon('plus') New tag .oneline Tags give the ability to mark specific points in history as being important From a3da3d8e3b60f8eeca716ce231ab5670a637e3eb Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:54:58 +0100 Subject: [PATCH 391/410] Use monospace font for commit SHA in branch list --- app/views/projects/branches/_commit.html.haml | 2 +- app/views/projects/tree/_tree_content.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml index 22d77dda93..9fe65cbb10 100644 --- a/app/views/projects/branches/_commit.html.haml +++ b/app/views/projects/branches/_commit.html.haml @@ -1,5 +1,5 @@ .branch-commit - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-id" + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-id monospace" · %span.str-truncated = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml index c64e684df2..1bc90edd8f 100644 --- a/app/views/projects/tree/_tree_content.html.haml +++ b/app/views/projects/tree/_tree_content.html.haml @@ -12,7 +12,7 @@ %i.fa.fa-angle-right   %small.light - = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit) + = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit), class: "monospace" – = truncate(@commit.title, length: 50) = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right' From 59c3ec4e67f598e1a67df233f2d12fbec3d033dc Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:55:36 +0100 Subject: [PATCH 392/410] Use file-type specific file icon in blame view and diff item --- app/views/projects/blame/show.html.haml | 3 +-- app/views/projects/diffs/_file.html.haml | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index 6518c4173e..8d9ec068a4 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -6,7 +6,7 @@ #tree-holder.tree-holder .file-holder .file-title - %i.fa.fa-file + = blob_icon @blob.mode, @blob.name %strong = @path %small= number_to_human_size @blob.size @@ -43,4 +43,3 @@ - blame_group[:lines].each do |line| :erb <%= highlight(@blob.name, line, nowrap: true, continue: true).html_safe %> - diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index c745b4e69b..b3392d00e0 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -2,19 +2,27 @@ .diff-header{id: "file-path-#{hexdigest(diff_file.file_path)}"} - if diff_file.diff.submodule? %span + = icon('archive fw') - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path) - = submodule_link(submodule_item, @commit.id, project.repository) + %strong + = submodule_link(submodule_item, @commit.id, project.repository) - else %span + = blob_icon blob.mode, blob.name + = link_to "#diff-#{i}" do + %strong + = diff_file.new_path + - if diff_file.deleted_file - = "#{diff_file.old_path} deleted" + deleted - elsif diff_file.renamed_file - = "#{diff_file.old_path} renamed to #{diff_file.new_path}" - - else - = diff_file.new_path + renamed from + %strong + = diff_file.old_path - if diff_file.mode_changed? - %span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}" + %small + = "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}" .diff-controls - if blob.text? From 854deae4129c002faf97f615c17e742e8eff9166 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 14:56:17 +0100 Subject: [PATCH 393/410] Brefer "Directory" over "Dir", "Files" over "Code --- app/helpers/commits_helper.rb | 4 ++-- app/views/projects/commit/_commit_box.html.haml | 5 +++-- features/steps/project/source/browse_files.rb | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index 9df20c9fce..590d20ac7b 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -109,7 +109,7 @@ module CommitsHelper ) elsif @path.present? return link_to( - "Browse Dir »", + "Browse Directory »", namespace_project_tree_path(project.namespace, project, tree_join(commit.id, @path)), class: "pull-right" @@ -117,7 +117,7 @@ module CommitsHelper end end link_to( - "Browse Code »", + "Browse Files »", namespace_project_tree_path(project.namespace, project, commit), class: "pull-right" ) diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 776768537d..d8bfe6a07a 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -13,8 +13,9 @@ - unless @commit.parents.length > 1 %li= link_to "Email Patches", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch) %li= link_to "Plain Diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff) - = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-primary btn-grouped" do - %span Browse Code » + = link_to namespace_project_tree_path(@project.namespace, @project, @commit), class: "btn btn-grouped" do + = icon('files-o') + Browse Files %div %p diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index bee3150677..ceab23b9a4 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -192,7 +192,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps end step 'I see Browse dir link' do - expect(page).to have_link 'Browse Dir »' + expect(page).to have_link 'Browse Directory »' expect(page).not_to have_link 'Browse Code »' end @@ -204,13 +204,13 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps step 'I see Browse file link' do expect(page).to have_link 'Browse File »' - expect(page).not_to have_link 'Browse Code »' + expect(page).not_to have_link 'Browse Files »' end step 'I see Browse code link' do - expect(page).to have_link 'Browse Code »' + expect(page).to have_link 'Browse Files »' expect(page).not_to have_link 'Browse File »' - expect(page).not_to have_link 'Browse Dir »' + expect(page).not_to have_link 'Browse Directory »' end step 'I click on Permalink' do From e3fe255c057e86d841a68f9a96b1a62b07036781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Rosen=C3=B6gger?= <123haynes@gmail.com> Date: Wed, 2 Dec 2015 15:21:02 +0100 Subject: [PATCH 394/410] fixed the documentation of the Guest role in permission.md --- doc/permissions/permissions.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index 8d4c2ceab7..bcd00cfc6b 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -6,6 +6,9 @@ If a user is both in a project group and in the project itself, the highest perm If a user is a GitLab administrator they receive all permissions. +On public projects the Guest role is not enforced. +All users will be able to create issues, leave comments, and pull or download the project code. + To add or import a user, you can follow the [project users and members documentation](doc/workflow/add-user/add-user.md). @@ -15,8 +18,8 @@ documentation](doc/workflow/add-user/add-user.md). |---------------------------------------|---------|------------|-------------|----------|--------| | Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ | | Leave comments | ✓ | ✓ | ✓ | ✓ | ✓ | -| Pull project code | ✓ | ✓ | ✓ | ✓ | ✓ | -| Download project | ✓ | ✓ | ✓ | ✓ | ✓ | +| Pull project code | | ✓ | ✓ | ✓ | ✓ | +| Download project | | ✓ | ✓ | ✓ | ✓ | | Create code snippets | | ✓ | ✓ | ✓ | ✓ | | Manage issue tracker | | ✓ | ✓ | ✓ | ✓ | | Manage labels | | ✓ | ✓ | ✓ | ✓ | From 1fb3c0996acbe9b8e44376e72e24c867a588f4f0 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 15:32:45 +0100 Subject: [PATCH 395/410] Fix styling of empty repo message --- app/assets/stylesheets/framework/blocks.scss | 4 ++++ app/assets/stylesheets/pages/projects.scss | 7 ------- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index dec50a0e0f..8836c8b666 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -68,6 +68,10 @@ .oneline { line-height: 42px; } + + > p:last-child { + margin-bottom: 0; + } } .cover-block { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index ac2d50f9c1..9d5b62c75d 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -26,12 +26,6 @@ } .project-home-panel { - text-align: center; - background: #f7f8fa; - margin: -$gl-padding; - padding: $gl-padding; - padding: 44px 0 17px 0; - .project-identicon-holder { margin-bottom: 16px; @@ -101,7 +95,6 @@ display: inline-table; position: relative; top: 17px; - margin-bottom: 44px; } .project-repo-buttons { From 845f26d07b97a281057a09facae28b2bbdb0168b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 15:04:29 +0100 Subject: [PATCH 396/410] Remove minimum containe height --- app/assets/stylesheets/framework/layout.scss | 7 ++++--- app/assets/stylesheets/framework/sidebar.scss | 5 ++--- app/assets/stylesheets/pages/login.scss | 2 ++ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/framework/layout.scss b/app/assets/stylesheets/framework/layout.scss index b91c15d891..a60940a8be 100644 --- a/app/assets/stylesheets/framework/layout.scss +++ b/app/assets/stylesheets/framework/layout.scss @@ -2,10 +2,10 @@ html { overflow-y: scroll; &.touch .tooltip { display: none !important; } +} - body { - padding-top: $header-height; - } +body { + background-color: #EAEBEC !important; } .container { @@ -18,6 +18,7 @@ html { } .navless-container { + padding-top: $header-height; margin-top: 30px; } diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index c1b0129c86..81cda68b94 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -1,4 +1,6 @@ .page-with-sidebar { + padding-top: $header-height; + .sidebar-wrapper { position: fixed; top: 0; @@ -18,15 +20,12 @@ } .content-wrapper { - min-height: 100vh; width: 100%; padding: 20px; - background: #EAEBEC; .container-fluid { background: #FFF; padding: $gl-padding; - min-height: 90vh; &.container-blank { background: none; diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 83b866c3a6..edd5170513 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -1,5 +1,7 @@ /* Login Page */ .login-page { + background-color: white; + .container { max-width: 960px; } From f10bd7df90cd509848dfcc9a574d7e4569a0428e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 2 Dec 2015 17:37:06 +0100 Subject: [PATCH 397/410] Tweak merge request list item --- .../projects/merge_requests/_merge_request.html.haml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 60dd4f4f87..1d4c9b66c4 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -33,7 +33,12 @@ \##{merge_request.iid} · opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)} - - if merge_request.milestone_id? + - if merge_request.target_project.default_branch != merge_request.target_branch +   + = link_to namespace_project_commits_path(merge_request.project.namespace, merge_request.project, merge_request.target_branch) do + = icon('code-fork') + = merge_request.target_branch + - if merge_request.milestone   = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, milestone_title: merge_request.milestone.title) do = icon('clock-o') @@ -42,11 +47,6 @@   - merge_request.labels.each do |label| = link_to_label(label, project: merge_request.project) - - if merge_request.target_project.default_branch != merge_request.target_branch -   - = link_to namespace_project_commits_path(merge_request.project.namespace, merge_request.project, merge_request.target_branch) do - = icon('code-fork') - = merge_request.target_branch - if merge_request.tasks?   %span.task-status From ec754d221368fad2b765fa60c665a461b2b29c78 Mon Sep 17 00:00:00 2001 From: Andrew Tomaka Date: Wed, 2 Dec 2015 13:21:07 -0500 Subject: [PATCH 398/410] Be more explicit with the impersonate return URL --- app/controllers/admin/impersonation_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb index 102dd43740..bf98af7861 100644 --- a/app/controllers/admin/impersonation_controller.rb +++ b/app/controllers/admin/impersonation_controller.rb @@ -11,7 +11,7 @@ class Admin::ImpersonationController < Admin::ApplicationController redirect_to admin_user_path(@user) else session[:impersonator_id] = current_user.username - session[:impersonator_return_to] = request.env['HTTP_REFERER'] + session[:impersonator_return_to] = admin_user_path(@user) warden.set_user(user, scope: 'user') From f94afdbdb232418da08658516bc469cf8429dadc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20D=C3=A1vila?= Date: Wed, 2 Dec 2015 14:40:43 -0500 Subject: [PATCH 399/410] Fix broken spec related to MySQL and fractional seconds support. --- spec/models/project_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index f80fada45e..06a02c13bf 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -444,7 +444,9 @@ describe Project do before do 2.times do - create(:note_on_commit, project: project2, created_at: date) + # Little fix for special issue related to Fractional Seconds support for MySQL. + # See: https://github.com/rails/rails/pull/14359/files + create(:note_on_commit, project: project2, created_at: date + 1) end end From 162cd0099c6c1f4ee0051e35562636468e88ee0c Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 3 Dec 2015 10:33:38 +0200 Subject: [PATCH 400/410] fix deprecation messages in tests --- app/models/project_services/buildkite_service.rb | 2 +- app/models/project_services/drone_ci_service.rb | 2 +- app/models/user.rb | 2 +- app/views/ci/notify/build_fail_email.html.haml | 2 +- lib/gitlab/lfs/response.rb | 2 +- spec/mailers/notify_spec.rb | 12 +++++++----- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index 40058b53df..199ee3a9d0 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -37,7 +37,7 @@ class BuildkiteService < CiService def compose_service_hook hook = service_hook || build_service_hook hook.url = webhook_url - hook.enable_ssl_verification = enable_ssl_verification + hook.enable_ssl_verification = !!enable_ssl_verification hook.save end diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index 127684bd27..06c3922593 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -34,7 +34,7 @@ class DroneCiService < CiService hook = service_hook || build_service_hook # If using a service template, project may not be available hook.url = [drone_url, "/api/hook", "?owner=#{project.namespace.path}", "&name=#{project.path}", "&access_token=#{token}"].join if project - hook.enable_ssl_verification = enable_ssl_verification + hook.enable_ssl_verification = !!enable_ssl_verification hook.save end diff --git a/app/models/user.rb b/app/models/user.rb index 15aab31564..719b49b16f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -690,7 +690,7 @@ class User < ActiveRecord::Base end def starred?(project) - starred_projects.exists?(project) + starred_projects.exists?(project.id) end def toggle_star(project) diff --git a/app/views/ci/notify/build_fail_email.html.haml b/app/views/ci/notify/build_fail_email.html.haml index b0aaea8907..de6291aa91 100644 --- a/app/views/ci/notify/build_fail_email.html.haml +++ b/app/views/ci/notify/build_fail_email.html.haml @@ -7,7 +7,7 @@ = @project.name %p - Commit: #{link_to @build.short_sha, namespace_project_commit_path(@build.gl_project.namespace, @build.gl_project, @build.sha)} + Commit: #{link_to @build.short_sha, namespace_project_commit_url(@build.gl_project.namespace, @build.gl_project, @build.sha)} %p Author: #{@build.commit.git_author_name} %p diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb index c18dfbd485..9be9a65671 100644 --- a/lib/gitlab/lfs/response.rb +++ b/lib/gitlab/lfs/response.rb @@ -260,7 +260,7 @@ module Gitlab end def link_to_project(object) - if object && !object.projects.exists?(@project) + if object && !object.projects.exists?(@project.id) object.projects << @project object.save end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index d6796b07a5..27e509933b 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -247,7 +247,7 @@ describe Notify do end describe 'that have been reassigned' do - subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user) } + subject { Notify.reassigned_issue_email(recipient.id, issue.id, previous_assignee.id, current_user.id) } it_behaves_like 'a multiple recipients email' it_behaves_like 'an answer to an existing thread', 'issue' @@ -278,7 +278,7 @@ describe Notify do describe 'status changed' do let(:status) { 'closed' } - subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user) } + subject { Notify.issue_status_changed_email(recipient.id, issue.id, status, current_user.id) } it_behaves_like 'an answer to an existing thread', 'issue' it_behaves_like 'it should show Gmail Actions View Issue link' @@ -382,7 +382,7 @@ describe Notify do describe 'status changed' do let(:status) { 'reopened' } - subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user) } + subject { Notify.merge_request_status_email(recipient.id, merge_request.id, status, current_user.id) } it_behaves_like 'an answer to an existing thread', 'merge_request' it_behaves_like 'it should show Gmail Actions View Merge request link' @@ -597,8 +597,10 @@ describe Notify do let(:user) { create(:user, email: 'old-email@mail.com') } before do - user.email = "new-email@mail.com" - user.save + perform_enqueued_jobs do + user.email = "new-email@mail.com" + user.save + end end subject { ActionMailer::Base.deliveries.last } From 54b74aa99885b4f35f580bbb2cd923a381562c39 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 3 Dec 2015 10:49:02 +0200 Subject: [PATCH 401/410] fix Celluloid warnings --- Gemfile.lock | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index dcb4a74e23..cd1855758b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -116,23 +116,8 @@ GEM activemodel (>= 3.2.0) activesupport (>= 3.2.0) json (>= 1.7) - celluloid (0.17.2) - celluloid-essentials - celluloid-extras - celluloid-fsm - celluloid-pool - celluloid-supervision - timers (>= 4.1.1) - celluloid-essentials (0.20.5) - timers (>= 4.1.1) - celluloid-extras (0.20.5) - timers (>= 4.1.1) - celluloid-fsm (0.20.5) - timers (>= 4.1.1) - celluloid-pool (0.20.5) - timers (>= 4.1.1) - celluloid-supervision (0.20.5) - timers (>= 4.1.1) + celluloid (0.16.0) + timers (~> 4.0.0) charlock_holmes (0.7.3) chunky_png (1.3.5) cliver (0.3.2) @@ -757,7 +742,7 @@ GEM thor (0.19.1) thread_safe (0.3.5) tilt (1.4.1) - timers (4.1.1) + timers (4.0.4) hitimes timfel-krb5-auth (0.8.3) tinder (1.10.1) From f21bbd4444f9466d0dc622b0de286deace642598 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 3 Dec 2015 10:53:58 +0200 Subject: [PATCH 402/410] Rails deprecation warning about log_level --- config/environments/production.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/production.rb b/config/environments/production.rb index 317b113e10..909526605a 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -32,7 +32,7 @@ Rails.application.configure do # config.force_ssl = true # See everything in the log (default is :info) - # config.log_level = :debug + config.log_level = :info # Suppress 'Rendered template ...' messages in the log # source: http://stackoverflow.com/a/16369363 From b871c38bf2ca23c915fd5ce55ecfb2245b53bbe3 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 3 Dec 2015 11:49:11 +0200 Subject: [PATCH 403/410] Fix failures in master --- app/views/admin/labels/_label.html.haml | 2 +- app/views/projects/milestones/show.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/labels/_label.html.haml b/app/views/admin/labels/_label.html.haml index 596e06243d..e3ccbf6c3a 100644 --- a/app/views/admin/labels/_label.html.haml +++ b/app/views/admin/labels/_label.html.haml @@ -2,4 +2,4 @@ = render_colored_label(label) .pull-right = link_to 'Edit', edit_admin_label_path(label), class: 'btn btn-sm' - = link_to 'Remove', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} + = link_to 'Delete', admin_label_path(label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Delete this label? Are you sure?"} diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 3a898dfbcf..eab5333ca2 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -23,7 +23,7 @@ = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped" = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do %i.fa.fa-trash-o - Remove + Delete %hr - if @milestone.issues.any? && @milestone.can_be_closed? From 5ab1b11e901712ab462b10f030971610ba140257 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 3 Dec 2015 18:42:37 +0100 Subject: [PATCH 404/410] Fix specs --- features/steps/project/source/markdown_render.rb | 4 ++-- features/steps/project/wiki.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb index ec88c8c20c..3a4f7a6e01 100644 --- a/features/steps/project/source/markdown_render.rb +++ b/features/steps/project/source/markdown_render.rb @@ -252,7 +252,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps step 'I see Gitlab API document' do expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "api") - expect(page).to have_content "Editing" + expect(page).to have_content "Edit Page api" end step 'I click on Rake tasks link' do @@ -261,7 +261,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps step 'I see Rake tasks directory' do expect(current_path).to eq namespace_project_wiki_path(@project.namespace, @project, "raketasks") - expect(page).to have_content "Editing" + expect(page).to have_content "Edit Page raketasks" end step 'I go directory which contains README file' do diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb index 935c1ca8a2..91d227fadb 100644 --- a/features/steps/project/wiki.rb +++ b/features/steps/project/wiki.rb @@ -156,7 +156,7 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps end step 'I should see the Editing page' do - expect(page).to have_content('Editing') + expect(page).to have_content('Edit Page') end step 'I view the page history of a Wiki page that has a path' do From a89d6d1428d61bd2ae6f530acfc5a34d5a9c46e8 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 3 Dec 2015 18:53:17 +0100 Subject: [PATCH 405/410] Add authorization to new branch/tag pages. --- app/controllers/projects/branches_controller.rb | 2 +- app/controllers/projects/tags_controller.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 3ac0a75fa7..3c2849a760 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -3,7 +3,7 @@ class Projects::BranchesController < Projects::ApplicationController # Authorize before_action :require_non_empty_project before_action :authorize_download_code! - before_action :authorize_push_code!, only: [:create, :destroy] + before_action :authorize_push_code!, only: [:new, :create, :destroy] def index @sort = params[:sort] || 'name' diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index cb39c2b878..280fe12cc7 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -2,7 +2,7 @@ class Projects::TagsController < Projects::ApplicationController # Authorize before_action :require_non_empty_project before_action :authorize_download_code! - before_action :authorize_push_code!, only: [:create] + before_action :authorize_push_code!, only: [:new, :create] before_action :authorize_admin_project!, only: [:destroy] def index From 494ebad39b465ca6a70888ca376db0dd962a618f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 3 Dec 2015 18:56:43 +0100 Subject: [PATCH 406/410] Fix spec --- features/steps/project/issues/issues.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb index af2da41bad..9683bebd56 100644 --- a/features/steps/project/issues/issues.rb +++ b/features/steps/project/issues/issues.rb @@ -65,7 +65,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps step 'I see current user as the first user' do expect(page).to have_selector('.user-result', visible: true, count: 4) users = page.all('.user-name') - expect(users[0].text).to eq 'Any' + expect(users[0].text).to eq 'Any Assignee' expect(users[1].text).to eq 'Unassigned' expect(users[2].text).to eq current_user.name end From 82e916b0f2cbd500d14f545095ac82464c0a3889 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Tue, 1 Dec 2015 18:11:12 -0200 Subject: [PATCH 407/410] Touch project when toggling stars to update cache --- app/models/users_star_project.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb index 3d49cb0594..413f3f485a 100644 --- a/app/models/users_star_project.rb +++ b/app/models/users_star_project.rb @@ -10,7 +10,7 @@ # class UsersStarProject < ActiveRecord::Base - belongs_to :project, counter_cache: :star_count + belongs_to :project, counter_cache: :star_count, touch: true belongs_to :user validates :user, presence: true From c5b6b31c847d5d2f467453d8292e13ca735ee630 Mon Sep 17 00:00:00 2001 From: Anton Baklanov Date: Wed, 18 Nov 2015 20:08:07 +0200 Subject: [PATCH 408/410] Fixed invalid link on starred projects dashboard. Fixes #3468 --- CHANGELOG | 1 + app/views/dashboard/projects/index.html.haml | 2 +- app/views/dashboard/projects/starred.html.haml | 2 +- app/views/explore/projects/index.html.haml | 2 +- app/views/explore/projects/starred.html.haml | 2 +- app/views/explore/projects/trending.html.haml | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index db812796b6..438cae617a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -13,6 +13,7 @@ v 8.2.2 - Fix Error 500 when viewing user's personal projects from admin page (Stan Hu) - Fix: Raw private snippets access workflow - Prevent "413 Request entity too large" errors when pushing large files with LFS + - Fix invalid links within projects dashboard header v 8.2.1 - Forcefully update builds that didn't want to update with state machine diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index 7a16b811f6..53abf274bd 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -3,7 +3,7 @@ = auto_discovery_link_tag(:atom, dashboard_projects_url(format: :atom, private_token: current_user.private_token), title: "All activity") - page_title "Projects" -- header_title "Projects", root_path +- header_title "Projects", dashboard_projects_path = render 'dashboard/projects_head' diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index f75f2e0a32..70705923d4 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -1,5 +1,5 @@ - page_title "Starred Projects" -- header_title "Projects", projects_path +- header_title "Projects", dashboard_projects_path = render 'dashboard/projects_head' diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index 67e38ca312..76bdd68fd7 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -1,5 +1,5 @@ - page_title "Projects" -- header_title "Projects", root_path +- header_title "Projects", dashboard_projects_path - if current_user = render 'dashboard/projects_head' diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index 596cb0a96c..e30c363322 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -1,5 +1,5 @@ - page_title "Projects" -- header_title "Projects", root_path +- header_title "Projects", dashboard_projects_path - if current_user = render 'dashboard/projects_head' diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index 5ea6d81c5b..1412b19acd 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -1,5 +1,5 @@ - page_title "Projects" -- header_title "Projects", root_path +- header_title "Projects", dashboard_projects_path - if current_user = render 'dashboard/projects_head' From 83e6d8294f9e1c4ee199a3577e5fc810df33def6 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 3 Dec 2015 23:14:19 +0100 Subject: [PATCH 409/410] Update .ruby-version to 2.1.7 --- .ruby-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ruby-version b/.ruby-version index 399088bf46..04b10b4f15 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.1.6 +2.1.7 From 387e5656a1e158eaa1c010f22d332d758b336179 Mon Sep 17 00:00:00 2001 From: Kevin Pankonen Date: Thu, 3 Dec 2015 15:40:08 -0700 Subject: [PATCH 410/410] fixes #3263 slashes are replaced with two underscores --- doc/ci/docker/using_docker_images.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index ef8a7ec1e8..64e52eba3a 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -60,11 +60,11 @@ This is image that have fully preconfigured `wordpress` and have `MySQL` server ``` Next time when you run your application the `tutum/wordpress` will be started -and you will have access to it from your build container under hostname: `tutum_wordpress`. +and you will have access to it from your build container under hostname: `tutum__wordpress`. Alias hostname for the service is made from the image name: 1. Everything after `:` is stripped, -2. '/' is replaced to `_`. +2. '/' is replaced with `__`. ### Configuring services Many services accept environment variables, which allow you to easily change database names or set account names depending on the environment.