summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorick Peterse <yorickpeterse@gmail.com>2016-06-15 18:07:04 +0200
committerYorick Peterse <yorickpeterse@gmail.com>2016-06-16 13:04:59 +0200
commit425987861530c9c0fb7fe618d7f4bab017a80253 (patch)
treefbeb789604d219eb26049e0f1e5280bc66642b7f
parent33b68f87803edc2b484e94e277dcaf67175340c1 (diff)
downloadgitlab-ce-fix-project-find-with-namespace-order.tar.gz
Fixed ordering in Project.find_with_namespacefix-project-find-with-namespace-order
This ensures that Project.find_with_namespace returns a row matching literally as the first value, instead of returning a random value. The ordering here is _only_ applied to Project.find_with_namespace and _not_ Project.where_paths_in as currently there's no code that requires Project.where_paths_in to return rows in a certain order. Since this method also returns all rows that match there's no real harm in not setting a specific order either. Another reason is that generating all the "WHEN" arms for multiple values in Project.where_paths_in becomes really messy. On MySQL we have to use the "BINARY" operator to turn a "WHERE" into a case-sensitive WHERE as otherwise MySQL may still end up returning rows in an unpredictable order. Fixes gitlab-org/gitlab-ce#18603
-rw-r--r--app/models/project.rb18
-rw-r--r--spec/models/project_spec.rb18
2 files changed, 34 insertions, 2 deletions
diff --git a/app/models/project.rb b/app/models/project.rb
index fdbc84474ed..9228ccab718 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -262,7 +262,23 @@ class Project < ActiveRecord::Base
#
# Returns a Project, or nil if no project could be found.
def find_with_namespace(path)
- where_paths_in([path]).reorder(nil).take
+ namespace_path, project_path = path.split('/', 2)
+
+ return unless namespace_path && project_path
+
+ namespace_path = connection.quote(namespace_path)
+ project_path = connection.quote(project_path)
+
+ # On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
+ # any literal matches come first, for this we have to use "BINARY".
+ # Without this there's still no guarantee in what order MySQL will return
+ # rows.
+ binary = Gitlab::Database.mysql? ? 'BINARY' : ''
+
+ order_sql = "(CASE WHEN #{binary} namespaces.path = #{namespace_path} " \
+ "AND #{binary} projects.path = #{project_path} THEN 0 ELSE 1 END)"
+
+ where_paths_in([path]).reorder(order_sql).take
end
# Builds a relation to find multiple projects by their full paths.
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index fedab1f913b..53c8408633c 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -220,7 +220,7 @@ describe Project, models: true do
end
end
- describe :find_with_namespace do
+ describe '.find_with_namespace' do
context 'with namespace' do
before do
@group = create :group, name: 'gitlab'
@@ -231,6 +231,22 @@ describe Project, models: true do
it { expect(Project.find_with_namespace('GitLab/GitlabHQ')).to eq(@project) }
it { expect(Project.find_with_namespace('gitlab-ci')).to be_nil }
end
+
+ context 'when multiple projects using a similar name exist' do
+ let(:group) { create(:group, name: 'gitlab') }
+
+ let!(:project1) do
+ create(:empty_project, name: 'gitlab1', path: 'gitlab', namespace: group)
+ end
+
+ let!(:project2) do
+ create(:empty_project, name: 'gitlab2', path: 'GITLAB', namespace: group)
+ end
+
+ it 'returns the row where the path matches literally' do
+ expect(Project.find_with_namespace('gitlab/GITLAB')).to eq(project2)
+ end
+ end
end
describe :to_param do