daq_flow 1.0.4
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 +7 -0
- data/.gitignore +11 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +62 -0
- data/LICENSE.txt +22 -0
- data/README.md +165 -0
- data/Rakefile +10 -0
- data/bin/daq_flow +23 -0
- data/flash_flow.gemspec +28 -0
- data/flash_flow.yml.erb.example +42 -0
- data/lib/flash_flow.rb +7 -0
- data/lib/flash_flow/branch_merger.rb +55 -0
- data/lib/flash_flow/cmd_runner.rb +54 -0
- data/lib/flash_flow/config.rb +84 -0
- data/lib/flash_flow/data.rb +6 -0
- data/lib/flash_flow/data/base.rb +89 -0
- data/lib/flash_flow/data/bitbucket.rb +152 -0
- data/lib/flash_flow/data/branch.rb +124 -0
- data/lib/flash_flow/data/collection.rb +211 -0
- data/lib/flash_flow/data/github.rb +140 -0
- data/lib/flash_flow/data/store.rb +44 -0
- data/lib/flash_flow/git.rb +267 -0
- data/lib/flash_flow/install.rb +19 -0
- data/lib/flash_flow/lock.rb +23 -0
- data/lib/flash_flow/merge.rb +6 -0
- data/lib/flash_flow/merge/acceptance.rb +154 -0
- data/lib/flash_flow/merge/base.rb +116 -0
- data/lib/flash_flow/merge_order.rb +27 -0
- data/lib/flash_flow/notifier.rb +23 -0
- data/lib/flash_flow/options.rb +34 -0
- data/lib/flash_flow/resolve.rb +143 -0
- data/lib/flash_flow/shadow_repo.rb +44 -0
- data/lib/flash_flow/time_helper.rb +32 -0
- data/lib/flash_flow/version.rb +4 -0
- data/log/.keep +0 -0
- data/test/lib/data/test_base.rb +10 -0
- data/test/lib/data/test_branch.rb +206 -0
- data/test/lib/data/test_collection.rb +308 -0
- data/test/lib/data/test_store.rb +70 -0
- data/test/lib/lock/test_github.rb +74 -0
- data/test/lib/merge/test_acceptance.rb +230 -0
- data/test/lib/test_branch_merger.rb +78 -0
- data/test/lib/test_config.rb +63 -0
- data/test/lib/test_git.rb +73 -0
- data/test/lib/test_merge_order.rb +71 -0
- data/test/lib/test_notifier.rb +33 -0
- data/test/lib/test_resolve.rb +69 -0
- data/test/minitest_helper.rb +41 -0
- data/update_gem.sh +5 -0
- metadata +192 -0
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'flash_flow/data/branch'
|
2
|
+
require 'flash_flow/data/bitbucket'
|
3
|
+
require 'flash_flow/data/github'
|
4
|
+
|
5
|
+
module FlashFlow
|
6
|
+
module Data
|
7
|
+
|
8
|
+
class Collection
|
9
|
+
|
10
|
+
attr_accessor :branches
|
11
|
+
|
12
|
+
def initialize(config=nil)
|
13
|
+
@branches = {}
|
14
|
+
|
15
|
+
if config && config['class'] && config['class']['name']
|
16
|
+
collection_class = Object.const_get(config['class']['name'])
|
17
|
+
@collection_instance = collection_class.new(config['class'])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.fetch(config=nil)
|
22
|
+
collection = new(config)
|
23
|
+
collection.fetch
|
24
|
+
collection
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.from_hash(hash, collection_instance=nil)
|
28
|
+
collection = new
|
29
|
+
collection.branches = branches_from_hash(hash)
|
30
|
+
collection.instance_variable_set(:@collection_instance, collection_instance)
|
31
|
+
collection
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.branches_from_hash(hash)
|
35
|
+
{}.tap do |new_branches|
|
36
|
+
hash.each do |_, val|
|
37
|
+
branch = val.is_a?(Branch) ? val : Branch.from_hash(val)
|
38
|
+
new_branches[branch.ref] = branch
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.key(ref)
|
44
|
+
ref
|
45
|
+
end
|
46
|
+
|
47
|
+
def get(ref)
|
48
|
+
@branches[key(ref)]
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_hash
|
52
|
+
{}.tap do |hash|
|
53
|
+
@branches.each do |key, val|
|
54
|
+
hash[key] = val.to_hash
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
alias :to_h :to_hash
|
59
|
+
|
60
|
+
def reverse_merge(old)
|
61
|
+
merged_branches = @branches.dup
|
62
|
+
|
63
|
+
merged_branches.each do |_, info|
|
64
|
+
info.updated_at = Time.now
|
65
|
+
info.created_at ||= Time.now
|
66
|
+
end
|
67
|
+
|
68
|
+
old.branches.each do |full_ref, info|
|
69
|
+
if merged_branches.has_key?(full_ref)
|
70
|
+
branch = merged_branches[full_ref]
|
71
|
+
|
72
|
+
branch.created_at = info.created_at
|
73
|
+
branch.resolutions = info.resolutions.to_h.merge(branch.resolutions.to_h)
|
74
|
+
branch.stories = info.stories.to_a | merged_branches[full_ref].stories.to_a
|
75
|
+
branch.merge_order ||= info.merge_order
|
76
|
+
if branch.fail?
|
77
|
+
branch.conflict_sha ||= info.conflict_sha
|
78
|
+
end
|
79
|
+
else
|
80
|
+
merged_branches[full_ref] = info
|
81
|
+
merged_branches[full_ref].status = nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
self.class.from_hash(merged_branches, @collection_instance)
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_a
|
89
|
+
@branches.values
|
90
|
+
end
|
91
|
+
|
92
|
+
def each
|
93
|
+
to_a.each
|
94
|
+
end
|
95
|
+
|
96
|
+
def current_branches
|
97
|
+
to_a.select { |branch| branch.current_record }
|
98
|
+
end
|
99
|
+
|
100
|
+
def mergeable
|
101
|
+
current_branches.select { |branch| (branch.success? || branch.fail? || branch.unknown?) }
|
102
|
+
end
|
103
|
+
|
104
|
+
def failures
|
105
|
+
current_branches.select { |branch| branch.fail? }
|
106
|
+
end
|
107
|
+
|
108
|
+
def successes
|
109
|
+
current_branches.select { |branch| branch.success? }
|
110
|
+
end
|
111
|
+
|
112
|
+
def removals
|
113
|
+
to_a.select { |branch| branch.removed? }
|
114
|
+
end
|
115
|
+
|
116
|
+
def fetch
|
117
|
+
return unless @collection_instance.respond_to?(:fetch)
|
118
|
+
|
119
|
+
@collection_instance.fetch.each do |b|
|
120
|
+
update_or_add(b)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def mark_all_as_current
|
125
|
+
@branches.each do |_, branch|
|
126
|
+
branch.current_record = true
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def add_to_merge(ref)
|
131
|
+
branch = record(ref)
|
132
|
+
branch.current_record = true
|
133
|
+
@collection_instance.add_to_merge(branch) if @collection_instance.respond_to?(:add_to_merge)
|
134
|
+
branch
|
135
|
+
end
|
136
|
+
|
137
|
+
def remove_from_merge(ref)
|
138
|
+
branch = record(ref)
|
139
|
+
branch.current_record = true
|
140
|
+
branch.removed!
|
141
|
+
@collection_instance.remove_from_merge(branch) if @collection_instance.respond_to?(:remove_from_merge)
|
142
|
+
branch
|
143
|
+
end
|
144
|
+
|
145
|
+
def mark_failure(branch, conflict_sha=nil)
|
146
|
+
update_or_add(branch)
|
147
|
+
branch.fail!(conflict_sha)
|
148
|
+
@collection_instance.mark_failure(branch) if @collection_instance.respond_to?(:mark_failure)
|
149
|
+
branch
|
150
|
+
end
|
151
|
+
|
152
|
+
def mark_deleted(branch)
|
153
|
+
update_or_add(branch)
|
154
|
+
branch.deleted!
|
155
|
+
@collection_instance.mark_deleted(branch) if @collection_instance.respond_to?(:mark_deleted)
|
156
|
+
branch
|
157
|
+
end
|
158
|
+
|
159
|
+
def mark_success(branch)
|
160
|
+
update_or_add(branch)
|
161
|
+
branch.success!
|
162
|
+
@collection_instance.mark_success(branch) if @collection_instance.respond_to?(:mark_success)
|
163
|
+
branch
|
164
|
+
end
|
165
|
+
|
166
|
+
def add_story(ref, story_id)
|
167
|
+
branch = get(ref)
|
168
|
+
branch.stories ||= []
|
169
|
+
branch.stories << story_id
|
170
|
+
|
171
|
+
@collection_instance.add_story(branch, story_id) if @collection_instance.respond_to?(:add_story)
|
172
|
+
branch
|
173
|
+
end
|
174
|
+
|
175
|
+
def code_reviewed?(branch)
|
176
|
+
@collection_instance.respond_to?(:code_reviewed?) ? @collection_instance.code_reviewed?(branch) : true
|
177
|
+
end
|
178
|
+
|
179
|
+
def can_ship?(branch)
|
180
|
+
@collection_instance.respond_to?(:can_ship?) ? @collection_instance.can_ship?(branch) : true
|
181
|
+
end
|
182
|
+
|
183
|
+
def branch_link(branch)
|
184
|
+
@collection_instance.branch_link(branch) if @collection_instance.respond_to?(:branch_link)
|
185
|
+
end
|
186
|
+
|
187
|
+
def set_resolutions(branch, resolutions)
|
188
|
+
update_or_add(branch)
|
189
|
+
branch.set_resolutions(resolutions)
|
190
|
+
@collection_instance.set_resolutions(branch) if @collection_instance.respond_to?(:set_resolutions)
|
191
|
+
branch
|
192
|
+
end
|
193
|
+
|
194
|
+
private
|
195
|
+
|
196
|
+
def key(ref)
|
197
|
+
self.class.key(ref)
|
198
|
+
end
|
199
|
+
|
200
|
+
def update_or_add(branch)
|
201
|
+
old_branch = @branches[key(branch.ref)]
|
202
|
+
@branches[key(branch.ref)] = old_branch.nil? ? branch : old_branch.merge(branch)
|
203
|
+
end
|
204
|
+
|
205
|
+
def record(ref)
|
206
|
+
update_or_add(Branch.new(ref))
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'octokit'
|
2
|
+
require 'flash_flow/data/branch'
|
3
|
+
|
4
|
+
module FlashFlow
|
5
|
+
module Data
|
6
|
+
class Github
|
7
|
+
|
8
|
+
attr_accessor :repo, :unmergeable_label
|
9
|
+
|
10
|
+
def initialize(config={})
|
11
|
+
initialize_connection!(config['token'])
|
12
|
+
@repo = config['repo']
|
13
|
+
@master_branch = config['master_branch'] || master
|
14
|
+
@unmergeable_label = config['unmergeable_label'] || 'unmergeable'
|
15
|
+
@do_not_merge_label = config['do_not_merge_label'] || 'do not merge'
|
16
|
+
@code_reviewed_label = config['code_reviewed_label'] || 'code reviewed'
|
17
|
+
@shippable_label = config['shippable_label'] || 'shippable'
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize_connection!(token)
|
21
|
+
if token.nil?
|
22
|
+
raise RuntimeError.new("Github token must be set in your flash_flow config file.")
|
23
|
+
end
|
24
|
+
octokit.configure do |c|
|
25
|
+
c.access_token = token
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def remove_from_merge(branch)
|
30
|
+
pr = pr_for(branch)
|
31
|
+
if pr && @do_not_merge_label
|
32
|
+
add_label(pr.number, @do_not_merge_label)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def fetch
|
37
|
+
pull_requests.map do |pr|
|
38
|
+
Branch.from_hash(
|
39
|
+
'ref' => pr.head.ref,
|
40
|
+
'status' => status_from_labels(pr),
|
41
|
+
'metadata' => metadata(pr),
|
42
|
+
'sha' => pr.head.sha
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_to_merge(branch)
|
48
|
+
pr = pr_for(branch)
|
49
|
+
|
50
|
+
pr ||= create_pr(branch.ref, branch.ref, branch.ref)
|
51
|
+
branch.add_metadata(metadata(pr))
|
52
|
+
|
53
|
+
if pr && @do_not_merge_label
|
54
|
+
remove_label(pr.number, @do_not_merge_label)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def mark_success(branch)
|
59
|
+
remove_label(branch.metadata['pr_number'], @unmergeable_label)
|
60
|
+
end
|
61
|
+
|
62
|
+
def mark_failure(branch)
|
63
|
+
add_label(branch.metadata['pr_number'], @unmergeable_label)
|
64
|
+
end
|
65
|
+
|
66
|
+
def code_reviewed?(branch)
|
67
|
+
has_label?(branch.metadata['pr_number'], @code_reviewed_label)
|
68
|
+
end
|
69
|
+
|
70
|
+
def can_ship?(branch)
|
71
|
+
has_label?(branch.metadata['pr_number'], @shippable_label)
|
72
|
+
end
|
73
|
+
|
74
|
+
def branch_link(branch)
|
75
|
+
branch.metadata['pr_url']
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def status_from_labels(pull_request)
|
81
|
+
case
|
82
|
+
when has_label?(pull_request.number, @do_not_merge_label)
|
83
|
+
'removed'
|
84
|
+
when has_label?(pull_request.number, @unmergeable_label)
|
85
|
+
'fail'
|
86
|
+
else
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def pr_for(branch)
|
92
|
+
pull_requests.detect { |p| branch.ref == p.head.ref }
|
93
|
+
end
|
94
|
+
|
95
|
+
def create_pr(branch, title, body)
|
96
|
+
pr = octokit.create_pull_request(repo, @master_branch, branch, title, body)
|
97
|
+
pull_requests << pr
|
98
|
+
pr
|
99
|
+
end
|
100
|
+
|
101
|
+
def pull_requests
|
102
|
+
@pull_requests ||= octokit.pull_requests(repo).sort_by(&:created_at)
|
103
|
+
end
|
104
|
+
|
105
|
+
def remove_label(pull_request_number, label)
|
106
|
+
if has_label?(pull_request_number, label)
|
107
|
+
octokit.remove_label(repo, pull_request_number, label)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def add_label(pull_request_number, label)
|
112
|
+
unless has_label?(pull_request_number, label)
|
113
|
+
octokit.add_labels_to_an_issue(repo, pull_request_number, [label])
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def has_label?(pull_request_number, label_name)
|
118
|
+
!!labels(pull_request_number).detect { |label| label == label_name }
|
119
|
+
end
|
120
|
+
|
121
|
+
def labels(pull_request_number)
|
122
|
+
@labels ||= {}
|
123
|
+
@labels[pull_request_number] ||= octokit.labels_for_issue(repo, pull_request_number).map(&:name)
|
124
|
+
end
|
125
|
+
|
126
|
+
def metadata(pr)
|
127
|
+
{
|
128
|
+
'pr_number' => pr.number,
|
129
|
+
'pr_url' => pr.html_url,
|
130
|
+
'user_url' => pr.user.html_url,
|
131
|
+
'repo_url' => pr.head.repo.html_url
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
def octokit
|
136
|
+
Octokit
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'flash_flow/data'
|
3
|
+
|
4
|
+
module FlashFlow
|
5
|
+
module Data
|
6
|
+
class Store
|
7
|
+
def initialize(filename, git, opts={})
|
8
|
+
@filename = filename
|
9
|
+
@git = git
|
10
|
+
@logger = opts[:logger] || Logger.new('/dev/null')
|
11
|
+
end
|
12
|
+
|
13
|
+
def get
|
14
|
+
file_contents = @git.read_file_from_merge_branch(@filename)
|
15
|
+
JSON.parse(file_contents)
|
16
|
+
|
17
|
+
rescue JSON::ParserError, Errno::ENOENT
|
18
|
+
@logger.error "Unable to read branch info from file: #{@filename}"
|
19
|
+
{}
|
20
|
+
end
|
21
|
+
|
22
|
+
def write(branches, file=nil)
|
23
|
+
@git.in_dir do
|
24
|
+
file ||= File.open(@filename, 'w')
|
25
|
+
file.puts JSON.pretty_generate(sort_branches(branches))
|
26
|
+
file.close
|
27
|
+
end
|
28
|
+
|
29
|
+
@git.in_temp_merge_branch do
|
30
|
+
@git.add_and_commit(@filename, 'Branch Info', add: {force: true})
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def sort_branches(branches)
|
37
|
+
return branches unless branches.is_a?(Hash)
|
38
|
+
sorted_branches = {}
|
39
|
+
branches.keys.sort.each { |key| sorted_branches[key] = sort_branches(branches[key]) }
|
40
|
+
sorted_branches
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,267 @@
|
|
1
|
+
require 'flash_flow/cmd_runner'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
module FlashFlow
|
5
|
+
class Git
|
6
|
+
ATTRIBUTES = [:remote, :merge_branch, :master_branch, :release_branch, :use_rerere]
|
7
|
+
|
8
|
+
attr_reader *ATTRIBUTES
|
9
|
+
attr_reader :working_branch
|
10
|
+
|
11
|
+
UNMERGED_STATUSES = %w{DD AU UD UA DU AA UU}
|
12
|
+
|
13
|
+
def initialize(config, logger=nil)
|
14
|
+
@cmd_runner = CmdRunner.new(logger: logger)
|
15
|
+
|
16
|
+
config['release_branch'] ||= config['master_branch']
|
17
|
+
config['remote'] ||= config['merge_remote'] # For backwards compatibility
|
18
|
+
|
19
|
+
ATTRIBUTES.each do |attr|
|
20
|
+
unless config.has_key?(attr.to_s)
|
21
|
+
raise RuntimeError.new("git configuration missing. Required config parameters: #{ATTRIBUTES}")
|
22
|
+
end
|
23
|
+
|
24
|
+
instance_variable_set("@#{attr}", config[attr.to_s])
|
25
|
+
end
|
26
|
+
|
27
|
+
@working_branch = current_branch
|
28
|
+
end
|
29
|
+
|
30
|
+
def in_dir
|
31
|
+
Dir.chdir(@cmd_runner.dir) do
|
32
|
+
yield
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def last_stdout
|
37
|
+
@cmd_runner.last_stdout
|
38
|
+
end
|
39
|
+
|
40
|
+
def last_command
|
41
|
+
@cmd_runner.last_command
|
42
|
+
end
|
43
|
+
|
44
|
+
def last_success?
|
45
|
+
@cmd_runner.last_success?
|
46
|
+
end
|
47
|
+
|
48
|
+
def run(cmd, opts={})
|
49
|
+
@cmd_runner.run("git #{cmd}", opts)
|
50
|
+
end
|
51
|
+
|
52
|
+
def working_dir
|
53
|
+
@cmd_runner.dir
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_and_commit(files, message, opts={})
|
57
|
+
files = [files].flatten
|
58
|
+
run("add #{'-f ' if opts[:add] && opts[:add][:force]}#{files.join(' ')}")
|
59
|
+
run("commit -m '#{message}'")
|
60
|
+
end
|
61
|
+
|
62
|
+
def merge(branch)
|
63
|
+
run("merge #{branch}")
|
64
|
+
end
|
65
|
+
|
66
|
+
def branch_contains?(branch, ref)
|
67
|
+
run("branch -a --contains #{ref}", log: CmdRunner::LOG_CMD)
|
68
|
+
last_stdout.split("\n").detect { |str| str[2..-1] == branch }
|
69
|
+
end
|
70
|
+
|
71
|
+
def master_branch_contains?(sha)
|
72
|
+
branch_contains?("remotes/#{remote}/#{master_branch}", sha)
|
73
|
+
end
|
74
|
+
|
75
|
+
def in_original_merge_branch
|
76
|
+
in_branch("#{remote}/#{merge_branch}") { yield }
|
77
|
+
end
|
78
|
+
|
79
|
+
def read_file_from_merge_branch(filename)
|
80
|
+
run("show #{remote}/#{merge_branch}:#{filename}", log: CmdRunner::LOG_CMD)
|
81
|
+
last_stdout
|
82
|
+
end
|
83
|
+
|
84
|
+
def initialize_rerere(copy_from_dir=nil)
|
85
|
+
return unless use_rerere
|
86
|
+
|
87
|
+
@cmd_runner.run('mkdir .git/rr-cache')
|
88
|
+
@cmd_runner.run('cp -R rr-cache/* .git/rr-cache/')
|
89
|
+
@cmd_runner.run("cp -R #{File.join(copy_from_dir, '.git/rr-cache/*')} .git/rr-cache/") if copy_from_dir
|
90
|
+
end
|
91
|
+
|
92
|
+
def commit_rerere(current_rereres)
|
93
|
+
return unless use_rerere
|
94
|
+
@cmd_runner.run('mkdir rr-cache')
|
95
|
+
@cmd_runner.run('rm -rf rr-cache/*')
|
96
|
+
current_rereres.each do |rerere|
|
97
|
+
@cmd_runner.run("cp -R .git/rr-cache/#{rerere} rr-cache/")
|
98
|
+
end
|
99
|
+
|
100
|
+
run('add rr-cache/')
|
101
|
+
run("commit -m 'Update rr-cache'")
|
102
|
+
end
|
103
|
+
|
104
|
+
def rerere_resolve!
|
105
|
+
return false unless use_rerere
|
106
|
+
|
107
|
+
if unresolved_conflicts.empty?
|
108
|
+
merging_files = staged_and_working_dir_files.select { |s| UNMERGED_STATUSES.include?(s[0..1]) }.map { |s| s[3..-1] }
|
109
|
+
conflicts = conflicted_files
|
110
|
+
|
111
|
+
run("add #{merging_files.join(" ")}")
|
112
|
+
run('commit --no-edit')
|
113
|
+
|
114
|
+
resolutions(conflicts)
|
115
|
+
else
|
116
|
+
false
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def unresolved_conflicts
|
121
|
+
in_dir do
|
122
|
+
conflicted_files.map do |file|
|
123
|
+
File.open(file) { |f| f.grep(/>>>>/) }.empty? ? nil : file
|
124
|
+
end.compact
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def resolutions(files)
|
129
|
+
{}.tap do |hash|
|
130
|
+
files.map do |file|
|
131
|
+
hash[file] = resolution_candidates(file)
|
132
|
+
end.flatten
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# git rerere doesn't give you a deterministic way to determine which resolution was used
|
137
|
+
def resolution_candidates(file)
|
138
|
+
@cmd_runner.run("diff -q --from-file #{file} .git/rr-cache/*/postimage*", log: CmdRunner::LOG_CMD)
|
139
|
+
different_files = split_diff_lines(@cmd_runner.last_stdout)
|
140
|
+
|
141
|
+
@cmd_runner.run('ls -la .git/rr-cache/*/postimage*', log: CmdRunner::LOG_CMD)
|
142
|
+
all_files = split_diff_lines(@cmd_runner.last_stdout)
|
143
|
+
|
144
|
+
all_files - different_files
|
145
|
+
end
|
146
|
+
|
147
|
+
def split_diff_lines(arr)
|
148
|
+
arr.split("\n").map { |s| s.split(".git/rr-cache/").last.split("/postimage").first }
|
149
|
+
end
|
150
|
+
|
151
|
+
def staged_and_working_dir_files
|
152
|
+
run("status --porcelain")
|
153
|
+
last_stdout.split("\n").reject { |line| line[0..1] == '??' }
|
154
|
+
end
|
155
|
+
|
156
|
+
def conflicted_files
|
157
|
+
run("diff --name-only --diff-filter=U")
|
158
|
+
last_stdout.split("\n")
|
159
|
+
end
|
160
|
+
|
161
|
+
def current_branch
|
162
|
+
run("rev-parse --abbrev-ref HEAD")
|
163
|
+
last_stdout.strip
|
164
|
+
end
|
165
|
+
|
166
|
+
def most_recent_commit
|
167
|
+
run("show -s --format=%cd head")
|
168
|
+
end
|
169
|
+
|
170
|
+
def reset_temp_merge_branch
|
171
|
+
in_branch(master_branch) do
|
172
|
+
run("fetch #{remote}")
|
173
|
+
run("branch -D #{temp_merge_branch}")
|
174
|
+
run("checkout -b #{temp_merge_branch}")
|
175
|
+
run("reset --hard #{remote}/#{master_branch}")
|
176
|
+
run("clean -x -f -d")
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def push(branch, force=false)
|
181
|
+
run("push #{'-f' if force} #{remote} #{branch}:#{branch}")
|
182
|
+
end
|
183
|
+
|
184
|
+
def copy_temp_to_branch(branch, squash_message = nil)
|
185
|
+
run("checkout #{temp_merge_branch}")
|
186
|
+
run("merge --strategy=ours --no-edit #{branch}")
|
187
|
+
run("checkout #{branch}")
|
188
|
+
run("merge #{temp_merge_branch}")
|
189
|
+
|
190
|
+
squash_commits(branch, squash_message) if squash_message
|
191
|
+
end
|
192
|
+
|
193
|
+
def delete_temp_merge_branch
|
194
|
+
in_branch(master_branch) do
|
195
|
+
run("branch -d #{temp_merge_branch}")
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def in_temp_merge_branch(&block)
|
200
|
+
in_branch(temp_merge_branch, &block)
|
201
|
+
end
|
202
|
+
|
203
|
+
def in_merge_branch(&block)
|
204
|
+
in_branch(merge_branch, &block)
|
205
|
+
end
|
206
|
+
|
207
|
+
def in_branch(branch)
|
208
|
+
begin
|
209
|
+
starting_branch = current_branch
|
210
|
+
run("checkout #{branch}")
|
211
|
+
|
212
|
+
yield
|
213
|
+
ensure
|
214
|
+
run("checkout #{starting_branch}")
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def temp_merge_branch
|
219
|
+
"flash_flow-#{merge_branch}"
|
220
|
+
end
|
221
|
+
|
222
|
+
def get_sha(branch, opts={})
|
223
|
+
if opts[:short]
|
224
|
+
run("rev-parse --short #{branch}")
|
225
|
+
else
|
226
|
+
run("rev-parse #{branch}")
|
227
|
+
end
|
228
|
+
last_stdout.strip if last_success?
|
229
|
+
end
|
230
|
+
|
231
|
+
def branch_exists?(branch)
|
232
|
+
run("rev-parse --verify #{branch}")
|
233
|
+
last_success?
|
234
|
+
end
|
235
|
+
|
236
|
+
def ahead_of_master?(branch)
|
237
|
+
branch_exists?(branch) && !master_branch_contains?(get_sha(branch))
|
238
|
+
end
|
239
|
+
|
240
|
+
def version
|
241
|
+
run('--version')
|
242
|
+
semver_regex = Regexp.new('.*(\d+\.\d+\.\d+).*')
|
243
|
+
running_version = last_stdout.strip
|
244
|
+
if semver = semver_regex.match(running_version)
|
245
|
+
semver[1]
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
private
|
250
|
+
|
251
|
+
def squash_commits(branch, commit_message)
|
252
|
+
unless branch_exists?("#{remote}/#{branch}")
|
253
|
+
run("push #{remote} #{master_branch}:#{branch}")
|
254
|
+
end
|
255
|
+
|
256
|
+
# Get all the files that differ between existing acceptance and new acceptance
|
257
|
+
run("diff --name-only #{remote}/#{branch} #{branch}")
|
258
|
+
files = last_stdout.split("\n")
|
259
|
+
run("reset #{remote}/#{branch}")
|
260
|
+
|
261
|
+
run("add -f #{files.map { |f| "\"#{Shellwords.escape(f)}\"" }.join(" ")}")
|
262
|
+
|
263
|
+
run("commit -m '#{commit_message}'")
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
end
|