diff --git a/README.mkd b/README.mkd index 184146b75..e273dc7f6 100755 --- a/README.mkd +++ b/README.mkd @@ -377,6 +377,45 @@ credentials create" dialog is actually a convenience dialog in that it allows th even suggesting a name for the deployment credential, with the eye to deployments that have a separate deploy key for each repository. Reusing a deploy key in another credential is a simple matter of selecting the key from a drop-down menu. +## Additional Post-Receive Hooks + +As of version 0.5.1x, this plugin supports the execution of additional +post-receive hooks in repositories managed with it. To get updated +about new data in the repository, the plugin installs a post-receive +hook. If you want additional post-receive hooks to be run, you have to +decide whether a hook should be a local or a global one. Local extra +hooks will only be run for the repository they are installed in. +Global extra hooks will be run for *every* gitolite repository. + +To create local hooks, place them in a directory with the name +"post-receive.local.d" within the repository's hook directory. If a +file in this folder is marked as executable, the plugin's hook will +execute it. + +In a standard gitolite install with a repository called "test", this +will be a valid path of an additional local post-receive hook: + + /home/gitolite/repositories/test.git/hooks/post-receive.local.d/do-something + +If the "do-something" file is executable, the plugin will pick it up. + +To create global hooks, place an executable in a folder called +"post-receive.d" that lives in a hook directory of a gitolite +repository. All these "post-receive.d" directories are actually links +to one common "post-receive.d" directory within the gitolite +installation. + +In a standard gitolite install with a repository called "test", this +will be a valid path of an additional global post-receive hook: + + /home/gitolite/repositories/test.git/hooks/post-receive.d/do-something + +Again, the "do-something" file needs to be executable, to be picked +up. All other repositories will have the "do-something" hook as well. + +Note that all additional hooks get passed the same STDIN stream as the +plugin's hook got. + ## Post-Receive URLs As of version 0.4.6x, this plugin supports the inclusion of GitHub-style Post-Receive URLs. Once added, a post-receive URL will be notified when new changes diff --git a/contrib/hooks/post-receive.redmine_gitolite.rb b/contrib/hooks/post-receive.redmine_gitolite.rb index 2266a99cb..3df349861 100755 --- a/contrib/hooks/post-receive.redmine_gitolite.rb +++ b/contrib/hooks/post-receive.redmine_gitolite.rb @@ -74,8 +74,72 @@ def run_query(url_str, params, with_https) success end +def get_extra_hooks() + # Get global extra hooks + global_extra_hooks = get_executables("hooks/post-receive.d") + if global_extra_hooks.length == 0 + log("No global extra hooks found", true, true) + end + # Get local extra hooks + local_extra_hooks = get_executables("hooks/post-receive.local.d") + if local_extra_hooks.length == 0 + log("No local extra hooks found", true, true) + end + # Join both results and return result + result = [] + result.concat(global_extra_hooks) + result.concat(local_extra_hooks) + result +end +def get_executables(directory) + executables = Array.new + if File.directory?(directory) + log("Found folder: #{directory}", true, true) + Dir.foreach(directory) do |item| + next if item == '.' or item == '..' + # Use full relative path + path = "#{directory}/#{item}" + # Test if the file is executable + if File.executable?(path) + log("Found executable file: #{path}...", true, false) + # Remember it, if so + executables.push path + log("Added", true, true) + else + log("Found non-executable file: #{item}", true, true) + end + end + else + log("\n\nFolder not found: #{directory}", true, true) + end + executables +end +def call_extra_hooks(stdin, extra_hooks) + success = false + # Call each exectuble found with the parameters we got + log("Beginning to execute additional hooks", true, true) + extra_hooks.each do |extra_hook| + log("Executing extra hook '#{extra_hook}'") + + output = "" + IO.popen("#{extra_hook}", "w+") do |pipe| + begin + pipe.puts stdin + pipe.close_write + rescue Errno::EPIPE + log("The hook #{extra_hook} does't expect data on STDIN", true, true) + end + output = pipe.read + end + + log("#{output}") + log("Done", true, true) + end + success = true + success +end rgh_vars = {} rgh_var_names = [ "hooks.redmine_gitolite.key", "hooks.redmine_gitolite.url", "hooks.redmine_gitolite.projectid", "hooks.redmine_gitolite.repositoryid", "hooks.redmine_gitolite.debug", "hooks.redmine_gitolite.asynch"] @@ -96,11 +160,14 @@ def run_query(url_str, params, with_https) $debug = rgh_vars["debug"] == "true" -# Let's read the refs passed to us +# Let's read the refs passed to us, but also copy stdin +# for potential use with extra hooks. refs = [] -$<.each do |line| +stdin_copy = "" +$<.each do |line| r = line.chomp.strip.split refs.push( [ r[0].to_s, r[1].to_s, r[2].to_s ].join(",") ) + stdin_copy = (stdin_copy == ""? "" : $/) + line end rgh_vars["refs[]"] = refs @@ -132,4 +199,20 @@ def run_query(url_str, params, with_https) end log("\n\n", false, true) +extra_hooks = get_extra_hooks() +if extra_hooks.length > 0 + log("Calling additional post-receive hooks...", true, true) + success = call_extra_hooks(stdin_copy, extra_hooks) + if(!success) + log("Error calling additional hooks.", false, true) + else + log("Success", true, true) + log("", true, true) + end + log("\n\n", false, true) +else + log("No extra hooks found that could be run additionally.", true, true) + log("\n\n", true, true) +end + exit diff --git a/lib/git_adapter_hooks.rb b/lib/git_adapter_hooks.rb index 35f76967c..177b6a9ac 100755 --- a/lib/git_adapter_hooks.rb +++ b/lib/git_adapter_hooks.rb @@ -4,10 +4,16 @@ module GitHosting class GitAdapterHooks + def self.check_hooks_installed + files_installed = check_hook_files_installed + dir_installed = check_hook_dir_installed + files_installed && dir_installed + end + @@check_hooks_installed_stamp = nil @@check_hooks_installed_cached = nil @@post_receive_hook_path = nil - def self.check_hooks_installed + def self.check_hook_files_installed if not @@check_hooks_installed_cached.nil? and (Time.new - @@check_hooks_installed_stamp <= 0.5) return @@check_hooks_installed_cached end @@ -31,7 +37,7 @@ def self.check_hooks_installed @@check_hooks_installed_stamp = Time.new @@check_hooks_installed_cached = true rescue - logger.error "check_hooks_installed(): Problems installing hooks and initializing gitolite!" + logger.error "check_hook_files_installed(): Problems installing hooks and initializing gitolite!" end return @@check_hooks_installed_cached else @@ -59,7 +65,7 @@ def self.check_hooks_installed logger.info "Finished installing hooks in the gitolite install..." @@check_hooks_installed_cached = true rescue - logger.error "check_hooks_installed(): Problems installing hooks and initializing gitolite!" + logger.error "check_hook_files_installed(): Problems installing hooks and initializing gitolite!" end end @@check_hooks_installed_stamp = Time.new @@ -68,6 +74,47 @@ def self.check_hooks_installed end end + @@check_hooks_dir_installed_cached = nil + @@check_hooks_dir_installed_stamp = nil + def self.check_hook_dir_installed + if not @@check_hooks_dir_installed_cached.nil? and (Time.new - @@check_hooks_dir_installed_stamp <= 0.5) + return @@check_hooks_dir_installed_cached + end + + @@post_receive_hook_dir_path ||= File.join(gitolite_hooks_dir, 'post-receive.d') + post_receive_dir_exists = (%x[#{GitHosting.git_user_runner} test -r '#{@@post_receive_hook_dir_path}' && echo 'yes' || echo 'no']).match(/yes/) + + if (!post_receive_dir_exists) + begin + logger.info "\"post-receive.d\" folder not yet created, installing it..." + install_hook_dir("post-receive.d") + logger.info "\"post-receive.d installed" + logger.info "Running \"gl-setup\" on the gitolite install..." + GitHosting.shell %[#{GitHosting.git_user_runner} gl-setup] + logger.info "Finished installing global hook directory in the gitolite install..." + @@check_hooks_dir_installed_stamp = Time.new + @@check_hooks_dir_installed_cached = true + rescue + logger.error "check_hook_dir_installed(): Problems installing hooks and initializing gitolite!" + end + return @@check_hooks_dir_installed_cached + else + error_msg = "Global \"post-receive.d\" directory is already present, will not touch it!" + logger.warn error_msg + @@check_hooks_dir_installed_cached = error_msg + begin + logger.info "Running \"gl-setup\" on the gitolite install..." + GitHosting.shell %[#{GitHosting.git_user_runner} gl-setup] + logger.info "Finished installing hook directory in the gitolite install..." + @@check_hooks_dir_installed_cached = true + rescue + logger.error "check_hook_dir_installed(): Problems installing hook directory and initializing gitolite!" + end + @@check_hooks_dir_installed_stamp = Time.new + return @@check_hooks_dir_installed_cached + end + end + def self.setup_hooks(projects=nil) check_hooks_installed @@ -145,6 +192,18 @@ def self.rgh_hook_digest(recreate=false) @@cached_hook_digest end + def self.install_hook_dir(hooks_dir) + begin + dest_dir = File.join(gitolite_hooks_dir, hooks_dir) + logger.info "Installing hook directory to #{dest_dir}" + GitHosting.shell %[#{GitHosting.git_user_runner} 'mkdir -p #{dest_dir}'] + GitHosting.shell %[#{GitHosting.git_user_runner} 'chown #{git_user} #{dest_dir}'] + GitHosting.shell %[#{GitHosting.git_user_runner} 'chmod 700 #{dest_dir}'] + rescue + logger.error "install_hooks_dir(): Problems installing hook directory to #{dest_dir}" + end + end + def self.install_hook(hook_name) begin hook_source_path = File.join(package_hooks_dir, hook_name)