From ee1090e2b2bc7b3762f6e2775f3fd15e92ae212b Mon Sep 17 00:00:00 2001
From: Gabriel Mazetto <gabriel@gitlab.com>
Date: Fri, 15 Apr 2016 08:08:22 -0300
Subject: [PATCH] Added System Hooks for push and tag_push

Code is based on Project Webhooks, removing deprecations and without
commits listing.
---
 app/models/hooks/system_hook.rb               |  6 ++
 app/models/project.rb                         | 21 +++--
 app/services/git_push_service.rb              |  6 ++
 app/services/git_tag_push_service.rb          |  6 ++
 app/services/system_hooks_service.rb          | 12 +--
 doc/system_hooks/system_hooks.md              | 77 +++++++++++++++++++
 doc/web_hooks/web_hooks.md                    |  4 +-
 lib/gitlab/push_data_builder.rb               | 22 +++++-
 spec/lib/gitlab/push_data_builder_spec.rb     | 31 +++++++-
 .../project_hook_data_shared_example.rb       | 17 +++-
 10 files changed, 181 insertions(+), 21 deletions(-)

diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index c147d8762a9..bacd42ec60a 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -19,4 +19,10 @@
 #
 
 class SystemHook < WebHook
+  scope :push_hooks, -> { where(push_events: true) }
+  scope :tag_push_hooks, -> { where(tag_push_events: true) }
+
+  def async_execute(data, hook_name)
+    Sidekiq::Client.enqueue(SystemHookWorker, id, data, hook_name)
+  end
 end
diff --git a/app/models/project.rb b/app/models/project.rb
index 8f20922e3c5..e7dac23a122 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -831,8 +831,8 @@ class Project < ActiveRecord::Base
     end
   end
 
-  def hook_attrs
-    {
+  def hook_attrs(backward: true)
+    attrs = {
       name: name,
       description: description,
       web_url: web_url,
@@ -843,12 +843,19 @@ class Project < ActiveRecord::Base
       visibility_level: visibility_level,
       path_with_namespace: path_with_namespace,
       default_branch: default_branch,
-      # Backward compatibility
-      homepage: web_url,
-      url: url_to_repo,
-      ssh_url: ssh_url_to_repo,
-      http_url: http_url_to_repo
     }
+
+    # Backward compatibility
+    if backward
+      attrs.merge!({
+                    homepage: web_url,
+                    url: url_to_repo,
+                    ssh_url: ssh_url_to_repo,
+                    http_url: http_url_to_repo
+                  })
+    end
+
+    attrs
   end
 
   # Reset events cache related to this project
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index dc74c02760b..25d8e2cf052 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -73,6 +73,7 @@ class GitPushService < BaseService
     @project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user)
 
     EventCreateService.new.push(@project, current_user, build_push_data)
+    SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks)
     @project.execute_hooks(build_push_data.dup, :push_hooks)
     @project.execute_services(build_push_data.dup, :push_hooks)
     CreateCommitBuildsService.new.execute(@project, current_user, build_push_data)
@@ -138,6 +139,11 @@ class GitPushService < BaseService
       build(@project, current_user, params[:oldrev], params[:newrev], params[:ref], push_commits)
   end
 
+  def build_push_data_system_hook
+    @push_data_system ||= Gitlab::PushDataBuilder.
+      build_system(@project, current_user, params[:oldrev], params[:newrev], params[:ref])
+  end
+
   def push_to_existing_branch?
     # Return if this is not a push to a branch (e.g. new commits)
     Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev])
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index c88c7672805..81ba68fa674 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -8,6 +8,7 @@ class GitTagPushService
     @push_data = build_push_data(oldrev, newrev, ref)
 
     EventCreateService.new.push(project, user, @push_data)
+    SystemHooksService.new.execute_hooks(build_system_push_data.dup, :tag_push_hooks)
     project.execute_hooks(@push_data.dup, :tag_push_hooks)
     project.execute_services(@push_data.dup, :tag_push_hooks)
     CreateCommitBuildsService.new.execute(project, @user, @push_data)
@@ -35,4 +36,9 @@ class GitTagPushService
     Gitlab::PushDataBuilder.
       build(project, user, oldrev, newrev, ref, commits, message)
   end
+
+  def build_system_push_data(oldrev, newrev, ref)
+    Gitlab::PushDataBuilder.
+      build_system(project, user, oldrev, newrev, ref)
+  end
 end
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index f0615ec7420..e43b5b51e5b 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -3,17 +3,13 @@ class SystemHooksService
     execute_hooks(build_event_data(model, event))
   end
 
-  private
-
-  def execute_hooks(data)
-    SystemHook.all.each do |sh|
-      async_execute_hook(sh, data, 'system_hooks')
+  def execute_hooks(data, hooks_scope = :all)
+    SystemHook.send(hooks_scope).each do |hook|
+      hook.async_execute(data, 'system_hooks')
     end
   end
 
-  def async_execute_hook(hook, data, hook_name)
-    Sidekiq::Client.enqueue(SystemHookWorker, hook.id, data, hook_name)
-  end
+  private
 
   def build_event_data(model, event)
     data = {
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index 612376e3a49..4c5a9e366cf 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -240,3 +240,80 @@ X-Gitlab-Event: System Hook
        "user_id": 41
 }
 ```
+
+## Push events
+
+Triggered when you push to the repository except when pushing tags.
+
+**Request header**:
+
+```
+X-Gitlab-Event: System Hook
+```
+
+**Request body:**
+
+```json
+{
+  "event_name": "push",
+  "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
+  "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+  "ref": "refs/heads/master",
+  "checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
+  "user_id": 4,
+  "user_name": "John Smith",
+  "user_email": "john@example.com",
+  "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
+  "project_id": 15,
+  "project":{
+    "name":"Diaspora",
+    "description":"",
+    "web_url":"http://example.com/mike/diaspora",
+    "avatar_url":null,
+    "git_ssh_url":"git@example.com:mike/diaspora.git",
+    "git_http_url":"http://example.com/mike/diaspora.git",
+    "namespace":"Mike",
+    "visibility_level":0,
+    "path_with_namespace":"mike/diaspora",
+    "default_branch":"master",
+  }
+}
+```
+
+## Tag events
+
+Triggered when you create (or delete) tags to the repository.
+
+**Request header**:
+
+```
+X-Gitlab-Event: System Hook
+```
+
+**Request body:**
+
+```json
+{
+  "event_name": "tag_push",
+  "before": "0000000000000000000000000000000000000000",
+  "after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
+  "ref": "refs/tags/v1.0.0",
+  "checkout_sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
+  "user_id": 1,
+  "user_name": "John Smith",
+  "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
+  "project_id": 1,
+  "project":{
+    "name":"Example",
+    "description":"",
+    "web_url":"http://example.com/jsmith/example",
+    "avatar_url":null,
+    "git_ssh_url":"git@example.com:jsmith/example.git",
+    "git_http_url":"http://example.com/jsmith/example.git",
+    "namespace":"Jsmith",
+    "visibility_level":0,
+    "path_with_namespace":"jsmith/example",
+    "default_branch":"master",
+  }
+}
+```
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 22e207b6d32..c1c51302e79 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -41,6 +41,7 @@ X-Gitlab-Event: Push Hook
   "before": "95790bf891e76fee5e1747ab589903a6a1f80f22",
   "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
   "ref": "refs/heads/master",
+  "checkout_sha": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
   "user_id": 4,
   "user_name": "John Smith",
   "user_email": "john@example.com",
@@ -118,9 +119,10 @@ X-Gitlab-Event: Tag Push Hook
 ```json
 {
   "object_kind": "tag_push",
-  "ref": "refs/tags/v1.0.0",
   "before": "0000000000000000000000000000000000000000",
   "after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
+  "ref": "refs/tags/v1.0.0",
+  "checkout_sha": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7",
   "user_id": 1,
   "user_name": "John Smith",
   "user_avatar": "https://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=8://s.gravatar.com/avatar/d4c74594d841139328695756648b6bd6?s=80",
diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb
index 97d1edab9c1..eb8e45040bc 100644
--- a/lib/gitlab/push_data_builder.rb
+++ b/lib/gitlab/push_data_builder.rb
@@ -36,7 +36,7 @@ module Gitlab
           commit.hook_attrs(with_changed_files: true)
         end
 
-        type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push"
+        type = Gitlab::Git.tag_ref?(ref) ? 'tag_push' : 'push'
 
         # Hash to be passed as post_receive_data
         data = {
@@ -62,6 +62,26 @@ module Gitlab
         data
       end
 
+      def build_system(project, user, oldrev, newrev, ref)
+        type = Gitlab::Git.tag_ref?(ref) ? 'tag_push' : 'push'
+
+        data = {
+          event_name: type,
+          before: oldrev,
+          after: newrev,
+          ref: ref,
+          checkout_sha: checkout_sha(project.repository, newrev, ref),
+          user_id: user.id,
+          user_name: user.name,
+          user_email: user.email,
+          user_avatar: user.avatar_url,
+          project_id: project.id,
+          project: project.hook_attrs(backward: false)
+        }
+
+        data
+      end
+
       # This method provide a sample data generated with
       # existing project and commits to test webhooks
       def build_sample(project, user)
diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb
index 961022b9d12..d6fb65e6948 100644
--- a/spec/lib/gitlab/push_data_builder_spec.rb
+++ b/spec/lib/gitlab/push_data_builder_spec.rb
@@ -14,11 +14,11 @@ describe Gitlab::PushDataBuilder, lib: true do
     it { expect(data[:ref]).to eq('refs/heads/master') }
     it { expect(data[:commits].size).to eq(3) }
     it { expect(data[:total_commits_count]).to eq(3) }
-    it { expect(data[:commits].first[:added]).to eq(["gitlab-grack"]) }
-    it { expect(data[:commits].first[:modified]).to eq([".gitmodules"]) }
+    it { expect(data[:commits].first[:added]).to eq(['gitlab-grack']) }
+    it { expect(data[:commits].first[:modified]).to eq(['.gitmodules']) }
     it { expect(data[:commits].first[:removed]).to eq([]) }
 
-    include_examples 'project hook data'
+    include_examples 'project hook data with deprecateds'
     include_examples 'deprecated repository hook data'
   end
 
@@ -37,9 +37,34 @@ describe Gitlab::PushDataBuilder, lib: true do
     it { expect(data[:commits]).to be_empty }
     it { expect(data[:total_commits_count]).to be_zero }
 
+    include_examples 'project hook data with deprecateds'
+    include_examples 'deprecated repository hook data'
+
     it 'does not raise an error when given nil commits' do
       expect { described_class.build(spy, spy, spy, spy, spy, nil) }.
         not_to raise_error
     end
   end
+
+  describe '.build_system' do
+    let(:data) do
+      described_class.build_system(project, user, Gitlab::Git::BLANK_SHA,
+                                   '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b',
+                                   'refs/tags/v1.1.0')
+    end
+
+    it { expect(data).to be_a(Hash) }
+    it { expect(data[:before]).to eq(Gitlab::Git::BLANK_SHA) }
+    it { expect(data[:checkout_sha]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
+    it { expect(data[:after]).to eq('8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b') }
+    it { expect(data[:ref]).to eq('refs/tags/v1.1.0') }
+    it { expect(data[:user_id]).to eq(user.id) }
+    it { expect(data[:user_name]).to eq(user.name) }
+    it { expect(data[:user_email]).to eq(user.email) }
+    it { expect(data[:user_avatar]).to eq(user.avatar_url) }
+    it { expect(data[:project_id]).to eq(project.id) }
+    it { expect(data[:project]).to be_a(Hash) }
+
+    include_examples 'project hook data'
+  end
 end
diff --git a/spec/support/project_hook_data_shared_example.rb b/spec/support/project_hook_data_shared_example.rb
index 422083875d7..7dbaa6a6459 100644
--- a/spec/support/project_hook_data_shared_example.rb
+++ b/spec/support/project_hook_data_shared_example.rb
@@ -1,4 +1,4 @@
-RSpec.shared_examples 'project hook data' do |project_key: :project|
+RSpec.shared_examples 'project hook data with deprecateds' do |project_key: :project|
   it 'contains project data' do
     expect(data[project_key][:name]).to eq(project.name)
     expect(data[project_key][:description]).to eq(project.description)
@@ -17,6 +17,21 @@ RSpec.shared_examples 'project hook data' do |project_key: :project|
   end
 end
 
+RSpec.shared_examples 'project hook data' do |project_key: :project|
+  it 'contains project data' do
+    expect(data[project_key][:name]).to eq(project.name)
+    expect(data[project_key][:description]).to eq(project.description)
+    expect(data[project_key][:web_url]).to eq(project.web_url)
+    expect(data[project_key][:avatar_url]).to eq(project.avatar_url)
+    expect(data[project_key][:git_http_url]).to eq(project.http_url_to_repo)
+    expect(data[project_key][:git_ssh_url]).to eq(project.ssh_url_to_repo)
+    expect(data[project_key][:namespace]).to eq(project.namespace.name)
+    expect(data[project_key][:visibility_level]).to eq(project.visibility_level)
+    expect(data[project_key][:path_with_namespace]).to eq(project.path_with_namespace)
+    expect(data[project_key][:default_branch]).to eq(project.default_branch)
+  end
+end
+
 RSpec.shared_examples 'deprecated repository hook data' do |project_key: :project|
   it 'contains deprecated repository data' do
     expect(data[:repository][:name]).to eq(project.name)
-- 
GitLab