Commit f89c3101 authored by Stan Hu's avatar Stan Hu

Merge branch 'ce-to-ee-2018-04-06' into 'master'

CE upstream - 2018-04-06 15:28 UTC

See merge request gitlab-org/gitlab-ee!5270
parents f349bdde 2c1510ff
......@@ -107,7 +107,6 @@
}
}
.commits-compare-switch {
float: left;
margin-right: 9px;
......@@ -179,7 +178,7 @@
.commit-detail {
display: flex;
justify-content: space-between;
align-items: flex-start;
align-items: center;
flex-grow: 1;
.merge-request-branches & {
......@@ -204,37 +203,63 @@
}
.ci-status-link {
display: inline-block;
position: relative;
top: 2px;
display: inline-flex;
}
.btn-clipboard,
.btn-transparent {
padding-left: 0;
padding-right: 0;
> .ci-status-link,
> .btn,
> .commit-sha-group {
margin-left: $gl-padding-8;
}
}
.commit-sha-group {
display: inline-flex;
.label,
.btn {
&:not(:first-child) {
margin-left: $gl-padding;
}
padding: $gl-vert-padding $gl-btn-padding;
border: 1px $border-color solid;
font-size: $gl-font-size;
line-height: $line-height-base;
border-radius: 0;
display: flex;
align-items: center;
}
.label-monospace {
@extend .monospace;
user-select: text;
color: $gl-text-color;
background-color: $gray-light;
}
.commit-sha {
font-size: 14px;
font-weight: $gl-font-weight-bold;
.btn svg {
top: auto;
fill: $gl-text-color-secondary;
}
.ci-status-icon {
position: relative;
top: 2px;
.fa-clipboard {
color: $gl-text-color-secondary;
}
:first-child {
border-bottom-left-radius: $border-radius-default;
border-top-left-radius: $border-radius-default;
}
:not(:first-child) {
border-left: 0;
}
:last-child {
border-bottom-right-radius: $border-radius-default;
border-top-right-radius: $border-radius-default;
}
}
.commit,
.generic_commit_status {
a,
button {
color: $gl-text-color;
......@@ -307,10 +332,8 @@
}
}
.gpg-status-box {
padding: 2px 10px;
margin-right: $gl-padding;
&:empty {
display: none;
......
......@@ -10,7 +10,7 @@ module AuthenticatesWithTwoFactor
# This action comes from DeviseController, but because we call `sign_in`
# manually, not skipping this action would cause a "You are already signed
# in." error message to be shown upon successful login.
skip_before_action :require_no_authentication, only: [:create]
skip_before_action :require_no_authentication, only: [:create], raise: false
end
# Store the user's ID in the session for later retrieval and render the
......
class Projects::RepositoriesController < Projects::ApplicationController
include ExtractsPath
# Authorize
before_action :require_non_empty_project, except: :create
before_action :assign_archive_vars, only: :archive
before_action :authorize_download_code!
before_action :authorize_admin_project!, only: :create
......@@ -11,9 +14,21 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
send_git_archive @repository, ref: params[:ref], format: params[:format]
append_sha = params[:append_sha]
shortname = "#{@project.path}-#{@ref.tr('/', '-')}"
append_sha = false if @filename == shortname
send_git_archive @repository, ref: @ref, format: params[:format], append_sha: append_sha
rescue => ex
logger.error("#{self.class.name}: #{ex}")
return git_not_found!
end
def assign_archive_vars
@id = params[:id]
@ref, @filename = extract_ref(@id)
rescue InvalidPathError
render_404
end
end
......@@ -93,25 +93,18 @@ module CommitsHelper
return unless current_controller?(:commits)
if @path.blank?
return link_to(
_("Browse Files"),
project_tree_path(project, commit),
class: "btn btn-default"
)
url = project_tree_path(project, commit)
tooltip = _("Browse Files")
elsif @repo.blob_at(commit.id, @path)
return link_to(
_("Browse File"),
project_blob_path(project,
tree_join(commit.id, @path)),
class: "btn btn-default"
)
url = project_blob_path(project, tree_join(commit.id, @path))
tooltip = _("Browse File")
elsif @path.present?
return link_to(
_("Browse Directory"),
project_tree_path(project,
tree_join(commit.id, @path)),
class: "btn btn-default"
)
url = project_tree_path(project, tree_join(commit.id, @path))
tooltip = _("Browse Directory")
end
link_to url, class: "btn btn-default has-tooltip", title: tooltip, data: { container: "body" } do
sprite_icon('folder-open')
end
end
......
......@@ -24,8 +24,8 @@ module WorkhorseHelper
end
# Archive a Git repository and send it through Workhorse
def send_git_archive(repository, ref:, format:)
headers.store(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
def send_git_archive(repository, **kwargs)
headers.store(*Gitlab::Workhorse.send_git_archive(repository, **kwargs))
head :ok
end
......
......@@ -330,6 +330,10 @@ class Group < Namespace
false
end
def refresh_project_authorizations
refresh_members_authorized_projects(blocking: false)
end
private
def update_two_factor_requirement
......
......@@ -262,6 +262,10 @@ class Namespace < ActiveRecord::Base
[]
end
def refresh_project_authorizations
owner.refresh_authorized_projects
end
private
def path_or_parent_changed?
......
......@@ -1486,7 +1486,9 @@ class Project < ActiveRecord::Base
end
def rename_repo_notify!
send_move_instructions(full_path_was)
# When we import a project overwriting the original project, there
# is a move operation. In that case we don't want to send the instructions.
send_move_instructions(full_path_was) unless started?
expires_full_path_cache
self.old_path_with_namespace = full_path_was
......
module Projects
class BaseMoveRelationsService < BaseService
attr_reader :source_project
def execute(source_project, remove_remaining_elements: true)
return if source_project.blank?
@source_project = source_project
true
end
private
def prepare_relation(relation, id_param = :id)
if Gitlab::Database.postgresql?
relation
else
relation.model.where("#{id_param}": relation.pluck(id_param))
end
end
end
end
......@@ -47,6 +47,20 @@ module Projects
raise
end
def attempt_repositories_rollback
return unless @project
flush_caches(@project)
unless rollback_repository(removal_path(repo_path), repo_path)
raise_error('Failed to restore project repository. Please contact the administrator.')
end
unless rollback_repository(removal_path(wiki_path), wiki_path)
raise_error('Failed to restore wiki repository. Please contact the administrator.')
end
end
private
def repo_path
......@@ -72,11 +86,11 @@ module Projects
return true if params[:skip_repo] == true
# There is a possibility project does not have repository or wiki
return true unless gitlab_shell.exists?(project.repository_storage_path, path + '.git')
return true unless repo_exists?(path)
new_path = removal_path(path)
if gitlab_shell.mv_repository(project.repository_storage_path, path, new_path)
if mv_repository(path, new_path)
log_info("Repository \"#{path}\" moved to \"#{new_path}\"")
project.run_after_commit do
......@@ -88,6 +102,21 @@ module Projects
end
end
def rollback_repository(old_path, new_path)
# There is a possibility project does not have repository or wiki
return true unless repo_exists?(old_path)
mv_repository(old_path, new_path)
end
def repo_exists?(path)
gitlab_shell.exists?(project.repository_storage_path, path + '.git')
end
def mv_repository(from_path, to_path)
gitlab_shell.mv_repository(project.repository_storage_path, from_path, to_path)
end
def attempt_rollback(project, message)
return unless project
......
......@@ -15,9 +15,18 @@ module Projects
file = params.delete(:file)
FileUtils.copy_entry(file.path, import_upload_path)
@overwrite = params.delete(:overwrite)
data = {}
data[:override_params] = @override_params if @override_params
if overwrite_project?
data[:original_path] = params[:path]
params[:path] += "-#{tmp_filename}"
end
params[:import_type] = 'gitlab_project'
params[:import_source] = import_upload_path
params[:import_data] = { data: { override_params: @override_params } } if @override_params
params[:import_data] = { data: data } if data.present?
::Projects::CreateService.new(current_user, params).execute
end
......@@ -31,5 +40,17 @@ module Projects
def tmp_filename
SecureRandom.hex
end
def overwrite_project?
@overwrite && project_with_same_full_path?
end
def project_with_same_full_path?
Project.find_by_full_path("#{current_namespace.full_path}/#{params[:path]}").present?
end
def current_namespace
@current_namespace ||= Namespace.find_by(id: params[:namespace_id])
end
end
end
module Projects
class MoveAccessService < BaseMoveRelationsService
def execute(source_project, remove_remaining_elements: true)
return unless super
@project.with_transaction_returning_status do
if @project.namespace != source_project.namespace
@project.run_after_commit do
source_project.namespace.refresh_project_authorizations
self.namespace.refresh_project_authorizations
end
end
::Projects::MoveProjectMembersService.new(@project, @current_user)
.execute(source_project, remove_remaining_elements: remove_remaining_elements)
::Projects::MoveProjectGroupLinksService.new(@project, @current_user)
.execute(source_project, remove_remaining_elements: remove_remaining_elements)
::Projects::MoveProjectAuthorizationsService.new(@project, @current_user)
.execute(source_project, remove_remaining_elements: remove_remaining_elements)
success
end
end
end
end
module Projects
class MoveDeployKeysProjectsService < BaseMoveRelationsService
def execute(source_project, remove_remaining_elements: true)
return unless super
Project.transaction(requires_new: true) do
move_deploy_keys_projects
remove_remaining_deploy_keys_projects if remove_remaining_elements
success
end
end
private
def move_deploy_keys_projects
prepare_relation(non_existent_deploy_keys_projects)
.update_all(project_id: @project.id)
end
def non_existent_deploy_keys_projects
source_project.deploy_keys_projects
.joins(:deploy_key)
.where.not(keys: { fingerprint: @project.deploy_keys.select(:fingerprint) })
end
def remove_remaining_deploy_keys_projects
source_project.deploy_keys_projects.destroy_all
end
end
end
module Projects
class MoveForksService < BaseMoveRelationsService
def execute(source_project, remove_remaining_elements: true)
return unless super && source_project.fork_network
Project.transaction(requires_new: true) do
move_forked_project_links
move_fork_network_members
update_root_project
refresh_forks_count
success
end
end
private
def move_forked_project_links
# Update ancestor
ForkedProjectLink.where(forked_to_project: source_project)
.update_all(forked_to_project_id: @project.id)
# Update the descendants
ForkedProjectLink.where(forked_from_project: source_project)
.update_all(forked_from_project_id: @project.id)
end
def move_fork_network_members
ForkNetworkMember.where(project: source_project).update_all(project_id: @project.id)
ForkNetworkMember.where(forked_from_project: source_project).update_all(forked_from_project_id: @project.id)
end
def update_root_project
# Update root network project
ForkNetwork.where(root_project: source_project).update_all(root_project_id: @project.id)
end
def refresh_forks_count
Projects::ForksCountService.new(@project).refresh_cache
end
end
end
module Projects
class MoveLfsObjectsProjectsService < BaseMoveRelationsService
def execute(source_project, remove_remaining_elements: true)
return unless super
Project.transaction(requires_new: true) do
move_lfs_objects_projects
remove_remaining_lfs_objects_project if remove_remaining_elements
success
end
end
private
def move_lfs_objects_projects
prepare_relation(non_existent_lfs_objects_projects)
.update_all(project_id: @project.lfs_storage_project.id)
end
def remove_remaining_lfs_objects_project
source_project.lfs_objects_projects.destroy_all
end
def non_existent_lfs_objects_projects
source_project.lfs_objects_projects.where.not(lfs_object: @project.lfs_objects)
end
end
end
module Projects
class MoveNotificationSettingsService < BaseMoveRelationsService
def execute(source_project, remove_remaining_elements: true)
return unless super
Project.transaction(requires_new: true) do
move_notification_settings
remove_remaining_notification_settings if remove_remaining_elements
success
end
end
private
def move_notification_settings
prepare_relation(non_existent_notifications)
.update_all(source_id: @project.id)
end
# Remove remaining notification settings from source_project
def remove_remaining_notification_settings
source_project.notification_settings.destroy_all
end
# Get users of current notification_settings
def users_in_target_project
@project.notification_settings.select(:user_id)
end
# Look for notification_settings in source_project that are not in the target project
def non_existent_notifications
source_project.notification_settings
.select(:id)
.where.not(user_id: users_in_target_project)
end
end
end
# NOTE: This service cannot be used directly because it is part of a
# a bigger process. Instead, use the service MoveAccessService which moves
# project memberships, project group links, authorizations and refreshes
# the authorizations if neccessary
module Projects
class MoveProjectAuthorizationsService < BaseMoveRelationsService
def execute(source_project, remove_remaining_elements: true)
return unless super
Project.transaction(requires_new: true) do
move_project_authorizations
remove_remaining_authorizations if remove_remaining_elements
success
end
end
private
def move_project_authorizations
prepare_relation(non_existent_authorization, :user_id)
.update_all(project_id: @project.id)
end
def remove_remaining_authorizations
# I think because the Project Authorization table does not have a primary key
# it brings a lot a problems/bugs. First, Rails raises PG::SyntaxException if we use
# destroy_all instead of delete_all.
source_project.project_authorizations.delete_all(:delete_all)
end
# Look for authorizations in source_project that are not in the target project
def non_existent_authorization
source_project.project_authorizations
.select(:user_id)
.where.not(user: @project.authorized_users)
end
end
end
# NOTE: This service cannot be used directly because it is part of a
# a bigger process. Instead, use the service MoveAccessService which moves
# project memberships, project group links, authorizations and refreshes
# the authorizations if neccessary
module Projects
class MoveProjectGroupLinksService < BaseMoveRelationsService
def execute(source_project, remove_remaining_elements: true)
return unless super
Project.transaction(requires_new: true) do
move_group_links
remove_remaining_project_group_links if remove_remaining_elements
success
end
end
private
def move_group_links
prepare_relation(non_existent_group_links)
.update_all(project_id: @project.id)
end
# Remove remaining project group links from source_project
def remove_remaining_project_group_links
source_project.reload.project_group_links.destroy_all
end
def group_links_in_target_project
@project.project_group_links.select(:group_id)
end
# Look for groups in source_project that are not in the target project
def non_existent_group_links
source_project.project_group_links
.where.not(group_id: group_links_in_target_project)
end
end
end
# NOTE: This service cannot be used directly because it is part of a
# a bigger process. Instead, use the service MoveAccessService which moves
# project memberships, project group links, authorizations and refreshes
# the authorizations if neccessary
module Projects
class MoveProjectMembersService < BaseMoveRelationsService
def execute(source_project, remove_remaining_elements: true)
return unless super
Project.transaction(requires_new: true) do
move_project_members
remove_remaining_members if remove_remaining_elements
success
end
end
private
def move_project_members
prepare_relation(non_existent_members).update_all(source_id: @project.id)
end
def remove_remaining_members
# Remove remaining members and authorizations from source_project
source_project.project_members.destroy_all
end
def project_members_in_target_project
@project.project_members.select(:user_id)
end
# Look for members in source_project that are not in the target project
def non_existent_members
source_project.members
.select(:id)
.where.not(user_id: @project.project_members.select(:user_id))
end
end
end
module Projects
class MoveUsersStarProjectsService < BaseMoveRelationsService
def execute(source_project, remove_remaining_elements: true)
return unless super
user_stars = source_project.users_star_projects
return unless user_stars.any?
Project.transaction(requires_new: true) do
user_stars.update_all(project_id: @project.id)
Project.reset_counters @project.id, :users_star_projects
Project.reset_counters source_project.id, :users_star_projects
success
end
end
end
end
module Projects
class OverwriteProjectService < BaseService
def execute(source_project)
return unless source_project && source_project.namespace == @project.namespace
Project.transaction do
move_before_destroy_relationships(source_project)
destroy_old_project(source_project)
rename_project(source_project.name, source_project.path)
@project
end
# Projects::DestroyService can raise Exceptions, but we don't want
# to pass that kind of exception to the caller. Instead, we change it
# for a StandardError exception
rescue Exception => e # rubocop:disable Lint/RescueException
attempt_restore_repositories(source_project)
if e.class == Exception
raise StandardError, e.message
else
raise
end
end
private
def move_before_destroy_relationships(source_project)
options = { remove_remaining_elements: false }
::Projects::MoveUsersStarProjectsService.new(@project, @current_user).execute(source_project, options)
::Projects::MoveAccessService.new(@project, @current_user).execute(source_project, options)
::Projects::MoveDeployKeysProjectsService.new(@project, @current_user).execute(source_project, options)
::Projects::MoveNotificationSettingsService.new(@project, @current_user).execute(source_project, options)
::Projects::MoveForksService.new(@project, @current_user).execute(source_project, options)
::Projects::MoveLfsObjectsProjectsService.new(@project, @current_user).execute(source_project, options)
add_source_project_to_fork_network(source_project)
end
def destroy_old_project(source_project)
# Delete previous project (synchronously) and unlink relations
::Projects::DestroyService.new(source_project, @current_user).execute
end
def rename_project(name, path)
# Update de project's name and path to the original name/path
::Projects::UpdateService.new(@project,
@current_user,
{ name: name, path: path })
.execute
end
def attempt_restore_repositories(project)
::Projects::DestroyService.new(project, @current_user).attempt_repositories_rollback
end
def add_source_project_to_fork_network(source_project)
return unless @project.fork_network
# Because he have moved all references in the fork network from the source_project
# we won't be able to query the database (only through its cached data),
# for its former relationships. That's why we're adding it to the network
# as a fork of the target project
ForkNetworkMember.create!(fork_network: @project.fork_network,
project: source_project,
forked_from_project: @project)
end
end
end
- pipeline = local_assigns.fetch(:pipeline) { project.latest_successful_pipeline_for(ref) }
- if !project.empty_repo? && can?(current_user, :download_code, project)
- archive_prefix = "#{project.path}-#{ref.tr('/', '-')}"
.project-action-button.dropdown.inline>
%button.btn.has-tooltip{ title: s_('DownloadSource|Download'), 'data-toggle' => 'dropdown', 'aria-label' => s_('DownloadSource|Download') }
= sprite_icon('download')
......@@ -10,16 +11,16 @@
%li.dropdown-header
#{ _('Source code') }
%li
= link_to archive_project_repository_path(project, ref: ref, format: 'zip'), rel: 'nofollow', download: '' do
= link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'zip'), rel: 'nofollow', download: '' do
%span= _('Download zip')
%li
= link_to archive_project_repository_path(project, ref: ref, format: 'tar.gz'), rel: 'nofollow', download: '' do
= link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.gz'), rel: 'nofollow', download: '' do
%span= _('Download tar.gz')
%li
= link_to archive_project_repository_path(project, ref: ref, format: 'tar.bz2'), rel: 'nofollow', download: '' do
= link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar.bz2'), rel: 'nofollow', download: '' do
%span= _('Download tar.bz2')
%li
= link_to archive_project_repository_path(project, ref: ref, format: 'tar'), rel: 'nofollow', download: '' do
= link_to project_archive_path(project, id: tree_join(ref, archive_prefix), format: 'tar'), rel: 'nofollow', download: '' do
%span= _('Download tar')
- if pipeline && pipeline.latest_builds_with_artifacts.any?
......
......@@ -26,7 +26,10 @@
.commit-detail.flex-list
.commit-content
= link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
- if view_details && merge_request
= link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title"
- else
= link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
%span.commit-row-message.visible-xs-inline
&middot;
= commit.short_id
......@@ -59,9 +62,9 @@
= render_commit_status(commit, ref: ref)
.js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } }
= link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link"
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
= link_to_browse_code(project, commit)
- if view_details && merge_request
= link_to "View details", project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "btn btn-default"
.commit-sha-group
.label.label-monospace
= commit.short_id
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"), class: "btn btn-default", container: "body")
= link_to_browse_code(project, commit)
......@@ -10,7 +10,7 @@
= hidden_field(resource_name, field, value: value)
= hidden_field_tag(:spam_log_id, spammable.spam_log.id)
= hidden_field_tag(:recaptcha_verification, true)
= recaptcha_tags script: script, callback: 'recaptchaDialogCallback'
= recaptcha_tags script: script, callback: 'recaptchaDialogCallback' unless Rails.env.test?
-# Yields a block with given extra params.
= yield
......
---
title: Improved visual styles and consistency for commit hash and possible actions
across commit lists
merge_request: 17406
author:
type: changed
---
title: Extend API for importing a project export with overwrite support
merge_request: 17883
author:
type: added
---
title: Add alternate archive route for simplified packaging
merge_request: 17225
author:
type: added
---
title: Remove support for legacy tar.gz pages artifacts
merge_request: 18090
author:
type: deprecated
......@@ -287,6 +287,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
scope '-' do
get 'archive/*id', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+?/ }, to: 'repositories#archive', as: 'archive'
resources :jobs, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
post :cancel_all
......
......@@ -2,10 +2,11 @@
resource :repository, only: [:create] do
member do
get ':ref/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, ref: /.+/ }, action: 'archive', as: 'archive'
# deprecated since GitLab 9.5
get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative'
get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative', defaults: { append_sha: true }
# deprecated since GitLab 10.7
get ':id/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, id: /.+/ }, action: 'archive', as: 'archive_deprecated', defaults: { append_sha: true }
end
end
......
......@@ -111,6 +111,7 @@ POST /projects/import
| `namespace` | integer/string | no | The ID or path of the namespace that the project will be imported to. Defaults to the current user's namespace |
| `file` | string | yes | The file to be uploaded |
| `path` | string | yes | Name and path for new project |
| `overwrite` | boolean | no | If there is a project with the same path the import will overwrite it. Default to false |
| `override_params` | Hash | no | Supports all fields defined in the [Project API](projects.md)] |
The override params passed will take precendence over all values defined inside the export file.
......
......@@ -507,8 +507,8 @@ module API
header(*Gitlab::Workhorse.send_git_blob(repository, blob))
end
def send_git_archive(repository, ref:, format:)
header(*Gitlab::Workhorse.send_git_archive(repository, ref: ref, format: format))
def send_git_archive(repository, **kwargs)
header(*Gitlab::Workhorse.send_git_archive(repository, **kwargs))
end
def send_artifacts_entry(build, entry)
......
......@@ -26,6 +26,7 @@ module API
requires :path, type: String, desc: 'The new project path and name'
requires :file, type: File, desc: 'The project export file to be imported'
optional :namespace, type: String, desc: "The ID or name of the namespace that the project will be imported into. Defaults to the current user's namespace."
optional :overwrite, type: Boolean, default: false, desc: 'If there is a project in the same namespace and with the same name overwrite it'
optional :override_params,
type: Hash,
desc: 'New project params to override values in the export' do
......@@ -50,7 +51,8 @@ module API
project_params = {
path: import_params[:path],
namespace_id: namespace.id,
file: import_params[:file]['tempfile']
file: import_params[:file]['tempfile'],
overwrite: import_params[:overwrite]
}
override_params = import_params.delete(:override_params)
......
......@@ -88,7 +88,7 @@ module API
end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
begin
send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true
rescue
not_found!('File')
end
......
......@@ -75,7 +75,7 @@ module API
end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
begin
send_git_archive user_project.repository, ref: params[:sha], format: params[:format]
send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true
rescue
not_found!('File')
end
......
......@@ -396,17 +396,24 @@ module Gitlab
nil
end
def archive_prefix(ref, sha)
def archive_prefix(ref, sha, append_sha:)
append_sha = (ref != sha) if append_sha.nil?
project_name = self.name.chomp('.git')
"#{project_name}-#{ref.tr('/', '-')}-#{sha}"
formatted_ref = ref.tr('/', '-')
prefix_segments = [project_name, formatted_ref]
prefix_segments << sha if append_sha
prefix_segments.join('-')
end
def archive_metadata(ref, storage_path, format = "tar.gz")
def archive_metadata(ref, storage_path, format = "tar.gz", append_sha:)
ref ||= root_ref
commit = Gitlab::Git::Commit.find(self, ref)
return {} if commit.nil?
prefix = archive_prefix(ref, commit.id)
prefix = archive_prefix(ref, commit.id, append_sha: append_sha)
{
'RepoPath' => path,
......
module Gitlab
module ImportExport
class Importer
include Gitlab::Allowable
include Gitlab::Utils::StrongMemoize
def self.imports_repository?
true
end
......@@ -13,12 +16,14 @@ module Gitlab
end
def execute
if import_file && check_version! && restorers.all?(&:restore)
if import_file && check_version! && restorers.all?(&:restore) && overwrite_project
project_tree.restored_project
else
raise Projects::ImportService::Error.new(@shared.errors.join(', '))
end
rescue => e
raise Projects::ImportService::Error.new(e.message)
ensure
remove_import_file
end
......@@ -26,7 +31,7 @@ module Gitlab
def restorers
[repo_restorer, wiki_restorer, project_tree, avatar_restorer,
uploads_restorer, lfs_restorer]
uploads_restorer, lfs_restorer, statistics_restorer]
end
def import_file
......@@ -69,6 +74,10 @@ module Gitlab
Gitlab::ImportExport::LfsRestorer.new(project: project_tree.restored_project, shared: @shared)
end
def statistics_restorer
Gitlab::ImportExport::StatisticsRestorer.new(project: project_tree.restored_project, shared: @shared)
end
def path_with_namespace
File.join(@project.namespace.full_path, @project.path)
end
......@@ -84,6 +93,33 @@ module Gitlab
def remove_import_file
FileUtils.rm_rf(@archive_file)
end
def overwrite_project
project = project_tree.restored_project
return unless can?(@current_user, :admin_namespace, project.namespace)
if overwrite_project?
::Projects::OverwriteProjectService.new(project, @current_user)
.execute(project_to_overwrite)
end
true
end
def original_path
@project.import_data&.data&.fetch('original_path', nil)
end
def overwrite_project?
original_path.present? && project_to_overwrite.present?
end
def project_to_overwrite
strong_memoize(:project_to_overwrite) do
Project.find_by_full_path("#{@project.namespace.full_path}/#{original_path}")
end
end
end
end
end
......@@ -92,7 +92,7 @@ module Gitlab
end
def override_params
return {} unless params = @project.import_data&.data&.fetch('override_params')
return {} unless params = @project.import_data&.data&.fetch('override_params', nil)
@override_params ||= params.select do |key, _value|
Project.column_names.include?(key.to_s) &&
......
module Gitlab
module ImportExport
class StatisticsRestorer
def initialize(project:, shared:)
@project = project
@shared = shared
end
def restore
@project.statistics.refresh!
rescue => e
@shared.error(e)
false
end
end
end
end
......@@ -59,10 +59,10 @@ module Gitlab
]
end
def send_git_archive(repository, ref:, format:)
def send_git_archive(repository, ref:, format:, append_sha:)
format ||= 'tar.gz'
format.downcase!
params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format)
params = repository.archive_metadata(ref, Gitlab.config.gitlab.repository_downloads_path, format, append_sha: append_sha)
raise "Repository or ref not found" if params.empty?
if Gitlab::GitalyClient.feature_enabled?(:workhorse_archive, status: Gitlab::GitalyClient::MigrationStatus::OPT_OUT)
......
......@@ -6,7 +6,7 @@ describe Projects::RepositoriesController do
describe "GET archive" do
context 'as a guest' do
it 'responds with redirect in correct format' do
get :archive, namespace_id: project.namespace, project_id: project, format: "zip", ref: 'master'
get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip"
expect(response.header["Content-Type"]).to start_with('text/html')
expect(response).to be_redirect
......@@ -22,18 +22,25 @@ describe Projects::RepositoriesController do
end
it "uses Gitlab::Workhorse" do
get :archive, namespace_id: project.namespace, project_id: project, ref: "master", format: "zip"
get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip"
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
end
it 'responds with redirect to the short name archive if fully qualified' do
get :archive, namespace_id: project.namespace, project_id: project, id: "master/#{project.path}-master", format: "zip"
expect(assigns(:ref)).to eq("master")
expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("git-archive:")
end
context "when the service raises an error" do
before do
allow(Gitlab::Workhorse).to receive(:send_git_archive).and_raise("Archive failed")
end
it "renders Not Found" do
get :archive, namespace_id: project.namespace, project_id: project, ref: "master", format: "zip"
get :archive, namespace_id: project.namespace, project_id: project, id: "master", format: "zip"
expect(response).to have_gitlab_http_status(404)
end
......
FactoryBot.define do
factory :users_star_project do
project
user
end
end
......@@ -34,9 +34,6 @@ describe 'New issue', :js do
click_button 'Submit issue'
# reCAPTCHA alerts when it can't contact the server, so just accept it and move on
page.driver.browser.switch_to.alert.accept
# it is impossible to test recaptcha automatically and there is no possibility to fill in recaptcha
# recaptcha verification is skipped in test environment and it always returns true
expect(page).not_to have_content('issue title')
......
......@@ -247,38 +247,44 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
it 'returns parameterised string for a ref containing slashes' do
prefix = repository.archive_prefix('test/branch', 'SHA')
prefix = repository.archive_prefix('test/branch', 'SHA', append_sha: nil)
expect(prefix).to eq("#{project_name}-test-branch-SHA")
end
it 'returns correct string for a ref containing dots' do
prefix = repository.archive_prefix('test.branch', 'SHA')
prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: nil)
expect(prefix).to eq("#{project_name}-test.branch-SHA")
end
it 'returns string with sha when append_sha is false' do
prefix = repository.archive_prefix('test.branch', 'SHA', append_sha: false)
expect(prefix).to eq("#{project_name}-test.branch")
end
end
describe '#archive' do
let(:metadata) { repository.archive_metadata('master', '/tmp') }
let(:metadata) { repository.archive_metadata('master', '/tmp', append_sha: true) }
it_should_behave_like 'archive check', '.tar.gz'
end
describe '#archive_zip' do
let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip') }
let(:metadata) { repository.archive_metadata('master', '/tmp', 'zip', append_sha: true) }
it_should_behave_like 'archive check', '.zip'
end
describe '#archive_bz2' do
let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2') }
let(:metadata) { repository.archive_metadata('master', '/tmp', 'tbz2', append_sha: true) }
it_should_behave_like 'archive check', '.tar.bz2'
end
describe '#archive_fallback' do
let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup') }
let(:metadata) { repository.archive_metadata('master', '/tmp', 'madeup', append_sha: true) }
it_should_behave_like 'archive check', '.tar.gz'
end
......
require 'spec_helper'
describe Gitlab::ImportExport::Importer do
let(:user) { create(:user) }
let(:test_path) { "#{Dir.tmpdir}/importer_spec" }
let(:shared) { project.import_export_shared }
let(:project) { create(:project, import_source: File.join(test_path, 'exported-project.gz')) }
......@@ -11,6 +12,7 @@ describe Gitlab::ImportExport::Importer do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(test_path)
FileUtils.mkdir_p(shared.export_path)
FileUtils.cp(Rails.root.join('spec', 'fixtures', 'exported-project.gz'), test_path)
allow(subject).to receive(:remove_import_file)
end
after do
......@@ -42,7 +44,8 @@ describe Gitlab::ImportExport::Importer do
Gitlab::ImportExport::RepoRestorer,
Gitlab::ImportExport::WikiRestorer,
Gitlab::ImportExport::UploadsRestorer,
Gitlab::ImportExport::LfsRestorer
Gitlab::ImportExport::LfsRestorer,
Gitlab::ImportExport::StatisticsRestorer
].each do |restorer|
it "calls the #{restorer}" do
fake_restorer = double(restorer.to_s)
......@@ -60,5 +63,42 @@ describe Gitlab::ImportExport::Importer do
importer.execute
end
end
context 'when project successfully restored' do
let!(:existing_project) { create(:project, namespace: user.namespace) }
let(:project) { create(:project, namespace: user.namespace, name: 'whatever', path: 'whatever') }
before do
restorers = double
allow(subject).to receive(:import_file).and_return(true)
allow(subject).to receive(:check_version!).and_return(true)
allow(subject).to receive(:restorers).and_return(restorers)
allow(restorers).to receive(:all?).and_return(true)
allow(project).to receive(:import_data).and_return(double(data: { 'original_path' => existing_project.path }))
end
context 'when import_data' do
context 'has original_path' do
it 'overwrites existing project' do
expect_any_instance_of(::Projects::OverwriteProjectService).to receive(:execute).with(existing_project)
subject.execute
end
end
context 'has not original_path' do
before do
allow(project).to receive(:import_data).and_return(double(data: {}))
end
it 'does not call the overwrite service' do
expect_any_instance_of(::Projects::OverwriteProjectService).not_to receive(:execute).with(existing_project)
subject.execute
end
end
end
end
end
end
......@@ -16,7 +16,7 @@ describe Gitlab::Workhorse do
let(:ref) { 'master' }
let(:format) { 'zip' }
let(:storage_path) { Gitlab.config.gitlab.repository_downloads_path }
let(:base_params) { repository.archive_metadata(ref, storage_path, format) }
let(:base_params) { repository.archive_metadata(ref, storage_path, format, append_sha: nil) }
let(:gitaly_params) do
base_params.merge(
'GitalyServer' => {
......@@ -29,7 +29,7 @@ describe Gitlab::Workhorse do
let(:cache_disabled) { false }
subject do
described_class.send_git_archive(repository, ref: ref, format: format)
described_class.send_git_archive(repository, ref: ref, format: format, append_sha: nil)
end
before do
......
......@@ -375,7 +375,8 @@ describe Environment do
context 'when there is a deployment platform for environment' do
let!(:cluster) do
create(:cluster, :provided_by_gcp, projects: [project])
create(:cluster, :provided_by_gcp,
environment_scope: '*', projects: [project])
end
it 'finds a deployment platform' do
......
......@@ -114,6 +114,29 @@ describe API::ProjectImport do
expect(import_project.description).to eq('Hello world')
end
context 'when target path already exists in namespace' do
let(:existing_project) { create(:project, namespace: user.namespace) }
it 'does not schedule an import' do
expect_any_instance_of(Project).not_to receive(:import_schedule)
post api('/projects/import', user), path: existing_project.path, file: fixture_file_upload(file)
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']).to eq('Name has already been taken')
end
context 'when param overwrite is true' do
it 'schedules an import' do
stub_import(user.namespace)
post api('/projects/import', user), path: existing_project.path, file: fixture_file_upload(file), overwrite: true
expect(response).to have_gitlab_http_status(201)
end
end
end
def stub_import(namespace)
expect_any_instance_of(Project).to receive(:import_schedule)
expect(::Projects::CreateService).to receive(:new).with(user, hash_including(namespace_id: namespace.id)).and_call_original
......
......@@ -164,20 +164,36 @@ describe 'project routing' do
# archive_project_repository GET /:project_id/repository/archive(.:format) projects/repositories#archive
# edit_project_repository GET /:project_id/repository/edit(.:format) projects/repositories#edit
describe Projects::RepositoriesController, 'routing' do
it 'to #archive' do
expect(get('/gitlab/gitlabhq/repository/master/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', ref: 'master')
end
it 'to #archive format:zip' do
expect(get('/gitlab/gitlabhq/repository/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', ref: 'master')
expect(get('/gitlab/gitlabhq/-/archive/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', id: 'master/archive')
end
it 'to #archive format:tar.bz2' do
expect(get('/gitlab/gitlabhq/repository/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', ref: 'master')
expect(get('/gitlab/gitlabhq/-/archive/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', id: 'master/archive')
end
it 'to #archive with "/" in route' do
expect(get('/gitlab/gitlabhq/repository/improve/awesome/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', ref: 'improve/awesome')
expect(get('/gitlab/gitlabhq/-/archive/improve/awesome/gitlabhq-improve-awesome.tar.gz')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.gz', id: 'improve/awesome/gitlabhq-improve-awesome')
end
it 'to #archive_alternative' do
expect(get('/gitlab/gitlabhq/repository/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', append_sha: true)
end
it 'to #archive_deprecated' do
expect(get('/gitlab/gitlabhq/repository/master/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', append_sha: true)
end
it 'to #archive_deprecated format:zip' do
expect(get('/gitlab/gitlabhq/repository/master/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip', id: 'master', append_sha: true)
end
it 'to #archive_deprecated format:tar.bz2' do
expect(get('/gitlab/gitlabhq/repository/master/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2', id: 'master', append_sha: true)
end
it 'to #archive_deprecated with "/" in route' do
expect(get('/gitlab/gitlabhq/repository/improve/awesome/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'improve/awesome', append_sha: true)
end
end
......
......@@ -261,6 +261,28 @@ describe Projects::DestroyService do
end
end
context '#attempt_restore_repositories' do
let(:path) { project.disk_path + '.git' }
before do
expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_truthy
expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey
# Dont run sidekiq to check if renamed repository exists
Sidekiq::Testing.fake! { destroy_project(project, user, {}) }
expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_falsey
expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_truthy
end
it 'restores the repositories' do
Sidekiq::Testing.fake! { described_class.new(project, user).attempt_repositories_rollback }
expect(project.gitlab_shell.exists?(project.repository_storage_path, path)).to be_truthy
expect(project.gitlab_shell.exists?(project.repository_storage_path, remove_path)).to be_falsey
end
end
def destroy_project(project, user, params = {})
if async
Projects::DestroyService.new(project, user, params).async_execute
......
......@@ -4,7 +4,8 @@ describe Projects::GitlabProjectsImportService do
set(:namespace) { create(:namespace) }
let(:path) { 'test-path' }
let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') }
let(:import_params) { { namespace_id: namespace.id, path: path, file: file } }
let(:overwrite) { false }
let(:import_params) { { namespace_id: namespace.id, path: path, file: file, overwrite: overwrite } }
subject { described_class.new(namespace.owner, import_params) }
describe '#execute' do
......@@ -37,5 +38,28 @@ describe Projects::GitlabProjectsImportService do
expect(project.import_data.data['override_params']['description']).to eq('Hello')
end
end
context 'when there is a project with the same path' do
let(:existing_project) { create(:project, namespace: namespace) }
let(:path) { existing_project.path}
it 'does not create the project' do
project = subject.execute
expect(project).to be_invalid
expect(project).not_to be_persisted
end
context 'when overwrite param is set' do
let(:overwrite) { true }
it 'creates a project in a temporary full_path' do
project = subject.execute
expect(project).to be_valid
expect(project).to be_persisted
end
end
end
end
end
require 'spec_helper'
describe Projects::MoveAccessService do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project_with_access) { create(:project, namespace: user.namespace) }
let(:master_user) { create(:user) }
let(:reporter_user) { create(:user) }
let(:developer_user) { create(:user) }
let(:master_group) { create(:group) }
let(:reporter_group) { create(:group) }
let(:developer_group) { create(:group) }
before do
project_with_access.add_master(master_user)
project_with_access.add_developer(developer_user)
project_with_access.add_reporter(reporter_user)
project_with_access.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
project_with_access.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
project_with_access.project_group_links.create(group: reporter_group, group_access: Gitlab::Access::REPORTER)
end
subject { described_class.new(target_project, user) }
describe '#execute' do
shared_examples 'move the accesses' do
it do
expect(project_with_access.project_members.count).to eq 4
expect(project_with_access.project_group_links.count).to eq 3
expect(project_with_access.authorized_users.count).to eq 4
subject.execute(project_with_access)
expect(project_with_access.project_members.count).to eq 0
expect(project_with_access.project_group_links.count).to eq 0
expect(project_with_access.authorized_users.count).to eq 1
expect(target_project.project_members.count).to eq 4
expect(target_project.project_group_links.count).to eq 3
expect(target_project.authorized_users.count).to eq 4
end
it 'rollbacks if an exception is raised' do
allow(subject).to receive(:success).and_raise(StandardError)
expect { subject.execute(project_with_groups) }.to raise_error(StandardError)
expect(project_with_access.project_members.count).to eq 4
expect(project_with_access.project_group_links.count).to eq 3
expect(project_with_access.authorized_users.count).to eq 4
end
end
context 'when both projects are in the same namespace' do
let(:target_project) { create(:project, namespace: user.namespace) }
it 'does not refresh project owner authorized projects' do
allow(project_with_access).to receive(:namespace).and_return(user.namespace)
expect(project_with_access.namespace).not_to receive(:refresh_project_authorizations)
expect(target_project.namespace).not_to receive(:refresh_project_authorizations)
subject.execute(project_with_access)
end
it_behaves_like 'move the accesses'
end
context 'when projects are in different namespaces' do
let(:target_project) { create(:project, namespace: group) }
before do
group.add_owner(user)
end
it 'refreshes both project owner authorized projects' do
allow(project_with_access).to receive(:namespace).and_return(user.namespace)
expect(user.namespace).to receive(:refresh_project_authorizations).once
expect(group).to receive(:refresh_project_authorizations).once
subject.execute(project_with_access)
end
it_behaves_like 'move the accesses'
end
context 'when remove_remaining_elements is false' do
let(:target_project) { create(:project, namespace: user.namespace) }
let(:options) { { remove_remaining_elements: false } }
it 'does not remove remaining memberships' do
target_project.add_master(master_user)
subject.execute(project_with_access, options)
expect(project_with_access.project_members.count).not_to eq 0
end
it 'does not remove remaining group links' do
target_project.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
subject.execute(project_with_access, options)
expect(project_with_access.project_group_links.count).not_to eq 0
end
it 'does not remove remaining authorizations' do
target_project.add_developer(developer_user)
subject.execute(project_with_access, options)
expect(project_with_access.project_authorizations.count).not_to eq 0
end
end
end
end
require 'spec_helper'
describe Projects::MoveDeployKeysProjectsService do
let!(:user) { create(:user) }
let!(:project_with_deploy_keys) { create(:project, namespace: user.namespace) }
let!(:target_project) { create(:project, namespace: user.namespace) }
subject { described_class.new(target_project, user) }
describe '#execute' do
before do
create_list(:deploy_keys_project, 2, project: project_with_deploy_keys)
end
it 'moves the user\'s deploy keys from one project to another' do
expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 2
expect(target_project.deploy_keys_projects.count).to eq 0
subject.execute(project_with_deploy_keys)
expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 0
expect(target_project.deploy_keys_projects.count).to eq 2
end
it 'does not link existent deploy_keys in the current project' do
target_project.deploy_keys << project_with_deploy_keys.deploy_keys.first
expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 2
expect(target_project.deploy_keys_projects.count).to eq 1
subject.execute(project_with_deploy_keys)
expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 0
expect(target_project.deploy_keys_projects.count).to eq 2
end
it 'rollbacks changes if transaction fails' do
allow(subject).to receive(:success).and_raise(StandardError)
expect { subject.execute(project_with_deploy_keys) }.to raise_error(StandardError)
expect(project_with_deploy_keys.deploy_keys_projects.count).to eq 2
expect(target_project.deploy_keys_projects.count).to eq 0
end
context 'when remove_remaining_elements is false' do
let(:options) { { remove_remaining_elements: false } }
it 'does not remove remaining deploy keys projects' do
target_project.deploy_keys << project_with_deploy_keys.deploy_keys.first
subject.execute(project_with_deploy_keys, options)
expect(project_with_deploy_keys.deploy_keys_projects.count).not_to eq 0
end
end
end
end
require 'spec_helper'
describe Projects::MoveForksService do
include ProjectForksHelper
let!(:user) { create(:user) }
let!(:project_with_forks) { create(:project, namespace: user.namespace) }
let!(:target_project) { create(:project, namespace: user.namespace) }
let!(:lvl1_forked_project_1) { fork_project(project_with_forks, user) }
let!(:lvl1_forked_project_2) { fork_project(project_with_forks, user) }
let!(:lvl2_forked_project_1_1) { fork_project(lvl1_forked_project_1, user) }
let!(:lvl2_forked_project_1_2) { fork_project(lvl1_forked_project_1, user) }
subject { described_class.new(target_project, user) }
describe '#execute' do
context 'when moving a root forked project' do
it 'moves the descendant forks' do
expect(project_with_forks.forks.count).to eq 2
expect(target_project.forks.count).to eq 0
subject.execute(project_with_forks)
expect(project_with_forks.forks.count).to eq 0
expect(target_project.forks.count).to eq 2
expect(lvl1_forked_project_1.forked_from_project).to eq target_project
expect(lvl1_forked_project_1.fork_network_member.forked_from_project).to eq target_project
expect(lvl1_forked_project_2.forked_from_project).to eq target_project
expect(lvl1_forked_project_2.fork_network_member.forked_from_project).to eq target_project
end
it 'updates the fork network' do
expect(project_with_forks.fork_network.root_project).to eq project_with_forks
expect(project_with_forks.fork_network.fork_network_members.map(&:project)).to include project_with_forks
subject.execute(project_with_forks)
expect(target_project.reload.fork_network.root_project).to eq target_project
expect(target_project.fork_network.fork_network_members.map(&:project)).not_to include project_with_forks
end
end
context 'when moving a intermediate forked project' do
it 'moves the descendant forks' do
expect(lvl1_forked_project_1.forks.count).to eq 2
expect(target_project.forks.count).to eq 0
subject.execute(lvl1_forked_project_1)
expect(lvl1_forked_project_1.forks.count).to eq 0
expect(target_project.forks.count).to eq 2
expect(lvl2_forked_project_1_1.forked_from_project).to eq target_project
expect(lvl2_forked_project_1_1.fork_network_member.forked_from_project).to eq target_project
expect(lvl2_forked_project_1_2.forked_from_project).to eq target_project
expect(lvl2_forked_project_1_2.fork_network_member.forked_from_project).to eq target_project
end
it 'moves the ascendant fork' do
subject.execute(lvl1_forked_project_1)
expect(target_project.forked_from_project).to eq project_with_forks
expect(target_project.fork_network_member.forked_from_project).to eq project_with_forks
end
it 'does not update fork network' do
subject.execute(lvl1_forked_project_1)
expect(target_project.reload.fork_network.root_project).to eq project_with_forks
end
end
context 'when moving a leaf forked project' do
it 'moves the ascendant fork' do
subject.execute(lvl2_forked_project_1_1)
expect(target_project.forked_from_project).to eq lvl1_forked_project_1
expect(target_project.fork_network_member.forked_from_project).to eq lvl1_forked_project_1
end
it 'does not update fork network' do
subject.execute(lvl2_forked_project_1_1)
expect(target_project.reload.fork_network.root_project).to eq project_with_forks
end
end
it 'rollbacks changes if transaction fails' do
allow(subject).to receive(:success).and_raise(StandardError)
expect { subject.execute(project_with_forks) }.to raise_error(StandardError)
expect(project_with_forks.forks.count).to eq 2
expect(target_project.forks.count).to eq 0
end
end
end
require 'spec_helper'
describe Projects::MoveLfsObjectsProjectsService do
let!(:user) { create(:user) }
let!(:project_with_lfs_objects) { create(:project, namespace: user.namespace) }
let!(:target_project) { create(:project, namespace: user.namespace) }
subject { described_class.new(target_project, user) }
before do
create_list(:lfs_objects_project, 3, project: project_with_lfs_objects)
end
describe '#execute' do
it 'links the lfs objects from existent in source project' do
expect(target_project.lfs_objects.count).to eq 0
subject.execute(project_with_lfs_objects)
expect(project_with_lfs_objects.reload.lfs_objects.count).to eq 0
expect(target_project.reload.lfs_objects.count).to eq 3
end
it 'does not link existent lfs_object in the current project' do
target_project.lfs_objects << project_with_lfs_objects.lfs_objects.first(2)
expect(target_project.lfs_objects.count).to eq 2
subject.execute(project_with_lfs_objects)
expect(target_project.lfs_objects.count).to eq 3
end
it 'rollbacks changes if transaction fails' do
allow(subject).to receive(:success).and_raise(StandardError)
expect { subject.execute(project_with_lfs_objects) }.to raise_error(StandardError)
expect(project_with_lfs_objects.lfs_objects.count).to eq 3
expect(target_project.lfs_objects.count).to eq 0
end
context 'when remove_remaining_elements is false' do
let(:options) { { remove_remaining_elements: false } }
it 'does not remove remaining lfs objects' do
target_project.lfs_objects << project_with_lfs_objects.lfs_objects.first(2)
subject.execute(project_with_lfs_objects, options)
expect(project_with_lfs_objects.lfs_objects.count).not_to eq 0
end
end
end
end
require 'spec_helper'
describe Projects::MoveNotificationSettingsService do
let(:user) { create(:user) }
let(:project_with_notifications) { create(:project, namespace: user.namespace) }
let(:target_project) { create(:project, namespace: user.namespace) }
subject { described_class.new(target_project, user) }
describe '#execute' do
context 'with notification settings' do
before do
create_list(:notification_setting, 2, source: project_with_notifications)
end
it 'moves the user\'s notification settings from one project to another' do
expect(project_with_notifications.notification_settings.count).to eq 3
expect(target_project.notification_settings.count).to eq 1
subject.execute(project_with_notifications)
expect(project_with_notifications.notification_settings.count).to eq 0
expect(target_project.notification_settings.count).to eq 3
end
it 'rollbacks changes if transaction fails' do
allow(subject).to receive(:success).and_raise(StandardError)
expect { subject.execute(project_with_notifications) }.to raise_error(StandardError)
expect(project_with_notifications.notification_settings.count).to eq 3
expect(target_project.notification_settings.count).to eq 1
end
end
it 'does not move existent notification settings in the current project' do
expect(project_with_notifications.notification_settings.count).to eq 1
expect(target_project.notification_settings.count).to eq 1
expect(user.notification_settings.count).to eq 2
subject.execute(project_with_notifications)
expect(user.notification_settings.count).to eq 1
end
context 'when remove_remaining_elements is false' do
let(:options) { { remove_remaining_elements: false } }
it 'does not remove remaining notification settings' do
subject.execute(project_with_notifications, options)
expect(project_with_notifications.notification_settings.count).not_to eq 0
end
end
end
end
require 'spec_helper'
describe Projects::MoveProjectAuthorizationsService do
let!(:user) { create(:user) }
let(:project_with_users) { create(:project, namespace: user.namespace) }
let(:target_project) { create(:project, namespace: user.namespace) }
let(:master_user) { create(:user) }
let(:reporter_user) { create(:user) }
let(:developer_user) { create(:user) }
subject { described_class.new(target_project, user) }
describe '#execute' do
before do
project_with_users.add_master(master_user)
project_with_users.add_developer(developer_user)
project_with_users.add_reporter(reporter_user)
end
it 'moves the authorizations from one project to another' do
expect(project_with_users.authorized_users.count).to eq 4
expect(target_project.authorized_users.count).to eq 1
subject.execute(project_with_users)
expect(project_with_users.authorized_users.count).to eq 0
expect(target_project.authorized_users.count).to eq 4
end
it 'does not move existent authorizations to the current project' do
target_project.add_master(developer_user)
target_project.add_developer(reporter_user)
expect(project_with_users.authorized_users.count).to eq 4
expect(target_project.authorized_users.count).to eq 3
subject.execute(project_with_users)
expect(project_with_users.authorized_users.count).to eq 0
expect(target_project.authorized_users.count).to eq 4
end
context 'when remove_remaining_elements is false' do
let(:options) { { remove_remaining_elements: false } }
it 'does not remove remaining project authorizations' do
target_project.add_master(developer_user)
target_project.add_developer(reporter_user)
subject.execute(project_with_users, options)
expect(project_with_users.project_authorizations.count).not_to eq 0
end
end
end
end
require 'spec_helper'
describe Projects::MoveProjectGroupLinksService do
let!(:user) { create(:user) }
let(:project_with_groups) { create(:project, namespace: user.namespace) }
let(:target_project) { create(:project, namespace: user.namespace) }
let(:master_group) { create(:group) }
let(:reporter_group) { create(:group) }
let(:developer_group) { create(:group) }
subject { described_class.new(target_project, user) }
describe '#execute' do
before do
project_with_groups.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
project_with_groups.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
project_with_groups.project_group_links.create(group: reporter_group, group_access: Gitlab::Access::REPORTER)
end
it 'moves the group links from one project to another' do
expect(project_with_groups.project_group_links.count).to eq 3
expect(target_project.project_group_links.count).to eq 0
subject.execute(project_with_groups)
expect(project_with_groups.project_group_links.count).to eq 0
expect(target_project.project_group_links.count).to eq 3
end
it 'does not move existent group links in the current project' do
target_project.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
target_project.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
expect(project_with_groups.project_group_links.count).to eq 3
expect(target_project.project_group_links.count).to eq 2
subject.execute(project_with_groups)
expect(project_with_groups.project_group_links.count).to eq 0
expect(target_project.project_group_links.count).to eq 3
end
it 'rollbacks changes if transaction fails' do
allow(subject).to receive(:success).and_raise(StandardError)
expect { subject.execute(project_with_groups) }.to raise_error(StandardError)
expect(project_with_groups.project_group_links.count).to eq 3
expect(target_project.project_group_links.count).to eq 0
end
context 'when remove_remaining_elements is false' do
let(:options) { { remove_remaining_elements: false } }
it 'does not remove remaining project group links' do
target_project.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
target_project.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
subject.execute(project_with_groups, options)
expect(project_with_groups.project_group_links.count).not_to eq 0
end
end
end
end
require 'spec_helper'
describe Projects::MoveProjectMembersService do
let!(:user) { create(:user) }
let(:project_with_users) { create(:project, namespace: user.namespace) }
let(:target_project) { create(:project, namespace: user.namespace) }
let(:master_user) { create(:user) }
let(:reporter_user) { create(:user) }
let(:developer_user) { create(:user) }
subject { described_class.new(target_project, user) }
describe '#execute' do
before do
project_with_users.add_master(master_user)
project_with_users.add_developer(developer_user)
project_with_users.add_reporter(reporter_user)
end
it 'moves the members from one project to another' do
expect(project_with_users.project_members.count).to eq 4
expect(target_project.project_members.count).to eq 1
subject.execute(project_with_users)
expect(project_with_users.project_members.count).to eq 0
expect(target_project.project_members.count).to eq 4
end
it 'does not move existent members to the current project' do
target_project.add_master(developer_user)
target_project.add_developer(reporter_user)
expect(project_with_users.project_members.count).to eq 4
expect(target_project.project_members.count).to eq 3
subject.execute(project_with_users)
expect(project_with_users.project_members.count).to eq 0
expect(target_project.project_members.count).to eq 4
end
it 'rollbacks changes if transaction fails' do
allow(subject).to receive(:success).and_raise(StandardError)
expect { subject.execute(project_with_users) }.to raise_error(StandardError)
expect(project_with_users.project_members.count).to eq 4
expect(target_project.project_members.count).to eq 1
end
context 'when remove_remaining_elements is false' do
let(:options) { { remove_remaining_elements: false } }
it 'does not remove remaining project members' do
target_project.add_master(developer_user)
target_project.add_developer(reporter_user)
subject.execute(project_with_users, options)
expect(project_with_users.project_members.count).not_to eq 0
end
end
end
end
require 'spec_helper'
describe Projects::MoveUsersStarProjectsService do
let!(:user) { create(:user) }
let!(:project_with_stars) { create(:project, namespace: user.namespace) }
let!(:target_project) { create(:project, namespace: user.namespace) }
subject { described_class.new(target_project, user) }
describe '#execute' do
before do
create_list(:users_star_project, 2, project: project_with_stars)
end
it 'moves the user\'s stars from one project to another' do
expect(project_with_stars.users_star_projects.count).to eq 2
expect(project_with_stars.star_count).to eq 2
expect(target_project.users_star_projects.count).to eq 0
expect(target_project.star_count).to eq 0
subject.execute(project_with_stars)
project_with_stars.reload
target_project.reload
expect(project_with_stars.users_star_projects.count).to eq 0
expect(project_with_stars.star_count).to eq 0
expect(target_project.users_star_projects.count).to eq 2
expect(target_project.star_count).to eq 2
end
it 'rollbacks changes if transaction fails' do
allow(subject).to receive(:success).and_raise(StandardError)
expect { subject.execute(project_with_stars) }.to raise_error(StandardError)
expect(project_with_stars.users_star_projects.count).to eq 2
expect(project_with_stars.star_count).to eq 2
expect(target_project.users_star_projects.count).to eq 0
expect(target_project.star_count).to eq 0
end
end
end
require 'spec_helper'
describe Projects::OverwriteProjectService do
include ProjectForksHelper
let(:user) { create(:user) }
let(:project_from) { create(:project, namespace: user.namespace) }
let(:project_to) { create(:project, namespace: user.namespace) }
let!(:lvl1_forked_project_1) { fork_project(project_from, user) }
let!(:lvl1_forked_project_2) { fork_project(project_from, user) }
let!(:lvl2_forked_project_1_1) { fork_project(lvl1_forked_project_1, user) }
let!(:lvl2_forked_project_1_2) { fork_project(lvl1_forked_project_1, user) }
subject { described_class.new(project_to, user) }
before do
allow(project_to).to receive(:import_data).and_return(double(data: { 'original_path' => project_from.path }))
end
describe '#execute' do
shared_examples 'overwrite actions' do
it 'moves deploy keys' do
deploy_keys_count = project_from.deploy_keys_projects.count
subject.execute(project_from)
expect(project_to.deploy_keys_projects.count).to eq deploy_keys_count
end
it 'moves notification settings' do
notification_count = project_from.notification_settings.count
subject.execute(project_from)
expect(project_to.notification_settings.count).to eq notification_count
end
it 'moves users stars' do
stars_count = project_from.users_star_projects.count
subject.execute(project_from)
project_to.reload
expect(project_to.users_star_projects.count).to eq stars_count
expect(project_to.star_count).to eq stars_count
end
it 'moves project group links' do
group_links_count = project_from.project_group_links.count
subject.execute(project_from)
expect(project_to.project_group_links.count).to eq group_links_count
end
it 'moves memberships and authorizations' do
members_count = project_from.project_members.count
project_authorizations = project_from.project_authorizations.count
subject.execute(project_from)
expect(project_to.project_members.count).to eq members_count
expect(project_to.project_authorizations.count).to eq project_authorizations
end
context 'moves lfs objects relationships' do
before do
create_list(:lfs_objects_project, 3, project: project_from)
end
it do
lfs_objects_count = project_from.lfs_objects.count
subject.execute(project_from)
expect(project_to.lfs_objects.count).to eq lfs_objects_count
end
end
it 'removes the original project' do
subject.execute(project_from)
expect { Project.find(project_from.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'renames the project' do
subject.execute(project_from)
expect(project_to.full_path).to eq project_from.full_path
end
end
context 'when project does not have any relation' do
it_behaves_like 'overwrite actions'
end
context 'when project with elements' do
it_behaves_like 'overwrite actions' do
let(:master_user) { create(:user) }
let(:reporter_user) { create(:user) }
let(:developer_user) { create(:user) }
let(:master_group) { create(:group) }
let(:reporter_group) { create(:group) }
let(:developer_group) { create(:group) }
before do
create_list(:deploy_keys_project, 2, project: project_from)
create_list(:notification_setting, 2, source: project_from)
create_list(:users_star_project, 2, project: project_from)
project_from.project_group_links.create(group: master_group, group_access: Gitlab::Access::MASTER)
project_from.project_group_links.create(group: developer_group, group_access: Gitlab::Access::DEVELOPER)
project_from.project_group_links.create(group: reporter_group, group_access: Gitlab::Access::REPORTER)
project_from.add_master(master_user)
project_from.add_developer(developer_user)
project_from.add_reporter(reporter_user)
end
end
end
context 'forks' do
context 'when moving a root forked project' do
it 'moves the descendant forks' do
expect(project_from.forks.count).to eq 2
expect(project_to.forks.count).to eq 0
subject.execute(project_from)
expect(project_from.forks.count).to eq 0
expect(project_to.forks.count).to eq 2
expect(lvl1_forked_project_1.forked_from_project).to eq project_to
expect(lvl1_forked_project_1.fork_network_member.forked_from_project).to eq project_to
expect(lvl1_forked_project_2.forked_from_project).to eq project_to
expect(lvl1_forked_project_2.fork_network_member.forked_from_project).to eq project_to
end
it 'updates the fork network' do
expect(project_from.fork_network.root_project).to eq project_from
expect(project_from.fork_network.fork_network_members.map(&:project)).to include project_from
subject.execute(project_from)
expect(project_to.reload.fork_network.root_project).to eq project_to
expect(project_to.fork_network.fork_network_members.map(&:project)).not_to include project_from
end
end
context 'when moving a intermediate forked project' do
let(:project_to) { create(:project, namespace: lvl1_forked_project_1.namespace) }
it 'moves the descendant forks' do
expect(lvl1_forked_project_1.forks.count).to eq 2
expect(project_to.forks.count).to eq 0
subject.execute(lvl1_forked_project_1)
expect(lvl1_forked_project_1.forks.count).to eq 0
expect(project_to.forks.count).to eq 2
expect(lvl2_forked_project_1_1.forked_from_project).to eq project_to
expect(lvl2_forked_project_1_1.fork_network_member.forked_from_project).to eq project_to
expect(lvl2_forked_project_1_2.forked_from_project).to eq project_to
expect(lvl2_forked_project_1_2.fork_network_member.forked_from_project).to eq project_to
end
it 'moves the ascendant fork' do
subject.execute(lvl1_forked_project_1)
expect(project_to.reload.forked_from_project).to eq project_from
expect(project_to.fork_network_member.forked_from_project).to eq project_from
end
it 'does not update fork network' do
subject.execute(lvl1_forked_project_1)
expect(project_to.reload.fork_network.root_project).to eq project_from
end
end
end
context 'if an exception is raised' do
it 'rollbacks changes' do
updated_at = project_from.updated_at
allow(subject).to receive(:rename_project).and_raise(StandardError)
expect { subject.execute(project_from) }.to raise_error(StandardError)
expect(Project.find(project_from.id)).not_to be_nil
expect(project_from.reload.updated_at.change(usec: 0)).to eq updated_at.change(usec: 0)
end
it 'tries to restore the original project repositories' do
allow(subject).to receive(:rename_project).and_raise(StandardError)
expect(subject).to receive(:attempt_restore_repositories).with(project_from)
expect { subject.execute(project_from) }.to raise_error(StandardError)
end
end
end
end
......@@ -28,6 +28,6 @@ describe 'projects/merge_requests/_commits.html.haml' do
commit = merge_request.commits.first # HEAD
href = diffs_project_merge_request_path(target_project, merge_request, commit_id: commit)
expect(rendered).to have_link(Commit.truncate_sha(commit.sha), href: href)
expect(rendered).to have_link(href: href)
end
end
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