hookworm-handlers 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +3 -0
  5. data/.simplecov +6 -0
  6. data/.travis.yml +17 -0
  7. data/.yardopts +1 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE.md +23 -0
  10. data/README.md +29 -0
  11. data/Rakefile +20 -0
  12. data/bin/hookworm-annotator +6 -0
  13. data/bin/hookworm-build-index-handler +6 -0
  14. data/bin/hookworm-logging-handler +6 -0
  15. data/bin/hookworm-rogue-commit-handler +6 -0
  16. data/hookworm-handlers.gemspec +32 -0
  17. data/lib/hookworm/annotator.rb +49 -0
  18. data/lib/hookworm/build_index_handler.rb +132 -0
  19. data/lib/hookworm/github_payload_annotator.rb +110 -0
  20. data/lib/hookworm/handlers/version.rb +5 -0
  21. data/lib/hookworm/handlers.rb +5 -0
  22. data/lib/hookworm/logging_handler.rb +55 -0
  23. data/lib/hookworm/rogue-commit-email-tmpl.erb +153 -0
  24. data/lib/hookworm/rogue_commit_handler.rb +240 -0
  25. data/lib/hookworm/travis_payload_annotator.rb +23 -0
  26. data/lib/hookworm-handlers.rb +7 -0
  27. data/sampledata/github-payloads/bogus.json +4 -0
  28. data/sampledata/github-payloads/branch_delete.json +41 -0
  29. data/sampledata/github-payloads/pull_request.json +153 -0
  30. data/sampledata/github-payloads/rogue.json +152 -0
  31. data/sampledata/github-payloads/rogue_unwatched_branch.json +152 -0
  32. data/sampledata/github-payloads/rogue_unwatched_path.json +91 -0
  33. data/sampledata/github-payloads/valid.json +66 -0
  34. data/sampledata/github-payloads/with_nulls.json +67 -0
  35. data/sampledata/travis-payloads/annotated_success.json +76 -0
  36. data/sampledata/travis-payloads/success.json +74 -0
  37. data/sampledata/travis-payloads/valid.json +56 -0
  38. data/spec/lib/hookworm/annotator_spec.rb +103 -0
  39. data/spec/lib/hookworm/build_index_handler_spec.rb +110 -0
  40. data/spec/lib/hookworm/logging_handler_spec.rb +134 -0
  41. data/spec/lib/hookworm/rogue_commit_handler_spec.rb +65 -0
  42. data/spec/spec_helper.rb +3 -0
  43. data/spec/support.rb +117 -0
  44. metadata +222 -0
@@ -0,0 +1,153 @@
1
+ From: <%= @from %>
2
+ To: <%= @recipients %>
3
+ Subject: [hookworm] Rogue commit by <%= @head_commit_author %> to <%= @repo %> <%= @ref %> (<%= @head_commit_id %>)
4
+ Date: <%= @date %>
5
+ Message-ID: <<%= @message_id %>@<%= @hostname %>>
6
+ List-ID: <%= @repo %> <hookworm.github.com>
7
+ Content-Type: multipart/alternative;
8
+ boundary="--==ZOMGBOUNDARAAAYYYYY";
9
+ charset=UTF-8
10
+ Content-Transfer-Encoding: 7bit
11
+
12
+ ----==ZOMGBOUNDARAAAYYYYY
13
+ Date: <%= @date %>
14
+ Mime-Version: 1.0
15
+ Content-Type: text/plain; charset=utf8
16
+ Content-Transfer-Encoding: 7bit
17
+
18
+ Rogue commit detected!
19
+
20
+ Repo <%= @repo %>
21
+ Ref <%= @ref %>
22
+ ID <%= @head_commit_id %>
23
+ Author <%= @head_commit_author %>
24
+ Committer <%= @head_commit_committer %>
25
+ Timestamp <%= @head_commit_timestamp %>
26
+ Message <%= @head_commit_message_text %>
27
+
28
+ --
29
+ This email was sent by hookworm: https://github.com/modcloth-labs/hookworm
30
+
31
+ A rogue commit is a commit made directly to a branch that is being watched such
32
+ that only pull requests should be merged into them.
33
+
34
+ The configured watched branches are:
35
+
36
+ <% (@watched_branches || []).each do |branch| %> - <%= branch %>
37
+ <% end %>
38
+
39
+ The configured watched paths are:
40
+
41
+ <% (@watched_paths || []).each do |path| %> - <%= path %>
42
+ <% end %>
43
+
44
+
45
+ If you believe this rogue commit email is an error, you should hunt down the
46
+ party responsible for the hookworm instance registered as a WebHook URL in this
47
+ repo's service hook settings (<%= @repo_url %>/settings/hooks).
48
+
49
+ Pretty please submit issues specific to hookworm functionality on github:
50
+ https://github.com/modcloth-labs/hookworm/issues/new
51
+
52
+ ----==ZOMGBOUNDARAAAYYYYY
53
+ Date: <%= @date %>
54
+ Mime-Version: 1.0
55
+ Content-Type: text/html; charset=utf8
56
+ Content-Transfer-Encoding: 7bit
57
+
58
+ <div>
59
+ <h1><a href="<%= @head_commit_url %>">Rogue commit detected!</a></h1>
60
+
61
+ <table>
62
+ <thead><th></th><th></th></thead>
63
+ <tbody>
64
+ <tr>
65
+ <td style="text-align:right;vertical-align:top;">
66
+ <strong>Repo</strong>:
67
+ </td>
68
+ <td><%= @repo %></td>
69
+ </tr>
70
+ <tr>
71
+ <td style="text-align:right;vertical-align:top;">
72
+ <strong>Ref</strong>:
73
+ </td>
74
+ <td><%= @ref %></td>
75
+ </tr>
76
+ <tr>
77
+ <td style="text-align:right;vertical-align:top;">
78
+ <strong>ID</strong>:
79
+ </td>
80
+ <td><a href="<%= @head_commit_url %>"><%= @head_commit_id %></a></td>
81
+ </tr>
82
+ <tr>
83
+ <td style="text-align:right;vertical-align:top;">
84
+ <strong>Author</strong>:
85
+ </td>
86
+ <td><%= @head_commit_author %></td>
87
+ </tr>
88
+ <tr>
89
+ <td style="text-align:right;vertical-align:top;">
90
+ <strong>Committer</strong>:
91
+ </td>
92
+ <td><%= @head_commit_committer %></td>
93
+ </tr>
94
+ <tr>
95
+ <td style="text-align:right;vertical-align:top;">
96
+ <strong>Timestamp</strong>:
97
+ </td>
98
+ <td><%= @head_commit_timestamp %></td>
99
+ </tr>
100
+ <tr>
101
+ <td style="text-align:right;vertical-align:top;">
102
+ <strong>Message</strong>:
103
+ </td>
104
+ <td><%= @head_commit_message_html %></td>
105
+ </tr>
106
+ </tbody>
107
+ </table>
108
+ </div>
109
+
110
+ <hr/>
111
+ <div style="font-size:.8em">
112
+ <p>
113
+ This email was sent by
114
+ <a href="https://github.com/modcloth-labs/hookworm">hookworm</a>.
115
+ </p>
116
+
117
+ <p>
118
+ A rogue commit is a commit made directly to a branch that is being watched such
119
+ that only pull requests should be merged into them.
120
+ </p>
121
+
122
+ <p>
123
+ The configured watched branches are:
124
+ </p>
125
+
126
+ <ul>
127
+ <% (@watched_branches || []).each do |branch| %><li><strong><%= branch %></strong></li>
128
+ <% end %>
129
+ </ul>
130
+
131
+ <p>
132
+ The configured watched paths are:
133
+ </p>
134
+
135
+ <ul>
136
+ <% (@watched_paths || []).each do |path| %><li><strong><%= path %></strong></li>
137
+ <% end %>
138
+ </ul>
139
+
140
+ <p>
141
+ If you believe this rogue commit email is an error, you should hunt down the
142
+ party responsible for the hookworm instance registered as a WebHook URL in
143
+ this repo's <a href="<%= @repo_url %>/settings/hooks">service hook settings</a>.
144
+ </p>
145
+
146
+ <p>
147
+ Pretty please submit issues specific to hookworm functionality
148
+ <a href="https://github.com/modcloth-labs/hookworm/issues/new">on github</a>
149
+ </p>
150
+
151
+ </div>
152
+
153
+ ----==ZOMGBOUNDARAAAYYYYY--
@@ -0,0 +1,240 @@
1
+ # vim:fileencoding=utf-8
2
+
3
+ require 'erb'
4
+ require 'json'
5
+ require 'net/smtp'
6
+ require 'time'
7
+ require 'uri'
8
+ require 'hookworm-base'
9
+ require 'hookworm/emailer'
10
+
11
+ module Hookworm
12
+ # # Hookworm Rogue Commit Handler
13
+ #
14
+ # The rogue commit handler is specific to GitHub payloads. It will inspect a
15
+ # payload in the context of the given `watched_branches` and `watched_paths`
16
+ # and send a "rogue commit email" to the email recipients given in
17
+ # `email_recipients` to provide visibility roughly equivalent to those
18
+ # commits that result from pull request merges.
19
+ #
20
+ # Because the rogue commit handler is affected by so many arguments, here
21
+ # they are again with more details about their associated behavior:
22
+ #
23
+ # ## `watched_branches`
24
+ # The `watched_branches` argument should be a comma-delimited list of regular
25
+ # expressions, e.g.: `watched_branches='^master$,^release_[0-9]'`. If a
26
+ # commit payload is received that was not the result of a pull request merge
27
+ # and the Hookworm Annotator handler has determined that the branch name
28
+ # matches any of the entries in `watched_branches`, then a rogue commit email
29
+ # will be sent.
30
+ #
31
+ # ## `watched_paths`
32
+ # The `watched_paths` argument should be a comma-delimited list of regular
33
+ # expressions, e.g.: `watched_paths='.*\.(go|rb|py)$,bin/.*'`. If a commit
34
+ # payload is received that was not the result of a pull request merge and the
35
+ # Hookworm Annotator handler has determined that one of the commits in the
36
+ # payload contains a path matching any of the entries in `watched_paths`,
37
+ # then a rogue commit email will be sent.
38
+ #
39
+ # ## `email_from_addr`
40
+ # The `email_from_addr` is the email address used as the `From` header and
41
+ # SMTP MAIL address when sending rogue commit emails, e.g.:
42
+ # `email_from_addr='hookworm-noreply@company.example.com'`.
43
+ #
44
+ # ## `email_recipients`
45
+ # The `email_recipients` argument should be a comma-delimited list of email
46
+ # addresses (without display name) used in the `To` header and SMTP RCPT
47
+ # addresses when sending rogue commit emails, e.g.:
48
+ # `email_recipients='devs@example.com,proj+hookworm@partner.example.net'`
49
+ #
50
+ # ## `email_uri`
51
+ # The `email_uri` argument should be a well-formed URI containing the SMTP
52
+ # hostname and port and potentially the username and password used for plain
53
+ # SMTP auth, e.g.:
54
+ # `email_uri='smtp://hookworm:secret@mailhost.example.com:1587'`
55
+ class RogueCommitHandler
56
+ include Hookworm::Base
57
+
58
+ private
59
+
60
+ def handle_github
61
+ payload = JSON.parse(input_stream.read, symbolize_names: true)
62
+ begin
63
+ Judger.new(cfg).judge_payload(payload)
64
+ rescue StandardError
65
+ return 1
66
+ ensure
67
+ output_stream.puts JSON.pretty_generate(payload)
68
+ end
69
+ 0
70
+ end
71
+
72
+ def handle_travis
73
+ 78
74
+ end
75
+ end
76
+
77
+ class Judger
78
+ attr_reader :cfg
79
+
80
+ def initialize(cfg)
81
+ @cfg = cfg
82
+ end
83
+
84
+ def judge_payload(payload)
85
+ return unless watched_branch?(payload)
86
+ hcid = payload[:head_commit][:id]
87
+ return if pr_merge?(payload, hcid)
88
+ return unless watched_path?(payload, hcid)
89
+ safe_send_rogue_commit_email!(payload)
90
+ end
91
+
92
+ def new_judge_payload(payload)
93
+ return unless watched_branch?(payload)
94
+ hcid = payload[:head_commit][:id]
95
+ return if pr_merge?(payload, hcid)
96
+ return unless watched_path?(payload, hcid)
97
+ safe_send_rogue_commit_email!(payload)
98
+ end
99
+
100
+ def watched_branch?(payload)
101
+ unless payload[:is_watched_branch]
102
+ log.debug { "#{payload[:ref]} is not a watched branch, yay!" }
103
+ return false
104
+ end
105
+
106
+ log.debug { "#{payload[:ref]} is a watched branch!" }
107
+ true
108
+ end
109
+
110
+ def pr_merge?(payload, hcid)
111
+ if payload[:is_pr_merge]
112
+ log.info { "#{hcid} is a pull request merge, yay!" }
113
+ return true
114
+ end
115
+
116
+ log.debug { "#{hcid} is not a pull request merge!" }
117
+ false
118
+ end
119
+
120
+ def watched_path?(payload, hcid)
121
+ unless payload[:has_watched_path]
122
+ log.debug { "#{hcid} does not contain watched paths, yay!" }
123
+ return false
124
+ end
125
+
126
+ log.debug { "#{hcid} contains watched paths!" }
127
+ true
128
+ end
129
+
130
+ def safe_send_rogue_commit_email!(payload)
131
+ send_rogue_commit_email!(payload)
132
+ log.debug { "Sent rogue commit email to #{recipients}" }
133
+ rescue => e
134
+ log.error { "#{e.class.name} #{e.message}" }
135
+ log.debug { e.backtrace.join("\n") }
136
+ raise e
137
+ end
138
+
139
+ def send_rogue_commit_email!(payload)
140
+ log_rogue_commit(payload)
141
+
142
+ if recipients.empty?
143
+ log.warn { 'No email recipients specified, so no emailing!' }
144
+ return
145
+ end
146
+
147
+ email_body = render_email(payload)
148
+ log.debug { "Email message:\n#{email_body}" }
149
+
150
+ emailer.send(fromaddr, recipients, email_body)
151
+ end
152
+
153
+ def render_email(payload)
154
+ EmailRenderContext.new(self, payload, rogue_commit_email_tmpl).render
155
+ end
156
+
157
+ def rogue_commit_email_tmpl
158
+ ERB.new(rogue_commit_email_tmpl_string)
159
+ end
160
+
161
+ def rogue_commit_email_tmpl_string
162
+ File.read(File.expand_path('../rogue-commit-email-tmpl.erb', __FILE__))
163
+ end
164
+
165
+ def recipients
166
+ @recipients ||= (cfg[:worm_flags][:email_recipients] || '').commasplit
167
+ end
168
+
169
+ def fromaddr
170
+ @fromaddr ||= cfg[:worm_flags][:email_from_addr]
171
+ end
172
+
173
+ def watched_branches
174
+ @watched_branches ||= (
175
+ cfg[:worm_flags][:watched_branches] || ''
176
+ ).commasplit
177
+ end
178
+
179
+ def watched_paths
180
+ @watched_paths ||= (cfg[:worm_flags][:watched_paths] || '').commasplit
181
+ end
182
+
183
+ def emailer
184
+ @emailer ||= Hookworm::Emailer.new(cfg[:worm_flags][:email_uri])
185
+ end
186
+
187
+ def log_rogue_commit(payload)
188
+ log.warn do
189
+ "WARNING rogue commit! #{payload}, " <<
190
+ "head commit: #{payload[:head_commit]}"
191
+ end
192
+ end
193
+
194
+ def log
195
+ @log ||= Logger.new($stderr)
196
+ end
197
+
198
+ class EmailRenderContext
199
+ def initialize(judger, payload, tmpl)
200
+ assign_judger_vars(judger)
201
+ assign_payload_vars(payload)
202
+ @tmpl = tmpl
203
+ @date = Time.now.utc.rfc2822
204
+ @message_id = Time.now.strftime('%s%9N')
205
+ @hostname = Socket.gethostname
206
+ assign_head_commit_vars(payload[:head_commit])
207
+ end
208
+
209
+ def assign_judger_vars(judger)
210
+ @cfg = judger.cfg
211
+ @from = judger.fromaddr
212
+ @recipients = judger.recipients.join(', ')
213
+ @watched_branches = judger.watched_branches
214
+ @watched_paths = judger.watched_paths
215
+ end
216
+
217
+ def assign_payload_vars(payload)
218
+ @payload = payload
219
+ @repo = "#{payload[:repository][:owner][:name]}/" <<
220
+ "#{payload[:repository][:name]}"
221
+ @ref = payload[:ref]
222
+ @repo_url = payload[:repository][:url]
223
+ end
224
+
225
+ def assign_head_commit_vars(hc)
226
+ @head_commit_id = hc[:id]
227
+ @head_commit_url = hc[:url]
228
+ @head_commit_author = hc[:author][:name]
229
+ @head_commit_committer = hc[:committer][:name]
230
+ @head_commit_message_text = hc[:message].to_plaintext
231
+ @head_commit_message_html = hc[:message].to_html
232
+ @head_commit_timestamp = hc[:timestamp]
233
+ end
234
+
235
+ def render
236
+ @tmpl.result(binding)
237
+ end
238
+ end
239
+ end
240
+ end
@@ -0,0 +1,23 @@
1
+ module Hookworm
2
+ class TravisPayloadAnnotator
3
+ def annotate(payload)
4
+ annotated = payload.clone
5
+ add_repo_slug!(annotated)
6
+ annotated[:success] = success?(payload)
7
+ annotated
8
+ end
9
+
10
+ private
11
+
12
+ def add_repo_slug!(payload)
13
+ repo = (payload[:repository] || {})
14
+ if repo[:owner_name] && repo[:name]
15
+ repo[:slug] = "#{repo[:owner_name]}/#{repo[:name]}"
16
+ end
17
+ end
18
+
19
+ def success?(payload)
20
+ payload[:result] == 0 && payload[:result_message].downcase == 'passed'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,7 @@
1
+ module Hookworm
2
+ autoload :Annotator, 'hookworm/annotator'
3
+ autoload :BuildIndexHandler, 'hookworm/build_index_handler'
4
+ autoload :Handlers, 'hookworm/handlers'
5
+ autoload :LoggingHandler, 'hookworm/logging_handler'
6
+ autoload :RogueCommitHandler, 'hookworm/rogue_commit_handler'
7
+ end
@@ -0,0 +1,4 @@
1
+ {
2
+ "fizz": "buzz",
3
+ "bonk": 9000
4
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "ref": "refs/heads/ensuring-watched-branches-and-paths-are-quote-free",
3
+ "after": "0000000000000000000000000000000000000000",
4
+ "before": "a1fcfc8fe4017bccb4f156ac5a786c08a6968517",
5
+ "created": false,
6
+ "deleted": true,
7
+ "forced": true,
8
+ "compare": "https://github.com/modcloth-labs/hookworm/compare/a1fcfc8fe401...000000000000",
9
+ "commits": [],
10
+ "head_commit": null,
11
+ "repository": {
12
+ "id": 10417548,
13
+ "name": "hookworm",
14
+ "url": "https://github.com/modcloth-labs/hookworm",
15
+ "description": "GitHub & Travis hook receiving thingydoo",
16
+ "homepage": "",
17
+ "watchers": 2,
18
+ "stargazers": 2,
19
+ "forks": 2,
20
+ "fork": false,
21
+ "size": 612,
22
+ "owner": {
23
+ "name": "modcloth-labs",
24
+ "email": "labs@modcloth.com"
25
+ },
26
+ "private": false,
27
+ "open_issues": 0,
28
+ "has_issues": true,
29
+ "has_downloads": true,
30
+ "has_wiki": true,
31
+ "language": "Ruby",
32
+ "created_at": 1370060266,
33
+ "pushed_at": 1378653604,
34
+ "master_branch": "master",
35
+ "organization": "modcloth-labs"
36
+ },
37
+ "pusher": {
38
+ "name": "meatballhat",
39
+ "email": "dan@meatballhat.com"
40
+ }
41
+ }
@@ -0,0 +1,153 @@
1
+ {
2
+ "ref": "refs/heads/master",
3
+ "after": "d0afb08e931361f179a7e0fd29daec7ca1663233",
4
+ "before": "926ed297f149354424c1968924bf944c5b69a7ec",
5
+ "created": false,
6
+ "deleted": false,
7
+ "forced": false,
8
+ "compare": "https://github.com/modcloth-labs/hookworm/compare/926ed297f149...d0afb08e9313",
9
+ "commits": [
10
+ {
11
+ "id": "7afdcae33a1903e52d0290e738b868d944ce3f60",
12
+ "distinct": true,
13
+ "message": "Merge pull request #1 from modcloth-labs/payload-parsing-and-emailing\n\nPayload parsing and emaœling",
14
+ "timestamp": "2013-06-01T19:28:39-07:00",
15
+ "url": "https://github.com/modcloth-labs/hookworm/commit/7afdcae33a1903e52d0290e738b868d944ce3f60",
16
+ "author": {
17
+ "name": "Dan Buch",
18
+ "email": "daniel.buch@gmail.com",
19
+ "username": "meatballhat"
20
+ },
21
+ "committer": {
22
+ "name": "Dan Buch",
23
+ "email": "daniel.buch@gmail.com",
24
+ "username": "meatballhat"
25
+ },
26
+ "added": [
27
+ "emailer.go",
28
+ "example-payload.json",
29
+ "payload.go"
30
+ ],
31
+ "removed": [
32
+
33
+ ],
34
+ "modified": [
35
+ "Makefile",
36
+ "server.go"
37
+ ]
38
+ },
39
+ {
40
+ "id": "323fa5bebc49cbeac869a242682a56c83d4566cc",
41
+ "distinct": true,
42
+ "message": "Handling JSON nulls all over the place\n\nwhich I may end up reverting in favor of the {}interface + type\nassertion approach.",
43
+ "timestamp": "2013-06-02T09:42:59-07:00",
44
+ "url": "https://github.com/modcloth-labs/hookworm/commit/323fa5bebc49cbeac869a242682a56c83d4566cc",
45
+ "author": {
46
+ "name": "Dan Buch",
47
+ "email": "d.buch@modcloth.com",
48
+ "username": "meatballhat"
49
+ },
50
+ "committer": {
51
+ "name": "Dan Buch",
52
+ "email": "d.buch@modcloth.com",
53
+ "username": "meatballhat"
54
+ },
55
+ "added": [
56
+ "sampledata/payload-with-nulls.json",
57
+ "sampledata/payload.json"
58
+ ],
59
+ "removed": [
60
+ "example-payload.json"
61
+ ],
62
+ "modified": [
63
+ "payload.go",
64
+ "server.go"
65
+ ]
66
+ },
67
+ {
68
+ "id": "d0afb08e931361f179a7e0fd29daec7ca1663233",
69
+ "distinct": true,
70
+ "message": "Merge pull request #2 from modcloth-labs/payload-parsing-and-emailing\n\nHandling JSON nulls all over the place",
71
+ "timestamp": "2013-06-02T09:47:08-07:00",
72
+ "url": "https://github.com/modcloth-labs/hookworm/commit/d0afb08e931361f179a7e0fd29daec7ca1663233",
73
+ "author": {
74
+ "name": "Dan Buch",
75
+ "email": "daniel.buch@gmail.com",
76
+ "username": "meatballhat"
77
+ },
78
+ "committer": {
79
+ "name": "Dan Buch",
80
+ "email": "daniel.buch@gmail.com",
81
+ "username": "meatballhat"
82
+ },
83
+ "added": [
84
+ "sampledata/payload-with-nulls.json",
85
+ "sampledata/payload.json"
86
+ ],
87
+ "removed": [
88
+ "example-payload.json"
89
+ ],
90
+ "modified": [
91
+ "payload.go",
92
+ "server.go"
93
+ ]
94
+ }
95
+ ],
96
+ "head_commit": {
97
+ "id": "d0afb08e931361f179a7e0fd29daec7ca1663233",
98
+ "distinct": true,
99
+ "message": "Merge pull request #2 from modcloth-labs/payload-parsing-and-emailing\n\nHandling JSON nulls all over the place",
100
+ "timestamp": "2013-06-02T09:47:08-07:00",
101
+ "url": "https://github.com/modcloth-labs/hookworm/commit/d0afb08e931361f179a7e0fd29daec7ca1663233",
102
+ "author": {
103
+ "name": "Dan Buch",
104
+ "email": "daniel.buch@gmail.com",
105
+ "username": "meatballhat"
106
+ },
107
+ "committer": {
108
+ "name": "Dan Buch",
109
+ "email": "daniel.buch@gmail.com",
110
+ "username": "meatballhat"
111
+ },
112
+ "added": [
113
+ "sampledata/payload-with-nulls.json",
114
+ "sampledata/payload.json"
115
+ ],
116
+ "removed": [
117
+ "example-payload.json"
118
+ ],
119
+ "modified": [
120
+ "payload.go",
121
+ "server.go"
122
+ ]
123
+ },
124
+ "repository": {
125
+ "id": 10417548,
126
+ "name": "hookworm",
127
+ "url": "https://github.com/modcloth-labs/hookworm",
128
+ "description": "github hook receiving thingydoo",
129
+ "watchers": 0,
130
+ "stargazers": 0,
131
+ "forks": 0,
132
+ "fork": false,
133
+ "size": 208,
134
+ "owner": {
135
+ "name": "modcloth-labs",
136
+ "email": null
137
+ },
138
+ "private": false,
139
+ "open_issues": 0,
140
+ "has_issues": true,
141
+ "has_downloads": true,
142
+ "has_wiki": true,
143
+ "language": "Go",
144
+ "created_at": 1370060266,
145
+ "pushed_at": 1370191630,
146
+ "master_branch": "master",
147
+ "organization": "modcloth-labs"
148
+ },
149
+ "pusher": {
150
+ "name": "none"
151
+ },
152
+ "is_pr_merge": true
153
+ }