Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorValery Sizov <valery@gitlab.com>2015-08-12 16:44:16 +0300
committerValery Sizov <valery@gitlab.com>2015-08-12 16:44:16 +0300
commit35d3c6df50a34f421cc12c4ae48d73df62bff20a (patch)
tree52a85d540f4f54a1c7e85e3d414e3f3b679e75de
parent7c3e8bcd5eb89ad1d8c7ab1f7d5f90be85698c89 (diff)
parentf4f6d60ca8a9528db92490e2fc893491b54ac0b9 (diff)
Merge branch '7-13-5-stable-candidate' into '7-13-stable'
7-13-5-stable candidate See merge request !1145
-rw-r--r--CHANGELOG5
-rw-r--r--app/controllers/projects/blob_controller.rb60
-rw-r--r--app/models/merge_request.rb9
-rw-r--r--app/models/repository.rb56
-rw-r--r--app/services/files/base_service.rb80
-rw-r--r--app/services/files/create_service.rb44
-rw-r--r--app/services/files/delete_service.rb33
-rw-r--r--app/services/files/update_service.rb36
-rw-r--r--app/services/git_push_service.rb3
-rw-r--r--app/services/merge_requests/auto_merge_service.rb51
-rw-r--r--app/views/projects/blob/_editor.html.haml4
-rw-r--r--app/views/projects/blob/new.html.haml11
-rw-r--r--lib/api/files.rb50
-rw-r--r--lib/gitlab/satellite/files/delete_file_action.rb50
-rw-r--r--lib/gitlab/satellite/files/edit_file_action.rb68
-rw-r--r--lib/gitlab/satellite/files/file_action.rb25
-rw-r--r--lib/gitlab/satellite/files/new_file_action.rb67
-rw-r--r--spec/models/repository_spec.rb14
-rw-r--r--spec/requests/api/files_spec.rb38
19 files changed, 415 insertions, 289 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 99ee87b8979..0acc3f110f2 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,4 +1,8 @@
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
- Allow users to send abuse reports
@@ -142,7 +146,6 @@ v 7.12.0
- Add SAML support as an omniauth provider
- Allow to configure a URL to show after sign out
- 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
- When remove project - move repository and schedule it removal
- Improve group removing logic
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 100d3d3b317..b762518d377 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -13,20 +13,27 @@ class Projects::BlobController < Projects::ApplicationController
before_action :commit, except: [:new, :create]
before_action :blob, except: [:new, :create]
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 :require_branch_head, only: [:edit, :update]
def new
commit unless @repository.empty?
end
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
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
flash[:alert] = result[:message]
render :new
@@ -41,10 +48,22 @@ class Projects::BlobController < Projects::ApplicationController
end
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
flash[:notice] = "Your changes have been successfully committed"
+
+ if from_merge_request
+ from_merge_request.reload_code
+ end
+
redirect_to after_edit_path
else
flash[:alert] = result[:message]
@@ -61,11 +80,12 @@ class Projects::BlobController < Projects::ApplicationController
end
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
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
flash[:alert] = result[:message]
render :show
@@ -115,6 +135,7 @@ class Projects::BlobController < Projects::ApplicationController
@id = params[:id]
@ref, @path = extract_ref(@id)
+
rescue InvalidPathError
not_found!
end
@@ -124,8 +145,8 @@ class Projects::BlobController < Projects::ApplicationController
if 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)}"
- elsif @target_branch.present?
- namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
+ elsif sanitized_new_branch_name.present?
+ namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path))
else
namespace_project_blob_path(@project.namespace, @project, @id)
end
@@ -139,25 +160,4 @@ class Projects::BlobController < Projects::ApplicationController
def sanitized_new_branch_name
@new_branch ||= sanitize(strip_tags(params[:new_branch]))
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
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 1ef76d16700..324d1795ab4 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -205,14 +205,7 @@ class MergeRequest < ActiveRecord::Base
end
def check_if_can_be_merged
- 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
+ if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
mark_as_mergeable
else
mark_as_unmergeable
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 277a9178592..1b03cd848ca 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -364,62 +364,6 @@ class Repository
@root_ref ||= raw_repository.root_ref
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)
offset = 2
args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index 646784f2d9d..bd245100955 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -1,34 +1,11 @@
module Files
class BaseService < ::BaseService
- class ValidationError < StandardError; end
+ attr_reader :ref, :path
- def execute
- @current_branch = params[:current_branch]
- @target_branch = params[:target_branch]
- @commit_message = params[:commit_message]
- @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)
+ def initialize(project, user, params, ref, path = nil)
+ @project, @current_user, @params = project, user, params.dup
+ @ref = ref
+ @path = path
end
private
@@ -36,52 +13,5 @@ module Files
def repository
project.repository
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
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 91d715b2d63..23833aa78ec 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -1,30 +1,52 @@
require_relative "base_service"
module Files
- class CreateService < Files::BaseService
- def commit
- repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
- end
+ class CreateService < BaseService
+ def execute
+ allowed = Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
- def validate
- super
+ unless allowed
+ 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
- raise_error(
+ return error(
'Your changes could not be committed, because the file name ' +
Gitlab::Regex.file_name_regex_message
)
end
- unless project.empty_repo?
- blob = repository.blob_at_branch(@current_branch, @file_path)
+ if project.empty_repo?
+ # 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
- 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
+
+
+ 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
diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb
index 27c881c3430..1497a0f883b 100644
--- a/app/services/files/delete_service.rb
+++ b/app/services/files/delete_service.rb
@@ -1,9 +1,36 @@
require_relative "base_service"
module Files
- class DeleteService < Files::BaseService
- def commit
- repository.remove_file(current_user, @file_path, @commit_message, @target_branch)
+ class DeleteService < BaseService
+ def execute
+ 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
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index a20903c6f02..0724d3ae634 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -1,9 +1,39 @@
require_relative "base_service"
module Files
- class UpdateService < Files::BaseService
- def commit
- repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
+ class UpdateService < BaseService
+ def execute
+ 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
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 3511392d1d8..5a2c97b08af 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -133,8 +133,7 @@ class GitPushService
end
def is_default_branch?(ref)
- Gitlab::Git.branch_ref?(ref) &&
- (Gitlab::Git.ref_name(ref) == project.default_branch || project.default_branch.nil?)
+ Gitlab::Git.branch_ref?(ref) && Gitlab::Git.ref_name(ref) == project.default_branch
end
def commit_user(commit)
diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb
index db824d452d0..cdedf48b0c0 100644
--- a/app/services/merge_requests/auto_merge_service.rb
+++ b/app/services/merge_requests/auto_merge_service.rb
@@ -5,20 +5,17 @@ module MergeRequests
# mark merge request as merged and execute all hooks and notifications
# Called when you do merge via GitLab UI
class AutoMergeService < BaseMergeService
- attr_reader :merge_request, :commit_message
-
def execute(merge_request, commit_message)
- @commit_message = commit_message
- @merge_request = merge_request
-
merge_request.lock_mr
- if merge!
+ if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
merge_request.merge
+
create_merge_event(merge_request, current_user)
create_note(merge_request)
notification_service.merge_mr(merge_request, current_user)
execute_hooks(merge_request, 'merge')
+
true
else
merge_request.unlock_mr
@@ -29,47 +26,5 @@ module MergeRequests
merge_request.mark_as_unmergeable
false
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
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 9c3e1703c89..96f188e4aa7 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -12,8 +12,8 @@
\/
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
required: true, class: 'form-control new-file-name'
- .pull-right
- = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
+ .pull-right
+ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
.file-content.code
%pre.js-edit-mode-pane#editor
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 7c2a4fece94..dac984f8c31 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -6,12 +6,11 @@
= render 'shared/commit_message_container', params: params,
placeholder: 'Add new file'
- - unless @project.empty_repo?
- .form-group.branch
- = label_tag 'branch', class: 'control-label' do
- Branch
- .col-sm-10
- = text_field_tag 'new_branch', @ref, class: "form-control"
+ .form-group.branch
+ = label_tag 'branch', class: 'control-label' do
+ Branch
+ .col-sm-10
+ = text_field_tag 'new_branch', @ref, class: "form-control"
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
diff --git a/lib/api/files.rb b/lib/api/files.rb
index c7b30cf2f07..e0ea6d7dd1d 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -3,26 +3,6 @@ module API
class Files < Grape::API
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
# Get file from repository
# File content is Base64 encoded
@@ -93,11 +73,17 @@ module API
required_attributes! [:file_path, :branch_name, :content, :commit_message]
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
status(201)
- commit_response(attrs)
+
+ {
+ file_path: file_path,
+ branch_name: branch_name
+ }
else
render_api_error!(result[:message], 400)
end
@@ -119,11 +105,17 @@ module API
required_attributes! [:file_path, :branch_name, :content, :commit_message]
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
status(200)
- commit_response(attrs)
+
+ {
+ file_path: file_path,
+ branch_name: branch_name
+ }
else
http_status = result[:http_status] || 400
render_api_error!(result[:message], http_status)
@@ -146,11 +138,17 @@ module API
required_attributes! [: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
status(200)
- commit_response(attrs)
+
+ {
+ file_path: file_path,
+ branch_name: branch_name
+ }
else
render_api_error!(result[:message], 400)
end
diff --git a/lib/gitlab/satellite/files/delete_file_action.rb b/lib/gitlab/satellite/files/delete_file_action.rb
new file mode 100644
index 00000000000..0d37b9dea85
--- /dev/null
+++ b/lib/gitlab/satellite/files/delete_file_action.rb
@@ -0,0 +1,50 @@
+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
diff --git a/lib/gitlab/satellite/files/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb
new file mode 100644
index 00000000000..3cb9c0b5ecb
--- /dev/null
+++ b/lib/gitlab/satellite/files/edit_file_action.rb
@@ -0,0 +1,68 @@
+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
diff --git a/lib/gitlab/satellite/files/file_action.rb b/lib/gitlab/satellite/files/file_action.rb
new file mode 100644
index 00000000000..6446b14568a
--- /dev/null
+++ b/lib/gitlab/satellite/files/file_action.rb
@@ -0,0 +1,25 @@
+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
diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb
new file mode 100644
index 00000000000..724dfa0d042
--- /dev/null
+++ b/lib/gitlab/satellite/files/new_file_action.rb
@@ -0,0 +1,67 @@
+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
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index d25351b0f0e..0927cde61a6 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -34,20 +34,6 @@ describe Repository do
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
let(:results) { repository.search_files('feature', 'master') }
subject { results }
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 6c7860511e8..1d14a1729c6 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -49,6 +49,8 @@ describe API::API, api: true do
end
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
expect(response.status).to eq(201)
expect(json_response['file_path']).to eq('newfile.rb')
@@ -59,9 +61,8 @@ describe API::API, api: true do
expect(response.status).to eq(400)
end
- it "should return a 400 if editor fails to create file" do
- allow_any_instance_of(Repository).to receive(:commit_file).
- and_return(false)
+ it "should return a 400 if satellite fails to create file" do
+ expect_any_instance_of(Gitlab::Satellite::NewFileAction).to receive(:commit!).and_return(false)
post api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(400)
@@ -79,6 +80,8 @@ describe API::API, api: true do
end
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
expect(response.status).to eq(200)
expect(json_response['file_path']).to eq(file_path)
@@ -88,6 +91,32 @@ describe API::API, api: true do
put api("/projects/#{project.id}/repository/files", user)
expect(response.status).to eq(400)
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
describe "DELETE /projects/:id/repository/files" do
@@ -100,6 +129,7 @@ describe API::API, api: true do
end
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
expect(response.status).to eq(200)
expect(json_response['file_path']).to eq(file_path)
@@ -111,7 +141,7 @@ describe API::API, api: true do
end
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
expect(response.status).to eq(400)