ndr_support 3.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.
- checksums.yaml +15 -0
- data/.gitignore +14 -0
- data/.rubocop.yml +27 -0
- data/.ruby-version +1 -0
- data/.travis.yml +22 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/Guardfile +16 -0
- data/LICENSE.txt +21 -0
- data/README.md +91 -0
- data/Rakefile +12 -0
- data/code_safety.yml +258 -0
- data/gemfiles/Gemfile.rails32 +6 -0
- data/gemfiles/Gemfile.rails32.lock +108 -0
- data/gemfiles/Gemfile.rails41 +6 -0
- data/gemfiles/Gemfile.rails41.lock +111 -0
- data/gemfiles/Gemfile.rails42 +6 -0
- data/gemfiles/Gemfile.rails42.lock +111 -0
- data/lib/ndr_support.rb +21 -0
- data/lib/ndr_support/array.rb +52 -0
- data/lib/ndr_support/concerns/working_days.rb +94 -0
- data/lib/ndr_support/date_and_time_extensions.rb +103 -0
- data/lib/ndr_support/daterange.rb +196 -0
- data/lib/ndr_support/fixnum/calculations.rb +15 -0
- data/lib/ndr_support/fixnum/julian_date_conversions.rb +14 -0
- data/lib/ndr_support/hash.rb +52 -0
- data/lib/ndr_support/integer.rb +12 -0
- data/lib/ndr_support/nil.rb +38 -0
- data/lib/ndr_support/ourdate.rb +97 -0
- data/lib/ndr_support/ourtime.rb +51 -0
- data/lib/ndr_support/regexp_range.rb +65 -0
- data/lib/ndr_support/safe_file.rb +185 -0
- data/lib/ndr_support/safe_path.rb +268 -0
- data/lib/ndr_support/string/cleaning.rb +136 -0
- data/lib/ndr_support/string/conversions.rb +137 -0
- data/lib/ndr_support/tasks.rb +1 -0
- data/lib/ndr_support/time/conversions.rb +13 -0
- data/lib/ndr_support/utf8_encoding.rb +72 -0
- data/lib/ndr_support/utf8_encoding/control_characters.rb +53 -0
- data/lib/ndr_support/utf8_encoding/force_binary.rb +44 -0
- data/lib/ndr_support/utf8_encoding/object_support.rb +31 -0
- data/lib/ndr_support/version.rb +5 -0
- data/lib/ndr_support/yaml/serialization_migration.rb +65 -0
- data/lib/tasks/audit_code.rake +423 -0
- data/ndr_support.gemspec +39 -0
- data/test/array_test.rb +20 -0
- data/test/concerns/working_days_test.rb +122 -0
- data/test/daterange_test.rb +194 -0
- data/test/fixnum/calculations_test.rb +28 -0
- data/test/hash_test.rb +84 -0
- data/test/integer_test.rb +14 -0
- data/test/nil_test.rb +40 -0
- data/test/ourdate_test.rb +27 -0
- data/test/ourtime_test.rb +27 -0
- data/test/regexp_range_test.rb +135 -0
- data/test/resources/filesystem_paths.yml +37 -0
- data/test/safe_file_test.rb +597 -0
- data/test/safe_path_test.rb +168 -0
- data/test/string/cleaning_test.rb +176 -0
- data/test/string/conversions_test.rb +353 -0
- data/test/test_helper.rb +41 -0
- data/test/time/conversions_test.rb +15 -0
- data/test/utf8_encoding/control_characters_test.rb +84 -0
- data/test/utf8_encoding/force_binary_test.rb +64 -0
- data/test/utf8_encoding_test.rb +170 -0
- data/test/yaml/serialization_test.rb +145 -0
- metadata +295 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'ndr_support/utf8_encoding'
|
2
|
+
|
3
|
+
# Allows any object (if supported) to have all related
|
4
|
+
# strings encoded in place to UTF-8.
|
5
|
+
module UTF8Encoding
|
6
|
+
module ObjectSupport
|
7
|
+
# Recursively ensure the correct encoding is being used:
|
8
|
+
def ensure_utf8_object!(object)
|
9
|
+
case object
|
10
|
+
when String
|
11
|
+
ensure_utf8!(object)
|
12
|
+
when Hash
|
13
|
+
ensure_utf8_hash!(object)
|
14
|
+
when Array
|
15
|
+
ensure_utf8_array!(object)
|
16
|
+
else
|
17
|
+
object
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Ensures all values of the given `hash` are UTF-8, where possible.
|
22
|
+
def ensure_utf8_hash!(hash)
|
23
|
+
hash.each_value { |value| ensure_utf8_object!(value) }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Ensures all elements of the given `array` are UTF-8, where possible.
|
27
|
+
def ensure_utf8_array!(array)
|
28
|
+
array.each { |element| ensure_utf8_object!(element) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'ndr_support/utf8_encoding'
|
3
|
+
|
4
|
+
module NdrSupport
|
5
|
+
module YAML
|
6
|
+
# Lightweight wrapper around YAML serialization, to provide any
|
7
|
+
# necessary support for YAML engines and string encodings.
|
8
|
+
module SerializationMigration
|
9
|
+
include UTF8Encoding
|
10
|
+
|
11
|
+
# Wrapper around: YAML.load(string)
|
12
|
+
def load_yaml(string, coerce_invalid_chars = false)
|
13
|
+
fix_encoding!(string, coerce_invalid_chars)
|
14
|
+
|
15
|
+
# Achieve same behaviour using `syck` and `psych`:
|
16
|
+
handle_special_characters!(string, coerce_invalid_chars)
|
17
|
+
fix_encoding!(string, coerce_invalid_chars)
|
18
|
+
|
19
|
+
object = Psych.load(string)
|
20
|
+
|
21
|
+
# Ensure that any string related to the object
|
22
|
+
# we've loaded is also valid UTF-8.
|
23
|
+
ensure_utf8_object!(object)
|
24
|
+
|
25
|
+
# We escape all non-printing control chars:
|
26
|
+
escape_control_chars_in_object!(object)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Wrapper around: YAML.dump(object)
|
30
|
+
def dump_yaml(object)
|
31
|
+
# Psych produces UTF-8 encoded output; we'd rather
|
32
|
+
# have YAML that can be safely stored in stores with
|
33
|
+
# other encodings. If #load_yaml is used, the binary
|
34
|
+
# encoding of the object will be reversed on load.
|
35
|
+
Psych.dump binary_encode_any_high_ascii(object)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# Makes `string` valid UTF-8. If `coerce` is true,
|
41
|
+
# any invalid characters will be escaped - if false,
|
42
|
+
# they will trigger an UTF8Encoding::UTF8CoercionError.
|
43
|
+
def fix_encoding!(string, coerce)
|
44
|
+
coerce ? coerce_utf8!(string) : ensure_utf8!(string)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Within double quotes, YAML allows special characters.
|
48
|
+
# While `psych` emits UTF-8 YAML, `syck` double escapes
|
49
|
+
# higher characters. We need to unescape any we find:
|
50
|
+
def handle_special_characters!(string, coerce_invalid_chars)
|
51
|
+
# Replace any encoded hex chars with their actual value:
|
52
|
+
string.gsub!(/((?:\\x[0-9A-F]{2})+)/) do
|
53
|
+
byte_sequence = $1.scan(/[0-9A-F]{2}/)
|
54
|
+
byte_sequence.pack('H2' * byte_sequence.length).tap do |sequence|
|
55
|
+
fix_encoding!(sequence, coerce_invalid_chars)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Re-escape any non-printing control characters,
|
60
|
+
# as they can break the YAML parser:
|
61
|
+
escape_control_chars_in_object!(string)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,423 @@
|
|
1
|
+
SAFETY_FILE =
|
2
|
+
if defined?(Rails)
|
3
|
+
Rails.root.join('config', 'code_safety.yml')
|
4
|
+
else
|
5
|
+
'code_safety.yml'
|
6
|
+
end
|
7
|
+
|
8
|
+
# Temporary overrides to only audit external access files
|
9
|
+
SAFETY_REPOS = [['/svn/era', '/svn/extra/era/external-access']]
|
10
|
+
|
11
|
+
require 'yaml'
|
12
|
+
|
13
|
+
|
14
|
+
# Parameter max_print is number of entries to print before truncating output
|
15
|
+
# (negative value => print all)
|
16
|
+
def audit_code_safety(max_print = 20, ignore_new = false, show_diffs = false, show_in_priority = false, user_name = 'usr')
|
17
|
+
puts 'Running source code safety audit script.'
|
18
|
+
puts
|
19
|
+
|
20
|
+
max_print = 1_000_000 if max_print < 0
|
21
|
+
safety_cfg = File.exist?(SAFETY_FILE) ? YAML.load_file(SAFETY_FILE) : {}
|
22
|
+
file_safety = safety_cfg["file safety"]
|
23
|
+
if file_safety.nil?
|
24
|
+
puts "Creating new 'file safety' block in #{SAFETY_FILE}"
|
25
|
+
safety_cfg["file safety"] = file_safety = {}
|
26
|
+
end
|
27
|
+
file_safety.each do |k, v|
|
28
|
+
rev = v['safe_revision']
|
29
|
+
v['safe_revision'] = rev.to_s if rev.is_a?(Integer)
|
30
|
+
end
|
31
|
+
orig_count = file_safety.size
|
32
|
+
|
33
|
+
safety_repo = trunk_repo = get_trunk_repo
|
34
|
+
|
35
|
+
# TODO: below is broken for git-svn
|
36
|
+
# Is it needed?
|
37
|
+
|
38
|
+
SAFETY_REPOS.each{|suffix, alt|
|
39
|
+
# Temporarily override to only audit a different file list
|
40
|
+
if safety_repo.end_with?(suffix)
|
41
|
+
safety_repo = safety_repo[0...-suffix.length]+alt
|
42
|
+
break
|
43
|
+
end
|
44
|
+
}
|
45
|
+
|
46
|
+
if ignore_new
|
47
|
+
puts "Not checking for new files in #{safety_repo}"
|
48
|
+
else
|
49
|
+
puts "Checking for new files in #{safety_repo}"
|
50
|
+
new_files = get_new_files(safety_repo)
|
51
|
+
# Ignore subdirectories, and exclude code_safety.yml by default.
|
52
|
+
new_files.delete_if{|f| f =~ /[\/\\]$/ || f == SAFETY_FILE}
|
53
|
+
new_files.each{|f|
|
54
|
+
unless file_safety.has_key?(f)
|
55
|
+
file_safety[f] = {
|
56
|
+
"comments" => nil,
|
57
|
+
"reviewed_by" => nil,
|
58
|
+
"safe_revision" => nil }
|
59
|
+
end
|
60
|
+
}
|
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{|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
|
+
}
|
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
|
+
if show_in_priority
|
87
|
+
file_list = file_safety.sort_by {|k,v| v.nil? ? -100 : v["last_changed_rev"].to_i}.map(&:first)
|
88
|
+
else
|
89
|
+
file_list = file_safety.keys.sort
|
90
|
+
end
|
91
|
+
|
92
|
+
file_list.each{|f|
|
93
|
+
if print_file_safety(file_safety, trunk_repo, f, false, printed.size >= max_print)
|
94
|
+
printed << f
|
95
|
+
end
|
96
|
+
}
|
97
|
+
puts "... and #{printed.size - max_print} others" if printed.size > max_print
|
98
|
+
if show_diffs
|
99
|
+
puts
|
100
|
+
printed.each{|f|
|
101
|
+
print_file_diffs(file_safety, trunk_repo, f, user_name)
|
102
|
+
}
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Print summary details of a file's known safety
|
107
|
+
# If not verbose, only prints details for unsafe files
|
108
|
+
# Returns true if anything printed (or would have been printed if silent),
|
109
|
+
# or false otherwise.
|
110
|
+
def print_file_safety(file_safety, repo, fname, verbose = false, silent = false)
|
111
|
+
msg = "#{fname}\n "
|
112
|
+
entry = file_safety[fname]
|
113
|
+
msg += "File not in audit list" if entry.nil?
|
114
|
+
|
115
|
+
if entry["safe_revision"].nil?
|
116
|
+
msg += "No safe revision known"
|
117
|
+
msg += ", last changed #{entry['last_changed_rev']}" unless entry["last_changed_rev"].nil?
|
118
|
+
else
|
119
|
+
repolatest = entry["last_changed_rev"] # May have been prepopulated en mass
|
120
|
+
msg += 'Not in repository: ' if entry['last_changed_rev'] == -1
|
121
|
+
if (repolatest != entry['safe_revision']) &&
|
122
|
+
!(repolatest =~ /^[0-9]+$/ && entry['safe_revision'] =~ /^[0-9]+$/ &&
|
123
|
+
repolatest.to_i < entry['safe_revision'].to_i)
|
124
|
+
# (Allow later revisions to be treated as safe for svn)
|
125
|
+
msg += "No longer safe since revision #{repolatest}: "
|
126
|
+
else
|
127
|
+
return false unless verbose
|
128
|
+
msg += "Safe: "
|
129
|
+
end
|
130
|
+
msg += "revision #{entry['safe_revision']} reviewed by #{entry['reviewed_by']}"
|
131
|
+
end
|
132
|
+
msg += "\n Comments: #{entry['comments']}" if entry['comments']
|
133
|
+
puts msg unless silent
|
134
|
+
return true
|
135
|
+
end
|
136
|
+
|
137
|
+
def flag_file_as_safe(release, reviewed_by, comments, f)
|
138
|
+
safety_cfg = YAML.load_file(SAFETY_FILE)
|
139
|
+
file_safety = safety_cfg["file safety"]
|
140
|
+
|
141
|
+
unless File.exist?(f)
|
142
|
+
abort("Error: Unable to flag non-existent file as safe: #{f}")
|
143
|
+
end
|
144
|
+
unless file_safety.has_key?(f)
|
145
|
+
file_safety[f] = {
|
146
|
+
"comments" => nil,
|
147
|
+
"reviewed_by" => :dummy, # dummy value, will be overwritten
|
148
|
+
"safe_revision" => nil }
|
149
|
+
end
|
150
|
+
entry = file_safety[f]
|
151
|
+
entry_orig = entry.dup
|
152
|
+
if comments.to_s.length > 0 && entry["comments"] != comments
|
153
|
+
entry["comments"] = if entry["comments"].to_s.empty?
|
154
|
+
comments
|
155
|
+
else
|
156
|
+
"#{entry["comments"]}#{'.' unless entry["comments"].end_with?('.')} Revision #{release}: #{comments}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
if entry["safe_revision"]
|
160
|
+
unless release
|
161
|
+
abort("Error: File already has safe revision #{entry["safe_revision"]}: #{f}")
|
162
|
+
end
|
163
|
+
if release.is_a?(Integer) && release < entry["safe_revision"]
|
164
|
+
puts("Warning: Rolling back safe revision from #{entry['safe_revision']} to #{release} for #{f}")
|
165
|
+
end
|
166
|
+
end
|
167
|
+
entry["safe_revision"] = release
|
168
|
+
entry["reviewed_by"] = reviewed_by
|
169
|
+
if entry == entry_orig
|
170
|
+
puts "No changes when updating safe_revision to #{release || '[none]'} for #{f}"
|
171
|
+
else
|
172
|
+
File.open(SAFETY_FILE, 'w') do |file|
|
173
|
+
# Consistent file diffs, as ruby preserves Hash insertion order since v1.9
|
174
|
+
safety_cfg["file safety"] = Hash[file_safety.sort]
|
175
|
+
YAML.dump(safety_cfg, file) # Save changes before checking latest revisions
|
176
|
+
end
|
177
|
+
puts "Updated safe_revision to #{release || '[none]'} for #{f}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Determine the type of repository
|
182
|
+
def repository_type
|
183
|
+
return 'svn' if Dir.exists?('.svn')
|
184
|
+
return 'git-svn' if Dir.exists?('.git') && open('.git/config').grep(/svn/).any?
|
185
|
+
return 'git' if Dir.exists?('.git') && open('.git/config').grep(/git/).any?
|
186
|
+
return 'not known'
|
187
|
+
end
|
188
|
+
|
189
|
+
def get_trunk_repo
|
190
|
+
case repository_type
|
191
|
+
when 'svn'
|
192
|
+
repo_info = %x[svn info]
|
193
|
+
puts 'svn case'
|
194
|
+
return repo_info.split("\n").select{|x| x=~ /^URL: /}.collect{|x| x[5..-1]}.first
|
195
|
+
when 'git-svn'
|
196
|
+
puts 'git-svn case'
|
197
|
+
repo_info = %x[git svn info]
|
198
|
+
return repo_info.split("\n").select{|x| x =~ /^URL: /}.collect{|x| x[5..-1]}.first
|
199
|
+
when 'git'
|
200
|
+
puts 'git case'
|
201
|
+
repo_info = %x[git remote -v]
|
202
|
+
return repo_info.split("\n").first[7..-9]
|
203
|
+
else
|
204
|
+
return 'Information not available. Unknown repository type'
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def get_new_files(safety_repo)
|
209
|
+
case repository_type
|
210
|
+
when 'svn', 'git-svn'
|
211
|
+
%x[svn ls -R "#{safety_repo}"].split("\n")
|
212
|
+
when 'git'
|
213
|
+
#%x[git ls-files --modified].split("\n")
|
214
|
+
%x[git ls-files].split("\n")
|
215
|
+
|
216
|
+
# TODO: Below is for remote repository - for testing use local files
|
217
|
+
#new_files = %x[git ls-files --modified #{safety_repo}].split("\n")
|
218
|
+
# TODO: Do we need the --modified option?
|
219
|
+
#new_files = %x[git ls-files --modified].split("\n")
|
220
|
+
else
|
221
|
+
[]
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Fill in the latest changed revisions in a file safety map.
|
226
|
+
# (Don't write this data to the YAML file, as it is intrinsic to the SVN
|
227
|
+
# repository.)
|
228
|
+
def set_last_changed_revision(repo, file_safety, fnames)
|
229
|
+
dot_freq = (file_safety.size / 40.0).ceil # Print up to 40 progress dots
|
230
|
+
case repository_type
|
231
|
+
when 'git'
|
232
|
+
fnames = file_safety.keys if fnames.nil?
|
233
|
+
|
234
|
+
fnames.each_with_index do |f, i|
|
235
|
+
info = %x[git log -n 1 #{f}].split("\n").first[7..-1]
|
236
|
+
if info.nil? || info.empty?
|
237
|
+
file_safety[f]["last_changed_rev"] = -1
|
238
|
+
else
|
239
|
+
file_safety[f]["last_changed_rev"] = info
|
240
|
+
end
|
241
|
+
# Show progress
|
242
|
+
print '.' if (i % dot_freq) == 0
|
243
|
+
end
|
244
|
+
puts
|
245
|
+
when 'git-svn', 'svn'
|
246
|
+
fnames = file_safety.keys if fnames.nil?
|
247
|
+
|
248
|
+
fnames.each_with_index do |f, i|
|
249
|
+
last_revision = get_last_changed_revision(repo, f)
|
250
|
+
if last_revision.nil? || last_revision.empty?
|
251
|
+
file_safety[f]['last_changed_rev'] = -1
|
252
|
+
else
|
253
|
+
file_safety[f]['last_changed_rev'] = last_revision
|
254
|
+
end
|
255
|
+
# Show progress
|
256
|
+
print '.' if (i % dot_freq) == 0
|
257
|
+
end
|
258
|
+
puts
|
259
|
+
# NOTE: Do we need the following for retries?
|
260
|
+
# if retries && result.size != fnames.size && fnames.size > 1
|
261
|
+
# # At least one invalid (deleted file --> subsequent arguments ignored)
|
262
|
+
# # Try each file individually
|
263
|
+
# # (It would probably be safe to continue from the extra_info.size argument)
|
264
|
+
# puts "Retrying (got #{result.size}, expected #{fnames.size})" if debug >= 2
|
265
|
+
# result = []
|
266
|
+
# fnames.each{ |f|
|
267
|
+
# result += svn_info_entries([f], repo, false, debug)
|
268
|
+
# }
|
269
|
+
# end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
# Return the last changed revision
|
274
|
+
def get_last_changed_revision(repo, fname)
|
275
|
+
case repository_type
|
276
|
+
when 'git'
|
277
|
+
%x[git log -n 1 "#{fname}"].split("\n").first[7..-1]
|
278
|
+
when 'git-svn', 'svn'
|
279
|
+
begin
|
280
|
+
svn_info = %x[svn info -r head "#{repo}/#{fname}"]
|
281
|
+
rescue
|
282
|
+
puts "we have an error in the svn info line"
|
283
|
+
end
|
284
|
+
begin
|
285
|
+
svn_info.match('Last Changed Rev: ([0-9]*)').to_a[1]
|
286
|
+
rescue
|
287
|
+
puts "We have an error in getting the revision"
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# Get mime type. Note that Git does not have this information
|
293
|
+
def get_mime_type(repo, fname)
|
294
|
+
case repository_type
|
295
|
+
when 'git'
|
296
|
+
'Git does not provide mime types'
|
297
|
+
when 'git-svn', 'svn'
|
298
|
+
%x[svn propget svn:mime-type "#{repo}/#{fname}"].chomp
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
# # Print file diffs, for code review
|
303
|
+
def print_file_diffs(file_safety, repo, fname, user_name)
|
304
|
+
entry = file_safety[fname]
|
305
|
+
repolatest = entry['last_changed_rev']
|
306
|
+
safe_revision = entry['safe_revision']
|
307
|
+
|
308
|
+
if safe_revision.nil?
|
309
|
+
first_revision = set_safe_revision
|
310
|
+
print_repo_file_diffs(repolatest, repo, fname, user_name, first_revision)
|
311
|
+
else
|
312
|
+
|
313
|
+
rev = get_last_changed_revision(repo, fname)
|
314
|
+
if rev
|
315
|
+
mime = get_mime_type(repo, fname)
|
316
|
+
end
|
317
|
+
|
318
|
+
print_repo_file_diffs(repolatest, repo, fname, user_name, safe_revision) if repolatest != safe_revision
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
# Returns first commit for git and 0 for svn in order to be used to display
|
323
|
+
# new files. Called from print_file_diffs
|
324
|
+
def set_safe_revision
|
325
|
+
case repository_type
|
326
|
+
when 'git'
|
327
|
+
%x[git rev-list --max-parents=0 HEAD].chomp
|
328
|
+
when 'git-svn', 'svn'
|
329
|
+
0
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
def print_repo_file_diffs(repolatest, repo, fname, user_name, safe_revision)
|
334
|
+
case repository_type
|
335
|
+
when 'git'
|
336
|
+
puts %[git --no-pager diff -b #{safe_revision}..#{repolatest} #{fname}]
|
337
|
+
puts %x[git --no-pager diff -b #{safe_revision}..#{repolatest} #{fname}]
|
338
|
+
when 'git-svn', 'svn'
|
339
|
+
puts %[svn diff -r #{safe_revision.to_i}:#{repolatest.to_i} -x -b #{repo}/#{fname}]
|
340
|
+
puts %x[svn diff -r #{safe_revision.to_i}:#{repolatest.to_i} -x -b #{repo}/#{fname}]
|
341
|
+
else
|
342
|
+
puts 'Unknown repo'
|
343
|
+
end
|
344
|
+
|
345
|
+
puts %(To flag the changes to this file as safe, run:)
|
346
|
+
puts %( rake audit:safe release=#{repolatest} file=#{fname} reviewed_by=#{user_name} comments="")
|
347
|
+
puts
|
348
|
+
end
|
349
|
+
|
350
|
+
def release_valid?
|
351
|
+
case repository_type
|
352
|
+
when 'svn', 'git-svn'
|
353
|
+
ENV['release'] =~ /\A[0-9][0-9]*\Z/
|
354
|
+
when 'git'
|
355
|
+
ENV['release'] =~ /\A[0-9a-f]{40}\Z/
|
356
|
+
else
|
357
|
+
false
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def get_release
|
362
|
+
release = ENV['release']
|
363
|
+
release = nil if release == '0'
|
364
|
+
case repository_type
|
365
|
+
when 'svn', 'git-svn'
|
366
|
+
release.to_i
|
367
|
+
when 'git'
|
368
|
+
release
|
369
|
+
else
|
370
|
+
''
|
371
|
+
end
|
372
|
+
return release
|
373
|
+
end
|
374
|
+
|
375
|
+
namespace :audit do
|
376
|
+
|
377
|
+
desc "Audit safety of source code.
|
378
|
+
Usage: audit:code [max_print=n] [ignore_new=false|true] [show_diffs=false|true] [reviewed_by=usr]
|
379
|
+
|
380
|
+
File #{SAFETY_FILE} lists the safety and revision information
|
381
|
+
of the era source code. This task updates the list, and [TODO] warns about
|
382
|
+
files which have changed since they were last verified as safe."
|
383
|
+
task(:code) do
|
384
|
+
puts "Usage: audit:code [max_print=n] [ignore_new=false|true] [show_diffs=false|true] [show_in_priority=false|true] [reviewed_by=usr]"
|
385
|
+
puts "This is a #{repository_type} repository"
|
386
|
+
|
387
|
+
ignore_new = (ENV['ignore_new'].to_s =~ /\Atrue\Z/i)
|
388
|
+
show_diffs = (ENV['show_diffs'].to_s =~ /\Atrue\Z/i)
|
389
|
+
show_in_priority = (ENV['show_in_priority'].to_s =~ /\Atrue\Z/i)
|
390
|
+
max_print = ENV['max_print'] =~ /\A-?[0-9][0-9]*\Z/ ? ENV['max_print'].to_i : 20
|
391
|
+
|
392
|
+
audit_code_safety(max_print, ignore_new, show_diffs, show_in_priority, ENV['reviewed_by'])
|
393
|
+
|
394
|
+
unless show_diffs
|
395
|
+
puts "To show file diffs, run: rake audit:code max_print=-1 show_diffs=true"
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
desc "Flag a source file as safe.
|
400
|
+
|
401
|
+
Usage:
|
402
|
+
Flag as safe: rake audit:safe release=revision reviewed_by=usr [comments=...] file=f
|
403
|
+
Needs review: rake audit:safe release=0 [comments=...] file=f"
|
404
|
+
task (:safe) do
|
405
|
+
required_fields = ["release", "file"]
|
406
|
+
required_fields << "reviewed_by" unless ENV['release'] == '0'
|
407
|
+
missing = required_fields.collect{|f| (f if ENV[f].to_s.empty? || (f=='reviewed_by' && ENV[f] == 'usr'))}.compact # Avoid accidental missing username
|
408
|
+
if !missing.empty?
|
409
|
+
puts "Usage: rake audit:safe release=revision reviewed_by=usr [comments=...] file=f"
|
410
|
+
puts "or, to flag a file for review: rake audit:safe release=0 [comments=...] file=f"
|
411
|
+
abort("Error: Missing required argument(s): #{missing.join(', ')}")
|
412
|
+
end
|
413
|
+
|
414
|
+
unless release_valid?
|
415
|
+
puts "Usage: rake audit:safe release=revision reviewed_by=usr [comments=...] file=f"
|
416
|
+
puts "or, to flag a file for review: rake audit:safe release=0 [comments=...] file=f"
|
417
|
+
abort("Error: Invalid release: #{ENV['release']}")
|
418
|
+
end
|
419
|
+
|
420
|
+
release = get_release
|
421
|
+
flag_file_as_safe(release, ENV['reviewed_by'], ENV['comments'], ENV['file'])
|
422
|
+
end
|
423
|
+
end
|