diff options
author | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2015-08-13 13:57:24 +0300 |
---|---|---|
committer | Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com> | 2015-08-13 13:57:24 +0300 |
commit | 34690142bf42e0a3d48b1b30075387abefe86318 (patch) | |
tree | e76ef5c883e30ada67942fb81f71ffb8bd1c60e8 /app/services | |
parent | 69c193e78a14e34d1c67527df821886a38c2e5d8 (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.rb | 37 | ||||
-rw-r--r-- | app/services/files/base_service.rb | 5 | ||||
-rw-r--r-- | app/services/files/create_service.rb | 4 | ||||
-rw-r--r-- | app/services/files/delete_service.rb | 4 | ||||
-rw-r--r-- | app/services/files/update_service.rb | 4 | ||||
-rw-r--r-- | app/services/pre_commit_service.rb | 71 |
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 |