shipit-engine 0.32.0 → 0.33.0

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 (176) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/images/magic-solid.svg +1 -0
  3. data/app/assets/javascripts/shipit/repositories_search.js.coffee +60 -0
  4. data/app/assets/javascripts/shipit/{search.js.coffee → stack_search.js.coffee} +0 -0
  5. data/app/assets/stylesheets/_pages/_repositories.scss +148 -0
  6. data/app/assets/stylesheets/_pages/_stacks.scss +19 -0
  7. data/app/assets/stylesheets/shipit.scss +1 -0
  8. data/app/controllers/shipit/api/{pull_requests_controller.rb → merge_requests_controller.rb} +8 -8
  9. data/app/controllers/shipit/api/stacks_controller.rb +14 -1
  10. data/app/controllers/shipit/deploys_controller.rb +2 -2
  11. data/app/controllers/shipit/merge_requests_controller.rb +31 -0
  12. data/app/controllers/shipit/merge_status_controller.rb +15 -15
  13. data/app/controllers/shipit/repositories_controller.rb +74 -0
  14. data/app/controllers/shipit/tasks_controller.rb +4 -4
  15. data/app/helpers/shipit/chunks_helper.rb +2 -2
  16. data/app/helpers/shipit/github_url_helper.rb +8 -0
  17. data/app/helpers/shipit/stacks_helper.rb +4 -0
  18. data/app/jobs/shipit/create_on_github_job.rb +1 -0
  19. data/app/jobs/shipit/destroy_repository_job.rb +24 -0
  20. data/app/jobs/shipit/destroy_stack_job.rb +2 -2
  21. data/app/jobs/shipit/perform_task_job.rb +4 -98
  22. data/app/jobs/shipit/process_merge_requests_job.rb +32 -0
  23. data/app/jobs/shipit/refresh_merge_request_job.rb +11 -0
  24. data/app/models/shipit/anonymous_user.rb +4 -0
  25. data/app/models/shipit/check_run.rb +2 -2
  26. data/app/models/shipit/command_line_user.rb +4 -0
  27. data/app/models/shipit/commit.rb +11 -11
  28. data/app/models/shipit/commit_checks.rb +1 -0
  29. data/app/models/shipit/deploy.rb +1 -0
  30. data/app/models/shipit/deploy_spec.rb +16 -4
  31. data/app/models/shipit/deploy_spec/file_system.rb +11 -5
  32. data/app/models/shipit/hook.rb +2 -0
  33. data/app/models/shipit/merge_request.rb +302 -0
  34. data/app/models/shipit/provisioning_handler.rb +32 -0
  35. data/app/models/shipit/provisioning_handler/base.rb +30 -0
  36. data/app/models/shipit/provisioning_handler/unregistered_provisioning_handler.rb +35 -0
  37. data/app/models/shipit/pull_request.rb +25 -264
  38. data/app/models/shipit/pull_request_assignment.rb +10 -0
  39. data/app/models/shipit/repository.rb +54 -0
  40. data/app/models/shipit/review_stack.rb +116 -0
  41. data/app/models/shipit/review_stack_provisioning_queue.rb +39 -0
  42. data/app/models/shipit/stack.rb +22 -8
  43. data/app/models/shipit/task.rb +56 -7
  44. data/app/models/shipit/task_execution_strategy/base.rb +20 -0
  45. data/app/models/shipit/task_execution_strategy/default.rb +110 -0
  46. data/app/models/shipit/user.rb +6 -1
  47. data/app/models/shipit/webhooks.rb +10 -0
  48. data/app/models/shipit/webhooks/handlers/pull_request/assigned_handler.rb +74 -0
  49. data/app/models/shipit/webhooks/handlers/pull_request/closed_handler.rb +68 -0
  50. data/app/models/shipit/webhooks/handlers/pull_request/edited_handler.rb +74 -0
  51. data/app/models/shipit/webhooks/handlers/pull_request/label_capturing_handler.rb +127 -0
  52. data/app/models/shipit/webhooks/handlers/pull_request/labeled_handler.rb +106 -0
  53. data/app/models/shipit/webhooks/handlers/pull_request/opened_handler.rb +83 -0
  54. data/app/models/shipit/webhooks/handlers/pull_request/reopened_handler.rb +88 -0
  55. data/app/models/shipit/webhooks/handlers/pull_request/review_stack_adapter.rb +103 -0
  56. data/app/models/shipit/webhooks/handlers/pull_request/unlabeled_handler.rb +107 -0
  57. data/app/serializers/shipit/deploy_serializer.rb +6 -0
  58. data/app/serializers/shipit/merge_request_serializer.rb +21 -0
  59. data/app/serializers/shipit/pull_request_serializer.rb +5 -8
  60. data/app/serializers/shipit/review_stack_serializer.rb +7 -0
  61. data/app/serializers/shipit/stack_serializer.rb +7 -6
  62. data/app/serializers/shipit/tail_task_serializer.rb +10 -2
  63. data/app/serializers/shipit/task_serializer.rb +1 -1
  64. data/app/views/shipit/merge_requests/_merge_request.html.erb +29 -0
  65. data/app/views/shipit/{pull_requests → merge_requests}/index.html.erb +2 -2
  66. data/app/views/shipit/merge_requests/merge_requests/_pull_request.html.erb +29 -0
  67. data/app/views/shipit/merge_requests/merge_requests/index.html.erb +20 -0
  68. data/app/views/shipit/merge_status/_merge_queue_button.html.erb +3 -3
  69. data/app/views/shipit/merge_status/backlogged.html.erb +1 -1
  70. data/app/views/shipit/merge_status/failure.html.erb +1 -1
  71. data/app/views/shipit/merge_status/locked.html.erb +1 -1
  72. data/app/views/shipit/merge_status/success.html.erb +2 -2
  73. data/app/views/shipit/repositories/_header.html.erb +19 -0
  74. data/app/views/shipit/repositories/index.html.erb +31 -0
  75. data/app/views/shipit/repositories/new.html.erb +23 -0
  76. data/app/views/shipit/repositories/settings.html.erb +53 -0
  77. data/app/views/shipit/repositories/show.html.erb +30 -0
  78. data/app/views/shipit/stacks/_banners.html.erb +13 -0
  79. data/app/views/shipit/stacks/_header.html.erb +5 -2
  80. data/app/views/shipit/stacks/_stack.html.erb +8 -0
  81. data/app/views/shipit/stacks/index.html.erb +2 -1
  82. data/app/views/shipit/stacks/settings.html.erb +5 -5
  83. data/app/views/shipit/stacks/show.html.erb +1 -1
  84. data/app/views/shipit/tasks/_task_output.html.erb +1 -1
  85. data/config/routes.rb +15 -5
  86. data/db/migrate/20200706145406_add_review_stacks.rb +12 -0
  87. data/db/migrate/20200804144639_rename_pull_request_to_merge_request.rb +7 -0
  88. data/db/migrate/20200804161512_rename_commits_pull_request_id_to_merge_request_id.rb +5 -0
  89. data/db/migrate/20200813134712_recreate_shipit_pull_requests.rb +22 -0
  90. data/db/migrate/20200813194056_create_pull_request_assignments.rb +8 -0
  91. data/db/migrate/20201001125502_add_provision_pr_stacks_flag_to_repositories.rb +7 -0
  92. data/db/migrate/20201008145809_add_retry_attempt_to_tasks.rb +5 -0
  93. data/db/migrate/20201008152744_add_max_retries_to_tasks.rb +5 -0
  94. data/lib/shipit.rb +11 -1
  95. data/lib/shipit/github_app.rb +1 -1
  96. data/lib/shipit/review_stack_commands.rb +8 -0
  97. data/lib/shipit/stack_commands.rb +6 -1
  98. data/lib/shipit/task_commands.rb +1 -0
  99. data/lib/shipit/version.rb +1 -1
  100. data/lib/tasks/cron.rake +11 -2
  101. data/test/controllers/api/{pull_requests_controller_test.rb → merge_requests_controller_test.rb} +12 -12
  102. data/test/controllers/api/outputs_controller_test.rb +1 -0
  103. data/test/controllers/api/rollback_controller_test.rb +1 -1
  104. data/test/controllers/api/stacks_controller_test.rb +21 -1
  105. data/test/controllers/{pull_requests_controller_test.rb → merge_requests_controller_test.rb} +6 -6
  106. data/test/controllers/repositories_controller_test.rb +71 -0
  107. data/test/controllers/stacks_controller_test.rb +9 -1
  108. data/test/controllers/tasks_controller_test.rb +14 -2
  109. data/test/controllers/webhooks_controller_test.rb +1 -1
  110. data/test/dummy/config/application.rb +6 -1
  111. data/test/dummy/config/environments/development.rb +0 -3
  112. data/test/dummy/config/environments/test.rb +0 -5
  113. data/test/dummy/db/schema.rb +52 -14
  114. data/test/dummy/db/seeds.rb +1 -1
  115. data/test/fixtures/payloads/check_suite_master.json +2 -2
  116. data/test/fixtures/payloads/invalid_pull_request.json +117 -0
  117. data/test/fixtures/payloads/provision_disabled_pull_request.json +454 -0
  118. data/test/fixtures/payloads/pull_request_assigned.json +480 -0
  119. data/test/fixtures/payloads/pull_request_closed.json +454 -0
  120. data/test/fixtures/payloads/pull_request_labeled.json +461 -0
  121. data/test/fixtures/payloads/pull_request_opened.json +454 -0
  122. data/test/fixtures/payloads/pull_request_reopened.json +454 -0
  123. data/test/fixtures/payloads/pull_request_unlabeled.json +454 -0
  124. data/test/fixtures/payloads/pull_request_with_no_repo.json +454 -0
  125. data/test/fixtures/shipit/commits.yml +15 -2
  126. data/test/fixtures/shipit/merge_requests.yml +141 -0
  127. data/test/fixtures/shipit/pull_request_assignments.yml +3 -0
  128. data/test/fixtures/shipit/pull_requests.yml +10 -131
  129. data/test/fixtures/shipit/repositories.yml +1 -0
  130. data/test/fixtures/shipit/stacks.yml +145 -0
  131. data/test/fixtures/shipit/statuses.yml +9 -0
  132. data/test/fixtures/shipit/tasks.yml +3 -0
  133. data/test/fixtures/shipit/users.yml +7 -0
  134. data/test/helpers/payloads_helper.rb +4 -0
  135. data/test/jobs/chunk_rollup_job_test.rb +15 -1
  136. data/test/jobs/destroy_repository_job_test.rb +27 -0
  137. data/test/jobs/perform_task_job_test.rb +8 -8
  138. data/test/jobs/{merge_pull_requests_job_test.rb → process_merge_requests_job_test.rb} +18 -18
  139. data/test/lib/shipit/deploy_commands_test.rb +16 -0
  140. data/test/lib/shipit/task_commands_test.rb +17 -0
  141. data/test/models/commits_test.rb +22 -13
  142. data/test/models/deploy_spec_test.rb +57 -24
  143. data/test/models/deploys_test.rb +148 -14
  144. data/test/models/{pull_request_test.rb → merge_request_test.rb} +30 -30
  145. data/test/models/pull_request_assignment_test.rb +16 -0
  146. data/test/models/shipit/provisioning_handler/base_test.rb +33 -0
  147. data/test/models/shipit/provisioning_handler/unregistered_provisioning_handler_test.rb +49 -0
  148. data/test/models/shipit/provisioning_handler_test.rb +64 -0
  149. data/test/models/shipit/pull_request_test.rb +52 -0
  150. data/test/models/shipit/repository_test.rb +5 -1
  151. data/test/models/shipit/review_stack_provision_status_test.rb +77 -0
  152. data/test/models/shipit/review_stack_provisioning_queue_test.rb +63 -0
  153. data/test/models/shipit/review_stack_test.rb +59 -0
  154. data/test/models/{stacks_test.rb → shipit/stacks_test.rb} +10 -4
  155. data/test/models/shipit/webhooks/handlers/pull_request/assigned_handler_test.rb +45 -0
  156. data/test/models/shipit/webhooks/handlers/pull_request/closed_handler_test.rb +192 -0
  157. data/test/models/shipit/webhooks/handlers/pull_request/edited_handler_test.rb +47 -0
  158. data/test/models/shipit/webhooks/handlers/pull_request/label_capturing_handler_test.rb +209 -0
  159. data/test/models/shipit/webhooks/handlers/pull_request/labeled_handler_test.rb +332 -0
  160. data/test/models/shipit/webhooks/handlers/pull_request/opened_handler_test.rb +238 -0
  161. data/test/models/shipit/webhooks/handlers/pull_request/reopened_handler_test.rb +282 -0
  162. data/test/models/shipit/webhooks/handlers/pull_request/review_stack_adapter_test.rb +83 -0
  163. data/test/models/shipit/webhooks/handlers/pull_request/unlabeled_handler_test.rb +324 -0
  164. data/test/models/shipit/{wehbooks → webhooks}/handlers_test.rb +0 -0
  165. data/test/models/tasks_test.rb +44 -3
  166. data/test/serializers/shipit/pull_request_serializer_test.rb +29 -0
  167. data/test/unit/command_test.rb +3 -3
  168. data/test/unit/github_url_helper_test.rb +5 -0
  169. data/test/unit/shipit_task_execution_strategy_test.rb +47 -0
  170. metadata +260 -154
  171. data/app/controllers/shipit/pull_requests_controller.rb +0 -31
  172. data/app/jobs/shipit/merge_pull_requests_job.rb +0 -32
  173. data/app/jobs/shipit/refresh_pull_request_job.rb +0 -11
  174. data/app/views/shipit/pull_requests/_pull_request.html.erb +0 -29
  175. data/test/fixtures/shipit/output_chunks.yml +0 -47
  176. data/test/models/output_chunk_test.rb +0 -21
@@ -6,6 +6,7 @@ module Shipit
6
6
 
7
7
  def initialize(commit)
8
8
  @commit = commit
9
+ super(commit)
9
10
  end
10
11
 
11
12
  def synchronize(&block)
@@ -92,6 +92,7 @@ module Shipit
92
92
  env: env&.to_h || {},
93
93
  allow_concurrency: force,
94
94
  ignored_safeties: force,
95
+ max_retries: stack.retries_on_rollback,
95
96
  )
96
97
  end
97
98
 
@@ -104,6 +104,10 @@ module Shipit
104
104
  Duration.parse(config('deploy', 'interval') { 0 })
105
105
  end
106
106
 
107
+ def provisioning_handler_name
108
+ config('provision', 'handler_name')
109
+ end
110
+
107
111
  def deploy_steps
108
112
  around_steps('deploy') do
109
113
  config('deploy', 'override') { discover_deploy_steps }
@@ -122,6 +126,10 @@ module Shipit
122
126
  deploy_variables.map { |v| [v.name, v.default] }.to_h
123
127
  end
124
128
 
129
+ def retries_on_deploy
130
+ config('deploy', 'retries') { nil }
131
+ end
132
+
125
133
  def rollback_steps
126
134
  around_steps('rollback') do
127
135
  config('rollback', 'override') { discover_rollback_steps }
@@ -132,6 +140,10 @@ module Shipit
132
140
  rollback_steps || cant_detect!(:rollback)
133
141
  end
134
142
 
143
+ def retries_on_rollback
144
+ config('rollback', 'retries') { nil }
145
+ end
146
+
135
147
  def fetch_deployed_revision_steps
136
148
  config('fetch') || discover_fetch_deployed_revision_steps
137
149
  end
@@ -179,12 +191,12 @@ module Shipit
179
191
  Array.wrap(config('ci', 'blocking'))
180
192
  end
181
193
 
182
- def pull_request_merge_method
194
+ def merge_request_merge_method
183
195
  method = config('merge', 'method')
184
196
  method if %w(merge rebase squash).include?(method)
185
197
  end
186
198
 
187
- def pull_request_required_statuses
199
+ def merge_request_required_statuses
188
200
  if config('merge', 'require') || config('merge', 'ignore')
189
201
  Array.wrap(config('merge', 'require'))
190
202
  else
@@ -192,7 +204,7 @@ module Shipit
192
204
  end
193
205
  end
194
206
 
195
- def pull_request_ignored_statuses
207
+ def merge_request_ignored_statuses
196
208
  if config('merge', 'require') || config('merge', 'ignore')
197
209
  Array.wrap(config('merge', 'ignore')) + [release_status_context].compact
198
210
  else
@@ -200,7 +212,7 @@ module Shipit
200
212
  end
201
213
  end
202
214
 
203
- def revalidate_pull_requests_after
215
+ def revalidate_merge_requests_after
204
216
  if timeout = config('merge', 'revalidate_after')
205
217
  begin
206
218
  Duration.parse(timeout)
@@ -13,6 +13,7 @@ module Shipit
13
13
  def initialize(app_dir, env)
14
14
  @app_dir = Pathname(app_dir)
15
15
  @env = env
16
+ super(nil)
16
17
  end
17
18
 
18
19
  def cacheable
@@ -32,10 +33,10 @@ module Shipit
32
33
  def cacheable_config
33
34
  (config || {}).deep_merge(
34
35
  'merge' => {
35
- 'require' => pull_request_required_statuses,
36
- 'ignore' => pull_request_ignored_statuses,
37
- 'revalidate_after' => revalidate_pull_requests_after&.to_i,
38
- 'method' => pull_request_merge_method,
36
+ 'require' => merge_request_required_statuses,
37
+ 'ignore' => merge_request_ignored_statuses,
38
+ 'revalidate_after' => revalidate_merge_requests_after&.to_i,
39
+ 'method' => merge_request_merge_method,
39
40
  'max_divergence' => {
40
41
  'commits' => max_divergence_commits&.to_i,
41
42
  'age' => max_divergence_age&.to_i,
@@ -63,13 +64,18 @@ module Shipit
63
64
  'delay' => release_status_delay,
64
65
  },
65
66
  'dependencies' => { 'override' => dependencies_steps },
67
+ 'provision' => { 'handler_name' => provisioning_handler_name },
66
68
  'deploy' => {
67
69
  'override' => deploy_steps,
68
70
  'variables' => deploy_variables.map(&:to_h),
69
71
  'max_commits' => maximum_commits_per_deploy,
70
72
  'interval' => pause_between_deploys,
73
+ 'retries' => retries_on_deploy,
74
+ },
75
+ 'rollback' => {
76
+ 'override' => rollback_steps,
77
+ 'retries' => retries_on_rollback,
71
78
  },
72
- 'rollback' => { 'override' => rollback_steps },
73
79
  'fetch' => fetch_deployed_revision_steps,
74
80
  'tasks' => cacheable_tasks,
75
81
  )
@@ -45,6 +45,7 @@ module Shipit
45
45
 
46
46
  EVENTS = %w(
47
47
  stack
48
+ review_stack
48
49
  task
49
50
  deploy
50
51
  rollback
@@ -53,6 +54,7 @@ module Shipit
53
54
  deployable_status
54
55
  merge_status
55
56
  merge
57
+ pull_request
56
58
  ).freeze
57
59
 
58
60
  belongs_to :stack, required: false
@@ -0,0 +1,302 @@
1
+ # frozen_string_literal: true
2
+ module Shipit
3
+ class MergeRequest < ApplicationRecord
4
+ include DeferredTouch
5
+
6
+ MERGE_REQUEST_FIELD = 'Merge-Requested-By'
7
+
8
+ WAITING_STATUSES = %w(fetching pending).freeze
9
+ QUEUED_STATUSES = %w(pending revalidating).freeze
10
+ REJECTION_REASONS = %w(ci_missing ci_failing merge_conflict requires_rebase).freeze
11
+ InvalidTransition = Class.new(StandardError)
12
+ NotReady = Class.new(StandardError)
13
+
14
+ class StatusChecker < Status::Group
15
+ def initialize(commit, statuses, deploy_spec)
16
+ @deploy_spec = deploy_spec
17
+ super(commit, statuses)
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :deploy_spec
23
+
24
+ def reject_hidden(statuses)
25
+ statuses.reject { |s| ignored_statuses.include?(s.context) }
26
+ end
27
+
28
+ def reject_allowed_to_fail(statuses)
29
+ statuses.reject { |s| ignored_statuses.include?(s.context) }
30
+ end
31
+
32
+ def ignored_statuses
33
+ deploy_spec&.merge_request_ignored_statuses || []
34
+ end
35
+
36
+ def required_statuses
37
+ deploy_spec&.merge_request_required_statuses || []
38
+ end
39
+ end
40
+
41
+ belongs_to :stack
42
+ belongs_to :head, class_name: 'Shipit::Commit', optional: true
43
+ belongs_to :base_commit, class_name: 'Shipit::Commit', optional: true
44
+ belongs_to :merge_requested_by, class_name: 'Shipit::User', optional: true
45
+ has_one :merge_commit, class_name: 'Shipit::Commit'
46
+
47
+ deferred_touch stack: :updated_at
48
+
49
+ validates :number, presence: true, uniqueness: { scope: :stack_id }
50
+
51
+ scope :waiting, -> { where(merge_status: WAITING_STATUSES) }
52
+ scope :pending, -> { where(merge_status: 'pending') }
53
+ scope :to_be_merged, -> { pending.order(merge_requested_at: :asc) }
54
+ scope :queued, -> { where(merge_status: QUEUED_STATUSES).order(merge_requested_at: :asc) }
55
+
56
+ after_save :record_merge_status_change
57
+ after_commit :emit_hooks
58
+
59
+ state_machine :merge_status, initial: :fetching do
60
+ state :fetching
61
+ state :pending
62
+ state :rejected
63
+ state :canceled
64
+ state :merged
65
+ state :revalidating
66
+
67
+ event :fetched do
68
+ transition fetching: :pending
69
+ end
70
+
71
+ event :reject do
72
+ transition pending: :rejected
73
+ end
74
+
75
+ event :revalidate do
76
+ transition pending: :revalidating
77
+ end
78
+
79
+ event :cancel do
80
+ transition any => :canceled
81
+ end
82
+
83
+ event :complete do
84
+ transition pending: :merged
85
+ end
86
+
87
+ event :retry do
88
+ transition %i(rejected canceled revalidating) => :pending
89
+ end
90
+
91
+ before_transition rejected: any do |pr|
92
+ pr.rejection_reason = nil
93
+ end
94
+
95
+ before_transition %i(fetching rejected canceled) => :pending do |pr|
96
+ pr.merge_requested_at = Time.now.utc
97
+ end
98
+
99
+ before_transition any => :pending do |pr|
100
+ pr.revalidated_at = Time.now.utc
101
+ end
102
+
103
+ before_transition %i(pending) => :merged do |pr|
104
+ Stack.increment_counter(:undeployed_commits_count, pr.stack_id)
105
+ end
106
+ end
107
+
108
+ def self.schedule_merges
109
+ Shipit::Stack.where(merge_queue_enabled: true).find_each(&:schedule_merges)
110
+ end
111
+
112
+ def self.extract_number(stack, number_or_url)
113
+ case number_or_url
114
+ when /\A#?(\d+)\z/
115
+ $1.to_i
116
+ when %r{\Ahttps://#{Regexp.escape(Shipit.github.domain)}/([^/]+)/([^/]+)/pull/(\d+)}
117
+ return unless $1.downcase == stack.repo_owner.downcase
118
+ return unless $2.downcase == stack.repo_name.downcase
119
+ $3.to_i
120
+ end
121
+ end
122
+
123
+ def self.request_merge!(stack, number, user)
124
+ now = Time.now.utc
125
+ merge_request = begin
126
+ create_with(
127
+ merge_requested_at: now,
128
+ merge_requested_by: user.presence,
129
+ ).find_or_create_by!(
130
+ stack: stack,
131
+ number: number,
132
+ )
133
+ rescue ActiveRecord::RecordNotUnique
134
+ retry
135
+ end
136
+ merge_request.update!(merge_requested_by: user.presence)
137
+ merge_request.retry! if merge_request.rejected? || merge_request.canceled? || merge_request.revalidating?
138
+ merge_request.schedule_refresh!
139
+ merge_request
140
+ end
141
+
142
+ def reject!(reason)
143
+ unless REJECTION_REASONS.include?(reason)
144
+ raise ArgumentError, "invalid reason: #{reason.inspect}, must be one of: #{REJECTION_REASONS.inspect}"
145
+ end
146
+ self.rejection_reason = reason.presence
147
+ super()
148
+ true
149
+ end
150
+
151
+ def reject_unless_mergeable!
152
+ return reject!('merge_conflict') if merge_conflict?
153
+ return reject!('ci_missing') if any_status_checks_missing?
154
+ return reject!('ci_failing') if any_status_checks_failed?
155
+ return reject!('requires_rebase') if stale?
156
+ false
157
+ end
158
+
159
+ def merge!
160
+ raise InvalidTransition unless pending?
161
+
162
+ raise NotReady if not_mergeable_yet?
163
+
164
+ Shipit.github.api.merge_pull_request(
165
+ stack.github_repo_name,
166
+ number,
167
+ merge_message,
168
+ sha: head.sha,
169
+ commit_message: 'Merged by Shipit',
170
+ merge_method: stack.merge_method,
171
+ )
172
+ begin
173
+ if Shipit.github.api.pull_requests(stack.github_repo_name, base: branch).empty?
174
+ Shipit.github.api.delete_branch(stack.github_repo_name, branch)
175
+ end
176
+ rescue Octokit::UnprocessableEntity
177
+ # branch was already deleted somehow
178
+ end
179
+ complete!
180
+ true
181
+ rescue Octokit::MethodNotAllowed # merge conflict
182
+ reject!('merge_conflict')
183
+ false
184
+ rescue Octokit::Conflict # shas didn't match, PR was updated.
185
+ raise NotReady
186
+ end
187
+
188
+ def all_status_checks_passed?
189
+ return false unless head
190
+ StatusChecker.new(head, head.statuses_and_check_runs, stack.cached_deploy_spec).success?
191
+ end
192
+
193
+ def any_status_checks_failed?
194
+ status = StatusChecker.new(head, head.statuses_and_check_runs, stack.cached_deploy_spec)
195
+ status.failure? || status.error?
196
+ end
197
+
198
+ def any_status_checks_missing?
199
+ StatusChecker.new(head, head.statuses_and_check_runs, stack.cached_deploy_spec).missing?
200
+ end
201
+
202
+ def waiting?
203
+ WAITING_STATUSES.include?(merge_status)
204
+ end
205
+
206
+ def need_revalidation?
207
+ timeout = stack.cached_deploy_spec&.revalidate_merge_requests_after
208
+ return false unless timeout
209
+ (revalidated_at + timeout).past?
210
+ end
211
+
212
+ def merge_conflict?
213
+ mergeable == false
214
+ end
215
+
216
+ def not_mergeable_yet?
217
+ mergeable.nil?
218
+ end
219
+
220
+ def schedule_refresh!
221
+ RefreshMergeRequestJob.perform_later(self)
222
+ end
223
+
224
+ def closed?
225
+ state == "closed"
226
+ end
227
+
228
+ def merged_upstream?
229
+ closed? && merged_at
230
+ end
231
+
232
+ def refresh!
233
+ update!(github_pull_request: Shipit.github.api.pull_request(stack.github_repo_name, number))
234
+ head.refresh_statuses!
235
+ fetched! if fetching?
236
+ @comparison = nil
237
+ end
238
+
239
+ def github_pull_request=(github_pull_request)
240
+ self.github_id = github_pull_request.id
241
+ self.api_url = github_pull_request.url
242
+ self.title = github_pull_request.title
243
+ self.state = github_pull_request.state
244
+ self.mergeable = github_pull_request.mergeable
245
+ self.additions = github_pull_request.additions
246
+ self.deletions = github_pull_request.deletions
247
+ self.branch = github_pull_request.head.ref
248
+ self.head = find_or_create_commit_from_github_by_sha!(github_pull_request.head.sha, detached: true)
249
+ self.merged_at = github_pull_request.merged_at
250
+ self.base_ref = github_pull_request.base.ref
251
+ self.base_commit = find_or_create_commit_from_github_by_sha!(github_pull_request.base.sha, detached: true)
252
+ end
253
+
254
+ def merge_message
255
+ return title unless merge_requested_by
256
+ "#{title}\n\n#{MERGE_REQUEST_FIELD}: #{merge_requested_by.login}\n"
257
+ end
258
+
259
+ def stale?
260
+ return false unless base_commit
261
+ spec = stack.cached_deploy_spec
262
+ if max_branch_age = spec.max_divergence_age
263
+ return true if Time.now.utc - head.committed_at > max_branch_age
264
+ end
265
+ if commit_count_limit = spec.max_divergence_commits
266
+ return true if comparison.behind_by > commit_count_limit
267
+ end
268
+ false
269
+ end
270
+
271
+ def comparison
272
+ @comparison ||= Shipit.github.api.compare(
273
+ stack.github_repo_name,
274
+ base_ref,
275
+ head.sha,
276
+ )
277
+ end
278
+
279
+ private
280
+
281
+ def record_merge_status_change
282
+ @merge_status_changed ||= saved_change_to_attribute?(:merge_status)
283
+ end
284
+
285
+ def emit_hooks
286
+ return unless @merge_status_changed
287
+ @merge_status_changed = nil
288
+ Hook.emit('merge', stack, merge_request: self, status: merge_status, stack: stack)
289
+ end
290
+
291
+ def find_or_create_commit_from_github_by_sha!(sha, attributes)
292
+ if commit = stack.commits.by_sha(sha)
293
+ commit
294
+ else
295
+ github_commit = Shipit.github.api.commit(stack.github_repo_name, sha)
296
+ stack.commits.create_from_github!(github_commit, attributes)
297
+ end
298
+ rescue ActiveRecord::RecordNotUnique
299
+ retry
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shipit
4
+ module ProvisioningHandler
5
+ class << self
6
+ def registry
7
+ @registry ||= reset_registry!
8
+ end
9
+
10
+ def reset_registry!
11
+ @registry = {}
12
+ end
13
+
14
+ def register(handler_class)
15
+ registry[handler_class.to_s] = handler_class
16
+ end
17
+
18
+ def fetch(name)
19
+ return default if name.blank?
20
+ registry.fetch(name) { ProvisioningHandler::UnregisteredProvisioningHandler }
21
+ end
22
+
23
+ def default=(handler_class)
24
+ registry[:default] = handler_class
25
+ end
26
+
27
+ def default
28
+ registry.fetch(:default) { ProvisioningHandler::Base }
29
+ end
30
+ end
31
+ end
32
+ end