shipit-engine 0.15.0 → 0.16.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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -1
  3. data/app/assets/javascripts/shipit/page_updater.js.coffee +63 -0
  4. data/app/assets/javascripts/shipit/stacks.js.coffee +9 -21
  5. data/app/assets/stylesheets/_base/_base.scss +2 -2
  6. data/app/assets/stylesheets/_base/_colors.scss +0 -1
  7. data/app/assets/stylesheets/_base/_forms.scss +14 -0
  8. data/app/assets/stylesheets/_pages/_commits.scss +16 -6
  9. data/app/assets/stylesheets/_pages/_settings.scss +8 -0
  10. data/app/assets/stylesheets/_pages/_stacks.scss +1 -1
  11. data/app/controllers/shipit/api/base_controller.rb +7 -3
  12. data/app/controllers/shipit/api/ccmenu_controller.rb +33 -0
  13. data/app/controllers/shipit/api/pull_requests_controller.rb +36 -0
  14. data/app/controllers/shipit/api/stacks_controller.rb +1 -0
  15. data/app/controllers/shipit/ccmenu_url_controller.rb +22 -0
  16. data/app/controllers/shipit/pull_requests_controller.rb +30 -0
  17. data/app/controllers/shipit/stacks_controller.rb +7 -2
  18. data/app/controllers/shipit/webhooks_controller.rb +1 -2
  19. data/app/helpers/shipit/github_url_helper.rb +8 -2
  20. data/app/helpers/shipit/shipit_helper.rb +9 -0
  21. data/app/helpers/shipit/stacks_helper.rb +22 -7
  22. data/app/jobs/shipit/background_job/unique.rb +19 -1
  23. data/app/jobs/shipit/cache_deploy_spec_job.rb +1 -1
  24. data/app/jobs/shipit/merge_pull_requests_job.rb +26 -0
  25. data/app/jobs/shipit/perform_task_job.rb +1 -1
  26. data/app/jobs/shipit/refresh_pull_request_job.rb +8 -0
  27. data/app/models/concerns/shipit/deferred_touch.rb +6 -1
  28. data/app/models/shipit/anonymous_user.rb +4 -0
  29. data/app/models/shipit/application_record.rb +5 -0
  30. data/app/models/shipit/commit.rb +51 -49
  31. data/app/models/shipit/commit_message.rb +32 -0
  32. data/app/models/shipit/deploy.rb +5 -0
  33. data/app/models/shipit/deploy_spec.rb +26 -1
  34. data/app/models/shipit/deploy_spec/file_system.rb +6 -1
  35. data/app/models/shipit/deploy_spec/kubernetes_discovery.rb +10 -13
  36. data/app/models/shipit/deploy_spec/npm_discovery.rb +2 -1
  37. data/app/models/shipit/duration.rb +3 -1
  38. data/app/models/shipit/hook.rb +1 -0
  39. data/app/models/shipit/pull_request.rb +252 -0
  40. data/app/models/shipit/stack.rb +33 -17
  41. data/app/models/shipit/status.rb +1 -16
  42. data/app/models/shipit/status/common.rb +45 -0
  43. data/app/models/shipit/status/group.rb +82 -0
  44. data/app/models/shipit/status/missing.rb +30 -0
  45. data/app/models/shipit/status/unknown.rb +33 -0
  46. data/app/models/shipit/unlimited_api_client.rb +10 -0
  47. data/app/serializers/shipit/commit_serializer.rb +1 -1
  48. data/app/serializers/shipit/pull_request_serializer.rb +20 -0
  49. data/app/serializers/shipit/stack_serializer.rb +6 -2
  50. data/app/views/layouts/shipit.html.erb +41 -39
  51. data/app/views/shipit/ccmenu/project.xml.builder +13 -0
  52. data/app/views/shipit/commits/_commit.html.erb +1 -1
  53. data/app/views/shipit/deploys/_deploy.html.erb +1 -1
  54. data/app/views/shipit/pull_requests/_pull_request.html.erb +29 -0
  55. data/app/views/shipit/pull_requests/index.html.erb +20 -0
  56. data/app/views/shipit/shared/_author.html.erb +7 -0
  57. data/app/views/shipit/stacks/_header.html.erb +5 -0
  58. data/app/views/shipit/stacks/settings.html.erb +13 -0
  59. data/app/views/shipit/stacks/show.html.erb +3 -2
  60. data/app/views/shipit/statuses/_group.html.erb +1 -1
  61. data/app/views/shipit/tasks/_task.html.erb +1 -1
  62. data/config/initializers/inflections.rb +3 -0
  63. data/config/locales/en.yml +1 -3
  64. data/config/routes.rb +8 -0
  65. data/db/migrate/20170130113633_create_shipit_pull_requests.rb +25 -0
  66. data/db/migrate/20170208143657_add_pull_request_number_and_title_to_commits.rb +7 -0
  67. data/db/migrate/20170208154609_backfill_merge_commits.rb +13 -0
  68. data/db/migrate/20170209160355_add_branch_to_pull_requests.rb +5 -0
  69. data/db/migrate/20170215123538_add_merge_queue_enabled_to_stacks.rb +5 -0
  70. data/db/migrate/20170220152410_improve_users_indexing.rb +6 -0
  71. data/db/migrate/20170221102128_improve_tasks_indexing.rb +8 -0
  72. data/db/migrate/20170221130336_add_last_revalidated_at_on_pull_requests.rb +10 -0
  73. data/lib/shipit.rb +2 -0
  74. data/lib/shipit/version.rb +1 -1
  75. data/lib/tasks/cron.rake +1 -0
  76. data/test/controllers/api/ccmenu_controller_test.rb +57 -0
  77. data/test/controllers/api/commits_controller_test.rb +1 -1
  78. data/test/controllers/api/pull_requests_controller_test.rb +59 -0
  79. data/test/controllers/ccmenu_controller_test.rb +33 -0
  80. data/test/controllers/pull_requests_controller_test.rb +31 -0
  81. data/test/controllers/webhooks_controller_test.rb +3 -4
  82. data/test/dummy/config/environments/development.rb +3 -1
  83. data/test/dummy/data/stacks/shopify/junk/production/git/README.md +8 -0
  84. data/test/dummy/data/stacks/shopify/junk/production/git/circle.yml +4 -0
  85. data/test/dummy/data/stacks/shopify/junk/production/git/shipit.yml +4 -0
  86. data/test/dummy/db/development.sqlite3 +0 -0
  87. data/test/dummy/db/schema.rb +45 -11
  88. data/test/dummy/db/seeds.rb +33 -10
  89. data/test/dummy/db/test.sqlite3 +0 -0
  90. data/test/fixtures/shipit/commits.yml +14 -0
  91. data/test/fixtures/shipit/pull_requests.yml +56 -0
  92. data/test/fixtures/shipit/stacks.yml +5 -1
  93. data/test/fixtures/shipit/statuses.yml +8 -0
  94. data/test/helpers/json_helper.rb +16 -14
  95. data/test/jobs/merge_pull_requests_job_test.rb +59 -0
  96. data/test/models/commits_test.rb +104 -49
  97. data/test/{unit → models}/deploy_spec_test.rb +138 -12
  98. data/test/models/deploys_test.rb +10 -4
  99. data/test/models/pull_request_test.rb +197 -0
  100. data/test/models/stacks_test.rb +46 -53
  101. data/test/models/status/group_test.rb +44 -0
  102. data/test/models/status/missing_test.rb +23 -0
  103. data/test/models/status_test.rb +3 -6
  104. data/test/unit/csv_serializer_test.rb +10 -2
  105. metadata +57 -12
  106. data/app/models/shipit/missing_status.rb +0 -21
  107. data/app/models/shipit/status_group.rb +0 -35
  108. data/app/models/shipit/unknown_status.rb +0 -48
  109. data/app/views/shipit/commits/_commit_author.html.erb +0 -7
  110. data/test/models/missing_status_test.rb +0 -23
  111. data/test/models/status_group_test.rb +0 -26
@@ -27,8 +27,7 @@ module Shipit
27
27
  end
28
28
  end
29
29
  def state
30
- if params.branches.map(&:name).include?(stack.branch)
31
- commit = stack.commits.find_by_sha!(params.sha)
30
+ if commit = stack.commits.find_by_sha(params.sha)
32
31
  commit.create_status_from_github!(params)
33
32
  end
34
33
  head :ok
@@ -40,8 +40,14 @@ module Shipit
40
40
  github_repo_url(commit.stack.repo_owner, commit.stack.repo_name, 'commit', commit.sha)
41
41
  end
42
42
 
43
- def github_pull_request_url(commit)
44
- github_repo_url(commit.stack.repo_owner, commit.stack.repo_name, 'pull', commit.pull_request_number)
43
+ def github_pull_request_url(pull_request_or_commit)
44
+ stack = pull_request_or_commit.stack
45
+ number = if pull_request_or_commit.respond_to?(:pull_request_number)
46
+ pull_request_or_commit.pull_request_number
47
+ else
48
+ pull_request_or_commit.number
49
+ end
50
+ github_repo_url(stack.repo_owner, stack.repo_name, 'pull', number)
45
51
  end
46
52
 
47
53
  def link_to_github_deploy(deploy)
@@ -1,5 +1,14 @@
1
1
  module Shipit
2
2
  module ShipitHelper
3
+ def subscribe(url, *selectors)
4
+ content_for(:update_subscription) do
5
+ [
6
+ tag('meta', name: 'subscription-channel', content: url),
7
+ *selectors.map { |s| tag('meta', name: 'subscription-selector', content: s) },
8
+ ].join("\n").html_safe
9
+ end
10
+ end
11
+
3
12
  def emojify(content)
4
13
  h(content).to_str.gsub(/:([\w+-]+):/) do |match|
5
14
  if emoji = Emoji.find_by_alias($1)
@@ -33,16 +33,26 @@ module Shipit
33
33
  end
34
34
 
35
35
  def github_change_url(commit)
36
- commit.pull_request_url || github_commit_url(commit)
36
+ if commit.pull_request?
37
+ github_pull_request_url(commit)
38
+ else
39
+ github_commit_url(commit)
40
+ end
37
41
  end
38
42
 
39
- def render_commit_message(commit)
40
- message = commit.pull_request_title || commit.message
41
- content_tag(:span, emojify(message.truncate(COMMIT_TITLE_LENGTH)), class: 'event-message')
43
+ def render_commit_message(pull_request_or_commit)
44
+ message = pull_request_or_commit.title.to_s.truncate(COMMIT_TITLE_LENGTH)
45
+ content_tag(:span, emojify(message), class: 'event-message')
46
+ end
47
+
48
+ def render_pull_request_title_with_link(pull_request)
49
+ message = render_commit_message(pull_request)
50
+ link_to(message, github_pull_request_url(pull_request), target: '_blank')
42
51
  end
43
52
 
44
53
  def render_commit_message_with_link(commit)
45
- link_to(render_commit_message(commit), github_change_url(commit), target: '_blank')
54
+ message = render_commit_message(commit)
55
+ link_to(message, github_change_url(commit), target: '_blank')
46
56
  end
47
57
 
48
58
  def render_commit_id_link(commit)
@@ -53,8 +63,13 @@ module Shipit
53
63
  end
54
64
  end
55
65
 
56
- def pull_request_link(commit)
57
- link_to("##{commit.pull_request_number}", commit.pull_request_url, target: '_blank', class: 'number')
66
+ def pull_request_link(pull_request_or_commit)
67
+ number = if pull_request_or_commit.respond_to?(:pull_request_number)
68
+ pull_request_or_commit.pull_request_number
69
+ else
70
+ pull_request_or_commit.number
71
+ end
72
+ link_to("##{number}", github_pull_request_url(pull_request_or_commit), target: '_blank', class: 'number')
58
73
  end
59
74
 
60
75
  def render_raw_commit_id_link(commit)
@@ -2,12 +2,12 @@ module Shipit
2
2
  class BackgroundJob
3
3
  module Unique
4
4
  extend ActiveSupport::Concern
5
-
6
5
  DEFAULT_TIMEOUT = 10
7
6
 
8
7
  included do
9
8
  around_perform { |job, block| job.acquire_lock(&block) }
10
9
  cattr_accessor :lock_timeout
10
+ on_duplicate :retry
11
11
  end
12
12
 
13
13
  def acquire_lock(&block)
@@ -18,11 +18,29 @@ module Shipit
18
18
  timeout: self.class.lock_timeout || 0,
19
19
  )
20
20
  mutex.lock(&block)
21
+ rescue Redis::Lock::LockTimeout
22
+ raise unless self.class.drop_duplicate_jobs?
21
23
  end
22
24
 
23
25
  def lock_key(*args)
24
26
  ActiveJob::Arguments.serialize([self.class.name] + args).join('-')
25
27
  end
28
+
29
+ module ClassMethods
30
+ ACTIONS = %i(retry drop).freeze
31
+ ACTIONS_LIST = ACTIONS.map(&:inspect).join(', ').freeze
32
+ def on_duplicate(action)
33
+ unless ACTIONS.include?(action)
34
+ raise ArgumentsError, "invalid action: #{action.inspect}, should be one of #{ACTIONS_LIST}"
35
+ end
36
+
37
+ @on_duplicate = action
38
+ end
39
+
40
+ def drop_duplicate_jobs?
41
+ @on_duplicate == :drop
42
+ end
43
+ end
26
44
  end
27
45
  end
28
46
  end
@@ -6,7 +6,7 @@ module Shipit
6
6
  return if stack.inaccessible?
7
7
 
8
8
  commands = Commands.for(stack)
9
- commands.with_temporary_working_directory(commit: stack.commits.last) do |path|
9
+ commands.with_temporary_working_directory(commit: stack.commits.reachable.last) do |path|
10
10
  stack.update!(cached_deploy_spec: DeploySpec::FileSystem.new(path, stack.environment))
11
11
  end
12
12
  end
@@ -0,0 +1,26 @@
1
+ module Shipit
2
+ class MergePullRequestsJob < BackgroundJob
3
+ include BackgroundJob::Unique
4
+ on_duplicate :drop
5
+
6
+ def perform(stack)
7
+ pull_requests = stack.pull_requests.to_be_merged.to_a
8
+ pull_requests.each do |pull_request|
9
+ pull_request.refresh!
10
+ pull_request.reject_unless_mergeable!
11
+ end
12
+
13
+ return false unless stack.allows_merges?
14
+
15
+ pull_requests.select(&:pending?).each do |pull_request|
16
+ pull_request.refresh!
17
+ begin
18
+ pull_request.merge!
19
+ rescue PullRequest::NotReady
20
+ MergePullRequestsJob.set(wait: 10.seconds).perform_later(stack)
21
+ return false
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -52,7 +52,7 @@ module Shipit
52
52
  end
53
53
 
54
54
  def perform_task
55
- Bundler.with_original_env do
55
+ Bundler.with_clean_env do
56
56
  capture_all! @commands.install_dependencies
57
57
  capture_all! @commands.perform
58
58
  end
@@ -0,0 +1,8 @@
1
+ module Shipit
2
+ class RefreshPullRequestJob < BackgroundJob
3
+ def perform(pull_request)
4
+ pull_request.refresh!
5
+ MergePullRequestsJob.perform_later(pull_request.stack)
6
+ end
7
+ end
8
+ end
@@ -77,7 +77,12 @@ module Shipit
77
77
 
78
78
  def schedule_touches
79
79
  return unless self.class.deferred_touches
80
- touches = self.class.deferred_touches.map { |m, fk, a| [m, self[fk], a].join('|') }
80
+ deferred_touches = self.class.deferred_touches.reject do |m, _fk, _a|
81
+ ActiveRecord::NoTouching.applied_to?(m.constantize)
82
+ end
83
+ return if deferred_touches.empty?
84
+
85
+ touches = deferred_touches.map { |m, fk, a| [m, self[fk], a].join('|') }
81
86
  Shipit.redis.sadd(SET_KEY, touches)
82
87
  if DeferredTouch.enabled
83
88
  Rails.cache.fetch(CACHE_KEY, expires_in: THROTTLE_TTL) do
@@ -1,5 +1,9 @@
1
1
  module Shipit
2
2
  class AnonymousUser
3
+ def present?
4
+ false
5
+ end
6
+
3
7
  def email
4
8
  'anonymous@example.com'
5
9
  end
@@ -0,0 +1,5 @@
1
+ module Shipit
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -8,9 +8,11 @@ module Shipit
8
8
  has_many :deploys
9
9
  has_many :statuses, -> { order(created_at: :desc) }, dependent: :destroy
10
10
  has_many :commit_deployments, dependent: :destroy
11
+ belongs_to :pull_request, inverse_of: :merge_commit
11
12
 
12
13
  deferred_touch stack: :updated_at
13
14
 
15
+ before_create :identify_pull_request
14
16
  after_commit { broadcast_update }
15
17
  after_create { stack.update_undeployed_commits_count }
16
18
 
@@ -21,7 +23,8 @@ module Shipit
21
23
 
22
24
  scope :reachable, -> { where(detached: false) }
23
25
 
24
- delegate :broadcast_update, :github_repo_name, to: :stack
26
+ delegate :broadcast_update, :github_repo_name, :hidden_statuses, :required_statuses,
27
+ :soft_failing_statuses, to: :stack
25
28
 
26
29
  def self.newer_than(commit)
27
30
  return all unless commit
@@ -69,12 +72,14 @@ module Shipit
69
72
  end
70
73
 
71
74
  def reload(*)
72
- @last_statuses = nil
75
+ @status = nil
73
76
  super
74
77
  end
75
78
 
76
- def self.create_from_github!(commit)
77
- from_github(commit).save!
79
+ def self.create_from_github!(commit, extra_attributes = {})
80
+ record = from_github(commit)
81
+ record.update!(extra_attributes)
82
+ record
78
83
  end
79
84
 
80
85
  def schedule_refresh_statuses!
@@ -112,30 +117,26 @@ module Shipit
112
117
  children.detach!
113
118
  end
114
119
 
115
- def pull_request_url
116
- parsed && Shipit.github_url("/#{stack.repo_owner}/#{stack.repo_name}/pull/#{pull_request_number}")
120
+ def pull_request?
121
+ pull_request_number.present?
117
122
  end
118
123
 
119
- def pull_request_number
120
- parsed && parsed['pr_id'].to_i
124
+ def pull_request_number # TODO: remove in a few versions when it is assumed the commits table was backfilled
125
+ super || message_parser.pull_request_number
121
126
  end
122
127
 
123
- def pull_request_title
124
- parsed && parsed['pr_title']
128
+ def title
129
+ pull_request_title || message
125
130
  end
126
131
 
127
- def pull_request?
128
- !!parsed
132
+ def pull_request_title # TODO: remove in a few versions when it is assumed the commits table was backfilled
133
+ super || message_parser.pull_request_title
129
134
  end
130
135
 
131
136
  def short_sha
132
137
  sha[0..9]
133
138
  end
134
139
 
135
- def parsed
136
- @parsed ||= message.match(/\AMerge pull request #(?<pr_id>\d+) from [\w\-.\/]+\n\n(?<pr_title>.*)/)
137
- end
138
-
139
140
  def schedule_continuous_delivery
140
141
  return unless deployable? && stack.continuous_deployment? && stack.deployable?
141
142
  ContinuousDeliveryJob.perform_later(stack)
@@ -156,30 +157,8 @@ module Shipit
156
157
  )
157
158
  end
158
159
 
159
- def visible_statuses
160
- stack.filter_visible_statuses(last_statuses).presence || [UnknownStatus.new(self)]
161
- end
162
-
163
- def meaningful_statuses
164
- stack.filter_meaningful_statuses(last_statuses).presence || [UnknownStatus.new(self)]
165
- end
166
-
167
- def last_statuses
168
- @last_statuses ||= statuses.to_a.uniq(&:context).sort_by(&:context).presence || [UnknownStatus.new(self)]
169
- end
170
-
171
160
  def status
172
- visibles = visible_statuses
173
- status = visibles.size > 1 ? StatusGroup.new(significant_status, visibles) : visibles.first
174
- missing_statuses.empty? ? status : MissingStatus.new(status, missing_statuses)
175
- end
176
-
177
- def significant_status
178
- statuses = meaningful_statuses
179
- return UnknownStatus.new(self) if statuses.empty?
180
- return statuses.first if statuses.all?(&:success?)
181
- non_success_statuses = statuses.reject(&:success?)
182
- non_success_statuses.reject(&:pending?).first || non_success_statuses.first || UnknownStatus.new(self)
161
+ @status ||= Status::Group.compact(self, statuses)
183
162
  end
184
163
 
185
164
  def deployed?
@@ -190,24 +169,47 @@ module Shipit
190
169
  stack.deploys.unsuccessful.where(until_commit_id: id).any?
191
170
  end
192
171
 
172
+ def identify_pull_request
173
+ return unless message_parser.pull_request?
174
+ if pull_request = stack.pull_requests.find_by_number(message_parser.pull_request_number)
175
+ self.pull_request = pull_request
176
+ self.pull_request_number = pull_request.number
177
+ self.pull_request_title = pull_request.title
178
+ self.author = pull_request.merge_requested_by if pull_request.merge_requested_by
179
+ end
180
+
181
+ self.pull_request_number = message_parser.pull_request_number unless self[:pull_request_number]
182
+ self.pull_request_title = message_parser.pull_request_title unless self[:pull_request_title]
183
+ end
184
+
193
185
  private
194
186
 
187
+ def message_parser
188
+ @message_parser ||= CommitMessage.new(message)
189
+ end
190
+
195
191
  def add_status
196
- previous_status = significant_status
192
+ already_deployed = deployed?
193
+
194
+ previous_status = status
197
195
  yield
198
196
  reload # to get the statuses into the right order (since sorted :desc)
199
- new_status = significant_status
197
+ new_status = status
200
198
 
201
- payload = {commit: self, stack: stack, status: new_status.state}
202
- Hook.emit(:commit_status, stack, payload.merge(commit_status: new_status)) if previous_status != new_status
203
- if previous_status.simple_state != new_status.simple_state && (!new_status.pending? || previous_status.unknown?)
204
- Hook.emit(:deployable_status, stack, payload.merge(deployable_status: new_status))
199
+ unless already_deployed
200
+ payload = {commit: self, stack: stack, status: new_status.state}
201
+ Hook.emit(:commit_status, stack, payload.merge(commit_status: new_status)) if previous_status != new_status
205
202
  end
206
- new_status
207
- end
208
203
 
209
- def missing_statuses
210
- stack.required_statuses - last_statuses.map(&:context)
204
+ if previous_status.simple_state != new_status.simple_state
205
+ if !already_deployed && (!new_status.pending? || previous_status.unknown?)
206
+ Hook.emit(:deployable_status, stack, payload.merge(deployable_status: new_status))
207
+ end
208
+ if new_status.pending? || new_status.success?
209
+ stack.schedule_merges
210
+ end
211
+ end
212
+ new_status
211
213
  end
212
214
  end
213
215
  end
@@ -0,0 +1,32 @@
1
+ module Shipit
2
+ class CommitMessage
3
+ GITHUB_MERGE_COMMIT_PATTERN = %r{\AMerge pull request #(?<pr_id>\d+) from [\w\-./]+\n\n(?<pr_title>.*)}
4
+
5
+ def initialize(text)
6
+ @text = text
7
+ end
8
+
9
+ def pull_request?
10
+ !!parsed
11
+ end
12
+
13
+ def pull_request_number
14
+ parsed && parsed['pr_id'].to_i
15
+ end
16
+
17
+ def pull_request_title
18
+ parsed && parsed['pr_title']
19
+ end
20
+
21
+ def to_s
22
+ @text
23
+ end
24
+
25
+ private
26
+
27
+ def parsed
28
+ return @parsed if defined?(@parsed)
29
+ @parsed = to_s.match(GITHUB_MERGE_COMMIT_PATTERN)
30
+ end
31
+ end
32
+ end
@@ -6,6 +6,7 @@ module Shipit
6
6
 
7
7
  state_machine :status do
8
8
  after_transition to: :success, do: :schedule_continuous_delivery
9
+ after_transition to: :success, do: :schedule_merges
9
10
  after_transition to: :success, do: :update_undeployed_commits_count
10
11
  after_transition to: :aborted, do: :trigger_revert_if_required
11
12
  after_transition any => any, do: :update_commit_deployments
@@ -163,6 +164,10 @@ module Shipit
163
164
  self.deletions = commits.map(&:deletions).compact.sum
164
165
  end
165
166
 
167
+ def schedule_merges
168
+ stack.schedule_merges
169
+ end
170
+
166
171
  def schedule_continuous_delivery
167
172
  return unless stack.continuous_deployment?
168
173
  ContinuousDeliveryJob.perform_later(stack)
@@ -64,7 +64,7 @@ module Shipit
64
64
  alias_method :dependencies_steps!, :dependencies_steps
65
65
 
66
66
  def maximum_commits_per_deploy
67
- config('deploy', 'max_commits')
67
+ config('deploy', 'max_commits') { 8 }
68
68
  end
69
69
 
70
70
  def pause_between_deploys
@@ -143,6 +143,31 @@ module Shipit
143
143
  Array.wrap(config('ci', 'allow_failures'))
144
144
  end
145
145
 
146
+ def pull_request_required_statuses
147
+ if config('merge', 'require') || config('merge', 'ignore')
148
+ Array.wrap(config('merge', 'require'))
149
+ else
150
+ required_statuses
151
+ end
152
+ end
153
+
154
+ def pull_request_ignored_statuses
155
+ if config('merge', 'require') || config('merge', 'ignore')
156
+ Array.wrap(config('merge', 'ignore'))
157
+ else
158
+ soft_failing_statuses | hidden_statuses
159
+ end
160
+ end
161
+
162
+ def revalidate_pull_requests_after
163
+ if timeout = config('merge', 'revalidate_after')
164
+ begin
165
+ Duration.parse(timeout)
166
+ rescue Duration::ParseError
167
+ end
168
+ end
169
+ end
170
+
146
171
  def review_checks
147
172
  config('review', 'checks') || []
148
173
  end