From 2d774eeae8ccfb211cc6ab6aeab5db600f3fdc7c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dirk=20H=C3=B6rner?= <dirker@gmail.com>
Date: Mon, 5 Sep 2016 10:44:18 +0200
Subject: [PATCH 01/21] custom_hook: only execute hook if file is executable

This commit fixes an issue where an existing but unexecutable hook would
cause an uncaught execption.
---
 lib/gitlab_custom_hook.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/gitlab_custom_hook.rb b/lib/gitlab_custom_hook.rb
index 25385cf..e84d702 100644
--- a/lib/gitlab_custom_hook.rb
+++ b/lib/gitlab_custom_hook.rb
@@ -56,6 +56,6 @@ class GitlabCustomHook
   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)
+    hook_file if File.executable?(hook_file)
   end
 end
-- 
GitLab


From d05522de85dcdfa91349c0d9fc78bf72931d6a39 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dirk=20H=C3=B6rner?= <dirker@gmail.com>
Date: Mon, 5 Sep 2016 11:59:25 +0200
Subject: [PATCH 02/21] custom_hook: refactor to pull repo_path into class

This commit takes the GitlabCustomHook a bit clother to the other hook
handling classes by receiving the repo_path as argument to initialize()
instead of passing it to each method.
---
 hooks/post-receive              |  2 +-
 hooks/pre-receive               |  2 +-
 hooks/update                    |  2 +-
 lib/gitlab_custom_hook.rb       | 15 ++++++++-------
 spec/gitlab_custom_hook_spec.rb |  8 ++++----
 5 files changed, 15 insertions(+), 14 deletions(-)

diff --git a/hooks/post-receive b/hooks/post-receive
index b84d0d1..7877306 100755
--- a/hooks/post-receive
+++ b/hooks/post-receive
@@ -11,7 +11,7 @@ require_relative '../lib/gitlab_custom_hook'
 require_relative '../lib/gitlab_post_receive'
 
 if GitlabPostReceive.new(repo_path, key_id, refs).exec &&
-    GitlabCustomHook.new(key_id).post_receive(refs, repo_path)
+    GitlabCustomHook.new(repo_path, key_id).post_receive(refs)
   exit 0
 else
   exit 1
diff --git a/hooks/pre-receive b/hooks/pre-receive
index 4d9a4e9..1b16fd0 100755
--- a/hooks/pre-receive
+++ b/hooks/pre-receive
@@ -17,7 +17,7 @@ require_relative '../lib/gitlab_access'
 # other hand, we run GitlabPostReceive first because the push is already done
 # and we don't want to skip it if the custom hook fails.
 if GitlabAccess.new(repo_path, key_id, refs, protocol).exec &&
-    GitlabCustomHook.new(key_id).pre_receive(refs, repo_path) &&
+    GitlabCustomHook.new(repo_path, key_id).pre_receive(refs) &&
     GitlabReferenceCounter.new(repo_path).increase
   exit 0
 else
diff --git a/hooks/update b/hooks/update
index 223575d..4c2fc08 100755
--- a/hooks/update
+++ b/hooks/update
@@ -11,7 +11,7 @@ key_id    = ENV.delete('GL_ID')
 
 require_relative '../lib/gitlab_custom_hook'
 
-if GitlabCustomHook.new(key_id).update(ref_name, old_value, new_value, repo_path)
+if GitlabCustomHook.new(repo_path, key_id).update(ref_name, old_value, new_value)
   exit 0
 else
   exit 1
diff --git a/lib/gitlab_custom_hook.rb b/lib/gitlab_custom_hook.rb
index e84d702..0187e1e 100644
--- a/lib/gitlab_custom_hook.rb
+++ b/lib/gitlab_custom_hook.rb
@@ -5,26 +5,27 @@ require_relative 'gitlab_metrics'
 class GitlabCustomHook
   attr_reader :vars
 
-  def initialize(key_id)
+  def initialize(repo_path, key_id)
+    @repo_path = repo_path
     @vars = { 'GL_ID' => key_id }
   end
 
-  def pre_receive(changes, repo_path)
-    hook = hook_file('pre-receive', repo_path)
+  def pre_receive(changes)
+    hook = hook_file('pre-receive', @repo_path)
     return true if hook.nil?
 
     GitlabMetrics.measure("pre-receive-hook") { call_receive_hook(hook, changes) }
   end
 
-  def post_receive(changes, repo_path)
-    hook = hook_file('post-receive', repo_path)
+  def post_receive(changes)
+    hook = hook_file('post-receive', @repo_path)
     return true if hook.nil?
 
     GitlabMetrics.measure("post-receive-hook") { call_receive_hook(hook, changes) }
   end
 
-  def update(ref_name, old_value, new_value, repo_path)
-    hook = hook_file('update', repo_path)
+  def update(ref_name, old_value, new_value)
+    hook = hook_file('update', @repo_path)
     return true if hook.nil?
 
     GitlabMetrics.measure("update-hook") { system(vars, hook, ref_name, old_value, new_value) }
diff --git a/spec/gitlab_custom_hook_spec.rb b/spec/gitlab_custom_hook_spec.rb
index f93c8b4..328b925 100644
--- a/spec/gitlab_custom_hook_spec.rb
+++ b/spec/gitlab_custom_hook_spec.rb
@@ -4,14 +4,14 @@ require 'pry'
 require 'gitlab_custom_hook'
 
 describe GitlabCustomHook do
-  let(:gitlab_custom_hook) { GitlabCustomHook.new('key_1') }
+  let(:gitlab_custom_hook) { GitlabCustomHook.new('repo_path', 'key_1') }
   let(:hook_path) { File.join(ROOT_PATH, 'spec/support/gl_id_test_hook') }
 
   context 'pre_receive hook' do
     it 'passes GL_ID variable to hook' do
       allow(gitlab_custom_hook).to receive(:hook_file).and_return(hook_path)
 
-      expect(gitlab_custom_hook.pre_receive('changes', 'repo_path')).to be_true
+      expect(gitlab_custom_hook.pre_receive('changes')).to be_true
     end
   end
 
@@ -19,7 +19,7 @@ describe GitlabCustomHook do
     it 'passes GL_ID variable to hook' do
       allow(gitlab_custom_hook).to receive(:hook_file).and_return(hook_path)
 
-      expect(gitlab_custom_hook.post_receive('changes', 'repo_path')).to be_true
+      expect(gitlab_custom_hook.post_receive('changes')).to be_true
     end
   end
 
@@ -27,7 +27,7 @@ describe GitlabCustomHook do
     it 'passes GL_ID variable to hook' do
       allow(gitlab_custom_hook).to receive(:hook_file).and_return(hook_path)
 
-      expect(gitlab_custom_hook.update('master', '', '', 'repo_path')).to be_true
+      expect(gitlab_custom_hook.update('master', '', '')).to be_true
     end
   end
 end
-- 
GitLab


From 0e409ee49b1d68ea949da2d0504f325439ad53b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dirk=20H=C3=B6rner?= <dirker@gmail.com>
Date: Mon, 5 Sep 2016 12:06:32 +0200
Subject: [PATCH 03/21] custom_hook: add support for global custom hooks

This commit adds the option of having another set of global custom hooks
along with the already supported repository local custom hooks.

The repository local custom hook is executed first (if available). If
successful, execution continues with the global custom hook (if available).
This way, local custom hooks get priority over global custom hooks.

Global custom hooks can be enabled by placing an executable file into the
"custom_hooks" directory within gitlab-shell (create if it does not exist,
yet).
---
 .gitignore                |  1 +
 hooks/post-receive        |  1 +
 hooks/pre-receive         |  1 +
 hooks/update              |  1 +
 lib/gitlab_custom_hook.rb | 34 ++++++++++++++++++++++------------
 5 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/.gitignore b/.gitignore
index 62e2cd1..995e19d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@ coverage/
 .bundle
 tags
 .bundle/
+custom_hooks
diff --git a/hooks/post-receive b/hooks/post-receive
index 7877306..16a6fa9 100755
--- a/hooks/post-receive
+++ b/hooks/post-receive
@@ -7,6 +7,7 @@ refs = $stdin.read
 key_id  = ENV.delete('GL_ID')
 repo_path = Dir.pwd
 
+require_relative '../lib/gitlab_init'
 require_relative '../lib/gitlab_custom_hook'
 require_relative '../lib/gitlab_post_receive'
 
diff --git a/hooks/pre-receive b/hooks/pre-receive
index 1b16fd0..97b9669 100755
--- a/hooks/pre-receive
+++ b/hooks/pre-receive
@@ -8,6 +8,7 @@ key_id = ENV.delete('GL_ID')
 protocol = ENV.delete('GL_PROTOCOL')
 repo_path = Dir.pwd
 
+require_relative '../lib/gitlab_init'
 require_relative '../lib/gitlab_custom_hook'
 require_relative '../lib/gitlab_reference_counter'
 require_relative '../lib/gitlab_access'
diff --git a/hooks/update b/hooks/update
index 4c2fc08..e569446 100755
--- a/hooks/update
+++ b/hooks/update
@@ -9,6 +9,7 @@ new_value = ARGV[2]
 repo_path = Dir.pwd
 key_id    = ENV.delete('GL_ID')
 
+require_relative '../lib/gitlab_init'
 require_relative '../lib/gitlab_custom_hook'
 
 if GitlabCustomHook.new(repo_path, key_id).update(ref_name, old_value, new_value)
diff --git a/lib/gitlab_custom_hook.rb b/lib/gitlab_custom_hook.rb
index 0187e1e..f7ae6de 100644
--- a/lib/gitlab_custom_hook.rb
+++ b/lib/gitlab_custom_hook.rb
@@ -11,28 +11,38 @@ class GitlabCustomHook
   end
 
   def pre_receive(changes)
-    hook = hook_file('pre-receive', @repo_path)
-    return true if hook.nil?
-
-    GitlabMetrics.measure("pre-receive-hook") { call_receive_hook(hook, changes) }
+    GitlabMetrics.measure("pre-receive-hook") do
+      find_hooks('pre-receive').all? do |hook|
+        call_receive_hook(hook, changes)
+      end
+    end
   end
 
   def post_receive(changes)
-    hook = hook_file('post-receive', @repo_path)
-    return true if hook.nil?
-
-    GitlabMetrics.measure("post-receive-hook") { call_receive_hook(hook, changes) }
+    GitlabMetrics.measure("post-receive-hook") do
+      find_hooks('post-receive').all? do |hook|
+        call_receive_hook(hook, changes)
+      end
+    end
   end
 
   def update(ref_name, old_value, new_value)
-    hook = hook_file('update', @repo_path)
-    return true if hook.nil?
-
-    GitlabMetrics.measure("update-hook") { system(vars, hook, ref_name, old_value, new_value) }
+    GitlabMetrics.measure("update-hook") do
+      find_hooks('update').all? do |hook|
+        system(vars, hook, ref_name, old_value, new_value)
+      end
+    end
   end
 
   private
 
+  def find_hooks(hook_name)
+    [
+      hook_file(hook_name, @repo_path),
+      hook_file(hook_name, ROOT_PATH)
+    ].compact
+  end
+
   def call_receive_hook(hook, changes)
     # Prepare the hook subprocess. Attach a pipe to its stdin, and merge
     # both its stdout and stderr into our own stdout.
-- 
GitLab


From c5cf54392a8853c07352a54f94a754a61259124f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dirk=20H=C3=B6rner?= <dirker@gmail.com>
Date: Fri, 9 Sep 2016 13:57:21 +0200
Subject: [PATCH 04/21] spec: add tests for global custom hooks

---
 spec/gitlab_custom_hook_spec.rb | 174 +++++++++++++++++++++++++++++---
 spec/support/hook_fail          |   3 +
 spec/support/hook_ok            |   3 +
 3 files changed, 165 insertions(+), 15 deletions(-)
 create mode 100755 spec/support/hook_fail
 create mode 100755 spec/support/hook_ok

diff --git a/spec/gitlab_custom_hook_spec.rb b/spec/gitlab_custom_hook_spec.rb
index 328b925..a456709 100644
--- a/spec/gitlab_custom_hook_spec.rb
+++ b/spec/gitlab_custom_hook_spec.rb
@@ -1,33 +1,177 @@
 # coding: utf-8
 require 'spec_helper'
-require 'pry'
 require 'gitlab_custom_hook'
 
 describe GitlabCustomHook do
-  let(:gitlab_custom_hook) { GitlabCustomHook.new('repo_path', 'key_1') }
-  let(:hook_path) { File.join(ROOT_PATH, 'spec/support/gl_id_test_hook') }
+  let(:tmp_repo_path) { File.join(ROOT_PATH, 'tmp', 'repo.git') }
+  let(:tmp_root_path) { File.join(ROOT_PATH, 'tmp') }
+  let(:hook_ok) { File.join(ROOT_PATH, 'spec', 'support', 'hook_ok') }
+  let(:hook_fail) { File.join(ROOT_PATH, 'spec', 'support', 'hook_fail') }
 
-  context 'pre_receive hook' do
-    it 'passes GL_ID variable to hook' do
-      allow(gitlab_custom_hook).to receive(:hook_file).and_return(hook_path)
+  let(:vars) { {"GL_ID" => "key_1"} }
+  let(:old_value) { "old-value" }
+  let(:new_value) { "new-value" }
+  let(:ref_name) { "name/of/ref" }
+  let(:changes) { "#{old_value} #{new_value} #{ref_name}\n" }
 
-      expect(gitlab_custom_hook.pre_receive('changes')).to be_true
+  let(:gitlab_custom_hook) { GitlabCustomHook.new(tmp_repo_path, 'key_1') }
+
+  before do
+    FileUtils.mkdir_p(File.join(tmp_repo_path, 'custom_hooks'))
+    FileUtils.mkdir_p(File.join(tmp_root_path, 'custom_hooks'))
+  end
+
+  after do
+    FileUtils.rm_rf(File.join(tmp_repo_path, 'custom_hooks'))
+    FileUtils.rm_rf(File.join(tmp_root_path, 'custom_hooks'))
+  end
+
+  context 'with gl_id_test_hook' do
+    let(:hook_path) { File.join(ROOT_PATH, 'spec/support/gl_id_test_hook') }
+
+    context 'pre_receive hook' do
+      it 'passes GL_ID variable to hook' do
+        allow(gitlab_custom_hook).to receive(:hook_file).and_return(hook_path)
+
+        expect(gitlab_custom_hook.pre_receive(changes)).to be_true
+      end
+    end
+
+    context 'post_receive hook' do
+      it 'passes GL_ID variable to hook' do
+        allow(gitlab_custom_hook).to receive(:hook_file).and_return(hook_path)
+
+        expect(gitlab_custom_hook.post_receive(changes)).to be_true
+      end
+    end
+
+    context 'update hook' do
+      it 'passes GL_ID variable to hook' do
+        allow(gitlab_custom_hook).to receive(:hook_file).and_return(hook_path)
+
+        expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to be_true
+      end
+    end
+  end
+
+  context "having no hooks" do
+    it "returns true" do
+      stub_const("ROOT_PATH", tmp_root_path)
+      expect(gitlab_custom_hook.pre_receive(changes)).to eq(true)
+      expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to eq(true)
+      expect(gitlab_custom_hook.post_receive(changes)).to eq(true)
+    end
+  end
+
+  context "having only ok repo hooks" do
+    before do
+      create_hooks(tmp_repo_path, hook_ok)
+    end
+
+    it "returns true" do
+      stub_const("ROOT_PATH", tmp_root_path)
+      expect(gitlab_custom_hook.pre_receive(changes)).to eq(true)
+      expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to eq(true)
+      expect(gitlab_custom_hook.post_receive(changes)).to eq(true)
+    end
+  end
+
+  context "having both ok repo and global hooks" do
+    before do
+      create_hooks(tmp_repo_path, hook_ok)
+      create_hooks(tmp_root_path, hook_ok)
+    end
+
+    it "returns true" do
+      stub_const("ROOT_PATH", tmp_root_path)
+      expect(gitlab_custom_hook.pre_receive(changes)).to eq(true)
+      expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to eq(true)
+      expect(gitlab_custom_hook.post_receive(changes)).to eq(true)
     end
   end
 
-  context 'post_receive hook' do
-    it 'passes GL_ID variable to hook' do
-      allow(gitlab_custom_hook).to receive(:hook_file).and_return(hook_path)
+  context "having failing repo and ok global hooks" do
+    before do
+      create_hooks(tmp_repo_path, hook_fail)
+      create_hooks(tmp_root_path, hook_ok)
+    end
+
+    it "returns false" do
+      stub_const("ROOT_PATH", tmp_root_path)
+      expect(gitlab_custom_hook.pre_receive(changes)).to eq(false)
+      expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to eq(false)
+      expect(gitlab_custom_hook.post_receive(changes)).to eq(false)
+    end
+
+    it "only executes the repo hook" do
+      expect(gitlab_custom_hook).to receive(:call_receive_hook)
+        .with(hook_path(tmp_repo_path, "pre-receive"), changes)
+        .and_call_original
+      expect(gitlab_custom_hook).to receive(:system)
+        .with(vars, hook_path(tmp_repo_path, "update"), ref_name, old_value, new_value)
+        .and_call_original
+      expect(gitlab_custom_hook).to receive(:call_receive_hook)
+        .with(hook_path(tmp_repo_path, "post-receive"), changes)
+        .and_call_original
 
-      expect(gitlab_custom_hook.post_receive('changes')).to be_true
+      stub_const("ROOT_PATH", tmp_root_path)
+      gitlab_custom_hook.pre_receive(changes)
+      gitlab_custom_hook.update(ref_name, old_value, new_value)
+      gitlab_custom_hook.post_receive(changes)
     end
   end
 
-  context 'update hook' do
-    it 'passes GL_ID variable to hook' do
-      allow(gitlab_custom_hook).to receive(:hook_file).and_return(hook_path)
+  context "having ok repo but failing global hooks" do
+    before do
+      create_hooks(tmp_repo_path, hook_ok)
+      create_hooks(tmp_root_path, hook_fail)
+    end
 
-      expect(gitlab_custom_hook.update('master', '', '')).to be_true
+    it "returns false" do
+      stub_const("ROOT_PATH", tmp_root_path)
+      expect(gitlab_custom_hook.pre_receive(changes)).to eq(false)
+      expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to eq(false)
+      expect(gitlab_custom_hook.post_receive(changes)).to eq(false)
     end
+
+    it "executes the relevant hooks" do
+      expect(gitlab_custom_hook).to receive(:call_receive_hook)
+        .with(hook_path(tmp_repo_path, "pre-receive"), changes)
+        .and_call_original
+      expect(gitlab_custom_hook).to receive(:call_receive_hook)
+        .with(hook_path(tmp_root_path, "pre-receive"), changes)
+        .and_call_original
+      expect(gitlab_custom_hook).to receive(:system)
+        .with(vars, hook_path(tmp_repo_path, "update"), ref_name, old_value, new_value)
+        .and_call_original
+      expect(gitlab_custom_hook).to receive(:system)
+        .with(vars, hook_path(tmp_root_path, "update"), ref_name, old_value, new_value)
+        .and_call_original
+      expect(gitlab_custom_hook).to receive(:call_receive_hook)
+        .with(hook_path(tmp_repo_path, "post-receive"), changes)
+        .and_call_original
+      expect(gitlab_custom_hook).to receive(:call_receive_hook)
+        .with(hook_path(tmp_root_path, "post-receive"), changes)
+        .and_call_original
+
+      stub_const("ROOT_PATH", tmp_root_path)
+      gitlab_custom_hook.pre_receive(changes)
+      gitlab_custom_hook.update(ref_name, old_value, new_value)
+      gitlab_custom_hook.post_receive(changes)
+    end
+  end
+
+  def hook_path(path, name)
+    File.join(path, 'custom_hooks', name)
+  end
+
+  def create_hook(path, name, which)
+    FileUtils.ln_sf(which, hook_path(path, name))
+  end
+
+  def create_hooks(path, which)
+    create_hook(path, 'pre-receive', which)
+    create_hook(path, 'update', which)
+    create_hook(path, 'post-receive', which)
   end
 end
diff --git a/spec/support/hook_fail b/spec/support/hook_fail
new file mode 100755
index 0000000..4420796
--- /dev/null
+++ b/spec/support/hook_fail
@@ -0,0 +1,3 @@
+#!/bin/bash
+#echo "fail: $0"
+exit 1
diff --git a/spec/support/hook_ok b/spec/support/hook_ok
new file mode 100755
index 0000000..eb1e3bc
--- /dev/null
+++ b/spec/support/hook_ok
@@ -0,0 +1,3 @@
+#!/bin/bash
+#echo "ok:   $0"
+exit 0
-- 
GitLab


From dbd4bc264baf611b91c03d799f77376963633551 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Wed, 26 Oct 2016 21:13:01 +0300
Subject: [PATCH 05/21] custom_hook: chain custom hooks

update hooks lookup to use <hook>.d/* from repository hooks dir

the order would be:

1. <repository>.git/custom_hooks/<hook_name> - per project hook
2. <repository>.git/custom_hooks/<hook_name>.d/* - per project hooks
3. <repository>.git/hooks/<hook_name>.d/* - global hooks

only executable files are matched and backup files excluded (*~)
and the resulting list is sorted per each lookup
---
 lib/gitlab_custom_hook.rb | 48 ++++++++++++++++++++++++++++++---------
 1 file changed, 37 insertions(+), 11 deletions(-)

diff --git a/lib/gitlab_custom_hook.rb b/lib/gitlab_custom_hook.rb
index f7ae6de..451d5b4 100644
--- a/lib/gitlab_custom_hook.rb
+++ b/lib/gitlab_custom_hook.rb
@@ -36,13 +36,6 @@ class GitlabCustomHook
 
   private
 
-  def find_hooks(hook_name)
-    [
-      hook_file(hook_name, @repo_path),
-      hook_file(hook_name, ROOT_PATH)
-    ].compact
-  end
-
   def call_receive_hook(hook, changes)
     # Prepare the hook subprocess. Attach a pipe to its stdin, and merge
     # both its stdout and stderr into our own stdout.
@@ -64,9 +57,42 @@ class GitlabCustomHook
     $?.success?
   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.executable?(hook_file)
+  # lookup hook files in this order:
+  #
+  # 1. <repository>.git/custom_hooks/<hook_name> - per project hook
+  # 2. <repository>.git/custom_hooks/<hook_name>.d/* - per project hooks
+  # 3. <repository>.git/hooks/<hook_name>.d/* - global hooks
+  #
+  def find_hooks(hook_name)
+    hook_files = []
+
+    # <repository>.git/custom_hooks/<hook_name>
+    hook_file = File.join(@repo_path, 'custom_hooks', hook_name)
+    hook_files.push(hook_file) if File.executable?(hook_file)
+
+    # <repository>.git/custom_hooks/<hook_name>.d/*
+    hook_path = File.join(@repo_path, 'custom_hooks', "#{hook_name}.d")
+    if Dir.exist?(hook_path)
+      hook_files += match_hook_files(hook_path)
+    end
+
+    # <repository>.git/hooks/<hook_name>.d/*
+    hook_path = File.join(@repo_path, 'hooks', "#{hook_name}.d")
+    if Dir.exist?(hook_path)
+      hook_files += match_hook_files(hook_path)
+    end
+
+    hook_files
+  end
+
+  # match files from path:
+  # 1. file must be executable
+  # 2. file must not match backup file
+  #
+  # the resulting list is sorted
+  def match_hook_files(path)
+    Dir["#{path}/*"].select do |f|
+      !f[/~$/] && File.executable?(f)
+    end.sort
   end
 end
-- 
GitLab


From e60a0ee7bb00962a5e1ee3b49c6686f86dd3ea60 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Wed, 9 Nov 2016 22:22:16 +0200
Subject: [PATCH 06/21] spec: updated tests to match current code

---
 Gemfile.lock                    |   3 -
 spec/gitlab_custom_hook_spec.rb | 101 +++++++++++++++++++++-----------
 2 files changed, 67 insertions(+), 37 deletions(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 0fbade8..9bf7d9f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -81,6 +81,3 @@ DEPENDENCIES
   rubocop (= 0.28.0)
   vcr
   webmock
-
-BUNDLED WITH
-   1.11.2
diff --git a/spec/gitlab_custom_hook_spec.rb b/spec/gitlab_custom_hook_spec.rb
index a456709..701218c 100644
--- a/spec/gitlab_custom_hook_spec.rb
+++ b/spec/gitlab_custom_hook_spec.rb
@@ -16,14 +16,34 @@ describe GitlabCustomHook do
 
   let(:gitlab_custom_hook) { GitlabCustomHook.new(tmp_repo_path, 'key_1') }
 
+  # setup paths
+  # <repository>.git/hooks/ - symlink to gitlab-shell/hooks global dir
+  # <repository>.git/hooks/<hook_name> - executed by git itself, this is gitlab-shell/hooks/<hook_name>
+  # <repository>.git/hooks/<hook_name>.d/* - global hooks: all executable files (minus editor backup files)
+  # <repository>.git/custom_hooks/<hook_name> - per project hook (this is already existing behavior)
+  # <repository>.git/custom_hooks/<hook_name>.d/* - per project hooks
+  #
+  # custom hooks are invoked in such way that first failure prevents other scripts being ran
+  # as global scripts are ran first, failing global skips repo hooks
+
   before do
+    FileUtils.mkdir_p(File.join(tmp_root_path, 'hooks'))
+    FileUtils.mkdir_p(File.join(tmp_root_path, 'hooks', 'update.d'))
+    FileUtils.mkdir_p(File.join(tmp_root_path, 'hooks', 'pre-receive.d'))
+    FileUtils.mkdir_p(File.join(tmp_root_path, 'hooks', 'post-receive.d'))
+
+    FileUtils.symlink(File.join(tmp_root_path, 'hooks'), File.join(tmp_repo_path, 'hooks'))
     FileUtils.mkdir_p(File.join(tmp_repo_path, 'custom_hooks'))
-    FileUtils.mkdir_p(File.join(tmp_root_path, 'custom_hooks'))
+    FileUtils.mkdir_p(File.join(tmp_repo_path, 'custom_hooks', 'update.d'))
+    FileUtils.mkdir_p(File.join(tmp_repo_path, 'custom_hooks', 'pre-receive.d'))
+    FileUtils.mkdir_p(File.join(tmp_repo_path, 'custom_hooks', 'post-receive.d'))
   end
 
   after do
     FileUtils.rm_rf(File.join(tmp_repo_path, 'custom_hooks'))
-    FileUtils.rm_rf(File.join(tmp_root_path, 'custom_hooks'))
+    FileUtils.rm_rf(File.join(tmp_repo_path, 'hooks'))
+    FileUtils.rm_rf(File.join(tmp_repo_path, 'hooks.d'))
+    FileUtils.rm_rf(File.join(tmp_root_path, 'hooks'))
   end
 
   context 'with gl_id_test_hook' do
@@ -56,7 +76,6 @@ describe GitlabCustomHook do
 
   context "having no hooks" do
     it "returns true" do
-      stub_const("ROOT_PATH", tmp_root_path)
       expect(gitlab_custom_hook.pre_receive(changes)).to eq(true)
       expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to eq(true)
       expect(gitlab_custom_hook.post_receive(changes)).to eq(true)
@@ -65,11 +84,10 @@ describe GitlabCustomHook do
 
   context "having only ok repo hooks" do
     before do
-      create_hooks(tmp_repo_path, hook_ok)
+      create_repo_hooks(tmp_repo_path, hook_ok)
     end
 
     it "returns true" do
-      stub_const("ROOT_PATH", tmp_root_path)
       expect(gitlab_custom_hook.pre_receive(changes)).to eq(true)
       expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to eq(true)
       expect(gitlab_custom_hook.post_receive(changes)).to eq(true)
@@ -78,12 +96,11 @@ describe GitlabCustomHook do
 
   context "having both ok repo and global hooks" do
     before do
-      create_hooks(tmp_repo_path, hook_ok)
-      create_hooks(tmp_root_path, hook_ok)
+      create_repo_hooks(tmp_repo_path, hook_ok)
+      create_global_hooks_d(tmp_root_path, hook_ok)
     end
 
     it "returns true" do
-      stub_const("ROOT_PATH", tmp_root_path)
       expect(gitlab_custom_hook.pre_receive(changes)).to eq(true)
       expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to eq(true)
       expect(gitlab_custom_hook.post_receive(changes)).to eq(true)
@@ -92,29 +109,27 @@ describe GitlabCustomHook do
 
   context "having failing repo and ok global hooks" do
     before do
-      create_hooks(tmp_repo_path, hook_fail)
-      create_hooks(tmp_root_path, hook_ok)
+      create_repo_hooks_d(tmp_repo_path, hook_fail)
+      create_global_hooks_d(tmp_repo_path, hook_ok)
     end
 
     it "returns false" do
-      stub_const("ROOT_PATH", tmp_root_path)
       expect(gitlab_custom_hook.pre_receive(changes)).to eq(false)
       expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to eq(false)
       expect(gitlab_custom_hook.post_receive(changes)).to eq(false)
     end
 
-    it "only executes the repo hook" do
+    it "only executes the global hook" do
       expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path(tmp_repo_path, "pre-receive"), changes)
+        .with(hook_path(tmp_repo_path, "custom_hooks/pre-receive.d/hook"), changes)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:system)
-        .with(vars, hook_path(tmp_repo_path, "update"), ref_name, old_value, new_value)
+        .with(vars, hook_path(tmp_repo_path, "custom_hooks/update.d/hook"), ref_name, old_value, new_value)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path(tmp_repo_path, "post-receive"), changes)
+        .with(hook_path(tmp_repo_path, "custom_hooks/post-receive.d/hook"), changes)
         .and_call_original
 
-      stub_const("ROOT_PATH", tmp_root_path)
       gitlab_custom_hook.pre_receive(changes)
       gitlab_custom_hook.update(ref_name, old_value, new_value)
       gitlab_custom_hook.post_receive(changes)
@@ -123,12 +138,11 @@ describe GitlabCustomHook do
 
   context "having ok repo but failing global hooks" do
     before do
-      create_hooks(tmp_repo_path, hook_ok)
-      create_hooks(tmp_root_path, hook_fail)
+      create_repo_hooks_d(tmp_repo_path, hook_ok)
+      create_global_hooks_d(tmp_repo_path, hook_fail)
     end
 
     it "returns false" do
-      stub_const("ROOT_PATH", tmp_root_path)
       expect(gitlab_custom_hook.pre_receive(changes)).to eq(false)
       expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to eq(false)
       expect(gitlab_custom_hook.post_receive(changes)).to eq(false)
@@ -136,42 +150,61 @@ describe GitlabCustomHook do
 
     it "executes the relevant hooks" do
       expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path(tmp_repo_path, "pre-receive"), changes)
+        .with(hook_path(tmp_repo_path, "hooks/pre-receive.d/hook"), changes)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path(tmp_root_path, "pre-receive"), changes)
+        .with(hook_path(tmp_repo_path, "custom_hooks/pre-receive.d/hook"), changes)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:system)
-        .with(vars, hook_path(tmp_repo_path, "update"), ref_name, old_value, new_value)
+        .with(vars, hook_path(tmp_repo_path, "hooks/update.d/hook"), ref_name, old_value, new_value)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:system)
-        .with(vars, hook_path(tmp_root_path, "update"), ref_name, old_value, new_value)
+        .with(vars, hook_path(tmp_repo_path, "custom_hooks/update.d/hook"), ref_name, old_value, new_value)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path(tmp_repo_path, "post-receive"), changes)
+        .with(hook_path(tmp_repo_path, "hooks/post-receive.d/hook"), changes)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path(tmp_root_path, "post-receive"), changes)
+        .with(hook_path(tmp_repo_path, "custom_hooks/post-receive.d/hook"), changes)
         .and_call_original
 
-      stub_const("ROOT_PATH", tmp_root_path)
       gitlab_custom_hook.pre_receive(changes)
       gitlab_custom_hook.update(ref_name, old_value, new_value)
       gitlab_custom_hook.post_receive(changes)
     end
   end
 
-  def hook_path(path, name)
-    File.join(path, 'custom_hooks', name)
+  def hook_path(repo_path, path)
+    File.join(repo_path, path.split('/'))
+  end
+
+  def create_hook(repo_path, path, which)
+    FileUtils.ln_sf(which, hook_path(repo_path, path))
+  end
+
+  def create_global_hooks(path, which)
+    # should not be tested, as the "global hooks" is gitlab-shell itself (gitlab-shell/hooks/<hook_name>)
+    raise "no method, this is gitlab-shell itself"
+  end
+
+  # global hooks multiplexed
+  def create_global_hooks_d(path, which)
+    create_hook(path, 'hooks/pre-receive.d/hook', which)
+    create_hook(path, 'hooks/update.d/hook', which)
+    create_hook(path, 'hooks/post-receive.d/hook', which)
   end
 
-  def create_hook(path, name, which)
-    FileUtils.ln_sf(which, hook_path(path, name))
+  # repo hooks
+  def create_repo_hooks(path, which)
+    create_hook(path, 'custom_hooks/pre-receive', which)
+    create_hook(path, 'custom_hooks/update', which)
+    create_hook(path, 'custom_hooks/post-receive', which)
   end
 
-  def create_hooks(path, which)
-    create_hook(path, 'pre-receive', which)
-    create_hook(path, 'update', which)
-    create_hook(path, 'post-receive', which)
+  # repo hooks multiplexed
+  def create_repo_hooks_d(path, which)
+    create_hook(path, 'custom_hooks/pre-receive.d/hook', which)
+    create_hook(path, 'custom_hooks/update.d/hook', which)
+    create_hook(path, 'custom_hooks/post-receive.d/hook', which)
   end
 end
-- 
GitLab


From 5b740fcf2978decca31a459184b16531d7517e35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Wed, 16 Nov 2016 02:05:25 +0200
Subject: [PATCH 07/21] remove no longer needed gitlab_init

---
 hooks/post-receive | 1 -
 hooks/pre-receive  | 1 -
 hooks/update       | 1 -
 3 files changed, 3 deletions(-)

diff --git a/hooks/post-receive b/hooks/post-receive
index 16a6fa9..7877306 100755
--- a/hooks/post-receive
+++ b/hooks/post-receive
@@ -7,7 +7,6 @@ refs = $stdin.read
 key_id  = ENV.delete('GL_ID')
 repo_path = Dir.pwd
 
-require_relative '../lib/gitlab_init'
 require_relative '../lib/gitlab_custom_hook'
 require_relative '../lib/gitlab_post_receive'
 
diff --git a/hooks/pre-receive b/hooks/pre-receive
index 97b9669..1b16fd0 100755
--- a/hooks/pre-receive
+++ b/hooks/pre-receive
@@ -8,7 +8,6 @@ key_id = ENV.delete('GL_ID')
 protocol = ENV.delete('GL_PROTOCOL')
 repo_path = Dir.pwd
 
-require_relative '../lib/gitlab_init'
 require_relative '../lib/gitlab_custom_hook'
 require_relative '../lib/gitlab_reference_counter'
 require_relative '../lib/gitlab_access'
diff --git a/hooks/update b/hooks/update
index e569446..4c2fc08 100755
--- a/hooks/update
+++ b/hooks/update
@@ -9,7 +9,6 @@ new_value = ARGV[2]
 repo_path = Dir.pwd
 key_id    = ENV.delete('GL_ID')
 
-require_relative '../lib/gitlab_init'
 require_relative '../lib/gitlab_custom_hook'
 
 if GitlabCustomHook.new(repo_path, key_id).update(ref_name, old_value, new_value)
-- 
GitLab


From d87c4b9c0d3aed4a23d74f340ece2c4bff83101b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Wed, 16 Nov 2016 02:08:50 +0200
Subject: [PATCH 08/21] use String.end_with? instead of regexp

---
 lib/gitlab_custom_hook.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/gitlab_custom_hook.rb b/lib/gitlab_custom_hook.rb
index 451d5b4..6265ec1 100644
--- a/lib/gitlab_custom_hook.rb
+++ b/lib/gitlab_custom_hook.rb
@@ -92,7 +92,7 @@ class GitlabCustomHook
   # the resulting list is sorted
   def match_hook_files(path)
     Dir["#{path}/*"].select do |f|
-      !f[/~$/] && File.executable?(f)
+      !f.end_with?('~') && File.executable?(f)
     end.sort
   end
 end
-- 
GitLab


From 872c66e977b3041e0c18543d2e00a255e8e89e41 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Wed, 16 Nov 2016 02:12:07 +0200
Subject: [PATCH 09/21] avoid Dir.exists? duplication by moving the check to
 match_hook_files

---
 lib/gitlab_custom_hook.rb | 10 ++++------
 1 file changed, 4 insertions(+), 6 deletions(-)

diff --git a/lib/gitlab_custom_hook.rb b/lib/gitlab_custom_hook.rb
index 6265ec1..a48ad12 100644
--- a/lib/gitlab_custom_hook.rb
+++ b/lib/gitlab_custom_hook.rb
@@ -72,15 +72,11 @@ class GitlabCustomHook
 
     # <repository>.git/custom_hooks/<hook_name>.d/*
     hook_path = File.join(@repo_path, 'custom_hooks', "#{hook_name}.d")
-    if Dir.exist?(hook_path)
-      hook_files += match_hook_files(hook_path)
-    end
+    hook_files += match_hook_files(hook_path)
 
     # <repository>.git/hooks/<hook_name>.d/*
     hook_path = File.join(@repo_path, 'hooks', "#{hook_name}.d")
-    if Dir.exist?(hook_path)
-      hook_files += match_hook_files(hook_path)
-    end
+    hook_files += match_hook_files(hook_path)
 
     hook_files
   end
@@ -91,6 +87,8 @@ class GitlabCustomHook
   #
   # the resulting list is sorted
   def match_hook_files(path)
+    return [] unless Dir.exist?(path)
+
     Dir["#{path}/*"].select do |f|
       !f.end_with?('~') && File.executable?(f)
     end.sort
-- 
GitLab


From f8e64aa2baa7fa30be43ab6759233d4082be9f5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Wed, 16 Nov 2016 02:13:10 +0200
Subject: [PATCH 10/21] remove unused create_global_hooks

---
 spec/gitlab_custom_hook_spec.rb | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/spec/gitlab_custom_hook_spec.rb b/spec/gitlab_custom_hook_spec.rb
index 701218c..d91690f 100644
--- a/spec/gitlab_custom_hook_spec.rb
+++ b/spec/gitlab_custom_hook_spec.rb
@@ -182,11 +182,6 @@ describe GitlabCustomHook do
     FileUtils.ln_sf(which, hook_path(repo_path, path))
   end
 
-  def create_global_hooks(path, which)
-    # should not be tested, as the "global hooks" is gitlab-shell itself (gitlab-shell/hooks/<hook_name>)
-    raise "no method, this is gitlab-shell itself"
-  end
-
   # global hooks multiplexed
   def create_global_hooks_d(path, which)
     create_hook(path, 'hooks/pre-receive.d/hook', which)
-- 
GitLab


From 23b90272acc4bcd0c12e4127c0697ede620e503a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Wed, 16 Nov 2016 02:14:28 +0200
Subject: [PATCH 11/21] improve wording by using successful instead of ok

---
 spec/gitlab_custom_hook_spec.rb | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/spec/gitlab_custom_hook_spec.rb b/spec/gitlab_custom_hook_spec.rb
index d91690f..0718f16 100644
--- a/spec/gitlab_custom_hook_spec.rb
+++ b/spec/gitlab_custom_hook_spec.rb
@@ -82,7 +82,7 @@ describe GitlabCustomHook do
     end
   end
 
-  context "having only ok repo hooks" do
+  context "having only successful repo hooks" do
     before do
       create_repo_hooks(tmp_repo_path, hook_ok)
     end
@@ -94,7 +94,7 @@ describe GitlabCustomHook do
     end
   end
 
-  context "having both ok repo and global hooks" do
+  context "having both successful repo and global hooks" do
     before do
       create_repo_hooks(tmp_repo_path, hook_ok)
       create_global_hooks_d(tmp_root_path, hook_ok)
@@ -107,7 +107,7 @@ describe GitlabCustomHook do
     end
   end
 
-  context "having failing repo and ok global hooks" do
+  context "having failing repo and successful global hooks" do
     before do
       create_repo_hooks_d(tmp_repo_path, hook_fail)
       create_global_hooks_d(tmp_repo_path, hook_ok)
@@ -136,7 +136,7 @@ describe GitlabCustomHook do
     end
   end
 
-  context "having ok repo but failing global hooks" do
+  context "having successful repo but failing global hooks" do
     before do
       create_repo_hooks_d(tmp_repo_path, hook_ok)
       create_global_hooks_d(tmp_repo_path, hook_fail)
-- 
GitLab


From 91ba63969a418583454b54879afb0f335e9e06c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Wed, 16 Nov 2016 21:04:34 +0200
Subject: [PATCH 12/21] changelog entry

---
 CHANGELOG | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/CHANGELOG b/CHANGELOG
index e520a99..7cb6b19 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,9 @@
+v4.1.0
+  - Add support for global custom hooks and chanined hooks (Elan Ruusamäe, Dirk Hörner), !93, !89, #32
+
 v4.0.3
   - Fetch repositories with `--prune` option by default
+
 v4.0.2
   - Fix gitlab_custom_hook dependencies
 
-- 
GitLab


From 17691041ae723145f78a081a16809d0afb8136f1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Wed, 16 Nov 2016 23:12:55 +0200
Subject: [PATCH 13/21] move helpers to top

---
 spec/gitlab_custom_hook_spec.rb | 58 ++++++++++++++++-----------------
 1 file changed, 29 insertions(+), 29 deletions(-)

diff --git a/spec/gitlab_custom_hook_spec.rb b/spec/gitlab_custom_hook_spec.rb
index 0718f16..69809b9 100644
--- a/spec/gitlab_custom_hook_spec.rb
+++ b/spec/gitlab_custom_hook_spec.rb
@@ -16,6 +16,35 @@ describe GitlabCustomHook do
 
   let(:gitlab_custom_hook) { GitlabCustomHook.new(tmp_repo_path, 'key_1') }
 
+  def hook_path(repo_path, path)
+    File.join(repo_path, path.split('/'))
+  end
+
+  def create_hook(repo_path, path, which)
+    FileUtils.ln_sf(which, hook_path(repo_path, path))
+  end
+
+  # global hooks multiplexed
+  def create_global_hooks_d(path, which)
+    create_hook(path, 'hooks/pre-receive.d/hook', which)
+    create_hook(path, 'hooks/update.d/hook', which)
+    create_hook(path, 'hooks/post-receive.d/hook', which)
+  end
+
+  # repo hooks
+  def create_repo_hooks(path, which)
+    create_hook(path, 'custom_hooks/pre-receive', which)
+    create_hook(path, 'custom_hooks/update', which)
+    create_hook(path, 'custom_hooks/post-receive', which)
+  end
+
+  # repo hooks multiplexed
+  def create_repo_hooks_d(path, which)
+    create_hook(path, 'custom_hooks/pre-receive.d/hook', which)
+    create_hook(path, 'custom_hooks/update.d/hook', which)
+    create_hook(path, 'custom_hooks/post-receive.d/hook', which)
+  end
+
   # setup paths
   # <repository>.git/hooks/ - symlink to gitlab-shell/hooks global dir
   # <repository>.git/hooks/<hook_name> - executed by git itself, this is gitlab-shell/hooks/<hook_name>
@@ -173,33 +202,4 @@ describe GitlabCustomHook do
       gitlab_custom_hook.post_receive(changes)
     end
   end
-
-  def hook_path(repo_path, path)
-    File.join(repo_path, path.split('/'))
-  end
-
-  def create_hook(repo_path, path, which)
-    FileUtils.ln_sf(which, hook_path(repo_path, path))
-  end
-
-  # global hooks multiplexed
-  def create_global_hooks_d(path, which)
-    create_hook(path, 'hooks/pre-receive.d/hook', which)
-    create_hook(path, 'hooks/update.d/hook', which)
-    create_hook(path, 'hooks/post-receive.d/hook', which)
-  end
-
-  # repo hooks
-  def create_repo_hooks(path, which)
-    create_hook(path, 'custom_hooks/pre-receive', which)
-    create_hook(path, 'custom_hooks/update', which)
-    create_hook(path, 'custom_hooks/post-receive', which)
-  end
-
-  # repo hooks multiplexed
-  def create_repo_hooks_d(path, which)
-    create_hook(path, 'custom_hooks/pre-receive.d/hook', which)
-    create_hook(path, 'custom_hooks/update.d/hook', which)
-    create_hook(path, 'custom_hooks/post-receive.d/hook', which)
-  end
 end
-- 
GitLab


From 5043d13bac184ed1092a67afb33a1b108c5e03d6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Wed, 16 Nov 2016 23:15:19 +0200
Subject: [PATCH 14/21] cleanup dirs in before to fixup aborted tests

---
 spec/gitlab_custom_hook_spec.rb | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/spec/gitlab_custom_hook_spec.rb b/spec/gitlab_custom_hook_spec.rb
index 69809b9..f6b16bb 100644
--- a/spec/gitlab_custom_hook_spec.rb
+++ b/spec/gitlab_custom_hook_spec.rb
@@ -45,6 +45,13 @@ describe GitlabCustomHook do
     create_hook(path, 'custom_hooks/post-receive.d/hook', which)
   end
 
+  def cleanup_hook_setup
+    FileUtils.rm_rf(File.join(tmp_repo_path, 'custom_hooks'))
+    FileUtils.rm_rf(File.join(tmp_repo_path, 'hooks'))
+    FileUtils.rm_rf(File.join(tmp_repo_path, 'hooks.d'))
+    FileUtils.rm_rf(File.join(tmp_root_path, 'hooks'))
+  end
+
   # setup paths
   # <repository>.git/hooks/ - symlink to gitlab-shell/hooks global dir
   # <repository>.git/hooks/<hook_name> - executed by git itself, this is gitlab-shell/hooks/<hook_name>
@@ -56,6 +63,8 @@ describe GitlabCustomHook do
   # as global scripts are ran first, failing global skips repo hooks
 
   before do
+    cleanup_hook_setup
+
     FileUtils.mkdir_p(File.join(tmp_root_path, 'hooks'))
     FileUtils.mkdir_p(File.join(tmp_root_path, 'hooks', 'update.d'))
     FileUtils.mkdir_p(File.join(tmp_root_path, 'hooks', 'pre-receive.d'))
@@ -69,10 +78,7 @@ describe GitlabCustomHook do
   end
 
   after do
-    FileUtils.rm_rf(File.join(tmp_repo_path, 'custom_hooks'))
-    FileUtils.rm_rf(File.join(tmp_repo_path, 'hooks'))
-    FileUtils.rm_rf(File.join(tmp_repo_path, 'hooks.d'))
-    FileUtils.rm_rf(File.join(tmp_root_path, 'hooks'))
+    cleanup_hook_setup
   end
 
   context 'with gl_id_test_hook' do
-- 
GitLab


From 8910d4b069cea1c5d1fa25f192ca20afc062ed9d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Sun, 20 Nov 2016 15:43:00 +0200
Subject: [PATCH 15/21] fix gl_id_test_hook

---
 spec/gitlab_custom_hook_spec.rb | 18 ++++++++----------
 1 file changed, 8 insertions(+), 10 deletions(-)

diff --git a/spec/gitlab_custom_hook_spec.rb b/spec/gitlab_custom_hook_spec.rb
index f6b16bb..e47fae0 100644
--- a/spec/gitlab_custom_hook_spec.rb
+++ b/spec/gitlab_custom_hook_spec.rb
@@ -7,6 +7,7 @@ describe GitlabCustomHook do
   let(:tmp_root_path) { File.join(ROOT_PATH, 'tmp') }
   let(:hook_ok) { File.join(ROOT_PATH, 'spec', 'support', 'hook_ok') }
   let(:hook_fail) { File.join(ROOT_PATH, 'spec', 'support', 'hook_fail') }
+  let(:hook_gl_id) { File.join(ROOT_PATH, 'spec', 'support', 'gl_id_test_hook') }
 
   let(:vars) { {"GL_ID" => "key_1"} }
   let(:old_value) { "old-value" }
@@ -82,29 +83,26 @@ describe GitlabCustomHook do
   end
 
   context 'with gl_id_test_hook' do
-    let(:hook_path) { File.join(ROOT_PATH, 'spec/support/gl_id_test_hook') }
+    before do
+      create_repo_hooks(tmp_repo_path, hook_gl_id)
+      create_global_hooks_d(tmp_root_path, hook_gl_id)
+    end
 
     context 'pre_receive hook' do
       it 'passes GL_ID variable to hook' do
-        allow(gitlab_custom_hook).to receive(:hook_file).and_return(hook_path)
-
-        expect(gitlab_custom_hook.pre_receive(changes)).to be_true
+        expect(gitlab_custom_hook.pre_receive(changes)).to eq(true)
       end
     end
 
     context 'post_receive hook' do
       it 'passes GL_ID variable to hook' do
-        allow(gitlab_custom_hook).to receive(:hook_file).and_return(hook_path)
-
-        expect(gitlab_custom_hook.post_receive(changes)).to be_true
+        expect(gitlab_custom_hook.post_receive(changes)).to eq(true)
       end
     end
 
     context 'update hook' do
       it 'passes GL_ID variable to hook' do
-        allow(gitlab_custom_hook).to receive(:hook_file).and_return(hook_path)
-
-        expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to be_true
+        expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to eq(true)
       end
     end
   end
-- 
GitLab


From a04895942167e65dcc39bf2d9b6557b28f1e5573 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Sun, 20 Nov 2016 15:53:01 +0200
Subject: [PATCH 16/21] spec/custom_hooks: cleanup helpers not to repeat repo
 path parameter

---
 spec/gitlab_custom_hook_spec.rb | 68 ++++++++++++++++-----------------
 1 file changed, 34 insertions(+), 34 deletions(-)

diff --git a/spec/gitlab_custom_hook_spec.rb b/spec/gitlab_custom_hook_spec.rb
index e47fae0..05ec38b 100644
--- a/spec/gitlab_custom_hook_spec.rb
+++ b/spec/gitlab_custom_hook_spec.rb
@@ -17,33 +17,33 @@ describe GitlabCustomHook do
 
   let(:gitlab_custom_hook) { GitlabCustomHook.new(tmp_repo_path, 'key_1') }
 
-  def hook_path(repo_path, path)
-    File.join(repo_path, path.split('/'))
+  def hook_path(path)
+    File.join(tmp_repo_path, path.split('/'))
   end
 
-  def create_hook(repo_path, path, which)
-    FileUtils.ln_sf(which, hook_path(repo_path, path))
+  def create_hook(path, which)
+    FileUtils.ln_sf(which, hook_path(path))
   end
 
   # global hooks multiplexed
-  def create_global_hooks_d(path, which)
-    create_hook(path, 'hooks/pre-receive.d/hook', which)
-    create_hook(path, 'hooks/update.d/hook', which)
-    create_hook(path, 'hooks/post-receive.d/hook', which)
+  def create_global_hooks_d(which)
+    create_hook('hooks/pre-receive.d/hook', which)
+    create_hook('hooks/update.d/hook', which)
+    create_hook('hooks/post-receive.d/hook', which)
   end
 
   # repo hooks
-  def create_repo_hooks(path, which)
-    create_hook(path, 'custom_hooks/pre-receive', which)
-    create_hook(path, 'custom_hooks/update', which)
-    create_hook(path, 'custom_hooks/post-receive', which)
+  def create_repo_hooks(which)
+    create_hook('custom_hooks/pre-receive', which)
+    create_hook('custom_hooks/update', which)
+    create_hook('custom_hooks/post-receive', which)
   end
 
   # repo hooks multiplexed
-  def create_repo_hooks_d(path, which)
-    create_hook(path, 'custom_hooks/pre-receive.d/hook', which)
-    create_hook(path, 'custom_hooks/update.d/hook', which)
-    create_hook(path, 'custom_hooks/post-receive.d/hook', which)
+  def create_repo_hooks_d(which)
+    create_hook('custom_hooks/pre-receive.d/hook', which)
+    create_hook('custom_hooks/update.d/hook', which)
+    create_hook('custom_hooks/post-receive.d/hook', which)
   end
 
   def cleanup_hook_setup
@@ -84,8 +84,8 @@ describe GitlabCustomHook do
 
   context 'with gl_id_test_hook' do
     before do
-      create_repo_hooks(tmp_repo_path, hook_gl_id)
-      create_global_hooks_d(tmp_root_path, hook_gl_id)
+      create_repo_hooks(hook_gl_id)
+      create_global_hooks_d(hook_gl_id)
     end
 
     context 'pre_receive hook' do
@@ -117,7 +117,7 @@ describe GitlabCustomHook do
 
   context "having only successful repo hooks" do
     before do
-      create_repo_hooks(tmp_repo_path, hook_ok)
+      create_repo_hooks(hook_ok)
     end
 
     it "returns true" do
@@ -129,8 +129,8 @@ describe GitlabCustomHook do
 
   context "having both successful repo and global hooks" do
     before do
-      create_repo_hooks(tmp_repo_path, hook_ok)
-      create_global_hooks_d(tmp_root_path, hook_ok)
+      create_repo_hooks(hook_ok)
+      create_global_hooks_d(hook_ok)
     end
 
     it "returns true" do
@@ -142,8 +142,8 @@ describe GitlabCustomHook do
 
   context "having failing repo and successful global hooks" do
     before do
-      create_repo_hooks_d(tmp_repo_path, hook_fail)
-      create_global_hooks_d(tmp_repo_path, hook_ok)
+      create_repo_hooks_d(hook_fail)
+      create_global_hooks_d(hook_ok)
     end
 
     it "returns false" do
@@ -154,13 +154,13 @@ describe GitlabCustomHook do
 
     it "only executes the global hook" do
       expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path(tmp_repo_path, "custom_hooks/pre-receive.d/hook"), changes)
+        .with(hook_path("custom_hooks/pre-receive.d/hook"), changes)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:system)
-        .with(vars, hook_path(tmp_repo_path, "custom_hooks/update.d/hook"), ref_name, old_value, new_value)
+        .with(vars, hook_path("custom_hooks/update.d/hook"), ref_name, old_value, new_value)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path(tmp_repo_path, "custom_hooks/post-receive.d/hook"), changes)
+        .with(hook_path("custom_hooks/post-receive.d/hook"), changes)
         .and_call_original
 
       gitlab_custom_hook.pre_receive(changes)
@@ -171,8 +171,8 @@ describe GitlabCustomHook do
 
   context "having successful repo but failing global hooks" do
     before do
-      create_repo_hooks_d(tmp_repo_path, hook_ok)
-      create_global_hooks_d(tmp_repo_path, hook_fail)
+      create_repo_hooks_d(hook_ok)
+      create_global_hooks_d(hook_fail)
     end
 
     it "returns false" do
@@ -183,22 +183,22 @@ describe GitlabCustomHook do
 
     it "executes the relevant hooks" do
       expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path(tmp_repo_path, "hooks/pre-receive.d/hook"), changes)
+        .with(hook_path("hooks/pre-receive.d/hook"), changes)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path(tmp_repo_path, "custom_hooks/pre-receive.d/hook"), changes)
+        .with(hook_path("custom_hooks/pre-receive.d/hook"), changes)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:system)
-        .with(vars, hook_path(tmp_repo_path, "hooks/update.d/hook"), ref_name, old_value, new_value)
+        .with(vars, hook_path("hooks/update.d/hook"), ref_name, old_value, new_value)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:system)
-        .with(vars, hook_path(tmp_repo_path, "custom_hooks/update.d/hook"), ref_name, old_value, new_value)
+        .with(vars, hook_path("custom_hooks/update.d/hook"), ref_name, old_value, new_value)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path(tmp_repo_path, "hooks/post-receive.d/hook"), changes)
+        .with(hook_path("hooks/post-receive.d/hook"), changes)
         .and_call_original
       expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path(tmp_repo_path, "custom_hooks/post-receive.d/hook"), changes)
+        .with(hook_path("custom_hooks/post-receive.d/hook"), changes)
         .and_call_original
 
       gitlab_custom_hook.pre_receive(changes)
-- 
GitLab


From 24d6df6c882c4224d66271f81cc04c5a9ba3b3f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Sun, 20 Nov 2016 16:30:45 +0200
Subject: [PATCH 17/21] spec/custom_hook: ensure "before" does complete cleanup

this fixes problem that tests succeeded locally but failed in ci
where parent dirs were missing
---
 spec/gitlab_custom_hook_spec.rb | 13 ++++++-------
 1 file changed, 6 insertions(+), 7 deletions(-)

diff --git a/spec/gitlab_custom_hook_spec.rb b/spec/gitlab_custom_hook_spec.rb
index 05ec38b..faa4f2b 100644
--- a/spec/gitlab_custom_hook_spec.rb
+++ b/spec/gitlab_custom_hook_spec.rb
@@ -47,9 +47,7 @@ describe GitlabCustomHook do
   end
 
   def cleanup_hook_setup
-    FileUtils.rm_rf(File.join(tmp_repo_path, 'custom_hooks'))
-    FileUtils.rm_rf(File.join(tmp_repo_path, 'hooks'))
-    FileUtils.rm_rf(File.join(tmp_repo_path, 'hooks.d'))
+    FileUtils.rm_rf(File.join(tmp_repo_path))
     FileUtils.rm_rf(File.join(tmp_root_path, 'hooks'))
   end
 
@@ -66,16 +64,17 @@ describe GitlabCustomHook do
   before do
     cleanup_hook_setup
 
+    FileUtils.mkdir_p(File.join(tmp_repo_path, 'custom_hooks'))
+    FileUtils.mkdir_p(File.join(tmp_repo_path, 'custom_hooks', 'update.d'))
+    FileUtils.mkdir_p(File.join(tmp_repo_path, 'custom_hooks', 'pre-receive.d'))
+    FileUtils.mkdir_p(File.join(tmp_repo_path, 'custom_hooks', 'post-receive.d'))
+
     FileUtils.mkdir_p(File.join(tmp_root_path, 'hooks'))
     FileUtils.mkdir_p(File.join(tmp_root_path, 'hooks', 'update.d'))
     FileUtils.mkdir_p(File.join(tmp_root_path, 'hooks', 'pre-receive.d'))
     FileUtils.mkdir_p(File.join(tmp_root_path, 'hooks', 'post-receive.d'))
 
     FileUtils.symlink(File.join(tmp_root_path, 'hooks'), File.join(tmp_repo_path, 'hooks'))
-    FileUtils.mkdir_p(File.join(tmp_repo_path, 'custom_hooks'))
-    FileUtils.mkdir_p(File.join(tmp_repo_path, 'custom_hooks', 'update.d'))
-    FileUtils.mkdir_p(File.join(tmp_repo_path, 'custom_hooks', 'pre-receive.d'))
-    FileUtils.mkdir_p(File.join(tmp_repo_path, 'custom_hooks', 'post-receive.d'))
   end
 
   after do
-- 
GitLab


From 37148a6c56ab252728cba9c11c5f25ed889211f1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Fri, 25 Nov 2016 20:27:33 +0200
Subject: [PATCH 18/21] test gl_id_test_hook as global and as repo hook
 separately

---
 spec/gitlab_custom_hook_spec.rb | 25 ++++++++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/spec/gitlab_custom_hook_spec.rb b/spec/gitlab_custom_hook_spec.rb
index faa4f2b..5cc9e0b 100644
--- a/spec/gitlab_custom_hook_spec.rb
+++ b/spec/gitlab_custom_hook_spec.rb
@@ -81,9 +81,32 @@ describe GitlabCustomHook do
     cleanup_hook_setup
   end
 
-  context 'with gl_id_test_hook' do
+  context 'with gl_id_test_hook as repo hook' do
     before do
       create_repo_hooks(hook_gl_id)
+    end
+
+    context 'pre_receive hook' do
+      it 'passes GL_ID variable to hook' do
+        expect(gitlab_custom_hook.pre_receive(changes)).to eq(true)
+      end
+    end
+
+    context 'post_receive hook' do
+      it 'passes GL_ID variable to hook' do
+        expect(gitlab_custom_hook.post_receive(changes)).to eq(true)
+      end
+    end
+
+    context 'update hook' do
+      it 'passes GL_ID variable to hook' do
+        expect(gitlab_custom_hook.update(ref_name, old_value, new_value)).to eq(true)
+      end
+    end
+  end
+
+  context 'with gl_id_test_hook as global hook' do
+    before do
       create_global_hooks_d(hook_gl_id)
     end
 
-- 
GitLab


From f3512031fefe7a566d4a8a911cc024fb6bc1c99f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Fri, 25 Nov 2016 20:56:24 +0200
Subject: [PATCH 19/21] DRY: add helpers for expect hook calls

---
 spec/gitlab_custom_hook_spec.rb | 66 ++++++++++++++++-----------------
 1 file changed, 31 insertions(+), 35 deletions(-)

diff --git a/spec/gitlab_custom_hook_spec.rb b/spec/gitlab_custom_hook_spec.rb
index 5cc9e0b..cbf2d03 100644
--- a/spec/gitlab_custom_hook_spec.rb
+++ b/spec/gitlab_custom_hook_spec.rb
@@ -26,10 +26,10 @@ describe GitlabCustomHook do
   end
 
   # global hooks multiplexed
-  def create_global_hooks_d(which)
-    create_hook('hooks/pre-receive.d/hook', which)
-    create_hook('hooks/update.d/hook', which)
-    create_hook('hooks/post-receive.d/hook', which)
+  def create_global_hooks_d(which, hook_name = 'hook')
+    create_hook('hooks/pre-receive.d/' + hook_name, which)
+    create_hook('hooks/update.d/' + hook_name, which)
+    create_hook('hooks/post-receive.d/' + hook_name, which)
   end
 
   # repo hooks
@@ -40,10 +40,10 @@ describe GitlabCustomHook do
   end
 
   # repo hooks multiplexed
-  def create_repo_hooks_d(which)
-    create_hook('custom_hooks/pre-receive.d/hook', which)
-    create_hook('custom_hooks/update.d/hook', which)
-    create_hook('custom_hooks/post-receive.d/hook', which)
+  def create_repo_hooks_d(which, hook_name = 'hook')
+    create_hook('custom_hooks/pre-receive.d/' + hook_name, which)
+    create_hook('custom_hooks/update.d/' + hook_name, which)
+    create_hook('custom_hooks/post-receive.d/' + hook_name, which)
   end
 
   def cleanup_hook_setup
@@ -51,6 +51,20 @@ describe GitlabCustomHook do
     FileUtils.rm_rf(File.join(tmp_root_path, 'hooks'))
   end
 
+  def expect_call_receive_hook(path)
+    expect(gitlab_custom_hook)
+      .to receive(:call_receive_hook)
+      .with(hook_path(path), changes)
+      .and_call_original
+  end
+
+  def expect_call_update_hook(path)
+    expect(gitlab_custom_hook)
+      .to receive(:system)
+      .with(vars, hook_path(path), ref_name, old_value, new_value)
+      .and_call_original
+  end
+
   # setup paths
   # <repository>.git/hooks/ - symlink to gitlab-shell/hooks global dir
   # <repository>.git/hooks/<hook_name> - executed by git itself, this is gitlab-shell/hooks/<hook_name>
@@ -175,15 +189,9 @@ describe GitlabCustomHook do
     end
 
     it "only executes the global hook" do
-      expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path("custom_hooks/pre-receive.d/hook"), changes)
-        .and_call_original
-      expect(gitlab_custom_hook).to receive(:system)
-        .with(vars, hook_path("custom_hooks/update.d/hook"), ref_name, old_value, new_value)
-        .and_call_original
-      expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path("custom_hooks/post-receive.d/hook"), changes)
-        .and_call_original
+      expect_call_receive_hook("custom_hooks/pre-receive.d/hook")
+      expect_call_update_hook("custom_hooks/update.d/hook")
+      expect_call_receive_hook("custom_hooks/post-receive.d/hook")
 
       gitlab_custom_hook.pre_receive(changes)
       gitlab_custom_hook.update(ref_name, old_value, new_value)
@@ -204,24 +212,12 @@ describe GitlabCustomHook do
     end
 
     it "executes the relevant hooks" do
-      expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path("hooks/pre-receive.d/hook"), changes)
-        .and_call_original
-      expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path("custom_hooks/pre-receive.d/hook"), changes)
-        .and_call_original
-      expect(gitlab_custom_hook).to receive(:system)
-        .with(vars, hook_path("hooks/update.d/hook"), ref_name, old_value, new_value)
-        .and_call_original
-      expect(gitlab_custom_hook).to receive(:system)
-        .with(vars, hook_path("custom_hooks/update.d/hook"), ref_name, old_value, new_value)
-        .and_call_original
-      expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path("hooks/post-receive.d/hook"), changes)
-        .and_call_original
-      expect(gitlab_custom_hook).to receive(:call_receive_hook)
-        .with(hook_path("custom_hooks/post-receive.d/hook"), changes)
-        .and_call_original
+      expect_call_receive_hook("hooks/pre-receive.d/hook")
+      expect_call_receive_hook("custom_hooks/pre-receive.d/hook")
+      expect_call_update_hook("hooks/update.d/hook")
+      expect_call_update_hook("custom_hooks/update.d/hook")
+      expect_call_receive_hook("hooks/post-receive.d/hook")
+      expect_call_receive_hook("custom_hooks/post-receive.d/hook")
 
       gitlab_custom_hook.pre_receive(changes)
       gitlab_custom_hook.update(ref_name, old_value, new_value)
-- 
GitLab


From d9afbf47ea25834498e0dfe1f981e1fa576ba88a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Fri, 25 Nov 2016 20:56:44 +0200
Subject: [PATCH 20/21] test expected hook order

---
 spec/gitlab_custom_hook_spec.rb | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/spec/gitlab_custom_hook_spec.rb b/spec/gitlab_custom_hook_spec.rb
index cbf2d03..b5be8ec 100644
--- a/spec/gitlab_custom_hook_spec.rb
+++ b/spec/gitlab_custom_hook_spec.rb
@@ -224,4 +224,34 @@ describe GitlabCustomHook do
       gitlab_custom_hook.post_receive(changes)
     end
   end
+
+  context "executing hooks in expected order" do
+    before do
+      create_repo_hooks_d(hook_ok, '01-test')
+      create_repo_hooks_d(hook_ok, '02-test')
+      create_global_hooks_d(hook_ok, '03-test')
+      create_global_hooks_d(hook_ok, '04-test')
+    end
+
+    it "executes hooks in order" do
+      expect_call_receive_hook("custom_hooks/pre-receive.d/01-test").ordered
+      expect_call_receive_hook("custom_hooks/pre-receive.d/02-test").ordered
+      expect_call_receive_hook("hooks/pre-receive.d/03-test").ordered
+      expect_call_receive_hook("hooks/pre-receive.d/04-test").ordered
+
+      expect_call_update_hook("custom_hooks/update.d/01-test").ordered
+      expect_call_update_hook("custom_hooks/update.d/02-test").ordered
+      expect_call_update_hook("hooks/update.d/03-test").ordered
+      expect_call_update_hook("hooks/update.d/04-test").ordered
+
+      expect_call_receive_hook("custom_hooks/post-receive.d/01-test").ordered
+      expect_call_receive_hook("custom_hooks/post-receive.d/02-test").ordered
+      expect_call_receive_hook("hooks/post-receive.d/03-test").ordered
+      expect_call_receive_hook("hooks/post-receive.d/04-test").ordered
+
+      gitlab_custom_hook.pre_receive(changes)
+      gitlab_custom_hook.update(ref_name, old_value, new_value)
+      gitlab_custom_hook.post_receive(changes)
+    end
+  end
 end
-- 
GitLab


From b77f6530b96c520ef8637ceba4ef2889a008d383 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Elan=20Ruusam=C3=A4e?= <glen@delfi.ee>
Date: Fri, 25 Nov 2016 21:03:16 +0200
Subject: [PATCH 21/21] changelog spelling

---
 CHANGELOG | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG b/CHANGELOG
index 7cb6b19..0caca15 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,5 @@
 v4.1.0
-  - Add support for global custom hooks and chanined hooks (Elan Ruusamäe, Dirk Hörner), !93, !89, #32
+  - Add support for global custom hooks and chained hook directories (Elan Ruusamäe, Dirk Hörner), !93, !89, #32
 
 v4.0.3
   - Fetch repositories with `--prune` option by default
-- 
GitLab