diff --git a/lib/gitlab/diff/highlight_cache.rb b/lib/gitlab/diff/highlight_cache.rb index 0eb22e6b3cbd8c965bcc1929d0ef5d22ce529b00..e873e9c17d57cbe5af03f66ad607a974042345bf 100644 --- a/lib/gitlab/diff/highlight_cache.rb +++ b/lib/gitlab/diff/highlight_cache.rb @@ -20,10 +20,23 @@ def initialize(diff_collection) # - Assigns DiffFile#highlighted_diff_lines for cached files # def decorate(diff_file) - if content = read_file(diff_file) - diff_file.highlighted_diff_lines = content.map do |line| - Gitlab::Diff::Line.safe_init_from_hash(line) - end + content = read_file(diff_file) + + return [] unless content + + if content.empty? && recache_due_to_size?(diff_file) + # If the file is missing from the cache and there's reason to believe + # it is uncached due to a size issue around changing the values for + # max patch size, manually populate the hash and then set the value. + # + new_cache_content = {} + new_cache_content[diff_file.file_path] = diff_file.highlighted_diff_lines.map(&:to_hash) + + write_to_redis_hash(new_cache_content) + + set_highlighted_diff_lines(diff_file, read_file(diff_file)) + else + set_highlighted_diff_lines(diff_file, content) end end @@ -58,6 +71,28 @@ def key private + def set_highlighted_diff_lines(diff_file, content) + diff_file.highlighted_diff_lines = content.map do |line| + Gitlab::Diff::Line.safe_init_from_hash(line) + end + end + + def recache_due_to_size?(diff_file) + diff_file_class = diff_file.diff.class + + current_patch_safe_limit_bytes = diff_file_class.patch_safe_limit_bytes + default_patch_safe_limit_bytes = diff_file_class.patch_safe_limit_bytes(diff_file_class::DEFAULT_MAX_PATCH_BYTES) + + # If the diff is >= than the default limit, but less than the current + # limit, it is likely uncached due to having hit the default limit, + # making it eligible for recalculating. + # + diff_file.diff.diff_bytesize.between?( + default_patch_safe_limit_bytes, + current_patch_safe_limit_bytes + ) + end + def cacheable_files strong_memoize(:cacheable_files) do diff_files.select { |file| cacheable?(file) && read_file(file).nil? } diff --git a/lib/gitlab/git/diff.rb b/lib/gitlab/git/diff.rb index 09a49b6c1ca647a0da9399ca52f9519f8244468c..78c47023c081fa31d54f697ff8cbb53c0c227873 100644 --- a/lib/gitlab/git/diff.rb +++ b/lib/gitlab/git/diff.rb @@ -120,8 +120,8 @@ def binary_message(old_path, new_path) # default. # # Patches surpassing this limit should still be persisted in the database. - def patch_safe_limit_bytes - patch_hard_limit_bytes / 10 + def patch_safe_limit_bytes(limit = patch_hard_limit_bytes) + limit / 10 end # Returns the limit for a single diff file (patch). @@ -174,9 +174,13 @@ def line_count @line_count ||= Util.count_lines(@diff) end + def diff_bytesize + @diff_bytesize ||= @diff.bytesize + end + def too_large? if @too_large.nil? - @too_large = @diff.bytesize >= self.class.patch_hard_limit_bytes + @too_large = diff_bytesize >= self.class.patch_hard_limit_bytes else @too_large end @@ -194,7 +198,7 @@ def too_large! def collapsed? return @collapsed if defined?(@collapsed) - @collapsed = !expanded && @diff.bytesize >= self.class.patch_safe_limit_bytes + @collapsed = !expanded && diff_bytesize >= self.class.patch_safe_limit_bytes end def collapse! diff --git a/spec/lib/gitlab/diff/highlight_cache_spec.rb b/spec/lib/gitlab/diff/highlight_cache_spec.rb index 7e926f8609682689ce77c89a26621af0f57f2fa4..f6810d7a96670486025dd7bb45a2fcbf63865f3e 100644 --- a/spec/lib/gitlab/diff/highlight_cache_spec.rb +++ b/spec/lib/gitlab/diff/highlight_cache_spec.rb @@ -43,7 +43,8 @@ describe '#decorate' do # Manually creates a Diff::File object to avoid triggering the cache on - # the FileCollection::MergeRequestDiff + # the FileCollection::MergeRequestDiff + # let(:diff_file) do diffs = merge_request.diffs raw_diff = diffs.diffable.raw_diffs(diffs.diff_options.merge(paths: ['CHANGELOG'])).first @@ -73,6 +74,37 @@ expect(rich_texts).to all(be_html_safe) end + + context "when diff_file is uncached due to default_max_patch_bytes change" do + before do + expect(cache).to receive(:read_file).at_least(:once).and_return([]) + + # Stub out the application's default and current patch size limits. We + # want them to be different, and the diff file to be sized between + # the 2 values. + # + diff_file_size_kb = (diff_file.diff.diff.bytesize * 10) + + stub_const("#{diff_file.diff.class}::DEFAULT_MAX_PATCH_BYTES", diff_file_size_kb - 1 ) + expect(diff_file.diff.class).to receive(:patch_safe_limit_bytes).and_return(diff_file_size_kb + 1) + expect(diff_file.diff.class) + .to receive(:patch_safe_limit_bytes) + .with(diff_file.diff.class::DEFAULT_MAX_PATCH_BYTES) + .and_call_original + end + + it "manually writes highlighted lines to the cache" do + expect(cache).to receive(:write_to_redis_hash).and_call_original + + cache.decorate(diff_file) + end + + it "assigns highlighted diff lines to the DiffFile" do + expect(diff_file.highlighted_diff_lines.size).to be > 5 + + cache.decorate(diff_file) + end + end end shared_examples 'caches missing entries' do diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb index 117c519e98de6deb2366bcf8607a778f0f7afff9..980a52bb61e867cd5438dd1937ad14a62851c7f5 100644 --- a/spec/lib/gitlab/git/diff_spec.rb +++ b/spec/lib/gitlab/git/diff_spec.rb @@ -284,13 +284,21 @@ end describe '#line_count' do - it 'returns the correct number of lines' do - diff = described_class.new(gitaly_diff) + let(:diff) { described_class.new(gitaly_diff) } + it 'returns the correct number of lines' do expect(diff.line_count).to eq(7) end end + describe "#diff_bytesize" do + let(:diff) { described_class.new(gitaly_diff) } + + it "returns the size of the diff in bytes" do + expect(diff.diff_bytesize).to eq(diff.diff.bytesize) + end + end + describe '#too_large?' do it 'returns true for a diff that is too large' do diff = described_class.new(diff: 'a' * 204800)