From 35266de2f0e91ac73995ab8ced1bbcb12e35f773 Mon Sep 17 00:00:00 2001 From: Chris McKnight Date: Wed, 6 Jan 2016 11:20:52 -0600 Subject: [PATCH 001/152] Updates git lfs initialize command --- 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 ba91685a20..83db44c10b 100644 --- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md +++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md @@ -44,7 +44,7 @@ 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 install # initialize the Git LFS project project git lfs track "*.iso" # select the file extensions that you want to treat as large files ``` @@ -152,4 +152,4 @@ If you are using OS X you can use `osxkeychain` to store and encrypt your creden 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). \ No newline at end of file +on [Git Credential Storage documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). From ff1e7474ed0f210df004c714e1b83c1c2eb0d91c Mon Sep 17 00:00:00 2001 From: Andrew Collett Date: Thu, 21 Apr 2016 10:30:27 +0000 Subject: [PATCH 002/152] Update cas.md to reflect the current syntax, and added that gitlab-ctl reconfigure should be run. --- doc/integration/cas.md | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/doc/integration/cas.md b/doc/integration/cas.md index e6b2071f19..e34e306f9a 100644 --- a/doc/integration/cas.md +++ b/doc/integration/cas.md @@ -27,17 +27,18 @@ To enable the CAS OmniAuth provider you must register your application with your ```ruby gitlab_rails['omniauth_providers'] = [ { - name: "cas3", - label: "cas", - args: { - url: 'CAS_SERVER', - login_url: '/CAS_PATH/login', - service_validate_url: '/CAS_PATH/p3/serviceValidate', - logout_url: '/CAS_PATH/logout'} } - } + "name"=> "cas3", + "label"=> "cas", + "args"=> { + "url"=> 'CAS_SERVER', + "login_url"=> '/CAS_PATH/login', + "service_validate_url"=> '/CAS_PATH/p3/serviceValidate', + "logout_url"=> '/CAS_PATH/logout' + } } ] ``` + For installations from source: @@ -57,6 +58,8 @@ To enable the CAS OmniAuth provider you must register your application with your 1. Save the configuration file. +1. Run `gitlab-ctl reconfigure` for the omnibus package. + 1. Restart GitLab for the changes to take effect. On the sign in page there should now be a CAS tab in the sign in form. From 9ab70184e3299766165017267a74f53e80b03836 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Thu, 28 Apr 2016 12:05:45 -0700 Subject: [PATCH 003/152] Pass trusted_proxies to action_dispatch as IPAddrs instead of strings Without this setting your own trusted_proxies does not work. --- config/initializers/trusted_proxies.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb index b8cc025bae..d256a16d42 100644 --- a/config/initializers/trusted_proxies.rb +++ b/config/initializers/trusted_proxies.rb @@ -1,2 +1,3 @@ -Rails.application.config.action_dispatch.trusted_proxies = +Rails.application.config.action_dispatch.trusted_proxies = ( [ '127.0.0.1', '::1' ] + Array(Gitlab.config.gitlab.trusted_proxies) +).map { |proxy| IPAddr.new(proxy) } From c0f02aad4a1a178109a235d34bd70218c0aec86c Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Mon, 2 May 2016 16:37:12 +0700 Subject: [PATCH 004/152] Add snippet tab under user profile --- app/assets/javascripts/user_tabs.js.coffee | 9 ++++++++- app/controllers/users_controller.rb | 22 ++++++++++++++++++++++ app/views/users/show.html.haml | 6 ++++++ config/routes.rb | 5 +++-- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/user_tabs.js.coffee b/app/assets/javascripts/user_tabs.js.coffee index 09b7eec910..aa798b96ed 100644 --- a/app/assets/javascripts/user_tabs.js.coffee +++ b/app/assets/javascripts/user_tabs.js.coffee @@ -26,6 +26,10 @@ # Personal projects # # +#
  • +# +# +#
  • # # #
    @@ -41,6 +45,9 @@ #
    # Projects content #
    +#
    +# Snippets content +#
    #
    # #
    @@ -100,7 +107,7 @@ class @UserTabs if action is 'activity' @loadActivities(source) - if action in ['groups', 'contributed', 'projects'] + if action in ['groups', 'contributed', 'projects', 'snippets'] @loadTab(source, action) loadTab: (source, action) -> diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 2ae180c8a1..799421c185 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -58,6 +58,19 @@ class UsersController < ApplicationController end end + def snippets + load_snippets + + respond_to do |format| + format.html { render 'show' } + format.json do + render json: { + html: view_to_html_string("snippets/_snippets", collection: @snippets) + } + end + end + end + def calendar calendar = contributions_calendar @timestamps = calendar.timestamps @@ -116,6 +129,15 @@ class UsersController < ApplicationController @groups = JoinedGroupsFinder.new(user).execute(current_user) end + def load_snippets + @snippets = SnippetsFinder.new.execute( + current_user, + filter: :by_user, + user: user, + scope: params[:scope] + ).page(params[:page]) + end + def projects_for_current_user ProjectsFinder.new.execute(current_user) end diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 3028491e5b..a453a7fedb 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -81,6 +81,9 @@ %li.projects-tab = link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do Personal projects + %li.snippets-tab + = link_to user_snippets_path, data: {target: 'div#snippets', action: 'snippets', toggle: 'tab'} do + Snippets %div{ class: container_class } .tab-content @@ -104,6 +107,9 @@ #projects.tab-pane - # This tab is always loaded via AJAX + #snippets.tab-pane + - # This tab is always loaded via AJAX + .loading-status = spinner diff --git a/config/routes.rb b/config/routes.rb index 2f820aafed..f6a41331ec 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -89,8 +89,6 @@ Rails.application.routes.draw do end end - get '/s/:username' => 'snippets#index', as: :user_snippets, constraints: { username: /.*/ } - # # Invites # @@ -355,6 +353,9 @@ Rails.application.routes.draw do get 'u/:username/contributed' => 'users#contributed', as: :user_contributed_projects, constraints: { username: /.*/ } + get 'u/:username/snippets' => 'users#snippets', as: :user_snippets, + constraints: { username: /.*/ } + get '/u/:username' => 'users#show', as: :user, constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Tue, 3 May 2016 13:03:10 +0200 Subject: [PATCH 005/152] Improve documentation and web test for web hooks I wanted to share what I learned trying to debug web hooks using netcat. --- app/controllers/projects/hooks_controller.rb | 2 +- app/models/hooks/web_hook.rb | 2 +- doc/web_hooks/web_hooks.md | 13 +++++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index dfa9bd259e..366373b0f0 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -28,7 +28,7 @@ class Projects::HooksController < Projects::ApplicationController status, message = TestHookService.new.execute(hook, current_user) if status - flash[:notice] = 'Hook successfully executed.' + flash[:notice] = "Hook successfully executed, HTTP #{status} #{message}" else flash[:alert] = "Hook execution failed: #{message}" end diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 1e3b481559..818abbf4cc 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -59,7 +59,7 @@ class WebHook < ActiveRecord::Base basic_auth: auth) end - [(response.code >= 200 && response.code < 300), ActionView::Base.full_sanitizer.sanitize(response.to_s)] + [response.code, ActionView::Base.full_sanitizer.sanitize(response.to_s)] rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e logger.error("WebHook Error => #{e}") [false, e.to_s] diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index c1c51302e7..6ffdb18339 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -13,6 +13,19 @@ You can configure webhooks to listen for specific events like pushes, issues or Webhooks can be used to update an external issue tracker, trigger CI builds, update a backup mirror, or even deploy to your production server. +## Webhook endpoint tips + +If you are writing your own endpoint (web server) that will receive +GitLab web hooks keep in mind the following things: + +- Your endpoint should send its HTTP response as fast as possible. If + you wait too long, GitLab may decide the hook failed and retry it. +- Your endpoint should ALWAYS return a valid HTTP response. If you do + not do this then GitLab will think the hook failed and retry it. + Most HTTP libraries take care of this for you automatically but if + you are writing a low-level hook this is important to remember. +- GitLab ignores the HTTP status code returned by your endpoint. + ## SSL Verification By default, the SSL certificate of the webhook endpoint is verified based on From 23a3e3756a4f44aa8bd69310a2e584c1d4f7af1d Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 3 May 2016 13:40:59 +0200 Subject: [PATCH 006/152] Inform user about questionable hook success --- app/controllers/projects/hooks_controller.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 366373b0f0..9869d90831 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -27,8 +27,10 @@ class Projects::HooksController < Projects::ApplicationController if !@project.empty_repo? status, message = TestHookService.new.execute(hook, current_user) - if status - flash[:notice] = "Hook successfully executed, HTTP #{status} #{message}" + if status && status >= 200 && status < 400 + flash[:notice] = "Hook executed successfully" + elsif status + flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}" else flash[:alert] = "Hook execution failed: #{message}" end From 6957fb7c4f0993ba1e0cbd9949a7e96c2c2def32 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 3 May 2016 13:42:56 +0200 Subject: [PATCH 007/152] Always mention HTTP status --- app/controllers/projects/hooks_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 9869d90831..47524b1cf0 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -28,7 +28,7 @@ class Projects::HooksController < Projects::ApplicationController status, message = TestHookService.new.execute(hook, current_user) if status && status >= 200 && status < 400 - flash[:notice] = "Hook executed successfully" + flash[:notice] = "Hook executed successfully: HTTP #{status}" elsif status flash[:alert] = "Hook executed successfully but returned HTTP #{status} #{message}" else From faaab2aef8e7f6b06f8f04dc68596e1229d507db Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Tue, 3 May 2016 11:58:43 -0500 Subject: [PATCH 008/152] Add to label :id to response --- app/controllers/dashboard/labels_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/dashboard/labels_controller.rb b/app/controllers/dashboard/labels_controller.rb index 23a4ef21ea..2a88350a4c 100644 --- a/app/controllers/dashboard/labels_controller.rb +++ b/app/controllers/dashboard/labels_controller.rb @@ -1,6 +1,6 @@ class Dashboard::LabelsController < Dashboard::ApplicationController def index - labels = Label.where(project_id: projects).select(:title, :color).uniq(:title) + labels = Label.where(project_id: projects).select(:id, :title, :color).uniq(:title) respond_to do |format| format.json { render json: labels } From 17d23283a119fcfc2f6ac070bb5df8d913abf2e7 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Wed, 4 May 2016 12:46:53 -0500 Subject: [PATCH 009/152] Restore "r" shortcut --- app/assets/javascripts/shortcuts_issuable.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee index ad9b3c1c6b..ccb42ab216 100644 --- a/app/assets/javascripts/shortcuts_issuable.coffee +++ b/app/assets/javascripts/shortcuts_issuable.coffee @@ -6,6 +6,10 @@ class @ShortcutsIssuable extends ShortcutsNavigation super() Mousetrap.bind('a', @openSidebarDropdown.bind(@, 'assignee')) Mousetrap.bind('m', @openSidebarDropdown.bind(@, 'milestone')) + Mousetrap.bind('r', => + @replyWithSelectedText() + return false + ) Mousetrap.bind('j', => @prevIssue() return false From f18ec70743375024aa7ec7bb86c437ca9198e729 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 4 May 2016 17:05:16 -0400 Subject: [PATCH 010/152] Backport changes from gitlab-org/gitlab-ee!372 Mostly replaces several Spinach tests with RSpec Feature tests. --- app/helpers/projects_helper.rb | 3 +- features/project/create.feature | 16 +---- features/steps/project/create.rb | 26 +------- spec/factories/projects.rb | 10 ++- ...r_views_empty_project_instructions_spec.rb | 63 +++++++++++++++++++ spec/helpers/projects_helper_spec.rb | 10 +-- 6 files changed, 80 insertions(+), 48 deletions(-) create mode 100644 spec/features/projects/developer_views_empty_project_instructions_spec.rb diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 3d5e61d2c1..62e8c03cc8 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -209,7 +209,8 @@ module ProjectsHelper end def default_url_to_repo(project = @project) - if default_clone_protocol == "ssh" + case default_clone_protocol + when 'ssh' project.ssh_url_to_repo else project.http_url_to_repo diff --git a/features/project/create.feature b/features/project/create.feature index 27136798e3..67336d73bf 100644 --- a/features/project/create.feature +++ b/features/project/create.feature @@ -7,20 +7,8 @@ Feature: Project Create @javascript Scenario: User create a project Given I sign in as a user - When I visit new project page And I have an ssh key + When I visit new project page And fill project form with valid data Then I should see project page - And I should see empty project instuctions - - @javascript - Scenario: Empty project instructions - Given I sign in as a user - And I have an ssh key - When I visit new project page - And fill project form with valid data - Then I see empty project instuctions - And I click on HTTP - Then Remote url should update to http link - And If I click on SSH - Then Remote url should update to ssh link + And I should see empty project instructions diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb index 422b151eaa..5f5f806df3 100644 --- a/features/steps/project/create.rb +++ b/features/steps/project/create.rb @@ -13,33 +13,9 @@ class Spinach::Features::ProjectCreate < Spinach::FeatureSteps expect(current_path).to eq namespace_project_path(Project.last.namespace, Project.last) end - step 'I should see empty project instuctions' do + step 'I should see empty project instructions' do expect(page).to have_content "git init" expect(page).to have_content "git remote" expect(page).to have_content Project.last.url_to_repo end - - step 'I see empty project instuctions' do - expect(page).to have_content "git init" - expect(page).to have_content "git remote" - expect(page).to have_content Project.last.url_to_repo - end - - step 'I click on HTTP' do - find('#clone-dropdown').click - find('.http-selector').click - end - - step 'Remote url should update to http link' do - expect(page).to have_content "git remote add origin #{Project.last.http_url_to_repo}" - end - - step 'If I click on SSH' do - find('#clone-dropdown').click - find('.ssh-selector').click - end - - step 'Remote url should update to ssh link' do - expect(page).to have_content "git remote add origin #{Project.last.url_to_repo}" - end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index c14b99606b..5338c88dd8 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -61,6 +61,12 @@ FactoryGirl.define do trait :private do visibility_level Gitlab::VisibilityLevel::PRIVATE end + + trait :empty_repo do + after(:create) do |project| + project.create_repository + end + end end # Project with empty repository @@ -68,9 +74,7 @@ FactoryGirl.define do # This is a case when you just created a project # but not pushed any code there yet factory :project_empty_repo, parent: :empty_project do - after :create do |project| - project.create_repository - end + empty_repo end # Project with test repository diff --git a/spec/features/projects/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/developer_views_empty_project_instructions_spec.rb new file mode 100644 index 0000000000..0c51fe72ca --- /dev/null +++ b/spec/features/projects/developer_views_empty_project_instructions_spec.rb @@ -0,0 +1,63 @@ +require 'rails_helper' + +feature 'Developer views empty project instructions', feature: true do + let(:project) { create(:empty_project, :empty_repo) } + let(:developer) { create(:user) } + + background do + project.team << [developer, :developer] + + login_as(developer) + end + + context 'without an SSH key' do + scenario 'defaults to HTTP' do + visit_project + + expect_instructions_for('http') + end + + scenario 'switches to SSH', js: true do + visit_project + + select_protocol('SSH') + + expect_instructions_for('ssh') + end + end + + context 'with an SSH key' do + background do + create(:personal_key, user: developer) + end + + scenario 'defaults to SSH' do + visit_project + + expect_instructions_for('ssh') + end + + scenario 'switches to HTTP', js: true do + visit_project + + select_protocol('HTTP') + + expect_instructions_for('http') + end + end + + def visit_project + visit namespace_project_path(project.namespace, project) + end + + def select_protocol(protocol) + find('#clone-dropdown').click + find(".#{protocol.downcase}-selector").click + end + + def expect_instructions_for(protocol) + msg = :"#{protocol.downcase}_url_to_repo" + + expect(page).to have_content("git clone #{project.send(msg)}") + end +end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 62389188d2..6f7e2ae78f 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -88,18 +88,18 @@ describe ProjectsHelper do end describe 'default_clone_protocol' do - describe 'using HTTP' do + context 'when user is not logged in and gitlab protocol is HTTP' do it 'returns HTTP' do - expect(helper).to receive(:current_user).and_return(nil) + allow(helper).to receive(:current_user).and_return(nil) expect(helper.send(:default_clone_protocol)).to eq('http') end end - describe 'using HTTPS' do + context 'when user is not logged in and gitlab protocol is HTTPS' do it 'returns HTTPS' do - allow(Gitlab.config.gitlab).to receive(:protocol).and_return('https') - expect(helper).to receive(:current_user).and_return(nil) + stub_config_setting(protocol: 'https') + allow(helper).to receive(:current_user).and_return(nil) expect(helper.send(:default_clone_protocol)).to eq('https') end From e4c64855e8531a9375de1d64a95f2e593b80c2bd Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Wed, 27 Apr 2016 23:37:07 +0200 Subject: [PATCH 011/152] Don't read otp_secret_encryption_key from hardcoded path in models/user Variable `Gitlab::Application.config.secret_key_base` is set in config/initializers/secret_token.rb. It's very bad practice to use hard-coded paths inside an application and really unnecessary in this case. --- 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 ab48f8f196..a468b6ea07 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -85,7 +85,7 @@ class User < ActiveRecord::Base default_value_for :theme_id, gitlab_config.default_theme devise :two_factor_authenticatable, - otp_secret_encryption_key: File.read(Rails.root.join('.secret')).chomp + otp_secret_encryption_key: Gitlab::Application.config.secret_key_base alias_attribute :two_factor_enabled, :otp_required_for_login devise :two_factor_backupable, otp_number_of_backup_codes: 10 From e37b31456774c92fd718f7ef0a576266ffcca894 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Wed, 4 May 2016 15:49:39 -0700 Subject: [PATCH 012/152] Add tests for setting trusted_proxies Each test reloads the trusted_proxies initializer, which in turn will set Rails.application.config.action_dispatch.trusted_proxies to something new. This will leak into the other tests, but the middleware that it is used in has already been loaded for the whole test suite, so it should have no impact. --- spec/initializers/trusted_proxies_spec.rb | 51 +++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 spec/initializers/trusted_proxies_spec.rb diff --git a/spec/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb new file mode 100644 index 0000000000..4bb149f25f --- /dev/null +++ b/spec/initializers/trusted_proxies_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe 'trusted_proxies', lib: true do + context 'with default config' do + before do + set_trusted_proxies([]) + end + + it 'preserves private IPs as remote_ip' do + request = stub_request('HTTP_X_FORWARDED_FOR' => '10.1.5.89') + expect(request.remote_ip).to eq('10.1.5.89') + end + + it 'filters out localhost from remote_ip' do + request = stub_request('HTTP_X_FORWARDED_FOR' => '1.1.1.1, 10.1.5.89, 127.0.0.1') + expect(request.remote_ip).to eq('10.1.5.89') + end + end + + context 'with private IP ranges added' do + before do + set_trusted_proxies([ "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" ]) + end + + it 'filters out private and local IPs from remote_ip' do + request = stub_request('HTTP_X_FORWARDED_FOR' => '1.2.3.6, 1.1.1.1, 10.1.5.89, 127.0.0.1') + expect(request.remote_ip).to eq('1.1.1.1') + end + end + + context 'with proxy IP added' do + before do + set_trusted_proxies([ "60.98.25.47" ]) + end + + it 'filters out proxy IP from remote_ip' do + request = stub_request('HTTP_X_FORWARDED_FOR' => '1.2.3.6, 1.1.1.1, 60.98.25.47, 127.0.0.1') + expect(request.remote_ip).to eq('1.1.1.1') + end + end + + def stub_request(headers = {}) + ActionDispatch::RemoteIp.new(Proc.new { }, false, Rails.application.config.action_dispatch.trusted_proxies).call(headers) + ActionDispatch::Request.new(headers) + end + + def set_trusted_proxies(proxies = []) + stub_config_setting('trusted_proxies' => proxies) + load File.join(__dir__, '../../config/initializers/trusted_proxies.rb') + end +end From 8dc19494c3fdae366daa8849b5e2a3f58f98878c Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Thu, 5 May 2016 13:26:36 +0700 Subject: [PATCH 013/152] Remove unused code, update spec, and update changelog --- CHANGELOG | 1 + app/controllers/snippets_controller.rb | 20 +------------------- spec/routing/routing_spec.rb | 8 -------- 3 files changed, 2 insertions(+), 27 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 558897ad89..6c044192d0 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.8.0 (unreleased) + - Snippets tab under user profile. !4001 (Long Nguyen) - Remove future dates from contribution calendar graph. - Fix error when visiting commit builds page before build was updated - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 2daceed039..f0bd842ca5 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -10,29 +10,11 @@ class SnippetsController < ApplicationController # Allow destroy snippet before_action :authorize_admin_snippet!, only: [:destroy] - skip_before_action :authenticate_user!, only: [:index, :user_index, :show, :raw] + skip_before_action :authenticate_user!, only: [:show, :raw] layout 'snippets' respond_to :html - def index - if params[:username].present? - @user = User.find_by(username: params[:username]) - - render_404 and return unless @user - - @snippets = SnippetsFinder.new.execute(current_user, { - filter: :by_user, - user: @user, - scope: params[:scope] }). - page(params[:page]) - - render 'index' - else - redirect_to(current_user ? dashboard_snippets_path : explore_snippets_path) - end - end - def new @snippet = PersonalSnippet.new end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 1527eddfa4..9deffd0a1e 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -27,18 +27,10 @@ end # PUT /snippets/:id(.:format) snippets#update # DELETE /snippets/:id(.:format) snippets#destroy describe SnippetsController, "routing" do - it "to #user_index" do - expect(get("/s/User")).to route_to('snippets#index', username: 'User') - end - it "to #raw" do expect(get("/snippets/1/raw")).to route_to('snippets#raw', id: '1') end - it "to #index" do - expect(get("/snippets")).to route_to('snippets#index') - end - it "to #create" do expect(post("/snippets")).to route_to('snippets#create') end From ae29ec31e4f71d722e975bfce945aaed7e0d0bd1 Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Thu, 5 May 2016 14:57:34 +0700 Subject: [PATCH 014/152] Remove unused view and update redirect when destroy snippet --- app/controllers/snippets_controller.rb | 2 +- app/views/snippets/index.html.haml | 13 ------------- 2 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 app/views/snippets/index.html.haml diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index f0bd842ca5..2c038bdfda 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -43,7 +43,7 @@ class SnippetsController < ApplicationController @snippet.destroy - redirect_to snippets_path + redirect_to dashboard_snippets_path end def raw diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml deleted file mode 100644 index 7e4918a608..0000000000 --- a/app/views/snippets/index.html.haml +++ /dev/null @@ -1,13 +0,0 @@ -- page_title "By #{@user.name}", "Snippets" - -%ol.breadcrumb - %li - = link_to snippets_path do - Snippets - %li - = @user.name - .pull-right.hidden-xs - = link_to user_path(@user) do - #{@user.name} profile page - -= render 'snippets' From e449a6c05c734ff9e38e13013591d07d03c0fff6 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 4 May 2016 13:14:11 +0200 Subject: [PATCH 015/152] Added documentation on how to instrument methods [ci skip] --- doc/development/instrumentation.md | 129 +++++++++++++++++++++++++++-- 1 file changed, 123 insertions(+), 6 deletions(-) diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md index c1cf2e77c2..9168c70945 100644 --- a/doc/development/instrumentation.md +++ b/doc/development/instrumentation.md @@ -1,12 +1,125 @@ # Instrumenting Ruby Code -GitLab Performance Monitoring allows instrumenting of custom blocks of Ruby -code. This can be used to measure the time spent in a specific part of a larger -chunk of code. The resulting data is stored as a field in the transaction that -executed the block. +GitLab Performance Monitoring allows instrumenting of both methods and custom +blocks of Ruby code. Method instrumentation is the primary form of +instrumentation with block-based instrumentation only being used when we want to +drill down to specific regions of code within a method. -To start measuring a block of Ruby code you should use `Gitlab::Metrics.measure` -and give it a name: +## Instrumenting Methods + +Instrumenting methods is done by using the `Gitlab::Metrics::Instrumentation` +module. This module offers a few different methods that can be used to +instrument code: + +* `instrument_method`: instruments a single class method. +* `instrument_instance_method`: instruments a single instance method. +* `instrument_class_hierarchy`: given a Class this method will recursively + instrument all sub-classes (both class and instance methods). +* `instrument_methods`: instruments all public class methods of a Module. +* `instrument_instance_methods`: instruments all public instance methods of a + Module. + +To remove the need for typing the full `Gitlab::Metrics::Instrumentation` +namespace you can use the `configure` class method. This method simply yields +the supplied block while passing `Gitlab::Metrics::Instrumentation` as its +argument. An example: + +``` +Gitlab::Metrics::Instrumentation.configure do |conf| + conf.instrument_method(Foo, :bar) + conf.instrument_method(Foo, :baz) +end +``` + +Using this method is in general preferred over directly calling the various +instrumentation methods. + +Method instrumentation should be added in the initializer +`config/initializers/metrics.rb`. + +### Examples + +Instrumenting a single method: + +``` +Gitlab::Metrics::Instrumentation.configure do |conf| + conf.instrument_method(User, :find_by) +end +``` + +Instrumenting an entire class hierarchy: + +``` +Gitlab::Metrics::Instrumentation.configure do |conf| + conf.instrument_class_hierarchy(ActiveRecord::Base) +end +``` + +Instrumenting all public class methods: + +``` +Gitlab::Metrics::Instrumentation.configure do |conf| + conf.instrument_methods(User) +end +``` + +### Checking Instrumented Methods + +The easiest way to check if a method has been instrumented is to check its +source location. For example: + +``` +method = Rugged::TagCollection.instance_method(:[]) + +method.source_location +``` + +If the source location points to `lib/gitlab/metrics/instrumentation.rb` you +know the method has been instrumented. + +If you're using Pry you can use the `$` command to display the source code of a +method (along with its source location), this is easier than running the above +Ruby code. In case of the above snippet you'd run the following: + +``` +$ Rugged::TagCollection#[] +``` + +This will print out something along the lines of: + +``` +From: /path/to/your/gitlab/lib/gitlab/metrics/instrumentation.rb @ line 148: +Owner: # +Visibility: public +Number of lines: 21 + +def #{name}(#{args_signature}) + trans = Gitlab::Metrics::Instrumentation.transaction + + if trans + start = Time.now + retval = super + duration = (Time.now - start) * 1000.0 + + if duration >= Gitlab::Metrics.method_call_threshold + trans.increment(:method_duration, duration) + + trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES, + { duration: duration }, + method: #{label.inspect}) + end + + retval + else + super + end +end +``` + +## Instrumenting Ruby Blocks + +Measuring blocks of Ruby code is done by calling `Gitlab::Metrics.measure` and +passing it a block. For example: ```ruby Gitlab::Metrics.measure(:foo) do @@ -14,6 +127,10 @@ Gitlab::Metrics.measure(:foo) do end ``` +The block is executed and the execution time is stored as a set of fields in the +currently running transaction. If no transaction is present the block is yielded +without measuring anything. + 3 values are measured for a block: 1. The real time elapsed, stored in NAME_real_time. From d028863eda8b97f6e4db129ef57f0d3a2130c9b3 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 4 May 2016 18:21:57 -0300 Subject: [PATCH 016/152] Sanitize milestones and label titles --- app/models/label.rb | 5 +++++ app/models/milestone.rb | 5 +++++ spec/lib/banzai/filter/milestone_reference_filter_spec.rb | 2 +- spec/models/label_spec.rb | 8 ++++++++ spec/models/milestone_spec.rb | 8 ++++++++ 5 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/models/label.rb b/app/models/label.rb index 60bdce3295..0b34911a4e 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -117,6 +117,11 @@ class Label < ActiveRecord::Base LabelsHelper::text_color_for_bg(self.color) end + def title= value + value = Sanitize.clean(value.to_s) if value + write_attribute(:title, Sanitize.clean(value)) + end + private def label_format_reference(format = :id) diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 986184dd30..ed81791c69 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -129,6 +129,11 @@ class Milestone < ActiveRecord::Base nil end + def title= value + value = Sanitize.clean(value.to_s) if value + write_attribute(:title, value) + end + # Sorts the issues for the given IDs. # # This method runs a single SQL query using a CASE statement to update the diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb index ebf3d7489b..5beb61dac5 100644 --- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb @@ -43,7 +43,7 @@ describe Banzai::Filter::MilestoneReferenceFilter, lib: true do milestone.update_attribute(:title, %{">whateverwhatever" end it 'includes default classes' do diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 0614ca1e7c..b61c55a3f6 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -55,6 +55,14 @@ describe Label, models: true do end end + describe "#title" do + let(:label) { create(:label, title: "test") } + + it "sanitizes title" do + expect(label.title).to eq("test") + end + end + describe '#to_reference' do context 'using id' do it 'returns a String reference to the object' do diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 72a4ea7022..e2c89a4b3e 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -34,6 +34,14 @@ describe Milestone, models: true do let(:issue) { create(:issue) } let(:user) { create(:user) } + describe "#title" do + let(:milestone) { create(:milestone, title: "test") } + + it "sanitizes title" do + expect(milestone.title).to eq("test") + end + end + describe "unique milestone title per project" do it "shouldn't accept the same title in a project twice" do new_milestone = Milestone.new(project: milestone.project, title: milestone.title) From ab4671f26ab789b1e3647754911197aaba1f81bb Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Sat, 7 May 2016 01:31:56 +0200 Subject: [PATCH 017/152] Fix adding a todo for private group members Fixes #14002 --- CHANGELOG | 1 + app/models/concerns/mentionable.rb | 4 ++-- spec/services/todo_service_spec.rb | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 78d6c1ec2b..c68a11a91f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ v 8.8.0 (unreleased) - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko) - Expire repository exists? and has_visible_content? caches after a push if necessary - Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi) + - Fix adding a todo for private group members (Ahmad Sherif) v 8.7.3 - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 98f71ae8cb..b381d22548 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -43,8 +43,8 @@ module Mentionable self end - def all_references(current_user = self.author, text = nil) - ext = Gitlab::ReferenceExtractor.new(self.project, current_user, self.author) + def all_references(current_user = nil, text = nil) + ext = Gitlab::ReferenceExtractor.new(self.project, current_user || self.author, self.author) if text ext.analyze(text) diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index 82b7fbfa81..a075496ee6 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -55,6 +55,25 @@ describe TodoService, services: true do should_create_todo(user: admin, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::MENTIONED) end + + context 'when a private group is mentioned' do + let(:group) { create :group, :private } + let(:project) { create :project, :private, group: group } + let(:issue) { create :issue, author: author, project: project, description: group.to_reference } + + before do + group.add_owner(author) + group.add_user(member, Gitlab::Access::DEVELOPER) + group.add_user(john_doe, Gitlab::Access::DEVELOPER) + + service.new_issue(issue, author) + end + + it 'creates a todo for group members' do + should_create_todo(user: member, target: issue) + should_create_todo(user: john_doe, target: issue) + end + end end describe '#update_issue' do From 8d8189be9d4121bb5a18489e1bee4992efc8f120 Mon Sep 17 00:00:00 2001 From: Morton Fox Date: Sat, 7 May 2016 00:06:06 -0400 Subject: [PATCH 018/152] Update Subscription and EE links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c1a29c3bb1..d1e5bcf256 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,11 @@ There are two editions of GitLab: On [about.gitlab.com](https://about.gitlab.com/) you can find more information about: -- [Subscriptions](https://about.gitlab.com/subscription/) +- [Subscriptions](https://about.gitlab.com/pricing/) - [Consultancy](https://about.gitlab.com/consultancy/) - [Community](https://about.gitlab.com/community/) - [Hosted GitLab.com](https://about.gitlab.com/gitlab-com/) use GitLab as a free service -- [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations. +- [GitLab Enterprise Edition](https://about.gitlab.com/features/#enterprise) with additional features aimed at larger organizations. - [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab. ## Requirements From adf9a51899b02f7adc678dca7909201500f05026 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 7 May 2016 08:41:10 -0700 Subject: [PATCH 019/152] Escape HTML in commit titles in system note messages Closes #17348 --- CHANGELOG | 1 + app/services/system_note_service.rb | 6 +++++- spec/services/system_note_service_spec.rb | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 78d6c1ec2b..ebc27c2157 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.8.0 (unreleased) - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen) - Project#open_branches has been cleaned up and no longer loads entire records into memory. + - Escape HTML in commit titles in system note messages - Log to application.log when an admin starts and stops impersonating a user - Updated gitlab_git to 10.1.0 - GitAccess#protected_tag? no longer loads all tags just to check if a single one exists diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 82a0e2fd1f..4bdb1b0c07 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -351,7 +351,7 @@ class SystemNoteService # Returns an Array of Strings def self.new_commit_summary(new_commits) new_commits.collect do |commit| - "* #{commit.short_id} - #{commit.title}" + "* #{commit.short_id} - #{escape_html(commit.title)}" end end @@ -433,4 +433,8 @@ class SystemNoteService body = "Moved #{direction} #{cross_reference}" create_note(noteable: noteable, project: project, author: author, note: body) end + + def self.escape_html(text) + Rack::Utils.escape_html(text) + end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 240eae1005..5fbf2ae524 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -506,6 +506,15 @@ describe SystemNoteService, services: true do end end + describe '.new_commit_summary' do + it 'escapes HTML titles' do + commit = double(title: '
    This is a test
    ', short_id: '12345678') + escaped = '* 12345678 - <pre>This is a test</pre>' + + expect(described_class.new_commit_summary([commit])).to eq([escaped]) + end + end + include JiraServiceHelper describe 'JIRA integration' do From ffda8a1a0eb273e62fcb0197f352400946571778 Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Sun, 8 May 2016 15:27:33 +0700 Subject: [PATCH 020/152] user routings refactor --- app/controllers/snippets_controller.rb | 22 ++++++++++- .../search/results/_snippet_blob.html.haml | 2 +- .../search/results/_snippet_title.html.haml | 2 +- app/views/shared/snippets/_snippet.html.haml | 2 +- app/views/snippets/index.html.haml | 13 +++++++ app/views/users/calendar.html.haml | 2 +- app/views/users/show.html.haml | 12 +++--- config/routes.rb | 37 +++++++++---------- spec/routing/routing_spec.rb | 4 ++ 9 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 app/views/snippets/index.html.haml diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 2c038bdfda..2a17c1f34d 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -10,11 +10,29 @@ class SnippetsController < ApplicationController # Allow destroy snippet before_action :authorize_admin_snippet!, only: [:destroy] - skip_before_action :authenticate_user!, only: [:show, :raw] + skip_before_action :authenticate_user!, only: [:index, :show, :raw] layout 'snippets' respond_to :html + def index + if params[:username].present? + @user = User.find_by(username: params[:username]) + + render_404 and return unless @user + + @snippets = SnippetsFinder.new.execute(current_user, { + filter: :by_user, + user: @user, + scope: params[:scope] }). + page(params[:page]) + + render 'index' + else + redirect_to(current_user ? dashboard_snippets_path : explore_snippets_path) + end + end + def new @snippet = PersonalSnippet.new end @@ -43,7 +61,7 @@ class SnippetsController < ApplicationController @snippet.destroy - redirect_to dashboard_snippets_path + redirect_to snippets_path end def raw diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index c9b7bd154a..6b7e06f2b2 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -6,7 +6,7 @@ %span = snippet.title by - = link_to user_snippets_path(snippet.author) do + = link_to snippets_user_path(snippet.author) do = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: '' = snippet.author_name %span.light #{time_ago_with_tooltip(snippet.created_at)} diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml index c414acb6a1..8c884926db 100644 --- a/app/views/search/results/_snippet_title.html.haml +++ b/app/views/search/results/_snippet_title.html.haml @@ -17,7 +17,7 @@ = "##{snippet_title.id}" %span by - = link_to user_snippets_path(snippet_title.author) do + = link_to snippets_user_path(snippet_title.author) do = image_tag avatar_icon(snippet_title.author_email), class: "avatar avatar-inline s16", alt: '' = snippet_title.author_name %span.light #{time_ago_with_tooltip(snippet_title.created_at)} diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index c96dfefe17..e6a7a7777b 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -16,6 +16,6 @@ = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project) .snippet-info - = link_to user_snippets_path(snippet.author) do + = link_to snippets_user_path(snippet.author) do = snippet.author_name authored #{time_ago_with_tooltip(snippet.created_at)} diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml new file mode 100644 index 0000000000..8aa3d490fa --- /dev/null +++ b/app/views/snippets/index.html.haml @@ -0,0 +1,13 @@ + - page_title "By #{@user.name}", "Snippets" + + %ol.breadcrumb + %li + = link_to snippets_path do + Snippets + %li + = @user.name + .pull-right.hidden-xs + = link_to user_path(@user) do + #{@user.name} profile page + + = render 'snippets' diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml index 1de71f37d1..6ff4eb79b0 100644 --- a/app/views/users/calendar.html.haml +++ b/app/views/users/calendar.html.haml @@ -4,7 +4,7 @@ #{@timestamps.to_json}, #{@starting_year}, #{@starting_month}, - '#{user_calendar_activities_path}' + '#{calendar_activities_user_path}' ); .calendar-hint Summary of issues, merge requests, and push events diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 2ccdf843a6..e894a8e939 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -70,19 +70,19 @@ %ul.nav-links.center.user-profile-nav %li.js-activity-tab - = link_to user_calendar_activities_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do + = link_to calendar_activities_user_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do Activity %li.js-groups-tab - = link_to user_groups_path, data: {target: 'div#groups', action: 'groups', toggle: 'tab'} do + = link_to groups_user_path, data: {target: 'div#groups', action: 'groups', toggle: 'tab'} do Groups %li.js-contributed-tab - = link_to user_contributed_projects_path, data: {target: 'div#contributed', action: 'contributed', toggle: 'tab'} do + = link_to contributed_projects_user_path, data: {target: 'div#contributed', action: 'contributed', toggle: 'tab'} do Contributed projects %li.projects-tab - = link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do + = link_to projects_user_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do Personal projects %li.snippets-tab - = link_to user_snippets_path, data: {target: 'div#snippets', action: 'snippets', toggle: 'tab'} do + = link_to snippets_user_path, data: {target: 'div#snippets', action: 'snippets', toggle: 'tab'} do Snippets %div{ class: container_class } @@ -90,7 +90,7 @@ #activity.tab-pane .gray-content-block.calender-block.white.second-block.hidden-xs %div{ class: container_class } - .user-calendar{data: {href: user_calendar_path}} + .user-calendar{data: {href: calendar_user_path}} %h4.center.light %i.fa.fa-spinner.fa-spin .user-calendar-activities diff --git a/config/routes.rb b/config/routes.rb index 2bfaa2c2af..533c3b98e8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -91,6 +91,9 @@ Rails.application.routes.draw do end end + get '/s/:username', to: redirect('/u/:username/snippets'), + constraints: { username: /[a-zA-Z.0-9_\-]+(? 'users#calendar', as: :user_calendar, - constraints: { username: /.*/ } - - get 'u/:username/calendar_activities' => 'users#calendar_activities', as: :user_calendar_activities, - constraints: { username: /.*/ } - - get 'u/:username/groups' => 'users#groups', as: :user_groups, - constraints: { username: /.*/ } - - get 'u/:username/projects' => 'users#projects', as: :user_projects, - constraints: { username: /.*/ } - - get 'u/:username/contributed' => 'users#contributed', as: :user_contributed_projects, - constraints: { username: /.*/ } - - get 'u/:username/snippets' => 'users#snippets', as: :user_snippets, - constraints: { username: /.*/ } - - get '/u/:username' => 'users#show', as: :user, - constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Sun, 8 May 2016 15:29:51 +0700 Subject: [PATCH 021/152] Fix routing error --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 533c3b98e8..299b0dd1ac 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -91,7 +91,7 @@ Rails.application.routes.draw do end end - get '/s/:username', to: redirect('/u/:username/snippets'), + get '/s/:username', to: redirect('/u/%{username}/snippets'), constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Sun, 8 May 2016 09:38:24 +0100 Subject: [PATCH 022/152] Update build triggers API documentation (#10153) Update build triggers API documentation [ci skip] * Ensure updated/deleted timestamps are consistent --- doc/api/build_triggers.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/api/build_triggers.md b/doc/api/build_triggers.md index 4a12e962b6..0881a7d7a9 100644 --- a/doc/api/build_triggers.md +++ b/doc/api/build_triggers.md @@ -101,8 +101,18 @@ DELETE /projects/:id/triggers/:token | Attribute | Type | required | Description | |-----------|---------|----------|--------------------------| | `id` | integer | yes | The ID of a project | -| `token` | string | yes | The `token` of a project | +| `token` | string | yes | The `token` of a trigger | ``` curl -X DELETE -H "PRIVATE_TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/triggers/7b9148c158980bbd9bcea92c17522d" ``` + +```json +{ + "created_at": "2015-12-23T16:25:56.760Z", + "deleted_at": "2015-12-24T12:32:20.100Z", + "last_used": null, + "token": "7b9148c158980bbd9bcea92c17522d", + "updated_at": "2015-12-24T12:32:20.100Z" +} +``` From 29089352e388882a799daff7623fc7213f60e1c9 Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Sun, 8 May 2016 20:48:07 +0700 Subject: [PATCH 023/152] Routing refactoring --- .../search/results/_snippet_blob.html.haml | 2 +- .../search/results/_snippet_title.html.haml | 2 +- app/views/shared/snippets/_snippet.html.haml | 2 +- app/views/users/calendar.html.haml | 2 +- app/views/users/show.html.haml | 12 +++++----- config/routes.rb | 23 ++++++++----------- 6 files changed, 20 insertions(+), 23 deletions(-) diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index 6b7e06f2b2..c9b7bd154a 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -6,7 +6,7 @@ %span = snippet.title by - = link_to snippets_user_path(snippet.author) do + = link_to user_snippets_path(snippet.author) do = image_tag avatar_icon(snippet.author_email), class: "avatar avatar-inline s16", alt: '' = snippet.author_name %span.light #{time_ago_with_tooltip(snippet.created_at)} diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml index 8c884926db..c414acb6a1 100644 --- a/app/views/search/results/_snippet_title.html.haml +++ b/app/views/search/results/_snippet_title.html.haml @@ -17,7 +17,7 @@ = "##{snippet_title.id}" %span by - = link_to snippets_user_path(snippet_title.author) do + = link_to user_snippets_path(snippet_title.author) do = image_tag avatar_icon(snippet_title.author_email), class: "avatar avatar-inline s16", alt: '' = snippet_title.author_name %span.light #{time_ago_with_tooltip(snippet_title.created_at)} diff --git a/app/views/shared/snippets/_snippet.html.haml b/app/views/shared/snippets/_snippet.html.haml index e6a7a7777b..c96dfefe17 100644 --- a/app/views/shared/snippets/_snippet.html.haml +++ b/app/views/shared/snippets/_snippet.html.haml @@ -16,6 +16,6 @@ = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project) .snippet-info - = link_to snippets_user_path(snippet.author) do + = link_to user_snippets_path(snippet.author) do = snippet.author_name authored #{time_ago_with_tooltip(snippet.created_at)} diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml index 6ff4eb79b0..1de71f37d1 100644 --- a/app/views/users/calendar.html.haml +++ b/app/views/users/calendar.html.haml @@ -4,7 +4,7 @@ #{@timestamps.to_json}, #{@starting_year}, #{@starting_month}, - '#{calendar_activities_user_path}' + '#{user_calendar_activities_path}' ); .calendar-hint Summary of issues, merge requests, and push events diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index d5f482f716..9017fd54fc 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -70,19 +70,19 @@ %ul.nav-links.center.user-profile-nav %li.js-activity-tab - = link_to calendar_activities_user_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do + = link_to user_calendar_activities_path, data: {target: 'div#activity', action: 'activity', toggle: 'tab'} do Activity %li.js-groups-tab - = link_to groups_user_path, data: {target: 'div#groups', action: 'groups', toggle: 'tab'} do + = link_to user_groups_path, data: {target: 'div#groups', action: 'groups', toggle: 'tab'} do Groups %li.js-contributed-tab - = link_to contributed_projects_user_path, data: {target: 'div#contributed', action: 'contributed', toggle: 'tab'} do + = link_to user_contributed_projects_path, data: {target: 'div#contributed', action: 'contributed', toggle: 'tab'} do Contributed projects %li.projects-tab - = link_to projects_user_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do + = link_to user_projects_path, data: {target: 'div#projects', action: 'projects', toggle: 'tab'} do Personal projects %li.snippets-tab - = link_to snippets_user_path, data: {target: 'div#snippets', action: 'snippets', toggle: 'tab'} do + = link_to user_snippets_path, data: {target: 'div#snippets', action: 'snippets', toggle: 'tab'} do Snippets %div{ class: container_class } @@ -90,7 +90,7 @@ #activity.tab-pane .row-content-block.calender-block.white.second-block.hidden-xs %div{ class: container_class } - .user-calendar{data: {href: calendar_user_path}} + .user-calendar{data: {href: user_calendar_path}} %h4.center.light %i.fa.fa-spinner.fa-spin .user-calendar-activities diff --git a/config/routes.rb b/config/routes.rb index 299b0dd1ac..594bab204f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -343,19 +343,16 @@ Rails.application.routes.draw do end end - resources(:users, - path: 'u', - param: :username, - constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Sun, 8 May 2016 22:06:19 +0700 Subject: [PATCH 024/152] Add specs for user routing and update spec for user controller --- spec/controllers/users_controller_spec.rb | 22 ++++++++++++++ spec/routing/routing_spec.rb | 37 +++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index 8045c8b940..c61ec17466 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -112,4 +112,26 @@ describe UsersController do expect(response).to render_template('calendar_activities') end end + + describe 'GET #snippets' do + before do + sign_in(user) + end + + context 'format html' do + it 'renders snippets page' do + get :snippets, username: user.username + expect(response.status).to eq(200) + expect(response).to render_template('show') + end + end + + context 'format json' do + it 'response with snippets json data' do + get :snippets, username: user.username, format: :json + expect(response.status).to eq(200) + expect(JSON.parse(response.body)).to have_key('html') + end + end + end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 543088fa08..8530a2f31d 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -1,5 +1,42 @@ require 'spec_helper' +# user GET /u/:username/ +# user_groups GET /u/:username/groups(.:format) +# user_projects GET /u/:username/projects(.:format) +# user_contributed_projects GET /u/:username/contributed(.:format) +# user_snippets GET /u/:username/snippets(.:format) +# user_calendar GET /u/:username/calendar(.:format) +# user_calendar_activities GET /u/:username/calendar_activities(.:format) +describe UsersController, "routing" do + it "to #show" do + expect(get("/u/User")).to route_to('users#show', username: 'User') + end + + it "to #groups" do + expect(get("/u/User/groups")).to route_to('users#groups', username: 'User') + end + + it "to #projects" do + expect(get("/u/User/projects")).to route_to('users#projects', username: 'User') + end + + it "to #contributed" do + expect(get("/u/User/contributed")).to route_to('users#contributed', username: 'User') + end + + it "to #snippets" do + expect(get("/u/User/snippets")).to route_to('users#snippets', username: 'User') + end + + it "to #calendar" do + expect(get("/u/User/calendar")).to route_to('users#calendar', username: 'User') + end + + it "to #calendar_activities" do + expect(get("/u/User/calendar_activities")).to route_to('users#calendar_activities', username: 'User') + end +end + # search GET /search(.:format) search#show describe SearchController, "routing" do it "to #show" do From ed2a7a1ec616fa948d3e24d77bfc7626ae55e9b9 Mon Sep 17 00:00:00 2001 From: Benedikt Huss Date: Sat, 7 May 2016 21:12:15 +0200 Subject: [PATCH 025/152] Fix build notification on merge request page change even if the build status didn't change --- CHANGELOG | 3 +++ app/assets/javascripts/merge_request_widget.js.coffee | 9 +++++---- .../projects/merge_requests/widget/_heading.html.haml | 7 +------ spec/javascripts/merge_request_widget_spec.js.coffee | 6 ++++++ 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e9b3e7b097..e9204d32bd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,9 @@ v 8.8.0 (unreleased) - Expire repository exists? and has_visible_content? caches after a push if necessary - Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi) +v 8.7.4 + - Fix always showing build notification message when switching between merge requests + v 8.7.3 - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented - Merge request widget displays TeamCity build state and code coverage correctly again. diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee index 17a5a057a9..f58647988a 100644 --- a/app/assets/javascripts/merge_request_widget.js.coffee +++ b/app/assets/javascripts/merge_request_widget.js.coffee @@ -9,11 +9,12 @@ class @MergeRequestWidget constructor: (@opts) -> $('#modal_merge_info').modal(show: false) @firstCICheck = true - @readyForCICheck = true + @readyForCICheck = false clearInterval @fetchBuildStatusInterval @clearEventListeners() @addEventListeners() + @getCIStatus(false) @pollCIStatus() notifyPermissions() @@ -71,7 +72,7 @@ class @MergeRequestWidget if data.status is '' return - if @firstCiCheck || data.status isnt @opts.ci_status and data.status? + if @firstCICheck || data.status isnt @opts.ci_status and data.status? @opts.ci_status = data.status @showCIStatus data.status if data.coverage @@ -79,7 +80,7 @@ class @MergeRequestWidget # The first check should only update the UI, a notification # should only be displayed on status changes - if showNotification and not @firstCiCheck + if showNotification and not @firstCICheck status = @ciLabelForStatus(data.status) if status is "preparing" @@ -102,7 +103,7 @@ class @MergeRequestWidget @close() Turbolinks.visit _this.opts.builds_path ) - @firstCiCheck = false + @firstCICheck = false showCIStatus: (state) -> $('.ci_widget').hide() diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 2ec0d20a87..4d38175461 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -41,9 +41,4 @@ .ci_widget.ci-error{style: "display:none"} = icon("times-circle") - Could not connect to the CI server. Please check your settings and try again. - - :javascript - $(function() { - merge_request_widget.getCIStatus(false); - }); + Could not connect to the CI server. Please check your settings and try again. \ No newline at end of file diff --git a/spec/javascripts/merge_request_widget_spec.js.coffee b/spec/javascripts/merge_request_widget_spec.js.coffee index c0bd8a29e4..92b7eeb111 100644 --- a/spec/javascripts/merge_request_widget_spec.js.coffee +++ b/spec/javascripts/merge_request_widget_spec.js.coffee @@ -47,3 +47,9 @@ describe 'MergeRequestWidget', -> spy = spyOn(@class, 'showCICoverage').and.stub() @class.getCIStatus(false) expect(spy).not.toHaveBeenCalled() + + it 'should not display a notification on the first check after the widget has been created', -> + spy = spyOn(window, 'notify') + @class = new MergeRequestWidget(@opts) + @class.getCIStatus(true) + expect(spy).not.toHaveBeenCalled() From 0e29653f51b439eb018cefe434a4a57db4239cf4 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 8 May 2016 15:45:04 -0400 Subject: [PATCH 026/152] Remove `wall_enabled` field from Project --- app/models/project.rb | 2 -- config/gitlab.teatro.yml | 1 - .../20160508194200_remove_wall_enabled_from_projects.rb | 5 +++++ db/schema.rb | 3 +-- spec/factories/projects.rb | 1 - spec/models/project_spec.rb | 1 - 6 files changed, 6 insertions(+), 7 deletions(-) create mode 100644 db/migrate/20160508194200_remove_wall_enabled_from_projects.rb diff --git a/app/models/project.rb b/app/models/project.rb index dfd1e54ecf..86a7835c5f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -10,7 +10,6 @@ # updated_at :datetime # creator_id :integer # issues_enabled :boolean default(TRUE), not null -# wall_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null # namespace_id :integer @@ -67,7 +66,6 @@ class Project < ActiveRecord::Base 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 default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled } diff --git a/config/gitlab.teatro.yml b/config/gitlab.teatro.yml index f0656400be..01c8dc5ff9 100644 --- a/config/gitlab.teatro.yml +++ b/config/gitlab.teatro.yml @@ -15,7 +15,6 @@ production: &base issues: true merge_requests: true wiki: true - wall: false snippets: false visibility_level: "private" # can be "private" | "internal" | "public" diff --git a/db/migrate/20160508194200_remove_wall_enabled_from_projects.rb b/db/migrate/20160508194200_remove_wall_enabled_from_projects.rb new file mode 100644 index 0000000000..aa560bc0f0 --- /dev/null +++ b/db/migrate/20160508194200_remove_wall_enabled_from_projects.rb @@ -0,0 +1,5 @@ +class RemoveWallEnabledFromProjects < ActiveRecord::Migration + def change + remove_column :projects, :wall_enabled, :boolean, default: true, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 7683aad968..71d953afe3 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: 20160421130527) do +ActiveRecord::Schema.define(version: 20160508194200) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -728,7 +728,6 @@ ActiveRecord::Schema.define(version: 20160421130527) do t.datetime "updated_at" t.integer "creator_id" t.boolean "issues_enabled", default: true, null: false - t.boolean "wall_enabled", default: true, null: false t.boolean "merge_requests_enabled", default: true, null: false t.boolean "wiki_enabled", default: true, null: false t.integer "namespace_id" diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index c14b99606b..ecd0e44dd6 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -10,7 +10,6 @@ # updated_at :datetime # creator_id :integer # issues_enabled :boolean default(TRUE), not null -# wall_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null # namespace_id :integer diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 5b1cf71337..3a8a6c2c16 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -10,7 +10,6 @@ # updated_at :datetime # creator_id :integer # issues_enabled :boolean default(TRUE), not null -# wall_enabled :boolean default(TRUE), not null # merge_requests_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null # namespace_id :integer From 9cc0937b3a41caca89fa6722149248a8f7b0a447 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Sun, 8 May 2016 15:33:34 -0600 Subject: [PATCH 027/152] Enable the Rubocop DeprecatedClassMethods cop This reports uses of `File.exists?` and `Dir.exists?`, which were both deprecated in Ruby and will eventually be removed in favor of `.exist?`. Also fixes all existing uses of the deprecated methods. --- .rubocop.yml | 2 +- app/models/ci/build.rb | 2 +- app/models/merge_request.rb | 2 +- config/boot.rb | 2 +- config/initializers/carrierwave.rb | 2 +- lib/gitlab/backend/shell.rb | 2 +- lib/gitlab/redis.rb | 2 +- spec/config/mail_room_spec.rb | 2 +- spec/services/projects/create_service_spec.rb | 4 ++-- spec/services/projects/destroy_service_spec.rb | 8 ++++---- 10 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 9f179efa3c..b49b60d676 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -770,7 +770,7 @@ Lint/DefEndAlignment: # Check for deprecated class method calls. Lint/DeprecatedClassMethods: - Enabled: false + Enabled: true # Check for duplicate method definitions. Lint/DuplicateMethods: diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 4bc3a225e2..073ec9dd89 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -238,7 +238,7 @@ module Ci end def recreate_trace_dir - unless Dir.exists?(dir_to_trace) + unless Dir.exist?(dir_to_trace) FileUtils.mkdir_p(dir_to_trace) end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 4175e1e5fb..365ccc90b4 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -544,7 +544,7 @@ class MergeRequest < ActiveRecord::Base end def ref_is_fetched? - File.exists?(File.join(project.repository.path_to_repo, ref_path)) + File.exist?(File.join(project.repository.path_to_repo, ref_path)) end def ensure_ref_fetched diff --git a/config/boot.rb b/config/boot.rb index 4489e58688..f2830ae316 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -3,4 +3,4 @@ require 'rubygems' # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb index df28d30d75..0d278717d6 100644 --- a/config/initializers/carrierwave.rb +++ b/config/initializers/carrierwave.rb @@ -2,7 +2,7 @@ CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/ aws_file = Rails.root.join('config', 'aws.yml') -if File.exists?(aws_file) +if File.exist?(aws_file) AWS_CONFIG = YAML.load(File.read(aws_file))[Rails.env] CarrierWave.configure do |config| diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 132f9cd196..3e3986d638 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -180,7 +180,7 @@ module Gitlab # exists?('gitlab/cookies.git') # def exists?(dir_name) - File.exists?(full_path(dir_name)) + File.exist?(full_path(dir_name)) end protected diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb index 5c352c96de..f7db7a6391 100644 --- a/lib/gitlab/redis.rb +++ b/lib/gitlab/redis.rb @@ -42,7 +42,7 @@ module Gitlab config_file = File.expand_path('../../../config/resque.yml', __FILE__) @url = "redis://localhost:6379" - if File.exists?(config_file) + if File.exist?(config_file) @url =YAML.load_file(config_file)[rails_env] end end diff --git a/spec/config/mail_room_spec.rb b/spec/config/mail_room_spec.rb index 462afb24f0..6fad7e2b9e 100644 --- a/spec/config/mail_room_spec.rb +++ b/spec/config/mail_room_spec.rb @@ -43,7 +43,7 @@ describe "mail_room.yml" do redis_config_file = Rails.root.join('config', 'resque.yml') redis_url = - if File.exists?(redis_config_file) + if File.exist?(redis_config_file) YAML.load_file(redis_config_file)[Rails.env] else "redis://localhost:6379" diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index e43903dbd3..fd11435946 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -64,7 +64,7 @@ describe Projects::CreateService, services: true do @path = ProjectWiki.new(@project, @user).send(:path_to_repo) end - it { expect(File.exists?(@path)).to be_truthy } + it { expect(File.exist?(@path)).to be_truthy } end context 'wiki_enabled false does not create wiki repository directory' do @@ -74,7 +74,7 @@ describe Projects::CreateService, services: true do @path = ProjectWiki.new(@project, @user).send(:path_to_repo) end - it { expect(File.exists?(@path)).to be_falsey } + it { expect(File.exist?(@path)).to be_falsey } end end diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb index 1ec2707771..a5cb6f382e 100644 --- a/spec/services/projects/destroy_service_spec.rb +++ b/spec/services/projects/destroy_service_spec.rb @@ -13,8 +13,8 @@ describe Projects::DestroyService, services: true do end it { expect(Project.all).not_to include(project) } - it { expect(Dir.exists?(path)).to be_falsey } - it { expect(Dir.exists?(remove_path)).to be_falsey } + it { expect(Dir.exist?(path)).to be_falsey } + it { expect(Dir.exist?(remove_path)).to be_falsey } end context 'Sidekiq fake' do @@ -24,8 +24,8 @@ describe Projects::DestroyService, services: true do end it { expect(Project.all).not_to include(project) } - it { expect(Dir.exists?(path)).to be_falsey } - it { expect(Dir.exists?(remove_path)).to be_truthy } + it { expect(Dir.exist?(path)).to be_falsey } + it { expect(Dir.exist?(remove_path)).to be_truthy } end def destroy_project(project, user, params) From 4be77d0b057e3b26f48fbaee947bf8f91a755f41 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 8 May 2016 19:09:33 -0700 Subject: [PATCH 028/152] Improve multiple branch push performance by memoizing permission checking If you attempt to push thousands of branches at once, the 60-second timeout will occur because GitAccess checking does a lot of work to check if the user has permission to push to a branch. This changes does two things: 1. Instead of making 1 DB query per branch push, use a memoized list of protected branches to check 2. Memoize what permissions the user has to perform on this project On a test of 10,000 branch pushes, this prevents gitlab-shell from hitting the 60-second timeout. Closes #17225 --- CHANGELOG | 1 + app/models/project.rb | 2 +- lib/gitlab/git_access.rb | 7 ++++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e85d3c88b2..822fa4be56 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.8.0 (unreleased) - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen) - Project#open_branches has been cleaned up and no longer loads entire records into memory. - Escape HTML in commit titles in system note messages + - Improve multiple branch push performance by memoizing permission checking - Log to application.log when an admin starts and stops impersonating a user - Updated gitlab_git to 10.1.0 - GitAccess#protected_tag? no longer loads all tags just to check if a single one exists diff --git a/app/models/project.rb b/app/models/project.rb index dfd1e54ecf..5b556a2275 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -767,7 +767,7 @@ class Project < ActiveRecord::Base # Check if current branch name is marked as protected in the system def protected_branch?(branch_name) - protected_branches.where(name: branch_name).any? + protected_branch_names.include?(branch_name) end def developers_can_push_to_protected_branch?(branch_name) diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 6cb4123987..d2a0e316cb 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -122,6 +122,11 @@ module Gitlab build_status_object(true) end + def can_user_do_action?(action) + @permission_cache ||= {} + @permission_cache[action] ||= user.can?(action, project) + end + def change_access_check(change) oldrev, newrev, ref = change.split(' ') @@ -135,7 +140,7 @@ module Gitlab :push_code end - unless user.can?(action, project) + unless can_user_do_action?(action) status = case action when :force_push_code_to_protected_branches From 14b36f91d9d803850f59fe49961d6d6a9d540aab Mon Sep 17 00:00:00 2001 From: Artem Sidorenko Date: Tue, 3 May 2016 17:33:43 +0200 Subject: [PATCH 029/152] Use the proper GitLab URL for links in Wiki --- CHANGELOG | 1 + app/models/project_wiki.rb | 2 +- lib/gitlab/url_builder.rb | 2 +- spec/lib/gitlab/url_builder_spec.rb | 2 +- spec/models/project_wiki_spec.rb | 4 +++- 5 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 822fa4be56..b07b7da330 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.8.0 (unreleased) v 8.7.4 - Fix always showing build notification message when switching between merge requests + - Fix links on wiki pages for relative url setups. !4026 (Artem Sidorenko) v 8.7.3 - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 7c1a61bb0b..c91cb70ae2 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -40,7 +40,7 @@ class ProjectWiki end def wiki_base_path - ["/", @project.path_with_namespace, "/wikis"].join('') + [Gitlab.config.gitlab.url, "/", @project.path_with_namespace, "/wikis"].join('') end # Returns the Gollum::Wiki object. diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index 2bbbd3074e..67a09d5abf 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -62,7 +62,7 @@ module Gitlab end def wiki_page_url - "#{Gitlab.config.gitlab.url}#{object.wiki.wiki_base_path}/#{object.slug}" + "#{object.wiki.wiki_base_path}/#{object.slug}" end end end diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index bf11472407..c8d3bc0139 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -112,7 +112,7 @@ describe Gitlab::UrlBuilder, lib: true do wiki_page = build(:wiki_page) url = described_class.build(wiki_page) - expect(url).to eq "#{Gitlab.config.gitlab.url}#{wiki_page.wiki.wiki_base_path}/#{wiki_page.slug}" + expect(url).to eq "#{Gitlab.config.gitlab.url}/#{wiki_page.wiki.project.path_with_namespace}/wikis/#{wiki_page.slug}" end end end diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index 532e3f013f..ea659f417f 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -38,7 +38,9 @@ describe ProjectWiki, models: true do describe "#wiki_base_path" do it "returns the wiki base path" do - wiki_base_path = "/#{project.path_with_namespace}/wikis" + gitlab_url = Gitlab.config.gitlab.url + wiki_base_path = "#{gitlab_url}/#{project.path_with_namespace}/wikis" + expect(subject.wiki_base_path).to eq(wiki_base_path) end end From 5bd32954f619b521496bca200a0ed31615b8e736 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 9 May 2016 13:52:20 +0200 Subject: [PATCH 030/152] Remove space --- doc/web_hooks/web_hooks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index 6ffdb18339..45506ac1d7 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -16,7 +16,7 @@ Webhooks can be used to update an external issue tracker, trigger CI builds, upd ## Webhook endpoint tips If you are writing your own endpoint (web server) that will receive -GitLab web hooks keep in mind the following things: +GitLab webhooks keep in mind the following things: - Your endpoint should send its HTTP response as fast as possible. If you wait too long, GitLab may decide the hook failed and retry it. From 607996c664ddfb67befaf372b108081169ebeaea Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 9 May 2016 13:56:15 +0200 Subject: [PATCH 031/152] Remove broken inline runner description edit form --- .../javascripts/ci/application.js.coffee | 28 ------------------- app/views/admin/runners/_runner.html.haml | 12 ++------ 2 files changed, 2 insertions(+), 38 deletions(-) diff --git a/app/assets/javascripts/ci/application.js.coffee b/app/assets/javascripts/ci/application.js.coffee index 05aa0f366b..ca24c1d759 100644 --- a/app/assets/javascripts/ci/application.js.coffee +++ b/app/assets/javascripts/ci/application.js.coffee @@ -1,34 +1,6 @@ -# This is a manifest file that'll be compiled into application.js, which will include all the files -# listed below. -# -# Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, -# or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. -# -# It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -# the compiled file. -# -# WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD -# GO AFTER THE REQUIRES BELOW. -# #= require pager #= require jquery_nested_form #= require_tree . -# -$(document).on 'click', '.edit-runner-link', (event) -> - event.preventDefault() - - descr = $(this).closest('.runner-description').first() - descr.addClass('hide') - form = descr.next('.runner-description-form') - descrInput = form.find('input.description') - originalValue = descrInput.val() - form.removeClass('hide') - form.find('.cancel').on 'click', (event) -> - event.preventDefault() - - form.addClass('hide') - descrInput.val(originalValue) - descr.removeClass('hide') $(document).on 'click', '.assign-all-runner', -> $(this).replaceWith(' Assign in progress..') diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index 6745e58dec..97b6ec1f85 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -11,18 +11,10 @@ = link_to admin_runner_path(runner) do = runner.short_sha %td - .runner-description - = runner.description - %span (#{link_to 'edit', '#', class: 'edit-runner-link'}) - .runner-description-form.hide - = form_for [:admin, runner], remote: true, html: { class: 'form-inline' } do |f| - .form-group - = f.text_field :description, class: 'form-control' - = f.submit 'Save', class: 'btn' - %span (#{link_to 'cancel', '#', class: 'cancel'}) + = runner.description %td - if runner.shared? - \- + = '-' - else = runner.projects.count(:all) %td From a19508d6b79aa3a399f479b09b52b35b5289a92a Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 9 May 2016 13:58:43 +0200 Subject: [PATCH 032/152] Fix test failures --- features/steps/project/hooks.rb | 2 +- spec/models/hooks/web_hook_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb index b1ffe7f7b4..13c0713669 100644 --- a/features/steps/project/hooks.rb +++ b/features/steps/project/hooks.rb @@ -59,7 +59,7 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps step 'hook should be triggered' do expect(current_path).to eq namespace_project_hooks_path(current_project.namespace, current_project) expect(page).to have_selector '.flash-notice', - text: 'Hook successfully executed.' + text: 'Hook executed successfully: HTTP 200' end step 'I should see hook error message' do diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index 37a27d73aa..f9bab487b9 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -95,13 +95,13 @@ describe WebHook, models: true do it "handles 200 status code" do WebMock.stub_request(:post, project_hook.url).to_return(status: 200, body: "Success") - expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success']) + expect(project_hook.execute(@data, 'push_hooks')).to eq([200, 'Success']) end it "handles 2xx status codes" do WebMock.stub_request(:post, project_hook.url).to_return(status: 201, body: "Success") - expect(project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success']) + expect(project_hook.execute(@data, 'push_hooks')).to eq([201, 'Success']) end end end From 5364400741b7d041c945ccca98283bc465d5a87c Mon Sep 17 00:00:00 2001 From: cnam-dep Date: Wed, 24 Feb 2016 00:59:32 +0300 Subject: [PATCH 033/152] API: Expose Issue#user_notes_count --- CHANGELOG | 3 +++ app/models/concerns/issuable.rb | 4 ++++ doc/api/issues.md | 15 ++++++++++----- lib/api/entities.rb | 2 +- spec/requests/api/issues_spec.rb | 15 +++++++++++++++ 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index b07b7da330..567b571717 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -51,6 +51,9 @@ v 8.7.2 - Label titles in filters are now escaped properly v 8.7.1 + - API: Expose Issue#user_notes_count (Anton Popov) + +v 8.7.1 (unreleased) - Throttle the update of `project.last_activity_at` to 1 minute. !3848 - Fix .gitlab-ci.yml parsing issue when hidde job is a template without script definition. !3849 - Fix license detection to detect all license files, not only known licenses. !3878 diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 2e4efc4e8d..7a3db74203 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -160,6 +160,10 @@ module Issuable notes.awards.where(note: "thumbsup").count end + def user_notes_count + notes.user.count + end + def subscribed_without_subscriptions?(user) participants(user).include?(user) end diff --git a/doc/api/issues.md b/doc/api/issues.md index 3e78149f44..fc7a7ae0c0 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -77,7 +77,8 @@ Example response: "created_at" : "2016-01-04T15:31:51.081Z", "iid" : 6, "labels" : [], - "subscribed" : false + "subscribed" : false, + "user_notes_count": 1 } ] ``` @@ -154,7 +155,8 @@ Example response: "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.", "updated_at" : "2016-01-04T15:31:46.176Z", "created_at" : "2016-01-04T15:31:46.176Z", - "subscribed" : false + "subscribed" : false, + "user_notes_count": 1 } ] ``` @@ -216,7 +218,8 @@ Example response: "title" : "Ut commodi ullam eos dolores perferendis nihil sunt.", "updated_at" : "2016-01-04T15:31:46.176Z", "created_at" : "2016-01-04T15:31:46.176Z", - "subscribed": false + "subscribed": false, + "user_notes_count": 1 } ``` @@ -271,7 +274,8 @@ Example response: "description" : null, "updated_at" : "2016-01-07T12:44:33.959Z", "milestone" : null, - "subscribed" : true + "subscribed" : true, + "user_notes_count": 0 } ``` @@ -329,7 +333,8 @@ Example response: "id" : 85, "assignee" : null, "milestone" : null, - "subscribed" : true + "subscribed" : true, + "user_notes_count": 0 } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 716ca6f7ed..42f623c5a3 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -170,10 +170,10 @@ module API expose :label_names, as: :labels expose :milestone, using: Entities::Milestone expose :assignee, :author, using: Entities::UserBasic - expose :subscribed do |issue, options| issue.subscribed?(options[:current_user]) end + expose :user_notes_count end class MergeRequest < ProjectEntity diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index f88e39cad9..008bcae174 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -39,6 +39,7 @@ describe API::API, api: true do let!(:empty_milestone) do create(:milestone, title: '2.0.0', project: project) end + let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) } before { project.team << [user, :reporter] } @@ -128,6 +129,13 @@ describe API::API, api: true do expect(json_response).to be_an Array expect(json_response.length).to eq(0) end + + it 'should return an count notes in issue' do + get api("/issues", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['user_notes_count']).to eq(1) + end end end @@ -229,6 +237,13 @@ describe API::API, api: true do expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_issue.id) end + + it 'should return an count notes in issue' do + get api("#{base_url}/issues", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['user_notes_count']).to eq(1) + end end describe "GET /projects/:id/issues/:issue_id" do From f5240f9703fea9902ac4304b8e12daed5c21820d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 15 Mar 2016 16:17:24 +0100 Subject: [PATCH 034/152] Expose MergeRequest#user_notes_count in the API and use the method in issues list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 4 +-- app/views/projects/issues/_issue.html.haml | 14 +++----- .../merge_requests/_merge_request.html.haml | 14 +++----- doc/api/merge_requests.md | 22 ++++++++---- lib/api/entities.rb | 2 +- spec/requests/api/issues_spec.rb | 36 +++++++++++-------- spec/requests/api/merge_requests_spec.rb | 28 +++++++++++++++ 7 files changed, 74 insertions(+), 46 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 567b571717..aeade934b4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -25,6 +25,7 @@ v 8.8.0 (unreleased) - Added button to toggle whitespaces changes on diff view - Backport GitHub Enterprise import support from EE - Create tags using Rugged for performance reasons. !3745 + - API: Expose Issue#user_notes_count. !3126 (Anton Popov) - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718 - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes) - Added multiple colors for labels in dropdowns when dups happen. @@ -51,9 +52,6 @@ v 8.7.2 - Label titles in filters are now escaped properly v 8.7.1 - - API: Expose Issue#user_notes_count (Anton Popov) - -v 8.7.1 (unreleased) - Throttle the update of `project.last_activity_at` to 1 minute. !3848 - Fix .gitlab-ci.yml parsing issue when hidde job is a template without script definition. !3849 - Fix license detection to detect all license files, not only known licenses. !3878 diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 9ad86ed71c..5cf70ea3bb 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -28,16 +28,10 @@ = downvotes - note_count = issue.notes.user.nonawards.count - - if note_count > 0 - %li - = link_to issue_path(issue) + "#notes" do - = icon('comments') - = note_count - - else - %li - = link_to issue_path(issue) + "#notes", class: "issue-no-comments" do - = icon('comments') - = note_count + %li + = link_to issue_path(issue, anchor: 'notes'), class: ('issue-no-comments' if note_count.zero?) do + = icon('comments') + = note_count .issue-info #{issue.to_reference} · diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index e740fe8c84..73c6a95f5c 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -36,16 +36,10 @@ = downvotes - note_count = merge_request.mr_and_commit_notes.user.nonawards.count - - if note_count > 0 - %li - = link_to merge_request_path(merge_request) + "#notes" do - = icon('comments') - = note_count - - else - %li - = link_to merge_request_path(merge_request) + "#notes", class: "merge-request-no-comments" do - = icon('comments') - = note_count + %li + = link_to merge_request_path(merge_request, anchor: 'notes'), class: ('merge-request-no-comments' if note_count.zero?) do + = icon('comments') + = note_count .merge-request-info #{merge_request.to_reference} · diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 2057f9d77a..8217e30fe2 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -67,7 +67,8 @@ Parameters: }, "merge_when_build_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : false + "subscribed" : false, + "user_notes_count": 1 } ] ``` @@ -130,7 +131,8 @@ Parameters: }, "merge_when_build_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : true + "subscribed" : true, + "user_notes_count": 1 } ``` @@ -230,6 +232,7 @@ Parameters: "merge_when_build_succeeds": true, "merge_status": "can_be_merged", "subscribed" : true, + "user_notes_count": 1, "changes": [ { "old_path": "VERSION", @@ -308,7 +311,8 @@ Parameters: }, "merge_when_build_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : true + "subscribed" : true, + "user_notes_count": 0 } ``` @@ -378,7 +382,8 @@ Parameters: }, "merge_when_build_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : true + "subscribed" : true, + "user_notes_count": 1 } ``` @@ -472,7 +477,8 @@ Parameters: }, "merge_when_build_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : true + "subscribed" : true, + "user_notes_count": 1 } ``` @@ -537,7 +543,8 @@ Parameters: }, "merge_when_build_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : true + "subscribed" : true, + "user_notes_count": 1 } ``` @@ -602,7 +609,8 @@ Example response: "title" : "Consequatur vero maxime deserunt laboriosam est voluptas dolorem.", "created_at" : "2016-01-04T15:31:51.081Z", "iid" : 6, - "labels" : [] + "labels" : [], + "user_notes_count": 1 }, ] ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 42f623c5a3..2870a6a40e 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -187,10 +187,10 @@ module API expose :milestone, using: Entities::Milestone expose :merge_when_build_succeeds expose :merge_status - expose :subscribed do |merge_request, options| merge_request.subscribed?(options[:current_user]) end + expose :user_notes_count end class MergeRequestChanges < MergeRequest diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 008bcae174..9dd43f4fab 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -39,7 +39,7 @@ describe API::API, api: true do let!(:empty_milestone) do create(:milestone, title: '2.0.0', project: project) end - let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) } + let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } before { project.team << [user, :reporter] } @@ -129,13 +129,6 @@ describe API::API, api: true do expect(json_response).to be_an Array expect(json_response.length).to eq(0) end - - it 'should return an count notes in issue' do - get api("/issues", user) - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.first['user_notes_count']).to eq(1) - end end end @@ -237,18 +230,31 @@ describe API::API, api: true do expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_issue.id) end - - it 'should return an count notes in issue' do - get api("#{base_url}/issues", user) - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.first['user_notes_count']).to eq(1) - end end describe "GET /projects/:id/issues/:issue_id" do + it 'exposes known attributes' do + get api("/projects/#{project.id}/issues/#{issue.id}", user) + + expect(response.status).to eq(200) + expect(json_response['id']).to eq(issue.id) + expect(json_response['iid']).to eq(issue.iid) + expect(json_response['project_id']).to eq(issue.project.id) + expect(json_response['title']).to eq(issue.title) + expect(json_response['description']).to eq(issue.description) + expect(json_response['state']).to eq(issue.state) + expect(json_response['created_at']).to be_present + expect(json_response['updated_at']).to be_present + expect(json_response['labels']).to eq(issue.label_names) + expect(json_response['milestone']).to be_a Hash + expect(json_response['assignee']).to be_a Hash + expect(json_response['author']).to be_a Hash + expect(json_response['user_notes_count']).to be(1) + end + it "should return a project issue by id" do get api("/projects/#{project.id}/issues/#{issue.id}", user) + expect(response.status).to eq(200) expect(json_response['title']).to eq(issue.title) expect(json_response['iid']).to eq(issue.iid) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 1fa7e76894..4b0111df14 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -113,6 +113,34 @@ describe API::API, api: true do end describe "GET /projects/:id/merge_requests/:merge_request_id" do + it 'exposes known attributes' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) + + expect(response.status).to eq(200) + expect(json_response['id']).to eq(merge_request.id) + expect(json_response['iid']).to eq(merge_request.iid) + expect(json_response['project_id']).to eq(merge_request.project.id) + expect(json_response['title']).to eq(merge_request.title) + expect(json_response['description']).to eq(merge_request.description) + expect(json_response['state']).to eq(merge_request.state) + expect(json_response['created_at']).to be_present + expect(json_response['updated_at']).to be_present + expect(json_response['labels']).to eq(merge_request.label_names) + expect(json_response['milestone']).to be_nil + expect(json_response['assignee']).to be_a Hash + expect(json_response['author']).to be_a Hash + expect(json_response['target_branch']).to eq(merge_request.target_branch) + expect(json_response['source_branch']).to eq(merge_request.source_branch) + expect(json_response['upvotes']).to eq(0) + expect(json_response['downvotes']).to eq(0) + expect(json_response['source_project_id']).to eq(merge_request.source_project.id) + expect(json_response['target_project_id']).to eq(merge_request.target_project.id) + expect(json_response['work_in_progress']).to be_falsy + expect(json_response['merge_when_build_succeeds']).to be_falsy + expect(json_response['merge_status']).to eq('can_be_merged') + expect(json_response['user_notes_count']).to be(2) + end + it "should return merge_request" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}", user) expect(response.status).to eq(200) From 32811d98fe2893a0671fe22c4aebf8bd254b7709 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Mon, 9 May 2016 11:58:20 -0300 Subject: [PATCH 035/152] Make model sanitization methods one liners --- CHANGELOG | 1 + app/models/label.rb | 5 ++--- app/models/milestone.rb | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 925740e5d9..fe56dd97ee 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.8.0 (unreleased) - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project - Updated search UI - Display informative message when new milestone is created + - Sanitize milestones and labels titles - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea) - Added button to toggle whitespaces changes on diff view - Backport GitLab Enterprise support from EE diff --git a/app/models/label.rb b/app/models/label.rb index 0b34911a4e..c551ceef64 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -117,9 +117,8 @@ class Label < ActiveRecord::Base LabelsHelper::text_color_for_bg(self.color) end - def title= value - value = Sanitize.clean(value.to_s) if value - write_attribute(:title, Sanitize.clean(value)) + def title=(value) + write_attribute(:title, Sanitize.clean(value.to_s)) if value.present? end private diff --git a/app/models/milestone.rb b/app/models/milestone.rb index ed81791c69..15a786210f 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -129,9 +129,8 @@ class Milestone < ActiveRecord::Base nil end - def title= value - value = Sanitize.clean(value.to_s) if value - write_attribute(:title, value) + def title=(value) + write_attribute(:title, Sanitize.clean(value.to_s)) if value.present? end # Sorts the issues for the given IDs. From 63c45fa75b2fb6519106417d58fe879db352b812 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 9 May 2016 15:21:23 +0000 Subject: [PATCH 036/152] Revert "Merge branch 'patch-1' into 'master'" This reverts merge request !4030 --- doc/api/notes.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/api/notes.md b/doc/api/notes.md index a6b5b1787f..7aa1c2155b 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -15,7 +15,7 @@ GET /projects/:id/issues/:issue_id/notes Parameters: - `id` (required) - The ID of a project -- `issue_id` (required) - The IID of an issue (not ID) +- `issue_id` (required) - The ID of an issue ```json [ @@ -73,7 +73,7 @@ GET /projects/:id/issues/:issue_id/notes/:note_id Parameters: - `id` (required) - The ID of a project -- `issue_id` (required) - The IID of a project issue (not ID) +- `issue_id` (required) - The ID of a project issue - `note_id` (required) - The ID of an issue note ### Create new issue note @@ -87,7 +87,7 @@ POST /projects/:id/issues/:issue_id/notes Parameters: - `id` (required) - The ID of a project -- `issue_id` (required) - The IID of an issue (not ID) +- `issue_id` (required) - The ID of an issue - `body` (required) - The content of a note - `created_at` (optional) - Date time string, ISO 8601 formatted, e.g. 2016-03-11T03:45:40Z @@ -102,7 +102,7 @@ PUT /projects/:id/issues/:issue_id/notes/:note_id Parameters: - `id` (required) - The ID of a project -- `issue_id` (required) - The IID of an issue (not ID) +- `issue_id` (required) - The ID of an issue - `note_id` (required) - The ID of a note - `body` (required) - The content of a note @@ -120,7 +120,7 @@ Parameters: | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `id` | integer | yes | The ID of a project | -| `issue_id` | integer | yes | The IID of an issue | +| `issue_id` | integer | yes | The ID of an issue | | `note_id` | integer | yes | The ID of a note | ```bash From f1479b56b71bef63517b90fcde99e6508e9c54af Mon Sep 17 00:00:00 2001 From: Jeroen van Baarsen Date: Mon, 9 May 2016 18:00:28 +0200 Subject: [PATCH 037/152] Remove the annotate gem and delete old annotations In 8278b763d96ef10c6494409b18b7eb541463af29 the default behaviour of annotation has changes, which was causing a lot of noise in diffs. We decided in #17382 that it is better to get rid of the whole annotate gem, and instead let people look at schema.rb for the columns in a table. Fixes: #17382 --- Gemfile | 1 - Gemfile.lock | 6 +- app/models/abuse_report.rb | 12 ---- app/models/appearance.rb | 13 ---- app/models/application_setting.rb | 52 --------------- app/models/audit_event.rb | 14 ---- app/models/broadcast_message.rb | 14 ---- app/models/ci/build.rb | 37 ----------- app/models/ci/commit.rb | 22 ------- app/models/ci/runner.rb | 19 ------ app/models/ci/runner_project.rb | 12 ---- app/models/ci/trigger.rb | 13 ---- app/models/ci/trigger_request.rb | 12 ---- app/models/ci/variable.rb | 14 ---- app/models/commit_status.rb | 37 ----------- app/models/deploy_key.rb | 15 ----- app/models/deploy_keys_project.rb | 11 ---- app/models/email.rb | 11 ---- app/models/event.rb | 16 ----- app/models/forked_project_link.rb | 11 ---- app/models/generic_commit_status.rb | 37 ----------- app/models/group.rb | 17 ----- app/models/hooks/project_hook.rb | 22 ------- app/models/hooks/service_hook.rb | 22 ------- app/models/hooks/system_hook.rb | 22 ------- app/models/hooks/web_hook.rb | 22 ------- app/models/identity.rb | 12 ---- app/models/issue.rb | 24 ------- app/models/key.rb | 15 ----- app/models/label.rb | 14 ---- app/models/label_link.rb | 12 ---- app/models/lfs_object.rb | 12 ---- app/models/lfs_objects_project.rb | 11 ---- app/models/member.rb | 19 ------ app/models/members/group_member.rb | 19 ------ app/models/members/project_member.rb | 19 ------ app/models/merge_request.rb | 30 --------- app/models/merge_request_diff.rb | 15 ----- app/models/milestone.rb | 15 ----- app/models/namespace.rb | 17 ----- app/models/note.rb | 21 ------ app/models/notification_setting.rb | 13 ---- app/models/oauth_access_token.rb | 15 ----- app/models/personal_snippet.rb | 16 ----- app/models/project.rb | 45 ------------- app/models/project_group_link.rb | 12 ---- app/models/project_import_data.rb | 12 ---- app/models/project_services/asana_service.rb | 24 ------- .../project_services/assembla_service.rb | 24 ------- app/models/project_services/bamboo_service.rb | 24 ------- .../project_services/buildkite_service.rb | 24 ------- .../project_services/builds_email_service.rb | 24 ------- .../project_services/campfire_service.rb | 24 ------- app/models/project_services/ci_service.rb | 24 ------- .../custom_issue_tracker_service.rb | 24 ------- .../project_services/drone_ci_service.rb | 24 ------- .../emails_on_push_service.rb | 24 ------- .../project_services/external_wiki_service.rb | 24 ------- .../project_services/flowdock_service.rb | 24 ------- .../project_services/gemnasium_service.rb | 24 ------- .../project_services/gitlab_ci_service.rb | 24 ------- .../gitlab_issue_tracker_service.rb | 24 ------- .../project_services/hipchat_service.rb | 24 ------- app/models/project_services/irker_service.rb | 24 ------- .../project_services/issue_tracker_service.rb | 24 ------- app/models/project_services/jira_service.rb | 24 ------- .../pivotaltracker_service.rb | 24 ------- .../project_services/pushover_service.rb | 24 ------- .../project_services/redmine_service.rb | 24 ------- app/models/project_services/slack_service.rb | 24 ------- .../project_services/teamcity_service.rb | 24 ------- app/models/project_snippet.rb | 16 ----- app/models/protected_branch.rb | 12 ---- app/models/release.rb | 12 ---- app/models/security_event.rb | 14 ---- app/models/sent_notification.rb | 14 ---- app/models/service.rb | 24 ------- app/models/snippet.rb | 16 ----- app/models/spam_log.rb | 17 ----- app/models/subscription.rb | 13 ---- app/models/todo.rb | 18 ----- app/models/user.rb | 65 ------------------- app/models/users_star_project.rb | 11 ---- spec/factories/abuse_reports.rb | 12 ---- spec/factories/broadcast_messages.rb | 14 ---- spec/factories/forked_project_links.rb | 11 ---- spec/factories/label_links.rb | 12 ---- spec/factories/labels.rb | 13 ---- spec/factories/lfs_objects.rb | 12 ---- spec/factories/lfs_objects_projects.rb | 11 ---- spec/factories/merge_requests.rb | 29 --------- spec/factories/notes.rb | 21 ------ spec/factories/oauth_access_tokens.rb | 15 ----- spec/factories/projects.rb | 39 ----------- spec/factories/releases.rb | 12 ---- spec/factories/todos.rb | 18 ----- spec/models/abuse_report_spec.rb | 12 ---- spec/models/application_setting_spec.rb | 46 ------------- spec/models/broadcast_message_spec.rb | 14 ---- spec/models/ci/commit_spec.rb | 18 ----- spec/models/ci/runner_project_spec.rb | 12 ---- spec/models/ci/runner_spec.rb | 19 ------ spec/models/ci/trigger_spec.rb | 13 ---- spec/models/ci/variable_spec.rb | 14 ---- spec/models/commit_status_spec.rb | 34 ---------- spec/models/deploy_key_spec.rb | 15 ----- spec/models/deploy_keys_project_spec.rb | 11 ---- spec/models/email_spec.rb | 11 ---- spec/models/event_spec.rb | 16 ----- spec/models/forked_project_link_spec.rb | 11 ---- spec/models/generic_commit_status_spec.rb | 34 ---------- spec/models/group_spec.rb | 15 ----- spec/models/identity_spec.rb | 12 ---- spec/models/issue_spec.rb | 20 ------ spec/models/key_spec.rb | 15 ----- spec/models/label_link_spec.rb | 12 ---- spec/models/label_spec.rb | 13 ---- spec/models/member_spec.rb | 19 ------ spec/models/merge_request_spec.rb | 29 --------- spec/models/milestone_spec.rb | 15 ----- spec/models/namespace_spec.rb | 15 ----- spec/models/note_spec.rb | 21 ------ spec/models/project_snippet_spec.rb | 16 ----- spec/models/project_spec.rb | 39 ----------- spec/models/protected_branch_spec.rb | 12 ---- spec/models/release_spec.rb | 12 ---- spec/models/service_spec.rb | 21 ------ spec/models/snippet_spec.rb | 16 ----- spec/models/todo_spec.rb | 18 ----- spec/models/user_spec.rb | 63 ------------------ 130 files changed, 1 insertion(+), 2541 deletions(-) diff --git a/Gemfile b/Gemfile index 1137ef4d72..3e5c604ae0 100644 --- a/Gemfile +++ b/Gemfile @@ -242,7 +242,6 @@ group :development do gem "foreman" gem 'brakeman', '~> 3.2.0', require: false - gem "annotate", "~> 2.7.0" gem 'letter_opener_web', '~> 1.3.0' gem 'quiet_assets', '~> 1.0.2' gem 'rerun', '~> 0.11.0' diff --git a/Gemfile.lock b/Gemfile.lock index fe083c2b56..86b9142ef2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -51,9 +51,6 @@ GEM activerecord (>= 3.0) akismet (2.0.0) allocations (1.0.4) - annotate (2.7.0) - activerecord (>= 3.2, < 6.0) - rake (~> 10.4) arel (6.0.3) asana (0.4.0) faraday (~> 0.9) @@ -893,7 +890,6 @@ DEPENDENCIES after_commit_queue akismet (~> 2.0) allocations (~> 1.0) - annotate (~> 2.7.0) asana (~> 0.4.0) asciidoctor (~> 1.5.2) attr_encrypted (~> 1.3.4) @@ -1061,4 +1057,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.12.1 + 1.12.3 diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb index b61f512312..b01a244032 100644 --- a/app/models/abuse_report.rb +++ b/app/models/abuse_report.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: abuse_reports -# -# id :integer not null, primary key -# reporter_id :integer -# user_id :integer -# message :text -# created_at :datetime -# updated_at :datetime -# - class AbuseReport < ActiveRecord::Base belongs_to :reporter, class_name: 'User' belongs_to :user diff --git a/app/models/appearance.rb b/app/models/appearance.rb index 4528760fef..4cf8dd9a8c 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -1,16 +1,3 @@ -# == Schema Information -# -# Table name: appearances -# -# id :integer not null, primary key -# title :string -# description :text -# header_logo :string -# logo :string -# created_at :datetime not null -# updated_at :datetime not null -# - class Appearance < ActiveRecord::Base validates :title, presence: true validates :description, presence: true diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 72ec91d290..7039db2d41 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -1,55 +1,3 @@ -# == Schema Information -# -# Table name: application_settings -# -# id :integer not null, primary key -# default_projects_limit :integer -# signup_enabled :boolean -# signin_enabled :boolean -# gravatar_enabled :boolean -# sign_in_text :text -# created_at :datetime -# updated_at :datetime -# home_page_url :string -# default_branch_protection :integer default(2) -# restricted_visibility_levels :text -# version_check_enabled :boolean default(TRUE) -# max_attachment_size :integer default(10), not null -# default_project_visibility :integer -# default_snippet_visibility :integer -# restricted_signup_domains :text -# user_oauth_applications :boolean default(TRUE) -# after_sign_out_path :string -# session_expire_delay :integer default(10080), not null -# import_sources :text -# help_page_text :text -# admin_notification_email :string -# shared_runners_enabled :boolean default(TRUE), not null -# max_artifacts_size :integer default(100), not null -# runners_registration_token :string -# require_two_factor_authentication :boolean default(FALSE) -# two_factor_grace_period :integer default(48) -# metrics_enabled :boolean default(FALSE) -# metrics_host :string default("localhost") -# metrics_pool_size :integer default(16) -# metrics_timeout :integer default(10) -# metrics_method_call_threshold :integer default(10) -# recaptcha_enabled :boolean default(FALSE) -# recaptcha_site_key :string -# recaptcha_private_key :string -# metrics_port :integer default(8089) -# metrics_sample_interval :integer default(15) -# sentry_enabled :boolean default(FALSE) -# sentry_dsn :string -# akismet_enabled :boolean default(FALSE) -# akismet_api_key :string -# email_author_in_body :boolean default(FALSE) -# default_group_visibility :integer -# repository_checks_enabled :boolean default(FALSE) -# metrics_packet_size :integer default(1) -# shared_runners_text :text -# - class ApplicationSetting < ActiveRecord::Base include TokenAuthenticatable add_authentication_token_field :runners_registration_token diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb index 44b090260e..967ffd46db 100644 --- a/app/models/audit_event.rb +++ b/app/models/audit_event.rb @@ -1,17 +1,3 @@ -# == Schema Information -# -# Table name: audit_events -# -# id :integer not null, primary key -# author_id :integer not null -# type :string not null -# entity_id :integer not null -# entity_type :string not null -# details :text -# created_at :datetime -# updated_at :datetime -# - class AuditEvent < ActiveRecord::Base serialize :details, Hash diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index 075ac733bf..61498140f2 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -1,17 +1,3 @@ -# == Schema Information -# -# Table name: broadcast_messages -# -# id :integer not null, primary key -# message :text not null -# starts_at :datetime -# ends_at :datetime -# created_at :datetime -# updated_at :datetime -# color :string -# font :string -# - class BroadcastMessage < ActiveRecord::Base include Sortable diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 4bc3a225e2..c9506dd305 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -1,40 +1,3 @@ -# == Schema Information -# -# Table name: ci_builds -# -# id :integer not null, primary key -# project_id :integer -# status :string -# 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 -# deploy :boolean default(FALSE) -# options :text -# allow_failure :boolean default(FALSE), not null -# stage :string -# trigger_request_id :integer -# stage_idx :integer -# tag :boolean -# ref :string -# user_id :integer -# type :string -# target_url :string -# description :string -# artifacts_file :text -# gl_project_id :integer -# artifacts_metadata :text -# erased_by_id :integer -# erased_at :datetime -# - module Ci class Build < CommitStatus belongs_to :runner, class_name: 'Ci::Runner' diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 4ac4e0fb8b..f4b61c75ab 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -1,25 +1,3 @@ -# == Schema Information -# -# Table name: ci_commits -# -# id :integer not null, primary key -# project_id :integer -# ref :string -# sha :string -# before_sha :string -# push_data :text -# created_at :datetime -# updated_at :datetime -# tag :boolean default(FALSE) -# yaml_errors :text -# committed_at :datetime -# gl_project_id :integer -# status :string -# started_at :datetime -# finished_at :datetime -# duration :integer -# - module Ci class Commit < ActiveRecord::Base extend Ci::Model diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index add59a0889..0cdb64d6c3 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -1,22 +1,3 @@ -# == Schema Information -# -# Table name: ci_runners -# -# id :integer not null, primary key -# token :string -# created_at :datetime -# updated_at :datetime -# description :string -# contacted_at :datetime -# active :boolean default(TRUE), not null -# is_shared :boolean default(FALSE) -# name :string -# version :string -# revision :string -# platform :string -# architecture :string -# - module Ci class Runner < ActiveRecord::Base extend Ci::Model diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb index 7b16f207a2..4b44ffa886 100644 --- a/app/models/ci/runner_project.rb +++ b/app/models/ci/runner_project.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: ci_runner_projects -# -# id :integer not null, primary key -# runner_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# gl_project_id :integer -# - module Ci class RunnerProject < ActiveRecord::Base extend Ci::Model diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index 4f3f4d79fa..a0b19b51a1 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -1,16 +1,3 @@ -# == Schema Information -# -# Table name: ci_triggers -# -# id :integer not null, primary key -# token :string -# project_id :integer -# deleted_at :datetime -# created_at :datetime -# updated_at :datetime -# gl_project_id :integer -# - module Ci class Trigger < ActiveRecord::Base extend Ci::Model diff --git a/app/models/ci/trigger_request.rb b/app/models/ci/trigger_request.rb index 9973d2e5ad..872d5fb31d 100644 --- a/app/models/ci/trigger_request.rb +++ b/app/models/ci/trigger_request.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: ci_trigger_requests -# -# id :integer not null, primary key -# trigger_id :integer not null -# variables :text -# created_at :datetime -# updated_at :datetime -# commit_id :integer -# - module Ci class TriggerRequest < ActiveRecord::Base extend Ci::Model diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb index 4229fe085a..10802f6481 100644 --- a/app/models/ci/variable.rb +++ b/app/models/ci/variable.rb @@ -1,17 +1,3 @@ -# == Schema Information -# -# Table name: ci_variables -# -# id :integer not null, primary key -# project_id :integer -# key :string -# value :text -# encrypted_value :text -# encrypted_value_salt :string -# encrypted_value_iv :string -# gl_project_id :integer -# - module Ci class Variable < ActiveRecord::Base extend Ci::Model diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 1260c448de..cacbc13b39 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -1,40 +1,3 @@ -# == Schema Information -# -# Table name: ci_builds -# -# id :integer not null, primary key -# project_id :integer -# status :string -# 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 -# deploy :boolean default(FALSE) -# options :text -# allow_failure :boolean default(FALSE), not null -# stage :string -# trigger_request_id :integer -# stage_idx :integer -# tag :boolean -# ref :string -# user_id :integer -# type :string -# target_url :string -# description :string -# artifacts_file :text -# gl_project_id :integer -# artifacts_metadata :text -# erased_by_id :integer -# erased_at :datetime -# - class CommitStatus < ActiveRecord::Base include Statuseable diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index 43cf625f77..2c525d4cd7 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -1,18 +1,3 @@ -# == Schema Information -# -# Table name: keys -# -# id :integer not null, primary key -# user_id :integer -# created_at :datetime -# updated_at :datetime -# key :text -# title :string -# type :string -# fingerprint :string -# public :boolean default(FALSE), not null -# - class DeployKey < Key has_many :deploy_keys_projects, dependent: :destroy has_many :projects, through: :deploy_keys_projects diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb index 18db521741..ae8486bd9a 100644 --- a/app/models/deploy_keys_project.rb +++ b/app/models/deploy_keys_project.rb @@ -1,14 +1,3 @@ -# == Schema Information -# -# Table name: deploy_keys_projects -# -# id :integer not null, primary key -# deploy_key_id :integer not null -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# - class DeployKeysProject < ActiveRecord::Base belongs_to :project belongs_to :deploy_key diff --git a/app/models/email.rb b/app/models/email.rb index eae2472f33..32a412ab87 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -1,14 +1,3 @@ -# == Schema Information -# -# Table name: emails -# -# id :integer not null, primary key -# user_id :integer not null -# email :string not null -# created_at :datetime -# updated_at :datetime -# - class Email < ActiveRecord::Base include Sortable diff --git a/app/models/event.rb b/app/models/event.rb index 25c7c3e6dc..17ee48b91a 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -1,19 +1,3 @@ -# == Schema Information -# -# Table name: events -# -# id :integer not null, primary key -# target_type :string -# target_id :integer -# title :string -# data :text -# project_id :integer -# created_at :datetime -# updated_at :datetime -# action :integer -# author_id :integer -# - class Event < ActiveRecord::Base include Sortable default_scope { where.not(author_id: nil) } diff --git a/app/models/forked_project_link.rb b/app/models/forked_project_link.rb index 9b0c6263a9..9803bae0be 100644 --- a/app/models/forked_project_link.rb +++ b/app/models/forked_project_link.rb @@ -1,14 +1,3 @@ -# == Schema Information -# -# Table name: forked_project_links -# -# id :integer not null, primary key -# forked_to_project_id :integer not null -# forked_from_project_id :integer not null -# created_at :datetime -# updated_at :datetime -# - class ForkedProjectLink < ActiveRecord::Base belongs_to :forked_to_project, class_name: Project belongs_to :forked_from_project, class_name: Project diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb index d4afd8cbe8..fa54e3540d 100644 --- a/app/models/generic_commit_status.rb +++ b/app/models/generic_commit_status.rb @@ -1,40 +1,3 @@ -# == Schema Information -# -# Table name: ci_builds -# -# id :integer not null, primary key -# project_id :integer -# status :string -# 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 -# deploy :boolean default(FALSE) -# options :text -# allow_failure :boolean default(FALSE), not null -# stage :string -# trigger_request_id :integer -# stage_idx :integer -# tag :boolean -# ref :string -# user_id :integer -# type :string -# target_url :string -# description :string -# artifacts_file :text -# gl_project_id :integer -# artifacts_metadata :text -# erased_by_id :integer -# erased_at :datetime -# - class GenericCommitStatus < CommitStatus before_validation :set_default_values diff --git a/app/models/group.rb b/app/models/group.rb index cff7687795..aec92e335e 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -1,20 +1,3 @@ -# == Schema Information -# -# Table name: namespaces -# -# id :integer not null, primary key -# name :string not null -# path :string not null -# owner_id :integer -# created_at :datetime -# updated_at :datetime -# type :string -# description :string default(""), not null -# avatar :string -# share_with_group_lock :boolean default(FALSE) -# visibility_level :integer default(20), not null -# - require 'carrierwave/orm/activerecord' class Group < Namespace diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb index 2b8f34a056..ba42a8eeb7 100644 --- a/app/models/hooks/project_hook.rb +++ b/app/models/hooks/project_hook.rb @@ -1,25 +1,3 @@ -# == Schema Information -# -# Table name: web_hooks -# -# id :integer not null, primary key -# url :string(2000) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string 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) -# build_events :boolean default(FALSE), not null -# wiki_page_events :boolean default(FALSE), not null -# token :string -# - class ProjectHook < WebHook belongs_to :project diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb index 0e176de5ef..eef24052a0 100644 --- a/app/models/hooks/service_hook.rb +++ b/app/models/hooks/service_hook.rb @@ -1,25 +1,3 @@ -# == Schema Information -# -# Table name: web_hooks -# -# id :integer not null, primary key -# url :string(2000) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string 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) -# build_events :boolean default(FALSE), not null -# wiki_page_events :boolean default(FALSE), not null -# token :string -# - class ServiceHook < WebHook belongs_to :service diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb index ad508cbbcb..777bad1e72 100644 --- a/app/models/hooks/system_hook.rb +++ b/app/models/hooks/system_hook.rb @@ -1,25 +1,3 @@ -# == Schema Information -# -# Table name: web_hooks -# -# id :integer not null, primary key -# url :string(2000) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string 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) -# build_events :boolean default(FALSE), not null -# wiki_page_events :boolean default(FALSE), not null -# token :string -# - class SystemHook < WebHook def async_execute(data, hook_name) Sidekiq::Client.enqueue(SystemHookWorker, id, data, hook_name) diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 8e58c9583a..fde05f729d 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -1,25 +1,3 @@ -# == Schema Information -# -# Table name: web_hooks -# -# id :integer not null, primary key -# url :string(2000) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string 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) -# build_events :boolean default(FALSE), not null -# wiki_page_events :boolean default(FALSE), not null -# token :string -# - class WebHook < ActiveRecord::Base include Sortable include HTTParty diff --git a/app/models/identity.rb b/app/models/identity.rb index ef4d5f9909..3bacc450e6 100644 --- a/app/models/identity.rb +++ b/app/models/identity.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: identities -# -# id :integer not null, primary key -# extern_uid :string -# provider :string -# user_id :integer -# created_at :datetime -# updated_at :datetime -# - class Identity < ActiveRecord::Base include Sortable include CaseSensitivity diff --git a/app/models/issue.rb b/app/models/issue.rb index abaa509707..2d4a9b9f19 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: issues -# -# id :integer not null, primary key -# title :string -# assignee_id :integer -# author_id :integer -# project_id :integer -# created_at :datetime -# updated_at :datetime -# position :integer default(0) -# branch_name :string -# description :text -# milestone_id :integer -# state :string -# iid :integer -# updated_by_id :integer -# moved_to_id :integer -# confidential :boolean default(FALSE) -# deleted_at :datetime -# due_date :date -# - require 'carrierwave/orm/activerecord' class Issue < ActiveRecord::Base diff --git a/app/models/key.rb b/app/models/key.rb index b2b57849f8..d52afda67d 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -1,18 +1,3 @@ -# == Schema Information -# -# Table name: keys -# -# id :integer not null, primary key -# user_id :integer -# created_at :datetime -# updated_at :datetime -# key :text -# title :string -# type :string -# fingerprint :string -# public :boolean default(FALSE), not null -# - require 'digest/md5' class Key < ActiveRecord::Base diff --git a/app/models/label.rb b/app/models/label.rb index 9a22398d95..83375db88c 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -1,17 +1,3 @@ -# == Schema Information -# -# Table name: labels -# -# id :integer not null, primary key -# title :string -# color :string -# project_id :integer -# created_at :datetime -# updated_at :datetime -# template :boolean default(FALSE) -# description :string -# - class Label < ActiveRecord::Base include Referable include Subscribable diff --git a/app/models/label_link.rb b/app/models/label_link.rb index 7b8e872b6d..47bd6eaf35 100644 --- a/app/models/label_link.rb +++ b/app/models/label_link.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: label_links -# -# id :integer not null, primary key -# label_id :integer -# target_id :integer -# target_type :string -# created_at :datetime -# updated_at :datetime -# - class LabelLink < ActiveRecord::Base belongs_to :target, polymorphic: true belongs_to :label diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index 927e764af9..18657c3e1c 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: lfs_objects -# -# id :integer not null, primary key -# oid :string not null -# size :integer not null -# created_at :datetime -# updated_at :datetime -# file :string -# - class LfsObject < ActiveRecord::Base has_many :lfs_objects_projects, dependent: :destroy has_many :projects, through: :lfs_objects_projects diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb index 890736bfc8..0fd5f089db 100644 --- a/app/models/lfs_objects_project.rb +++ b/app/models/lfs_objects_project.rb @@ -1,14 +1,3 @@ -# == Schema Information -# -# Table name: lfs_objects_projects -# -# id :integer not null, primary key -# lfs_object_id :integer not null -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# - class LfsObjectsProject < ActiveRecord::Base belongs_to :project belongs_to :lfs_object diff --git a/app/models/member.rb b/app/models/member.rb index cca82da89f..d3060f07fc 100644 --- a/app/models/member.rb +++ b/app/models/member.rb @@ -1,22 +1,3 @@ -# == Schema Information -# -# Table name: members -# -# id :integer not null, primary key -# access_level :integer not null -# source_id :integer not null -# source_type :string not null -# user_id :integer -# notification_level :integer not null -# type :string -# created_at :datetime -# updated_at :datetime -# created_by_id :integer -# invite_email :string -# invite_token :string -# invite_accepted_at :datetime -# - class Member < ActiveRecord::Base include Sortable include Gitlab::Access diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb index a48c1943e6..f63a0debf1 100644 --- a/app/models/members/group_member.rb +++ b/app/models/members/group_member.rb @@ -1,22 +1,3 @@ -# == Schema Information -# -# Table name: members -# -# id :integer not null, primary key -# access_level :integer not null -# source_id :integer not null -# source_type :string not null -# user_id :integer -# notification_level :integer not null -# type :string -# created_at :datetime -# updated_at :datetime -# created_by_id :integer -# invite_email :string -# invite_token :string -# invite_accepted_at :datetime -# - class GroupMember < Member SOURCE_TYPE = 'Namespace' diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index 143350a0b5..8dae3bb2ef 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -1,22 +1,3 @@ -# == Schema Information -# -# Table name: members -# -# id :integer not null, primary key -# access_level :integer not null -# source_id :integer not null -# source_type :string not null -# user_id :integer -# notification_level :integer not null -# type :string -# created_at :datetime -# updated_at :datetime -# created_by_id :integer -# invite_email :string -# invite_token :string -# invite_accepted_at :datetime -# - class ProjectMember < Member SOURCE_TYPE = 'Project' diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 4175e1e5fb..5460935366 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1,33 +1,3 @@ -# == Schema Information -# -# Table name: merge_requests -# -# id :integer not null, primary key -# target_branch :string not null -# source_branch :string not null -# source_project_id :integer not null -# author_id :integer -# assignee_id :integer -# title :string -# created_at :datetime -# updated_at :datetime -# milestone_id :integer -# state :string -# merge_status :string -# target_project_id :integer not null -# iid :integer -# description :text -# position :integer default(0) -# locked_at :datetime -# updated_by_id :integer -# merge_error :string -# merge_params :text -# merge_when_build_succeeds :boolean default(FALSE), not null -# merge_user_id :integer -# merge_commit_sha :string -# deleted_at :datetime -# - class MergeRequest < ActiveRecord::Base include InternalId include Issuable diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 8951e92a0b..eb42c07b9b 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -1,18 +1,3 @@ -# == Schema Information -# -# Table name: merge_request_diffs -# -# id :integer not null, primary key -# state :string -# st_commits :text -# st_diffs :text -# merge_request_id :integer not null -# created_at :datetime -# updated_at :datetime -# base_commit_sha :string -# real_size :string -# - class MergeRequestDiff < ActiveRecord::Base include Sortable diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 5ee8a965ad..97705a374d 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -1,18 +1,3 @@ -# == Schema Information -# -# Table name: milestones -# -# id :integer not null, primary key -# title :string not null -# project_id :integer not null -# description :text -# due_date :date -# created_at :datetime -# updated_at :datetime -# state :string -# iid :integer -# - class Milestone < ActiveRecord::Base # Represents a "No Milestone" state used for filtering Issues and Merge # Requests that have no milestone assigned. diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 741e912171..9c942a8f4e 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -1,20 +1,3 @@ -# == Schema Information -# -# Table name: namespaces -# -# id :integer not null, primary key -# name :string not null -# path :string not null -# owner_id :integer -# created_at :datetime -# updated_at :datetime -# type :string -# description :string default(""), not null -# avatar :string -# share_with_group_lock :boolean default(FALSE) -# visibility_level :integer default(20), not null -# - class Namespace < ActiveRecord::Base include Sortable include Gitlab::ShellAdapter diff --git a/app/models/note.rb b/app/models/note.rb index deee2b9e88..f26aa1bf63 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -1,24 +1,3 @@ -# == Schema Information -# -# Table name: notes -# -# id :integer not null, primary key -# note :text -# noteable_type :string -# author_id :integer -# created_at :datetime -# updated_at :datetime -# project_id :integer -# attachment :string -# line_code :string -# commit_id :string -# noteable_id :integer -# system :boolean default(FALSE), not null -# st_diff :text -# updated_by_id :integer -# is_award :boolean default(FALSE), not null -# - require 'carrierwave/orm/activerecord' class Note < ActiveRecord::Base diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb index 846773752a..5001738f41 100644 --- a/app/models/notification_setting.rb +++ b/app/models/notification_setting.rb @@ -1,16 +1,3 @@ -# == Schema Information -# -# Table name: notification_settings -# -# id :integer not null, primary key -# user_id :integer not null -# source_id :integer not null -# source_type :string not null -# level :integer default(0), not null -# created_at :datetime not null -# updated_at :datetime not null -# - class NotificationSetting < ActiveRecord::Base enum level: { disabled: 0, participating: 1, watch: 2, global: 3, mention: 4 } diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb index c78c7f4aa0..116fb71ac0 100644 --- a/app/models/oauth_access_token.rb +++ b/app/models/oauth_access_token.rb @@ -1,18 +1,3 @@ -# == Schema Information -# -# Table name: oauth_access_tokens -# -# id :integer not null, primary key -# resource_owner_id :integer -# application_id :integer -# token :string not null -# refresh_token :string -# expires_in :integer -# revoked_at :datetime -# created_at :datetime not null -# scopes :string -# - class OauthAccessToken < ActiveRecord::Base belongs_to :resource_owner, class_name: 'User' belongs_to :application, class_name: 'Doorkeeper::Application' diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb index 1d5f4c5025..82c1c4de3a 100644 --- a/app/models/personal_snippet.rb +++ b/app/models/personal_snippet.rb @@ -1,18 +1,2 @@ -# == Schema Information -# -# Table name: snippets -# -# id :integer not null, primary key -# title :string -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string -# type :string -# visibility_level :integer default(0), not null -# - class PersonalSnippet < Snippet end diff --git a/app/models/project.rb b/app/models/project.rb index 05e14185a3..418b85e028 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1,48 +1,3 @@ -# == Schema Information -# -# Table name: projects -# -# id :integer not null, primary key -# name :string -# path :string -# description :text -# created_at :datetime -# updated_at :datetime -# creator_id :integer -# issues_enabled :boolean default(TRUE), not null -# merge_requests_enabled :boolean default(TRUE), not null -# wiki_enabled :boolean default(TRUE), not null -# namespace_id :integer -# issues_tracker :string default("gitlab"), not null -# issues_tracker_id :string -# snippets_enabled :boolean default(TRUE), not null -# last_activity_at :datetime -# import_url :string -# visibility_level :integer default(0), not null -# archived :boolean default(FALSE), not null -# avatar :string -# import_status :string -# repository_size :float default(0.0) -# star_count :integer default(0), not null -# import_type :string -# import_source :string -# commit_count :integer default(0) -# import_error :text -# ci_id :integer -# builds_enabled :boolean default(TRUE), not null -# shared_runners_enabled :boolean default(TRUE), not null -# runners_token :string -# build_coverage_regex :string -# build_allow_git_fetch :boolean default(TRUE), not null -# build_timeout :integer default(3600), not null -# pending_delete :boolean default(FALSE) -# public_builds :boolean default(TRUE), not null -# main_language :string -# pushes_since_gc :integer default(0) -# last_repository_check_failed :boolean -# last_repository_check_at :datetime -# - require 'carrierwave/orm/activerecord' class Project < ActiveRecord::Base diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb index 66f5a609bf..e52a6bd7c8 100644 --- a/app/models/project_group_link.rb +++ b/app/models/project_group_link.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: project_group_links -# -# id :integer not null, primary key -# project_id :integer not null -# group_id :integer not null -# created_at :datetime -# updated_at :datetime -# group_access :integer default(30), not null -# - class ProjectGroupLink < ActiveRecord::Base GUEST = 10 REPORTER = 20 diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb index 7830f764ed..e2f9ffb69a 100644 --- a/app/models/project_import_data.rb +++ b/app/models/project_import_data.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: project_import_data -# -# id :integer not null, primary key -# project_id :integer -# data :text -# encrypted_credentials :text -# encrypted_credentials_iv :string -# encrypted_credentials_salt :string -# - require 'carrierwave/orm/activerecord' class ProjectImportData < ActiveRecord::Base diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb index 368485a060..7c23b76676 100644 --- a/app/models/project_services/asana_service.rb +++ b/app/models/project_services/asana_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - require 'asana' class AsanaService < Service diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb index ffb7455b01..d839221d31 100644 --- a/app/models/project_services/assembla_service.rb +++ b/app/models/project_services/assembla_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class AssemblaService < Service include HTTParty diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index c36ee95e37..1d1780dcfb 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class BambooService < CiService include HTTParty diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb index f9f4897a06..86a06321e2 100644 --- a/app/models/project_services/buildkite_service.rb +++ b/app/models/project_services/buildkite_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - require "addressable/uri" class BuildkiteService < CiService diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb index 20cdfcaffb..54da4d74fc 100644 --- a/app/models/project_services/builds_email_service.rb +++ b/app/models/project_services/builds_email_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class BuildsEmailService < Service prop_accessor :recipients boolean_accessor :add_pusher diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index 28c969fe57..511b2eac79 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class CampfireService < Service prop_accessor :token, :subdomain, :room validates :token, presence: true, if: :activated? diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index 9bc8f982da..596c00705a 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - # Base class for CI services # List methods you need to implement to get your CI service # working with GitLab Merge Requests diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb index 4d1319eb6f..6b2b1daa72 100644 --- a/app/models/project_services/custom_issue_tracker_service.rb +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class CustomIssueTrackerService < IssueTrackerService prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb index d8e00e018c..966dbc41d7 100644 --- a/app/models/project_services/drone_ci_service.rb +++ b/app/models/project_services/drone_ci_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class DroneCiService < CiService prop_accessor :drone_url, :token, :enable_ssl_verification diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index 2dbd29062d..e0083c43ad 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class EmailsOnPushService < Service prop_accessor :send_from_committer_email prop_accessor :disable_diffs diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb index 5469049bb5..f042bfc521 100644 --- a/app/models/project_services/external_wiki_service.rb +++ b/app/models/project_services/external_wiki_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class ExternalWikiService < Service include HTTParty diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 3dc1e0fbe8..dd00275187 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - require "flowdock-git-hook" class FlowdockService < Service diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index b4c311cf66..598aca5e06 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - require "gemnasium/gitlab_service" class GemnasiumService < Service diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index a92f722608..bbc312f521 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - # TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed class GitlabCiService < CiService # We override the active accessor to always make GitLabCiService disabled diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb index 1adaeeb3b2..5d17c35833 100644 --- a/app/models/project_services/gitlab_issue_tracker_service.rb +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class GitlabIssueTrackerService < IssueTrackerService include Gitlab::Routing.url_helpers diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index f9ddf58872..0ff4f4c8dd 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class HipchatService < Service MAX_COMMITS = 3 diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb index b9a592d709..91015e6c9b 100644 --- a/app/models/project_services/irker_service.rb +++ b/app/models/project_services/irker_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - require 'uri' class IrkerService < Service diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 98a3a7c6b8..6ae9b16d3c 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class IssueTrackerService < Service validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated? diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index ba68658f0b..beda89d396 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class JiraService < IssueTrackerService include HTTParty include Gitlab::Routing.url_helpers diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb index acaa0c3936..ad19b7795d 100644 --- a/app/models/project_services/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class PivotaltrackerService < Service include HTTParty diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb index a640c8cb44..3dd878e4c7 100644 --- a/app/models/project_services/pushover_service.rb +++ b/app/models/project_services/pushover_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class PushoverService < Service include HTTParty base_uri 'https://api.pushover.net/1' diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb index e2137e92c6..11cce3e056 100644 --- a/app/models/project_services/redmine_service.rb +++ b/app/models/project_services/redmine_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class RedmineService < IssueTrackerService prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 83ffa53a40..cf9e4d5a8b 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class SlackService < Service prop_accessor :webhook, :username, :channel boolean_accessor :notify_only_broken_builds diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index 4015da3150..b0dcb52eba 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - class TeamcityService < CiService include HTTParty diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb index b4b2807eba..5fba6baa20 100644 --- a/app/models/project_snippet.rb +++ b/app/models/project_snippet.rb @@ -1,19 +1,3 @@ -# == Schema Information -# -# Table name: snippets -# -# id :integer not null, primary key -# title :string -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string -# type :string -# visibility_level :integer default(0), not null -# - class ProjectSnippet < Snippet belongs_to :project belongs_to :author, class_name: "User" diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 3d2052c892..33cf046fa7 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: protected_branches -# -# id :integer not null, primary key -# project_id :integer not null -# name :string not null -# created_at :datetime -# updated_at :datetime -# developers_can_push :boolean default(FALSE), not null -# - class ProtectedBranch < ActiveRecord::Base include Gitlab::ShellAdapter diff --git a/app/models/release.rb b/app/models/release.rb index dc700d1ea5..e196b84eb1 100644 --- a/app/models/release.rb +++ b/app/models/release.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: releases -# -# id :integer not null, primary key -# tag :string -# description :text -# project_id :integer -# created_at :datetime -# updated_at :datetime -# - class Release < ActiveRecord::Base belongs_to :project diff --git a/app/models/security_event.rb b/app/models/security_event.rb index 0bee03974f..d131c11cb6 100644 --- a/app/models/security_event.rb +++ b/app/models/security_event.rb @@ -1,16 +1,2 @@ -# == Schema Information -# -# Table name: audit_events -# -# id :integer not null, primary key -# author_id :integer not null -# type :string not null -# entity_id :integer not null -# entity_type :string not null -# details :text -# created_at :datetime -# updated_at :datetime -# - class SecurityEvent < AuditEvent end diff --git a/app/models/sent_notification.rb b/app/models/sent_notification.rb index 99279a2e08..375f195dba 100644 --- a/app/models/sent_notification.rb +++ b/app/models/sent_notification.rb @@ -1,17 +1,3 @@ -# == Schema Information -# -# Table name: sent_notifications -# -# id :integer not null, primary key -# project_id :integer -# noteable_id :integer -# noteable_type :string -# recipient_id :integer -# commit_id :string -# reply_key :string not null -# line_code :string -# - class SentNotification < ActiveRecord::Base belongs_to :project belongs_to :noteable, polymorphic: true diff --git a/app/models/service.rb b/app/models/service.rb index bf16a54530..de3fd24584 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -1,27 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string -# title :string -# project_id :integer -# created_at :datetime not null -# updated_at :datetime not null -# active :boolean not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# category :string default("common"), not null -# default :boolean default(FALSE) -# wiki_page_events :boolean default(TRUE) -# - # To add new service you should build a class inherited from Service # and implement a set of methods class Service < ActiveRecord::Base diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 2f905a9094..0a3c3b5766 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -1,19 +1,3 @@ -# == Schema Information -# -# Table name: snippets -# -# id :integer not null, primary key -# title :string -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string -# type :string -# visibility_level :integer default(0), not null -# - class Snippet < ActiveRecord::Base include Gitlab::VisibilityLevel include Linguist::BlobHelper diff --git a/app/models/spam_log.rb b/app/models/spam_log.rb index f49eb7d88e..12df68ef83 100644 --- a/app/models/spam_log.rb +++ b/app/models/spam_log.rb @@ -1,20 +1,3 @@ -# == Schema Information -# -# Table name: spam_logs -# -# id :integer not null, primary key -# user_id :integer -# source_ip :string -# user_agent :string -# via_api :boolean -# project_id :integer -# noteable_type :string -# title :string -# description :text -# created_at :datetime not null -# updated_at :datetime not null -# - class SpamLog < ActiveRecord::Base belongs_to :user diff --git a/app/models/subscription.rb b/app/models/subscription.rb index 242faa7d32..3b8aa1eb86 100644 --- a/app/models/subscription.rb +++ b/app/models/subscription.rb @@ -1,16 +1,3 @@ -# == Schema Information -# -# Table name: subscriptions -# -# id :integer not null, primary key -# user_id :integer -# subscribable_id :integer -# subscribable_type :string -# subscribed :boolean -# created_at :datetime -# updated_at :datetime -# - class Subscription < ActiveRecord::Base belongs_to :user belongs_to :subscribable, polymorphic: true diff --git a/app/models/todo.rb b/app/models/todo.rb index d85f7bfdf5..f8b59fe412 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -1,21 +1,3 @@ -# == Schema Information -# -# Table name: todos -# -# id :integer not null, primary key -# user_id :integer not null -# project_id :integer not null -# target_id :integer -# target_type :string not null -# author_id :integer -# action :integer not null -# state :string not null -# created_at :datetime -# updated_at :datetime -# note_id :integer -# commit_id :string -# - class Todo < ActiveRecord::Base ASSIGNED = 1 MENTIONED = 2 diff --git a/app/models/user.rb b/app/models/user.rb index 56d6de610f..1e4814641d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,68 +1,3 @@ -# == Schema Information -# -# Table name: users -# -# id :integer not null, primary key -# email :string default(""), not null -# encrypted_password :string default(""), not null -# reset_password_token :string -# reset_password_sent_at :datetime -# remember_created_at :datetime -# sign_in_count :integer default(0) -# current_sign_in_at :datetime -# last_sign_in_at :datetime -# current_sign_in_ip :string -# last_sign_in_ip :string -# created_at :datetime -# updated_at :datetime -# name :string -# admin :boolean default(FALSE), not null -# projects_limit :integer default(10) -# skype :string default(""), not null -# linkedin :string default(""), not null -# twitter :string default(""), not null -# authentication_token :string -# theme_id :integer default(1), not null -# bio :string -# failed_attempts :integer default(0) -# locked_at :datetime -# username :string -# can_create_group :boolean default(TRUE), not null -# can_create_team :boolean default(TRUE), not null -# state :string -# color_scheme_id :integer default(1), not null -# notification_level :integer default(1), not null -# password_expires_at :datetime -# created_by_id :integer -# last_credential_check_at :datetime -# avatar :string -# confirmation_token :string -# confirmed_at :datetime -# confirmation_sent_at :datetime -# unconfirmed_email :string -# hide_no_ssh_key :boolean default(FALSE) -# website_url :string default(""), not null -# notification_email :string -# hide_no_password :boolean default(FALSE) -# password_automatically_set :boolean default(FALSE) -# location :string -# encrypted_otp_secret :string -# encrypted_otp_secret_iv :string -# encrypted_otp_secret_salt :string -# otp_required_for_login :boolean default(FALSE), not null -# otp_backup_codes :text -# public_email :string default(""), not null -# dashboard :integer default(0) -# project_view :integer default(0) -# consumed_timestep :integer -# layout :integer default(0) -# hide_project_limit :boolean default(FALSE) -# unlock_token :string -# otp_grace_period_started_at :datetime -# ldap_email :boolean default(FALSE), not null -# external :boolean default(FALSE) -# - require 'carrierwave/orm/activerecord' class User < ActiveRecord::Base diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb index 413f3f485a..0dfe597317 100644 --- a/app/models/users_star_project.rb +++ b/app/models/users_star_project.rb @@ -1,14 +1,3 @@ -# == Schema Information -# -# Table name: users_star_projects -# -# id :integer not null, primary key -# project_id :integer not null -# user_id :integer not null -# created_at :datetime -# updated_at :datetime -# - class UsersStarProject < ActiveRecord::Base belongs_to :project, counter_cache: :star_count, touch: true belongs_to :user diff --git a/spec/factories/abuse_reports.rb b/spec/factories/abuse_reports.rb index d0e8c77851..8f6422a782 100644 --- a/spec/factories/abuse_reports.rb +++ b/spec/factories/abuse_reports.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: abuse_reports -# -# id :integer not null, primary key -# reporter_id :integer -# user_id :integer -# message :text -# created_at :datetime -# updated_at :datetime -# - FactoryGirl.define do factory :abuse_report do reporter factory: :user diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb index c80e736655..efe9803b1a 100644 --- a/spec/factories/broadcast_messages.rb +++ b/spec/factories/broadcast_messages.rb @@ -1,17 +1,3 @@ -# == Schema Information -# -# Table name: broadcast_messages -# -# id :integer not null, primary key -# message :text not null -# starts_at :datetime -# ends_at :datetime -# created_at :datetime -# updated_at :datetime -# color :string(255) -# font :string(255) -# - FactoryGirl.define do factory :broadcast_message do message "MyText" diff --git a/spec/factories/forked_project_links.rb b/spec/factories/forked_project_links.rb index 19a54946fe..b16c1272e6 100644 --- a/spec/factories/forked_project_links.rb +++ b/spec/factories/forked_project_links.rb @@ -1,14 +1,3 @@ -# == Schema Information -# -# Table name: forked_project_links -# -# id :integer not null, primary key -# forked_to_project_id :integer not null -# forked_from_project_id :integer not null -# created_at :datetime -# updated_at :datetime -# - FactoryGirl.define do factory :forked_project_link do association :forked_to_project, factory: :project diff --git a/spec/factories/label_links.rb b/spec/factories/label_links.rb index 2939d4307c..3580174e87 100644 --- a/spec/factories/label_links.rb +++ b/spec/factories/label_links.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: label_links -# -# id :integer not null, primary key -# label_id :integer -# target_id :integer -# target_type :string(255) -# created_at :datetime -# updated_at :datetime -# - FactoryGirl.define do factory :label_link do label diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index ea2be8928d..eb48909985 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -1,16 +1,3 @@ -# == Schema Information -# -# Table name: labels -# -# id :integer not null, primary key -# title :string(255) -# color :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# template :boolean default(FALSE) -# - FactoryGirl.define do factory :label do sequence(:title) { |n| "label#{n}" } diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb index 327858ce43..a81645acd2 100644 --- a/spec/factories/lfs_objects.rb +++ b/spec/factories/lfs_objects.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: lfs_objects -# -# id :integer not null, primary key -# oid :string(255) not null -# size :integer not null -# created_at :datetime -# updated_at :datetime -# file :string(255) -# - include ActionDispatch::TestProcess FactoryGirl.define do diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb index 50b45843c9..1ed0355c8e 100644 --- a/spec/factories/lfs_objects_projects.rb +++ b/spec/factories/lfs_objects_projects.rb @@ -1,14 +1,3 @@ -# == Schema Information -# -# Table name: lfs_objects_projects -# -# id :integer not null, primary key -# lfs_object_id :integer not null -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# - FactoryGirl.define do factory :lfs_objects_project do lfs_object diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index e281e2f227..c6a08d78b7 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -1,32 +1,3 @@ -# == Schema Information -# -# Table name: merge_requests -# -# id :integer not null, primary key -# target_branch :string(255) not null -# source_branch :string(255) not null -# source_project_id :integer not null -# author_id :integer -# assignee_id :integer -# title :string(255) -# created_at :datetime -# updated_at :datetime -# milestone_id :integer -# state :string(255) -# merge_status :string(255) -# target_project_id :integer not null -# iid :integer -# description :text -# position :integer default(0) -# locked_at :datetime -# updated_by_id :integer -# merge_error :string(255) -# merge_params :text -# merge_when_build_succeeds :boolean default(FALSE), not null -# merge_user_id :integer -# merge_commit_sha :string -# - FactoryGirl.define do factory :merge_request do title diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index e5dcb15901..840b13196a 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -1,24 +1,3 @@ -# == Schema Information -# -# Table name: notes -# -# id :integer not null, primary key -# note :text -# noteable_type :string(255) -# author_id :integer -# created_at :datetime -# updated_at :datetime -# project_id :integer -# attachment :string(255) -# line_code :string(255) -# commit_id :string(255) -# noteable_id :integer -# system :boolean default(FALSE), not null -# st_diff :text -# updated_by_id :integer -# is_award :boolean default(FALSE), not null -# - require_relative '../support/repo_helpers' include ActionDispatch::TestProcess diff --git a/spec/factories/oauth_access_tokens.rb b/spec/factories/oauth_access_tokens.rb index 7700b15d53..ccf02d0719 100644 --- a/spec/factories/oauth_access_tokens.rb +++ b/spec/factories/oauth_access_tokens.rb @@ -1,18 +1,3 @@ -# == Schema Information -# -# Table name: oauth_access_tokens -# -# id :integer not null, primary key -# resource_owner_id :integer -# application_id :integer -# token :string not null -# refresh_token :string -# expires_in :integer -# revoked_at :datetime -# created_at :datetime not null -# scopes :string -# - FactoryGirl.define do factory :oauth_access_token do resource_owner diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 4d61345696..da8d97c9f8 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -1,42 +1,3 @@ -# == Schema Information -# -# Table name: projects -# -# id :integer not null, primary key -# name :string(255) -# path :string(255) -# description :text -# created_at :datetime -# updated_at :datetime -# creator_id :integer -# issues_enabled :boolean default(TRUE), not null -# merge_requests_enabled :boolean default(TRUE), not null -# wiki_enabled :boolean default(TRUE), not null -# namespace_id :integer -# issues_tracker :string(255) default("gitlab"), not null -# issues_tracker_id :string(255) -# snippets_enabled :boolean default(TRUE), not null -# last_activity_at :datetime -# import_url :string(255) -# visibility_level :integer default(0), not null -# archived :boolean default(FALSE), not null -# avatar :string(255) -# import_status :string(255) -# repository_size :float default(0.0) -# star_count :integer default(0), not null -# import_type :string(255) -# import_source :string(255) -# commit_count :integer default(0) -# import_error :text -# ci_id :integer -# builds_enabled :boolean default(TRUE), not null -# shared_runners_enabled :boolean default(TRUE), not null -# runners_token :string -# build_coverage_regex :string -# build_allow_git_fetch :boolean default(TRUE), not null -# build_timeout :integer default(3600), not null -# - FactoryGirl.define do # Project without repository # diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb index 7f331c3725..74497dc82c 100644 --- a/spec/factories/releases.rb +++ b/spec/factories/releases.rb @@ -1,15 +1,3 @@ -# == 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 -# - FactoryGirl.define do factory :release do tag "v1.1.0" diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index 7ae06c2784..e3681ae93a 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -1,21 +1,3 @@ -# == Schema Information -# -# Table name: todos -# -# id :integer not null, primary key -# user_id :integer not null -# project_id :integer not null -# target_id :integer -# target_type :string not null -# author_id :integer -# action :integer not null -# state :string not null -# created_at :datetime -# updated_at :datetime -# note_id :integer -# commit_id :string -# - FactoryGirl.define do factory :todo do project diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index ac12ab6c75..305f8bc88c 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: abuse_reports -# -# id :integer not null, primary key -# reporter_id :integer -# user_id :integer -# message :text -# created_at :datetime -# updated_at :datetime -# - require 'rails_helper' RSpec.describe AbuseReport, type: :model do diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 520cf1b75d..1ce22feed5 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -1,49 +1,3 @@ -# == Schema Information -# -# Table name: application_settings -# -# id :integer not null, primary key -# default_projects_limit :integer -# signup_enabled :boolean -# signin_enabled :boolean -# gravatar_enabled :boolean -# sign_in_text :text -# created_at :datetime -# updated_at :datetime -# home_page_url :string(255) -# default_branch_protection :integer default(2) -# restricted_visibility_levels :text -# version_check_enabled :boolean default(TRUE) -# max_attachment_size :integer default(10), not null -# default_project_visibility :integer -# default_snippet_visibility :integer -# restricted_signup_domains :text -# user_oauth_applications :boolean default(TRUE) -# 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 -# runners_registration_token :string -# require_two_factor_authentication :boolean default(FALSE) -# two_factor_grace_period :integer default(48) -# metrics_enabled :boolean default(FALSE) -# metrics_host :string default("localhost") -# metrics_username :string -# metrics_password :string -# metrics_pool_size :integer default(16) -# metrics_timeout :integer default(10) -# metrics_method_call_threshold :integer default(10) -# recaptcha_enabled :boolean default(FALSE) -# recaptcha_site_key :string -# recaptcha_private_key :string -# metrics_port :integer default(8089) -# sentry_enabled :boolean default(FALSE) -# sentry_dsn :string -# - require 'spec_helper' describe ApplicationSetting, models: true do diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb index f6f84db57e..6ad8bfef4f 100644 --- a/spec/models/broadcast_message_spec.rb +++ b/spec/models/broadcast_message_spec.rb @@ -1,17 +1,3 @@ -# == Schema Information -# -# Table name: broadcast_messages -# -# id :integer not null, primary key -# message :text not null -# starts_at :datetime -# ends_at :datetime -# created_at :datetime -# updated_at :datetime -# color :string(255) -# font :string(255) -# - require 'spec_helper' describe BroadcastMessage, models: true do diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb index a747aa0844..dc071ad1c9 100644 --- a/spec/models/ci/commit_spec.rb +++ b/spec/models/ci/commit_spec.rb @@ -1,21 +1,3 @@ -# == Schema Information -# -# 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 -# gl_project_id :integer -# - require 'spec_helper' describe Ci::Commit, models: true do diff --git a/spec/models/ci/runner_project_spec.rb b/spec/models/ci/runner_project_spec.rb index 000a732db7..95fc160b23 100644 --- a/spec/models/ci/runner_project_spec.rb +++ b/spec/models/ci/runner_project_spec.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: ci_runner_projects -# -# id :integer not null, primary key -# runner_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# gl_project_id :integer -# - require 'spec_helper' describe Ci::RunnerProject, models: true do diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 25e9e5eca4..eaa9422892 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -1,22 +1,3 @@ -# == Schema Information -# -# Table name: ci_runners -# -# id :integer not null, primary key -# token :string(255) -# created_at :datetime -# updated_at :datetime -# description :string(255) -# contacted_at :datetime -# active :boolean default(TRUE), not null -# is_shared :boolean default(FALSE) -# name :string(255) -# version :string(255) -# revision :string(255) -# platform :string(255) -# architecture :string(255) -# - require 'spec_helper' describe Ci::Runner, models: true do diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index 159be93930..474b0b1621 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -1,16 +1,3 @@ -# == Schema Information -# -# Table name: ci_triggers -# -# id :integer not null, primary key -# token :string(255) -# project_id :integer -# deleted_at :datetime -# created_at :datetime -# updated_at :datetime -# gl_project_id :integer -# - require 'spec_helper' describe Ci::Trigger, models: true do diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb index 71e84091cb..c712d211b0 100644 --- a/spec/models/ci/variable_spec.rb +++ b/spec/models/ci/variable_spec.rb @@ -1,17 +1,3 @@ -# == Schema Information -# -# Table name: ci_variables -# -# id :integer not null, primary key -# project_id :integer -# key :string(255) -# value :text -# encrypted_value :text -# encrypted_value_salt :string(255) -# encrypted_value_iv :string(255) -# gl_project_id :integer -# - require 'spec_helper' describe Ci::Variable, models: true do diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 971e675037..434e58cfd0 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -1,37 +1,3 @@ -# == 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 -# gl_project_id :integer -# - require 'spec_helper' describe CommitStatus, models: true do diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb index 64ba778afe..6a90598a62 100644 --- a/spec/models/deploy_key_spec.rb +++ b/spec/models/deploy_key_spec.rb @@ -1,18 +1,3 @@ -# == Schema Information -# -# Table name: keys -# -# id :integer not null, primary key -# user_id :integer -# created_at :datetime -# updated_at :datetime -# key :text -# title :string(255) -# type :string(255) -# fingerprint :string(255) -# public :boolean default(FALSE), not null -# - require 'spec_helper' describe DeployKey, models: true do diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb index 8aedbfb863..8a1e337c1a 100644 --- a/spec/models/deploy_keys_project_spec.rb +++ b/spec/models/deploy_keys_project_spec.rb @@ -1,14 +1,3 @@ -# == Schema Information -# -# Table name: deploy_keys_projects -# -# id :integer not null, primary key -# deploy_key_id :integer not null -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# - require 'spec_helper' describe DeployKeysProject, models: true do diff --git a/spec/models/email_spec.rb b/spec/models/email_spec.rb index a20a614964..5d0bd31db5 100644 --- a/spec/models/email_spec.rb +++ b/spec/models/email_spec.rb @@ -1,14 +1,3 @@ -# == Schema Information -# -# Table name: emails -# -# id :integer not null, primary key -# user_id :integer not null -# email :string(255) not null -# created_at :datetime -# updated_at :datetime -# - require 'spec_helper' describe Email, models: true do diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 0c3cd13f39..b0e76fec69 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -1,19 +1,3 @@ -# == Schema Information -# -# Table name: events -# -# id :integer not null, primary key -# target_type :string(255) -# target_id :integer -# title :string(255) -# data :text -# project_id :integer -# created_at :datetime -# updated_at :datetime -# action :integer -# author_id :integer -# - require 'spec_helper' describe Event, models: true do diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index d90fbfe1ea..3b817608ce 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -1,14 +1,3 @@ -# == Schema Information -# -# Table name: forked_project_links -# -# id :integer not null, primary key -# forked_to_project_id :integer not null -# forked_from_project_id :integer not null -# created_at :datetime -# updated_at :datetime -# - require 'spec_helper' describe ForkedProjectLink, "add link on fork" do diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb index 5b0883d870..0caf5869c2 100644 --- a/spec/models/generic_commit_status_spec.rb +++ b/spec/models/generic_commit_status_spec.rb @@ -1,37 +1,3 @@ -# == 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 -# gl_project_id :integer -# - require 'spec_helper' describe GenericCommitStatus, models: true do diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 7bfca1e72c..6fa16be7f0 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -1,18 +1,3 @@ -# == Schema Information -# -# Table name: namespaces -# -# id :integer not null, primary key -# name :string(255) not null -# path :string(255) not null -# owner_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) -# description :string(255) default(""), not null -# avatar :string(255) -# - require 'spec_helper' describe Group, models: true do diff --git a/spec/models/identity_spec.rb b/spec/models/identity_spec.rb index 5afe042e15..1b987588f5 100644 --- a/spec/models/identity_spec.rb +++ b/spec/models/identity_spec.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: identities -# -# id :integer not null, primary key -# extern_uid :string(255) -# provider :string(255) -# user_id :integer -# created_at :datetime -# updated_at :datetime -# - require 'spec_helper' RSpec.describe Identity, models: true do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 060e659910..8ab00c70f9 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -1,23 +1,3 @@ -# == Schema Information -# -# Table name: issues -# -# id :integer not null, primary key -# title :string(255) -# assignee_id :integer -# author_id :integer -# project_id :integer -# created_at :datetime -# updated_at :datetime -# position :integer default(0) -# branch_name :string(255) -# description :text -# milestone_id :integer -# state :string(255) -# iid :integer -# updated_by_id :integer -# - require 'spec_helper' describe Issue, models: true do diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index c962b83644..26fbedbef2 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -1,18 +1,3 @@ -# == Schema Information -# -# Table name: keys -# -# id :integer not null, primary key -# user_id :integer -# created_at :datetime -# updated_at :datetime -# key :text -# title :string(255) -# type :string(255) -# fingerprint :string(255) -# public :boolean default(FALSE), not null -# - require 'spec_helper' describe Key, models: true do diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb index dc7510b1de..5e6f8ca152 100644 --- a/spec/models/label_link_spec.rb +++ b/spec/models/label_link_spec.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: label_links -# -# id :integer not null, primary key -# label_id :integer -# target_id :integer -# target_type :string(255) -# created_at :datetime -# updated_at :datetime -# - require 'spec_helper' describe LabelLink, models: true do diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 0614ca1e7c..eff8ee222a 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -1,16 +1,3 @@ -# == Schema Information -# -# Table name: labels -# -# id :integer not null, primary key -# title :string(255) -# color :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# template :boolean default(FALSE) -# - require 'spec_helper' describe Label, models: true do diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 2d8f1cc1ad..6e51730eec 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -1,22 +1,3 @@ -# == Schema Information -# -# Table name: members -# -# id :integer not null, primary key -# access_level :integer not null -# source_id :integer not null -# source_type :string(255) not null -# user_id :integer -# notification_level :integer not null -# type :string(255) -# created_at :datetime -# updated_at :datetime -# created_by_id :integer -# invite_email :string(255) -# invite_token :string(255) -# invite_accepted_at :datetime -# - require 'spec_helper' describe Member, models: true do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index d7884cea33..c8578749b2 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1,32 +1,3 @@ -# == Schema Information -# -# Table name: merge_requests -# -# id :integer not null, primary key -# target_branch :string(255) not null -# source_branch :string(255) not null -# source_project_id :integer not null -# author_id :integer -# assignee_id :integer -# title :string(255) -# created_at :datetime -# updated_at :datetime -# milestone_id :integer -# state :string(255) -# merge_status :string(255) -# target_project_id :integer not null -# iid :integer -# description :text -# position :integer default(0) -# locked_at :datetime -# updated_by_id :integer -# merge_error :string(255) -# merge_params :text -# merge_when_build_succeeds :boolean default(FALSE), not null -# merge_user_id :integer -# merge_commit_sha :string -# - require 'spec_helper' describe MergeRequest, models: true do diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 72a4ea7022..26bfd2e124 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -1,18 +1,3 @@ -# == Schema Information -# -# Table name: milestones -# -# id :integer not null, primary key -# title :string(255) not null -# project_id :integer not null -# description :text -# due_date :date -# created_at :datetime -# updated_at :datetime -# state :string(255) -# iid :integer -# - require 'spec_helper' describe Milestone, models: true do diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 3c3a580942..4074f96629 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -1,18 +1,3 @@ -# == Schema Information -# -# Table name: namespaces -# -# id :integer not null, primary key -# name :string(255) not null -# path :string(255) not null -# owner_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) -# description :string(255) default(""), not null -# avatar :string(255) -# - require 'spec_helper' describe Namespace, models: true do diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 6b18936edb..4b788b5788 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -1,24 +1,3 @@ -# == Schema Information -# -# Table name: notes -# -# id :integer not null, primary key -# note :text -# noteable_type :string(255) -# author_id :integer -# created_at :datetime -# updated_at :datetime -# project_id :integer -# attachment :string(255) -# line_code :string(255) -# commit_id :string(255) -# noteable_id :integer -# system :boolean default(FALSE), not null -# st_diff :text -# updated_by_id :integer -# is_award :boolean default(FALSE), not null -# - require 'spec_helper' describe Note, models: true do diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb index e0feb606f7..d9d7c0b0aa 100644 --- a/spec/models/project_snippet_spec.rb +++ b/spec/models/project_snippet_spec.rb @@ -1,19 +1,3 @@ -# == Schema Information -# -# Table name: snippets -# -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# type :string(255) -# visibility_level :integer default(0), not null -# - require 'spec_helper' describe ProjectSnippet, models: true do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 3a8a6c2c16..f6e5b13264 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1,42 +1,3 @@ -# == Schema Information -# -# Table name: projects -# -# id :integer not null, primary key -# name :string(255) -# path :string(255) -# description :text -# created_at :datetime -# updated_at :datetime -# creator_id :integer -# issues_enabled :boolean default(TRUE), not null -# merge_requests_enabled :boolean default(TRUE), not null -# wiki_enabled :boolean default(TRUE), not null -# namespace_id :integer -# issues_tracker :string(255) default("gitlab"), not null -# issues_tracker_id :string(255) -# snippets_enabled :boolean default(TRUE), not null -# last_activity_at :datetime -# import_url :string(255) -# visibility_level :integer default(0), not null -# archived :boolean default(FALSE), not null -# avatar :string(255) -# import_status :string(255) -# repository_size :float default(0.0) -# star_count :integer default(0), not null -# import_type :string(255) -# import_source :string(255) -# commit_count :integer default(0) -# import_error :text -# ci_id :integer -# builds_enabled :boolean default(TRUE), not null -# shared_runners_enabled :boolean default(TRUE), not null -# runners_token :string -# build_coverage_regex :string -# build_allow_git_fetch :boolean default(TRUE), not null -# build_timeout :integer default(3600), not null -# - require 'spec_helper' describe Project, models: true do diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index 7e956cf677..b523834c6e 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -1,15 +1,3 @@ -# == Schema Information -# -# Table name: protected_branches -# -# id :integer not null, primary key -# project_id :integer not null -# name :string(255) not null -# created_at :datetime -# updated_at :datetime -# developers_can_push :boolean default(FALSE), not null -# - require 'spec_helper' describe ProtectedBranch, models: true do diff --git a/spec/models/release_spec.rb b/spec/models/release_spec.rb index 72ecb442a3..527005b2b6 100644 --- a/spec/models/release_spec.rb +++ b/spec/models/release_spec.rb @@ -1,15 +1,3 @@ -# == 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/service_spec.rb b/spec/models/service_spec.rb index 173628c08d..8592e112c5 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -1,24 +1,3 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# properties :text -# template :boolean default(FALSE) -# push_events :boolean default(TRUE) -# issues_events :boolean default(TRUE) -# merge_requests_events :boolean default(TRUE) -# tag_push_events :boolean default(TRUE) -# note_events :boolean default(TRUE), not null -# build_events :boolean default(FALSE), not null -# - require 'spec_helper' describe Service, models: true do diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 5077ac7b62..7a613e360d 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -1,19 +1,3 @@ -# == Schema Information -# -# Table name: snippets -# -# id :integer not null, primary key -# title :string(255) -# content :text -# author_id :integer not null -# project_id :integer -# created_at :datetime -# updated_at :datetime -# file_name :string(255) -# type :string(255) -# visibility_level :integer default(0), not null -# - require 'spec_helper' describe Snippet, models: true do diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index d9b86b9368..623b82c01d 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -1,21 +1,3 @@ -# == Schema Information -# -# Table name: todos -# -# id :integer not null, primary key -# user_id :integer not null -# project_id :integer not null -# target_id :integer -# target_type :string not null -# author_id :integer -# action :integer not null -# state :string not null -# created_at :datetime -# updated_at :datetime -# note_id :integer -# commit_id :string -# - require 'spec_helper' describe Todo, models: true do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 8b2fb77e28..26d4e13939 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,66 +1,3 @@ -# == Schema Information -# -# Table name: users -# -# id :integer not null, primary key -# email :string(255) default(""), not null -# encrypted_password :string(255) default(""), not null -# reset_password_token :string(255) -# reset_password_sent_at :datetime -# remember_created_at :datetime -# sign_in_count :integer default(0) -# current_sign_in_at :datetime -# last_sign_in_at :datetime -# current_sign_in_ip :string(255) -# last_sign_in_ip :string(255) -# created_at :datetime -# updated_at :datetime -# name :string(255) -# admin :boolean default(FALSE), not null -# projects_limit :integer default(10) -# skype :string(255) default(""), not null -# linkedin :string(255) default(""), not null -# twitter :string(255) default(""), not null -# authentication_token :string(255) -# theme_id :integer default(1), not null -# bio :string(255) -# failed_attempts :integer default(0) -# locked_at :datetime -# username :string(255) -# can_create_group :boolean default(TRUE), not null -# can_create_team :boolean default(TRUE), not null -# state :string(255) -# color_scheme_id :integer default(1), not null -# notification_level :integer default(1), not null -# password_expires_at :datetime -# created_by_id :integer -# last_credential_check_at :datetime -# avatar :string(255) -# confirmation_token :string(255) -# confirmed_at :datetime -# confirmation_sent_at :datetime -# unconfirmed_email :string(255) -# hide_no_ssh_key :boolean default(FALSE) -# website_url :string(255) default(""), not null -# notification_email :string(255) -# hide_no_password :boolean default(FALSE) -# password_automatically_set :boolean default(FALSE) -# location :string(255) -# encrypted_otp_secret :string(255) -# encrypted_otp_secret_iv :string(255) -# encrypted_otp_secret_salt :string(255) -# otp_required_for_login :boolean default(FALSE), not null -# otp_backup_codes :text -# public_email :string(255) default(""), not null -# dashboard :integer default(0) -# project_view :integer default(0) -# consumed_timestep :integer -# layout :integer default(0) -# hide_project_limit :boolean default(FALSE) -# unlock_token :string -# otp_grace_period_started_at :datetime -# - require 'spec_helper' describe User, models: true do From baef6728fa4e8e515ccdeba1ea54da996f322aab Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 May 2016 19:59:45 +0300 Subject: [PATCH 038/152] Send trace to a browser incrementally when build is running We send a state of ansi2html to client, client needs to send this state back. The state describes the configuration of generator and position within trace. --- app/controllers/projects/builds_controller.rb | 15 +++ app/models/ci/build.rb | 8 +- app/views/projects/builds/show.html.haml | 7 +- config/routes.rb | 1 + lib/ci/ansi2html.rb | 78 ++++++++---- spec/lib/ci/ansi2html_spec.rb | 111 ++++++++++++------ 6 files changed, 162 insertions(+), 58 deletions(-) diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index b8b9e78427..3c9a52a5dd 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -38,6 +38,14 @@ class Projects::BuildsController < Projects::ApplicationController end end + def trace + respond_to do |format| + format.json do + render json: @build.trace_with_state(params_state).merge!(id: @build.id, status: @build.status) + end + end + end + def retry unless @build.retryable? return render_404 @@ -72,6 +80,13 @@ class Projects::BuildsController < Projects::ApplicationController private + def params_state + begin + JSON.parse(params[:state], symbolize_names: true) + rescue + end + end + def build @build ||= project.builds.unscoped.find_by!(id: params[:id]) end diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 4bc3a225e2..1eb6a0d902 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -132,8 +132,12 @@ module Ci end def trace_html - html = Ci::Ansi2html::convert(trace) if trace.present? - html || '' + trace_with_state[:html] + end + + def trace_with_state(state = nil) + trace_with_state = Ci::Ansi2html::convert(trace, state) if trace.present? + trace_with_state || {} end def timeout diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index c0f7a7686f..0da0477bdd 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -1,5 +1,6 @@ - page_title "#{@build.name} (##{@build.id})", "Builds" = render "header_title" +- trace = build.trace_for_state .build-page .row-content-block.top-block @@ -85,7 +86,9 @@ %pre.trace#build-trace %code.bash = preserve do - = raw @build.trace_html + = raw trace[:html] + - if @build.active? + %i{:class => "fa fa-refresh fa-spin"} %div#down-build-trace @@ -216,4 +219,4 @@ :javascript - new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}") + new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}", "#{trace[:state]}") diff --git a/config/routes.rb b/config/routes.rb index dafecc9464..e5c7d656da 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -672,6 +672,7 @@ Rails.application.routes.draw do post :cancel post :retry post :erase + get :trace get :raw end diff --git a/lib/ci/ansi2html.rb b/lib/ci/ansi2html.rb index ac6d667cf8..d29e68570f 100644 --- a/lib/ci/ansi2html.rb +++ b/lib/ci/ansi2html.rb @@ -23,8 +23,8 @@ module Ci cross: 0x10, } - def self.convert(ansi) - Converter.new().convert(ansi) + def self.convert(ansi, state = nil) + Converter.new.convert(ansi, state) end class Converter @@ -84,22 +84,36 @@ module Ci def on_107(s) set_bg_color(7, 'l') end def on_109(s) set_bg_color(9, 'l') end - def convert(ansi) - @out = "" - @n_open_tags = 0 - reset() + attr_accessor :offset, :n_open_tags, :fg_color, :bg_color, :style_mask - s = StringScanner.new(ansi.gsub("<", "<")) + STATE_PARAMS = [:offset, :n_open_tags, :fg_color, :bg_color, :style_mask] + + def convert(raw, new_state) + reset_state + restore_state(new_state) if new_state && new_state[:offset].to_i < raw.length + + start = @offset + ansi = raw[@offset..-1] + + open_new_tag + + s = StringScanner.new(ansi) while(!s.eos?) if s.scan(/\e([@-_])(.*?)([@-~])/) handle_sequence(s) + elsif s.scan(/\e(([@-_])(.*?)?)?$/) + break + elsif s.scan(/ 0 } end def handle_sequence(s) @@ -121,6 +135,20 @@ module Ci evaluate_command_stack(commands) + open_new_tag + end + + def evaluate_command_stack(stack) + return unless command = stack.shift() + + if self.respond_to?("on_#{command}", true) + self.send("on_#{command}", stack) + end + + evaluate_command_stack(stack) + end + + def open_new_tag css_classes = [] unless @fg_color.nil? @@ -138,20 +166,8 @@ module Ci css_classes << "term-#{css_class}" if @style_mask & flag != 0 end - open_new_tag(css_classes) if css_classes.length > 0 - end + return if css_classes.empty? - def evaluate_command_stack(stack) - return unless command = stack.shift() - - if self.respond_to?("on_#{command}", true) - self.send("on_#{command}", stack) - end - - evaluate_command_stack(stack) - end - - def open_new_tag(css_classes) @out << %{} @n_open_tags += 1 end @@ -163,6 +179,26 @@ module Ci end end + def reset_state + @offset = 0 + @n_open_tags = 0 + @out = '' + reset + end + + def state + STATE_PARAMS.inject({}) do |h, param| + h[param] = send(param) + h + end + end + + def restore_state(new_state) + STATE_PARAMS.each do |param| + send("#{param}=".to_sym, new_state[param]) + end + end + def reset @fg_color = nil @bg_color = nil diff --git a/spec/lib/ci/ansi2html_spec.rb b/spec/lib/ci/ansi2html_spec.rb index 3a2b568f4c..04afbd0692 100644 --- a/spec/lib/ci/ansi2html_spec.rb +++ b/spec/lib/ci/ansi2html_spec.rb @@ -4,131 +4,176 @@ describe Ci::Ansi2html, lib: true do subject { Ci::Ansi2html } it "prints non-ansi as-is" do - expect(subject.convert("Hello")).to eq('Hello') + expect(subject.convert("Hello")[:html]).to eq('Hello') end it "strips non-color-changing controll sequences" do - expect(subject.convert("Hello \e[2Kworld")).to eq('Hello world') + expect(subject.convert("Hello \e[2Kworld")[:html]).to eq('Hello world') end it "prints simply red" do - expect(subject.convert("\e[31mHello\e[0m")).to eq('Hello') + expect(subject.convert("\e[31mHello\e[0m")[:html]).to eq('Hello') end it "prints simply red without trailing reset" do - expect(subject.convert("\e[31mHello")).to eq('Hello') + expect(subject.convert("\e[31mHello")[:html]).to eq('Hello') end it "prints simply yellow" do - expect(subject.convert("\e[33mHello\e[0m")).to eq('Hello') + expect(subject.convert("\e[33mHello\e[0m")[:html]).to eq('Hello') end it "prints default on blue" do - expect(subject.convert("\e[39;44mHello")).to eq('Hello') + expect(subject.convert("\e[39;44mHello")[:html]).to eq('Hello') end it "prints red on blue" do - expect(subject.convert("\e[31;44mHello")).to eq('Hello') + expect(subject.convert("\e[31;44mHello")[:html]).to eq('Hello') end it "resets colors after red on blue" do - expect(subject.convert("\e[31;44mHello\e[0m world")).to eq('Hello world') + expect(subject.convert("\e[31;44mHello\e[0m world")[:html]).to eq('Hello world') end it "performs color change from red/blue to yellow/blue" do - expect(subject.convert("\e[31;44mHello \e[33mworld")).to eq('Hello world') + expect(subject.convert("\e[31;44mHello \e[33mworld")[:html]).to eq('Hello world') end it "performs color change from red/blue to yellow/green" do - expect(subject.convert("\e[31;44mHello \e[33;42mworld")).to eq('Hello world') + expect(subject.convert("\e[31;44mHello \e[33;42mworld")[:html]).to eq('Hello world') end it "performs color change from red/blue to reset to yellow/green" do - expect(subject.convert("\e[31;44mHello\e[0m \e[33;42mworld")).to eq('Hello world') + expect(subject.convert("\e[31;44mHello\e[0m \e[33;42mworld")[:html]).to eq('Hello world') end it "ignores unsupported codes" do - expect(subject.convert("\e[51mHello\e[0m")).to eq('Hello') + expect(subject.convert("\e[51mHello\e[0m")[:html]).to eq('Hello') end it "prints light red" do - expect(subject.convert("\e[91mHello\e[0m")).to eq('Hello') + expect(subject.convert("\e[91mHello\e[0m")[:html]).to eq('Hello') end it "prints default on light red" do - expect(subject.convert("\e[101mHello\e[0m")).to eq('Hello') + expect(subject.convert("\e[101mHello\e[0m")[:html]).to eq('Hello') end it "performs color change from red/blue to default/blue" do - expect(subject.convert("\e[31;44mHello \e[39mworld")).to eq('Hello world') + expect(subject.convert("\e[31;44mHello \e[39mworld")[:html]).to eq('Hello world') end it "performs color change from light red/blue to default/blue" do - expect(subject.convert("\e[91;44mHello \e[39mworld")).to eq('Hello world') + expect(subject.convert("\e[91;44mHello \e[39mworld")[:html]).to eq('Hello world') end it "prints bold text" do - expect(subject.convert("\e[1mHello")).to eq('Hello') + expect(subject.convert("\e[1mHello")[:html]).to eq('Hello') end it "resets bold text" do - expect(subject.convert("\e[1mHello\e[21m world")).to eq('Hello world') - expect(subject.convert("\e[1mHello\e[22m world")).to eq('Hello world') + expect(subject.convert("\e[1mHello\e[21m world")[:html]).to eq('Hello world') + expect(subject.convert("\e[1mHello\e[22m world")[:html]).to eq('Hello world') end it "prints italic text" do - expect(subject.convert("\e[3mHello")).to eq('Hello') + expect(subject.convert("\e[3mHello")[:html]).to eq('Hello') end it "resets italic text" do - expect(subject.convert("\e[3mHello\e[23m world")).to eq('Hello world') + expect(subject.convert("\e[3mHello\e[23m world")[:html]).to eq('Hello world') end it "prints underlined text" do - expect(subject.convert("\e[4mHello")).to eq('Hello') + expect(subject.convert("\e[4mHello")[:html]).to eq('Hello') end it "resets underlined text" do - expect(subject.convert("\e[4mHello\e[24m world")).to eq('Hello world') + expect(subject.convert("\e[4mHello\e[24m world")[:html]).to eq('Hello world') end it "prints concealed text" do - expect(subject.convert("\e[8mHello")).to eq('Hello') + expect(subject.convert("\e[8mHello")[:html]).to eq('Hello') end it "resets concealed text" do - expect(subject.convert("\e[8mHello\e[28m world")).to eq('Hello world') + expect(subject.convert("\e[8mHello\e[28m world")[:html]).to eq('Hello world') end it "prints crossed-out text" do - expect(subject.convert("\e[9mHello")).to eq('Hello') + expect(subject.convert("\e[9mHello")[:html]).to eq('Hello') end it "resets crossed-out text" do - expect(subject.convert("\e[9mHello\e[29m world")).to eq('Hello world') + expect(subject.convert("\e[9mHello\e[29m world")[:html]).to eq('Hello world') end it "can print 256 xterm fg colors" do - expect(subject.convert("\e[38;5;16mHello")).to eq('Hello') + expect(subject.convert("\e[38;5;16mHello")[:html]).to eq('Hello') end it "can print 256 xterm fg colors on normal magenta background" do - expect(subject.convert("\e[38;5;16;45mHello")).to eq('Hello') + expect(subject.convert("\e[38;5;16;45mHello")[:html]).to eq('Hello') end it "can print 256 xterm bg colors" do - expect(subject.convert("\e[48;5;240mHello")).to eq('Hello') + expect(subject.convert("\e[48;5;240mHello")[:html]).to eq('Hello') end it "can print 256 xterm bg colors on normal magenta foreground" do - expect(subject.convert("\e[48;5;16;35mHello")).to eq('Hello') + expect(subject.convert("\e[48;5;16;35mHello")[:html]).to eq('Hello') end it "prints bold colored text vividly" do - expect(subject.convert("\e[1;31mHello\e[0m")).to eq('Hello') + expect(subject.convert("\e[1;31mHello\e[0m")[:html]).to eq('Hello') end it "prints bold light colored text correctly" do - expect(subject.convert("\e[1;91mHello\e[0m")).to eq('Hello') + expect(subject.convert("\e[1;91mHello\e[0m")[:html]).to eq('Hello') + end + + it "prints <" do + expect(subject.convert("<")[:html]).to eq('<') + end + + describe "incremental update" do + shared_examples 'stateable converter' do + let(:pass1) { subject.convert(pre_text) } + let(:pass2) { subject.convert(pre_text + text, pass1[:state]) } + + it "to returns html to append" do + expect(pass2[:append]).to be_truthy + expect(pass2[:html]).to eq(html) + expect(pass1[:text] + pass2[:text]).to eq(pre_text + text) + expect(pass1[:html] + pass2[:html]).to eq(pre_html + html) + end + end + + context "with split word" do + let(:pre_text) { "\e[1mHello" } + let(:pre_html) { "Hello" } + let(:text) { "\e[1mWorld" } + let(:html) { "World" } + + it_behaves_like 'stateable converter' + end + + context "with split sequence" do + let(:pre_text) { "\e[1m" } + let(:pre_html) { "" } + let(:text) { "Hello" } + let(:html) { "Hello" } + + it_behaves_like 'stateable converter' + end + + context "with partial sequence" do + let(:pre_text) { "Hello\e" } + let(:pre_html) { "Hello" } + let(:text) { "[1m World" } + let(:html) { " World" } + + it_behaves_like 'stateable converter' + end end end From 74520f23db51c95b4aea8856fb51c4246785f776 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 9 May 2016 20:19:27 +0300 Subject: [PATCH 039/152] Encode state as base64 string --- app/assets/javascripts/ci/build.coffee | 21 ++++++++++++------- app/controllers/projects/builds_controller.rb | 9 +------- app/views/projects/builds/show.html.haml | 6 +++--- lib/ci/ansi2html.rb | 13 ++++++++---- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/ci/build.coffee b/app/assets/javascripts/ci/build.coffee index 7afe8bf79e..fca0c3bae5 100644 --- a/app/assets/javascripts/ci/build.coffee +++ b/app/assets/javascripts/ci/build.coffee @@ -1,9 +1,12 @@ class CiBuild @interval: null + @state: null - constructor: (build_url, build_status) -> + constructor: (build_url, build_status, build_state) -> clearInterval(CiBuild.interval) + @state = build_state + @initScrollButtonAffix() if build_status == "running" || build_status == "pending" @@ -26,14 +29,18 @@ class CiBuild CiBuild.interval = setInterval => if window.location.href.split("#").first() is build_url $.ajax - url: build_url + url: build_url + "/trace.json?state=" + encodeURIComponent(@state) dataType: "json" - success: (build) => - if build.status == "running" - $('#build-trace code').html build.trace_html - $('#build-trace code').append '' + success: (log) => + @state = log.state + if log.status is "running" + if log.append + $('.fa-refresh').before log.html + else + $('#build-trace code').html log.html + $('#build-trace code').append '' @checkAutoscroll() - else if build.status != build_status + else if log.status isnt build_status Turbolinks.visit build_url , 4000 diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 3c9a52a5dd..bb1f6c5e98 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -41,7 +41,7 @@ class Projects::BuildsController < Projects::ApplicationController def trace respond_to do |format| format.json do - render json: @build.trace_with_state(params_state).merge!(id: @build.id, status: @build.status) + render json: @build.trace_with_state(params[:state]).merge!(id: @build.id, status: @build.status) end end end @@ -80,13 +80,6 @@ class Projects::BuildsController < Projects::ApplicationController private - def params_state - begin - JSON.parse(params[:state], symbolize_names: true) - rescue - end - end - def build @build ||= project.builds.unscoped.find_by!(id: params[:id]) end diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml index 0da0477bdd..c7b9c36a3a 100644 --- a/app/views/projects/builds/show.html.haml +++ b/app/views/projects/builds/show.html.haml @@ -1,6 +1,6 @@ - page_title "#{@build.name} (##{@build.id})", "Builds" = render "header_title" -- trace = build.trace_for_state +- trace_with_state = @build.trace_with_state .build-page .row-content-block.top-block @@ -86,7 +86,7 @@ %pre.trace#build-trace %code.bash = preserve do - = raw trace[:html] + = raw trace_with_state[:html] - if @build.active? %i{:class => "fa fa-refresh fa-spin"} @@ -219,4 +219,4 @@ :javascript - new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}", "#{trace[:state]}") + new CiBuild("#{namespace_project_build_url(@project.namespace, @project, @build)}", "#{@build.status}", "#{trace_with_state[:state]}") diff --git a/lib/ci/ansi2html.rb b/lib/ci/ansi2html.rb index d29e68570f..5fed43aaeb 100644 --- a/lib/ci/ansi2html.rb +++ b/lib/ci/ansi2html.rb @@ -90,7 +90,7 @@ module Ci def convert(raw, new_state) reset_state - restore_state(new_state) if new_state && new_state[:offset].to_i < raw.length + restore_state(raw, new_state) if new_state start = @offset ansi = raw[@offset..-1] @@ -187,15 +187,20 @@ module Ci end def state - STATE_PARAMS.inject({}) do |h, param| + state = STATE_PARAMS.inject({}) do |h, param| h[param] = send(param) h end + Base64.urlsafe_encode64(state.to_json) end - def restore_state(new_state) + def restore_state(raw, new_state) + state = Base64.urlsafe_decode64(new_state) + state = JSON.parse(state, symbolize_names: true) + return if state[:offset].to_i > raw.length + STATE_PARAMS.each do |param| - send("#{param}=".to_sym, new_state[param]) + send("#{param}=".to_sym, state[param]) end end From 10d4d5842bed749fa3477fb8808ec1c7cc05be75 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Mon, 9 May 2016 12:20:18 -0500 Subject: [PATCH 040/152] Add if exists to drop command --- lib/tasks/gitlab/db.rake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake index 1c706dc11b..e473b75602 100644 --- a/lib/tasks/gitlab/db.rake +++ b/lib/tasks/gitlab/db.rake @@ -29,10 +29,12 @@ namespace :gitlab do tables.delete 'schema_migrations' # Truncate schema_migrations to ensure migrations re-run connection.execute('TRUNCATE schema_migrations') + # Drop tables with cascade to avoid dependent table errors # PG: http://www.postgresql.org/docs/current/static/ddl-depend.html # MySQL: http://dev.mysql.com/doc/refman/5.7/en/drop-table.html - tables.each { |t| connection.execute("DROP TABLE #{t} CASCADE") } + # Add `IF EXISTS` because cascade could have already deleted a table. + tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{t} CASCADE") } end end end From 849cc380d8f2ed895bde11c01987e6633bc3d567 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 7 May 2016 10:56:08 -0700 Subject: [PATCH 041/152] Use a case-insensitive comparison in sanitizing URI schemes Closes #1625 --- CHANGELOG | 1 + lib/banzai/filter/sanitization_filter.rb | 2 +- spec/lib/banzai/filter/sanitization_filter_spec.rb | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 63077523aa..917f9ed3a8 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.8.0 (unreleased) - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen) + - Use a case-insensitive comparison in sanitizing URI schemes - Project#open_branches has been cleaned up and no longer loads entire records into memory. - Escape HTML in commit titles in system note messages - Improve multiple branch push performance by memoizing permission checking diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index 42dbab9d27..ca80aac5a0 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -63,7 +63,7 @@ module Banzai begin uri = Addressable::URI.parse(node['href']) - uri.scheme.strip! if uri.scheme + uri.scheme = uri.scheme.strip.downcase if uri.scheme node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme) rescue Addressable::URI::InvalidURIError diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb index 27ce312b11..b38e3b17e6 100644 --- a/spec/lib/banzai/filter/sanitization_filter_spec.rb +++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb @@ -22,6 +22,12 @@ describe Banzai::Filter::SanitizationFilter, lib: true do expect(filter(act).to_html).to eq exp end + it 'sanitizes mixed-cased javascript in attributes' do + act = %q(
    Text) + exp = 'Text' + expect(filter(act).to_html).to eq exp + end + it 'allows whitelisted HTML tags from the user' do exp = act = "
    \n
    Term
    \n
    Definition
    \n
    " expect(filter(act).to_html).to eq exp From bf0fd3ea1bcbf0245c973411d8e5b28a15ef206f Mon Sep 17 00:00:00 2001 From: Benedikt Huss Date: Thu, 5 May 2016 01:16:16 +0200 Subject: [PATCH 042/152] Links for redmine issue references are generated correctly again --- CHANGELOG | 1 + lib/banzai/filter/issue_reference_filter.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 63077523aa..1c6e24bc10 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -40,6 +40,7 @@ v 8.8.0 (unreleased) v 8.7.4 - Fix always showing build notification message when switching between merge requests - Fix links on wiki pages for relative url setups. !4026 (Artem Sidorenko) + - Links for Redmine issue references are generated correctly again (Benedikt Huss) v 8.7.3 - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb index 2732e0b514..59c5e89c54 100644 --- a/lib/banzai/filter/issue_reference_filter.rb +++ b/lib/banzai/filter/issue_reference_filter.rb @@ -10,6 +10,9 @@ module Banzai end def self.user_can_see_reference?(user, node, context) + # It is not possible to check access rights for external issue trackers + return true if context[:project].try(:external_issue_tracker) + issue = Issue.find(node.attr('data-issue')) rescue nil Ability.abilities.allowed?(user, :read_issue, issue) end From 8eb31c40fc854ee5c1290c429d71ceb77bc5f6ba Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 9 May 2016 18:53:25 -0500 Subject: [PATCH 043/152] Hide navbar on mobile --- app/assets/javascripts/application.js.coffee | 1 + app/assets/javascripts/sidebar.js.coffee | 2 +- app/assets/stylesheets/framework/header.scss | 50 +++++++++++++++++-- app/assets/stylesheets/framework/sidebar.scss | 40 +++++++++++++++ .../stylesheets/framework/variables.scss | 1 + app/assets/stylesheets/pages/profile.scss | 2 +- app/views/layouts/header/_default.html.haml | 5 +- 7 files changed, 95 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 5bac8eef1c..bffce5a0c0 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -204,6 +204,7 @@ $ -> $('.header-content .title').toggle() $('.header-content .navbar-collapse').toggle() $('.navbar-toggle').toggleClass('active') + $('.navbar-toggle i').toggleClass("fa-angle-right fa-angle-left") # Show/hide comments on diff $("body").on "click", ".js-toggle-diff-comments", (e) -> diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee index 860d4f438d..ea4ac52da3 100644 --- a/app/assets/javascripts/sidebar.js.coffee +++ b/app/assets/javascripts/sidebar.js.coffee @@ -12,7 +12,7 @@ toggleSidebar = -> niceScrollBars.updateScrollBar(); ), 300 -$(document).on("click", '.toggle-nav-collapse', (e) -> +$(document).on("click", '.toggle-nav-collapse, .side-nav-toggle', (e) -> e.preventDefault() toggleSidebar() diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 97f9d58200..3bcaa17fa7 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -8,7 +8,7 @@ header { &.navbar-empty { height: 58px; background: #fff; - border-bottom: 1px solid #eee; + border-bottom: 1px solid $btn-gray-hover; .center-logo { margin: 11px 0; @@ -30,6 +30,10 @@ header { border: none; border-bottom: 1px solid $border-color; + @media (max-width: $screen-xs-min) { + padding: 0 16px; + } + &.with-horizontal-nav { border-bottom: none; } @@ -60,16 +64,44 @@ header { margin: 6px 0; border-radius: 0; position: absolute; - right: 2px; + right: -10px; + padding: 6px 10px; &:hover { - background-color: #eee; + background-color: $btn-gray-hover; } + &.active { color: $gl-icon-color; } } } + + &.header-collapsed { + padding: 0 16px; + } + + .side-nav-toggle { + display: none; + position: absolute; + left: -10px; + margin: 6px 0; + padding: 6px 10px; + border: none; + background-color: $background-color; + + &:hover { + background-color: $btn-gray-hover; + } + + &:focus { + outline: none; + } + + @media (max-width: $screen-xs-min) { + display: block; + } + } } .header-content { @@ -77,6 +109,10 @@ header { height: $header-height; padding-right: 40px; + @media (max-width: $screen-xs-min) { + padding-left: 40px; + } + @media (min-width: $screen-sm-min) { padding-right: 0; } @@ -145,6 +181,10 @@ header { @media (min-width: $screen-md-min) { @include collapsed-header; } + + @media (max-width: $screen-xs-min) { + margin-left: 0; + } } .header-expanded { @@ -153,6 +193,10 @@ header { @media (min-width: $screen-md-min) { margin-left: $sidebar_width; } + + @media (max-width: $screen-xs-min) { + margin-left: 0; + } } @media (max-width: $screen-xs-max) { diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index e940fd7286..f4b79911b6 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -210,15 +210,33 @@ } } +.sidebar-wrapper { + &.hidden-nav { + width: 0; + } +} + .page-sidebar-collapsed { padding-left: $sidebar_collapsed_width; + @media (max-width: $screen-xs-min) { + padding-left: 0; + } + .sidebar-wrapper { width: $sidebar_collapsed_width; + @media (max-width: $screen-xs-min) { + width: 0; + } + .header-logo { width: $sidebar_collapsed_width; + @media (max-width: $screen-xs-min) { + width: 0; + } + a { padding-left: ($sidebar_collapsed_width - 36) / 2; @@ -244,12 +262,22 @@ .collapse-nav a { width: $sidebar_collapsed_width; + + @media (max-width: $screen-xs-min) { + width: 0; + } } .sidebar-user { padding-left: ($sidebar_collapsed_width - 36) / 2; width: $sidebar_collapsed_width; + @media (max-width: $screen-xs-min) { + width: 0; + padding-left: 0; + padding-right: 0; + } + .username { display: none; } @@ -258,6 +286,10 @@ .layout-nav { padding-right: $sidebar_collapsed_width; + + @media (max-width: $screen-xs-min) { + padding-right: 0;; + } } } @@ -268,6 +300,10 @@ padding-left: $sidebar_width; } + @media (max-width: $screen-xs-min) { + padding-left: 0; + } + .sidebar-wrapper { width: $sidebar_width; @@ -288,6 +324,10 @@ .layout-nav { padding-right: $sidebar_width; + + @media (max-width: $screen-xs-min) { + padding-right: 0;; + } } } diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index ccb4e5381b..84e74db06b 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -215,6 +215,7 @@ $dropdown-toggle-hover-icon-color: $dropdown-toggle-hover-border-color; $btn-active-gray: #ececec; $btn-placeholder-gray: #c7c7c7; $btn-white-active: #848484; +$btn-gray-hover: #eee; /* * Award emoji diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index abc5a0e987..843379a3f5 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -218,7 +218,7 @@ .btn { display: inline-block; - width: 48%; + width: 46%; } } } diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 86930d4eaa..172579dafd 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -1,9 +1,12 @@ %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } %div{ class: fluid_layout ? "container-fluid" : "container-fluid" } .header-content - %button.navbar-toggle{type: 'button'} + %button.side-nav-toggle{type: 'button'} %span.sr-only Toggle navigation = icon('bars') + %button.navbar-toggle{type: 'button'} + %span.sr-only Toggle navigation + = icon('angle-left') .navbar-collapse.collapse %ul.nav.navbar-nav From 36f60982ce2361844e405052c597b23f66299897 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 9 May 2016 19:34:41 -0500 Subject: [PATCH 044/152] Fix media queries for absolutely positioned nav element --- app/assets/stylesheets/framework/header.scss | 2 +- app/assets/stylesheets/framework/nav.scss | 6 +++++- app/assets/stylesheets/framework/sidebar.scss | 10 ++++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 3bcaa17fa7..51b54ceb4b 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -22,7 +22,7 @@ header { } &.navbar-gitlab { - padding: 0 20px; + padding: 0 16px; z-index: 100; margin-bottom: 0; min-height: $header-height; diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 7c18e93a26..f2d7dc625e 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -201,9 +201,13 @@ border-bottom: 1px solid $border-color; transition-duration: .3s; + .container-fluid { + position: relative; + } + .controls { float: right; - padding: 7px 5px 0 0; + padding: 7px 0 0; i { color: $layout-link-gray; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index f4b79911b6..593f65aaf6 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -323,11 +323,17 @@ } .layout-nav { - padding-right: $sidebar_width; - @media (max-width: $screen-xs-min) { padding-right: 0;; } + + @media (min-width: $screen-xs-min) and (max-width: $screen-md-min) { + padding-right: 62px; + } + + @media (min-width: $screen-md-min) { + padding-right: $sidebar_width; + } } } From dd1ad6bf3e5a378eb8698e95b73e58f935eb0956 Mon Sep 17 00:00:00 2001 From: Long Nguyen Date: Tue, 10 May 2016 09:56:12 +0700 Subject: [PATCH 045/152] Code improve --- app/views/snippets/index.html.haml | 22 +++++++++++----------- config/routes.rb | 15 ++++++++------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml index 8aa3d490fa..7e4918a608 100644 --- a/app/views/snippets/index.html.haml +++ b/app/views/snippets/index.html.haml @@ -1,13 +1,13 @@ - - page_title "By #{@user.name}", "Snippets" +- page_title "By #{@user.name}", "Snippets" - %ol.breadcrumb - %li - = link_to snippets_path do - Snippets - %li - = @user.name - .pull-right.hidden-xs - = link_to user_path(@user) do - #{@user.name} profile page +%ol.breadcrumb + %li + = link_to snippets_path do + Snippets + %li + = @user.name + .pull-right.hidden-xs + = link_to user_path(@user) do + #{@user.name} profile page - = render 'snippets' += render 'snippets' diff --git a/config/routes.rb b/config/routes.rb index 594bab204f..881de37f10 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -344,15 +344,16 @@ Rails.application.routes.draw do end scope(path: 'u/:username', + as: :user, constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Tue, 10 May 2016 07:36:44 +0200 Subject: [PATCH 046/152] Remove the annotate rake task --- lib/tasks/auto_annotate_models.rake | 44 ----------------------------- 1 file changed, 44 deletions(-) delete mode 100644 lib/tasks/auto_annotate_models.rake diff --git a/lib/tasks/auto_annotate_models.rake b/lib/tasks/auto_annotate_models.rake deleted file mode 100644 index 16bad4bd2b..0000000000 --- a/lib/tasks/auto_annotate_models.rake +++ /dev/null @@ -1,44 +0,0 @@ -if Rails.env.development? - task :set_annotation_options do - # You can override any of these by setting an environment variable of the - # same name. - Annotate.set_defaults( - 'routes' => 'false', - 'position_in_routes' => 'before', - 'position_in_class' => 'before', - 'position_in_test' => 'before', - 'position_in_fixture' => 'before', - 'position_in_factory' => 'before', - 'position_in_serializer' => 'before', - 'show_foreign_keys' => 'true', - 'show_indexes' => 'false', - 'simple_indexes' => 'false', - 'model_dir' => 'app/models', - 'root_dir' => '', - 'include_version' => 'false', - 'require' => '', - 'exclude_tests' => 'true', - 'exclude_fixtures' => 'true', - 'exclude_factories' => 'true', - 'exclude_serializers' => 'true', - 'exclude_scaffolds' => 'true', - 'exclude_controllers' => 'true', - 'exclude_helpers' => 'true', - 'ignore_model_sub_dir' => 'false', - 'ignore_columns' => nil, - 'ignore_unknown_models' => 'false', - 'hide_limit_column_types' => 'integer,boolean', - 'skip_on_db_migrate' => 'false', - 'format_bare' => 'true', - 'format_rdoc' => 'false', - 'format_markdown' => 'false', - 'sort' => 'false', - 'force' => 'false', - 'trace' => 'false', - 'wrapper_open' => nil, - 'wrapper_close' => nil, - ) - end - - Annotate.load_tasks -end From f52a15e022fb451fa9074495897cdd10dbeeceb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 10 May 2016 10:56:58 +0200 Subject: [PATCH 047/152] Revert "Merge branch 'wiki-fix' into 'master' " MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 4cc85a58e8ca00f5a4ecbd2bca40a0cad867b817, reversing changes made to 90ae445ba930068d04adc5a009dbdeabe4cb3e34. Signed-off-by: Rémy Coutable --- CHANGELOG | 1 - app/models/project_wiki.rb | 2 +- lib/gitlab/url_builder.rb | 2 +- spec/lib/gitlab/url_builder_spec.rb | 2 +- spec/models/project_wiki_spec.rb | 4 +--- 5 files changed, 4 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1c6e24bc10..6a5b1dce5c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -39,7 +39,6 @@ v 8.8.0 (unreleased) v 8.7.4 - Fix always showing build notification message when switching between merge requests - - Fix links on wiki pages for relative url setups. !4026 (Artem Sidorenko) - Links for Redmine issue references are generated correctly again (Benedikt Huss) v 8.7.3 diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index c91cb70ae2..7c1a61bb0b 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -40,7 +40,7 @@ class ProjectWiki end def wiki_base_path - [Gitlab.config.gitlab.url, "/", @project.path_with_namespace, "/wikis"].join('') + ["/", @project.path_with_namespace, "/wikis"].join('') end # Returns the Gollum::Wiki object. diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index 67a09d5abf..2bbbd3074e 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -62,7 +62,7 @@ module Gitlab end def wiki_page_url - "#{object.wiki.wiki_base_path}/#{object.slug}" + "#{Gitlab.config.gitlab.url}#{object.wiki.wiki_base_path}/#{object.slug}" end end end diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index c8d3bc0139..bf11472407 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -112,7 +112,7 @@ describe Gitlab::UrlBuilder, lib: true do wiki_page = build(:wiki_page) url = described_class.build(wiki_page) - expect(url).to eq "#{Gitlab.config.gitlab.url}/#{wiki_page.wiki.project.path_with_namespace}/wikis/#{wiki_page.slug}" + expect(url).to eq "#{Gitlab.config.gitlab.url}#{wiki_page.wiki.wiki_base_path}/#{wiki_page.slug}" end end end diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index ea659f417f..532e3f013f 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -38,9 +38,7 @@ describe ProjectWiki, models: true do describe "#wiki_base_path" do it "returns the wiki base path" do - gitlab_url = Gitlab.config.gitlab.url - wiki_base_path = "#{gitlab_url}/#{project.path_with_namespace}/wikis" - + wiki_base_path = "/#{project.path_with_namespace}/wikis" expect(subject.wiki_base_path).to eq(wiki_base_path) end end From ebf80db3abcaf4a0e08273bf180aa33368610b8a Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 10 May 2016 11:14:46 +0200 Subject: [PATCH 048/152] Remove extra sanitization Robert Speicher and I believe this string gets sanitized further down the stack anyway. Doing this in a model class feels wrong. --- app/models/hooks/web_hook.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 818abbf4cc..a5ac8bee6b 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -59,7 +59,7 @@ class WebHook < ActiveRecord::Base basic_auth: auth) end - [response.code, ActionView::Base.full_sanitizer.sanitize(response.to_s)] + [response.code, response.to_s] rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e logger.error("WebHook Error => #{e}") [false, e.to_s] From f95a8e450c37e89b2d03b1c7bd2ca609a1d288e7 Mon Sep 17 00:00:00 2001 From: Calin Seciu Date: Tue, 16 Feb 2016 13:13:47 +0200 Subject: [PATCH 049/152] Replace text field with textarea in 'New Tag' view and display tag message in `
    `
    
    ---
     app/views/projects/tags/new.html.haml  | 2 +-
     app/views/projects/tags/show.html.haml | 7 +++----
     2 files changed, 4 insertions(+), 5 deletions(-)
    
    diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
    index b40a6e5cb2..f930645329 100644
    --- a/app/views/projects/tags/new.html.haml
    +++ b/app/views/projects/tags/new.html.haml
    @@ -23,7 +23,7 @@
       .form-group
         = label_tag :message, nil, class: 'control-label'
         .col-sm-10
    -      = text_field_tag :message, nil, required: false, tabindex: 3, class: 'form-control'
    +      = text_area_tag :message, nil, required: false, tabindex: 3, class: 'form-control', rows: 5
           .help-block Optionally, enter a message to create an annotated tag.
       %hr
       .form-group
    diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
    index 9c916fd02d..0442b33958 100644
    --- a/app/views/projects/tags/show.html.haml
    +++ b/app/views/projects/tags/show.html.haml
    @@ -19,14 +19,13 @@
               %i.fa.fa-trash-o
       .title
         %span.item-title= @tag.name
    -    - if @tag.message.present?
    -      %span.light
    -         
    -        = strip_gpg_signature(@tag.message)
       - if @commit
         = render 'projects/branches/commit', commit: @commit, project: @project
       - else
         Cant find HEAD commit for this tag
    +  - if @tag.message.present?
    +    %pre.body
    +      = strip_gpg_signature(@tag.message)
     
     
     .append-bottom-default.prepend-top-default
    
    From 1026f2ad0982493cb5465404ef4ebe71a0082bc0 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?R=C3=A9my=20Coutable?= 
    Date: Wed, 20 Apr 2016 14:34:28 +0200
    Subject: [PATCH 050/152] Add integration specs for tags
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    Signed-off-by: Rémy Coutable 
    ---
     CHANGELOG                                     |  1 +
     app/views/projects/tags/show.html.haml        |  1 -
     features/project/commits/tags.feature         | 46 ----------
     features/steps/project/commits/tags.rb        | 90 -------------------
     spec/features/tags/master_creates_tag_spec.rb | 62 +++++++++++++
     spec/features/tags/master_deletes_tag_spec.rb | 41 +++++++++
     spec/features/tags/master_updates_tag_spec.rb | 42 +++++++++
     spec/features/tags/master_views_tags_spec.rb  | 73 +++++++++++++++
     8 files changed, 219 insertions(+), 137 deletions(-)
     delete mode 100644 features/project/commits/tags.feature
     delete mode 100644 features/steps/project/commits/tags.rb
     create mode 100644 spec/features/tags/master_creates_tag_spec.rb
     create mode 100644 spec/features/tags/master_deletes_tag_spec.rb
     create mode 100644 spec/features/tags/master_updates_tag_spec.rb
     create mode 100644 spec/features/tags/master_views_tags_spec.rb
    
    diff --git a/CHANGELOG b/CHANGELOG
    index 1c6e24bc10..8159d3258a 100644
    --- a/CHANGELOG
    +++ b/CHANGELOG
    @@ -22,6 +22,7 @@ v 8.8.0 (unreleased)
       - Updated search UI
       - Display informative message when new milestone is created
       - Sanitize milestones and labels titles
    +  - Support multi-line tag messages. !3833 (Calin Seciu)
       - Allow "NEWS" and "CHANGES" as alternative names for CHANGELOG. !3768 (Connor Shea)
       - Added button to toggle whitespaces changes on diff view
       - Backport GitHub Enterprise import support from EE
    diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
    index 0442b33958..9f1424aecc 100644
    --- a/app/views/projects/tags/show.html.haml
    +++ b/app/views/projects/tags/show.html.haml
    @@ -27,7 +27,6 @@
         %pre.body
           = strip_gpg_signature(@tag.message)
     
    -
     .append-bottom-default.prepend-top-default
       - if @release.description.present?
         .description
    diff --git a/features/project/commits/tags.feature b/features/project/commits/tags.feature
    deleted file mode 100644
    index a4be39b2d4..0000000000
    --- a/features/project/commits/tags.feature
    +++ /dev/null
    @@ -1,46 +0,0 @@
    -@project_commits
    -Feature: Project Commits Tags
    -  Background:
    -    Given I sign in as a user
    -    And I own project "Shop"
    -    Given I visit project tags page
    -
    -  Scenario: I can see all git tags
    -    Then I should see "Shop" all tags list
    -
    -  Scenario: I create a tag
    -    And I click new tag link
    -    And I submit new tag form
    -    Then I should see new tag created
    -
    -  Scenario: I create a tag with release notes
    -    Given I click new tag link
    -    And I submit new tag form with release notes
    -    Then I should see new tag created
    -    And I should see tag release notes
    -
    -  Scenario: I create a tag with invalid name
    -    And I click new tag link
    -    And I submit new tag form with invalid name
    -    Then I should see new an error that tag is invalid
    -
    -  Scenario: I create a tag with invalid reference
    -    And I click new tag link
    -    And I submit new tag form with invalid reference
    -    Then I should see new an error that tag ref is invalid
    -
    -  Scenario: I create a tag that already exists
    -    And I click new tag link
    -    And I submit new tag form with tag that already exists
    -    Then I should see new an error that tag already exists
    -
    -  Scenario: I delete a tag
    -    Given I visit tag 'v1.1.0' page
    -    Given I delete tag 'v1.1.0'
    -    Then I should not see tag 'v1.1.0'
    -
    -  Scenario: I add release notes to the tag
    -    Given I visit tag 'v1.1.0' page
    -    When I click edit tag link
    -    And I fill release notes and submit form
    -    Then I should see tag release notes
    diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb
    deleted file mode 100644
    index 912ba580ef..0000000000
    --- a/features/steps/project/commits/tags.rb
    +++ /dev/null
    @@ -1,90 +0,0 @@
    -class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps
    -  include SharedAuthentication
    -  include SharedProject
    -  include SharedPaths
    -
    -  step 'I should see "Shop" all tags list' do
    -    expect(page).to have_content "Tags"
    -    expect(page).to have_content "v1.0.0"
    -  end
    -
    -  step 'I click new tag link' do
    -    click_link 'New tag'
    -  end
    -
    -  step 'I submit new tag form' do
    -    fill_in 'tag_name', with: 'v7.0'
    -    fill_in 'ref', with: 'master'
    -    click_button 'Create tag'
    -  end
    -
    -  step 'I submit new tag form with release notes' do
    -    fill_in 'tag_name', with: 'v7.0'
    -    fill_in 'ref', with: 'master'
    -    fill_in 'release_description', with: 'Awesome release notes'
    -    click_button 'Create tag'
    -  end
    -
    -  step 'I fill release notes and submit form' do
    -    fill_in 'release_description', with: 'Awesome release notes'
    -    click_button 'Save changes'
    -  end
    -
    -  step 'I submit new tag form with invalid name' do
    -    fill_in 'tag_name', with: 'v 1.0'
    -    fill_in 'ref', with: 'master'
    -    click_button 'Create tag'
    -  end
    -
    -  step 'I submit new tag form with invalid reference' do
    -    fill_in 'tag_name', with: 'foo'
    -    fill_in 'ref', with: 'foo'
    -    click_button 'Create tag'
    -  end
    -
    -  step 'I submit new tag form with tag that already exists' do
    -    fill_in 'tag_name', with: 'v1.0.0'
    -    fill_in 'ref', with: 'master'
    -    click_button 'Create tag'
    -  end
    -
    -  step 'I should see new tag created' do
    -    expect(page).to have_content 'v7.0'
    -  end
    -
    -  step 'I should see new an error that tag is invalid' do
    -    expect(page).to have_content 'Tag name invalid'
    -  end
    -
    -  step 'I should see new an error that tag ref is invalid' do
    -    expect(page).to have_content 'Target foo is invalid'
    -  end
    -
    -  step 'I should see new an error that tag already exists' do
    -    expect(page).to have_content 'Tag v1.0.0 already exists'
    -  end
    -
    -  step "I visit tag 'v1.1.0' page" do
    -    click_link 'v1.1.0'
    -  end
    -
    -  step "I delete tag 'v1.1.0'" do
    -    page.within('.content') do
    -      first('.btn-remove').click
    -    end
    -  end
    -
    -  step "I should not see tag 'v1.1.0'" do
    -    page.within '.tags' do
    -      expect(page).not_to have_link 'v1.1.0'
    -    end
    -  end
    -
    -  step 'I click edit tag link' do
    -    click_link 'Edit release notes'
    -  end
    -
    -  step 'I should see tag release notes' do
    -    expect(page).to have_content 'Awesome release notes'
    -  end
    -end
    diff --git a/spec/features/tags/master_creates_tag_spec.rb b/spec/features/tags/master_creates_tag_spec.rb
    new file mode 100644
    index 0000000000..08a97085a9
    --- /dev/null
    +++ b/spec/features/tags/master_creates_tag_spec.rb
    @@ -0,0 +1,62 @@
    +require 'spec_helper'
    +
    +feature 'Master creates tag', feature: true do
    +  let(:user) { create(:user) }
    +  let(:project) { create(:project, namespace: user.namespace) }
    +
    +  before do
    +    project.team << [user, :master]
    +    login_with(user)
    +    visit namespace_project_tags_path(project.namespace, project)
    +  end
    +
    +  scenario 'with an invalid name displays an error' do
    +    create_tag_in_form(tag: 'v 1.0', ref: 'master')
    +
    +    expect(page).to have_content 'Tag name invalid'
    +  end
    +
    +  scenario 'with an invalid reference displays an error' do
    +    create_tag_in_form(tag: 'v2.0', ref: 'foo')
    +
    +    expect(page).to have_content 'Target foo is invalid'
    +  end
    +
    +  scenario 'that already exists displays an error' do
    +    create_tag_in_form(tag: 'v1.1.0', ref: 'master')
    +
    +    expect(page).to have_content 'Tag v1.1.0 already exists'
    +  end
    +
    +  scenario 'with multiline message displays the message in a 
     block' do
    +    create_tag_in_form(tag: 'v3.0', ref: 'master', message: "Awesome tag message\n\n- hello\n- world")
    +
    +    expect(current_path).to eq(
    +      namespace_project_tag_path(project.namespace, project, 'v3.0'))
    +    expect(page).to have_content 'v3.0'
    +    page.within 'pre.body' do
    +      expect(page).to have_content "Awesome tag message\n\n- hello\n- world"
    +    end
    +  end
    +
    +  scenario 'with multiline release notes parses the release note as Markdown' do
    +    create_tag_in_form(tag: 'v4.0', ref: 'master', desc: "Awesome release notes\n\n- hello\n- world")
    +
    +    expect(current_path).to eq(
    +      namespace_project_tag_path(project.namespace, project, 'v4.0'))
    +    expect(page).to have_content 'v4.0'
    +    page.within '.description' do
    +      expect(page).to have_content 'Awesome release notes'
    +      expect(page).to have_selector('ul li', count: 2)
    +    end
    +  end
    +
    +  def create_tag_in_form(tag:, ref:, message: nil, desc: nil)
    +    click_link 'New tag'
    +    fill_in 'tag_name', with: tag
    +    fill_in 'ref', with: ref
    +    fill_in 'message', with: message unless message.nil?
    +    fill_in 'release_description', with: desc unless desc.nil?
    +    click_button 'Create tag'
    +  end
    +end
    diff --git a/spec/features/tags/master_deletes_tag_spec.rb b/spec/features/tags/master_deletes_tag_spec.rb
    new file mode 100644
    index 0000000000..f0990118e3
    --- /dev/null
    +++ b/spec/features/tags/master_deletes_tag_spec.rb
    @@ -0,0 +1,41 @@
    +require 'spec_helper'
    +
    +feature 'Master deletes tag', feature: true do
    +  let(:user) { create(:user) }
    +  let(:project) { create(:project, namespace: user.namespace) }
    +
    +  before do
    +    project.team << [user, :master]
    +    login_with(user)
    +    visit namespace_project_tags_path(project.namespace, project)
    +  end
    +
    +  context 'from the tags list page' do
    +    scenario 'deletes the tag' do
    +      expect(page).to have_content 'v1.1.0'
    +
    +      page.within('.content') do
    +        first('.btn-remove').click
    +      end
    +
    +      expect(current_path).to eq(
    +        namespace_project_tags_path(project.namespace, project))
    +      expect(page).not_to have_content 'v1.1.0'
    +    end
    +
    +  end
    +
    +  context 'from a specific tag page' do
    +    scenario 'deletes the tag' do
    +      click_on 'v1.0.0'
    +      expect(current_path).to eq(
    +        namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
    +
    +      click_on 'Delete tag'
    +
    +      expect(current_path).to eq(
    +        namespace_project_tags_path(project.namespace, project))
    +      expect(page).not_to have_content 'v1.0.0'
    +    end
    +  end
    +end
    diff --git a/spec/features/tags/master_updates_tag_spec.rb b/spec/features/tags/master_updates_tag_spec.rb
    new file mode 100644
    index 0000000000..c926e9841f
    --- /dev/null
    +++ b/spec/features/tags/master_updates_tag_spec.rb
    @@ -0,0 +1,42 @@
    +require 'spec_helper'
    +
    +feature 'Master updates tag', feature: true do
    +  let(:user) { create(:user) }
    +  let(:project) { create(:project, namespace: user.namespace) }
    +
    +  before do
    +    project.team << [user, :master]
    +    login_with(user)
    +    visit namespace_project_tags_path(project.namespace, project)
    +  end
    +
    +  context 'from the tags list page' do
    +    scenario 'updates the release notes' do
    +      page.within(first('.controls')) do
    +        click_link 'Edit release notes'
    +      end
    +
    +      fill_in 'release_description', with: 'Awesome release notes'
    +      click_button 'Save changes'
    +
    +      expect(current_path).to eq(
    +        namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
    +      expect(page).to have_content 'v1.1.0'
    +      expect(page).to have_content 'Awesome release notes'
    +    end
    +  end
    +
    +  context 'from a specific tag page' do
    +    scenario 'updates the release notes' do
    +      click_on 'v1.1.0'
    +      click_link 'Edit release notes'
    +      fill_in 'release_description', with: 'Awesome release notes'
    +      click_button 'Save changes'
    +
    +      expect(current_path).to eq(
    +        namespace_project_tag_path(project.namespace, project, 'v1.1.0'))
    +      expect(page).to have_content 'v1.1.0'
    +      expect(page).to have_content 'Awesome release notes'
    +    end
    +  end
    +end
    diff --git a/spec/features/tags/master_views_tags_spec.rb b/spec/features/tags/master_views_tags_spec.rb
    new file mode 100644
    index 0000000000..29d2c24472
    --- /dev/null
    +++ b/spec/features/tags/master_views_tags_spec.rb
    @@ -0,0 +1,73 @@
    +require 'spec_helper'
    +
    +feature 'Master views tags', feature: true do
    +  let(:user) { create(:user) }
    +
    +  before do
    +    project.team << [user, :master]
    +    login_with(user)
    +  end
    +
    +  context 'when project has no tags' do
    +    let(:project) { create(:project_empty_repo) }
    +    before do
    +      visit namespace_project_path(project.namespace, project)
    +      click_on 'README'
    +      fill_in :commit_message, with: 'Add a README file', visible: true
    +      # Remove pre-receive hook so we can push without auth
    +      FileUtils.rm_f(File.join(project.repository.path, 'hooks', 'pre-receive'))
    +      click_button 'Commit Changes'
    +      visit namespace_project_tags_path(project.namespace, project)
    +    end
    +
    +    scenario 'displays a specific message' do
    +      expect(page).to have_content 'Repository has no tags yet.'
    +    end
    +  end
    +
    +  context 'when project has tags' do
    +    let(:project) { create(:project, namespace: user.namespace) }
    +    before do
    +      visit namespace_project_tags_path(project.namespace, project)
    +    end
    +
    +    scenario 'views the tags list page' do
    +      expect(page).to have_content 'v1.0.0'
    +    end
    +
    +    scenario 'views a specific tag page' do
    +      click_on 'v1.0.0'
    +
    +      expect(current_path).to eq(
    +        namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
    +      expect(page).to have_content 'v1.0.0'
    +      expect(page).to have_content 'This tag has no release notes.'
    +    end
    +
    +    describe 'links on the tag page' do
    +      scenario 'has a button to browse files' do
    +        click_on 'v1.0.0'
    +
    +        expect(current_path).to eq(
    +          namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
    +
    +        click_on 'Browse files'
    +
    +        expect(current_path).to eq(
    +          namespace_project_tree_path(project.namespace, project, 'v1.0.0'))
    +      end
    +
    +      scenario 'has a button to browse commits' do
    +        click_on 'v1.0.0'
    +
    +        expect(current_path).to eq(
    +          namespace_project_tag_path(project.namespace, project, 'v1.0.0'))
    +
    +        click_on 'Browse commits'
    +
    +        expect(current_path).to eq(
    +          namespace_project_commits_path(project.namespace, project, 'v1.0.0'))
    +      end
    +    end
    +  end
    +end
    
    From 6da3388569b3bf31ac72663a4c13dba83d064356 Mon Sep 17 00:00:00 2001
    From: Stan Hu 
    Date: Tue, 5 Apr 2016 09:43:48 -0700
    Subject: [PATCH 051/152] Add eager load paths to help prevent dependency load
     issues with Sidekiq workers
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    Attempts to address #13521, #14825
    
    Signed-off-by: Rémy Coutable 
    ---
     CHANGELOG             |  2 ++
     config/application.rb | 19 +++++++++++++------
     2 files changed, 15 insertions(+), 6 deletions(-)
    
    diff --git a/CHANGELOG b/CHANGELOG
    index 6a5b1dce5c..80ed7d2020 100644
    --- a/CHANGELOG
    +++ b/CHANGELOG
    @@ -91,6 +91,8 @@ v 8.7.0
       - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524
       - Improved Markdown rendering performance !3389
       - Make shared runners text in box configurable
    +  - Add eager load paths to help prevent dependency load issues with Sidekiq workers (Stan Hu)
    +  - Improved Markdown rendering performance !3389 (Yorick Peterse)
       - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu)
       - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling)
       - Expose project badges in project settings
    diff --git a/config/application.rb b/config/application.rb
    index b602e2b616..b33e57f5fc 100644
    --- a/config/application.rb
    +++ b/config/application.rb
    @@ -12,12 +12,19 @@ module Gitlab
         # Application configuration should go into files in config/initializers
         # -- all .rb files in that directory are automatically loaded.
     
    -    # Custom directories with classes and modules you want to be autoloadable.
    -    config.autoload_paths.push(*%W(#{config.root}/lib
    -                                   #{config.root}/app/models/hooks
    -                                   #{config.root}/app/models/concerns
    -                                   #{config.root}/app/models/project_services
    -                                   #{config.root}/app/models/members))
    +    # Sidekiq uses eager loading, but directories not in the standard Rails
    +    # directories must be added to the eager load paths:
    +    # https://github.com/mperham/sidekiq/wiki/FAQ#why-doesnt-sidekiq-autoload-my-rails-application-code
    +    # Also, there is no need to add `lib` to autoload_paths since autoloading is
    +    # configured to check for eager loaded paths:
    +    # https://github.com/rails/rails/blob/v4.2.6/railties/lib/rails/engine.rb#L687
    +    # This is a nice reference article on autoloading/eager loading:
    +    # http://blog.arkency.com/2014/11/dont-forget-about-eager-load-when-extending-autoload
    +    config.eager_load_paths.push(*%W(#{config.root}/lib
    +                                     #{config.root}/app/models/hooks
    +                                     #{config.root}/app/models/concerns
    +                                     #{config.root}/app/models/project_services
    +                                     #{config.root}/app/models/members))
     
         # Only load the plugins named here, in the order given (default is alphabetical).
         # :all can be used as a placeholder for all plugins not explicitly named.
    
    From 5589dcf8db0daf2235158724f6b18115a9abfa42 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?R=C3=A9my=20Coutable?= 
    Date: Fri, 15 Apr 2016 17:35:40 +0200
    Subject: [PATCH 052/152] Fix a few places where autoloading would fail
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    - Fix naming of API::CommitStatuses
    - Ensure we use require_dependency instead of require
    - Ensure the namespace is right in lib/api/api.rb, otherwise, we
      might require Grape::API::Helpers which defines the `#params` method.
      This is to avoid requiring a file multiple times and getting an "Already
      initialized constant" error.
    
    Signed-off-by: Rémy Coutable 
    ---
     CHANGELOG                                     |   3 +-
     app/models/repository.rb                      |   2 +-
     config/application.rb                         |  12 +-
     config/initializers/1_settings.rb             |   2 +-
     config/initializers/5_backend.rb              |   6 +-
     lib/api/api.rb                                |  67 ++--
     lib/api/api_guard.rb                          | 302 +++++++++---------
     lib/api/commit_statuses.rb                    |   2 +-
     lib/ci/api/api.rb                             |  10 +-
     lib/gitlab.rb                                 |   2 +-
     ...status_spec.rb => commit_statuses_spec.rb} |   2 +-
     11 files changed, 205 insertions(+), 205 deletions(-)
     rename spec/requests/api/{commit_status_spec.rb => commit_statuses_spec.rb} (99%)
    
    diff --git a/CHANGELOG b/CHANGELOG
    index 80ed7d2020..1cd428323d 100644
    --- a/CHANGELOG
    +++ b/CHANGELOG
    @@ -29,6 +29,7 @@ v 8.8.0 (unreleased)
       - API: Expose Issue#user_notes_count. !3126 (Anton Popov)
       - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718
       - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes)
    +  - Add eager load paths to help prevent dependency load issues in Sidekiq workers. !3724
       - Added multiple colors for labels in dropdowns when dups happen.
       - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea)
       - API support for the 'since' and 'until' operators on commit requests (Paco Guzman)
    @@ -91,8 +92,6 @@ v 8.7.0
       - Fix `signed_in_ip` being set to 127.0.0.1 when using a reverse proxy !3524
       - Improved Markdown rendering performance !3389
       - Make shared runners text in box configurable
    -  - Add eager load paths to help prevent dependency load issues with Sidekiq workers (Stan Hu)
    -  - Improved Markdown rendering performance !3389 (Yorick Peterse)
       - Don't attempt to look up an avatar in repo if repo directory does not exist (Stan Hu)
       - API: Ability to subscribe and unsubscribe from issues and merge requests (Robert Schilling)
       - Expose project badges in project settings
    diff --git a/app/models/repository.rb b/app/models/repository.rb
    index 7aebfe279f..a4b42d7226 100644
    --- a/app/models/repository.rb
    +++ b/app/models/repository.rb
    @@ -81,7 +81,7 @@ class Repository
       def commit(id = 'HEAD')
         return nil unless exists?
         commit = Gitlab::Git::Commit.find(raw_repository, id)
    -    commit = Commit.new(commit, @project) if commit
    +    commit = ::Commit.new(commit, @project) if commit
         commit
       rescue Rugged::OdbError
         nil
    diff --git a/config/application.rb b/config/application.rb
    index b33e57f5fc..cba80f38f1 100644
    --- a/config/application.rb
    +++ b/config/application.rb
    @@ -1,13 +1,13 @@
     require File.expand_path('../boot', __FILE__)
     
     require 'rails/all'
    -require 'devise'
    -I18n.config.enforce_available_locales = false
    +
     Bundler.require(:default, Rails.env)
    -require_relative '../lib/gitlab/redis'
     
     module Gitlab
       class Application < Rails::Application
    +    require_dependency Rails.root.join('lib/gitlab/redis')
    +
         # Settings in config/environments/* take precedence over those specified here.
         # Application configuration should go into files in config/initializers
         # -- all .rb files in that directory are automatically loaded.
    @@ -21,10 +21,10 @@ module Gitlab
         # This is a nice reference article on autoloading/eager loading:
         # http://blog.arkency.com/2014/11/dont-forget-about-eager-load-when-extending-autoload
         config.eager_load_paths.push(*%W(#{config.root}/lib
    +                                     #{config.root}/app/models/ci
                                          #{config.root}/app/models/hooks
    -                                     #{config.root}/app/models/concerns
    -                                     #{config.root}/app/models/project_services
    -                                     #{config.root}/app/models/members))
    +                                     #{config.root}/app/models/members
    +                                     #{config.root}/app/models/project_services))
     
         # Only load the plugins named here, in the order given (default is alphabetical).
         # :all can be used as a placeholder for all plugins not explicitly named.
    diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
    index 8db2c05fe4..23c8cea038 100644
    --- a/config/initializers/1_settings.rb
    +++ b/config/initializers/1_settings.rb
    @@ -1,4 +1,4 @@
    -require 'gitlab' # Load lib/gitlab.rb as soon as possible
    +require_dependency Rails.root.join('lib/gitlab') # Load Gitlab as soon as possible
     
     class Settings < Settingslogic
       source ENV.fetch('GITLAB_CONFIG') { "#{Rails.root}/config/gitlab.yml" }
    diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb
    index 80d641d73a..e026151a03 100644
    --- a/config/initializers/5_backend.rb
    +++ b/config/initializers/5_backend.rb
    @@ -1,11 +1,11 @@
     # GIT over HTTP
    -require Rails.root.join("lib", "gitlab", "backend", "grack_auth")
    +require_dependency Rails.root.join('lib/gitlab/backend/grack_auth')
     
     # GIT over SSH
    -require Rails.root.join("lib", "gitlab", "backend", "shell")
    +require_dependency Rails.root.join('lib/gitlab/backend/shell')
     
     # GitLab shell adapter
    -require Rails.root.join("lib", "gitlab", "backend", "shell_adapter")
    +require_dependency Rails.root.join('lib/gitlab/backend/shell_adapter')
     
     required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required)
     current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)
    diff --git a/lib/api/api.rb b/lib/api/api.rb
    index cc1004f800..5fd9c30cb4 100644
    --- a/lib/api/api.rb
    +++ b/lib/api/api.rb
    @@ -1,5 +1,3 @@
    -Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file}
    -
     module API
       class API < Grape::API
         include APIGuard
    @@ -25,38 +23,39 @@ module API
         format :json
         content_type :txt, "text/plain"
     
    -    helpers Helpers
    +    # Ensure the namespace is right, otherwise we might load Grape::API::Helpers
    +    helpers ::API::Helpers
     
    -    mount Groups
    -    mount GroupMembers
    -    mount Users
    -    mount Projects
    -    mount Repositories
    -    mount Issues
    -    mount Milestones
    -    mount Session
    -    mount MergeRequests
    -    mount Notes
    -    mount Internal
    -    mount SystemHooks
    -    mount ProjectSnippets
    -    mount ProjectMembers
    -    mount DeployKeys
    -    mount ProjectHooks
    -    mount Services
    -    mount Files
    -    mount Commits
    -    mount CommitStatus
    -    mount Namespaces
    -    mount Branches
    -    mount Labels
    -    mount Settings
    -    mount Keys
    -    mount Tags
    -    mount Triggers
    -    mount Builds
    -    mount Variables
    -    mount Runners
    -    mount Licenses
    +    mount ::API::Groups
    +    mount ::API::GroupMembers
    +    mount ::API::Users
    +    mount ::API::Projects
    +    mount ::API::Repositories
    +    mount ::API::Issues
    +    mount ::API::Milestones
    +    mount ::API::Session
    +    mount ::API::MergeRequests
    +    mount ::API::Notes
    +    mount ::API::Internal
    +    mount ::API::SystemHooks
    +    mount ::API::ProjectSnippets
    +    mount ::API::ProjectMembers
    +    mount ::API::DeployKeys
    +    mount ::API::ProjectHooks
    +    mount ::API::Services
    +    mount ::API::Files
    +    mount ::API::Commits
    +    mount ::API::CommitStatuses
    +    mount ::API::Namespaces
    +    mount ::API::Branches
    +    mount ::API::Labels
    +    mount ::API::Settings
    +    mount ::API::Keys
    +    mount ::API::Tags
    +    mount ::API::Triggers
    +    mount ::API::Builds
    +    mount ::API::Variables
    +    mount ::API::Runners
    +    mount ::API::Licenses
       end
     end
    diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb
    index b9994fcefd..7e67edb203 100644
    --- a/lib/api/api_guard.rb
    +++ b/lib/api/api_guard.rb
    @@ -2,171 +2,175 @@
     
     require 'rack/oauth2'
     
    -module APIGuard
    -  extend ActiveSupport::Concern
    +module API
    +  module APIGuard
    +    extend ActiveSupport::Concern
     
    -  included do |base|
    -    # OAuth2 Resource Server Authentication
    -    use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request|
    -      # The authenticator only fetches the raw token string
    +    included do |base|
    +      # OAuth2 Resource Server Authentication
    +      use Rack::OAuth2::Server::Resource::Bearer, 'The API' do |request|
    +        # The authenticator only fetches the raw token string
     
    -      # Must yield access token to store it in the env
    -      request.access_token
    -    end
    -
    -    helpers HelperMethods
    -
    -    install_error_responders(base)
    -  end
    -
    -  # Helper Methods for Grape Endpoint
    -  module HelperMethods
    -    # Invokes the doorkeeper guard.
    -    #
    -    # If token is presented and valid, then it sets @current_user.
    -    #
    -    # If the token does not have sufficient scopes to cover the requred scopes,
    -    # then it raises InsufficientScopeError.
    -    #
    -    # If the token is expired, then it raises ExpiredError.
    -    #
    -    # If the token is revoked, then it raises RevokedError.
    -    #
    -    # If the token is not found (nil), then it raises TokenNotFoundError.
    -    #
    -    # Arguments:
    -    #
    -    #   scopes: (optional) scopes required for this guard.
    -    #           Defaults to empty array.
    -    #
    -    def doorkeeper_guard!(scopes: [])
    -      if (access_token = find_access_token).nil?
    -        raise TokenNotFoundError
    -
    -      else
    -        case validate_access_token(access_token, scopes)
    -        when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
    -          raise InsufficientScopeError.new(scopes)
    -        when Oauth2::AccessTokenValidationService::EXPIRED
    -          raise ExpiredError
    -        when Oauth2::AccessTokenValidationService::REVOKED
    -          raise RevokedError
    -        when Oauth2::AccessTokenValidationService::VALID
    -          @current_user = User.find(access_token.resource_owner_id)
    -        end
    +        # Must yield access token to store it in the env
    +        request.access_token
           end
    +
    +      helpers HelperMethods
    +
    +      install_error_responders(base)
         end
     
    -    def doorkeeper_guard(scopes: [])
    -      if access_token = find_access_token
    -        case validate_access_token(access_token, scopes)
    -        when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
    -          raise InsufficientScopeError.new(scopes)
    +    # Helper Methods for Grape Endpoint
    +    module HelperMethods
    +      # Invokes the doorkeeper guard.
    +      #
    +      # If token is presented and valid, then it sets @current_user.
    +      #
    +      # If the token does not have sufficient scopes to cover the requred scopes,
    +      # then it raises InsufficientScopeError.
    +      #
    +      # If the token is expired, then it raises ExpiredError.
    +      #
    +      # If the token is revoked, then it raises RevokedError.
    +      #
    +      # If the token is not found (nil), then it raises TokenNotFoundError.
    +      #
    +      # Arguments:
    +      #
    +      #   scopes: (optional) scopes required for this guard.
    +      #           Defaults to empty array.
    +      #
    +      def doorkeeper_guard!(scopes: [])
    +        if (access_token = find_access_token).nil?
    +          raise TokenNotFoundError
     
    -        when Oauth2::AccessTokenValidationService::EXPIRED
    -          raise ExpiredError
    -
    -        when Oauth2::AccessTokenValidationService::REVOKED
    -          raise RevokedError
    -
    -        when Oauth2::AccessTokenValidationService::VALID
    -          @current_user = User.find(access_token.resource_owner_id)
    -        end
    -      end
    -    end
    -
    -    def current_user
    -      @current_user
    -    end
    -
    -    private
    -    def find_access_token
    -      @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods)
    -    end
    -
    -    def doorkeeper_request
    -      @doorkeeper_request ||= ActionDispatch::Request.new(env)
    -    end
    -
    -    def validate_access_token(access_token, scopes)
    -      Oauth2::AccessTokenValidationService.validate(access_token, scopes: scopes)
    -    end
    -  end
    -
    -  module ClassMethods
    -    # Installs the doorkeeper guard on the whole Grape API endpoint.
    -    #
    -    # Arguments:
    -    #
    -    #   scopes: (optional) scopes required for this guard.
    -    #           Defaults to empty array.
    -    #
    -    def guard_all!(scopes: [])
    -      before do
    -        guard! scopes: scopes
    -      end
    -    end
    -
    -    private
    -    def install_error_responders(base)
    -      error_classes = [ MissingTokenError, TokenNotFoundError,
    -                        ExpiredError, RevokedError, InsufficientScopeError]
    -
    -      base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler
    -    end
    -
    -    def oauth2_bearer_token_error_handler
    -      Proc.new do |e|
    -        response =
    -          case e
    -          when MissingTokenError
    -            Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new
    -
    -          when TokenNotFoundError
    -            Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
    -              :invalid_token,
    -              "Bad Access Token.")
    -
    -          when ExpiredError
    -            Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
    -              :invalid_token,
    -              "Token is expired. You can either do re-authorization or token refresh.")
    -
    -          when RevokedError
    -            Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
    -              :invalid_token,
    -              "Token was revoked. You have to re-authorize from the user.")
    -
    -          when InsufficientScopeError
    -            # FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2)
    -            # does not include WWW-Authenticate header, which breaks the standard.
    -            Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(
    -              :insufficient_scope,
    -              Rack::OAuth2::Server::Resource::ErrorMethods::DEFAULT_DESCRIPTION[:insufficient_scope],
    -              { scope: e.scopes })
    +        else
    +          case validate_access_token(access_token, scopes)
    +          when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
    +            raise InsufficientScopeError.new(scopes)
    +          when Oauth2::AccessTokenValidationService::EXPIRED
    +            raise ExpiredError
    +          when Oauth2::AccessTokenValidationService::REVOKED
    +            raise RevokedError
    +          when Oauth2::AccessTokenValidationService::VALID
    +            @current_user = User.find(access_token.resource_owner_id)
               end
    +        end
    +      end
     
    -        response.finish
    +      def doorkeeper_guard(scopes: [])
    +        if access_token = find_access_token
    +          case validate_access_token(access_token, scopes)
    +          when Oauth2::AccessTokenValidationService::INSUFFICIENT_SCOPE
    +            raise InsufficientScopeError.new(scopes)
    +
    +          when Oauth2::AccessTokenValidationService::EXPIRED
    +            raise ExpiredError
    +
    +          when Oauth2::AccessTokenValidationService::REVOKED
    +            raise RevokedError
    +
    +          when Oauth2::AccessTokenValidationService::VALID
    +            @current_user = User.find(access_token.resource_owner_id)
    +          end
    +        end
    +      end
    +
    +      def current_user
    +        @current_user
    +      end
    +
    +      private
    +
    +      def find_access_token
    +        @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods)
    +      end
    +
    +      def doorkeeper_request
    +        @doorkeeper_request ||= ActionDispatch::Request.new(env)
    +      end
    +
    +      def validate_access_token(access_token, scopes)
    +        Oauth2::AccessTokenValidationService.validate(access_token, scopes: scopes)
           end
         end
    -  end
     
    -  #
    -  # Exceptions
    -  #
    +    module ClassMethods
    +      # Installs the doorkeeper guard on the whole Grape API endpoint.
    +      #
    +      # Arguments:
    +      #
    +      #   scopes: (optional) scopes required for this guard.
    +      #           Defaults to empty array.
    +      #
    +      def guard_all!(scopes: [])
    +        before do
    +          guard! scopes: scopes
    +        end
    +      end
     
    -  class MissingTokenError < StandardError; end
    +      private
     
    -  class TokenNotFoundError < StandardError; end
    +      def install_error_responders(base)
    +        error_classes = [ MissingTokenError, TokenNotFoundError,
    +                          ExpiredError, RevokedError, InsufficientScopeError]
     
    -  class ExpiredError < StandardError; end
    +        base.send :rescue_from, *error_classes, oauth2_bearer_token_error_handler
    +      end
     
    -  class RevokedError < StandardError; end
    +      def oauth2_bearer_token_error_handler
    +        Proc.new do |e|
    +          response =
    +            case e
    +            when MissingTokenError
    +              Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new
     
    -  class InsufficientScopeError < StandardError
    -    attr_reader :scopes
    -    def initialize(scopes)
    -      @scopes = scopes
    +            when TokenNotFoundError
    +              Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
    +                :invalid_token,
    +                "Bad Access Token.")
    +
    +            when ExpiredError
    +              Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
    +                :invalid_token,
    +                "Token is expired. You can either do re-authorization or token refresh.")
    +
    +            when RevokedError
    +              Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new(
    +                :invalid_token,
    +                "Token was revoked. You have to re-authorize from the user.")
    +
    +            when InsufficientScopeError
    +              # FIXME: ForbiddenError (inherited from Bearer::Forbidden of Rack::Oauth2)
    +              # does not include WWW-Authenticate header, which breaks the standard.
    +              Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(
    +                :insufficient_scope,
    +                Rack::OAuth2::Server::Resource::ErrorMethods::DEFAULT_DESCRIPTION[:insufficient_scope],
    +                { scope: e.scopes })
    +            end
    +
    +          response.finish
    +        end
    +      end
    +    end
    +
    +    #
    +    # Exceptions
    +    #
    +
    +    class MissingTokenError < StandardError; end
    +
    +    class TokenNotFoundError < StandardError; end
    +
    +    class ExpiredError < StandardError; end
    +
    +    class RevokedError < StandardError; end
    +
    +    class InsufficientScopeError < StandardError
    +      attr_reader :scopes
    +      def initialize(scopes)
    +        @scopes = scopes
    +      end
         end
       end
     end
    diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
    index 7388ed2f4e..9bcd33ff19 100644
    --- a/lib/api/commit_statuses.rb
    +++ b/lib/api/commit_statuses.rb
    @@ -2,7 +2,7 @@ require 'mime/types'
     
     module API
       # Project commit statuses API
    -  class CommitStatus < Grape::API
    +  class CommitStatuses < Grape::API
         resource :projects do
           before { authenticate! }
     
    diff --git a/lib/ci/api/api.rb b/lib/ci/api/api.rb
    index 353c4ddebf..17bb99a2ae 100644
    --- a/lib/ci/api/api.rb
    +++ b/lib/ci/api/api.rb
    @@ -1,9 +1,7 @@
    -Dir["#{Rails.root}/lib/ci/api/*.rb"].each {|file| require file}
    -
     module Ci
       module API
         class API < Grape::API
    -      include APIGuard
    +      include ::API::APIGuard
           version 'v1', using: :path
     
           rescue_from ActiveRecord::RecordNotFound do
    @@ -31,9 +29,9 @@ module Ci
           helpers ::API::Helpers
           helpers Gitlab::CurrentSettings
     
    -      mount Builds
    -      mount Runners
    -      mount Triggers
    +      mount ::Ci::API::Builds
    +      mount ::Ci::API::Runners
    +      mount ::Ci::API::Triggers
         end
       end
     end
    diff --git a/lib/gitlab.rb b/lib/gitlab.rb
    index 7479e729db..37f4c34054 100644
    --- a/lib/gitlab.rb
    +++ b/lib/gitlab.rb
    @@ -1,4 +1,4 @@
    -require 'gitlab/git'
    +require_dependency 'gitlab/git'
     
     module Gitlab
       def self.com?
    diff --git a/spec/requests/api/commit_status_spec.rb b/spec/requests/api/commit_statuses_spec.rb
    similarity index 99%
    rename from spec/requests/api/commit_status_spec.rb
    rename to spec/requests/api/commit_statuses_spec.rb
    index f3785b1936..633927c8c3 100644
    --- a/spec/requests/api/commit_status_spec.rb
    +++ b/spec/requests/api/commit_statuses_spec.rb
    @@ -1,6 +1,6 @@
     require 'spec_helper'
     
    -describe API::CommitStatus, api: true do
    +describe API::CommitStatuses, api: true do
       include ApiHelpers
     
       let!(:project) { create(:project) }
    
    From bbc94e15a3f66852ac3283388327e25d92ae9c0a Mon Sep 17 00:00:00 2001
    From: Grzegorz Bizon 
    Date: Tue, 10 May 2016 13:07:14 +0200
    Subject: [PATCH 053/152] Use abbrevation in runners table in admin area
    
    ---
     app/views/admin/runners/_runner.html.haml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml
    index 97b6ec1f85..36b21eefde 100644
    --- a/app/views/admin/runners/_runner.html.haml
    +++ b/app/views/admin/runners/_runner.html.haml
    @@ -14,7 +14,7 @@
         = runner.description
       %td
         - if runner.shared?
    -      = '-'
    +      n/a
         - else
           = runner.projects.count(:all)
       %td
    
    From 0f1cf1d87b74aa1e6ca2c28fdfa21d189bd68181 Mon Sep 17 00:00:00 2001
    From: Dmitriy Zaporozhets 
    Date: Tue, 10 May 2016 13:16:09 +0200
    Subject: [PATCH 054/152] Minor consistency fixes to sidebar & header
    
    Signed-off-by: Dmitriy Zaporozhets 
    ---
     app/assets/stylesheets/framework/header.scss  | 2 +-
     app/assets/stylesheets/framework/sidebar.scss | 2 +-
     2 files changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
    index 51b54ceb4b..8190a97ed5 100644
    --- a/app/assets/stylesheets/framework/header.scss
    +++ b/app/assets/stylesheets/framework/header.scss
    @@ -25,7 +25,7 @@ header {
         padding: 0 16px;
         z-index: 100;
         margin-bottom: 0;
    -    min-height: $header-height;
    +    height: $header-height;
         background-color: $background-color;
         border: none;
         border-bottom: 1px solid $border-color;
    diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
    index 593f65aaf6..bd91f51708 100644
    --- a/app/assets/stylesheets/framework/sidebar.scss
    +++ b/app/assets/stylesheets/framework/sidebar.scss
    @@ -98,7 +98,7 @@
       }
     
       .sidebar-user {
    -    padding: 9px 22px;
    +    padding: 7px 22px;
         position: fixed;
         bottom: 40px;
         width: $sidebar_width;
    
    From 98d8e3fe9ff4d120469378490c41381ae751597e Mon Sep 17 00:00:00 2001
    From: Dmitriy Zaporozhets 
    Date: Tue, 10 May 2016 13:24:53 +0200
    Subject: [PATCH 055/152] Change dashboard button color to white on hover
    
    Signed-off-by: Dmitriy Zaporozhets 
    ---
     app/assets/stylesheets/framework/gitlab-theme.scss | 4 ++++
     1 file changed, 4 insertions(+)
    
    diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss
    index c83cf88159..dc4668877f 100644
    --- a/app/assets/stylesheets/framework/gitlab-theme.scss
    +++ b/app/assets/stylesheets/framework/gitlab-theme.scss
    @@ -24,6 +24,10 @@
             background-color: $color-darker;
             a {
               color: #fff;
    +
    +          h3 {
    +            color: #fff;
    +          }
             }
           }
         }
    
    From 4c506c4498192b6d60a3731ad5428e2354b777cb Mon Sep 17 00:00:00 2001
    From: Grzegorz Bizon 
    Date: Sat, 7 May 2016 21:28:04 +0200
    Subject: [PATCH 056/152] Fix using link to uploads in global snippets
    
    Closes #17342, closes #17363
    ---
     lib/banzai/filter/upload_link_filter.rb        |  4 +++-
     .../banzai/filter/upload_link_filter_spec.rb   | 18 ++++++++++++++++++
     2 files changed, 21 insertions(+), 1 deletion(-)
    
    diff --git a/lib/banzai/filter/upload_link_filter.rb b/lib/banzai/filter/upload_link_filter.rb
    index 7edfe5ade2..483d40c46d 100644
    --- a/lib/banzai/filter/upload_link_filter.rb
    +++ b/lib/banzai/filter/upload_link_filter.rb
    @@ -31,7 +31,9 @@ module Banzai
           end
     
           def build_url(uri)
    -        File.join(Gitlab.config.gitlab.url, context[:project].path_with_namespace, uri)
    +        return '#' unless project = context[:project]
    +
    +        File.join(Gitlab.config.gitlab.url, project.path_with_namespace, uri)
           end
     
           # Ensure that a :project key exists in context
    diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
    index 3b073a90a9..7a7103079e 100644
    --- a/spec/lib/banzai/filter/upload_link_filter_spec.rb
    +++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb
    @@ -8,6 +8,10 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do
           project: project
         })
     
    +    raw_filter(doc, contexts)
    +  end
    +
    +  def raw_filter(doc, contexts = {})
         described_class.call(doc, contexts)
       end
     
    @@ -70,4 +74,18 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do
           expect(doc.at_css('img')['src']).to match "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/uploads/%ED%95%9C%EA%B8%80.png"
         end
       end
    +
    +  context 'when project context does not exist' do
    +    let(:upload_link) { link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg') }
    +
    +    it 'does not raise error' do
    +      expect { raw_filter(upload_link, project: nil) }.to_not raise_error
    +    end
    +
    +    it 'provides an empty link' do
    +      doc = raw_filter(upload_link, project: nil)
    +
    +      expect(doc.at_css('a')['href']).to eq '#'
    +    end
    +  end
     end
    
    From c97a81d274ff0e7b792ed409385318c4ec25d92e Mon Sep 17 00:00:00 2001
    From: Grzegorz Bizon 
    Date: Sat, 7 May 2016 21:34:30 +0200
    Subject: [PATCH 057/152] Add Changelog entry for upload link in snippet fix
    
    ---
     CHANGELOG | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/CHANGELOG b/CHANGELOG
    index b07b7da330..841f5870d5 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.8.0 (unreleased)
    +  - Fix error when using link to uploads in global snippets
       - Assign labels and milestone to target project when moving issue. !3934 (Long Nguyen)
       - Project#open_branches has been cleaned up and no longer loads entire records into memory.
       - Escape HTML in commit titles in system note messages
    
    From 535be93a77ec677dbb561f8c1545a60d0d908832 Mon Sep 17 00:00:00 2001
    From: Grzegorz Bizon 
    Date: Tue, 10 May 2016 13:46:04 +0200
    Subject: [PATCH 058/152] Do not process upload links if no project context
    
    ---
     lib/banzai/filter/upload_link_filter.rb           | 8 ++++++--
     spec/lib/banzai/filter/upload_link_filter_spec.rb | 6 +++---
     2 files changed, 9 insertions(+), 5 deletions(-)
    
    diff --git a/lib/banzai/filter/upload_link_filter.rb b/lib/banzai/filter/upload_link_filter.rb
    index 483d40c46d..c0f503c9af 100644
    --- a/lib/banzai/filter/upload_link_filter.rb
    +++ b/lib/banzai/filter/upload_link_filter.rb
    @@ -8,6 +8,8 @@ module Banzai
         #
         class UploadLinkFilter < HTML::Pipeline::Filter
           def call
    +        return doc unless project
    +
             doc.search('a').each do |el|
               process_link_attr el.attribute('href')
             end
    @@ -31,11 +33,13 @@ module Banzai
           end
     
           def build_url(uri)
    -        return '#' unless project = context[:project]
    -
             File.join(Gitlab.config.gitlab.url, project.path_with_namespace, uri)
           end
     
    +      def project
    +        context[:project]
    +      end
    +
           # Ensure that a :project key exists in context
           #
           # Note that while the key might exist, its value could be nil!
    diff --git a/spec/lib/banzai/filter/upload_link_filter_spec.rb b/spec/lib/banzai/filter/upload_link_filter_spec.rb
    index 7a7103079e..b83be54746 100644
    --- a/spec/lib/banzai/filter/upload_link_filter_spec.rb
    +++ b/spec/lib/banzai/filter/upload_link_filter_spec.rb
    @@ -79,13 +79,13 @@ describe Banzai::Filter::UploadLinkFilter, lib: true do
         let(:upload_link) { link('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg') }
     
         it 'does not raise error' do
    -      expect { raw_filter(upload_link, project: nil) }.to_not raise_error
    +      expect { raw_filter(upload_link, project: nil) }.not_to raise_error
         end
     
    -    it 'provides an empty link' do
    +    it 'does not rewrite link' do
           doc = raw_filter(upload_link, project: nil)
     
    -      expect(doc.at_css('a')['href']).to eq '#'
    +      expect(doc.to_html).to eq upload_link
         end
       end
     end
    
    From 732e98eee7da718d282903844649d02f935fd2be Mon Sep 17 00:00:00 2001
    From: Grzegorz Bizon 
    Date: Mon, 9 May 2016 12:47:44 +0200
    Subject: [PATCH 059/152] Reuse runners edit form in admin and project area
    
    ---
     app/views/admin/runners/show.html.haml     | 22 +++---------------
     app/views/projects/runners/_form.html.haml | 25 +++++++++++++++++++++
     app/views/projects/runners/edit.html.haml  | 26 +---------------------
     3 files changed, 29 insertions(+), 44 deletions(-)
     create mode 100644 app/views/projects/runners/_form.html.haml
    
    diff --git a/app/views/admin/runners/show.html.haml b/app/views/admin/runners/show.html.haml
    index 8700b4820c..4dfb3ed05b 100644
    --- a/app/views/admin/runners/show.html.haml
    +++ b/app/views/admin/runners/show.html.haml
    @@ -22,25 +22,9 @@
         %h4 This runner will process builds only from ASSIGNED projects
         %p You can't make this a shared runner.
     %hr
    -= form_for @runner, url: admin_runner_path(@runner), html: { class: 'form-horizontal' } do |f|
    -  .form-group
    -    = label_tag :token, class: 'control-label' do
    -      Token
    -    .col-sm-10
    -      = f.text_field :token, class: 'form-control', readonly: true
    -  .form-group
    -    = label_tag :description, class: 'control-label' do
    -      Description
    -    .col-sm-10
    -      = f.text_field :description, class: 'form-control'
    -  .form-group
    -    = label_tag :tag_list, class: 'control-label' do
    -      Tags
    -    .col-sm-10
    -      = 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'
    +
    +.append-bottom-20
    +  = render '/projects/runners/form', runner: @runner, runner_form_url: admin_runner_path(@runner)
     
     .row
       .col-md-6
    diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml
    new file mode 100644
    index 0000000000..2d6c964ae9
    --- /dev/null
    +++ b/app/views/projects/runners/_form.html.haml
    @@ -0,0 +1,25 @@
    += form_for runner, url: runner_form_url, html: { class: 'form-horizontal' } do |f|
    +  .form-group
    +    = label :active, "Active", class: 'control-label'
    +    .col-sm-10
    +      .checkbox
    +        = f.check_box :active
    +        %span.light Paused runners don't accept new builds
    +  .form-group
    +    = label_tag :token, class: 'control-label' do
    +      Token
    +    .col-sm-10
    +      = f.text_field :token, class: 'form-control', readonly: true
    +  .form-group
    +    = label_tag :description, class: 'control-label' do
    +      Description
    +    .col-sm-10
    +      = f.text_field :description, class: 'form-control'
    +  .form-group
    +    = label_tag :tag_list, class: 'control-label' do
    +      Tags
    +    .col-sm-10
    +      = 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 changes', class: 'btn btn-save'
    diff --git a/app/views/projects/runners/edit.html.haml b/app/views/projects/runners/edit.html.haml
    index eba03028af..771947d790 100644
    --- a/app/views/projects/runners/edit.html.haml
    +++ b/app/views/projects/runners/edit.html.haml
    @@ -2,28 +2,4 @@
     
     %h4 Runner ##{@runner.id}
     %hr
    -= form_for @runner, url: runner_path(@runner), html: { class: 'form-horizontal' } do |f|
    -  .form-group
    -    = label :active, "Active", class: 'control-label'
    -    .col-sm-10
    -      .checkbox
    -        = f.check_box :active
    -        %span.light Paused runners don't accept new builds
    -  .form-group
    -    = label_tag :token, class: 'control-label' do
    -      Token
    -    .col-sm-10
    -      = f.text_field :token, class: 'form-control', readonly: true
    -  .form-group
    -    = label_tag :description, class: 'control-label' do
    -      Description
    -    .col-sm-10
    -      = f.text_field :description, class: 'form-control'
    -  .form-group
    -    = label_tag :tag_list, class: 'control-label' do
    -      Tags
    -    .col-sm-10
    -      = 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 changes', class: 'btn btn-save'
    + = render 'form', runner: @runner, runner_form_url: runner_path(@runner)
    
    From 7d6d3421d64085c05c8b7a17dcf0b70fbdd478b5 Mon Sep 17 00:00:00 2001
    From: Grzegorz Bizon 
    Date: Mon, 9 May 2016 13:07:10 +0200
    Subject: [PATCH 060/152] Share form editable attributes mf runner via model
    
    ---
     app/controllers/admin/runners_controller.rb    | 2 +-
     app/controllers/projects/runners_controller.rb | 2 +-
     app/models/ci/runner.rb                        | 1 +
     3 files changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
    index a701d49b84..8b8a732007 100644
    --- a/app/controllers/admin/runners_controller.rb
    +++ b/app/controllers/admin/runners_controller.rb
    @@ -58,6 +58,6 @@ class Admin::RunnersController < Admin::ApplicationController
       end
     
       def runner_params
    -    params.require(:runner).permit(:token, :description, :tag_list, :active)
    +    params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
       end
     end
    diff --git a/app/controllers/projects/runners_controller.rb b/app/controllers/projects/runners_controller.rb
    index 0dd2d6a99b..3a9d67aff6 100644
    --- a/app/controllers/projects/runners_controller.rb
    +++ b/app/controllers/projects/runners_controller.rb
    @@ -64,6 +64,6 @@ class Projects::RunnersController < Projects::ApplicationController
       end
     
       def runner_params
    -    params.require(:runner).permit(:description, :tag_list, :active)
    +    params.require(:runner).permit(Ci::Runner::FORM_EDITABLE)
       end
     end
    diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
    index add59a0889..d27963e8ec 100644
    --- a/app/models/ci/runner.rb
    +++ b/app/models/ci/runner.rb
    @@ -23,6 +23,7 @@ module Ci
     
         LAST_CONTACT_TIME = 5.minutes.ago
         AVAILABLE_SCOPES = ['specific', 'shared', 'active', 'paused', 'online']
    +    FORM_EDITABLE = [:description, :tag_list, :active]
     
         has_many :builds, class_name: 'Ci::Build'
         has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
    
    From 67dc3b9c1131b5a7f3f861a0fe35169fda5cd398 Mon Sep 17 00:00:00 2001
    From: DJ Mountney 
    Date: Fri, 22 Apr 2016 09:50:08 -0700
    Subject: [PATCH 061/152] Add the health_check gem end expose the health_check
     route
    
    ---
     Gemfile                             |  3 +++
     Gemfile.lock                        |  3 +++
     config/initializers/health_check.rb | 28 ++++++++++++++++++++++++++++
     config/routes.rb                    |  3 +++
     4 files changed, 37 insertions(+)
     create mode 100644 config/initializers/health_check.rb
    
    diff --git a/Gemfile b/Gemfile
    index 3e5c604ae0..204d19f711 100644
    --- a/Gemfile
    +++ b/Gemfile
    @@ -333,3 +333,6 @@ gem 'oauth2', '~> 1.0.0'
     
     # Soft deletion
     gem "paranoia", "~> 2.0"
    +
    +# Health check
    +gem 'health_check', '~> 1.5.1'
    diff --git a/Gemfile.lock b/Gemfile.lock
    index 86b9142ef2..de0f536641 100644
    --- a/Gemfile.lock
    +++ b/Gemfile.lock
    @@ -402,6 +402,8 @@ GEM
           html2haml (>= 1.0.1)
           railties (>= 4.0.1)
         hashie (3.4.3)
    +    health_check (1.5.1)
    +      rails (>= 2.3.0)
         highline (1.7.8)
         hipchat (1.5.2)
           httparty
    @@ -947,6 +949,7 @@ DEPENDENCIES
       grape (~> 0.13.0)
       grape-entity (~> 0.4.2)
       haml-rails (~> 0.9.0)
    +  health_check (~> 1.5.1)
       hipchat (~> 1.5.0)
       html-pipeline (~> 1.11.0)
       httparty (~> 0.13.3)
    diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb
    new file mode 100644
    index 0000000000..c111cfdf5c
    --- /dev/null
    +++ b/config/initializers/health_check.rb
    @@ -0,0 +1,28 @@
    +# HealthCheck.setup do |config|
    +#   # Text output upon success
    +#   config.success = 'success'
    +#
    +#   # Timeout in seconds used when checking smtp server
    +#   config.smtp_timeout = 30.0
    +#
    +#   # http status code used when plain text error message is output
    +#   # Set to 200 if you want your want to distinguish between partial (text does not include success) and
    +#   # total failure of rails application (http status of 500 etc)
    +#   config.http_status_for_error_text = 500
    +#
    +#   # http status code used when an error object is output (json or xml)
    +#   # Set to 200 if you want your want to distinguish between partial (healthy property == false) and
    +#   # total failure of rails application (http status of 500 etc)
    +#   config.http_status_for_error_object = 500
    +#
    +#   # You can customize which checks happen on a standard health check
    +#   config.standard_checks = [ 'database', 'migrations', 'custom' ]
    +#
    +#   # You can set what tests are run with the 'full' or 'all' parameter
    +#   config.full_checks = ['database', 'migrations', 'custom', 'email', 'cache']
    +#
    +#   # Add one or more custom checks that return a blank string if ok, or an error message if there is an error
    +#   config.add_custom_check do
    +#      any code that returns blank on success and non blank string upon failure
    +#   end
    +# end
    diff --git a/config/routes.rb b/config/routes.rb
    index dafecc9464..cac800b9a9 100644
    --- a/config/routes.rb
    +++ b/config/routes.rb
    @@ -73,6 +73,9 @@ Rails.application.routes.draw do
         mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq
       end
     
    +  # Health check
    +  health_check_routes
    +
       # Enable Grack support
       mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post, :put]
     
    
    From 9898f9b4e6b80edaa914675edfa9b229498b31fe Mon Sep 17 00:00:00 2001
    From: DJ Mountney 
    Date: Thu, 5 May 2016 12:25:02 -0700
    Subject: [PATCH 062/152] Set the standard health_check options to include the
     cache check.
    
    ---
     config/initializers/health_check.rb | 31 +++--------------------------
     1 file changed, 3 insertions(+), 28 deletions(-)
    
    diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb
    index c111cfdf5c..a93e427303 100644
    --- a/config/initializers/health_check.rb
    +++ b/config/initializers/health_check.rb
    @@ -1,28 +1,3 @@
    -# HealthCheck.setup do |config|
    -#   # Text output upon success
    -#   config.success = 'success'
    -#
    -#   # Timeout in seconds used when checking smtp server
    -#   config.smtp_timeout = 30.0
    -#
    -#   # http status code used when plain text error message is output
    -#   # Set to 200 if you want your want to distinguish between partial (text does not include success) and
    -#   # total failure of rails application (http status of 500 etc)
    -#   config.http_status_for_error_text = 500
    -#
    -#   # http status code used when an error object is output (json or xml)
    -#   # Set to 200 if you want your want to distinguish between partial (healthy property == false) and
    -#   # total failure of rails application (http status of 500 etc)
    -#   config.http_status_for_error_object = 500
    -#
    -#   # You can customize which checks happen on a standard health check
    -#   config.standard_checks = [ 'database', 'migrations', 'custom' ]
    -#
    -#   # You can set what tests are run with the 'full' or 'all' parameter
    -#   config.full_checks = ['database', 'migrations', 'custom', 'email', 'cache']
    -#
    -#   # Add one or more custom checks that return a blank string if ok, or an error message if there is an error
    -#   config.add_custom_check do
    -#      any code that returns blank on success and non blank string upon failure
    -#   end
    -# end
    +HealthCheck.setup do |config|
    +  config.standard_checks = [ 'database', 'migrations', 'cache' ]
    +end
    
    From 160ef66d1bbbbc593516c7575d6b02ddb019c000 Mon Sep 17 00:00:00 2001
    From: DJ Mountney 
    Date: Mon, 9 May 2016 16:21:22 -0700
    Subject: [PATCH 063/152] Add health_check access token, and enforce on the
     health_check endpoint
    
    Also added a health check page to the admin section for resetting the token.
    ---
     .../admin/application_settings_controller.rb  |  6 +++
     .../admin/health_check_controller.rb          |  9 +++++
     app/controllers/health_check_controller.rb    | 13 ++++++
     app/models/application_setting.rb             |  6 +++
     app/views/admin/health_check/show.html.haml   | 40 +++++++++++++++++++
     app/views/layouts/nav/_admin.html.haml        |  5 +++
     config/routes.rb                              |  4 +-
     ...ck_access_token_to_application_settings.rb |  5 +++
     db/schema.rb                                  |  3 +-
     9 files changed, 89 insertions(+), 2 deletions(-)
     create mode 100644 app/controllers/admin/health_check_controller.rb
     create mode 100644 app/controllers/health_check_controller.rb
     create mode 100644 app/views/admin/health_check/show.html.haml
     create mode 100644 db/migrate/20160509201028_add_health_check_access_token_to_application_settings.rb
    
    diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
    index ec22548dde..7b9a88cd31 100644
    --- a/app/controllers/admin/application_settings_controller.rb
    +++ b/app/controllers/admin/application_settings_controller.rb
    @@ -19,6 +19,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
         redirect_to admin_runners_path
       end
     
    +  def reset_health_check_token
    +    @application_setting.reset_health_check_access_token!
    +    flash[:notice] = 'New health check access token has been generated!'
    +    redirect_to :back
    +  end
    +
       def clear_repository_check_states
         RepositoryCheck::ClearWorker.perform_async
     
    diff --git a/app/controllers/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb
    new file mode 100644
    index 0000000000..3153a765e3
    --- /dev/null
    +++ b/app/controllers/admin/health_check_controller.rb
    @@ -0,0 +1,9 @@
    +class Admin::HealthCheckController < Admin::ApplicationController
    +  def show
    +    begin
    +      @errors = HealthCheck::Utils.process_checks('standard')
    +    rescue => e
    +      @errors = e.message.blank? ? e.class.to_s : e.message.to_s
    +    end
    +  end
    +end
    diff --git a/app/controllers/health_check_controller.rb b/app/controllers/health_check_controller.rb
    new file mode 100644
    index 0000000000..b974489836
    --- /dev/null
    +++ b/app/controllers/health_check_controller.rb
    @@ -0,0 +1,13 @@
    +class HealthCheckController < HealthCheck::HealthCheckController
    +  before_action :validate_health_check_access!
    +
    +  protected
    +
    +  def validate_health_check_access!
    +    return render_404 unless params[:token].presence && params[:token] == current_application_settings.health_check_access_token
    +  end
    +
    +  def render_404
    +    render file: Rails.root.join("public", "404"), layout: false, status: "404"
    +  end
    +end
    diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
    index 7039db2d41..bf88326c11 100644
    --- a/app/models/application_setting.rb
    +++ b/app/models/application_setting.rb
    @@ -1,6 +1,7 @@
     class ApplicationSetting < ActiveRecord::Base
       include TokenAuthenticatable
       add_authentication_token_field :runners_registration_token
    +  add_authentication_token_field :health_check_access_token
     
       CACHE_KEY = 'application_setting.last'
     
    @@ -70,6 +71,7 @@ class ApplicationSetting < ActiveRecord::Base
       end
     
       before_save :ensure_runners_registration_token
    +  before_save :ensure_health_check_access_token
     
       after_commit do
         Rails.cache.write(CACHE_KEY, self)
    @@ -133,4 +135,8 @@ class ApplicationSetting < ActiveRecord::Base
       def runners_registration_token
         ensure_runners_registration_token!
       end
    +
    +  def health_check_access_token
    +    ensure_health_check_access_token!
    +  end
     end
    diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml
    new file mode 100644
    index 0000000000..70e5d04e35
    --- /dev/null
    +++ b/app/views/admin/health_check/show.html.haml
    @@ -0,0 +1,40 @@
    +- page_title "Health Check"
    +
    +%h3.page-title
    +  Health Check
    +%p.light
    +  Health information can be reteived as plain text, json, or xml using:
    +  %ul
    +    %li
    +      %code= "/health_check?token=#{current_application_settings.health_check_access_token}"
    +    %li
    +      %code= "/health_check.json?token=#{current_application_settings.health_check_access_token}"
    +    %li
    +      %code= "/health_check.xml?token=#{current_application_settings.health_check_access_token}"
    +
    +.bs-callout.clearfix
    +  .pull-left
    +    %p
    +      You can reset the health check access token by pressing the button below.
    +    %p
    +      = button_to reset_health_check_token_admin_application_settings_path,
    +        method: :put, class: 'btn btn-default',
    +        data: { confirm: 'Are you sure you want to reset the health check token?' } do
    +        = icon('refresh')
    +        Reset health check access token
    +
    +%hr
    +.panel.panel-default
    +  .panel-heading
    +    Current Status:
    +    - if @errors.blank?
    +      = icon('circle', class: 'cgreen')
    +      Healthy
    +    - else
    +      = icon('warning', class: 'cred')
    +      Unhealthy
    +  .panel-body
    +    - if @errors.blank?
    +      No Health Problems Detected
    +    - else
    +      = @errors
    diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
    index 280a1b9372..f292730fe4 100644
    --- a/app/views/layouts/nav/_admin.html.haml
    +++ b/app/views/layouts/nav/_admin.html.haml
    @@ -41,6 +41,11 @@
           = icon('file-text fw')
           %span
             Logs
    +  = nav_link(controller: :health_check) do
    +    = link_to admin_health_check_path, title: 'Health Check' do
    +      = icon('medkit fw')
    +      %span
    +        Health Check
       = nav_link(controller: :broadcast_messages) do
         = link_to admin_broadcast_messages_path, title: 'Messages' do
           = icon('bullhorn fw')
    diff --git a/config/routes.rb b/config/routes.rb
    index cac800b9a9..c81bf294a5 100644
    --- a/config/routes.rb
    +++ b/config/routes.rb
    @@ -74,7 +74,7 @@ Rails.application.routes.draw do
       end
     
       # Health check
    -  health_check_routes
    +  get 'health_check(/:checks)(.:format)'  => 'health_check#index'
     
       # Enable Grack support
       mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post, :put]
    @@ -256,6 +256,7 @@ Rails.application.routes.draw do
         end
     
         resource :logs, only: [:show]
    +    resource :health_check, controller: 'health_check', only: [:show]
         resource :background_jobs, controller: 'background_jobs', only: [:show]
     
         resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
    @@ -287,6 +288,7 @@ Rails.application.routes.draw do
         resource :application_settings, only: [:show, :update] do
           resources :services
           put :reset_runners_token
    +      put :reset_health_check_token
           put :clear_repository_check_states
         end
     
    diff --git a/db/migrate/20160509201028_add_health_check_access_token_to_application_settings.rb b/db/migrate/20160509201028_add_health_check_access_token_to_application_settings.rb
    new file mode 100644
    index 0000000000..9d729fec18
    --- /dev/null
    +++ b/db/migrate/20160509201028_add_health_check_access_token_to_application_settings.rb
    @@ -0,0 +1,5 @@
    +class AddHealthCheckAccessTokenToApplicationSettings < ActiveRecord::Migration
    +  def change
    +    add_column :application_settings, :health_check_access_token, :string
    +  end
    +end
    diff --git a/db/schema.rb b/db/schema.rb
    index 71d953afe3..8ac3eeae62 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: 20160508194200) do
    +ActiveRecord::Schema.define(version: 20160509201028) do
     
       # These are extensions that must be enabled in order to support this database
       enable_extension "plpgsql"
    @@ -80,6 +80,7 @@ ActiveRecord::Schema.define(version: 20160508194200) do
         t.boolean  "repository_checks_enabled",         default: false
         t.text     "shared_runners_text"
         t.integer  "metrics_packet_size",               default: 1
    +    t.string   "health_check_access_token"
       end
     
       create_table "audit_events", force: :cascade do |t|
    
    From a278b36ab0f30e8bfa491256bddf9de1c0572382 Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?R=C3=A9my=20Coutable?= 
    Date: Fri, 22 Apr 2016 18:32:08 +0200
    Subject: [PATCH 064/152] Fix an issue when filtering merge requests with more
     than one label
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    Signed-off-by: Rémy Coutable 
    ---
     CHANGELOG                                     |   1 +
     app/models/concerns/issuable.rb               |   4 +-
     .../user_lists_merge_requests_spec.rb         | 137 ++++++++++++++++++
     3 files changed, 140 insertions(+), 2 deletions(-)
     create mode 100644 spec/features/merge_requests/user_lists_merge_requests_spec.rb
    
    diff --git a/CHANGELOG b/CHANGELOG
    index 1c21ad36b6..499febdfd0 100644
    --- a/CHANGELOG
    +++ b/CHANGELOG
    @@ -43,6 +43,7 @@ v 8.8.0 (unreleased)
     v 8.7.4
       - Fix always showing build notification message when switching between merge requests
       - Links for Redmine issue references are generated correctly again (Benedikt Huss)
    +  - Fix an issue when filtering merge requests with more than one label. !3886
     
     v 8.7.3
       - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented
    diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
    index 7a3db74203..c1248b5303 100644
    --- a/app/models/concerns/issuable.rb
    +++ b/app/models/concerns/issuable.rb
    @@ -124,8 +124,8 @@ module Issuable
         end
     
         def with_label(title)
    -      if title.is_a?(Array) && title.count > 1
    -        joins(:labels).where(labels: { title: title }).group('issues.id').having("count(distinct labels.title) = #{title.count}")
    +      if title.is_a?(Array) && title.size > 1
    +        joins(:labels).where(labels: { title: title }).group(arel_table[:id]).having("COUNT(DISTINCT labels.title) = #{title.size}")
           else
             joins(:labels).where(labels: { title: title })
           end
    diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
    new file mode 100644
    index 0000000000..cc7f78e732
    --- /dev/null
    +++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
    @@ -0,0 +1,137 @@
    +require 'spec_helper'
    +
    +describe 'Projects > Merge requests > User lists merge requests', feature: true do
    +  include SortingHelper
    +
    +  let(:project) { create(:project, :public) }
    +  let(:user) { create(:user) }
    +
    +  before do
    +    @fix = create(:merge_request,
    +                  title: 'fix',
    +                  source_project: project,
    +                  source_branch: 'fix',
    +                  assignee: user,
    +                  milestone: create(:milestone, due_date: '2013-12-11'),
    +                  created_at: 1.minute.ago,
    +                  updated_at: 1.minute.ago)
    +    create(:merge_request,
    +           title: 'markdown',
    +           source_project: project,
    +           source_branch: 'markdown',
    +           assignee: user,
    +           milestone: create(:milestone, due_date: '2013-12-12'),
    +           created_at: 2.minutes.ago,
    +           updated_at: 2.minutes.ago)
    +    create(:merge_request,
    +           title: 'lfs',
    +           source_project: project,
    +           source_branch: 'lfs',
    +           created_at: 3.minutes.ago,
    +           updated_at: 10.seconds.ago)
    +  end
    +
    +  it 'filters on no assignee' do
    +    visit_merge_requests(project, assignee_id: IssuableFinder::NONE)
    +
    +    expect(current_path).to eq(namespace_project_merge_requests_path(project.namespace, project))
    +    expect(page).to have_content 'lfs'
    +    expect(page).not_to have_content 'fix'
    +    expect(page).not_to have_content 'markdown'
    +  end
    +
    +  it 'filters on a specific assignee' do
    +    visit_merge_requests(project, assignee_id: user.id)
    +
    +    expect(page).not_to have_content 'lfs'
    +    expect(page).to have_content 'fix'
    +    expect(page).to have_content 'markdown'
    +  end
    +
    +  it 'sorts by newest' do
    +    visit_merge_requests(project, sort: sort_value_recently_created)
    +
    +    expect(first_merge_request).to include('lfs')
    +    expect(last_merge_request).to include('fix')
    +  end
    +
    +  it 'sorts by oldest' do
    +    visit_merge_requests(project, sort: sort_value_oldest_created)
    +
    +    expect(first_merge_request).to include('fix')
    +    expect(last_merge_request).to include('lfs')
    +  end
    +
    +  it 'sorts by last updated' do
    +    visit_merge_requests(project, sort: sort_value_recently_updated)
    +
    +    expect(first_merge_request).to include('lfs')
    +  end
    +
    +  it 'sorts by oldest updated' do
    +    visit_merge_requests(project, sort: sort_value_oldest_updated)
    +
    +    expect(first_merge_request).to include('markdown')
    +  end
    +
    +  it 'sorts by milestone due soon' do
    +    visit_merge_requests(project, sort: sort_value_milestone_soon)
    +
    +    expect(first_merge_request).to include('fix')
    +  end
    +
    +  it 'sorts by milestone due later' do
    +    visit_merge_requests(project, sort: sort_value_milestone_later)
    +
    +    expect(first_merge_request).to include('markdown')
    +  end
    +
    +  it 'filters on one label and sorts by due soon' do
    +    label = create(:label, project: project)
    +    create(:label_link, label: label, target: @fix)
    +
    +    visit_merge_requests(project, label_name: [label.name],
    +                                  sort: sort_value_due_date_soon)
    +
    +    expect(first_merge_request).to include('fix')
    +  end
    +
    +  context 'while filtering on two labels' do
    +    let(:label) { create(:label, project: project) }
    +    let(:label2) { create(:label, project: project) }
    +
    +    before do
    +      create(:label_link, label: label, target: @fix)
    +      create(:label_link, label: label2, target: @fix)
    +    end
    +
    +    it 'sorts by due soon' do
    +      visit_merge_requests(project, label_name: [label.name, label2.name],
    +                                    sort: sort_value_due_date_soon)
    +
    +      expect(first_merge_request).to include('fix')
    +    end
    +
    +    context 'filter on assignee and' do
    +      it 'sorts by due soon' do
    +        visit_merge_requests(project, label_name: [label.name, label2.name],
    +                                      assignee_id: user.id,
    +                                      sort: sort_value_due_date_soon)
    +
    +        expect(first_merge_request).to include('fix')
    +      end
    +    end
    +  end
    +
    +  def visit_merge_requests(project, opts = {})
    +    visit namespace_project_merge_requests_path(project.namespace, project, opts)
    +  end
    +
    +  def first_merge_request
    +    page.all('ul.mr-list > li').first.text
    +  end
    +
    +  def last_merge_request
    +    page.all('ul.mr-list > li').last.text
    +  end
    +end
    
    From 97424ea544d0954e582a356586270e983d3bbb7a Mon Sep 17 00:00:00 2001
    From: Sean McGivern 
    Date: Tue, 10 May 2016 18:03:55 +0100
    Subject: [PATCH 065/152] Restrict starred projects to viewable ones
    
    `User#starred_projects` doesn't perform any visibility checks. This has
    a couple of problems:
    
    1. It assumes a user can always view all of their starred projects in
       perpetuity (project not changed to private, access revoked, etc.).
    2. It assumes that we'll only ever allow a user to star a project they
       can view. This is currently the case, but bugs happen.
    
    Add `User#viewable_starred_projects` to filter the starred projects by
    those the user either has explicit access to, or are public or
    internal. Then use that in all places where we list the user's starred
    projects.
    ---
     .../dashboard/projects_controller.rb          |  2 +-
     app/controllers/dashboard_controller.rb       |  2 +-
     app/models/user.rb                            |  5 ++++
     lib/api/projects.rb                           |  2 +-
     spec/models/user_spec.rb                      | 24 +++++++++++++++++++
     spec/requests/api/projects_spec.rb            | 21 +++++++---------
     6 files changed, 41 insertions(+), 15 deletions(-)
    
    diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
    index 71acc244a9..c08eb81153 100644
    --- a/app/controllers/dashboard/projects_controller.rb
    +++ b/app/controllers/dashboard/projects_controller.rb
    @@ -28,7 +28,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
       end
     
       def starred
    -    @projects = current_user.starred_projects.sorted_by_activity
    +    @projects = current_user.viewable_starred_projects.sorted_by_activity
         @projects = filter_projects(@projects)
         @projects = @projects.includes(:namespace, :forked_from_project, :tags)
         @projects = @projects.sort(@sort = params[:sort])
    diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
    index 1dce4a2172..4dda4e51f6 100644
    --- a/app/controllers/dashboard_controller.rb
    +++ b/app/controllers/dashboard_controller.rb
    @@ -25,7 +25,7 @@ class DashboardController < Dashboard::ApplicationController
       def load_events
         projects =
           if params[:filter] == "starred"
    -        current_user.starred_projects
    +        current_user.viewable_starred_projects
           else
             current_user.authorized_projects
           end
    diff --git a/app/models/user.rb b/app/models/user.rb
    index 1e4814641d..a0115957f0 100644
    --- a/app/models/user.rb
    +++ b/app/models/user.rb
    @@ -381,6 +381,11 @@ class User < ActiveRecord::Base
         Project.where("projects.id IN (#{projects_union.to_sql})")
       end
     
    +  def viewable_starred_projects
    +    starred_projects.where("projects.visibility_level IN (?) OR projects.id IN (#{projects_union.to_sql})",
    +                           [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::INTERNAL])
    +  end
    +
       def owned_projects
         @owned_projects ||=
           Project.where('namespace_id IN (?) OR namespace_id = ?',
    diff --git a/lib/api/projects.rb b/lib/api/projects.rb
    index cc2c7a0c50..9b59577267 100644
    --- a/lib/api/projects.rb
    +++ b/lib/api/projects.rb
    @@ -44,7 +44,7 @@ module API
           # Example Request:
           #   GET /projects/starred
           get '/starred' do
    -        @projects = current_user.starred_projects
    +        @projects = current_user.viewable_starred_projects
             @projects = filter_projects(@projects)
             @projects = paginate @projects
             present @projects, with: Entities::Project
    diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
    index 26d4e13939..06d1ca3b7d 100644
    --- a/spec/models/user_spec.rb
    +++ b/spec/models/user_spec.rb
    @@ -233,6 +233,8 @@ describe User, models: true do
           @project = create :project, namespace: @user.namespace
           @project_2 = create :project, group: create(:group) # Grant MASTER access to the user
           @project_3 = create :project, group: create(:group) # Grant DEVELOPER access to the user
    +      @project_4 = create :project, group: create(:group)
    +      @project_5 = create :project, group: create(:group)
     
           @project_2.team << [@user, :master]
           @project_3.team << [@user, :developer]
    @@ -782,4 +784,26 @@ describe User, models: true do
     
         it { is_expected.to eq([private_project]) }
       end
    +
    +  describe '#viewable_starred_projects' do
    +    let(:user) { create(:user) }
    +    let(:public_project) { create(:project, :public) }
    +    let(:private_project) { create(:project, :private) }
    +    let(:private_viewable_project) { create(:project, :private) }
    +    let(:viewable?) { -> (project) { user.can?(:read_project, project) } }
    +    let(:projects) { [public_project, private_project, private_viewable_project] }
    +
    +    before do
    +      private_viewable_project.team << [user, Gitlab::Access::MASTER]
    +      projects.each { |project| user.toggle_star(project) }
    +    end
    +
    +    it 'returns only starred projects the user can view' do
    +      expect(user.viewable_starred_projects).to all(satisfy(&viewable?))
    +    end
    +
    +    it 'rejects only starred projects the user can not view' do
    +      expect(projects - user.viewable_starred_projects).not_to include(satisfy(&viewable?))
    +    end
    +  end
     end
    diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
    index 66193eac05..f167813e07 100644
    --- a/spec/requests/api/projects_spec.rb
    +++ b/spec/requests/api/projects_spec.rb
    @@ -10,20 +10,20 @@ describe API::API, api: true  do
       let(:admin) { create(:admin) }
       let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
       let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
    -  let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) }
       let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
       let(:project_member) { create(:project_member, :master, user: user, project: project) }
       let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
       let(:user4) { create(:user) }
       let(:project3) do
         create(:project,
    +    :private,
         name: 'second_project',
         path: 'second_project',
         creator_id: user.id,
         namespace: user.namespace,
         merge_requests_enabled: false,
         issues_enabled: false, wiki_enabled: false,
    -    snippets_enabled: false, visibility_level: 0)
    +    snippets_enabled: false)
       end
       let(:project_member3) do
         create(:project_member,
    @@ -164,21 +164,18 @@ describe API::API, api: true  do
       end
     
       describe 'GET /projects/starred' do
    +    let(:public_project) { create(:project, :public) }
    +
         before do
    -      admin.starred_projects << project
    -      admin.save!
    +      project_member2
    +      user3.update_attributes(starred_projects: [project, project2, project3, public_project])
         end
     
    -    it 'should return the starred projects' do
    -      get api('/projects/all', admin)
    +    it 'should return the starred projects viewable by the user' do
    +      get api('/projects/starred', user3)
           expect(response.status).to eq(200)
           expect(json_response).to be_an Array
    -
    -      expect(json_response).to satisfy do |response|
    -        response.one? do |entry|
    -          entry['name'] == project.name
    -        end
    -      end
    +      expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, public_project.id)
         end
       end
     
    
    From 53568da9ed6e2c4bae3c06b0fb1d595033921334 Mon Sep 17 00:00:00 2001
    From: Alfredo Sumaran 
    Date: Mon, 9 May 2016 16:15:01 -0500
    Subject: [PATCH 066/152] Update Ace Editor
    
    ---
     Gemfile      | 2 +-
     Gemfile.lock | 4 ++--
     2 files changed, 3 insertions(+), 3 deletions(-)
    
    diff --git a/Gemfile b/Gemfile
    index 3e5c604ae0..77d351419f 100644
    --- a/Gemfile
    +++ b/Gemfile
    @@ -197,7 +197,7 @@ gem 'licensee', '~> 8.0.0'
     gem "rack-attack", '~> 4.3.1'
     
     # Ace editor
    -gem 'ace-rails-ap', '~> 2.0.1'
    +gem 'ace-rails-ap', '~> 4.0.2'
     
     # Keyboard shortcuts
     gem 'mousetrap-rails', '~> 1.4.6'
    diff --git a/Gemfile.lock b/Gemfile.lock
    index 86b9142ef2..c02698bcca 100644
    --- a/Gemfile.lock
    +++ b/Gemfile.lock
    @@ -3,7 +3,7 @@ GEM
       specs:
         CFPropertyList (2.3.2)
         RedCloth (4.2.9)
    -    ace-rails-ap (2.0.1)
    +    ace-rails-ap (4.0.2)
         actionmailer (4.2.6)
           actionpack (= 4.2.6)
           actionview (= 4.2.6)
    @@ -882,7 +882,7 @@ PLATFORMS
     
     DEPENDENCIES
       RedCloth (~> 4.2.9)
    -  ace-rails-ap (~> 2.0.1)
    +  ace-rails-ap (~> 4.0.2)
       activerecord-deprecated_finders (~> 1.0.3)
       activerecord-session_store (~> 0.1.0)
       acts-as-taggable-on (~> 3.4)
    
    From 77f1b9dcd44d5017502a6a0807ccdab643a32ede Mon Sep 17 00:00:00 2001
    From: Alfredo Sumaran 
    Date: Mon, 9 May 2016 16:15:08 -0500
    Subject: [PATCH 067/152] Update CHANGELOG
    
    ---
     CHANGELOG | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/CHANGELOG b/CHANGELOG
    index 1c21ad36b6..5490af3a14 100644
    --- a/CHANGELOG
    +++ b/CHANGELOG
    @@ -39,6 +39,7 @@ v 8.8.0 (unreleased)
       - Expire repository exists? and has_visible_content? caches after a push if necessary
       - Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi)
       - Fix adding a todo for private group members (Ahmad Sherif)
    +  - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3
     
     v 8.7.4
       - Fix always showing build notification message when switching between merge requests
    
    From e689d9fb70d7484de708f8f436b294b40a9f6486 Mon Sep 17 00:00:00 2001
    From: Annabel Dunstone 
    Date: Tue, 10 May 2016 16:32:59 -0500
    Subject: [PATCH 068/152] Reduce width of side nav
    
    ---
     app/assets/stylesheets/framework/sidebar.scss   | 11 +++++++----
     app/assets/stylesheets/framework/variables.scss |  2 +-
     2 files changed, 8 insertions(+), 5 deletions(-)
    
    diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
    index bd91f51708..a509ef2f14 100644
    --- a/app/assets/stylesheets/framework/sidebar.scss
    +++ b/app/assets/stylesheets/framework/sidebar.scss
    @@ -63,7 +63,7 @@
           float: left;
           height: $header-height;
           width: 100%;
    -      padding-left: 22px;
    +      padding-left: 16px;
           overflow: hidden;
           outline: none;
           transition-duration: .3s;
    @@ -78,7 +78,7 @@
           }
     
           .gitlab-text-container {
    -        width: 230px;
    +        width: 220px;
     
             h3 {
               width: 158px;
    @@ -146,12 +146,12 @@
     
         a {
           padding: 7px 15px;
    +      transition-duration: .3s;
           font-size: $gl-font-size;
           line-height: 24px;
           color: $gray;
           display: block;
           text-decoration: none;
    -      padding-left: 23px;
           font-weight: normal;
           outline: none;
     
    @@ -253,6 +253,9 @@
             width: auto;
     
             a {
    +          padding-left: 23px;
    +          transition-duration: .3s;
    +
               span {
                 display: none;
               }
    @@ -312,7 +315,7 @@
         }
     
         .nav-sidebar li a {
    -      width: 230px;
    +      width: 220px;
     
           &.back-link {
             i {
    diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
    index 84e74db06b..5fa4c26660 100644
    --- a/app/assets/stylesheets/framework/variables.scss
    +++ b/app/assets/stylesheets/framework/variables.scss
    @@ -2,7 +2,7 @@
      * Layout
      */
     $sidebar_collapsed_width: 62px;
    -$sidebar_width: 230px;
    +$sidebar_width: 220px;
     $gutter_collapsed_width: 62px;
     $gutter_width: 290px;
     $gutter_inner_width: 258px;
    
    From a51064906bd4dcb29ea61c2278e2c32e848a5de6 Mon Sep 17 00:00:00 2001
    From: Annabel Dunstone 
    Date: Tue, 10 May 2016 16:51:57 -0500
    Subject: [PATCH 069/152] No extra link padding at xs screen size
    
    ---
     app/assets/stylesheets/framework/sidebar.scss | 6 ++++--
     1 file changed, 4 insertions(+), 2 deletions(-)
    
    diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
    index a509ef2f14..c243dd8a42 100644
    --- a/app/assets/stylesheets/framework/sidebar.scss
    +++ b/app/assets/stylesheets/framework/sidebar.scss
    @@ -253,8 +253,10 @@
             width: auto;
     
             a {
    -          padding-left: 23px;
    -          transition-duration: .3s;
    +          @media (min-width: $screen-xs-min) {
    +            padding-left: 23px;
    +            transition-duration: .3s;
    +          }
     
               span {
                 display: none;
    
    From 0e0caf4d17c28b6b0f3488b25efa265ce2804cc4 Mon Sep 17 00:00:00 2001
    From: DJ Mountney 
    Date: Tue, 10 May 2016 16:19:16 -0700
    Subject: [PATCH 070/152] Add tests for the health check feature
    
    ---
     app/views/admin/health_check/show.html.haml   |  3 +
     .../health_check_controller_spec.rb           | 90 +++++++++++++++++++
     .../features/admin/admin_health_check_spec.rb | 55 ++++++++++++
     spec/routing/admin_routing_spec.rb            |  7 ++
     spec/routing/routing_spec.rb                  | 10 +++
     5 files changed, 165 insertions(+)
     create mode 100644 spec/controllers/health_check_controller_spec.rb
     create mode 100644 spec/features/admin/admin_health_check_spec.rb
    
    diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml
    index 70e5d04e35..23a931995a 100644
    --- a/app/views/admin/health_check/show.html.haml
    +++ b/app/views/admin/health_check/show.html.haml
    @@ -2,6 +2,9 @@
     
     %h3.page-title
       Health Check
    +%p.light
    +  Access token is
    +  %code{ id:'health-check-token' }= "#{current_application_settings.health_check_access_token}"
     %p.light
       Health information can be reteived as plain text, json, or xml using:
       %ul
    diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
    new file mode 100644
    index 0000000000..3b9cc5c98f
    --- /dev/null
    +++ b/spec/controllers/health_check_controller_spec.rb
    @@ -0,0 +1,90 @@
    +require 'spec_helper'
    +
    +describe HealthCheckController do
    +  let(:token) { current_application_settings.health_check_access_token }
    +  let(:json_response) { JSON.parse(response.body) }
    +  let(:xml_response) { Hash.from_xml(response.body)['hash'] }
    +
    +  describe 'GET #index' do
    +    context 'when services are up but NO access token' do
    +      it 'returns a not found page' do
    +        get :index
    +        expect(response).to be_not_found
    +      end
    +    end
    +
    +    context 'when services are up and an access token is provided' do
    +      it 'supports successful plaintest response' do
    +        get :index, token: token
    +        expect(response).to be_success
    +        expect(response.content_type).to eq 'text/plain'
    +      end
    +
    +      it 'supports successful json response' do
    +        get :index, token: token, format: :json
    +        expect(response).to be_success
    +        expect(response.content_type).to eq 'application/json'
    +        expect(json_response['healthy']).to be true
    +      end
    +
    +      it 'supports successful xml response' do
    +        get :index, token: token, format: :xml
    +        expect(response).to be_success
    +        expect(response.content_type).to eq 'application/xml'
    +        expect(xml_response['healthy']).to be true
    +      end
    +
    +      it 'supports successful responses for specific checks' do
    +        get :index, token: token, checks: 'email', format: :json
    +        expect(response).to be_success
    +        expect(response.content_type).to eq 'application/json'
    +        expect(json_response['healthy']).to be true
    +      end
    +    end
    +
    +    context 'when a service is down but NO access token' do
    +      it 'returns a not found page' do
    +        get :index
    +        expect(response).to be_not_found
    +      end
    +    end
    +
    +    context 'when a service is down and an access token is provided' do
    +      before do
    +        allow(HealthCheck::Utils).to receive(:process_checks).with('standard').and_return('The server is on fire')
    +        allow(HealthCheck::Utils).to receive(:process_checks).with('email').and_return('Email is on fire')
    +      end
    +
    +      it 'supports failure plaintest response' do
    +        get :index, token: token
    +        expect(response.status).to eq(500)
    +        expect(response.content_type).to eq 'text/plain'
    +        expect(response.body).to include('The server is on fire')
    +      end
    +
    +      it 'supports failure json response' do
    +        get :index, token: token, format: :json
    +        expect(response.status).to eq(500)
    +        expect(response.content_type).to eq 'application/json'
    +        expect(json_response['healthy']).to be false
    +        expect(json_response['message']).to include('The server is on fire')
    +      end
    +
    +      it 'supports failure xml response' do
    +        get :index, token: token, format: :xml
    +        expect(response.status).to eq(500)
    +        expect(response.content_type).to eq 'application/xml'
    +        expect(xml_response['healthy']).to be false
    +        expect(xml_response['message']).to include('The server is on fire')
    +      end
    +
    +      it 'supports failure responses for specific checks' do
    +        get :index, token: token, checks: 'email', format: :json
    +        expect(response.status).to eq(500)
    +        expect(response.content_type).to eq 'application/json'
    +        expect(json_response['healthy']).to be false
    +        expect(json_response['message']).to include('Email is on fire')
    +      end
    +    end
    +  end
    +end
    diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
    new file mode 100644
    index 0000000000..4fde04b609
    --- /dev/null
    +++ b/spec/features/admin/admin_health_check_spec.rb
    @@ -0,0 +1,55 @@
    +require 'spec_helper'
    +
    +feature "Admin Health Check", feature: true do
    +  include WaitForAjax
    +
    +  before do
    +    login_as :admin
    +  end
    +
    +  describe '#show' do
    +    before do
    +      visit admin_health_check_path
    +    end
    +
    +    it { page.has_text? 'Health Check' }
    +    it { page.has_text? 'Health information can be reteived' }
    +
    +    it 'has a health check access token' do
    +      token = current_application_settings.health_check_access_token
    +      expect(page).to have_content("Access token is #{token}")
    +      expect(page).to have_selector('#health-check-token', text: token)
    +    end
    +
    +    describe 'reload access token', js: true do
    +      it 'changes the access token' do
    +        orig_token = current_application_settings.health_check_access_token
    +        click_button 'Reset health check access token'
    +        wait_for_ajax
    +        expect(find('#health-check-token').text).not_to eq orig_token
    +      end
    +    end
    +  end
    +
    +  context 'when services are up' do
    +    before do
    +      visit admin_health_check_path
    +    end
    +
    +    it 'shows healthy status' do
    +      expect(page).to have_content('Current Status: Healthy')
    +    end
    +  end
    +
    +  context 'when a service is down' do
    +    before do
    +      allow(HealthCheck::Utils).to receive(:process_checks).and_return('The server is on fire')
    +      visit admin_health_check_path
    +    end
    +
    +    it 'shows unhealthy status' do
    +      expect(page).to have_content('Current Status: Unhealthy')
    +      expect(page).to have_content('The server is on fire')
    +    end
    +  end
    +end
    diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
    index cd16a8e632..b5ed8584c8 100644
    --- a/spec/routing/admin_routing_spec.rb
    +++ b/spec/routing/admin_routing_spec.rb
    @@ -118,3 +118,10 @@ describe Admin::DashboardController, "routing" do
         expect(get("/admin")).to route_to('admin/dashboard#index')
       end
     end
    +
    +# admin_health_check GET    /admin/health_check(.:format) admin/health_check#show
    +describe Admin::HealthCheckController, "routing" do
    +  it "to #show" do
    +    expect(get("/admin/health_check")).to route_to('admin/health_check#show')
    +  end
    +end
    diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
    index 1527eddfa4..e4dfd4bca3 100644
    --- a/spec/routing/routing_spec.rb
    +++ b/spec/routing/routing_spec.rb
    @@ -243,3 +243,13 @@ describe "Groups", "routing" do
         expect(get('/1')).to route_to('namespaces#show', id: '1')
       end
     end
    +
    +describe HealthCheckController, 'routing' do
    +  it 'to #index' do
    +    expect(get('/health_check')).to route_to('health_check#index')
    +  end
    +
    +  it 'also supports passing checks in the url' do
    +    expect(get('/health_check/email')).to route_to('health_check#index', checks: 'email')
    +  end
    +end
    
    From 377583a361988d9884808a3c3f5375497a9d5b56 Mon Sep 17 00:00:00 2001
    From: Connor Shea 
    Date: Wed, 27 Apr 2016 16:52:32 -0600
    Subject: [PATCH 071/152] Enable Rubocop Casecmp Performance Cop.
    
    Also fixes the errors caused by enabling the cop. casecmp is more performant than `.downcase` and `==`.
    ---
     .rubocop.yml                | 3 +--
     app/models/repository.rb    | 2 +-
     lib/gitlab/database.rb      | 4 ++--
     lib/gitlab/markup_helper.rb | 2 +-
     4 files changed, 5 insertions(+), 6 deletions(-)
    
    diff --git a/.rubocop.yml b/.rubocop.yml
    index 9f179efa3c..f07d2184be 100644
    --- a/.rubocop.yml
    +++ b/.rubocop.yml
    @@ -937,10 +937,9 @@ Lint/Void:
     
     ##################### Performance ############################
     
    -# TODO: Enable Casecmp Cop.
     # Use `casecmp` rather than `downcase ==`.
     Performance/Casecmp:
    -  Enabled: false
    +  Enabled: true
     
     # TODO: Enable DoubleStartEndWith Cop.
     # Use `str.{start,end}_with?(x, ..., y, ...)` instead of
    diff --git a/app/models/repository.rb b/app/models/repository.rb
    index 7aebfe279f..5f562750d1 100644
    --- a/app/models/repository.rb
    +++ b/app/models/repository.rb
    @@ -453,7 +453,7 @@ class Repository
       def version
         cache.fetch(:version) do
           tree(:head).blobs.find do |file|
    -        file.name.downcase == 'version'
    +        file.name.casecmp('version').zero?
           end
         end
       end
    diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
    index 6f9da69983..42bec913a4 100644
    --- a/lib/gitlab/database.rb
    +++ b/lib/gitlab/database.rb
    @@ -5,11 +5,11 @@ module Gitlab
         end
     
         def self.mysql?
    -      adapter_name.downcase == 'mysql2'
    +      adapter_name.casecmp('mysql2').zero?
         end
     
         def self.postgresql?
    -      adapter_name.downcase == 'postgresql'
    +      adapter_name.casecmp('postgresql').zero?
         end
     
         def self.version
    diff --git a/lib/gitlab/markup_helper.rb b/lib/gitlab/markup_helper.rb
    index a5f767b134..dda371e655 100644
    --- a/lib/gitlab/markup_helper.rb
    +++ b/lib/gitlab/markup_helper.rb
    @@ -40,7 +40,7 @@ module Gitlab
         # Returns boolean
         def plain?(filename)
           filename.downcase.end_with?('.txt') ||
    -        filename.downcase == 'readme'
    +        filename.casecmp('readme').zero?
         end
     
         def previewable?(filename)
    
    From 976a70522f47bd57f3545efe5d71a58bb2596649 Mon Sep 17 00:00:00 2001
    From: Connor Shea 
    Date: Sun, 8 May 2016 15:44:14 -0600
    Subject: [PATCH 072/152] Removes jquery-scrollto-rails.
    
    Instead the script is now included directly from the vendor directory.
    
    Resolves #17166.
    ---
     Gemfile                                      |   1 -
     Gemfile.lock                                 |   3 -
     vendor/assets/javascripts/jquery.scrollTo.js | 210 +++++++++++++++++++
     3 files changed, 210 insertions(+), 4 deletions(-)
     create mode 100755 vendor/assets/javascripts/jquery.scrollTo.js
    
    diff --git a/Gemfile b/Gemfile
    index 77d351419f..2854bf8a57 100644
    --- a/Gemfile
    +++ b/Gemfile
    @@ -218,7 +218,6 @@ gem 'gitlab_emoji',       '~> 0.3.0'
     gem 'gon',                '~> 6.0.1'
     gem 'jquery-atwho-rails', '~> 1.3.2'
     gem 'jquery-rails',       '~> 4.1.0'
    -gem 'jquery-scrollto-rails', '~> 1.4.3'
     gem 'jquery-ui-rails',    '~> 5.0.0'
     gem 'raphael-rails',      '~> 2.1.2'
     gem 'request_store',      '~> 1.3.0'
    diff --git a/Gemfile.lock b/Gemfile.lock
    index c02698bcca..bc47533e5b 100644
    --- a/Gemfile.lock
    +++ b/Gemfile.lock
    @@ -432,8 +432,6 @@ GEM
           rails-dom-testing (>= 1, < 3)
           railties (>= 4.2.0)
           thor (>= 0.14, < 2.0)
    -    jquery-scrollto-rails (1.4.3)
    -      railties (> 3.1, < 5.0)
         jquery-turbolinks (2.1.0)
           railties (>= 3.1.0)
           turbolinks
    @@ -953,7 +951,6 @@ DEPENDENCIES
       influxdb (~> 0.2)
       jquery-atwho-rails (~> 1.3.2)
       jquery-rails (~> 4.1.0)
    -  jquery-scrollto-rails (~> 1.4.3)
       jquery-turbolinks (~> 2.1.0)
       jquery-ui-rails (~> 5.0.0)
       kaminari (~> 0.16.3)
    diff --git a/vendor/assets/javascripts/jquery.scrollTo.js b/vendor/assets/javascripts/jquery.scrollTo.js
    new file mode 100755
    index 0000000000..7ba17766b7
    --- /dev/null
    +++ b/vendor/assets/javascripts/jquery.scrollTo.js
    @@ -0,0 +1,210 @@
    +/*!
    + * jQuery.scrollTo
    + * Copyright (c) 2007-2015 Ariel Flesler - afleslergmailcom | http://flesler.blogspot.com
    + * Licensed under MIT
    + * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
    + * @projectDescription Lightweight, cross-browser and highly customizable animated scrolling with jQuery
    + * @author Ariel Flesler
    + * @version 2.1.2
    + */
    +;(function(factory) {
    +	'use strict';
    +	if (typeof define === 'function' && define.amd) {
    +		// AMD
    +		define(['jquery'], factory);
    +	} else if (typeof module !== 'undefined' && module.exports) {
    +		// CommonJS
    +		module.exports = factory(require('jquery'));
    +	} else {
    +		// Global
    +		factory(jQuery);
    +	}
    +})(function($) {
    +	'use strict';
    +
    +	var $scrollTo = $.scrollTo = function(target, duration, settings) {
    +		return $(window).scrollTo(target, duration, settings);
    +	};
    +
    +	$scrollTo.defaults = {
    +		axis:'xy',
    +		duration: 0,
    +		limit:true
    +	};
    +
    +	function isWin(elem) {
    +		return !elem.nodeName ||
    +			$.inArray(elem.nodeName.toLowerCase(), ['iframe','#document','html','body']) !== -1;
    +	}		
    +
    +	$.fn.scrollTo = function(target, duration, settings) {
    +		if (typeof duration === 'object') {
    +			settings = duration;
    +			duration = 0;
    +		}
    +		if (typeof settings === 'function') {
    +			settings = { onAfter:settings };
    +		}
    +		if (target === 'max') {
    +			target = 9e9;
    +		}
    +
    +		settings = $.extend({}, $scrollTo.defaults, settings);
    +		// Speed is still recognized for backwards compatibility
    +		duration = duration || settings.duration;
    +		// Make sure the settings are given right
    +		var queue = settings.queue && settings.axis.length > 1;
    +		if (queue) {
    +			// Let's keep the overall duration
    +			duration /= 2;
    +		}
    +		settings.offset = both(settings.offset);
    +		settings.over = both(settings.over);
    +
    +		return this.each(function() {
    +			// Null target yields nothing, just like jQuery does
    +			if (target === null) return;
    +
    +			var win = isWin(this),
    +				elem = win ? this.contentWindow || window : this,
    +				$elem = $(elem),
    +				targ = target, 
    +				attr = {},
    +				toff;
    +
    +			switch (typeof targ) {
    +				// A number will pass the regex
    +				case 'number':
    +				case 'string':
    +					if (/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(targ)) {
    +						targ = both(targ);
    +						// We are done
    +						break;
    +					}
    +					// Relative/Absolute selector
    +					targ = win ? $(targ) : $(targ, elem);
    +					/* falls through */
    +				case 'object':
    +					if (targ.length === 0) return;
    +					// DOMElement / jQuery
    +					if (targ.is || targ.style) {
    +						// Get the real position of the target
    +						toff = (targ = $(targ)).offset();
    +					}
    +			}
    +
    +			var offset = $.isFunction(settings.offset) && settings.offset(elem, targ) || settings.offset;
    +
    +			$.each(settings.axis.split(''), function(i, axis) {
    +				var Pos	= axis === 'x' ? 'Left' : 'Top',
    +					pos = Pos.toLowerCase(),
    +					key = 'scroll' + Pos,
    +					prev = $elem[key](),
    +					max = $scrollTo.max(elem, axis);
    +
    +				if (toff) {// jQuery / DOMElement
    +					attr[key] = toff[pos] + (win ? 0 : prev - $elem.offset()[pos]);
    +
    +					// If it's a dom element, reduce the margin
    +					if (settings.margin) {
    +						attr[key] -= parseInt(targ.css('margin'+Pos), 10) || 0;
    +						attr[key] -= parseInt(targ.css('border'+Pos+'Width'), 10) || 0;
    +					}
    +
    +					attr[key] += offset[pos] || 0;
    +
    +					if (settings.over[pos]) {
    +						// Scroll to a fraction of its width/height
    +						attr[key] += targ[axis === 'x'?'width':'height']() * settings.over[pos];
    +					}
    +				} else {
    +					var val = targ[pos];
    +					// Handle percentage values
    +					attr[key] = val.slice && val.slice(-1) === '%' ?
    +						parseFloat(val) / 100 * max
    +						: val;
    +				}
    +
    +				// Number or 'number'
    +				if (settings.limit && /^\d+$/.test(attr[key])) {
    +					// Check the limits
    +					attr[key] = attr[key] <= 0 ? 0 : Math.min(attr[key], max);
    +				}
    +
    +				// Don't waste time animating, if there's no need.
    +				if (!i && settings.axis.length > 1) {
    +					if (prev === attr[key]) {
    +						// No animation needed
    +						attr = {};
    +					} else if (queue) {
    +						// Intermediate animation
    +						animate(settings.onAfterFirst);
    +						// Don't animate this axis again in the next iteration.
    +						attr = {};
    +					}
    +				}
    +			});
    +
    +			animate(settings.onAfter);
    +
    +			function animate(callback) {
    +				var opts = $.extend({}, settings, {
    +					// The queue setting conflicts with animate()
    +					// Force it to always be true
    +					queue: true,
    +					duration: duration,
    +					complete: callback && function() {
    +						callback.call(elem, targ, settings);
    +					}
    +				});
    +				$elem.animate(attr, opts);
    +			}
    +		});
    +	};
    +
    +	// Max scrolling position, works on quirks mode
    +	// It only fails (not too badly) on IE, quirks mode.
    +	$scrollTo.max = function(elem, axis) {
    +		var Dim = axis === 'x' ? 'Width' : 'Height',
    +			scroll = 'scroll'+Dim;
    +
    +		if (!isWin(elem))
    +			return elem[scroll] - $(elem)[Dim.toLowerCase()]();
    +
    +		var size = 'client' + Dim,
    +			doc = elem.ownerDocument || elem.document,
    +			html = doc.documentElement,
    +			body = doc.body;
    +
    +		return Math.max(html[scroll], body[scroll]) - Math.min(html[size], body[size]);
    +	};
    +
    +	function both(val) {
    +		return $.isFunction(val) || $.isPlainObject(val) ? val : { top:val, left:val };
    +	}
    +
    +	// Add special hooks so that window scroll properties can be animated
    +	$.Tween.propHooks.scrollLeft = 
    +	$.Tween.propHooks.scrollTop = {
    +		get: function(t) {
    +			return $(t.elem)[t.prop]();
    +		},
    +		set: function(t) {
    +			var curr = this.get(t);
    +			// If interrupt is true and user scrolled, stop animating
    +			if (t.options.interrupt && t._last && t._last !== curr) {
    +				return $(t.elem).stop();
    +			}
    +			var next = Math.round(t.now);
    +			// Don't waste CPU
    +			// Browsers don't render floating point scroll
    +			if (curr !== next) {
    +				$(t.elem)[t.prop](next);
    +				t._last = this.get(t);
    +			}
    +		}
    +	};
    +
    +	// AMD requirement
    +	return $scrollTo;
    +});
    
    From 4995302df64b71846370aa80f5b12a2d73112421 Mon Sep 17 00:00:00 2001
    From: Connor Shea 
    Date: Wed, 27 Apr 2016 18:22:46 -0600
    Subject: [PATCH 073/152] Enable the StringReplacement cop.
    
    Also fix one use of `gsub` that would be faster as `delete`.
    
    Use `tr` instead of `gsub` when you are replacing the same number of
    characters. Use `delete` instead of `gsub` when you are deleting
    characters.
    ---
     .rubocop.yml              | 3 ++-
     lib/gitlab/diff/parser.rb | 2 +-
     2 files changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/.rubocop.yml b/.rubocop.yml
    index 9f179efa3c..c96c7123c1 100644
    --- a/.rubocop.yml
    +++ b/.rubocop.yml
    @@ -990,11 +990,12 @@ Performance/RedundantSortBy:
     # string.
     Performance/StartWith:
       Enabled: false
    +
     # Use `tr` instead of `gsub` when you are replacing the same number of
     # characters. Use `delete` instead of `gsub` when you are deleting
     # characters.
     Performance/StringReplacement:
    -  Enabled: false
    +  Enabled: true
     
     # TODO: Enable TimesMap Cop.
     # Checks for `.times.map` calls.
    diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb
    index d0815fc7ee..6fe7faa547 100644
    --- a/lib/gitlab/diff/parser.rb
    +++ b/lib/gitlab/diff/parser.rb
    @@ -18,7 +18,7 @@ module Gitlab
               @lines.each do |line|
                 next if filename?(line)
       
    -            full_line = line.gsub(/\n/, '')
    +            full_line = line.delete("\n")
       
                 if line.match(/^@@ -/)
                   type = "match"
    
    From b42f39d2c27ec2a87e21dea7414d9845f2512144 Mon Sep 17 00:00:00 2001
    From: Connor Shea 
    Date: Thu, 28 Apr 2016 19:44:56 -0600
    Subject: [PATCH 074/152] Remove activerecord-deprecated_finders gem
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    We don’t use any of the deprecated finders, so it should be safe to
    remove.
    
    Resolves #17015.
    ---
     Gemfile      | 1 -
     Gemfile.lock | 2 --
     2 files changed, 3 deletions(-)
    
    diff --git a/Gemfile b/Gemfile
    index 77d351419f..8de9602510 100644
    --- a/Gemfile
    +++ b/Gemfile
    @@ -324,7 +324,6 @@ gem "mail_room", "~> 0.7"
     gem 'email_reply_parser', '~> 0.5.8'
     
     ## CI
    -gem 'activerecord-deprecated_finders', '~> 1.0.3'
     gem 'activerecord-session_store', '~> 0.1.0'
     gem "nested_form", '~> 0.3.2'
     
    diff --git a/Gemfile.lock b/Gemfile.lock
    index c02698bcca..45d6de18f2 100644
    --- a/Gemfile.lock
    +++ b/Gemfile.lock
    @@ -33,7 +33,6 @@ GEM
           activemodel (= 4.2.6)
           activesupport (= 4.2.6)
           arel (~> 6.0)
    -    activerecord-deprecated_finders (1.0.4)
         activerecord-session_store (0.1.2)
           actionpack (>= 4.0.0, < 5)
           activerecord (>= 4.0.0, < 5)
    @@ -883,7 +882,6 @@ PLATFORMS
     DEPENDENCIES
       RedCloth (~> 4.2.9)
       ace-rails-ap (~> 4.0.2)
    -  activerecord-deprecated_finders (~> 1.0.3)
       activerecord-session_store (~> 0.1.0)
       acts-as-taggable-on (~> 3.4)
       addressable (~> 2.3.8)
    
    From 51e198fb38428387a976385cae2d5e4fc455ee9e Mon Sep 17 00:00:00 2001
    From: Connor Shea 
    Date: Tue, 10 May 2016 21:56:40 -0600
    Subject: [PATCH 075/152] Don't show the Todo count if there aren't any Todos.
    
    When there are 0 Todos, the Todos icon still had the blue badge with a "0" in it, which made me think I had a new Todo left. This makes it less annoying.
    
    Follow-up on !4077.
    ---
     app/views/layouts/header/_default.html.haml | 5 +++--
     1 file changed, 3 insertions(+), 2 deletions(-)
    
    diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
    index 172579dafd..c33740e23f 100644
    --- a/app/views/layouts/header/_default.html.haml
    +++ b/app/views/layouts/header/_default.html.haml
    @@ -27,8 +27,9 @@
                 %li
                   = link_to dashboard_todos_path, title: 'Todos', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
                     = icon('bell fw')
    -                %span.badge.todos-pending-count
    -                  = todos_pending_count
    +                - unless todos_pending_count == 0
    +                  %span.badge.todos-pending-count
    +                    = todos_pending_count
                 - if current_user.can_create_project?
                   %li
                     = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do
    
    From f2548f3f2b3dcd1c6ecbae20af28933f3ab88fdd Mon Sep 17 00:00:00 2001
    From: Stan Hu 
    Date: Tue, 10 May 2016 22:07:13 -0700
    Subject: [PATCH 076/152] Improve log message when a project is destroyed to
     include the namespace
    
    ---
     app/services/projects/destroy_service.rb | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
    index 19aab999e0..48a6131b44 100644
    --- a/app/services/projects/destroy_service.rb
    +++ b/app/services/projects/destroy_service.rb
    @@ -35,7 +35,7 @@ module Projects
             end
           end
     
    -      log_info("Project \"#{project.name}\" was removed")
    +      log_info("Project \"#{project.path_with_namespace}\" was removed")
           system_hook_service.execute_hooks_for(project, :destroy)
           true
         end
    
    From acd8930c46c06312f87c5ea0bb549b5435feece8 Mon Sep 17 00:00:00 2001
    From: Sean McGivern 
    Date: Wed, 11 May 2016 09:03:23 +0100
    Subject: [PATCH 077/152] Tidy up user project specs
    
    ---
     app/models/user.rb       |  2 +-
     spec/models/user_spec.rb | 21 ++++++++-------------
     2 files changed, 9 insertions(+), 14 deletions(-)
    
    diff --git a/app/models/user.rb b/app/models/user.rb
    index a0115957f0..489bff3fa4 100644
    --- a/app/models/user.rb
    +++ b/app/models/user.rb
    @@ -383,7 +383,7 @@ class User < ActiveRecord::Base
     
       def viewable_starred_projects
         starred_projects.where("projects.visibility_level IN (?) OR projects.id IN (#{projects_union.to_sql})",
    -                           [Gitlab::VisibilityLevel::PUBLIC, Gitlab::VisibilityLevel::INTERNAL])
    +                           [Project::PUBLIC, Project::INTERNAL])
       end
     
       def owned_projects
    diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
    index 06d1ca3b7d..10e7e69357 100644
    --- a/spec/models/user_spec.rb
    +++ b/spec/models/user_spec.rb
    @@ -233,8 +233,6 @@ describe User, models: true do
           @project = create :project, namespace: @user.namespace
           @project_2 = create :project, group: create(:group) # Grant MASTER access to the user
           @project_3 = create :project, group: create(:group) # Grant DEVELOPER access to the user
    -      @project_4 = create :project, group: create(:group)
    -      @project_5 = create :project, group: create(:group)
     
           @project_2.team << [@user, :master]
           @project_3.team << [@user, :developer]
    @@ -787,23 +785,20 @@ describe User, models: true do
     
       describe '#viewable_starred_projects' do
         let(:user) { create(:user) }
    -    let(:public_project) { create(:project, :public) }
    -    let(:private_project) { create(:project, :private) }
    -    let(:private_viewable_project) { create(:project, :private) }
    -    let(:viewable?) { -> (project) { user.can?(:read_project, project) } }
    -    let(:projects) { [public_project, private_project, private_viewable_project] }
    +    let(:public_project) { create(:empty_project, :public) }
    +    let(:private_project) { create(:empty_project, :private) }
    +    let(:private_viewable_project) { create(:empty_project, :private) }
     
         before do
           private_viewable_project.team << [user, Gitlab::Access::MASTER]
    -      projects.each { |project| user.toggle_star(project) }
    +
    +      [public_project, private_project, private_viewable_project].each do |project|
    +        user.toggle_star(project)
    +      end
         end
     
         it 'returns only starred projects the user can view' do
    -      expect(user.viewable_starred_projects).to all(satisfy(&viewable?))
    -    end
    -
    -    it 'rejects only starred projects the user can not view' do
    -      expect(projects - user.viewable_starred_projects).not_to include(satisfy(&viewable?))
    +      expect(user.viewable_starred_projects).not_to include(private_project)
         end
       end
     end
    
    From 5f27e26bb4d073c04fd6d3f4116fc1a122db8c00 Mon Sep 17 00:00:00 2001
    From: Sean McGivern 
    Date: Fri, 6 May 2016 13:16:53 +0100
    Subject: [PATCH 078/152] Only generate repository push email once
    
    The repository push email can be very expensive to generate, especially
    with syntax-highlighted diffs. Instead of generating the email for each
    recipient, generate one email object and reset the Message-Id and To
    headers for each recipient. (Cloning would also be expensive in the case
    of large emails, although probably not as bad as generating from
    scratch.)
    ---
     CHANGELOG                                     |  1 +
     app/mailers/emails/projects.rb                |  5 +-
     app/workers/emails_on_push_worker.rb          | 25 +++++--
     lib/gitlab/email/message/repository_push.rb   |  4 +-
     .../email/message/repository_push_spec.rb     |  2 +-
     spec/mailers/notify_spec.rb                   | 36 ++--------
     spec/workers/emails_on_push_worker_spec.rb    | 65 +++++++++++++++----
     7 files changed, 83 insertions(+), 55 deletions(-)
    
    diff --git a/CHANGELOG b/CHANGELOG
    index 1c21ad36b6..96643fc88a 100644
    --- a/CHANGELOG
    +++ b/CHANGELOG
    @@ -21,6 +21,7 @@ v 8.8.0 (unreleased)
       - Fix error when visiting commit builds page before build was updated
       - Add 'l' shortcut to open Label dropdown on issuables and 'i' to create new issue on a project
       - Update SVG sanitizer to conform to SVG 1.1
    +  - Speed up push emails with multiple recipients by only generating the email once
       - Updated search UI
       - Display informative message when new milestone is created
       - Sanitize milestones and labels titles
    diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
    index 377c2999d6..5489283432 100644
    --- a/app/mailers/emails/projects.rb
    +++ b/app/mailers/emails/projects.rb
    @@ -59,9 +59,9 @@ module Emails
                subject: subject("Project was moved"))
         end
     
    -    def repository_push_email(project_id, recipient, opts = {})
    +    def repository_push_email(project_id, opts = {})
           @message =
    -        Gitlab::Email::Message::RepositoryPush.new(self, project_id, recipient, opts)
    +        Gitlab::Email::Message::RepositoryPush.new(self, project_id, opts)
     
           # used in notify layout
           @target_url = @message.target_url
    @@ -72,7 +72,6 @@ module Emails
     
           mail(from:      sender(@message.author_id, @message.send_from_committer_email?),
                reply_to:  @message.reply_to,
    -           to:        @message.recipient,
                subject:   @message.subject)
         end
       end
    diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
    index c4d8595d45..6ebcba5f39 100644
    --- a/app/workers/emails_on_push_worker.rb
    +++ b/app/workers/emails_on_push_worker.rb
    @@ -1,6 +1,8 @@
     class EmailsOnPushWorker
       include Sidekiq::Worker
     
    +  attr_reader :email, :skip_premailer
    +
       def perform(project_id, recipients, push_data, options = {})
         options.symbolize_keys!
         options.reverse_merge!(
    @@ -41,11 +43,11 @@ class EmailsOnPushWorker
           end
         end
     
    -    recipients.split(" ").each do |recipient|
    +    recipients.split.each do |recipient|
           begin
    -        Notify.repository_push_email(
    -          project_id,
    +        send_email(
               recipient,
    +          project_id,
               author_id:                  author_id,
               ref:                        ref,
               action:                     action,
    @@ -53,14 +55,29 @@ class EmailsOnPushWorker
               reverse_compare:            reverse_compare,
               send_from_committer_email:  send_from_committer_email,
               disable_diffs:              disable_diffs
    -        ).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}")
           end
         end
       ensure
    +    @email = nil
         compare = nil
         GC.start
       end
    +
    +  private
    +
    +  def send_email(recipient, project_id, options)
    +    # Generating the body of this email can be expensive, so only do it once
    +    @skip_premailer ||= email.present?
    +    @email ||= Notify.repository_push_email(project_id, options)
    +
    +    email.to = recipient
    +    email.add_message_id
    +    email.header[:skip_premailer] = true if skip_premailer
    +    email.deliver_now
    +  end
     end
    diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb
    index 8f9be6cd9a..2c91a0487c 100644
    --- a/lib/gitlab/email/message/repository_push.rb
    +++ b/lib/gitlab/email/message/repository_push.rb
    @@ -2,7 +2,6 @@ module Gitlab
       module Email
         module Message
           class RepositoryPush
    -        attr_accessor :recipient
             attr_reader :author_id, :ref, :action
     
             include Gitlab::Routing.url_helpers
    @@ -11,13 +10,12 @@ module Gitlab
             delegate :name, to: :author, prefix: :author
             delegate :username, to: :author, prefix: :author
     
    -        def initialize(notify, project_id, recipient, opts = {})
    +        def initialize(notify, project_id, opts = {})
               raise ArgumentError, 'Missing options: author_id, ref, action' unless
                 opts[:author_id] && opts[:ref] && opts[:action]
     
               @notify = notify
               @project_id = project_id
    -          @recipient = recipient
               @opts = opts.dup
     
               @author_id = @opts.delete(:author_id)
    diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
    index b2d7a79981..7d6cce6dae 100644
    --- a/spec/lib/gitlab/email/message/repository_push_spec.rb
    +++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
    @@ -8,7 +8,7 @@ describe Gitlab::Email::Message::RepositoryPush do
       let!(:author) { create(:author, name: 'Author') }
     
       let(:message) do
    -    described_class.new(Notify, project.id, 'recipient@example.com', opts)
    +    described_class.new(Notify, project.id, opts)
       end
     
       context 'new commits have been pushed to repository' do
    diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
    index 495c5cbac0..5f7e4a526e 100644
    --- a/spec/mailers/notify_spec.rb
    +++ b/spec/mailers/notify_spec.rb
    @@ -593,7 +593,7 @@ describe Notify do
         let(:user) { create(:user) }
         let(:tree_path) { namespace_project_tree_path(project.namespace, project, "master") }
     
    -    subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :create) }
    +    subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :create) }
     
         it_behaves_like 'it should not have Gmail Actions links'
         it_behaves_like "a user cannot unsubscribe through footer link"
    @@ -606,10 +606,6 @@ describe Notify do
           expect(sender.address).to eq(gitlab_sender)
         end
     
    -    it 'is sent to recipient' do
    -      is_expected.to deliver_to 'devs@company.name'
    -    end
    -
         it 'has the correct subject' do
           is_expected.to have_subject /Pushed new branch master/
         end
    @@ -624,7 +620,7 @@ describe Notify do
         let(:user) { create(:user) }
         let(:tree_path) { namespace_project_tree_path(project.namespace, project, "v1.0") }
     
    -    subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
    +    subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :create) }
     
         it_behaves_like 'it should not have Gmail Actions links'
         it_behaves_like "a user cannot unsubscribe through footer link"
    @@ -637,10 +633,6 @@ describe Notify do
           expect(sender.address).to eq(gitlab_sender)
         end
     
    -    it 'is sent to recipient' do
    -      is_expected.to deliver_to 'devs@company.name'
    -    end
    -
         it 'has the correct subject' do
           is_expected.to have_subject /Pushed new tag v1\.0/
         end
    @@ -654,7 +646,7 @@ describe Notify do
         let(:example_site_path) { root_path }
         let(:user) { create(:user) }
     
    -    subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :delete) }
    +    subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :delete) }
     
         it_behaves_like 'it should not have Gmail Actions links'
         it_behaves_like "a user cannot unsubscribe through footer link"
    @@ -667,10 +659,6 @@ describe Notify do
           expect(sender.address).to eq(gitlab_sender)
         end
     
    -    it 'is sent to recipient' do
    -      is_expected.to deliver_to 'devs@company.name'
    -    end
    -
         it 'has the correct subject' do
           is_expected.to have_subject /Deleted branch master/
         end
    @@ -680,7 +668,7 @@ describe Notify do
         let(:example_site_path) { root_path }
         let(:user) { create(:user) }
     
    -    subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) }
    +    subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/tags/v1.0', action: :delete) }
     
         it_behaves_like 'it should not have Gmail Actions links'
         it_behaves_like "a user cannot unsubscribe through footer link"
    @@ -693,10 +681,6 @@ describe Notify do
           expect(sender.address).to eq(gitlab_sender)
         end
     
    -    it 'is sent to recipient' do
    -      is_expected.to deliver_to 'devs@company.name'
    -    end
    -
         it 'has the correct subject' do
           is_expected.to have_subject /Deleted tag v1\.0/
         end
    @@ -710,7 +694,7 @@ describe Notify do
         let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) }
         let(:send_from_committer_email) { false }
     
    -    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) }
    +    subject { Notify.repository_push_email(project.id, 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_behaves_like "a user cannot unsubscribe through footer link"
    @@ -723,10 +707,6 @@ describe Notify do
           expect(sender.address).to eq(gitlab_sender)
         end
     
    -    it 'is sent to recipient' do
    -      is_expected.to deliver_to 'devs@company.name'
    -    end
    -
         it 'has the correct subject' do
           is_expected.to have_subject /\[#{project.path_with_namespace}\]\[master\] #{commits.length} commits:/
         end
    @@ -818,7 +798,7 @@ describe Notify do
         let(:commits) { Commit.decorate(compare.commits, nil) }
         let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) }
     
    -    subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) }
    +    subject { Notify.repository_push_email(project.id, author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) }
     
         it_behaves_like 'it should show Gmail Actions View Commit link'
         it_behaves_like "a user cannot unsubscribe through footer link"
    @@ -831,10 +811,6 @@ describe Notify do
           expect(sender.address).to eq(gitlab_sender)
         end
     
    -    it 'is sent to recipient' do
    -      is_expected.to deliver_to 'devs@company.name'
    -    end
    -
         it 'has the correct subject' do
           is_expected.to have_subject /#{commits.first.title}/
         end
    diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb
    index 3600c77107..439da765c2 100644
    --- a/spec/workers/emails_on_push_worker_spec.rb
    +++ b/spec/workers/emails_on_push_worker_spec.rb
    @@ -6,29 +6,66 @@ describe EmailsOnPushWorker do
       let(:project) { create(:project) }
       let(:user) { create(:user) }
       let(:data) { Gitlab::PushDataBuilder.build_sample(project, user) }
    +  let(:recipients) { user.email }
    +  let(:perform) { subject.perform(project.id, recipients, data.stringify_keys) }
     
       subject { EmailsOnPushWorker.new }
     
    -  before do
    -    allow(Project).to receive(:find).and_return(project)
    -  end
    -
       describe "#perform" do
    -    it "sends mail" do
    -      subject.perform(project.id, user.email, data.stringify_keys)
    +    context "when there are no errors in sending" do
    +      let(:email) { ActionMailer::Base.deliveries.last }
     
    -      email = ActionMailer::Base.deliveries.last
    -      expect(email.subject).to include('Change some files')
    -      expect(email.to).to eq([user.email])
    +      before { perform }
    +
    +      it "sends a mail with the correct subject" do
    +        expect(email.subject).to include('Change some files')
    +      end
    +
    +      it "sends the mail to the correct recipient" do
    +        expect(email.to).to eq([user.email])
    +      end
         end
     
    -    it "gracefully handles an input SMTP error" do
    -      ActionMailer::Base.deliveries.clear
    -      allow(Notify).to receive(:repository_push_email).and_raise(Net::SMTPFatalError)
    +    context "when there is an SMTP error" do
    +      before do
    +        ActionMailer::Base.deliveries.clear
    +        allow(Notify).to receive(:repository_push_email).and_raise(Net::SMTPFatalError)
    +        perform
    +      end
     
    -      subject.perform(project.id, user.email, data.stringify_keys)
    +      it "gracefully handles an input SMTP error" do
    +        expect(ActionMailer::Base.deliveries.count).to eq(0)
    +      end
    +    end
     
    -      expect(ActionMailer::Base.deliveries.count).to eq(0)
    +    context "when there are multiple recipients" do
    +      let(:recipients) do
    +        1.upto(5).map { |i| user.email.sub('@', "+#{i}@") }.join("\n")
    +      end
    +
    +      before do
    +        # This is a hack because we modify the mail object before sending, for efficency,
    +        # but the TestMailer adapter just appends the objects to an array. To clone a mail
    +        # object, create a new one!
    +        #   https://github.com/mikel/mail/issues/314#issuecomment-12750108
    +        allow_any_instance_of(Mail::TestMailer).to receive(:deliver!).and_wrap_original do |original, mail|
    +          original.call(Mail.new(mail.encoded))
    +        end
    +
    +        ActionMailer::Base.deliveries.clear
    +      end
    +
    +      it "sends the mail to each of the recipients" do
    +        perform
    +        expect(ActionMailer::Base.deliveries.count).to eq(5)
    +        expect(ActionMailer::Base.deliveries.map(&:to).flatten).to contain_exactly(*recipients.split)
    +      end
    +
    +      it "only generates the mail once" do
    +        expect(Notify).to receive(:repository_push_email).once.and_call_original
    +        expect(Premailer::Rails::CustomizedPremailer).to receive(:new).once.and_call_original
    +        perform
    +      end
         end
       end
     end
    
    From d9574a7b93de7e88c77ed1351650843749da60f0 Mon Sep 17 00:00:00 2001
    From: Sean McGivern 
    Date: Thu, 5 May 2016 10:00:31 +0100
    Subject: [PATCH 079/152] Group commits by date in server timezone
    
    `Time#to_date` just takes the (timezone-less) year, date, and month, and
    creates a new date from that. Because the commits in the list are
    grouped by date, rather than chunked when the date changes, a commit can
    be shown in the wrong order if its CommitDate has a timezone-less date
    that's different to other commits around it.
    
    Convert all CommitDates to the server timezone before grouping, as that
    will at least produce consistent results. Users can still see a
    timestamp on the commit that doesn't match the date it's grouped under,
    because the timestamp shown uses the user's local timezone, and the
    grouping uses the server's timezone, but that was an issue anyway.
    ---
     CHANGELOG                                     | 1 +
     app/views/projects/commits/_commits.html.haml | 2 +-
     2 files changed, 2 insertions(+), 1 deletion(-)
    
    diff --git a/CHANGELOG b/CHANGELOG
    index e989e622b9..ab41545548 100644
    --- a/CHANGELOG
    +++ b/CHANGELOG
    @@ -19,6 +19,7 @@ v 8.8.0 (unreleased)
       - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718
       - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes)
       - Added multiple colors for labels in dropdowns when dups happen.
    +  - Always group commits by server timezone, not commit timestamp
       - Improve description for the Two-factor Authentication sign-in screen. (Connor Shea)
       - API support for the 'since' and 'until' operators on commit requests (Paco Guzman)
       - Fix Gravatar hint in user profile when Gravatar is disabled. !3988 (Artem Sidorenko)
    diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
    index 64e8da9201..82f39e5928 100644
    --- a/app/views/projects/commits/_commits.html.haml
    +++ b/app/views/projects/commits/_commits.html.haml
    @@ -3,7 +3,7 @@
     
     - commits, hidden = limited_commits(@commits)
     
    -- commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits|
    +- commits.group_by { |c| c.committed_date.in_time_zone.to_date }.sort.reverse.each do |day, commits|
       .row.commits-row
         .col-md-2.hidden-xs.hidden-sm
           %h5.commits-row-date
    
    From 941301339dd8d1e282c3e225808875ef86b45f2b Mon Sep 17 00:00:00 2001
    From: =?UTF-8?q?R=C3=A9my=20Coutable?= 
    Date: Wed, 11 May 2016 12:32:48 +0200
    Subject: [PATCH 080/152] Remove Rails monkey-patches now that we're using
     Rails 4.2.6
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    Signed-off-by: Rémy Coutable 
    ---
     config/initializers/monkey_patch.rb | 48 -----------------------------
     1 file changed, 48 deletions(-)
     delete mode 100644 config/initializers/monkey_patch.rb
    
    diff --git a/config/initializers/monkey_patch.rb b/config/initializers/monkey_patch.rb
    deleted file mode 100644
    index 62b05a5528..0000000000
    --- a/config/initializers/monkey_patch.rb
    +++ /dev/null
    @@ -1,48 +0,0 @@
    -## This patch is from rails 4.2-stable. Remove it when 4.2.6 is released
    -## https://github.com/rails/rails/issues/21108
    -
    -module ActiveRecord
    -  module ConnectionAdapters
    -    class AbstractMysqlAdapter < AbstractAdapter
    -      # SHOW VARIABLES LIKE 'name'
    -      def show_variable(name)
    -        variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
    -        variables.first['Value'] unless variables.empty?
    -      rescue ActiveRecord::StatementInvalid
    -        nil
    -      end
    -
    -      
    -      # MySQL is too stupid to create a temporary table for use subquery, so we have
    -      # to give it some prompting in the form of a subsubquery. Ugh!
    -      def subquery_for(key, select)
    -        subsubselect = select.clone
    -        subsubselect.projections = [key]
    -
    -        subselect = Arel::SelectManager.new(select.engine)
    -        subselect.project Arel.sql(key.name)
    -        # Materialized subquery by adding distinct
    -        # to work with MySQL 5.7.6 which sets optimizer_switch='derived_merge=on'
    -        subselect.from subsubselect.distinct.as('__active_record_temp')
    -      end
    -    end
    -  end
    -end
    -
    -module ActiveRecord
    -  module ConnectionAdapters
    -    class MysqlAdapter < AbstractMysqlAdapter
    -      ADAPTER_NAME = 'MySQL'.freeze
    -
    -      # Get the client encoding for this database
    -      def client_encoding
    -        return @client_encoding if @client_encoding
    -
    -        result = exec_query(
    -          "select @@character_set_client",
    -          'SCHEMA')
    -        @client_encoding = ENCODINGS[result.rows.last.last]
    -      end
    -    end
    -  end
    -end
    
    From a2934f45317ed64beb948b8487199251a987c4a1 Mon Sep 17 00:00:00 2001
    From: Grzegorz Bizon 
    Date: Wed, 11 May 2016 12:39:07 +0200
    Subject: [PATCH 081/152] Use % notation for arrays in runner model constants
    
    ---
     app/models/ci/runner.rb | 4 ++--
     1 file changed, 2 insertions(+), 2 deletions(-)
    
    diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
    index d27963e8ec..e598a13286 100644
    --- a/app/models/ci/runner.rb
    +++ b/app/models/ci/runner.rb
    @@ -22,8 +22,8 @@ module Ci
         extend Ci::Model
     
         LAST_CONTACT_TIME = 5.minutes.ago
    -    AVAILABLE_SCOPES = ['specific', 'shared', 'active', 'paused', 'online']
    -    FORM_EDITABLE = [:description, :tag_list, :active]
    +    AVAILABLE_SCOPES = %w[specific shared active paused online]
    +    FORM_EDITABLE = %i[description tag_list active]
     
         has_many :builds, class_name: 'Ci::Build'
         has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject'
    
    From c446a86510a6586c69a306ae09e41053f20d11a1 Mon Sep 17 00:00:00 2001
    From: Yorick Peterse 
    Date: Wed, 11 May 2016 14:19:30 +0200
    Subject: [PATCH 082/152] Updated 8.7.4 CHANGELOG entries
    
    [ci skip]
    ---
     CHANGELOG | 10 +++++++---
     1 file changed, 7 insertions(+), 3 deletions(-)
    
    diff --git a/CHANGELOG b/CHANGELOG
    index 54c79551b1..caa0923573 100644
    --- a/CHANGELOG
    +++ b/CHANGELOG
    @@ -43,9 +43,13 @@ v 8.8.0 (unreleased)
       - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3
     
     v 8.7.4
    -  - Fix always showing build notification message when switching between merge requests
    -  - Links for Redmine issue references are generated correctly again (Benedikt Huss)
    -  - Fix an issue when filtering merge requests with more than one label. !3886
    +  - Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss)
    +  - Fix setting trusted proxies !3970
    +  - Fix BitBucket importer bug when throwing exceptions !3941
    +  - Use sign out path only if not empty !3989
    +  - Running rake gitlab:db:drop_tables now drops tables with cascade !4020
    +  - Running rake gitlab:db:drop_tables uses "IF EXISTS" as a precaution !4100
    +  - Use a case-insensitive comparison in sanitizing URI schemes
     
     v 8.7.3
       - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented
    
    From 973cd0fec7b69d1398a86df67d10f7c38a9a4ced Mon Sep 17 00:00:00 2001
    From: Valery Sizov 
    Date: Wed, 11 May 2016 18:08:17 +0300
    Subject: [PATCH 083/152] Update 8.8 guides
    
    ---
     doc/install/installation.md |   2 +-
     doc/update/8.7-to-8.8.md    | 154 ++++++++++++++++++++++++++++++++++++
     2 files changed, 155 insertions(+), 1 deletion(-)
     create mode 100644 doc/update/8.7-to-8.8.md
    
    diff --git a/doc/install/installation.md b/doc/install/installation.md
    index e3af302226..44ae0be406 100644
    --- a/doc/install/installation.md
    +++ b/doc/install/installation.md
    @@ -269,7 +269,7 @@ sudo usermod -aG redis git
     ### Clone the Source
     
         # Clone GitLab repository
    -    sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-7-stable gitlab
    +    sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-8-stable gitlab
     
     **Note:** You can change `8-7-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
     
    diff --git a/doc/update/8.7-to-8.8.md b/doc/update/8.7-to-8.8.md
    new file mode 100644
    index 0000000000..b4d9212289
    --- /dev/null
    +++ b/doc/update/8.7-to-8.8.md
    @@ -0,0 +1,154 @@
    +# From 8.7 to 8.8
    +
    +Make sure you view this update guide from the tag (version) of GitLab you would
    +like to install. In most cases this should be the highest numbered production
    +tag (without rc in it). You can select the tag in the version dropdown at the
    +top left corner of GitLab (below the menu bar).
    +
    +If the highest number stable branch is unclear please check the
    +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
    +guide links by version.
    +
    +### 1. Stop server
    +
    +    sudo service gitlab stop
    +
    +### 2. Backup
    +
    +```bash
    +cd /home/git/gitlab
    +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
    +```
    +
    +### 3. Get latest code
    +
    +```bash
    +sudo -u git -H git fetch --all
    +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
    +```
    +
    +For GitLab Community Edition:
    +
    +```bash
    +sudo -u git -H git checkout 8-8-stable
    +```
    +
    +OR
    +
    +For GitLab Enterprise Edition:
    +
    +```bash
    +sudo -u git -H git checkout 8-8-stable-ee
    +```
    +
    +### 4. Update gitlab-shell
    +
    +```bash
    +cd /home/git/gitlab-shell
    +sudo -u git -H git fetch --all --tags
    +sudo -u git -H git checkout v2.7.2
    +```
    +
    +### 5. Update gitlab-workhorse
    +
    +Install and compile gitlab-workhorse. This requires
    +[Go 1.5](https://golang.org/dl) which should already be on your system from
    +GitLab 8.1.
    +
    +```bash
    +cd /home/git/gitlab-workhorse
    +sudo -u git -H git fetch --all
    +sudo -u git -H git checkout v0.7.1
    +sudo -u git -H make
    +```
    +
    +### 6. Install libs, migrations, etc.
    +
    +```bash
    +cd /home/git/gitlab
    +
    +# MySQL installations (note: the line below states '--without postgres')
    +sudo -u git -H bundle install --without postgres development test --deployment
    +
    +# PostgreSQL installations (note: the line below states '--without mysql')
    +sudo -u git -H bundle install --without mysql development test --deployment
    +
    +# Optional: clean up old gems
    +sudo -u git -H bundle clean
    +
    +# Run database migrations
    +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
    +
    +# Clean up assets and cache
    +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
    +
    +```
    +
    +### 7. Update configuration files
    +
    +#### Git configuration
    +
    +Disable `git gc --auto` because GitLab runs `git gc` for us already.
    +
    +```sh
    +sudo -u git -H git config --global gc.auto 0
    +```
    +
    +#### Nginx configuration
    +
    +Ensure you're still up-to-date with the latest NGINX configuration changes:
    +
    +```sh
    +# For HTTPS configurations
    +git diff origin/8-7-stable:lib/support/nginx/gitlab-ssl origin/8-8-stable:lib/support/nginx/gitlab-ssl
    +
    +# For HTTP configurations
    +git diff origin/8-7-stable:lib/support/nginx/gitlab origin/8-8-stable:lib/support/nginx/gitlab
    +```
    +
    +If you are using Apache instead of NGINX please see the updated [Apache templates].
    +Also note that because Apache does not support upstreams behind Unix sockets you
    +will need to let gitlab-workhorse listen on a TCP port. You can do this
    +via [/etc/default/gitlab].
    +
    +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
    +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-8-stable/lib/support/init.d/gitlab.default.example#L37
    +
    +#### Init script
    +
    +Ensure you're still up-to-date with the latest init script changes:
    +
    +    sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
    +
    +### 8. Start application
    +
    +    sudo service gitlab start
    +    sudo service nginx restart
    +
    +### 9. 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
    +
    +To make sure you didn't miss anything run a more thorough check:
    +
    +    sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
    +
    +If all items are green, then congratulations, the upgrade is complete!
    +
    +## Things went south? Revert to previous version (8.6)
    +
    +### 1. Revert the code to the previous version
    +
    +Follow the [upgrade guide from 8.6 to 8.7](8.6-to-8.7.md), except for the
    +database migration (the backup is already migrated to the previous version).
    +
    +### 2. Restore from the backup
    +
    +```bash
    +cd /home/git/gitlab
    +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
    +```
    +
    +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
    
    From b523dec83dc832029c6e40daaeb121920bbcd394 Mon Sep 17 00:00:00 2001
    From: Robert Speicher 
    Date: Sun, 8 May 2016 14:11:28 -0400
    Subject: [PATCH 084/152] Remove unused `icon_for_event` helper
    
    ---
     app/helpers/events_helper.rb | 9 ---------
     1 file changed, 9 deletions(-)
    
    diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
    index 592bad8ba2..0bf328e7d1 100644
    --- a/app/helpers/events_helper.rb
    +++ b/app/helpers/events_helper.rb
    @@ -39,15 +39,6 @@ module EventsHelper
         end
       end
     
    -  def icon_for_event
    -    {
    -      EventFilter.push     => 'upload',
    -      EventFilter.merged   => 'check-square-o',
    -      EventFilter.comments => 'comments',
    -      EventFilter.team     => 'user',
    -    }
    -  end
    -
       def event_preposition(event)
         if event.push? || event.commented? || event.target
           "at"
    
    From c8f23bd2edc19f968446b149120df1f7798eb4b1 Mon Sep 17 00:00:00 2001
    From: DJ Mountney 
    Date: Wed, 11 May 2016 17:27:08 -0700
    Subject: [PATCH 085/152] Support token header for health check token, and
     general cleanup of the health_check feature.
    
    ---
     app/controllers/health_check_controller.rb    | 15 ++++++--
     app/views/admin/health_check/show.html.haml   | 36 +++++++++++--------
     config/initializers/health_check.rb           |  2 +-
     config/routes.rb                              |  2 +-
     .../health_check_controller_spec.rb           | 15 ++++++++
     5 files changed, 50 insertions(+), 20 deletions(-)
    
    diff --git a/app/controllers/health_check_controller.rb b/app/controllers/health_check_controller.rb
    index b974489836..037da7d2bc 100644
    --- a/app/controllers/health_check_controller.rb
    +++ b/app/controllers/health_check_controller.rb
    @@ -1,13 +1,22 @@
     class HealthCheckController < HealthCheck::HealthCheckController
       before_action :validate_health_check_access!
     
    -  protected
    +  private
     
       def validate_health_check_access!
    -    return render_404 unless params[:token].presence && params[:token] == current_application_settings.health_check_access_token
    +    render_404 unless token_valid?
    +  end
    +
    +  def token_valid?
    +    token = params[:token].presence || request.headers['TOKEN']
    +    token.present? &&
    +      ActiveSupport::SecurityUtils.variable_size_secure_compare(
    +        token,
    +        current_application_settings.health_check_access_token
    +      )
       end
     
       def render_404
    -    render file: Rails.root.join("public", "404"), layout: false, status: "404"
    +    render file: Rails.root.join('public', '404'), layout: false, status: '404'
       end
     end
    diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml
    index 23a931995a..ed7025f7a0 100644
    --- a/app/views/admin/health_check/show.html.haml
    +++ b/app/views/admin/health_check/show.html.haml
    @@ -2,29 +2,35 @@
     
     %h3.page-title
       Health Check
    -%p.light
    +.bs-callout.clearfix
    +  .pull-left
    +    %p
       Access token is
    -  %code{ id:'health-check-token' }= "#{current_application_settings.health_check_access_token}"
    +  %code#health-check-token= current_application_settings.health_check_access_token
    +  = button_to reset_health_check_token_admin_application_settings_path,
    +    method: :put, class: 'btn btn-default',
    +    data: { confirm: 'Are you sure you want to reset the health check token?' } do
    +    = icon('refresh')
    +    Reset health check access token
     %p.light
       Health information can be reteived as plain text, json, or xml using:
       %ul
         %li
    -      %code= "/health_check?token=#{current_application_settings.health_check_access_token}"
    +      %code= health_check_url(token:current_application_settings.health_check_access_token)
         %li
    -      %code= "/health_check.json?token=#{current_application_settings.health_check_access_token}"
    +      %code= health_check_url(token:current_application_settings.health_check_access_token, format: :json)
         %li
    -      %code= "/health_check.xml?token=#{current_application_settings.health_check_access_token}"
    +      %code= health_check_url(token:current_application_settings.health_check_access_token, format: :xml)
     
    -.bs-callout.clearfix
    -  .pull-left
    -    %p
    -      You can reset the health check access token by pressing the button below.
    -    %p
    -      = button_to reset_health_check_token_admin_application_settings_path,
    -        method: :put, class: 'btn btn-default',
    -        data: { confirm: 'Are you sure you want to reset the health check token?' } do
    -        = icon('refresh')
    -        Reset health check access token
    +%p.light
    +  You can also ask for the status of specific services:
    +  %ul
    +    %li
    +      %code= health_check_url(token:current_application_settings.health_check_access_token, checks: :cache)
    +    %li
    +      %code= health_check_url(token:current_application_settings.health_check_access_token, checks: :database)
    +    %li
    +      %code= health_check_url(token:current_application_settings.health_check_access_token, checks: :migrations)
     
     %hr
     .panel.panel-default
    diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb
    index a93e427303..79e2d23ab2 100644
    --- a/config/initializers/health_check.rb
    +++ b/config/initializers/health_check.rb
    @@ -1,3 +1,3 @@
     HealthCheck.setup do |config|
    -  config.standard_checks = [ 'database', 'migrations', 'cache' ]
    +  config.standard_checks = ['database', 'migrations', 'cache']
     end
    diff --git a/config/routes.rb b/config/routes.rb
    index c81bf294a5..f794a881f7 100644
    --- a/config/routes.rb
    +++ b/config/routes.rb
    @@ -74,7 +74,7 @@ Rails.application.routes.draw do
       end
     
       # Health check
    -  get 'health_check(/:checks)(.:format)'  => 'health_check#index'
    +  get 'health_check(/:checks)'  => 'health_check#index', as: :health_check
     
       # Enable Grack support
       mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post, :put]
    diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
    index 3b9cc5c98f..0d8a68bb51 100644
    --- a/spec/controllers/health_check_controller_spec.rb
    +++ b/spec/controllers/health_check_controller_spec.rb
    @@ -14,6 +14,13 @@ describe HealthCheckController do
         end
     
         context 'when services are up and an access token is provided' do
    +      it 'supports passing the token in the header' do
    +        request.headers['TOKEN'] = token
    +        get :index
    +        expect(response).to be_success
    +        expect(response.content_type).to eq 'text/plain'
    +      end
    +
           it 'supports successful plaintest response' do
             get :index, token: token
             expect(response).to be_success
    @@ -55,6 +62,14 @@ describe HealthCheckController do
             allow(HealthCheck::Utils).to receive(:process_checks).with('email').and_return('Email is on fire')
           end
     
    +      it 'supports passing the token in the header' do
    +        request.headers['TOKEN'] = token
    +        get :index
    +        expect(response.status).to eq(500)
    +        expect(response.content_type).to eq 'text/plain'
    +        expect(response.body).to include('The server is on fire')
    +      end
    +
           it 'supports failure plaintest response' do
             get :index, token: token
             expect(response.status).to eq(500)
    
    From a59ad3936a0bdbfd64d9c54af631a272317fe680 Mon Sep 17 00:00:00 2001
    From: Timothy Andrew 
    Date: Thu, 5 May 2016 15:38:01 +0530
    Subject: [PATCH 086/152] Add a spec for `WikiLinkFilter`
    
    - And fix behavior for non-file hierarchical links.
    ---
     lib/banzai/filter/wiki_link_filter.rb         | 11 ++-
     .../banzai/filter/wiki_link_filter_spec.rb    | 77 +++++++++++++++++++
     2 files changed, 85 insertions(+), 3 deletions(-)
     create mode 100644 spec/lib/banzai/filter/wiki_link_filter_spec.rb
    
    diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb
    index 06d10c9850..7dc771afd7 100644
    --- a/lib/banzai/filter/wiki_link_filter.rb
    +++ b/lib/banzai/filter/wiki_link_filter.rb
    @@ -25,7 +25,7 @@ module Banzai
           end
     
           def process_link_attr(html_attr)
    -        return if html_attr.blank? || file_reference?(html_attr)
    +        return if html_attr.blank? || file_reference?(html_attr) || hierarchical_link?(html_attr)
     
             uri = URI(html_attr.value)
             if uri.relative? && uri.path.present?
    @@ -40,12 +40,17 @@ module Banzai
             uri
           end
     
    +      def project_wiki
    +        context[:project_wiki]
    +      end
    +
           def file_reference?(html_attr)
             !File.extname(html_attr.value).blank?
           end
     
    -      def project_wiki
    -        context[:project_wiki]
    +      # Of the form `./link`, `../link`, or similar
    +      def hierarchical_link?(html_attr)
    +        html_attr.value[0] == '.'
           end
     
           def project_wiki_base_path
    diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
    new file mode 100644
    index 0000000000..56b1a26739
    --- /dev/null
    +++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
    @@ -0,0 +1,77 @@
    +require 'spec_helper'
    +
    +describe Banzai::Filter::WikiLinkFilter, lib: true do
    +  include FilterSpecHelper
    +
    +  let(:namespace) { build(:namespace, name: "wiki_link_ns") }
    +  let(:project)   { build(:empty_project, :public, name: "wiki_link_project", namespace: namespace) }
    +  let(:user) { double }
    +  let(:project_wiki) { ProjectWiki.new(project, user) }
    +
    +  describe "links within the wiki (relative)" do
    +    describe "hierarchical links to the current directory" do
    +      it "doesn't rewrite non-file links" do
    +        link = "Link to Page"
    +        filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +        expect(filtered_link.attribute('href').value).to eq('./page')
    +      end
    +
    +      it "doesn't rewrite file links" do
    +        link = "Link to Page"
    +        filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +        expect(filtered_link.attribute('href').value).to eq('./page.md')
    +      end
    +    end
    +
    +    describe "hierarchical links to the parent directory" do
    +      it "doesn't rewrite non-file links" do
    +        link = "Link to Page"
    +        filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +        expect(filtered_link.attribute('href').value).to eq('../page')
    +      end
    +
    +      it "doesn't rewrite file links" do
    +        link = "Link to Page"
    +        filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +        expect(filtered_link.attribute('href').value).to eq('../page.md')
    +      end
    +    end
    +
    +    describe "hierarchical links to a sub-directory" do
    +      it "doesn't rewrite non-file links" do
    +        link = "Link to Page"
    +        filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +        expect(filtered_link.attribute('href').value).to eq('./subdirectory/page')
    +      end
    +
    +      it "doesn't rewrite file links" do
    +        link = "Link to Page"
    +        filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +        expect(filtered_link.attribute('href').value).to eq('./subdirectory/page.md')
    +      end
    +    end
    +
    +    describe "non-hierarchical links" do
    +      it 'rewrites non-file links to be at the scope of the wiki root' do
    +        link = "Link to Page"
    +        filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +
    +        expect(filtered_link.attribute('href').value).to match('/wiki_link_ns/wiki_link_project/wikis/page')
    +      end
    +
    +      it "doesn't rewrite file links" do
    +        link = "Link to Page"
    +        filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +        expect(filtered_link.attribute('href').value).to eq('page.md')
    +      end
    +    end
    +  end
    +
    +  describe "links outside the wiki (absolute)" do
    +    it "doesn't rewrite links" do
    +      link = "Link to Page"
    +      filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +      expect(filtered_link.attribute('href').value).to eq('http://example.com/page')
    +    end
    +  end
    +end
    
    From a4ee7d25e398f9d2e2311703fbbcf5f6b9bdf728 Mon Sep 17 00:00:00 2001
    From: Timothy Andrew 
    Date: Mon, 9 May 2016 16:04:05 +0530
    Subject: [PATCH 087/152] Implement @rymai's feedback after review.
    
    - Separate 'exercise' and 'verify' steps of tests.
    - Use `build_stubbed` instead of `build`
    ---
     spec/lib/banzai/filter/wiki_link_filter_spec.rb | 12 ++++++++++--
     1 file changed, 10 insertions(+), 2 deletions(-)
    
    diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
    index 56b1a26739..185abbb210 100644
    --- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
    +++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
    @@ -3,8 +3,8 @@ require 'spec_helper'
     describe Banzai::Filter::WikiLinkFilter, lib: true do
       include FilterSpecHelper
     
    -  let(:namespace) { build(:namespace, name: "wiki_link_ns") }
    -  let(:project)   { build(:empty_project, :public, name: "wiki_link_project", namespace: namespace) }
    +  let(:namespace) { build_stubbed(:namespace, name: "wiki_link_ns") }
    +  let(:project)   { build_stubbed(:empty_project, :public, name: "wiki_link_project", namespace: namespace) }
       let(:user) { double }
       let(:project_wiki) { ProjectWiki.new(project, user) }
     
    @@ -13,12 +13,14 @@ describe Banzai::Filter::WikiLinkFilter, lib: true do
           it "doesn't rewrite non-file links" do
             link = "Link to Page"
             filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +
             expect(filtered_link.attribute('href').value).to eq('./page')
           end
     
           it "doesn't rewrite file links" do
             link = "Link to Page"
             filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +
             expect(filtered_link.attribute('href').value).to eq('./page.md')
           end
         end
    @@ -27,12 +29,14 @@ describe Banzai::Filter::WikiLinkFilter, lib: true do
           it "doesn't rewrite non-file links" do
             link = "Link to Page"
             filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +
             expect(filtered_link.attribute('href').value).to eq('../page')
           end
     
           it "doesn't rewrite file links" do
             link = "Link to Page"
             filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +
             expect(filtered_link.attribute('href').value).to eq('../page.md')
           end
         end
    @@ -41,12 +45,14 @@ describe Banzai::Filter::WikiLinkFilter, lib: true do
           it "doesn't rewrite non-file links" do
             link = "Link to Page"
             filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +
             expect(filtered_link.attribute('href').value).to eq('./subdirectory/page')
           end
     
           it "doesn't rewrite file links" do
             link = "Link to Page"
             filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +
             expect(filtered_link.attribute('href').value).to eq('./subdirectory/page.md')
           end
         end
    @@ -62,6 +68,7 @@ describe Banzai::Filter::WikiLinkFilter, lib: true do
           it "doesn't rewrite file links" do
             link = "Link to Page"
             filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +
             expect(filtered_link.attribute('href').value).to eq('page.md')
           end
         end
    @@ -71,6 +78,7 @@ describe Banzai::Filter::WikiLinkFilter, lib: true do
         it "doesn't rewrite links" do
           link = "Link to Page"
           filtered_link = filter(link, project_wiki: project_wiki).children[0]
    +
           expect(filtered_link.attribute('href').value).to eq('http://example.com/page')
         end
       end
    
    From 555610b619b552ca058a7656f373743db4d34765 Mon Sep 17 00:00:00 2001
    From: Timothy Andrew 
    Date: Tue, 10 May 2016 09:04:14 +0530
    Subject: [PATCH 088/152] Add CHANGELOG entry.
    
    ---
     CHANGELOG | 3 +++
     1 file changed, 3 insertions(+)
    
    diff --git a/CHANGELOG b/CHANGELOG
    index d5ab79602f..187011c601 100644
    --- a/CHANGELOG
    +++ b/CHANGELOG
    @@ -44,6 +44,9 @@ v 8.8.0 (unreleased)
       - Fix adding a todo for private group members (Ahmad Sherif)
       - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3
     
    +v 8.7.5
    +  - Fix relative links in wiki pages. !4050
    +
     v 8.7.4
       - Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss)
       - Fix setting trusted proxies !3970
    
    From 7db3fb5a0b6c07b65c61c920e18f056f6c127932 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Wed, 4 May 2016 13:05:34 +0300
    Subject: [PATCH 089/152] disabled_oauth_sign_in_sources column
    
    ---
     ...disabled_oauth_sign_in_sources_to_application_settings.rb | 5 +++++
     db/schema.rb                                                 | 1 +
     2 files changed, 6 insertions(+)
     create mode 100644 db/migrate/20160504091942_add_disabled_oauth_sign_in_sources_to_application_settings.rb
    
    diff --git a/db/migrate/20160504091942_add_disabled_oauth_sign_in_sources_to_application_settings.rb b/db/migrate/20160504091942_add_disabled_oauth_sign_in_sources_to_application_settings.rb
    new file mode 100644
    index 0000000000..facd33875b
    --- /dev/null
    +++ b/db/migrate/20160504091942_add_disabled_oauth_sign_in_sources_to_application_settings.rb
    @@ -0,0 +1,5 @@
    +class AddDisabledOauthSignInSourcesToApplicationSettings < ActiveRecord::Migration
    +  def change
    +    add_column :application_settings, :disabled_oauth_sign_in_sources, :text
    +  end
    +end
    diff --git a/db/schema.rb b/db/schema.rb
    index 71d953afe3..74facd1208 100644
    --- a/db/schema.rb
    +++ b/db/schema.rb
    @@ -80,6 +80,7 @@ ActiveRecord::Schema.define(version: 20160508194200) do
         t.boolean  "repository_checks_enabled",         default: false
         t.text     "shared_runners_text"
         t.integer  "metrics_packet_size",               default: 1
    +    t.text     "disabled_oauth_sign_in_sources"
       end
     
       create_table "audit_events", force: :cascade do |t|
    
    From ca288587256b8995105b601ef02850f388d59ff9 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Wed, 4 May 2016 13:06:18 +0300
    Subject: [PATCH 090/152] serialize the disabled_oauth_sign_in_sources
    
    ---
     app/models/application_setting.rb | 50 +++++++++++++++++++++++++++++++
     1 file changed, 50 insertions(+)
    
    diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
    index 7039db2d41..39f74b2082 100644
    --- a/app/models/application_setting.rb
    +++ b/app/models/application_setting.rb
    @@ -1,3 +1,52 @@
    +# == Schema Information
    +#
    +# Table name: application_settings
    +#
    +#  id                                :integer          not null, primary key
    +#  default_projects_limit            :integer
    +#  signup_enabled                    :boolean
    +#  signin_enabled                    :boolean
    +#  gravatar_enabled                  :boolean
    +#  sign_in_text                      :text
    +#  created_at                        :datetime
    +#  updated_at                        :datetime
    +#  home_page_url                     :string(255)
    +#  default_branch_protection         :integer          default(2)
    +#  restricted_visibility_levels      :text
    +#  version_check_enabled             :boolean          default(TRUE)
    +#  max_attachment_size               :integer          default(10), not null
    +#  default_project_visibility        :integer
    +#  default_snippet_visibility        :integer
    +#  default_group_visibility          :integer
    +#  restricted_signup_domains         :text
    +#  user_oauth_applications           :boolean          default(TRUE)
    +#  after_sign_out_path               :string(255)
    +#  session_expire_delay              :integer          default(10080), not null
    +#  import_sources                    :text
    +#  disabled_oauth_sign_in_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
    +#  runners_registration_token        :string
    +#  require_two_factor_authentication :boolean          default(FALSE)
    +#  two_factor_grace_period           :integer          default(48)
    +#  metrics_enabled                   :boolean          default(FALSE)
    +#  metrics_host                      :string           default("localhost")
    +#  metrics_username                  :string
    +#  metrics_password                  :string
    +#  metrics_pool_size                 :integer          default(16)
    +#  metrics_timeout                   :integer          default(10)
    +#  metrics_method_call_threshold     :integer          default(10)
    +#  recaptcha_enabled                 :boolean          default(FALSE)
    +#  recaptcha_site_key                :string
    +#  recaptcha_private_key             :string
    +#  metrics_port                      :integer          default(8089)
    +#  sentry_enabled                    :boolean          default(FALSE)
    +#  sentry_dsn                        :string
    +#  email_author_in_body              :boolean          default(FALSE)
    +#
    +
     class ApplicationSetting < ActiveRecord::Base
       include TokenAuthenticatable
       add_authentication_token_field :runners_registration_token
    @@ -6,6 +55,7 @@ class ApplicationSetting < ActiveRecord::Base
     
       serialize :restricted_visibility_levels
       serialize :import_sources
    +  serialize :disabled_oauth_sign_in_sources
       serialize :restricted_signup_domains, Array
       attr_accessor :restricted_signup_domains_raw
     
    
    From e5ddd6d21eb6125605d3f47ea5db884b54514796 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Wed, 4 May 2016 13:08:06 +0300
    Subject: [PATCH 091/152] disabled_oauth_sign_in_sources parameter handling in
     ApplicationSettingsController
    
    ---
     .../admin/application_settings_controller.rb         | 12 +++++++++++-
     1 file changed, 11 insertions(+), 1 deletion(-)
    
    diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
    index ec22548dde..cebc01a2af 100644
    --- a/app/controllers/admin/application_settings_controller.rb
    +++ b/app/controllers/admin/application_settings_controller.rb
    @@ -53,6 +53,15 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
           end
         end
     
    +    disabled_oauth_sign_in_sources = params[:application_setting][:disabled_oauth_sign_in_sources]
    +    if disabled_oauth_sign_in_sources.nil?
    +      params[:application_setting][:disabled_oauth_sign_in_sources] = []
    +    else
    +      disabled_oauth_sign_in_sources.map! do |source|
    +        source.to_str
    +      end
    +    end
    +
         params.require(:application_setting).permit(
           :default_projects_limit,
           :default_branch_protection,
    @@ -95,7 +104,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
           :repository_checks_enabled,
           :metrics_packet_size,
           restricted_visibility_levels: [],
    -      import_sources: []
    +      import_sources: [],
    +      disabled_oauth_sign_in_sources: []
         )
       end
     end
    
    From d8085d8e128e4ef50443f7a58bfbe7bf21565ab0 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Wed, 4 May 2016 13:32:33 +0300
    Subject: [PATCH 092/152] default value for disabled_oauth_sign_in_sources in
     create_from_defaults @ ApplicationSetting
    
    ---
     app/models/application_setting.rb | 1 +
     1 file changed, 1 insertion(+)
    
    diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
    index 39f74b2082..a48deccb02 100644
    --- a/app/models/application_setting.rb
    +++ b/app/models/application_setting.rb
    @@ -157,6 +157,7 @@ class ApplicationSetting < ActiveRecord::Base
           recaptcha_enabled: false,
           akismet_enabled: false,
           repository_checks_enabled: true,
    +      disabled_oauth_sign_in_sources: []
         )
       end
     
    
    From cf300443147f1a9ba3acf83ce839afdffa5173bd Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Wed, 4 May 2016 13:33:33 +0300
    Subject: [PATCH 093/152] OAuth Providers disable from the ApplicationSettings
     page
    
    ---
     app/helpers/application_settings_helper.rb        | 15 +++++++++++++++
     .../admin/application_settings/_form.html.haml    | 10 ++++++++++
     2 files changed, 25 insertions(+)
    
    diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
    index 914b0ef604..17d7389987 100644
    --- a/app/helpers/application_settings_helper.rb
    +++ b/app/helpers/application_settings_helper.rb
    @@ -60,4 +60,19 @@ module ApplicationSettingsHelper
           end
         end
       end
    +
    +  def oauth_providers_checkboxes(help_block_id)
    +    button_based_providers.map do |source|
    +      checked = current_application_settings.disabled_oauth_sign_in_sources.include?(source.to_s)
    +      css_class = 'btn'
    +      css_class += ' active' if checked
    +      checkbox_name = 'application_setting[disabled_oauth_sign_in_sources][]'
    +
    +      label_tag(checkbox_name, class: css_class) do
    +        check_box_tag(checkbox_name, source, checked,
    +                      autocomplete: 'off',
    +                      'aria-describedby' => help_block_id) + Gitlab::OAuth::Provider.label_for(source)
    +      end
    +    end
    +  end
     end
    diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
    index e0d8d16a95..057b237c06 100644
    --- a/app/views/admin/application_settings/_form.html.haml
    +++ b/app/views/admin/application_settings/_form.html.haml
    @@ -109,6 +109,16 @@
               = f.label :signin_enabled do
                 = f.check_box :signin_enabled
                 Sign-in enabled
    +    .form-group
    +      = f.label :disable_oauth_signin_sources, class: 'control-label col-sm-2'
    +      .col-sm-10
    +        - data_attrs = { toggle: 'buttons' }
    +        .btn-group{ data: data_attrs }
    +          - oauth_providers_checkboxes('oauth-providers-help').each do |source|
    +            = source
    +        %span.help-block#oauth-providers-help
    +          Enabled OmniAuth must be configured for GitHub
    +          = link_to "(?)", help_page_path("integration", "github")
         .form-group
           = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
           .col-sm-10
    
    From 8d26836e945f3e66a46370905b9fb34244a0ff91 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Wed, 4 May 2016 13:35:03 +0300
    Subject: [PATCH 094/152] method to get the enabled_button_based_providers
    
    ---
     app/helpers/auth_helper.rb | 6 ++++++
     1 file changed, 6 insertions(+)
    
    diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
    index b4f80fd9b3..24eb813d0b 100644
    --- a/app/helpers/auth_helper.rb
    +++ b/app/helpers/auth_helper.rb
    @@ -38,6 +38,12 @@ module AuthHelper
         auth_providers.reject { |provider| form_based_provider?(provider) }
       end
     
    +  def enabled_button_based_providers
    +    disabled_providers = current_application_settings.disabled_oauth_sign_in_sources || []
    +
    +    button_based_providers.map(&:to_s) - disabled_providers
    +  end
    +
       def provider_image_tag(provider, size = 64)
         label = label_for_provider(provider)
     
    
    From d943e5f691ad9c59e349eab95f1a21bd0f85ab1b Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Wed, 4 May 2016 13:36:15 +0300
    Subject: [PATCH 095/152] method to check if oauth button based providers are
     enabled
    
    ---
     app/helpers/auth_helper.rb | 4 ++++
     1 file changed, 4 insertions(+)
    
    diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
    index 24eb813d0b..3e1f420925 100644
    --- a/app/helpers/auth_helper.rb
    +++ b/app/helpers/auth_helper.rb
    @@ -44,6 +44,10 @@ module AuthHelper
         button_based_providers.map(&:to_s) - disabled_providers
       end
     
    +  def button_based_providers_enabled?
    +    !enabled_button_based_providers.empty?
    +  end
    +
       def provider_image_tag(provider, size = 64)
         label = label_for_provider(provider)
     
    
    From 284eccb5e68c863ea8e1001a24af33dd0da5e201 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Wed, 4 May 2016 13:36:57 +0300
    Subject: [PATCH 096/152] render the omniauth_box if there are any
     button_based_providers
    
    ---
     app/views/devise/sessions/new.html.haml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
    index d65fa60025..28194506ac 100644
    --- a/app/views/devise/sessions/new.html.haml
    +++ b/app/views/devise/sessions/new.html.haml
    @@ -4,7 +4,7 @@
         = render 'devise/shared/signin_box'
     
       -# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box
    -  - if omniauth_enabled? && devise_mapping.omniauthable?
    +  - if omniauth_enabled? && devise_mapping.omniauthable? && button_based_providers_enabled?
         .clearfix.prepend-top-20
           = render 'devise/shared/omniauth_box'
     
    
    From 28da3a88de339a95d49e171da87494f65219cb21 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Wed, 4 May 2016 13:37:18 +0300
    Subject: [PATCH 097/152] show only the eanbled oauth providers
    
    ---
     app/views/devise/shared/_omniauth_box.html.haml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml
    index ecf680e7b2..de18bc2d84 100644
    --- a/app/views/devise/shared/_omniauth_box.html.haml
    +++ b/app/views/devise/shared/_omniauth_box.html.haml
    @@ -1,7 +1,7 @@
     %p
       %span.light
         Sign in with  
    -  - providers = button_based_providers
    +  - providers = enabled_button_based_providers
       - providers.each do |provider|
         %span.light
           - has_icon = provider_has_icon?(provider)
    
    From 7c7c5b7e40ff4fed64a9f62beed027064c292eaf Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Wed, 4 May 2016 15:22:24 +0300
    Subject: [PATCH 098/152] text for the disable_oauth_signin_sources label
    
    ---
     app/views/admin/application_settings/_form.html.haml | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
    index 057b237c06..729c2ff563 100644
    --- a/app/views/admin/application_settings/_form.html.haml
    +++ b/app/views/admin/application_settings/_form.html.haml
    @@ -110,7 +110,7 @@
                 = f.check_box :signin_enabled
                 Sign-in enabled
         .form-group
    -      = f.label :disable_oauth_signin_sources, class: 'control-label col-sm-2'
    +      = f.label :disable_oauth_signin_sources, 'Disable OAuth Sign-In sources', class: 'control-label col-sm-2'
           .col-sm-10
             - data_attrs = { toggle: 'buttons' }
             .btn-group{ data: data_attrs }
    
    From 47ee5125e881694b7713f187b48589a2f4bbd747 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Wed, 4 May 2016 15:57:00 +0300
    Subject: [PATCH 099/152] validate disabled_oauth_sign_in_sources in
     ApplicationSe
    
    ---
     app/models/application_setting.rb       | 10 ++++++++++
     spec/models/application_setting_spec.rb |  3 +++
     2 files changed, 13 insertions(+)
    
    diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
    index a48deccb02..d565dbc71d 100644
    --- a/app/models/application_setting.rb
    +++ b/app/models/application_setting.rb
    @@ -119,6 +119,16 @@ class ApplicationSetting < ActiveRecord::Base
         end
       end
     
    +  validates_each :disabled_oauth_sign_in_sources do |record, attr, value|
    +    unless value.nil?
    +      value.each do |source|
    +        unless Devise.omniauth_providers.include?(source.to_sym)
    +          record.errors.add(attr, "'#{source}' is not an ouath sign-in source")
    +        end
    +      end
    +    end
    +  end
    +
       before_save :ensure_runners_registration_token
     
       after_commit do
    diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
    index 1ce22feed5..fb3ea491df 100644
    --- a/spec/models/application_setting_spec.rb
    +++ b/spec/models/application_setting_spec.rb
    @@ -20,6 +20,9 @@ describe ApplicationSetting, models: true do
         it { is_expected.to allow_value(https).for(:after_sign_out_path) }
         it { is_expected.not_to allow_value(ftp).for(:after_sign_out_path) }
     
    +    it { is_expected.to allow_value([:github]).for(:disabled_oauth_sign_in_sources) }
    +    it { is_expected.not_to allow_value([:test]).for(:disabled_oauth_sign_in_sources) }
    +
         it { is_expected.to validate_presence_of(:max_attachment_size) }
     
         it do
    
    From 8c2b72b1c80e8bbd082ac8e1aedb21a1aad07235 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Wed, 4 May 2016 17:04:54 +0300
    Subject: [PATCH 100/152] tests for enabled_button_based_providers helper
     method of AuthHelper
    
    ---
     spec/helpers/auth_helper_spec.rb | 22 +++++++++++++++++++++-
     1 file changed, 21 insertions(+), 1 deletion(-)
    
    diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
    index e47a54fdac..5d66c92d26 100644
    --- a/spec/helpers/auth_helper_spec.rb
    +++ b/spec/helpers/auth_helper_spec.rb
    @@ -2,7 +2,9 @@ require "spec_helper"
     
     describe AuthHelper do
       describe "button_based_providers" do
    -    it 'returns all enabled providers' do
    +    let(:settings) { ApplicationSetting.create_from_defaults }
    +
    +    it 'returns all enabled providers from devise' do
           allow(helper).to receive(:auth_providers) { [:twitter, :github] }
           expect(helper.button_based_providers).to include(*[:twitter, :github])
         end
    @@ -16,5 +18,23 @@ describe AuthHelper do
           allow(helper).to receive(:auth_providers) { [] }
           expect(helper.button_based_providers).to eq([])
         end
    +
    +    it 'returns all the enabled providers from settings' do
    +      allow(helper).to receive(:auth_providers) { [:twitter, :github] }
    +      expect(helper.enabled_button_based_providers).to include(*['twitter', 'github'])
    +    end
    +
    +    it 'should not return github as provider because it\'s disabled from settings' do
    +      settings.update_attribute(
    +        :disabled_oauth_sign_in_sources,
    +        ['github']
    +      )
    +
    +      allow(helper).to receive(:auth_providers) { [:twitter, :github] }
    +      allow(helper).to receive(:current_application_settings) {  settings }
    +
    +      expect(helper.enabled_button_based_providers).to include('twitter')
    +      expect(helper.enabled_button_based_providers).to_not include('github')
    +    end
       end
     end
    
    From 6f69f6a1ab70bd2252803a64927d0c8df1d03612 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Wed, 4 May 2016 17:06:07 +0300
    Subject: [PATCH 101/152] tests for button_based_providers_enabled? helper
     method of AuthHelper
    
    ---
     spec/helpers/auth_helper_spec.rb | 18 ++++++++++++++++++
     1 file changed, 18 insertions(+)
    
    diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
    index 5d66c92d26..a6a366bc19 100644
    --- a/spec/helpers/auth_helper_spec.rb
    +++ b/spec/helpers/auth_helper_spec.rb
    @@ -36,5 +36,23 @@ describe AuthHelper do
           expect(helper.enabled_button_based_providers).to include('twitter')
           expect(helper.enabled_button_based_providers).to_not include('github')
         end
    +
    +    it 'returns true for button_based_providers_enabled? because there providers' do
    +      allow(helper).to receive(:auth_providers) { [:twitter, :github] }
    +
    +      expect(helper.button_based_providers_enabled?).to be true
    +    end
    +
    +    it 'returns false for button_based_providers_enabled? because there providers' do
    +      settings.update_attribute(
    +        :disabled_oauth_sign_in_sources,
    +        ['github', 'twitter']
    +      )
    +
    +      allow(helper).to receive(:auth_providers) { [:twitter, :github] }
    +      allow(helper).to receive(:current_application_settings) {  settings }
    +
    +      expect(helper.button_based_providers_enabled?).to be false
    +    end
       end
     end
    
    From deca3da1a7f4e59c97c27bedf7aa26185a4d883b Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Thu, 5 May 2016 10:34:51 +0300
    Subject: [PATCH 102/152] stub Devise.omniauth_providers to return GitHub even
     if the gitlab.yml has no omniauth provider enabled This will fix failing
     tests in case gitlab.yml file has no omniauth providers enabled
    
    ---
     spec/models/application_setting_spec.rb | 10 ++++++++--
     1 file changed, 8 insertions(+), 2 deletions(-)
    
    diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
    index fb3ea491df..d84f3e998f 100644
    --- a/spec/models/application_setting_spec.rb
    +++ b/spec/models/application_setting_spec.rb
    @@ -20,8 +20,14 @@ describe ApplicationSetting, models: true do
         it { is_expected.to allow_value(https).for(:after_sign_out_path) }
         it { is_expected.not_to allow_value(ftp).for(:after_sign_out_path) }
     
    -    it { is_expected.to allow_value([:github]).for(:disabled_oauth_sign_in_sources) }
    -    it { is_expected.not_to allow_value([:test]).for(:disabled_oauth_sign_in_sources) }
    +    describe 'disabled_oauth_sign_in_sources validations' do
    +      before do
    +        allow(Devise).to receive(:omniauth_providers).and_return([:github])
    +      end
    +
    +      it { is_expected.to allow_value(['github']).for(:disabled_oauth_sign_in_sources) }
    +      it { is_expected.not_to allow_value(['test']).for(:disabled_oauth_sign_in_sources) }
    +    end
     
         it { is_expected.to validate_presence_of(:max_attachment_size) }
     
    
    From 2e4c914ae88b77c8a3871f8415163a51e23254e5 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Mon, 9 May 2016 10:31:25 +0300
    Subject: [PATCH 103/152] between ""
    
    ---
     spec/helpers/auth_helper_spec.rb | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
    index a6a366bc19..6dc1135927 100644
    --- a/spec/helpers/auth_helper_spec.rb
    +++ b/spec/helpers/auth_helper_spec.rb
    @@ -24,7 +24,7 @@ describe AuthHelper do
           expect(helper.enabled_button_based_providers).to include(*['twitter', 'github'])
         end
     
    -    it 'should not return github as provider because it\'s disabled from settings' do
    +    it "should not return github as provider because it's disabled from settings" do
           settings.update_attribute(
             :disabled_oauth_sign_in_sources,
             ['github']
    
    From fc88527c9e21f99a9411423f1c8a6475957c02f3 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Mon, 9 May 2016 10:40:27 +0300
    Subject: [PATCH 104/152] use stub_application_setting instead
    
    ---
     spec/helpers/auth_helper_spec.rb | 14 ++++----------
     1 file changed, 4 insertions(+), 10 deletions(-)
    
    diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
    index 6dc1135927..e6af074a78 100644
    --- a/spec/helpers/auth_helper_spec.rb
    +++ b/spec/helpers/auth_helper_spec.rb
    @@ -2,8 +2,6 @@ require "spec_helper"
     
     describe AuthHelper do
       describe "button_based_providers" do
    -    let(:settings) { ApplicationSetting.create_from_defaults }
    -
         it 'returns all enabled providers from devise' do
           allow(helper).to receive(:auth_providers) { [:twitter, :github] }
           expect(helper.button_based_providers).to include(*[:twitter, :github])
    @@ -25,13 +23,11 @@ describe AuthHelper do
         end
     
         it "should not return github as provider because it's disabled from settings" do
    -      settings.update_attribute(
    -        :disabled_oauth_sign_in_sources,
    -        ['github']
    +      stub_application_setting(
    +        disabled_oauth_sign_in_sources: ['github']
           )
     
           allow(helper).to receive(:auth_providers) { [:twitter, :github] }
    -      allow(helper).to receive(:current_application_settings) {  settings }
     
           expect(helper.enabled_button_based_providers).to include('twitter')
           expect(helper.enabled_button_based_providers).to_not include('github')
    @@ -44,13 +40,11 @@ describe AuthHelper do
         end
     
         it 'returns false for button_based_providers_enabled? because there providers' do
    -      settings.update_attribute(
    -        :disabled_oauth_sign_in_sources,
    -        ['github', 'twitter']
    +      stub_application_setting(
    +        disabled_oauth_sign_in_sources: ['github', 'twitter']
           )
     
           allow(helper).to receive(:auth_providers) { [:twitter, :github] }
    -      allow(helper).to receive(:current_application_settings) {  settings }
     
           expect(helper.button_based_providers_enabled?).to be false
         end
    
    From 3922e7ee2249bb5a683b7474b281d8e83d465740 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Mon, 9 May 2016 10:42:57 +0300
    Subject: [PATCH 105/152] enabled_button_based_providers into their own
     describe section
    
    ---
     spec/helpers/auth_helper_spec.rb | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
    index e6af074a78..04b5128279 100644
    --- a/spec/helpers/auth_helper_spec.rb
    +++ b/spec/helpers/auth_helper_spec.rb
    @@ -16,7 +16,9 @@ describe AuthHelper do
           allow(helper).to receive(:auth_providers) { [] }
           expect(helper.button_based_providers).to eq([])
         end
    +  end
     
    +  describe 'enabled_button_based_providers' do
         it 'returns all the enabled providers from settings' do
           allow(helper).to receive(:auth_providers) { [:twitter, :github] }
           expect(helper.enabled_button_based_providers).to include(*['twitter', 'github'])
    
    From 038dbb6803948e050484ed3f34c99b2f3bf7a6c5 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Mon, 9 May 2016 10:51:24 +0300
    Subject: [PATCH 106/152] DRYing enabled_button_based_providers tests
    
    ---
     spec/helpers/auth_helper_spec.rb | 11 ++++-------
     1 file changed, 4 insertions(+), 7 deletions(-)
    
    diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
    index 04b5128279..a6df820421 100644
    --- a/spec/helpers/auth_helper_spec.rb
    +++ b/spec/helpers/auth_helper_spec.rb
    @@ -19,8 +19,11 @@ describe AuthHelper do
       end
     
       describe 'enabled_button_based_providers' do
    -    it 'returns all the enabled providers from settings' do
    +    before do
           allow(helper).to receive(:auth_providers) { [:twitter, :github] }
    +    end
    +
    +    it 'returns all the enabled providers from settings' do
           expect(helper.enabled_button_based_providers).to include(*['twitter', 'github'])
         end
     
    @@ -29,15 +32,11 @@ describe AuthHelper do
             disabled_oauth_sign_in_sources: ['github']
           )
     
    -      allow(helper).to receive(:auth_providers) { [:twitter, :github] }
    -
           expect(helper.enabled_button_based_providers).to include('twitter')
           expect(helper.enabled_button_based_providers).to_not include('github')
         end
     
         it 'returns true for button_based_providers_enabled? because there providers' do
    -      allow(helper).to receive(:auth_providers) { [:twitter, :github] }
    -
           expect(helper.button_based_providers_enabled?).to be true
         end
     
    @@ -46,8 +45,6 @@ describe AuthHelper do
             disabled_oauth_sign_in_sources: ['github', 'twitter']
           )
     
    -      allow(helper).to receive(:auth_providers) { [:twitter, :github] }
    -
           expect(helper.button_based_providers_enabled?).to be false
         end
       end
    
    From 3c3bc93747ebe5864a50d7b8203b357a40012ed4 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Mon, 9 May 2016 19:32:01 +0300
    Subject: [PATCH 107/152] if omniauth is enabled and if there are
     button_based_providers available show the disable OAuth sources
    
    ---
     .../application_settings/_form.html.haml      | 21 ++++++++++---------
     1 file changed, 11 insertions(+), 10 deletions(-)
    
    diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
    index 729c2ff563..81c232b7e9 100644
    --- a/app/views/admin/application_settings/_form.html.haml
    +++ b/app/views/admin/application_settings/_form.html.haml
    @@ -109,16 +109,17 @@
               = f.label :signin_enabled do
                 = f.check_box :signin_enabled
                 Sign-in enabled
    -    .form-group
    -      = f.label :disable_oauth_signin_sources, 'Disable OAuth Sign-In sources', class: 'control-label col-sm-2'
    -      .col-sm-10
    -        - data_attrs = { toggle: 'buttons' }
    -        .btn-group{ data: data_attrs }
    -          - oauth_providers_checkboxes('oauth-providers-help').each do |source|
    -            = source
    -        %span.help-block#oauth-providers-help
    -          Enabled OmniAuth must be configured for GitHub
    -          = link_to "(?)", help_page_path("integration", "github")
    +      - if omniauth_enabled? && !button_based_providers.empty?
    +        .form-group
    +          = f.label :disable_oauth_signin_sources, 'Disable OAuth Sign-In sources', class: 'control-label col-sm-2'
    +          .col-sm-10
    +            - data_attrs = { toggle: 'buttons' }
    +            .btn-group{ data: data_attrs }
    +              - oauth_providers_checkboxes('oauth-providers-help').each do |source|
    +                = source
    +            %span.help-block#oauth-providers-help
    +              Enabled OmniAuth must be configured for GitHub
    +              = link_to "(?)", help_page_path("integration", "github")
         .form-group
           = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
           .col-sm-10
    
    From dad501a2367a35f6d791efac9002e487176f2c9e Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Mon, 9 May 2016 19:34:44 +0300
    Subject: [PATCH 108/152] no need to show the help about Github or the other
     authentication sources
    
    ---
     app/views/admin/application_settings/_form.html.haml | 3 ---
     1 file changed, 3 deletions(-)
    
    diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
    index 81c232b7e9..99f86e5244 100644
    --- a/app/views/admin/application_settings/_form.html.haml
    +++ b/app/views/admin/application_settings/_form.html.haml
    @@ -117,9 +117,6 @@
                 .btn-group{ data: data_attrs }
                   - oauth_providers_checkboxes('oauth-providers-help').each do |source|
                     = source
    -            %span.help-block#oauth-providers-help
    -              Enabled OmniAuth must be configured for GitHub
    -              = link_to "(?)", help_page_path("integration", "github")
         .form-group
           = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
           .col-sm-10
    
    From a2d4c349b5772231d7428cf32ac8a66d70beb627 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Mon, 9 May 2016 19:38:07 +0300
    Subject: [PATCH 109/152] unindent 1 level the Disable OAuth form
    
    ---
     .../admin/application_settings/_form.html.haml   | 16 ++++++++--------
     1 file changed, 8 insertions(+), 8 deletions(-)
    
    diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
    index 99f86e5244..383f206079 100644
    --- a/app/views/admin/application_settings/_form.html.haml
    +++ b/app/views/admin/application_settings/_form.html.haml
    @@ -109,14 +109,14 @@
               = f.label :signin_enabled do
                 = f.check_box :signin_enabled
                 Sign-in enabled
    -      - if omniauth_enabled? && !button_based_providers.empty?
    -        .form-group
    -          = f.label :disable_oauth_signin_sources, 'Disable OAuth Sign-In sources', class: 'control-label col-sm-2'
    -          .col-sm-10
    -            - data_attrs = { toggle: 'buttons' }
    -            .btn-group{ data: data_attrs }
    -              - oauth_providers_checkboxes('oauth-providers-help').each do |source|
    -                = source
    +    - if omniauth_enabled? && !button_based_providers.empty?
    +      .form-group
    +        = f.label :disable_oauth_signin_sources, 'Disable OAuth Sign-In sources', class: 'control-label col-sm-2'
    +        .col-sm-10
    +          - data_attrs = { toggle: 'buttons' }
    +          .btn-group{ data: data_attrs }
    +            - oauth_providers_checkboxes('oauth-providers-help').each do |source|
    +              = source
         .form-group
           = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2'
           .col-sm-10
    
    From 81d0a4a47390f3810b03528182d3eea68ab46112 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Tue, 10 May 2016 10:21:14 +0300
    Subject: [PATCH 110/152] typo
    
    ---
     app/models/application_setting.rb | 2 +-
     1 file changed, 1 insertion(+), 1 deletion(-)
    
    diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
    index d565dbc71d..667b45335b 100644
    --- a/app/models/application_setting.rb
    +++ b/app/models/application_setting.rb
    @@ -123,7 +123,7 @@ class ApplicationSetting < ActiveRecord::Base
         unless value.nil?
           value.each do |source|
             unless Devise.omniauth_providers.include?(source.to_sym)
    -          record.errors.add(attr, "'#{source}' is not an ouath sign-in source")
    +          record.errors.add(attr, "'#{source}' is not an OAuth sign-in source")
             end
           end
         end
    
    From 96122034cfc2eb7039ae75b20b729f35e9aa832e Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Tue, 10 May 2016 11:17:37 +0300
    Subject: [PATCH 111/152] more readable specs for
     enabled_button_based_providers and button_based_providers_enabled?
    
    ---
     spec/helpers/auth_helper_spec.rb | 44 +++++++++++++++++++++-----------
     1 file changed, 29 insertions(+), 15 deletions(-)
    
    diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
    index a6df820421..16fbb5dcec 100644
    --- a/spec/helpers/auth_helper_spec.rb
    +++ b/spec/helpers/auth_helper_spec.rb
    @@ -23,29 +23,43 @@ describe AuthHelper do
           allow(helper).to receive(:auth_providers) { [:twitter, :github] }
         end
     
    -    it 'returns all the enabled providers from settings' do
    -      expect(helper.enabled_button_based_providers).to include(*['twitter', 'github'])
    +    context 'all providers are enabled to sign in' do
    +      it 'returns all the enabled providers from settings' do
    +        expect(helper.enabled_button_based_providers).to include('twitter', 'github')
    +      end
         end
     
    -    it "should not return github as provider because it's disabled from settings" do
    -      stub_application_setting(
    -        disabled_oauth_sign_in_sources: ['github']
    -      )
    +    context 'GitHub OAuth sign in is disabled from application setting' do
    +      it "doesn't return github as provider" do
    +        stub_application_setting(
    +          disabled_oauth_sign_in_sources: ['github']
    +        )
     
    -      expect(helper.enabled_button_based_providers).to include('twitter')
    -      expect(helper.enabled_button_based_providers).to_not include('github')
    +        expect(helper.enabled_button_based_providers).to include('twitter')
    +        expect(helper.enabled_button_based_providers).to_not include('github')
    +      end
    +    end
    +  end
    +
    +  describe 'button_based_providers_enabled?' do
    +    before do
    +      allow(helper).to receive(:auth_providers) { [:twitter, :github] }
         end
     
    -    it 'returns true for button_based_providers_enabled? because there providers' do
    -      expect(helper.button_based_providers_enabled?).to be true
    +    context 'button based providers enabled' do
    +      it 'returns true' do
    +        expect(helper.button_based_providers_enabled?).to be true
    +      end
         end
     
    -    it 'returns false for button_based_providers_enabled? because there providers' do
    -      stub_application_setting(
    -        disabled_oauth_sign_in_sources: ['github', 'twitter']
    -      )
    +    context 'all the button based providers are disabled via application_setting' do
    +      it 'returns false' do
    +        stub_application_setting(
    +          disabled_oauth_sign_in_sources: ['github', 'twitter']
    +        )
     
    -      expect(helper.button_based_providers_enabled?).to be false
    +        expect(helper.button_based_providers_enabled?).to be false
    +      end
         end
       end
     end
    
    From 7818f7329a436d86b30dff003b60ab8a15a4f0db Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Tue, 10 May 2016 11:29:19 +0300
    Subject: [PATCH 112/152] On Application Settings Page let the user select the
     enabled OAuth Sign in sources instead of the disabled ones
    
    ---
     .../admin/application_settings_controller.rb     | 16 ++++++++--------
     app/helpers/application_settings_helper.rb       |  8 ++++----
     .../admin/application_settings/_form.html.haml   |  2 +-
     3 files changed, 13 insertions(+), 13 deletions(-)
    
    diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
    index cebc01a2af..d7a052700a 100644
    --- a/app/controllers/admin/application_settings_controller.rb
    +++ b/app/controllers/admin/application_settings_controller.rb
    @@ -53,14 +53,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
           end
         end
     
    -    disabled_oauth_sign_in_sources = params[:application_setting][:disabled_oauth_sign_in_sources]
    -    if disabled_oauth_sign_in_sources.nil?
    -      params[:application_setting][:disabled_oauth_sign_in_sources] = []
    -    else
    -      disabled_oauth_sign_in_sources.map! do |source|
    -        source.to_str
    -      end
    -    end
    +    enabled_oauth_sign_in_sources = params[:application_setting][:enabled_oauth_sign_in_sources]
    +
    +    params[:application_setting][:disabled_oauth_sign_in_sources] =
    +      AuthHelper.button_based_providers.map(&:to_s) -
    +      (enabled_oauth_sign_in_sources.nil? ? [] : enabled_oauth_sign_in_sources)
    +
    +    params[:application_setting].delete(:enabled_oauth_sign_in_sources)
     
         params.require(:application_setting).permit(
           :default_projects_limit,
    @@ -105,6 +104,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
           :metrics_packet_size,
           restricted_visibility_levels: [],
           import_sources: [],
    +      enabled_oauth_sign_in_sources: [],
           disabled_oauth_sign_in_sources: []
         )
       end
    diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
    index 17d7389987..ced8e16de7 100644
    --- a/app/helpers/application_settings_helper.rb
    +++ b/app/helpers/application_settings_helper.rb
    @@ -63,13 +63,13 @@ module ApplicationSettingsHelper
     
       def oauth_providers_checkboxes(help_block_id)
         button_based_providers.map do |source|
    -      checked = current_application_settings.disabled_oauth_sign_in_sources.include?(source.to_s)
    +      disabled = current_application_settings.disabled_oauth_sign_in_sources.include?(source.to_s)
           css_class = 'btn'
    -      css_class += ' active' if checked
    -      checkbox_name = 'application_setting[disabled_oauth_sign_in_sources][]'
    +      css_class += ' active' unless disabled
    +      checkbox_name = 'application_setting[enabled_oauth_sign_in_sources][]'
     
           label_tag(checkbox_name, class: css_class) do
    -        check_box_tag(checkbox_name, source, checked,
    +        check_box_tag(checkbox_name, source, !disabled,
                           autocomplete: 'off',
                           'aria-describedby' => help_block_id) + Gitlab::OAuth::Provider.label_for(source)
           end
    diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
    index 383f206079..ecd032acfd 100644
    --- a/app/views/admin/application_settings/_form.html.haml
    +++ b/app/views/admin/application_settings/_form.html.haml
    @@ -111,7 +111,7 @@
                 Sign-in enabled
         - if omniauth_enabled? && !button_based_providers.empty?
           .form-group
    -        = f.label :disable_oauth_signin_sources, 'Disable OAuth Sign-In sources', class: 'control-label col-sm-2'
    +        = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth Sign-In sources', class: 'control-label col-sm-2'
             .col-sm-10
               - data_attrs = { toggle: 'buttons' }
               .btn-group{ data: data_attrs }
    
    From bc099d799ea502c9fd8f1a09f07af70cdcf51156 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Tue, 10 May 2016 11:50:53 +0300
    Subject: [PATCH 113/152] Allow Admins to remove the Login with buttons for
     OAuth services and still be able to import on the changelog
    
    ---
     CHANGELOG | 2 ++
     1 file changed, 2 insertions(+)
    
    diff --git a/CHANGELOG b/CHANGELOG
    index 187011c601..8d70ef9944 100644
    --- a/CHANGELOG
    +++ b/CHANGELOG
    @@ -55,6 +55,8 @@ v 8.7.4
       - Running rake gitlab:db:drop_tables now drops tables with cascade !4020
       - Running rake gitlab:db:drop_tables uses "IF EXISTS" as a precaution !4100
       - Use a case-insensitive comparison in sanitizing URI schemes
    +  - Merge request widget displays TeamCity build state and code coverage correctly again.
    +  - Allow Admins to remove the Login with buttons for OAuth services and still be able to import !4034
     
     v 8.7.3
       - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented
    
    From 3a91f5081eeeef8dca52a418aba2d786cbc8ddf4 Mon Sep 17 00:00:00 2001
    From: Andrei Gliga 
    Date: Tue, 10 May 2016 11:53:22 +0300
    Subject: [PATCH 114/152] documentation for enabling or disabling OAuth sign in
    
    ---
     .../img/enabled-oauth-sign-in-sources.png       | Bin 0 -> 49081 bytes
     doc/integration/omniauth.md                     |  14 ++++++++++++++
     2 files changed, 14 insertions(+)
     create mode 100644 doc/integration/img/enabled-oauth-sign-in-sources.png
    
    diff --git a/doc/integration/img/enabled-oauth-sign-in-sources.png b/doc/integration/img/enabled-oauth-sign-in-sources.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..95f8bbdcd2489c4eb14c100c92dffadf3a1a65fc
    GIT binary patch
    literal 49081
    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 T3O4XXNYA9j6~w-Z7zO**Note:** +This setting was introduced with version 8.8 of GitLab + +Administrators are able to enable or disable Sign In via some OmniAuth providers. + +>**Note:** +By default Sign In is enabled via all the OAuth Providers that have been configured in config/gitlab.yml. + +In order to enable/disable an OmniAuth provider go to Admin Area -> Settings-> Sign-in Restrictions section -> Enabled OAuth Sign-In sources and select the providers you want to enable or disable. + +![Enabled OAuth Sign-In sources](img/enabled-oauth-sign-in-sources.png) From 2a99c51590da3c430b27538a1f570d34209d51c7 Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Tue, 10 May 2016 18:39:27 +0300 Subject: [PATCH 115/152] corrections to the Enable or disable Sign In with OmniAuth --- doc/integration/omniauth.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index f0d7394005..f3e891fa45 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -194,14 +194,15 @@ While we can't officially support every possible authentication mechanism out th we'd like to at least help those with specific needs. ## Enable or disable Sign In with an OmniAuth provider without disabling import sources + >**Note:** -This setting was introduced with version 8.8 of GitLab +This setting was introduced with version 8.8 of GitLab. Administrators are able to enable or disable Sign In via some OmniAuth providers. >**Note:** By default Sign In is enabled via all the OAuth Providers that have been configured in config/gitlab.yml. -In order to enable/disable an OmniAuth provider go to Admin Area -> Settings-> Sign-in Restrictions section -> Enabled OAuth Sign-In sources and select the providers you want to enable or disable. +In order to enable/disable an OmniAuth provider, go to Admin Area -> Settings -> Sign-in Restrictions section -> Enabled OAuth Sign-In sources and select the providers you want to enable or disable. ![Enabled OAuth Sign-In sources](img/enabled-oauth-sign-in-sources.png) From 4372043392d735db506f1e696c213c3ece47bc1a Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Tue, 10 May 2016 18:40:20 +0300 Subject: [PATCH 116/152] credits for !4034 --- CHANGELOG | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8d70ef9944..7cd8f63321 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -43,6 +43,7 @@ v 8.8.0 (unreleased) - Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi) - Fix adding a todo for private group members (Ahmad Sherif) - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3 + - Allow Admins to remove the Login with buttons for OAuth services and still be able to import !4034. (Andrei Gliga) v 8.7.5 - Fix relative links in wiki pages. !4050 @@ -55,8 +56,6 @@ v 8.7.4 - Running rake gitlab:db:drop_tables now drops tables with cascade !4020 - Running rake gitlab:db:drop_tables uses "IF EXISTS" as a precaution !4100 - Use a case-insensitive comparison in sanitizing URI schemes - - Merge request widget displays TeamCity build state and code coverage correctly again. - - Allow Admins to remove the Login with buttons for OAuth services and still be able to import !4034 v 8.7.3 - Emails, Gitlab::Email::Message, Gitlab::Diff, and Premailer::Adapter::Nokogiri are now instrumented From 9bfa74fcfe79c244fe4fd7d074b87bb22ba7971f Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Tue, 10 May 2016 18:43:54 +0300 Subject: [PATCH 117/152] enabled_oauth_sign_in_sources param doesn't need permit --- app/controllers/admin/application_settings_controller.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index d7a052700a..a2dfed0b7a 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -104,7 +104,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :metrics_packet_size, restricted_visibility_levels: [], import_sources: [], - enabled_oauth_sign_in_sources: [], disabled_oauth_sign_in_sources: [] ) end From ab7f9c9ae1281857bef655a1af64774c628befa1 Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Tue, 10 May 2016 18:45:39 +0300 Subject: [PATCH 118/152] Array(enabled_oauth_sign_in_sources) instead of the if --- app/controllers/admin/application_settings_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index a2dfed0b7a..c300560236 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -57,7 +57,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController params[:application_setting][:disabled_oauth_sign_in_sources] = AuthHelper.button_based_providers.map(&:to_s) - - (enabled_oauth_sign_in_sources.nil? ? [] : enabled_oauth_sign_in_sources) + Array(enabled_oauth_sign_in_sources) params[:application_setting].delete(:enabled_oauth_sign_in_sources) From 25ff3fd5bdd8645f45254aa52397f8d6b49386b5 Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Tue, 10 May 2016 18:46:40 +0300 Subject: [PATCH 119/152] button_based_providers.any? instead of !button_based_providers.empty? --- app/views/admin/application_settings/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index ecd032acfd..909339f7bc 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -109,7 +109,7 @@ = f.label :signin_enabled do = f.check_box :signin_enabled Sign-in enabled - - if omniauth_enabled? && !button_based_providers.empty? + - if omniauth_enabled? && button_based_providers.any? .form-group = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth Sign-In sources', class: 'control-label col-sm-2' .col-sm-10 From e87c96eef6d8d837bf4475e2681f4c5c5e40488d Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Tue, 10 May 2016 18:48:08 +0300 Subject: [PATCH 120/152] enabled_button_based_providers.any? instead of ! empty? for button_based_providers_enabled? --- app/helpers/auth_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index 3e1f420925..b05fa0a14d 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -45,7 +45,7 @@ module AuthHelper end def button_based_providers_enabled? - !enabled_button_based_providers.empty? + enabled_button_based_providers.any? end def provider_image_tag(provider, size = 64) From 46064cb84c579bd1657baffe24f5a515759bff67 Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Tue, 10 May 2016 18:50:03 +0300 Subject: [PATCH 121/152] << instead of += --- app/helpers/application_settings_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index ced8e16de7..878b1b254c 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -65,7 +65,7 @@ module ApplicationSettingsHelper button_based_providers.map do |source| disabled = current_application_settings.disabled_oauth_sign_in_sources.include?(source.to_s) css_class = 'btn' - css_class += ' active' unless disabled + css_class << ' active' unless disabled checkbox_name = 'application_setting[enabled_oauth_sign_in_sources][]' label_tag(checkbox_name, class: css_class) do From 6bd8d3ad48b9151c0bcb69bfbce0bc2551cdf184 Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Tue, 10 May 2016 18:53:02 +0300 Subject: [PATCH 122/152] inline btn-group{ data: { toggle: 'buttons' } } --- app/views/admin/application_settings/_form.html.haml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 909339f7bc..576509e3f2 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -113,8 +113,7 @@ .form-group = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth Sign-In sources', class: 'control-label col-sm-2' .col-sm-10 - - data_attrs = { toggle: 'buttons' } - .btn-group{ data: data_attrs } + .btn-group{ data: { toggle: 'buttons' } } - oauth_providers_checkboxes('oauth-providers-help').each do |source| = source .form-group From 9079b56bf4f149054e347aa09ad151ab617ef123 Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Wed, 11 May 2016 10:35:18 +0300 Subject: [PATCH 123/152] Revert "no need to show the help about Github or the other authentication sources" This reverts commit 78a832d0fd7ff445c707809134dbb2a42d2a9e03. Show help only for the enabled OAuth Providers on oauth-providers-help block aria: { describedby: help_block_id } instead of 'aria-describedby' => help_block_id provider and not p a more simple approach to the OmniAuth helper links for oauth-providers-help --- app/helpers/application_settings_helper.rb | 9 ++++++++- app/views/admin/application_settings/_form.html.haml | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 878b1b254c..82056a4c97 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -71,8 +71,15 @@ module ApplicationSettingsHelper label_tag(checkbox_name, class: css_class) do check_box_tag(checkbox_name, source, !disabled, autocomplete: 'off', - 'aria-describedby' => help_block_id) + Gitlab::OAuth::Provider.label_for(source) + aria: { describedby: help_block_id }) + Gitlab::OAuth::Provider.label_for(source) end end end + + def oauth_providers_with_help_links + button_based_providers.map do |provider| + Gitlab::OAuth::Provider.label_for(provider) + ' ' + + link_to("(?)", help_page_path("integration", provider)) + end + end end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 576509e3f2..f3681bc96e 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -116,6 +116,9 @@ .btn-group{ data: { toggle: 'buttons' } } - oauth_providers_checkboxes('oauth-providers-help').each do |source| = source + %span.help-block#oauth-providers-help + Enabled OmniAuth must be configured for + = oauth_providers_with_help_links.to_sentence.html_safe .form-group = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2' .col-sm-10 From 2576a55e35f06d2d59783867be5e7e2ccbed982e Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Wed, 11 May 2016 19:52:06 +0300 Subject: [PATCH 124/152] config/gitlab.yml between `` --- doc/integration/omniauth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index f3e891fa45..820f40f81a 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -201,7 +201,7 @@ This setting was introduced with version 8.8 of GitLab. Administrators are able to enable or disable Sign In via some OmniAuth providers. >**Note:** -By default Sign In is enabled via all the OAuth Providers that have been configured in config/gitlab.yml. +By default Sign In is enabled via all the OAuth Providers that have been configured in `config/gitlab.yml`. In order to enable/disable an OmniAuth provider, go to Admin Area -> Settings -> Sign-in Restrictions section -> Enabled OAuth Sign-In sources and select the providers you want to enable or disable. From c0c2cd4ed72cda88e182f3eafb472687d9d075f1 Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Wed, 11 May 2016 19:55:24 +0300 Subject: [PATCH 125/152] new screenshot for the docs on how to enabled/disable OmniAuth Sign In --- .../img/enabled-oauth-sign-in-sources.png | Bin 49081 -> 92674 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/integration/img/enabled-oauth-sign-in-sources.png b/doc/integration/img/enabled-oauth-sign-in-sources.png index 95f8bbdcd2489c4eb14c100c92dffadf3a1a65fc..08155a4fade75af85f36fdbe8cacff1af936280c 100644 GIT binary patch literal 92674 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 n{ErgGcytqE7Zr6^h`m+mD8d?#n7A>v#VTY~gf5 literal 49081 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 T3O4XXNYA9j6~w-Z7zO Date: Wed, 11 May 2016 20:20:27 +0300 Subject: [PATCH 126/152] Revert "disabled_oauth_sign_in_sources column" and keep on schema only the disabled_oauth_sign_in_sources column This reverts commit 95358e0095403ad44149d11922d52e4590285a20. --- db/schema.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 74facd1208..b21cc16289 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -70,16 +70,16 @@ ActiveRecord::Schema.define(version: 20160508194200) do t.string "recaptcha_site_key" t.string "recaptcha_private_key" t.integer "metrics_port", default: 8089 - t.boolean "akismet_enabled", default: false - t.string "akismet_api_key" t.integer "metrics_sample_interval", default: 15 t.boolean "sentry_enabled", default: false t.string "sentry_dsn" + t.boolean "akismet_enabled", default: false + t.string "akismet_api_key" t.boolean "email_author_in_body", default: false t.integer "default_group_visibility" t.boolean "repository_checks_enabled", default: false - t.text "shared_runners_text" t.integer "metrics_packet_size", default: 1 + t.text "shared_runners_text" t.text "disabled_oauth_sign_in_sources" end @@ -427,10 +427,10 @@ ActiveRecord::Schema.define(version: 20160508194200) do t.string "state" t.integer "iid" t.integer "updated_by_id" + t.integer "moved_to_id" t.boolean "confidential", default: false t.datetime "deleted_at" t.date "due_date" - t.integer "moved_to_id" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree @@ -717,8 +717,8 @@ ActiveRecord::Schema.define(version: 20160508194200) do t.integer "project_id" t.text "data" t.text "encrypted_credentials" - t.string "encrypted_credentials_iv" - t.string "encrypted_credentials_salt" + t.text "encrypted_credentials_iv" + t.text "encrypted_credentials_salt" end create_table "projects", force: :cascade do |t| @@ -816,9 +816,9 @@ ActiveRecord::Schema.define(version: 20160508194200) do t.string "type" t.string "title" t.integer "project_id" - t.datetime "created_at" - t.datetime "updated_at" - t.boolean "active", default: false, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "active", null: false t.text "properties" t.boolean "template", default: false t.boolean "push_events", default: true From 8313476f5e1f1b51b3857ba065a65a707f5ca8af Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Wed, 11 May 2016 23:39:07 +0300 Subject: [PATCH 127/152] no schema info for ApplicationSetting --- app/models/application_setting.rb | 49 ------------------------------- 1 file changed, 49 deletions(-) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 667b45335b..c143cf215e 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -1,52 +1,3 @@ -# == Schema Information -# -# Table name: application_settings -# -# id :integer not null, primary key -# default_projects_limit :integer -# signup_enabled :boolean -# signin_enabled :boolean -# gravatar_enabled :boolean -# sign_in_text :text -# created_at :datetime -# updated_at :datetime -# home_page_url :string(255) -# default_branch_protection :integer default(2) -# restricted_visibility_levels :text -# version_check_enabled :boolean default(TRUE) -# max_attachment_size :integer default(10), not null -# default_project_visibility :integer -# default_snippet_visibility :integer -# default_group_visibility :integer -# restricted_signup_domains :text -# user_oauth_applications :boolean default(TRUE) -# after_sign_out_path :string(255) -# session_expire_delay :integer default(10080), not null -# import_sources :text -# disabled_oauth_sign_in_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 -# runners_registration_token :string -# require_two_factor_authentication :boolean default(FALSE) -# two_factor_grace_period :integer default(48) -# metrics_enabled :boolean default(FALSE) -# metrics_host :string default("localhost") -# metrics_username :string -# metrics_password :string -# metrics_pool_size :integer default(16) -# metrics_timeout :integer default(10) -# metrics_method_call_threshold :integer default(10) -# recaptcha_enabled :boolean default(FALSE) -# recaptcha_site_key :string -# recaptcha_private_key :string -# metrics_port :integer default(8089) -# sentry_enabled :boolean default(FALSE) -# sentry_dsn :string -# email_author_in_body :boolean default(FALSE) -# - class ApplicationSetting < ActiveRecord::Base include TokenAuthenticatable add_authentication_token_field :runners_registration_token From b17cca1d9460a00510a2e9f04300f07cfb5eb95a Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Thu, 12 May 2016 09:37:15 +0300 Subject: [PATCH 128/152] delete the enabled_oauth_sign_in_sources after getting it's value into enabled_oauth_sign_in_sources just delete enabled_oauth_sign_in_sources and get it's value --- app/controllers/admin/application_settings_controller.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index c300560236..b9eb7ae792 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -53,14 +53,12 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController end end - enabled_oauth_sign_in_sources = params[:application_setting][:enabled_oauth_sign_in_sources] + enabled_oauth_sign_in_sources = params[:application_setting].delete(:enabled_oauth_sign_in_sources) params[:application_setting][:disabled_oauth_sign_in_sources] = AuthHelper.button_based_providers.map(&:to_s) - Array(enabled_oauth_sign_in_sources) - params[:application_setting].delete(:enabled_oauth_sign_in_sources) - params.require(:application_setting).permit( :default_projects_limit, :default_branch_protection, From 5553ad24e4fe42670ba0f4e2010819abac499e7d Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Thu, 12 May 2016 13:37:44 +0300 Subject: [PATCH 129/152] Revert "new screenshot for the docs on how to enabled/disable OmniAuth Sign In" [ci skip] This reverts commit 7b295370efc124360d52e37e8e709914028f6443. --- .../img/enabled-oauth-sign-in-sources.png | Bin 92674 -> 49081 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/integration/img/enabled-oauth-sign-in-sources.png b/doc/integration/img/enabled-oauth-sign-in-sources.png index 08155a4fade75af85f36fdbe8cacff1af936280c..95f8bbdcd2489c4eb14c100c92dffadf3a1a65fc 100644 GIT binary patch literal 49081 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 n{ErgGcytqE7Zr6^h`m+mD8d?#n7A>v#VTY~gf5 From 6cdce04937837de8014b1c044c91ed5b32bdc90a Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Thu, 12 May 2016 13:40:24 +0300 Subject: [PATCH 130/152] Revert "Revert "no need to show the help about Github or the other authentication sources"" This reverts commit 075ace5686c63d40f7e9fa02bf37e1bd3168f7e7. --- app/helpers/application_settings_helper.rb | 9 +-------- app/views/admin/application_settings/_form.html.haml | 3 --- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 82056a4c97..878b1b254c 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -71,15 +71,8 @@ module ApplicationSettingsHelper label_tag(checkbox_name, class: css_class) do check_box_tag(checkbox_name, source, !disabled, autocomplete: 'off', - aria: { describedby: help_block_id }) + Gitlab::OAuth::Provider.label_for(source) + 'aria-describedby' => help_block_id) + Gitlab::OAuth::Provider.label_for(source) end end end - - def oauth_providers_with_help_links - button_based_providers.map do |provider| - Gitlab::OAuth::Provider.label_for(provider) + ' ' + - link_to("(?)", help_page_path("integration", provider)) - end - end end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index f3681bc96e..576509e3f2 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -116,9 +116,6 @@ .btn-group{ data: { toggle: 'buttons' } } - oauth_providers_checkboxes('oauth-providers-help').each do |source| = source - %span.help-block#oauth-providers-help - Enabled OmniAuth must be configured for - = oauth_providers_with_help_links.to_sentence.html_safe .form-group = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2' .col-sm-10 From 260c88a75865a1b849a4bac8a14c897fbe522047 Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Thu, 12 May 2016 13:43:12 +0300 Subject: [PATCH 131/152] no need to define the help block since there's no help block --- app/helpers/application_settings_helper.rb | 5 ++--- app/views/admin/application_settings/_form.html.haml | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 878b1b254c..03080d2593 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -61,7 +61,7 @@ module ApplicationSettingsHelper end end - def oauth_providers_checkboxes(help_block_id) + def oauth_providers_checkboxes button_based_providers.map do |source| disabled = current_application_settings.disabled_oauth_sign_in_sources.include?(source.to_s) css_class = 'btn' @@ -70,8 +70,7 @@ module ApplicationSettingsHelper label_tag(checkbox_name, class: css_class) do check_box_tag(checkbox_name, source, !disabled, - autocomplete: 'off', - 'aria-describedby' => help_block_id) + Gitlab::OAuth::Provider.label_for(source) + autocomplete: 'off') + Gitlab::OAuth::Provider.label_for(source) end end end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 576509e3f2..f7c799c968 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -114,7 +114,7 @@ = f.label :enabled_oauth_sign_in_sources, 'Enabled OAuth Sign-In sources', class: 'control-label col-sm-2' .col-sm-10 .btn-group{ data: { toggle: 'buttons' } } - - oauth_providers_checkboxes('oauth-providers-help').each do |source| + - oauth_providers_checkboxes.each do |source| = source .form-group = f.label :two_factor_authentication, 'Two-factor authentication', class: 'control-label col-sm-2' From d66833bca5ae0347751ceab99062b638965eab78 Mon Sep 17 00:00:00 2001 From: Andrei Gliga Date: Thu, 12 May 2016 13:56:04 +0300 Subject: [PATCH 132/152] =?UTF-8?q?schema=20fix=20=F0=9F=98=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- db/schema.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index b21cc16289..74facd1208 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -70,16 +70,16 @@ ActiveRecord::Schema.define(version: 20160508194200) do t.string "recaptcha_site_key" t.string "recaptcha_private_key" t.integer "metrics_port", default: 8089 + t.boolean "akismet_enabled", default: false + t.string "akismet_api_key" t.integer "metrics_sample_interval", default: 15 t.boolean "sentry_enabled", default: false t.string "sentry_dsn" - t.boolean "akismet_enabled", default: false - t.string "akismet_api_key" t.boolean "email_author_in_body", default: false t.integer "default_group_visibility" t.boolean "repository_checks_enabled", default: false - t.integer "metrics_packet_size", default: 1 t.text "shared_runners_text" + t.integer "metrics_packet_size", default: 1 t.text "disabled_oauth_sign_in_sources" end @@ -427,10 +427,10 @@ ActiveRecord::Schema.define(version: 20160508194200) do t.string "state" t.integer "iid" t.integer "updated_by_id" - t.integer "moved_to_id" t.boolean "confidential", default: false t.datetime "deleted_at" t.date "due_date" + t.integer "moved_to_id" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree @@ -717,8 +717,8 @@ ActiveRecord::Schema.define(version: 20160508194200) do t.integer "project_id" t.text "data" t.text "encrypted_credentials" - t.text "encrypted_credentials_iv" - t.text "encrypted_credentials_salt" + t.string "encrypted_credentials_iv" + t.string "encrypted_credentials_salt" end create_table "projects", force: :cascade do |t| @@ -816,9 +816,9 @@ ActiveRecord::Schema.define(version: 20160508194200) do t.string "type" t.string "title" t.integer "project_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "active", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.boolean "active", default: false, null: false t.text "properties" t.boolean "template", default: false t.boolean "push_events", default: true From 945c5b3fe6e0552f77da8b1a1efe75cd04434f53 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 12 May 2016 15:14:14 +0200 Subject: [PATCH 133/152] Removed tracking of total method execution times Because method call timings are inclusive (that is, they include the time of any sub method calls) this would lead to the total method execution time often being far greater than the total transaction time. Because this is incredibly confusing it's best to simply _not_ track the total method execution time, after all it's not that useful to begin with. Fixes gitlab-org/gitlab-ce#17239 --- CHANGELOG | 1 + lib/gitlab/metrics/instrumentation.rb | 2 -- spec/lib/gitlab/metrics/instrumentation_spec.rb | 6 ------ 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 187011c601..985e25aab3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -43,6 +43,7 @@ v 8.8.0 (unreleased) - Fix unintentional filtering bug in issues sorted by milestone due (Takuya Noguchi) - Fix adding a todo for private group members (Ahmad Sherif) - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3 + - Total method execution timings are no longer tracked v 8.7.5 - Fix relative links in wiki pages. !4050 diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index 708ef79f30..0f115893a1 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -154,8 +154,6 @@ module Gitlab duration = (Time.now - start) * 1000.0 if duration >= Gitlab::Metrics.method_call_threshold - trans.increment(:method_duration, duration) - trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES, { duration: duration }, method: #{label.inspect}) diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index 5c885a7a98..7b86450a22 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -56,9 +56,6 @@ describe Gitlab::Metrics::Instrumentation do allow(described_class).to receive(:transaction). and_return(transaction) - expect(transaction).to receive(:increment). - with(:method_duration, a_kind_of(Numeric)) - expect(transaction).to receive(:add_metric). with(described_class::SERIES, an_instance_of(Hash), method: 'Dummy.foo') @@ -139,9 +136,6 @@ describe Gitlab::Metrics::Instrumentation do allow(described_class).to receive(:transaction). and_return(transaction) - expect(transaction).to receive(:increment). - with(:method_duration, a_kind_of(Numeric)) - expect(transaction).to receive(:add_metric). with(described_class::SERIES, an_instance_of(Hash), method: 'Dummy#bar') From 5bd356eb31fb4684199f6f681f4901ebe025ed92 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 12 May 2016 09:27:58 -0500 Subject: [PATCH 134/152] Fix specs --- app/models/ci/build.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 01236b8e3c..4a3f92ad34 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -95,7 +95,7 @@ module Ci end def trace_html - trace_with_state[:html] + trace_with_state[:html] || '' end def trace_with_state(state = nil) From 827fbc10f6147c3c86278ed7f1675b085cc2a3cb Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 3 May 2016 13:58:50 +0100 Subject: [PATCH 135/152] Only show forks for users who can download code The ForksController enforces this, so don't show the link if it won't go anywhere. --- CHANGELOG | 1 + app/helpers/projects_helper.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 187011c601..878453e861 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,7 @@ v 8.8.0 (unreleased) - Backport GitHub Enterprise import support from EE - Create tags using Rugged for performance reasons. !3745 - API: Expose Issue#user_notes_count. !3126 (Anton Popov) + - Don't show forks button when user can't view forks - Files over 5MB can only be viewed in their raw form, files over 1MB without highlighting !3718 - Add support for supressing text diffs using .gitattributes on the default branch (Matt Oakes) - Add eager load paths to help prevent dependency load issues in Sidekiq workers. !3724 diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 85f8854d2e..e1ab78df69 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -138,10 +138,10 @@ module ProjectsHelper private def get_project_nav_tabs(project, current_user) - nav_tabs = [:home, :forks] + nav_tabs = [:home] if !project.empty_repo? && can?(current_user, :download_code, project) - nav_tabs << [:files, :commits, :network, :graphs] + nav_tabs << [:files, :commits, :network, :graphs, :forks] end if project.repo_exists? && can?(current_user, :read_merge_request, project) From ad77ab0376fabf3dfadea86c716358964b526956 Mon Sep 17 00:00:00 2001 From: DJ Mountney Date: Thu, 12 May 2016 09:04:04 -0700 Subject: [PATCH 136/152] Add Changelog entry and drop exception handling around HealthCheck::Utils.process_checks, it wasn't needed --- CHANGELOG | 1 + app/controllers/admin/health_check_controller.rb | 6 +----- app/views/admin/health_check/show.html.haml | 12 ++++++------ 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7d5f424eae..7ded902776 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.8.0 (unreleased) - Reduce delay in destroying a project from 1-minute to immediately - Make build status canceled if any of the jobs was canceled and none failed - Upgrade Sidekiq to 4.1.2 + - Added /health_check endpoint for checking service status - Sanitize repo paths in new project error message - Bump mail_room to 0.7.0 to fix stuck IDLE connections - Remove future dates from contribution calendar graph. diff --git a/app/controllers/admin/health_check_controller.rb b/app/controllers/admin/health_check_controller.rb index 3153a765e3..241c7be0ea 100644 --- a/app/controllers/admin/health_check_controller.rb +++ b/app/controllers/admin/health_check_controller.rb @@ -1,9 +1,5 @@ class Admin::HealthCheckController < Admin::ApplicationController def show - begin - @errors = HealthCheck::Utils.process_checks('standard') - rescue => e - @errors = e.message.blank? ? e.class.to_s : e.message.to_s - end + @errors = HealthCheck::Utils.process_checks('standard') end end diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml index ed7025f7a0..ad79fd26d0 100644 --- a/app/views/admin/health_check/show.html.haml +++ b/app/views/admin/health_check/show.html.haml @@ -16,21 +16,21 @@ Health information can be reteived as plain text, json, or xml using: %ul %li - %code= health_check_url(token:current_application_settings.health_check_access_token) + %code= health_check_url(token: current_application_settings.health_check_access_token) %li - %code= health_check_url(token:current_application_settings.health_check_access_token, format: :json) + %code= health_check_url(token: current_application_settings.health_check_access_token, format: :json) %li - %code= health_check_url(token:current_application_settings.health_check_access_token, format: :xml) + %code= health_check_url(token: current_application_settings.health_check_access_token, format: :xml) %p.light You can also ask for the status of specific services: %ul %li - %code= health_check_url(token:current_application_settings.health_check_access_token, checks: :cache) + %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :cache) %li - %code= health_check_url(token:current_application_settings.health_check_access_token, checks: :database) + %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :database) %li - %code= health_check_url(token:current_application_settings.health_check_access_token, checks: :migrations) + %code= health_check_url(token: current_application_settings.health_check_access_token, checks: :migrations) %hr .panel.panel-default From 0641fe6f72a3d42f94062fd00b1cf8a0d643e6a4 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 12 May 2016 18:11:03 +0200 Subject: [PATCH 137/152] Updated CHANGELOG for 8.7.5 [ci skip] --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 1d8712ae92..5dc5e05705 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -48,6 +48,8 @@ v 8.8.0 (unreleased) v 8.7.5 - Fix relative links in wiki pages. !4050 + - Fix always showing build notification message when switching between merge requests !4086 + - Fix an issue when filtering merge requests with more than one label. !3886 v 8.7.4 - Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss) From 18fdbf0a035b6feec3b576c01ee1a2f3a95e4305 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Wed, 11 May 2016 23:46:19 +0900 Subject: [PATCH 138/152] Fix a description for default scope on builds --- CHANGELOG | 1 + app/views/admin/builds/index.html.haml | 2 +- app/views/projects/builds/index.html.haml | 2 +- spec/features/admin/admin_builds_spec.rb | 1 + spec/features/builds_spec.rb | 1 + 5 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 777b211daa..8d993e3013 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -51,6 +51,7 @@ v 8.7.5 - Fix relative links in wiki pages. !4050 - Fix always showing build notification message when switching between merge requests !4086 - Fix an issue when filtering merge requests with more than one label. !3886 + - Fix short note for the default scope on build page (Takuya Noguchi) v 8.7.4 - Links for Redmine issue references are generated correctly again !4048 (Benedikt Huss) diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 804d7851bd..ed24757087 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -20,7 +20,7 @@ = link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post .row-content-block.second-block - #{(@scope || 'running').capitalize} builds + #{(@scope || 'all').capitalize} builds %ul.content-list - if @builds.blank? diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 2e8015d119..98f4a9416e 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -36,7 +36,7 @@ %span CI Lint .row-content-block - #{(@scope || 'running').capitalize} builds from this project + #{(@scope || 'all').capitalize} builds from this project %ul.content-list - if @builds.blank? diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb index 2e9851fb44..7bbe20fec4 100644 --- a/spec/features/admin/admin_builds_spec.rb +++ b/spec/features/admin/admin_builds_spec.rb @@ -19,6 +19,7 @@ describe 'Admin Builds' do visit admin_builds_path expect(page).to have_selector('.nav-links li.active', text: 'All') + expect(page).to have_selector('.row-content-block', text: 'All builds') expect(page.all('.build-link').size).to eq(4) expect(page).to have_link 'Cancel all' end diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index 090a941958..f83a78308e 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -43,6 +43,7 @@ describe "Builds" do end it { expect(page).to have_selector('.nav-links li.active', text: 'All') } + it { expect(page).to have_selector('.row-content-block', text: 'All builds from this project') } it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.name } From 0c22698bd4dbe7d0d3e4a6c8bc946ac6f5de1c12 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Thu, 12 May 2016 22:48:09 +0200 Subject: [PATCH 139/152] Add API endpoints for un/subscribing from/to a label Closes #15638 --- CHANGELOG | 1 + app/models/concerns/subscribable.rb | 6 ++ doc/api/labels.md | 70 +++++++++++++++++++ lib/api/api.rb | 1 + lib/api/entities.rb | 4 ++ lib/api/helpers.rb | 11 +++ lib/api/issues.rb | 39 +---------- lib/api/labels.rb | 6 +- lib/api/merge_requests.rb | 36 ---------- lib/api/subscriptions.rb | 60 +++++++++++++++++ spec/models/concerns/subscribable_spec.rb | 10 +++ spec/requests/api/issues_spec.rb | 12 ++++ spec/requests/api/labels_spec.rb | 82 +++++++++++++++++++++++ 13 files changed, 261 insertions(+), 77 deletions(-) create mode 100644 lib/api/subscriptions.rb diff --git a/CHANGELOG b/CHANGELOG index efb6dc6f61..aedd9f8ebd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -47,6 +47,7 @@ v 8.8.0 (unreleased) - Bump ace-rails-ap gem version from 2.0.1 to 4.0.2 which upgrades Ace Editor from 1.1.2 to 1.2.3 - Total method execution timings are no longer tracked - Allow Admins to remove the Login with buttons for OAuth services and still be able to import !4034. (Andrei Gliga) + - Add API endpoints for un/subscribing from/to a label. !4051 (Ahmad Sherif) v 8.7.5 - Fix relative links in wiki pages. !4050 diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb index d5a881b244..083257f100 100644 --- a/app/models/concerns/subscribable.rb +++ b/app/models/concerns/subscribable.rb @@ -36,6 +36,12 @@ module Subscribable update(subscribed: !subscribed?(user)) end + def subscribe(user) + subscriptions. + find_or_initialize_by(user_id: user.id). + update(subscribed: true) + end + def unsubscribe(user) subscriptions. find_or_initialize_by(user_id: user.id). diff --git a/doc/api/labels.md b/doc/api/labels.md index 3730c07c5a..b857d81768 100644 --- a/doc/api/labels.md +++ b/doc/api/labels.md @@ -165,3 +165,73 @@ Example response: "description": "Documentation" } ``` + +## Subscribe to a label + +Subscribes the authenticated user to a label to receive notifications. If the +operation is successful, status code `201` together with the updated label is +returned. If the user is already subscribed to the label, the status code `304` +is returned. If the project or label is not found, status code `404` is +returned. + +``` +POST /projects/:id/labels/:label_id/subscription +``` + +| Attribute | Type | Required | Description | +| ---------- | ----------------- | -------- | ------------------------------------ | +| `id` | integer | yes | The ID of a project | +| `label_id` | integer or string | yes | The ID or title of a project's label | + +```bash +curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscription +``` + +Example response: + +```json +{ + "name": "Docs", + "color": "#cc0033", + "description": "", + "open_issues_count": 0, + "closed_issues_count": 0, + "open_merge_requests_count": 0, + "subscribed": true +} +``` + +## Unsubscribe from a label + +Unsubscribes the authenticated user from a label to not receive notifications +from it. If the operation is successful, status code `200` together with the +updated label is returned. If the user is not subscribed to the label, the +status code `304` is returned. If the project or label is not found, status code +`404` is returned. + +``` +DELETE /projects/:id/labels/:label_id/subscription +``` + +| Attribute | Type | Required | Description | +| ---------- | ----------------- | -------- | ------------------------------------ | +| `id` | integer | yes | The ID of a project | +| `label_id` | integer or string | yes | The ID or title of a project's label | + +```bash +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/5/labels/1/subscription +``` + +Example response: + +```json +{ + "name": "Docs", + "color": "#cc0033", + "description": "", + "open_issues_count": 0, + "closed_issues_count": 0, + "open_merge_requests_count": 0, + "subscribed": false +} +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 5fd9c30cb4..360fb41a72 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -57,5 +57,6 @@ module API mount ::API::Variables mount ::API::Runners mount ::API::Licenses + mount ::API::Subscriptions end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 2870a6a40e..406f5ea913 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -307,6 +307,10 @@ module API class Label < Grape::Entity expose :name, :color, :description expose :open_issues_count, :closed_issues_count, :open_merge_requests_count + + expose :subscribed do |label, options| + label.subscribed?(options[:current_user]) + end end class Compare < Grape::Entity diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 40c967453f..5e638dbe16 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -95,6 +95,17 @@ module API end end + def find_project_label(id) + label = user_project.labels.find_by_id(id) || user_project.labels.find_by_title(id) + label || not_found!('Label') + end + + def find_project_issue(id) + issue = user_project.issues.find(id) + not_found! unless can?(current_user, :read_issue, issue) + issue + end + def paginate(relation) relation.page(params[:page]).per(params[:per_page].to_i).tap do |data| add_pagination_headers(data) diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 4092874948..f59a4d6c01 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -103,8 +103,7 @@ module API # Example Request: # GET /projects/:id/issues/:issue_id get ":id/issues/:issue_id" do - @issue = user_project.issues.find(params[:issue_id]) - not_found! unless can?(current_user, :read_issue, @issue) + @issue = find_project_issue(params[:issue_id]) present @issue, with: Entities::Issue, current_user: current_user end @@ -234,42 +233,6 @@ module API authorize!(:destroy_issue, issue) issue.destroy end - - # Subscribes to a project issue - # - # Parameters: - # id (required) - The ID of a project - # issue_id (required) - The ID of a project issue - # Example Request: - # POST /projects/:id/issues/:issue_id/subscription - post ':id/issues/:issue_id/subscription' do - issue = user_project.issues.find(params[:issue_id]) - - if issue.subscribed?(current_user) - not_modified! - else - issue.toggle_subscription(current_user) - present issue, with: Entities::Issue, current_user: current_user - end - end - - # Unsubscribes from a project issue - # - # Parameters: - # id (required) - The ID of a project - # issue_id (required) - The ID of a project issue - # Example Request: - # DELETE /projects/:id/issues/:issue_id/subscription - delete ':id/issues/:issue_id/subscription' do - issue = user_project.issues.find(params[:issue_id]) - - if issue.subscribed?(current_user) - issue.unsubscribe(current_user) - present issue, with: Entities::Issue, current_user: current_user - else - not_modified! - end - end end end end diff --git a/lib/api/labels.rb b/lib/api/labels.rb index 4af6bef0fa..c806829d69 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -11,7 +11,7 @@ module API # Example Request: # GET /projects/:id/labels get ':id/labels' do - present user_project.labels, with: Entities::Label + present user_project.labels, with: Entities::Label, current_user: current_user end # Creates a new label @@ -36,7 +36,7 @@ module API label = user_project.labels.create(attrs) if label.valid? - present label, with: Entities::Label + present label, with: Entities::Label, current_user: current_user else render_validation_error!(label) end @@ -90,7 +90,7 @@ module API attrs[:name] = attrs.delete(:new_name) if attrs.key?(:new_name) if label.update(attrs) - present label, with: Entities::Label + present label, with: Entities::Label, current_user: current_user else render_validation_error!(label) end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 7e78609ecb..4e7de8867b 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -327,42 +327,6 @@ module API issues = ::Kaminari.paginate_array(merge_request.closes_issues(current_user)) present paginate(issues), with: Entities::Issue, current_user: current_user end - - # Subscribes to a merge request - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - The ID of a merge request - # Example Request: - # POST /projects/:id/issues/:merge_request_id/subscription - post "#{path}/subscription" do - merge_request = user_project.merge_requests.find(params[:merge_request_id]) - - if merge_request.subscribed?(current_user) - not_modified! - else - merge_request.toggle_subscription(current_user) - present merge_request, with: Entities::MergeRequest, current_user: current_user - end - end - - # Unsubscribes from a merge request - # - # Parameters: - # id (required) - The ID of a project - # merge_request_id (required) - The ID of a merge request - # Example Request: - # DELETE /projects/:id/merge_requests/:merge_request_id/subscription - delete "#{path}/subscription" do - merge_request = user_project.merge_requests.find(params[:merge_request_id]) - - if merge_request.subscribed?(current_user) - merge_request.unsubscribe(current_user) - present merge_request, with: Entities::MergeRequest, current_user: current_user - else - not_modified! - end - end end end end diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb new file mode 100644 index 0000000000..c49e2a21b8 --- /dev/null +++ b/lib/api/subscriptions.rb @@ -0,0 +1,60 @@ +module API + class Subscriptions < Grape::API + before { authenticate! } + + subscribable_types = { + 'merge_request' => proc { |id| user_project.merge_requests.find(id) }, + 'merge_requests' => proc { |id| user_project.merge_requests.find(id) }, + 'issues' => proc { |id| find_project_issue(id) }, + 'labels' => proc { |id| find_project_label(id) }, + } + + resource :projects do + subscribable_types.each do |type, finder| + type_singularized = type.singularize + type_id_str = :"#{type_singularized}_id" + entity_class = Entities.const_get(type_singularized.camelcase) + + # Subscribe to a resource + # + # Parameters: + # id (required) - The ID of a project + # subscribable_id (required) - The ID of a resource + # Example Request: + # POST /projects/:id/labels/:subscribable_id/subscription + # POST /projects/:id/issues/:subscribable_id/subscription + # POST /projects/:id/merge_requests/:subscribable_id/subscription + post ":id/#{type}/:#{type_id_str}/subscription" do + resource = instance_exec(params[type_id_str], &finder) + + if resource.subscribed?(current_user) + not_modified! + else + resource.subscribe(current_user) + present resource, with: entity_class, current_user: current_user + end + end + + # Unsubscribe from a resource + # + # Parameters: + # id (required) - The ID of a project + # subscribable_id (required) - The ID of a resource + # Example Request: + # DELETE /projects/:id/labels/:subscribable_id/subscription + # DELETE /projects/:id/issues/:subscribable_id/subscription + # DELETE /projects/:id/merge_requests/:subscribable_id/subscription + delete ":id/#{type}/:#{type_id_str}/subscription" do + resource = instance_exec(params[type_id_str], &finder) + + if !resource.subscribed?(current_user) + not_modified! + else + resource.unsubscribe(current_user) + present resource, with: entity_class, current_user: current_user + end + end + end + end + end +end diff --git a/spec/models/concerns/subscribable_spec.rb b/spec/models/concerns/subscribable_spec.rb index e31fdb0bff..b7fc5a9249 100644 --- a/spec/models/concerns/subscribable_spec.rb +++ b/spec/models/concerns/subscribable_spec.rb @@ -44,6 +44,16 @@ describe Subscribable, 'Subscribable' do end end + describe '#subscribe' do + it 'subscribes the given user' do + expect(resource.subscribed?(user)).to be_falsey + + resource.subscribe(user) + + expect(resource.subscribed?(user)).to be_truthy + end + end + describe '#unsubscribe' do it 'unsubscribes the given current user' do resource.subscriptions.create(user: user, subscribed: true) diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 9dd43f4fab..37ab9cc8cf 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -623,6 +623,12 @@ describe API::API, api: true do expect(response.status).to eq(404) end + + it 'returns 404 if the issue is confidential' do + post api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member) + + expect(response.status).to eq(404) + end end describe 'DELETE :id/issues/:issue_id/subscription' do @@ -644,5 +650,11 @@ describe API::API, api: true do expect(response.status).to eq(404) end + + it 'returns 404 if the issue is confidential' do + delete api("/projects/#{project.id}/issues/#{confidential_issue.id}/subscription", non_member) + + expect(response.status).to eq(404) + end end end diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 6943ff9d26..b2c7f8d9ac 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -190,4 +190,86 @@ describe API::API, api: true do expect(json_response['message']['color']).to eq(['must be a valid color code']) end end + + describe "POST /projects/:id/labels/:label_id/subscription" do + context "when label_id is a label title" do + it "should subscribe to the label" do + post api("/projects/#{project.id}/labels/#{label1.title}/subscription", user) + + expect(response.status).to eq(201) + expect(json_response["name"]).to eq(label1.title) + expect(json_response["subscribed"]).to be_truthy + end + end + + context "when label_id is a label ID" do + it "should subscribe to the label" do + post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) + + expect(response.status).to eq(201) + expect(json_response["name"]).to eq(label1.title) + expect(json_response["subscribed"]).to be_truthy + end + end + + context "when user is already subscribed to label" do + before { label1.subscribe(user) } + + it "should return 304" do + post api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) + + expect(response.status).to eq(304) + end + end + + context "when label ID is not found" do + it "should a return 404 error" do + post api("/projects/#{project.id}/labels/1234/subscription", user) + + expect(response.status).to eq(404) + end + end + end + + describe "DELETE /projects/:id/labels/:label_id/subscription" do + before { label1.subscribe(user) } + + context "when label_id is a label title" do + it "should unsubscribe from the label" do + delete api("/projects/#{project.id}/labels/#{label1.title}/subscription", user) + + expect(response.status).to eq(200) + expect(json_response["name"]).to eq(label1.title) + expect(json_response["subscribed"]).to be_falsey + end + end + + context "when label_id is a label ID" do + it "should unsubscribe from the label" do + delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) + + expect(response.status).to eq(200) + expect(json_response["name"]).to eq(label1.title) + expect(json_response["subscribed"]).to be_falsey + end + end + + context "when user is already unsubscribed from label" do + before { label1.unsubscribe(user) } + + it "should return 304" do + delete api("/projects/#{project.id}/labels/#{label1.id}/subscription", user) + + expect(response.status).to eq(304) + end + end + + context "when label ID is not found" do + it "should a return 404 error" do + delete api("/projects/#{project.id}/labels/1234/subscription", user) + + expect(response.status).to eq(404) + end + end + end end From e4d3e949555c51cf54ead163bf001b4ec86a2733 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 12 May 2016 16:22:21 -0500 Subject: [PATCH 140/152] Move broadcast msg into content-wrapper --- app/views/layouts/_page.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 3c3bc41bf0..3c0660d34f 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -1,5 +1,4 @@ .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" } - = render "layouts/broadcast" .sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .header-logo %a#logo @@ -27,6 +26,7 @@ .container-fluid = render "layouts/nav/#{nav}" .content-wrapper{ class: ('page-with-layout-nav' if defined?(nav) && nav) } + = render "layouts/broadcast" = render "layouts/flash" = yield :flash_message %div{ class: (container_class unless @no_container) } From 05bbad5817c567b547177441769643042d699d8a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 11 May 2016 15:17:16 -0500 Subject: [PATCH 141/152] Clean up EventsHelper spec --- spec/helpers/events_helper_spec.rb | 95 +++++++++++++++--------------- 1 file changed, 48 insertions(+), 47 deletions(-) diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb index e68a5ec29a..c0d2be98e8 100644 --- a/spec/helpers/events_helper_spec.rb +++ b/spec/helpers/events_helper_spec.rb @@ -1,64 +1,65 @@ require 'spec_helper' describe EventsHelper do - include ApplicationHelper - include GitlabMarkdownHelper + describe '#event_note' do + before do + allow(helper).to receive(:current_user).and_return(double) + end - let(:current_user) { create(:user, email: "current@email.com") } + it 'should display one line of plain text without alteration' do + input = 'A short, plain note' + expect(helper.event_note(input)).to match(input) + expect(helper.event_note(input)).not_to match(/\.\.\.\z/) + end - it 'should display one line of plain text without alteration' do - input = 'A short, plain note' - expect(event_note(input)).to match(input) - expect(event_note(input)).not_to match(/\.\.\.\z/) - end + it 'should display inline code' do + input = 'A note with `inline code`' + expected = 'A note with inline code' - it 'should display inline code' do - input = 'A note with `inline code`' - expected = 'A note with inline code' + expect(helper.event_note(input)).to match(expected) + end - expect(event_note(input)).to match(expected) - end + it 'should truncate a note with multiple paragraphs' do + input = "Paragraph 1\n\nParagraph 2" + expected = 'Paragraph 1...' - it 'should truncate a note with multiple paragraphs' do - input = "Paragraph 1\n\nParagraph 2" - expected = 'Paragraph 1...' + expect(helper.event_note(input)).to match(expected) + end - expect(event_note(input)).to match(expected) - end + it 'should display the first line of a code block' do + input = "```\nCode block\nwith two lines\n```" + expected = %r{Code block\.\.\.

    } - it 'should display the first line of a code block' do - input = "```\nCode block\nwith two lines\n```" - expected = %r{Code block\.\.\.
    } + expect(helper.event_note(input)).to match(expected) + end - expect(event_note(input)).to match(expected) - end + it 'should truncate a single long line of text' do + text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars + input = text * 4 + expected = (text * 2).sub(/.{3}/, '...') - it 'should truncate a single long line of text' do - text = 'The quick brown fox jumped over the lazy dog twice' # 50 chars - input = "#{text}#{text}#{text}#{text}" # 200 chars - expected = "#{text}#{text}".sub(/.{3}/, '...') + expect(helper.event_note(input)).to match(expected) + end - expect(event_note(input)).to match(expected) - end + it 'should preserve a link href when link text is truncated' do + text = 'The quick brown fox jumped over the lazy dog' # 44 chars + input = "#{text}#{text}#{text} " # 133 chars + link_url = 'http://example.com/foo/bar/baz' # 30 chars + input << link_url + expected_link_text = 'http://example...' - it 'should preserve a link href when link text is truncated' do - text = 'The quick brown fox jumped over the lazy dog' # 44 chars - input = "#{text}#{text}#{text} " # 133 chars - link_url = 'http://example.com/foo/bar/baz' # 30 chars - input << link_url - expected_link_text = 'http://example...' + expect(helper.event_note(input)).to match(link_url) + expect(helper.event_note(input)).to match(expected_link_text) + end - expect(event_note(input)).to match(link_url) - expect(event_note(input)).to match(expected_link_text) - end - - it 'should preserve code color scheme' do - input = "```ruby\ndef test\n 'hello world'\nend\n```" - expected = '
    ' \
    -      "def test\n" \
    -      "  \'hello world\'\n" \
    -      "end" \
    -      '
    ' - expect(event_note(input)).to eq(expected) + it 'should preserve code color scheme' do + input = "```ruby\ndef test\n 'hello world'\nend\n```" + expected = '
    ' \
    +        "def test\n" \
    +        "  \'hello world\'\n" \
    +        "end" \
    +        '
    ' + expect(helper.event_note(input)).to eq(expected) + end end end From 7bc594735b7923d1e57eb504f09716bbb72c8c06 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 12 May 2016 17:06:47 -0500 Subject: [PATCH 142/152] Undo changes; reduce sidebar internal width --- app/assets/stylesheets/framework/sidebar.scss | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index c243dd8a42..f90d7a806d 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -63,7 +63,7 @@ float: left; height: $header-height; width: 100%; - padding-left: 16px; + padding-left: 22px; overflow: hidden; outline: none; transition-duration: .3s; @@ -78,7 +78,7 @@ } .gitlab-text-container { - width: 220px; + width: 230px; h3 { width: 158px; @@ -146,12 +146,12 @@ a { padding: 7px 15px; - transition-duration: .3s; font-size: $gl-font-size; line-height: 24px; color: $gray; display: block; text-decoration: none; + padding-left: 23px; font-weight: normal; outline: none; @@ -253,11 +253,6 @@ width: auto; a { - @media (min-width: $screen-xs-min) { - padding-left: 23px; - transition-duration: .3s; - } - span { display: none; } @@ -317,7 +312,7 @@ } .nav-sidebar li a { - width: 220px; + width: $sidebar_width; &.back-link { i { From 4fea5cda0bf7ff56dafae90306441b0d65f3ca05 Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Thu, 12 May 2016 17:11:37 -0500 Subject: [PATCH 143/152] Fix minor typos in admin health check page --- app/views/admin/health_check/show.html.haml | 2 +- spec/features/admin/admin_health_check_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/admin/health_check/show.html.haml b/app/views/admin/health_check/show.html.haml index ad79fd26d0..c2313986a7 100644 --- a/app/views/admin/health_check/show.html.haml +++ b/app/views/admin/health_check/show.html.haml @@ -13,7 +13,7 @@ = icon('refresh') Reset health check access token %p.light - Health information can be reteived as plain text, json, or xml using: + Health information can be retrieved as plain text, JSON, or XML using: %ul %li %code= health_check_url(token: current_application_settings.health_check_access_token) diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb index 4fde04b609..dec2dedf2b 100644 --- a/spec/features/admin/admin_health_check_spec.rb +++ b/spec/features/admin/admin_health_check_spec.rb @@ -13,7 +13,7 @@ feature "Admin Health Check", feature: true do end it { page.has_text? 'Health Check' } - it { page.has_text? 'Health information can be reteived' } + it { page.has_text? 'Health information can be retrieved' } it 'has a health check access token' do token = current_application_settings.health_check_access_token From 311fb58c44fe6dd14709c8eb8f9346ec73e03aff Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Tue, 10 May 2016 23:24:23 -0300 Subject: [PATCH 144/152] Require space around operators --- .rubocop.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 3fb2f2df59..562197300b 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -276,7 +276,7 @@ Style/IdenticalConditionalBranches: Enabled: false # Checks the indentation of the first line of the right-hand-side of a -# multi-line assignment. +# multi-line assignment. Style/IndentAssignment: Enabled: false @@ -531,7 +531,7 @@ Style/SpaceAroundKeyword: # Use a single space around operators. Style/SpaceAroundOperators: - Enabled: false + Enabled: true # Checks that the left block brace has or doesn't have space before it. Style/SpaceBeforeBlockBraces: From f5a0ac0fc197bae2eb5fe1045ed237cdbbaf6ea4 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Tue, 10 May 2016 23:58:06 -0300 Subject: [PATCH 145/152] Codestyle: make sure we have space around operators --- app/controllers/application_controller.rb | 2 +- app/controllers/concerns/creates_commit.rb | 2 +- app/helpers/search_helper.rb | 2 +- app/helpers/selects_helper.rb | 2 +- .../project_services/external_wiki_service.rb | 2 +- app/models/project_wiki.rb | 2 +- app/models/repository.rb | 4 ++-- app/services/merge_requests/build_service.rb | 2 +- app/services/system_hooks_service.rb | 2 +- config/environments/test.rb | 2 +- config/initializers/1_settings.rb | 18 ++++++++--------- config/initializers/carrierwave.rb | 2 +- config/initializers/devise.rb | 2 +- config/routes.rb | 2 +- features/steps/dashboard/issues.rb | 2 +- features/steps/dashboard/merge_requests.rb | 2 +- features/steps/profile/profile.rb | 2 +- features/steps/project/team_management.rb | 2 +- lib/api/helpers.rb | 2 +- lib/gitlab/email/reply_parser.rb | 2 +- lib/gitlab/redis.rb | 6 +++--- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 4 ++-- .../ci/build/artifacts/metadata/entry_spec.rb | 4 ++-- spec/lib/gitlab/lfs/lfs_router_spec.rb | 4 ++-- spec/models/commit_spec.rb | 2 +- spec/models/hooks/service_hook_spec.rb | 4 ++-- spec/models/hooks/system_hook_spec.rb | 20 +++++++++---------- .../slack_service/note_message_spec.rb | 4 ++-- spec/requests/api/builds_spec.rb | 4 ++-- spec/requests/api/group_members_spec.rb | 10 +++++----- spec/requests/ci/api/builds_spec.rb | 4 ++-- spec/support/jira_service_helper.rb | 10 +++++----- spec/support/stub_gitlab_calls.rb | 20 +++++++++---------- 33 files changed, 77 insertions(+), 77 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 17b3f49aed..1429ee40bb 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -176,7 +176,7 @@ class ApplicationController < ActionController::Base end def check_password_expiration - if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user? + if current_user && current_user.password_expires_at && current_user.password_expires_at < Time.now && !current_user.ldap_user? redirect_to new_profile_password_path and return end end diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index 787416c17a..dacb5679dd 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -122,7 +122,7 @@ module CreatesCommit # Merge request from fork to this project @mr_source_project = @tree_edit_project @mr_target_project = @project - @mr_target_branch ||= @ref + @mr_target_branch ||= @ref end end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 24c4c098c6..d2f94d4ae6 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -59,7 +59,7 @@ module SearchHelper # Autocomplete results for the current project, if it's defined def project_autocomplete if @project && @project.repository.exists? && @project.repository.root_ref - ref = @ref || @project.repository.root_ref + ref = @ref || @project.repository.root_ref [ { category: "Current Project", label: "Files", url: namespace_project_tree_path(@project.namespace, @project, ref) }, diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index e951a87a21..bb395e3788 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -18,7 +18,7 @@ module SelectsHelper first_user: first_user, current_user: opts[:current_user] || false, "push-code-to-protected-branches" => opts[:push_code_to_protected_branches], - author_id: opts[:author_id] || '' + author_id: opts[:author_id] || '' } } diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb index f042bfc521..d7b6e50519 100644 --- a/app/models/project_services/external_wiki_service.rb +++ b/app/models/project_services/external_wiki_service.rb @@ -25,7 +25,7 @@ class ExternalWikiService < Service def execute(_data) @response = HTTParty.get(properties['external_wiki_url'], verify: true) rescue nil - if @response !=200 + if @response != 200 nil end end diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 7c1a61bb0b..060ed9b44e 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -113,7 +113,7 @@ class ProjectWiki end def page_title_and_dir(title) - title_array = title.split("/") + title_array = title.split("/") title = title_array.pop [title, title_array.join("/")] end diff --git a/app/models/repository.rb b/app/models/repository.rb index de7e163078..0eff74320f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -795,7 +795,7 @@ class Repository def check_revert_content(commit, base_branch) source_sha = find_branch(base_branch).target args = [commit.id, source_sha] - args << { mainline: 1 } if commit.merge_commit? + args << { mainline: 1 } if commit.merge_commit? revert_index = rugged.revert_commit(*args) return false if revert_index.conflicts? @@ -809,7 +809,7 @@ class Repository def check_cherry_pick_content(commit, base_branch) source_sha = find_branch(base_branch).target args = [commit.id, source_sha] - args << 1 if commit.merge_commit? + args << 1 if commit.merge_commit? cherry_pick_index = rugged.cherrypick_commit(*args) return false if cherry_pick_index.conflicts? diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index cd4230aa5e..1b48899bb0 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -66,7 +66,7 @@ module MergeRequests commits = merge_request.compare_commits if commits && commits.count == 1 commit = commits.first - merge_request.title = commit.title + merge_request.title = commit.title merge_request.description ||= commit.description.try(:strip) elsif iid && (issue = merge_request.target_project.get_issue(iid)) && !issue.try(:confidential?) case issue diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index e43b5b51e5..1fb72cf89e 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -85,7 +85,7 @@ class SystemHooksService path_with_namespace: model.path_with_namespace, project_id: model.id, owner_name: owner.name, - owner_email: owner.respond_to?(:email) ? owner.email : "", + owner_email: owner.respond_to?(:email) ? owner.email : "", project_visibility: Project.visibility_levels.key(model.visibility_level_field).downcase } end diff --git a/config/environments/test.rb b/config/environments/test.rb index a703c0934f..fb25d3a8b1 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -20,7 +20,7 @@ Rails.application.configure do config.action_dispatch.show_exceptions = false # Disable request forgery protection in test environment - config.action_controller.allow_forgery_protection = false + config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 23c8cea038..a69b933d81 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -126,7 +126,7 @@ end Settings['omniauth'] ||= Settingslogic.new({}) -Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil? +Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil? Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['auto_sign_in_with_provider'].nil? Settings.omniauth['allow_single_sign_on'] = false if Settings.omniauth['allow_single_sign_on'].nil? Settings.omniauth['external_providers'] = [] if Settings.omniauth['external_providers'].nil? @@ -134,7 +134,7 @@ Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil? Settings.omniauth['auto_link_saml_user'] = false if Settings.omniauth['auto_link_saml_user'].nil? -Settings.omniauth['providers'] ||= [] +Settings.omniauth['providers'] ||= [] Settings.omniauth['cas3'] ||= Settingslogic.new({}) Settings.omniauth.cas3['session_duration'] ||= 8.hours Settings.omniauth['session_tickets'] ||= Settingslogic.new({}) @@ -168,7 +168,7 @@ end Settings['shared'] ||= Settingslogic.new({}) Settings.shared['path'] = File.expand_path(Settings.shared['path'] || "shared", Rails.root) -Settings['issues_tracker'] ||= {} +Settings['issues_tracker'] ||= {} # # GitLab @@ -183,7 +183,7 @@ Settings.gitlab['ssh_host'] ||= Settings.gitlab.host Settings.gitlab['https'] = false if Settings.gitlab['https'].nil? Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80 Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || '' -Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http" +Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http" Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil? Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings.gitlab.host}" Settings.gitlab['email_display_name'] ||= ENV['GITLAB_EMAIL_DISPLAY_NAME'] || 'GitLab' @@ -196,7 +196,7 @@ Settings.gitlab['user_home'] ||= begin rescue ArgumentError # no user configured '/home/' + Settings.gitlab['user'] end -Settings.gitlab['time_zone'] ||= nil +Settings.gitlab['time_zone'] ||= nil Settings.gitlab['signup_enabled'] ||= true if Settings.gitlab['signup_enabled'].nil? Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil? Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) @@ -211,7 +211,7 @@ Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.g 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.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'] ||= [] Settings.gitlab['import_sources'] ||= ['github','bitbucket','gitlab','gitorious','google_code','fogbugz','git'] @@ -225,8 +225,8 @@ Settings['gitlab_ci'] ||= Settingslogic.new({}) Settings.gitlab_ci['shared_runners_enabled'] = true if Settings.gitlab_ci['shared_runners_enabled'].nil? Settings.gitlab_ci['all_broken_builds'] = true if Settings.gitlab_ci['all_broken_builds'].nil? 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['url'] ||= Settings.send(:build_gitlab_ci_url) # # Reply by email @@ -240,7 +240,7 @@ Settings.incoming_email['enabled'] = false if Settings.incoming_email['enabled'] 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 +Settings.artifacts['max_size'] ||= 100 # in megabytes # # Git LFS @@ -298,7 +298,7 @@ Settings['backup'] ||= Settingslogic.new({}) Settings.backup['keep_time'] ||= 0 Settings.backup['pg_schema'] = nil Settings.backup['path'] = File.expand_path(Settings.backup['path'] || "tmp/backups/", Rails.root) -Settings.backup['archive_permissions'] ||= 0600 +Settings.backup['archive_permissions'] ||= 0600 Settings.backup['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'connection' => nil }) # Convert upload connection settings to use symbol keys, to make Fog happy if Settings.backup['upload']['connection'] diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb index 0d278717d6..1933afcbfb 100644 --- a/config/initializers/carrierwave.rb +++ b/config/initializers/carrierwave.rb @@ -20,7 +20,7 @@ if File.exist?(aws_file) config.fog_public = false # optional, defaults to {} - config.fog_attributes = { 'Cache-Control'=>'max-age=315576000' } + config.fog_attributes = { 'Cache-Control' => 'max-age=315576000' } # optional time (in seconds) that authenticated urls will be valid. # when fog_public is false and provider is AWS or Google, defaults to 600 diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 31dceaebca..021bdb1125 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -243,7 +243,7 @@ Devise.setup do |config| when Hash # Add procs for handling SLO if provider['name'] == 'cas3' - provider['args'][:on_single_sign_out] = lambda do |request| + provider['args'][:on_single_sign_out] = lambda do |request| ticket = request.params[:session_index] raise "Service Ticket not found." unless Gitlab::OAuth::Session.valid?(:cas3, ticket) Gitlab::OAuth::Session.destroy(:cas3, ticket) diff --git a/config/routes.rb b/config/routes.rb index ec876eba34..0c01c52921 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -83,7 +83,7 @@ Rails.application.routes.draw do get 'help' => 'help#index' get 'help/:category/:file' => 'help#show', as: :help_page, constraints: { category: /.*/, file: /[^\/\.]+/ } get 'help/shortcuts' - get 'help/ui' => 'help#ui' + get 'help/ui' => 'help#ui' # # Global snippets diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb index e21af72a77..8706f0e8e7 100644 --- a/features/steps/dashboard/issues.rb +++ b/features/steps/dashboard/issues.rb @@ -74,7 +74,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps def project @project ||= begin - project =create :project + project = create :project project.team << [current_user, :master] project end diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb index a2adc87f8e..06db36c701 100644 --- a/features/steps/dashboard/merge_requests.rb +++ b/features/steps/dashboard/merge_requests.rb @@ -100,7 +100,7 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps def project @project ||= begin - project =create :project + project = create :project project.team << [current_user, :master] project end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 909de31a47..b1a87b96ef 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -166,7 +166,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps end step 'I have group with projects' do - @group = create(:group) + @group = create(:group) @group.add_owner(current_user) @project = create(:project, namespace: @group) @event = create(:closed_issue_event, project: @project) diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb index 3fbcf770b6..c6ced74737 100644 --- a/features/steps/project/team_management.rb +++ b/features/steps/project/team_management.rb @@ -126,7 +126,7 @@ class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps step 'I share project with group "OpenSource"' do project = Project.find_by(name: 'Shop') - os_group = create(:group, name: 'OpenSource') + os_group = create(:group, name: 'OpenSource') create(:project, group: os_group) @os_user1 = create(:user) @os_user2 = create(:user) diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 5e638dbe16..cadf9f98fe 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -2,7 +2,7 @@ module API module Helpers PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN" PRIVATE_TOKEN_PARAM = :private_token - SUDO_HEADER ="HTTP_SUDO" + SUDO_HEADER = "HTTP_SUDO" SUDO_PARAM = :sudo def parse_boolean(value) diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb index 6ed36b51f1..3411eb1d9c 100644 --- a/lib/gitlab/email/reply_parser.rb +++ b/lib/gitlab/email/reply_parser.rb @@ -65,7 +65,7 @@ module Gitlab (l =~ /On \w+ \d+,? \d+,?.*wrote:/) # Headers on subsequent lines - break if (0..2).all? { |off| lines[idx+off] =~ REPLYING_HEADER_REGEX } + break if (0..2).all? { |off| lines[idx + off] =~ REPLYING_HEADER_REGEX } # Headers on the same line break if REPLYING_HEADER_LABELS.count { |label| l.include?(label) } >= 3 diff --git a/lib/gitlab/redis.rb b/lib/gitlab/redis.rb index f7db7a6391..40766f35f7 100644 --- a/lib/gitlab/redis.rb +++ b/lib/gitlab/redis.rb @@ -25,7 +25,7 @@ module Gitlab end @pool.with { |redis| yield redis } end - + def self.redis_store_options url = new.url redis_config_hash = ::Redis::Store::Factory.extract_host_options_from_uri(url) @@ -40,10 +40,10 @@ module Gitlab def initialize(rails_env=nil) rails_env ||= Rails.env config_file = File.expand_path('../../../config/resque.yml', __FILE__) - + @url = "redis://localhost:6379" if File.exist?(config_file) - @url =YAML.load_file(config_file)[rails_env] + @url = YAML.load_file(config_file)[rails_env] end end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index c7ab318537..9eef8ea097 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -443,12 +443,12 @@ module Ci context 'when job variables are defined' do context 'when syntax is correct' do it 'returns job variables' do - variables = { + variables = { KEY1: 'value1', SOME_KEY_2: 'value2' } - config = YAML.dump( + config = YAML.dump( { before_script: ['pwd'], rspec: { variables: variables, diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb index acca0b08ba..46a5b7fce6 100644 --- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb +++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb @@ -10,8 +10,8 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do 'path/dir_1/subdir/subfile' => { size: 10 }, 'path/second_dir' => {}, 'path/second_dir/dir_3/file_2' => { size: 10 }, - 'path/second_dir/dir_3/file_3'=> { size: 10 }, - 'another_directory/'=> {}, + 'path/second_dir/dir_3/file_3' => { size: 10 }, + 'another_directory/' => {}, 'another_file' => {}, '/file/with/absolute_path' => {} } end diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb index 5852b31ab3..3325190789 100644 --- a/spec/lib/gitlab/lfs/lfs_router_spec.rb +++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb @@ -26,8 +26,8 @@ describe Gitlab::Lfs::Router, lib: true 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 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\"}"]]} + 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 before do diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index ad47e338a3..ccb100cd96 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -56,7 +56,7 @@ describe Commit, models: true do end it "does not truncates a message with a newline after 80 but less 100 characters" do - message =<'application/json', 'X-Gitlab-Event'=>'Service Hook' } + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Service Hook' } ).once end it "POSTs the data as JSON" do @service_hook.execute(@data) expect(WebMock).to have_requested(:post, @service_hook.url).with( - headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Service Hook' } + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'Service Hook' } ).once end diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index 56a9fbe972..4078b9e4ff 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -33,7 +33,7 @@ describe SystemHook, models: true do Projects::CreateService.new(user, name: 'empty').execute expect(WebMock).to have_requested(:post, system_hook.url).with( body: /project_create/, - headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } ).once end @@ -42,7 +42,7 @@ describe SystemHook, models: true do expect(WebMock).to have_requested(:post, system_hook.url).with( body: /project_destroy/, - headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } ).once end @@ -51,7 +51,7 @@ describe SystemHook, models: true do expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_create/, - headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } ).once end @@ -60,7 +60,7 @@ describe SystemHook, models: true do expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_destroy/, - headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } ).once end @@ -69,7 +69,7 @@ describe SystemHook, models: true do expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_add_to_team/, - headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } ).once end @@ -79,7 +79,7 @@ describe SystemHook, models: true do expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_remove_from_team/, - headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } ).once end @@ -88,7 +88,7 @@ describe SystemHook, models: true do expect(WebMock).to have_requested(:post, system_hook.url).with( body: /group_create/, - headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } ).once end @@ -97,7 +97,7 @@ describe SystemHook, models: true do expect(WebMock).to have_requested(:post, system_hook.url).with( body: /group_destroy/, - headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } ).once end @@ -106,7 +106,7 @@ describe SystemHook, models: true do expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_add_to_group/, - headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } ).once end @@ -116,7 +116,7 @@ describe SystemHook, models: true do expect(WebMock).to have_requested(:post, system_hook.url).with( body: /user_remove_from_group/, - headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' } + headers: { 'Content-Type' => 'application/json', 'X-Gitlab-Event' => 'System Hook' } ).once end end diff --git a/spec/models/project_services/slack_service/note_message_spec.rb b/spec/models/project_services/slack_service/note_message_spec.rb index d37590cab7..379c3e1219 100644 --- a/spec/models/project_services/slack_service/note_message_spec.rb +++ b/spec/models/project_services/slack_service/note_message_spec.rb @@ -65,7 +65,7 @@ describe SlackService::NoteMessage, models: true do expect(message.pretext).to eq("Test User commented on " \ " in : " \ "*merge request title*") - expected_attachments = [ + expected_attachments = [ { text: "comment on a merge request", color: color, @@ -117,7 +117,7 @@ describe SlackService::NoteMessage, models: true do expect(message.pretext).to eq("Test User commented on " \ " in : " \ "*snippet title*") - expected_attachments = [ + expected_attachments = [ { text: "comment on a snippet", color: color, diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 5ead735be4..0fbc984c06 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -106,8 +106,8 @@ describe API::API, api: true do context 'authorized user' do let(:download_headers) do - { 'Content-Transfer-Encoding'=>'binary', - 'Content-Disposition'=>'attachment; filename=ci_build_artifacts.zip' } + { 'Content-Transfer-Encoding' => 'binary', + 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' } end it 'should return specific build artifacts' do diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb index 96d89e6920..02553d0f8e 100644 --- a/spec/requests/api/group_members_spec.rb +++ b/spec/requests/api/group_members_spec.rb @@ -34,11 +34,11 @@ describe API::API, api: true do expect(response.status).to eq(200) expect(json_response).to be_an Array expect(json_response.size).to eq(5) - expect(json_response.find { |e| e['id']==owner.id }['access_level']).to eq(GroupMember::OWNER) - expect(json_response.find { |e| e['id']==reporter.id }['access_level']).to eq(GroupMember::REPORTER) - expect(json_response.find { |e| e['id']==developer.id }['access_level']).to eq(GroupMember::DEVELOPER) - expect(json_response.find { |e| e['id']==master.id }['access_level']).to eq(GroupMember::MASTER) - expect(json_response.find { |e| e['id']==guest.id }['access_level']).to eq(GroupMember::GUEST) + expect(json_response.find { |e| e['id'] == owner.id }['access_level']).to eq(GroupMember::OWNER) + expect(json_response.find { |e| e['id'] == reporter.id }['access_level']).to eq(GroupMember::REPORTER) + expect(json_response.find { |e| e['id'] == developer.id }['access_level']).to eq(GroupMember::DEVELOPER) + expect(json_response.find { |e| e['id'] == master.id }['access_level']).to eq(GroupMember::MASTER) + expect(json_response.find { |e| e['id'] == guest.id }['access_level']).to eq(GroupMember::GUEST) end end diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index dfd361a2cd..cae4656010 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -402,8 +402,8 @@ describe Ci::API::API do context 'build has artifacts' do let(:build) { create(:ci_build, :artifacts) } let(:download_headers) do - { 'Content-Transfer-Encoding'=>'binary', - 'Content-Disposition'=>'attachment; filename=ci_build_artifacts.zip' } + { 'Content-Transfer-Encoding' => 'binary', + 'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' } end it 'should download artifact' do diff --git a/spec/support/jira_service_helper.rb b/spec/support/jira_service_helper.rb index a3f496359b..5ebe095743 100644 --- a/spec/support/jira_service_helper.rb +++ b/spec/support/jira_service_helper.rb @@ -2,11 +2,11 @@ module JiraServiceHelper def jira_service_settings properties = { - "title"=>"JIRA tracker", - "project_url"=>"http://jira.example/issues/?jql=project=A", - "issues_url"=>"http://jira.example/browse/JIRA-1", - "new_issue_url"=>"http://jira.example/secure/CreateIssue.jspa", - "api_url"=>"http://jira.example/rest/api/2" + "title" => "JIRA tracker", + "project_url" => "http://jira.example/issues/?jql=project=A", + "issues_url" => "http://jira.example/browse/JIRA-1", + "new_issue_url" => "http://jira.example/secure/CreateIssue.jspa", + "api_url" => "http://jira.example/rest/api/2" } jira_tracker.update_attributes(properties: properties, active: true) diff --git a/spec/support/stub_gitlab_calls.rb b/spec/support/stub_gitlab_calls.rb index eec2e68111..b5ca34bc02 100644 --- a/spec/support/stub_gitlab_calls.rb +++ b/spec/support/stub_gitlab_calls.rb @@ -36,20 +36,20 @@ module StubGitlabCalls stub_request(:post, "#{gitlab_url}api/v3/session.json"). with(body: "{\"email\":\"test@test.com\",\"password\":\"123456\"}", - headers: { 'Content-Type'=>'application/json' }). - to_return(status: 201, body: f, headers: { 'Content-Type'=>'application/json' }) + headers: { 'Content-Type' => 'application/json' }). + to_return(status: 201, body: f, headers: { 'Content-Type' => 'application/json' }) end def stub_user f = File.read(Rails.root.join('spec/support/gitlab_stubs/user.json')) stub_request(:get, "#{gitlab_url}api/v3/user?private_token=Wvjy2Krpb7y8xi93owUz"). - with(headers: { 'Content-Type'=>'application/json' }). - to_return(status: 200, body: f, headers: { 'Content-Type'=>'application/json' }) + with(headers: { 'Content-Type' => 'application/json' }). + to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' }) stub_request(:get, "#{gitlab_url}api/v3/user?access_token=some_token"). - with(headers: { 'Content-Type'=>'application/json' }). - to_return(status: 200, body: f, headers: { 'Content-Type'=>'application/json' }) + with(headers: { 'Content-Type' => 'application/json' }). + to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' }) end def stub_project_8 @@ -66,19 +66,19 @@ module StubGitlabCalls f = File.read(Rails.root.join('spec/support/gitlab_stubs/projects.json')) stub_request(:get, "#{gitlab_url}api/v3/projects.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz"). - with(headers: { 'Content-Type'=>'application/json' }). - to_return(status: 200, body: f, headers: { 'Content-Type'=>'application/json' }) + with(headers: { 'Content-Type' => 'application/json' }). + to_return(status: 200, body: f, headers: { 'Content-Type' => 'application/json' }) end def stub_projects_owned stub_request(:get, "#{gitlab_url}api/v3/projects/owned.json?archived=false&ci_enabled_first=true&private_token=Wvjy2Krpb7y8xi93owUz"). - with(headers: { 'Content-Type'=>'application/json' }). + with(headers: { 'Content-Type' => 'application/json' }). to_return(status: 200, body: "", headers: {}) end def stub_ci_enable stub_request(:put, "#{gitlab_url}api/v3/projects/2/services/gitlab-ci.json?private_token=Wvjy2Krpb7y8xi93owUz"). - with(headers: { 'Content-Type'=>'application/json' }). + with(headers: { 'Content-Type' => 'application/json' }). to_return(status: 200, body: "", headers: {}) end From 8d58e691c69f623878522c4f0dccf9f24229e9ce Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Fri, 13 May 2016 04:51:13 -0300 Subject: [PATCH 146/152] remove extra space to fix codestyle --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 0c01c52921..9e776a1f54 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -74,7 +74,7 @@ Rails.application.routes.draw do end # Health check - get 'health_check(/:checks)' => 'health_check#index', as: :health_check + get 'health_check(/:checks)' => 'health_check#index', as: :health_check # Enable Grack support mount Grack::AuthSpawner, at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post, :put] From ae89161db5d6294948990b1d87f4fa306f2a1be9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 May 2016 09:25:38 -0500 Subject: [PATCH 147/152] Remove background color differentiation from gitlab logo/text in sidebar Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/gitlab-theme.scss | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/assets/stylesheets/framework/gitlab-theme.scss b/app/assets/stylesheets/framework/gitlab-theme.scss index dc4668877f..51a17d1469 100644 --- a/app/assets/stylesheets/framework/gitlab-theme.scss +++ b/app/assets/stylesheets/framework/gitlab-theme.scss @@ -9,9 +9,6 @@ @mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) { .page-with-sidebar { .header-logo { - background-color: $color; - border-color: $color; - a { color: $color-light; @@ -21,7 +18,7 @@ } &:hover { - background-color: $color-darker; + background-color: $color-dark; a { color: #fff; From 6da24056030ccf45ec39f2f3b6addc6f0ffe1d1f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 May 2016 10:05:52 -0500 Subject: [PATCH 148/152] Add UI changes in 8.8 to changelog Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 588a4b4eef..30d4955c9f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -49,6 +49,8 @@ v 8.8.0 (unreleased) - Total method execution timings are no longer tracked - Allow Admins to remove the Login with buttons for OAuth services and still be able to import !4034. (Andrei Gliga) - Add API endpoints for un/subscribing from/to a label. !4051 (Ahmad Sherif) + - Hide left sidebar on phone screens to give more space for content + - Redesign navigation for profile and group pages v 8.7.5 - Fix relative links in wiki pages. !4050 From 16b1d3a34f665776bbdfca041326b9c2d569e59b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Fri, 13 May 2016 11:12:49 -0500 Subject: [PATCH 149/152] Fix mobile dropdown bug --- app/assets/stylesheets/framework/nav.scss | 19 +++++++++++++++++++ app/helpers/nav_helper.rb | 8 ++++++++ app/views/layouts/_page.html.haml | 2 +- .../layouts/nav/_group_settings.html.haml | 2 +- 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index f2d7dc625e..a81fcb1c6b 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -209,6 +209,15 @@ float: right; padding: 7px 0 0; + @media (max-width: $screen-xs-min) { + float: none; + padding: 0 9px; + + .dropdown-new { + width: 100%; + } + } + i { color: $layout-link-gray; } @@ -225,6 +234,10 @@ .dropdown { margin-left: 7px; + + @media (max-width: $screen-xs-min) { + margin-left: 0; + } } } @@ -260,4 +273,10 @@ .page-with-layout-nav { margin-top: 50px; + + &.controls-dropdown-visible { + @media (max-width: $screen-xs-min) { + margin-top: 96px; + } + } } diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb index 3aa4103045..fbb799eecd 100644 --- a/app/helpers/nav_helper.rb +++ b/app/helpers/nav_helper.rb @@ -43,4 +43,12 @@ module NavHelper class_name += " with-horizontal-nav" if defined?(nav) && nav class_name end + + def layout_nav_class + "page-with-layout-nav" if defined?(nav) && nav + end + + def layout_dropdown_class + "controls-dropdown-visible" if current_user + end end diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 3c0660d34f..5be0b546a6 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -25,7 +25,7 @@ .layout-nav .container-fluid = render "layouts/nav/#{nav}" - .content-wrapper{ class: ('page-with-layout-nav' if defined?(nav) && nav) } + .content-wrapper{ class: "#{layout_nav_class} #{layout_dropdown_class}" } = render "layouts/broadcast" = render "layouts/flash" = yield :flash_message diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml index e391ec7f2b..0b2673f1a8 100644 --- a/app/views/layouts/nav/_group_settings.html.haml +++ b/app/views/layouts/nav/_group_settings.html.haml @@ -1,7 +1,7 @@ - if current_user - if access = @group.users.find_by(id: current_user.id) .controls - %span.dropdown.group-settings-dropdown + .dropdown.group-settings-dropdown %a.dropdown-new.btn.btn-default#group-settings-button{href: '#', 'data-toggle' => 'dropdown'} = icon('cog') = icon('caret-down') From 2148566b3dce96f36bbf86555ef0d68758044429 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 May 2016 11:21:31 -0500 Subject: [PATCH 150/152] Fix issue sidebar top position after header height change Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/header.scss | 4 ++-- app/assets/stylesheets/pages/issuable.scss | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 8190a97ed5..0da96c4017 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -6,12 +6,12 @@ header { transition-duration: .3s; &.navbar-empty { - height: 58px; + height: $header-height; background: #fff; border-bottom: 1px solid $btn-gray-hover; .center-logo { - margin: 11px 0; + margin: 8px 0; text-align: center; #tanuki-logo, img { diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 1cf3023ecc..d06086a581 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -125,7 +125,7 @@ .right-sidebar { position: fixed; - top: 58px; + top: $header-height; bottom: 0; right: 0; z-index: 10; From 9a7a0415535da7541095de363dee37b9f09fe702 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 13 May 2016 11:24:00 -0500 Subject: [PATCH 151/152] Fix login btn vertical align Signed-off-by: Dmitriy Zaporozhets --- 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 3386523dbf..f8aecd0558 100644 --- a/app/assets/stylesheets/framework/common.scss +++ b/app/assets/stylesheets/framework/common.scss @@ -289,7 +289,7 @@ table { text-shadow: none; @media (min-width: $screen-sm-min) { - margin-top: 11px; + margin-top: 8px; } } From 935c684ad3427069431a1130d364a4481af6cb65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 13 May 2016 18:39:32 +0200 Subject: [PATCH 152/152] Revert "Merge branch 'doc-fix-db-gem-install-instructions' into 'master' " This reverts commit 66d8ef1ad9d5952b7e7f026658f71aafc861950e, reversing changes made to 6a8359f3d3be01af6f5b124b61af7ee1c77c17d0. The `--with` option was added in Bundler 1.10 but in this case it shouldn't even be needed: I've tested the original command locally and the pg gem was installed, so I believe the user that opened the original MR ran the wrong command (i.e. `sudo -u git -H bundle install --without development test postgres --deployment` instead of `sudo -u git -H bundle install --without development test mysql --deployment`. See discussion at https://gitlab.com/gitlab-org/gitlab-ce/commit/66d8ef1ad9d5952b7e7f026658f71aafc861950e for more context. --- doc/update/patch_versions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index b4283a526f..60729316cd 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -57,10 +57,10 @@ sudo -u git -H make cd /home/git/gitlab # PostgreSQL -sudo -u git -H bundle install --without development test mysql --with postgres --deployment +sudo -u git -H bundle install --without development test mysql --deployment # MySQL -sudo -u git -H bundle install --without development test postgres --with mysql --deployment +sudo -u git -H bundle install --without development test postgres --deployment # Optional: clean up old gems sudo -u git -H bundle clean