flash_flow 1.2.1 → 1.2.2.a
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -1
- data/bin/flash_flow +2 -0
- data/flash_flow.gemspec +1 -0
- data/lib/flash_flow.rb +1 -0
- data/lib/flash_flow/cmd_runner.rb +21 -5
- data/lib/flash_flow/data/base.rb +6 -14
- data/lib/flash_flow/data/branch.rb +2 -1
- data/lib/flash_flow/data/collection.rb +22 -3
- data/lib/flash_flow/data/github.rb +14 -4
- data/lib/flash_flow/data/store.rb +4 -2
- data/lib/flash_flow/deploy.rb +31 -48
- data/lib/flash_flow/git.rb +25 -11
- data/lib/flash_flow/issue_tracker.rb +21 -1
- data/lib/flash_flow/issue_tracker/pivotal.rb +44 -3
- data/lib/flash_flow/merge_master.rb +6 -0
- data/lib/flash_flow/merge_master/merge_status.html.erb +144 -0
- data/lib/flash_flow/merge_master/release_graph.rb +135 -0
- data/lib/flash_flow/merge_master/status.rb +108 -0
- data/lib/flash_flow/options.rb +1 -0
- data/lib/flash_flow/resolve.rb +18 -35
- data/lib/flash_flow/shadow_repo.rb +7 -14
- data/lib/flash_flow/version.rb +1 -1
- data/test/lib/data/test_collection.rb +50 -0
- data/test/lib/data/test_store.rb +3 -0
- data/test/lib/issue_tracker/test_pivotal.rb +71 -0
- data/test/lib/merge_master/test_release_graph.rb +74 -0
- data/test/lib/merge_master/test_status.rb +119 -0
- data/test/lib/test_deploy.rb +6 -8
- data/test/lib/test_git.rb +4 -4
- data/test/lib/test_issue_tracker.rb +15 -0
- data/test/lib/test_resolve.rb +0 -29
- data/test/minitest_helper.rb +6 -4
- data/update_gem.sh +5 -0
- metadata +27 -4
data/lib/flash_flow/options.rb
CHANGED
@@ -21,6 +21,7 @@ module FlashFlow
|
|
21
21
|
opts.on('-c', '--config-file FILE_PATH', 'The path to your config file. Defaults to config/flash_flow.yml.erb') { |v| options[:config_file] = v }
|
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
|
+
opts.on('--merge-status', 'Print instructions to use git to resolve conflicts') { |v| options[:merge_status] = true }
|
24
25
|
|
25
26
|
opts.on_tail("-h", "--help", "Show this message") do
|
26
27
|
puts opts
|
data/lib/flash_flow/resolve.rb
CHANGED
@@ -12,7 +12,7 @@ module FlashFlow
|
|
12
12
|
@logger = opts[:logger]
|
13
13
|
@branch_info_file = branch_info_file
|
14
14
|
@cmd_runner = CmdRunner.new(logger: @logger)
|
15
|
-
@git =
|
15
|
+
@git = ShadowGit.new(git_config, @logger)
|
16
16
|
end
|
17
17
|
|
18
18
|
def manual_instructions
|
@@ -23,24 +23,22 @@ module FlashFlow
|
|
23
23
|
def start
|
24
24
|
check_for_conflict
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
merge_conflicted
|
26
|
+
in_working_branch do
|
27
|
+
merge_conflicted
|
29
28
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
if unresolved_conflicts.empty?
|
30
|
+
puts "You have already resolved all conflicts."
|
31
|
+
else
|
32
|
+
launch_bash
|
34
33
|
|
35
|
-
|
34
|
+
rerere
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
36
|
+
unless unresolved_conflicts.empty?
|
37
|
+
puts "There are still unresolved conflicts in these files:\n#{unresolved_conflicts.join("\n")}\n\n"
|
40
38
|
end
|
41
|
-
|
42
|
-
git_reset
|
43
39
|
end
|
40
|
+
|
41
|
+
git_reset
|
44
42
|
end
|
45
43
|
end
|
46
44
|
|
@@ -102,42 +100,27 @@ Run the following commands to fix the merge conflict and then re-run flash_flow:
|
|
102
100
|
private
|
103
101
|
|
104
102
|
def data
|
105
|
-
|
106
|
-
|
107
|
-
in_shadow_repo do
|
108
|
-
@data = Data::Base.new({}, @branch_info_file, @git, logger: @logger)
|
109
|
-
end
|
110
|
-
|
111
|
-
@data
|
112
|
-
|
103
|
+
@data ||= Data::Base.new({}, @branch_info_file, @git, logger: @logger)
|
113
104
|
end
|
114
105
|
|
115
106
|
def branch
|
116
107
|
@branch ||= data.saved_branches.detect { |branch| branch.ref == working_branch }
|
117
108
|
end
|
118
109
|
|
119
|
-
def shadow_repo
|
120
|
-
@shadow_repo ||= ShadowRepo.new(@git, logger: @logger)
|
121
|
-
end
|
122
|
-
|
123
|
-
def in_shadow_repo
|
124
|
-
shadow_repo.in_dir do
|
125
|
-
yield
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
110
|
def working_branch
|
130
111
|
@git.working_branch
|
131
112
|
end
|
132
113
|
|
133
114
|
def in_working_branch
|
134
|
-
@git.
|
135
|
-
|
115
|
+
@git.in_dir do
|
116
|
+
@git.in_branch(working_branch) do
|
117
|
+
yield
|
118
|
+
end
|
136
119
|
end
|
137
120
|
end
|
138
121
|
|
139
122
|
def flash_flow_directory
|
140
|
-
|
123
|
+
@git.flash_flow_dir
|
141
124
|
end
|
142
125
|
|
143
126
|
def init_file_contents
|
@@ -3,24 +3,17 @@ require 'logger'
|
|
3
3
|
require 'flash_flow/git'
|
4
4
|
|
5
5
|
module FlashFlow
|
6
|
-
class
|
6
|
+
class ShadowGit < Git
|
7
7
|
|
8
|
+
def initialize(config, logger=nil)
|
9
|
+
super
|
8
10
|
|
9
|
-
def initialize(git, opts={})
|
10
|
-
@git = git
|
11
|
-
@cmd_runner = CmdRunner.new(logger: opts[:logger])
|
12
|
-
end
|
13
|
-
|
14
|
-
def in_dir(opts={})
|
15
|
-
opts = { reset: true, go_back: true }.merge(opts)
|
16
11
|
create_shadow_repo
|
12
|
+
@cmd_runner.dir = flash_flow_dir
|
17
13
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
yield
|
23
|
-
end
|
14
|
+
run("clean -x -f")
|
15
|
+
fetch(merge_remote)
|
16
|
+
run("reset --hard HEAD")
|
24
17
|
end
|
25
18
|
|
26
19
|
def create_shadow_repo
|
data/lib/flash_flow/version.rb
CHANGED
@@ -214,6 +214,56 @@ module FlashFlow
|
|
214
214
|
@fake_branches.verify
|
215
215
|
end
|
216
216
|
|
217
|
+
def test_can_ship_returns_true
|
218
|
+
collection = Collection.new({})
|
219
|
+
assert(collection.can_ship?(@branch))
|
220
|
+
end
|
221
|
+
|
222
|
+
def test_can_ship_calls_branches_class
|
223
|
+
@fake_branches.expect(:can_ship?, true, [@branch])
|
224
|
+
@collection.can_ship?(@branch)
|
225
|
+
@fake_branches.verify
|
226
|
+
end
|
227
|
+
|
228
|
+
def test_branch_link_returns_nil
|
229
|
+
collection = Collection.new({})
|
230
|
+
assert_nil(collection.branch_link(@branch))
|
231
|
+
end
|
232
|
+
|
233
|
+
def test_branch_link_calls_branches_class
|
234
|
+
@fake_branches.expect(:branch_link, 'http://link_to_branch.com', [@branch])
|
235
|
+
assert_equal('http://link_to_branch.com', @collection.branch_link(@branch))
|
236
|
+
@fake_branches.verify
|
237
|
+
end
|
238
|
+
|
239
|
+
def test_current_branches
|
240
|
+
branch1 = Branch.new('111', '111', '111')
|
241
|
+
branch2 = Branch.new('222', '222', '222')
|
242
|
+
branch3 = Branch.new('333', '333', '333')
|
243
|
+
branch2.current_record = true
|
244
|
+
@collection.mark_success(branch1)
|
245
|
+
@collection.mark_success(branch2)
|
246
|
+
@collection.mark_success(branch3)
|
247
|
+
|
248
|
+
assert_equal(@collection.current_branches, [branch2])
|
249
|
+
end
|
250
|
+
|
251
|
+
def test_mark_all_as_current
|
252
|
+
branch1 = Branch.new('111', '111', '111')
|
253
|
+
branch2 = Branch.new('222', '222', '222')
|
254
|
+
branch3 = Branch.new('333', '333', '333')
|
255
|
+
branch2.current_record = true
|
256
|
+
@collection.mark_success(branch1)
|
257
|
+
@collection.mark_success(branch2)
|
258
|
+
@collection.mark_success(branch3)
|
259
|
+
|
260
|
+
assert_equal(@collection.current_branches, [branch2])
|
261
|
+
|
262
|
+
@collection.mark_all_as_current
|
263
|
+
|
264
|
+
assert_equal(@collection.current_branches, [branch1, branch2, branch3])
|
265
|
+
end
|
266
|
+
|
217
267
|
def test_failures
|
218
268
|
branch1 = Branch.new('111', '111', '111')
|
219
269
|
branch2 = Branch.new('222', '222', '222')
|
data/test/lib/data/test_store.rb
CHANGED
@@ -172,6 +172,73 @@ module FlashFlow
|
|
172
172
|
fake_file.verify
|
173
173
|
end
|
174
174
|
|
175
|
+
def test_story_deployable
|
176
|
+
story_mock = MiniTest::Mock.new
|
177
|
+
.expect(:id, '111')
|
178
|
+
.expect(:current_state, 'accepted')
|
179
|
+
|
180
|
+
stub_tracker_gem(@project_mock) do
|
181
|
+
mock_find(story_mock)
|
182
|
+
assert(Pivotal.new(sample_branches, mock_git).story_deployable?('111'))
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_story_deployable_false
|
187
|
+
story_mock = MiniTest::Mock.new
|
188
|
+
.expect(:id, '111')
|
189
|
+
.expect(:current_state, 'delivered')
|
190
|
+
|
191
|
+
stub_tracker_gem(@project_mock) do
|
192
|
+
mock_find(story_mock)
|
193
|
+
refute(Pivotal.new(sample_branches, mock_git).story_deployable?('111'))
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def test_story_link
|
198
|
+
story_mock = MiniTest::Mock.new
|
199
|
+
.expect(:id, '111')
|
200
|
+
.expect(:url, 'http://some_url')
|
201
|
+
|
202
|
+
stub_tracker_gem(@project_mock) do
|
203
|
+
mock_find(story_mock)
|
204
|
+
assert_equal('http://some_url', Pivotal.new(sample_branches, mock_git).story_link('111'))
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_story_title
|
209
|
+
story_mock = MiniTest::Mock.new
|
210
|
+
.expect(:id, '111')
|
211
|
+
.expect(:name, 'Some Title')
|
212
|
+
|
213
|
+
stub_tracker_gem(@project_mock) do
|
214
|
+
mock_find(story_mock)
|
215
|
+
assert_equal('Some Title', Pivotal.new(sample_branches, mock_git).story_title('111'))
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_release_keys
|
220
|
+
story_mock = MiniTest::Mock.new
|
221
|
+
.expect(:id, '111')
|
222
|
+
.expect(:labels, 'Release-1, Not-A-Release-2, release-3')
|
223
|
+
.expect(:labels, 'Release-1, Not-A-Release-2, release-3')
|
224
|
+
|
225
|
+
stub_tracker_gem(@project_mock) do
|
226
|
+
mock_find(story_mock)
|
227
|
+
assert_equal(['Release-1', 'release-3'], Pivotal.new(sample_branches, mock_git, 'release_label_prefix' => 'release').release_keys('111'))
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def test_stories_for_release
|
232
|
+
story_mock = MiniTest::Mock.new
|
233
|
+
.expect(:id, '111')
|
234
|
+
|
235
|
+
stub_tracker_gem(@project_mock) do
|
236
|
+
mock_all([story_mock], label: 'release')
|
237
|
+
assert_equal(['111'], Pivotal.new(sample_branches, mock_git).stories_for_release('release'))
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
175
242
|
private
|
176
243
|
|
177
244
|
def stub_tracker_gem(project)
|
@@ -208,6 +275,10 @@ module FlashFlow
|
|
208
275
|
@project_mock.expect(:stories, @stories.expect(:find, story, [story_id]))
|
209
276
|
end
|
210
277
|
|
278
|
+
def mock_all(stories, opts={})
|
279
|
+
@project_mock.expect(:stories, @stories.expect(:all, stories, [opts]))
|
280
|
+
end
|
281
|
+
|
211
282
|
def sample_branches
|
212
283
|
@sample_branches ||= [Data::Branch.from_hash({'ref' => 'branch1', 'remote' => 'origin', 'sha' => 'sha1', 'status' => 'success', 'created_at' => (Time.now - 3600), 'stories' => ['111']}),
|
213
284
|
Data::Branch.from_hash({'ref' => 'branch2', 'remote' => 'origin', 'sha' => 'sha2', 'status' => 'success', 'created_at' => (Time.now - 1800), 'stories' => ['222']}),
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
require 'flash_flow/merge_master'
|
3
|
+
|
4
|
+
module FlashFlow
|
5
|
+
module MergeMaster
|
6
|
+
class TestReleaseGraph < Minitest::Test
|
7
|
+
|
8
|
+
class FakeIssueTracker
|
9
|
+
STORY_ID1 = '111'
|
10
|
+
STORY_ID2 = '222'
|
11
|
+
STORY_ID3 = '333'
|
12
|
+
STORY_ID4 = '444'
|
13
|
+
STORY_ID5 = '555'
|
14
|
+
STORY_ID6 = '666'
|
15
|
+
|
16
|
+
def release_keys(story_id)
|
17
|
+
{
|
18
|
+
STORY_ID1 => ['release1', 'release2'],
|
19
|
+
STORY_ID2 => [],
|
20
|
+
STORY_ID3 => ['release2'],
|
21
|
+
STORY_ID4 => ['release3', 'release4'],
|
22
|
+
STORY_ID5 => [],
|
23
|
+
STORY_ID6 => ['release3'],
|
24
|
+
}[story_id]
|
25
|
+
end
|
26
|
+
|
27
|
+
def stories_for_release(release_keys)
|
28
|
+
hash = Hash.new([])
|
29
|
+
hash['release1'] = [STORY_ID1]
|
30
|
+
hash['release2'] = [STORY_ID1, STORY_ID3]
|
31
|
+
|
32
|
+
hash[release_keys]
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
BRANCH1 = Data::Branch.from_hash('ref' => 'branch1', 'stories' => ['111', '222'])
|
38
|
+
BRANCH2 = Data::Branch.from_hash('ref' => 'branch2', 'stories' => ['333'])
|
39
|
+
BRANCH3 = Data::Branch.from_hash('ref' => 'branch3', 'stories' => ['444', '555', '666'])
|
40
|
+
|
41
|
+
################
|
42
|
+
## Begin actual tests
|
43
|
+
|
44
|
+
def setup
|
45
|
+
@graph = ReleaseGraph.build([BRANCH1, BRANCH2, BRANCH3], FakeIssueTracker.new)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_connected_branches
|
49
|
+
assert_equal(@graph.connected_branches(BRANCH1.ref), [BRANCH1, BRANCH2])
|
50
|
+
assert_equal(@graph.connected_branches(BRANCH2.ref), [BRANCH1, BRANCH2])
|
51
|
+
assert_equal(@graph.connected_branches(BRANCH3.ref), [BRANCH3])
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_connected_stories
|
55
|
+
assert_equal(@graph.connected_stories(BRANCH1.ref), ['111', '222', '333'])
|
56
|
+
assert_equal(@graph.connected_stories(BRANCH2.ref), ['111', '222', '333'])
|
57
|
+
assert_equal(@graph.connected_stories(BRANCH3.ref), ['444', '555', '666'])
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_connected_releases
|
61
|
+
assert_equal(@graph.connected_releases(BRANCH1.ref), ['release1', 'release2'])
|
62
|
+
assert_equal(@graph.connected_releases(BRANCH2.ref), ['release1', 'release2'])
|
63
|
+
assert_equal(@graph.connected_releases(BRANCH3.ref), ['release3', 'release4'])
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_no_branches
|
67
|
+
g = ReleaseGraph.build([], FakeIssueTracker.new)
|
68
|
+
assert(g.connected_branches(BRANCH1.ref).empty?)
|
69
|
+
assert(g.connected_stories(BRANCH1.ref).empty?)
|
70
|
+
assert(g.connected_releases(BRANCH1.ref).empty?)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'minitest_helper'
|
2
|
+
require 'flash_flow/merge_master'
|
3
|
+
|
4
|
+
module FlashFlow
|
5
|
+
module MergeMaster
|
6
|
+
class TestStatus < Minitest::Test
|
7
|
+
|
8
|
+
class TestableMergeMaster < Status
|
9
|
+
def initialize;
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :issue_tracker, :collection
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class FakeIssueTracker
|
17
|
+
STORY_ID1 = '111'
|
18
|
+
STORY_ID2 = '222'
|
19
|
+
STORY_ID3 = '333'
|
20
|
+
STORY_ID4 = '444'
|
21
|
+
STORY_ID5 = '555'
|
22
|
+
STORY_ID6 = '666'
|
23
|
+
|
24
|
+
def story_deployable?(story_id)
|
25
|
+
{
|
26
|
+
STORY_ID1 => true,
|
27
|
+
STORY_ID2 => true,
|
28
|
+
STORY_ID3 => true,
|
29
|
+
STORY_ID4 => true,
|
30
|
+
STORY_ID5 => false,
|
31
|
+
STORY_ID6 => true,
|
32
|
+
}[story_id]
|
33
|
+
end
|
34
|
+
|
35
|
+
def release_keys(story_id)
|
36
|
+
{
|
37
|
+
STORY_ID1 => ['release1', 'release2'],
|
38
|
+
STORY_ID2 => [],
|
39
|
+
STORY_ID3 => ['release2'],
|
40
|
+
STORY_ID4 => ['release3', 'release4'],
|
41
|
+
STORY_ID5 => [],
|
42
|
+
STORY_ID6 => ['release4'],
|
43
|
+
}[story_id]
|
44
|
+
end
|
45
|
+
|
46
|
+
def stories_for_release(release_keys)
|
47
|
+
hash = Hash.new([])
|
48
|
+
hash['release1'] = [STORY_ID1]
|
49
|
+
hash['release2'] = [STORY_ID1, STORY_ID3]
|
50
|
+
|
51
|
+
hash[release_keys]
|
52
|
+
end
|
53
|
+
|
54
|
+
def story_link(story_id)
|
55
|
+
; "link: #{story_id}";
|
56
|
+
end
|
57
|
+
|
58
|
+
def story_title(story_id)
|
59
|
+
; "title: #{story_id}";
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class FakeCollection
|
64
|
+
BRANCH1 = Data::Branch.from_hash('ref' => 'branch1', 'stories' => ['111', '222'])
|
65
|
+
BRANCH2 = Data::Branch.from_hash('ref' => 'branch2', 'stories' => ['333'])
|
66
|
+
BRANCH3 = Data::Branch.from_hash('ref' => 'branch3', 'stories' => ['444', '555', '666'])
|
67
|
+
|
68
|
+
def branch_link(branch)
|
69
|
+
return "link-#{branch.ref}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def can_ship?(branch)
|
73
|
+
{
|
74
|
+
BRANCH1 => true,
|
75
|
+
BRANCH2 => true,
|
76
|
+
BRANCH3 => false,
|
77
|
+
}[branch]
|
78
|
+
end
|
79
|
+
|
80
|
+
def current_branches
|
81
|
+
[BRANCH1, BRANCH2, BRANCH3]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
################
|
86
|
+
## Begin actual tests
|
87
|
+
|
88
|
+
def setup
|
89
|
+
@merge_master = TestableMergeMaster.new
|
90
|
+
@merge_master.issue_tracker = FakeIssueTracker.new
|
91
|
+
@merge_master.collection = FakeCollection.new
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_shippable_branch
|
95
|
+
branches = @merge_master.branches
|
96
|
+
branch1 = branches[FakeCollection::BRANCH1]
|
97
|
+
branch2 = branches[FakeCollection::BRANCH2]
|
98
|
+
|
99
|
+
assert(branch1[:stories].all? { |s| @merge_master.stories[s][:can_ship?] })
|
100
|
+
assert_equal(branch1[:stories], branch2[:stories])
|
101
|
+
assert(branch1[:shippable?])
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_not_shippable_branch
|
105
|
+
branches = @merge_master.branches
|
106
|
+
branch3 = branches[FakeCollection::BRANCH3]
|
107
|
+
|
108
|
+
assert_equal(branch3[:stories].map { |s| @merge_master.stories[s][:can_ship?] }, [true, false, true])
|
109
|
+
refute(branch3[:shippable?])
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def find_branch(status_list, branch)
|
115
|
+
status_list[branch]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|