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 +4 -4
- data/.ruby-version +1 -1
- data/Gemfile.lock +18 -16
- data/README.md +1 -1
- data/bin/flash_flow +4 -0
- data/flash_flow.yml.erb.example +1 -0
- data/lib/flash_flow/branch_merger.rb +7 -4
- data/lib/flash_flow/cmd_runner.rb +1 -0
- data/lib/flash_flow/data/base.rb +2 -2
- data/lib/flash_flow/data/branch.rb +1 -0
- data/lib/flash_flow/data/collection.rb +4 -0
- data/lib/flash_flow/data/github.rb +6 -1
- data/lib/flash_flow/deploy.rb +108 -13
- data/lib/flash_flow/git.rb +23 -24
- data/lib/flash_flow/issue_tracker/pivotal.rb +2 -2
- data/lib/flash_flow/merge_master/merge_status.html.erb +5 -5
- data/lib/flash_flow/merge_master/status.rb +30 -6
- data/lib/flash_flow/notifier/hipchat.rb +9 -3
- data/lib/flash_flow/options.rb +2 -0
- data/lib/flash_flow/version.rb +1 -1
- data/test/lib/data/test_collection.rb +5 -5
- data/test/lib/merge_master/test_status.rb +25 -5
- data/test/lib/test_branch_merger.rb +2 -1
- data/test/lib/test_deploy.rb +6 -13
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de553df31b0a6a9dfd5740c63c8319d9709df4fb
|
4
|
+
data.tar.gz: 49eabf79d3b1d31d3df371ee18eedbe324fca399
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a27253b1eb9e7bbc74347728049b29125ff4ebd92280fdcfb339f6e89bbc502fdbaca44093ef38fe977c36c4f70fb7e265b251a311c3825240be4d8d5d5e81c7
|
7
|
+
data.tar.gz: 11f3fe54cf130178f9a4e66fc78b3ed71f883f148946391303500c3cc5622b789d4a7fafd2ea5c700c84b9124605caee9af68133f5cee7f9c18d2536110b7c81
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.1
|
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.
|
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.
|
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.
|
20
|
+
crack (0.4.3)
|
21
21
|
safe_yaml (~> 1.0.0)
|
22
22
|
debugger-linecache (1.2.0)
|
23
|
-
domain_name (0.5.
|
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.
|
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.
|
38
|
-
mini_portile2 (2.
|
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.
|
45
|
-
mini_portile2 (~> 2.
|
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.
|
49
|
-
sawyer (~> 0.
|
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.
|
64
|
-
addressable (
|
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.
|
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.
|
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
|
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
|
data/flash_flow.yml.erb.example
CHANGED
@@ -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
|
-
|
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
|
-
|
20
|
+
@result = :success
|
18
21
|
else
|
19
22
|
@conflict_sha = merge_rollback
|
20
|
-
|
23
|
+
@result = :conflict
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
data/lib/flash_flow/data/base.rb
CHANGED
@@ -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, :
|
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
|
@@ -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
|
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
|
data/lib/flash_flow/deploy.rb
CHANGED
@@ -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.
|
116
|
+
@git.copy_temp_to_branch(@git.merge_branch, commit_message)
|
56
117
|
@git.delete_temp_merge_branch
|
57
|
-
@git.
|
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,
|
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
|
193
|
+
merger = git_merge(branch)
|
194
|
+
|
195
|
+
yield(branch, merger)
|
119
196
|
end
|
120
197
|
end
|
121
198
|
|
122
|
-
def git_merge(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
|
-
|
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,
|
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
|
data/lib/flash_flow/git.rb
CHANGED
@@ -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
|
-
|
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
|
208
|
-
run("push -f #{merge_remote} #{
|
196
|
+
def push(branch, force=false)
|
197
|
+
run("push #{'-f' if force} #{merge_remote} #{branch}")
|
209
198
|
end
|
210
199
|
|
211
|
-
def
|
200
|
+
def copy_temp_to_branch(branch, squash_message = nil)
|
212
201
|
run("checkout #{temp_merge_branch}")
|
213
|
-
run("merge --strategy=ours --no-edit #{
|
214
|
-
run("checkout #{
|
202
|
+
run("merge --strategy=ours --no-edit #{branch}")
|
203
|
+
run("checkout #{branch}")
|
215
204
|
run("merge #{temp_merge_branch}")
|
216
205
|
|
217
|
-
|
206
|
+
|
207
|
+
squash_commits(branch, squash_message) if squash_message
|
218
208
|
end
|
219
209
|
|
220
210
|
def delete_temp_merge_branch
|
221
|
-
|
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}/#{
|
243
|
+
run("diff --name-only #{merge_remote}/#{branch} #{branch}")
|
250
244
|
files = last_stdout.split("\n")
|
251
|
-
run("reset #{merge_remote}/#{
|
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 []
|
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[:
|
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[:
|
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[:
|
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[:
|
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
|
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[:
|
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[:
|
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][:
|
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
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
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)
|
data/lib/flash_flow/options.rb
CHANGED
@@ -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
|
data/lib/flash_flow/version.rb
CHANGED
@@ -214,14 +214,14 @@ module FlashFlow
|
|
214
214
|
@fake_branches.verify
|
215
215
|
end
|
216
216
|
|
217
|
-
def
|
217
|
+
def test_code_reviewd_returns_true
|
218
218
|
collection = Collection.new({})
|
219
|
-
assert(collection.
|
219
|
+
assert(collection.code_reviewed?(@branch))
|
220
220
|
end
|
221
221
|
|
222
|
-
def
|
223
|
-
@fake_branches.expect(:
|
224
|
-
@collection.
|
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
|
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][:
|
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)
|
data/test/lib/test_deploy.rb
CHANGED
@@ -87,11 +87,9 @@ module FlashFlow
|
|
87
87
|
|
88
88
|
notifier.expect(:deleted_branch, true, [@branch])
|
89
89
|
|
90
|
-
merger.expect(:
|
90
|
+
merger.expect(:result, :deleted)
|
91
91
|
|
92
|
-
|
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
|
-
|
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(:
|
118
|
+
expect(:result, :success).
|
124
119
|
expect(:sha, 'sha').
|
125
120
|
expect(:resolutions, { 'filename' => ["resolution_sha"] })
|
126
121
|
|
127
|
-
|
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.
|
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-
|
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.
|
227
|
+
rubygems_version: 2.5.1
|
228
228
|
signing_key:
|
229
229
|
specification_version: 4
|
230
230
|
summary: Implementation of the flashfunders workflow
|