Skip to content

Commit 076ab2e

Browse files
author
Robert Speicher
committed
Merge branch 'fix/handle-large-commit-tag-message' into 'master'
Add handling for commit/tags with big messages See merge request gitlab-org/gitlab-ce!17892
2 parents ee59fdf + 019f5e2 commit 076ab2e

File tree

13 files changed

+310
-32
lines changed

13 files changed

+310
-32
lines changed

.flayignore

+4
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ lib/gitlab/background_migration/*
1010
app/models/project_services/kubernetes_service.rb
1111
lib/gitlab/workhorse.rb
1212
lib/gitlab/ci/trace/chunked_io.rb
13+
lib/gitlab/gitaly_client/ref_service.rb
14+
lib/gitlab/gitaly_client/commit_service.rb
15+
lib/gitlab/git/commit.rb
16+
lib/gitlab/git/tag.rb

lib/gitlab/git/commit.rb

+57-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class Commit
66

77
attr_accessor :raw_commit, :head
88

9+
MAX_COMMIT_MESSAGE_DISPLAY_SIZE = 10.megabytes
910
MIN_SHA_LENGTH = 7
1011
SERIALIZE_KEYS = [
1112
:id, :message, :parent_ids,
@@ -63,9 +64,7 @@ def find(repo, commit_id = "HEAD")
6364
if is_enabled
6465
repo.gitaly_commit_client.find_commit(commit_id)
6566
else
66-
obj = repo.rev_parse_target(commit_id)
67-
68-
obj.is_a?(Rugged::Commit) ? obj : nil
67+
rugged_find(repo, commit_id)
6968
end
7069
end
7170

@@ -76,6 +75,12 @@ def find(repo, commit_id = "HEAD")
7675
nil
7776
end
7877

78+
def rugged_find(repo, commit_id)
79+
obj = repo.rev_parse_target(commit_id)
80+
81+
obj.is_a?(Rugged::Commit) ? obj : nil
82+
end
83+
7984
# Get last commit for HEAD
8085
#
8186
# Ex.
@@ -297,11 +302,40 @@ def rugged_extract_signature(repository, commit_id)
297302
nil
298303
end
299304
end
305+
306+
def get_message(repository, commit_id)
307+
BatchLoader.for({ repository: repository, commit_id: commit_id }).batch do |items, loader|
308+
items_by_repo = items.group_by { |i| i[:repository] }
309+
310+
items_by_repo.each do |repo, items|
311+
commit_ids = items.map { |i| i[:commit_id] }
312+
313+
messages = get_messages(repository, commit_ids)
314+
315+
messages.each do |commit_sha, message|
316+
loader.call({ repository: repository, commit_id: commit_sha }, message)
317+
end
318+
end
319+
end
320+
end
321+
322+
def get_messages(repository, commit_ids)
323+
repository.gitaly_migrate(:commit_messages) do |is_enabled|
324+
if is_enabled
325+
repository.gitaly_commit_client.get_commit_messages(commit_ids)
326+
else
327+
commit_ids.map { |id| [id, rugged_find(repository, id).message] }.to_h
328+
end
329+
end
330+
end
300331
end
301332

302333
def initialize(repository, raw_commit, head = nil)
303334
raise "Nil as raw commit passed" unless raw_commit
304335

336+
@repository = repository
337+
@head = head
338+
305339
case raw_commit
306340
when Hash
307341
init_from_hash(raw_commit)
@@ -312,9 +346,6 @@ def initialize(repository, raw_commit, head = nil)
312346
else
313347
raise "Invalid raw commit type: #{raw_commit.class}"
314348
end
315-
316-
@repository = repository
317-
@head = head
318349
end
319350

320351
def sha
@@ -518,7 +549,7 @@ def init_from_gitaly(commit)
518549
# TODO: Once gitaly "takes over" Rugged consider separating the
519550
# subject from the message to make it clearer when there's one
520551
# available but not the other.
521-
@message = (commit.body.presence || commit.subject).dup
552+
@message = message_from_gitaly_body
522553
@authored_date = Time.at(commit.author.date.seconds).utc
523554
@author_name = commit.author.name.dup
524555
@author_email = commit.author.email.dup
@@ -570,6 +601,25 @@ def gitaly_commit_author_from_rugged(author_or_committer)
570601
def refs(repo)
571602
repo.refs_hash[id]
572603
end
604+
605+
def message_from_gitaly_body
606+
return @raw_commit.subject.dup if @raw_commit.body_size.zero?
607+
return @raw_commit.body.dup if full_body_fetched_from_gitaly?
608+
609+
if @raw_commit.body_size > MAX_COMMIT_MESSAGE_DISPLAY_SIZE
610+
"#{@raw_commit.subject}\n\n--commit message is too big".strip
611+
else
612+
fetch_body_from_gitaly
613+
end
614+
end
615+
616+
def full_body_fetched_from_gitaly?
617+
@raw_commit.body.bytesize == @raw_commit.body_size
618+
end
619+
620+
def fetch_body_from_gitaly
621+
self.class.get_message(@repository, id)
622+
end
573623
end
574624
end
575625
end

lib/gitlab/git/repository.rb

+6-1
Original file line numberDiff line numberDiff line change
@@ -1962,7 +1962,12 @@ def tags_from_rugged
19621962
end
19631963

19641964
target_commit = Gitlab::Git::Commit.find(self, ref.target)
1965-
Gitlab::Git::Tag.new(self, ref.name, ref.target, target_commit, message)
1965+
Gitlab::Git::Tag.new(self, {
1966+
name: ref.name,
1967+
target: ref.target,
1968+
target_commit: target_commit,
1969+
message: message
1970+
})
19661971
end.sort_by(&:name)
19671972
end
19681973

lib/gitlab/git/repository_mirroring.rb

+5-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ def remote_tags(remote)
3535
next if name =~ /\^\{\}\Z/
3636

3737
target_commit = Gitlab::Git::Commit.find(self, target)
38-
Gitlab::Git::Tag.new(self, name, target, target_commit)
38+
Gitlab::Git::Tag.new(self, {
39+
name: name,
40+
target: target,
41+
target_commit: target_commit
42+
})
3943
end.compact
4044
end
4145

lib/gitlab/git/tag.rb

+85-3
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,99 @@
11
module Gitlab
22
module Git
33
class Tag < Ref
4-
attr_reader :object_sha
4+
extend Gitlab::EncodingHelper
5+
6+
attr_reader :object_sha, :repository
7+
8+
MAX_TAG_MESSAGE_DISPLAY_SIZE = 10.megabytes
9+
SERIALIZE_KEYS = %i[name target target_commit message].freeze
10+
11+
attr_accessor *SERIALIZE_KEYS # rubocop:disable Lint/AmbiguousOperator
12+
13+
class << self
14+
def get_message(repository, tag_id)
15+
BatchLoader.for({ repository: repository, tag_id: tag_id }).batch do |items, loader|
16+
items_by_repo = items.group_by { |i| i[:repository] }
17+
18+
items_by_repo.each do |repo, items|
19+
tag_ids = items.map { |i| i[:tag_id] }
20+
21+
messages = get_messages(repository, tag_ids)
22+
23+
messages.each do |id, message|
24+
loader.call({ repository: repository, tag_id: id }, message)
25+
end
26+
end
27+
end
28+
end
29+
30+
def get_messages(repository, tag_ids)
31+
repository.gitaly_migrate(:tag_messages) do |is_enabled|
32+
if is_enabled
33+
repository.gitaly_ref_client.get_tag_messages(tag_ids)
34+
else
35+
tag_ids.map do |id|
36+
tag = repository.rugged.lookup(id)
37+
message = tag.is_a?(Rugged::Commit) ? "" : tag.message
38+
39+
[id, message]
40+
end.to_h
41+
end
42+
end
43+
end
44+
end
45+
46+
def initialize(repository, raw_tag)
47+
@repository = repository
48+
@raw_tag = raw_tag
49+
50+
case raw_tag
51+
when Hash
52+
init_from_hash
53+
when Gitaly::Tag
54+
init_from_gitaly
55+
end
556

6-
def initialize(repository, name, target, target_commit, message = nil)
757
super(repository, name, target, target_commit)
58+
end
59+
60+
def init_from_hash
61+
raw_tag = @raw_tag.symbolize_keys
62+
63+
SERIALIZE_KEYS.each do |key|
64+
send("#{key}=", raw_tag[key]) # rubocop:disable GitlabSecurity/PublicSend
65+
end
66+
end
67+
68+
def init_from_gitaly
69+
@name = encode!(@raw_tag.name.dup)
70+
@target = @raw_tag.id
71+
@message = message_from_gitaly_tag
872

9-
@message = message
73+
if @raw_tag.target_commit.present?
74+
@target_commit = Gitlab::Git::Commit.decorate(repository, @raw_tag.target_commit)
75+
end
1076
end
1177

1278
def message
1379
encode! @message
1480
end
81+
82+
private
83+
84+
def message_from_gitaly_tag
85+
return @raw_tag.message.dup if full_message_fetched_from_gitaly?
86+
87+
if @raw_tag.message_size > MAX_TAG_MESSAGE_DISPLAY_SIZE
88+
'--tag message is too big'
89+
else
90+
self.class.get_message(@repository, target)
91+
end
92+
end
93+
94+
def full_message_fetched_from_gitaly?
95+
@raw_tag.message.bytesize == @raw_tag.message_size
96+
end
1597
end
1698
end
1799
end

lib/gitlab/gitaly_client/commit_service.rb

+16
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,22 @@ def get_commit_signatures(commit_ids)
334334
signatures
335335
end
336336

337+
def get_commit_messages(commit_ids)
338+
request = Gitaly::GetCommitMessagesRequest.new(repository: @gitaly_repo, commit_ids: commit_ids)
339+
response = GitalyClient.call(@repository.storage, :commit_service, :get_commit_messages, request)
340+
341+
messages = Hash.new { |h, k| h[k] = ''.b }
342+
current_commit_id = nil
343+
344+
response.each do |rpc_message|
345+
current_commit_id = rpc_message.commit_id if rpc_message.commit_id.present?
346+
347+
messages[current_commit_id] << rpc_message.message
348+
end
349+
350+
messages
351+
end
352+
337353
private
338354

339355
def call_commit_diff(request_params, options = {})

lib/gitlab/gitaly_client/operation_service.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def add_tag(tag_name, user, target, message)
4040
raise Gitlab::Git::Repository::TagExistsError
4141
end
4242

43-
Util.gitlab_tag_from_gitaly_tag(@repository, response.tag)
43+
Gitlab::Git::Tag.new(@repository, response.tag)
4444
rescue GRPC::FailedPrecondition => e
4545
raise Gitlab::Git::Repository::InvalidRef, e
4646
end

lib/gitlab/gitaly_client/ref_service.rb

+17-1
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,22 @@ def branch_names_contains_sha(sha, limit: 0)
171171
consume_ref_contains_sha_response(stream, :branch_names)
172172
end
173173

174+
def get_tag_messages(tag_ids)
175+
request = Gitaly::GetTagMessagesRequest.new(repository: @gitaly_repo, tag_ids: tag_ids)
176+
response = GitalyClient.call(@repository.storage, :ref_service, :get_tag_messages, request)
177+
178+
messages = Hash.new { |h, k| h[k] = ''.b }
179+
current_tag_id = nil
180+
181+
response.each do |rpc_message|
182+
current_tag_id = rpc_message.tag_id if rpc_message.tag_id.present?
183+
184+
messages[current_tag_id] << rpc_message.message
185+
end
186+
187+
messages
188+
end
189+
174190
private
175191

176192
def consume_refs_response(response)
@@ -210,7 +226,7 @@ def consume_find_all_branches_response(response)
210226

211227
def consume_tags_response(response)
212228
response.flat_map do |message|
213-
message.tags.map { |gitaly_tag| Util.gitlab_tag_from_gitaly_tag(@repository, gitaly_tag) }
229+
message.tags.map { |gitaly_tag| Gitlab::Git::Tag.new(@repository, gitaly_tag) }
214230
end
215231
end
216232

lib/gitlab/gitaly_client/util.rb

-14
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,6 @@ def git_repository(gitaly_repository)
2121
gitaly_repository.relative_path,
2222
gitaly_repository.gl_repository)
2323
end
24-
25-
def gitlab_tag_from_gitaly_tag(repository, gitaly_tag)
26-
if gitaly_tag.target_commit.present?
27-
commit = Gitlab::Git::Commit.decorate(repository, gitaly_tag.target_commit)
28-
end
29-
30-
Gitlab::Git::Tag.new(
31-
repository,
32-
Gitlab::EncodingHelper.encode!(gitaly_tag.name.dup),
33-
gitaly_tag.id,
34-
commit,
35-
Gitlab::EncodingHelper.encode!(gitaly_tag.message.chomp)
36-
)
37-
end
3824
end
3925
end
4026
end

spec/factories/gitaly/tag.rb

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
FactoryBot.define do
2+
factory :gitaly_tag, class: Gitaly::Tag do
3+
skip_create
4+
5+
name { 'v3.1.4' }
6+
message { 'Pie release' }
7+
target_commit factory: :gitaly_commit
8+
end
9+
end

0 commit comments

Comments
 (0)