Commit ffc3acd4 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'issues-show-performance' into 'master'

Improve performance of viewing individual issues

This MR does two things:

1. `Issue#related_branches` no longer performs Git operations that aren't needed
2. The output of `Repository#exists?` is now cached and flushed properly

Combined these two changes should further cut down the amount of Git operations performed when viewing individual issues (and possibly other pages).

See merge request !3296
parents 3fca30d2 75aaf91c
......@@ -108,9 +108,9 @@ class Issue < ActiveRecord::Base
end
def related_branches
return [] if self.project.empty_repo?
self.project.repository.branch_names.select { |branch| branch.end_with?("-#{iid}") }
project.repository.branch_names.select do |branch|
branch.end_with?("-#{iid}")
end
end
# Reset issue events cache
......
......@@ -876,6 +876,7 @@ class Project < ActiveRecord::Base
# Forked import is handled asynchronously
unless forked?
if gitlab_shell.add_repository(path_with_namespace)
repository.after_create
true
else
errors.add(:base, 'Failed to create repository via gitlab-shell')
......
......@@ -123,23 +123,27 @@ class ProjectWiki
end
def repository
Repository.new(path_with_namespace, @project)
@repository ||= Repository.new(path_with_namespace, @project)
end
def default_branch
wiki.class.default_ref
end
private
def create_repo!
if init_repo(path_with_namespace)
Gollum::Wiki.new(path_to_repo)
wiki = Gollum::Wiki.new(path_to_repo)
else
raise CouldNotCreateWikiError
end
repository.after_create
wiki
end
private
def init_repo(path_with_namespace)
gitlab_shell.add_repository(path_with_namespace)
end
......
......@@ -42,13 +42,16 @@ class Repository
end
def exists?
return false unless raw_repository
return @exists unless @exists.nil?
raw_repository.rugged
true
@exists = cache.fetch(:exists?) do
begin
raw_repository && raw_repository.rugged ? true : false
rescue Gitlab::Git::Repository::NoRepository
false
end
end
end
def empty?
return @empty unless @empty.nil?
......@@ -320,12 +323,23 @@ class Repository
@avatar = nil
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.
def before_delete
expire_cache if exists?
expire_root_ref_cache
expire_emptiness_caches
expire_exists_cache
end
# Runs code just before the HEAD of a repository is changed.
......@@ -351,6 +365,7 @@ class Repository
# Runs code after a repository has been forked/imported.
def after_import
expire_emptiness_caches
expire_exists_cache
end
# Runs code after a new commit has been pushed.
......
......@@ -20,14 +20,15 @@ class RepositoryForkWorker
return
end
project.repository.after_import
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.import_fail
return
end
project.repository.after_import
project.import_finish
end
end
......@@ -160,6 +160,7 @@ Feature: Project Issues
Scenario: Issues on empty project
Given empty project "Empty Project"
And I have an ssh key
When I visit empty project page
And I see empty project details with ssh clone info
When I visit empty project's issues page
......
......@@ -5,6 +5,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
include SharedNote
include SharedPaths
include SharedMarkdown
include SharedUser
step 'I should see "Release 0.4" in issues' do
expect(page).to have_content "Release 0.4"
......@@ -240,7 +241,7 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'empty project "Empty Project"' do
create :empty_project, name: 'Empty Project', namespace: @user.namespace
create :project_empty_repo, name: 'Empty Project', namespace: @user.namespace
end
When 'I visit empty project page' do
......
......@@ -2,7 +2,7 @@ require('spec_helper')
describe Projects::IssuesController do
describe "GET #index" do
let(:project) { create(:project) }
let(:project) { create(:project_empty_repo) }
let(:user) { create(:user) }
let(:issue) { create(:issue, project: project) }
......@@ -41,7 +41,7 @@ describe Projects::IssuesController do
end
describe 'Confidential Issues' do
let(:project) { create(:empty_project, :public) }
let(:project) { create(:project_empty_repo, :public) }
let(:assignee) { create(:assignee) }
let(:author) { create(:user) }
let(:non_member) { create(:user) }
......
......@@ -185,7 +185,7 @@ describe Issue, models: true do
allow(subject.project.repository).to receive(:branch_names).
and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name])
expect(subject.related_branches).to eq [subject.to_branch_name]
expect(subject.related_branches).to eq([subject.to_branch_name])
end
end
......
......@@ -720,4 +720,45 @@ describe Project, models: true do
expect(described_class.search_by_title('KITTENS')).to eq([project])
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
......@@ -244,6 +244,18 @@ describe ProjectWiki, models: true do
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
def create_temp_repo(path)
......
......@@ -537,6 +537,12 @@ describe Repository, models: true do
repository.before_delete
end
it 'flushes the exists cache' do
expect(repository).to receive(:expire_exists_cache)
repository.before_delete
end
end
describe 'when a repository exists' do
......@@ -593,6 +599,12 @@ describe Repository, models: true do
repository.after_import
end
it 'flushes the exists cache' do
expect(repository).to receive(:expire_exists_cache)
repository.after_import
end
end
describe '#after_push_commit' do
......@@ -619,6 +631,14 @@ describe Repository, models: true do
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
it 'shows the main language of the project' do
expect(repository.main_language).to eq("Ruby")
......@@ -781,6 +801,16 @@ describe Repository, models: true do
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
let(:cache) { repository.send(:cache) }
......
......@@ -3,12 +3,17 @@ require 'spec_helper'
describe RepositoryForkWorker do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:shell) { Gitlab::Shell.new }
subject { RepositoryForkWorker.new }
before do
allow(subject).to receive(:gitlab_shell).and_return(shell)
end
describe "#perform" 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,
fork_project.namespace.path
).and_return(true)
......@@ -19,20 +24,26 @@ describe RepositoryForkWorker do
fork_project.namespace.path)
end
it 'flushes the empty caches' do
expect_any_instance_of(Gitlab::Shell).to receive(:fork_repository).
it 'flushes various caches' do
expect(shell).to receive(:fork_repository).
with(project.path_with_namespace, fork_project.namespace.path).
and_return(true)
expect_any_instance_of(Repository).to receive(:expire_emptiness_caches).
and_call_original
expect_any_instance_of(Repository).to receive(:expire_exists_cache).
and_call_original
subject.perform(project.id, project.path_with_namespace,
fork_project.namespace.path)
end
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(
project.id,
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