Commit 854f7123 authored by Luke Bennett's avatar Luke Bennett

Merge remote-tracking branch 'origin/master' into move-plan-badge-groups-scss-to-ee-namespace

parents e7e2f82a 392227ee
...@@ -1190,7 +1190,22 @@ RSpec/SubjectStub: ...@@ -1190,7 +1190,22 @@ RSpec/SubjectStub:
RSpec/VerifiedDoubles: RSpec/VerifiedDoubles:
Enabled: false Enabled: false
# GitlabSecurity ############################################################## # Gitlab ###################################################################
Gitlab/ModuleWithInstanceVariables:
Enable: true
Exclude:
# We ignore Rails helpers right now because it's hard to workaround it
- app/helpers/**/*_helper.rb
- ee/app/helpers/**/*_helper.rb
# We ignore Rails mailers right now because it's hard to workaround it
- app/mailers/emails/**/*.rb
- ee/**/emails/**/*.rb
# We ignore spec helpers because it usually doesn't matter
- spec/support/**/*.rb
- features/steps/**/*.rb
# GitlabSecurity ###########################################################
GitlabSecurity/DeepMunge: GitlabSecurity/DeepMunge:
Enabled: true Enabled: true
......
...@@ -323,7 +323,7 @@ group :development, :test do ...@@ -323,7 +323,7 @@ group :development, :test do
gem 'fuubar', '~> 2.2.0' gem 'fuubar', '~> 2.2.0'
gem 'database_cleaner', '~> 1.5.0' gem 'database_cleaner', '~> 1.5.0'
gem 'factory_girl_rails', '~> 4.7.0' gem 'factory_bot_rails', '~> 4.8.2'
gem 'rspec-rails', '~> 3.6.0' gem 'rspec-rails', '~> 3.6.0'
gem 'rspec-retry', '~> 0.4.5' gem 'rspec-retry', '~> 0.4.5'
gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rails', '~> 0.2.1'
......
...@@ -216,10 +216,10 @@ GEM ...@@ -216,10 +216,10 @@ GEM
excon (0.57.1) excon (0.57.1)
execjs (2.6.0) execjs (2.6.0)
expression_parser (0.9.0) expression_parser (0.9.0)
factory_girl (4.7.0) factory_bot (4.8.2)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
factory_girl_rails (4.7.0) factory_bot_rails (4.8.2)
factory_girl (~> 4.7.0) factory_bot (~> 4.8.2)
railties (>= 3.0.0) railties (>= 3.0.0)
faraday (0.12.2) faraday (0.12.2)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
...@@ -1054,7 +1054,7 @@ DEPENDENCIES ...@@ -1054,7 +1054,7 @@ DEPENDENCIES
elasticsearch-rails (~> 0.1.9) elasticsearch-rails (~> 0.1.9)
email_reply_trimmer (~> 0.1) email_reply_trimmer (~> 0.1)
email_spec (~> 1.6.0) email_spec (~> 1.6.0)
factory_girl_rails (~> 4.7.0) factory_bot_rails (~> 4.8.2)
faraday (~> 0.12) faraday (~> 0.12)
faraday_middleware-aws-signers-v4 faraday_middleware-aws-signers-v4
ffaker (~> 2.4) ffaker (~> 2.4)
......
...@@ -3,7 +3,7 @@ import axios from 'axios'; ...@@ -3,7 +3,7 @@ import axios from 'axios';
import SmartInterval from '~/smart_interval'; import SmartInterval from '~/smart_interval';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { parseSeconds, stringifyTime } from './lib/utils/pretty_time'; import { parseSeconds, stringifyTime } from './lib/utils/pretty_time';
import { timeIntervalInWords } from './lib/utils/datetime_utility'; import { formatDate, timeIntervalInWords } from './lib/utils/datetime_utility';
import timeago from './vue_shared/mixins/timeago'; import timeago from './vue_shared/mixins/timeago';
const healthyClass = 'geo-node-healthy'; const healthyClass = 'geo-node-healthy';
...@@ -115,7 +115,7 @@ class GeoNodeStatus { ...@@ -115,7 +115,7 @@ class GeoNodeStatus {
let eventDate = notAvailable; let eventDate = notAvailable;
if (eventTimestamp && eventTimestamp > 0) { if (eventTimestamp && eventTimestamp > 0) {
eventDate = gl.utils.formatDate(new Date(eventTimestamp * 1000)); eventDate = formatDate(new Date(eventTimestamp * 1000));
} }
if (eventId) { if (eventId) {
......
...@@ -24,11 +24,11 @@ module BoardsResponses ...@@ -24,11 +24,11 @@ module BoardsResponses
end end
def respond_with_boards def respond_with_boards
respond_with(@boards) respond_with(@boards) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def respond_with_board def respond_with_board
respond_with(@board) respond_with(@board) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def respond_with(resource) def respond_with(resource)
......
module CreatesCommit module CreatesCommit
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil) def create_commit(service, success_path:, failure_path:, failure_view: nil, success_notice: nil)
if can?(current_user, :push_code, @project) if can?(current_user, :push_code, @project)
@project_to_commit_into = @project @project_to_commit_into = @project
...@@ -45,6 +47,7 @@ module CreatesCommit ...@@ -45,6 +47,7 @@ module CreatesCommit
end end
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def authorize_edit_tree! def authorize_edit_tree!
return if can_collaborate_with_project? return if can_collaborate_with_project?
...@@ -77,6 +80,7 @@ module CreatesCommit ...@@ -77,6 +80,7 @@ module CreatesCommit
end end
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def new_merge_request_path def new_merge_request_path
project_new_merge_request_path( project_new_merge_request_path(
@project_to_commit_into, @project_to_commit_into,
...@@ -88,20 +92,28 @@ module CreatesCommit ...@@ -88,20 +92,28 @@ module CreatesCommit
} }
) )
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def existing_merge_request_path def existing_merge_request_path
project_merge_request_path(@project, @merge_request) project_merge_request_path(@project, @merge_request) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def merge_request_exists? def merge_request_exists?
return @merge_request if defined?(@merge_request) strong_memoize(:merge_request) do
MergeRequestsFinder.new(current_user, project_id: @project.id)
@merge_request = MergeRequestsFinder.new(current_user, project_id: @project.id).execute.opened .execute
.find_by(source_project_id: @project_to_commit_into, source_branch: @branch_name, target_branch: @start_branch) .opened
.find_by(
source_project_id: @project_to_commit_into,
source_branch: @branch_name,
target_branch: @start_branch)
end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def different_project? def different_project?
@project_to_commit_into != @project @project_to_commit_into != @project # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def create_merge_request? def create_merge_request?
...@@ -109,6 +121,6 @@ module CreatesCommit ...@@ -109,6 +121,6 @@ module CreatesCommit
# as the target branch in the same project, # as the target branch in the same project,
# we don't want to create a merge request. # we don't want to create a merge request.
params[:create_merge_request].present? && params[:create_merge_request].present? &&
(different_project? || @start_branch != @branch_name) (different_project? || @start_branch != @branch_name) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
end end
module GroupTree module GroupTree
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def render_group_tree(groups) def render_group_tree(groups)
@groups = if params[:filter].present? @groups = if params[:filter].present?
Gitlab::GroupHierarchy.new(groups.search(params[:filter])) Gitlab::GroupHierarchy.new(groups.search(params[:filter]))
...@@ -20,5 +21,6 @@ module GroupTree ...@@ -20,5 +21,6 @@ module GroupTree
render json: serializer.represent(@groups) render json: serializer.represent(@groups)
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end end
end end
...@@ -17,7 +17,7 @@ module IssuableActions ...@@ -17,7 +17,7 @@ module IssuableActions
end end
def update def update
@issuable = update_service.execute(issuable) @issuable = update_service.execute(issuable) # rubocop:disable Gitlab/ModuleWithInstanceVariables
respond_to do |format| respond_to do |format|
format.html do format.html do
...@@ -81,7 +81,7 @@ module IssuableActions ...@@ -81,7 +81,7 @@ module IssuableActions
private private
def recaptcha_check_if_spammable(should_redirect = true, &block) def recaptcha_check_if_spammable(should_redirect = true, &block)
return yield unless @issuable.is_a? Spammable return yield unless issuable.is_a? Spammable
recaptcha_check_with_fallback(should_redirect, &block) recaptcha_check_with_fallback(should_redirect, &block)
end end
...@@ -89,7 +89,7 @@ module IssuableActions ...@@ -89,7 +89,7 @@ module IssuableActions
def render_conflict_response def render_conflict_response
respond_to do |format| respond_to do |format|
format.html do format.html do
@conflict = true @conflict = true # rubocop:disable Gitlab/ModuleWithInstanceVariables
render :edit render :edit
end end
...@@ -104,7 +104,7 @@ module IssuableActions ...@@ -104,7 +104,7 @@ module IssuableActions
end end
def labels def labels
@labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute @labels ||= LabelsFinder.new(current_user, project_id: @project.id).execute # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def authorize_destroy_issuable! def authorize_destroy_issuable!
...@@ -114,7 +114,7 @@ module IssuableActions ...@@ -114,7 +114,7 @@ module IssuableActions
end end
def authorize_admin_issuable! def authorize_admin_issuable!
unless can?(current_user, :"admin_#{resource_name}", @project) unless can?(current_user, :"admin_#{resource_name}", @project) # rubocop:disable Gitlab/ModuleWithInstanceVariables
return access_denied! return access_denied!
end end
end end
...@@ -149,6 +149,7 @@ module IssuableActions ...@@ -149,6 +149,7 @@ module IssuableActions
@resource_name ||= controller_name.singularize @resource_name ||= controller_name.singularize
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def render_entity_json def render_entity_json
if @issuable.valid? if @issuable.valid?
render json: serializer.represent(@issuable) render json: serializer.represent(@issuable)
...@@ -156,6 +157,7 @@ module IssuableActions ...@@ -156,6 +157,7 @@ module IssuableActions
render json: { errors: @issuable.errors.full_messages }, status: :unprocessable_entity render json: { errors: @issuable.errors.full_messages }, status: :unprocessable_entity
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def serializer def serializer
raise NotImplementedError raise NotImplementedError
...@@ -166,6 +168,6 @@ module IssuableActions ...@@ -166,6 +168,6 @@ module IssuableActions
end end
def parent def parent
@project || @group @project || @group # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
end end
...@@ -2,6 +2,7 @@ module IssuableCollections ...@@ -2,6 +2,7 @@ module IssuableCollections
extend ActiveSupport::Concern extend ActiveSupport::Concern
include SortingHelper include SortingHelper
include Gitlab::IssuableMetadata include Gitlab::IssuableMetadata
include Gitlab::Utils::StrongMemoize
included do included do
helper_method :finder helper_method :finder
...@@ -9,6 +10,7 @@ module IssuableCollections ...@@ -9,6 +10,7 @@ module IssuableCollections
private private
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def set_issuables_index def set_issuables_index
@issuables = issuables_collection @issuables = issuables_collection
@issuables = @issuables.page(params[:page]) @issuables = @issuables.page(params[:page])
...@@ -33,6 +35,7 @@ module IssuableCollections ...@@ -33,6 +35,7 @@ module IssuableCollections
@users.push(author) if author @users.push(author) if author
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def issuables_collection def issuables_collection
finder.execute.preload(preload_for_collection) finder.execute.preload(preload_for_collection)
...@@ -41,7 +44,7 @@ module IssuableCollections ...@@ -41,7 +44,7 @@ module IssuableCollections
def redirect_out_of_range(total_pages) def redirect_out_of_range(total_pages)
return false if total_pages.zero? return false if total_pages.zero?
out_of_range = @issuables.current_page > total_pages out_of_range = @issuables.current_page > total_pages # rubocop:disable Gitlab/ModuleWithInstanceVariables
if out_of_range if out_of_range
redirect_to(url_for(params.merge(page: total_pages, only_path: true))) redirect_to(url_for(params.merge(page: total_pages, only_path: true)))
...@@ -51,7 +54,7 @@ module IssuableCollections ...@@ -51,7 +54,7 @@ module IssuableCollections
end end
def issuable_page_count def issuable_page_count
page_count_for_relation(@issuables, finder.row_count) page_count_for_relation(@issuables, finder.row_count) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def page_count_for_relation(relation, row_count) def page_count_for_relation(relation, row_count)
...@@ -66,6 +69,7 @@ module IssuableCollections ...@@ -66,6 +69,7 @@ module IssuableCollections
finder_class.new(current_user, filter_params) finder_class.new(current_user, filter_params)
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def filter_params def filter_params
set_sort_order_from_cookie set_sort_order_from_cookie
set_default_state set_default_state
...@@ -90,6 +94,7 @@ module IssuableCollections ...@@ -90,6 +94,7 @@ module IssuableCollections
@filter_params.permit(IssuableFinder::VALID_PARAMS) @filter_params.permit(IssuableFinder::VALID_PARAMS)
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def set_default_state def set_default_state
params[:state] = 'opened' if params[:state].blank? params[:state] = 'opened' if params[:state].blank?
...@@ -131,9 +136,9 @@ module IssuableCollections ...@@ -131,9 +136,9 @@ module IssuableCollections
end end
def finder def finder
return @finder if defined?(@finder) strong_memoize(:finder) do
issuable_finder_for(@finder_type) # rubocop:disable Gitlab/ModuleWithInstanceVariables
@finder = issuable_finder_for(@finder_type) end
end end
def collection_type def collection_type
......
...@@ -2,6 +2,7 @@ module IssuesAction ...@@ -2,6 +2,7 @@ module IssuesAction
extend ActiveSupport::Concern extend ActiveSupport::Concern
include IssuableCollections include IssuableCollections
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def issues def issues
@finder_type = IssuesFinder @finder_type = IssuesFinder
@label = finder.labels.first @label = finder.labels.first
...@@ -17,4 +18,5 @@ module IssuesAction ...@@ -17,4 +18,5 @@ module IssuesAction
format.atom { render layout: 'xml.atom' } format.atom { render layout: 'xml.atom' }
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end end
...@@ -2,6 +2,7 @@ module MergeRequestsAction ...@@ -2,6 +2,7 @@ module MergeRequestsAction
extend ActiveSupport::Concern extend ActiveSupport::Concern
include IssuableCollections include IssuableCollections
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def merge_requests def merge_requests
@finder_type = MergeRequestsFinder @finder_type = MergeRequestsFinder
@label = finder.labels.first @label = finder.labels.first
...@@ -10,6 +11,7 @@ module MergeRequestsAction ...@@ -10,6 +11,7 @@ module MergeRequestsAction
@issuable_meta_data = issuable_meta_data(@merge_requests, collection_type) @issuable_meta_data = issuable_meta_data(@merge_requests, collection_type)
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
private private
......
...@@ -6,7 +6,7 @@ module MilestoneActions ...@@ -6,7 +6,7 @@ module MilestoneActions
format.html { redirect_to milestone_redirect_path } format.html { redirect_to milestone_redirect_path }
format.json do format.json do
render json: tabs_json("shared/milestones/_merge_requests_tab", { render json: tabs_json("shared/milestones/_merge_requests_tab", {
merge_requests: @milestone.sorted_merge_requests, merge_requests: @milestone.sorted_merge_requests, # rubocop:disable Gitlab/ModuleWithInstanceVariables
show_project_name: true show_project_name: true
}) })
end end
...@@ -18,7 +18,7 @@ module MilestoneActions ...@@ -18,7 +18,7 @@ module MilestoneActions
format.html { redirect_to milestone_redirect_path } format.html { redirect_to milestone_redirect_path }
format.json do format.json do
render json: tabs_json("shared/milestones/_participants_tab", { render json: tabs_json("shared/milestones/_participants_tab", {
users: @milestone.participants users: @milestone.participants # rubocop:disable Gitlab/ModuleWithInstanceVariables
}) })
end end
end end
...@@ -29,7 +29,7 @@ module MilestoneActions ...@@ -29,7 +29,7 @@ module MilestoneActions
format.html { redirect_to milestone_redirect_path } format.html { redirect_to milestone_redirect_path }
format.json do format.json do
render json: tabs_json("shared/milestones/_labels_tab", { render json: tabs_json("shared/milestones/_labels_tab", {
labels: @milestone.labels labels: @milestone.labels # rubocop:disable Gitlab/ModuleWithInstanceVariables
}) })
end end
end end
...@@ -43,6 +43,7 @@ module MilestoneActions ...@@ -43,6 +43,7 @@ module MilestoneActions
} }
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def milestone_redirect_path def milestone_redirect_path
if @project if @project
project_milestone_path(@project, @milestone) project_milestone_path(@project, @milestone)
...@@ -52,4 +53,5 @@ module MilestoneActions ...@@ -52,4 +53,5 @@ module MilestoneActions
dashboard_milestone_path(@milestone.safe_title, title: @milestone.title) dashboard_milestone_path(@milestone.safe_title, title: @milestone.title)
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end end
module NotesActions module NotesActions
include RendersNotes include RendersNotes
include Gitlab::Utils::StrongMemoize
extend ActiveSupport::Concern extend ActiveSupport::Concern
included do included do
...@@ -30,6 +31,7 @@ module NotesActions ...@@ -30,6 +31,7 @@ module NotesActions
render json: notes_json render json: notes_json
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def create def create
create_params = note_params.merge( create_params = note_params.merge(
merge_request_diff_head_sha: params[:merge_request_diff_head_sha], merge_request_diff_head_sha: params[:merge_request_diff_head_sha],
...@@ -47,7 +49,9 @@ module NotesActions ...@@ -47,7 +49,9 @@ module NotesActions
format.html { redirect_back_or_default } format.html { redirect_back_or_default }
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def update def update
@note = Notes::UpdateService.new(project, current_user, note_params).execute(note) @note = Notes::UpdateService.new(project, current_user, note_params).execute(note)
...@@ -60,6 +64,7 @@ module NotesActions ...@@ -60,6 +64,7 @@ module NotesActions
format.html { redirect_back_or_default } format.html { redirect_back_or_default }
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def destroy def destroy
if note.editable? if note.editable?
...@@ -138,7 +143,7 @@ module NotesActions ...@@ -138,7 +143,7 @@ module NotesActions
end end
else else
template = "discussions/_diff_discussion" template = "discussions/_diff_discussion"
@fresh_discussion = true @fresh_discussion = true # rubocop:disable Gitlab/ModuleWithInstanceVariables
locals = { discussions: [discussion], on_image: on_image } locals = { discussions: [discussion], on_image: on_image }
end end
...@@ -191,7 +196,7 @@ module NotesActions ...@@ -191,7 +196,7 @@ module NotesActions
end end
def noteable def noteable
@noteable ||= notes_finder.target || @note&.noteable @noteable ||= notes_finder.target || @note&.noteable # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def require_noteable! def require_noteable!
...@@ -211,20 +216,21 @@ module NotesActions ...@@ -211,20 +216,21 @@ module NotesActions
end end
def note_project def note_project
return @note_project if defined?(@note_project) strong_memoize(:note_project) do
return nil unless project return nil unless project
note_project_id = params[:note_project_id] note_project_id = params[:note_project_id]
@note_project = the_project =
if note_project_id.present? if note_project_id.present?
Project.find(note_project_id) Project.find(note_project_id)
else else
project project
end end
return access_denied! unless can?(current_user, :create_note, @note_project) return access_denied! unless can?(current_user, :create_note, the_project)
@note_project the_project
end
end end
end end
...@@ -14,6 +14,6 @@ module OauthApplications ...@@ -14,6 +14,6 @@ module OauthApplications
end end
def load_scopes def load_scopes
@scopes = Doorkeeper.configuration.scopes @scopes ||= Doorkeeper.configuration.scopes
end end
end end
module PreviewMarkdown module PreviewMarkdown
extend ActiveSupport::Concern extend ActiveSupport::Concern
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def preview_markdown def preview_markdown
result = PreviewMarkdownService.new(@project, current_user, params).execute result = PreviewMarkdownService.new(@project, current_user, params).execute
...@@ -20,4 +21,5 @@ module PreviewMarkdown ...@@ -20,4 +21,5 @@ module PreviewMarkdown
} }
} }
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end end
module RendersCommits module RendersCommits
def prepare_commits_for_rendering(commits) def prepare_commits_for_rendering(commits)
Banzai::CommitRenderer.render(commits, @project, current_user) Banzai::CommitRenderer.render(commits, @project, current_user) # rubocop:disable Gitlab/ModuleWithInstanceVariables
commits commits
end end
......
module RendersNotes module RendersNotes
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def prepare_notes_for_rendering(notes, noteable = nil) def prepare_notes_for_rendering(notes, noteable = nil)
preload_noteable_for_regular_notes(notes) preload_noteable_for_regular_notes(notes)
preload_max_access_for_authors(notes, @project) preload_max_access_for_authors(notes, @project)
...@@ -7,6 +8,7 @@ module RendersNotes ...@@ -7,6 +8,7 @@ module RendersNotes
notes notes
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
private private
......
...@@ -67,7 +67,7 @@ module ServiceParams ...@@ -67,7 +67,7 @@ module ServiceParams
FILTER_BLANK_PARAMS = [:password].freeze FILTER_BLANK_PARAMS = [:password].freeze
def service_params def service_params
dynamic_params = @service.event_channel_names + @service.event_names dynamic_params = @service.event_channel_names + @service.event_names # rubocop:disable Gitlab/ModuleWithInstanceVariables
service_params = params.permit(:id, service: allowed_service_params + dynamic_params) service_params = params.permit(:id, service: allowed_service_params + dynamic_params)
if service_params[:service].is_a?(Hash) if service_params[:service].is_a?(Hash)
......
...@@ -4,6 +4,7 @@ module SnippetsActions ...@@ -4,6 +4,7 @@ module SnippetsActions
def edit def edit
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def raw def raw
disposition = params[:inline] == 'false' ? 'attachment' : 'inline' disposition = params[:inline] == 'false' ? 'attachment' : 'inline'
...@@ -14,6 +15,7 @@ module SnippetsActions ...@@ -14,6 +15,7 @@ module SnippetsActions
filename: @snippet.sanitized_file_name filename: @snippet.sanitized_file_name
) )
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
private private
......
...@@ -2,6 +2,7 @@ module SpammableActions ...@@ -2,6 +2,7 @@ module SpammableActions
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Recaptcha::Verify include Recaptcha::Verify
include Gitlab::Utils::StrongMemoize
included do included do
before_action :authorize_submit_spammable!, only: :mark_as_spam before_action :authorize_submit_spammable!, only: :mark_as_spam
...@@ -18,9 +19,9 @@ module SpammableActions ...@@ -18,9 +19,9 @@ module SpammableActions
private private
def ensure_spam_config_loaded! def ensure_spam_config_loaded!
return @spam_config_loaded if defined?(@spam_config_loaded) strong_memoize(:spam_config_loaded) do
Gitlab::Recaptcha.load_configurations!
@spam_config_loaded = Gitlab::Recaptcha.load_configurations! end
end end
def recaptcha_check_with_fallback(should_redirect = true, &fallback) def recaptcha_check_with_fallback(should_redirect = true, &fallback)
......
...@@ -12,7 +12,7 @@ module ToggleSubscriptionAction ...@@ -12,7 +12,7 @@ module ToggleSubscriptionAction
private private
def subscribable_project def subscribable_project
@project || raise(NotImplementedError) @project ||= raise(NotImplementedError)
end end
def subscribable_resource def subscribable_resource
......
...@@ -11,7 +11,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -11,7 +11,7 @@ class Projects::NotesController < Projects::ApplicationController
# Controller actions are returned from AbstractController::Base and methods of parent classes are # Controller actions are returned from AbstractController::Base and methods of parent classes are
# excluded in order to return only specific controller related methods. # excluded in order to return only specific controller related methods.
# That is ok for the app (no :create method in ancestors) # That is ok for the app (no :create method in ancestors)
# but fails for tests because there is a :create method on FactoryGirl (one of the ancestors) # but fails for tests because there is a :create method on FactoryBot (one of the ancestors)
# #
# see https://github.com/rails/rails/blob/v4.2.7/actionpack/lib/abstract_controller/base.rb#L78 # see https://github.com/rails/rails/blob/v4.2.7/actionpack/lib/abstract_controller/base.rb#L78
# #
......
class AuditEvent < ActiveRecord::Base class AuditEvent < ActiveRecord::Base
prepend EE::AuditEvent prepend EE::AuditEvent
include Gitlab::Utils::StrongMemoize
serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize serialize :details, Hash # rubocop:disable Cop/ActiveRecordSerialize
......
...@@ -44,13 +44,11 @@ module Mentionable ...@@ -44,13 +44,11 @@ module Mentionable
end end
def all_references(current_user = nil, extractor: nil) def all_references(current_user = nil, extractor: nil)
@extractors ||= {}
# Use custom extractor if it's passed in the function parameters. # Use custom extractor if it's passed in the function parameters.
if extractor if extractor
@extractors[current_user] = extractor extractors[current_user] = extractor
else else
extractor = @extractors[current_user] ||= Gitlab::ReferenceExtractor.new(project, current_user) extractor = extractors[current_user] ||= Gitlab::ReferenceExtractor.new(project, current_user)
extractor.reset_memoized_values extractor.reset_memoized_values
end end
...@@ -69,6 +67,10 @@ module Mentionable ...@@ -69,6 +67,10 @@ module Mentionable
extractor extractor
end end
def extractors
@extractors ||= {}
end
def mentioned_users(current_user = nil) def mentioned_users(current_user = nil)
all_references(current_user).users all_references(current_user).users
end end
......
...@@ -103,9 +103,11 @@ module Milestoneish ...@@ -103,9 +103,11 @@ module Milestoneish
end end
def memoize_per_user(user, method_name) def memoize_per_user(user, method_name)
@memoized ||= {} memoized_users[method_name][user&.id] ||= yield
@memoized[method_name] ||= {} end
@memoized[method_name][user&.id] ||= yield
def memoized_users
@memoized_users ||= Hash.new { |h, k| h[k] = {} }
end end
# override in a class that includes this module to get a faster query # override in a class that includes this module to get a faster query
......
...@@ -46,6 +46,7 @@ module Noteable ...@@ -46,6 +46,7 @@ module Noteable
notes.inc_relations_for_view.grouped_diff_discussions(*args) notes.inc_relations_for_view.grouped_diff_discussions(*args)
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def resolvable_discussions def resolvable_discussions
@resolvable_discussions ||= @resolvable_discussions ||=
if defined?(@discussions) if defined?(@discussions)
...@@ -54,6 +55,7 @@ module Noteable ...@@ -54,6 +55,7 @@ module Noteable
discussion_notes.resolvable.discussions(self) discussion_notes.resolvable.discussions(self)
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def discussions_resolvable? def discussions_resolvable?
resolvable_discussions.any?(&:resolvable?) resolvable_discussions.any?(&:resolvable?)
......
...@@ -56,15 +56,17 @@ module Participable ...@@ -56,15 +56,17 @@ module Participable
# #
# Returns an Array of User instances. # Returns an Array of User instances.
def participants(current_user = nil) def participants(current_user = nil)
@participants ||= Hash.new do |hash, user| all_participants[current_user]
hash[user] = raw_participants(user)
end
@participants[current_user]
end end
private private
def all_participants
@all_participants ||= Hash.new do |hash, user|
hash[user] = raw_participants(user)
end
end
def raw_participants(current_user = nil) def raw_participants(current_user = nil)
current_user ||= author current_user ||= author
ext = Gitlab::ReferenceExtractor.new(project, current_user) ext = Gitlab::ReferenceExtractor.new(project, current_user)
......
...@@ -52,7 +52,7 @@ module RelativePositioning ...@@ -52,7 +52,7 @@ module RelativePositioning
# to its predecessor. This process will recursively move all the predecessors until we have a place # to its predecessor. This process will recursively move all the predecessors until we have a place
if (after.relative_position - before.relative_position) < 2 if (after.relative_position - before.relative_position) < 2
before.move_before before.move_before
@positionable_neighbours = [before] @positionable_neighbours = [before] # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
self.relative_position = position_between(before.relative_position, after.relative_position) self.relative_position = position_between(before.relative_position, after.relative_position)
...@@ -65,7 +65,7 @@ module RelativePositioning ...@@ -65,7 +65,7 @@ module RelativePositioning
if before.shift_after? if before.shift_after?
issue_to_move = self.class.in_projects(project_ids).find_by!(relative_position: pos_after) issue_to_move = self.class.in_projects(project_ids).find_by!(relative_position: pos_after)
issue_to_move.move_after issue_to_move.move_after
@positionable_neighbours = [issue_to_move] @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
pos_after = issue_to_move.relative_position pos_after = issue_to_move.relative_position
end end
...@@ -80,7 +80,7 @@ module RelativePositioning ...@@ -80,7 +80,7 @@ module RelativePositioning
if after.shift_before? if after.shift_before?
issue_to_move = self.class.in_projects(project_ids).find_by!(relative_position: pos_before) issue_to_move = self.class.in_projects(project_ids).find_by!(relative_position: pos_before)
issue_to_move.move_before issue_to_move.move_before
@positionable_neighbours = [issue_to_move] @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
pos_before = issue_to_move.relative_position pos_before = issue_to_move.relative_position
end end
...@@ -132,6 +132,7 @@ module RelativePositioning ...@@ -132,6 +132,7 @@ module RelativePositioning
end end
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def save_positionable_neighbours def save_positionable_neighbours
return unless @positionable_neighbours return unless @positionable_neighbours
...@@ -140,4 +141,5 @@ module RelativePositioning ...@@ -140,4 +141,5 @@ module RelativePositioning
status status
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end end
...@@ -31,15 +31,11 @@ module ResolvableDiscussion ...@@ -31,15 +31,11 @@ module ResolvableDiscussion
end end
def resolvable? def resolvable?
return @resolvable if @resolvable.present? @resolvable ||= potentially_resolvable? && notes.any?(&:resolvable?)
@resolvable = potentially_resolvable? && notes.any?(&:resolvable?)
end end
def resolved? def resolved?
return @resolved if @resolved.present? @resolved ||= resolvable? && notes.none?(&:to_be_resolved?)
@resolved = resolvable? && notes.none?(&:to_be_resolved?)
end end
def first_note def first_note
...@@ -49,13 +45,13 @@ module ResolvableDiscussion ...@@ -49,13 +45,13 @@ module ResolvableDiscussion
def first_note_to_resolve def first_note_to_resolve
return unless resolvable? return unless resolvable?
@first_note_to_resolve ||= notes.find(&:to_be_resolved?) @first_note_to_resolve ||= notes.find(&:to_be_resolved?) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def last_resolved_note def last_resolved_note
return unless resolved? return unless resolved?
@last_resolved_note ||= resolved_notes.sort_by(&:resolved_at).last @last_resolved_note ||= resolved_notes.sort_by(&:resolved_at).last # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def resolved_notes def resolved_notes
...@@ -95,7 +91,7 @@ module ResolvableDiscussion ...@@ -95,7 +91,7 @@ module ResolvableDiscussion
yield(notes_relation) yield(notes_relation)
# Set the notes array to the updated notes # Set the notes array to the updated notes
@notes = notes_relation.fresh.to_a @notes = notes_relation.fresh.to_a # rubocop:disable Gitlab/ModuleWithInstanceVariables
self.class.memoized_values.each do |var| self.class.memoized_values.each do |var|
instance_variable_set(:"@#{var}", nil) instance_variable_set(:"@#{var}", nil)
......
...@@ -88,7 +88,7 @@ module Routable ...@@ -88,7 +88,7 @@ module Routable
def full_name def full_name
if route && route.name.present? if route && route.name.present?
@full_name ||= route.name @full_name ||= route.name # rubocop:disable Gitlab/ModuleWithInstanceVariables
else else
update_route if persisted? update_route if persisted?
...@@ -112,7 +112,7 @@ module Routable ...@@ -112,7 +112,7 @@ module Routable
def expires_full_path_cache def expires_full_path_cache
RequestStore.delete(full_path_key) if RequestStore.active? RequestStore.delete(full_path_key) if RequestStore.active?
@full_path = nil @full_path = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def build_full_path def build_full_path
...@@ -127,7 +127,7 @@ module Routable ...@@ -127,7 +127,7 @@ module Routable
def uncached_full_path def uncached_full_path
if route && route.path.present? if route && route.path.present?
@full_path ||= route.path @full_path ||= route.path # rubocop:disable Gitlab/ModuleWithInstanceVariables
else else
update_route if persisted? update_route if persisted?
...@@ -166,7 +166,7 @@ module Routable ...@@ -166,7 +166,7 @@ module Routable
route || build_route(source: self) route || build_route(source: self)
route.path = build_full_path route.path = build_full_path
route.name = build_full_name route.name = build_full_name
@full_path = nil @full_path = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
@full_name = nil @full_name = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
end end
...@@ -12,6 +12,7 @@ module Spammable ...@@ -12,6 +12,7 @@ module Spammable
attr_accessor :spam attr_accessor :spam
attr_accessor :spam_log attr_accessor :spam_log
alias_method :spam?, :spam
after_validation :check_for_spam, on: [:create, :update] after_validation :check_for_spam, on: [:create, :update]
...@@ -34,10 +35,6 @@ module Spammable ...@@ -34,10 +35,6 @@ module Spammable
end end
end end
def spam?
@spam
end
def check_for_spam def check_for_spam
error_msg = if Gitlab::Recaptcha.enabled? error_msg = if Gitlab::Recaptcha.enabled?
"Your #{spammable_entity_type} has been recognized as spam. "\ "Your #{spammable_entity_type} has been recognized as spam. "\
......
...@@ -39,7 +39,7 @@ module Taskable ...@@ -39,7 +39,7 @@ module Taskable
def task_list_items def task_list_items
return [] if description.blank? return [] if description.blank?
@task_list_items ||= Taskable.get_tasks(description) @task_list_items ||= Taskable.get_tasks(description) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def tasks def tasks
......
...@@ -21,6 +21,7 @@ module TimeTrackable ...@@ -21,6 +21,7 @@ module TimeTrackable
has_many :timelogs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :timelogs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def spend_time(options) def spend_time(options)
@time_spent = options[:duration] @time_spent = options[:duration]
@time_spent_user = options[:user] @time_spent_user = options[:user]
...@@ -36,6 +37,7 @@ module TimeTrackable ...@@ -36,6 +37,7 @@ module TimeTrackable
end end
end end
alias_method :spend_time=, :spend_time alias_method :spend_time=, :spend_time
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def total_time_spent def total_time_spent
timelogs.sum(:time_spent) timelogs.sum(:time_spent)
...@@ -52,9 +54,10 @@ module TimeTrackable ...@@ -52,9 +54,10 @@ module TimeTrackable
private private
def reset_spent_time def reset_spent_time
timelogs.new(time_spent: total_time_spent * -1, user: @time_spent_user) timelogs.new(time_spent: total_time_spent * -1, user: @time_spent_user) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def add_or_subtract_spent_time def add_or_subtract_spent_time
timelogs.new( timelogs.new(
time_spent: time_spent, time_spent: time_spent,
...@@ -62,16 +65,19 @@ module TimeTrackable ...@@ -62,16 +65,19 @@ module TimeTrackable
spent_at: @spent_at spent_at: @spent_at
) )
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def check_negative_time_spent def check_negative_time_spent
return if time_spent.nil? || time_spent == :reset return if time_spent.nil? || time_spent == :reset
# we need to cache the total time spent so multiple calls to #valid? if time_spent < 0 && (time_spent.abs > original_total_time_spent)
# doesn't give a false error
@original_total_time_spent ||= total_time_spent
if time_spent < 0 && (time_spent.abs > @original_total_time_spent)
errors.add(:time_spent, 'Time to subtract exceeds the total time spent') errors.add(:time_spent, 'Time to subtract exceeds the total time spent')
end end
end end
# we need to cache the total time spent so multiple calls to #valid?
# doesn't give a false error
def original_total_time_spent
@original_total_time_spent ||= total_time_spent
end
end end
...@@ -14,7 +14,7 @@ module WithPagination ...@@ -14,7 +14,7 @@ module WithPagination
# we shouldn't try to paginate single resources # we shouldn't try to paginate single resources
def represent(resource, opts = {}) def represent(resource, opts = {})
if paginated? && resource.respond_to?(:page) if paginated? && resource.respond_to?(:page)
super(@paginator.paginate(resource), opts) super(paginator.paginate(resource), opts)
else else
super(resource, opts) super(resource, opts)
end end
......
module Issues module Issues
module ResolveDiscussions module ResolveDiscussions
include Gitlab::Utils::StrongMemoize
attr_reader :merge_request_to_resolve_discussions_of_iid, :discussion_to_resolve_id attr_reader :merge_request_to_resolve_discussions_of_iid, :discussion_to_resolve_id
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def filter_resolve_discussion_params def filter_resolve_discussion_params
@merge_request_to_resolve_discussions_of_iid ||= params.delete(:merge_request_to_resolve_discussions_of) @merge_request_to_resolve_discussions_of_iid ||= params.delete(:merge_request_to_resolve_discussions_of)
@discussion_to_resolve_id ||= params.delete(:discussion_to_resolve) @discussion_to_resolve_id ||= params.delete(:discussion_to_resolve)
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def merge_request_to_resolve_discussions_of def merge_request_to_resolve_discussions_of
return @merge_request_to_resolve_discussions_of if defined?(@merge_request_to_resolve_discussions_of) strong_memoize(:merge_request_to_resolve_discussions_of) do
MergeRequestsFinder.new(current_user, project_id: project.id)
@merge_request_to_resolve_discussions_of = MergeRequestsFinder.new(current_user, project_id: project.id) .execute
.execute .find_by(iid: merge_request_to_resolve_discussions_of_iid)
.find_by(iid: merge_request_to_resolve_discussions_of_iid) end
end end
def discussions_to_resolve def discussions_to_resolve
return [] unless merge_request_to_resolve_discussions_of return [] unless merge_request_to_resolve_discussions_of
@discussions_to_resolve ||= @discussions_to_resolve ||= # rubocop:disable Gitlab/ModuleWithInstanceVariables
if discussion_to_resolve_id if discussion_to_resolve_id
discussion_or_nil = merge_request_to_resolve_discussions_of discussion_or_nil = merge_request_to_resolve_discussions_of
.find_discussion(discussion_to_resolve_id) .find_discussion(discussion_to_resolve_id)
......
...@@ -7,16 +7,19 @@ ...@@ -7,16 +7,19 @@
# - params with :request # - params with :request
# #
module SpamCheckService module SpamCheckService
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def filter_spam_check_params def filter_spam_check_params
@request = params.delete(:request) @request = params.delete(:request)
@api = params.delete(:api) @api = params.delete(:api)
@recaptcha_verified = params.delete(:recaptcha_verified) @recaptcha_verified = params.delete(:recaptcha_verified)
@spam_log_id = params.delete(:spam_log_id) @spam_log_id = params.delete(:spam_log_id)
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
# In order to be proceed to the spam check process, @spammable has to be # In order to be proceed to the spam check process, @spammable has to be
# a dirty instance, which means it should be already assigned with the new # a dirty instance, which means it should be already assigned with the new
# attribute values. # attribute values.
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def spam_check(spammable, user) def spam_check(spammable, user)
spam_service = SpamService.new(spammable, @request) spam_service = SpamService.new(spammable, @request)
...@@ -24,4 +27,5 @@ module SpamCheckService ...@@ -24,4 +27,5 @@ module SpamCheckService
user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true) user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true)
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end end
%div %div
- if current_application_settings.help_text.present? - if current_application_settings.help_page_text.present?
= markdown(current_application_settings.help_text) = markdown(current_application_settings.help_page_text)
%hr %hr
- unless current_application_settings.help_page_hide_commercial_content? - unless current_application_settings.help_page_hide_commercial_content?
......
...@@ -9,15 +9,15 @@ module NewIssuable ...@@ -9,15 +9,15 @@ module NewIssuable
end end
def set_user(user_id) def set_user(user_id)
@user = User.find_by(id: user_id) @user = User.find_by(id: user_id) # rubocop:disable Gitlab/ModuleWithInstanceVariables
log_error(User, user_id) unless @user log_error(User, user_id) unless @user # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def set_issuable(issuable_id) def set_issuable(issuable_id)
@issuable = issuable_class.find_by(id: issuable_id) @issuable = issuable_class.find_by(id: issuable_id) # rubocop:disable Gitlab/ModuleWithInstanceVariables
log_error(issuable_class, issuable_id) unless @issuable log_error(issuable_class, issuable_id) unless @issuable # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def log_error(record_class, record_id) def log_error(record_class, record_id)
......
---
title: 'Geo: Added Authorized Keys specific checks'
merge_request: 3728
author:
type: added
---
title: Import some code and functionality from gitlab-shell to improve subprocess
handling
merge_request:
author:
type: other
...@@ -180,7 +180,7 @@ module Gitlab ...@@ -180,7 +180,7 @@ module Gitlab
config.middleware.insert_after ActionDispatch::Flash, 'Gitlab::Middleware::ReadOnly' config.middleware.insert_after ActionDispatch::Flash, 'Gitlab::Middleware::ReadOnly'
config.generators do |g| config.generators do |g|
g.factory_girl false g.factory_bot false
end end
config.after_initialize do config.after_initialize do
......
...@@ -10,8 +10,8 @@ module Prependable ...@@ -10,8 +10,8 @@ module Prependable
super super
base.singleton_class.send(:prepend, const_get('ClassMethods')) if const_defined?(:ClassMethods) base.singleton_class.send(:prepend, const_get('ClassMethods')) if const_defined?(:ClassMethods)
@_dependencies.each { |dep| base.send(:prepend, dep) } @_dependencies.each { |dep| base.send(:prepend, dep) } # rubocop:disable Gitlab/ModuleWithInstanceVariables
base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block) base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
end end
end end
......
...@@ -6,7 +6,7 @@ module LocalCacheRegistryCleanupWithEnsure ...@@ -6,7 +6,7 @@ module LocalCacheRegistryCleanupWithEnsure
def call(env) def call(env)
LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new) LocalCacheRegistry.set_cache_for(local_cache_key, LocalStore.new)
response = @app.call(env) response = @app.call(env) # rubocop:disable Gitlab/ModuleWithInstanceVariables
response[2] = ::Rack::BodyProxy.new(response[2]) do response[2] = ::Rack::BodyProxy.new(response[2]) do
LocalCacheRegistry.set_cache_for(local_cache_key, nil) LocalCacheRegistry.set_cache_for(local_cache_key, nil)
end end
......
...@@ -19,10 +19,10 @@ module RspecProfilingExt ...@@ -19,10 +19,10 @@ module RspecProfilingExt
def example_finished(*args) def example_finished(*args)
super super
rescue => err rescue => err
return if @already_logged_example_finished_error return if @already_logged_example_finished_error # rubocop:disable Gitlab/ModuleWithInstanceVariables
$stderr.puts "rspec_profiling couldn't collect an example: #{err}. Further warnings suppressed." $stderr.puts "rspec_profiling couldn't collect an example: #{err}. Further warnings suppressed."
@already_logged_example_finished_error = true @already_logged_example_finished_error = true # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
alias_method :example_passed, :example_finished alias_method :example_passed, :example_finished
......
...@@ -15,8 +15,11 @@ module Rugged ...@@ -15,8 +15,11 @@ module Rugged
class Repository class Repository
module UseGitlabGitAttributes module UseGitlabGitAttributes
def fetch_attributes(name, *) def fetch_attributes(name, *)
attributes.attributes(name)
end
def attributes
@attributes ||= Gitlab::Git::Attributes.new(path) @attributes ||= Gitlab::Git::Attributes.new(path)
@attributes.attributes(name)
end end
end end
......
...@@ -140,8 +140,8 @@ class Gitlab::Seeder::CycleAnalytics ...@@ -140,8 +140,8 @@ class Gitlab::Seeder::CycleAnalytics
issue.update(milestone: @project.milestones.sample) issue.update(milestone: @project.milestones.sample)
else else
label_name = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}" label_name = "#{FFaker::Product.brand}-#{FFaker::Product.brand}-#{rand(1000)}"
list_label = FactoryGirl.create(:label, title: label_name, project: issue.project) list_label = FactoryBot.create(:label, title: label_name, project: issue.project)
FactoryGirl.create(:list, board: FactoryGirl.create(:board, project: issue.project), label: list_label) FactoryBot.create(:list, board: FactoryBot.create(:board, project: issue.project), label: list_label)
issue.update(labels: [list_label]) issue.update(labels: [list_label])
end end
......
...@@ -107,6 +107,7 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i ...@@ -107,6 +107,7 @@ Manage your [repositories](user/project/repository/index.md) from the UI (user i
- [Work In Progress Merge Requests](user/project/merge_requests/work_in_progress_merge_requests.md) - [Work In Progress Merge Requests](user/project/merge_requests/work_in_progress_merge_requests.md)
- [Merge Request discussion resolution](user/discussions/index.md#moving-a-single-discussion-to-a-new-issue): Resolve discussions, move discussions in a merge request to an issue, only allow merge requests to be merged if all discussions are resolved. - [Merge Request discussion resolution](user/discussions/index.md#moving-a-single-discussion-to-a-new-issue): Resolve discussions, move discussions in a merge request to an issue, only allow merge requests to be merged if all discussions are resolved.
- **(EES/EEP)** [Merge Request approval](user/project/merge_requests/merge_request_approvals.md): Make sure every merge request is approved by one or more people before getting merged. - **(EES/EEP)** [Merge Request approval](user/project/merge_requests/merge_request_approvals.md): Make sure every merge request is approved by one or more people before getting merged.
- **(EEU)** [Static Application Security Testing](user/project/merge_requests/sast.md): Scan your code for vulnerabilities and display the results in merge requests.
- [Checkout merge requests locally](user/project/merge_requests/index.md#checkout-merge-requests-locally) - [Checkout merge requests locally](user/project/merge_requests/index.md#checkout-merge-requests-locally)
- [Cherry-pick](user/project/merge_requests/cherry_pick_changes.md) - [Cherry-pick](user/project/merge_requests/cherry_pick_changes.md)
- [Milestones](user/project/milestones/index.md): Organize issues and merge requests into a cohesive group, optionally setting a due date. - [Milestones](user/project/milestones/index.md): Organize issues and merge requests into a cohesive group, optionally setting a due date.
......
# Fast lookup of authorized SSH keys in the database
Regular SSH operations become slow as the number of users grows because OpenSSH
searches for a key to authorize a user via a linear search. In the worst case,
such as when the user is not authorized to access GitLab, OpenSSH will scan the
entire file to search for a key. This can take significant time and disk I/O,
which will delay users attempting to push or pull to a repository. Making
matters worse, if users add or remove keys frequently, the operating system may
not be able to cache the `authorized_keys` file, which causes the disk to be
accessed repeatedly.
GitLab Shell solves this by providing a way to authorize SSH users via a fast,
indexed lookup in the GitLab database. This page describes how to enable the fast
lookup of authorized SSH keys.
> **Warning:** OpenSSH version 6.9+ is required because
`AuthorizedKeysCommand` must be able to accept a fingerprint. These
instructions will break installations using older versions of OpenSSH, such as
those included with CentOS 6 as of September 2017. If you want to use this
feature for CentOS 6, follow [the instructions on how to build and install a custom OpenSSH package](#compiling-a-custom-version-of-openssh-for-centos-6) before continuing.
## Fast lookup is required for GitLab Geo
By default, GitLab manages an `authorized_keys` file, which contains all the
public SSH keys for users allowed to access GitLab. However, to maintain a
single source of truth, [Geo](../../gitlab-geo/README.md) needs to be configured to perform SSH fingerprint
lookups via database lookup.
As part of [setting up GitLab Geo](../../gitlab-geo/README.md#setup-instructions),
you will be required to follow the steps outlined below for both the primary and
secondary nodes, but note that the `Write to "authorized keys" file` checkbox
only needs to be unchecked on the primary node since it will be reflected
automatically on the secondary if database replication is working.
## Setting up fast lookup via GitLab Shell
GitLab Shell provides a way to authorize SSH users via a fast, indexed lookup
to the GitLab database. GitLab Shell uses the fingerprint of the SSH key to
check whether the user is authorized to access GitLab.
Create the directory `/opt/gitlab-shell` first:
```bash
sudo mkdir -p /opt/gitlab-shell
```
Create this file at `/opt/gitlab-shell/authorized_keys`:
```
#!/bin/bash
if [[ "$1" == "git" ]]; then
/opt/gitlab/embedded/service/gitlab-shell/bin/authorized_keys $2
fi
```
Set appropriate ownership and permissions:
```
sudo chown root:git /opt/gitlab-shell/authorized_keys
sudo chmod 0650 /opt/gitlab-shell/authorized_keys
```
Add the following to `/etc/ssh/sshd_config` or to `/assets/sshd_config` if you
are using Omnibus Docker:
```
AuthorizedKeysCommand /opt/gitlab-shell/authorized_keys %u %k
AuthorizedKeysCommandUser git
```
Reload OpenSSH:
```bash
# Debian or Ubuntu installations
sudo service ssh reload
# CentOS installations
sudo service sshd reload
```
Confirm that SSH is working by removing your user's SSH key in the UI, adding a
new one, and attempting to pull a repo.
> **Warning:** Do not disable writes until SSH is confirmed to be working
perfectly because the file will quickly become out-of-date.
In the case of lookup failures (which are not uncommon), the `authorized_keys`
file will still be scanned. So git SSH performance will still be slow for many
users as long as a large file exists.
You can disable any more writes to the `authorized_keys` file by unchecking
`Write to "authorized_keys" file` in the Application Settings of your GitLab
installation.
![Write to authorized keys setting](img/write_to_authorized_keys_setting.png)
Again, confirm that SSH is working by removing your user's SSH key in the UI,
adding a new one, and attempting to pull a repo.
Then you can backup and delete your `authorized_keys` file for best performance.
## How to go back to using the `authorized_keys` file
This is a brief overview. Please refer to the above instructions for more context.
1. [Rebuild the `authorized_keys` file](../raketasks/maintenance.md#rebuild-authorized_keys-file)
1. Enable writes to the `authorized_keys` file in Application Settings
1. Remove the `AuthorizedKeysCommand` lines from `/etc/ssh/sshd_config` or from `/assets/sshd_config` if you are using Omnibus Docker.
1. Reload sshd: `sudo service sshd reload`
1. Remove the `/opt/gitlab-shell/authorized_keys` file
## Compiling a custom version of OpenSSH for CentOS 6
Building a custom version of OpenSSH is not necessary for Ubuntu 16.04 users,
since Ubuntu 16.04 ships with OpenSSH 7.2.
It is also unnecessary for CentOS 7.4 users, as that version ships with
OpenSSH 7.4. If you are using CentOS 7.0 - 7.3, we strongly recommend that you
upgrade to CentOS 7.4 instead of following this procedure. This should be as
simple as running `yum update`.
CentOS 6 users must build their own OpenSSH package to enable SSH lookups via
the database. The following instructions can be used to build OpenSSH 7.5:
1. First, download the package and install the required packages:
```
sudo su -
cd /tmp
curl --remote-name https://mirrors.evowise.com/pub/OpenBSD/OpenSSH/portable/openssh-7.5p1.tar.gz
tar xzvf openssh-7.5p1.tar.gz
yum install rpm-build gcc make wget openssl-devel krb5-devel pam-devel libX11-devel xmkmf libXt-devel
```
3. Prepare the build by copying files to the right place:
```
mkdir -p /root/rpmbuild/{SOURCES,SPECS}
cp ./openssh-7.5p1/contrib/redhat/openssh.spec /root/rpmbuild/SPECS/
cp openssh-7.5p1.tar.gz /root/rpmbuild/SOURCES/
cd /root/rpmbuild/SPECS
```
3. Next, set the spec settings properly:
```
sed -i -e "s/%define no_gnome_askpass 0/%define no_gnome_askpass 1/g" openssh.spec
sed -i -e "s/%define no_x11_askpass 0/%define no_x11_askpass 1/g" openssh.spec
sed -i -e "s/BuildPreReq/BuildRequires/g" openssh.spec
```
3. Build the RPMs:
```
rpmbuild -bb openssh.spec
```
4. Ensure the RPMs were built:
```
ls -al /root/rpmbuild/RPMS/x86_64/
```
You should see something as the following:
```
total 1324
drwxr-xr-x. 2 root root 4096 Jun 20 19:37 .
drwxr-xr-x. 3 root root 19 Jun 20 19:37 ..
-rw-r--r--. 1 root root 470828 Jun 20 19:37 openssh-7.5p1-1.x86_64.rpm
-rw-r--r--. 1 root root 490716 Jun 20 19:37 openssh-clients-7.5p1-1.x86_64.rpm
-rw-r--r--. 1 root root 17020 Jun 20 19:37 openssh-debuginfo-7.5p1-1.x86_64.rpm
-rw-r--r--. 1 root root 367516 Jun 20 19:37 openssh-server-7.5p1-1.x86_64.rpm
```
5. Install the packages. OpenSSH packages will replace `/etc/pam.d/sshd`
with its own version, which may prevent users from logging in, so be sure
that the file is backed up and restored after installation:
```
timestamp=$(date +%s)
cp /etc/pam.d/sshd pam-ssh-conf-$timestamp
rpm -Uvh /root/rpmbuild/RPMS/x86_64/*.rpm
yes | cp pam-ssh-conf-$timestamp /etc/pam.d/sshd
```
6. Verify the installed version. In another window, attempt to login to the server:
```
ssh -v <your-centos-machine>
```
You should see a line that reads: "debug1: Remote protocol version 2.0, remote software version OpenSSH_7.5"
If not, you may need to restart sshd (e.g. `systemctl restart sshd.service`).
7. *IMPORTANT!* Open a new SSH session to your server before exiting to make
sure everything is working! If you need to downgrade, simple install the
older package:
```
# Only run this if you run into a problem logging in
yum downgrade openssh-server openssh openssh-clients
```
...@@ -15,5 +15,4 @@ that to prioritize important jobs. ...@@ -15,5 +15,4 @@ that to prioritize important jobs.
to restart Sidekiq. to restart Sidekiq.
- **(EES/EEP)** [Extra Sidekiq operations](extra_sidekiq_processes.md): Configure an extra set of Sidekiq processes to ensure certain queues always have dedicated workers, no matter the amount of jobs that need to be processed. - **(EES/EEP)** [Extra Sidekiq operations](extra_sidekiq_processes.md): Configure an extra set of Sidekiq processes to ensure certain queues always have dedicated workers, no matter the amount of jobs that need to be processed.
- [Unicorn](unicorn.md): Understand Unicorn and unicorn-worker-killer. - [Unicorn](unicorn.md): Understand Unicorn and unicorn-worker-killer.
- **(EES/EEP)** [Speed up SSH operations](speed_up_ssh.md): Authorize SSH users via a fast, indexed lookup to the GitLab database. - **(EES/EEP)** [Speed up SSH operations](fast_ssh_key_lookup.md): Authorize SSH users via a fast, indexed lookup to the GitLab database.
- [Unicorn](unicorn.md): Understand Unicorn and unicorn-worker-killer.
# Speed up SSH operations This document was moved to [another location](fast_ssh_key_lookup.md).
>
- [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/250) in GitLab Enterprise Edition 8.7.
- Available in GitLab Enterprise Edition Starter.
## The problem
SSH operations become slow as the number of users grows.
## The reason
OpenSSH searches for a key to authorize a user via a linear search. In the worst case, such as when the user is not authorized to access GitLab, OpenSSH will scan the entire file to search for a key. This can take significant time and disk I/O, which will delay users attempting to push or pull to a repository. Making matters worse, if users add or remove keys frequently, the operating system may not be able to cache the authorized_keys file, which causes the disk to be accessed repeatedly.
## The solution
GitLab Shell provides a way to authorize SSH users via a fast, indexed lookup to the GitLab database. GitLab Shell uses the fingerprint of the SSH key to check whether the user is authorized to access GitLab.
> **Warning:** OpenSSH version 6.9+ is required because
`AuthorizedKeysCommand` must be able to accept a fingerprint. These
instructions will break installations using older versions of OpenSSH, such as
those included with CentOS 6 as of September 2017. If you want to use this
feature for CentOS 6, follow [the instructions on how to build and install a custom OpenSSH package]
(#compiling-a-custom-version-of-openssh-for-centos-6) before continuing.
Create the directory `/opt/gitlab-shell` first:
```bash
sudo mkdir -p /opt/gitlab-shell
```
Create this file at `/opt/gitlab-shell/authorized_keys`:
```
#!/bin/bash
if [[ "$1" == "git" ]]; then
/opt/gitlab/embedded/service/gitlab-shell/bin/authorized_keys $2
fi
```
Set appropriate ownership and permissions:
```
sudo chown root:git /opt/gitlab-shell/authorized_keys
sudo chmod 0650 /opt/gitlab-shell/authorized_keys
```
Add the following to `/etc/ssh/sshd_config` or to `/assets/sshd_config` if you are using Omnibus Docker:
```
AuthorizedKeysCommand /opt/gitlab-shell/authorized_keys %u %k
AuthorizedKeysCommandUser git
```
Reload OpenSSH:
```bash
# Debian or Ubuntu installations
sudo service ssh reload
# CentOS installations
sudo service sshd reload
```
Confirm that SSH is working by removing your user's SSH key in the UI, adding a new one, and attempting to pull a repo.
> **Warning:** Do not disable writes until SSH is confirmed to be working perfectly because the file will quickly become out-of-date.
In the case of lookup failures (which are not uncommon), the `authorized_keys` file will still be scanned. So git SSH performance will still be slow for many users as long as a large file exists.
You can disable any more writes to the `authorized_keys` file by unchecking `Write to "authorized_keys" file` in the Application Settings of your GitLab installation.
![Write to authorized keys setting](img/write_to_authorized_keys_setting.png)
Again, confirm that SSH is working by removing your user's SSH key in the UI, adding a new one, and attempting to pull a repo.
Then you can backup and delete your `authorized_keys` file for best performance.
## How to go back to using the `authorized_keys` file
This is a brief overview. Please refer to the above instructions for more context.
1. [Rebuild the `authorized_keys` file](../raketasks/maintenance.md#rebuild-authorized_keys-file)
1. Enable writes to the `authorized_keys` file in Application Settings
1. Remove the `AuthorizedKeysCommand` lines from `/etc/ssh/sshd_config` or from `/assets/sshd_config` if you are using Omnibus Docker.
1. Reload sshd: `sudo service sshd reload`
1. Remove the `/opt/gitlab-shell/authorized_keys` file
## Compiling a custom version of OpenSSH for CentOS 6
Building a custom version of OpenSSH is not necessary for Ubuntu 16.04 users,
since Ubuntu 16.04 ships with OpenSSH 7.2.
It is also unnecessary for CentOS 7.4 users, as that version ships with
OpenSSH 7.4. If you are using CentOS 7.0 - 7.3, we strongly recommend that you
upgrade to CentOS 7.4 instead of following this procedure. This should be as
simple as running `yum update`.
CentOS 6 users must build their own OpenSSH package to enable SSH lookups via
the database. The following instructions can be used to build OpenSSH 7.5:
1. First, download the package and install the required packages:
```
sudo su -
cd /tmp
curl --remote-name https://mirrors.evowise.com/pub/OpenBSD/OpenSSH/portable/openssh-7.5p1.tar.gz
tar xzvf openssh-7.5p1.tar.gz
yum install rpm-build gcc make wget openssl-devel krb5-devel pam-devel libX11-devel xmkmf libXt-devel
```
3. Prepare the build by copying files to the right place:
```
mkdir -p /root/rpmbuild/{SOURCES,SPECS}
cp ./openssh-7.5p1/contrib/redhat/openssh.spec /root/rpmbuild/SPECS/
cp openssh-7.5p1.tar.gz /root/rpmbuild/SOURCES/
cd /root/rpmbuild/SPECS
```
3. Next, set the spec settings properly:
```
sed -i -e "s/%define no_gnome_askpass 0/%define no_gnome_askpass 1/g" openssh.spec
sed -i -e "s/%define no_x11_askpass 0/%define no_x11_askpass 1/g" openssh.spec
sed -i -e "s/BuildPreReq/BuildRequires/g" openssh.spec
```
3. Build the RPMs:
```
rpmbuild -bb openssh.spec
```
4. Ensure the RPMs were built:
```
ls -al /root/rpmbuild/RPMS/x86_64/
```
You should see something as the following:
```
total 1324
drwxr-xr-x. 2 root root 4096 Jun 20 19:37 .
drwxr-xr-x. 3 root root 19 Jun 20 19:37 ..
-rw-r--r--. 1 root root 470828 Jun 20 19:37 openssh-7.5p1-1.x86_64.rpm
-rw-r--r--. 1 root root 490716 Jun 20 19:37 openssh-clients-7.5p1-1.x86_64.rpm
-rw-r--r--. 1 root root 17020 Jun 20 19:37 openssh-debuginfo-7.5p1-1.x86_64.rpm
-rw-r--r--. 1 root root 367516 Jun 20 19:37 openssh-server-7.5p1-1.x86_64.rpm
```
5. Install the packages. OpenSSH packages will replace `/etc/pam.d/sshd`
with its own version, which may prevent users from logging in, so be sure
that the file is backed up and restored after installation:
```
timestamp=$(date +%s)
cp /etc/pam.d/sshd pam-ssh-conf-$timestamp
rpm -Uvh /root/rpmbuild/RPMS/x86_64/*.rpm
yes | cp pam-ssh-conf-$timestamp /etc/pam.d/sshd
```
6. Verify the installed version. In another window, attempt to login to the server:
```
ssh -v <your-centos-machine>
```
You should see a line that reads: "debug1: Remote protocol version 2.0, remote software version OpenSSH_7.5"
If not, you may need to restart sshd (e.g. `systemctl restart sshd.service`).
7. *IMPORTANT!* Open a new SSH session to your server before exiting to make
sure everything is working! If you need to downgrade, simple install the
older package:
```
# Only run this if you run into a problem logging in
yum downgrade openssh-server openssh openssh-clients
```
...@@ -366,16 +366,16 @@ POST /groups ...@@ -366,16 +366,16 @@ POST /groups
Parameters: Parameters:
- `name` (required) - The name of the group | Attribute | Type | Required | Description |
- `path` (required) - The path of the group | --------- | ---- | -------- | ----------- |
- `description` (optional) - The group's description | `name` | string | yes | The name of the group |
- `membership_lock` (optional, boolean) - Prevent adding new members to project membership within this group | `path` | string | yes | The path of the group |
- `share_with_group_lock` (optional, boolean) - Prevent sharing a project with another group within this group | `description` | string | no | The group's description |
- `visibility` (optional) - The group's visibility. Can be `private`, `internal`, or `public`. | `visibility` | string | no | The group's visibility. Can be `private`, `internal`, or `public`. |
- `lfs_enabled` (optional) - Enable/disable Large File Storage (LFS) for the projects in this group | `lfs_enabled` | boolean | no | Enable/disable Large File Storage (LFS) for the projects in this group |
- `request_access_enabled` (optional) - Allow users to request member access. | `request_access_enabled` | boolean | no | Allow users to request member access. |
- `parent_id` (optional) - The parent group id for creating nested group. | `parent_id` | integer | no | The parent group id for creating nested group. |
- `shared_runners_minutes_limit` (optional) - (admin-only) Pipeline minutes quota for this group | `shared_runners_minutes_limit` | integer | no | (admin-only) Pipeline minutes quota for this group. |
## Transfer project to group ## Transfer project to group
...@@ -387,8 +387,10 @@ POST /groups/:id/projects/:project_id ...@@ -387,8 +387,10 @@ POST /groups/:id/projects/:project_id
Parameters: Parameters:
- `id` (required) - The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user | Attribute | Type | Required | Description |
- `project_id` (required) - The ID or path of a project | --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
| `project_id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
## Update group ## Update group
......
...@@ -58,6 +58,10 @@ Apart from those, here is an collection of tutorials and guides on setting up yo ...@@ -58,6 +58,10 @@ Apart from those, here is an collection of tutorials and guides on setting up yo
- [Analyze code quality with the Code Climate CLI](code_climate.md) - [Analyze code quality with the Code Climate CLI](code_climate.md)
### Static Application Security Testing (SAST)
- [Scan your code for vulnerabilities](sast.md)
### Other ### Other
- [Using `dpl` as deployment tool](deployment/README.md) - [Using `dpl` as deployment tool](deployment/README.md)
......
# Static application security testing with GitLab CI/CD
NOTE: **Note:**
In order to use this tool, a [GitLab Enterprise Edition Ultimate][ee] license
is needed.
This example shows how to run
[Static Application Security Testing (SAST)](https://en.wikipedia.org/wiki/Static_program_analysis)
on your project's source code by using GitLab CI/CD.
All you need is a GitLab Runner with the Docker executor (the shared Runners on
GitLab.com will work fine). You can then add a new job to `.gitlab-ci.yml`,
called `sast`:
```yaml
sast:
image: registry.gitlab.com/gitlab-org/gl-sast:latest
script:
- /app/bin/run .
artifacts:
paths: [gl-sast-report.json]
```
Behind the scenes, the [gl-sast Docker image](https://gitlab.com/gitlab-org/gl-sast)
is used to detect the language/framework and in turn runs the matching scan tool.
The above example will create a `sast` job in your CI pipeline and will allow
you to download and analyze the report artifact in JSON format.
The results are sorted by the priority of the vulnerability:
1. High
1. Medium
1. Low
1. Unknown
1. Everything else
TIP: **Tip:**
Starting with GitLab Enterprise Edition Ultimate 10.3, this information will
be automatically extracted and shown right in the merge request widget. To do
so, the CI job must be named `sast` and the artifact path must be
`gl-sast-report.json`.
[Learn more on application security testing results shown in merge requests](../../user/project/merge_requests/sast.md).
## Supported languages and frameworks
The following languages and frameworks are supported.
| Language / framework | Scan tool |
| -------------------- | --------- |
| JavaScript | [Retire.js](https://retirejs.github.io/retire.js)
| Python | [bandit](https://github.com/openstack/bandit) |
| Ruby | [bundler-audit](https://github.com/rubysec/bundler-audit) |
| Ruby on Rails | [brakeman](https://brakemanscanner.org) |
[ee]: https://about.gitlab.com/gitlab-ee/
...@@ -37,6 +37,7 @@ comments: false ...@@ -37,6 +37,7 @@ comments: false
- [`Gemfile` guidelines](gemfile.md) - [`Gemfile` guidelines](gemfile.md)
- [Sidekiq debugging](sidekiq_debugging.md) - [Sidekiq debugging](sidekiq_debugging.md)
- [Gotchas](gotchas.md) to avoid - [Gotchas](gotchas.md) to avoid
- [Avoid modules with instance variables](module_with_instance_variables.md) if possible
- [Issue and merge requests state models](object_state_models.md) - [Issue and merge requests state models](object_state_models.md)
- [How to dump production data to staging](db_dump.md) - [How to dump production data to staging](db_dump.md)
- [Working with the GitHub importer](github_importer.md) - [Working with the GitHub importer](github_importer.md)
...@@ -81,10 +82,9 @@ comments: false ...@@ -81,10 +82,9 @@ comments: false
## Documentation guides ## Documentation guides
- [Documentation styleguide](doc_styleguide.md): Use this styleguide if you are
contributing to the documentation.
- [Writing documentation](writing_documentation.md) - [Writing documentation](writing_documentation.md)
- [Distinction between general documentation and technical articles](writing_documentation.md#distinction-between-general-documentation-and-technical-articles) - [Documentation styleguide](doc_styleguide.md)
- [Markdown](../user/markdown.md)
## Internationalization (i18n) guides ## Internationalization (i18n) guides
......
...@@ -8,7 +8,7 @@ might encounter or should avoid during development of GitLab CE and EE. ...@@ -8,7 +8,7 @@ might encounter or should avoid during development of GitLab CE and EE.
Consider the following factory: Consider the following factory:
```ruby ```ruby
FactoryGirl.define do FactoryBot.define do
factory :label do factory :label do
sequence(:title) { |n| "label#{n}" } sequence(:title) { |n| "label#{n}" }
end end
...@@ -53,7 +53,7 @@ When run, this spec doesn't do what we might expect: ...@@ -53,7 +53,7 @@ When run, this spec doesn't do what we might expect:
(compared using ==) (compared using ==)
``` ```
That's because FactoryGirl sequences are not reseted for each example. That's because FactoryBot sequences are not reseted for each example.
Please remember that sequence-generated values exist only to avoid having to Please remember that sequence-generated values exist only to avoid having to
explicitly set attributes that have a uniqueness constraint when using a factory. explicitly set attributes that have a uniqueness constraint when using a factory.
......
## Modules with instance variables could be considered harmful
### Background
Rails somehow encourages people using modules and instance variables
everywhere. For example, using instance variables in the controllers,
helpers, and views. They're also encouraging the use of
`ActiveSupport::Concern`, which further strengthens the idea of
saving everything in a giant, single object, and people could access
everything in that one giant object.
### The problems
Of course this is convenient to develop, because we just have everything
within reach. However this has a number of downsides when that chosen object
is growing, it would later become out of control for the same reason.
There are just too many things in the same context, and we don't know if
those things are tightly coupled or not, depending on each others or not.
It's very hard to tell when the complexity grows to a point, and it makes
tracking the code also extremely hard. For example, a class could be using
3 different instance variables, and all of them could be initialized and
manipulated from 3 different modules. It's hard to track when those variables
start giving us troubles. We don't know which module would suddenly change
one of the variables. Everything could touch anything.
### Similar concerns
People are saying multiple inheritance is bad. Mixing multiple modules with
multiple instance variables scattering everywhere suffer from the same issue.
The same applies to `ActiveSupport::Concern`. See:
[Consider replacing concerns with dedicated classes & composition](
https://gitlab.com/gitlab-org/gitlab-ce/issues/23786)
There's also a similar idea:
[Use decorators and interface segregation to solve overgrowing models problem](
https://gitlab.com/gitlab-org/gitlab-ce/issues/13484)
Note that `included` doesn't solve the whole issue. They define the
dependencies, but they still allow each modules to talk implicitly via the
instance variables in the final giant object, and that's where the problem is.
### Solutions
We should split the giant object into multiple objects, and they communicate
with each other with the API, i.e. public methods. In short, composition over
inheritance. This way, each smaller objects would have their own respective
limited states, i.e. instance variables. If one instance variable goes wrong,
we would be very clear that it's from that single small object, because
no one else could be touching it.
With clearly defined API, this would make things less coupled and much easier
to debug and track, and much more extensible for other objects to use, because
they communicate in a clear way, rather than implicit dependencies.
### Acceptable use
However, it's not always bad to use instance variables in a module,
as long as it's contained in the same module; that is, no other modules or
objects are touching them, then it would be an acceptable use.
We especially allow the case where a single instance variable is used with
`||=` to setup the value. This would look like:
``` ruby
module M
def f
@f ||= true
end
end
```
Unfortunately it's not easy to code more complex rules into the cop, so
we rely on people's best judgement. If we could find another good pattern
we could easily add to the cop, we should do it.
### How to rewrite and avoid disabling this cop
Even if we could just disable the cop, we should avoid doing so. Some code
could be easily rewritten in simple form. Consider this acceptable method:
``` ruby
module Gitlab
module Emoji
def emoji_unicode_version(name)
@emoji_unicode_versions_by_name ||=
JSON.parse(File.read(Rails.root.join('fixtures', 'emojis', 'emoji-unicode-version-map.json')))
@emoji_unicode_versions_by_name[name]
end
end
end
```
This method is totally fine because it's already self-contained. No other
methods should be using `@emoji_unicode_versions_by_name` and we're good.
However it's still offending the cop because it's not just `||=`, and the
cop is not smart enough to judge that this is fine.
On the other hand, we could split this method into two:
``` ruby
module Gitlab
module Emoji
def emoji_unicode_version(name)
emoji_unicode_versions_by_name[name]
end
private
def emoji_unicode_versions_by_name
@emoji_unicode_versions_by_name ||=
JSON.parse(File.read(Rails.root.join('fixtures', 'emojis', 'emoji-unicode-version-map.json')))
end
end
end
```
Now the cop won't complain. Here's a bad example which we could rewrite:
``` ruby
module SpamCheckService
def filter_spam_check_params
@request = params.delete(:request)
@api = params.delete(:api)
@recaptcha_verified = params.delete(:recaptcha_verified)
@spam_log_id = params.delete(:spam_log_id)
end
def spam_check(spammable, user)
spam_service = SpamService.new(spammable, @request)
spam_service.when_recaptcha_verified(@recaptcha_verified, @api) do
user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true)
end
end
end
```
There are several implicit dependencies here. First, `params` should be
defined before use. Second, `filter_spam_check_params` should be called
before `spam_check`. These are all implicit and the includer could be using
those instance variables without awareness.
This should be rewritten like:
``` ruby
class SpamCheckService
def initialize(request:, api:, recaptcha_verified:, spam_log_id:)
@request = request
@api = api
@recaptcha_verified = recaptcha_verified
@spam_log_id = spam_log_id
end
def spam_check(spammable, user)
spam_service = SpamService.new(spammable, @request)
spam_service.when_recaptcha_verified(@recaptcha_verified, @api) do
user.spam_logs.find_by(id: @spam_log_id)&.update!(recaptcha_verified: true)
end
end
end
```
And use it like:
``` ruby
class UpdateSnippetService < BaseService
def execute
# ...
spam = SpamCheckService.new(params.slice!(:request, :api, :recaptcha_verified, :spam_log_id))
spam.check(snippet, current_user)
# ...
end
end
```
This way, all those instance variables are isolated in `SpamCheckService`
rather than whatever includes the module, and those modules which were also
included, making it much easier to track down any issues,
and reducing the chance of having name conflicts.
### How to disable this cop
Put the disabling comment right after your code in the same line:
``` ruby
module M
def violating_method
@f + @g # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
end
```
If there are multiple lines, you could also enable and disable for a section:
``` ruby
module M
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def violating_method
@f = 0
@g = 1
@h = 2
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end
```
Note that you need to enable it at some point, otherwise everything below
won't be checked.
### Things we might need to ignore right now
Because of the way Rails helpers and mailers work, we might not be able to
avoid the use of instance variables there. For those cases, we could ignore
them at the moment. At least we're not going to share those modules with
other random objects, so they're still somewhat isolated.
### Instance variables in views
They're bad because we can't easily tell who's using the instance variables
(from controller's point of view) and where we set them up (from partials'
point of view), making it extremely hard to track data dependency.
We're trying to use something like this instead:
``` haml
= render 'projects/commits/commit', commit: commit, ref: ref, project: project
```
And in the partial:
``` haml
- ref = local_assigns.fetch(:ref)
- commit = local_assigns.fetch(:commit)
- project = local_assigns.fetch(:project)
```
This way it's clearer where those values were coming from, and we gain the
benefit to have typo check over using instance variables. In the future,
we should also forbid the use of instance variables in partials.
...@@ -8,8 +8,8 @@ and effective _as well as_ fast. ...@@ -8,8 +8,8 @@ and effective _as well as_ fast.
Here are some things to keep in mind regarding test performance: Here are some things to keep in mind regarding test performance:
- `double` and `spy` are faster than `FactoryGirl.build(...)` - `double` and `spy` are faster than `FactoryBot.build(...)`
- `FactoryGirl.build(...)` and `.build_stubbed` are faster than `.create`. - `FactoryBot.build(...)` and `.build_stubbed` are faster than `.create`.
- Don't `create` an object when `build`, `build_stubbed`, `attributes_for`, - Don't `create` an object when `build`, `build_stubbed`, `attributes_for`,
`spy`, or `double` will do. Database persistence is slow! `spy`, or `double` will do. Database persistence is slow!
- Don't mark a feature as requiring JavaScript (through `@javascript` in - Don't mark a feature as requiring JavaScript (through `@javascript` in
...@@ -254,13 +254,13 @@ end ...@@ -254,13 +254,13 @@ end
### Factories ### Factories
GitLab uses [factory_girl] as a test fixture replacement. GitLab uses [factory_bot] as a test fixture replacement.
- Factory definitions live in `spec/factories/`, named using the pluralization - Factory definitions live in `spec/factories/`, named using the pluralization
of their corresponding model (`User` factories are defined in `users.rb`). of their corresponding model (`User` factories are defined in `users.rb`).
- There should be only one top-level factory definition per file. - There should be only one top-level factory definition per file.
- FactoryGirl methods are mixed in to all RSpec groups. This means you can (and - FactoryBot methods are mixed in to all RSpec groups. This means you can (and
should) call `create(...)` instead of `FactoryGirl.create(...)`. should) call `create(...)` instead of `FactoryBot.create(...)`.
- Make use of [traits] to clean up definitions and usages. - Make use of [traits] to clean up definitions and usages.
- When defining a factory, don't define attributes that are not required for the - When defining a factory, don't define attributes that are not required for the
resulting record to pass validation. resulting record to pass validation.
...@@ -269,8 +269,8 @@ GitLab uses [factory_girl] as a test fixture replacement. ...@@ -269,8 +269,8 @@ GitLab uses [factory_girl] as a test fixture replacement.
- Factories don't have to be limited to `ActiveRecord` objects. - Factories don't have to be limited to `ActiveRecord` objects.
[See example](https://gitlab.com/gitlab-org/gitlab-ce/commit/0b8cefd3b2385a21cfed779bd659978c0402766d). [See example](https://gitlab.com/gitlab-org/gitlab-ce/commit/0b8cefd3b2385a21cfed779bd659978c0402766d).
[factory_girl]: https://github.com/thoughtbot/factory_girl [factory_bot]: https://github.com/thoughtbot/factory_bot
[traits]: http://www.rubydoc.info/gems/factory_girl/file/GETTING_STARTED.md#Traits [traits]: http://www.rubydoc.info/gems/factory_bot/file/GETTING_STARTED.md#Traits
### Fixtures ### Fixtures
......
...@@ -33,7 +33,7 @@ changes should be tested. ...@@ -33,7 +33,7 @@ changes should be tested.
## [Testing best practices](best_practices.md) ## [Testing best practices](best_practices.md)
Everything you should know about how to write good tests: RSpec, FactoryGirl, Everything you should know about how to write good tests: RSpec, FactoryBot,
system tests, parameterized tests etc. system tests, parameterized tests etc.
--- ---
......
This diff is collapsed.
...@@ -86,7 +86,7 @@ current version of OpenSSH: ...@@ -86,7 +86,7 @@ current version of OpenSSH:
Note that CentOS 6 and 7.0 ship with an old version of OpenSSH that do not Note that CentOS 6 and 7.0 ship with an old version of OpenSSH that do not
support a feature that Geo requires. See the [documentation on GitLab Geo SSH support a feature that Geo requires. See the [documentation on GitLab Geo SSH
access](ssh.md) for more details. access](../administration/operations/fast_ssh_key_lookup.md) for more details.
### LDAP ### LDAP
...@@ -145,8 +145,8 @@ If you installed GitLab using the Omnibus packages (highly recommended): ...@@ -145,8 +145,8 @@ If you installed GitLab using the Omnibus packages (highly recommended):
Geo node to unlock GitLab Geo. Geo node to unlock GitLab Geo.
1. [Setup the database replication](database.md) (`primary (read-write) <-> 1. [Setup the database replication](database.md) (`primary (read-write) <->
secondary (read-only)` topology). secondary (read-only)` topology).
1. [Lookup authorized SSH keys in the database](../administration/operations/speed_up_ssh.html), 1. [Configure fast lookup of authorized SSH keys in the database](../administration/operations/fast_ssh_key_lookup.md),
do this step for both primary AND secondary nodes. this step is required and needs to be done on both the primary AND secondary nodes.
1. [Configure GitLab](configuration.md) to set the primary and secondary nodes. 1. [Configure GitLab](configuration.md) to set the primary and secondary nodes.
1. Optional: [Configure a secondary LDAP server](../administration/auth/ldap.md) 1. Optional: [Configure a secondary LDAP server](../administration/auth/ldap.md)
for the secondary. See [notes on LDAP](#ldap). for the secondary. See [notes on LDAP](#ldap).
...@@ -165,7 +165,7 @@ If you installed GitLab from source: ...@@ -165,7 +165,7 @@ If you installed GitLab from source:
Geo node to unlock GitLab Geo. Geo node to unlock GitLab Geo.
1. [Setup the database replication](database_source.md) (`primary (read-write) 1. [Setup the database replication](database_source.md) (`primary (read-write)
<-> secondary (read-only)` topology). <-> secondary (read-only)` topology).
1. [Lookup authorized SSH keys in the database](../administration/operations/speed_up_ssh.html), 1. [Configure fast lookup of authorized SSH keys in the database](../administration/operations/fast_ssh_key_lookup.md),
do this step for both primary AND secondary nodes. do this step for both primary AND secondary nodes.
1. [Configure GitLab](configuration_source.md) to set the primary and secondary 1. [Configure GitLab](configuration_source.md) to set the primary and secondary
nodes. nodes.
......
This document was moved to [another location](using_a_geo_server.md).
This diff is collapsed.
This diff is collapsed.
# GitLab Geo SSH access This document was moved to [another location](../administration/operations/fast_ssh_key_lookup.md).
By default, GitLab manages an `authorized_keys` file, which contains all the
public SSH keys for users allowed to access GitLab. However, to maintain a
single source of truth, Geo needs to be configured to perform SSH fingerprint
lookups via database lookup. This approach is also much faster than scanning a
file.
>**Note:**
GitLab 10.0 and higher require database lookups for SSH keys.
Note this feature is only available on operating systems that support OpenSSH
6.9 and above. For CentOS 6, see the [instructions on building custom
version of OpenSSH for your server]
(../administration/operations/speed_up_ssh.html#compiling-a-custom-version-of-openssh-for-centos-6).
For both primary AND secondary nodes, follow the instructions on
[looking up authorized SSH keys in the database](../administration/operations/speed_up_ssh.html).
Note that the 'Write to "authorized keys" file' checkbox only needs
to be unchecked on the primary node since it will be reflected automatically
in the secondary if database replication is working.
...@@ -93,7 +93,7 @@ for existing repositories was added in GitLab 10.1. ...@@ -93,7 +93,7 @@ for existing repositories was added in GitLab 10.1.
## Upgrading to GitLab 10.0 ## Upgrading to GitLab 10.0
Since GitLab 10.0, we require all **Geo** systems to [use SSH key lookups via Since GitLab 10.0, we require all **Geo** systems to [use SSH key lookups via
the database](ssh.md) to avoid having to maintain consistency of the the database](../administration/operations/fast_ssh_key_lookup.md) to avoid having to maintain consistency of the
`authorized_keys` file for SSH access. Failing to do this will prevent users `authorized_keys` file for SSH access. Failing to do this will prevent users
from being able to clone via SSH. from being able to clone via SSH.
......
...@@ -14,3 +14,4 @@ comments: false ...@@ -14,3 +14,4 @@ comments: false
- [How we manage the CRIME vulnerability](crime_vulnerability.md) - [How we manage the CRIME vulnerability](crime_vulnerability.md)
- [Enforce Two-factor authentication](two_factor_authentication.md) - [Enforce Two-factor authentication](two_factor_authentication.md)
- [Send email confirmation on sign-up](user_email_confirmation.md) - [Send email confirmation on sign-up](user_email_confirmation.md)
- [Security of running jobs](https://docs.gitlab.com/runner/security/#security-of-running-jobs)
...@@ -19,6 +19,7 @@ project in an easy and automatic way: ...@@ -19,6 +19,7 @@ project in an easy and automatic way:
1. [Auto Build](#auto-build) 1. [Auto Build](#auto-build)
1. [Auto Test](#auto-test) 1. [Auto Test](#auto-test)
1. [Auto Code Quality](#auto-code-quality) 1. [Auto Code Quality](#auto-code-quality)
1. [Auto SAST (Static Application Security Testing)](#auto-sast)
1. [Auto Review Apps](#auto-review-apps) 1. [Auto Review Apps](#auto-review-apps)
1. [Auto Deploy](#auto-deploy) 1. [Auto Deploy](#auto-deploy)
1. [Auto Monitoring](#auto-monitoring) 1. [Auto Monitoring](#auto-monitoring)
...@@ -198,6 +199,18 @@ out. In GitLab Enterprise Edition Starter, differences between the source and ...@@ -198,6 +199,18 @@ out. In GitLab Enterprise Edition Starter, differences between the source and
target branches are target branches are
[shown in the merge request widget](../../user/project/merge_requests/code_quality_diff.md). [shown in the merge request widget](../../user/project/merge_requests/code_quality_diff.md).
### Auto SAST
> Introduced in [GitLab Enterprise Edition Ultimate][ee] 10.3.
Static Application Security Testing (SAST) uses the
[gl-sast Docker image](https://gitlab.com/gitlab-org/gl-sast) to run static
analysis on the current code and checks for potential security issues. Once the
report is created, it's uploaded as an artifact which you can later download and
check out.
Any security warnings are also [shown in the merge request widget](../../user/project/merge_requests/sast.md).
### Auto Review Apps ### Auto Review Apps
NOTE: **Note:** NOTE: **Note:**
...@@ -536,3 +549,4 @@ curl --data "value=true" --header "PRIVATE-TOKEN: personal_access_token" https:/ ...@@ -536,3 +549,4 @@ curl --data "value=true" --header "PRIVATE-TOKEN: personal_access_token" https:/
[postgresql]: https://www.postgresql.org/ [postgresql]: https://www.postgresql.org/
[Auto DevOps template]: https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Auto-DevOps.gitlab-ci.yml [Auto DevOps template]: https://gitlab.com/gitlab-org/gitlab-ci-yml/blob/master/Auto-DevOps.gitlab-ci.yml
[GitLab Omnibus Helm Chart]: ../../install/kubernetes/gitlab_omnibus.md [GitLab Omnibus Helm Chart]: ../../install/kubernetes/gitlab_omnibus.md
[ee]: https://about.gitlab.com/gitlab-ee/
...@@ -55,6 +55,7 @@ and [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board. ...@@ -55,6 +55,7 @@ and [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.
- [Lock files](https://docs.gitlab.com/ee/user/project/file_lock.html) to prevent conflicts - [Lock files](https://docs.gitlab.com/ee/user/project/file_lock.html) to prevent conflicts
- View of the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html) - View of the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
- Leverage your continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) - Leverage your continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
- Scan your code for vulnerabilities and [display them in merge requests](project/merge_requests/sast.md).
You can also [integrate](project/integrations/project_services.md) GitLab with numerous third-party applications, such as Mattermost, Microsoft Teams, HipChat, Trello, Slack, Bamboo CI, JIRA, and a lot more. You can also [integrate](project/integrations/project_services.md) GitLab with numerous third-party applications, such as Mattermost, Microsoft Teams, HipChat, Trello, Slack, Bamboo CI, JIRA, and a lot more.
......
...@@ -193,6 +193,17 @@ can show the Code Climate report right in the merge request widget area. ...@@ -193,6 +193,17 @@ can show the Code Climate report right in the merge request widget area.
[Read more about Code Quality reports.](code_quality_diff.md) [Read more about Code Quality reports.](code_quality_diff.md)
## Static Application Security Testing
> Introduced in [GitLab Enterprise Edition Ultimate][products] 10.3.
If you are using [GitLab CI/CD][ci], you can analyze your source code for known
vulnerabilities using Static Application Security Testing (SAST).
Going a step further, GitLab can show the vulnerability report right in the
merge request widget area.
[Read more about Static Application Security Testing reports.](sast.md)
## Live preview with Review Apps ## Live preview with Review Apps
If you configured [Review Apps](https://about.gitlab.com/features/review-apps/) for your project, If you configured [Review Apps](https://about.gitlab.com/features/review-apps/) for your project,
......
# Static Application Security Testing (SAST)
> [Introduced][ee-3775] in [GitLab Enterprise Edition Ultimate][ee] 10.3.
## Overview
If you are using [GitLab CI/CD][ci], you can analyze your source code for known
vulnerabilities using Static Application Security Testing (SAST).
Going a step further, GitLab can show the vulnerability list right in the merge
request widget area:
![SAST Widget](img/sast.png)
## Use cases
- Your application is using an external (open source) library, locked to a
specific version (e.g., via `Gemfile.lock`) and the version is known to be
vulnerable.
- Your code has a potentially dangerous attribute in a class, or unsafe code
that can lead to unintended code execution.
## How it works
In order for the report to show in the merge request, you need to specify a
`sast` job (exact name) that will analyze the code and upload the resulting
`gl-sast-report.json` file as an artifact. GitLab will then check this file and
show the information inside the merge request.
This JSON file needs to be the only artifact file for the job. If you try
to also include other files, it will break the vulnerability display in the
merge request.
For more information on how the `sast` job should look like, check the
example on [analyzing a project's code for vulnerabilities][cc-docs].
[ee-3775]: https://gitlab.com/gitlab-org/gitlab-ee/issues/3775
[ee]: https://about.gitlab.com/gitlab-ee/
[ci]: ../../../ci/README.md
[cc-docs]: ../../../ci/examples/sast.md
...@@ -43,7 +43,9 @@ Annex to Git LFS. ...@@ -43,7 +43,9 @@ Annex to Git LFS.
If you know what you are doing and want to skip the reading, this is what you If you know what you are doing and want to skip the reading, this is what you
need to do (we assume you have [git-annex enabled][annex-gitlab-use] in your need to do (we assume you have [git-annex enabled][annex-gitlab-use] in your
repository). Fire up a terminal, navigate to your Git repository and: repository and that you have made backups in case something goes wrong).
Fire up a terminal, navigate to your Git repository and:
1. Disable `git-annex`: 1. Disable `git-annex`:
...@@ -57,6 +59,7 @@ repository). Fire up a terminal, navigate to your Git repository and: ...@@ -57,6 +59,7 @@ repository). Fire up a terminal, navigate to your Git repository and:
1. Enable `git-lfs`: 1. Enable `git-lfs`:
``` ```
git lfs install
git lfs track <files> git lfs track <files>
git add . git add .
git commit -m "commit message" git commit -m "commit message"
...@@ -81,9 +84,22 @@ deprecated in Git Annex version 6, so you may need to upgrade your repository ...@@ -81,9 +84,22 @@ deprecated in Git Annex version 6, so you may need to upgrade your repository
if the server also has Git Annex 6 installed. Read more in the if the server also has Git Annex 6 installed. Read more in the
[Git Annex troubleshooting tips][annex-tips] section. [Git Annex troubleshooting tips][annex-tips] section.
1. Backup your repository
```bash
cd repository
git annex sync --content
cd ..
git clone repository repository-backup
cd repository-backup
git annex get
cd ..
```
1. Use `annex direct`: 1. Use `annex direct`:
```bash ```bash
cd repository
git annex direct git annex direct
``` ```
...@@ -198,6 +214,17 @@ branches created by Git Annex: `git-annex`, and all under `synced/`. ...@@ -198,6 +214,17 @@ branches created by Git Annex: `git-annex`, and all under `synced/`.
![repository branches](images/git-annex-branches.png) ![repository branches](images/git-annex-branches.png)
You can also do this on the commandline with:
```bash
git branch -d synced/master
git branch -d synced/git-annex
git push origin :synced/master
git push origin :synced/git-annex
git push origin :git-annex
git remote prune origin
```
If there are still some Annex objects inside your repository (`.git/annex/`) If there are still some Annex objects inside your repository (`.git/annex/`)
or references inside `.git/config`, run `annex uninit` again: or references inside `.git/config`, run `annex uninit` again:
......
module EE module EE
module LfsRequest module LfsRequest
extend ActiveSupport::Concern extend ActiveSupport::Concern
include ::Gitlab::Utils::StrongMemoize
def lfs_forbidden! def lfs_forbidden!
if project.above_size_limit? || objects_exceed_repo_limit? if project.above_size_limit? || objects_exceed_repo_limit?
...@@ -13,7 +14,7 @@ module EE ...@@ -13,7 +14,7 @@ module EE
def render_size_error def render_size_error
render( render(
json: { json: {
message: ::Gitlab::RepositorySizeError.new(project).push_error(@exceeded_limit), message: ::Gitlab::RepositorySizeError.new(project).push_error(@exceeded_limit), # rubocop:disable Gitlab/ModuleWithInstanceVariables
documentation_url: help_url documentation_url: help_url
}, },
content_type: "application/vnd.git-lfs+json", content_type: "application/vnd.git-lfs+json",
...@@ -23,13 +24,14 @@ module EE ...@@ -23,13 +24,14 @@ module EE
def objects_exceed_repo_limit? def objects_exceed_repo_limit?
return false unless project.size_limit_enabled? return false unless project.size_limit_enabled?
return @limit_exceeded if defined?(@limit_exceeded)
lfs_push_size = objects.sum { |o| o[:size] } strong_memoize(:limit_exceeded) do
size_with_lfs_push = project.repository_and_lfs_size + lfs_push_size lfs_push_size = objects.sum { |o| o[:size] }
size_with_lfs_push = project.repository_and_lfs_size + lfs_push_size
@exceeded_limit = size_with_lfs_push - project.actual_size_limit @exceeded_limit = size_with_lfs_push - project.actual_size_limit # rubocop:disable Gitlab/ModuleWithInstanceVariables
@limit_exceeded = @exceeded_limit > 0 @exceeded_limit > 0 # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
end end
end end
end end
...@@ -14,6 +14,6 @@ module SafeMirrorParams ...@@ -14,6 +14,6 @@ module SafeMirrorParams
end end
def default_mirror_users def default_mirror_users
[current_user, @project.mirror_user].compact.uniq [current_user, project.mirror_user].compact.uniq
end end
end end
module EE::Admin::LogsController module EE::Admin::LogsController
include ::Gitlab::Utils::StrongMemoize
def loggers def loggers
raise NotImplementedError unless defined?(super) raise NotImplementedError unless defined?(super)
@loggers ||= super + [ strong_memoize(:loggers) do
Gitlab::GeoLogger super + [
] Gitlab::GeoLogger
]
end
end end
end end
...@@ -24,6 +24,7 @@ module EE ...@@ -24,6 +24,7 @@ module EE
end end
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def update def update
service = ::Boards::UpdateService.new(parent, current_user, board_params) service = ::Boards::UpdateService.new(parent, current_user, board_params)
...@@ -40,10 +41,11 @@ module EE ...@@ -40,10 +41,11 @@ module EE
end end
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def destroy def destroy
service = ::Boards::DestroyService.new(parent, current_user) service = ::Boards::DestroyService.new(parent, current_user)
service.execute(@board) service.execute(@board) # rubocop:disable Gitlab/ModuleWithInstanceVariables
respond_to do |format| respond_to do |format|
format.json { head :ok } format.json { head :ok }
...@@ -62,15 +64,15 @@ module EE ...@@ -62,15 +64,15 @@ module EE
end end
def find_board def find_board
@board = parent.boards.find(params[:id]) @board = parent.boards.find(params[:id]) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def parent def parent
@parent ||= @project || @group @parent ||= @project || @group # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def boards_path def boards_path
if @group if @group # rubocop:disable Gitlab/ModuleWithInstanceVariables
group_boards_path(parent) group_boards_path(parent)
else else
project_boards_path(parent) project_boards_path(parent)
...@@ -78,7 +80,7 @@ module EE ...@@ -78,7 +80,7 @@ module EE
end end
def board_path(board) def board_path(board)
if @group if @group # rubocop:disable Gitlab/ModuleWithInstanceVariables
group_board_path(parent, board) group_board_path(parent, board)
else else
project_board_path(parent, board) project_board_path(parent, board)
......
module EE module EE
module Groups module Groups
module GroupMembersController module GroupMembersController
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def override def override
@group_member = @group.group_members.find(params[:id]) @group_member = @group.group_members.find(params[:id])
...@@ -14,6 +15,7 @@ module EE ...@@ -14,6 +15,7 @@ module EE
end end
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
protected protected
......
...@@ -3,7 +3,7 @@ module EE ...@@ -3,7 +3,7 @@ module EE
protected protected
def fail_login def fail_login
log_failed_login(@user.username, oauth['provider']) log_failed_login(@user.username, oauth['provider']) # rubocop:disable Gitlab/ModuleWithInstanceVariables
super super
end end
......
...@@ -33,7 +33,7 @@ module EE ...@@ -33,7 +33,7 @@ module EE
payload = ::Gitlab::Geo::JwtRequestDecoder.new(request.headers['Authorization']).decode payload = ::Gitlab::Geo::JwtRequestDecoder.new(request.headers['Authorization']).decode
if payload if payload
@authentication_result = ::Gitlab::Auth::Result.new(nil, project, :geo, [:download_code]) @authentication_result = ::Gitlab::Auth::Result.new(nil, project, :geo, [:download_code]) # rubocop:disable Gitlab/ModuleWithInstanceVariables
return # grant access return # grant access
end end
......
...@@ -11,8 +11,8 @@ module EE ...@@ -11,8 +11,8 @@ module EE
end end
def service_desk def service_desk
@issues = @issuables @issues = @issuables # rubocop:disable Gitlab/ModuleWithInstanceVariables
@users.push(::User.support_bot) @users.push(::User.support_bot) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def export_csv def export_csv
......
...@@ -7,11 +7,11 @@ module EE ...@@ -7,11 +7,11 @@ module EE
private private
def set_suggested_approvers def set_suggested_approvers
if @merge_request.requires_approve? if merge_request.requires_approve?
@suggested_approvers = ::Gitlab::AuthorityAnalyzer.new( @suggested_approvers = ::Gitlab::AuthorityAnalyzer.new( # rubocop:disable Gitlab/ModuleWithInstanceVariables
@merge_request, merge_request,
@merge_request.author || current_user merge_request.author || current_user
).calculate(@merge_request.approvals_required) ).calculate(merge_request.approvals_required)
end end
end end
...@@ -38,12 +38,12 @@ module EE ...@@ -38,12 +38,12 @@ module EE
# Target the MR target project in priority, else it depends whether the project # Target the MR target project in priority, else it depends whether the project
# is forked. # is forked.
target_project = if @merge_request target_project = if @merge_request # rubocop:disable Gitlab/ModuleWithInstanceVariables
@merge_request.target_project @merge_request.target_project # rubocop:disable Gitlab/ModuleWithInstanceVariables
elsif @project.forked? && @project.id.to_s != mr_params[:target_project_id] elsif project.forked? && project.id.to_s != mr_params[:target_project_id]
@project.forked_from_project project.forked_from_project
else else
@project project
end end
if mr_params[:approvals_before_merge].to_i <= target_project.approvals_before_merge if mr_params[:approvals_before_merge].to_i <= target_project.approvals_before_merge
......
...@@ -9,19 +9,19 @@ module EE ...@@ -9,19 +9,19 @@ module EE
end end
def rebase def rebase
RebaseWorker.perform_async(@merge_request.id, current_user.id) RebaseWorker.perform_async(merge_request.id, current_user.id)
render nothing: true, status: 200 render nothing: true, status: 200
end end
def approve def approve
unless @merge_request.can_approve?(current_user) unless merge_request.can_approve?(current_user)
return render_404 return render_404
end end
::MergeRequests::ApprovalService ::MergeRequests::ApprovalService
.new(project, current_user) .new(project, current_user)
.execute(@merge_request) .execute(merge_request)
render_approvals_json render_approvals_json
end end
...@@ -31,10 +31,10 @@ module EE ...@@ -31,10 +31,10 @@ module EE
end end
def unapprove def unapprove
if @merge_request.has_approved?(current_user) if merge_request.has_approved?(current_user)
::MergeRequests::RemoveApprovalService ::MergeRequests::RemoveApprovalService
.new(project, current_user) .new(project, current_user)
.execute(@merge_request) .execute(merge_request)
end end
render_approvals_json render_approvals_json
...@@ -51,7 +51,7 @@ module EE ...@@ -51,7 +51,7 @@ module EE
def render_approvals_json def render_approvals_json
respond_to do |format| respond_to do |format|
format.json do format.json do
entity = ::API::Entities::MergeRequestApprovals.new(@merge_request, current_user: current_user) entity = ::API::Entities::MergeRequestApprovals.new(merge_request, current_user: current_user)
render json: entity render json: entity
end end
end end
...@@ -65,11 +65,11 @@ module EE ...@@ -65,11 +65,11 @@ module EE
end end
def check_user_can_push_to_source_branch! def check_user_can_push_to_source_branch!
return access_denied! unless @merge_request.source_branch_exists? return access_denied! unless merge_request.source_branch_exists?
access_check = ::Gitlab::UserAccess access_check = ::Gitlab::UserAccess
.new(current_user, project: @merge_request.source_project) .new(current_user, project: merge_request.source_project)
.can_push_to_branch?(@merge_request.source_branch) .can_push_to_branch?(merge_request.source_branch)
access_denied! unless access_check access_denied! unless access_check
end end
......
...@@ -15,15 +15,16 @@ module EE ...@@ -15,15 +15,16 @@ module EE
return unless project.feature_available?(:push_rules) return unless project.feature_available?(:push_rules)
project.create_push_rule unless project.push_rule project.create_push_rule unless project.push_rule
@push_rule = project.push_rule @push_rule = project.push_rule # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def remote_mirror def remote_mirror
return unless project.feature_available?(:repository_mirrors) return unless project.feature_available?(:repository_mirrors)
@remote_mirror = @project.remote_mirrors.first_or_initialize @remote_mirror = project.remote_mirrors.first_or_initialize # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def acces_levels_options def acces_levels_options
super.merge( super.merge(
selected_merge_access_levels: @protected_branch.merge_access_levels.map { |access_level| access_level.user_id || access_level.access_level }, selected_merge_access_levels: @protected_branch.merge_access_levels.map { |access_level| access_level.user_id || access_level.access_level },
...@@ -31,11 +32,12 @@ module EE ...@@ -31,11 +32,12 @@ module EE
selected_create_access_levels: @protected_tag.create_access_levels.map { |access_level| access_level.user_id || access_level.access_level } selected_create_access_levels: @protected_tag.create_access_levels.map { |access_level| access_level.user_id || access_level.access_level }
) )
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def load_gon_index def load_gon_index
super super
gon.push(current_project_id: @project.id) if @project gon.push(current_project_id: project.id) if project
end end
end end
end end
......
...@@ -26,7 +26,7 @@ module EE ...@@ -26,7 +26,7 @@ module EE
return unless ::Gitlab::Geo.secondary? return unless ::Gitlab::Geo.secondary?
oauth = ::Gitlab::Geo::OauthSession.new(access_token: session[:access_token]) oauth = ::Gitlab::Geo::OauthSession.new(access_token: session[:access_token])
@geo_logout_state = oauth.generate_logout_state @geo_logout_state = oauth.generate_logout_state # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def log_failed_login def log_failed_login
......
...@@ -10,7 +10,7 @@ module EE ...@@ -10,7 +10,7 @@ module EE
return unless entity_type && entity_id return unless entity_type && entity_id
# Avoiding exception if the record doesn't exist # Avoiding exception if the record doesn't exist
@entity ||= entity_type.constantize.find_by_id(entity_id) @entity ||= entity_type.constantize.find_by_id(entity_id) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
def present def present
......
...@@ -5,6 +5,7 @@ module EE ...@@ -5,6 +5,7 @@ module EE
# and be prepended in the `Namespace` model # and be prepended in the `Namespace` model
module Namespace module Namespace
extend ActiveSupport::Concern extend ActiveSupport::Concern
include ::Gitlab::Utils::StrongMemoize
FREE_PLAN = 'free'.freeze FREE_PLAN = 'free'.freeze
...@@ -67,19 +68,23 @@ module EE ...@@ -67,19 +68,23 @@ module EE
# for a given Namespace plan. This method should consider ancestor groups # for a given Namespace plan. This method should consider ancestor groups
# being licensed. # being licensed.
def feature_available?(feature) def feature_available?(feature)
@feature_available ||= Hash.new do |h, feature| available_features = strong_memoize(:feature_available) do
h[feature] = load_feature_available(feature) Hash.new do |h, feature|
h[feature] = load_feature_available(feature)
end
end end
@feature_available[feature] available_features[feature]
end end
def feature_available_in_plan?(feature) def feature_available_in_plan?(feature)
@features_available_in_plan ||= Hash.new do |h, feature| available_features = strong_memoize(:features_available_in_plan) do
h[feature] = (plans.map(&:name) & self.class.plans_with_feature(feature)).any? Hash.new do |h, feature|
h[feature] = (plans.map(&:name) & self.class.plans_with_feature(feature)).any?
end
end end
@features_available_in_plan[feature] available_features[feature]
end end
# The main difference between the "plan" column and this method is that "plan" # The main difference between the "plan" column and this method is that "plan"
...@@ -128,9 +133,9 @@ module EE ...@@ -128,9 +133,9 @@ module EE
# These helper methods are required to not break the Namespace API. # These helper methods are required to not break the Namespace API.
def plan=(plan_name) def plan=(plan_name)
if plan_name.is_a?(String) if plan_name.is_a?(String)
@plan_name = plan_name @plan_name = plan_name # rubocop:disable Gitlab/ModuleWithInstanceVariables
super(Plan.find_by(name: @plan_name)) super(Plan.find_by(name: @plan_name)) # rubocop:disable Gitlab/ModuleWithInstanceVariables
else else
super super
end end
...@@ -149,7 +154,7 @@ module EE ...@@ -149,7 +154,7 @@ module EE
private private
def validate_plan_name def validate_plan_name
if @plan_name.present? && PLANS.exclude?(@plan_name) if @plan_name.present? && PLANS.exclude?(@plan_name) # rubocop:disable Gitlab/ModuleWithInstanceVariables
errors.add(:plan, 'is not included in the list') errors.add(:plan, 'is not included in the list')
end end
end end
......
...@@ -5,6 +5,7 @@ module EE ...@@ -5,6 +5,7 @@ module EE
# and be prepended in the `Project` model # and be prepended in the `Project` model
module Project module Project
extend ActiveSupport::Concern extend ActiveSupport::Concern
include ::Gitlab::Utils::StrongMemoize
prepended do prepended do
include Elastic::ProjectsSearch include Elastic::ProjectsSearch
...@@ -258,8 +259,9 @@ module EE ...@@ -258,8 +259,9 @@ module EE
def deployment_platform(environment: nil) def deployment_platform(environment: nil)
return super unless environment && feature_available?(:multiple_clusters) return super unless environment && feature_available?(:multiple_clusters)
@deployment_platform ||= clusters.enabled.on_environment(environment.name) @deployment_platform ||= # rubocop:disable Gitlab/ModuleWithInstanceVariables
.last&.platform_kubernetes clusters.enabled.on_environment(environment.name)
.last&.platform_kubernetes
super # Wildcard or KubernetesService super # Wildcard or KubernetesService
end end
...@@ -323,8 +325,11 @@ module EE ...@@ -323,8 +325,11 @@ module EE
end end
def find_path_lock(path, exact_match: false, downstream: false) def find_path_lock(path, exact_match: false, downstream: false)
@path_lock_finder ||= ::Gitlab::PathLocksFinder.new(self) path_lock_finder = strong_memoize(:path_lock_finder) do
@path_lock_finder.find(path, exact_match: exact_match, downstream: downstream) ::Gitlab::PathLocksFinder.new(self)
end
path_lock_finder.find(path, exact_match: exact_match, downstream: downstream)
end end
def import_url_updated? def import_url_updated?
...@@ -450,15 +455,15 @@ module EE ...@@ -450,15 +455,15 @@ module EE
end end
def disabled_services def disabled_services
return @disabled_services if defined?(@disabled_services) strong_memoize(:disabled_services) do
disabled_services = []
@disabled_services = [] unless feature_available?(:jenkins_integration)
disabled_services.push('jenkins', 'jenkins_deprecated')
end
unless feature_available?(:jenkins_integration) disabled_services
@disabled_services.push('jenkins', 'jenkins_deprecated')
end end
@disabled_services
end end
def remote_mirror_available? def remote_mirror_available?
...@@ -479,11 +484,13 @@ module EE ...@@ -479,11 +484,13 @@ module EE
end end
def licensed_feature_available?(feature) def licensed_feature_available?(feature)
@licensed_feature_available ||= Hash.new do |h, feature| available_features = strong_memoize(:licensed_feature_available) do
h[feature] = load_licensed_feature_available(feature) Hash.new do |h, feature|
h[feature] = load_licensed_feature_available(feature)
end
end end
@licensed_feature_available[feature] available_features[feature]
end end
def load_licensed_feature_available(feature) def load_licensed_feature_available(feature)
......
module EE module EE
module Applications module Applications
# rubocop:disable Gitlab/ModuleWithInstanceVariables
module CreateService module CreateService
def execute(request) def execute(request)
super.tap do |application| super.tap do |application|
......
module EE module EE
module AuditEventService module AuditEventService
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def for_member(member) def for_member(member)
action = @details[:action] action = @details[:action]
old_access_level = @details[:old_access_level] old_access_level = @details[:old_access_level]
...@@ -199,5 +200,6 @@ module EE ...@@ -199,5 +200,6 @@ module EE
} }
end end
end end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end end
end end
...@@ -3,8 +3,8 @@ module EE ...@@ -3,8 +3,8 @@ module EE
module Issues module Issues
module ListService module ListService
def set_parent def set_parent
if @parent.is_a?(Group) if parent.is_a?(Group)
params[:group_id] = @parent.id params[:group_id] = parent.id
else else
super super
end end
......
...@@ -12,11 +12,11 @@ module EE ...@@ -12,11 +12,11 @@ module EE
end end
def audit_event_service def audit_event_service
::AuditEventService.new(@user, ::AuditEventService.new(user,
@user, user,
action: :custom, action: :custom,
custom_message: 'Added SSH key', custom_message: 'Added SSH key',
ip_address: @ip_address) ip_address: @ip_address) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end end
end end
end end
......
...@@ -25,8 +25,8 @@ module EE ...@@ -25,8 +25,8 @@ module EE
private private
def check_size_limit def check_size_limit
if @merge_request.target_project.above_size_limit? if merge_request.target_project.above_size_limit?
message = ::Gitlab::RepositorySizeError.new(@merge_request.target_project).merge_error message = ::Gitlab::RepositorySizeError.new(merge_request.target_project).merge_error
raise ::MergeRequests::MergeService::MergeError, message raise ::MergeRequests::MergeService::MergeError, message
end end
......
...@@ -41,7 +41,7 @@ module EE ...@@ -41,7 +41,7 @@ module EE
create_predefined_push_rule create_predefined_push_rule
@project.group&.refresh_members_authorized_projects project.group&.refresh_members_authorized_projects
end end
def create_predefined_push_rule def create_predefined_push_rule
......
...@@ -8,12 +8,12 @@ module EE ...@@ -8,12 +8,12 @@ module EE
super super
EE::Audit::ProjectChangesAuditor.new(@current_user, project).execute EE::Audit::ProjectChangesAuditor.new(current_user, project).execute
::Geo::RepositoryRenamedEventStore.new( ::Geo::RepositoryRenamedEventStore.new(
project, project,
old_path: project.path, old_path: project.path,
old_path_with_namespace: @old_path old_path_with_namespace: @old_path # rubocop:disable Gitlab/ModuleWithInstanceVariables
).create ).create
end end
end end
......
...@@ -26,15 +26,15 @@ module EE ...@@ -26,15 +26,15 @@ module EE
end end
def groups_accessible? def groups_accessible?
group_ids = @merge_params.group_ids + @push_params.group_ids group_ids = @merge_params.group_ids + @push_params.group_ids # rubocop:disable Gitlab/ModuleWithInstanceVariables
allowed_groups = @project.invited_groups.where(id: group_ids) allowed_groups = @project.invited_groups.where(id: group_ids) # rubocop:disable Gitlab/ModuleWithInstanceVariables
group_ids.count == allowed_groups.count group_ids.count == allowed_groups.count
end end
def users_accessible? def users_accessible?
user_ids = @merge_params.user_ids + @push_params.user_ids user_ids = @merge_params.user_ids + @push_params.user_ids # rubocop:disable Gitlab/ModuleWithInstanceVariables
allowed_users = @project.team.users.where(id: user_ids) allowed_users = @project.team.users.where(id: user_ids) # rubocop:disable Gitlab/ModuleWithInstanceVariables
user_ids.count == allowed_users.count user_ids.count == allowed_users.count
end end
......
...@@ -3,7 +3,7 @@ module EE ...@@ -3,7 +3,7 @@ module EE
def execute(blocking: true) def execute(blocking: true)
result = super result = super
@user_ids.each do |id| @user_ids.each do |id| # rubocop:disable Gitlab/ModuleWithInstanceVariables
::Gitlab::Database::LoadBalancing::Sticking.stick(:user, id) ::Gitlab::Database::LoadBalancing::Sticking.stick(:user, id)
end end
......
...@@ -6,7 +6,7 @@ module EE ...@@ -6,7 +6,7 @@ module EE
private private
def notify_success(user_exists) def notify_success(user_exists)
notify_new_user(@user, nil) unless user_exists notify_new_user(@user, nil) unless user_exists # rubocop:disable Gitlab/ModuleWithInstanceVariables
audit_changes(:email, as: 'email address') audit_changes(:email, as: 'email address')
audit_changes(:encrypted_password, as: 'password', skip_changes: true) audit_changes(:encrypted_password, as: 'password', skip_changes: true)
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
= expanded ? 'Collapse' : 'Expand' = expanded ? 'Collapse' : 'Expand'
%p %p
Customize your service desk settings. Customize your service desk settings.
= link_to "Learn more about service desk.", help_page_path('user/project/service_desk') = link_to "Learn more about service desk.", help_page_path('user/project/service_desk'), target: '_blank'
.settings-content .settings-content
- if EE::Gitlab::ServiceDesk.enabled?(project: @project) - if EE::Gitlab::ServiceDesk.enabled?(project: @project)
.js-service-desk-setting-root{ data: { endpoint: project_service_desk_path(@project), .js-service-desk-setting-root{ data: { endpoint: project_service_desk_path(@project),
......
...@@ -2,14 +2,14 @@ module EE ...@@ -2,14 +2,14 @@ module EE
module API module API
module Helpers module Helpers
def current_user def current_user
return @current_user if defined?(@current_user) strong_memoize(:current_user) do
user = super
user = super ::Gitlab::Database::LoadBalancing::RackMiddleware
.stick_or_unstick(env, :user, user.id) if user
::Gitlab::Database::LoadBalancing::RackMiddleware user
.stick_or_unstick(env, :user, user.id) if user end
user
end end
def check_project_feature_available!(feature) def check_project_feature_available!(feature)
......
...@@ -6,7 +6,7 @@ module EE ...@@ -6,7 +6,7 @@ module EE
private private
def project_or_wiki def project_or_wiki
@project.wiki project.wiki
end end
end end
end end
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment