Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Boxiang Sun
gitlab-ce
Commits
6db1a280
Commit
6db1a280
authored
Jun 02, 2017
by
James Edwards-Jones
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Rollback project folder move after error in Projects::TransferService
parent
f07aee72
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
139 additions
and
40 deletions
+139
-40
app/services/projects/transfer_service.rb
app/services/projects/transfer_service.rb
+74
-40
changelogs/unreleased/30213-project-transfer-move-rollback.yml
...elogs/unreleased/30213-project-transfer-move-rollback.yml
+4
-0
spec/services/projects/transfer_service_spec.rb
spec/services/projects/transfer_service_spec.rb
+61
-0
No files found.
app/services/projects/transfer_service.rb
View file @
6db1a280
...
...
@@ -12,87 +12,121 @@ module Projects
TransferError
=
Class
.
new
(
StandardError
)
def
execute
(
new_namespace
)
if
new_namespace
.
blank?
@new_namespace
=
new_namespace
if
@new_namespace
.
blank?
raise
TransferError
,
'Please select a new namespace for your project.'
end
unless
allowed_transfer?
(
current_user
,
project
,
new_namespace
)
unless
allowed_transfer?
(
current_user
,
project
)
raise
TransferError
,
'Transfer failed, please contact an admin.'
end
transfer
(
project
,
new_namespace
)
transfer
(
project
)
true
rescue
Projects
::
TransferService
::
TransferError
=>
ex
project
.
reload
project
.
errors
.
add
(
:new_namespace
,
ex
.
message
)
false
end
def
transfer
(
project
,
new_namespace
)
old_namespace
=
project
.
namespace
private
Project
.
transaction
do
old_path
=
project
.
path_with_namespace
old_group
=
project
.
group
new_path
=
File
.
join
(
new_namespace
.
try
(
:full_path
)
||
''
,
project
.
path
)
def
transfer
(
project
)
@old_path
=
project
.
path_with_namespace
@old_group
=
project
.
group
@new_path
=
File
.
join
(
@new_namespace
.
try
(
:full_path
)
||
''
,
project
.
path
)
@old_namespace
=
project
.
namespace
if
Project
.
where
(
path:
project
.
path
,
namespace_id:
new_namespace
.
try
(
:id
)).
present
?
if
Project
.
where
(
path:
project
.
path
,
namespace_id:
@new_namespace
.
try
(
:id
)).
exists
?
raise
TransferError
.
new
(
"Project with same path in target namespace already exists"
)
end
if
project
.
has_container_registry_tags?
# we currently does
n't support renaming repository if it contains tags in container registry
# We currently do
n't support renaming repository if it contains tags in container registry
raise
TransferError
.
new
(
'Project cannot be transferred, because tags are present in its container registry'
)
end
project
.
expire_caches_before_rename
(
old_path
)
attempt_transfer_transaction
end
def
attempt_transfer_transaction
Project
.
transaction
do
project
.
expire_caches_before_rename
(
@old_path
)
# Apply new namespace id and visibility level
project
.
namespace
=
new_namespace
project
.
visibility_level
=
new_namespace
.
visibility_level
unless
project
.
visibility_level_allowed_by_group?
project
.
save!
update_namespace_and_visibility
(
@new_namespace
)
# Notifications
project
.
send_move_instructions
(
old_path
)
project
.
send_move_instructions
(
@
old_path
)
# Move main repository
unless
gitlab_shell
.
mv_repository
(
project
.
repository_storage_path
,
old_path
,
new_path
)
unless
move_repo_folder
(
@old_path
,
@
new_path
)
raise
TransferError
.
new
(
'Cannot move project'
)
end
# Move wiki repo also if present
gitlab_shell
.
mv_repository
(
project
.
repository_storage_path
,
"
#{
old_path
}
.wiki"
,
"
#{
new_path
}
.wiki"
)
move_repo_folder
(
"
#{
@old_path
}
.wiki"
,
"
#{
@
new_path
}
.wiki"
)
# Move missing group labels to project
Labels
::
TransferService
.
new
(
current_user
,
old_group
,
project
).
execute
Labels
::
TransferService
.
new
(
current_user
,
@
old_group
,
project
).
execute
# Move uploads
Gitlab
::
UploadsTransfer
.
new
.
move_project
(
project
.
path
,
old_namespace
.
full_path
,
new_namespace
.
full_path
)
Gitlab
::
UploadsTransfer
.
new
.
move_project
(
project
.
path
,
@old_namespace
.
full_path
,
@
new_namespace
.
full_path
)
# Move pages
Gitlab
::
PagesTransfer
.
new
.
move_project
(
project
.
path
,
old_namespace
.
full_path
,
new_namespace
.
full_path
)
Gitlab
::
PagesTransfer
.
new
.
move_project
(
project
.
path
,
@old_namespace
.
full_path
,
@
new_namespace
.
full_path
)
project
.
old_path_with_namespace
=
old_path
project
.
old_path_with_namespace
=
@
old_path
SystemHooksService
.
new
.
execute_hooks_for
(
project
,
:transfer
)
execute_system_hooks
end
refresh_permissions
(
old_namespace
,
new_namespace
)
true
rescue
Exception
# rubocop:disable Lint/RescueException
rollback_side_effects
raise
ensure
refresh_permissions
end
def
allowed_transfer?
(
current_user
,
project
,
namespace
)
namespace
&&
def
allowed_transfer?
(
current_user
,
project
)
@new_
namespace
&&
can?
(
current_user
,
:change_namespace
,
project
)
&&
namespace
.
id
!=
project
.
namespace_id
&&
current_user
.
can?
(
:create_projects
,
namespace
)
@new_
namespace
.
id
!=
project
.
namespace_id
&&
current_user
.
can?
(
:create_projects
,
@new_
namespace
)
end
def
refresh_permissions
(
old_namespace
,
new_namespace
)
def
update_namespace_and_visibility
(
to_namespace
)
# Apply new namespace id and visibility level
project
.
namespace
=
to_namespace
project
.
visibility_level
=
to_namespace
.
visibility_level
unless
project
.
visibility_level_allowed_by_group?
project
.
save!
end
def
refresh_permissions
# This ensures we only schedule 1 job for every user that has access to
# the namespaces.
user_ids
=
old_namespace
.
user_ids_for_project_authorizations
|
new_namespace
.
user_ids_for_project_authorizations
user_ids
=
@
old_namespace
.
user_ids_for_project_authorizations
|
@
new_namespace
.
user_ids_for_project_authorizations
UserProjectAccessChangedService
.
new
(
user_ids
).
execute
end
def
rollback_side_effects
rollback_folder_move
update_namespace_and_visibility
(
@old_namespace
)
end
def
rollback_folder_move
move_repo_folder
(
@new_path
,
@old_path
)
move_repo_folder
(
"
#{
@new_path
}
.wiki"
,
"
#{
@old_path
}
.wiki"
)
end
def
move_repo_folder
(
from_name
,
to_name
)
gitlab_shell
.
mv_repository
(
project
.
repository_storage_path
,
from_name
,
to_name
)
end
def
execute_system_hooks
SystemHooksService
.
new
.
execute_hooks_for
(
project
,
:transfer
)
end
end
end
changelogs/unreleased/30213-project-transfer-move-rollback.yml
0 → 100644
View file @
6db1a280
---
title
:
Rollback project repo move if there is an error in Projects::TransferService
merge_request
:
11877
author
:
spec/services/projects/transfer_service_spec.rb
View file @
6db1a280
...
...
@@ -19,6 +19,67 @@ describe Projects::TransferService, services: true do
it
{
expect
(
project
.
namespace
).
to
eq
(
group
)
}
end
context
'when transfer succeeds'
do
before
do
group
.
add_owner
(
user
)
end
it
'sends notifications'
do
expect_any_instance_of
(
NotificationService
).
to
receive
(
:project_was_moved
)
transfer_project
(
project
,
user
,
group
)
end
it
'executes system hooks'
do
expect_any_instance_of
(
Projects
::
TransferService
).
to
receive
(
:execute_system_hooks
)
transfer_project
(
project
,
user
,
group
)
end
end
context
'when transfer fails'
do
let!
(
:original_path
)
{
project_path
(
project
)
}
def
attempt_project_transfer
expect
do
transfer_project
(
project
,
user
,
group
)
end
.
to
raise_error
(
ActiveRecord
::
ActiveRecordError
)
end
before
do
group
.
add_owner
(
user
)
expect_any_instance_of
(
Labels
::
TransferService
).
to
receive
(
:execute
).
and_raise
(
ActiveRecord
::
StatementInvalid
,
"PG ERROR"
)
end
def
project_path
(
project
)
File
.
join
(
project
.
repository_storage_path
,
"
#{
project
.
path_with_namespace
}
.git"
)
end
def
current_path
project_path
(
project
)
end
it
'rolls back repo location'
do
attempt_project_transfer
expect
(
Dir
.
exist?
(
original_path
)).
to
be_truthy
expect
(
original_path
).
to
eq
current_path
end
it
"doesn't send move notifications"
do
expect_any_instance_of
(
NotificationService
).
not_to
receive
(
:project_was_moved
)
attempt_project_transfer
end
it
"doesn't run system hooks"
do
expect_any_instance_of
(
Projects
::
TransferService
).
not_to
receive
(
:execute_system_hooks
)
attempt_project_transfer
end
end
context
'namespace -> no namespace'
do
before
do
@result
=
transfer_project
(
project
,
user
,
nil
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment