Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions hooks/post-receive
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

# This file was placed here by GitLab. It makes sure that your pushed commits
# will be processed properly.
# You can add your own hooks to this file, but be careful when updating gitlab-shell!

changes = ARGF.read
refs = ARGF.read
key_id = ENV['GL_ID']
repo_path = Dir.pwd

require_relative '../lib/gitlab_custom_hook'
require_relative '../lib/gitlab_post_receive'

if GitlabPostReceive.new(repo_path, key_id, changes).exec
if GitlabPostReceive.new(repo_path, key_id, refs).exec &&
GitlabCustomHook.new.post_receive(refs, repo_path)
exit 0
else
exit 1
Expand Down
5 changes: 3 additions & 2 deletions hooks/pre-receive
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

# This file was placed here by GitLab. It makes sure that your pushed commits
# will be processed properly.
# You can add your own hooks to this file, but be careful when updating gitlab-shell!

refs = ARGF.read
key_id = ENV['GL_ID']
repo_path = Dir.pwd

require_relative '../lib/gitlab_custom_hook'
require_relative '../lib/gitlab_access'

if GitlabAccess.new(repo_path, key_id, refs).exec
if GitlabAccess.new(repo_path, key_id, refs).exec &&
GitlabCustomHook.new.pre_receive(refs, repo_path)
exit 0
else
exit 1
Expand Down
17 changes: 17 additions & 0 deletions hooks/update
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env ruby

# This file was placed here by GitLab. It makes sure that your pushed commits
# will be processed properly.

ref_name = ARGV[0]
old_value = ARGV[1]
new_value = ARGV[2]
repo_path = Dir.pwd

require_relative '../lib/gitlab_custom_hook'

if GitlabCustomHook.new.update(ref_name, old_value, new_value, repo_path)
exit 0
else
exit 1
end
2 changes: 1 addition & 1 deletion lib/gitlab_access.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def exec
# reset GL_ID env since we stop git push here
ENV['GL_ID'] = nil
puts "GitLab: You are not allowed to access some of the refs!"
exit 1
return false
end
end

Expand Down
69 changes: 69 additions & 0 deletions lib/gitlab_custom_hook.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
require 'open3'

class GitlabCustomHook
def pre_receive(changes, repo_path)
hook = hook_file('pre-receive', repo_path)
return true if hook.nil?
if call_receive_hook(hook, changes)
return true
else
# reset GL_ID env since we stop git push here
ENV['GL_ID'] = nil
return false
end
end

def post_receive(changes, repo_path)
hook = hook_file('post-receive', repo_path)
return true if hook.nil?
call_receive_hook(hook, changes) ? true : false
end
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this would not be multiple files. Only one hook per type per repo.


def update(ref_name, old_value, new_value, repo_path)
hook = hook_file('update', repo_path)
return true if hook.nil?
system(hook, ref_name, old_value, new_value) ? true : false
end

private

def call_receive_hook(hook, changes)
# function will return true if succesful
exit_status = false

# we combine both stdout and stderr as we don't know what stream
# will be used by the custom hook
Open3.popen2e (hook) do |stdin, stdout_stderr, wait_thr|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dblessing can you describe me what happens in call_receive_hook method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pre/post-receive hooks expect potentially more than 1 set of refs in one push because you can push more than one branch at a time. This method takes all the refs we grabbed from the original hook call and pushes them to stdin for the custom hook, too. Sending them as command line args is simply not enough.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a nutshell, this function emulates the pre- andpost-receive hook API that a standalone git provides. To be more precise, the git interface for the pre-receive and post-receive comments sends the information of what branch and references have changed directly thru stdin, not thru the command line arguments. This is what this function does. Like @dblessing said, you can push multiple branches. That's the other part that this function does, feeding each line thru stdin. This has the benefit that you can use standard git hook code as we respect the API.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aha so when executed custom pre-receive hook behaves like it was executed directly by git right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. call_receive_hook() does that does that for both pre-receive and post-receive custom hooks.

There may just one difference, though, but the git API is not clear about this. When you do a multiple branch push and the custom pre- and post- receive hooks fail for one or more of the pushed branches, it is not clear how to tell git that some branches failed. I took the choice of not returning success unless the hook returns success for all the branches. The git docs don't talk about this at all.

The custom update hook calls works exactly as git's.

The tests I did was that I wrote very simple pre-receive, post-receive, and update hooks that just output what's being fed by the API and tested them all in permutations when returning with exit 0 or exit 1. First did the tests on a standalone vanilla git environment and then verified that we had the the same behavior inside gitlab by means of gitlab_custom_hook.rb

My own more complex custom hooks now work the same in either the vanilla git or gitlab.

The git doc: http://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkbzh thanks

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
stdout_stderr.each_line { |line| puts line }
end
end

exit_status
end

def hook_file(hook_type, repo_path)
hook_path = File.join(repo_path.strip, 'custom_hooks')
hook_file = "#{hook_path}/#{hook_type}"
hook_file if File.exist?(hook_file)
end
end
7 changes: 5 additions & 2 deletions lib/gitlab_post_receive.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ def exec
def update_redis
queue = "#{config.redis_namespace}:queue:post_receive"
msg = JSON.dump({'class' => 'PostReceive', 'args' => [@repo_path, @actor, @changes]})
unless system(*config.redis_command, 'rpush', queue, msg, err: '/dev/null', out: '/dev/null')
if system(*config.redis_command, 'rpush', queue, msg,
err: '/dev/null', out: '/dev/null')
return true
else
puts "GitLab: An unexpected error occurred (redis-cli returned #{$?.exitstatus})."
exit 1
return false
end
end
end