txgh-server 3.1.0 → 4.0.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +5 -5
  2. data/lib/txgh-server.rb +1 -0
  3. data/lib/txgh-server/application.rb +7 -1
  4. data/lib/txgh-server/download_handler.rb +1 -1
  5. data/lib/txgh-server/gitlab_request_auth.rb +13 -0
  6. data/lib/txgh-server/triggers/handler.rb +18 -1
  7. data/lib/txgh-server/triggers/pull_handler.rb +1 -10
  8. data/lib/txgh-server/triggers/push_handler.rb +1 -10
  9. data/lib/txgh-server/version.rb +1 -1
  10. data/lib/txgh-server/webhooks.rb +2 -0
  11. data/lib/txgh-server/webhooks/git.rb +15 -0
  12. data/lib/txgh-server/webhooks/git/blank_attributes.rb +20 -0
  13. data/lib/txgh-server/webhooks/git/delete_attributes.rb +54 -0
  14. data/lib/txgh-server/webhooks/git/delete_handler.rb +45 -0
  15. data/lib/txgh-server/webhooks/git/ping_handler.rb +21 -0
  16. data/lib/txgh-server/webhooks/git/push_attributes.rb +79 -0
  17. data/lib/txgh-server/webhooks/git/push_handler.rb +98 -0
  18. data/lib/txgh-server/webhooks/git/request_handler.rb +76 -0
  19. data/lib/txgh-server/webhooks/git/status_updater.rb +15 -0
  20. data/lib/txgh-server/webhooks/github/blank_attributes.rb +1 -13
  21. data/lib/txgh-server/webhooks/github/delete_attributes.rb +1 -38
  22. data/lib/txgh-server/webhooks/github/delete_handler.rb +1 -38
  23. data/lib/txgh-server/webhooks/github/ping_handler.rb +1 -12
  24. data/lib/txgh-server/webhooks/github/push_attributes.rb +1 -51
  25. data/lib/txgh-server/webhooks/github/push_handler.rb +2 -88
  26. data/lib/txgh-server/webhooks/github/request_handler.rb +2 -47
  27. data/lib/txgh-server/webhooks/github/status_updater.rb +1 -9
  28. data/lib/txgh-server/webhooks/gitlab.rb +14 -0
  29. data/lib/txgh-server/webhooks/gitlab/blank_attributes.rb +8 -0
  30. data/lib/txgh-server/webhooks/gitlab/delete_attributes.rb +17 -0
  31. data/lib/txgh-server/webhooks/gitlab/delete_handler.rb +8 -0
  32. data/lib/txgh-server/webhooks/gitlab/push_attributes.rb +29 -0
  33. data/lib/txgh-server/webhooks/gitlab/push_handler.rb +13 -0
  34. data/lib/txgh-server/webhooks/gitlab/request_handler.rb +59 -0
  35. data/lib/txgh-server/webhooks/gitlab/status_updater.rb +37 -0
  36. data/lib/txgh-server/webhooks/transifex/request_handler.rb +1 -1
  37. data/spec/application_spec.rb +11 -11
  38. data/spec/download_handler_spec.rb +6 -8
  39. data/spec/github_request_auth_spec.rb +6 -8
  40. data/spec/gitlab_request_auth_spec.rb +26 -0
  41. data/spec/helpers/gitlab_payload_builder.rb +132 -0
  42. data/spec/integration/hooks_spec.rb +1 -1
  43. data/spec/tgz_stream_response_spec.rb +2 -4
  44. data/spec/transifex_request_auth_spec.rb +6 -8
  45. data/spec/webhooks/github/delete_attributes_spec.rb +2 -5
  46. data/spec/webhooks/github/delete_handler_spec.rb +4 -7
  47. data/spec/webhooks/github/ping_handler_spec.rb +2 -5
  48. data/spec/webhooks/github/push_attributes_spec.rb +3 -6
  49. data/spec/webhooks/github/push_handler_spec.rb +5 -8
  50. data/spec/webhooks/github/request_handler_spec.rb +8 -10
  51. data/spec/webhooks/github/status_updater_spec.rb +2 -4
  52. data/spec/webhooks/gitlab/delete_attributes_spec.rb +27 -0
  53. data/spec/webhooks/gitlab/delete_handler_spec.rb +34 -0
  54. data/spec/webhooks/gitlab/push_attributes_spec.rb +64 -0
  55. data/spec/webhooks/gitlab/push_handler_spec.rb +99 -0
  56. data/spec/webhooks/gitlab/request_handler_spec.rb +73 -0
  57. data/spec/webhooks/gitlab/status_updater_spec.rb +66 -0
  58. data/spec/webhooks/transifex/hook_handler_spec.rb +2 -5
  59. data/spec/webhooks/transifex/request_handler_spec.rb +3 -5
  60. data/spec/zip_stream_response_spec.rb +2 -4
  61. data/txgh-server.gemspec +1 -2
  62. metadata +35 -10
  63. data/spec/integration/payloads/github_postbody_l10n.json +0 -136
@@ -3,15 +3,7 @@ require 'octokit'
3
3
  module TxghServer
4
4
  module Webhooks
5
5
  module Github
6
- class StatusUpdater
7
- attr_reader :project, :repo, :branch
8
-
9
- def initialize(project, repo, branch)
10
- @project = project
11
- @repo = repo
12
- @branch = branch
13
- end
14
-
6
+ class StatusUpdater < TxghServer::Webhooks::Git::StatusUpdater
15
7
  def report_error_and_update_status(error)
16
8
  error_params = Txgh.events.publish_error!(error)
17
9
 
@@ -0,0 +1,14 @@
1
+ module TxghServer
2
+ module Webhooks
3
+ module Gitlab
4
+ autoload :BlankAttributes, 'txgh-server/webhooks/gitlab/blank_attributes'
5
+ autoload :DeleteAttributes, 'txgh-server/webhooks/gitlab/delete_attributes'
6
+ autoload :DeleteHandler, 'txgh-server/webhooks/gitlab/delete_handler'
7
+ autoload :Handler, 'txgh-server/webhooks/gitlab/handler'
8
+ autoload :PushHandler, 'txgh-server/webhooks/gitlab/push_handler'
9
+ autoload :PushAttributes, 'txgh-server/webhooks/gitlab/push_attributes'
10
+ autoload :RequestHandler, 'txgh-server/webhooks/gitlab/request_handler'
11
+ autoload :StatusUpdater, 'txgh-server/webhooks/gitlab/status_updater'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ module TxghServer
2
+ module Webhooks
3
+ module Gitlab
4
+ class BlankAttributes < TxghServer::Webhooks::Git::BlankAttributes
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,17 @@
1
+ module TxghServer
2
+ module Webhooks
3
+ module Gitlab
4
+ class DeleteAttributes < TxghServer::Webhooks::Git::DeleteAttributes
5
+ class << self
6
+ def repo_name(payload)
7
+ payload.fetch('repository').fetch('name')
8
+ end
9
+
10
+ def ref_type(_payload)
11
+ 'branch'
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+ module TxghServer
2
+ module Webhooks
3
+ module Gitlab
4
+ class DeleteHandler < TxghServer::Webhooks::Git::DeleteHandler
5
+ end
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,29 @@
1
+ module TxghServer
2
+ module Webhooks
3
+ module Gitlab
4
+ class PushAttributes < TxghServer::Webhooks::Git::PushAttributes
5
+ class << self
6
+ def repo_name(payload)
7
+ payload.fetch('repository').fetch('name')
8
+ end
9
+
10
+ def added_files(payload)
11
+ extract_files(payload, 'added')
12
+ end
13
+
14
+ def modified_files(payload)
15
+ extract_files(payload, 'modified')
16
+ end
17
+
18
+ def author(payload)
19
+ payload.fetch('user_name')
20
+ end
21
+
22
+ def extract_files(payload, state)
23
+ payload.fetch('commits').flat_map { |c| c[state] }.uniq
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ module TxghServer
2
+ module Webhooks
3
+ module Gitlab
4
+ class PushHandler < TxghServer::Webhooks::Git::PushHandler
5
+ private
6
+
7
+ def status_updater
8
+ @status_updater = TxghServer::Webhooks::Gitlab::StatusUpdater.new(project, repo, branch)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,59 @@
1
+ require 'json'
2
+
3
+ module TxghServer
4
+ module Webhooks
5
+ module Gitlab
6
+ class RequestHandler < TxghServer::Webhooks::Git::RequestHandler
7
+ def handle_request
8
+ handle_safely do
9
+ if gitlab_event == 'Push Hook'
10
+ if delete_event?
11
+ DeleteHandler.new(project, repo, logger, attributes).execute
12
+ else
13
+ PushHandler.new(project, repo, logger, attributes).execute
14
+ end
15
+ else
16
+ respond_with_error(400, 'Unexpected event type')
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def attributes
24
+ unless gitlab_event == 'Push Hook'
25
+ return BlankAttributes.from_webhook_payload(payload)
26
+ end
27
+
28
+ if delete_event?
29
+ return DeleteAttributes.from_webhook_payload(payload)
30
+ end
31
+
32
+ PushAttributes.from_webhook_payload(payload)
33
+ end
34
+
35
+ def gitlab_event
36
+ request.env['HTTP_X_GITLAB_EVENT']
37
+ end
38
+
39
+ def delete_event?
40
+ payload.fetch('after', {}) == '0000000000000000000000000000000000000000'
41
+ end
42
+
43
+ def git_repo_name
44
+ payload.fetch('project', {})['path_with_namespace']
45
+ end
46
+
47
+ def authentic_request?
48
+ if repo.webhook_protected?
49
+ GitlabRequestAuth.authentic_request?(
50
+ request, repo.webhook_secret
51
+ )
52
+ else
53
+ true
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,37 @@
1
+ require 'gitlab'
2
+
3
+ module TxghServer
4
+ module Webhooks
5
+ module Gitlab
6
+ class StatusUpdater < TxghServer::Webhooks::Git::StatusUpdater
7
+ def report_error_and_update_status(error)
8
+ error_params = Txgh.events.publish_error!(error)
9
+
10
+ Txgh.events.publish_each('gitlab.status.error', error_params) do |status_params|
11
+ if status_params
12
+ update_status_safely do
13
+ Txgh::GitlabStatus.error(project, repo, branch, status_params)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ def update_status
20
+ update_status_safely do
21
+ Txgh::GitlabStatus.update(project, repo, branch)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def update_status_safely
28
+ yield
29
+ rescue ::Gitlab::Error::Unprocessable, ::Gitlab::Error::BadRequest
30
+ # raised because we've tried to create too many statuses for the commit
31
+ rescue Txgh::TransifexNotFoundError
32
+ # raised if transifex resource can't be found
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -29,7 +29,7 @@ module TxghServer
29
29
  handle_safely do
30
30
  handler = TxghServer::Webhooks::Transifex::HookHandler.new(
31
31
  project: config.transifex_project,
32
- repo: config.github_repo,
32
+ repo: config.git_repo,
33
33
  resource_slug: payload[:resource],
34
34
  language: payload[:language],
35
35
  logger: logger
@@ -94,7 +94,7 @@ describe TxghServer::WebhookEndpoints do
94
94
  end
95
95
 
96
96
  let(:config) do
97
- Txgh::Config::ConfigPair.new(project_config, repo_config)
97
+ Txgh::Config::ConfigPair.new(project_config, github_config)
98
98
  end
99
99
 
100
100
  before(:each) do
@@ -103,7 +103,7 @@ describe TxghServer::WebhookEndpoints do
103
103
  )
104
104
 
105
105
  allow(Txgh::Config::KeyManager).to(
106
- receive(:config_from_repo).with(repo_name).and_return(config)
106
+ receive(:config_from_repo).with(github_repo_name).and_return(config)
107
107
  )
108
108
  end
109
109
 
@@ -145,7 +145,7 @@ describe TxghServer::WebhookEndpoints do
145
145
  allow(TxghServer::Webhooks::Transifex::HookHandler).to(
146
146
  receive(:new) do |options|
147
147
  expect(options[:project].name).to eq(project_name)
148
- expect(options[:repo].name).to eq(repo_name)
148
+ expect(options[:repo].name).to eq(github_repo_name)
149
149
  handler
150
150
  end
151
151
  )
@@ -186,7 +186,7 @@ describe TxghServer::WebhookEndpoints do
186
186
  header(
187
187
  TxghServer::GithubRequestAuth::GITHUB_HEADER,
188
188
  TxghServer::GithubRequestAuth.compute_signature(
189
- body, config.github_repo.webhook_secret
189
+ body, config.git_repo.webhook_secret
190
190
  )
191
191
  )
192
192
  end
@@ -198,7 +198,7 @@ describe TxghServer::WebhookEndpoints do
198
198
  allow(TxghServer::Webhooks::Github::PushHandler).to(
199
199
  receive(:new) do |project, repo, logger, attributes|
200
200
  expect(project.name).to eq(project_name)
201
- expect(repo.name).to eq(repo_name)
201
+ expect(repo.name).to eq(github_repo_name)
202
202
  handler
203
203
  end
204
204
  )
@@ -209,7 +209,7 @@ describe TxghServer::WebhookEndpoints do
209
209
  receive(:execute).and_return(respond_with(200, true))
210
210
  )
211
211
 
212
- payload = GithubPayloadBuilder.push_payload(repo_name, ref)
212
+ payload = GithubPayloadBuilder.push_payload(github_repo_name, ref)
213
213
  payload.add_commit
214
214
 
215
215
  sign_with payload.to_json
@@ -220,7 +220,7 @@ describe TxghServer::WebhookEndpoints do
220
220
  end
221
221
 
222
222
  it 'returns unauthorized if not properly signed' do
223
- payload = GithubPayloadBuilder.push_payload(repo_name, ref)
223
+ payload = GithubPayloadBuilder.push_payload(github_repo_name, ref)
224
224
 
225
225
  header 'X-GitHub-Event', 'push'
226
226
  post '/github', payload.to_json
@@ -229,7 +229,7 @@ describe TxghServer::WebhookEndpoints do
229
229
  end
230
230
 
231
231
  it 'returns invalid request if event unrecognized' do
232
- payload = GithubPayloadBuilder.push_payload(repo_name, ref)
232
+ payload = GithubPayloadBuilder.push_payload(github_repo_name, ref)
233
233
 
234
234
  sign_with payload.to_json
235
235
  header 'X-GitHub-Event', 'foobar'
@@ -239,7 +239,7 @@ describe TxghServer::WebhookEndpoints do
239
239
  end
240
240
 
241
241
  it 'returns internal error on unexpected error' do
242
- payload = GithubPayloadBuilder.push_payload(repo_name, ref)
242
+ payload = GithubPayloadBuilder.push_payload(github_repo_name, ref)
243
243
 
244
244
  expect(Txgh::Config::KeyManager).to(
245
245
  receive(:config_from_repo).and_raise(StandardError)
@@ -266,7 +266,7 @@ describe TxghServer::TriggerEndpoints do
266
266
  end
267
267
 
268
268
  let(:config) do
269
- Txgh::Config::ConfigPair.new(project_config, repo_config)
269
+ Txgh::Config::ConfigPair.new(project_config, github_config)
270
270
  end
271
271
 
272
272
  before(:each) do
@@ -275,7 +275,7 @@ describe TxghServer::TriggerEndpoints do
275
275
  )
276
276
 
277
277
  allow(Txgh::Config::KeyManager).to(
278
- receive(:config_from_repo).with(repo_name).and_return(config)
278
+ receive(:config_from_repo).with(github_repo_name).and_return(config)
279
279
  )
280
280
  end
281
281
 
@@ -2,12 +2,10 @@ require 'spec_helper'
2
2
  require 'helpers/standard_txgh_setup'
3
3
  require 'yaml'
4
4
 
5
- include TxghServer
6
-
7
- describe DownloadHandler do
5
+ describe TxghServer::DownloadHandler do
8
6
  include StandardTxghSetup
9
7
 
10
- let(:format) { DownloadHandler::DEFAULT_FORMAT }
8
+ let(:format) { described_class::DEFAULT_FORMAT }
11
9
 
12
10
  let(:params) do
13
11
  {
@@ -30,7 +28,7 @@ describe DownloadHandler do
30
28
  end
31
29
 
32
30
  it 'responds with a streaming zip and has the project name as the attachment' do
33
- response = DownloadHandler.handle_request(request)
31
+ response = described_class.handle_request(request)
34
32
  expect(response).to be_streaming
35
33
  expect(response).to be_a(ZipStreamResponse)
36
34
  expect(response.attachment).to eq(project_name)
@@ -40,7 +38,7 @@ describe DownloadHandler do
40
38
  let(:format) { '.tgz' }
41
39
 
42
40
  it 'responds with a streaming tgz download' do
43
- response = DownloadHandler.handle_request(request)
41
+ response = described_class.handle_request(request)
44
42
  expect(response).to be_streaming
45
43
  expect(response).to be_a(TgzStreamResponse)
46
44
  end
@@ -49,7 +47,7 @@ describe DownloadHandler do
49
47
  context 'when an error occurs' do
50
48
  before(:each) do
51
49
  expect(request).to receive(:params).and_raise(StandardError)
52
- response = DownloadHandler.handle_request(request)
50
+ response = described_class.handle_request(request)
53
51
  expect(response).to_not be_streaming
54
52
  expect(response.status).to eq(500)
55
53
  end
@@ -58,7 +56,7 @@ describe DownloadHandler do
58
56
 
59
57
  context '#execute' do
60
58
  let(:handler) do
61
- DownloadHandler.new(transifex_project, github_repo, params, logger)
59
+ described_class.new(transifex_project, github_repo, params, logger)
62
60
  end
63
61
 
64
62
  it 'responds with a streaming zip download' do
@@ -1,9 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'rack'
3
3
 
4
- include TxghServer
5
-
6
- describe GithubRequestAuth do
4
+ describe TxghServer::GithubRequestAuth do
7
5
  let(:secret) { 'abc123' }
8
6
  let(:params) { '{"param1":"value1","param2":"value2","param3":123}' }
9
7
  let(:valid_signature) { 'ea62c3f65c8e42f155d96a25b7ba6eb5d320630e' }
@@ -11,28 +9,28 @@ describe GithubRequestAuth do
11
9
  describe '.authentic_request?' do
12
10
  it 'returns true if the request is signed correctly' do
13
11
  request = Rack::Request.new(
14
- GithubRequestAuth::RACK_HEADER => "sha1=#{valid_signature}",
12
+ described_class::RACK_HEADER => "sha1=#{valid_signature}",
15
13
  'rack.input' => StringIO.new(params)
16
14
  )
17
15
 
18
- authentic = GithubRequestAuth.authentic_request?(request, secret)
16
+ authentic = described_class.authentic_request?(request, secret)
19
17
  expect(authentic).to eq(true)
20
18
  end
21
19
 
22
20
  it 'returns false if the request is not signed correctly' do
23
21
  request = Rack::Request.new(
24
- GithubRequestAuth::RACK_HEADER => 'incorrect',
22
+ described_class::RACK_HEADER => 'incorrect',
25
23
  'rack.input' => StringIO.new(params)
26
24
  )
27
25
 
28
- authentic = GithubRequestAuth.authentic_request?(request, secret)
26
+ authentic = described_class.authentic_request?(request, secret)
29
27
  expect(authentic).to eq(false)
30
28
  end
31
29
  end
32
30
 
33
31
  describe '.compute_signature' do
34
32
  it 'calculates the signature and formats it as an http header' do
35
- value = GithubRequestAuth.compute_signature(params, secret)
33
+ value = described_class.compute_signature(params, secret)
36
34
  expect(value).to eq("sha1=#{valid_signature}")
37
35
  end
38
36
  end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+ require 'rack'
3
+
4
+ describe TxghServer::GitlabRequestAuth do
5
+ let(:secret) { 'abc123' }
6
+
7
+ describe '.authentic_request?' do
8
+ it 'returns true if the request is signed correctly' do
9
+ request = Rack::Request.new(
10
+ described_class::RACK_HEADER => secret
11
+ )
12
+
13
+ authentic = described_class.authentic_request?(request, secret)
14
+ expect(authentic).to eq(true)
15
+ end
16
+
17
+ it 'returns false if the request is not signed correctly' do
18
+ request = Rack::Request.new(
19
+ described_class::RACK_HEADER => 'incorrect'
20
+ )
21
+
22
+ authentic = described_class.authentic_request?(request, secret)
23
+ expect(authentic).to eq(false)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,132 @@
1
+ require 'json'
2
+
3
+ class GitlabPayloadBuilder
4
+ class << self
5
+ def push_payload(*args)
6
+ GitlabPushPayload.new(*args)
7
+ end
8
+
9
+ def delete_payload(*args)
10
+ GitlabDeletePayload.new(*args)
11
+ end
12
+ end
13
+ end
14
+
15
+ class GitlabPayload
16
+ def to_h
17
+ # convert symbolized keys to strings
18
+ JSON.parse(to_json)
19
+ end
20
+
21
+ def to_json
22
+ @result.to_json
23
+ end
24
+
25
+ def merge!(hash)
26
+ @result.merge!(hash)
27
+ end
28
+
29
+ private
30
+
31
+ def digits
32
+ @@digits ||= ('a'..'f').to_a + ('0'..'9').to_a
33
+ end
34
+
35
+ def generate_timestamp
36
+ Time.now.strftime('%Y-%m-%dT%H:%M:%S%:z')
37
+ end
38
+
39
+ def generate_sha
40
+ blank_commit_id.gsub(/0/) { digits.sample }
41
+ end
42
+
43
+ def blank_commit_id
44
+ '0' * 40
45
+ end
46
+ end
47
+
48
+ class GitlabDeletePayload < GitlabPayload
49
+ attr_reader :repo, :ref
50
+
51
+ def initialize(repo, ref)
52
+ @repo = repo
53
+ @ref = ref
54
+
55
+ @result = {
56
+ ref: "refs/#{ref}",
57
+ before: generate_sha,
58
+ after: blank_commit_id,
59
+ project: {
60
+ path_with_namespace: repo
61
+ },
62
+ repository: {
63
+ name: repo.split('/').last
64
+ }
65
+ }
66
+ end
67
+ end
68
+
69
+ class GitlabPushPayload < GitlabPayload
70
+ attr_reader :repo, :ref, :before, :after
71
+
72
+ DEFAULT_USER = {
73
+ name: 'Test User',
74
+ email: 'test@user.com',
75
+ username: 'testuser'
76
+ }
77
+
78
+ def initialize(repo, ref, before = nil, after = nil)
79
+ @repo = repo
80
+ @ref = ref
81
+ @before = before || blank_commit_id
82
+ @after = after || generate_sha
83
+
84
+ @result = {
85
+ ref: "refs/#{ref}",
86
+ before: @before,
87
+ after: @after,
88
+ commits: [],
89
+ user_name: DEFAULT_USER[:username],
90
+ project: {
91
+ path_with_namespace: repo
92
+ },
93
+ repository: {
94
+ name: repo.split('/').last
95
+ }
96
+ }
97
+ end
98
+
99
+ def add_commit(options = {})
100
+ id = if commits.empty? && !options.include?(:id)
101
+ after
102
+ else
103
+ options.fetch(:id) { generate_sha }
104
+ end
105
+
106
+ commit_data = {
107
+ id: id,
108
+ message: options.fetch(:message, 'Default commit message'),
109
+ timestamp: options.fetch(:timestamp) { generate_timestamp },
110
+ url: "https://gitlab.com/#{repo}/-/commit/#{id}",
111
+ author: options.fetch(:author, DEFAULT_USER),
112
+ committer: options.fetch(:committer, DEFAULT_USER),
113
+ added: options.fetch(:added, []),
114
+ removed: options.fetch(:removed, []),
115
+ modified: options.fetch(:modified, [])
116
+ }
117
+
118
+ if commit_data[:id] == after
119
+ @result[:head_commit] = commit_data
120
+ end
121
+
122
+ commits << commit_data
123
+ end
124
+
125
+ def commits
126
+ @result[:commits]
127
+ end
128
+
129
+ def head_commit
130
+ @result[:head_commit]
131
+ end
132
+ end