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 +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
|