Class: CommitCollection

Inherits:
Object
  • Object
show all
Includes:
Enumerable, Gitlab::Utils::StrongMemoize
Defined in:
app/models/commit_collection.rb

Overview

A collection of Commit instances for a specific container and Git reference.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(container, commits, ref = nil, page: nil, per_page: nil, count: nil) ⇒ CommitCollection

container - The object the commits belong to. commits - The Commit instances to store. ref - The name of the ref (e.g. “master”).



16
17
18
19
20
21
# File 'app/models/commit_collection.rb', line 16

def initialize(container, commits, ref = nil, page: nil, per_page: nil, count: nil)
  @container = container
  @commits = commits
  @ref = ref
  @pagination = Gitlab::PaginationDelegate.new(page: page, per_page: per_page, count: count)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(message, *args, &block) ⇒ Object

rubocop:disable GitlabSecurity/PublicSend



144
145
146
# File 'app/models/commit_collection.rb', line 144

def method_missing(message, *args, &block)
  commits.public_send(message, *args, &block)
end

Instance Attribute Details

#commitsObject (readonly)

Returns the value of attribute commits.



8
9
10
# File 'app/models/commit_collection.rb', line 8

def commits
  @commits
end

#containerObject (readonly)

Returns the value of attribute container.



8
9
10
# File 'app/models/commit_collection.rb', line 8

def container
  @container
end

#refObject (readonly)

Returns the value of attribute ref.



8
9
10
# File 'app/models/commit_collection.rb', line 8

def ref
  @ref
end

Instance Method Details

#committer_user_idsObject



59
60
61
# File 'app/models/commit_collection.rb', line 59

def committer_user_ids
  committers.pluck(:id)
end

#committers(with_merge_commits: false, lazy: false, include_author_when_signed: false) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'app/models/commit_collection.rb', line 27

def committers(with_merge_commits: false, lazy: false, include_author_when_signed: false)
  if lazy
    return committers_lazy(
      with_merge_commits: with_merge_commits,
      include_author_when_signed: include_author_when_signed
    ).flatten
  end

  User.by_any_email(
    committers_emails(
      with_merge_commits: with_merge_commits,
      include_author_when_signed: include_author_when_signed
    )
  )
end

#committers_lazy(with_merge_commits: false, include_author_when_signed: false) ⇒ Object Also known as: add_committers_to_batch_loader



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'app/models/commit_collection.rb', line 43

def committers_lazy(with_merge_commits: false, include_author_when_signed: false)
  emails = committers_emails(
    with_merge_commits: with_merge_commits,
    include_author_when_signed: include_author_when_signed
  )

  emails.map do |email|
    BatchLoader.for(email.downcase).batch(default_value: []) do |committer_emails, loader|
      User.by_any_email(committer_emails).each do |user|
        loader.call(user.email) { |memo| memo << user }
      end
    end
  end
end

#each(&block) ⇒ Object



23
24
25
# File 'app/models/commit_collection.rb', line 23

def each(&block)
  commits.each(&block)
end

#enrich!Object

Batch load any commits that are not backed by full gitaly data, and replace them in the collection.



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/commit_collection.rb', line 111

def enrich!
  # A container is needed in order to fetch data from gitaly. Containers
  # can be absent from commits in certain rare situations (like when
  # viewing a MR of a deleted fork). In these cases, assume that the
  # enriched data is not needed.
  return self if container.blank? || fully_enriched?

  # Batch load full Commits from the repository
  # and map to a Hash of id => Commit
  replacements = unenriched.each_with_object({}) do |c, result|
    result[c.id] = Commit.lazy(container, c.id)
  end.compact

  # Replace the commits, keeping the same order
  @commits = @commits.map do |original_commit|
    # Return the original instance: if it didn't need to be batchloaded, it was
    # already enriched.
    batch_loaded_commit = replacements.fetch(original_commit.id, original_commit)

    # If batch loading the commit failed, fall back to the original commit.
    # We need to explicitly check `.nil?` since otherwise a `BatchLoader` instance
    # that looks like `nil` is returned.
    batch_loaded_commit.nil? ? original_commit : batch_loaded_commit
  end

  self
end

#fully_enriched?Boolean

Returns:

  • (Boolean)


105
106
107
# File 'app/models/commit_collection.rb', line 105

def fully_enriched?
  unenriched.empty?
end

#load_tagsObject



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'app/models/commit_collection.rb', line 152

def load_tags
  oids = commits.map(&:id)
  references = repository.list_refs([Gitlab::Git::TAG_REF_PREFIX], pointing_at_oids: oids, peel_tags: true)
  oid_to_references = references.group_by { |reference| reference.peeled_target.presence || reference.target }

  return self if oid_to_references.empty?

  commits.each do |commit|
    grouped_references = oid_to_references[commit.id]
    next unless grouped_references

    commit.referenced_by = grouped_references.map(&:name)
  end

  self
end

#next_pageObject



148
149
150
# File 'app/models/commit_collection.rb', line 148

def next_page
  @pagination.next_page
end

#respond_to_missing?(message, inc_private = false) ⇒ Boolean

Returns:

  • (Boolean)


139
140
141
# File 'app/models/commit_collection.rb', line 139

def respond_to_missing?(message, inc_private = false)
  commits.respond_to?(message, inc_private)
end

#unenrichedObject



101
102
103
# File 'app/models/commit_collection.rb', line 101

def unenriched
  commits.reject(&:gitaly_commit?)
end

#with_latest_pipeline(ref = nil) ⇒ Object

Returns the collection with the latest pipeline for every commit pre-set.

Setting the pipeline for each commit ahead of time removes the need for running a query for every commit we’re displaying.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'app/models/commit_collection.rb', line 75

def with_latest_pipeline(ref = nil)
  return self unless project

  pipelines = project.ci_pipelines.latest_pipeline_per_commit(map(&:id), ref)

  each do |commit|
    pipeline = pipelines[commit.id]
    pipeline&.number_of_warnings # preload number of warnings

    commit.set_latest_pipeline_for_ref(ref, pipeline)
  end

  self
end

#with_markdown_cacheObject

Returns the collection with markdown fields preloaded.

Get the markdown cache from redis using pipeline to prevent n+1 requests when rendering the markdown of an attribute (e.g. title, full_title, description).



95
96
97
98
99
# File 'app/models/commit_collection.rb', line 95

def with_markdown_cache
  Commit.preload_markdown_cache!(commits)

  self
end

#without_merge_commitsObject



63
64
65
66
67
68
69
# File 'app/models/commit_collection.rb', line 63

def without_merge_commits
  strong_memoize(:without_merge_commits) do
    # `#enrich!` the collection to ensure all commits contain
    # the necessary parent data
    enrich!.commits.reject(&:merge_commit?)
  end
end