Fix project moved message after git operation

parent a799004a
---
title: Fix project moved message after git operation
merge_request: 27341
author:
type: fixed
...@@ -8,7 +8,7 @@ module EE ...@@ -8,7 +8,7 @@ module EE
return super unless License.feature_available?(:project_aliases) return super unless License.feature_available?(:project_aliases)
if project_alias = ProjectAlias.find_by_name(project_path) if project_alias = ProjectAlias.find_by_name(project_path)
[project_alias.project, false] [project_alias.project, nil]
else else
super super
end end
......
...@@ -25,14 +25,14 @@ describe Gitlab::RepoPath do ...@@ -25,14 +25,14 @@ describe Gitlab::RepoPath do
let(:project_alias) { create(:project_alias, project: project) } let(:project_alias) { create(:project_alias, project: project) }
it 'returns the project' do it 'returns the project' do
expect(described_class.find_project(project_alias.name)).to eq([project, false]) expect(described_class.find_project(project_alias.name)).to eq([project, nil])
end end
end end
context 'project_path does not match a project alias' do context 'project_path does not match a project alias' do
context 'project path matches project full path' do context 'project path matches project full path' do
it 'returns the project' do it 'returns the project' do
expect(described_class.find_project(project.full_path)).to eq([project, false]) expect(described_class.find_project(project.full_path)).to eq([project, nil])
end end
end end
......
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
module Gitlab module Gitlab
module Checks module Checks
class PostPushMessage class PostPushMessage
def initialize(project, user, protocol) def initialize(repository, user, protocol)
@project = project @repository = repository
@user = user @user = user
@protocol = protocol @protocol = protocol
end end
...@@ -34,14 +34,21 @@ module Gitlab ...@@ -34,14 +34,21 @@ module Gitlab
protected protected
attr_reader :project, :user, :protocol attr_reader :repository, :user, :protocol
delegate :project, to: :repository, allow_nil: true
delegate :container, to: :repository, allow_nil: false
def self.message_key(user_id, project_id) def self.message_key(user_id, project_id)
raise NotImplementedError raise NotImplementedError
end end
def url_to_repo def url_to_repo
protocol == 'ssh' ? project.ssh_url_to_repo : project.http_url_to_repo protocol == 'ssh' ? message_subject.ssh_url_to_repo : message_subject.http_url_to_repo
end
def message_subject
repository.repo_type.wiki? ? project.wiki : container
end end
end end
end end
......
...@@ -5,10 +5,10 @@ module Gitlab ...@@ -5,10 +5,10 @@ module Gitlab
class ProjectMoved < PostPushMessage class ProjectMoved < PostPushMessage
REDIRECT_NAMESPACE = "redirect_namespace" REDIRECT_NAMESPACE = "redirect_namespace"
def initialize(project, user, protocol, redirected_path) def initialize(repository, user, protocol, redirected_path)
@redirected_path = redirected_path @redirected_path = redirected_path
super(project, user, protocol) super(repository, user, protocol)
end end
def message def message
......
...@@ -188,7 +188,7 @@ module Gitlab ...@@ -188,7 +188,7 @@ module Gitlab
def add_project_moved_message! def add_project_moved_message!
return if redirected_path.nil? return if redirected_path.nil?
project_moved = Checks::ProjectMoved.new(project, user, protocol, redirected_path) project_moved = Checks::ProjectMoved.new(repository, user, protocol, redirected_path)
project_moved.add_message project_moved.add_message
end end
...@@ -250,7 +250,7 @@ module Gitlab ...@@ -250,7 +250,7 @@ module Gitlab
@project = project @project = project
user_access.project = @project user_access.project = @project
Checks::ProjectCreated.new(project, user, protocol).add_message Checks::ProjectCreated.new(repository, user, protocol).add_message
end end
def check_repository_existence! def check_repository_existence!
......
...@@ -39,11 +39,11 @@ module Gitlab ...@@ -39,11 +39,11 @@ module Gitlab
override :check_project! override :check_project!
def check_project!(cmd, changes) def check_project!(cmd, changes)
if snippet.is_a?(ProjectSnippet) return unless snippet.is_a?(ProjectSnippet)
check_namespace! check_namespace!
check_project_accessibility! check_project_accessibility!
# TODO add add_project_moved_message! to handle non-project repo https://gitlab.com/gitlab-org/gitlab/issues/205646 add_project_moved_message!
end
end end
override :check_push_access! override :check_push_access!
......
...@@ -19,8 +19,7 @@ module Gitlab ...@@ -19,8 +19,7 @@ module Gitlab
# Removing the suffix (.wiki, .design, ...) from the project path # Removing the suffix (.wiki, .design, ...) from the project path
full_path = repo_path.chomp(type.path_suffix) full_path = repo_path.chomp(type.path_suffix)
container, project, was_redirected = find_container(type, full_path) container, project, redirected_path = find_container(type, full_path)
redirected_path = repo_path if was_redirected
return [container, project, type, redirected_path] if container return [container, project, type, redirected_path] if container
end end
...@@ -33,22 +32,23 @@ module Gitlab ...@@ -33,22 +32,23 @@ module Gitlab
def self.find_container(type, full_path) def self.find_container(type, full_path)
if type.snippet? if type.snippet?
snippet, was_redirected = find_snippet(full_path) snippet, redirected_path = find_snippet(full_path)
[snippet, snippet&.project, was_redirected] [snippet, snippet&.project, redirected_path]
else else
project, was_redirected = find_project(full_path) project, redirected_path = find_project(full_path)
[project, project, was_redirected] [project, project, redirected_path]
end end
end end
def self.find_project(project_path) def self.find_project(project_path)
return [nil, false] if project_path.blank? return [nil, nil] if project_path.blank?
project = Project.find_by_full_path(project_path, follow_redirects: true) project = Project.find_by_full_path(project_path, follow_redirects: true)
redirected_path = redirected?(project, project_path) ? project_path : nil
[project, redirected?(project, project_path)] [project, redirected_path]
end end
def self.redirected?(project, project_path) def self.redirected?(project, project_path)
...@@ -59,12 +59,12 @@ module Gitlab ...@@ -59,12 +59,12 @@ module Gitlab
# - snippets/1 # - snippets/1
# - h5bp/html5-boilerplate/snippets/53 # - h5bp/html5-boilerplate/snippets/53
def self.find_snippet(snippet_path) def self.find_snippet(snippet_path)
return [nil, false] if snippet_path.blank? return [nil, nil] if snippet_path.blank?
snippet_id, project_path = extract_snippet_info(snippet_path) snippet_id, project_path = extract_snippet_info(snippet_path)
project, was_redirected = find_project(project_path) project, redirected_path = find_project(project_path)
[Snippet.find_by_id_and_project(id: snippet_id, project: project), was_redirected] [Snippet.find_by_id_and_project(id: snippet_id, project: project), redirected_path]
end end
def self.extract_snippet_info(snippet_path) def self.extract_snippet_info(snippet_path)
......
...@@ -3,24 +3,29 @@ ...@@ -3,24 +3,29 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:project) { create(:project) } let_it_be(:project) { create(:project, :repository, namespace: user.namespace) }
let(:protocol) { 'http' }
let(:git_user) { user }
let(:repository) { project.repository }
subject { described_class.new(repository, git_user, 'http') }
describe '.fetch_message' do describe '.fetch_message' do
context 'with a project created message queue' do context 'with a project created message queue' do
let(:project_created) { described_class.new(project, user, 'http') }
before do before do
project_created.add_message subject.add_message
end end
it 'returns project created message' do it 'returns project created message' do
expect(described_class.fetch_message(user.id, project.id)).to eq(project_created.message) expect(described_class.fetch_message(user.id, project.id)).to eq(subject.message)
end end
it 'deletes the project created message from redis' do it 'deletes the project created message from redis' do
expect(Gitlab::Redis::SharedState.with { |redis| redis.get("project_created:#{user.id}:#{project.id}") }).not_to be_nil expect(Gitlab::Redis::SharedState.with { |redis| redis.get("project_created:#{user.id}:#{project.id}") }).not_to be_nil
described_class.fetch_message(user.id, project.id) described_class.fetch_message(user.id, project.id)
expect(Gitlab::Redis::SharedState.with { |redis| redis.get("project_created:#{user.id}:#{project.id}") }).to be_nil expect(Gitlab::Redis::SharedState.with { |redis| redis.get("project_created:#{user.id}:#{project.id}") }).to be_nil
end end
end end
...@@ -34,15 +39,15 @@ describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do ...@@ -34,15 +39,15 @@ describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
describe '#add_message' do describe '#add_message' do
it 'queues a project created message' do it 'queues a project created message' do
project_created = described_class.new(project, user, 'http') expect(subject.add_message).to eq('OK')
expect(project_created.add_message).to eq('OK')
end end
it 'handles anonymous push' do context 'when user is nil' do
project_created = described_class.new(nil, user, 'http') let(:git_user) { nil }
expect(project_created.add_message).to be_nil it 'handles anonymous push' do
expect(subject.add_message).to be_nil
end
end end
end end
end end
...@@ -3,24 +3,30 @@ ...@@ -3,24 +3,30 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
let(:user) { create(:user) } let_it_be(:user) { create(:user) }
let(:project) { create(:project) } let_it_be(:project) { create(:project, :repository, :wiki_repo, namespace: user.namespace) }
let(:repository) { project.repository }
let(:protocol) { 'http' }
let(:git_user) { user }
let(:redirect_path) { 'foo/bar' }
subject { described_class.new(repository, git_user, protocol, redirect_path) }
describe '.fetch_message' do describe '.fetch_message' do
context 'with a redirect message queue' do context 'with a redirect message queue' do
it 'returns the redirect message' do before do
project_moved = described_class.new(project, user, 'http', 'foo/bar') subject.add_message
project_moved.add_message end
expect(described_class.fetch_message(user.id, project.id)).to eq(project_moved.message) it 'returns the redirect message' do
expect(described_class.fetch_message(user.id, project.id)).to eq(subject.message)
end end
it 'deletes the redirect message from redis' do it 'deletes the redirect message from redis' do
project_moved = described_class.new(project, user, 'http', 'foo/bar')
project_moved.add_message
expect(Gitlab::Redis::SharedState.with { |redis| redis.get("redirect_namespace:#{user.id}:#{project.id}") }).not_to be_nil expect(Gitlab::Redis::SharedState.with { |redis| redis.get("redirect_namespace:#{user.id}:#{project.id}") }).not_to be_nil
described_class.fetch_message(user.id, project.id) described_class.fetch_message(user.id, project.id)
expect(Gitlab::Redis::SharedState.with { |redis| redis.get("redirect_namespace:#{user.id}:#{project.id}") }).to be_nil expect(Gitlab::Redis::SharedState.with { |redis| redis.get("redirect_namespace:#{user.id}:#{project.id}") }).to be_nil
end end
end end
...@@ -34,29 +40,82 @@ describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do ...@@ -34,29 +40,82 @@ describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
describe '#add_message' do describe '#add_message' do
it 'queues a redirect message' do it 'queues a redirect message' do
project_moved = described_class.new(project, user, 'http', 'foo/bar') expect(subject.add_message).to eq("OK")
expect(project_moved.add_message).to eq("OK")
end end
it 'handles anonymous clones' do context 'when user is nil' do
project_moved = described_class.new(project, nil, 'http', 'foo/bar') let(:git_user) { nil }
expect(project_moved.add_message).to eq(nil) it 'handles anonymous clones' do
expect(subject.add_message).to be_nil
end
end end
end end
describe '#message' do describe '#message' do
it 'returns a redirect message' do shared_examples 'errors per protocol' do
project_moved = described_class.new(project, user, 'http', 'foo/bar') shared_examples 'returns redirect message' do
it do
message = <<~MSG message = <<~MSG
Project 'foo/bar' was moved to '#{project.full_path}'. Project '#{redirect_path}' was moved to '#{project.full_path}'.
Please update your Git remote: Please update your Git remote:
git remote set-url origin #{project.http_url_to_repo} git remote set-url origin #{url_to_repo}
MSG MSG
expect(project_moved.message).to eq(message) expect(subject.message).to eq(message)
end
end
context 'when protocol is http' do
it_behaves_like 'returns redirect message' do
let(:url_to_repo) { http_url_to_repo }
end
end
context 'when protocol is ssh' do
let(:protocol) { 'ssh' }
it_behaves_like 'returns redirect message' do
let(:url_to_repo) { ssh_url_to_repo }
end
end
end
context 'with project' do
it_behaves_like 'errors per protocol' do
let(:http_url_to_repo) { project.http_url_to_repo }
let(:ssh_url_to_repo) { project.ssh_url_to_repo }
end
end
context 'with wiki' do
let(:repository) { project.wiki.repository }
it_behaves_like 'errors per protocol' do
let(:http_url_to_repo) { project.wiki.http_url_to_repo }
let(:ssh_url_to_repo) { project.wiki.ssh_url_to_repo }
end
end
context 'with project snippet' do
let_it_be(:snippet) { create(:project_snippet, :repository, project: project, author: user) }
let(:repository) { snippet.repository }
it_behaves_like 'errors per protocol' do
let(:http_url_to_repo) { snippet.http_url_to_repo }
let(:ssh_url_to_repo) { snippet.ssh_url_to_repo }
end
end
context 'with personal snippet' do
let_it_be(:snippet) { create(:personal_snippet, :repository, author: user) }
let(:repository) { snippet.repository }
it 'returns nil' do
expect(subject.add_message).to be_nil
end
end end
end end
end end
...@@ -8,7 +8,8 @@ describe ::Gitlab::RepoPath do ...@@ -8,7 +8,8 @@ describe ::Gitlab::RepoPath do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:personal_snippet) { create(:personal_snippet) } let_it_be(:personal_snippet) { create(:personal_snippet) }
let_it_be(:project_snippet) { create(:project_snippet, project: project) } let_it_be(:project_snippet) { create(:project_snippet, project: project) }
let_it_be(:redirect) { project.route.create_redirect('foo/bar/baz') } let_it_be(:redirect_route) { 'foo/bar/baz' }
let_it_be(:redirect) { project.route.create_redirect(redirect_route) }
describe '.parse' do describe '.parse' do
context 'a repository storage path' do context 'a repository storage path' do
...@@ -43,22 +44,20 @@ describe ::Gitlab::RepoPath do ...@@ -43,22 +44,20 @@ describe ::Gitlab::RepoPath do
end end
context 'of a redirected project' do context 'of a redirected project' do
let(:redirect) { project.route.create_redirect('foo/bar') }
it 'parses a relative repository path' do it 'parses a relative repository path' do
expect(described_class.parse(redirect.path + '.git')).to eq([project, project, Gitlab::GlRepository::PROJECT, 'foo/bar']) expect(described_class.parse(redirect.path + '.git')).to eq([project, project, Gitlab::GlRepository::PROJECT, redirect_route])
end end
it 'parses a relative wiki path' do it 'parses a relative wiki path' do
expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, project, Gitlab::GlRepository::WIKI, 'foo/bar.wiki']) expect(described_class.parse(redirect.path + '.wiki.git')).to eq([project, project, Gitlab::GlRepository::WIKI, redirect_route])
end end
it 'parses a relative path starting with /' do it 'parses a relative path starting with /' do
expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, project, Gitlab::GlRepository::PROJECT, 'foo/bar']) expect(described_class.parse('/' + redirect.path + '.git')).to eq([project, project, Gitlab::GlRepository::PROJECT, redirect_route])
end end
it 'parses a redirected project snippet repository path' do it 'parses a redirected project snippet repository path' do
expect(described_class.parse(redirect.path + "/snippets/#{project_snippet.id}.git")).to eq([project_snippet, project, Gitlab::GlRepository::SNIPPET, "foo/bar/snippets/#{project_snippet.id}"]) expect(described_class.parse(redirect.path + "/snippets/#{project_snippet.id}.git")).to eq([project_snippet, project, Gitlab::GlRepository::SNIPPET, redirect_route])
end end
end end
end end
...@@ -71,8 +70,8 @@ describe ::Gitlab::RepoPath do ...@@ -71,8 +70,8 @@ describe ::Gitlab::RepoPath do
describe '.find_project' do describe '.find_project' do
context 'when finding a project by its canonical path' do context 'when finding a project by its canonical path' do
context 'when the cases match' do context 'when the cases match' do
it 'returns the project and false' do it 'returns the project and nil' do
expect(described_class.find_project(project.full_path)).to eq([project, false]) expect(described_class.find_project(project.full_path)).to eq([project, nil])
end end
end end
...@@ -81,45 +80,45 @@ describe ::Gitlab::RepoPath do ...@@ -81,45 +80,45 @@ describe ::Gitlab::RepoPath do
# easy and safe to redirect someone to the correctly-cased URL. For git # easy and safe to redirect someone to the correctly-cased URL. For git
# requests, we should accept wrongly-cased URLs because it is a pain to # requests, we should accept wrongly-cased URLs because it is a pain to
# block people's git operations and force them to update remote URLs. # block people's git operations and force them to update remote URLs.
it 'returns the project and false' do it 'returns the project and nil' do
expect(described_class.find_project(project.full_path.upcase)).to eq([project, false]) expect(described_class.find_project(project.full_path.upcase)).to eq([project, nil])
end end
end end
end end
context 'when finding a project via a redirect' do context 'when finding a project via a redirect' do
it 'returns the project and true' do it 'returns the project and nil' do
expect(described_class.find_project(redirect.path)).to eq([project, true]) expect(described_class.find_project(redirect.path)).to eq([project, redirect.path])
end end
end end
end end
describe '.find_snippet' do describe '.find_snippet' do
it 'extracts path and id from personal snippet route' do it 'extracts path and id from personal snippet route' do
expect(described_class.find_snippet("snippets/#{personal_snippet.id}")).to eq([personal_snippet, false]) expect(described_class.find_snippet("snippets/#{personal_snippet.id}")).to eq([personal_snippet, nil])
end end
it 'extracts path and id from project snippet route' do it 'extracts path and id from project snippet route' do
expect(described_class.find_snippet("#{project.full_path}/snippets/#{project_snippet.id}")).to eq([project_snippet, false]) expect(described_class.find_snippet("#{project.full_path}/snippets/#{project_snippet.id}")).to eq([project_snippet, nil])
end end
it 'returns nil for invalid snippet paths' do it 'returns nil for invalid snippet paths' do
aggregate_failures do aggregate_failures do
expect(described_class.find_snippet("snippets/#{project_snippet.id}")).to eq([nil, false]) expect(described_class.find_snippet("snippets/#{project_snippet.id}")).to eq([nil, nil])
expect(described_class.find_snippet("#{project.full_path}/snippets/#{personal_snippet.id}")).to eq([nil, false]) expect(described_class.find_snippet("#{project.full_path}/snippets/#{personal_snippet.id}")).to eq([nil, nil])
expect(described_class.find_snippet('')).to eq([nil, false]) expect(described_class.find_snippet('')).to eq([nil, nil])
end end
end end
it 'returns nil for snippets not associated with the project' do it 'returns nil for snippets not associated with the project' do
snippet = create(:project_snippet) snippet = create(:project_snippet)
expect(described_class.find_snippet("#{project.full_path}/snippets/#{snippet.id}")).to eq([nil, false]) expect(described_class.find_snippet("#{project.full_path}/snippets/#{snippet.id}")).to eq([nil, nil])
end end
context 'when finding a project snippet via a redirect' do context 'when finding a project snippet via a redirect' do
it 'returns the project and true' do it 'returns the project and true' do
expect(described_class.find_snippet("#{redirect.path}/snippets/#{project_snippet.id}")).to eq([project_snippet, true]) expect(described_class.find_snippet("#{redirect.path}/snippets/#{project_snippet.id}")).to eq([project_snippet, redirect.path])
end end
end end
end end
......
...@@ -234,7 +234,7 @@ describe PostReceiveService do ...@@ -234,7 +234,7 @@ describe PostReceiveService do
context 'with a redirected data' do context 'with a redirected data' do
it 'returns redirected message on the response' do it 'returns redirected message on the response' do
project_moved = Gitlab::Checks::ProjectMoved.new(project, user, 'http', 'foo/baz') project_moved = Gitlab::Checks::ProjectMoved.new(project.repository, user, 'http', 'foo/baz')
project_moved.add_message project_moved.add_message
expect(subject).to include(build_basic_message(project_moved.message)) expect(subject).to include(build_basic_message(project_moved.message))
...@@ -243,7 +243,7 @@ describe PostReceiveService do ...@@ -243,7 +243,7 @@ describe PostReceiveService do
context 'with new project data' do context 'with new project data' do
it 'returns new project message on the response' do it 'returns new project message on the response' do
project_created = Gitlab::Checks::ProjectCreated.new(project, user, 'http') project_created = Gitlab::Checks::ProjectCreated.new(project.repository, user, 'http')
project_created.add_message project_created.add_message
expect(subject).to include(build_basic_message(project_created.message)) expect(subject).to include(build_basic_message(project_created.message))
......
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