flash_flow 1.2.1 → 1.2.2.a

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 49d73fb6eb9880932364ebc295d6ad33ca786fa1
4
- data.tar.gz: 4c573c7241c9df3e771f09bb04a7eeab3294f65d
3
+ metadata.gz: 3aab35b80fc7adc67ef12b0833d6434f49c6ec52
4
+ data.tar.gz: 4c400dc91f4caf0c10c2f15adbec98d00861fa6a
5
5
  SHA512:
6
- metadata.gz: 508ada836ac3ca4a1eda158034af4190dd841e448d251b6a75ed15fbc005f1cf849254e55c1d11bbf473378722830afac5725c41be982d1d33d4468310e6fb1b
7
- data.tar.gz: df18ba6a3399e38fda31dd14d9a1f0a0f401a8f21eaa989d9acca7558cb043c6036303e80e7d423889819f73f03a2cd14c0355dea839268090393dc095e835bd
6
+ metadata.gz: 324c1d564582f90976a185bc6fc5491b6b4f7003694609c17b73cb42ebb5fc9533d78e4c04618b2bbebccc1e540c3ba04cfb4f3776b385f2c754abdb24b12d8e
7
+ data.tar.gz: 4d96b4211dea4aed6d7b6c7bb89a0b2c39bd61f0e33cfdbd701e60b30930f58231782e53f80fcf1728220c3c93e48938765098960a1b899467c63c41f1cfbff1
data/Gemfile.lock CHANGED
@@ -1,10 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- flash_flow (1.2.1)
4
+ flash_flow (1.2.2.a)
5
5
  hipchat (~> 1.5)
6
6
  octokit (~> 4.1)
7
7
  pivotal-tracker (~> 0.5)
8
+ ruby-graphviz (> 0)
8
9
 
9
10
  GEM
10
11
  remote: https://rubygems.org/
@@ -57,6 +58,7 @@ GEM
57
58
  http-cookie (>= 1.0.2, < 2.0)
58
59
  mime-types (>= 1.16, < 3.0)
59
60
  netrc (~> 0.7)
61
+ ruby-graphviz (1.2.2)
60
62
  safe_yaml (1.0.4)
61
63
  sawyer (0.6.0)
62
64
  addressable (~> 2.3.5)
data/bin/flash_flow CHANGED
@@ -24,6 +24,8 @@ case
24
24
  FlashFlow::Resolve.new(FlashFlow::Config.configuration.git, FlashFlow::Config.configuration.branch_info_file, logger: FlashFlow::Config.configuration.logger).start
25
25
  when options[:resolve_manual]
26
26
  FlashFlow::Resolve.new(FlashFlow::Config.configuration.git, FlashFlow::Config.configuration.branch_info_file, logger: FlashFlow::Config.configuration.logger).manual_instructions
27
+ when options[:merge_status]
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
27
29
  else
28
30
  FlashFlow::Deploy.new(options).run
29
31
  FlashFlow::IssueTracker::Base.new(FlashFlow::Config.configuration.issue_tracker).stories_pushed
data/flash_flow.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.add_dependency 'octokit', "~> 4.1"
21
21
  spec.add_dependency 'hipchat', "~> 1.5"
22
22
  spec.add_dependency 'pivotal-tracker', "~> 0.5"
23
+ spec.add_dependency 'ruby-graphviz', "> 0"
23
24
 
24
25
  spec.add_development_dependency "bundler", "~> 1.6"
25
26
  spec.add_development_dependency "rake", "> 0"
data/lib/flash_flow.rb CHANGED
@@ -6,3 +6,4 @@ require 'flash_flow/config'
6
6
  require 'flash_flow/deploy'
7
7
  require 'flash_flow/issue_tracker'
8
8
  require 'flash_flow/resolve'
9
+ require 'flash_flow/merge_master'
@@ -3,7 +3,11 @@ require 'open3'
3
3
 
4
4
  module FlashFlow
5
5
  class CmdRunner
6
- attr_reader :dry_run, :dir, :last_command, :last_stderr, :last_stdout
6
+ LOG_NONE = :log_none
7
+ LOG_CMD = :log_cmd
8
+
9
+ attr_reader :dry_run, :last_command, :last_stderr, :last_stdout
10
+ attr_accessor :dir
7
11
 
8
12
  def initialize(opts={})
9
13
  @dir = opts[:dir] || '.'
@@ -11,7 +15,7 @@ module FlashFlow
11
15
  @logger = opts[:logger] || Logger.new('/dev/null')
12
16
  end
13
17
 
14
- def run(cmd)
18
+ def run(cmd, opts={})
15
19
  @last_command = cmd
16
20
  if dry_run
17
21
  puts "#{dir}$ #{cmd}"
@@ -24,14 +28,26 @@ module FlashFlow
24
28
  @success = wait_thr.value.success?
25
29
  end
26
30
  end
27
- @logger.debug("#{dir}$ #{cmd}")
28
- last_stdout.split("\n").each { |line| @logger.debug(line) }
29
- last_stderr.split("\n").each { |line| @logger.debug(line) }
31
+ log(cmd, opts[:log])
30
32
  end
31
33
  end
32
34
 
33
35
  def last_success?
34
36
  @success
35
37
  end
38
+
39
+ private
40
+
41
+ def log(cmd, log_what)
42
+ if log_what == LOG_NONE
43
+ # Do nothing
44
+ else
45
+ @logger.debug("#{dir}$ #{cmd}")
46
+ unless log_what == LOG_CMD
47
+ last_stdout.split("\n").each { |line| @logger.debug(line) }
48
+ last_stderr.split("\n").each { |line| @logger.debug(line) }
49
+ end
50
+ end
51
+ end
36
52
  end
37
53
  end
@@ -9,8 +9,8 @@ module FlashFlow
9
9
  class Base
10
10
  extend Forwardable
11
11
 
12
- def_delegators :@collection, :add_story, :mergeable, :mark_deleted, :mark_success,
13
- :mark_failure, :remove_from_merge, :add_to_merge, :failures, :set_resolutions
12
+ def_delegators :@collection, :add_story, :mergeable, :mark_deleted, :mark_success, :mark_failure,
13
+ :remove_from_merge, :add_to_merge, :failures, :set_resolutions, :to_a, :can_ship?, :branch_link
14
14
 
15
15
  def initialize(branch_config, filename, git, opts={})
16
16
  @git = git
@@ -19,8 +19,10 @@ module FlashFlow
19
19
  end
20
20
 
21
21
  def initialize_collection(branch_config, remotes)
22
- Collection.fetch(remotes, branch_config) ||
22
+ collection = Collection.fetch(remotes, branch_config) ||
23
23
  Collection.from_hash(remotes, backwards_compatible_store['branches'])
24
+ collection.mark_all_as_current
25
+ collection
24
26
  end
25
27
 
26
28
  def version
@@ -44,9 +46,7 @@ module FlashFlow
44
46
 
45
47
  def backwards_compatible_store
46
48
  @backwards_compatible_store ||= begin
47
- hash = in_shadow_repo do
48
- @store.get
49
- end
49
+ hash = @store.get
50
50
 
51
51
  hash.has_key?('branches') ? hash : { 'branches' => hash }
52
52
  end
@@ -55,14 +55,6 @@ module FlashFlow
55
55
  def saved_branches
56
56
  Collection.from_hash(@git.remotes, backwards_compatible_store['branches']).to_a
57
57
  end
58
-
59
- private
60
-
61
- def in_shadow_repo
62
- ShadowRepo.new(@git).in_dir do
63
- yield
64
- end
65
- end
66
58
  end
67
59
  end
68
60
  end
@@ -4,7 +4,8 @@ module FlashFlow
4
4
  module Data
5
5
 
6
6
  class Branch
7
- attr_accessor :remote, :remote_url, :ref, :sha, :status, :resolutions, :stories, :conflict_sha, :metadata, :updated_at, :created_at
7
+ attr_accessor :remote, :remote_url, :ref, :sha, :status, :resolutions, :stories, :conflict_sha, :metadata,
8
+ :current_record, :updated_at, :created_at
8
9
 
9
10
  def initialize(_remote, _remote_url, _ref)
10
11
  @remote = _remote
@@ -24,9 +24,10 @@ module FlashFlow
24
24
  collection
25
25
  end
26
26
 
27
- def self.from_hash(remotes, hash)
27
+ def self.from_hash(remotes, hash, collection_instance=nil)
28
28
  collection = new(remotes)
29
29
  collection.branches = branches_from_hash(hash.dup)
30
+ collection.instance_variable_set(:@collection_instance, collection_instance)
30
31
  collection
31
32
  end
32
33
 
@@ -73,7 +74,7 @@ module FlashFlow
73
74
  end
74
75
  end
75
76
 
76
- self.class.from_hash(remotes, merged_branches)
77
+ self.class.from_hash(remotes, merged_branches, @collection_instance)
77
78
  end
78
79
 
79
80
  def to_a
@@ -84,8 +85,12 @@ module FlashFlow
84
85
  to_a.each
85
86
  end
86
87
 
88
+ def current_branches
89
+ to_a.select { |branch| branch.current_record }
90
+ end
91
+
87
92
  def mergeable
88
- to_a.select { |branch| branch.success? || branch.fail? || branch.unknown? }
93
+ current_branches.select { |branch| (branch.success? || branch.fail? || branch.unknown?) }
89
94
  end
90
95
 
91
96
  def failures
@@ -100,6 +105,12 @@ module FlashFlow
100
105
  end
101
106
  end
102
107
 
108
+ def mark_all_as_current
109
+ @branches.each do |_, branch|
110
+ branch.current_record = true
111
+ end
112
+ end
113
+
103
114
  def add_to_merge(remote, ref)
104
115
  branch = record(remote, nil, ref)
105
116
  @collection_instance.add_to_merge(branch) if @collection_instance.respond_to?(:add_to_merge)
@@ -143,6 +154,14 @@ module FlashFlow
143
154
  branch
144
155
  end
145
156
 
157
+ def can_ship?(branch)
158
+ @collection_instance.respond_to?(:can_ship?) ? @collection_instance.can_ship?(branch) : true
159
+ end
160
+
161
+ def branch_link(branch)
162
+ @collection_instance.branch_link(branch) if @collection_instance.respond_to?(:branch_link)
163
+ end
164
+
146
165
  def set_resolutions(branch, resolutions)
147
166
  update_or_add(branch)
148
167
  branch.set_resolutions(resolutions)
@@ -13,6 +13,7 @@ module FlashFlow
13
13
  @master_branch = config['master_branch'] || master
14
14
  @unmergeable_label = config['unmergeable_label'] || 'unmergeable'
15
15
  @do_not_merge_label = config['do_not_merge_label'] || 'do not merge'
16
+ @code_reviewed_label = config['code_reviewed_label'] || 'code reviewed'
16
17
  end
17
18
 
18
19
  def initialize_connection!(token)
@@ -61,6 +62,14 @@ module FlashFlow
61
62
  add_label(branch.metadata['pr_number'], @unmergeable_label)
62
63
  end
63
64
 
65
+ def can_ship?(branch)
66
+ has_label?(branch.metadata['pr_number'], @code_reviewed_label)
67
+ end
68
+
69
+ def branch_link(branch)
70
+ branch.metadata['pr_url']
71
+ end
72
+
64
73
  private
65
74
 
66
75
  def status_from_labels(pull_request)
@@ -105,19 +114,20 @@ module FlashFlow
105
114
  end
106
115
 
107
116
  def has_label?(pull_request_number, label_name)
108
- labels(pull_request_number).detect { |label| label.name == label_name }
117
+ !!labels(pull_request_number).detect { |label| label == label_name }
109
118
  end
110
119
 
111
120
  def labels(pull_request_number)
112
121
  @labels ||= {}
113
- @labels[pull_request_number] ||= octokit.labels_for_issue(repo, pull_request_number)
122
+ @labels[pull_request_number] ||= octokit.labels_for_issue(repo, pull_request_number).map(&:name)
114
123
  end
115
124
 
116
125
  def metadata(pr)
117
126
  {
118
127
  'pr_number' => pr.number,
119
- 'user_url' => pr.user.html_url,
120
- 'repo_url' => pr.head.repo.html_url
128
+ 'pr_url' => pr.html_url,
129
+ 'user_url' => pr.user.html_url,
130
+ 'repo_url' => pr.head.repo.html_url
121
131
  }
122
132
  end
123
133
 
@@ -20,12 +20,14 @@ module FlashFlow
20
20
  end
21
21
 
22
22
  def write(branches, file=nil)
23
- @git.in_temp_merge_branch do
23
+ @git.in_dir do
24
24
  file ||= File.open(@filename, 'w')
25
25
  file.puts JSON.pretty_generate(sort_branches(branches))
26
26
  file.close
27
+ end
27
28
 
28
- @git.add_and_commit(@filename, 'Branch Info', add: { force: true })
29
+ @git.in_temp_merge_branch do
30
+ @git.add_and_commit(@filename, 'Branch Info', add: {force: true})
29
31
  end
30
32
  end
31
33
 
@@ -18,7 +18,8 @@ module FlashFlow
18
18
  @rerere_forget = opts[:rerere_forget]
19
19
  @stories = [opts[:stories]].flatten.compact
20
20
 
21
- @git = Git.new(Config.configuration.git, logger)
21
+ @local_git = Git.new(Config.configuration.git, logger)
22
+ @git = ShadowGit.new(Config.configuration.git, logger)
22
23
  @lock = Lock::Base.new(Config.configuration.lock)
23
24
  @notifier = Notifier::Base.new(Config.configuration.notifier)
24
25
  @data = Data::Base.new(Config.configuration.branches, Config.configuration.branch_info_file, @git, logger: logger)
@@ -31,45 +32,42 @@ module FlashFlow
31
32
  def run
32
33
  check_version
33
34
  check_repo
34
- puts "Building #{@git.merge_branch}... Log can be found in #{FlashFlow::Config.configuration.log_file}"
35
- logger.info "\n\n### Beginning #{@git.merge_branch} merge ###\n\n"
36
-
35
+ puts "Building #{@local_git.merge_branch}... Log can be found in #{FlashFlow::Config.configuration.log_file}"
36
+ logger.info "\n\n### Beginning #{@local_git.merge_branch} merge ###\n\n"
37
37
 
38
38
  begin
39
39
  open_pull_request
40
40
 
41
- in_shadow_repo do
42
- @lock.with_lock do
43
- fetch(@git.merge_remote)
44
- @git.in_original_merge_branch do
45
- @git.initialize_rerere
46
- end
47
-
48
- @git.reset_temp_merge_branch
49
- @git.in_temp_merge_branch do
50
- merge_branches
51
- commit_branch_info
52
- commit_rerere
53
- end
54
-
55
- @git.copy_temp_to_merge_branch
56
- @git.delete_temp_merge_branch
57
- @git.push_merge_branch
41
+ @lock.with_lock do
42
+ @git.fetch(@git.merge_remote)
43
+ @git.in_original_merge_branch do
44
+ @git.initialize_rerere
45
+ end
46
+
47
+ @git.reset_temp_merge_branch
48
+ @git.in_temp_merge_branch do
49
+ merge_branches
50
+ commit_branch_info
51
+ commit_rerere
58
52
  end
53
+
54
+ @git.copy_temp_to_merge_branch
55
+ @git.delete_temp_merge_branch
56
+ @git.push_merge_branch
59
57
  end
60
58
 
61
59
  print_errors
62
- logger.info "### Finished #{@git.merge_branch} merge ###"
60
+ logger.info "### Finished #{@local_git.merge_branch} merge ###"
63
61
  rescue Lock::Error, OutOfSyncWithRemote => e
64
62
  puts 'Failure!'
65
63
  puts e.message
66
64
  ensure
67
- @git.run("checkout #{@git.working_branch}")
65
+ @local_git.run("checkout #{@local_git.working_branch}")
68
66
  end
69
67
  end
70
68
 
71
69
  def check_repo
72
- if @git.staged_and_working_dir_files.any?
70
+ if @local_git.staged_and_working_dir_files.any?
73
71
  raise RuntimeError.new('You have changes in your working directory. Please stash and try again')
74
72
  end
75
73
  end
@@ -112,7 +110,7 @@ module FlashFlow
112
110
  raise RuntimeError.new("No remote found for #{branch.remote_url}. Please run 'git remote add *your_remote_name* #{branch.remote_url}' and try again.")
113
111
  end
114
112
 
115
- fetch(branch.remote)
113
+ @git.fetch(branch.remote)
116
114
  git_merge(branch, branch.ref == @git.working_branch)
117
115
  end
118
116
  end
@@ -142,17 +140,17 @@ module FlashFlow
142
140
  end
143
141
 
144
142
  def open_pull_request
145
- return false if [@git.master_branch, @git.merge_branch].include?(@git.working_branch)
143
+ return false if [@local_git.master_branch, @local_git.merge_branch].include?(@local_git.working_branch)
146
144
 
147
145
  # TODO - This should use the actual remote for the branch we're on
148
- @git.push(@git.working_branch, force: @force)
149
- raise OutOfSyncWithRemote.new("Your branch is out of sync with the remote. If you want to force push, run 'flash_flow -f'") unless @git.last_success?
146
+ @local_git.push(@local_git.working_branch, force: @force)
147
+ 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?
150
148
 
151
149
  # TODO - This should use the actual remote for the branch we're on
152
150
  if @do_not_merge
153
- @data.remove_from_merge(@git.merge_remote, @git.working_branch)
151
+ @data.remove_from_merge(@local_git.merge_remote, @local_git.working_branch)
154
152
  else
155
- @data.add_to_merge(@git.merge_remote, @git.working_branch)
153
+ @data.add_to_merge(@local_git.merge_remote, @local_git.working_branch)
156
154
  end
157
155
  end
158
156
 
@@ -164,10 +162,10 @@ module FlashFlow
164
162
  errors = []
165
163
  branch_not_merged = nil
166
164
  @data.failures.each do |full_ref, failure|
167
- if failure.ref == @git.working_branch
168
- branch_not_merged = "ERROR: Your branch did not merge to #{@git.merge_branch}. Run 'flash_flow --resolve', fix the merge conflict(s) and then re-run this script\n"
165
+ if failure.ref == @local_git.working_branch
166
+ branch_not_merged = "ERROR: Your branch did not merge to #{@local_git.merge_branch}. Run 'flash_flow --resolve', fix the merge conflict(s) and then re-run this script\n"
169
167
  else
170
- errors << "WARNING: Unable to merge branch #{failure.remote}/#{failure.ref} to #{@git.merge_branch} due to conflicts."
168
+ errors << "WARNING: Unable to merge branch #{failure.remote}/#{failure.ref} to #{@local_git.merge_branch} due to conflicts."
171
169
  end
172
170
  end
173
171
  errors << branch_not_merged if branch_not_merged
@@ -179,20 +177,5 @@ module FlashFlow
179
177
  end
180
178
  end
181
179
 
182
- private
183
-
184
- def in_shadow_repo
185
- ShadowRepo.new(@git, logger: logger).in_dir do
186
- yield
187
- end
188
- end
189
-
190
- def fetch(remote)
191
- @fetched_remotes ||= {}
192
- unless @fetched_remotes[remote]
193
- @git.fetch(remote)
194
- @fetched_remotes[remote] = true
195
- end
196
- end
197
180
  end
198
181
  end
@@ -1,4 +1,5 @@
1
1
  require 'flash_flow/cmd_runner'
2
+ require 'shellwords'
2
3
 
3
4
  module FlashFlow
4
5
  class Git
@@ -22,6 +23,12 @@ module FlashFlow
22
23
  @working_branch = current_branch
23
24
  end
24
25
 
26
+ def in_dir
27
+ Dir.chdir(@cmd_runner.dir) do
28
+ yield
29
+ end
30
+ end
31
+
25
32
  def last_stdout
26
33
  @cmd_runner.last_stdout
27
34
  end
@@ -34,8 +41,8 @@ module FlashFlow
34
41
  @cmd_runner.last_success?
35
42
  end
36
43
 
37
- def run(cmd)
38
- @cmd_runner.run("git #{cmd}")
44
+ def run(cmd, opts={})
45
+ @cmd_runner.run("git #{cmd}", opts)
39
46
  end
40
47
 
41
48
  def add_and_commit(files, message, opts={})
@@ -53,7 +60,11 @@ module FlashFlow
53
60
  end
54
61
 
55
62
  def fetch(remote)
56
- run("fetch #{remote}")
63
+ @fetched_remotes ||= {}
64
+ unless @fetched_remotes[remote]
65
+ run("fetch #{remote}")
66
+ @fetched_remotes[remote] = true
67
+ end
57
68
  end
58
69
 
59
70
  def master_branch_contains?(ref)
@@ -73,7 +84,7 @@ module FlashFlow
73
84
  end
74
85
 
75
86
  def read_file_from_merge_branch(filename)
76
- run("show #{merge_remote}/#{merge_branch}:#{filename}")
87
+ run("show #{merge_remote}/#{merge_branch}:#{filename}", log: CmdRunner::LOG_CMD)
77
88
  last_stdout
78
89
  end
79
90
 
@@ -113,9 +124,11 @@ module FlashFlow
113
124
  end
114
125
 
115
126
  def unresolved_conflicts
116
- conflicted_files.map do |file|
117
- File.open(file) { |f| f.grep(/>>>>/) }.empty? ? nil : file
118
- end.compact
127
+ in_dir do
128
+ conflicted_files.map do |file|
129
+ File.open(file) { |f| f.grep(/>>>>/) }.empty? ? nil : file
130
+ end.compact
131
+ end
119
132
  end
120
133
 
121
134
  def resolutions(files)
@@ -128,10 +141,10 @@ module FlashFlow
128
141
 
129
142
  # git rerere doesn't give you a deterministic way to determine which resolution was used
130
143
  def resolution_candidates(file)
131
- @cmd_runner.run("diff -q --from-file #{file} .git/rr-cache/*/postimage")
144
+ @cmd_runner.run("diff -q --from-file #{file} .git/rr-cache/*/postimage", log: CmdRunner::LOG_CMD)
132
145
  different_files = split_diff_lines(@cmd_runner.last_stdout)
133
146
 
134
- @cmd_runner.run('ls -la .git/rr-cache/*/postimage')
147
+ @cmd_runner.run('ls -la .git/rr-cache/*/postimage', log: CmdRunner::LOG_CMD)
135
148
  all_files = split_diff_lines(@cmd_runner.last_stdout)
136
149
 
137
150
  all_files - different_files
@@ -237,14 +250,15 @@ module FlashFlow
237
250
 
238
251
  def squash_commits
239
252
  # There are three commits created by flash flow that we don't need in the message
240
- run("log #{merge_remote}/#{merge_branch}..#{merge_branch}~3")
253
+ run("log #{merge_remote}/#{merge_branch}..#{merge_branch}~3", log: CmdRunner::LOG_CMD)
241
254
  log = last_stdout
242
255
 
243
256
  # Get all the files that differ between existing acceptance and new acceptance
244
257
  run("diff --name-only #{merge_remote}/#{merge_branch} #{merge_branch}")
245
258
  files = last_stdout.split("\n")
246
259
  run("reset #{merge_remote}/#{merge_branch}")
247
- run("add #{files.map { |f| "'#{f}'" }.join(" ")}")
260
+
261
+ run("add -f #{files.map { |f| "\"#{Shellwords.escape(f)}\"" }.join(" ")}")
248
262
 
249
263
  run("commit -m '#{commit_message(log)}'")
250
264
  end