From 9817f4cdd1d3944edc7f0ba1d530d729a995cfde Mon Sep 17 00:00:00 2001 From: Tom Kazimiers Date: Fri, 12 Oct 2012 12:13:02 +0200 Subject: [PATCH 1/2] Add execution of additional post-receive hooks As this plugin occupies the post-receive hook of a managed repository, no other post-receive hook can be used. To get around this limitation, this commit changes the installed hook in such a way that it looks for additional executable files in a sub-directory of the repository's hook folder. This sub-directory has to be called "post-receive.d". The plugin's hook will execute all the files in there marked as executable and will provide the processes with the same STDIN stream it got when called in the first place. --- README.mkd | 18 +++++ .../hooks/post-receive.redmine_gitolite.rb | 68 ++++++++++++++++++- 2 files changed, 83 insertions(+), 3 deletions(-) diff --git a/README.mkd b/README.mkd index 184146b75..30b85be00 100755 --- a/README.mkd +++ b/README.mkd @@ -377,6 +377,24 @@ 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-receve +hook. If you want additional post-receive hooks to be run, you can +place them in a directory with the name "post-receive.d" within the +repository's hook directory. If a file in this folder is marked as +executable, the plugin's hook will execute it. Note that additional +hooks get passed the same STDIN stream as the plugin's hook got. + +In a standard gitolite install with a repository called "test", this +will be a valid path of an additional post-receive hook: + + /home/gitolite/repositories/test.git/hooks/post-receive.d/do-something + +If the "do-something" file is executable, the plugin will pick it up. + ## 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..ba6b02d4e 100755 --- a/contrib/hooks/post-receive.redmine_gitolite.rb +++ b/contrib/hooks/post-receive.redmine_gitolite.rb @@ -74,8 +74,51 @@ def run_query(url_str, params, with_https) success end +def get_extra_hooks() + extra_hooks = Array.new + extra_hooks_dir = "hooks/post-receive.d" + if File.directory?(extra_hooks_dir) + log("Found dir: #{extra_hooks_dir}", true, true) + Dir.foreach(extra_hooks_dir) do |item| + next if item == '.' or item == '..' + # Use full relative path + path = "#{extra_hooks_dir}/#{item}" + # Test if the file is executable + if File.executable?(path) + log("Found executable file: #{path}...", true, false) + # Remember it, if so + extra_hooks.push path + log("Added", true, true) + else + log("Found non-executable file: #{item}", true, true) + end + end + else + log("\n\nNo additional post-receive-hooks folder (post-receive.d) found.", true, true) + end + extra_hooks +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| + pipe.puts stdin + pipe.close_write + 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 +139,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 +178,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 From ee4c5d85bca8900ba80119a0a06e87ab611f9242 Mon Sep 17 00:00:00 2001 From: Tom Kazimiers Date: Tue, 16 Oct 2012 21:48:41 +0200 Subject: [PATCH 2/2] Add execution of additional global post-receive hooks Up to now the plugin executed additional hooks when they lived in the 'postreceive.d' folder in the hooks folter of a repository. This allwed only for either local or global hooks. To support both use cases at a time, this commit will do two things: 1. The plugin will check whether there is a global hook directory 'post-receive.d' present. If not, the plugin will create it in the common hooks directory of gitolite. Gitolite will link it from every repository automatically. 2. It will execute executables in the folders 'post-receive.d' and 'post-receive.local.d', if present. The first one is meant to be linked to a common folder (see 1.) in gitolite's common hooks folder. The second folder can be used to have additional local per-repository hooks. --- README.mkd | 37 ++++++++--- .../hooks/post-receive.redmine_gitolite.rb | 39 ++++++++--- lib/git_adapter_hooks.rb | 65 ++++++++++++++++++- 3 files changed, 121 insertions(+), 20 deletions(-) diff --git a/README.mkd b/README.mkd index 30b85be00..e273dc7f6 100755 --- a/README.mkd +++ b/README.mkd @@ -381,20 +381,41 @@ another credential is a simple matter of selecting the key from a drop-down menu 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-receve -hook. If you want additional post-receive hooks to be run, you can -place them in a directory with the name "post-receive.d" within the -repository's hook directory. If a file in this folder is marked as -executable, the plugin's hook will execute it. Note that additional -hooks get passed the same STDIN stream as the plugin's hook got. +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 post-receive hook: +will be a valid path of an additional local post-receive hook: - /home/gitolite/repositories/test.git/hooks/post-receive.d/do-something + /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 ba6b02d4e..3df349861 100755 --- a/contrib/hooks/post-receive.redmine_gitolite.rb +++ b/contrib/hooks/post-receive.redmine_gitolite.rb @@ -75,28 +75,45 @@ def run_query(url_str, params, with_https) end def get_extra_hooks() - extra_hooks = Array.new - extra_hooks_dir = "hooks/post-receive.d" - if File.directory?(extra_hooks_dir) - log("Found dir: #{extra_hooks_dir}", true, true) - Dir.foreach(extra_hooks_dir) do |item| + # 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 = "#{extra_hooks_dir}/#{item}" + path = "#{directory}/#{item}" # Test if the file is executable if File.executable?(path) log("Found executable file: #{path}...", true, false) # Remember it, if so - extra_hooks.push path + executables.push path log("Added", true, true) else log("Found non-executable file: #{item}", true, true) end end else - log("\n\nNo additional post-receive-hooks folder (post-receive.d) found.", true, true) + log("\n\nFolder not found: #{directory}", true, true) end - extra_hooks + executables end def call_extra_hooks(stdin, extra_hooks) @@ -108,8 +125,12 @@ def call_extra_hooks(stdin, extra_hooks) 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 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)