summaryrefslogtreecommitdiff
path: root/lib/gitlab_post_receive.rb
blob: ee908d8c4c17d7ca4625123735c9d7bb35434c82 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
require_relative 'gitlab_init'
require_relative 'gitlab_net'
require_relative 'gitaly_client'
require_relative 'gitlab_reference_counter'
require_relative 'gitlab_metrics'
require 'json'
require 'base64'
require 'securerandom'

class GitlabPostReceive
  include NamesHelper

  attr_reader :config, :repo_path, :changes, :jid

  def initialize(repo_path, actor, changes)
    @config = GitlabConfig.new
    @repo_path, @actor = repo_path.strip, actor
    @changes = changes
    @jid = SecureRandom.hex(12)
  end

  def exec
    result = update_redis

    begin
      broadcast_message = GitlabMetrics.measure("broadcast-message") do
        api.broadcast_message
      end

      if broadcast_message.has_key?("message")
        puts
        print_broadcast_message(broadcast_message["message"])
      end

      merge_request_urls = GitlabMetrics.measure("merge-request-urls") do
        api.merge_request_urls(@repo_path, @changes)
      end
      print_merge_request_links(merge_request_urls)

      result &&= notify_gitaly
    rescue HttpClient::ApiUnreachableError
      nil
    end

    result && GitlabReferenceCounter.new(repo_path).decrease
  end

  protected

  def api
    @api ||= GitlabNet.new
  end

  def print_merge_request_links(merge_request_urls)
    return if merge_request_urls.empty?
    puts
    merge_request_urls.each { |mr| print_merge_request_link(mr) }
  end

  def print_merge_request_link(merge_request)
    if merge_request["new_merge_request"]
      message = "To create a merge request for #{merge_request["branch_name"]}, visit:"
    else
      message = "View merge request for #{merge_request["branch_name"]}:"
    end

    puts message
    puts((" " * 2) + merge_request["url"])
    puts
  end

  def print_broadcast_message(message)
    # A standard terminal window is (at least) 80 characters wide.
    total_width = 80

    # Git prefixes remote messages with "remote: ", so this width is subtracted
    # from the width available to us.
    total_width -= "remote: ".length

    # Our centered text shouldn't start or end right at the edge of the window,
    # so we add some horizontal padding: 2 chars on either side.
    text_width = total_width - 2 * 2

    # Automatically wrap message at text_width (= 68) characters:
    # Splits the message up into the longest possible chunks matching
    # "<between 0 and text_width characters><space or end-of-line>".
    # The last result is always an empty string (0 chars and the end-of-line),
    # so drop that.
    # message.scan returns a nested array of capture groups, so flatten.
    lines = message.scan(/(.{,#{text_width}})(?:\s|$)/)[0...-1].flatten

    puts "=" * total_width
    puts

    lines.each do |line|
      line.strip!

      # Center the line by calculating the left padding measured in characters.
      line_padding = [(total_width - line.length) / 2, 0].max
      puts((" " * line_padding) + line)
    end

    puts
    puts "=" * total_width
  end

  def update_redis
    # Encode changes as base64 so we don't run into trouble with non-UTF-8 input.
    changes = Base64.encode64(@changes)

    queue = "#{config.redis_namespace}:queue:post_receive"
    msg = JSON.dump({
      'class' => 'PostReceive',
      'args' => [@repo_path, @actor, changes],
      'jid' => @jid,
      'enqueued_at' => Time.now.to_f
    })

    begin
      GitlabNet.new.redis_client.rpush(queue, msg)
      true
    rescue => e
      $stderr.puts "GitLab: An unexpected error occurred in writing to Redis: #{e}"
      false
    end
  end

  def notify_gitaly
    return true unless gitaly_client

    gitaly_client.notify_post_receive(repo_path)
  end

  def gitaly_client
    unless defined?(@gitaly_client)
      socket_path = api.gitaly_socket_path
      @gitaly_client = socket_path && GitalyClient.new(socket_path)
    end

    @gitaly_client
  rescue HttpClient::ApiUnreachableError
    nil
  end
end