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

changes_access.rb « checks « gitlab « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 84c01cf4bafeed994c48ca42f6f0bd59b10edff5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# 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')