ndr_dev_support 1.1.1

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.
@@ -0,0 +1,82 @@
1
+ require 'English'
2
+ require 'rubocop'
3
+ require 'shellwords'
4
+
5
+ module NdrDevSupport
6
+ module Rubocop
7
+ # Produces diffs, and parses from them the file/hunk boundaries
8
+ class RangeFinder
9
+ class << self
10
+ # Use RuboCop to produce a list of all files that should be scanned.
11
+ def target_files
12
+ @target_files ||= begin
13
+ defaults = ::RuboCop::ConfigStore.new
14
+ ::RuboCop::TargetFinder.new(defaults, {}).target_files_in_dir
15
+ end
16
+ end
17
+ end
18
+
19
+ def diff_files(files)
20
+ diff = Array(files).map { |file| git_diff(%(-- "#{file}")) }.join
21
+ file_change_locations_from diff
22
+ end
23
+
24
+ def diff_head
25
+ file_change_locations_from git_diff('HEAD')
26
+ end
27
+
28
+ def diff_staged
29
+ file_change_locations_from git_diff('--staged')
30
+ end
31
+
32
+ def diff_unstaged
33
+ file_change_locations_from git_diff('')
34
+ end
35
+
36
+ def diff_expr(expr)
37
+ file_change_locations_from git_diff(expr)
38
+ end
39
+
40
+ private
41
+
42
+ def git_diff(args)
43
+ diff = `git diff --no-prefix --unified=0 #{Shellwords.escape(args)}`
44
+ fail "Failed to diff: '#{args}'" unless $CHILD_STATUS.exitstatus.zero?
45
+ diff
46
+ end
47
+
48
+ def file_changes_hash
49
+ Hash.new { |hash, file| hash[file] = [] }
50
+ end
51
+
52
+ def file_change_locations_from(diff, changes = file_changes_hash)
53
+ current_file = nil
54
+ diff.each_line do |line|
55
+ if line.start_with?('+++')
56
+ current_file = line[4..-1].strip
57
+ elsif line.start_with?('@@')
58
+ range = hunk_range_from(line)
59
+ changes[current_file].push(range) unless range.end.zero?
60
+ end
61
+ end
62
+
63
+ ruby_files_from changes
64
+ end
65
+
66
+ # Don't report on changes in files that RuboCop won't understand:
67
+ def ruby_files_from(changes)
68
+ whitelist = RangeFinder.target_files
69
+ changes.reject { |file, _ranges| !whitelist.include? File.join(Dir.pwd, file) }
70
+ end
71
+
72
+ def hunk_range_from(line)
73
+ match_data = line.match(/\A@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/)
74
+ start_line = match_data[1].to_i
75
+ new_lines = match_data[2].to_i
76
+ end_line = new_lines.zero? ? start_line : start_line + new_lines - 1
77
+
78
+ start_line..end_line
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,76 @@
1
+ require 'rainbow'
2
+
3
+ module NdrDevSupport
4
+ module Rubocop
5
+ # Handles the display of any rubocop output
6
+ class Reporter
7
+ HEADER = (('=' * 34) << ' Summary: ' << ('=' * 34)).freeze
8
+ FOOTER = ('=' * HEADER.length).freeze
9
+
10
+ COLOURS = {
11
+ 'refactor' => :yellow,
12
+ 'convention' => :yellow,
13
+ 'warning' => :magenta,
14
+ 'error' => :red,
15
+ 'fatal' => :red
16
+ }.freeze
17
+
18
+ def initialize(offenses)
19
+ @offenses = offenses
20
+ end
21
+
22
+ # Prints out a report, and returns an appriopriate
23
+ # exit status for the rake task to terminate with.
24
+ def report
25
+ if @offenses.any?
26
+ print_summary
27
+ puts
28
+ print_offenses
29
+ return @offenses.values.all?(&:empty?)
30
+ else
31
+ puts Rainbow('No files scanned.').red
32
+ return false
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def colour_severity(severity, initial_only = false)
39
+ colour = COLOURS[severity]
40
+ message = initial_only ? severity[0, 1].upcase : severity
41
+ colour ? Rainbow(message).color(colour) : message
42
+ end
43
+
44
+ def print_summary
45
+ puts HEADER
46
+ @offenses.each do |filename, file_offenses|
47
+ puts format(' * %s has %d relevant offence%s.',
48
+ Rainbow(filename).fg(file_offenses.empty? ? :green : :red),
49
+ file_offenses.length,
50
+ file_offenses.one? ? '' : 's'
51
+ )
52
+ end
53
+ puts FOOTER
54
+ end
55
+
56
+ def print_offenses
57
+ @offenses.each do |filename, file_offenses|
58
+ file_offenses.each do |offense|
59
+ puts formatted_offense(filename, offense)
60
+ end
61
+ end
62
+ end
63
+
64
+ def formatted_offense(filename, offense)
65
+ format('%s:%d:%d: %s: %s %s',
66
+ Rainbow(filename).cyan,
67
+ offense['location']['line'],
68
+ offense['location']['column'],
69
+ colour_severity(offense['severity'], true),
70
+ offense['message'],
71
+ colour_severity(offense['severity'])
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,2 @@
1
+ load 'tasks/audit_code.rake'
2
+ load 'tasks/rubocop.rake'
@@ -0,0 +1,5 @@
1
+ # This defines the NdrDevSupport version. If you change it, rebuild and commit the gem.
2
+ # Use "rake build" to build the gem, see rake -T for all bundler rake tasks (and our own).
3
+ module NdrDevSupport
4
+ VERSION = '1.1.1'.freeze
5
+ end
@@ -0,0 +1,475 @@
1
+ SAFETY_FILE =
2
+ if File.exist?('code_safety.yml')
3
+ 'code_safety.yml'
4
+ elsif defined?(Rails)
5
+ Rails.root.join('config', 'code_safety.yml')
6
+ else
7
+ 'code_safety.yml'
8
+ end
9
+
10
+ # Temporary overrides to only audit external access files
11
+ SAFETY_REPOS = [['/svn/era', '/svn/extra/era/external-access']]
12
+
13
+ require 'yaml'
14
+
15
+ # Parameter max_print is number of entries to print before truncating output
16
+ # (negative value => print all)
17
+ def audit_code_safety(max_print = 20, ignore_new = false, show_diffs = false, show_in_priority = false, user_name = 'usr')
18
+ puts 'Running source code safety audit script.'
19
+ puts
20
+
21
+ max_print = 1_000_000 if max_print < 0
22
+ safety_cfg = File.exist?(SAFETY_FILE) ? YAML.load_file(SAFETY_FILE) : {}
23
+ file_safety = safety_cfg['file safety']
24
+ if file_safety.nil?
25
+ puts "Creating new 'file safety' block in #{SAFETY_FILE}"
26
+ safety_cfg['file safety'] = file_safety = {}
27
+ end
28
+ file_safety.each do |_k, v|
29
+ rev = v['safe_revision']
30
+ v['safe_revision'] = rev.to_s if rev.is_a?(Integer)
31
+ end
32
+ orig_count = file_safety.size
33
+
34
+ safety_repo = trunk_repo = get_trunk_repo
35
+
36
+ # TODO: below is broken for git-svn
37
+ # Is it needed?
38
+
39
+ SAFETY_REPOS.each do |suffix, alt|
40
+ # Temporarily override to only audit a different file list
41
+ if safety_repo.end_with?(suffix)
42
+ safety_repo = safety_repo[0...-suffix.length] + alt
43
+ break
44
+ end
45
+ end
46
+
47
+ if ignore_new
48
+ puts "Not checking for new files in #{safety_repo}"
49
+ else
50
+ puts "Checking for new files in #{safety_repo}"
51
+ new_files = get_new_files(safety_repo)
52
+ # Ignore subdirectories, and exclude code_safety.yml by default.
53
+ new_files.delete_if { |f| f =~ /[\/\\]$/ || f == SAFETY_FILE }
54
+ new_files.each do |f|
55
+ next if file_safety.key?(f)
56
+ file_safety[f] = {
57
+ 'comments' => nil,
58
+ 'reviewed_by' => nil,
59
+ 'safe_revision' => nil }
60
+ end
61
+ File.open(SAFETY_FILE, 'w') do |file|
62
+ # Consistent file diffs, as ruby preserves Hash insertion order since v1.9
63
+ safety_cfg['file safety'] = Hash[file_safety.sort]
64
+ YAML.dump(safety_cfg, file) # Save changes before checking latest revisions
65
+ end
66
+ end
67
+ puts "Updating latest revisions for #{file_safety.size} files"
68
+ set_last_changed_revision(trunk_repo, file_safety, file_safety.keys)
69
+ puts "\nSummary:"
70
+ puts "Number of files originally in #{SAFETY_FILE}: #{orig_count}"
71
+ puts "Number of new files added: #{file_safety.size - orig_count}"
72
+
73
+ # Now generate statistics:
74
+ unknown = file_safety.values.select { |x| x['safe_revision'].nil? }
75
+ unsafe = file_safety.values.select do |x|
76
+ !x['safe_revision'].nil? && x['safe_revision'] != -1 &&
77
+ x['last_changed_rev'] != x['safe_revision'] &&
78
+ !(x['last_changed_rev'] =~ /^[0-9]+$/ && x['safe_revision'] =~ /^[0-9]+$/ &&
79
+ x['last_changed_rev'].to_i < x['safe_revision'].to_i)
80
+ end
81
+ puts "Number of files with no safe version: #{unknown.size}"
82
+ puts "Number of files which are no longer safe: #{unsafe.size}"
83
+ puts
84
+ printed = []
85
+ # We also print a third category: ones which are no longer in the repository
86
+ file_list =
87
+ if show_in_priority
88
+ file_safety.sort_by { |_k, v| v.nil? ? -100 : v['last_changed_rev'].to_i }.map(&:first)
89
+ else
90
+ file_safety.keys.sort
91
+ end
92
+
93
+ file_list.each do |f|
94
+ if print_file_safety(file_safety, trunk_repo, f, false, printed.size >= max_print)
95
+ printed << f
96
+ end
97
+ end
98
+ puts "... and #{printed.size - max_print} others" if printed.size > max_print
99
+ if show_diffs
100
+ puts
101
+ printed.each do |f|
102
+ print_file_diffs(file_safety, trunk_repo, f, user_name)
103
+ end
104
+ end
105
+
106
+ # Returns `true` unless there are pending reviews:
107
+ unsafe.length.zero? && unknown.length.zero?
108
+ end
109
+
110
+ # Print summary details of a file's known safety
111
+ # If not verbose, only prints details for unsafe files
112
+ # Returns true if anything printed (or would have been printed if silent),
113
+ # or false otherwise.
114
+ def print_file_safety(file_safety, repo, fname, verbose = false, silent = false)
115
+ msg = "#{fname}\n "
116
+ entry = file_safety[fname]
117
+ msg += 'File not in audit list' if entry.nil?
118
+
119
+ if entry['safe_revision'].nil?
120
+ msg += 'No safe revision known'
121
+ msg += ", last changed #{entry['last_changed_rev']}" unless entry['last_changed_rev'].nil?
122
+ else
123
+ repolatest = entry['last_changed_rev'] # May have been prepopulated en mass
124
+ msg += 'Not in repository: ' if entry['last_changed_rev'] == -1
125
+ if (repolatest != entry['safe_revision']) &&
126
+ !(repolatest =~ /^[0-9]+$/ && entry['safe_revision'] =~ /^[0-9]+$/ &&
127
+ repolatest.to_i < entry['safe_revision'].to_i)
128
+ # (Allow later revisions to be treated as safe for svn)
129
+ msg += "No longer safe since revision #{repolatest}: "
130
+ else
131
+ return false unless verbose
132
+ msg += 'Safe: '
133
+ end
134
+ msg += "revision #{entry['safe_revision']} reviewed by #{entry['reviewed_by']}"
135
+ end
136
+ msg += "\n Comments: #{entry['comments']}" if entry['comments']
137
+ puts msg unless silent
138
+ true
139
+ end
140
+
141
+ def flag_file_as_safe(release, reviewed_by, comments, f)
142
+ safety_cfg = YAML.load_file(SAFETY_FILE)
143
+ file_safety = safety_cfg['file safety']
144
+
145
+ unless File.exist?(f)
146
+ abort("Error: Unable to flag non-existent file as safe: #{f}")
147
+ end
148
+ unless file_safety.key?(f)
149
+ file_safety[f] = {
150
+ 'comments' => nil,
151
+ 'reviewed_by' => :dummy, # dummy value, will be overwritten
152
+ 'safe_revision' => nil }
153
+ end
154
+ entry = file_safety[f]
155
+ entry_orig = entry.dup
156
+ if comments.to_s.length > 0 && entry['comments'] != comments
157
+ entry['comments'] = if entry['comments'].to_s.empty?
158
+ comments
159
+ else
160
+ "#{entry['comments']}#{'.' unless entry['comments'].end_with?('.')} Revision #{release}: #{comments}"
161
+ end
162
+ end
163
+ if entry['safe_revision']
164
+ unless release
165
+ abort("Error: File already has safe revision #{entry['safe_revision']}: #{f}")
166
+ end
167
+ if release.is_a?(Integer) && release < entry['safe_revision']
168
+ puts("Warning: Rolling back safe revision from #{entry['safe_revision']} to #{release} for #{f}")
169
+ end
170
+ end
171
+ entry['safe_revision'] = release
172
+ entry['reviewed_by'] = reviewed_by
173
+ if entry == entry_orig
174
+ puts "No changes when updating safe_revision to #{release || '[none]'} for #{f}"
175
+ else
176
+ File.open(SAFETY_FILE, 'w') do |file|
177
+ # Consistent file diffs, as ruby preserves Hash insertion order since v1.9
178
+ safety_cfg['file safety'] = Hash[file_safety.sort]
179
+ YAML.dump(safety_cfg, file) # Save changes before checking latest revisions
180
+ end
181
+ puts "Updated safe_revision to #{release || '[none]'} for #{f}"
182
+ end
183
+ end
184
+
185
+ # Determine the type of repository
186
+ def repository_type
187
+ @repository_type ||= if Dir.exist?('.svn') || system("svn info . > /dev/null 2>&1")
188
+ 'svn'
189
+ elsif Dir.exist?('.git') && open('.git/config').grep(/svn/).any?
190
+ 'git-svn'
191
+ elsif Dir.exist?('.git') && open('.git/config').grep(/git/).any?
192
+ 'git'
193
+ else
194
+ 'not known'
195
+ end
196
+ end
197
+
198
+ def get_trunk_repo
199
+ case repository_type
200
+ when 'svn'
201
+ repo_info = %x[svn info]
202
+ puts 'svn case'
203
+ return repo_info.split("\n").select { |x| x =~ /^URL: / }.collect { |x| x[5..-1] }.first
204
+ when 'git-svn'
205
+ puts 'git-svn case'
206
+ repo_info = %x[git svn info]
207
+ return repo_info.split("\n").select { |x| x =~ /^URL: / }.collect { |x| x[5..-1] }.first
208
+ when 'git'
209
+ puts 'git case'
210
+ repo_info = %x[git remote -v]
211
+ return repo_info.split("\n").first[7..-9]
212
+ else
213
+ return 'Information not available. Unknown repository type'
214
+ end
215
+ end
216
+
217
+ def get_new_files(safety_repo)
218
+ case repository_type
219
+ when 'svn', 'git-svn'
220
+ %x[svn ls -R "#{safety_repo}"].split("\n")
221
+ when 'git'
222
+ #%x[git ls-files --modified].split("\n")
223
+ %x[git ls-files].split("\n")
224
+
225
+ # TODO: Below is for remote repository - for testing use local files
226
+ #new_files = %x[git ls-files --modified #{safety_repo}].split("\n")
227
+ # TODO: Do we need the --modified option?
228
+ #new_files = %x[git ls-files --modified].split("\n")
229
+ else
230
+ []
231
+ end
232
+ end
233
+
234
+ # Fill in the latest changed revisions in a file safety map.
235
+ # (Don't write this data to the YAML file, as it is intrinsic to the SVN
236
+ # repository.)
237
+ def set_last_changed_revision(repo, file_safety, fnames)
238
+ dot_freq = (file_safety.size / 40.0).ceil # Print up to 40 progress dots
239
+ case repository_type
240
+ when 'git'
241
+ fnames = file_safety.keys if fnames.nil?
242
+
243
+ fnames.each_with_index do |f, i|
244
+ info = %x[git log -n 1 #{f}].split("\n").first[7..-1]
245
+ if info.nil? || info.empty?
246
+ file_safety[f]['last_changed_rev'] = -1
247
+ else
248
+ file_safety[f]['last_changed_rev'] = info
249
+ end
250
+ # Show progress
251
+ print '.' if (i % dot_freq) == 0
252
+ end
253
+ puts
254
+ when 'git-svn', 'svn'
255
+ fnames = file_safety.keys if fnames.nil?
256
+
257
+ fnames.each_with_index do |f, i|
258
+ last_revision = get_last_changed_revision(repo, f)
259
+ if last_revision.nil? || last_revision.empty?
260
+ file_safety[f]['last_changed_rev'] = -1
261
+ else
262
+ file_safety[f]['last_changed_rev'] = last_revision
263
+ end
264
+ # Show progress
265
+ print '.' if (i % dot_freq) == 0
266
+ end
267
+ puts
268
+ # NOTE: Do we need the following for retries?
269
+ # if retries && result.size != fnames.size && fnames.size > 1
270
+ # # At least one invalid (deleted file --> subsequent arguments ignored)
271
+ # # Try each file individually
272
+ # # (It would probably be safe to continue from the extra_info.size argument)
273
+ # puts "Retrying (got #{result.size}, expected #{fnames.size})" if debug >= 2
274
+ # result = []
275
+ # fnames.each{ |f|
276
+ # result += svn_info_entries([f], repo, false, debug)
277
+ # }
278
+ # end
279
+ end
280
+ end
281
+
282
+ # Return the last changed revision
283
+ def get_last_changed_revision(repo, fname)
284
+ case repository_type
285
+ when 'git'
286
+ %x[git log -n 1 "#{fname}"].split("\n").first[7..-1]
287
+ when 'git-svn', 'svn'
288
+ begin
289
+ svn_info = %x[svn info -r head "#{repo}/#{fname}"]
290
+ rescue
291
+ puts 'we have an error in the svn info line'
292
+ end
293
+ begin
294
+ svn_info.match('Last Changed Rev: ([0-9]*)').to_a[1]
295
+ rescue
296
+ puts 'We have an error in getting the revision'
297
+ end
298
+ end
299
+ end
300
+
301
+ # Get mime type. Note that Git does not have this information
302
+ def get_mime_type(repo, fname)
303
+ case repository_type
304
+ when 'git'
305
+ 'Git does not provide mime types'
306
+ when 'git-svn', 'svn'
307
+ %x[svn propget svn:mime-type "#{repo}/#{fname}"].chomp
308
+ end
309
+ end
310
+
311
+ # # Print file diffs, for code review
312
+ def print_file_diffs(file_safety, repo, fname, user_name)
313
+ entry = file_safety[fname]
314
+ repolatest = entry['last_changed_rev']
315
+ safe_revision = entry['safe_revision']
316
+
317
+ if safe_revision.nil?
318
+ first_revision = set_safe_revision
319
+ print_repo_file_diffs(repolatest, repo, fname, user_name, first_revision)
320
+ else
321
+
322
+ rev = get_last_changed_revision(repo, fname)
323
+ if rev
324
+ mime = get_mime_type(repo, fname)
325
+ end
326
+
327
+ print_repo_file_diffs(repolatest, repo, fname, user_name, safe_revision) if repolatest != safe_revision
328
+ end
329
+ end
330
+
331
+ # Returns first commit for git and 0 for svn in order to be used to display
332
+ # new files. Called from print_file_diffs
333
+ def set_safe_revision
334
+ case repository_type
335
+ when 'git'
336
+ %x[git rev-list --max-parents=0 HEAD].chomp
337
+ when 'git-svn', 'svn'
338
+ 0
339
+ end
340
+ end
341
+
342
+ def print_repo_file_diffs(repolatest, repo, fname, user_name, safe_revision)
343
+ require 'open3'
344
+ cmd = nil
345
+ case repository_type
346
+ when 'git'
347
+ cmd = ['git', '--no-pager', 'diff', '-b', "#{safe_revision}..#{repolatest}", fname]
348
+ when 'git-svn', 'svn'
349
+ cmd = ['svn', 'diff', '-r', "#{safe_revision.to_i}:#{repolatest.to_i}", '-x', '-b', "#{repo}/#{fname}"]
350
+ end
351
+ if cmd
352
+ puts(cmd.join(' '))
353
+ stdout_and_err_str, status = Open3.capture2e(*cmd)
354
+ puts(stdout_and_err_str)
355
+ else
356
+ puts 'Unknown repo'
357
+ end
358
+
359
+ puts %(To flag the changes to this file as safe, run:)
360
+ puts %( rake audit:safe release=#{repolatest} file=#{fname} reviewed_by=#{user_name} comments="")
361
+ puts
362
+ end
363
+
364
+ def release_valid?(release)
365
+ case repository_type
366
+ when 'svn', 'git-svn'
367
+ release =~ /\A[0-9][0-9]*\Z/
368
+ when 'git'
369
+ release =~ /\A[0-9a-f]{40}\Z/
370
+ else
371
+ false
372
+ end
373
+ end
374
+
375
+ def get_release
376
+ release = ENV['release']
377
+ release = nil if release == '0'
378
+ case repository_type
379
+ when 'svn', 'git-svn'
380
+ release.to_i
381
+ when 'git'
382
+ release
383
+ else
384
+ ''
385
+ end
386
+ release
387
+ end
388
+
389
+ def clean_working_copy?
390
+ case repository_type
391
+ when 'svn'
392
+ system('svn status | grep -q [^AMCDG]')
393
+ when 'git', 'git-svn'
394
+ system('git diff --quiet HEAD')
395
+ end
396
+ end
397
+
398
+ namespace :audit do
399
+ desc "Audit safety of source code.
400
+ Usage: audit:code [max_print=n] [ignore_new=false|true] [show_diffs=false|true] [reviewed_by=usr]
401
+
402
+ File #{SAFETY_FILE} lists the safety and revision information
403
+ of the era source code. This task updates the list, and [TODO] warns about
404
+ files which have changed since they were last verified as safe."
405
+ task(:code) do
406
+ puts 'Usage: audit:code [max_print=n] [ignore_new=false|true] [show_diffs=false|true] [show_in_priority=false|true] [reviewed_by=usr]'
407
+ puts "This is a #{repository_type} repository"
408
+
409
+ ignore_new = (ENV['ignore_new'].to_s =~ /\Atrue\Z/i)
410
+ show_diffs = (ENV['show_diffs'].to_s =~ /\Atrue\Z/i)
411
+ show_in_priority = (ENV['show_in_priority'].to_s =~ /\Atrue\Z/i)
412
+ max_print = ENV['max_print'] =~ /\A-?[0-9][0-9]*\Z/ ? ENV['max_print'].to_i : 20
413
+ reviewer = ENV['reviewed_by']
414
+
415
+ all_safe = audit_code_safety(max_print, ignore_new, show_diffs, show_in_priority, reviewer)
416
+
417
+ unless show_diffs
418
+ puts 'To show file diffs, run: rake audit:code max_print=-1 show_diffs=true'
419
+ end
420
+
421
+ exit(1) unless all_safe
422
+ end
423
+
424
+ desc "Flag a source file as safe.
425
+
426
+ Usage:
427
+ Flag as safe: rake audit:safe release=revision reviewed_by=usr [comments=...] file=f
428
+ Needs review: rake audit:safe release=0 [comments=...] file=f"
429
+ task(:safe) do
430
+ release = get_release
431
+
432
+ required_fields = %w(release file)
433
+ required_fields << 'reviewed_by' if release # 'Needs review' doesn't need a reviewer
434
+ missing = required_fields.collect { |f| (f if ENV[f].to_s.empty? || (f == 'reviewed_by' && ENV[f] == 'usr')) }.compact # Avoid accidental missing username
435
+ unless missing.empty?
436
+ puts 'Usage: rake audit:safe release=revision reviewed_by=usr [comments=...] file=f'
437
+ puts 'or, to flag a file for review: rake audit:safe release=0 [comments=...] file=f'
438
+ abort("Error: Missing required argument(s): #{missing.join(', ')}")
439
+ end
440
+
441
+ unless release.nil? || release_valid?(release)
442
+ puts 'Usage: rake audit:safe release=revision reviewed_by=usr [comments=...] file=f'
443
+ puts 'or, to flag a file for review: rake audit:safe release=0 [comments=...] file=f'
444
+ abort("Error: Invalid release: #{ENV['release']}")
445
+ end
446
+
447
+ flag_file_as_safe(release, ENV['reviewed_by'], ENV['comments'], ENV['file'])
448
+ end
449
+
450
+ desc 'Wraps audit:code, and stops if any review is pending/stale.'
451
+ task(:ensure_safe) do
452
+ abort('You have local changes, cannot verify code safety!') unless clean_working_copy?
453
+
454
+ puts 'Checking code safety...'
455
+
456
+ begin
457
+ begin
458
+ $stdout = $stderr = StringIO.new
459
+ Rake::Task['audit:code'].invoke
460
+ ensure
461
+ $stdout, $stderr = STDOUT, STDERR
462
+ end
463
+ rescue SystemExit => ex
464
+ puts '=============================================================='
465
+ puts 'Code safety review of some files are not up-to-date; aborting!'
466
+ puts ' - to review the files in question, run: rake audit:code'
467
+ puts '=============================================================='
468
+
469
+ raise ex
470
+ end
471
+ end
472
+ end
473
+
474
+ # Prevent building of un-reviewed gems:
475
+ task build: :'audit:ensure_safe'