Class: MergeRequest::CommitsMetadata

Inherits:
ApplicationRecord show all
Includes:
PartitionedTable, ShaAttribute
Defined in:
app/models/merge_request/commits_metadata.rb

Overview

rubocop:disable Style/ClassAndModuleChildren, Gitlab/BoundedContexts – Same as the rest of the models under MergeRequest namespace

Constant Summary

Constants inherited from ApplicationRecord

ApplicationRecord::MAX_PLUCK

Constants included from HasCheckConstraints

HasCheckConstraints::NOT_NULL_CHECK_PATTERN

Constants included from ResetOnColumnErrors

ResetOnColumnErrors::MAX_RESET_PERIOD

Class Method Summary collapse

Methods inherited from ApplicationRecord

===, cached_column_list, #create_or_load_association, current_transaction, declarative_enum, default_select_columns, delete_all_returning, #deleted_from_database?, id_in, id_not_in, iid_in, nullable_column?, primary_key_in, #readable_by?, safe_ensure_unique, safe_find_or_create_by, safe_find_or_create_by!, #to_ability_name, underscore, where_exists, where_not_exists, with_fast_read_statement_timeout, without_order

Methods included from Organizations::Sharding

#sharding_organization

Methods included from ResetOnColumnErrors

#reset_on_union_error, #reset_on_unknown_attribute_error

Methods included from Gitlab::SensitiveSerializableHash

#serializable_hash

Class Method Details

.bulk_find(project_id, shas) ⇒ Object

Finds many commits by project_id and array of SHAs in bulk. The return value is an array of ID and SHA pairs.



53
54
55
56
57
58
59
60
61
62
63
# File 'app/models/merge_request/commits_metadata.rb', line 53

def self.bulk_find(project_id, shas)
  rows = []

  shas.each_slice(1_000) do |slice|
    # rubocop:disable Database/AvoidUsingPluckWithoutLimit -- Already limited to 1K SHAs
    rows.concat(where(project_id: project_id, sha: slice).pluck(:id, :sha))
    # rubocop:enable Database/AvoidUsingPluckWithoutLimit
  end

  rows
end

.bulk_find_or_create(project_id, commit_rows) ⇒ Object

Finds or creates rows for the given project ID and commit rows.

The commit_rows argument must be an array of hashes. Each hash should have the following keys:

  • :commit_author_id

  • :committer_id

  • :raw_sha

  • :authored_date

  • :committed_date

  • :message

The return value is a hash that maps ID of each found or created commits metadata row to a SHA.



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
# File 'app/models/merge_request/commits_metadata.rb', line 78

def self.bulk_find_or_create(project_id, commit_rows)
  mapping = {}
  create = []

  # rubocop:disable Database/AvoidUsingPluckWithoutLimit -- This is an array of hashes
  commits_shas = commit_rows.pluck(:raw_sha)
  # rubocop:enable Database/AvoidUsingPluckWithoutLimit

  # Find commits that are already existing in `merge_request_commits_metadata`
  # table. Store them in `mapping` hash so we can map this with rows in
  # `merge_request_diff_commits` table by SHA.
  #
  # `row.last` is the SHA and `row.first` is the ID.
  bulk_find(project_id, commits_shas).each do |row|
    mapping[row.last] = row.first
  end

  # Check if there are commits that don't exist yet in the mapping. It means
  # they're not stored in the DB yet at this point so we need to create them.
  commit_rows.each do |commit_row|
    next if mapping[commit_row[:raw_sha]]

    create << {
      project_id: project_id,
      commit_author_id: commit_row[:commit_author_id],
      committer_id: commit_row[:committer_id],
      sha: commit_row[:raw_sha],
      authored_date: commit_row[:authored_date],
      committed_date: commit_row[:committed_date],
      message: commit_row[:message]
    }
  end

  return mapping if create.empty?

  sha_attribute = Gitlab::Database::ShaAttribute.new

  create.each_slice(1_000) do |slice|
    insert_all(
      slice,
      returning: %w[id sha],
      unique_by: :index_merge_request_commits_metadata_on_project_id_and_sha
    ).each do |row|
      # Need to deserialize sha since it's stored as binary in the database
      # and we need to match by its text value.
      mapping[sha_attribute.deserialize(row['sha'])] = row['id']
    end
  end

  # It's possible for commits to be inserted concurrently,
  # resulting in the above insert not returning anything. Here we get any
  # remaining commits that were created concurrently.
  #
  # `row.last` is the SHA and `row.first` is the ID.
  bulk_find(project_id, commits_shas.reject { |sha| mapping.key?(sha) }).each do |row|
    mapping[row.last] = row.first
  end

  mapping
end

.find_or_create(metadata = {}) ⇒ Object

Creates a new row, or returns an existing one if a row already exists.



39
40
41
42
43
44
45
46
47
48
49
# File 'app/models/merge_request/commits_metadata.rb', line 39

def self.find_or_create( = {})
  find_or_create_by!(project_id: ['project_id'], sha: ['sha']) do |record|
    record.committer = ['committer']
    record.commit_author = ['commit_author']
    record.message = ['message']
    record.authored_date = ['authored_date']
    record.committed_date = ['committed_date']
  end
rescue ActiveRecord::RecordNotUnique
  retry
end

.oldest_merge_request_id_per_commit(project_id, shas) ⇒ Object



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'app/models/merge_request/commits_metadata.rb', line 139

def self.oldest_merge_request_id_per_commit(project_id, shas)
  # This method is defined here and not on MergeRequest, otherwise the SHA
  # values used in the WHERE below won't be encoded correctly.
  return [] if shas.empty?

   = (project_id, shas).to_a
  found_shas = .map(&:sha)
  missing_shas = shas - found_shas

  # We query the SHA from `merge_request_commits_metadata` table first and
  # fallback to querying them from `merge_request_diff_commits` if there are no matches
  # This is needed temporarily while `merge_request_commits_metadata_id` is not fully populated
  if missing_shas.any?
    diff_commits_results = MergeRequestDiffCommit.oldest_merge_request_id_per_commit(project_id, missing_shas)
     + diff_commits_results.to_a
  else
    
  end
end

.oldest_merge_requests_commits_from_metadata(project_id, shas) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'app/models/merge_request/commits_metadata.rb', line 159

def self.(project_id, shas)
  select('sha', 'MIN(merge_requests.id) AS merge_request_id')
    .where(project_id: project_id, sha: shas)
    .joins(:merge_request_diff_commits)
    .joins(
      'INNER JOIN merge_request_diffs ON merge_request_diffs.id = merge_request_diff_commits.merge_request_diff_id'
    )
    .joins('INNER JOIN merge_requests ON merge_requests.latest_merge_request_diff_id = merge_request_diffs.id')
    .where(merge_requests: {
      target_project_id: project_id,
      state_id: MergeRequest.available_states[:merged]
    })
    .group(:sha)
end