Commit afd747d4d7af9e4f5387927bcaa709a2d3fefbef

Authored by Dmitriy Zaporozhets
2 parents 26c8b316 6ac73f45

Merge branch 'feature/email_on_push_service' of /home/git/repositories/gitlab/gitlabhq

app/mailers/emails/projects.rb
... ... @@ -13,5 +13,15 @@ module Emails
13 13 mail(to: @user.email,
14 14 subject: subject("Project was moved"))
15 15 end
  16 +
  17 + def repository_push_email(project_id, recipient, author_id, branch, compare)
  18 + @project = Project.find(project_id)
  19 + @author = User.find(author_id)
  20 + @commits = Commit.decorate(compare.commits)
  21 + @diffs = compare.diffs
  22 + @branch = branch
  23 +
  24 + mail(to: recipient, subject: subject("New push to repository"))
  25 + end
16 26 end
17 27 end
... ...
app/models/assembla_service.rb
... ... @@ -1,45 +0,0 @@
1   -# == Schema Information
2   -#
3   -# Table name: services
4   -#
5   -# id :integer not null, primary key
6   -# type :string(255)
7   -# title :string(255)
8   -# token :string(255)
9   -# project_id :integer not null
10   -# created_at :datetime not null
11   -# updated_at :datetime not null
12   -# active :boolean default(FALSE), not null
13   -# project_url :string(255)
14   -# subdomain :string(255)
15   -# room :string(255)
16   -#
17   -
18   -class AssemblaService < Service
19   - include HTTParty
20   -
21   - validates :token, presence: true, if: :activated?
22   -
23   - def title
24   - 'Assembla'
25   - end
26   -
27   - def description
28   - 'Project Management Software (Source Commits Endpoint)'
29   - end
30   -
31   - def to_param
32   - 'assembla'
33   - end
34   -
35   - def fields
36   - [
37   - { type: 'text', name: 'token', placeholder: '' }
38   - ]
39   - end
40   -
41   - def execute(push)
42   - url = "https://atlas.assembla.com/spaces/ouposp/github_tool?secret_key=#{token}"
43   - AssemblaService.post(url, body: { payload: push }.to_json, headers: { 'Content-Type' => 'application/json' })
44   - end
45   -end
app/models/campfire_service.rb
... ... @@ -1,78 +0,0 @@
1   -# == Schema Information
2   -#
3   -# Table name: services
4   -#
5   -# id :integer not null, primary key
6   -# type :string(255)
7   -# title :string(255)
8   -# token :string(255)
9   -# project_id :integer not null
10   -# created_at :datetime not null
11   -# updated_at :datetime not null
12   -# active :boolean default(FALSE), not null
13   -# project_url :string(255)
14   -# subdomain :string(255)
15   -# room :string(255)
16   -#
17   -
18   -class CampfireService < Service
19   - attr_accessible :subdomain, :room
20   -
21   - validates :token, presence: true, if: :activated?
22   -
23   - def title
24   - 'Campfire'
25   - end
26   -
27   - def description
28   - 'Simple web-based real-time group chat'
29   - end
30   -
31   - def to_param
32   - 'campfire'
33   - end
34   -
35   - def fields
36   - [
37   - { type: 'text', name: 'token', placeholder: '' },
38   - { type: 'text', name: 'subdomain', placeholder: '' },
39   - { type: 'text', name: 'room', placeholder: '' }
40   - ]
41   - end
42   -
43   - def execute(push_data)
44   - room = gate.find_room_by_name(self.room)
45   - return true unless room
46   -
47   - message = build_message(push_data)
48   -
49   - room.speak(message)
50   - end
51   -
52   - private
53   -
54   - def gate
55   - @gate ||= Tinder::Campfire.new(subdomain, token: token)
56   - end
57   -
58   - def build_message(push)
59   - ref = push[:ref].gsub("refs/heads/", "")
60   - before = push[:before]
61   - after = push[:after]
62   -
63   - message = ""
64   - message << "[#{project.name_with_namespace}] "
65   - message << "#{push[:user_name]} "
66   -
67   - if before =~ /000000/
68   - message << "pushed new branch #{ref} \n"
69   - elsif after =~ /000000/
70   - message << "removed branch #{ref} \n"
71   - else
72   - message << "pushed #{push[:total_commits_count]} commits to #{ref}. "
73   - message << "#{project.web_url}/compare/#{before}...#{after}"
74   - end
75   -
76   - message
77   - end
78   -end
app/models/flowdock_service.rb
... ... @@ -1,54 +0,0 @@
1   -# == Schema Information
2   -#
3   -# Table name: services
4   -#
5   -# id :integer not null, primary key
6   -# type :string(255)
7   -# title :string(255)
8   -# token :string(255)
9   -# project_id :integer not null
10   -# created_at :datetime not null
11   -# updated_at :datetime not null
12   -# active :boolean default(FALSE), not null
13   -# project_url :string(255)
14   -# subdomain :string(255)
15   -# room :string(255)
16   -#
17   -
18   -require "flowdock-git-hook"
19   -
20   -class FlowdockService < Service
21   - validates :token, presence: true, if: :activated?
22   -
23   - def title
24   - 'Flowdock'
25   - end
26   -
27   - def description
28   - 'Flowdock is a collaboration web app for technical teams.'
29   - end
30   -
31   - def to_param
32   - 'flowdock'
33   - end
34   -
35   - def fields
36   - [
37   - { type: 'text', name: 'token', placeholder: '' }
38   - ]
39   - end
40   -
41   - def execute(push_data)
42   - repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
43   - Flowdock::Git.post(
44   - push_data[:ref],
45   - push_data[:before],
46   - push_data[:after],
47   - token: token,
48   - repo: repo_path,
49   - repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}",
50   - commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s",
51   - diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s",
52   - )
53   - end
54   -end
app/models/gitlab_ci_service.rb
... ... @@ -1,78 +0,0 @@
1   -# == Schema Information
2   -#
3   -# Table name: services
4   -#
5   -# id :integer not null, primary key
6   -# type :string(255)
7   -# title :string(255)
8   -# token :string(255)
9   -# project_id :integer not null
10   -# created_at :datetime not null
11   -# updated_at :datetime not null
12   -# active :boolean default(FALSE), not null
13   -# project_url :string(255)
14   -# subdomain :string(255)
15   -# room :string(255)
16   -#
17   -
18   -class GitlabCiService < Service
19   - attr_accessible :project_url
20   -
21   - validates :project_url, presence: true, if: :activated?
22   - validates :token, presence: true, if: :activated?
23   -
24   - delegate :execute, to: :service_hook, prefix: nil
25   -
26   - after_save :compose_service_hook, if: :activated?
27   -
28   - def compose_service_hook
29   - hook = service_hook || build_service_hook
30   - hook.url = [project_url, "/build", "?token=#{token}"].join("")
31   - hook.save
32   - end
33   -
34   - def commit_status_path sha
35   - project_url + "/builds/#{sha}/status.json?token=#{token}"
36   - end
37   -
38   - def commit_status sha
39   - response = HTTParty.get(commit_status_path(sha))
40   -
41   - if response.code == 200 and response["status"]
42   - response["status"]
43   - else
44   - :error
45   - end
46   - end
47   -
48   - def build_page sha
49   - project_url + "/builds/#{sha}"
50   - end
51   -
52   - def builds_path
53   - project_url + "?ref=" + project.default_branch
54   - end
55   -
56   - def status_img_path
57   - project_url + "/status.png?ref=" + project.default_branch
58   - end
59   -
60   - def title
61   - 'GitLab CI'
62   - end
63   -
64   - def description
65   - 'Continuous integration server from GitLab'
66   - end
67   -
68   - def to_param
69   - 'gitlab_ci'
70   - end
71   -
72   - def fields
73   - [
74   - { type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' },
75   - { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3'}
76   - ]
77   - end
78   -end
app/models/hipchat_service.rb
... ... @@ -1,74 +0,0 @@
1   -# == Schema Information
2   -#
3   -# Table name: services
4   -#
5   -# id :integer not null, primary key
6   -# type :string(255)
7   -# title :string(255)
8   -# token :string(255)
9   -# project_id :integer not null
10   -# created_at :datetime not null
11   -# updated_at :datetime not null
12   -# active :boolean default(FALSE), not null
13   -# project_url :string(255)
14   -# subdomain :string(255)
15   -# room :string(255)
16   -#
17   -
18   -class HipchatService < Service
19   - attr_accessible :room
20   -
21   - validates :token, presence: true, if: :activated?
22   -
23   - def title
24   - 'Hipchat'
25   - end
26   -
27   - def description
28   - 'Private group chat and IM'
29   - end
30   -
31   - def to_param
32   - 'hipchat'
33   - end
34   -
35   - def fields
36   - [
37   - { type: 'text', name: 'token', placeholder: '' },
38   - { type: 'text', name: 'room', placeholder: '' }
39   - ]
40   - end
41   -
42   - def execute(push_data)
43   - gate[room].send('Gitlab', create_message(push_data))
44   - end
45   -
46   - private
47   -
48   - def gate
49   - @gate ||= HipChat::Client.new(token)
50   - end
51   -
52   - def create_message(push)
53   - ref = push[:ref].gsub("refs/heads/", "")
54   - before = push[:before]
55   - after = push[:after]
56   -
57   - message = ""
58   - message << "#{push[:user_name]} "
59   - if before =~ /000000/
60   - message << "pushed new branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> to <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a>\n"
61   - elsif after =~ /000000/
62   - message << "removed branch #{ref} from <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> \n"
63   - else
64   - message << "#pushed to branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> "
65   - message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> "
66   - message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
67   - for commit in push[:commits] do
68   - message << "<br /> - #{commit[:message]} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)"
69   - end
70   - end
71   -
72   - message
73   - end
74   -end
app/models/pivotaltracker_service.rb
... ... @@ -1,62 +0,0 @@
1   -# == Schema Information
2   -#
3   -# Table name: services
4   -#
5   -# id :integer not null, primary key
6   -# type :string(255)
7   -# title :string(255)
8   -# token :string(255)
9   -# project_id :integer not null
10   -# created_at :datetime not null
11   -# updated_at :datetime not null
12   -# active :boolean default(FALSE), not null
13   -# project_url :string(255)
14   -# subdomain :string(255)
15   -# room :string(255)
16   -#
17   -
18   -class PivotaltrackerService < Service
19   - include HTTParty
20   -
21   - validates :token, presence: true, if: :activated?
22   -
23   - def title
24   - 'PivotalTracker'
25   - end
26   -
27   - def description
28   - 'Project Management Software (Source Commits Endpoint)'
29   - end
30   -
31   - def to_param
32   - 'pivotaltracker'
33   - end
34   -
35   - def fields
36   - [
37   - { type: 'text', name: 'token', placeholder: '' }
38   - ]
39   - end
40   -
41   - def execute(push)
42   - url = 'https://www.pivotaltracker.com/services/v5/source_commits'
43   - push[:commits].each do |commit|
44   - message = {
45   - 'source_commit' => {
46   - 'commit_id' => commit[:id],
47   - 'author' => commit[:author][:name],
48   - 'url' => commit[:url],
49   - 'message' => commit[:message]
50   - }
51   - }
52   - PivotaltrackerService.post(
53   - url,
54   - body: message.to_json,
55   - headers: {
56   - 'Content-Type' => 'application/json',
57   - 'X-TrackerToken' => token
58   - }
59   - )
60   - end
61   - end
62   -end
app/models/project.rb
... ... @@ -48,6 +48,7 @@ class Project &lt; ActiveRecord::Base
48 48 has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
49 49 has_one :gitlab_ci_service, dependent: :destroy
50 50 has_one :campfire_service, dependent: :destroy
  51 + has_one :emails_on_push_service, dependent: :destroy
51 52 has_one :pivotaltracker_service, dependent: :destroy
52 53 has_one :hipchat_service, dependent: :destroy
53 54 has_one :flowdock_service, dependent: :destroy
... ... @@ -237,7 +238,7 @@ class Project &lt; ActiveRecord::Base
237 238 end
238 239  
239 240 def available_services_names
240   - %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla)
  241 + %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push)
241 242 end
242 243  
243 244 def gitlab_ci?
... ...
app/models/project_services/assembla_service.rb 0 → 100644
... ... @@ -0,0 +1,45 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +class AssemblaService < Service
  19 + include HTTParty
  20 +
  21 + validates :token, presence: true, if: :activated?
  22 +
  23 + def title
  24 + 'Assembla'
  25 + end
  26 +
  27 + def description
  28 + 'Project Management Software (Source Commits Endpoint)'
  29 + end
  30 +
  31 + def to_param
  32 + 'assembla'
  33 + end
  34 +
  35 + def fields
  36 + [
  37 + { type: 'text', name: 'token', placeholder: '' }
  38 + ]
  39 + end
  40 +
  41 + def execute(push)
  42 + url = "https://atlas.assembla.com/spaces/ouposp/github_tool?secret_key=#{token}"
  43 + AssemblaService.post(url, body: { payload: push }.to_json, headers: { 'Content-Type' => 'application/json' })
  44 + end
  45 +end
... ...
app/models/project_services/campfire_service.rb 0 → 100644
... ... @@ -0,0 +1,78 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +class CampfireService < Service
  19 + attr_accessible :subdomain, :room
  20 +
  21 + validates :token, presence: true, if: :activated?
  22 +
  23 + def title
  24 + 'Campfire'
  25 + end
  26 +
  27 + def description
  28 + 'Simple web-based real-time group chat'
  29 + end
  30 +
  31 + def to_param
  32 + 'campfire'
  33 + end
  34 +
  35 + def fields
  36 + [
  37 + { type: 'text', name: 'token', placeholder: '' },
  38 + { type: 'text', name: 'subdomain', placeholder: '' },
  39 + { type: 'text', name: 'room', placeholder: '' }
  40 + ]
  41 + end
  42 +
  43 + def execute(push_data)
  44 + room = gate.find_room_by_name(self.room)
  45 + return true unless room
  46 +
  47 + message = build_message(push_data)
  48 +
  49 + room.speak(message)
  50 + end
  51 +
  52 + private
  53 +
  54 + def gate
  55 + @gate ||= Tinder::Campfire.new(subdomain, token: token)
  56 + end
  57 +
  58 + def build_message(push)
  59 + ref = push[:ref].gsub("refs/heads/", "")
  60 + before = push[:before]
  61 + after = push[:after]
  62 +
  63 + message = ""
  64 + message << "[#{project.name_with_namespace}] "
  65 + message << "#{push[:user_name]} "
  66 +
  67 + if before =~ /000000/
  68 + message << "pushed new branch #{ref} \n"
  69 + elsif after =~ /000000/
  70 + message << "removed branch #{ref} \n"
  71 + else
  72 + message << "pushed #{push[:total_commits_count]} commits to #{ref}. "
  73 + message << "#{project.web_url}/compare/#{before}...#{after}"
  74 + end
  75 +
  76 + message
  77 + end
  78 +end
... ...
app/models/project_services/emails_on_push_service.rb 0 → 100644
... ... @@ -0,0 +1,44 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +class EmailsOnPushService < Service
  19 + attr_accessible :recipients
  20 +
  21 + validates :recipients, presence: true, if: :activated?
  22 +
  23 + def title
  24 + 'Emails on push'
  25 + end
  26 +
  27 + def description
  28 + 'Email the commits and diff of each push to a list of recipients.'
  29 + end
  30 +
  31 + def to_param
  32 + 'emails_on_push'
  33 + end
  34 +
  35 + def execute(push_data)
  36 + EmailsOnPushWorker.perform_async(project_id, recipients, push_data)
  37 + end
  38 +
  39 + def fields
  40 + [
  41 + { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' },
  42 + ]
  43 + end
  44 +end
... ...
app/models/project_services/flowdock_service.rb 0 → 100644
... ... @@ -0,0 +1,54 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +require "flowdock-git-hook"
  19 +
  20 +class FlowdockService < Service
  21 + validates :token, presence: true, if: :activated?
  22 +
  23 + def title
  24 + 'Flowdock'
  25 + end
  26 +
  27 + def description
  28 + 'Flowdock is a collaboration web app for technical teams.'
  29 + end
  30 +
  31 + def to_param
  32 + 'flowdock'
  33 + end
  34 +
  35 + def fields
  36 + [
  37 + { type: 'text', name: 'token', placeholder: '' }
  38 + ]
  39 + end
  40 +
  41 + def execute(push_data)
  42 + repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
  43 + Flowdock::Git.post(
  44 + push_data[:ref],
  45 + push_data[:before],
  46 + push_data[:after],
  47 + token: token,
  48 + repo: repo_path,
  49 + repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}",
  50 + commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s",
  51 + diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s",
  52 + )
  53 + end
  54 +end
... ...
app/models/project_services/gitlab_ci_service.rb 0 → 100644
... ... @@ -0,0 +1,78 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +class GitlabCiService < Service
  19 + attr_accessible :project_url
  20 +
  21 + validates :project_url, presence: true, if: :activated?
  22 + validates :token, presence: true, if: :activated?
  23 +
  24 + delegate :execute, to: :service_hook, prefix: nil
  25 +
  26 + after_save :compose_service_hook, if: :activated?
  27 +
  28 + def compose_service_hook
  29 + hook = service_hook || build_service_hook
  30 + hook.url = [project_url, "/build", "?token=#{token}"].join("")
  31 + hook.save
  32 + end
  33 +
  34 + def commit_status_path sha
  35 + project_url + "/builds/#{sha}/status.json?token=#{token}"
  36 + end
  37 +
  38 + def commit_status sha
  39 + response = HTTParty.get(commit_status_path(sha))
  40 +
  41 + if response.code == 200 and response["status"]
  42 + response["status"]
  43 + else
  44 + :error
  45 + end
  46 + end
  47 +
  48 + def build_page sha
  49 + project_url + "/builds/#{sha}"
  50 + end
  51 +
  52 + def builds_path
  53 + project_url + "?ref=" + project.default_branch
  54 + end
  55 +
  56 + def status_img_path
  57 + project_url + "/status.png?ref=" + project.default_branch
  58 + end
  59 +
  60 + def title
  61 + 'GitLab CI'
  62 + end
  63 +
  64 + def description
  65 + 'Continuous integration server from GitLab'
  66 + end
  67 +
  68 + def to_param
  69 + 'gitlab_ci'
  70 + end
  71 +
  72 + def fields
  73 + [
  74 + { type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' },
  75 + { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3'}
  76 + ]
  77 + end
  78 +end
... ...
app/models/project_services/hipchat_service.rb 0 → 100644
... ... @@ -0,0 +1,74 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +class HipchatService < Service
  19 + attr_accessible :room
  20 +
  21 + validates :token, presence: true, if: :activated?
  22 +
  23 + def title
  24 + 'Hipchat'
  25 + end
  26 +
  27 + def description
  28 + 'Private group chat and IM'
  29 + end
  30 +
  31 + def to_param
  32 + 'hipchat'
  33 + end
  34 +
  35 + def fields
  36 + [
  37 + { type: 'text', name: 'token', placeholder: '' },
  38 + { type: 'text', name: 'room', placeholder: '' }
  39 + ]
  40 + end
  41 +
  42 + def execute(push_data)
  43 + gate[room].send('Gitlab', create_message(push_data))
  44 + end
  45 +
  46 + private
  47 +
  48 + def gate
  49 + @gate ||= HipChat::Client.new(token)
  50 + end
  51 +
  52 + def create_message(push)
  53 + ref = push[:ref].gsub("refs/heads/", "")
  54 + before = push[:before]
  55 + after = push[:after]
  56 +
  57 + message = ""
  58 + message << "#{push[:user_name]} "
  59 + if before =~ /000000/
  60 + message << "pushed new branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> to <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a>\n"
  61 + elsif after =~ /000000/
  62 + message << "removed branch #{ref} from <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> \n"
  63 + else
  64 + message << "#pushed to branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> "
  65 + message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> "
  66 + message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
  67 + for commit in push[:commits] do
  68 + message << "<br /> - #{commit[:message]} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)"
  69 + end
  70 + end
  71 +
  72 + message
  73 + end
  74 +end
... ...
app/models/project_services/pivotaltracker_service.rb 0 → 100644
... ... @@ -0,0 +1,62 @@
  1 +# == Schema Information
  2 +#
  3 +# Table name: services
  4 +#
  5 +# id :integer not null, primary key
  6 +# type :string(255)
  7 +# title :string(255)
  8 +# token :string(255)
  9 +# project_id :integer not null
  10 +# created_at :datetime not null
  11 +# updated_at :datetime not null
  12 +# active :boolean default(FALSE), not null
  13 +# project_url :string(255)
  14 +# subdomain :string(255)
  15 +# room :string(255)
  16 +#
  17 +
  18 +class PivotaltrackerService < Service
  19 + include HTTParty
  20 +
  21 + validates :token, presence: true, if: :activated?
  22 +
  23 + def title
  24 + 'PivotalTracker'
  25 + end
  26 +
  27 + def description
  28 + 'Project Management Software (Source Commits Endpoint)'
  29 + end
  30 +
  31 + def to_param
  32 + 'pivotaltracker'
  33 + end
  34 +
  35 + def fields
  36 + [
  37 + { type: 'text', name: 'token', placeholder: '' }
  38 + ]
  39 + end
  40 +
  41 + def execute(push)
  42 + url = 'https://www.pivotaltracker.com/services/v5/source_commits'
  43 + push[:commits].each do |commit|
  44 + message = {
  45 + 'source_commit' => {
  46 + 'commit_id' => commit[:id],
  47 + 'author' => commit[:author][:name],
  48 + 'url' => commit[:url],
  49 + 'message' => commit[:message]
  50 + }
  51 + }
  52 + PivotaltrackerService.post(
  53 + url,
  54 + body: message.to_json,
  55 + headers: {
  56 + 'Content-Type' => 'application/json',
  57 + 'X-TrackerToken' => token
  58 + }
  59 + )
  60 + end
  61 + end
  62 +end
... ...
app/views/notify/repository_push_email.html.haml 0 → 100644
... ... @@ -0,0 +1,23 @@
  1 +%h3 #{@author.name} pushed to #{@branch} at #{@project.name_with_namespace}
  2 +
  3 +%h4 Commits:
  4 +
  5 +%ul
  6 + - @commits.each do |commit|
  7 + %li
  8 + #{commit.short_id} - #{commit.title}
  9 +
  10 +%h4 Diff:
  11 +- @diffs.each do |diff|
  12 + %li
  13 + %strong
  14 + - if diff.old_path == diff.new_path
  15 + = diff.new_path
  16 + - elsif diff.new_path && diff.old_path
  17 + #{diff.old_path} &rarr; #{diff.new_path}
  18 + - else
  19 + = diff.new_path || diff.old_path
  20 + %hr
  21 + %pre
  22 + = diff.diff
  23 + %br
... ...
app/views/notify/repository_push_email.text.haml 0 → 100644
... ... @@ -0,0 +1,20 @@
  1 +#{@author.name} pushed to #{@branch} at #{@project.name_with_namespace}
  2 +
  3 +\
  4 +Commits:
  5 +- @commits.each do |commit|
  6 + #{commit.short_id} - #{truncate(commit.title, length: 40)}
  7 +\
  8 +\
  9 +Diff:
  10 +- @diffs.each do |diff|
  11 + \
  12 + \=====================================
  13 + - if diff.old_path == diff.new_path
  14 + = diff.new_path
  15 + - elsif diff.new_path && diff.old_path
  16 + #{diff.old_path} &rarr; #{diff.new_path}
  17 + - else
  18 + = diff.new_path || diff.old_path
  19 + \=====================================
  20 + = diff.diff
... ...
app/views/projects/services/_form.html.haml
... ... @@ -33,6 +33,8 @@
33 33 .controls
34 34 - if type == 'text'
35 35 = f.text_field name, class: "input-xlarge", placeholder: placeholder
  36 + - elsif type == 'textarea'
  37 + = f.text_area name, rows: 5, class: "input-xxlarge", placeholder: placeholder
36 38 - elsif type == 'checkbox'
37 39 = f.check_box name
38 40  
... ...
app/views/projects/services/index.html.haml
... ... @@ -3,7 +3,7 @@
3 3 %hr
4 4  
5 5 %ul.bordered-list
6   - - @services.each do |service|
  6 + - @services.sort_by(&:title).each do |service|
7 7 %li
8 8 %h4
9 9 = link_to edit_project_service_path(@project, service.to_param) do
... ...
app/workers/emails_on_push_worker.rb 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +class EmailsOnPushWorker
  2 + include Sidekiq::Worker
  3 +
  4 + def perform(project_id, recipients, push_data)
  5 + project = Project.find(project_id)
  6 + before_sha = push_data["before"]
  7 + after_sha = push_data["after"]
  8 + branch = push_data["ref"]
  9 + author_id = push_data["user_id"]
  10 +
  11 + if before_sha =~ /^000000/ || after_sha =~ /^000000/
  12 + # skip if new branch was pushed or branch was removed
  13 + return true
  14 + end
  15 +
  16 + compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha)
  17 +
  18 + # Do not send emails if git compare failed
  19 + return false unless compare && compare.commits.present?
  20 +
  21 + recipients.split(" ").each do |recipient|
  22 + Notify.delay.repository_push_email(project_id, recipient, author_id, branch, compare)
  23 + end
  24 + end
  25 +end
... ...
config/application.rb
... ... @@ -12,7 +12,7 @@ module Gitlab
12 12 # -- all .rb files in that directory are automatically loaded.
13 13  
14 14 # Custom directories with classes and modules you want to be autoloadable.
15   - config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/concerns)
  15 + config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/models/concerns #{config.root}/app/models/project_services)
16 16  
17 17 # Only load the plugins named here, in the order given (default is alphabetical).
18 18 # :all can be used as a placeholder for all plugins not explicitly named.
... ...
db/migrate/20131217102743_add_recipients_to_service.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +class AddRecipientsToService < ActiveRecord::Migration
  2 + def change
  3 + add_column :services, :recipients, :text
  4 + end
  5 +end
... ...
db/schema.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 12 # It's strongly recommended that you check this file into your version control system.
13 13  
14   -ActiveRecord::Schema.define(version: 20131214224427) do
  14 +ActiveRecord::Schema.define(version: 20131217102743) do
15 15  
16 16 create_table "broadcast_messages", force: true do |t|
17 17 t.text "message", null: false
... ... @@ -219,6 +219,7 @@ ActiveRecord::Schema.define(version: 20131214224427) do
219 219 t.string "project_url"
220 220 t.string "subdomain"
221 221 t.string "room"
  222 + t.text "recipients"
222 223 end
223 224  
224 225 add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree
... ...
features/project/service.feature
... ... @@ -35,4 +35,10 @@ Feature: Project Services
35 35 When I visit project "Shop" services page
36 36 And I click Assembla service link
37 37 And I fill Assembla settings
38   - Then I should see Assembla service settings saved
39 38 \ No newline at end of file
  39 + Then I should see Assembla service settings saved
  40 +
  41 + Scenario: Activate email on push service
  42 + When I visit project "Shop" services page
  43 + And I click email on push service link
  44 + And I fill email on push settings
  45 + Then I should see email on push service settings saved
... ...
features/steps/project/project_services.rb
... ... @@ -3,11 +3,11 @@ class ProjectServices &lt; Spinach::FeatureSteps
3 3 include SharedProject
4 4 include SharedPaths
5 5  
6   - When 'I visit project "Shop" services page' do
  6 + step 'I visit project "Shop" services page' do
7 7 visit project_services_path(@project)
8 8 end
9 9  
10   - Then 'I should see list of available services' do
  10 + step 'I should see list of available services' do
11 11 page.should have_content 'Services'
12 12 page.should have_content 'Campfire'
13 13 page.should have_content 'Hipchat'
... ... @@ -15,76 +15,89 @@ class ProjectServices &lt; Spinach::FeatureSteps
15 15 page.should have_content 'Assembla'
16 16 end
17 17  
18   - And 'I click gitlab-ci service link' do
  18 + step 'I click gitlab-ci service link' do
19 19 click_link 'GitLab CI'
20 20 end
21 21  
22   - And 'I fill gitlab-ci settings' do
  22 + step 'I fill gitlab-ci settings' do
23 23 check 'Active'
24 24 fill_in 'Project url', with: 'http://ci.gitlab.org/projects/3'
25 25 fill_in 'Token', with: 'verySecret'
26 26 click_button 'Save'
27 27 end
28 28  
29   - Then 'I should see service settings saved' do
  29 + step 'I should see service settings saved' do
30 30 find_field('Project url').value.should == 'http://ci.gitlab.org/projects/3'
31 31 end
32 32  
33   - And 'I click hipchat service link' do
  33 + step 'I click hipchat service link' do
34 34 click_link 'Hipchat'
35 35 end
36 36  
37   - And 'I fill hipchat settings' do
  37 + step 'I fill hipchat settings' do
38 38 check 'Active'
39 39 fill_in 'Room', with: 'gitlab'
40 40 fill_in 'Token', with: 'verySecret'
41 41 click_button 'Save'
42 42 end
43 43  
44   - Then 'I should see hipchat service settings saved' do
  44 + step 'I should see hipchat service settings saved' do
45 45 find_field('Room').value.should == 'gitlab'
46 46 end
47 47  
48 48  
49   - And 'I click pivotaltracker service link' do
  49 + step 'I click pivotaltracker service link' do
50 50 click_link 'PivotalTracker'
51 51 end
52 52  
53   - And 'I fill pivotaltracker settings' do
  53 + step 'I fill pivotaltracker settings' do
54 54 check 'Active'
55 55 fill_in 'Token', with: 'verySecret'
56 56 click_button 'Save'
57 57 end
58 58  
59   - Then 'I should see pivotaltracker service settings saved' do
  59 + step 'I should see pivotaltracker service settings saved' do
60 60 find_field('Token').value.should == 'verySecret'
61 61 end
62 62  
63   - And 'I click Flowdock service link' do
  63 + step 'I click Flowdock service link' do
64 64 click_link 'Flowdock'
65 65 end
66 66  
67   - And 'I fill Flowdock settings' do
  67 + step 'I fill Flowdock settings' do
68 68 check 'Active'
69 69 fill_in 'Token', with: 'verySecret'
70 70 click_button 'Save'
71 71 end
72 72  
73   - Then 'I should see Flowdock service settings saved' do
  73 + step 'I should see Flowdock service settings saved' do
74 74 find_field('Token').value.should == 'verySecret'
75 75 end
76 76  
77   - And 'I click Assembla service link' do
  77 + step 'I click Assembla service link' do
78 78 click_link 'Assembla'
79 79 end
80 80  
81   - And 'I fill Assembla settings' do
  81 + step 'I fill Assembla settings' do
82 82 check 'Active'
83 83 fill_in 'Token', with: 'verySecret'
84 84 click_button 'Save'
85 85 end
86 86  
87   - Then 'I should see Assembla service settings saved' do
  87 + step 'I should see Assembla service settings saved' do
88 88 find_field('Token').value.should == 'verySecret'
89 89 end
  90 +
  91 + step 'I click email on push service link' do
  92 + click_link 'Emails on push'
  93 + end
  94 +
  95 + step 'I fill email on push settings' do
  96 + fill_in 'Recipients', with: 'qa@company.name'
  97 + click_button 'Save'
  98 + end
  99 +
  100 + step 'I should see email on push service settings saved' do
  101 + find_field('Recipients').value.should == 'qa@company.name'
  102 + end
90 103 end
... ...
spec/mailers/notify_spec.rb
... ... @@ -391,4 +391,28 @@ describe Notify do
391 391 should have_body_text /#{example_site_path}/
392 392 end
393 393 end
  394 +
  395 + describe 'email on push' do
  396 + let(:example_site_path) { root_path }
  397 + let(:user) { create(:user) }
  398 + let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, 'cd5c4bac', 'b1e6a9db') }
  399 +
  400 + subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) }
  401 +
  402 + it 'is sent to recipient' do
  403 + should deliver_to 'devs@company.name'
  404 + end
  405 +
  406 + it 'has the correct subject' do
  407 + should have_subject /New push to repository/
  408 + end
  409 +
  410 + it 'includes commits list' do
  411 + should have_body_text /tree css fixes/
  412 + end
  413 +
  414 + it 'includes diffs' do
  415 + should have_body_text /Checkout wiki pages for installation information/
  416 + end
  417 + end
394 418 end
... ...