aboutsummaryrefslogtreecommitdiffstats
path: root/Library/Homebrew/cmd/gist-logs.rb
blob: 5954c031ebc2d68cafba025067a57cea8b604cac (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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
require "formula"
require "cmd/config"
require "net/http"
require "net/https"
require "stringio"
require "socket"

module Homebrew
  def gistify_logs(f)
    files = load_logs(f.logs)
    build_time = f.logs.ctime
    timestamp = build_time.strftime("%Y-%m-%d_%H-%M-%S")

    s = StringIO.new
    Homebrew.dump_verbose_config(s)
    # Dummy summary file, asciibetically first, to control display title of gist
    files["# #{f.name} - #{timestamp}.txt"] = { :content => brief_build_info(f) }
    files["00.config.out"] = { :content => s.string }
    files["00.doctor.out"] = { :content => `brew doctor 2>&1` }
    unless f.core_formula?
      tap = <<-EOS.undent
        Formula: #{f.name}
        Tap: #{f.tap}
        Path: #{f.path}
      EOS
      files["00.tap.out"] = { :content => tap }
    end

    # Description formatted to work well as page title when viewing gist
    if f.core_formula?
      descr = "#{f.name} on #{OS_VERSION} - Homebrew build logs"
    else
      descr = "#{f.name} (#{f.full_name}) on #{OS_VERSION} - Homebrew build logs"
    end
    url = create_gist(files, descr)

    if ARGV.include?("--new-issue") || ARGV.switch?("n")
      auth = :AUTH_TOKEN

      if GitHub.api_credentials_type == :none
        puts "You can create a personal access token: https://github.com/settings/tokens"
        puts "and then set HOMEBREW_GITHUB_API_TOKEN as authentication method."
        puts

        auth = :AUTH_USER_LOGIN
      end

      url = new_issue(f.tap, "#{f.name} failed to build on #{MacOS.full_version}", url, auth)
    end

    puts url if url
  end

  def brief_build_info(f)
    build_time_str = f.logs.ctime.strftime("%Y-%m-%d %H:%M:%S")
    s = <<-EOS.undent
      Homebrew build logs for #{f.full_name} on #{OS_VERSION}
    EOS
    if ARGV.include?("--with-hostname")
      hostname = Socket.gethostname
      s << "Host: #{hostname}\n"
    end
    s << "Build date: #{build_time_str}\n"
    s
  end

  # Hack for ruby < 1.9.3
  def noecho_gets
    system "stty -echo"
    result = $stdin.gets
    system "stty echo"
    puts
    result
  end

  def login(request)
    print "GitHub User: "
    user = $stdin.gets.chomp
    print "Password: "
    password = noecho_gets.chomp
    puts
    request.basic_auth(user, password)
  end

  def load_logs(dir)
    logs = {}
    dir.children.sort.each do |file|
      contents = file.size? ? file.read : "empty log"
      logs[file.basename.to_s] = { :content => contents }
    end if dir.exist?
    raise "No logs." if logs.empty?
    logs
  end

  def create_gist(files, descr)
    post("/gists", { "public" => true, "files" => files, "description" => descr })["html_url"]
  end

  def new_issue(repo, title, body, auth)
    post("/repos/#{repo}/issues", { "title" => title, "body" => body }, auth)["html_url"]
  end

  def http
    @http ||= begin
      uri = URI.parse("https://api.github.com")
      p = ENV["http_proxy"] ? URI.parse(ENV["http_proxy"]) : nil
      if p.class == URI::HTTP || p.class == URI::HTTPS
        @http = Net::HTTP.new(uri.host, uri.port, p.host, p.port, p.user, p.password)
      else
        @http = Net::HTTP.new(uri.host, uri.port)
      end
      @http.use_ssl = true
      @http
    end
  end

  def make_request(path, data, auth)
    headers = GitHub.api_headers
    headers["Content-Type"] = "application/json"

    basic_auth_credentials = nil
    if auth != :AUTH_USER_LOGIN
      token, username = GitHub.api_credentials
      case GitHub.api_credentials_type
      when :keychain
        basic_auth_credentials = [username, token]
      when :environment
        headers["Authorization"] = "token #{token}"
      end
    end

    request = Net::HTTP::Post.new(path, headers)
    request.basic_auth(*basic_auth_credentials) if basic_auth_credentials

    login(request) if auth == :AUTH_USER_LOGIN

    request.body = Utils::JSON.dump(data)
    request
  end

  def post(path, data, auth = nil)
    request = make_request(path, data, auth)

    case response = http.request(request)
    when Net::HTTPCreated
      Utils::JSON.load get_body(response)
    else
      GitHub.api_credentials_error_message(response)
      raise "HTTP #{response.code} #{response.message} (expected 201)"
    end
  end

  def get_body(response)
    if !response.body.respond_to?(:force_encoding)
      response.body
    elsif response["Content-Type"].downcase == "application/json; charset=utf-8"
      response.body.dup.force_encoding(Encoding::UTF_8)
    else
      response.body.encode(Encoding::UTF_8, :undef => :replace)
    end
  end

  def gist_logs
    if ARGV.resolved_formulae.length != 1
      puts "usage: brew gist-logs [--new-issue|-n] <formula>"
      Homebrew.failed = true
      return
    end

    gistify_logs(ARGV.resolved_formulae[0])
  end
end