diff options
-rw-r--r-- | Gemfile | 2 | ||||
-rw-r--r-- | Gemfile.lock | 6 | ||||
-rw-r--r-- | Gemfile.rails5.lock | 6 | ||||
-rw-r--r-- | app/models/project_services/flowdock_service.rb | 2 | ||||
-rw-r--r-- | lib/flowdock/git.rb | 96 | ||||
-rw-r--r-- | lib/flowdock/git/builder.rb | 150 |
6 files changed, 250 insertions, 12 deletions
@@ -210,7 +210,7 @@ gem 'hipchat', '~> 1.5.0' gem 'jira-ruby', '~> 1.4' # Flowdock integration -gem 'gitlab-flowdock-git-hook', '~> 1.0.1' +gem 'flowdock', '~> 0.7' # Slack integration gem 'slack-notifier', '~> 1.5.1' diff --git a/Gemfile.lock b/Gemfile.lock index bd95f417caa..e0c098c3d8d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -278,10 +278,6 @@ GEM google-protobuf (~> 3.1) grpc (~> 1.10) github-markup (1.7.0) - gitlab-flowdock-git-hook (1.0.1) - flowdock (~> 0.7) - gitlab-grit (>= 2.4.1) - multi_json gitlab-gollum-lib (4.2.7.5) gemojione (~> 3.2) github-markup (~> 1.6) @@ -1009,6 +1005,7 @@ DEPENDENCIES flipper (~> 0.13.0) flipper-active_record (~> 0.13.0) flipper-active_support_cache_store (~> 0.13.0) + flowdock (~> 0.7) fog-aliyun (~> 0.2.0) fog-aws (~> 2.0.1) fog-core (~> 1.44) @@ -1025,7 +1022,6 @@ DEPENDENCIES gettext_i18n_rails_js (~> 1.3) gitaly-proto (~> 0.118.1) github-markup (~> 1.7.0) - gitlab-flowdock-git-hook (~> 1.0.1) gitlab-gollum-lib (~> 4.2) gitlab-markup (~> 1.6.4) gitlab-sidekiq-fetcher diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock index 8aebf78924f..56db3b4c9de 100644 --- a/Gemfile.rails5.lock +++ b/Gemfile.rails5.lock @@ -281,10 +281,6 @@ GEM google-protobuf (~> 3.1) grpc (~> 1.10) github-markup (1.7.0) - gitlab-flowdock-git-hook (1.0.1) - flowdock (~> 0.7) - gitlab-grit (>= 2.4.1) - multi_json gitlab-gollum-lib (4.2.7.5) gemojione (~> 3.2) github-markup (~> 1.6) @@ -1018,6 +1014,7 @@ DEPENDENCIES flipper (~> 0.13.0) flipper-active_record (~> 0.13.0) flipper-active_support_cache_store (~> 0.13.0) + flowdock (~> 0.7) fog-aliyun (~> 0.2.0) fog-aws (~> 2.0.1) fog-core (~> 1.44) @@ -1034,7 +1031,6 @@ DEPENDENCIES gettext_i18n_rails_js (~> 1.3) gitaly-proto (~> 0.118.1) github-markup (~> 1.7.0) - gitlab-flowdock-git-hook (~> 1.0.1) gitlab-gollum-lib (~> 4.2) gitlab-markup (~> 1.6.4) gitlab-sidekiq-fetcher diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 2545df06f6b..365792f03c3 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "flowdock-git-hook" +require 'flowdock/git' # Flow dock depends on Grit to compute the number of commits between two given # commits. To make this depend on Gitaly, a monkey patch is applied diff --git a/lib/flowdock/git.rb b/lib/flowdock/git.rb new file mode 100644 index 00000000000..43e729c27d7 --- /dev/null +++ b/lib/flowdock/git.rb @@ -0,0 +1,96 @@ + +require "multi_json" +require "cgi" +require "flowdock" +require "flowdock/git/builder" + +module Flowdock + class Git + class TokenError < StandardError; end + + class << self + def post(ref, from, to, options = {}) + Git.new(ref, from, to, options).post + end + + def background_post(ref, from, to, options = {}) + Git.new(ref, from, to, options).background_post + end + end + + def initialize(ref, from, to, options = {}) + @ref = ref + @from = from + @to = to + @options = options + @token = options[:token] || config["flowdock.token"] || raise(TokenError.new("Flowdock API token not found")) + @commit_url = options[:commit_url] || config["flowdock.commit-url-pattern"] || nil + @diff_url = options[:diff_url] || config["flowdock.diff-url-pattern"] || nil + @repo_url = options[:repo_url] || config["flowdock.repository-url"] || nil + @repo_name = options[:repo_name] || config["flowdock.repository-name"] || nil + @permanent_refs = options[:permanent_refs] || + (config["flowdock.permanent-references"] || "refs/heads/master") + .split(",") + .map(&:strip) + .map {|exp| Regexp.new(exp) } + end + + # Send git push notification to Flowdock + def post + messages.each do |message| + Flowdock::Client.new(flow_token: @token).post_to_thread(message) + end + end + + # Create and post notification in background process. Avoid blocking the push notification. + def background_post + pid = Process.fork + if pid.nil? + Grit::Git.with_timeout(600) do + post + end + else + Process.detach(pid) # Parent + end + end + + def repo + @repo ||= Grit::Repo.new( + @options[:repo] || Dir.pwd, + is_bare: @options[:is_bare] || false + ) + end + + private + + def messages + Git::Builder.new(repo: @repo, + ref: @ref, + before: @from, + after: @to, + commit_url: @commit_url, + branch_url: @branch_url, + diff_url: @diff_url, + repo_url: @repo_url, + repo_name: @repo_name, + permanent_refs: @permanent_refs, + tags: tags + ).to_hashes + end + + # Flowdock tags attached to the push notification + def tags + if @options[:tags] + @options[:tags] + else + config["flowdock.tags"].to_s.split(",").map(&:strip) + end.map do |t| + CGI.escape(t) + end + end + + def config + @config ||= Grit::Config.new(repo) + end + end +end diff --git a/lib/flowdock/git/builder.rb b/lib/flowdock/git/builder.rb new file mode 100644 index 00000000000..4aec6a86df1 --- /dev/null +++ b/lib/flowdock/git/builder.rb @@ -0,0 +1,150 @@ +require "grit" +require 'cgi' +require "securerandom" + +module Flowdock + class Git + class Commit + def initialize(external_thread_id, thread, tags, commit) + @commit = commit + @external_thread_id = external_thread_id + @thread = thread + @tags = tags + end + + def to_hash + hash = { + external_thread_id: @external_thread_id, + event: "activity", + author: { + name: @commit[:author][:name], + email: @commit[:author][:email] + }, + title: title, + thread: @thread, + body: body + } + hash[:tags] = @tags if @tags + encode(hash) + end + + private + + def encode(hash) + return hash unless "".respond_to? :encode + encode_as_utf8(hash) + end + + # This only works on Ruby 1.9 + def encode_as_utf8(obj) + if obj.is_a? Hash + obj.each_pair do |key, val| + encode_as_utf8(val) + end + elsif obj.is_a?(Array) + obj.each do |val| + encode_as_utf8(val) + end + elsif obj.is_a?(String) && obj.encoding != Encoding::UTF_8 + if !obj.force_encoding("UTF-8").valid_encoding? + obj.force_encoding("ISO-8859-1").encode!(Encoding::UTF_8, :invalid => :replace, :undef => :replace) + end + end + end + + def body + content = @commit[:message][first_line.size..-1] + content.strip! if content + "<pre>#{content}</pre>" unless content.empty? + end + + def first_line + @first_line ||= (@commit[:message].split("\n")[0] || @commit[:message]) + end + + def title + commit_id = @commit[:id][0, 7] + if @commit[:url] + "<a href=\"#{@commit[:url]}\">#{commit_id}</a> #{message_title}" + else + "#{commit_id} #{message_title}" + end + end + + def message_title + CGI.escape_html(first_line.strip) + end + end + + # Class used to build Git payload + class Builder + def initialize(opts) + @repo = opts[:repo] + @ref = opts[:ref] + @before = opts[:before] + @after = opts[:after] + @opts = opts + end + + def commits + @repo.commits_between(@before, @after).map do |commit| + { + url: if @opts[:commit_url] then @opts[:commit_url] % [commit.sha] end, + id: commit.sha, + message: commit.message, + author: { + name: commit.author.name, + email: commit.author.email + } + } + end + end + + def ref_name + @ref.to_s.sub(/\Arefs\/(heads|tags)\//, '') + end + + def to_hashes + commits.map do |commit| + Commit.new(external_thread_id, thread, @opts[:tags], commit).to_hash + end + end + + private + + def thread + @thread ||= { + title: thread_title, + external_url: @opts[:repo_url] + } + end + + def permanent? + @permanent ||= @opts[:permanent_refs].select do |regex| + regex.match(@ref) + end.size > 0 + end + + def thread_title + action = if permanent? + "updated" + end + type = if @ref.match(%r(^refs/heads/)) + "branch" + else + "tag" + end + [@opts[:repo_name], type, ref_name, action].compact.join(" ") + end + + def external_thread_id + @external_thread_id ||= + if permanent? + SecureRandom.hex + else + @ref + end + end + end + end +end |