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,32 +17,36 @@ class Tree ...@@ -16,32 +17,36 @@ 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
previewable_readmes = available_readmes.select do |blob|
previewable_readmes = available_readmes.select do |blob| previewable?(blob.name)
previewable?(blob.name) end
end
plain_readmes = available_readmes.select do |blob|
plain_readmes = available_readmes.select do |blob| plain?(blob.name)
plain?(blob.name) end
# Prioritize previewable over plain readmes
entry = previewable_readmes.first || plain_readmes.first
next nil unless entry
if path == '/'
entry.name
else
File.join(path, entry.name)
end
end end
end
# Prioritize previewable over plain readmes def readme
readme_tree = previewable_readmes.first || plain_readmes.first strong_memoize(:readme) do
repository.blob_at(sha, readme_path) if readme_path
# Return if we can't preview any of them
if readme_tree.nil?
return @readme = nil
end end
readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name)
@readme = repository.blob_at(sha, readme_path)
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