diff options
author | Kushal Pandya <kushalspandya@gmail.com> | 2017-04-06 09:46:50 +0000 |
---|---|---|
committer | Kushal Pandya <kushalspandya@gmail.com> | 2017-04-06 09:46:50 +0000 |
commit | 18506d4b8b8bc780b3b1e4c61339af38b5c49bb2 (patch) | |
tree | 6aab0c83abe14064433c326996ccbe8097495454 /lib | |
parent | cd5b36d04e79ed8fcd649127e0d47e09ec325242 (diff) | |
parent | 49bdd8d63b577f079cdc47f7dd10ba83c677771a (diff) | |
download | gitlab-ce-18506d4b8b8bc780b3b1e4c61339af38b5c49bb2.tar.gz |
Merge branch 'master' into '18471-restrict-tag-pushes-protected-tags'
# Conflicts:
# app/assets/javascripts/dispatcher.js
# app/assets/stylesheets/pages/projects.scss
Diffstat (limited to 'lib')
77 files changed, 1041 insertions, 359 deletions
diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb index 409cb5b924f..9fcf04efa38 100644 --- a/lib/api/api_guard.rb +++ b/lib/api/api_guard.rb @@ -121,7 +121,7 @@ module API end def oauth2_bearer_token_error_handler - Proc.new do |e| + proc do |e| response = case e when MissingTokenError diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 0cc6188938d..45625e00f7d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -200,7 +200,7 @@ module API expose :id, :name, :type, :path expose :mode do |obj, options| - filemode = obj.mode.to_s(8) + filemode = obj.mode filemode = "0" + filemode if filemode.length < 6 filemode end @@ -577,6 +577,7 @@ module API expose :plantuml_enabled expose :plantuml_url expose :terminal_max_session_time + expose :polling_interval_multiplier end class Release < Grape::Entity diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index bd22b82476b..61527c1e20b 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -90,6 +90,11 @@ module API MergeRequestsFinder.new(current_user, project_id: user_project.id).find_by!(iid: iid) end + def find_project_snippet(id) + finder_params = { filter: :by_project, project: user_project } + SnippetsFinder.new.execute(current_user, finder_params).find(id) + end + def find_merge_request_with_access(iid, access_level = :read_merge_request) merge_request = user_project.merge_requests.find_by!(iid: iid) authorize! access_level, merge_request diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb index 74848a6e144..1369b021ea4 100644 --- a/lib/api/helpers/runner.rb +++ b/lib/api/helpers/runner.rb @@ -50,10 +50,14 @@ module API forbidden!('Job has been erased!') if job.erased? end - def authenticate_job!(job) + def authenticate_job! + job = Ci::Build.find_by_id(params[:id]) + validate_job!(job) do forbidden! unless job_token_valid?(job) end + + job end def job_token_valid?(job) diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 7eed93aba00..56c597dffcb 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -138,8 +138,11 @@ module API return unless Gitlab::GitalyClient.enabled? + relative_path = Gitlab::RepoPath.strip_storage_path(params[:repo_path]) + project = Project.find_by_full_path(relative_path.sub(/\.(git|wiki)\z/, '')) + begin - Gitlab::GitalyClient::Notifications.new.post_receive(params[:repo_path]) + Gitlab::GitalyClient::Notifications.new(project.repository_storage, relative_path).post_receive rescue GRPC::Unavailable => e render_api_error(e, 500) end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index b3183357625..09053e615cb 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -26,6 +26,7 @@ module API desc: 'Return issues sorted in `asc` or `desc` order.' optional :milestone, type: String, desc: 'Return issues for a specific milestone' optional :iids, type: Array[Integer], desc: 'The IID array of issues' + optional :search, type: String, desc: 'Search issues for text present in the title or description' use :pagination end @@ -63,14 +64,14 @@ module API success Entities::IssueBasic end params do - optional :state, type: String, values: %w[opened closed all], default: 'opened', + optional :state, type: String, values: %w[opened closed all], default: 'all', desc: 'Return opened, closed, or all issues' use :issues_params end get ":id/issues" do group = find_group!(params[:id]) - issues = find_issues(group_id: group.id, state: params[:state] || 'opened') + issues = find_issues(group_id: group.id) present paginate(issues), with: Entities::IssueBasic, current_user: current_user end @@ -91,7 +92,7 @@ module API use :issues_params end get ":id/issues" do - project = find_project(params[:id]) + project = find_project!(params[:id]) issues = find_issues(project_id: project.id) diff --git a/lib/api/labels.rb b/lib/api/labels.rb index d9a3cb7bb6b..20b25529d0c 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -23,7 +23,7 @@ module API end params do requires :name, type: String, desc: 'The name of the label to be created' - requires :color, type: String, desc: "The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB)" + requires :color, type: String, desc: "The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names" optional :description, type: String, desc: 'The description of label to be created' optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true end @@ -34,7 +34,7 @@ module API conflict!('Label already exists') if label priority = params.delete(:priority) - label = user_project.labels.create(declared_params(include_missing: false)) + label = ::Labels::CreateService.new(declared_params(include_missing: false)).execute(project: user_project) if label.valid? label.prioritize!(user_project, priority) if priority @@ -65,7 +65,7 @@ module API params do requires :name, type: String, desc: 'The name of the label to be updated' optional :new_name, type: String, desc: 'The new name of the label' - optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB)" + optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names" optional :description, type: String, desc: 'The new description of label' optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true at_least_one_of :new_name, :color, :description, :priority @@ -82,7 +82,8 @@ module API # Rename new name to the actual label attribute name label_params[:name] = label_params.delete(:new_name) if label_params.key?(:new_name) - render_validation_error!(label) unless label.update(label_params) + label = ::Labels::UpdateService.new(label_params).execute(label) + render_validation_error!(label) unless label.valid? if update_priority if priority.nil? diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 5cc807d5bff..c8033664133 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -226,41 +226,6 @@ module API .cancel(merge_request) end - desc 'Get the comments of a merge request' do - success Entities::MRNote - end - params do - use :pagination - end - get ':id/merge_requests/:merge_request_iid/comments' do - merge_request = find_merge_request_with_access(params[:merge_request_iid]) - present paginate(merge_request.notes.fresh), with: Entities::MRNote - end - - desc 'Post a comment to a merge request' do - success Entities::MRNote - end - params do - requires :note, type: String, desc: 'The text of the comment' - end - post ':id/merge_requests/:merge_request_iid/comments' do - merge_request = find_merge_request_with_access(params[:merge_request_iid], :create_note) - - opts = { - note: params[:note], - noteable_type: 'MergeRequest', - noteable_id: merge_request.id - } - - note = ::Notes::CreateService.new(user_project, current_user, opts).execute - - if note.save - present note, with: Entities::MRNote - else - render_api_error!("Failed to save note #{note.errors.messages}", 400) - end - end - desc 'List issues that will be closed on merge' do success Entities::MRNote end diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index e7ab82f08db..a3ea619a2fb 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -139,7 +139,7 @@ module API finder_params = { project_id: user_project.id, - milestone_id: milestone.id, + milestone_title: milestone.title, sort: 'position_asc' } diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 29ceffdbd2d..de39e579ac3 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -21,7 +21,7 @@ module API use :pagination end get ":id/#{noteables_str}/:noteable_id/notes" do - noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id]) + noteable = find_project_noteable(noteables_str, params[:noteable_id]) if can?(current_user, noteable_read_ability_name(noteable), noteable) # We exclude notes that are cross-references and that cannot be viewed @@ -49,7 +49,7 @@ module API requires :noteable_id, type: Integer, desc: 'The ID of the noteable' end get ":id/#{noteables_str}/:noteable_id/notes/:note_id" do - noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id]) + noteable = find_project_noteable(noteables_str, params[:noteable_id]) note = noteable.notes.find(params[:note_id]) can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user) @@ -69,14 +69,14 @@ module API optional :created_at, type: String, desc: 'The creation date of the note' end post ":id/#{noteables_str}/:noteable_id/notes" do + noteable = find_project_noteable(noteables_str, params[:noteable_id]) + opts = { note: params[:body], noteable_type: noteables_str.classify, - noteable_id: params[:noteable_id] + noteable_id: noteable.id } - noteable = user_project.send(noteables_str.to_sym).find(params[:noteable_id]) - if can?(current_user, noteable_read_ability_name(noteable), noteable) if params[:created_at] && (current_user.is_admin? || user_project.owner == current_user) opts[:created_at] = params[:created_at] @@ -137,6 +137,10 @@ module API end helpers do + def find_project_noteable(noteables_str, noteable_id) + public_send("find_project_#{noteables_str.singularize}", noteable_id) + end + def noteable_read_ability_name(noteable) "read_#{noteable.class.to_s.underscore}".to_sym end diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 4c9db2c8716..d288369e362 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -113,8 +113,7 @@ module API optional :state, type: String, desc: %q(Job's status: success, failed) end put '/:id' do - job = Ci::Build.find_by_id(params[:id]) - authenticate_job!(job) + job = authenticate_job! job.update_attributes(trace: params[:trace]) if params[:trace] @@ -140,8 +139,7 @@ module API optional :token, type: String, desc: %q(Job's authentication token) end patch '/:id/trace' do - job = Ci::Build.find_by_id(params[:id]) - authenticate_job!(job) + job = authenticate_job! error!('400 Missing header Content-Range', 400) unless request.headers.has_key?('Content-Range') content_range = request.headers['Content-Range'] @@ -175,8 +173,7 @@ module API require_gitlab_workhorse! Gitlab::Workhorse.verify_api_request!(headers) - job = Ci::Build.find_by_id(params[:id]) - authenticate_job!(job) + job = authenticate_job! forbidden!('Job is not running') unless job.running? if params[:filesize] @@ -212,8 +209,7 @@ module API not_allowed! unless Gitlab.config.artifacts.enabled require_gitlab_workhorse! - job = Ci::Build.find_by_id(params[:id]) - authenticate_job!(job) + job = authenticate_job! forbidden!('Job is not running!') unless job.running? artifacts_upload_path = ArtifactUploader.artifacts_upload_path @@ -245,8 +241,7 @@ module API optional :token, type: String, desc: %q(Job's authentication token) end get '/:id/artifacts' do - job = Ci::Build.find_by_id(params[:id]) - authenticate_job!(job) + job = authenticate_job! artifacts_file = job.artifacts_file unless artifacts_file.file_storage? diff --git a/lib/api/services.rb b/lib/api/services.rb index 4e0c9cb1f63..6802a99311e 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -562,8 +562,14 @@ module API desc: 'URL to the mock service' } ] + services['mock-deployment'] = [] + services['mock-monitoring'] = [] - service_classes << MockCiService + service_classes += [ + MockCiService, + MockDeploymentService, + MockMonitoringService, + ] end trigger_services = { diff --git a/lib/api/settings.rb b/lib/api/settings.rb index d4d3229f0d1..c7f97ad2aab 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -110,6 +110,7 @@ module API requires :housekeeping_gc_period, type: Integer, desc: "Number of Git pushes after which 'git gc' is run." end optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.' + optional :polling_interval_multiplier, type: BigDecimal, desc: 'Interval multiplier used by endpoints that perform polling. Set to 0 to disable polling.' at_least_one_of :default_branch_protection, :default_project_visibility, :default_snippet_visibility, :default_group_visibility, :restricted_visibility_levels, :import_sources, :enabled_git_access_protocol, :gravatar_enabled, :default_projects_limit, @@ -125,7 +126,7 @@ module API :akismet_enabled, :admin_notification_email, :sentry_enabled, :repository_storage, :repository_checks_enabled, :koding_enabled, :plantuml_enabled, :version_check_enabled, :email_author_in_body, :html_emails_enabled, - :housekeeping_enabled, :terminal_max_session_time + :housekeeping_enabled, :terminal_max_session_time, :polling_interval_multiplier end put "application/settings" do attrs = declared_params(include_missing: false) diff --git a/lib/api/users.rb b/lib/api/users.rb index 2d4d5a25221..530ca0b5235 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -27,7 +27,7 @@ module API optional :location, type: String, desc: 'The location of the user' optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator' optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups' - optional :confirm, type: Boolean, desc: 'Flag indicating the account needs to be confirmed' + optional :skip_confirmation, type: Boolean, default: false, desc: 'Flag indicating the account is confirmed' optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' all_or_none_of :extern_uid, :provider end @@ -97,29 +97,10 @@ module API post do authenticated_as_admin! - # Filter out params which are used later - user_params = declared_params(include_missing: false) - identity_attrs = user_params.slice(:provider, :extern_uid) - confirm = user_params.delete(:confirm) - user = User.new(user_params.except(:extern_uid, :provider, :reset_password)) - - if user_params.delete(:reset_password) - user.attributes = { - force_random_password: true, - password_expires_at: nil, - created_by_id: current_user.id - } - user.generate_password - user.generate_reset_token - end - - user.skip_confirmation! unless confirm - - if identity_attrs.any? - user.identities.build(identity_attrs) - end + params = declared_params(include_missing: false) + user = ::Users::CreateService.new(current_user, params).execute - if user.save + if user.persisted? present user, with: Entities::UserPublic else conflict!('Email has already been taken') if User. @@ -312,7 +293,7 @@ module API user = User.find_by(id: params[:id]) not_found!('User') unless user - ::Users::DestroyService.new(current_user).execute(user) + DeleteUserWorker.perform_async(current_user.id, user.id) end desc 'Block a user. Available only for admins.' diff --git a/lib/api/v3/issues.rb b/lib/api/v3/issues.rb index cead03b1e6b..715083fc4f8 100644 --- a/lib/api/v3/issues.rb +++ b/lib/api/v3/issues.rb @@ -73,14 +73,14 @@ module API success ::API::Entities::Issue end params do - optional :state, type: String, values: %w[opened closed all], default: 'opened', + optional :state, type: String, values: %w[opened closed all], default: 'all', desc: 'Return opened, closed, or all issues' use :issues_params end get ":id/issues" do group = find_group!(params[:id]) - issues = find_issues(group_id: group.id, state: params[:state] || 'opened', match_all_labels: true) + issues = find_issues(group_id: group.id, match_all_labels: true) present paginate(issues), with: ::API::Entities::Issue, current_user: current_user end @@ -103,7 +103,7 @@ module API use :issues_params end get ":id/issues" do - project = find_project(params[:id]) + project = find_project!(params[:id]) issues = find_issues(project_id: project.id) diff --git a/lib/api/v3/users.rb b/lib/api/v3/users.rb index 14f54731730..5e18cecc431 100644 --- a/lib/api/v3/users.rb +++ b/lib/api/v3/users.rb @@ -9,6 +9,59 @@ module API end resource :users, requirements: { uid: /[0-9]*/, id: /[0-9]*/ } do + helpers do + params :optional_attributes do + optional :skype, type: String, desc: 'The Skype username' + optional :linkedin, type: String, desc: 'The LinkedIn username' + optional :twitter, type: String, desc: 'The Twitter username' + optional :website_url, type: String, desc: 'The website of the user' + optional :organization, type: String, desc: 'The organization of the user' + optional :projects_limit, type: Integer, desc: 'The number of projects a user can create' + optional :extern_uid, type: String, desc: 'The external authentication provider UID' + optional :provider, type: String, desc: 'The external provider' + optional :bio, type: String, desc: 'The biography of the user' + optional :location, type: String, desc: 'The location of the user' + optional :admin, type: Boolean, desc: 'Flag indicating the user is an administrator' + optional :can_create_group, type: Boolean, desc: 'Flag indicating the user can create groups' + optional :confirm, type: Boolean, default: true, desc: 'Flag indicating the account needs to be confirmed' + optional :external, type: Boolean, desc: 'Flag indicating the user is an external user' + all_or_none_of :extern_uid, :provider + end + end + + desc 'Create a user. Available only for admins.' do + success ::API::Entities::UserPublic + end + params do + requires :email, type: String, desc: 'The email of the user' + optional :password, type: String, desc: 'The password of the new user' + optional :reset_password, type: Boolean, desc: 'Flag indicating the user will be sent a password reset token' + at_least_one_of :password, :reset_password + requires :name, type: String, desc: 'The name of the user' + requires :username, type: String, desc: 'The username of the user' + use :optional_attributes + end + post do + authenticated_as_admin! + + params = declared_params(include_missing: false) + user = ::Users::CreateService.new(current_user, params.merge!(skip_confirmation: !params[:confirm])).execute + + if user.persisted? + present user, with: ::API::Entities::UserPublic + else + conflict!('Email has already been taken') if User. + where(email: user.email). + count > 0 + + conflict!('Username has already been taken') if User. + where(username: user.username). + count > 0 + + render_validation_error!(user) + end + end + desc 'Get the SSH keys of a specified user. Available only for admins.' do success ::API::Entities::SSHKey end diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index cd745d35e7c..6b29600a751 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -182,7 +182,9 @@ module Backup dir_entries = Dir.entries(path) - yield('custom_hooks') if dir_entries.include?('custom_hooks') + if dir_entries.include?('custom_hooks') || dir_entries.include?('custom_hooks.tar') + yield('custom_hooks') + end end def prepare diff --git a/lib/banzai/filter/markdown_filter.rb b/lib/banzai/filter/markdown_filter.rb index ff580ec68f8..ee73fa91589 100644 --- a/lib/banzai/filter/markdown_filter.rb +++ b/lib/banzai/filter/markdown_filter.rb @@ -14,7 +14,7 @@ module Banzai def self.renderer @renderer ||= begin - renderer = Redcarpet::Render::HTML.new + renderer = Banzai::Renderer::HTML.new Redcarpet::Markdown.new(renderer, redcarpet_options) end end diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb index ac5216d9cfb..3888acf935e 100644 --- a/lib/banzai/filter/merge_request_reference_filter.rb +++ b/lib/banzai/filter/merge_request_reference_filter.rb @@ -11,8 +11,8 @@ module Banzai MergeRequest end - def find_object(project, id) - project.merge_requests.find_by(iid: id) + def find_object(project, iid) + merge_requests_per_project[project][iid] end def url_for_object(mr, project) @@ -21,6 +21,31 @@ module Banzai only_path: context[:only_path]) end + def project_from_ref(ref) + projects_per_reference[ref || current_project_path] + end + + # Returns a Hash containing the merge_requests per Project instance. + def merge_requests_per_project + @merge_requests_per_project ||= begin + hash = Hash.new { |h, k| h[k] = {} } + + projects_per_reference.each do |path, project| + merge_request_ids = references_per_project[path] + + merge_requests = project.merge_requests + .where(iid: merge_request_ids.to_a) + .includes(target_project: :namespace) + + merge_requests.each do |merge_request| + hash[project][merge_request.iid.to_i] = merge_request + end + end + + hash + end + end + def object_link_text_extras(object, matches) extras = super diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index d5f9e252f62..522217deae4 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -24,10 +24,6 @@ module Banzai # Only push these customizations once return if customized?(whitelist[:transformers]) - # Allow code highlighting - whitelist[:attributes]['pre'] = %w(class v-pre) - whitelist[:attributes]['span'] = %w(class) - # Allow table alignment whitelist[:attributes]['th'] = %w(style) whitelist[:attributes]['td'] = %w(style) @@ -52,9 +48,6 @@ module Banzai # Remove `rel` attribute from `a` elements whitelist[:transformers].push(self.class.remove_rel) - # Remove `class` attribute from non-highlight spans - whitelist[:transformers].push(self.class.clean_spans) - whitelist end @@ -84,21 +77,6 @@ module Banzai end end end - - def clean_spans - lambda do |env| - node = env[:node] - - return unless node.name == 'span' - return unless node.has_attribute?('class') - - unless node.ancestors.any? { |n| n.name.casecmp('pre').zero? } - node.remove_attribute('class') - end - - { node_whitelist: [node] } - end - end end end end diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index 9f09ca90697..7da565043d1 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -14,7 +14,7 @@ module Banzai end def highlight_node(node) - language = node.attr('class') + language = node.attr('lang') code = node.text css_classes = "code highlight" lexer = lexer_for(language) diff --git a/lib/banzai/filter/user_reference_filter.rb b/lib/banzai/filter/user_reference_filter.rb index fe1f0923136..a798927823f 100644 --- a/lib/banzai/filter/user_reference_filter.rb +++ b/lib/banzai/filter/user_reference_filter.rb @@ -60,7 +60,7 @@ module Banzai self.class.references_in(text) do |match, username| if username == 'all' && !skip_project_check? link_to_all(link_content: link_content) - elsif namespace = namespaces[username] + elsif namespace = namespaces[username.downcase] link_to_namespace(namespace, link_content: link_content) || match else match @@ -74,7 +74,7 @@ module Banzai # The keys of this Hash are the namespace paths, the values the # corresponding Namespace objects. def namespaces - @namespaces ||= Namespace.where_full_path_in(usernames).index_by(&:full_path) + @namespaces ||= Namespace.where_full_path_in(usernames).index_by(&:full_path).transform_keys(&:downcase) end # Returns all usernames referenced in the current document. diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb index fd4a6a107c2..bd4d1aa9ff8 100644 --- a/lib/banzai/pipeline/gfm_pipeline.rb +++ b/lib/banzai/pipeline/gfm_pipeline.rb @@ -9,9 +9,9 @@ module Banzai # The GFM-to-HTML-to-GFM cycle is tested in spec/features/copy_as_gfm_spec.rb. def self.filters @filters ||= FilterArray[ - Filter::SyntaxHighlightFilter, Filter::PlantumlFilter, Filter::SanitizationFilter, + Filter::SyntaxHighlightFilter, Filter::MathFilter, Filter::UploadLinkFilter, diff --git a/lib/banzai/renderer/html.rb b/lib/banzai/renderer/html.rb new file mode 100644 index 00000000000..252caa35947 --- /dev/null +++ b/lib/banzai/renderer/html.rb @@ -0,0 +1,13 @@ +module Banzai + module Renderer + class HTML < Redcarpet::Render::HTML + def block_code(code, lang) + lang_attr = lang ? %Q{ lang="#{lang}"} : '' + + "\n<pre>" \ + "<code#{lang_attr}>#{html_escape(code)}</code>" \ + "</pre>" + end + end + end +end diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 746e76a1b1f..95cc6308c3b 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -86,8 +86,7 @@ module Ci # Example Request: # PATCH /builds/:id/trace.txt patch ":id/trace.txt" do - build = Ci::Build.find_by_id(params[:id]) - authenticate_build!(build) + build = authenticate_build! error!('400 Missing header Content-Range', 400) unless request.headers.has_key?('Content-Range') content_range = request.headers['Content-Range'] @@ -117,8 +116,7 @@ module Ci require_gitlab_workhorse! Gitlab::Workhorse.verify_api_request!(headers) not_allowed! unless Gitlab.config.artifacts.enabled - build = Ci::Build.find_by_id(params[:id]) - authenticate_build!(build) + build = authenticate_build! forbidden!('build is not running') unless build.running? if params[:filesize] @@ -154,8 +152,7 @@ module Ci post ":id/artifacts" do require_gitlab_workhorse! not_allowed! unless Gitlab.config.artifacts.enabled - build = Ci::Build.find_by_id(params[:id]) - authenticate_build!(build) + build = authenticate_build! forbidden!('Build is not running!') unless build.running? artifacts_upload_path = ArtifactUploader.artifacts_upload_path @@ -189,8 +186,7 @@ module Ci # Example Request: # GET /builds/:id/artifacts get ":id/artifacts" do - build = Ci::Build.find_by_id(params[:id]) - authenticate_build!(build) + build = authenticate_build! artifacts_file = build.artifacts_file unless artifacts_file.file_storage? @@ -214,8 +210,7 @@ module Ci # Example Request: # DELETE /builds/:id/artifacts delete ":id/artifacts" do - build = Ci::Build.find_by_id(params[:id]) - authenticate_build!(build) + build = authenticate_build! status(200) build.erase_artifacts! diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index 996990b464f..5109dc9670f 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -13,10 +13,14 @@ module Ci forbidden! unless current_runner end - def authenticate_build!(build) + def authenticate_build! + build = Ci::Build.find_by_id(params[:id]) + validate_build!(build) do forbidden! unless build_token_valid?(build) end + + build end def validate_build!(build) diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index 44323b47dca..f4efa20374a 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -130,8 +130,13 @@ module Gitlab end def create_labels - LABELS.each do |label| - @labels[label[:title]] = project.labels.create!(label) + LABELS.each do |label_params| + label = ::Labels::CreateService.new(label_params).execute(project: project) + if label.valid? + @labels[label_params[:title]] = label + else + raise "Failed to create label \"#{label_params[:title]}\" for project \"#{project.name_with_namespace}\"" + end end end diff --git a/lib/gitlab/ci/build/step.rb b/lib/gitlab/ci/build/step.rb index 1877429ac46..ee034d9cc56 100644 --- a/lib/gitlab/ci/build/step.rb +++ b/lib/gitlab/ci/build/step.rb @@ -7,13 +7,12 @@ module Gitlab WHEN_ALWAYS = 'always'.freeze attr_reader :name - attr_writer :script - attr_accessor :timeout, :when, :allow_failure + attr_accessor :script, :timeout, :when, :allow_failure class << self def from_commands(job) self.new(:script).tap do |step| - step.script = job.commands + step.script = job.commands.split("\n") step.timeout = job.timeout step.when = WHEN_ON_SUCCESS end @@ -36,10 +35,6 @@ module Gitlab @name = name @allow_failure = false end - - def script - @script.split("\n") - end end end end diff --git a/lib/gitlab/ci/status/canceled.rb b/lib/gitlab/ci/status/canceled.rb index dd6d99e9075..97c121ce7b9 100644 --- a/lib/gitlab/ci/status/canceled.rb +++ b/lib/gitlab/ci/status/canceled.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_canceled' end + + def favicon + 'favicon_status_canceled' + end end end end diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index 3dd2b9e01f6..d4fd83b93f8 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -18,6 +18,10 @@ module Gitlab raise NotImplementedError end + def favicon + raise NotImplementedError + end + def label raise NotImplementedError end diff --git a/lib/gitlab/ci/status/created.rb b/lib/gitlab/ci/status/created.rb index 6596d7e01ca..0721bf6ec7c 100644 --- a/lib/gitlab/ci/status/created.rb +++ b/lib/gitlab/ci/status/created.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_created' end + + def favicon + 'favicon_status_created' + end end end end diff --git a/lib/gitlab/ci/status/failed.rb b/lib/gitlab/ci/status/failed.rb index c5b5e3203ad..cb75e9383a8 100644 --- a/lib/gitlab/ci/status/failed.rb +++ b/lib/gitlab/ci/status/failed.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_failed' end + + def favicon + 'favicon_status_failed' + end end end end diff --git a/lib/gitlab/ci/status/manual.rb b/lib/gitlab/ci/status/manual.rb index 5f28521901d..f8f6c2903ba 100644 --- a/lib/gitlab/ci/status/manual.rb +++ b/lib/gitlab/ci/status/manual.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_manual' end + + def favicon + 'favicon_status_manual' + end end end end diff --git a/lib/gitlab/ci/status/pending.rb b/lib/gitlab/ci/status/pending.rb index d30f35a59a2..f40cc1314dc 100644 --- a/lib/gitlab/ci/status/pending.rb +++ b/lib/gitlab/ci/status/pending.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_pending' end + + def favicon + 'favicon_status_pending' + end end end end diff --git a/lib/gitlab/ci/status/running.rb b/lib/gitlab/ci/status/running.rb index 2aba3c373c7..1237cd47dc8 100644 --- a/lib/gitlab/ci/status/running.rb +++ b/lib/gitlab/ci/status/running.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_running' end + + def favicon + 'favicon_status_running' + end end end end diff --git a/lib/gitlab/ci/status/skipped.rb b/lib/gitlab/ci/status/skipped.rb index 16282aefd03..28005d91503 100644 --- a/lib/gitlab/ci/status/skipped.rb +++ b/lib/gitlab/ci/status/skipped.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_skipped' end + + def favicon + 'favicon_status_skipped' + end end end end diff --git a/lib/gitlab/ci/status/success.rb b/lib/gitlab/ci/status/success.rb index c09c5f006e3..88f7758a270 100644 --- a/lib/gitlab/ci/status/success.rb +++ b/lib/gitlab/ci/status/success.rb @@ -13,6 +13,10 @@ module Gitlab def icon 'icon_status_success' end + + def favicon + 'favicon_status_success' + end end end end diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb index e0fdf3f3d64..496ee0bdcb0 100644 --- a/lib/gitlab/ee_compat_check.rb +++ b/lib/gitlab/ee_compat_check.rb @@ -5,35 +5,44 @@ module Gitlab CE_REPO = 'https://gitlab.com/gitlab-org/gitlab-ce.git'.freeze EE_REPO = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze CHECK_DIR = Rails.root.join('ee_compat_check') - MAX_FETCH_DEPTH = 500 IGNORED_FILES_REGEX = /(VERSION|CHANGELOG\.md:\d+)/.freeze - - attr_reader :repo_dir, :patches_dir, :ce_repo, :ce_branch + PLEASE_READ_THIS_BANNER = %Q{ + ============================================================ + ===================== PLEASE READ THIS ===================== + ============================================================ + }.freeze + THANKS_FOR_READING_BANNER = %Q{ + ============================================================ + ==================== THANKS FOR READING ==================== + ============================================================\n + }.freeze + + attr_reader :ee_repo_dir, :patches_dir, :ce_repo, :ce_branch, :ee_branch_found + attr_reader :failed_files def initialize(branch:, ce_repo: CE_REPO) - @repo_dir = CHECK_DIR.join('repo') + @ee_repo_dir = CHECK_DIR.join('ee-repo') @patches_dir = CHECK_DIR.join('patches') @ce_branch = branch @ce_repo = ce_repo end def check - ensure_ee_repo ensure_patches_dir - generate_patch(ce_branch, ce_patch_full_path) - Dir.chdir(repo_dir) do - step("In the #{repo_dir} directory") + ensure_ee_repo + Dir.chdir(ee_repo_dir) do + step("In the #{ee_repo_dir} directory") status = catch(:halt_check) do ce_branch_compat_check! - delete_ee_branch_locally! + delete_ee_branches_locally! ee_branch_presence_check! ee_branch_compat_check! end - delete_ee_branch_locally! + delete_ee_branches_locally! if status.nil? true @@ -46,11 +55,13 @@ module Gitlab private def ensure_ee_repo - if Dir.exist?(repo_dir) - step("#{repo_dir} already exists") + if Dir.exist?(ee_repo_dir) + step("#{ee_repo_dir} already exists") else - cmd = %W[git clone --branch master --single-branch --depth 200 #{EE_REPO} #{repo_dir}] - step("Cloning #{EE_REPO} into #{repo_dir}", cmd) + step( + "Cloning #{EE_REPO} into #{ee_repo_dir}", + %W[git clone --branch master --single-branch --depth=200 #{EE_REPO} #{ee_repo_dir}] + ) end end @@ -61,23 +72,18 @@ module Gitlab def generate_patch(branch, patch_path) FileUtils.rm(patch_path, force: true) - depth = 0 - loop do - depth += 50 - cmd = %W[git fetch --depth #{depth} origin --prune +refs/heads/master:refs/remotes/origin/master] - Gitlab::Popen.popen(cmd) - _, status = Gitlab::Popen.popen(%w[git merge-base FETCH_HEAD HEAD]) + find_merge_base_with_master(branch: branch) - raise "#{branch} is too far behind master, please rebase it!" if depth >= MAX_FETCH_DEPTH - break if status.zero? - end + step( + "Generating the patch against origin/master in #{patch_path}", + %w[git format-patch origin/master --stdout] + ) do |output, status| + throw(:halt_check, :ko) unless status.zero? - step("Generating the patch against master in #{patch_path}") - output, status = Gitlab::Popen.popen(%w[git format-patch FETCH_HEAD --stdout]) - throw(:halt_check, :ko) unless status.zero? + File.write(patch_path, output) - File.write(patch_path, output) - throw(:halt_check, :ko) unless File.exist?(patch_path) + throw(:halt_check, :ko) unless File.exist?(patch_path) + end end def ce_branch_compat_check! @@ -88,9 +94,17 @@ module Gitlab end def ee_branch_presence_check! - status = step("Fetching origin/#{ee_branch}", %W[git fetch origin #{ee_branch}]) + _, status = step("Fetching origin/#{ee_branch_prefix}", %W[git fetch origin #{ee_branch_prefix}]) - unless status.zero? + if status.zero? + @ee_branch_found = ee_branch_prefix + else + _, status = step("Fetching origin/#{ee_branch_suffix}", %W[git fetch origin #{ee_branch_suffix}]) + end + + if status.zero? + @ee_branch_found = ee_branch_suffix + else puts puts ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg @@ -99,9 +113,9 @@ module Gitlab end def ee_branch_compat_check! - step("Checking out origin/#{ee_branch}", %W[git checkout -b #{ee_branch} FETCH_HEAD]) + step("Checking out origin/#{ee_branch_found}", %W[git checkout -b #{ee_branch_found} FETCH_HEAD]) - generate_patch(ee_branch, ee_patch_full_path) + generate_patch(ee_branch_found, ee_patch_full_path) unless check_patch(ee_patch_full_path).zero? puts @@ -111,41 +125,77 @@ module Gitlab end puts - puts applies_cleanly_msg(ee_branch) + puts applies_cleanly_msg(ee_branch_found) end def check_patch(patch_path) step("Checking out master", %w[git checkout master]) - step("Reseting to latest master", %w[git reset --hard origin/master]) - - step("Checking if #{patch_path} applies cleanly to EE/master") - output, status = Gitlab::Popen.popen(%W[git apply --check --3way #{patch_path}]) - - unless status.zero? - failed_files = output.lines.reduce([]) do |memo, line| - if line.start_with?('error: patch failed:') - file = line.sub(/\Aerror: patch failed: /, '') - memo << file unless file =~ IGNORED_FILES_REGEX + step("Resetting to latest master", %w[git reset --hard origin/master]) + step( + "Checking if #{patch_path} applies cleanly to EE/master", + %W[git apply --check --3way #{patch_path}] + ) do |output, status| + unless status.zero? + @failed_files = output.lines.reduce([]) do |memo, line| + if line.start_with?('error: patch failed:') + file = line.sub(/\Aerror: patch failed: /, '') + memo << file unless file =~ IGNORED_FILES_REGEX + end + memo end - memo + + status = 0 if failed_files.empty? end - if failed_files.empty? - status = 0 - else - puts "\nConflicting files:" - failed_files.each do |file| - puts " - #{file}" - end + status + end + end + + def delete_ee_branches_locally! + command(%w[git checkout master]) + command(%W[git branch --delete --force #{ee_branch_prefix}]) + command(%W[git branch --delete --force #{ee_branch_suffix}]) + end + + def merge_base_found? + step( + "Finding merge base with master", + %w[git merge-base origin/master HEAD] + ) do |output, status| + if status.zero? + puts "Merge base was found: #{output}" + true end end + end + + def find_merge_base_with_master(branch:) + return if merge_base_found? + + # Start with (Math.exp(3).to_i = 20) until (Math.exp(6).to_i = 403) + # In total we go (20 + 54 + 148 + 403 = 625) commits deeper + depth = 20 + success = + (3..6).any? do |factor| + depth += Math.exp(factor).to_i + # Repository is initially cloned with a depth of 20 so we need to fetch + # deeper in the case the branch has more than 20 commits on top of master + fetch(branch: branch, depth: depth) + fetch(branch: 'master', depth: depth) + + merge_base_found? + end - status + raise "\n#{branch} is too far behind master, please rebase it!\n" unless success end - def delete_ee_branch_locally! - command(%w[git checkout master]) - step("Deleting the local #{ee_branch} branch", %W[git branch -D #{ee_branch}]) + def fetch(branch:, depth:) + step( + "Fetching deeper...", + %W[git fetch --depth=#{depth} --prune origin +refs/heads/#{branch}:refs/remotes/origin/#{branch}] + ) do |output, status| + raise "Fetch failed: #{output}" unless status.zero? + end end def ce_patch_name @@ -156,12 +206,16 @@ module Gitlab @ce_patch_full_path ||= patches_dir.join(ce_patch_name) end - def ee_branch - @ee_branch ||= "#{ce_branch}-ee" + def ee_branch_suffix + @ee_branch_suffix ||= "#{ce_branch}-ee" + end + + def ee_branch_prefix + @ee_branch_prefix ||= "ee-#{ce_branch}" end def ee_patch_name - @ee_patch_name ||= patch_name_from_branch(ee_branch) + @ee_patch_name ||= patch_name_from_branch(ee_branch_found) end def ee_patch_full_path @@ -178,98 +232,125 @@ module Gitlab if cmd start = Time.now puts "\n$ #{cmd.join(' ')}" - status = command(cmd) - puts "\nFinished in #{Time.now - start} seconds" - status + + output, status = command(cmd) + puts "\n==> Finished in #{Time.now - start} seconds" + + if block_given? + yield(output, status) + else + [output, status] + end end end def command(cmd) - output, status = Gitlab::Popen.popen(cmd) - puts output - - status + Gitlab::Popen.popen(cmd) end def applies_cleanly_msg(branch) - <<-MSG.strip_heredoc - ================================================================= + %Q{ + #{PLEASE_READ_THIS_BANNER} 🎉 Congratulations!! 🎉 - The #{branch} branch applies cleanly to EE/master! + The `#{branch}` branch applies cleanly to EE/master! - Much ❤️!! - =================================================================\n - MSG + Much ❤️! For more information, see + https://docs.gitlab.com/ce/development/limit_ee_conflicts.html#check-the-rake-ee_compat_check-in-your-merge-requests + #{THANKS_FOR_READING_BANNER} + } end def ce_branch_doesnt_apply_cleanly_and_no_ee_branch_msg - <<-MSG.strip_heredoc - ================================================================= + %Q{ + #{PLEASE_READ_THIS_BANNER} 💥 Oh no! 💥 - The #{ce_branch} branch does not apply cleanly to the current - EE/master, and no #{ee_branch} branch was found in the EE repository. + The `#{ce_branch}` branch does not apply cleanly to the current + EE/master, and no `#{ee_branch_prefix}` or `#{ee_branch_suffix}` branch + was found in the EE repository. - Please create a #{ee_branch} branch that includes changes from - #{ce_branch} but also specific changes than can be applied cleanly - to EE/master. + #{conflicting_files_msg} + + We advise you to create a `#{ee_branch_prefix}` or `#{ee_branch_suffix}` + branch that includes changes from `#{ce_branch}` but also specific changes + than can be applied cleanly to EE/master. In some cases, the conflicts + are trivial and you can ignore the warning from this job. As always, + use your best judgment! There are different ways to create such branch: - 1. Create a new branch based on the CE branch and rebase it on top of EE/master + 1. Create a new branch from master and cherry-pick your CE commits # In the EE repo - $ git fetch #{ce_repo} #{ce_branch} - $ git checkout -b #{ee_branch} FETCH_HEAD - - # You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit - # before rebasing to limit the conflicts-resolving steps during the rebase $ git fetch origin - $ git rebase origin/master + $ git checkout -b #{ee_branch_prefix} origin/master + $ git fetch #{ce_repo} #{ce_branch} + $ git cherry-pick SHA # Repeat for all the commits you want to pick - At this point you will likely have conflicts. - Solve them, and continue/finish the rebase. + You can squash the `#{ce_branch}` commits into a single "Port of #{ce_branch} to EE" commit. - You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE". + 2. Apply your branch's patch to EE - 2. Create a new branch from master and cherry-pick your CE commits + # In the CE repo + $ git fetch origin master + $ git format-patch origin/master --stdout > #{ce_branch}.patch # In the EE repo - $ git fetch origin - $ git checkout -b #{ee_branch} origin/master - $ git fetch #{ce_repo} #{ce_branch} - $ git cherry-pick SHA # Repeat for all the commits you want to pick + $ git fetch origin master + $ git checkout -b #{ee_branch_prefix} origin/master + $ git apply --3way path/to/#{ce_branch}.patch + + At this point you might have conflicts such as: - You can squash the #{ce_branch} commits into a single "Port of #{ce_branch} to EE" commit. + error: patch failed: lib/gitlab/ee_compat_check.rb:5 + Falling back to three-way merge... + Applied patch to 'lib/gitlab/ee_compat_check.rb' with conflicts. + U lib/gitlab/ee_compat_check.rb - Don't forget to push your branch to #{EE_REPO}: + Resolve them, stage the changes and commit them. + + ⚠️ Don't forget to push your branch to gitlab-ee: # In the EE repo - $ git push origin #{ee_branch} + $ git push origin #{ee_branch_prefix} + + ⚠️ Also, don't forget to create a new merge request on gitlab-ce and + cross-link it with the CE merge request. - You can then retry this failed build, and hopefully it should pass. + Once this is done, you can retry this failed build, and it should pass. - Stay 💪 ! - =================================================================\n - MSG + Stay 💪 ! For more information, see + https://docs.gitlab.com/ce/development/limit_ee_conflicts.html#check-the-rake-ee_compat_check-in-your-merge-requests + #{THANKS_FOR_READING_BANNER} + } end def ee_branch_doesnt_apply_cleanly_msg - <<-MSG.strip_heredoc - ================================================================= + %Q{ + #{PLEASE_READ_THIS_BANNER} 💥 Oh no! 💥 - The #{ce_branch} does not apply cleanly to the current - EE/master, and even though a #{ee_branch} branch exists in the EE - repository, it does not apply cleanly either to EE/master! + The `#{ce_branch}` does not apply cleanly to the current EE/master, and + even though a `#{ee_branch_found}` branch + exists in the EE repository, it does not apply cleanly either to + EE/master! + + #{conflicting_files_msg} - Please update the #{ee_branch}, push it again to #{EE_REPO}, and + Please update the `#{ee_branch_found}`, push it again to gitlab-ee, and retry this build. - Stay 💪 ! - =================================================================\n - MSG + Stay 💪 ! For more information, see + https://docs.gitlab.com/ce/development/limit_ee_conflicts.html#check-the-rake-ee_compat_check-in-your-merge-requests + #{THANKS_FOR_READING_BANNER} + } + end + + def conflicting_files_msg + failed_files.reduce("The conflicts detected were as follows:\n") do |memo, file| + memo << "\n - #{file}" + end end end end diff --git a/lib/gitlab/etag_caching/middleware.rb b/lib/gitlab/etag_caching/middleware.rb index ffbc6e17dc5..cd4e318033d 100644 --- a/lib/gitlab/etag_caching/middleware.rb +++ b/lib/gitlab/etag_caching/middleware.rb @@ -1,9 +1,10 @@ module Gitlab module EtagCaching class Middleware - RESERVED_WORDS = ProjectPathValidator::RESERVED.map { |word| "/#{word}/" }.join('|') + RESERVED_WORDS = NamespaceValidator::WILDCARD_ROUTES.map { |word| "/#{word}/" }.join('|') ROUTE_REGEXP = Regexp.union( - %r(^(?!.*(#{RESERVED_WORDS})).*/noteable/issue/\d+/notes\z) + %r(^(?!.*(#{RESERVED_WORDS})).*/noteable/issue/\d+/notes\z), + %r(^(?!.*(#{RESERVED_WORDS})).*/issues/\d+/rendered_title\z) ) def initialize(app) @@ -18,8 +19,7 @@ module Gitlab if_none_match = env['HTTP_IF_NONE_MATCH'] if if_none_match == etag - Gitlab::Metrics.add_event(:etag_caching_cache_hit) - [304, { 'ETag' => etag }, ['']] + handle_cache_hit(etag) else track_cache_miss(if_none_match, cached_value_present) @@ -52,6 +52,14 @@ module Gitlab %Q{W/"#{value}"} end + def handle_cache_hit(etag) + Gitlab::Metrics.add_event(:etag_caching_cache_hit) + + status_code = Gitlab::PollingInterval.polling_enabled? ? 304 : 429 + + [status_code, { 'ETag' => etag }, ['']] + end + def track_cache_miss(if_none_match, cached_value_present) if if_none_match.blank? Gitlab::Metrics.add_event(:etag_caching_header_missing) diff --git a/lib/gitlab/etag_caching/store.rb b/lib/gitlab/etag_caching/store.rb index 9532e432f78..0039fc01c8f 100644 --- a/lib/gitlab/etag_caching/store.rb +++ b/lib/gitlab/etag_caching/store.rb @@ -1,7 +1,7 @@ module Gitlab module EtagCaching class Store - EXPIRY_TIME = 10.minutes + EXPIRY_TIME = 20.minutes REDIS_NAMESPACE = 'etag:'.freeze def get(key) diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index d3df3f1bca1..936606152e9 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -4,6 +4,8 @@ module Gitlab TAG_REF_PREFIX = "refs/tags/".freeze BRANCH_REF_PREFIX = "refs/heads/".freeze + CommandError = Class.new(StandardError) + class << self def ref_name(ref) ref.sub(/\Arefs\/(tags|heads)\//, '') diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 2187dd70ff4..2e4314932c8 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -25,9 +25,13 @@ module Gitlab # 'path' must be the path to a _bare_ git repository, e.g. # /path/to/my-repo.git - def initialize(path) - @path = path - @name = path.split("/").last + def initialize(repository_storage, relative_path) + @repository_storage = repository_storage + @relative_path = relative_path + + storage_path = Gitlab.config.repositories.storages[@repository_storage]['path'] + @path = File.join(storage_path, @relative_path) + @name = @relative_path.split("/").last @attributes = Gitlab::Git::Attributes.new(path) end @@ -37,7 +41,15 @@ module Gitlab # Default branch in the repository def root_ref - @root_ref ||= discover_default_branch + @root_ref ||= Gitlab::GitalyClient.migrate(:root_ref) do |is_enabled| + if is_enabled + gitaly_ref_client.default_branch_name + else + discover_default_branch + end + end + rescue GRPC::BadStatus => e + raise CommandError.new(e) end # Alias to old method for compatibility @@ -54,7 +66,15 @@ module Gitlab # Returns an Array of branch names # sorted by name ASC def branch_names - branches.map(&:name) + Gitlab::GitalyClient.migrate(:branch_names) do |is_enabled| + if is_enabled + gitaly_ref_client.branch_names + else + branches.map(&:name) + end + end + rescue GRPC::BadStatus => e + raise CommandError.new(e) end # Returns an Array of Branches @@ -107,7 +127,15 @@ module Gitlab # Returns an Array of tag names def tag_names - rugged.tags.map { |t| t.name } + Gitlab::GitalyClient.migrate(:tag_names) do |is_enabled| + if is_enabled + gitaly_ref_client.tag_names + else + rugged.tags.map { |t| t.name } + end + end + rescue GRPC::BadStatus => e + raise CommandError.new(e) end # Returns an Array of Tags @@ -320,7 +348,7 @@ module Gitlab def log_by_walk(sha, options) walk_options = { show: sha, - sort: Rugged::SORT_DATE, + sort: Rugged::SORT_NONE, limit: options[:limit], offset: options[:offset] } @@ -346,7 +374,12 @@ module Gitlab cmd << "--after=#{options[:after].iso8601}" if options[:after] cmd << "--before=#{options[:before].iso8601}" if options[:before] cmd << sha - cmd += %W[-- #{options[:path]}] if options[:path].present? + + # :path can be a string or an array of strings + if options[:path].present? + cmd << '--' + cmd += Array(options[:path]) + end raw_output = IO.popen(cmd) { |io| io.read } lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines @@ -382,7 +415,7 @@ module Gitlab # a detailed list of valid arguments. def commits_between(from, to) walker = Rugged::Walker.new(rugged) - walker.sorting(Rugged::SORT_DATE | Rugged::SORT_REVERSE) + walker.sorting(Rugged::SORT_NONE | Rugged::SORT_REVERSE) sha_from = sha_from_ref(from) sha_to = sha_from_ref(to) @@ -406,6 +439,11 @@ module Gitlab rugged.merge_base(from, to) end + # Returns true is +from+ is direct ancestor to +to+, otherwise false + def is_ancestor?(from, to) + Gitlab::GitalyClient::Commit.is_ancestor(self, from, to) + end + # Return an array of Diff objects that represent the diff # between +from+ and +to+. See Diff::filter_diff_options for the allowed # diff options. The +options+ hash can also include :break_rewrites to @@ -460,7 +498,7 @@ module Gitlab if actual_options[:order] == :topo walker.sorting(Rugged::SORT_TOPO) else - walker.sorting(Rugged::SORT_DATE) + walker.sorting(Rugged::SORT_NONE) end commits = [] @@ -828,23 +866,6 @@ module Gitlab Rugged::Commit.create(rugged, actual_options) end - def commits_since(from_date) - walker = Rugged::Walker.new(rugged) - walker.sorting(Rugged::SORT_DATE | Rugged::SORT_REVERSE) - - rugged.references.each("refs/heads/*") do |ref| - walker.push(ref.target_id) - end - - commits = [] - walker.each do |commit| - break if commit.author[:time].to_date < from_date - commits.push(commit) - end - - commits - end - AUTOCRLF_VALUES = { "true" => true, "false" => false, @@ -1209,6 +1230,10 @@ module Gitlab diff.find_similar!(break_rewrites: break_rewrites) diff.each_patch end + + def gitaly_ref_client + @gitaly_ref_client ||= Gitlab::GitalyClient::Ref.new(@repository_storage, @relative_path) + end end end end diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb index f7450e8b58f..b722d8a9f56 100644 --- a/lib/gitlab/git/tree.rb +++ b/lib/gitlab/git/tree.rb @@ -33,7 +33,7 @@ module Gitlab root_id: root_tree.oid, name: entry[:name], type: entry[:type], - mode: entry[:filemode], + mode: entry[:filemode].to_s(8), path: path ? File.join(path, entry[:name]) : entry[:name], commit_id: sha, ) diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 5534d4af439..bcdf1b1faa8 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -2,28 +2,62 @@ require 'gitaly' module Gitlab module GitalyClient - def self.gitaly_address - if Gitlab.config.gitaly.socket_path - "unix://#{Gitlab.config.gitaly.socket_path}" + SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION'.freeze + + # This function is not thread-safe because it updates Hashes in instance variables. + def self.configure_channels + @addresses = {} + @channels = {} + Gitlab.config.repositories.storages.each do |name, params| + address = params['gitaly_address'] + unless address.present? + raise "storage #{name.inspect} is missing a gitaly_address" + end + + unless URI(address).scheme.in?(%w(tcp unix)) + raise "Unsupported Gitaly address: #{address.inspect}" + end + + @addresses[name] = address + @channels[name] = new_channel(address) end end - def self.channel - return @channel if defined?(@channel) + def self.new_channel(address) + address = address.sub(%r{^tcp://}, '') if URI(address).scheme == 'tcp' + # NOTE: When Gitaly runs on a Unix socket, permissions are + # handled using the file system and no additional authentication is + # required (therefore the :this_channel_is_insecure flag) + # TODO: Add authentication support when Gitaly is running on a TCP socket. + GRPC::Core::Channel.new(address, {}, :this_channel_is_insecure) + end - @channel = - if enabled? - # NOTE: Gitaly currently runs on a Unix socket, so permissions are - # handled using the file system and no additional authentication is - # required (therefore the :this_channel_is_insecure flag) - GRPC::Core::Channel.new(gitaly_address, {}, :this_channel_is_insecure) - else - nil - end + def self.get_channel(storage) + if !Rails.env.production? && @channels.nil? + # In development mode the Rails auto-loader may reset the instance + # variables of this class. What we do here is not thread-safe. In normal + # circumstances (including production) these instance variables have + # been initialized from config/initializers. + configure_channels + end + + @channels[storage] + end + + def self.get_address(storage) + if !Rails.env.production? && @addresses.nil? + # In development mode the Rails auto-loader may reset the instance + # variables of this class. What we do here is not thread-safe. In normal + # circumstances (including development) these instance variables have + # been initialized from config/initializers. + configure_channels + end + + @addresses[storage] end def self.enabled? - gitaly_address.present? + Gitlab.config.gitaly.enabled end def self.feature_enabled?(feature) @@ -39,5 +73,10 @@ module Gitlab yield is_enabled end end + + def self.expected_server_version + path = Rails.root.join(SERVER_VERSION_FILE) + path.read.chomp + end end end diff --git a/lib/gitlab/gitaly_client/commit.rb b/lib/gitlab/gitaly_client/commit.rb index 525b8d680e9..f15faebe27e 100644 --- a/lib/gitlab/gitaly_client/commit.rb +++ b/lib/gitlab/gitaly_client/commit.rb @@ -7,8 +7,10 @@ module Gitlab class << self def diff_from_parent(commit, options = {}) - stub = Gitaly::Diff::Stub.new(nil, nil, channel_override: GitalyClient.channel) - repo = Gitaly::Repository.new(path: commit.project.repository.path_to_repo) + project = commit.project + channel = GitalyClient.get_channel(project.repository_storage) + stub = Gitaly::Diff::Stub.new(nil, nil, channel_override: channel) + repo = Gitaly::Repository.new(path: project.repository.path_to_repo) parent = commit.parents[0] parent_id = parent ? parent.id : EMPTY_TREE_ID request = Gitaly::CommitDiffRequest.new( @@ -19,6 +21,20 @@ module Gitlab Gitlab::Git::DiffCollection.new(stub.commit_diff(request), options) end + + def is_ancestor(repository, ancestor_id, child_id) + project = Project.find_by_path(repository.path) + channel = GitalyClient.get_channel(project.repository_storage) + stub = Gitaly::Commit::Stub.new(nil, nil, channel_override: channel) + repo = Gitaly::Repository.new(path: repository.path_to_repo) + request = Gitaly::CommitIsAncestorRequest.new( + repository: repo, + ancestor_id: ancestor_id, + child_id: child_id + ) + + stub.commit_is_ancestor(request).value + end end end end diff --git a/lib/gitlab/gitaly_client/notifications.rb b/lib/gitlab/gitaly_client/notifications.rb index b827a56207f..f0d93ded91b 100644 --- a/lib/gitlab/gitaly_client/notifications.rb +++ b/lib/gitlab/gitaly_client/notifications.rb @@ -3,14 +3,14 @@ module Gitlab class Notifications attr_accessor :stub - def initialize - @stub = Gitaly::Notifications::Stub.new(nil, nil, channel_override: GitalyClient.channel) + def initialize(repository_storage, relative_path) + @channel, @repository = Util.process_path(repository_storage, relative_path) + @stub = Gitaly::Notifications::Stub.new(nil, nil, channel_override: @channel) end - def post_receive(repo_path) - repository = Gitaly::Repository.new(path: repo_path) - request = Gitaly::PostReceiveRequest.new(repository: repository) - stub.post_receive(request) + def post_receive + request = Gitaly::PostReceiveRequest.new(repository: @repository) + @stub.post_receive(request) end end end diff --git a/lib/gitlab/gitaly_client/ref.rb b/lib/gitlab/gitaly_client/ref.rb new file mode 100644 index 00000000000..bfc5fa573c7 --- /dev/null +++ b/lib/gitlab/gitaly_client/ref.rb @@ -0,0 +1,35 @@ +module Gitlab + module GitalyClient + class Ref + attr_accessor :stub + + def initialize(repository_storage, relative_path) + @channel, @repository = Util.process_path(repository_storage, relative_path) + @stub = Gitaly::Ref::Stub.new(nil, nil, channel_override: @channel) + end + + def default_branch_name + request = Gitaly::FindDefaultBranchNameRequest.new(repository: @repository) + stub.find_default_branch_name(request).name.gsub(/^refs\/heads\//, '') + end + + def branch_names + request = Gitaly::FindAllBranchNamesRequest.new(repository: @repository) + consume_refs_response(stub.find_all_branch_names(request), prefix: 'refs/heads/') + end + + def tag_names + request = Gitaly::FindAllTagNamesRequest.new(repository: @repository) + consume_refs_response(stub.find_all_tag_names(request), prefix: 'refs/tags/') + end + + private + + def consume_refs_response(response, prefix:) + response.flat_map do |r| + r.names.map { |name| name.sub(/\A#{Regexp.escape(prefix)}/, '') } + end + end + end + end +end diff --git a/lib/gitlab/gitaly_client/util.rb b/lib/gitlab/gitaly_client/util.rb new file mode 100644 index 00000000000..d272c25d1f9 --- /dev/null +++ b/lib/gitlab/gitaly_client/util.rb @@ -0,0 +1,13 @@ +module Gitlab + module GitalyClient + module Util + def self.process_path(repository_storage, relative_path) + channel = GitalyClient.get_channel(repository_storage) + storage_path = Gitlab.config.repositories.storages[repository_storage]['path'] + repository = Gitaly::Repository.new(path: File.join(storage_path, relative_path)) + + [channel, repository] + end + end + end +end diff --git a/lib/gitlab/github_import/branch_formatter.rb b/lib/gitlab/github_import/branch_formatter.rb index 5d29e698b27..8aa885fb811 100644 --- a/lib/gitlab/github_import/branch_formatter.rb +++ b/lib/gitlab/github_import/branch_formatter.rb @@ -11,6 +11,14 @@ module Gitlab sha.present? && ref.present? end + def user + raw_data.user&.login || 'unknown' + end + + def short_sha + Commit.truncate_sha(sha) + end + private def branch_exists? diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb index eea4a91f17d..a8c0b47e786 100644 --- a/lib/gitlab/github_import/importer.rb +++ b/lib/gitlab/github_import/importer.rb @@ -157,7 +157,7 @@ module Gitlab end def restore_source_branch(pull_request) - project.repository.fetch_ref(repo_url, "pull/#{pull_request.number}/head", pull_request.source_branch_name) + project.repository.create_branch(pull_request.source_branch_name, pull_request.source_branch_sha) end def restore_target_branch(pull_request) diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb index add7236e339..150afa31432 100644 --- a/lib/gitlab/github_import/pull_request_formatter.rb +++ b/lib/gitlab/github_import/pull_request_formatter.rb @@ -1,8 +1,8 @@ module Gitlab module GithubImport class PullRequestFormatter < IssuableFormatter - delegate :exists?, :project, :ref, :repo, :sha, to: :source_branch, prefix: true - delegate :exists?, :project, :ref, :repo, :sha, to: :target_branch, prefix: true + delegate :user, :project, :ref, :repo, :sha, to: :source_branch, prefix: true + delegate :user, :exists?, :project, :ref, :repo, :sha, :short_sha, to: :target_branch, prefix: true def attributes { @@ -20,7 +20,8 @@ module Gitlab author_id: author_id, assignee_id: assignee_id, created_at: raw_data.created_at, - updated_at: raw_data.updated_at + updated_at: raw_data.updated_at, + imported: true } end @@ -37,13 +38,20 @@ module Gitlab end def source_branch_name - @source_branch_name ||= begin - if cross_project? - "pull/#{number}/#{source_branch_repo.full_name}/#{source_branch_ref}" + @source_branch_name ||= + if cross_project? || !source_branch_exists? + source_branch_name_prefixed else - source_branch_exists? ? source_branch_ref : "pull/#{number}/#{source_branch_ref}" + source_branch_ref end - end + end + + def source_branch_name_prefixed + "gh-#{target_branch_short_sha}/#{number}/#{source_branch_user}/#{source_branch_ref}" + end + + def source_branch_exists? + !cross_project? && source_branch.exists? end def target_branch @@ -51,13 +59,17 @@ module Gitlab end def target_branch_name - @target_branch_name ||= begin - target_branch_exists? ? target_branch_ref : "pull/#{number}/#{target_branch_ref}" - end + @target_branch_name ||= target_branch_exists? ? target_branch_ref : target_branch_name_prefixed + end + + def target_branch_name_prefixed + "gl-#{target_branch_short_sha}/#{number}/#{target_branch_user}/#{target_branch_ref}" end def cross_project? - source_branch.repo.id != target_branch.repo.id + return true if source_branch_repo.nil? + + source_branch_repo.id != target_branch_repo.id end def opened? diff --git a/lib/gitlab/import_export/hash_util.rb b/lib/gitlab/import_export/hash_util.rb new file mode 100644 index 00000000000..d4adeeb3797 --- /dev/null +++ b/lib/gitlab/import_export/hash_util.rb @@ -0,0 +1,25 @@ +module Gitlab + module ImportExport + class HashUtil + def self.deep_symbolize_array!(array) + return if array.blank? + + array.map! do |hash| + hash.deep_symbolize_keys! + + yield(hash) if block_given? + + hash + end + end + + def self.deep_symbolize_array_with_date!(array) + self.deep_symbolize_array!(array) do |hash| + hash.select { |k, _v| k.to_s.end_with?('_date') }.each do |key, value| + hash[key] = Time.zone.parse(value) + end + end + end + end + end +end diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 25e03ec64d3..745f9a1cfbd 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -91,3 +91,5 @@ methods: - :type merge_request_diff: - :utf8_st_diffs + merge_requests: + - :diff_head_sha diff --git a/lib/gitlab/import_export/importer.rb b/lib/gitlab/import_export/importer.rb index 063ce74ecad..fbdd74788bc 100644 --- a/lib/gitlab/import_export/importer.rb +++ b/lib/gitlab/import_export/importer.rb @@ -9,7 +9,7 @@ module Gitlab end def execute - if import_file && check_version! && [project_tree, avatar_restorer, repo_restorer, wiki_restorer, uploads_restorer].all?(&:restore) + if import_file && check_version! && [repo_restorer, wiki_restorer, project_tree, avatar_restorer, uploads_restorer].all?(&:restore) project_tree.restored_project else raise Projects::ImportService::Error.new(@shared.errors.join(', ')) diff --git a/lib/gitlab/import_export/merge_request_parser.rb b/lib/gitlab/import_export/merge_request_parser.rb new file mode 100644 index 00000000000..c20adc20bfd --- /dev/null +++ b/lib/gitlab/import_export/merge_request_parser.rb @@ -0,0 +1,41 @@ +module Gitlab + module ImportExport + class MergeRequestParser + FORKED_PROJECT_ID = -1 + + def initialize(project, diff_head_sha, merge_request, relation_hash) + @project = project + @diff_head_sha = diff_head_sha + @merge_request = merge_request + @relation_hash = relation_hash + end + + def parse! + if fork_merge_request? && @diff_head_sha + @merge_request.source_project_id = @relation_hash['project_id'] + + fetch_ref unless branch_exists?(@merge_request.source_branch) + create_target_branch unless branch_exists?(@merge_request.target_branch) + end + + @merge_request + end + + def create_target_branch + @project.repository.create_branch(@merge_request.target_branch, @merge_request.target_branch_sha) + end + + def fetch_ref + @project.repository.fetch_ref(@project.repository.path, @diff_head_sha, @merge_request.source_branch) + end + + def branch_exists?(branch_name) + @project.repository.branch_exists?(branch_name) + end + + def fork_merge_request? + @relation_hash['source_project_id'] == FORKED_PROJECT_ID + end + end + end +end diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index cda6ddf0443..df21ff22216 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -119,7 +119,7 @@ module Gitlab relation_hash: parsed_relation_hash(relation_hash), members_mapper: members_mapper, user: @user, - project_id: restored_project.id) + project: restored_project) end.compact relation_hash_list.is_a?(Array) ? relation_array : relation_array.first diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 430de9a1bf8..0545ca10862 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -30,11 +30,12 @@ module Gitlab new(*args).create end - def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project_id:) + def initialize(relation_sym:, relation_hash:, members_mapper:, user:, project:) @relation_name = OVERRIDES[relation_sym] || relation_sym - @relation_hash = relation_hash.except('noteable_id').merge('project_id' => project_id) + @relation_hash = relation_hash.except('noteable_id').merge('project_id' => project.id) @members_mapper = members_mapper @user = user + @project = project @imported_object_retries = 0 end @@ -67,7 +68,7 @@ module Gitlab remove_encrypted_attributes! @relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data'] - set_st_diffs if @relation_name == :merge_request_diff + set_st_diff_commits if @relation_name == :merge_request_diff end def update_user_references @@ -106,6 +107,8 @@ module Gitlab imported_object do |object| object.commit_id = nil end + elsif @relation_name == :merge_requests + MergeRequestParser.new(@project, @relation_hash.delete('diff_head_sha'), imported_object, @relation_hash).parse! else imported_object end @@ -116,7 +119,7 @@ module Gitlab # If source and target are the same, populate them with the new project ID. if @relation_hash['source_project_id'] - @relation_hash['source_project_id'] = same_source_and_target? ? project_id : -1 + @relation_hash['source_project_id'] = same_source_and_target? ? project_id : MergeRequestParser::FORKED_PROJECT_ID end # project_id may not be part of the export, but we always need to populate it if required. @@ -167,6 +170,7 @@ module Gitlab def imported_object yield(existing_or_new_object) if block_given? existing_or_new_object.importing = true if existing_or_new_object.respond_to?(:importing) + existing_or_new_object rescue ActiveRecord::RecordNotUnique # as the operation is not atomic, retry in the unlikely scenario an INSERT is @@ -189,8 +193,11 @@ module Gitlab relation_class: relation_class) end - def set_st_diffs + def set_st_diff_commits @relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs') + + HashUtil.deep_symbolize_array!(@relation_hash['st_diffs']) + HashUtil.deep_symbolize_array_with_date!(@relation_hash['st_commits']) end def existing_or_new_object diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb index 28129198438..46deea3cc9f 100644 --- a/lib/gitlab/ldap/config.rb +++ b/lib/gitlab/ldap/config.rb @@ -124,9 +124,9 @@ module Gitlab def name_proc if allow_username_or_email_login - Proc.new { |name| name.gsub(/@.*\z/, '') } + proc { |name| name.gsub(/@.*\z/, '') } else - Proc.new { |name| name } + proc { |name| name } end end diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb index fcf51b7fc5b..f98481c6d3a 100644 --- a/lib/gitlab/o_auth/user.rb +++ b/lib/gitlab/o_auth/user.rb @@ -147,10 +147,8 @@ module Gitlab end def build_new_user - user = ::User.new(user_attributes) - user.skip_confirmation! - user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider) - user + user_params = user_attributes.merge(extern_uid: auth_hash.uid, provider: auth_hash.provider, skip_confirmation: true) + Users::CreateService.new(nil, user_params).build end def user_attributes diff --git a/lib/gitlab/polling_interval.rb b/lib/gitlab/polling_interval.rb new file mode 100644 index 00000000000..f0c50584f07 --- /dev/null +++ b/lib/gitlab/polling_interval.rb @@ -0,0 +1,22 @@ +module Gitlab + class PollingInterval + include Gitlab::CurrentSettings + + HEADER_NAME = 'Poll-Interval'.freeze + + def self.set_header(response, interval:) + if polling_enabled? + multiplier = current_application_settings.polling_interval_multiplier + value = (interval * multiplier).to_i + else + value = -1 + end + + response.headers[HEADER_NAME] = value.to_s + end + + def self.polling_enabled? + !current_application_settings.polling_interval_multiplier.zero? + end + end +end diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb index db325c00705..0b8959f2fb9 100644 --- a/lib/gitlab/project_search_results.rb +++ b/lib/gitlab/project_search_results.rb @@ -62,7 +62,7 @@ module Gitlab data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '') end - OpenStruct.new( + FoundBlob.new( filename: filename, basename: basename, ref: ref, diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb new file mode 100644 index 00000000000..4b1d828c45c --- /dev/null +++ b/lib/gitlab/repo_path.rb @@ -0,0 +1,23 @@ +module Gitlab + module RepoPath + NotFoundError = Class.new(StandardError) + + def self.strip_storage_path(repo_path) + result = nil + + Gitlab.config.repositories.storages.values.each do |params| + storage_path = params['path'] + if repo_path.start_with?(storage_path) + result = repo_path.sub(storage_path, '') + break + end + end + + if result.nil? + raise NotFoundError.new("No known storage path matches #{repo_path.inspect}") + end + + result.sub(/\A\/*/, '') + end + end +end diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index ccfa517e04b..efe8095beea 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -1,5 +1,26 @@ module Gitlab class SearchResults + class FoundBlob + attr_reader :id, :filename, :basename, :ref, :startline, :data + + def initialize(opts = {}) + @id = opts.fetch(:id, nil) + @filename = opts.fetch(:filename, nil) + @basename = opts.fetch(:basename, nil) + @ref = opts.fetch(:ref, nil) + @startline = opts.fetch(:startline, nil) + @data = opts.fetch(:data, nil) + end + + def path + filename + end + + def no_highlighting? + false + end + end + attr_reader :current_user, :query # Limit search results by passed projects diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index da8d8ddb8ed..36a871e5bbc 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -35,7 +35,7 @@ module Gitlab end def strip_key(key) - key.split(/ /)[0, 2].join(' ') + key.split(/[ ]+/)[0, 2].join(' ') end private @@ -88,6 +88,26 @@ module Gitlab true end + # Fetch remote for repository + # + # name - project path with namespace + # remote - remote name + # forced - should we use --force flag? + # no_tags - should we use --no-tags flag? + # + # Ex. + # fetch_remote("gitlab/gitlab-ci", "upstream") + # + def fetch_remote(storage, name, remote, forced: false, no_tags: false) + args = [gitlab_shell_projects_path, 'fetch-remote', storage, "#{name}.git", remote, '800'] + args << '--force' if forced + args << '--no-tags' if no_tags + + output, status = Popen.popen(args) + raise Error, output unless status.zero? + true + end + # Move repository # storage - project's storage path # path - project path with namespace @@ -174,7 +194,10 @@ module Gitlab # add_namespace("/path/to/storage", "gitlab") # def add_namespace(storage, name) - FileUtils.mkdir_p(full_path(storage, name), mode: 0770) unless exists?(storage, name) + path = full_path(storage, name) + FileUtils.mkdir_p(path, mode: 0770) unless exists?(storage, name) + rescue Errno::EEXIST => e + Rails.logger.warn("Directory exists as a file: #{e} at: #{path}") end # Remove directory from repositories storage diff --git a/lib/gitlab/testing/request_blocker_middleware.rb b/lib/gitlab/testing/request_blocker_middleware.rb new file mode 100644 index 00000000000..aa67fa08577 --- /dev/null +++ b/lib/gitlab/testing/request_blocker_middleware.rb @@ -0,0 +1,61 @@ +# rubocop:disable Style/ClassVars + +# This is inspired by http://www.salsify.com/blog/engineering/tearing-capybara-ajax-tests +# Rack middleware that keeps track of the number of active requests and can block new requests. +module Gitlab + module Testing + class RequestBlockerMiddleware + @@num_active_requests = Concurrent::AtomicFixnum.new(0) + @@block_requests = Concurrent::AtomicBoolean.new(false) + + # Returns the number of requests the server is currently processing. + def self.num_active_requests + @@num_active_requests.value + end + + # Prevents the server from accepting new requests. Any new requests will return an HTTP + # 503 status. + def self.block_requests! + @@block_requests.value = true + end + + # Allows the server to accept requests again. + def self.allow_requests! + @@block_requests.value = false + end + + def initialize(app) + @app = app + end + + def call(env) + increment_active_requests + if block_requests? + block_request(env) + else + @app.call(env) + end + ensure + decrement_active_requests + end + + private + + def block_requests? + @@block_requests.true? + end + + def block_request(env) + [503, {}, []] + end + + def increment_active_requests + @@num_active_requests.increment + end + + def decrement_active_requests + @@num_active_requests.decrement + end + end + end +end diff --git a/lib/gitlab/uploads_transfer.rb b/lib/gitlab/uploads_transfer.rb index 81701831a6a..7d0c47c5361 100644 --- a/lib/gitlab/uploads_transfer.rb +++ b/lib/gitlab/uploads_transfer.rb @@ -1,7 +1,7 @@ module Gitlab class UploadsTransfer < ProjectTransfer def root_dir - File.join(Rails.root, "public", "uploads") + File.join(CarrierWave.root, GitlabUploader.base_dir) end end end diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index c81dc7e30d0..9ce13feb79a 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -18,6 +18,12 @@ module Gitlab false end + def self.http_credentials_for_user(user) + return {} unless user.respond_to?(:username) + + { user: user.username } + end + def initialize(url, credentials: nil) @url = Addressable::URI.parse(url.strip) @credentials = credentials diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index eae1a0abf06..a8a7bf9bc12 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -1,6 +1,7 @@ require 'base64' require 'json' require 'securerandom' +require 'uri' module Gitlab class Workhorse @@ -15,16 +16,37 @@ module Gitlab SECRET_LENGTH = 32 class << self - def git_http_ok(repository, user) + def git_http_ok(repository, user, action) + repo_path = repository.path_to_repo params = { GL_ID: Gitlab::GlId.gl_id(user), - RepoPath: repository.path_to_repo, + RepoPath: repo_path, } - params.merge!( - GitalySocketPath: Gitlab.config.gitaly.socket_path, - GitalyResourcePath: "/projects/#{repository.project.id}/git-http/info-refs", - ) if Gitlab.config.gitaly.socket_path.present? + if Gitlab.config.gitaly.enabled + storage = repository.project.repository_storage + address = Gitlab::GitalyClient.get_address(storage) + # TODO: use GitalyClient code to assemble the Repository message + params[:Repository] = Gitaly::Repository.new( + path: repo_path, + storage_name: storage, + relative_path: Gitlab::RepoPath.strip_storage_path(repo_path), + ).to_h + + feature_enabled = case action.to_s + when 'git_receive_pack' + # Disabled for now, see https://gitlab.com/gitlab-org/gitaly/issues/172 + false + when 'git_upload_pack' + Gitlab::GitalyClient.feature_enabled?(:post_upload_pack) + when 'info_refs' + true + else + raise "Unsupported action: #{action}" + end + + params[:GitalyAddress] = address if feature_enabled + end params end diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index 5fd7f0f98bd..09e121e5120 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -48,6 +48,10 @@ gitlab_pages_pid_path="$pid_path/gitlab-pages.pid" gitlab_pages_options="-pages-domain example.com -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090" gitlab_pages_log="$app_root/log/gitlab-pages.log" shell_path="/bin/bash" +gitaly_enabled=false +gitaly_dir=$(cd $app_root/../gitaly 2> /dev/null && pwd) +gitaly_pid_path="$pid_path/gitaly.pid" +gitaly_log="$app_root/log/gitaly.log" # Read configuration variable file if it is present test -f /etc/default/gitlab && . /etc/default/gitlab @@ -101,13 +105,20 @@ check_pids(){ gppid=0 fi fi + if [ "$gitaly_enabled" = true ]; then + if [ -f "$gitaly_pid_path" ]; then + gapid=$(cat "$gitaly_pid_path") + else + gapid=0 + fi + fi } ## Called when we have started the two processes and are waiting for their pid files. wait_for_pids(){ # We are sleeping a bit here mostly because sidekiq is slow at writing its pid i=0; - while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || [ ! -f $gitlab_workhorse_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ]; } || { [ "$gitlab_pages_enabled" = true ] && [ ! -f $gitlab_pages_pid_path ]; }; do + while [ ! -f $web_server_pid_path ] || [ ! -f $sidekiq_pid_path ] || [ ! -f $gitlab_workhorse_pid_path ] || { [ "$mail_room_enabled" = true ] && [ ! -f $mail_room_pid_path ]; } || { [ "$gitlab_pages_enabled" = true ] && [ ! -f $gitlab_pages_pid_path ]; } || { [ "$gitaly_enabled" = true ] && [ ! -f $gitaly_pid_path ]; }; do sleep 0.1; i=$((i+1)) if [ $((i%10)) = 0 ]; then @@ -164,7 +175,15 @@ check_status(){ gitlab_pages_status="-1" fi fi - if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && [ $gitlab_workhorse_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ]; } && { [ "$gitlab_pages_enabled" != true ] || [ $gitlab_pages_status = 0 ]; }; then + if [ "$gitaly_enabled" = true ]; then + if [ $gapid -ne 0 ]; then + kill -0 "$gapid" 2>/dev/null + gitaly_status="$?" + else + gitaly_status="-1" + fi + fi + if [ $web_status = 0 ] && [ $sidekiq_status = 0 ] && [ $gitlab_workhorse_status = 0 ] && { [ "$mail_room_enabled" != true ] || [ $mail_room_status = 0 ]; } && { [ "$gitlab_pages_enabled" != true ] || [ $gitlab_pages_status = 0 ]; } && { [ "$gitaly_enabled" != true ] || [ $gitaly_status = 0 ]; }; then gitlab_status=0 else # http://refspecs.linuxbase.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html @@ -213,12 +232,19 @@ check_stale_pids(){ exit 1 fi fi + if [ "$gitaly_enabled" = true ] && [ "$gapid" != "0" ] && [ "$gitaly_status" != "0" ]; then + echo "Removing stale Gitaly pid. This is most likely caused by Gitaly crashing the last time it ran." + if ! rm "$gitaly_pid_path"; then + echo "Unable to remove stale pid, exiting" + exit 1 + fi + fi } ## If no parts of the service is running, bail out. exit_if_not_running(){ check_stale_pids - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; } && { [ "$gitaly_enabled" != true ] || [ "$gitaly_status" != "0" ]; }; then echo "GitLab is not running." exit fi @@ -243,6 +269,9 @@ start_gitlab() { if [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" != "0" ]; then echo "Starting GitLab Pages" fi + if [ "$gitaly_enabled" = true ] && [ "$gitaly_status" != "0" ]; then + echo "Starting Gitaly" + fi # Then check if the service is running. If it is: don't start again. if [ "$web_status" = "0" ]; then @@ -292,6 +321,16 @@ start_gitlab() { fi fi + if [ "$gitaly_enabled" = true ]; then + if [ "$gitaly_status" = "0" ]; then + echo "Gitaly is already running with pid $gapid, not restarting" + else + $app_root/bin/daemon_with_pidfile $gitaly_pid_path \ + $app_root/bin/with_env $gitaly_dir/env \ + $gitaly_dir/gitaly >> $gitaly_log 2>&1 & + fi + fi + # Wait for the pids to be planted wait_for_pids # Finally check the status to tell wether or not GitLab is running @@ -322,13 +361,17 @@ stop_gitlab() { echo "Shutting down gitlab-pages" kill -- $(cat $gitlab_pages_pid_path) fi + if [ "$gitaly_status" = "0" ]; then + echo "Shutting down Gitaly" + kill -- $(cat $gitaly_pid_path) + fi # If something needs to be stopped, lets wait for it to stop. Never use SIGKILL in a script. - while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; } || { [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" = "0" ]; }; do + while [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse_status" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; } || { [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" = "0" ]; } || { [ "$gitaly_enabled" = true ] && [ "$gitaly_status" = "0" ]; }; do sleep 1 check_status printf "." - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; } && { [ "$gitaly_enabled" != true ] || [ "$gitaly_status" != "0" ]; }; then printf "\n" break fi @@ -343,6 +386,7 @@ stop_gitlab() { rm "$mail_room_pid_path" 2>/dev/null fi rm -f "$gitlab_pages_pid_path" + rm -f "$gitaly_pid_path" print_status } @@ -350,7 +394,7 @@ stop_gitlab() { ## Prints the status of GitLab and its components. print_status() { check_status - if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; }; then + if [ "$web_status" != "0" ] && [ "$sidekiq_status" != "0" ] && [ "$gitlab_workhorse_status" != "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" != "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" != "0" ]; } && { [ "$gitaly_enabled" != true ] || [ "$gitaly_status" != "0" ]; }; then echo "GitLab is not running." return fi @@ -383,7 +427,14 @@ print_status() { printf "The GitLab Pages is \033[31mnot running\033[0m.\n" fi fi - if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && [ "$gitlab_workhorse_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" = "0" ]; }; then + if [ "$gitaly_enabled" = true ]; then + if [ "$gitaly_status" = "0" ]; then + echo "Gitaly with pid $gapid is running." + else + printf "Gitaly is \033[31mnot running\033[0m.\n" + fi + fi + if [ "$web_status" = "0" ] && [ "$sidekiq_status" = "0" ] && [ "$gitlab_workhorse_status" = "0" ] && { [ "$mail_room_enabled" != true ] || [ "$mail_room_status" = "0" ]; } && { [ "$gitlab_pages_enabled" != true ] || [ "$gitlab_pages_status" = "0" ]; } && { [ "$gitaly_enabled" != true ] || [ "$gitaly_status" = "0" ]; }; then printf "GitLab and all its components are \033[32mup and running\033[0m.\n" fi } @@ -414,7 +465,7 @@ reload_gitlab(){ ## Restarts Sidekiq and Unicorn. restart_gitlab(){ check_status - if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; } || { [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" = "0" ]; }; then + if [ "$web_status" = "0" ] || [ "$sidekiq_status" = "0" ] || [ "$gitlab_workhorse" = "0" ] || { [ "$mail_room_enabled" = true ] && [ "$mail_room_status" = "0" ]; } || { [ "$gitlab_pages_enabled" = true ] && [ "$gitlab_pages_status" = "0" ]; } || { [ "$gitaly_enabled" = true ] && [ "$gitaly_status" = "0" ]; }; then stop_gitlab fi start_gitlab diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index f6642527639..9472c3c992f 100644 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -84,3 +84,7 @@ mail_room_pid_path="$pid_path/mail_room.pid" # shell other than "bash" # The default is "/bin/bash" shell_path="/bin/bash" + +# This variable controls whether the init script starts/stops Gitaly +gitaly_enabled=false +gitaly_log="$app_root/log/gitaly.log" diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake index 098f9851b45..003d57adbbd 100644 --- a/lib/tasks/gitlab/assets.rake +++ b/lib/tasks/gitlab/assets.rake @@ -3,16 +3,16 @@ namespace :gitlab do desc 'GitLab | Assets | Compile all frontend assets' task compile: [ 'yarn:check', - 'assets:precompile', + 'rake:assets:precompile', 'webpack:compile', - 'gitlab:assets:fix_urls' + 'fix_urls' ] desc 'GitLab | Assets | Clean up old compiled frontend assets' - task clean: ['assets:clean'] + task clean: ['rake:assets:clean'] desc 'GitLab | Assets | Remove all compiled frontend assets' - task purge: ['assets:clobber'] + task purge: ['rake:assets:clobber'] desc 'GitLab | Assets | Uninstall frontend dependencies' task purge_modules: ['yarn:clobber'] diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index a6f8c4ced5d..a9a48f7188f 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -617,7 +617,7 @@ namespace :gitlab do end def sidekiq_process_count - ps_ux, _ = Gitlab::Popen.popen(%w(ps ux)) + ps_ux, _ = Gitlab::Popen.popen(%w(ps uxww)) ps_ux.scan(/sidekiq \d+\.\d+\.\d+/).count end end @@ -751,7 +751,7 @@ namespace :gitlab do end def mail_room_running? - ps_ux, _ = Gitlab::Popen.popen(%w(ps ux)) + ps_ux, _ = Gitlab::Popen.popen(%w(ps uxww)) ps_ux.include?("mail_room") end end diff --git a/lib/tasks/gitlab/gitaly.rake b/lib/tasks/gitlab/gitaly.rake new file mode 100644 index 00000000000..c288e17ac8d --- /dev/null +++ b/lib/tasks/gitlab/gitaly.rake @@ -0,0 +1,23 @@ +namespace :gitlab do + namespace :gitaly do + desc "GitLab | Install or upgrade gitaly" + task :install, [:dir] => :environment do |t, args| + warn_user_is_not_gitlab + unless args.dir.present? + abort %(Please specify the directory where you want to install gitaly:\n rake "gitlab:gitaly:install[/home/git/gitaly]") + end + + tag = "v#{Gitlab::GitalyClient.expected_server_version}" + repo = 'https://gitlab.com/gitlab-org/gitaly.git' + + checkout_or_clone_tag(tag: tag, repo: repo, target_dir: args.dir) + + _, status = Gitlab::Popen.popen(%w[which gmake]) + command = status.zero? ? 'gmake' : 'make' + + Dir.chdir(args.dir) do + run_command!([command]) + end + end + end +end diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb index bb755ae689b..cdba2262bc2 100644 --- a/lib/tasks/gitlab/task_helpers.rb +++ b/lib/tasks/gitlab/task_helpers.rb @@ -81,7 +81,7 @@ module Gitlab def run_command!(command) output, status = Gitlab::Popen.popen(command) - raise Gitlab::TaskFailedError unless status.zero? + raise Gitlab::TaskFailedError.new(output) unless status.zero? output end diff --git a/lib/tasks/karma.rake b/lib/tasks/karma.rake index 40465ea3bf0..62a12174efa 100644 --- a/lib/tasks/karma.rake +++ b/lib/tasks/karma.rake @@ -1,9 +1,10 @@ unless Rails.env.production? namespace :karma do desc 'GitLab | Karma | Generate fixtures for JavaScript tests' - RSpec::Core::RakeTask.new(:fixtures) do |t| + RSpec::Core::RakeTask.new(:fixtures, [:pattern]) do |t, args| + args.with_defaults(pattern: 'spec/javascripts/fixtures/*.rb') ENV['NO_KNAPSACK'] = 'true' - t.pattern = 'spec/javascripts/fixtures/*.rb' + t.pattern = args[:pattern] t.rspec_opts = '--format documentation' end diff --git a/lib/tasks/migrate/setup_postgresql.rake b/lib/tasks/migrate/setup_postgresql.rake index f5caca3ddbf..8938bc515f5 100644 --- a/lib/tasks/migrate/setup_postgresql.rake +++ b/lib/tasks/migrate/setup_postgresql.rake @@ -3,10 +3,12 @@ require Rails.root.join('lib/gitlab/database/migration_helpers') require Rails.root.join('db/migrate/20151007120511_namespaces_projects_path_lower_indexes') require Rails.root.join('db/migrate/20151008110232_add_users_lower_username_email_indexes') require Rails.root.join('db/migrate/20161212142807_add_lower_path_index_to_routes') +require Rails.root.join('db/migrate/20170317203554_index_routes_path_for_like') desc 'GitLab | Sets up PostgreSQL' task setup_postgresql: :environment do NamespacesProjectsPathLowerIndexes.new.up AddUsersLowerUsernameEmailIndexes.new.up AddLowerPathIndexToRoutes.new.up + IndexRoutesPathForLike.new.up end diff --git a/lib/tasks/tokens.rake b/lib/tasks/tokens.rake new file mode 100644 index 00000000000..95735f43802 --- /dev/null +++ b/lib/tasks/tokens.rake @@ -0,0 +1,38 @@ +require_relative '../../app/models/concerns/token_authenticatable.rb' + +namespace :tokens do + desc "Reset all GitLab user auth tokens" + task reset_all_auth: :environment do + reset_all_users_token(:reset_authentication_token!) + end + + desc "Reset all GitLab email tokens" + task reset_all_email: :environment do + reset_all_users_token(:reset_incoming_email_token!) + end + + def reset_all_users_token(reset_token_method) + TmpUser.find_in_batches do |batch| + puts "Processing batch starting with user ID: #{batch.first.id}" + STDOUT.flush + + batch.each(&reset_token_method) + end + end +end + +class TmpUser < ActiveRecord::Base + include TokenAuthenticatable + + self.table_name = 'users' + + def reset_authentication_token! + write_new_token(:authentication_token) + save!(validate: false) + end + + def reset_incoming_email_token! + write_new_token(:incoming_email_token) + save!(validate: false) + end +end |