Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/api/projects.rb21
-rw-r--r--lib/api/protected_branches.rb18
-rw-r--r--lib/backup/repository.rb9
-rw-r--r--lib/banzai/cross_project_reference.rb2
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb98
-rw-r--r--lib/banzai/filter/commit_reference_filter.rb34
-rw-r--r--lib/banzai/filter/epic_reference_filter.rb12
-rw-r--r--lib/banzai/filter/issuable_reference_filter.rb31
-rw-r--r--lib/banzai/filter/issue_reference_filter.rb32
-rw-r--r--lib/banzai/filter/label_reference_filter.rb4
-rw-r--r--lib/banzai/filter/merge_request_reference_filter.rb37
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb2
-rw-r--r--lib/banzai/filter/upload_link_filter.rb18
-rw-r--r--lib/banzai/issuable_extractor.rb4
-rw-r--r--lib/banzai/object_renderer.rb12
-rw-r--r--lib/banzai/reference_parser/epic_parser.rb12
-rw-r--r--lib/banzai/reference_parser/issuable_parser.rb25
-rw-r--r--lib/banzai/reference_parser/issue_parser.rb12
-rw-r--r--lib/banzai/reference_parser/merge_request_parser.rb24
-rw-r--r--lib/gitlab/background_migration/populate_untracked_uploads.rb259
-rw-r--r--lib/gitlab/background_migration/prepare_untracked_uploads.rb163
-rw-r--r--lib/gitlab/ci/pipeline/chain/create.rb16
-rw-r--r--lib/gitlab/database.rb10
-rw-r--r--lib/gitlab/diff/diff_refs.rb22
-rw-r--r--lib/gitlab/git.rb12
-rw-r--r--lib/gitlab/git/commit.rb1
-rw-r--r--lib/gitlab/git/operation_service.rb2
-rw-r--r--lib/gitlab/git/remote_repository.rb6
-rw-r--r--lib/gitlab/git/repository.rb32
-rw-r--r--lib/gitlab/git_access.rb6
-rw-r--r--lib/gitlab/git_access_wiki.rb6
-rw-r--r--lib/gitlab/gitaly_client.rb8
-rw-r--r--lib/gitlab/gitaly_client/wiki_service.rb10
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb6
-rw-r--r--lib/gitlab/reference_extractor.rb2
-rw-r--r--lib/gitlab/utils.rb17
36 files changed, 758 insertions, 227 deletions
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 14a4fc6f025..fa222bf2b1c 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -367,15 +367,16 @@ module API
post ":id/fork/:forked_from_id" do
authenticated_as_admin!
- forked_from_project = find_project!(params[:forked_from_id])
- not_found!("Source Project") unless forked_from_project
+ fork_from_project = find_project!(params[:forked_from_id])
- if user_project.forked_from_project.nil?
- user_project.create_forked_project_link(forked_to_project_id: user_project.id, forked_from_project_id: forked_from_project.id)
+ not_found!("Source Project") unless fork_from_project
- ::Projects::ForksCountService.new(forked_from_project).refresh_cache
+ result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project)
+
+ if result
+ present user_project.reload, with: Entities::Project
else
- render_api_error!("Project already forked", 409)
+ render_api_error!("Project already forked", 409) if user_project.forked?
end
end
@@ -383,11 +384,11 @@ module API
delete ":id/fork" do
authorize! :remove_fork_project, user_project
- if user_project.forked?
- destroy_conditionally!(user_project.forked_project_link)
- else
- not_modified!
+ result = destroy_conditionally!(user_project) do
+ ::Projects::UnlinkForkService.new(user_project, current_user).execute
end
+
+ result ? status(204) : not_modified!
end
desc 'Share the project with a group' do
diff --git a/lib/api/protected_branches.rb b/lib/api/protected_branches.rb
index b5021e8a712..614822509f0 100644
--- a/lib/api/protected_branches.rb
+++ b/lib/api/protected_branches.rb
@@ -39,10 +39,10 @@ module API
end
params do
requires :name, type: String, desc: 'The name of the protected branch'
- optional :push_access_level, type: Integer, default: Gitlab::Access::MASTER,
+ optional :push_access_level, type: Integer,
values: ProtectedRefAccess::ALLOWED_ACCESS_LEVELS,
desc: 'Access levels allowed to push (defaults: `40`, master access level)'
- optional :merge_access_level, type: Integer, default: Gitlab::Access::MASTER,
+ optional :merge_access_level, type: Integer,
values: ProtectedRefAccess::ALLOWED_ACCESS_LEVELS,
desc: 'Access levels allowed to merge (defaults: `40`, master access level)'
end
@@ -52,15 +52,13 @@ module API
conflict!("Protected branch '#{params[:name]}' already exists")
end
- protected_branch_params = {
- name: params[:name],
- push_access_levels_attributes: [{ access_level: params[:push_access_level] }],
- merge_access_levels_attributes: [{ access_level: params[:merge_access_level] }]
- }
+ # Replace with `declared(params)` after updating to grape v1.0.2
+ # See https://github.com/ruby-grape/grape/pull/1710
+ # and https://gitlab.com/gitlab-org/gitlab-ce/issues/40843
+ declared_params = params.slice("name", "push_access_level", "merge_access_level", "allowed_to_push", "allowed_to_merge")
- service_args = [user_project, current_user, protected_branch_params]
-
- protected_branch = ::ProtectedBranches::CreateService.new(*service_args).execute
+ api_service = ::ProtectedBranches::ApiService.new(user_project, current_user, declared_params)
+ protected_branch = api_service.create
if protected_branch.persisted?
present protected_branch, with: Entities::ProtectedBranch, project: user_project
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index b6d273b98c2..2a04c03919d 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -193,12 +193,9 @@ module Backup
end
def empty_repo?(project_or_wiki)
- project_or_wiki.repository.expire_exists_cache # protect backups from stale cache
- project_or_wiki.repository.empty_repo?
- rescue => e
- progress.puts "Ignoring repository error and continuing backing up project: #{display_repo_path(project_or_wiki)} - #{e.message}".color(:orange)
-
- false
+ # Protect against stale caches
+ project_or_wiki.repository.expire_emptiness_caches
+ project_or_wiki.repository.empty?
end
def repository_storage_paths_args
diff --git a/lib/banzai/cross_project_reference.rb b/lib/banzai/cross_project_reference.rb
index e2b57adf611..d8fb7705b2a 100644
--- a/lib/banzai/cross_project_reference.rb
+++ b/lib/banzai/cross_project_reference.rb
@@ -11,7 +11,7 @@ module Banzai
# ref - String reference.
#
# Returns a Project, or nil if the reference can't be found
- def project_from_ref(ref)
+ def parent_from_ref(ref)
return context[:project] unless ref
Project.find_by_full_path(ref)
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 8975395aff1..e7e6a90b5fd 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -82,9 +82,9 @@ module Banzai
end
end
- def project_from_ref_cached(ref)
- cached_call(:banzai_project_refs, ref) do
- project_from_ref(ref)
+ def from_ref_cached(ref)
+ cached_call("banzai_#{parent_type}_refs".to_sym, ref) do
+ parent_from_ref(ref)
end
end
@@ -153,15 +153,20 @@ module Banzai
# have `gfm` and `gfm-OBJECT_NAME` class names attached for styling.
def object_link_filter(text, pattern, link_content: nil, link_reference: false)
references_in(text, pattern) do |match, id, project_ref, namespace_ref, matches|
- project_path = full_project_path(namespace_ref, project_ref)
- project = project_from_ref_cached(project_path)
+ parent_path = if parent_type == :group
+ full_group_path(namespace_ref)
+ else
+ full_project_path(namespace_ref, project_ref)
+ end
- if project
+ parent = from_ref_cached(parent_path)
+
+ if parent
object =
if link_reference
- find_object_from_link_cached(project, id)
+ find_object_from_link_cached(parent, id)
else
- find_object_cached(project, id)
+ find_object_cached(parent, id)
end
end
@@ -169,13 +174,13 @@ module Banzai
title = object_link_title(object)
klass = reference_class(object_sym)
- data = data_attributes_for(link_content || match, project, object, link: !!link_content)
+ data = data_attributes_for(link_content || match, parent, object, link: !!link_content)
url =
if matches.names.include?("url") && matches[:url]
matches[:url]
else
- url_for_object_cached(object, project)
+ url_for_object_cached(object, parent)
end
content = link_content || object_link_text(object, matches)
@@ -224,17 +229,24 @@ module Banzai
# Returns a Hash containing all object references (e.g. issue IDs) per the
# project they belong to.
- def references_per_project
- @references_per_project ||= begin
+ def references_per_parent
+ @references_per ||= {}
+
+ @references_per[parent_type] ||= begin
refs = Hash.new { |hash, key| hash[key] = Set.new }
regex = Regexp.union(object_class.reference_pattern, object_class.link_reference_pattern)
nodes.each do |node|
node.to_html.scan(regex) do
- project_path = full_project_path($~[:namespace], $~[:project])
+ path = if parent_type == :project
+ full_project_path($~[:namespace], $~[:project])
+ else
+ full_group_path($~[:group])
+ end
+
symbol = $~[object_sym]
- refs[project_path] << symbol if object_class.reference_valid?(symbol)
+ refs[path] << symbol if object_class.reference_valid?(symbol)
end
end
@@ -244,35 +256,41 @@ module Banzai
# Returns a Hash containing referenced projects grouped per their full
# path.
- def projects_per_reference
- @projects_per_reference ||= begin
+ def parent_per_reference
+ @per_reference ||= {}
+
+ @per_reference[parent_type] ||= begin
refs = Set.new
- references_per_project.each do |project_ref, _|
- refs << project_ref
+ references_per_parent.each do |ref, _|
+ refs << ref
end
- find_projects_for_paths(refs.to_a).index_by(&:full_path)
+ find_for_paths(refs.to_a).index_by(&:full_path)
end
end
- def projects_relation_for_paths(paths)
- Project.where_full_path_in(paths).includes(:namespace)
+ def relation_for_paths(paths)
+ klass = parent_type.to_s.camelize.constantize
+ result = klass.where_full_path_in(paths)
+ return result if parent_type == :group
+
+ result.includes(:namespace) if parent_type == :project
end
# Returns projects for the given paths.
- def find_projects_for_paths(paths)
+ def find_for_paths(paths)
if RequestStore.active?
- cache = project_refs_cache
+ cache = refs_cache
to_query = paths - cache.keys
unless to_query.empty?
- projects = projects_relation_for_paths(to_query)
+ records = relation_for_paths(to_query)
found = []
- projects.each do |project|
- ref = project.full_path
- get_or_set_cache(cache, ref) { project }
+ records.each do |record|
+ ref = record.full_path
+ get_or_set_cache(cache, ref) { record }
found << ref
end
@@ -284,33 +302,37 @@ module Banzai
cache.slice(*paths).values.compact
else
- projects_relation_for_paths(paths)
+ relation_for_paths(paths)
end
end
- def current_project_path
- return unless project
-
- @current_project_path ||= project.full_path
+ def current_parent_path
+ @current_parent_path ||= parent&.full_path
end
def current_project_namespace_path
- return unless project
-
- @current_project_namespace_path ||= project.namespace.full_path
+ @current_project_namespace_path ||= project&.namespace&.full_path
end
private
def full_project_path(namespace, project_ref)
- return current_project_path unless project_ref
+ return current_parent_path unless project_ref
namespace_ref = namespace || current_project_namespace_path
"#{namespace_ref}/#{project_ref}"
end
- def project_refs_cache
- RequestStore[:banzai_project_refs] ||= {}
+ def refs_cache
+ RequestStore["banzai_#{parent_type}_refs".to_sym] ||= {}
+ end
+
+ def parent_type
+ :project
+ end
+
+ def parent
+ parent_type == :project ? project : group
end
end
end
diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb
index 714e0319025..eedb95197aa 100644
--- a/lib/banzai/filter/commit_reference_filter.rb
+++ b/lib/banzai/filter/commit_reference_filter.rb
@@ -22,10 +22,30 @@ module Banzai
end
end
+ def referenced_merge_request_commit_shas
+ return [] unless noteable.is_a?(MergeRequest)
+
+ @referenced_merge_request_commit_shas ||= begin
+ referenced_shas = references_per_parent.values.reduce(:|).to_a
+ noteable.all_commit_shas.select do |sha|
+ referenced_shas.any? { |ref| Gitlab::Git.shas_eql?(sha, ref) }
+ end
+ end
+ end
+
def url_for_object(commit, project)
h = Gitlab::Routing.url_helpers
- h.project_commit_url(project, commit,
- only_path: context[:only_path])
+
+ if referenced_merge_request_commit_shas.include?(commit.id)
+ h.diffs_project_merge_request_url(project,
+ noteable,
+ commit_id: commit.id,
+ only_path: only_path?)
+ else
+ h.project_commit_url(project,
+ commit,
+ only_path: only_path?)
+ end
end
def object_link_text_extras(object, matches)
@@ -38,6 +58,16 @@ module Banzai
extras
end
+
+ private
+
+ def noteable
+ context[:noteable]
+ end
+
+ def only_path?
+ context[:only_path]
+ end
end
end
end
diff --git a/lib/banzai/filter/epic_reference_filter.rb b/lib/banzai/filter/epic_reference_filter.rb
new file mode 100644
index 00000000000..265924abe24
--- /dev/null
+++ b/lib/banzai/filter/epic_reference_filter.rb
@@ -0,0 +1,12 @@
+module Banzai
+ module Filter
+ # The actual filter is implemented in the EE mixin
+ class EpicReferenceFilter < IssuableReferenceFilter
+ self.reference_type = :epic
+
+ def self.object_class
+ Epic
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/issuable_reference_filter.rb b/lib/banzai/filter/issuable_reference_filter.rb
new file mode 100644
index 00000000000..7addf09be73
--- /dev/null
+++ b/lib/banzai/filter/issuable_reference_filter.rb
@@ -0,0 +1,31 @@
+module Banzai
+ module Filter
+ class IssuableReferenceFilter < AbstractReferenceFilter
+ def records_per_parent
+ @records_per_project ||= {}
+
+ @records_per_project[object_class.to_s.underscore] ||= begin
+ hash = Hash.new { |h, k| h[k] = {} }
+
+ parent_per_reference.each do |path, parent|
+ record_ids = references_per_parent[path]
+
+ parent_records(parent, record_ids).each do |record|
+ hash[parent][record.iid.to_i] = record
+ end
+ end
+
+ hash
+ end
+ end
+
+ def find_object(parent, iid)
+ records_per_parent[parent][iid]
+ end
+
+ def parent_from_ref(ref)
+ parent_per_reference[ref || current_parent_path]
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/issue_reference_filter.rb b/lib/banzai/filter/issue_reference_filter.rb
index ce1ab977d3b..6877cae8c55 100644
--- a/lib/banzai/filter/issue_reference_filter.rb
+++ b/lib/banzai/filter/issue_reference_filter.rb
@@ -8,46 +8,24 @@ module Banzai
# When external issues tracker like Jira is activated we should not
# use issue reference pattern, but we should still be able
# to reference issues from other GitLab projects.
- class IssueReferenceFilter < AbstractReferenceFilter
+ class IssueReferenceFilter < IssuableReferenceFilter
self.reference_type = :issue
def self.object_class
Issue
end
- def find_object(project, iid)
- issues_per_project[project][iid]
- end
-
def url_for_object(issue, project)
IssuesHelper.url_for_issue(issue.iid, project, only_path: context[:only_path], internal: true)
end
- def project_from_ref(ref)
- projects_per_reference[ref || current_project_path]
- end
-
- # Returns a Hash containing the issues per Project instance.
- def issues_per_project
- @issues_per_project ||= begin
- hash = Hash.new { |h, k| h[k] = {} }
-
- projects_per_reference.each do |path, project|
- issue_ids = references_per_project[path]
- issues = project.issues.where(iid: issue_ids.to_a)
-
- issues.each do |issue|
- hash[project][issue.iid.to_i] = issue
- end
- end
-
- hash
- end
- end
-
def projects_relation_for_paths(paths)
super(paths).includes(:gitlab_issue_tracker_service)
end
+
+ def parent_records(parent, ids)
+ parent.issues.where(iid: ids.to_a)
+ end
end
end
end
diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb
index 5364984c9d3..d5360ad8f68 100644
--- a/lib/banzai/filter/label_reference_filter.rb
+++ b/lib/banzai/filter/label_reference_filter.rb
@@ -33,7 +33,7 @@ module Banzai
end
def find_label(project_ref, label_id, label_name)
- project = project_from_ref(project_ref)
+ project = parent_from_ref(project_ref)
return unless project
label_params = label_params(label_id, label_name)
@@ -66,7 +66,7 @@ module Banzai
def object_link_text(object, matches)
project_path = full_project_path(matches[:namespace], matches[:project])
- project_from_ref = project_from_ref_cached(project_path)
+ project_from_ref = from_ref_cached(project_path)
reference = project_from_ref.to_human_reference(project)
label_suffix = " <i>in #{reference}</i>" if reference.present?
diff --git a/lib/banzai/filter/merge_request_reference_filter.rb b/lib/banzai/filter/merge_request_reference_filter.rb
index 0eab865ac04..b3cfa97d0e0 100644
--- a/lib/banzai/filter/merge_request_reference_filter.rb
+++ b/lib/banzai/filter/merge_request_reference_filter.rb
@@ -4,48 +4,19 @@ module Banzai
# to merge requests that do not exist are ignored.
#
# This filter supports cross-project references.
- class MergeRequestReferenceFilter < AbstractReferenceFilter
+ class MergeRequestReferenceFilter < IssuableReferenceFilter
self.reference_type = :merge_request
def self.object_class
MergeRequest
end
- def find_object(project, iid)
- merge_requests_per_project[project][iid]
- end
-
def url_for_object(mr, project)
h = Gitlab::Routing.url_helpers
h.project_merge_request_url(project, mr,
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
@@ -61,6 +32,12 @@ module Banzai
extras
end
+
+ def parent_records(parent, ids)
+ parent.merge_requests
+ .where(iid: ids.to_a)
+ .includes(target_project: :namespace)
+ end
end
end
end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
index bb5da310e09..2a6b0964ac5 100644
--- a/lib/banzai/filter/milestone_reference_filter.rb
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -38,7 +38,7 @@ module Banzai
def find_milestone(project_ref, namespace_ref, milestone_id, milestone_name)
project_path = full_project_path(namespace_ref, project_ref)
- project = project_from_ref(project_path)
+ project = parent_from_ref(project_path)
return unless project
diff --git a/lib/banzai/filter/upload_link_filter.rb b/lib/banzai/filter/upload_link_filter.rb
index 09844931be5..d64f9ac4eb6 100644
--- a/lib/banzai/filter/upload_link_filter.rb
+++ b/lib/banzai/filter/upload_link_filter.rb
@@ -8,7 +8,7 @@ module Banzai
#
class UploadLinkFilter < HTML::Pipeline::Filter
def call
- return doc unless project
+ return doc unless project || group
doc.xpath('descendant-or-self::a[starts-with(@href, "/uploads/")]').each do |el|
process_link_attr el.attribute('href')
@@ -28,13 +28,27 @@ module Banzai
end
def build_url(uri)
- File.join(Gitlab.config.gitlab.url, project.full_path, uri)
+ base_path = Gitlab.config.gitlab.url
+
+ if group
+ urls = Gitlab::Routing.url_helpers
+ # we need to get last 2 parts of the uri which are secret and filename
+ uri_parts = uri.split(File::SEPARATOR)
+ file_path = urls.show_group_uploads_path(group, uri_parts[-2], uri_parts[-1])
+ File.join(base_path, file_path)
+ else
+ File.join(base_path, project.full_path, uri)
+ end
end
def project
context[:project]
end
+ def group
+ context[:group]
+ end
+
# Ensure that a :project key exists in context
#
# Note that while the key might exist, its value could be nil!
diff --git a/lib/banzai/issuable_extractor.rb b/lib/banzai/issuable_extractor.rb
index cbabf9156de..49603d0b363 100644
--- a/lib/banzai/issuable_extractor.rb
+++ b/lib/banzai/issuable_extractor.rb
@@ -28,8 +28,8 @@ module Banzai
issue_parser = Banzai::ReferenceParser::IssueParser.new(project, user)
merge_request_parser = Banzai::ReferenceParser::MergeRequestParser.new(project, user)
- issuables_for_nodes = issue_parser.issues_for_nodes(nodes).merge(
- merge_request_parser.merge_requests_for_nodes(nodes)
+ issuables_for_nodes = issue_parser.records_for_nodes(nodes).merge(
+ merge_request_parser.records_for_nodes(nodes)
)
# The project for the issue/MR might be pending for deletion!
diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb
index ecb3affbba5..2691be81623 100644
--- a/lib/banzai/object_renderer.rb
+++ b/lib/banzai/object_renderer.rb
@@ -17,11 +17,11 @@ module Banzai
# project - A Project to use for redacting Markdown.
# user - The user viewing the Markdown/HTML documents, if any.
- # context - A Hash containing extra attributes to use during redaction
+ # redaction_context - A Hash containing extra attributes to use during redaction
def initialize(project, user = nil, redaction_context = {})
@project = project
@user = user
- @redaction_context = redaction_context
+ @redaction_context = base_context.merge(redaction_context)
end
# Renders and redacts an Array of objects.
@@ -73,19 +73,19 @@ module Banzai
# Returns a Banzai context for the given object and attribute.
def context_for(object, attribute)
- base_context.merge(object.banzai_render_context(attribute))
+ @redaction_context.merge(object.banzai_render_context(attribute))
end
def base_context
- @base_context ||= @redaction_context.merge(
+ {
current_user: user,
project: project,
skip_redaction: true
- )
+ }
end
def save_options
- return {} unless base_context[:xhtml]
+ return {} unless @redaction_context[:xhtml]
{ save_with: Nokogiri::XML::Node::SaveOptions::AS_XHTML }
end
diff --git a/lib/banzai/reference_parser/epic_parser.rb b/lib/banzai/reference_parser/epic_parser.rb
new file mode 100644
index 00000000000..08b8a4c9a0f
--- /dev/null
+++ b/lib/banzai/reference_parser/epic_parser.rb
@@ -0,0 +1,12 @@
+module Banzai
+ module ReferenceParser
+ # The actual parser is implemented in the EE mixin
+ class EpicParser < IssuableParser
+ self.reference_type = :epic
+
+ def records_for_nodes(_nodes)
+ {}
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/issuable_parser.rb b/lib/banzai/reference_parser/issuable_parser.rb
new file mode 100644
index 00000000000..3953867eb83
--- /dev/null
+++ b/lib/banzai/reference_parser/issuable_parser.rb
@@ -0,0 +1,25 @@
+module Banzai
+ module ReferenceParser
+ class IssuableParser < BaseParser
+ def nodes_visible_to_user(user, nodes)
+ records = records_for_nodes(nodes)
+
+ nodes.select do |node|
+ issuable = records[node]
+
+ issuable && can_read_reference?(user, issuable)
+ end
+ end
+
+ def referenced_by(nodes)
+ records = records_for_nodes(nodes)
+
+ nodes.map { |node| records[node] }.compact.uniq
+ end
+
+ def can_read_reference?(user, issuable)
+ can?(user, "read_#{issuable.class.to_s.underscore}".to_sym, issuable)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb
index e0a8ca653cb..38d4e3f3e44 100644
--- a/lib/banzai/reference_parser/issue_parser.rb
+++ b/lib/banzai/reference_parser/issue_parser.rb
@@ -1,10 +1,10 @@
module Banzai
module ReferenceParser
- class IssueParser < BaseParser
+ class IssueParser < IssuableParser
self.reference_type = :issue
def nodes_visible_to_user(user, nodes)
- issues = issues_for_nodes(nodes)
+ issues = records_for_nodes(nodes)
readable_issues = Ability
.issues_readable_by_user(issues.values, user).to_set
@@ -14,13 +14,7 @@ module Banzai
end
end
- def referenced_by(nodes)
- issues = issues_for_nodes(nodes)
-
- nodes.map { |node| issues[node] }.compact.uniq
- end
-
- def issues_for_nodes(nodes)
+ def records_for_nodes(nodes)
@issues_for_nodes ||= grouped_objects_for_nodes(
nodes,
Issue.all.includes(
diff --git a/lib/banzai/reference_parser/merge_request_parser.rb b/lib/banzai/reference_parser/merge_request_parser.rb
index 75cbc7fdac4..a370ff5b5b3 100644
--- a/lib/banzai/reference_parser/merge_request_parser.rb
+++ b/lib/banzai/reference_parser/merge_request_parser.rb
@@ -1,25 +1,9 @@
module Banzai
module ReferenceParser
- class MergeRequestParser < BaseParser
+ class MergeRequestParser < IssuableParser
self.reference_type = :merge_request
- def nodes_visible_to_user(user, nodes)
- merge_requests = merge_requests_for_nodes(nodes)
-
- nodes.select do |node|
- merge_request = merge_requests[node]
-
- merge_request && can?(user, :read_merge_request, merge_request.project)
- end
- end
-
- def referenced_by(nodes)
- merge_requests = merge_requests_for_nodes(nodes)
-
- nodes.map { |node| merge_requests[node] }.compact.uniq
- end
-
- def merge_requests_for_nodes(nodes)
+ def records_for_nodes(nodes)
@merge_requests_for_nodes ||= grouped_objects_for_nodes(
nodes,
MergeRequest.includes(
@@ -40,10 +24,6 @@ module Banzai
self.class.data_attribute
)
end
-
- def can_read_reference?(user, ref_project, node)
- can?(user, :read_merge_request, ref_project)
- end
end
end
end
diff --git a/lib/gitlab/background_migration/populate_untracked_uploads.rb b/lib/gitlab/background_migration/populate_untracked_uploads.rb
new file mode 100644
index 00000000000..81e95e5832d
--- /dev/null
+++ b/lib/gitlab/background_migration/populate_untracked_uploads.rb
@@ -0,0 +1,259 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This class processes a batch of rows in `untracked_files_for_uploads` by
+ # adding each file to the `uploads` table if it does not exist.
+ class PopulateUntrackedUploads # rubocop:disable Metrics/ClassLength
+ # This class is responsible for producing the attributes necessary to
+ # track an uploaded file in the `uploads` table.
+ class UntrackedFile < ActiveRecord::Base # rubocop:disable Metrics/ClassLength, Metrics/LineLength
+ self.table_name = 'untracked_files_for_uploads'
+
+ # Ends with /:random_hex/:filename
+ FILE_UPLOADER_PATH = %r{/\h+/[^/]+\z}
+ FULL_PATH_CAPTURE = %r{\A(.+)#{FILE_UPLOADER_PATH}}
+
+ # These regex patterns are tested against a relative path, relative to
+ # the upload directory.
+ # For convenience, if there exists a capture group in the pattern, then
+ # it indicates the model_id.
+ PATH_PATTERNS = [
+ {
+ pattern: %r{\A-/system/appearance/logo/(\d+)/},
+ uploader: 'AttachmentUploader',
+ model_type: 'Appearance'
+ },
+ {
+ pattern: %r{\A-/system/appearance/header_logo/(\d+)/},
+ uploader: 'AttachmentUploader',
+ model_type: 'Appearance'
+ },
+ {
+ pattern: %r{\A-/system/note/attachment/(\d+)/},
+ uploader: 'AttachmentUploader',
+ model_type: 'Note'
+ },
+ {
+ pattern: %r{\A-/system/user/avatar/(\d+)/},
+ uploader: 'AvatarUploader',
+ model_type: 'User'
+ },
+ {
+ pattern: %r{\A-/system/group/avatar/(\d+)/},
+ uploader: 'AvatarUploader',
+ model_type: 'Namespace'
+ },
+ {
+ pattern: %r{\A-/system/project/avatar/(\d+)/},
+ uploader: 'AvatarUploader',
+ model_type: 'Project'
+ },
+ {
+ pattern: FILE_UPLOADER_PATH,
+ uploader: 'FileUploader',
+ model_type: 'Project'
+ }
+ ].freeze
+
+ def to_h
+ @upload_hash ||= {
+ path: upload_path,
+ uploader: uploader,
+ model_type: model_type,
+ model_id: model_id,
+ size: file_size,
+ checksum: checksum
+ }
+ end
+
+ def upload_path
+ # UntrackedFile#path is absolute, but Upload#path depends on uploader
+ @upload_path ||=
+ if uploader == 'FileUploader'
+ # Path relative to project directory in uploads
+ matchd = path_relative_to_upload_dir.match(FILE_UPLOADER_PATH)
+ matchd[0].sub(%r{\A/}, '') # remove leading slash
+ else
+ path
+ end
+ end
+
+ def uploader
+ matching_pattern_map[:uploader]
+ end
+
+ def model_type
+ matching_pattern_map[:model_type]
+ end
+
+ def model_id
+ return @model_id if defined?(@model_id)
+
+ pattern = matching_pattern_map[:pattern]
+ matchd = path_relative_to_upload_dir.match(pattern)
+
+ # If something is captured (matchd[1] is not nil), it is a model_id
+ # Only the FileUploader pattern will not match an ID
+ @model_id = matchd[1] ? matchd[1].to_i : file_uploader_model_id
+ end
+
+ def file_size
+ File.size(absolute_path)
+ end
+
+ def checksum
+ Digest::SHA256.file(absolute_path).hexdigest
+ end
+
+ private
+
+ def matching_pattern_map
+ @matching_pattern_map ||= PATH_PATTERNS.find do |path_pattern_map|
+ path_relative_to_upload_dir.match(path_pattern_map[:pattern])
+ end
+
+ unless @matching_pattern_map
+ raise "Unknown upload path pattern \"#{path}\""
+ end
+
+ @matching_pattern_map
+ end
+
+ def file_uploader_model_id
+ matchd = path_relative_to_upload_dir.match(FULL_PATH_CAPTURE)
+ not_found_msg = <<~MSG
+ Could not capture project full_path from a FileUploader path:
+ "#{path_relative_to_upload_dir}"
+ MSG
+ raise not_found_msg unless matchd
+
+ full_path = matchd[1]
+ project = Project.find_by_full_path(full_path)
+ return nil unless project
+
+ project.id
+ end
+
+ # Not including a leading slash
+ def path_relative_to_upload_dir
+ upload_dir = Gitlab::BackgroundMigration::PrepareUntrackedUploads::RELATIVE_UPLOAD_DIR # rubocop:disable Metrics/LineLength
+ base = %r{\A#{Regexp.escape(upload_dir)}/}
+ @path_relative_to_upload_dir ||= path.sub(base, '')
+ end
+
+ def absolute_path
+ File.join(CarrierWave.root, path)
+ end
+ end
+
+ # This class is used to query the `uploads` table.
+ class Upload < ActiveRecord::Base
+ self.table_name = 'uploads'
+ end
+
+ def perform(start_id, end_id)
+ return unless migrate?
+
+ files = UntrackedFile.where(id: start_id..end_id)
+ processed_files = insert_uploads_if_needed(files)
+ processed_files.delete_all
+
+ drop_temp_table_if_finished
+ end
+
+ private
+
+ def migrate?
+ UntrackedFile.table_exists? && Upload.table_exists?
+ end
+
+ def insert_uploads_if_needed(files)
+ filtered_files, error_files = filter_error_files(files)
+ filtered_files = filter_existing_uploads(filtered_files)
+ filtered_files = filter_deleted_models(filtered_files)
+ insert(filtered_files)
+
+ processed_files = files.where.not(id: error_files.map(&:id))
+ processed_files
+ end
+
+ def filter_error_files(files)
+ files.partition do |file|
+ begin
+ file.to_h
+ true
+ rescue => e
+ msg = <<~MSG
+ Error parsing path "#{file.path}":
+ #{e.message}
+ #{e.backtrace.join("\n ")}
+ MSG
+ Rails.logger.error(msg)
+ false
+ end
+ end
+ end
+
+ def filter_existing_uploads(files)
+ paths = files.map(&:upload_path)
+ existing_paths = Upload.where(path: paths).pluck(:path).to_set
+
+ files.reject do |file|
+ existing_paths.include?(file.upload_path)
+ end
+ end
+
+ # There are files on disk that are not in the uploads table because their
+ # model was deleted, and we don't delete the files on disk.
+ def filter_deleted_models(files)
+ ids = deleted_model_ids(files)
+
+ files.reject do |file|
+ ids[file.model_type].include?(file.model_id)
+ end
+ end
+
+ def deleted_model_ids(files)
+ ids = {
+ 'Appearance' => [],
+ 'Namespace' => [],
+ 'Note' => [],
+ 'Project' => [],
+ 'User' => []
+ }
+
+ # group model IDs by model type
+ files.each do |file|
+ ids[file.model_type] << file.model_id
+ end
+
+ ids.each do |model_type, model_ids|
+ model_class = Object.const_get(model_type)
+ found_ids = model_class.where(id: model_ids.uniq).pluck(:id)
+ deleted_ids = ids[model_type] - found_ids
+ ids[model_type] = deleted_ids
+ end
+
+ ids
+ end
+
+ def insert(files)
+ rows = files.map do |file|
+ file.to_h.merge(created_at: 'NOW()')
+ end
+
+ Gitlab::Database.bulk_insert('uploads',
+ rows,
+ disable_quote: :created_at)
+ end
+
+ def drop_temp_table_if_finished
+ if UntrackedFile.all.empty?
+ UntrackedFile.connection.drop_table(:untracked_files_for_uploads,
+ if_exists: true)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/prepare_untracked_uploads.rb b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
new file mode 100644
index 00000000000..476c46341ae
--- /dev/null
+++ b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
@@ -0,0 +1,163 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This class finds all non-hashed uploaded file paths and saves them to a
+ # `untracked_files_for_uploads` table.
+ class PrepareUntrackedUploads # rubocop:disable Metrics/ClassLength
+ # For bulk_queue_background_migration_jobs_by_range
+ include Database::MigrationHelpers
+
+ FIND_BATCH_SIZE = 500
+ RELATIVE_UPLOAD_DIR = "uploads".freeze
+ ABSOLUTE_UPLOAD_DIR = "#{CarrierWave.root}/#{RELATIVE_UPLOAD_DIR}".freeze
+ FOLLOW_UP_MIGRATION = 'PopulateUntrackedUploads'.freeze
+ START_WITH_CARRIERWAVE_ROOT_REGEX = %r{\A#{CarrierWave.root}/}
+ EXCLUDED_HASHED_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/@hashed/*".freeze
+ EXCLUDED_TMP_UPLOADS_PATH = "#{ABSOLUTE_UPLOAD_DIR}/tmp/*".freeze
+
+ # This class is used to iterate over batches of
+ # `untracked_files_for_uploads` rows.
+ class UntrackedFile < ActiveRecord::Base
+ include EachBatch
+
+ self.table_name = 'untracked_files_for_uploads'
+ end
+
+ def perform
+ ensure_temporary_tracking_table_exists
+
+ # Since Postgres < 9.5 does not have ON CONFLICT DO NOTHING, and since
+ # doing inserts-if-not-exists without ON CONFLICT DO NOTHING would be
+ # slow, start with an empty table for Postgres < 9.5.
+ # That way we can do bulk inserts at ~30x the speed of individual
+ # inserts (~20 minutes worth of inserts at GitLab.com scale instead of
+ # ~10 hours).
+ # In all other cases, installations will get both bulk inserts and the
+ # ability for these jobs to retry without having to clear and reinsert.
+ clear_untracked_file_paths unless can_bulk_insert_and_ignore_duplicates?
+
+ store_untracked_file_paths
+
+ schedule_populate_untracked_uploads_jobs
+ end
+
+ private
+
+ def ensure_temporary_tracking_table_exists
+ table_name = :untracked_files_for_uploads
+ unless UntrackedFile.connection.table_exists?(table_name)
+ UntrackedFile.connection.create_table table_name do |t|
+ t.string :path, limit: 600, null: false
+ t.index :path, unique: true
+ end
+ end
+ end
+
+ def clear_untracked_file_paths
+ UntrackedFile.delete_all
+ end
+
+ def store_untracked_file_paths
+ return unless Dir.exist?(ABSOLUTE_UPLOAD_DIR)
+
+ each_file_batch(ABSOLUTE_UPLOAD_DIR, FIND_BATCH_SIZE) do |file_paths|
+ insert_file_paths(file_paths)
+ end
+ end
+
+ def each_file_batch(search_dir, batch_size, &block)
+ cmd = build_find_command(search_dir)
+
+ Open3.popen2(*cmd) do |stdin, stdout, status_thread|
+ yield_paths_in_batches(stdout, batch_size, &block)
+
+ raise "Find command failed" unless status_thread.value.success?
+ end
+ end
+
+ def yield_paths_in_batches(stdout, batch_size, &block)
+ paths = []
+
+ stdout.each_line("\0") do |line|
+ paths << line.chomp("\0").sub(START_WITH_CARRIERWAVE_ROOT_REGEX, '')
+
+ if paths.size >= batch_size
+ yield(paths)
+ paths = []
+ end
+ end
+
+ yield(paths)
+ end
+
+ def build_find_command(search_dir)
+ cmd = %W[find -L #{search_dir}
+ -type f
+ ! ( -path #{EXCLUDED_HASHED_UPLOADS_PATH} -prune )
+ ! ( -path #{EXCLUDED_TMP_UPLOADS_PATH} -prune )
+ -print0]
+
+ ionice = which_ionice
+ cmd = %W[#{ionice} -c Idle] + cmd if ionice
+
+ log_msg = "PrepareUntrackedUploads find command: \"#{cmd.join(' ')}\""
+ Rails.logger.info log_msg
+
+ cmd
+ end
+
+ def which_ionice
+ Gitlab::Utils.which('ionice')
+ rescue StandardError
+ # In this case, returning false is relatively safe,
+ # even though it isn't very nice
+ false
+ end
+
+ def insert_file_paths(file_paths)
+ sql = insert_sql(file_paths)
+
+ ActiveRecord::Base.connection.execute(sql)
+ end
+
+ def insert_sql(file_paths)
+ if postgresql_pre_9_5?
+ "INSERT INTO #{table_columns_and_values_for_insert(file_paths)};"
+ elsif postgresql?
+ "INSERT INTO #{table_columns_and_values_for_insert(file_paths)}"\
+ " ON CONFLICT DO NOTHING;"
+ else # MySQL
+ "INSERT IGNORE INTO"\
+ " #{table_columns_and_values_for_insert(file_paths)};"
+ end
+ end
+
+ def table_columns_and_values_for_insert(file_paths)
+ values = file_paths.map do |file_path|
+ ActiveRecord::Base.send(:sanitize_sql_array, ['(?)', file_path]) # rubocop:disable GitlabSecurity/PublicSend, Metrics/LineLength
+ end.join(', ')
+
+ "#{UntrackedFile.table_name} (path) VALUES #{values}"
+ end
+
+ def postgresql?
+ @postgresql ||= Gitlab::Database.postgresql?
+ end
+
+ def can_bulk_insert_and_ignore_duplicates?
+ !postgresql_pre_9_5?
+ end
+
+ def postgresql_pre_9_5?
+ @postgresql_pre_9_5 ||= postgresql? &&
+ Gitlab::Database.version.to_f < 9.5
+ end
+
+ def schedule_populate_untracked_uploads_jobs
+ bulk_queue_background_migration_jobs_by_range(
+ UntrackedFile, FOLLOW_UP_MIGRATION)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/chain/create.rb b/lib/gitlab/ci/pipeline/chain/create.rb
index d5e17a123df..d19a2519803 100644
--- a/lib/gitlab/ci/pipeline/chain/create.rb
+++ b/lib/gitlab/ci/pipeline/chain/create.rb
@@ -17,11 +17,27 @@ module Gitlab
end
rescue ActiveRecord::RecordInvalid => e
error("Failed to persist the pipeline: #{e}")
+ ensure
+ if pipeline.builds.where(stage_id: nil).any?
+ invalid_builds_counter.increment(node: hostname)
+ end
end
def break?
!pipeline.persisted?
end
+
+ private
+
+ def invalid_builds_counter
+ @counter ||= Gitlab::Metrics
+ .counter(:gitlab_ci_invalid_builds_total,
+ 'Invalid builds without stage assigned counter')
+ end
+
+ def hostname
+ @hostname ||= Socket.gethostname
+ end
end
end
end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 96922e1a62f..e51794fef99 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -120,15 +120,21 @@ module Gitlab
# values.
# return_ids - When set to true the return value will be an Array of IDs of
# the inserted rows, this only works on PostgreSQL.
- def self.bulk_insert(table, rows, return_ids: false)
+ # disable_quote - A key or an Array of keys to exclude from quoting (You
+ # become responsible for protection from SQL injection for
+ # these keys!)
+ def self.bulk_insert(table, rows, return_ids: false, disable_quote: [])
return if rows.empty?
keys = rows.first.keys
columns = keys.map { |key| connection.quote_column_name(key) }
return_ids = false if mysql?
+ disable_quote = Array(disable_quote).to_set
tuples = rows.map do |row|
- row.values_at(*keys).map { |value| connection.quote(value) }
+ keys.map do |k|
+ disable_quote.include?(k) ? row[k] : connection.quote(row[k])
+ end
end
sql = <<-EOF
diff --git a/lib/gitlab/diff/diff_refs.rb b/lib/gitlab/diff/diff_refs.rb
index c98eefbce25..88e0db830f6 100644
--- a/lib/gitlab/diff/diff_refs.rb
+++ b/lib/gitlab/diff/diff_refs.rb
@@ -13,9 +13,9 @@ module Gitlab
def ==(other)
other.is_a?(self.class) &&
- shas_equal?(base_sha, other.base_sha) &&
- shas_equal?(start_sha, other.start_sha) &&
- shas_equal?(head_sha, other.head_sha)
+ Git.shas_eql?(base_sha, other.base_sha) &&
+ Git.shas_eql?(start_sha, other.start_sha) &&
+ Git.shas_eql?(head_sha, other.head_sha)
end
alias_method :eql?, :==
@@ -47,22 +47,6 @@ module Gitlab
CompareService.new(project, head_sha).execute(project, start_sha, straight: straight)
end
end
-
- private
-
- def shas_equal?(sha1, sha2)
- return true if sha1 == sha2
- return false if sha1.nil? || sha2.nil?
- return false unless sha1.class == sha2.class
-
- length = [sha1.length, sha2.length].min
-
- # If either of the shas is below the minimum length, we cannot be sure
- # that they actually refer to the same commit because of hash collision.
- return false if length < Commit::MIN_SHA_LENGTH
-
- sha1[0, length] == sha2[0, length]
- end
end
end
end
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 1f31cdbc96d..1f7c35cafaa 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -70,6 +70,18 @@ module Gitlab
def diff_line_code(file_path, new_line_position, old_line_position)
"#{Digest::SHA1.hexdigest(file_path)}_#{old_line_position}_#{new_line_position}"
end
+
+ def shas_eql?(sha1, sha2)
+ return false if sha1.nil? || sha2.nil?
+ return false unless sha1.class == sha2.class
+
+ # If either of the shas is below the minimum length, we cannot be sure
+ # that they actually refer to the same commit because of hash collision.
+ length = [sha1.length, sha2.length].min
+ return false if length < Gitlab::Git::Commit::MIN_SHA_LENGTH
+
+ sha1[0, length] == sha2[0, length]
+ end
end
end
end
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 8900e2d7afe..e90b158fb34 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -6,6 +6,7 @@ module Gitlab
attr_accessor :raw_commit, :head
+ MIN_SHA_LENGTH = 7
SERIALIZE_KEYS = [
:id, :message, :parent_ids,
:authored_date, :author_name, :author_email,
diff --git a/lib/gitlab/git/operation_service.rb b/lib/gitlab/git/operation_service.rb
index e36d5410431..7e8fe173056 100644
--- a/lib/gitlab/git/operation_service.rb
+++ b/lib/gitlab/git/operation_service.rb
@@ -83,7 +83,7 @@ module Gitlab
Gitlab::Git.check_namespace!(start_repository)
start_repository = RemoteRepository.new(start_repository) unless start_repository.is_a?(RemoteRepository)
- start_branch_name = nil if start_repository.empty_repo?
+ start_branch_name = nil if start_repository.empty?
if start_branch_name && !start_repository.branch_exists?(start_branch_name)
raise ArgumentError, "Cannot find branch #{start_branch_name} in #{start_repository.relative_path}"
diff --git a/lib/gitlab/git/remote_repository.rb b/lib/gitlab/git/remote_repository.rb
index 3685aa20669..6bd6e58feeb 100644
--- a/lib/gitlab/git/remote_repository.rb
+++ b/lib/gitlab/git/remote_repository.rb
@@ -24,10 +24,12 @@ module Gitlab
@path = repository.path
end
- def empty_repo?
+ def empty?
# We will override this implementation in gitaly-ruby because we cannot
# use '@repository' there.
- @repository.empty_repo?
+ #
+ # Caches and memoization used on the Rails side
+ !@repository.exists? || @repository.empty?
end
def commit_id(revision)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 1468069a991..91dd2fbbdbc 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -75,9 +75,6 @@ module Gitlab
@attributes = Gitlab::Git::Attributes.new(path)
end
- delegate :empty?,
- to: :rugged
-
def ==(other)
path == other.path
end
@@ -206,6 +203,13 @@ module Gitlab
end
end
+ # Git repository can contains some hidden refs like:
+ # /refs/notes/*
+ # /refs/git-as-svn/*
+ # /refs/pulls/*
+ # This refs by default not visible in project page and not cloned to client side.
+ alias_method :has_visible_content?, :has_local_branches?
+
def has_local_branches_rugged?
rugged.branches.each(:local).any? do |ref|
begin
@@ -1004,7 +1008,7 @@ module Gitlab
Gitlab::Git.check_namespace!(start_repository)
start_repository = RemoteRepository.new(start_repository) unless start_repository.is_a?(RemoteRepository)
- return yield nil if start_repository.empty_repo?
+ return yield nil if start_repository.empty?
if start_repository.same_repository?(self)
yield commit(start_branch_name)
@@ -1120,24 +1124,8 @@ module Gitlab
Gitlab::Git::Commit.find(self, ref)
end
- # Refactoring aid; allows us to copy code from app/models/repository.rb
- def empty_repo?
- !exists? || !has_visible_content?
- end
-
- #
- # Git repository can contains some hidden refs like:
- # /refs/notes/*
- # /refs/git-as-svn/*
- # /refs/pulls/*
- # This refs by default not visible in project page and not cloned to client side.
- #
- # This method return true if repository contains some content visible in project page.
- #
- def has_visible_content?
- return @has_visible_content if defined?(@has_visible_content)
-
- @has_visible_content = has_local_branches?
+ def empty?
+ !has_visible_content?
end
# Like all public `Gitlab::Git::Repository` methods, this method is part
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 8998c4b1a83..9d7d921bb9c 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -166,7 +166,7 @@ module Gitlab
end
if Gitlab::Database.read_only?
- raise UnauthorizedError, ERROR_MESSAGES[:cannot_push_to_read_only]
+ raise UnauthorizedError, push_to_read_only_message
end
if deploy_key
@@ -280,5 +280,9 @@ module Gitlab
UserAccess.new(user, project: project)
end
end
+
+ def push_to_read_only_message
+ ERROR_MESSAGES[:cannot_push_to_read_only]
+ end
end
end
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index 98f1f45b338..1c9477e84b2 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -19,10 +19,14 @@ module Gitlab
end
if Gitlab::Database.read_only?
- raise UnauthorizedError, ERROR_MESSAGES[:read_only]
+ raise UnauthorizedError, push_to_read_only_message
end
true
end
+
+ def push_to_read_only_message
+ ERROR_MESSAGES[:read_only]
+ end
end
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index f27cd800bdd..b753ac46291 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -27,7 +27,7 @@ module Gitlab
end
SERVER_VERSION_FILE = 'GITALY_SERVER_VERSION'.freeze
- MAXIMUM_GITALY_CALLS = 30
+ MAXIMUM_GITALY_CALLS = 35
CLIENT_NAME = (Sidekiq.server? ? 'gitlab-sidekiq' : 'gitlab-web').freeze
MUTEX = Mutex.new
@@ -336,6 +336,12 @@ module Gitlab
s.dup.force_encoding(Encoding::ASCII_8BIT)
end
+ def self.binary_stringio(s)
+ io = StringIO.new(s || '')
+ io.set_encoding(Encoding::ASCII_8BIT)
+ io
+ end
+
def self.encode_repeated(a)
Google::Protobuf::RepeatedField.new(:bytes, a.map { |s| self.encode(s) } )
end
diff --git a/lib/gitlab/gitaly_client/wiki_service.rb b/lib/gitlab/gitaly_client/wiki_service.rb
index c8f065f5881..337d225d081 100644
--- a/lib/gitlab/gitaly_client/wiki_service.rb
+++ b/lib/gitlab/gitaly_client/wiki_service.rb
@@ -18,12 +18,11 @@ module Gitlab
commit_details: gitaly_commit_details(commit_details)
)
- strio = StringIO.new(content)
+ strio = GitalyClient.binary_stringio(content)
enum = Enumerator.new do |y|
until strio.eof?
- chunk = strio.read(MAX_MSG_SIZE)
- request.content = GitalyClient.encode(chunk)
+ request.content = strio.read(MAX_MSG_SIZE)
y.yield request
@@ -46,12 +45,11 @@ module Gitlab
commit_details: gitaly_commit_details(commit_details)
)
- strio = StringIO.new(content)
+ strio = GitalyClient.binary_stringio(content)
enum = Enumerator.new do |y|
until strio.eof?
- chunk = strio.read(MAX_MSG_SIZE)
- request.content = GitalyClient.encode(chunk)
+ request.content = strio.read(MAX_MSG_SIZE)
y.yield request
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index 436a9e9550d..f4901be9581 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -32,7 +32,7 @@ module Gitlab
def init_metrics
metrics = {}
- metrics[:sampler_duration] = Metrics.histogram(with_prefix(:sampler_duration, :seconds), 'Sampler time', {})
+ metrics[:sampler_duration] = Metrics.histogram(with_prefix(:sampler_duration, :seconds), 'Sampler time', { worker: nil })
metrics[:total_time] = Metrics.gauge(with_prefix(:gc, :time_total), 'Total GC time', labels, :livesum)
GC.stat.keys.each do |key|
metrics[key] = Metrics.gauge(with_prefix(:gc, key), to_doc_string(key), labels, :livesum)
@@ -100,9 +100,9 @@ module Gitlab
worker_no = ::Prometheus::Client::Support::Unicorn.worker_id
if worker_no
- { unicorn: worker_no }
+ { worker: worker_no }
else
- { unicorn: 'master' }
+ { worker: 'master' }
end
end
end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index bc836dcc08d..9ff82d628c0 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -1,7 +1,7 @@
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor < Banzai::ReferenceExtractor
- REFERABLES = %i(user issue label milestone merge_request snippet commit commit_range directly_addressed_user).freeze
+ REFERABLES = %i(user issue label milestone merge_request snippet commit commit_range directly_addressed_user epic).freeze
attr_accessor :project, :current_user, :author
def initialize(project, current_user = nil)
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index abb3d3a02c3..b3baaf036d8 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -46,5 +46,22 @@ module Gitlab
def random_string
Random.rand(Float::MAX.to_i).to_s(36)
end
+
+ # See: http://stackoverflow.com/questions/2108727/which-in-ruby-checking-if-program-exists-in-path-from-ruby
+ # Cross-platform way of finding an executable in the $PATH.
+ #
+ # which('ruby') #=> /usr/bin/ruby
+ def which(cmd, env = ENV)
+ exts = env['PATHEXT'] ? env['PATHEXT'].split(';') : ['']
+
+ env['PATH'].split(File::PATH_SEPARATOR).each do |path|
+ exts.each do |ext|
+ exe = File.join(path, "#{cmd}#{ext}")
+ return exe if File.executable?(exe) && !File.directory?(exe)
+ end
+ end
+
+ nil
+ end
end
end