137 lines
4.3 KiB
Ruby
137 lines
4.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Gitlab
|
|
module Checks
|
|
class ChangesAccess
|
|
ATTRIBUTES = %i[user_access project protocol changes logger].freeze
|
|
|
|
attr_reader(*ATTRIBUTES)
|
|
|
|
def initialize(
|
|
changes, user_access:, project:, protocol:, logger:
|
|
)
|
|
@changes = changes
|
|
@user_access = user_access
|
|
@project = project
|
|
@protocol = protocol
|
|
@logger = logger
|
|
end
|
|
|
|
def validate!
|
|
return if changes.empty?
|
|
|
|
single_access_checks!
|
|
|
|
logger.log_timed("Running checks for #{changes.length} changes") do
|
|
bulk_access_checks!
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
# All commits which have been newly introduced via any of the given
|
|
# changes. This set may also contain commits which are not referenced by
|
|
# any of the new revisions.
|
|
def commits
|
|
allow_quarantine = true
|
|
|
|
newrevs = @changes.map do |change|
|
|
oldrev = change[:oldrev]
|
|
newrev = change[:newrev]
|
|
|
|
next if blank_rev?(newrev)
|
|
|
|
# In case any of the old revisions is blank, then we cannot reliably
|
|
# detect which commits are new for a given change when enumerating
|
|
# objects via the object quarantine directory given that the client
|
|
# may have pushed too many commits, and we don't know when to
|
|
# terminate the walk. We thus fall back to using `git rev-list --not
|
|
# --all`, which is a lot less efficient but at least can only ever
|
|
# returns commits which really are new.
|
|
allow_quarantine = false if allow_quarantine && blank_rev?(oldrev)
|
|
|
|
newrev
|
|
end.compact
|
|
|
|
return [] if newrevs.empty?
|
|
|
|
@commits ||= project.repository.new_commits(newrevs, allow_quarantine: allow_quarantine)
|
|
end
|
|
|
|
# All commits which have been newly introduced via the given revision.
|
|
def commits_for(oldrev, newrev)
|
|
commits_by_id = commits.index_by(&:id)
|
|
|
|
result = []
|
|
pending = Set[newrev]
|
|
|
|
# We go up the parent chain of our newrev and collect all commits which
|
|
# are new. In case a commit's ID cannot be found in the set of new
|
|
# commits, then it must already be a preexisting commit.
|
|
while pending.any?
|
|
rev = pending.first
|
|
pending.delete(rev)
|
|
|
|
# Remove the revision from commit candidates such that we don't walk
|
|
# it multiple times. If the hash doesn't contain the revision, then
|
|
# we have either already walked the commit or it's not new.
|
|
commit = commits_by_id.delete(rev)
|
|
next if commit.nil?
|
|
|
|
# Only add the parent ID to the pending set if we actually know its
|
|
# commit to guards us against readding an ID which we have already
|
|
# queued up before. Furthermore, we stop walking as soon as we hit
|
|
# `oldrev` such that we do not include any commits in our checks
|
|
# which have been "over-pushed" by the client.
|
|
commit.parent_ids.each do |parent_id|
|
|
pending.add(parent_id) if commits_by_id.has_key?(parent_id) && parent_id != oldrev
|
|
end
|
|
|
|
result << commit
|
|
end
|
|
|
|
result
|
|
end
|
|
|
|
def single_change_accesses
|
|
@single_changes_accesses ||=
|
|
changes.map do |change|
|
|
commits =
|
|
if blank_rev?(change[:newrev])
|
|
[]
|
|
else
|
|
Gitlab::Lazy.new { commits_for(change[:oldrev], change[:newrev]) }
|
|
end
|
|
|
|
Checks::SingleChangeAccess.new(
|
|
change,
|
|
user_access: user_access,
|
|
project: project,
|
|
protocol: protocol,
|
|
logger: logger,
|
|
commits: commits
|
|
)
|
|
end
|
|
end
|
|
|
|
protected
|
|
|
|
def single_access_checks!
|
|
# Iterate over all changes to find if user allowed all of them to be applied
|
|
single_change_accesses.each do |single_change_access|
|
|
single_change_access.validate!
|
|
end
|
|
end
|
|
|
|
def bulk_access_checks!
|
|
Gitlab::Checks::LfsCheck.new(self).validate!
|
|
end
|
|
|
|
def blank_rev?(rev)
|
|
rev.blank? || Gitlab::Git.blank_ref?(rev)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
Gitlab::Checks::ChangesAccess.prepend_mod_with('Gitlab::Checks::ChangesAccess')
|