Commit 35d3c6df authored by Valery Sizov's avatar Valery Sizov

Merge branch '7-13-5-stable-candidate' into '7-13-stable'

7-13-5-stable candidate



See merge request !1145
parents 7c3e8bcd f4f6d60c
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 7.13.5
- Satellites reverted
v 7.13.4 v 7.13.4
- Allow users to send abuse reports - Allow users to send abuse reports
...@@ -142,7 +146,6 @@ v 7.12.0 ...@@ -142,7 +146,6 @@ v 7.12.0
- Add SAML support as an omniauth provider - Add SAML support as an omniauth provider
- Allow to configure a URL to show after sign out - Allow to configure a URL to show after sign out
- Add an option to automatically sign-in with an Omniauth provider - Add an option to automatically sign-in with an Omniauth provider
- Better performance for web editor (switched from satellites to rugged)
- GitLab CI service sends .gitlab-ci.yml in each push call - GitLab CI service sends .gitlab-ci.yml in each push call
- When remove project - move repository and schedule it removal - When remove project - move repository and schedule it removal
- Improve group removing logic - Improve group removing logic
......
...@@ -13,20 +13,27 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -13,20 +13,27 @@ class Projects::BlobController < Projects::ApplicationController
before_action :commit, except: [:new, :create] before_action :commit, except: [:new, :create]
before_action :blob, except: [:new, :create] before_action :blob, except: [:new, :create]
before_action :from_merge_request, only: [:edit, :update] before_action :from_merge_request, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update]
before_action :editor_variables, except: [:show, :preview, :diff]
before_action :after_edit_path, only: [:edit, :update] before_action :after_edit_path, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update]
def new def new
commit unless @repository.empty? commit unless @repository.empty?
end end
def create def create
result = Files::CreateService.new(@project, current_user, @commit_params).execute file_path = File.join(@path, File.basename(params[:file_name]))
result = Files::CreateService.new(
@project,
current_user,
params.merge(new_branch: sanitized_new_branch_name),
@ref,
file_path
).execute
if result[:status] == :success if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed" flash[:notice] = "Your changes have been successfully committed"
redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) ref = sanitized_new_branch_name.presence || @ref
redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(ref, file_path))
else else
flash[:alert] = result[:message] flash[:alert] = result[:message]
render :new render :new
...@@ -41,10 +48,22 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -41,10 +48,22 @@ class Projects::BlobController < Projects::ApplicationController
end end
def update def update
result = Files::UpdateService.new(@project, current_user, @commit_params).execute result = Files::UpdateService.
new(
@project,
current_user,
params.merge(new_branch: sanitized_new_branch_name),
@ref,
@path
).execute
if result[:status] == :success if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed" flash[:notice] = "Your changes have been successfully committed"
if from_merge_request
from_merge_request.reload_code
end
redirect_to after_edit_path redirect_to after_edit_path
else else
flash[:alert] = result[:message] flash[:alert] = result[:message]
...@@ -61,11 +80,12 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -61,11 +80,12 @@ class Projects::BlobController < Projects::ApplicationController
end end
def destroy def destroy
result = Files::DeleteService.new(@project, current_user, @commit_params).execute result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute
if result[:status] == :success if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed" flash[:notice] = "Your changes have been successfully committed"
redirect_to namespace_project_tree_path(@project.namespace, @project, @target_branch) redirect_to namespace_project_tree_path(@project.namespace, @project,
@ref)
else else
flash[:alert] = result[:message] flash[:alert] = result[:message]
render :show render :show
...@@ -115,6 +135,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -115,6 +135,7 @@ class Projects::BlobController < Projects::ApplicationController
@id = params[:id] @id = params[:id]
@ref, @path = extract_ref(@id) @ref, @path = extract_ref(@id)
rescue InvalidPathError rescue InvalidPathError
not_found! not_found!
end end
...@@ -124,8 +145,8 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -124,8 +145,8 @@ class Projects::BlobController < Projects::ApplicationController
if from_merge_request if from_merge_request
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}" "#file-path-#{hexdigest(@path)}"
elsif @target_branch.present? elsif sanitized_new_branch_name.present?
namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path)) namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path))
else else
namespace_project_blob_path(@project.namespace, @project, @id) namespace_project_blob_path(@project.namespace, @project, @id)
end end
...@@ -139,25 +160,4 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -139,25 +160,4 @@ class Projects::BlobController < Projects::ApplicationController
def sanitized_new_branch_name def sanitized_new_branch_name
@new_branch ||= sanitize(strip_tags(params[:new_branch])) @new_branch ||= sanitize(strip_tags(params[:new_branch]))
end end
def editor_variables
@current_branch = @ref
@target_branch = (sanitized_new_branch_name || @ref)
@file_path =
if action_name.to_s == 'create'
File.join(@path, File.basename(params[:file_name]))
else
@path
end
@commit_params = {
file_path: @file_path,
current_branch: @current_branch,
target_branch: @target_branch,
commit_message: params[:commit_message],
file_content: params[:content],
file_content_encoding: params[:encoding]
}
end
end end
...@@ -205,14 +205,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -205,14 +205,7 @@ class MergeRequest < ActiveRecord::Base
end end
def check_if_can_be_merged def check_if_can_be_merged
can_be_merged = if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
if for_fork?
Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
else
project.repository.can_be_merged?(source_branch, target_branch)
end
if can_be_merged
mark_as_mergeable mark_as_mergeable
else else
mark_as_unmergeable mark_as_unmergeable
......
...@@ -364,62 +364,6 @@ class Repository ...@@ -364,62 +364,6 @@ class Repository
@root_ref ||= raw_repository.root_ref @root_ref ||= raw_repository.root_ref
end end
def commit_file(user, path, content, message, ref)
path[0] = '' if path[0] == '/'
committer = user_to_comitter(user)
options = {}
options[:committer] = committer
options[:author] = committer
options[:commit] = {
message: message,
branch: ref
}
options[:file] = {
content: content,
path: path
}
Gitlab::Git::Blob.commit(raw_repository, options)
end
def remove_file(user, path, message, ref)
path[0] = '' if path[0] == '/'
committer = user_to_comitter(user)
options = {}
options[:committer] = committer
options[:author] = committer
options[:commit] = {
message: message,
branch: ref
}
options[:file] = {
path: path
}
Gitlab::Git::Blob.remove(raw_repository, options)
end
def user_to_comitter(user)
{
email: user.email,
name: user.name,
time: Time.now
}
end
def can_be_merged?(source_branch, target_branch)
our_commit = rugged.branches[target_branch].target
their_commit = rugged.branches[source_branch].target
if our_commit && their_commit
!rugged.merge_commits(our_commit, their_commit).conflicts?
end
end
def search_files(query, ref) def search_files(query, ref)
offset = 2 offset = 2
args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref}) args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
......
module Files module Files
class BaseService < ::BaseService class BaseService < ::BaseService
class ValidationError < StandardError; end attr_reader :ref, :path
def execute def initialize(project, user, params, ref, path = nil)
@current_branch = params[:current_branch] @project, @current_user, @params = project, user, params.dup
@target_branch = params[:target_branch] @ref = ref
@commit_message = params[:commit_message] @path = path
@file_path = params[:file_path]
@file_content = if params[:file_content_encoding] == 'base64'
Base64.decode64(params[:file_content])
else
params[:file_content]
end
# Validate parameters
validate
# Create new branch if it different from current_branch
if @target_branch != @current_branch
create_target_branch
end
if sha = commit
after_commit(sha, @target_branch)
success
else
error("Something went wrong. Your changes were not committed")
end
rescue ValidationError => ex
error(ex.message)
end end
private private
...@@ -36,52 +13,5 @@ module Files ...@@ -36,52 +13,5 @@ module Files
def repository def repository
project.repository project.repository
end end
def after_commit(sha, branch)
commit = repository.commit(sha)
full_ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch}"
old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA
GitPushService.new.execute(project, current_user, old_sha, sha, full_ref)
end
def current_branch
@current_branch ||= params[:current_branch]
end
def target_branch
@target_branch ||= params[:target_branch]
end
def raise_error(message)
raise ValidationError.new(message)
end
def validate
allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
unless allowed
raise_error("You are not allowed to push into this branch")
end
unless project.empty_repo?
unless repository.branch_names.include?(@current_branch)
raise_error("You can only create files if you are on top of a branch")
end
if @current_branch != @target_branch
if repository.branch_names.include?(@target_branch)
raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes")
end
end
end
end
def create_target_branch
result = CreateBranchService.new(project, current_user).execute(@target_branch, @current_branch)
unless result[:status] == :success
raise_error("Something went wrong when we tried to create #{@target_branch} for you")
end
end
end end
end end
require_relative "base_service" require_relative "base_service"
module Files module Files
class CreateService < Files::BaseService class CreateService < BaseService
def commit def execute
repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch) allowed = Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
end
def validate unless allowed
super return error("You are not allowed to create file in this branch")
end
file_name = File.basename(@file_path) file_name = File.basename(path)
file_path = path
unless file_name =~ Gitlab::Regex.file_name_regex unless file_name =~ Gitlab::Regex.file_name_regex
raise_error( return error(
'Your changes could not be committed, because the file name ' + 'Your changes could not be committed, because the file name ' +
Gitlab::Regex.file_name_regex_message Gitlab::Regex.file_name_regex_message
) )
end end
unless project.empty_repo? if project.empty_repo?
blob = repository.blob_at_branch(@current_branch, @file_path) # everything is ok because repo does not have a commits yet
else
unless repository.branch_names.include?(ref)
return error("You can only create files if you are on top of a branch")
end
blob = repository.blob_at_branch(ref, file_path)
if blob if blob
raise_error("Your changes could not be committed, because file with such name exists") return error("Your changes could not be committed, because file with such name exists")
end end
end end
new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path)
created_successfully = new_file_action.commit!(
params[:content],
params[:commit_message],
params[:encoding],
params[:new_branch]
)
if created_successfully
success
else
error("Your changes could not be committed, because the file has been changed")
end
end end
end end
end end
require_relative "base_service" require_relative "base_service"
module Files module Files
class DeleteService < Files::BaseService class DeleteService < BaseService
def commit def execute
repository.remove_file(current_user, @file_path, @commit_message, @target_branch) allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
unless allowed
return error("You are not allowed to push into this branch")
end
unless repository.branch_names.include?(ref)
return error("You can only create files if you are on top of a branch")
end
blob = repository.blob_at_branch(ref, path)
unless blob
return error("You can only edit text files")
end
delete_file_action = Gitlab::Satellite::DeleteFileAction.new(current_user, project, ref, path)
deleted_successfully = delete_file_action.commit!(
nil,
params[:commit_message]
)
if deleted_successfully
success
else
error("Your changes could not be committed, because the file has been changed")
end
end end
end end
end end
require_relative "base_service" require_relative "base_service"
module Files module Files
class UpdateService < Files::BaseService class UpdateService < BaseService
def commit def execute
repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch) allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
unless allowed
return error("You are not allowed to push into this branch")
end
unless repository.branch_names.include?(ref)
return error("You can only create files if you are on top of a branch")
end
blob = repository.blob_at_branch(ref, path)
unless blob
return error("You can only edit text files")
end
edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path)
edit_file_action.commit!(
params[:content],
params[:commit_message],
params[:encoding],
params[:new_branch]
)
success
rescue Gitlab::Satellite::CheckoutFailed => ex
error("Your changes could not be committed because ref '#{ref}' could not be checked out", 400)
rescue Gitlab::Satellite::CommitFailed => ex
error("Your changes could not be committed. Maybe there was nothing to commit?", 409)
rescue Gitlab::Satellite::PushFailed => ex
error("Your changes could not be committed. Maybe the file was changed by another process?", 409)
end end
end end
end end
...@@ -133,8 +133,7 @@ class GitPushService ...@@ -133,8 +133,7 @@ class GitPushService
end end
def is_default_branch?(ref) def is_default_branch?(ref)
Gitlab::Git.branch_ref?(ref) && Gitlab::Git.branch_ref?(ref) && Gitlab::Git.ref_name(ref) == project.default_branch
(Gitlab::Git.ref_name(ref) == project.default_branch || project.default_branch.nil?)
end end
def commit_user(commit) def commit_user(commit)
......
...@@ -5,20 +5,17 @@ module MergeRequests ...@@ -5,20 +5,17 @@ module MergeRequests
# mark merge request as merged and execute all hooks and notifications # mark merge request as merged and execute all hooks and notifications
# Called when you do merge via GitLab UI # Called when you do merge via GitLab UI
class AutoMergeService < BaseMergeService class AutoMergeService < BaseMergeService
attr_reader :merge_request, :commit_message
def execute(merge_request, commit_message) def execute(merge_request, commit_message)
@commit_message = commit_message
@merge_request = merge_request
merge_request.lock_mr merge_request.lock_mr
if merge! if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
merge_request.merge merge_request.merge
create_merge_event(merge_request, current_user) create_merge_event(merge_request, current_user)
create_note(merge_request) create_note(merge_request)
notification_service.merge_mr(merge_request, current_user) notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request, 'merge') execute_hooks(merge_request, 'merge')
true true
else else
merge_request.unlock_mr merge_request.unlock_mr
...@@ -29,47 +26,5 @@ module MergeRequests ...@@ -29,47 +26,5 @@ module MergeRequests
merge_request.mark_as_unmergeable merge_request.mark_as_unmergeable
false false
end end
def merge!
if merge_request.for_fork?
Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
else
# Merge local branches using rugged instead of satellites
if sha = commit
after_commit(sha, merge_request.target_branch)
if merge_request.remove_source_branch?
DeleteBranchService.new(merge_request.source_project, current_user).execute(merge_request.source_branch)
end
true
else
false
end
end
end
def commit
committer = repository.user_to_comitter(current_user)
options = {
message: commit_message,
author: committer,
committer: committer
}
repository.merge(merge_request.source_branch, merge_request.target_branch, options)
end
def after_commit(sha, branch)
commit = repository.commit(sha)
full_ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch}"
old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA
GitPushService.new.execute(project, current_user, old_sha, sha, full_ref)
end
def repository
project.repository
end
end end
end end
...@@ -12,8 +12,8 @@ ...@@ -12,8 +12,8 @@
\/ \/
= text_field_tag 'file_name', params[:file_name], placeholder: "File name", = text_field_tag 'file_name', params[:file_name], placeholder: "File name",
required: true, class: 'form-control new-file-name' required: true, class: 'form-control new-file-name'
.pull-right .pull-right
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
.file-content.code .file-content.code
%pre.js-edit-mode-pane#editor %pre.js-edit-mode-pane#editor
......
...@@ -6,12 +6,11 @@ ...@@ -6,12 +6,11 @@
= render 'shared/commit_message_container', params: params, = render 'shared/commit_message_container', params: params,
placeholder: 'Add new file' placeholder: 'Add new file'
- unless @project.empty_repo? .form-group.branch
.form-group.branch = label_tag 'branch', class: 'control-label' do
= label_tag 'branch', class: 'control-label' do Branch
Branch .col-sm-10
.col-sm-10 = text_field_tag 'new_branch', @ref, class: "form-control"
= text_field_tag 'new_branch', @ref, class: "form-control"
= hidden_field_tag 'content', '', id: 'file-content' = hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref, = render 'projects/commit_button', ref: @ref,
......
...@@ -3,26 +3,6 @@ module API ...@@ -3,26 +3,6 @@ module API
class Files < Grape::API class Files < Grape::API
before { authenticate! } before { authenticate! }
helpers do
def commit_params(attrs)
{
file_path: attrs[:file_path],
current_branch: attrs[:branch_name],
target_branch: attrs[:branch_name],
commit_message: attrs[:commit_message],
file_content: attrs[:content],
file_content_encoding: attrs[:encoding]
}
end
def commit_response(attrs)
{
file_path: attrs[:file_path],
branch_name: attrs[:branch_name],
}
end
end
resource :projects do resource :projects do
# Get file from repository # Get file from repository
# File content is Base64 encoded # File content is Base64 encoded
...@@ -93,11 +73,17 @@ module API ...@@ -93,11 +73,17 @@ module API
required_attributes! [:file_path, :branch_name, :content, :commit_message] required_attributes! [:file_path, :branch_name, :content, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding] attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute branch_name = attrs.delete(:branch_name)
file_path = attrs.delete(:file_path)
result = ::Files::CreateService.new(user_project, current_user, attrs, branch_name, file_path).execute
if result[:status] == :success if result[:status] == :success
status(201) status(201)
commit_response(attrs)
{
file_path: file_path,
branch_name: branch_name
}
else else
render_api_error!(result[:message], 400) render_api_error!(result[:message], 400)
end end
...@@ -119,11 +105,17 @@ module API ...@@ -119,11 +105,17 @@ module API
required_attributes! [:file_path, :branch_name, :content, :commit_message] required_attributes! [:file_path, :branch_name, :content, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding] attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute branch_name = attrs.delete(:branch_name)
file_path = attrs.delete(:file_path)
result = ::Files::UpdateService.new(user_project, current_user, attrs, branch_name, file_path).execute
if result[:status] == :success if result[:status] == :success
status(200) status(200)
commit_response(attrs)
{
file_path: file_path,
branch_name: branch_name
}
else else
http_status = result[:http_status] || 400 http_status = result[:http_status] || 400
render_api_error!(result[:message], http_status) render_api_error!(result[:message], http_status)
...@@ -146,11 +138,17 @@ module API ...@@ -146,11 +138,17 @@ module API
required_attributes! [:file_path, :branch_name, :commit_message] required_attributes! [:file_path, :branch_name, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :commit_message] attrs = attributes_for_keys [:file_path, :branch_name, :commit_message]
result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute branch_name = attrs.delete(:branch_name)
file_path = attrs.delete(:file_path)
result = ::Files::DeleteService.new(user_project, current_user, attrs, branch_name, file_path).execute
if result[:status] == :success if result[:status] == :success
status(200) status(200)
commit_response(attrs)
{
file_path: file_path,
branch_name: branch_name
}
else else
render_api_error!(result[:message], 400) render_api_error!(result[:message], 400)
end end
......
require_relative 'file_action'
module Gitlab
module Satellite
class DeleteFileAction < FileAction
# Deletes file and creates a new commit for it
#
# Returns false if committing the change fails
# Returns false if pushing from the satellite to bare repo failed or was rejected
# Returns true otherwise
def commit!(content, commit_message)
in_locked_and_timed_satellite do |repo|
prepare_satellite!(repo)
# create target branch in satellite at the corresponding commit from bare repo
repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
# update the file in the satellite's working dir
file_path_in_satellite = File.join(repo.working_dir, file_path)
# Prevent relative links
unless safe_path?(file_path_in_satellite)
Gitlab::GitLogger.error("FileAction: Relative path not allowed")
return false
end
File.delete(file_path_in_satellite)
# add removed file
repo.remove(file_path_in_satellite)
# commit the changes
# will raise CommandFailed when commit fails
repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
# push commit back to bare repo
# will raise CommandFailed when push fails
repo.git.push({ raise: true, timeout: true }, :origin, ref)
# everything worked
true
end
rescue Grit::Git::CommandFailed => ex
Gitlab::GitLogger.error(ex.message)
false
end
end
end
end
require_relative 'file_action'
module Gitlab
module Satellite
# GitLab server-side file update and commit
class EditFileAction < FileAction
# Updates the files content and creates a new commit for it
#
# Returns false if the ref has been updated while editing the file
# Returns false if committing the change fails
# Returns false if pushing from the satellite to bare repo failed or was rejected
# Returns true otherwise
def commit!(content, commit_message, encoding, new_branch = nil)
in_locked_and_timed_satellite do |repo|
prepare_satellite!(repo)
# create target branch in satellite at the corresponding commit from bare repo
begin
repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
rescue Grit::Git::CommandFailed => ex
log_and_raise(CheckoutFailed, ex.message)
end
# update the file in the satellite's working dir
file_path_in_satellite = File.join(repo.working_dir, file_path)
# Prevent relative links
unless safe_path?(file_path_in_satellite)
Gitlab::GitLogger.error("FileAction: Relative path not allowed")
return false
end
# Write file
write_file(file_path_in_satellite, content, encoding)
# commit the changes
# will raise CommandFailed when commit fails
begin
repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
rescue Grit::Git::CommandFailed => ex
log_and_raise(CommitFailed, ex.message)
end
target_branch = new_branch.present? ? "#{ref}:#{new_branch}" : ref
# push commit back to bare repo
# will raise CommandFailed when push fails
begin
repo.git.push({ raise: true, timeout: true }, :origin, target_branch)
rescue Grit::Git::CommandFailed => ex
log_and_raise(PushFailed, ex.message)
end
# everything worked
true
end
end
private
def log_and_raise(errorClass, message)
Gitlab::GitLogger.error(message)
raise(errorClass, message)
end
end
end
end
module Gitlab
module Satellite
class FileAction < Action
attr_accessor :file_path, :ref
def initialize(user, project, ref, file_path)
super user, project
@file_path = file_path
@ref = ref
end
def safe_path?(path)
File.absolute_path(path) == path
end
def write_file(abs_file_path, content, file_encoding = 'text')
if file_encoding == 'base64'
File.open(abs_file_path, 'wb') { |f| f.write(Base64.decode64(content)) }
else
File.open(abs_file_path, 'w') { |f| f.write(content) }
end
end
end
end
end
require_relative 'file_action'
module Gitlab
module Satellite
class NewFileAction < FileAction
# Updates the files content and creates a new commit for it
#
# Returns false if the ref has been updated while editing the file
# Returns false if committing the change fails
# Returns false if pushing from the satellite to bare repo failed or was rejected
# Returns true otherwise
def commit!(content, commit_message, encoding, new_branch = nil)
in_locked_and_timed_satellite do |repo|
prepare_satellite!(repo)
# create target branch in satellite at the corresponding commit from bare repo
current_ref =
if @project.empty_repo?
# skip this step if we want to add first file to empty repo
Satellite::PARKING_BRANCH
else
repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
ref
end
file_path_in_satellite = File.join(repo.working_dir, file_path)
dir_name_in_satellite = File.dirname(file_path_in_satellite)
# Prevent relative links
unless safe_path?(file_path_in_satellite)
Gitlab::GitLogger.error("FileAction: Relative path not allowed")
return false
end
# Create dir if not exists
FileUtils.mkdir_p(dir_name_in_satellite)
# Write file
write_file(file_path_in_satellite, content, encoding)
# add new file
repo.add(file_path_in_satellite)
# commit the changes
# will raise CommandFailed when commit fails
repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
target_branch = if new_branch.present? && !@project.empty_repo?
"#{ref}:#{new_branch}"
else
"#{current_ref}:#{ref}"
end
# push commit back to bare repo
# will raise CommandFailed when push fails
repo.git.push({ raise: true, timeout: true }, :origin, target_branch)
# everything worked
true
end
rescue Grit::Git::CommandFailed => ex
Gitlab::GitLogger.error(ex.message)
false
end
end
end
end
...@@ -34,20 +34,6 @@ describe Repository do ...@@ -34,20 +34,6 @@ describe Repository do
end end
end end
describe :can_be_merged? do
context 'mergeable branches' do
subject { repository.can_be_merged?('feature', 'master') }
it { is_expected.to be_truthy }
end
context 'non-mergeable branches' do
subject { repository.can_be_merged?('feature_conflict', 'feature') }
it { is_expected.to be_falsey }
end
end
describe "search_files" do describe "search_files" do
let(:results) { repository.search_files('feature', 'master') } let(:results) { repository.search_files('feature', 'master') }
subject { results } subject { results }
......
...@@ -49,6 +49,8 @@ describe API::API, api: true do ...@@ -49,6 +49,8 @@ describe API::API, api: true do
end end
it "should create a new file in project repo" do it "should create a new file in project repo" do
expect_any_instance_of(Gitlab::Satellite::NewFileAction).to receive(:commit!).and_return(true)
post api("/projects/#{project.id}/repository/files", user), valid_params post api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(201) expect(response.status).to eq(201)
expect(json_response['file_path']).to eq('newfile.rb') expect(json_response['file_path']).to eq('newfile.rb')
...@@ -59,9 +61,8 @@ describe API::API, api: true do ...@@ -59,9 +61,8 @@ describe API::API, api: true do
expect(response.status).to eq(400) expect(response.status).to eq(400)
end end
it "should return a 400 if editor fails to create file" do it "should return a 400 if satellite fails to create file" do
allow_any_instance_of(Repository).to receive(:commit_file). expect_any_instance_of(Gitlab::Satellite::NewFileAction).to receive(:commit!).and_return(false)
and_return(false)
post api("/projects/#{project.id}/repository/files", user), valid_params post api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(400) expect(response.status).to eq(400)
...@@ -79,6 +80,8 @@ describe API::API, api: true do ...@@ -79,6 +80,8 @@ describe API::API, api: true do
end end
it "should update existing file in project repo" do it "should update existing file in project repo" do
expect_any_instance_of(Gitlab::Satellite::EditFileAction).to receive(:commit!).and_return(true)
put api("/projects/#{project.id}/repository/files", user), valid_params put api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response['file_path']).to eq(file_path) expect(json_response['file_path']).to eq(file_path)
...@@ -88,6 +91,32 @@ describe API::API, api: true do ...@@ -88,6 +91,32 @@ describe API::API, api: true do
put api("/projects/#{project.id}/repository/files", user) put api("/projects/#{project.id}/repository/files", user)
expect(response.status).to eq(400) expect(response.status).to eq(400)
end end
it 'should return a 400 if the checkout fails' do
expect_any_instance_of(Gitlab::Satellite::EditFileAction).to receive(:commit!).and_raise(Gitlab::Satellite::CheckoutFailed)
put api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(400)
ref = valid_params[:branch_name]
expect(response.body).to match("ref '#{ref}' could not be checked out")
end
it 'should return a 409 if the file was not modified' do
expect_any_instance_of(Gitlab::Satellite::EditFileAction).to receive(:commit!).and_raise(Gitlab::Satellite::CommitFailed)
put api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(409)
expect(response.body).to match("Maybe there was nothing to commit?")
end
it 'should return a 409 if the push fails' do
expect_any_instance_of(Gitlab::Satellite::EditFileAction).to receive(:commit!).and_raise(Gitlab::Satellite::PushFailed)
put api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(409)
expect(response.body).to match("Maybe the file was changed by another process?")
end
end end
describe "DELETE /projects/:id/repository/files" do describe "DELETE /projects/:id/repository/files" do
...@@ -100,6 +129,7 @@ describe API::API, api: true do ...@@ -100,6 +129,7 @@ describe API::API, api: true do
end end
it "should delete existing file in project repo" do it "should delete existing file in project repo" do
expect_any_instance_of(Gitlab::Satellite::DeleteFileAction).to receive(:commit!).and_return(true)
delete api("/projects/#{project.id}/repository/files", user), valid_params delete api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response['file_path']).to eq(file_path) expect(json_response['file_path']).to eq(file_path)
...@@ -111,7 +141,7 @@ describe API::API, api: true do ...@@ -111,7 +141,7 @@ describe API::API, api: true do
end end
it "should return a 400 if satellite fails to create file" do it "should return a 400 if satellite fails to create file" do
allow_any_instance_of(Repository).to receive(:remove_file).and_return(false) expect_any_instance_of(Gitlab::Satellite::DeleteFileAction).to receive(:commit!).and_return(false)
delete api("/projects/#{project.id}/repository/files", user), valid_params delete api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(400) expect(response.status).to eq(400)
......
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