Commit 68a4c98f authored by Yorick Peterse's avatar Yorick Peterse

Cache output of Repository#exists?

This caches the output of Repository#exists? in Redis while making sure
it's flushed properly when creating new repositories, deleting them,
etc.

For the ProjectWiki tests to work I had to make ProjectWiki#create_repo!
public as testing private methods in RSpec is a bit of a pain.
parent 41b8d226
...@@ -876,6 +876,7 @@ class Project < ActiveRecord::Base ...@@ -876,6 +876,7 @@ class Project < ActiveRecord::Base
# Forked import is handled asynchronously # Forked import is handled asynchronously
unless forked? unless forked?
if gitlab_shell.add_repository(path_with_namespace) if gitlab_shell.add_repository(path_with_namespace)
repository.after_create
true true
else else
errors.add(:base, 'Failed to create repository via gitlab-shell') errors.add(:base, 'Failed to create repository via gitlab-shell')
......
...@@ -123,23 +123,27 @@ class ProjectWiki ...@@ -123,23 +123,27 @@ class ProjectWiki
end end
def repository def repository
Repository.new(path_with_namespace, @project) @repository ||= Repository.new(path_with_namespace, @project)
end end
def default_branch def default_branch
wiki.class.default_ref wiki.class.default_ref
end end
private
def create_repo! def create_repo!
if init_repo(path_with_namespace) if init_repo(path_with_namespace)
Gollum::Wiki.new(path_to_repo) wiki = Gollum::Wiki.new(path_to_repo)
else else
raise CouldNotCreateWikiError raise CouldNotCreateWikiError
end end
repository.after_create
wiki
end end
private
def init_repo(path_with_namespace) def init_repo(path_with_namespace)
gitlab_shell.add_repository(path_with_namespace) gitlab_shell.add_repository(path_with_namespace)
end end
......
...@@ -42,12 +42,15 @@ class Repository ...@@ -42,12 +42,15 @@ class Repository
end end
def exists? def exists?
return false unless raw_repository return @exists unless @exists.nil?
raw_repository.rugged @exists = cache.fetch(:exists?) do
true begin
rescue Gitlab::Git::Repository::NoRepository raw_repository && raw_repository.rugged ? true : false
false rescue Gitlab::Git::Repository::NoRepository
false
end
end
end end
def empty? def empty?
...@@ -320,12 +323,23 @@ class Repository ...@@ -320,12 +323,23 @@ class Repository
@avatar = nil @avatar = nil
end end
def expire_exists_cache
cache.expire(:exists?)
@exists = nil
end
# Runs code after a repository has been created.
def after_create
expire_exists_cache
end
# Runs code just before a repository is deleted. # Runs code just before a repository is deleted.
def before_delete def before_delete
expire_cache if exists? expire_cache if exists?
expire_root_ref_cache expire_root_ref_cache
expire_emptiness_caches expire_emptiness_caches
expire_exists_cache
end end
# Runs code just before the HEAD of a repository is changed. # Runs code just before the HEAD of a repository is changed.
...@@ -351,6 +365,7 @@ class Repository ...@@ -351,6 +365,7 @@ class Repository
# Runs code after a repository has been forked/imported. # Runs code after a repository has been forked/imported.
def after_import def after_import
expire_emptiness_caches expire_emptiness_caches
expire_exists_cache
end end
# Runs code after a new commit has been pushed. # Runs code after a new commit has been pushed.
......
...@@ -20,14 +20,15 @@ class RepositoryForkWorker ...@@ -20,14 +20,15 @@ class RepositoryForkWorker
return return
end end
project.repository.after_import
unless project.valid_repo? unless project.valid_repo?
logger.error("Project #{id} had an invalid repository after fork") logger.error("Project #{project_id} had an invalid repository after fork")
project.update(import_error: "The forked repository is invalid.") project.update(import_error: "The forked repository is invalid.")
project.import_fail project.import_fail
return return
end end
project.repository.after_import
project.import_finish project.import_finish
end end
end end
...@@ -720,4 +720,45 @@ describe Project, models: true do ...@@ -720,4 +720,45 @@ describe Project, models: true do
expect(described_class.search_by_title('KITTENS')).to eq([project]) expect(described_class.search_by_title('KITTENS')).to eq([project])
end end
end end
describe '#create_repository' do
let(:project) { create(:project) }
let(:shell) { Gitlab::Shell.new }
before do
allow(project).to receive(:gitlab_shell).and_return(shell)
end
context 'using a regular repository' do
it 'creates the repository' do
expect(shell).to receive(:add_repository).
with(project.path_with_namespace).
and_return(true)
expect(project.repository).to receive(:after_create)
expect(project.create_repository).to eq(true)
end
it 'adds an error if the repository could not be created' do
expect(shell).to receive(:add_repository).
with(project.path_with_namespace).
and_return(false)
expect(project.repository).not_to receive(:after_create)
expect(project.create_repository).to eq(false)
expect(project.errors).not_to be_empty
end
end
context 'using a forked repository' do
it 'does nothing' do
expect(project).to receive(:forked?).and_return(true)
expect(shell).not_to receive(:add_repository)
project.create_repository
end
end
end
end end
...@@ -244,6 +244,18 @@ describe ProjectWiki, models: true do ...@@ -244,6 +244,18 @@ describe ProjectWiki, models: true do
end end
end end
describe '#create_repo!' do
it 'creates a repository' do
expect(subject).to receive(:init_repo).
with(subject.path_with_namespace).
and_return(true)
expect(subject.repository).to receive(:after_create)
expect(subject.create_repo!).to be_an_instance_of(Gollum::Wiki)
end
end
private private
def create_temp_repo(path) def create_temp_repo(path)
......
...@@ -537,6 +537,12 @@ describe Repository, models: true do ...@@ -537,6 +537,12 @@ describe Repository, models: true do
repository.before_delete repository.before_delete
end end
it 'flushes the exists cache' do
expect(repository).to receive(:expire_exists_cache)
repository.before_delete
end
end end
describe 'when a repository exists' do describe 'when a repository exists' do
...@@ -593,6 +599,12 @@ describe Repository, models: true do ...@@ -593,6 +599,12 @@ describe Repository, models: true do
repository.after_import repository.after_import
end end
it 'flushes the exists cache' do
expect(repository).to receive(:expire_exists_cache)
repository.after_import
end
end end
describe '#after_push_commit' do describe '#after_push_commit' do
...@@ -619,6 +631,14 @@ describe Repository, models: true do ...@@ -619,6 +631,14 @@ describe Repository, models: true do
end end
end end
describe '#after_create' do
it 'flushes the exists cache' do
expect(repository).to receive(:expire_exists_cache)
repository.after_create
end
end
describe "#main_language" do describe "#main_language" do
it 'shows the main language of the project' do it 'shows the main language of the project' do
expect(repository.main_language).to eq("Ruby") expect(repository.main_language).to eq("Ruby")
...@@ -781,6 +801,16 @@ describe Repository, models: true do ...@@ -781,6 +801,16 @@ describe Repository, models: true do
end end
end end
describe '#expire_exists_cache' do
let(:cache) { repository.send(:cache) }
it 'expires the cache' do
expect(cache).to receive(:expire).with(:exists?)
repository.expire_exists_cache
end
end
describe '#build_cache' do describe '#build_cache' do
let(:cache) { repository.send(:cache) } let(:cache) { repository.send(:cache) }
......
...@@ -3,12 +3,17 @@ require 'spec_helper' ...@@ -3,12 +3,17 @@ require 'spec_helper'
describe RepositoryForkWorker do describe RepositoryForkWorker do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) } let(:fork_project) { create(:project, forked_from_project: project) }
let(:shell) { Gitlab::Shell.new }
subject { RepositoryForkWorker.new } subject { RepositoryForkWorker.new }
before do
allow(subject).to receive(:gitlab_shell).and_return(shell)
end
describe "#perform" do describe "#perform" do
it "creates a new repository from a fork" do it "creates a new repository from a fork" do
expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).with( expect(shell).to receive(:fork_repository).with(
project.path_with_namespace, project.path_with_namespace,
fork_project.namespace.path fork_project.namespace.path
).and_return(true) ).and_return(true)
...@@ -19,20 +24,26 @@ describe RepositoryForkWorker do ...@@ -19,20 +24,26 @@ describe RepositoryForkWorker do
fork_project.namespace.path) fork_project.namespace.path)
end end
it 'flushes the empty caches' do it 'flushes various caches' do
expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository). expect(shell).to receive(:fork_repository).
with(project.path_with_namespace, fork_project.namespace.path). with(project.path_with_namespace, fork_project.namespace.path).
and_return(true) and_return(true)
expect_any_instance_of(Repository).to receive(:expire_emptiness_caches). expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).
and_call_original and_call_original
expect_any_instance_of(Repository).to receive(:expire_exists_cache).
and_call_original
subject.perform(project.id, project.path_with_namespace, subject.perform(project.id, project.path_with_namespace,
fork_project.namespace.path) fork_project.namespace.path)
end end
it "handles bad fork" do it "handles bad fork" do
expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).and_return(false) expect(shell).to receive(:fork_repository).and_return(false)
expect(subject.logger).to receive(:error)
subject.perform( subject.perform(
project.id, project.id,
project.path_with_namespace, project.path_with_namespace,
......
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