Commit 0669127a authored by Nick Thomas's avatar Nick Thomas

Use cached readme blobs where appropriate

GitLab keeps a cache of the rendered HTML for a repository's README as
stored in the HEAD branch. However, it was not used in all
circumstances. In particular, the new blob viewer framework bypassed
this cache entirely.

This MR ensures a ::ReadmeBlob is returned instead of a ::Blob when
asking a repository for an individual blob, if the commit and path
match the readme for HEAD. This makes the cached HTML available to
consumers, including the blob viewer.

The ReadmeBlob is a simple delegator to the Blob, so should be
compatible in all cases. Adding the rendered_markdown method is the
only additional behaviour it contains.
parent e347170c
...@@ -487,7 +487,20 @@ class Repository ...@@ -487,7 +487,20 @@ class Repository
end end
def blob_at(sha, path) def blob_at(sha, path)
Blob.decorate(raw_repository.blob_at(sha, path), project) blob = Blob.decorate(raw_repository.blob_at(sha, path), project)
# Don't attempt to return a special result if there is no blob at all
return unless blob
# Don't attempt to return a special result unless we're looking at HEAD
return blob unless head_commit&.sha == sha
case path
when head_tree&.readme_path
ReadmeBlob.new(blob, self)
else
blob
end
rescue Gitlab::Git::Repository::NoRepository rescue Gitlab::Git::Repository::NoRepository
nil nil
end end
...@@ -569,9 +582,7 @@ class Repository ...@@ -569,9 +582,7 @@ class Repository
cache_method :merge_request_template_names, fallback: [] cache_method :merge_request_template_names, fallback: []
def readme def readme
if readme = tree(:head)&.readme head_tree&.readme
ReadmeBlob.new(readme, self)
end
end end
def rendered_readme def rendered_readme
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
class Tree class Tree
include Gitlab::MarkupHelper include Gitlab::MarkupHelper
include Gitlab::Utils::StrongMemoize
attr_accessor :repository, :sha, :path, :entries attr_accessor :repository, :sha, :path, :entries
...@@ -16,9 +17,8 @@ class Tree ...@@ -16,9 +17,8 @@ class Tree
@entries = Gitlab::Git::Tree.where(git_repo, @sha, @path, recursive) @entries = Gitlab::Git::Tree.where(git_repo, @sha, @path, recursive)
end end
def readme def readme_path
return @readme if defined?(@readme) strong_memoize(:readme_path) do
available_readmes = blobs.select do |blob| available_readmes = blobs.select do |blob|
Gitlab::FileDetector.type_of(blob.name) == :readme Gitlab::FileDetector.type_of(blob.name) == :readme
end end
...@@ -32,16 +32,21 @@ class Tree ...@@ -32,16 +32,21 @@ class Tree
end end
# Prioritize previewable over plain readmes # Prioritize previewable over plain readmes
readme_tree = previewable_readmes.first || plain_readmes.first entry = previewable_readmes.first || plain_readmes.first
next nil unless entry
# Return if we can't preview any of them if path == '/'
if readme_tree.nil? entry.name
return @readme = nil else
File.join(path, entry.name)
end
end
end end
readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name) def readme
strong_memoize(:readme) do
@readme = repository.blob_at(sha, readme_path) repository.blob_at(sha, readme_path) if readme_path
end
end end
def trees def trees
......
---
title: Use cached readme contents when available
merge_request: 22325
author:
type: performance
...@@ -527,28 +527,28 @@ describe Project do ...@@ -527,28 +527,28 @@ describe Project do
end end
describe "#readme_url" do describe "#readme_url" do
let(:project) { create(:project, :repository, path: "somewhere") }
context 'with a non-existing repository' do context 'with a non-existing repository' do
it 'returns nil' do let(:project) { create(:project) }
allow(project.repository).to receive(:tree).with(:head).and_return(nil)
it 'returns nil' do
expect(project.readme_url).to be_nil expect(project.readme_url).to be_nil
end end
end end
context 'with an existing repository' do context 'with an existing repository' do
context 'when no README exists' do context 'when no README exists' do
it 'returns nil' do let(:project) { create(:project, :empty_repo) }
allow_any_instance_of(Tree).to receive(:readme).and_return(nil)
it 'returns nil' do
expect(project.readme_url).to be_nil expect(project.readme_url).to be_nil
end end
end end
context 'when a README exists' do context 'when a README exists' do
let(:project) { create(:project, :repository) }
it 'returns the README' do it 'returns the README' do
expect(project.readme_url).to eql("#{Gitlab.config.gitlab.url}/#{project.namespace.full_path}/somewhere/blob/master/README.md") expect(project.readme_url).to eq("#{project.web_url}/blob/master/README.md")
end end
end end
end end
......
...@@ -461,6 +461,24 @@ describe Repository do ...@@ -461,6 +461,24 @@ describe Repository do
it { is_expected.to be_nil } it { is_expected.to be_nil }
end end
context 'regular blob' do
subject { repository.blob_at(repository.head_commit.sha, '.gitignore') }
it { is_expected.to be_an_instance_of(::Blob) }
end
context 'readme blob on HEAD' do
subject { repository.blob_at(repository.head_commit.sha, 'README.md') }
it { is_expected.to be_an_instance_of(::ReadmeBlob) }
end
context 'readme blob not on HEAD' do
subject { repository.blob_at(repository.find_branch('feature').target, 'README.md') }
it { is_expected.to be_an_instance_of(::Blob) }
end
end end
describe '#merged_to_root_ref?' do describe '#merged_to_root_ref?' do
...@@ -1922,23 +1940,25 @@ describe Repository do ...@@ -1922,23 +1940,25 @@ describe Repository do
describe '#readme', :use_clean_rails_memory_store_caching do describe '#readme', :use_clean_rails_memory_store_caching do
context 'with a non-existing repository' do context 'with a non-existing repository' do
it 'returns nil' do let(:project) { create(:project) }
allow(repository).to receive(:tree).with(:head).and_return(nil)
it 'returns nil' do
expect(repository.readme).to be_nil expect(repository.readme).to be_nil
end end
end end
context 'with an existing repository' do context 'with an existing repository' do
context 'when no README exists' do context 'when no README exists' do
it 'returns nil' do let(:project) { create(:project, :empty_repo) }
allow_any_instance_of(Tree).to receive(:readme).and_return(nil)
it 'returns nil' do
expect(repository.readme).to be_nil expect(repository.readme).to be_nil
end end
end end
context 'when a README exists' do context 'when a README exists' do
let(:project) { create(:project, :repository) }
it 'returns the README' do it 'returns the README' do
expect(repository.readme).to be_an_instance_of(ReadmeBlob) expect(repository.readme).to be_an_instance_of(ReadmeBlob)
end end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment