diff options
Diffstat (limited to 'lib')
33 files changed, 524 insertions, 166 deletions
diff --git a/lib/api/api.rb b/lib/api/api.rb index 5e93c129bc8..62ffebeacb0 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -136,6 +136,7 @@ module API mount ::API::MergeRequests mount ::API::Namespaces mount ::API::Notes + mount ::API::Discussions mount ::API::NotificationSettings mount ::API::PagesDomains mount ::API::Pipelines diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb new file mode 100644 index 00000000000..6abd575b6ad --- /dev/null +++ b/lib/api/discussions.rb @@ -0,0 +1,195 @@ +module API + class Discussions < Grape::API + include PaginationParams + helpers ::API::Helpers::NotesHelpers + + before { authenticate! } + + NOTEABLE_TYPES = [Issue, Snippet].freeze + + NOTEABLE_TYPES.each do |noteable_type| + parent_type = noteable_type.parent_class.to_s.underscore + noteables_str = noteable_type.to_s.underscore.pluralize + + params do + requires :id, type: String, desc: "The ID of a #{parent_type}" + end + resource parent_type.pluralize.to_sym, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do + desc "Get a list of #{noteable_type.to_s.downcase} discussions" do + success Entities::Discussion + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + use :pagination + end + get ":id/#{noteables_str}/:noteable_id/discussions" do + noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) + + return not_found!("Discussions") unless can?(current_user, noteable_read_ability_name(noteable), noteable) + + notes = noteable.notes + .inc_relations_for_view + .includes(:noteable) + .fresh + + notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) } + discussions = Kaminari.paginate_array(Discussion.build_collection(notes, noteable)) + + present paginate(discussions), with: Entities::Discussion + end + + desc "Get a single #{noteable_type.to_s.downcase} discussion" do + success Entities::Discussion + end + params do + requires :discussion_id, type: String, desc: 'The ID of a discussion' + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + end + get ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id" do + noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) + notes = readable_discussion_notes(noteable, params[:discussion_id]) + + if notes.empty? || !can?(current_user, noteable_read_ability_name(noteable), noteable) + return not_found!("Discussion") + end + + discussion = Discussion.build(notes, noteable) + + present discussion, with: Entities::Discussion + end + + desc "Create a new #{noteable_type.to_s.downcase} discussion" do + success Entities::Discussion + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :body, type: String, desc: 'The content of a note' + optional :created_at, type: String, desc: 'The creation date of the note' + end + post ":id/#{noteables_str}/:noteable_id/discussions" do + noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) + + opts = { + note: params[:body], + created_at: params[:created_at], + type: 'DiscussionNote', + noteable_type: noteables_str.classify, + noteable_id: noteable.id + } + + note = create_note(noteable, opts) + + if note.valid? + present note.discussion, with: Entities::Discussion + else + bad_request!("Note #{note.errors.messages}") + end + end + + desc "Get comments in a single #{noteable_type.to_s.downcase} discussion" do + success Entities::Discussion + end + params do + requires :discussion_id, type: String, desc: 'The ID of a discussion' + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + end + get ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes" do + noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) + notes = readable_discussion_notes(noteable, params[:discussion_id]) + + if notes.empty? || !can?(current_user, noteable_read_ability_name(noteable), noteable) + return not_found!("Notes") + end + + present notes, with: Entities::Note + end + + desc "Add a comment to a #{noteable_type.to_s.downcase} discussion" do + success Entities::Note + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :discussion_id, type: String, desc: 'The ID of a discussion' + requires :body, type: String, desc: 'The content of a note' + optional :created_at, type: String, desc: 'The creation date of the note' + end + post ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes" do + noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) + notes = readable_discussion_notes(noteable, params[:discussion_id]) + + return not_found!("Discussion") if notes.empty? + return bad_request!("Discussion is an individual note.") unless notes.first.part_of_discussion? + + opts = { + note: params[:body], + type: 'DiscussionNote', + in_reply_to_discussion_id: params[:discussion_id], + created_at: params[:created_at] + } + note = create_note(noteable, opts) + + if note.valid? + present note, with: Entities::Note + else + bad_request!("Note #{note.errors.messages}") + end + end + + desc "Get a comment in a #{noteable_type.to_s.downcase} discussion" do + success Entities::Note + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :discussion_id, type: String, desc: 'The ID of a discussion' + requires :note_id, type: Integer, desc: 'The ID of a note' + end + get ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes/:note_id" do + noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) + + get_note(noteable, params[:note_id]) + end + + desc "Edit a comment in a #{noteable_type.to_s.downcase} discussion" do + success Entities::Note + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :discussion_id, type: String, desc: 'The ID of a discussion' + requires :note_id, type: Integer, desc: 'The ID of a note' + requires :body, type: String, desc: 'The content of a note' + end + put ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes/:note_id" do + noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) + + update_note(noteable, params[:note_id]) + end + + desc "Delete a comment in a #{noteable_type.to_s.downcase} discussion" do + success Entities::Note + end + params do + requires :noteable_id, type: Integer, desc: 'The ID of the noteable' + requires :discussion_id, type: String, desc: 'The ID of a discussion' + requires :note_id, type: Integer, desc: 'The ID of a note' + end + delete ":id/#{noteables_str}/:noteable_id/discussions/:discussion_id/notes/:note_id" do + noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) + + delete_note(noteable, params[:note_id]) + end + end + end + + helpers do + def readable_discussion_notes(noteable, discussion_id) + notes = noteable.notes + .where(discussion_id: discussion_id) + .inc_relations_for_view + .includes(:noteable) + .fresh + + notes.reject { |n| n.cross_reference_not_visible_for?(current_user) } + end + end + end +end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index f39906270d8..16147ee90c9 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -547,6 +547,7 @@ module API expose :discussion_locked expose :should_remove_source_branch?, as: :should_remove_source_branch expose :force_remove_source_branch?, as: :force_remove_source_branch + expose :allow_maintainer_to_push, if: -> (merge_request, _) { merge_request.for_fork? } expose :web_url do |merge_request, options| Gitlab::UrlBuilder.build(merge_request) @@ -644,6 +645,7 @@ module API NOTEABLE_TYPES_WITH_IID = %w(Issue MergeRequest).freeze expose :id + expose :type expose :note, as: :body expose :attachment_identifier, as: :attachment expose :author, using: Entities::UserBasic @@ -655,6 +657,12 @@ module API expose(:noteable_iid) { |note| note.noteable.iid if NOTEABLE_TYPES_WITH_IID.include?(note.noteable_type) } end + class Discussion < Grape::Entity + expose :id + expose :individual_note?, as: :individual_note + expose :notes, using: Entities::Note + end + class AwardEmoji < Grape::Entity expose :id expose :name diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb index cd59da6fc70..4b564cfdef2 100644 --- a/lib/api/helpers/internal_helpers.rb +++ b/lib/api/helpers/internal_helpers.rb @@ -111,13 +111,6 @@ module API def gitaly_payload(action) return unless %w[git-receive-pack git-upload-pack].include?(action) - if action == 'git-receive-pack' - return unless Gitlab::GitalyClient.feature_enabled?( - :ssh_receive_pack, - status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT - ) - end - { repository: repository.gitaly_repository, address: Gitlab::GitalyClient.address(project.repository_storage), diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb new file mode 100644 index 00000000000..cd91df1ecd8 --- /dev/null +++ b/lib/api/helpers/notes_helpers.rb @@ -0,0 +1,76 @@ +module API + module Helpers + module NotesHelpers + def update_note(noteable, note_id) + note = noteable.notes.find(params[:note_id]) + + authorize! :admin_note, note + + opts = { + note: params[:body] + } + parent = noteable_parent(noteable) + project = parent if parent.is_a?(Project) + + note = ::Notes::UpdateService.new(project, current_user, opts).execute(note) + + if note.valid? + present note, with: Entities::Note + else + bad_request!("Failed to save note #{note.errors.messages}") + end + end + + def delete_note(noteable, note_id) + note = noteable.notes.find(note_id) + + authorize! :admin_note, note + + parent = noteable_parent(noteable) + project = parent if parent.is_a?(Project) + destroy_conditionally!(note) do |note| + ::Notes::DestroyService.new(project, current_user).execute(note) + end + end + + def get_note(noteable, note_id) + note = noteable.notes.with_metadata.find(params[:note_id]) + can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user) + + if can_read_note + present note, with: Entities::Note + else + not_found!("Note") + end + end + + def noteable_read_ability_name(noteable) + "read_#{noteable.class.to_s.underscore}".to_sym + end + + def find_noteable(parent, noteables_str, noteable_id) + public_send("find_#{parent}_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend + end + + def noteable_parent(noteable) + public_send("user_#{noteable.class.parent_class.to_s.underscore}") # rubocop:disable GitlabSecurity/PublicSend + end + + def create_note(noteable, opts) + noteables_str = noteable.model_name.to_s.underscore.pluralize + + return not_found!(noteables_str) unless can?(current_user, noteable_read_ability_name(noteable), noteable) + + authorize! :create_note, noteable + + parent = noteable_parent(noteable) + if opts[:created_at] + opts.delete(:created_at) unless current_user.admin? || parent.owner == current_user + end + + project = parent if parent.is_a?(Project) + ::Notes::CreateService.new(project, current_user, opts).execute + end + end + end +end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index ead1bb7957b..3264a26f7d2 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -144,6 +144,7 @@ module API optional :milestone_id, type: Integer, desc: 'The ID of a milestone to assign the merge request' optional :labels, type: String, desc: 'Comma-separated list of label names' optional :remove_source_branch, type: Boolean, desc: 'Remove source branch when merging' + optional :allow_maintainer_to_push, type: Boolean, desc: 'Whether a maintainer of the target project can push to the source project' use :optional_params_ee end diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 3588dc85c9e..69f1df6b341 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -1,19 +1,23 @@ module API class Notes < Grape::API include PaginationParams + helpers ::API::Helpers::NotesHelpers before { authenticate! } NOTEABLE_TYPES = [Issue, MergeRequest, Snippet].freeze - params do - requires :id, type: String, desc: 'The ID of a project' - end - resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do - NOTEABLE_TYPES.each do |noteable_type| + NOTEABLE_TYPES.each do |noteable_type| + parent_type = noteable_type.parent_class.to_s.underscore + noteables_str = noteable_type.to_s.underscore.pluralize + + params do + requires :id, type: String, desc: "The ID of a #{parent_type}" + end + resource parent_type.pluralize.to_sym, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do noteables_str = noteable_type.to_s.underscore.pluralize - desc 'Get a list of project +noteable+ notes' do + desc "Get a list of #{noteable_type.to_s.downcase} notes" do success Entities::Note end params do @@ -25,7 +29,7 @@ module API use :pagination end get ":id/#{noteables_str}/:noteable_id/notes" do - noteable = find_project_noteable(noteables_str, params[:noteable_id]) + noteable = find_noteable(parent_type, 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 @@ -46,7 +50,7 @@ module API end end - desc 'Get a single +noteable+ note' do + desc "Get a single #{noteable_type.to_s.downcase} note" do success Entities::Note end params do @@ -54,18 +58,11 @@ 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 = find_project_noteable(noteables_str, params[:noteable_id]) - note = noteable.notes.with_metadata.find(params[:note_id]) - can_read_note = can?(current_user, noteable_read_ability_name(noteable), noteable) && !note.cross_reference_not_visible_for?(current_user) - - if can_read_note - present note, with: Entities::Note - else - not_found!("Note") - end + noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) + get_note(noteable, params[:note_id]) end - desc 'Create a new +noteable+ note' do + desc "Create a new #{noteable_type.to_s.downcase} note" do success Entities::Note end params do @@ -74,34 +71,25 @@ 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]) + noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) opts = { note: params[:body], noteable_type: noteables_str.classify, - noteable_id: noteable.id + noteable_id: noteable.id, + created_at: params[:created_at] } - if can?(current_user, noteable_read_ability_name(noteable), noteable) - authorize! :create_note, noteable + note = create_note(noteable, opts) - if params[:created_at] && (current_user.admin? || user_project.owner == current_user) - opts[:created_at] = params[:created_at] - end - - note = ::Notes::CreateService.new(user_project, current_user, opts).execute - - if note.valid? - present note, with: Entities.const_get(note.class.name) - else - not_found!("Note #{note.errors.messages}") - end + if note.valid? + present note, with: Entities.const_get(note.class.name) else - not_found!("Note") + bad_request!("Note #{note.errors.messages}") end end - desc 'Update an existing +noteable+ note' do + desc "Update an existing #{noteable_type.to_s.downcase} note" do success Entities::Note end params do @@ -110,24 +98,12 @@ module API requires :body, type: String, desc: 'The content of a note' end put ":id/#{noteables_str}/:noteable_id/notes/:note_id" do - note = user_project.notes.find(params[:note_id]) + noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) - authorize! :admin_note, note - - opts = { - note: params[:body] - } - - note = ::Notes::UpdateService.new(user_project, current_user, opts).execute(note) - - if note.valid? - present note, with: Entities::Note - else - render_api_error!("Failed to save note #{note.errors.messages}", 400) - end + update_note(noteable, params[:note_id]) end - desc 'Delete a +noteable+ note' do + desc "Delete a #{noteable_type.to_s.downcase} note" do success Entities::Note end params do @@ -135,25 +111,11 @@ module API requires :note_id, type: Integer, desc: 'The ID of a note' end delete ":id/#{noteables_str}/:noteable_id/notes/:note_id" do - note = user_project.notes.find(params[:note_id]) - - authorize! :admin_note, note + noteable = find_noteable(parent_type, noteables_str, params[:noteable_id]) - destroy_conditionally!(note) do |note| - ::Notes::DestroyService.new(user_project, current_user).execute(note) - end + delete_note(noteable, params[:note_id]) end end end - - helpers do - def find_project_noteable(noteables_str, noteable_id) - public_send("find_project_#{noteables_str.singularize}", noteable_id) # rubocop:disable GitlabSecurity/PublicSend - end - - def noteable_read_ability_name(noteable) - "read_#{noteable.class.to_s.underscore}".to_sym - end - end end end diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb index eedb95197aa..43bf4fc6565 100644 --- a/lib/banzai/filter/commit_reference_filter.rb +++ b/lib/banzai/filter/commit_reference_filter.rb @@ -18,7 +18,8 @@ module Banzai def find_object(project, id) if project && project.valid_repo? - project.commit(id) + # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/43894 + Gitlab::GitalyClient.allow_n_plus_1_calls { project.commit(id) } end end diff --git a/lib/banzai/filter/markdown_engines/common_mark.rb b/lib/banzai/filter/markdown_engines/common_mark.rb new file mode 100644 index 00000000000..bc9597df894 --- /dev/null +++ b/lib/banzai/filter/markdown_engines/common_mark.rb @@ -0,0 +1,45 @@ +# `CommonMark` markdown engine for GitLab's Banzai markdown filter. +# This module is used in Banzai::Filter::MarkdownFilter. +# Used gem is `commonmarker` which is a ruby wrapper for libcmark (CommonMark parser) +# including GitHub's GFM extensions. +# Homepage: https://github.com/gjtorikian/commonmarker + +module Banzai + module Filter + module MarkdownEngines + class CommonMark + EXTENSIONS = [ + :autolink, # provides support for automatically converting URLs to anchor tags. + :strikethrough, # provides support for strikethroughs. + :table, # provides support for tables. + :tagfilter # strips out several "unsafe" HTML tags from being used: https://github.github.com/gfm/#disallowed-raw-html-extension- + ].freeze + + PARSE_OPTIONS = [ + :FOOTNOTES, # parse footnotes. + :STRIKETHROUGH_DOUBLE_TILDE, # parse strikethroughs by double tildes (as redcarpet does). + :VALIDATE_UTF8 # replace illegal sequences with the replacement character U+FFFD. + ].freeze + + # The `:GITHUB_PRE_LANG` option is not used intentionally because + # it renders a fence block with language as `<pre lang="LANG"><code>some code\n</code></pre>` + # while GitLab's syntax is `<pre><code lang="LANG">some code\n</code></pre>`. + # If in the future the syntax is about to be made GitHub-compatible, please, add `:GITHUB_PRE_LANG` render option below + # and remove `code_block` method from `lib/banzai/renderer/common_mark/html.rb`. + RENDER_OPTIONS = [ + :DEFAULT # default rendering system. Nothing special. + ].freeze + + def initialize + @renderer = Banzai::Renderer::CommonMark::HTML.new(options: RENDER_OPTIONS) + end + + def render(text) + doc = CommonMarker.render_doc(text, PARSE_OPTIONS, EXTENSIONS) + + @renderer.render(doc) + end + end + end + end +end diff --git a/lib/banzai/filter/markdown_engines/redcarpet.rb b/lib/banzai/filter/markdown_engines/redcarpet.rb new file mode 100644 index 00000000000..ac99941fefa --- /dev/null +++ b/lib/banzai/filter/markdown_engines/redcarpet.rb @@ -0,0 +1,32 @@ +# `Redcarpet` markdown engine for GitLab's Banzai markdown filter. +# This module is used in Banzai::Filter::MarkdownFilter. +# Used gem is `redcarpet` which is a ruby library for markdown processing. +# Homepage: https://github.com/vmg/redcarpet + +module Banzai + module Filter + module MarkdownEngines + class Redcarpet + OPTIONS = { + fenced_code_blocks: true, + footnotes: true, + lax_spacing: true, + no_intra_emphasis: true, + space_after_headers: true, + strikethrough: true, + superscript: true, + tables: true + }.freeze + + def initialize + html_renderer = Banzai::Renderer::Redcarpet::HTML.new + @renderer = ::Redcarpet::Markdown.new(html_renderer, OPTIONS) + end + + def render(text) + @renderer.render(text) + end + end + end + end +end diff --git a/lib/banzai/filter/markdown_filter.rb b/lib/banzai/filter/markdown_filter.rb index 9cac303e645..c1e2b680240 100644 --- a/lib/banzai/filter/markdown_filter.rb +++ b/lib/banzai/filter/markdown_filter.rb @@ -1,34 +1,31 @@ module Banzai module Filter class MarkdownFilter < HTML::Pipeline::TextFilter - # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use - REDCARPET_OPTIONS = { - fenced_code_blocks: true, - footnotes: true, - lax_spacing: true, - no_intra_emphasis: true, - space_after_headers: true, - strikethrough: true, - superscript: true, - tables: true - }.freeze - def initialize(text, context = nil, result = nil) - super text, context, result - @text = @text.delete "\r" + super(text, context, result) + + @renderer = renderer(context[:markdown_engine]).new + @text = @text.delete("\r") end def call - html = self.class.renderer.render(@text) - html.rstrip! - html + @renderer.render(@text).rstrip + end + + private + + DEFAULT_ENGINE = :redcarpet + + def engine(engine_from_context) + engine_from_context ||= DEFAULT_ENGINE + + engine_from_context.to_s.classify end - def self.renderer - Thread.current[:banzai_markdown_renderer] ||= begin - renderer = Banzai::Renderer::HTML.new - Redcarpet::Markdown.new(renderer, REDCARPET_OPTIONS) - end + def renderer(engine_from_context) + "Banzai::Filter::MarkdownEngines::#{engine(engine_from_context)}".constantize + rescue NameError + raise NameError, "`#{engine_from_context}` is unknown markdown engine" end end end diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index 0ac7e231b5b..6dbf0d68fe8 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -1,3 +1,4 @@ +require 'rouge/plugins/common_mark' require 'rouge/plugins/redcarpet' module Banzai diff --git a/lib/banzai/renderer/common_mark/html.rb b/lib/banzai/renderer/common_mark/html.rb new file mode 100644 index 00000000000..c7a54629f31 --- /dev/null +++ b/lib/banzai/renderer/common_mark/html.rb @@ -0,0 +1,21 @@ +module Banzai + module Renderer + module CommonMark + class HTML < CommonMarker::HtmlRenderer + def code_block(node) + block do + code = node.string_content + lang = node.fence_info + lang_attr = lang.present? ? %Q{ lang="#{lang}"} : '' + result = + "<pre>" \ + "<code#{lang_attr}>#{html_escape(code)}</code>" \ + "</pre>" + + out(result) + end + end + end + end + end +end diff --git a/lib/banzai/renderer/html.rb b/lib/banzai/renderer/html.rb deleted file mode 100644 index 252caa35947..00000000000 --- a/lib/banzai/renderer/html.rb +++ /dev/null @@ -1,13 +0,0 @@ -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/banzai/renderer/redcarpet/html.rb b/lib/banzai/renderer/redcarpet/html.rb new file mode 100644 index 00000000000..94df5d8b1e1 --- /dev/null +++ b/lib/banzai/renderer/redcarpet/html.rb @@ -0,0 +1,15 @@ +module Banzai + module Renderer + module Redcarpet + 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 +end diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index d48ae17aeaf..bffbcb86137 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -135,7 +135,7 @@ module Gitlab if label.valid? @labels[label_params[:title]] = label else - raise "Failed to create label \"#{label_params[:title]}\" for project \"#{project.name_with_namespace}\"" + raise "Failed to create label \"#{label_params[:title]}\" for project \"#{project.full_name}\"" end end end diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index 3ce5f807989..51ba09aa129 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -47,7 +47,7 @@ module Gitlab protected def push_checks - if user_access.cannot_do_action?(:push_code) + unless can_push? raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_code] end end @@ -183,6 +183,11 @@ module Gitlab def commits @commits ||= project.repository.new_commits(newrev) end + + def can_push? + user_access.can_do_action?(:push_code) || + user_access.can_push_to_branch?(branch_name) + end end end end diff --git a/lib/gitlab/ci/charts.rb b/lib/gitlab/ci/charts.rb index 525563a97f5..46ed330dbbf 100644 --- a/lib/gitlab/ci/charts.rb +++ b/lib/gitlab/ci/charts.rb @@ -68,10 +68,11 @@ module Gitlab class YearChart < Chart include MonthlyInterval + attr_reader :to, :from def initialize(*) - @to = Date.today.end_of_month - @from = @to.years_ago(1).beginning_of_month + @to = Date.today.end_of_month.end_of_day + @from = @to.years_ago(1).beginning_of_month.beginning_of_day @format = '%d %B %Y' super @@ -80,10 +81,11 @@ module Gitlab class MonthChart < Chart include DailyInterval + attr_reader :to, :from def initialize(*) - @to = Date.today - @from = @to - 30.days + @to = Date.today.end_of_day + @from = 1.month.ago.beginning_of_day @format = '%d %B' super @@ -92,10 +94,11 @@ module Gitlab class WeekChart < Chart include DailyInterval + attr_reader :to, :from def initialize(*) - @to = Date.today - @from = @to - 7.days + @to = Date.today.end_of_day + @from = 1.week.ago.beginning_of_day @format = '%d %B' super diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb index 02d3763514e..d7369060cc5 100644 --- a/lib/gitlab/contributions_calendar.rb +++ b/lib/gitlab/contributions_calendar.rb @@ -23,7 +23,7 @@ module Gitlab mr_events = event_counts(date_from, :merge_requests) .having(action: [Event::MERGED, Event::CREATED, Event::CLOSED], target_type: "MergeRequest") note_events = event_counts(date_from, :merge_requests) - .having(action: [Event::COMMENTED], target_type: %w(Note DiffNote)) + .having(action: [Event::COMMENTED]) union = Gitlab::SQL::Union.new([repo_events, issue_events, mr_events, note_events]) events = Event.find_by_sql(union.to_sql).map(&:attributes) diff --git a/lib/gitlab/data_builder/build.rb b/lib/gitlab/data_builder/build.rb index 8e74e18a311..2f1445a050a 100644 --- a/lib/gitlab/data_builder/build.rb +++ b/lib/gitlab/data_builder/build.rb @@ -31,7 +31,7 @@ module Gitlab # TODO: do we still need it? project_id: project.id, - project_name: project.name_with_namespace, + project_name: project.full_name, user: { id: user.try(:id), diff --git a/lib/gitlab/data_builder/pipeline.rb b/lib/gitlab/data_builder/pipeline.rb index e47fb85b5ee..1e283cc092b 100644 --- a/lib/gitlab/data_builder/pipeline.rb +++ b/lib/gitlab/data_builder/pipeline.rb @@ -22,6 +22,7 @@ module Gitlab sha: pipeline.sha, before_sha: pipeline.before_sha, status: pipeline.status, + detailed_status: pipeline.detailed_status(nil).label, stages: pipeline.stages_names, created_at: pipeline.created_at, finished_at: pipeline.finished_at, diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index 594b6a9cbc5..93037ed8d90 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -347,7 +347,7 @@ module Gitlab # # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/324 def to_diff - Gitlab::GitalyClient.migrate(:commit_patch) do |is_enabled| + Gitlab::GitalyClient.migrate(:commit_patch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled @repository.gitaly_commit_client.patch(id) else diff --git a/lib/gitlab/git/gitlab_projects.rb b/lib/gitlab/git/gitlab_projects.rb index e5a747cb987..5e1e22ae65c 100644 --- a/lib/gitlab/git/gitlab_projects.rb +++ b/lib/gitlab/git/gitlab_projects.rb @@ -63,11 +63,12 @@ module Gitlab end end - def fetch_remote(name, timeout, force:, tags:, ssh_key: nil, known_hosts: nil) + def fetch_remote(name, timeout, force:, tags:, ssh_key: nil, known_hosts: nil, prune: true) tags_option = tags ? '--tags' : '--no-tags' logger.info "Fetching remote #{name} for repository #{repository_absolute_path}." - cmd = %W(git fetch #{name} --prune --quiet) + cmd = %W(git fetch #{name} --quiet) + cmd << '--prune' if prune cmd << '--force' if force cmd << tags_option diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 21c79a7a550..d4f6b543daf 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -228,7 +228,7 @@ module Gitlab end def has_local_branches? - gitaly_migrate(:has_local_branches) do |is_enabled| + gitaly_migrate(:has_local_branches, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_repository_client.has_local_branches? else @@ -715,7 +715,7 @@ module Gitlab end def add_branch(branch_name, user:, target:) - gitaly_migrate(:operation_user_create_branch) do |is_enabled| + gitaly_migrate(:operation_user_create_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_add_branch(branch_name, user, target) else @@ -725,7 +725,7 @@ module Gitlab end def add_tag(tag_name, user:, target:, message: nil) - gitaly_migrate(:operation_user_add_tag) do |is_enabled| + gitaly_migrate(:operation_user_add_tag, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_add_tag(tag_name, user: user, target: target, message: message) else @@ -735,7 +735,7 @@ module Gitlab end def rm_branch(branch_name, user:) - gitaly_migrate(:operation_user_delete_branch) do |is_enabled| + gitaly_migrate(:operation_user_delete_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_operations_client.user_delete_branch(branch_name, user) else @@ -810,7 +810,7 @@ module Gitlab end def revert(user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) - gitaly_migrate(:revert) do |is_enabled| + gitaly_migrate(:revert, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| args = { user: user, commit: commit, @@ -876,7 +876,7 @@ module Gitlab # Delete the specified branch from the repository def delete_branch(branch_name) - gitaly_migrate(:delete_branch) do |is_enabled| + gitaly_migrate(:delete_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_ref_client.delete_branch(branch_name) else @@ -903,7 +903,7 @@ module Gitlab # create_branch("feature") # create_branch("other-feature", "master") def create_branch(ref, start_point = "HEAD") - gitaly_migrate(:create_branch) do |is_enabled| + gitaly_migrate(:create_branch, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_ref_client.create_branch(ref, start_point) else @@ -1010,7 +1010,7 @@ module Gitlab end def languages(ref = nil) - Gitlab::GitalyClient.migrate(:commit_languages) do |is_enabled| + gitaly_migrate(:commit_languages, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT) do |is_enabled| if is_enabled gitaly_commit_client.languages(ref) else @@ -1443,12 +1443,12 @@ module Gitlab end end - def last_commit_id_for_path(sha, path) + def last_commit_for_path(sha, path) gitaly_migrate(:last_commit_for_path) do |is_enabled| if is_enabled - last_commit_for_path_by_gitaly(sha, path).id + last_commit_for_path_by_gitaly(sha, path) else - last_commit_id_for_path_by_shelling_out(sha, path) + last_commit_for_path_by_rugged(sha, path) end end end @@ -1896,7 +1896,7 @@ module Gitlab end def last_commit_for_path_by_rugged(sha, path) - sha = last_commit_id_for_path(sha, path) + sha = last_commit_id_for_path_by_shelling_out(sha, path) commit(sha) end diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb index fdb3247cf4d..e1bc2f9ab61 100644 --- a/lib/gitlab/gitaly_client/repository_service.rb +++ b/lib/gitlab/gitaly_client/repository_service.rb @@ -45,10 +45,10 @@ module Gitlab GitalyClient.call(@storage, :repository_service, :apply_gitattributes, request) end - def fetch_remote(remote, ssh_auth:, forced:, no_tags:, timeout:) + def fetch_remote(remote, ssh_auth:, forced:, no_tags:, timeout:, prune: true) request = Gitaly::FetchRemoteRequest.new( repository: @gitaly_repo, remote: remote, force: forced, - no_tags: no_tags, timeout: timeout + no_tags: no_tags, timeout: timeout, no_prune: !prune ) if ssh_auth&.ssh_import? diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 4ba44e0feef..dda7afc0999 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -125,13 +125,13 @@ module Gitlab # Ex. # fetch_remote(my_repo, "upstream") # - def fetch_remote(repository, remote, ssh_auth: nil, forced: false, no_tags: false) + def fetch_remote(repository, remote, ssh_auth: nil, forced: false, no_tags: false, prune: true) gitaly_migrate(:fetch_remote) do |is_enabled| if is_enabled - repository.gitaly_repository_client.fetch_remote(remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags, timeout: git_timeout) + repository.gitaly_repository_client.fetch_remote(remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags, timeout: git_timeout, prune: prune) else storage_path = Gitlab.config.repositories.storages[repository.storage]["path"] - local_fetch_remote(storage_path, repository.relative_path, remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags) + local_fetch_remote(storage_path, repository.relative_path, remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags, prune: prune) end end end @@ -428,8 +428,8 @@ module Gitlab ) end - def local_fetch_remote(storage_path, repository_relative_path, remote, ssh_auth: nil, forced: false, no_tags: false) - vars = { force: forced, tags: !no_tags } + def local_fetch_remote(storage_path, repository_relative_path, remote, ssh_auth: nil, forced: false, no_tags: false, prune: true) + vars = { force: forced, tags: !no_tags, prune: prune } if ssh_auth&.ssh_import? if ssh_auth.ssh_key_auth? && ssh_auth.ssh_private_key.present? diff --git a/lib/gitlab/slash_commands/presenters/issue_new.rb b/lib/gitlab/slash_commands/presenters/issue_new.rb index 86490a39cc1..5964bfe9960 100644 --- a/lib/gitlab/slash_commands/presenters/issue_new.rb +++ b/lib/gitlab/slash_commands/presenters/issue_new.rb @@ -38,7 +38,7 @@ module Gitlab end def project_link - "[#{project.name_with_namespace}](#{project.web_url})" + "[#{project.full_name}](#{project.web_url})" end def author_profile_link diff --git a/lib/gitlab/slash_commands/presenters/issue_show.rb b/lib/gitlab/slash_commands/presenters/issue_show.rb index c99316df667..562f15f403c 100644 --- a/lib/gitlab/slash_commands/presenters/issue_show.rb +++ b/lib/gitlab/slash_commands/presenters/issue_show.rb @@ -53,7 +53,7 @@ module Gitlab end def pretext - "Issue *#{@resource.to_reference}* from #{project.name_with_namespace}" + "Issue *#{@resource.to_reference}* from #{project.full_name}" end end end diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb index 91b8bb2a83f..24393f96d96 100644 --- a/lib/gitlab/user_access.rb +++ b/lib/gitlab/user_access.rb @@ -63,13 +63,12 @@ module Gitlab request_cache def can_push_to_branch?(ref) return false unless can_access_git? + return false unless user.can?(:push_code, project) || project.branch_allows_maintainer_push?(user, ref) if protected?(ProtectedBranch, project, ref) - return true if project.user_can_push_to_empty_repo?(user) - - protected_branch_accessible_to?(ref, action: :push) + project.user_can_push_to_empty_repo?(user) || protected_branch_accessible_to?(ref, action: :push) else - user.can?(:push_code, project) + true end end diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb index fa22f0e37b2..dc9391f32cf 100644 --- a/lib/gitlab/utils.rb +++ b/lib/gitlab/utils.rb @@ -67,5 +67,13 @@ module Gitlab nil end + + # Used in EE + # Accepts either an Array or a String and returns an array + def ensure_array_from_string(string_or_array) + return string_or_array if string_or_array.is_a?(Array) + + string_or_array.split(',').map(&:strip) + end end end diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 823df67ea39..0b0d667d4fd 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -10,6 +10,7 @@ module Gitlab INTERNAL_API_CONTENT_TYPE = 'application/vnd.gitlab-workhorse+json'.freeze INTERNAL_API_REQUEST_HEADER = 'Gitlab-Workhorse-Api-Request'.freeze NOTIFICATION_CHANNEL = 'workhorse:notifications'.freeze + ALLOWED_GIT_HTTP_ACTIONS = %w[git_receive_pack git_upload_pack info_refs].freeze # Supposedly the effective key size for HMAC-SHA256 is 256 bits, i.e. 32 # bytes https://tools.ietf.org/html/rfc4868#section-2.6 @@ -17,6 +18,8 @@ module Gitlab class << self def git_http_ok(repository, is_wiki, user, action, show_all_refs: false) + raise "Unsupported action: #{action}" unless ALLOWED_GIT_HTTP_ACTIONS.include?(action.to_s) + project = repository.project repo_path = repository.path_to_repo params = { @@ -31,24 +34,7 @@ module Gitlab token: Gitlab::GitalyClient.token(project.repository_storage) } params[:Repository] = repository.gitaly_repository.to_h - - feature_enabled = case action.to_s - when 'git_receive_pack' - Gitlab::GitalyClient.feature_enabled?( - :post_receive_pack, - status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT - ) - when 'git_upload_pack' - true - when 'info_refs' - true - else - raise "Unsupported action: #{action}" - end - - if feature_enabled - params[:GitalyServer] = server - end + params[:GitalyServer] = server params end diff --git a/lib/rouge/plugins/common_mark.rb b/lib/rouge/plugins/common_mark.rb new file mode 100644 index 00000000000..8f9de061124 --- /dev/null +++ b/lib/rouge/plugins/common_mark.rb @@ -0,0 +1,20 @@ +# A rouge plugin for CommonMark markdown engine. +# Used to highlight code generated by CommonMark. + +module Rouge + module Plugins + module CommonMark + def code_block(code, language) + lexer = Lexer.find_fancy(language, code) || Lexers::PlainText + + formatter = rouge_formatter(lexer) + formatter.format(lexer.lex(code)) + end + + # override this method for custom formatting behavior + def rouge_formatter(lexer) + Formatters::HTMLLegacy.new(css_class: "highlight #{lexer.tag}") + end + end + end +end diff --git a/lib/system_check/helpers.rb b/lib/system_check/helpers.rb index 914ed794601..6227e461d24 100644 --- a/lib/system_check/helpers.rb +++ b/lib/system_check/helpers.rb @@ -50,7 +50,7 @@ module SystemCheck if should_sanitize? "#{project.namespace_id.to_s.color(:yellow)}/#{project.id.to_s.color(:yellow)} ... " else - "#{project.name_with_namespace.color(:yellow)} ... " + "#{project.full_name.color(:yellow)} ... " end end |