summaryrefslogtreecommitdiff
path: root/lib/gitlab_post_receive.rb
blob: 44042448a9118de5e8eafcf1d2f99497238ec4e4 (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
145
146
147
148
149
require_relative 'gitlab_init'
require_relative 'gitlab_net'
require_relative 'gitlab_reference_counter'
require_relative 'gitlab_metrics'
require 'json'
require 'base64'
require 'securerandom'

class GitlabPostReceive
  include NamesHelper

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

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

  def exec
    response = GitlabMetrics.measure("post-receive") do
      api.post_receive(gl_repository, @actor, changes)
    end

    return false unless response
    print_broadcast_message(response['broadcast_message']) if response['broadcast_message']
    print_merge_request_links(response['merge_request_urls']) if response['merge_request_urls']
    puts response['redirected_message'] if response['redirected_message']

    response['reference_counter_decreased']
  rescue GitlabNet::ApiUnreachableError
    false
  rescue GitlabNet::NotFound
    fallback_post_receive
  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
    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)
    # TODO: Change to `@gl_repository` in next release.
    # See https://gitlab.com/gitlab-org/gitlab-shell/merge_requests/130#note_28747613
    project_identifier = @gl_repository || @repo_path

    queue = "#{config.redis_namespace}:queue:post_receive"
    msg = JSON.dump({
      'class' => 'PostReceive',
      'args' => [project_identifier, @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

  private

  def fallback_post_receive
    result = update_redis

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

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

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

      api.notify_post_receive(gl_repository, repo_path)
    rescue GitlabNet::ApiUnreachableError
      nil
    end

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