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
1
Merge Requests
1
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
nexedi
gitlab-ce
Commits
9a130593
Commit
9a130593
authored
May 03, 2018
by
Tiago Botelho
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Backports every CE related change from ee-5484 to CE
parent
2d84de9e
Changes
33
Hide whitespace changes
Inline
Side-by-side
Showing
33 changed files
with
1645 additions
and
3 deletions
+1645
-3
app/controllers/projects/mirrors_controller.rb
app/controllers/projects/mirrors_controller.rb
+62
-0
app/controllers/projects/settings/repository_controller.rb
app/controllers/projects/settings/repository_controller.rb
+8
-0
app/models/project.rb
app/models/project.rb
+41
-0
app/models/remote_mirror.rb
app/models/remote_mirror.rb
+216
-0
app/models/repository.rb
app/models/repository.rb
+14
-0
app/serializers/project_mirror_entity.rb
app/serializers/project_mirror_entity.rb
+13
-0
app/services/git_push_service.rb
app/services/git_push_service.rb
+8
-0
app/services/projects/update_remote_mirror_service.rb
app/services/projects/update_remote_mirror_service.rb
+30
-0
app/views/projects/mirrors/_push.html.haml
app/views/projects/mirrors/_push.html.haml
+50
-0
app/views/projects/mirrors/_show.html.haml
app/views/projects/mirrors/_show.html.haml
+3
-0
app/views/shared/_remote_mirror_update_button.html.haml
app/views/shared/_remote_mirror_update_button.html.haml
+13
-0
app/workers/all_queues.yml
app/workers/all_queues.yml
+1
-0
app/workers/repository_remove_remote_worker.rb
app/workers/repository_remove_remote_worker.rb
+35
-0
app/workers/repository_update_remote_mirror_worker.rb
app/workers/repository_update_remote_mirror_worker.rb
+49
-0
config/sidekiq_queues.yml
config/sidekiq_queues.yml
+3
-0
db/schema.rb
db/schema.rb
+3
-3
lib/gitlab/import_export/import_export.yml
lib/gitlab/import_export/import_export.yml
+1
-0
lib/gitlab/usage_data.rb
lib/gitlab/usage_data.rb
+1
-0
spec/controllers/projects/mirrors_controller_spec.rb
spec/controllers/projects/mirrors_controller_spec.rb
+72
-0
spec/factories/projects.rb
spec/factories/projects.rb
+11
-0
spec/factories/remote_mirrors.rb
spec/factories/remote_mirrors.rb
+6
-0
spec/features/projects/remote_mirror_spec.rb
spec/features/projects/remote_mirror_spec.rb
+34
-0
spec/features/projects/settings/repository_settings_spec.rb
spec/features/projects/settings/repository_settings_spec.rb
+15
-0
spec/lib/gitlab/import_export/all_models.yml
spec/lib/gitlab/import_export/all_models.yml
+1
-0
spec/lib/gitlab/usage_data_spec.rb
spec/lib/gitlab/usage_data_spec.rb
+1
-0
spec/models/project_spec.rb
spec/models/project_spec.rb
+79
-0
spec/models/remote_mirror_spec.rb
spec/models/remote_mirror_spec.rb
+275
-0
spec/models/repository_spec.rb
spec/models/repository_spec.rb
+32
-0
spec/services/git_push_service_spec.rb
spec/services/git_push_service_spec.rb
+66
-0
spec/services/projects/destroy_service_spec.rb
spec/services/projects/destroy_service_spec.rb
+13
-0
spec/services/projects/update_remote_mirror_service_spec.rb
spec/services/projects/update_remote_mirror_service_spec.rb
+355
-0
spec/workers/repository_remove_remote_worker_spec.rb
spec/workers/repository_remove_remote_worker_spec.rb
+50
-0
spec/workers/repository_update_remote_mirror_worker_spec.rb
spec/workers/repository_update_remote_mirror_worker_spec.rb
+84
-0
No files found.
app/controllers/projects/mirrors_controller.rb
0 → 100644
View file @
9a130593
class
Projects::MirrorsController
<
Projects
::
ApplicationController
include
RepositorySettingsRedirect
# Authorize
before_action
:authorize_admin_mirror!
before_action
:remote_mirror
,
only:
[
:update
]
layout
"project_settings"
def
show
redirect_to_repository_settings
(
project
)
end
def
update
if
project
.
update_attributes
(
mirror_params
)
flash
[
:notice
]
=
'Mirroring settings were successfully updated.'
else
flash
[
:alert
]
=
project
.
errors
.
full_messages
.
join
(
', '
).
html_safe
end
respond_to
do
|
format
|
format
.
html
{
redirect_to_repository_settings
(
project
)
}
format
.
json
do
if
project
.
errors
.
present?
render
json:
project
.
errors
,
status: :unprocessable_entity
else
render
json:
ProjectMirrorSerializer
.
new
.
represent
(
project
)
end
end
end
end
def
update_now
if
params
[
:sync_remote
]
project
.
update_remote_mirrors
flash
[
:notice
]
=
"The remote repository is being updated..."
end
redirect_to_repository_settings
(
project
)
end
private
def
remote_mirror
@remote_mirror
=
project
.
remote_mirrors
.
first_or_initialize
end
def
mirror_params_attributes
[
remote_mirrors_attributes:
%i[
url
id
enabled
only_protected_branches
]
]
end
def
mirror_params
params
.
require
(
:project
).
permit
(
mirror_params_attributes
)
end
end
app/controllers/projects/settings/repository_controller.rb
View file @
9a130593
...
...
@@ -2,6 +2,7 @@ module Projects
module
Settings
class
RepositoryController
<
Projects
::
ApplicationController
before_action
:authorize_admin_project!
before_action
:remote_mirror
,
only:
[
:show
]
def
show
render_show
...
...
@@ -25,6 +26,7 @@ module Projects
define_deploy_token
define_protected_refs
remote_mirror
render
'show'
end
...
...
@@ -41,6 +43,12 @@ module Projects
load_gon_index
end
def
remote_mirror
return
unless
project
.
feature_available?
(
:repository_mirrors
)
@remote_mirror
=
project
.
remote_mirrors
.
first_or_initialize
end
def
access_levels_options
{
create_access_levels:
levels_for_dropdown
,
...
...
app/models/project.rb
View file @
9a130593
...
...
@@ -64,6 +64,9 @@ class Project < ActiveRecord::Base
default_value_for
:only_allow_merge_if_all_discussions_are_resolved
,
false
add_authentication_token_field
:runners_token
before_validation
:mark_remote_mirrors_for_removal
before_save
:ensure_runners_token
after_save
:update_project_statistics
,
if: :namespace_id_changed?
...
...
@@ -241,11 +244,17 @@ class Project < ActiveRecord::Base
has_many
:project_badges
,
class_name:
'ProjectBadge'
has_one
:ci_cd_settings
,
class_name:
'ProjectCiCdSetting'
,
inverse_of: :project
,
autosave:
true
,
dependent: :destroy
# rubocop:disable Cop/ActiveRecordDependent
has_many
:remote_mirrors
,
inverse_of: :project
accepts_nested_attributes_for
:variables
,
allow_destroy:
true
accepts_nested_attributes_for
:project_feature
,
update_only:
true
accepts_nested_attributes_for
:import_data
accepts_nested_attributes_for
:auto_devops
,
update_only:
true
accepts_nested_attributes_for
:remote_mirrors
,
allow_destroy:
true
,
reject_if:
->
(
attrs
)
{
attrs
[
:id
].
blank?
&&
attrs
[
:url
].
blank?
}
delegate
:name
,
to: :owner
,
allow_nil:
true
,
prefix:
true
delegate
:members
,
to: :team
,
prefix:
true
delegate
:add_user
,
:add_users
,
to: :team
...
...
@@ -335,6 +344,7 @@ class Project < ActiveRecord::Base
scope
:with_issues_enabled
,
->
{
with_feature_enabled
(
:issues
)
}
scope
:with_issues_available_for_user
,
->
(
current_user
)
{
with_feature_available_for_user
(
:issues
,
current_user
)
}
scope
:with_merge_requests_enabled
,
->
{
with_feature_enabled
(
:merge_requests
)
}
scope
:with_remote_mirrors
,
->
{
joins
(
:remote_mirrors
).
where
(
remote_mirrors:
{
enabled:
true
}).
distinct
}
scope
:with_group_runners_enabled
,
->
do
joins
(
:ci_cd_settings
)
...
...
@@ -754,6 +764,37 @@ class Project < ActiveRecord::Base
import_type
==
'gitea'
end
def
has_remote_mirror?
remote_mirror_available?
&&
remote_mirrors
.
enabled
.
exists?
end
def
updating_remote_mirror?
remote_mirrors
.
enabled
.
started
.
exists?
end
def
update_remote_mirrors
return
unless
remote_mirror_available?
remote_mirrors
.
enabled
.
each
(
&
:sync
)
end
def
mark_stuck_remote_mirrors_as_failed!
remote_mirrors
.
stuck
.
update_all
(
update_status: :failed
,
last_error:
'The remote mirror took to long to complete.'
,
last_update_at:
Time
.
now
)
end
def
mark_remote_mirrors_for_removal
remote_mirrors
.
each
(
&
:mark_for_delete_if_blank_url
)
end
def
remote_mirror_available?
remote_mirror_available_overridden
||
::
Gitlab
::
CurrentSettings
.
mirror_available
end
def
check_limit
unless
creator
.
can_create_project?
||
namespace
.
kind
==
'group'
projects_limit
=
creator
.
projects_limit
...
...
app/models/remote_mirror.rb
0 → 100644
View file @
9a130593
class
RemoteMirror
<
ActiveRecord
::
Base
include
AfterCommitQueue
PROTECTED_BACKOFF_DELAY
=
1
.
minute
UNPROTECTED_BACKOFF_DELAY
=
5
.
minutes
attr_encrypted
:credentials
,
key:
Gitlab
::
Application
.
secrets
.
db_key_base
,
marshal:
true
,
encode:
true
,
mode: :per_attribute_iv_and_salt
,
insecure_mode:
true
,
algorithm:
'aes-256-cbc'
default_value_for
:only_protected_branches
,
true
belongs_to
:project
,
inverse_of: :remote_mirrors
validates
:url
,
presence:
true
,
url:
{
protocols:
%w(ssh git http https)
,
allow_blank:
true
}
validates
:url
,
addressable_url:
true
,
if: :url_changed?
before_save
:set_new_remote_name
,
if: :mirror_url_changed?
after_save
:set_override_remote_mirror_available
,
unless:
->
{
Gitlab
::
CurrentSettings
.
current_application_settings
.
mirror_available
}
after_save
:refresh_remote
,
if: :mirror_url_changed?
after_update
:reset_fields
,
if: :mirror_url_changed?
after_commit
:remove_remote
,
on: :destroy
scope
:enabled
,
->
{
where
(
enabled:
true
)
}
scope
:started
,
->
{
with_update_status
(
:started
)
}
scope
:stuck
,
->
{
started
.
where
(
'last_update_at < ? OR (last_update_at IS NULL AND updated_at < ?)'
,
1
.
day
.
ago
,
1
.
day
.
ago
)
}
state_machine
:update_status
,
initial: :none
do
event
:update_start
do
transition
[
:none
,
:finished
,
:failed
]
=>
:started
end
event
:update_finish
do
transition
started: :finished
end
event
:update_fail
do
transition
started: :failed
end
state
:started
state
:finished
state
:failed
after_transition
any
=>
:started
do
|
remote_mirror
,
_
|
Gitlab
::
Metrics
.
add_event
(
:remote_mirrors_running
,
path:
remote_mirror
.
project
.
full_path
)
remote_mirror
.
update
(
last_update_started_at:
Time
.
now
)
end
after_transition
started: :finished
do
|
remote_mirror
,
_
|
Gitlab
::
Metrics
.
add_event
(
:remote_mirrors_finished
,
path:
remote_mirror
.
project
.
full_path
)
timestamp
=
Time
.
now
remote_mirror
.
update_attributes!
(
last_update_at:
timestamp
,
last_successful_update_at:
timestamp
,
last_error:
nil
)
end
after_transition
started: :failed
do
|
remote_mirror
,
_
|
Gitlab
::
Metrics
.
add_event
(
:remote_mirrors_failed
,
path:
remote_mirror
.
project
.
full_path
)
remote_mirror
.
update
(
last_update_at:
Time
.
now
)
end
end
def
remote_name
super
||
fallback_remote_name
end
def
update_failed?
update_status
==
'failed'
end
def
update_in_progress?
update_status
==
'started'
end
def
update_repository
(
options
)
raw
.
update
(
options
)
end
def
sync
return
unless
enabled?
return
if
Gitlab
::
Geo
.
secondary?
if
recently_scheduled?
RepositoryUpdateRemoteMirrorWorker
.
perform_in
(
backoff_delay
,
self
.
id
,
Time
.
now
)
else
RepositoryUpdateRemoteMirrorWorker
.
perform_async
(
self
.
id
,
Time
.
now
)
end
end
def
enabled
return
false
unless
project
&&
super
return
false
unless
project
.
remote_mirror_available?
return
false
unless
project
.
repository_exists?
return
false
if
project
.
pending_delete?
true
end
alias_method
:enabled?
,
:enabled
def
updated_since?
(
timestamp
)
last_update_started_at
&&
last_update_started_at
>
timestamp
&&
!
update_failed?
end
def
mark_for_delete_if_blank_url
mark_for_destruction
if
url
.
blank?
end
def
mark_as_failed
(
error_message
)
update_fail
update_column
(
:last_error
,
Gitlab
::
UrlSanitizer
.
sanitize
(
error_message
))
end
def
url
=
(
value
)
super
(
value
)
&&
return
unless
Gitlab
::
UrlSanitizer
.
valid?
(
value
)
mirror_url
=
Gitlab
::
UrlSanitizer
.
new
(
value
)
self
.
credentials
=
mirror_url
.
credentials
super
(
mirror_url
.
sanitized_url
)
end
def
url
if
super
Gitlab
::
UrlSanitizer
.
new
(
super
,
credentials:
credentials
).
full_url
end
rescue
super
end
def
safe_url
return
if
url
.
nil?
result
=
URI
.
parse
(
url
)
result
.
password
=
'*****'
if
result
.
password
result
.
user
=
'*****'
if
result
.
user
&&
result
.
user
!=
"git"
# tokens or other data may be saved as user
result
.
to_s
end
private
def
raw
@raw
||=
Gitlab
::
Git
::
RemoteMirror
.
new
(
project
.
repository
.
raw
,
remote_name
)
end
def
fallback_remote_name
return
unless
id
"remote_mirror_
#{
id
}
"
end
def
recently_scheduled?
return
false
unless
self
.
last_update_started_at
self
.
last_update_started_at
>=
Time
.
now
-
backoff_delay
end
def
backoff_delay
if
self
.
only_protected_branches
PROTECTED_BACKOFF_DELAY
else
UNPROTECTED_BACKOFF_DELAY
end
end
def
reset_fields
update_columns
(
last_error:
nil
,
last_update_at:
nil
,
last_successful_update_at:
nil
,
update_status:
'finished'
)
end
def
set_override_remote_mirror_available
enabled
=
read_attribute
(
:enabled
)
project
.
update
(
remote_mirror_available_overridden:
enabled
)
end
def
set_new_remote_name
self
.
remote_name
=
"remote_mirror_
#{
SecureRandom
.
hex
}
"
end
def
refresh_remote
return
unless
project
# Before adding a new remote we have to delete the data from
# the previous remote name
prev_remote_name
=
remote_name_was
||
fallback_remote_name
run_after_commit
do
project
.
repository
.
async_remove_remote
(
prev_remote_name
)
end
project
.
repository
.
add_remote
(
remote_name
,
url
)
end
def
remove_remote
return
unless
project
# could be pending to delete so don't need to touch the git repository
project
.
repository
.
async_remove_remote
(
remote_name
)
end
def
mirror_url_changed?
url_changed?
||
encrypted_credentials_changed?
end
end
app/models/repository.rb
View file @
9a130593
...
...
@@ -861,6 +861,20 @@ class Repository
gitlab_shell
.
fetch_remote
(
raw_repository
,
remote
,
ssh_auth:
ssh_auth
,
forced:
forced
,
no_tags:
no_tags
,
prune:
prune
)
end
def
async_remove_remote
(
remote_name
)
return
unless
remote_name
job_id
=
RepositoryRemoveRemoteWorker
.
perform_async
(
project
.
id
,
remote_name
)
if
job_id
Rails
.
logger
.
info
(
"Remove remote job scheduled for
#{
project
.
id
}
with remote name:
#{
remote_name
}
job ID
#{
job_id
}
."
)
else
Rails
.
logger
.
info
(
"Remove remote job failed to create for
#{
project
.
id
}
with remote name
#{
remote_name
}
."
)
end
job_id
end
def
fetch_source_branch!
(
source_repository
,
source_branch
,
local_ref
)
raw_repository
.
fetch_source_branch!
(
source_repository
.
raw_repository
,
source_branch
,
local_ref
)
end
...
...
app/serializers/project_mirror_entity.rb
0 → 100644
View file @
9a130593
class
ProjectMirrorEntity
<
Grape
::
Entity
prepend
::
EE
::
ProjectMirrorEntity
expose
:id
expose
:remote_mirrors_attributes
do
|
project
|
next
[]
unless
project
.
remote_mirrors
.
present?
project
.
remote_mirrors
.
map
do
|
remote
|
remote
.
as_json
(
only:
%i[id url enabled]
)
end
end
end
app/services/git_push_service.rb
View file @
9a130593
...
...
@@ -55,6 +55,7 @@ class GitPushService < BaseService
execute_related_hooks
perform_housekeeping
update_remote_mirrors
update_caches
update_signatures
...
...
@@ -119,6 +120,13 @@ class GitPushService < BaseService
protected
def
update_remote_mirrors
return
unless
@project
.
has_remote_mirror?
@project
.
mark_stuck_remote_mirrors_as_failed!
@project
.
update_remote_mirrors
end
def
execute_related_hooks
# Update merge requests that may be affected by this push. A new branch
# could cause the last commit of a merge request to change.
...
...
app/services/projects/update_remote_mirror_service.rb
0 → 100644
View file @
9a130593
module
Projects
class
UpdateRemoteMirrorService
<
BaseService
attr_reader
:errors
def
execute
(
remote_mirror
)
@errors
=
[]
return
success
unless
remote_mirror
.
enabled?
begin
repository
.
fetch_remote
(
remote_mirror
.
remote_name
,
no_tags:
true
)
opts
=
{}
if
remote_mirror
.
only_protected_branches?
opts
[
:only_branches_matching
]
=
project
.
protected_branches
.
select
(
:name
).
map
(
&
:name
)
end
remote_mirror
.
update_repository
(
opts
)
rescue
=>
e
errors
<<
e
.
message
.
strip
end
if
errors
.
present?
error
(
errors
.
join
(
"
\n\n
"
))
else
success
end
end
end
end
app/views/projects/mirrors/_push.html.haml
0 → 100644
View file @
9a130593
-
expanded
=
Rails
.
env
.
test?
%section
.settings.no-animate
{
class:
(
'expanded'
if
expanded
)
}
.settings-header
%h4
Push to a remote repository
%button
.btn.js-settings-toggle
=
expanded
?
'Collapse'
:
'Expand'
%p
Set up the remote repository that you want to update with the content of the current repository
every time someone pushes to it.
=
link_to
'Read more'
,
help_page_path
(
'workflow/repository_mirroring'
,
anchor:
'pushing-to-a-remote-repository'
),
target:
'_blank'
.settings-content
=
form_for
@project
,
url:
project_mirror_path
(
@project
)
do
|
f
|
%div
=
form_errors
(
@project
)
=
render
"shared/remote_mirror_update_button"
,
remote_mirror:
@remote_mirror
-
if
@remote_mirror
.
last_error
.
present?
.panel.panel-danger
.panel-heading
-
if
@remote_mirror
.
last_update_at
The remote repository failed to update
#{
time_ago_with_tooltip
(
@remote_mirror
.
last_update_at
)
}
.
-
else
The remote repository failed to update.
-
if
@remote_mirror
.
last_successful_update_at
Last successful update
#{
time_ago_with_tooltip
(
@remote_mirror
.
last_successful_update_at
)
}
.
.panel-body
%pre
:preserve
#{
h
(
@remote_mirror
.
last_error
.
strip
)
}
=
f
.
fields_for
:remote_mirrors
,
@remote_mirror
do
|
rm_form
|
.form-group
=
rm_form
.
check_box
:enabled
,
class:
"pull-left"
.prepend-left-20
=
rm_form
.
label
:enabled
,
"Remote mirror repository"
,
class:
"label-light append-bottom-0"
%p
.light.append-bottom-0
Automatically update the remote mirror's branches, tags, and commits from this repository every time someone pushes to it.
.form-group.has-feedback
=
rm_form
.
label
:url
,
"Git repository URL"
,
class:
"label-light"
=
rm_form
.
text_field
:url
,
class:
"form-control"
,
placeholder:
'https://username:password@gitlab.company.com/group/project.git'
=
render
"projects/mirrors/instructions"
.form-group
=
rm_form
.
check_box
:only_protected_branches
,
class:
'pull-left'
.prepend-left-20
=
rm_form
.
label
:only_protected_branches
,
class:
'label-light'
=
link_to
icon
(
'question-circle'
),
help_page_path
(
'user/project/protected_branches'
)
=
f
.
submit
'Save changes'
,
class:
'btn btn-create'
,
name:
'update_remote_mirror'
app/views/projects/mirrors/_show.html.haml
0 → 100644
View file @
9a130593
-
if
can?
(
current_user
,
:admin_mirror
,
@project
)
=
render
'projects/mirrors/push'
app/views/shared/_remote_mirror_update_button.html.haml
0 → 100644
View file @
9a130593
-
if
@project
.
has_remote_mirror?
.append-bottom-default
-
if
remote_mirror
.
update_in_progress?
%span
.btn.disabled
=
icon
(
"refresh spin"
)
Updating
…
-
else
=
link_to
update_now_project_mirror_path
(
@project
,
sync_remote:
true
),
method: :post
,
class:
"btn"
do
=
icon
(
"refresh"
)
Update Now
-
if
@remote_mirror
.
last_successful_update_at
%p
.inline.prepend-left-10
Successfully updated
#{
time_ago_with_tooltip
(
@remote_mirror
.
last_successful_update_at
)
}
.
app/workers/all_queues.yml
View file @
9a130593
...
...
@@ -112,3 +112,4 @@
-
update_user_activity
-
upload_checksum
-
web_hook
-
repository_update_remote_mirror
app/workers/repository_remove_remote_worker.rb
0 → 100644
View file @
9a130593
class
RepositoryRemoveRemoteWorker
include
ApplicationWorker
include
ExclusiveLeaseGuard
LEASE_TIMEOUT
=
1
.
hour
attr_reader
:project
,
:remote_name
def
perform
(
project_id
,
remote_name
)
@remote_name
=
remote_name
@project
=
Project
.
find_by_id
(
project_id
)
return
unless
@project
logger
.
info
(
"Removing remote
#{
remote_name
}
from project
#{
project
.
id
}
"
)
try_obtain_lease
do
remove_remote
=
@project
.
repository
.
remove_remote
(
remote_name
)
if
remove_remote
logger
.
info
(
"Remote
#{
remote_name
}
was successfully removed from project
#{
project
.
id
}
"
)
else
logger
.
error
(
"Could not remove remote
#{
remote_name
}
from project
#{
project
.
id
}
"
)
end
end
end
def
lease_timeout
LEASE_TIMEOUT
end
def
lease_key
"remove_remote_
#{
project
.
id
}
_
#{
remote_name
}
"
end
end
app/workers/repository_update_remote_mirror_worker.rb
0 → 100644
View file @
9a130593
class
RepositoryUpdateRemoteMirrorWorker
UpdateAlreadyInProgressError
=
Class
.
new
(
StandardError
)
UpdateError
=
Class
.
new
(
StandardError
)
include
ApplicationWorker
include
Gitlab
::
ShellAdapter
sidekiq_options
retry:
3
,
dead:
false
sidekiq_retry_in
{
|
count
|
30
*
count
}
sidekiq_retries_exhausted
do
|
msg
,
_
|
Sidekiq
.
logger
.
warn
"Failed
#{
msg
[
'class'
]
}
with
#{
msg
[
'args'
]
}
:
#{
msg
[
'error_message'
]
}
"
end
def
perform
(
remote_mirror_id
,
scheduled_time
)
remote_mirror
=
RemoteMirror
.
find
(
remote_mirror_id
)
return
if
remote_mirror
.
updated_since?
(
scheduled_time
)
raise
UpdateAlreadyInProgressError
if
remote_mirror
.
update_in_progress?
remote_mirror
.
update_start
project
=
remote_mirror
.
project
current_user
=
project
.
creator
result
=
Projects
::
UpdateRemoteMirrorService
.
new
(
project
,
current_user
).
execute
(
remote_mirror
)
raise
UpdateError
,
result
[
:message
]
if
result
[
:status
]
==
:error
remote_mirror
.
update_finish
rescue
UpdateAlreadyInProgressError
raise
rescue
UpdateError
=>
ex
fail_remote_mirror
(
remote_mirror
,
ex
.
message
)
raise
rescue
=>
ex
return
unless
remote_mirror
fail_remote_mirror
(
remote_mirror
,
ex
.
message
)
raise
UpdateError
,
"
#{
ex
.
class
}
:
#{
ex
.
message
}
"
end
private
def
fail_remote_mirror
(
remote_mirror
,
message
)
remote_mirror
.
mark_as_failed
(
message
)
Rails
.
logger
.
error
(
message
)
end
end
config/sidekiq_queues.yml
View file @
9a130593
...
...
@@ -73,3 +73,6 @@
- [object_storage, 1]
- [plugin, 1]
- [pipeline_background, 1]
- [repository_update_remote_mirror, 1]
- [repository_remove_remote, 1]
db/schema.rb
View file @
9a130593
...
...
@@ -314,10 +314,10 @@ ActiveRecord::Schema.define(version: 20180503200320) do
t
.
integer
"auto_canceled_by_id"
t
.
boolean
"retried"
t
.
integer
"stage_id"
t
.
integer
"artifacts_file_store"
t
.
integer
"artifacts_metadata_store"
t
.
boolean
"protected"
t
.
integer
"failure_reason"
t
.
integer
"artifacts_file_store"
t
.
integer
"artifacts_metadata_store"
end
add_index
"ci_builds"
,
[
"artifacts_expire_at"
],
name:
"index_ci_builds_on_artifacts_expire_at"
,
where:
"(artifacts_file <> ''::text)"
,
using: :btree
...
...
@@ -365,13 +365,13 @@ ActiveRecord::Schema.define(version: 20180503200320) do
t
.
integer
"project_id"
,
null:
false
t
.
integer
"job_id"
,
null:
false
t
.
integer
"file_type"
,
null:
false
t
.
integer
"file_store"
t
.
integer
"size"
,
limit:
8
t
.
datetime_with_timezone
"created_at"
,
null:
false
t
.
datetime_with_timezone
"updated_at"
,
null:
false
t
.
datetime_with_timezone
"expire_at"
t
.
string
"file"
t
.
binary
"file_sha256"
t
.
integer
"file_store"
end
add_index
"ci_job_artifacts"
,
[
"expire_at"
,
"job_id"
],
name:
"index_ci_job_artifacts_on_expire_at_and_job_id"
,
using: :btree
...
...
lib/gitlab/import_export/import_export.yml
View file @
9a130593
...
...
@@ -106,6 +106,7 @@ excluded_attributes:
-
:last_repository_updated_at
-
:last_repository_check_at
-
:storage_version
-
:remote_mirror_available_overridden
-
:description_html
snippets
:
-
:expired_at
...
...
lib/gitlab/usage_data.rb
View file @
9a130593
...
...
@@ -71,6 +71,7 @@ module Gitlab
projects_imported_from_github:
Project
.
where
(
import_type:
'github'
).
count
,
protected_branches:
ProtectedBranch
.
count
,
releases:
Release
.
count
,
remote_mirrors:
RemoteMirror
.
count
,
snippets:
Snippet
.
count
,
todos:
Todo
.
count
,
uploads:
Upload
.
count
,
...
...
spec/controllers/projects/mirrors_controller_spec.rb
0 → 100644
View file @
9a130593
require
'spec_helper'
describe
Projects
::
MirrorsController
do
include
ReactiveCachingHelpers
describe
'setting up a remote mirror'
do
set
(
:project
)
{
create
(
:project
,
:repository
)
}
context
'when the current project is not a mirror'
do
it
'allows to create a remote mirror'
do
sign_in
(
project
.
owner
)
expect
do
do_put
(
project
,
remote_mirrors_attributes:
{
'0'
=>
{
'enabled'
=>
1
,
'url'
=>
'http://foo.com'
}
})
end
.
to
change
{
RemoteMirror
.
count
}.
to
(
1
)
end
end
end
describe
'#update'
do
let
(
:project
)
{
create
(
:project
,
:repository
,
:remote_mirror
)
}
before
do
sign_in
(
project
.
owner
)
end
around
do
|
example
|
Sidekiq
::
Testing
.
fake!
{
example
.
run
}
end
context
'With valid URL for a push'
do
let
(
:remote_mirror_attributes
)
do
{
"0"
=>
{
"enabled"
=>
"0"
,
url:
'https://updated.example.com'
}
}
end
it
'processes a successful update'
do
do_put
(
project
,
remote_mirrors_attributes:
remote_mirror_attributes
)
expect
(
response
).
to
redirect_to
(
project_settings_repository_path
(
project
))
expect
(
flash
[
:notice
]).
to
match
(
/successfully updated/
)
end
it
'should create a RemoteMirror object'
do
expect
{
do_put
(
project
,
remote_mirrors_attributes:
remote_mirror_attributes
)
}.
to
change
(
RemoteMirror
,
:count
).
by
(
1
)
end
end
context
'With invalid URL for a push'
do
let
(
:remote_mirror_attributes
)
do
{
"0"
=>
{
"enabled"
=>
"0"
,
url:
'ftp://invalid.invalid'
}
}
end
it
'processes an unsuccessful update'
do
do_put
(
project
,
remote_mirrors_attributes:
remote_mirror_attributes
)
expect
(
response
).
to
redirect_to
(
project_settings_repository_path
(
project
))
expect
(
flash
[
:alert
]).
to
match
(
/must be a valid URL/
)
end
it
'should not create a RemoteMirror object'
do
expect
{
do_put
(
project
,
remote_mirrors_attributes:
remote_mirror_attributes
)
}.
not_to
change
(
RemoteMirror
,
:count
)
end
end
end
def
do_put
(
project
,
options
,
extra_attrs
=
{})
attrs
=
extra_attrs
.
merge
(
namespace_id:
project
.
namespace
.
to_param
,
project_id:
project
.
to_param
)
attrs
[
:project
]
=
options
put
:update
,
attrs
end
end
spec/factories/projects.rb
View file @
9a130593
...
...
@@ -183,6 +183,17 @@ FactoryBot.define do
end
end
trait
:remote_mirror
do
transient
do
remote_name
"remote_mirror_
#{
SecureRandom
.
hex
}
"
url
"http://foo.com"
enabled
true
end
after
(
:create
)
do
|
project
,
evaluator
|
project
.
remote_mirrors
.
create!
(
url:
evaluator
.
url
,
enabled:
evaluator
.
enabled
)
end
end
trait
:stubbed_repository
do
after
(
:build
)
do
|
project
|
allow
(
project
).
to
receive
(
:empty_repo?
).
and_return
(
false
)
...
...
spec/factories/remote_mirrors.rb
0 → 100644
View file @
9a130593
FactoryBot
.
define
do
factory
:remote_mirror
,
class:
'RemoteMirror'
do
association
:project
,
:repository
url
"http://foo:bar@test.com"
end
end
spec/features/projects/remote_mirror_spec.rb
0 → 100644
View file @
9a130593
require
'spec_helper'
feature
'Project remote mirror'
,
:feature
do
let
(
:project
)
{
create
(
:project
,
:repository
,
:remote_mirror
)
}
let
(
:remote_mirror
)
{
project
.
remote_mirrors
.
first
}
let
(
:user
)
{
create
(
:user
)
}
describe
'On a project'
,
:js
do
before
do
project
.
add_master
(
user
)
sign_in
user
end
context
'when last_error is present but last_update_at is not'
do
it
'renders error message without timstamp'
do
remote_mirror
.
update_attributes
(
last_error:
'Some new error'
,
last_update_at:
nil
)
visit
project_mirror_path
(
project
)
expect
(
page
).
to
have_content
(
'The remote repository failed to update.'
)
end
end
context
'when last_error and last_update_at are present'
do
it
'renders error message with timestamp'
do
remote_mirror
.
update_attributes
(
last_error:
'Some new error'
,
last_update_at:
Time
.
now
-
5
.
minutes
)
visit
project_mirror_path
(
project
)
expect
(
page
).
to
have_content
(
'The remote repository failed to update 5 minutes ago.'
)
end
end
end
end
spec/features/projects/settings/repository_settings_spec.rb
View file @
9a130593
...
...
@@ -115,5 +115,20 @@ describe 'Projects > Settings > Repository settings' do
expect
(
page
).
to
have_content
(
'Your new project deploy token has been created'
)
end
end
context
'remote mirror settings'
do
let
(
:user2
)
{
create
(
:user
)
}
before
do
project
.
add_master
(
user2
)
visit
project_settings_repository_path
(
project
)
end
it
'shows push mirror settings'
do
expect
(
page
).
to
have_selector
(
'#project_remote_mirrors_attributes_0_enabled'
)
expect
(
page
).
to
have_selector
(
'#project_remote_mirrors_attributes_0_url'
)
end
end
end
end
spec/lib/gitlab/import_export/all_models.yml
View file @
9a130593
...
...
@@ -268,6 +268,7 @@ project:
-
pages_domains
-
authorized_users
-
project_authorizations
-
remote_mirrors
-
route
-
redirect_routes
-
statistics
...
...
spec/lib/gitlab/usage_data_spec.rb
View file @
9a130593
...
...
@@ -96,6 +96,7 @@ describe Gitlab::UsageData do
pages_domains
protected_branches
releases
remote_mirrors
snippets
todos
uploads
...
...
spec/models/project_spec.rb
View file @
9a130593
...
...
@@ -1852,6 +1852,85 @@ describe Project do
it
{
expect
(
project
.
gitea_import?
).
to
be
true
}
end
describe
'#has_remote_mirror?'
do
let
(
:project
)
{
create
(
:project
,
:remote_mirror
,
:import_started
)
}
subject
{
project
.
has_remote_mirror?
}
before
do
allow_any_instance_of
(
RemoteMirror
).
to
receive
(
:refresh_remote
)
end
it
'returns true when a remote mirror is enabled'
do
is_expected
.
to
be_truthy
end
it
'returns false when remote mirror is disabled'
do
project
.
remote_mirrors
.
first
.
update_attributes
(
enabled:
false
)
is_expected
.
to
be_falsy
end
end
describe
'#update_remote_mirrors'
do
let
(
:project
)
{
create
(
:project
,
:remote_mirror
,
:import_started
)
}
delegate
:update_remote_mirrors
,
to: :project
before
do
allow_any_instance_of
(
RemoteMirror
).
to
receive
(
:refresh_remote
)
end
it
'syncs enabled remote mirror'
do
expect_any_instance_of
(
RemoteMirror
).
to
receive
(
:sync
)
update_remote_mirrors
end
# TODO: study if remote_mirror_available_overridden is still a necessary attribute considering that
# it is no longer under any license
it
'does nothing when remote mirror is disabled globally and not overridden'
do
stub_application_setting
(
mirror_available:
false
)
project
.
remote_mirror_available_overridden
=
false
expect_any_instance_of
(
RemoteMirror
).
not_to
receive
(
:sync
)
update_remote_mirrors
end
it
'does not sync disabled remote mirrors'
do
project
.
remote_mirrors
.
first
.
update_attributes
(
enabled:
false
)
expect_any_instance_of
(
RemoteMirror
).
not_to
receive
(
:sync
)
update_remote_mirrors
end
end
describe
'#remote_mirror_available?'
do
let
(
:project
)
{
create
(
:project
)
}
context
'when remote mirror global setting is enabled'
do
it
'returns true'
do
expect
(
project
.
remote_mirror_available?
).
to
be
(
true
)
end
end
context
'when remote mirror global setting is disabled'
do
before
do
stub_application_setting
(
mirror_available:
false
)
end
it
'returns true when overridden'
do
project
.
remote_mirror_available_overridden
=
true
expect
(
project
.
remote_mirror_available?
).
to
be
(
true
)
end
it
'returns false when not overridden'
do
expect
(
project
.
remote_mirror_available?
).
to
be
(
false
)
end
end
end
describe
'#ancestors_upto'
,
:nested_groups
do
let
(
:parent
)
{
create
(
:group
)
}
let
(
:child
)
{
create
(
:group
,
parent:
parent
)
}
...
...
spec/models/remote_mirror_spec.rb
0 → 100644
View file @
9a130593
require
'rails_helper'
describe
RemoteMirror
do
describe
'URL validation'
do
context
'with a valid URL'
do
it
'should be valid'
do
remote_mirror
=
build
(
:remote_mirror
)
expect
(
remote_mirror
).
to
be_valid
end
end
context
'with an invalid URL'
do
it
'should not be valid'
do
remote_mirror
=
build
(
:remote_mirror
,
url:
'ftp://invalid.invalid'
)
expect
(
remote_mirror
).
not_to
be_valid
expect
(
remote_mirror
.
errors
[
:url
].
size
).
to
eq
(
2
)
end
end
end
describe
'encrypting credentials'
do
context
'when setting URL for a first time'
do
it
'stores the URL without credentials'
do
mirror
=
create_mirror
(
url:
'http://foo:bar@test.com'
)
expect
(
mirror
.
read_attribute
(
:url
)).
to
eq
(
'http://test.com'
)
end
it
'stores the credentials on a separate field'
do
mirror
=
create_mirror
(
url:
'http://foo:bar@test.com'
)
expect
(
mirror
.
credentials
).
to
eq
({
user:
'foo'
,
password:
'bar'
})
end
it
'handles credentials with large content'
do
mirror
=
create_mirror
(
url:
'http://bxnhm8dote33ct932r3xavslj81wxmr7o8yux8do10oozckkif:9ne7fuvjn40qjt35dgt8v86q9m9g9essryxj76sumg2ccl2fg26c0krtz2gzfpyq4hf22h328uhq6npuiq6h53tpagtsj7vsrz75@test.com'
)
expect
(
mirror
.
credentials
).
to
eq
({
user:
'bxnhm8dote33ct932r3xavslj81wxmr7o8yux8do10oozckkif'
,
password:
'9ne7fuvjn40qjt35dgt8v86q9m9g9essryxj76sumg2ccl2fg26c0krtz2gzfpyq4hf22h328uhq6npuiq6h53tpagtsj7vsrz75'
})
end
end
context
'when updating the URL'
do
it
'allows a new URL without credentials'
do
mirror
=
create_mirror
(
url:
'http://foo:bar@test.com'
)
mirror
.
update_attribute
(
:url
,
'http://test.com'
)
expect
(
mirror
.
url
).
to
eq
(
'http://test.com'
)
expect
(
mirror
.
credentials
).
to
eq
({
user:
nil
,
password:
nil
})
end
it
'allows a new URL with credentials'
do
mirror
=
create_mirror
(
url:
'http://test.com'
)
mirror
.
update_attribute
(
:url
,
'http://foo:bar@test.com'
)
expect
(
mirror
.
url
).
to
eq
(
'http://foo:bar@test.com'
)
expect
(
mirror
.
credentials
).
to
eq
({
user:
'foo'
,
password:
'bar'
})
end
it
'updates the remote config if credentials changed'
do
mirror
=
create_mirror
(
url:
'http://foo:bar@test.com'
)
repo
=
mirror
.
project
.
repository
mirror
.
update_attribute
(
:url
,
'http://foo:baz@test.com'
)
config
=
repo
.
raw_repository
.
rugged
.
config
expect
(
config
[
"remote.
#{
mirror
.
remote_name
}
.url"
]).
to
eq
(
'http://foo:baz@test.com'
)
end
it
'removes previous remote'
do
mirror
=
create_mirror
(
url:
'http://foo:bar@test.com'
)
expect
(
RepositoryRemoveRemoteWorker
).
to
receive
(
:perform_async
).
with
(
mirror
.
project
.
id
,
mirror
.
remote_name
).
and_call_original
mirror
.
update_attributes
(
url:
'http://test.com'
)
end
end
end
describe
'#remote_name'
do
context
'when remote name is persisted in the database'
do
it
'returns remote name with random value'
do
allow
(
SecureRandom
).
to
receive
(
:hex
).
and_return
(
'secret'
)
remote_mirror
=
create
(
:remote_mirror
)
expect
(
remote_mirror
.
remote_name
).
to
eq
(
"remote_mirror_secret"
)
end
end
context
'when remote name is not persisted in the database'
do
it
'returns remote name with remote mirror id'
do
remote_mirror
=
create
(
:remote_mirror
)
remote_mirror
.
remote_name
=
nil
expect
(
remote_mirror
.
remote_name
).
to
eq
(
"remote_mirror_
#{
remote_mirror
.
id
}
"
)
end
end
context
'when remote is not persisted in the database'
do
it
'returns nil'
do
remote_mirror
=
build
(
:remote_mirror
,
remote_name:
nil
)
expect
(
remote_mirror
.
remote_name
).
to
be_nil
end
end
end
describe
'#safe_url'
do
context
'when URL contains credentials'
do
it
'masks the credentials'
do
mirror
=
create_mirror
(
url:
'http://foo:bar@test.com'
)
expect
(
mirror
.
safe_url
).
to
eq
(
'http://*****:*****@test.com'
)
end
end
context
'when URL does not contain credentials'
do
it
'shows the full URL'
do
mirror
=
create_mirror
(
url:
'http://test.com'
)
expect
(
mirror
.
safe_url
).
to
eq
(
'http://test.com'
)
end
end
end
context
'when remote mirror gets destroyed'
do
it
'removes remote'
do
mirror
=
create_mirror
(
url:
'http://foo:bar@test.com'
)
expect
(
RepositoryRemoveRemoteWorker
).
to
receive
(
:perform_async
).
with
(
mirror
.
project
.
id
,
mirror
.
remote_name
).
and_call_original
mirror
.
destroy!
end
end
context
'stuck mirrors'
do
it
'includes mirrors stuck in started with no last_update_at set'
do
mirror
=
create_mirror
(
url:
'http://cantbeblank'
,
update_status:
'started'
,
last_update_at:
nil
,
updated_at:
25
.
hours
.
ago
)
expect
(
described_class
.
stuck
.
last
).
to
eq
(
mirror
)
end
end
context
'#sync'
do
let
(
:remote_mirror
)
{
create
(
:project
,
:repository
,
:remote_mirror
).
remote_mirrors
.
first
}
around
do
|
example
|
Timecop
.
freeze
{
example
.
run
}
end
context
'with remote mirroring disabled'
do
it
'returns nil'
do
remote_mirror
.
update_attributes
(
enabled:
false
)
expect
(
remote_mirror
.
sync
).
to
be_nil
end
end
context
'as a Geo secondary'
do
it
'returns nil'
do
allow
(
Gitlab
::
Geo
).
to
receive
(
:secondary?
).
and_return
(
true
)
expect
(
remote_mirror
.
sync
).
to
be_nil
end
end
context
'with remote mirroring enabled'
do
context
'with only protected branches enabled'
do
context
'when it did not update in the last minute'
do
it
'schedules a RepositoryUpdateRemoteMirrorWorker to run now'
do
expect
(
RepositoryUpdateRemoteMirrorWorker
).
to
receive
(
:perform_async
).
with
(
remote_mirror
.
id
,
Time
.
now
)
remote_mirror
.
sync
end
end
context
'when it did update in the last minute'
do
it
'schedules a RepositoryUpdateRemoteMirrorWorker to run in the next minute'
do
remote_mirror
.
last_update_started_at
=
Time
.
now
-
30
.
seconds
expect
(
RepositoryUpdateRemoteMirrorWorker
).
to
receive
(
:perform_in
).
with
(
RemoteMirror
::
PROTECTED_BACKOFF_DELAY
,
remote_mirror
.
id
,
Time
.
now
)
remote_mirror
.
sync
end
end
end
context
'with only protected branches disabled'
do
before
do
remote_mirror
.
only_protected_branches
=
false
end
context
'when it did not update in the last 5 minutes'
do
it
'schedules a RepositoryUpdateRemoteMirrorWorker to run now'
do
expect
(
RepositoryUpdateRemoteMirrorWorker
).
to
receive
(
:perform_async
).
with
(
remote_mirror
.
id
,
Time
.
now
)
remote_mirror
.
sync
end
end
context
'when it did update within the last 5 minutes'
do
it
'schedules a RepositoryUpdateRemoteMirrorWorker to run in the next 5 minutes'
do
remote_mirror
.
last_update_started_at
=
Time
.
now
-
30
.
seconds
expect
(
RepositoryUpdateRemoteMirrorWorker
).
to
receive
(
:perform_in
).
with
(
RemoteMirror
::
UNPROTECTED_BACKOFF_DELAY
,
remote_mirror
.
id
,
Time
.
now
)
remote_mirror
.
sync
end
end
end
end
end
context
'#updated_since?'
do
let
(
:remote_mirror
)
{
create
(
:project
,
:repository
,
:remote_mirror
).
remote_mirrors
.
first
}
let
(
:timestamp
)
{
Time
.
now
-
5
.
minutes
}
around
do
|
example
|
Timecop
.
freeze
{
example
.
run
}
end
before
do
remote_mirror
.
update_attributes
(
last_update_started_at:
Time
.
now
)
end
context
'when remote mirror does not have status failed'
do
it
'returns true when last update started after the timestamp'
do
expect
(
remote_mirror
.
updated_since?
(
timestamp
)).
to
be
true
end
it
'returns false when last update started before the timestamp'
do
expect
(
remote_mirror
.
updated_since?
(
Time
.
now
+
5
.
minutes
)).
to
be
false
end
end
context
'when remote mirror has status failed'
do
it
'returns false when last update started after the timestamp'
do
remote_mirror
.
update_attributes
(
update_status:
'failed'
)
expect
(
remote_mirror
.
updated_since?
(
timestamp
)).
to
be
false
end
end
end
context
'no project'
do
it
'includes mirror with a project in pending_delete'
do
mirror
=
create_mirror
(
url:
'http://cantbeblank'
,
update_status:
'finished'
,
enabled:
true
,
last_update_at:
nil
,
updated_at:
25
.
hours
.
ago
)
project
=
mirror
.
project
project
.
pending_delete
=
true
project
.
save
mirror
.
reload
expect
(
mirror
.
sync
).
to
be_nil
expect
(
mirror
.
valid?
).
to
be_truthy
expect
(
mirror
.
update_status
).
to
eq
(
'finished'
)
end
end
def
create_mirror
(
params
)
project
=
FactoryBot
.
create
(
:project
,
:repository
)
project
.
remote_mirrors
.
create!
(
params
)
end
end
spec/models/repository_spec.rb
View file @
9a130593
...
...
@@ -758,6 +758,38 @@ describe Repository do
end
end
describe
'#async_remove_remote'
do
before
do
masterrev
=
repository
.
find_branch
(
'master'
).
dereferenced_target
create_remote_branch
(
'joe'
,
'remote_branch'
,
masterrev
)
end
context
'when worker is scheduled successfully'
do
before
do
masterrev
=
repository
.
find_branch
(
'master'
).
dereferenced_target
create_remote_branch
(
'remote_name'
,
'remote_branch'
,
masterrev
)
allow
(
RepositoryRemoveRemoteWorker
).
to
receive
(
:perform_async
).
and_return
(
'1234'
)
end
it
'returns job_id'
do
expect
(
repository
.
async_remove_remote
(
'joe'
)).
to
eq
(
'1234'
)
end
end
context
'when worker does not schedule successfully'
do
before
do
allow
(
RepositoryRemoveRemoteWorker
).
to
receive
(
:perform_async
).
and_return
(
nil
)
end
it
'returns nil'
do
expect
(
Rails
.
logger
).
to
receive
(
:info
).
with
(
"Remove remote job failed to create for
#{
project
.
id
}
with remote name joe."
)
expect
(
repository
.
async_remove_remote
(
'joe'
)).
to
be_nil
end
end
end
describe
'#fetch_ref'
do
let
(
:broken_repository
)
{
create
(
:project
,
:broken_storage
).
repository
}
...
...
spec/services/git_push_service_spec.rb
View file @
9a130593
...
...
@@ -14,6 +14,72 @@ describe GitPushService, services: true do
project
.
add_master
(
user
)
end
describe
'with remote mirrors'
do
let
(
:project
)
{
create
(
:project
,
:repository
,
:remote_mirror
)
}
subject
do
described_class
.
new
(
project
,
user
,
oldrev:
oldrev
,
newrev:
newrev
,
ref:
ref
)
end
context
'when remote mirror feature is enabled'
do
it
'fails stuck remote mirrors'
do
allow
(
project
).
to
receive
(
:update_remote_mirrors
).
and_return
(
project
.
remote_mirrors
)
expect
(
project
).
to
receive
(
:mark_stuck_remote_mirrors_as_failed!
)
subject
.
execute
end
it
'updates remote mirrors'
do
expect
(
project
).
to
receive
(
:update_remote_mirrors
)
subject
.
execute
end
end
context
'when remote mirror feature is disabled'
do
before
do
stub_application_setting
(
mirror_available:
false
)
end
context
'with remote mirrors global setting overridden'
do
before
do
project
.
remote_mirror_available_overridden
=
true
end
it
'fails stuck remote mirrors'
do
allow
(
project
).
to
receive
(
:update_remote_mirrors
).
and_return
(
project
.
remote_mirrors
)
expect
(
project
).
to
receive
(
:mark_stuck_remote_mirrors_as_failed!
)
subject
.
execute
end
it
'updates remote mirrors'
do
expect
(
project
).
to
receive
(
:update_remote_mirrors
)
subject
.
execute
end
end
context
'without remote mirrors global setting overridden'
do
before
do
project
.
remote_mirror_available_overridden
=
false
end
it
'does not fails stuck remote mirrors'
do
expect
(
project
).
not_to
receive
(
:mark_stuck_remote_mirrors_as_failed!
)
subject
.
execute
end
it
'does not updates remote mirrors'
do
expect
(
project
).
not_to
receive
(
:update_remote_mirrors
)
subject
.
execute
end
end
end
end
describe
'Push branches'
do
subject
do
execute_service
(
project
,
user
,
oldrev
,
newrev
,
ref
)
...
...
spec/services/projects/destroy_service_spec.rb
View file @
9a130593
...
...
@@ -65,6 +65,19 @@ describe Projects::DestroyService do
Sidekiq
::
Testing
.
inline!
{
destroy_project
(
project
,
user
,
{})
}
end
context
'when has remote mirrors'
do
let!
(
:project
)
do
create
(
:project
,
:repository
,
namespace:
user
.
namespace
).
tap
do
|
project
|
project
.
remote_mirrors
.
create
(
url:
'http://test.com'
)
end
end
let!
(
:async
)
{
true
}
it
'destroys them'
do
expect
(
RemoteMirror
.
count
).
to
eq
(
0
)
end
end
it_behaves_like
'deleting the project'
it
'invalidates personal_project_count cache'
do
...
...
spec/services/projects/update_remote_mirror_service_spec.rb
0 → 100644
View file @
9a130593
require
'spec_helper'
describe
Projects
::
UpdateRemoteMirrorService
do
let
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:remote_project
)
{
create
(
:forked_project_with_submodules
)
}
let
(
:repository
)
{
project
.
repository
}
let
(
:raw_repository
)
{
repository
.
raw
}
let
(
:remote_mirror
)
{
project
.
remote_mirrors
.
create!
(
url:
remote_project
.
http_url_to_repo
,
enabled:
true
,
only_protected_branches:
false
)
}
subject
{
described_class
.
new
(
project
,
project
.
creator
)
}
describe
"#execute"
,
:skip_gitaly_mock
do
before
do
create_branch
(
repository
,
'existing-branch'
)
allow
(
raw_repository
).
to
receive
(
:remote_tags
)
do
generate_tags
(
repository
,
'v1.0.0'
,
'v1.1.0'
)
end
allow
(
raw_repository
).
to
receive
(
:push_remote_branches
).
and_return
(
true
)
end
it
"fetches the remote repository"
do
expect
(
repository
).
to
receive
(
:fetch_remote
).
with
(
remote_mirror
.
remote_name
,
no_tags:
true
)
do
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
end
subject
.
execute
(
remote_mirror
)
end
it
"succeeds"
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
{
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
}
result
=
subject
.
execute
(
remote_mirror
)
expect
(
result
[
:status
]).
to
eq
(
:success
)
end
describe
'Syncing branches'
do
it
"push all the branches the first time"
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
expect
(
raw_repository
).
to
receive
(
:push_remote_branches
).
with
(
remote_mirror
.
remote_name
,
local_branch_names
)
subject
.
execute
(
remote_mirror
)
end
it
"does not push anything is remote is up to date"
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
{
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
}
expect
(
raw_repository
).
not_to
receive
(
:push_remote_branches
)
subject
.
execute
(
remote_mirror
)
end
it
"sync new branches"
do
# call local_branch_names early so it is not called after the new branch has been created
current_branches
=
local_branch_names
allow
(
repository
).
to
receive
(
:fetch_remote
)
{
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
current_branches
)
}
create_branch
(
repository
,
'my-new-branch'
)
expect
(
raw_repository
).
to
receive
(
:push_remote_branches
).
with
(
remote_mirror
.
remote_name
,
[
'my-new-branch'
])
subject
.
execute
(
remote_mirror
)
end
it
"sync updated branches"
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
do
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
update_branch
(
repository
,
'existing-branch'
)
end
expect
(
raw_repository
).
to
receive
(
:push_remote_branches
).
with
(
remote_mirror
.
remote_name
,
[
'existing-branch'
])
subject
.
execute
(
remote_mirror
)
end
context
'when push only protected branches option is set'
do
let
(
:unprotected_branch_name
)
{
'existing-branch'
}
let
(
:protected_branch_name
)
do
project
.
repository
.
branch_names
.
find
{
|
n
|
n
!=
unprotected_branch_name
}
end
let!
(
:protected_branch
)
do
create
(
:protected_branch
,
project:
project
,
name:
protected_branch_name
)
end
before
do
project
.
reload
remote_mirror
.
only_protected_branches
=
true
end
it
"sync updated protected branches"
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
do
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
update_branch
(
repository
,
protected_branch_name
)
end
expect
(
raw_repository
).
to
receive
(
:push_remote_branches
).
with
(
remote_mirror
.
remote_name
,
[
protected_branch_name
])
subject
.
execute
(
remote_mirror
)
end
it
'does not sync unprotected branches'
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
do
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
update_branch
(
repository
,
unprotected_branch_name
)
end
expect
(
raw_repository
).
not_to
receive
(
:push_remote_branches
).
with
(
remote_mirror
.
remote_name
,
[
unprotected_branch_name
])
subject
.
execute
(
remote_mirror
)
end
end
context
'when branch exists in local and remote repo'
do
context
'when it has diverged'
do
it
'syncs branches'
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
do
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
update_remote_branch
(
repository
,
remote_mirror
.
remote_name
,
'markdown'
)
end
expect
(
raw_repository
).
to
receive
(
:push_remote_branches
).
with
(
remote_mirror
.
remote_name
,
[
'markdown'
])
subject
.
execute
(
remote_mirror
)
end
end
end
describe
'for delete'
do
context
'when branch exists in local and remote repo'
do
it
'deletes the branch from remote repo'
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
do
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
delete_branch
(
repository
,
'existing-branch'
)
end
expect
(
raw_repository
).
to
receive
(
:delete_remote_branches
).
with
(
remote_mirror
.
remote_name
,
[
'existing-branch'
])
subject
.
execute
(
remote_mirror
)
end
end
context
'when push only protected branches option is set'
do
before
do
remote_mirror
.
only_protected_branches
=
true
end
context
'when branch exists in local and remote repo'
do
let!
(
:protected_branch_name
)
{
local_branch_names
.
first
}
before
do
create
(
:protected_branch
,
project:
project
,
name:
protected_branch_name
)
project
.
reload
end
it
'deletes the protected branch from remote repo'
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
do
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
delete_branch
(
repository
,
protected_branch_name
)
end
expect
(
raw_repository
).
not_to
receive
(
:delete_remote_branches
).
with
(
remote_mirror
.
remote_name
,
[
protected_branch_name
])
subject
.
execute
(
remote_mirror
)
end
it
'does not delete the unprotected branch from remote repo'
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
do
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
delete_branch
(
repository
,
'existing-branch'
)
end
expect
(
raw_repository
).
not_to
receive
(
:delete_remote_branches
).
with
(
remote_mirror
.
remote_name
,
[
'existing-branch'
])
subject
.
execute
(
remote_mirror
)
end
end
context
'when branch only exists on remote repo'
do
let!
(
:protected_branch_name
)
{
'remote-branch'
}
before
do
create
(
:protected_branch
,
project:
project
,
name:
protected_branch_name
)
end
context
'when it has diverged'
do
it
'does not delete the remote branch'
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
do
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
rev
=
repository
.
find_branch
(
'markdown'
).
dereferenced_target
create_remote_branch
(
repository
,
remote_mirror
.
remote_name
,
'remote-branch'
,
rev
.
id
)
end
expect
(
raw_repository
).
not_to
receive
(
:delete_remote_branches
)
subject
.
execute
(
remote_mirror
)
end
end
context
'when it has not diverged'
do
it
'deletes the remote branch'
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
do
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
masterrev
=
repository
.
find_branch
(
'master'
).
dereferenced_target
create_remote_branch
(
repository
,
remote_mirror
.
remote_name
,
protected_branch_name
,
masterrev
.
id
)
end
expect
(
raw_repository
).
to
receive
(
:delete_remote_branches
).
with
(
remote_mirror
.
remote_name
,
[
protected_branch_name
])
subject
.
execute
(
remote_mirror
)
end
end
end
end
context
'when branch only exists on remote repo'
do
context
'when it has diverged'
do
it
'does not delete the remote branch'
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
do
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
rev
=
repository
.
find_branch
(
'markdown'
).
dereferenced_target
create_remote_branch
(
repository
,
remote_mirror
.
remote_name
,
'remote-branch'
,
rev
.
id
)
end
expect
(
raw_repository
).
not_to
receive
(
:delete_remote_branches
)
subject
.
execute
(
remote_mirror
)
end
end
context
'when it has not diverged'
do
it
'deletes the remote branch'
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
do
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
masterrev
=
repository
.
find_branch
(
'master'
).
dereferenced_target
create_remote_branch
(
repository
,
remote_mirror
.
remote_name
,
'remote-branch'
,
masterrev
.
id
)
end
expect
(
raw_repository
).
to
receive
(
:delete_remote_branches
).
with
(
remote_mirror
.
remote_name
,
[
'remote-branch'
])
subject
.
execute
(
remote_mirror
)
end
end
end
end
end
describe
'Syncing tags'
do
before
do
allow
(
repository
).
to
receive
(
:fetch_remote
)
{
sync_remote
(
repository
,
remote_mirror
.
remote_name
,
local_branch_names
)
}
end
context
'when there are not tags to push'
do
it
'does not try to push tags'
do
allow
(
repository
).
to
receive
(
:remote_tags
)
{
{}
}
allow
(
repository
).
to
receive
(
:tags
)
{
[]
}
expect
(
repository
).
not_to
receive
(
:push_tags
)
subject
.
execute
(
remote_mirror
)
end
end
context
'when there are some tags to push'
do
it
'pushes tags to remote'
do
allow
(
raw_repository
).
to
receive
(
:remote_tags
)
{
{}
}
expect
(
raw_repository
).
to
receive
(
:push_remote_branches
).
with
(
remote_mirror
.
remote_name
,
[
'v1.0.0'
,
'v1.1.0'
])
subject
.
execute
(
remote_mirror
)
end
end
context
'when there are some tags to delete'
do
it
'deletes tags from remote'
do
remote_tags
=
generate_tags
(
repository
,
'v1.0.0'
,
'v1.1.0'
)
allow
(
raw_repository
).
to
receive
(
:remote_tags
)
{
remote_tags
}
repository
.
rm_tag
(
create
(
:user
),
'v1.0.0'
)
expect
(
raw_repository
).
to
receive
(
:delete_remote_branches
).
with
(
remote_mirror
.
remote_name
,
[
'v1.0.0'
])
subject
.
execute
(
remote_mirror
)
end
end
end
end
def
create_branch
(
repository
,
branch_name
)
rugged
=
repository
.
rugged
masterrev
=
repository
.
find_branch
(
'master'
).
dereferenced_target
parentrev
=
repository
.
commit
(
masterrev
).
parent_id
rugged
.
references
.
create
(
"refs/heads/
#{
branch_name
}
"
,
parentrev
)
repository
.
expire_branches_cache
end
def
create_remote_branch
(
repository
,
remote_name
,
branch_name
,
source_id
)
rugged
=
repository
.
rugged
rugged
.
references
.
create
(
"refs/remotes/
#{
remote_name
}
/
#{
branch_name
}
"
,
source_id
)
end
def
sync_remote
(
repository
,
remote_name
,
local_branch_names
)
rugged
=
repository
.
rugged
local_branch_names
.
each
do
|
branch
|
target
=
repository
.
find_branch
(
branch
).
try
(
:dereferenced_target
)
rugged
.
references
.
create
(
"refs/remotes/
#{
remote_name
}
/
#{
branch
}
"
,
target
.
id
)
if
target
end
end
def
update_remote_branch
(
repository
,
remote_name
,
branch
)
rugged
=
repository
.
rugged
masterrev
=
repository
.
find_branch
(
'master'
).
dereferenced_target
.
id
rugged
.
references
.
create
(
"refs/remotes/
#{
remote_name
}
/
#{
branch
}
"
,
masterrev
,
force:
true
)
repository
.
expire_branches_cache
end
def
update_branch
(
repository
,
branch
)
rugged
=
repository
.
rugged
masterrev
=
repository
.
find_branch
(
'master'
).
dereferenced_target
.
id
# Updated existing branch
rugged
.
references
.
create
(
"refs/heads/
#{
branch
}
"
,
masterrev
,
force:
true
)
repository
.
expire_branches_cache
end
def
delete_branch
(
repository
,
branch
)
rugged
=
repository
.
rugged
rugged
.
references
.
delete
(
"refs/heads/
#{
branch
}
"
)
repository
.
expire_branches_cache
end
def
generate_tags
(
repository
,
*
tag_names
)
tag_names
.
each_with_object
([])
do
|
name
,
tags
|
tag
=
repository
.
find_tag
(
name
)
target
=
tag
.
try
(
:target
)
target_commit
=
tag
.
try
(
:dereferenced_target
)
tags
<<
Gitlab
::
Git
::
Tag
.
new
(
repository
.
raw_repository
,
name
,
target
,
target_commit
)
end
end
def
local_branch_names
branch_names
=
repository
.
branches
.
map
(
&
:name
)
# we want the protected branch to be pushed first
branch_names
.
unshift
(
branch_names
.
delete
(
'master'
))
end
end
spec/workers/repository_remove_remote_worker_spec.rb
0 → 100644
View file @
9a130593
require
'rails_helper'
describe
RepositoryRemoveRemoteWorker
do
subject
(
:worker
)
{
described_class
.
new
}
describe
'#perform'
do
let
(
:remote_name
)
{
'joe'
}
let!
(
:project
)
{
create
(
:project
,
:repository
)
}
context
'when it cannot obtain lease'
do
it
'logs error'
do
allow_any_instance_of
(
Gitlab
::
ExclusiveLease
).
to
receive
(
:try_obtain
)
{
nil
}
expect_any_instance_of
(
Repository
).
not_to
receive
(
:remove_remote
)
expect
(
worker
).
to
receive
(
:log_error
).
with
(
'Cannot obtain an exclusive lease. There must be another instance already in execution.'
)
worker
.
perform
(
project
.
id
,
remote_name
)
end
end
context
'when it gets the lease'
do
before
do
allow_any_instance_of
(
Gitlab
::
ExclusiveLease
).
to
receive
(
:try_obtain
).
and_return
(
true
)
end
context
'when project does not exist'
do
it
'returns nil'
do
expect
(
worker
.
perform
(
-
1
,
'remote_name'
)).
to
be_nil
end
end
context
'when project exists'
do
it
'removes remote from repository'
do
masterrev
=
project
.
repository
.
find_branch
(
'master'
).
dereferenced_target
create_remote_branch
(
remote_name
,
'remote_branch'
,
masterrev
)
expect_any_instance_of
(
Repository
).
to
receive
(
:remove_remote
).
with
(
remote_name
).
and_call_original
worker
.
perform
(
project
.
id
,
remote_name
)
end
end
end
end
def
create_remote_branch
(
remote_name
,
branch_name
,
target
)
rugged
=
project
.
repository
.
rugged
rugged
.
references
.
create
(
"refs/remotes/
#{
remote_name
}
/
#{
branch_name
}
"
,
target
.
id
)
end
end
spec/workers/repository_update_remote_mirror_worker_spec.rb
0 → 100644
View file @
9a130593
require
'rails_helper'
describe
RepositoryUpdateRemoteMirrorWorker
do
subject
{
described_class
.
new
}
let
(
:remote_mirror
)
{
create
(
:project
,
:repository
,
:remote_mirror
).
remote_mirrors
.
first
}
let
(
:scheduled_time
)
{
Time
.
now
-
5
.
minutes
}
around
do
|
example
|
Timecop
.
freeze
(
Time
.
now
)
{
example
.
run
}
end
describe
'#perform'
do
context
'with status none'
do
before
do
remote_mirror
.
update_attributes
(
update_status:
'none'
)
end
it
'sets status as finished when update remote mirror service executes successfully'
do
expect_any_instance_of
(
Projects
::
UpdateRemoteMirrorService
).
to
receive
(
:execute
).
with
(
remote_mirror
).
and_return
(
status: :success
)
expect
{
subject
.
perform
(
remote_mirror
.
id
,
Time
.
now
)
}.
to
change
{
remote_mirror
.
reload
.
update_status
}.
to
(
'finished'
)
end
it
'sets status as failed when update remote mirror service executes with errors'
do
error_message
=
'fail!'
expect_any_instance_of
(
Projects
::
UpdateRemoteMirrorService
).
to
receive
(
:execute
).
with
(
remote_mirror
).
and_return
(
status: :error
,
message:
error_message
)
expect
do
subject
.
perform
(
remote_mirror
.
id
,
Time
.
now
)
end
.
to
raise_error
(
RepositoryUpdateRemoteMirrorWorker
::
UpdateError
,
error_message
)
expect
(
remote_mirror
.
reload
.
update_status
).
to
eq
(
'failed'
)
end
it
'does nothing if last_update_started_at is higher than the time the job was scheduled in'
do
remote_mirror
.
update_attributes
(
last_update_started_at:
Time
.
now
)
expect_any_instance_of
(
RemoteMirror
).
to
receive
(
:updated_since?
).
with
(
scheduled_time
).
and_return
(
true
)
expect_any_instance_of
(
Projects
::
UpdateRemoteMirrorService
).
not_to
receive
(
:execute
).
with
(
remote_mirror
)
expect
(
subject
.
perform
(
remote_mirror
.
id
,
scheduled_time
)).
to
be_nil
end
end
context
'with unexpected error'
do
it
'marks mirror as failed'
do
allow_any_instance_of
(
Projects
::
UpdateRemoteMirrorService
).
to
receive
(
:execute
).
with
(
remote_mirror
).
and_raise
(
RuntimeError
)
expect
do
subject
.
perform
(
remote_mirror
.
id
,
Time
.
now
)
end
.
to
raise_error
(
RepositoryUpdateRemoteMirrorWorker
::
UpdateError
)
expect
(
remote_mirror
.
reload
.
update_status
).
to
eq
(
'failed'
)
end
end
context
'with another worker already running'
do
before
do
remote_mirror
.
update_attributes
(
update_status:
'started'
)
end
it
'raises RemoteMirrorUpdateAlreadyInProgressError'
do
expect
do
subject
.
perform
(
remote_mirror
.
id
,
Time
.
now
)
end
.
to
raise_error
(
RepositoryUpdateRemoteMirrorWorker
::
UpdateAlreadyInProgressError
)
end
end
context
'with status failed'
do
before
do
remote_mirror
.
update_attributes
(
update_status:
'failed'
)
end
it
'sets status as finished if last_update_started_at is higher than the time the job was scheduled in'
do
remote_mirror
.
update_attributes
(
last_update_started_at:
Time
.
now
)
expect_any_instance_of
(
RemoteMirror
).
to
receive
(
:updated_since?
).
with
(
scheduled_time
).
and_return
(
false
)
expect_any_instance_of
(
Projects
::
UpdateRemoteMirrorService
).
to
receive
(
:execute
).
with
(
remote_mirror
).
and_return
(
status: :success
)
expect
{
subject
.
perform
(
remote_mirror
.
id
,
scheduled_time
)
}.
to
change
{
remote_mirror
.
reload
.
update_status
}.
to
(
'finished'
)
end
end
end
end
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