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:
authorDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2015-08-13 13:57:24 +0300
committerDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>2015-08-13 13:57:24 +0300
commit34690142bf42e0a3d48b1b30075387abefe86318 (patch)
treee76ef5c883e30ada67942fb81f71ffb8bd1c60e8 /app/services
parent69c193e78a14e34d1c67527df821886a38c2e5d8 (diff)
Implement commit transaction with pre-receive and post-receive hooks for web editor
Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
Diffstat (limited to 'app/services')
-rw-r--r--app/services/commit_service.rb37
-rw-r--r--app/services/files/base_service.rb5
-rw-r--r--app/services/files/create_service.rb4
-rw-r--r--app/services/files/delete_service.rb4
-rw-r--r--app/services/files/update_service.rb4
-rw-r--r--app/services/pre_commit_service.rb71
6 files changed, 117 insertions, 8 deletions
diff --git a/app/services/commit_service.rb b/app/services/commit_service.rb
new file mode 100644
index 00000000000..a5603f26692
--- /dev/null
+++ b/app/services/commit_service.rb
@@ -0,0 +1,37 @@
+require 'securerandom'
+
+class CommitService
+ def self.transaction(project, current_user, ref)
+ repository = project.repository
+ path_to_repo = repository.path_to_repo
+
+ # Create temporary ref
+ random_string = SecureRandom.hex
+ tmp_ref = "refs/tmp/#{random_string}/head"
+ target = repository.find_branch(ref).target
+ repository.rugged.references.create(tmp_ref, target)
+
+ # Make commit in tmp ref
+ sha = yield(tmp_ref)
+
+ unless sha
+ raise 'Failed to create commit'
+ end
+
+ # Run GitLab pre-receive hook
+ status = PreCommitService.new(project, current_user).execute(sha, ref)
+
+ if status
+ # Update head
+ repository.rugged.references.update(Gitlab::Git::BRANCH_REF_PREFIX + ref, sha)
+
+ # Run GitLab post receive hook
+ PostCommitService.new(project, current_user).execute(sha, ref)
+ else
+ # Remove tmp ref and return error to user
+ repository.rugged.references.delete(tmp_ref)
+
+ raise 'Commit was rejected by pre-reveive hook'
+ end
+ end
+end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index d7b40ee8906..1f7b92e222b 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -22,7 +22,6 @@ module Files
end
if sha = commit
- after_commit(sha, @target_branch)
success
else
error("Something went wrong. Your changes were not committed")
@@ -33,10 +32,6 @@ module Files
private
- def after_commit(sha, branch)
- PostCommitService.new(project, current_user).execute(sha, branch)
- end
-
def current_branch
@current_branch ||= params[:current_branch]
end
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 91d715b2d63..3e00864f00d 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -3,7 +3,9 @@ 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)
+ CommitService.transaction(project, current_user, @target_branch) do |tmp_ref|
+ repository.commit_file(current_user, @file_path, @file_content, @commit_message, tmp_ref)
+ end
end
def validate
diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb
index 27c881c3430..d61ca31cf9d 100644
--- a/app/services/files/delete_service.rb
+++ b/app/services/files/delete_service.rb
@@ -3,7 +3,9 @@ require_relative "base_service"
module Files
class DeleteService < Files::BaseService
def commit
- repository.remove_file(current_user, @file_path, @commit_message, @target_branch)
+ CommitService.transaction(project, current_user, @target_branch) do |tmp_ref|
+ repository.remove_file(current_user, @file_path, @commit_message, tmp_ref)
+ end
end
end
end
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index a20903c6f02..69212b36996 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -3,7 +3,9 @@ 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)
+ CommitService.transaction(project, current_user, @target_branch) do |tmp_ref|
+ repository.commit_file(current_user, @file_path, @file_content, @commit_message, tmp_ref)
+ end
end
end
end
diff --git a/app/services/pre_commit_service.rb b/app/services/pre_commit_service.rb
new file mode 100644
index 00000000000..ed8331ddce4
--- /dev/null
+++ b/app/services/pre_commit_service.rb
@@ -0,0 +1,71 @@
+class PreCommitService < BaseService
+ include Gitlab::Popen
+
+ attr_reader :changes, :repo_path
+
+ def execute(sha, branch)
+ commit = repository.commit(sha)
+ full_ref = Gitlab::Git::BRANCH_REF_PREFIX + branch
+ old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA
+ @changes = "#{old_sha} #{sha} #{full_ref}"
+ @repo_path = repository.path_to_repo
+
+ pre_receive
+ end
+
+ private
+
+ def pre_receive
+ hook = hook_file('pre-receive', repo_path)
+ return true if hook.nil?
+ call_receive_hook(hook)
+ end
+
+ def call_receive_hook(hook)
+ # function will return true if succesful
+ exit_status = false
+
+ vars = {
+ 'GL_ID' => Gitlab::ShellEnv.gl_id(current_user),
+ 'PWD' => repo_path
+ }
+
+ options = {
+ chdir: repo_path
+ }
+
+ # we combine both stdout and stderr as we don't know what stream
+ # will be used by the custom hook
+ Open3.popen2e(vars, hook, options) do |stdin, stdout_stderr, wait_thr|
+ exit_status = true
+ stdin.sync = true
+
+ # in git, pre- and post- receive hooks may just exit without
+ # reading stdin. We catch the exception to avoid a broken pipe
+ # warning
+ begin
+ # inject all the changes as stdin to the hook
+ changes.lines do |line|
+ stdin.puts line
+ end
+ rescue Errno::EPIPE
+ end
+
+ # need to close stdin before reading stdout
+ stdin.close
+
+ # only output stdut_stderr if scripts doesn't return 0
+ unless wait_thr.value == 0
+ exit_status = false
+ end
+ end
+
+ exit_status
+ end
+
+ def hook_file(hook_type, repo_path)
+ hook_path = File.join(repo_path.strip, 'hooks')
+ hook_file = "#{hook_path}/#{hook_type}"
+ hook_file if File.exist?(hook_file)
+ end
+end