flash_flow 1.3.1 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2a90282f76b1ac98c205366c66d51e2466b8f725
4
- data.tar.gz: f882884dda4d2baa6a9dd8b89262d3420bf7cef9
3
+ metadata.gz: de553df31b0a6a9dfd5740c63c8319d9709df4fb
4
+ data.tar.gz: 49eabf79d3b1d31d3df371ee18eedbe324fca399
5
5
  SHA512:
6
- metadata.gz: 34b9d57af05dcec0cffdd1983df11660803e0d7abd0391942fc1e5ca8ee848e6ac0082319015e80fd0d31bc116e38cb1ef15e1457ae715ff5b0e0db8c8e4468e
7
- data.tar.gz: f8ac58bdd94bbd0e0bac5566b086ae44cbdeeb53a2fe4cf7d224e86f88d6fbb3e55bcf8aa689978e8b5172b7d3504b715ac3d83b1e40ce2329664f2184e1832c
6
+ metadata.gz: a27253b1eb9e7bbc74347728049b29125ff4ebd92280fdcfb339f6e89bbc502fdbaca44093ef38fe977c36c4f70fb7e265b251a311c3825240be4d8d5d5e81c7
7
+ data.tar.gz: 11f3fe54cf130178f9a4e66fc78b3ed71f883f148946391303500c3cc5622b789d4a7fafd2ea5c700c84b9124605caee9af68133f5cee7f9c18d2536110b7c81
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.1.2
1
+ 2.3.1
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- flash_flow (1.3.1)
4
+ flash_flow (1.3.2)
5
5
  hipchat (~> 1.5)
6
6
  octokit (~> 4.1)
7
7
  pivotal-tracker (~> 0.5)
@@ -10,21 +10,21 @@ PATH
10
10
  GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
- addressable (2.3.8)
13
+ addressable (2.4.0)
14
14
  builder (3.2.2)
15
15
  byebug (3.5.1)
16
16
  columnize (~> 0.8)
17
17
  debugger-linecache (~> 1.2)
18
18
  slop (~> 3.6)
19
19
  columnize (0.9.0)
20
- crack (0.4.2)
20
+ crack (0.4.3)
21
21
  safe_yaml (~> 1.0.0)
22
22
  debugger-linecache (1.2.0)
23
- domain_name (0.5.25)
23
+ domain_name (0.5.20160310)
24
24
  unf (>= 0.0.5, < 1.0.0)
25
25
  faraday (0.9.2)
26
26
  multipart-post (>= 1.2, < 3)
27
- hipchat (1.5.2)
27
+ hipchat (1.5.3)
28
28
  httparty
29
29
  mimemagic
30
30
  http-cookie (1.0.2)
@@ -33,26 +33,28 @@ GEM
33
33
  json (~> 1.8)
34
34
  multi_xml (>= 0.5.2)
35
35
  json (1.8.3)
36
- mime-types (2.99)
37
- mimemagic (0.3.0)
38
- mini_portile2 (2.0.0)
36
+ mime-types (2.99.2)
37
+ mimemagic (0.3.1)
38
+ mini_portile2 (2.1.0)
39
39
  minitest (5.3.5)
40
40
  minitest-stub_any_instance (1.0.1)
41
41
  multi_xml (0.5.5)
42
42
  multipart-post (2.0.0)
43
43
  netrc (0.11.0)
44
- nokogiri (1.6.7.2)
45
- mini_portile2 (~> 2.0.0.rc2)
44
+ nokogiri (1.6.8)
45
+ mini_portile2 (~> 2.1.0)
46
+ pkg-config (~> 1.1.7)
46
47
  nokogiri-happymapper (0.5.9)
47
48
  nokogiri (~> 1.5)
48
- octokit (4.1.1)
49
- sawyer (~> 0.6.0, >= 0.5.3)
49
+ octokit (4.3.0)
50
+ sawyer (~> 0.7.0, >= 0.5.3)
50
51
  pivotal-tracker (0.5.13)
51
52
  builder
52
53
  crack
53
54
  nokogiri (>= 1.5.5)
54
55
  nokogiri-happymapper (>= 0.5.4)
55
56
  rest-client (>= 1.8.0)
57
+ pkg-config (1.1.7)
56
58
  rake (10.4.2)
57
59
  rest-client (1.8.0)
58
60
  http-cookie (>= 1.0.2, < 2.0)
@@ -60,13 +62,13 @@ GEM
60
62
  netrc (~> 0.7)
61
63
  ruby-graphviz (1.2.2)
62
64
  safe_yaml (1.0.4)
63
- sawyer (0.6.0)
64
- addressable (~> 2.3.5)
65
+ sawyer (0.7.0)
66
+ addressable (>= 2.3.5, < 2.5)
65
67
  faraday (~> 0.8, < 0.10)
66
68
  slop (3.6.0)
67
69
  unf (0.1.4)
68
70
  unf_ext
69
- unf_ext (0.0.7.1)
71
+ unf_ext (0.0.7.2)
70
72
 
71
73
  PLATFORMS
72
74
  ruby
@@ -80,4 +82,4 @@ DEPENDENCIES
80
82
  rake (> 0)
81
83
 
82
84
  BUNDLED WITH
83
- 1.10.6
85
+ 1.12.5
data/README.md CHANGED
@@ -147,7 +147,7 @@ When we first started using flash_flow, if your branch had a merge conflict you
147
147
  the branch that you were conflicting with to be merged to master, merge master into your branch, and then try again to
148
148
  get your code into the merge branch.
149
149
 
150
- Then we discovered git rerere, which is the coolest feature of git that almost no one seems to have heard of. Basically
150
+ Then we discovered `git rerere`, which is [the coolest feature of git that almost no one seems to have heard of](https://git-scm.com/blog/2010/03/08/rerere.html). Basically
151
151
  what rerere does is remember how you resolved conflicts and auto-apply those patches when it notices the same conflicts.
152
152
 
153
153
  If your branch has a conflict with the `merge_branch` flash_flow will look for a rerere patch and apply that if it
data/bin/flash_flow CHANGED
@@ -26,6 +26,10 @@ case
26
26
  FlashFlow::Resolve.new(FlashFlow::Config.configuration.git, FlashFlow::Config.configuration.branch_info_file, logger: FlashFlow::Config.configuration.logger).manual_instructions
27
27
  when options[:merge_status]
28
28
  FlashFlow::MergeMaster::Status.new(FlashFlow::Config.configuration.issue_tracker, FlashFlow::Config.configuration.branches, FlashFlow::Config.configuration.branch_info_file, FlashFlow::Config.configuration.git, logger: FlashFlow::Config.configuration.logger).status
29
+ when options[:merge_status_html]
30
+ FlashFlow::MergeMaster::Status.new(FlashFlow::Config.configuration.issue_tracker, FlashFlow::Config.configuration.branches, FlashFlow::Config.configuration.branch_info_file, FlashFlow::Config.configuration.git, logger: FlashFlow::Config.configuration.logger).status_html
31
+ when options[:release_branches]
32
+ FlashFlow::Deploy.new(options).run_release
29
33
  else
30
34
  FlashFlow::Deploy.new(options).run
31
35
  FlashFlow::IssueTracker::Base.new(FlashFlow::Config.configuration.issue_tracker).stories_pushed
@@ -81,3 +81,4 @@ branch_info_file: 'your/random/file'
81
81
  # master_branch: master
82
82
  # unmergeable_label: unmergeable
83
83
  # do_not_merge_label: 'do not merge'
84
+ # shippable_label: 'shippable'
@@ -1,7 +1,7 @@
1
1
  module FlashFlow
2
2
  class BranchMerger
3
3
 
4
- attr_reader :conflict_sha, :resolutions
4
+ attr_reader :conflict_sha, :resolutions, :result
5
5
 
6
6
  def initialize(git, branch)
7
7
  @git = git
@@ -9,15 +9,18 @@ module FlashFlow
9
9
  end
10
10
 
11
11
  def do_merge(rerere_forget)
12
- return :deleted if sha.nil?
12
+ if sha.nil?
13
+ @result = :deleted
14
+ return
15
+ end
13
16
 
14
17
  @git.run("merge --no-ff #{@branch.remote}/#{@branch.ref}")
15
18
 
16
19
  if @git.last_success? || try_rerere(rerere_forget)
17
- return :success
20
+ @result = :success
18
21
  else
19
22
  @conflict_sha = merge_rollback
20
- return :conflict
23
+ @result = :conflict
21
24
  end
22
25
  end
23
26
 
@@ -39,6 +39,7 @@ module FlashFlow
39
39
  private
40
40
 
41
41
  def log(cmd, log_what)
42
+ log_what = nil
42
43
  if log_what == LOG_NONE
43
44
  # Do nothing
44
45
  else
@@ -11,7 +11,7 @@ module FlashFlow
11
11
 
12
12
  def_delegators :@collection, :add_story, :mergeable, :mark_deleted, :mark_success, :mark_failure,
13
13
  :remove_from_merge, :add_to_merge, :failures, :successes, :removals, :set_resolutions,
14
- :to_a, :can_ship?, :branch_link
14
+ :to_a, :code_reviewed?, :branch_link
15
15
 
16
16
  def initialize(branch_config, filename, git, opts={})
17
17
  @git = git
@@ -58,4 +58,4 @@ module FlashFlow
58
58
  end
59
59
  end
60
60
  end
61
- end
61
+ end
@@ -13,6 +13,7 @@ module FlashFlow
13
13
  @ref = _ref
14
14
  @resolutions = {}
15
15
  @stories = []
16
+ @metadata = {}
16
17
  @updated_at = Time.now
17
18
  @created_at = Time.now
18
19
  end
@@ -165,6 +165,10 @@ module FlashFlow
165
165
  branch
166
166
  end
167
167
 
168
+ def code_reviewed?(branch)
169
+ @collection_instance.respond_to?(:code_reviewed?) ? @collection_instance.code_reviewed?(branch) : true
170
+ end
171
+
168
172
  def can_ship?(branch)
169
173
  @collection_instance.respond_to?(:can_ship?) ? @collection_instance.can_ship?(branch) : true
170
174
  end
@@ -14,6 +14,7 @@ module FlashFlow
14
14
  @unmergeable_label = config['unmergeable_label'] || 'unmergeable'
15
15
  @do_not_merge_label = config['do_not_merge_label'] || 'do not merge'
16
16
  @code_reviewed_label = config['code_reviewed_label'] || 'code reviewed'
17
+ @shippable_label = config['shippable_label'] || 'shippable'
17
18
  end
18
19
 
19
20
  def initialize_connection!(token)
@@ -63,10 +64,14 @@ module FlashFlow
63
64
  add_label(branch.metadata['pr_number'], @unmergeable_label)
64
65
  end
65
66
 
66
- def can_ship?(branch)
67
+ def code_reviewed?(branch)
67
68
  has_label?(branch.metadata['pr_number'], @code_reviewed_label)
68
69
  end
69
70
 
71
+ def can_ship?(branch)
72
+ has_label?(branch.metadata['pr_number'], @shippable_label)
73
+ end
74
+
70
75
  def branch_link(branch)
71
76
  branch.metadata['pr_url']
72
77
  end
@@ -11,7 +11,9 @@ require 'flash_flow/shadow_repo'
11
11
  module FlashFlow
12
12
  class Deploy
13
13
 
14
+ class GitPushFailure < RuntimeError ; end
14
15
  class OutOfSyncWithRemote < RuntimeError ; end
16
+ class UnmergeableBranch < RuntimeError ; end
15
17
 
16
18
  def initialize(opts={})
17
19
  @do_not_merge = opts[:do_not_merge]
@@ -24,12 +26,69 @@ module FlashFlow
24
26
  @lock = Lock::Base.new(Config.configuration.lock)
25
27
  @notifier = Notifier::Base.new(Config.configuration.notifier)
26
28
  @data = Data::Base.new(Config.configuration.branches, Config.configuration.branch_info_file, @git, logger: logger)
29
+
30
+ @release_branches = parse_branches(opts[:release_branches])
27
31
  end
28
32
 
29
33
  def logger
30
34
  @logger ||= FlashFlow::Config.configuration.logger
31
35
  end
32
36
 
37
+ def parse_branches(user_branches)
38
+ branch_list = user_branches == ['ready'] ? shippable_branch_names : [user_branches].flatten.compact
39
+
40
+ branch_list.map { |b| Data::Branch.new('origin', @git.remotes_hash['origin'], b) }
41
+ end
42
+
43
+ def run_release
44
+ check_version
45
+ check_repo
46
+ check_branches
47
+ puts "Merging these branches into #{@git.release_branch}:\n #{@release_branches.map(&:ref).join("\n ")}"
48
+ logger.info "\n\n### Beginning #{@local_git.merge_branch} merge ###\n\n"
49
+
50
+ begin
51
+ mergers, errors = [], []
52
+
53
+ @lock.with_lock do
54
+ @git.fetch(@git.merge_remote)
55
+ @git.in_original_merge_branch do
56
+ @git.initialize_rerere
57
+ end
58
+
59
+ @git.reset_temp_merge_branch
60
+ @git.in_temp_merge_branch do
61
+ merge_branches(@release_branches) do |branch, merger|
62
+ mergers << [branch, merger]
63
+ end
64
+ end
65
+
66
+ errors = mergers.select { |m| m.last.result != :success }
67
+
68
+ if errors.empty?
69
+ @git.copy_temp_to_branch(@git.release_branch)
70
+ @git.delete_temp_merge_branch
71
+ unless @git.push(@git.release_branch, false)
72
+ raise GitPushFailure.new("Unable to push to #{@git.release_branch}. See log for details.")
73
+ end
74
+ end
75
+ end
76
+
77
+ if errors.empty?
78
+ puts 'Success!'
79
+ else
80
+ raise UnmergeableBranch.new("The following branches didn't merge successfully:\n #{errors.map {|e| e.first.ref }.join("\n ")}")
81
+ end
82
+
83
+ logger.info "### Finished #{@git.release_branch} merge ###"
84
+ rescue Lock::Error, OutOfSyncWithRemote, UnmergeableBranch, GitPushFailure => e
85
+ puts 'Failure!'
86
+ puts e.message
87
+ ensure
88
+ @local_git.run("checkout #{@local_git.working_branch}")
89
+ end
90
+ end
91
+
33
92
  def run
34
93
  check_version
35
94
  check_repo
@@ -47,14 +106,16 @@ module FlashFlow
47
106
 
48
107
  @git.reset_temp_merge_branch
49
108
  @git.in_temp_merge_branch do
50
- merge_branches
109
+ merge_branches(@data.merged_branches.mergeable) do |branch, merger|
110
+ process_result(branch, merger)
111
+ end
51
112
  commit_branch_info
52
113
  commit_rerere
53
114
  end
54
115
 
55
- @git.copy_temp_to_merge_branch(commit_message)
116
+ @git.copy_temp_to_branch(@git.merge_branch, commit_message)
56
117
  @git.delete_temp_merge_branch
57
- @git.push_merge_branch
118
+ @git.push(@git.merge_branch, true)
58
119
  end
59
120
 
60
121
  print_errors
@@ -67,6 +128,20 @@ module FlashFlow
67
128
  end
68
129
  end
69
130
 
131
+ def check_branches
132
+ requested_not_ready_branches = (@release_branches.map(&:ref) - shippable_branch_names)
133
+ raise RuntimeError.new("The following branches are not ready to ship:\n#{requested_not_ready_branches.join("\n")}") unless requested_not_ready_branches.empty?
134
+ end
135
+
136
+ def shippable_branch_names
137
+ @shippable_branch_names ||= begin
138
+ status = MergeMaster::Status.new(Config.configuration.issue_tracker, Config.configuration.branches, Config.configuration.branch_info_file, Config.configuration.git, logger: logger)
139
+
140
+ all_branches = status.branches
141
+ all_branches.values.select { |b| b[:shippable?] }.map { |b| b[:name] }
142
+ end
143
+ end
144
+
70
145
  def check_repo
71
146
  if @local_git.staged_and_working_dir_files.any?
72
147
  raise RuntimeError.new('You have changes in your working directory. Please stash and try again')
@@ -104,8 +179,8 @@ module FlashFlow
104
179
  60 * 60 * 24 * 14
105
180
  end
106
181
 
107
- def merge_branches
108
- ordered_branches = MergeOrder.new(@git, @data.merged_branches.mergeable).get_order
182
+ def merge_branches(branches)
183
+ ordered_branches = MergeOrder.new(@git, branches).get_order
109
184
  ordered_branches.each_with_index do |branch, index|
110
185
  branch.merge_order = index + 1
111
186
 
@@ -115,18 +190,26 @@ module FlashFlow
115
190
  end
116
191
 
117
192
  @git.fetch(branch.remote)
118
- git_merge(branch, branch.ref == @git.working_branch)
193
+ merger = git_merge(branch)
194
+
195
+ yield(branch, merger)
119
196
  end
120
197
  end
121
198
 
122
- def git_merge(branch, is_working_branch)
199
+ def git_merge(branch)
123
200
  merger = BranchMerger.new(@git, branch)
124
- forget_rerere = is_working_branch && @rerere_forget
201
+ forget_rerere = is_working_branch(branch) && @rerere_forget
202
+
203
+ merger.do_merge(forget_rerere)
125
204
 
126
- case merger.do_merge(forget_rerere)
205
+ merger
206
+ end
207
+
208
+ def process_result(branch, merger)
209
+ case merger.result
127
210
  when :deleted
128
211
  @data.mark_deleted(branch)
129
- @notifier.deleted_branch(branch) unless is_working_branch
212
+ @notifier.deleted_branch(branch) unless is_working_branch(branch)
130
213
 
131
214
  when :success
132
215
  branch.sha = merger.sha
@@ -134,7 +217,7 @@ module FlashFlow
134
217
  @data.set_resolutions(branch, merger.resolutions)
135
218
 
136
219
  when :conflict
137
- if is_working_branch
220
+ if is_working_branch(branch)
138
221
  @data.mark_failure(branch, merger.conflict_sha)
139
222
  else
140
223
  @data.mark_failure(branch, nil)
@@ -143,11 +226,15 @@ module FlashFlow
143
226
  end
144
227
  end
145
228
 
229
+ def is_working_branch(branch)
230
+ branch.ref == @git.working_branch
231
+ end
232
+
146
233
  def open_pull_request
147
234
  return false if [@local_git.master_branch, @local_git.merge_branch].include?(@local_git.working_branch)
148
235
 
149
236
  # TODO - This should use the actual remote for the branch we're on
150
- @local_git.push(@local_git.working_branch, force: @force)
237
+ @local_git.push(@local_git.working_branch, @force)
151
238
  raise OutOfSyncWithRemote.new("Your branch is out of sync with the remote. If you want to force push, run 'flash_flow -f'") unless @local_git.last_success?
152
239
 
153
240
  # TODO - This should use the actual remote for the branch we're on
@@ -181,6 +268,14 @@ module FlashFlow
181
268
  end
182
269
  end
183
270
 
271
+ def release_commit_message
272
+ message =<<-EOS
273
+ Flash Flow merged these branches:
274
+ #{@release_branches.map(&:ref).join("\n")}
275
+ EOS
276
+ message.gsub(/'/, '')
277
+ end
278
+
184
279
  def commit_message
185
280
  message =<<-EOS
186
281
  Flash Flow run from branch: #{@local_git.working_branch}
@@ -198,4 +293,4 @@ Removed branches:
198
293
  end
199
294
 
200
295
  end
201
- end
296
+ end
@@ -3,7 +3,7 @@ require 'shellwords'
3
3
 
4
4
  module FlashFlow
5
5
  class Git
6
- ATTRIBUTES = [:merge_remote, :merge_branch, :master_branch, :use_rerere]
6
+ ATTRIBUTES = [:merge_remote, :merge_branch, :master_branch, :release_branch, :use_rerere]
7
7
  attr_reader *ATTRIBUTES
8
8
  attr_reader :working_branch
9
9
 
@@ -11,7 +11,7 @@ module FlashFlow
11
11
 
12
12
  def initialize(config, logger=nil)
13
13
  @cmd_runner = CmdRunner.new(logger: logger)
14
-
14
+ config['release_branch'] ||= config['master_branch']
15
15
  ATTRIBUTES.each do |attr|
16
16
  unless config.has_key?(attr.to_s)
17
17
  raise RuntimeError.new("git configuration missing. Required config parameters: #{ATTRIBUTES}")
@@ -51,10 +51,6 @@ module FlashFlow
51
51
  run("commit -m '#{message}'")
52
52
  end
53
53
 
54
- def push(branch, options)
55
- run("push #{'-f' if options[:force]} #{merge_remote} #{branch}")
56
- end
57
-
58
54
  def merge(branch)
59
55
  run("merge #{branch}")
60
56
  end
@@ -73,14 +69,7 @@ module FlashFlow
73
69
  end
74
70
 
75
71
  def in_original_merge_branch
76
- begin
77
- starting_branch = current_branch
78
- run("checkout #{merge_remote}/#{merge_branch}")
79
-
80
- yield
81
- ensure
82
- run("checkout #{starting_branch}")
83
- end
72
+ in_branch("#{merge_remote}/#{merge_branch}") { yield }
84
73
  end
85
74
 
86
75
  def read_file_from_merge_branch(filename)
@@ -204,21 +193,22 @@ module FlashFlow
204
193
  end
205
194
  end
206
195
 
207
- def push_merge_branch
208
- run("push -f #{merge_remote} #{merge_branch}")
196
+ def push(branch, force=false)
197
+ run("push #{'-f' if force} #{merge_remote} #{branch}")
209
198
  end
210
199
 
211
- def copy_temp_to_merge_branch(commit_message)
200
+ def copy_temp_to_branch(branch, squash_message = nil)
212
201
  run("checkout #{temp_merge_branch}")
213
- run("merge --strategy=ours --no-edit #{merge_branch}")
214
- run("checkout #{merge_branch}")
202
+ run("merge --strategy=ours --no-edit #{branch}")
203
+ run("checkout #{branch}")
215
204
  run("merge #{temp_merge_branch}")
216
205
 
217
- squash_commits(commit_message)
206
+
207
+ squash_commits(branch, squash_message) if squash_message
218
208
  end
219
209
 
220
210
  def delete_temp_merge_branch
221
- in_merge_branch do
211
+ in_branch(master_branch) do
222
212
  run("branch -d #{temp_merge_branch}")
223
213
  end
224
214
  end
@@ -244,17 +234,26 @@ module FlashFlow
244
234
 
245
235
  private
246
236
 
247
- def squash_commits(commit_message)
237
+ def squash_commits(branch, commit_message)
238
+ unless branch_exists?("#{merge_remote}/#{branch}")
239
+ run("push #{merge_remote} #{master_branch}:#{branch}")
240
+ end
241
+
248
242
  # Get all the files that differ between existing acceptance and new acceptance
249
- run("diff --name-only #{merge_remote}/#{merge_branch} #{merge_branch}")
243
+ run("diff --name-only #{merge_remote}/#{branch} #{branch}")
250
244
  files = last_stdout.split("\n")
251
- run("reset #{merge_remote}/#{merge_branch}")
245
+ run("reset #{merge_remote}/#{branch}")
252
246
 
253
247
  run("add -f #{files.map { |f| "\"#{Shellwords.escape(f)}\"" }.join(" ")}")
254
248
 
255
249
  run("commit -m '#{commit_message}'")
256
250
  end
257
251
 
252
+ def branch_exists?(branch)
253
+ run("rev-parse --verify #{branch}")
254
+ last_success?
255
+ end
256
+
258
257
  def temp_merge_branch
259
258
  "flash_flow/#{merge_branch}"
260
259
  end
@@ -61,7 +61,7 @@ module FlashFlow
61
61
  def story_deployable?(story_id)
62
62
  story = get_story(story_id)
63
63
 
64
- story.current_state == 'accepted'
64
+ story && story.current_state == 'accepted'
65
65
  end
66
66
 
67
67
  def story_link(story_id)
@@ -79,7 +79,7 @@ module FlashFlow
79
79
  def release_keys(story_id)
80
80
  story = get_story(story_id)
81
81
 
82
- return [] if story.labels.nil?
82
+ return [] unless story && story.labels
83
83
 
84
84
  story.labels.split(",").map(&:strip).select { |label| label =~ @release_label_prefix }.map(&:strip)
85
85
  end
@@ -55,7 +55,7 @@
55
55
  </h2>
56
56
 
57
57
  <h2>
58
- <span class="status <%= branch_hash[:branch_can_ship?] ? 'shippable' : 'not-shippable' %>"><%= branch_hash[:branch_can_ship?] ? 'Code ready' : 'Code not ready' %></span>
58
+ <span class="status <%= branch_hash[:code_reviewed?] ? 'shippable' : 'not-shippable' %>"><%= branch_hash[:code_reviewed?] ? 'Code ready' : 'Code not ready' %></span>
59
59
  <a href='<%= branch_hash[:branch_url] %>'><%= branch_hash[:name] %></a>
60
60
  </h2>
61
61
 
@@ -79,7 +79,7 @@
79
79
  <% branch_hash[:my_stories].each do |story_id| %>
80
80
  <% story = @stories[story_id] %>
81
81
  <div class="story">
82
- <span class="status <%= story[:can_ship?] ? 'shippable' : 'not-shippable' %>"><%= story[:can_ship?] ? 'Story ready' : 'Story not ready' %></span>
82
+ <span class="status <%= story[:accepted?] ? 'shippable' : 'not-shippable' %>"><%= story[:accepted?] ? 'Story ready' : 'Story not ready' %></span>
83
83
  <a href='<%= story[:url] %>'><%= story[:title] %></a>
84
84
  <span><%= story[:release_keys].empty? ? '' : "Related releases: #{story[:release_keys].join(", ")}" %></span>
85
85
  </div>
@@ -92,7 +92,7 @@
92
92
  <% (branch_hash[:stories] - branch_hash[:my_stories]).each do |story_id| %>
93
93
  <% story = @stories[story_id] %>
94
94
  <div class="story">
95
- <span class="status <%= story[:can_ship?] ? 'shippable' : 'not-shippable' %>"><%= story[:can_ship?] ? 'Story ready' : 'Story not ready' %></span>
95
+ <span class="status <%= story[:accepted?] ? 'shippable' : 'not-shippable' %>"><%= story[:accepted?] ? 'Story ready' : 'Story not ready' %></span>
96
96
  <a href='<%= story[:url] %>'><%= story[:title] %></a>
97
97
  <span><%= story[:release_keys].empty? ? '' : "Related releases: #{story[:release_keys].join(", ")}" %></span>
98
98
  </div>
@@ -116,7 +116,7 @@
116
116
  <% @releases[release_key][:stories].each do |story_id| %>
117
117
  <% story = @stories[story_id] %>
118
118
  <div class="story">
119
- <span class="status <%= story[:can_ship?] ? 'shippable' : 'not-shippable' %>"><%= story[:can_ship?] ? 'Story ready' : 'Story not ready' %></span>
119
+ <span class="status <%= story[:accepted?] ? 'shippable' : 'not-shippable' %>"><%= story[:accepted?] ? 'Story ready' : 'Story not ready' %></span>
120
120
  <a href='<%= story[:url] %>'><%= story[:title] %></a>
121
121
  <span><%= story[:release_keys].empty? ? '' : "Related releases: #{story[:release_keys].join(", ")}" %></span>
122
122
  </div>
@@ -141,4 +141,4 @@
141
141
 
142
142
  <% end %>
143
143
  </body>
144
- </html>
144
+ </html>
@@ -10,7 +10,27 @@ module FlashFlow
10
10
  @collection = Data::Base.new(branches_config, branch_info_file, ShadowGit.new(git_config)).merged_branches
11
11
  end
12
12
 
13
- def status(filename=nil)
13
+ def status
14
+ filename = File.dirname(__FILE__) + '/merge_status.csv'
15
+ checkmark = "\u2713".encode('utf-8')
16
+
17
+ CSV.open(filename, 'w') do |f|
18
+ f << ['Ready', 'Branch', 'Stories', 'Review', 'Can ship?']
19
+ branches.each do |_, branch_hash|
20
+ f << [
21
+ branch_hash[:shippable?] ? checkmark : 'x',
22
+ branch_hash[:name],
23
+ unshippable_stories(branch_hash[:stories]).empty? ? checkmark : 'x',
24
+ branch_hash[:code_reviewed?] ? checkmark : 'x',
25
+ branch_hash[:can_ship?] ? checkmark : 'x'
26
+ ]
27
+ end
28
+ end
29
+
30
+ CSV.foreach(filename) { |row| puts '%-10s %-70s %-10s %-10s %-10s' % row }
31
+ end
32
+
33
+ def status_html(filename=nil)
14
34
  filename = File.dirname(__FILE__) + '/merge_status.html'
15
35
  @branches = branches
16
36
 
@@ -39,7 +59,8 @@ module FlashFlow
39
59
  Hash.new.tap do |hash|
40
60
  hash[:name] = branch.ref
41
61
  hash[:branch_url] = collection.branch_link(branch)
42
- hash[:branch_can_ship?] = collection.can_ship?(branch)
62
+ hash[:code_reviewed?] = collection.code_reviewed?(branch)
63
+ hash[:can_ship?] = collection.can_ship?(branch)
43
64
  hash[:connected_branches] = connected_branches
44
65
  hash[:image] = graph_file
45
66
  hash[:my_stories] = branch.stories.to_a
@@ -70,7 +91,7 @@ module FlashFlow
70
91
 
71
92
  def mark_as_shippable(branches)
72
93
  branches.each do |_, b|
73
- b[:shippable?] = b[:branch_can_ship?] &&
94
+ b[:shippable?] = b[:code_reviewed?] && b[:can_ship?] &&
74
95
  unshippable_stories(b[:stories]).empty? &&
75
96
  unshippable_releases(b[:releases]).empty?
76
97
  end
@@ -86,11 +107,14 @@ module FlashFlow
86
107
  arr.select do |release_key|
87
108
  !unshippable_stories(@releases[release_key][:stories]).empty?
88
109
  end
89
-
90
110
  end
91
111
 
92
112
  def unshippable_stories(arr)
93
- arr.select { |story| !@stories[story][:can_ship?] }
113
+ arr.select { |story| !@stories[story][:accepted?] }
114
+ end
115
+
116
+ def stories_accepted_branches
117
+ branches.select { |_, b| unshippable_stories(b[:stories]).empty? }
94
118
  end
95
119
 
96
120
  def story_info_hash(story_id)
@@ -98,7 +122,7 @@ module FlashFlow
98
122
  id: story_id,
99
123
  url: issue_tracker.story_link(story_id),
100
124
  title: issue_tracker.story_title(story_id),
101
- can_ship?: issue_tracker.story_deployable?(story_id),
125
+ accepted?: issue_tracker.story_deployable?(story_id),
102
126
  release_keys: issue_tracker.release_keys(story_id)
103
127
  }
104
128
  end
@@ -10,9 +10,15 @@ module FlashFlow
10
10
  end
11
11
 
12
12
  def merge_conflict(branch)
13
- user_name = branch.metadata['user_url'].split('/').last
14
- user_url_link = %{<a href="#{branch.metadata['user_url']}">#{user_name}</a>}
15
- ref_link = %{<a href="#{branch.metadata['repo_url']}/tree/#{branch.ref}">#{branch.ref}</a>}
13
+ begin
14
+ user_name = branch.metadata['user_url'].split('/').last
15
+ user_url_link = %{<a href="#{branch.metadata['user_url']}">#{user_name}</a>}
16
+ ref_link = %{<a href="#{branch.metadata['repo_url']}/tree/#{branch.ref}">#{branch.ref}</a>}
17
+ rescue => e
18
+ puts "An error occurred in the hipchat notifier: #{e.message}."
19
+ user_url_link = 'Unknown'
20
+ ref_link = branch.ref
21
+ end
16
22
 
17
23
  message = %{#{user_url_link}'s branch (#{ref_link}) did not merge successfully}
18
24
  @client[@room].send("FlashFlow", message)
@@ -22,6 +22,8 @@ module FlashFlow
22
22
  opts.on('--resolve', 'Launch a bash shell to save your conflict resolutions') { |v| options[:resolve] = true }
23
23
  opts.on('--resolve-manual', 'Print instructions to use git to resolve conflicts') { |v| options[:resolve_manual] = true }
24
24
  opts.on('--merge-status', 'Show status of all branches and their stories and exit') { |v| options[:merge_status] = true }
25
+ opts.on('--merge-status-html', 'Show status of all branches and their stories in html format and exit') { |v| options[:merge_status_html] = true }
26
+ opts.on('--merge-master branch1,branch2', 'Comma-delimited list of branches to merge to master. Master gets pushed to origin. Run "--merge-master ready" to merge all ready to ship branches') { |v| options[:release_branches] = v.split(',') }
25
27
 
26
28
  opts.on_tail("-h", "--help", "Show this message") do
27
29
  puts opts
@@ -1,3 +1,3 @@
1
1
  module FlashFlow
2
- VERSION = "1.3.1"
2
+ VERSION = "1.3.2"
3
3
  end
@@ -214,14 +214,14 @@ module FlashFlow
214
214
  @fake_branches.verify
215
215
  end
216
216
 
217
- def test_can_ship_returns_true
217
+ def test_code_reviewd_returns_true
218
218
  collection = Collection.new({})
219
- assert(collection.can_ship?(@branch))
219
+ assert(collection.code_reviewed?(@branch))
220
220
  end
221
221
 
222
- def test_can_ship_calls_branches_class
223
- @fake_branches.expect(:can_ship?, true, [@branch])
224
- @collection.can_ship?(@branch)
222
+ def test_code_reviewd_calls_branches_class
223
+ @fake_branches.expect(:code_reviewed?, true, [@branch])
224
+ @collection.code_reviewed?(@branch)
225
225
  @fake_branches.verify
226
226
  end
227
227
 
@@ -20,6 +20,7 @@ module FlashFlow
20
20
  STORY_ID4 = '444'
21
21
  STORY_ID5 = '555'
22
22
  STORY_ID6 = '666'
23
+ STORY_ID7 = '777'
23
24
 
24
25
  def story_deployable?(story_id)
25
26
  {
@@ -29,6 +30,7 @@ module FlashFlow
29
30
  STORY_ID4 => true,
30
31
  STORY_ID5 => false,
31
32
  STORY_ID6 => true,
33
+ STORY_ID7 => true
32
34
  }[story_id]
33
35
  end
34
36
 
@@ -40,6 +42,7 @@ module FlashFlow
40
42
  STORY_ID4 => ['release3', 'release4'],
41
43
  STORY_ID5 => [],
42
44
  STORY_ID6 => ['release4'],
45
+ STORY_ID7 => [],
43
46
  }[story_id]
44
47
  end
45
48
 
@@ -64,21 +67,32 @@ module FlashFlow
64
67
  BRANCH1 = Data::Branch.from_hash('ref' => 'branch1', 'stories' => ['111', '222'])
65
68
  BRANCH2 = Data::Branch.from_hash('ref' => 'branch2', 'stories' => ['333'])
66
69
  BRANCH3 = Data::Branch.from_hash('ref' => 'branch3', 'stories' => ['444', '555', '666'])
70
+ BRANCH4 = Data::Branch.from_hash('ref' => 'branch4', 'stories' => ['777'])
67
71
 
68
72
  def branch_link(branch)
69
73
  return "link-#{branch.ref}"
70
74
  end
71
75
 
72
- def can_ship?(branch)
76
+ def code_reviewed?(branch)
73
77
  {
74
78
  BRANCH1 => true,
75
79
  BRANCH2 => true,
76
80
  BRANCH3 => false,
81
+ BRANCH4 => true,
82
+ }[branch]
83
+ end
84
+
85
+ def can_ship?(branch)
86
+ {
87
+ BRANCH1 => true,
88
+ BRANCH2 => true,
89
+ BRANCH3 => true,
90
+ BRANCH4 => false,
77
91
  }[branch]
78
92
  end
79
93
 
80
94
  def current_branches
81
- [BRANCH1, BRANCH2, BRANCH3]
95
+ [BRANCH1, BRANCH2, BRANCH3, BRANCH4]
82
96
  end
83
97
  end
84
98
 
@@ -96,7 +110,7 @@ module FlashFlow
96
110
  branch1 = branches[FakeCollection::BRANCH1]
97
111
  branch2 = branches[FakeCollection::BRANCH2]
98
112
 
99
- assert(branch1[:stories].all? { |s| @merge_master.stories[s][:can_ship?] })
113
+ assert(branch1[:stories].all? { |s| @merge_master.stories[s][:accepted?] })
100
114
  assert_equal(branch1[:stories], branch2[:stories])
101
115
  assert(branch1[:shippable?])
102
116
  end
@@ -104,11 +118,17 @@ module FlashFlow
104
118
  def test_not_shippable_branch
105
119
  branches = @merge_master.branches
106
120
  branch3 = branches[FakeCollection::BRANCH3]
107
-
108
- assert_equal(branch3[:stories].map { |s| @merge_master.stories[s][:can_ship?] }, [true, false, true])
121
+ assert_equal(branch3[:stories].map { |s| @merge_master.stories[s][:accepted?] }, [true, false, true])
109
122
  refute(branch3[:shippable?])
110
123
  end
111
124
 
125
+ def test_not_shippable_branch_without_shippable_label
126
+ branches = @merge_master.branches
127
+ branch4 = branches[FakeCollection::BRANCH4]
128
+ assert_equal(branch4[:stories].map { |s| @merge_master.stories[s][:accepted?] }, [true])
129
+ refute(branch4[:shippable?])
130
+ end
131
+
112
132
  private
113
133
 
114
134
  def find_branch(status_list, branch)
@@ -9,7 +9,8 @@ module FlashFlow
9
9
 
10
10
  def test_deleted_branch
11
11
  merger.stub(:sha, nil) do
12
- assert_equal(merger.do_merge(true), :deleted)
12
+ merger.do_merge(true)
13
+ assert_equal(merger.result, :deleted)
13
14
  end
14
15
  end
15
16
 
@@ -87,11 +87,9 @@ module FlashFlow
87
87
 
88
88
  notifier.expect(:deleted_branch, true, [@branch])
89
89
 
90
- merger.expect(:do_merge, :deleted, [ false ])
90
+ merger.expect(:result, :deleted)
91
91
 
92
- BranchMerger.stub(:new, merger) do
93
- @deploy.git_merge(@branch, false)
94
- end
92
+ @deploy.process_result(@branch, merger)
95
93
 
96
94
  notifier.verify
97
95
  data.verify
@@ -103,12 +101,9 @@ module FlashFlow
103
101
 
104
102
  notifier.expect(:merge_conflict, true, [@branch])
105
103
 
106
- merger
107
- .expect(:do_merge, :conflict, [ false ])
104
+ merger.expect(:result, :conflict)
108
105
 
109
- BranchMerger.stub(:new, merger) do
110
- @deploy.git_merge(@branch, false)
111
- end
106
+ @deploy.process_result(@branch, merger)
112
107
 
113
108
  notifier.verify
114
109
  data.verify
@@ -120,13 +115,11 @@ module FlashFlow
120
115
  data.expect(:set_resolutions, true, [ @branch, { 'filename' => ["resolution_sha"] } ])
121
116
 
122
117
  merger.
123
- expect(:do_merge, :success, [ false ]).
118
+ expect(:result, :success).
124
119
  expect(:sha, 'sha').
125
120
  expect(:resolutions, { 'filename' => ["resolution_sha"] })
126
121
 
127
- BranchMerger.stub(:new, merger) do
128
- @deploy.git_merge(@branch, false)
129
- end
122
+ @deploy.process_result(@branch, merger)
130
123
 
131
124
  data.verify
132
125
  merger.verify
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flash_flow
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Flashfunders
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-02-24 00:00:00.000000000 Z
11
+ date: 2016-07-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: octokit
@@ -224,7 +224,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
224
224
  version: '0'
225
225
  requirements: []
226
226
  rubyforge_project:
227
- rubygems_version: 2.2.2
227
+ rubygems_version: 2.5.1
228
228
  signing_key:
229
229
  specification_version: 4
230
230
  summary: Implementation of the flashfunders workflow