gem-compare 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +9 -0
- data/Rakefile +3 -1
- data/lib/rubygems/commands/compare_command.rb +7 -1
- data/lib/rubygems/comparator.rb +76 -13
- data/lib/rubygems/comparator/base.rb +17 -62
- data/lib/rubygems/comparator/dependency_comparator.rb +9 -12
- data/lib/rubygems/comparator/dir_utils.rb +51 -0
- data/lib/rubygems/comparator/file_list_comparator.rb +83 -154
- data/lib/rubygems/comparator/gemfile_comparator.rb +28 -31
- data/lib/rubygems/comparator/monitor.rb +106 -0
- data/lib/rubygems/comparator/report.rb +48 -39
- data/lib/rubygems/comparator/report/entry.rb +38 -0
- data/lib/rubygems/comparator/spec_comparator.rb +10 -23
- data/lib/rubygems/comparator/utils.rb +133 -0
- data/test/rubygems/comparator/test_dependency_comparator.rb +2 -1
- data/test/rubygems/comparator/test_dir_utils.rb +52 -0
- data/test/rubygems/comparator/test_file_list_comparator.rb +5 -2
- data/test/rubygems/comparator/test_monitor.rb +53 -0
- data/test/rubygems/comparator/test_report.rb +23 -0
- data/test/rubygems/comparator/test_utils.rb +33 -0
- data/test/test_helper.rb +12 -1
- metadata +38 -2
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'diffy'
|
2
2
|
require 'rubygems/comparator/base'
|
3
|
+
require 'rubygems/comparator/dir_utils'
|
4
|
+
require 'rubygems/comparator/monitor'
|
3
5
|
|
4
6
|
class Gem::Comparator
|
5
7
|
|
@@ -11,10 +13,18 @@ class Gem::Comparator
|
|
11
13
|
# To compare the files it needs to extract
|
12
14
|
# gem packages to +options[:output]+
|
13
15
|
|
14
|
-
class FileListComparator
|
15
|
-
include Gem::Comparator::Base
|
16
|
+
class FileListComparator < Gem::Comparator::Base
|
16
17
|
|
17
|
-
|
18
|
+
def initialize
|
19
|
+
expect(:packages)
|
20
|
+
|
21
|
+
# We need diff
|
22
|
+
begin
|
23
|
+
IO.popen('diff --version')
|
24
|
+
rescue Exception
|
25
|
+
error('Calling `diff` command failed. Do you have it installed?')
|
26
|
+
end
|
27
|
+
end
|
18
28
|
|
19
29
|
##
|
20
30
|
# Compare file lists for gem's Gem::Package objects
|
@@ -25,7 +35,6 @@ class Gem::Comparator
|
|
25
35
|
|
26
36
|
def compare(packages, report, options = {})
|
27
37
|
info 'Checking file lists...'
|
28
|
-
check_diff_command_is_installed
|
29
38
|
|
30
39
|
@packages = packages
|
31
40
|
|
@@ -44,38 +53,42 @@ class Gem::Comparator
|
|
44
53
|
|
45
54
|
vers = "#{packages[index-1].spec.version}->#{packages[index].spec.version}"
|
46
55
|
|
47
|
-
|
48
|
-
|
49
|
-
|
56
|
+
deleted = previous - current
|
57
|
+
added = current - previous
|
58
|
+
same = current - added
|
59
|
+
|
60
|
+
if options[:brief]
|
61
|
+
deleted, dirs_added = dir_changed(previous, current)
|
50
62
|
end
|
51
63
|
|
52
|
-
|
53
|
-
deleted = previous - current
|
54
|
-
added = current - previous
|
55
|
-
same = current - added
|
64
|
+
report[param].set_header "#{different} #{param}:"
|
56
65
|
|
57
|
-
|
58
|
-
|
59
|
-
|
66
|
+
report[param][vers].section do
|
67
|
+
set_header "#{Rainbow(packages[index-1].spec.version).blue}->" +
|
68
|
+
"#{Rainbow(packages[index].spec.version).blue}:"
|
69
|
+
nest('deleted').section do
|
70
|
+
set_header '* Deleted:'
|
71
|
+
puts deleted unless deleted.empty?
|
60
72
|
end
|
61
73
|
|
62
|
-
|
63
|
-
set_header
|
64
|
-
|
65
|
-
nest('deleted').section do
|
66
|
-
set_header '* Deleted:'
|
67
|
-
puts deleted unless deleted.empty?
|
68
|
-
end
|
69
|
-
|
70
|
-
nest('added').section do
|
71
|
-
set_header '* Added:'
|
72
|
-
puts added unless added.empty?
|
73
|
-
end
|
74
|
+
nest('added').section do
|
75
|
+
set_header '* Added:'
|
76
|
+
puts dirs_added if options[:brief]
|
74
77
|
end
|
78
|
+
end
|
79
|
+
# Add information about permissions, shebangs etc.
|
80
|
+
report = check_added_files(param, vers, index, added, report, options[:brief])
|
75
81
|
|
76
|
-
|
77
|
-
|
82
|
+
report[param][vers]['changed'].set_header '* Changed:'
|
83
|
+
report = check_same_files(param, vers, index, same, report, options[:brief])
|
84
|
+
same_files = report[param][vers]['changed'].messages.empty?
|
85
|
+
all_same = false unless same_files
|
86
|
+
|
87
|
+
if previous == current && same_files && !all_same
|
88
|
+
report[param][vers] << "#{Rainbow(packages[index-1].spec.version).blue}->" + \
|
89
|
+
"#{Rainbow(packages[index].spec.version).blue}: No change"
|
78
90
|
end
|
91
|
+
|
79
92
|
end
|
80
93
|
|
81
94
|
if all_same && options[:log_all]
|
@@ -93,27 +106,62 @@ class Gem::Comparator
|
|
93
106
|
##
|
94
107
|
# Access @unpacked_gem_dirs hash that stores
|
95
108
|
# paths to the unpacked gem dirs
|
96
|
-
#
|
109
|
+
#
|
97
110
|
# Keys of the hash are gem's versions
|
98
111
|
|
99
112
|
def unpacked_gem_dirs
|
100
113
|
@unpacked_gem_dirs ||= {}
|
101
114
|
end
|
102
115
|
|
103
|
-
|
116
|
+
##
|
117
|
+
# This returns [deleted, added] directories between
|
118
|
+
# +previous+ and +current+ file lists
|
119
|
+
#
|
120
|
+
# For top level (.) it compares files themselves
|
121
|
+
|
122
|
+
def dir_changed(previous, current)
|
123
|
+
prev_dirs = DirUtils.dirs_of_files(previous)
|
124
|
+
curr_dirs = DirUtils.dirs_of_files(current)
|
125
|
+
deleted = DirUtils.remove_subdirs(prev_dirs - curr_dirs)
|
126
|
+
added = DirUtils.remove_subdirs(curr_dirs - prev_dirs)
|
127
|
+
[deleted, added]
|
128
|
+
end
|
129
|
+
|
130
|
+
def check_added_files(param, vers, index, files, report, brief_mode)
|
131
|
+
files.each do |file|
|
132
|
+
added_file = File.join(unpacked_gem_dirs[@packages[index].spec.version], file)
|
133
|
+
|
134
|
+
#line_changes = lines_changed(prev_file, curr_file)
|
135
|
+
|
136
|
+
changes = Monitor.new_file_permissions(added_file),
|
137
|
+
Monitor.new_file_executability(added_file),
|
138
|
+
Monitor.new_file_shebang(added_file)
|
139
|
+
|
140
|
+
if(!changes.join.empty? || !brief_mode)
|
141
|
+
report[param][vers]['added'] << "#{file}"
|
142
|
+
end
|
143
|
+
|
144
|
+
changes.each do |change|
|
145
|
+
report[param][vers]['added'] << change unless change.empty?
|
146
|
+
end
|
147
|
+
end
|
148
|
+
report
|
149
|
+
end
|
150
|
+
|
151
|
+
def check_same_files(param, vers, index, files, report, brief_mode)
|
104
152
|
files.each do |file|
|
105
153
|
prev_file = File.join(unpacked_gem_dirs[@packages[index-1].spec.version], file)
|
106
154
|
curr_file = File.join(unpacked_gem_dirs[@packages[index].spec.version], file)
|
107
155
|
|
108
156
|
next unless check_files([prev_file, curr_file])
|
109
157
|
|
110
|
-
line_changes = lines_changed(prev_file, curr_file)
|
158
|
+
line_changes = Monitor.lines_changed(prev_file, curr_file)
|
111
159
|
|
112
|
-
changes =
|
113
|
-
|
114
|
-
|
160
|
+
changes = Monitor.files_permissions_changes(prev_file, curr_file),
|
161
|
+
Monitor.files_executability_changes(prev_file, curr_file),
|
162
|
+
Monitor.files_shebang_changes(prev_file, curr_file)
|
115
163
|
|
116
|
-
|
164
|
+
if(!changes.join.empty? || (!brief_mode && !line_changes.empty?))
|
117
165
|
report[param][vers]['changed'] << \
|
118
166
|
"#{file} #{line_changes}"
|
119
167
|
end
|
@@ -139,124 +187,5 @@ class Gem::Comparator
|
|
139
187
|
true
|
140
188
|
end
|
141
189
|
|
142
|
-
|
143
|
-
##
|
144
|
-
# Return how many lines differ between +prev_file+
|
145
|
-
# and +curr_file+ in format +ADDED/-DELETED
|
146
|
-
|
147
|
-
def lines_changed(prev_file, curr_file)
|
148
|
-
line = compact_files_diff(prev_file, curr_file)
|
149
|
-
return '' if line.empty?
|
150
|
-
"#{Rainbow(line.count('+')).green}/#{Rainbow(line.count('-')).red}"
|
151
|
-
end
|
152
|
-
|
153
|
-
##
|
154
|
-
# Return +value+ in the given +spec+
|
155
|
-
|
156
|
-
def value_from_spec(param, spec)
|
157
|
-
if spec.respond_to? :"#{param}"
|
158
|
-
spec.send(:"#{param}")
|
159
|
-
else
|
160
|
-
warn "#{spec.full_name} does not respond to " +
|
161
|
-
"#{param}, skipping check"
|
162
|
-
nil
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
##
|
167
|
-
# Return changes between files:
|
168
|
-
# + for line added
|
169
|
-
# - for line deleted
|
170
|
-
|
171
|
-
def compact_files_diff(prev_file, curr_file)
|
172
|
-
changes = ''
|
173
|
-
Diffy::Diff.new(
|
174
|
-
prev_file, curr_file, :source => 'files', :context => 0
|
175
|
-
).each do |line|
|
176
|
-
case line
|
177
|
-
when /^\+/ then changes << Rainbow('+').green
|
178
|
-
when /^-/ then changes << Rainbow('-').red
|
179
|
-
end
|
180
|
-
end
|
181
|
-
changes
|
182
|
-
end
|
183
|
-
|
184
|
-
##
|
185
|
-
# Get file's permission
|
186
|
-
|
187
|
-
def file_permissions(file)
|
188
|
-
sprintf("%o", File.stat(file).mode)
|
189
|
-
end
|
190
|
-
|
191
|
-
##
|
192
|
-
# Find and return permission changes between files
|
193
|
-
|
194
|
-
def permission_changed(prev_file, curr_file)
|
195
|
-
prev_permissions = file_permissions(prev_file)
|
196
|
-
curr_permissions = file_permissions(curr_file)
|
197
|
-
|
198
|
-
if prev_permissions != curr_permissions
|
199
|
-
" (!) New permissions: " +
|
200
|
-
"#{prev_permissions} -> #{curr_permissions}"
|
201
|
-
else
|
202
|
-
''
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
##
|
207
|
-
# Find if the file is now/or was executable
|
208
|
-
|
209
|
-
def executables_changed(prev_file, curr_file)
|
210
|
-
prev_executable = File.stat(prev_file).executable?
|
211
|
-
curr_executable = File.stat(curr_file).executable?
|
212
|
-
|
213
|
-
if !prev_executable && curr_executable
|
214
|
-
" (!) File is now executable!"
|
215
|
-
elsif prev_executable && !curr_executable
|
216
|
-
" (!) File is no longer executable!"
|
217
|
-
else
|
218
|
-
''
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
##
|
223
|
-
# Return the first line of the +file+
|
224
|
-
|
225
|
-
def first_line(file)
|
226
|
-
begin
|
227
|
-
File.open(file) { |f| f.readline }.gsub(/(.*)\n/, '\1')
|
228
|
-
rescue
|
229
|
-
info "#{file} is binary, skipping shebang check"
|
230
|
-
''
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
##
|
235
|
-
# Find if the shabang of the file has been changed
|
236
|
-
|
237
|
-
def shebangs_changed(prev_file, curr_file)
|
238
|
-
first_lines = {}
|
239
|
-
|
240
|
-
[prev_file, curr_file].each do |file|
|
241
|
-
first_lines[file] = first_line(file)
|
242
|
-
end
|
243
|
-
|
244
|
-
return '' if first_lines[prev_file] == first_lines[curr_file]
|
245
|
-
|
246
|
-
prev_has_shebang = (first_lines[prev_file] =~ SHEBANG_REGEX)
|
247
|
-
curr_has_shebang = (first_lines[curr_file] =~ SHEBANG_REGEX)
|
248
|
-
|
249
|
-
if prev_has_shebang && !curr_has_shebang
|
250
|
-
" (!) Shebang probably lost: #{first_lines[prev_file]}"
|
251
|
-
elsif !prev_has_shebang && curr_has_shebang
|
252
|
-
" (!) Shebang probably added: #{first_lines[curr_file]}"
|
253
|
-
elsif prev_has_shebang && curr_has_shebang
|
254
|
-
" (!) Shebang probably changed: " +
|
255
|
-
"#{first_lines[prev_file]} -> #{first_lines[curr_file]}"
|
256
|
-
else
|
257
|
-
''
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
190
|
end
|
262
191
|
end
|
@@ -11,10 +11,11 @@ class Gem::Comparator
|
|
11
11
|
# To compare Gemfiles it needs to extract
|
12
12
|
# gem packages to +options[:output]+
|
13
13
|
|
14
|
-
class GemfileComparator
|
15
|
-
include Gem::Comparator::Base
|
14
|
+
class GemfileComparator < Gem::Comparator::Base
|
16
15
|
|
17
|
-
|
16
|
+
def initialize
|
17
|
+
expect(:packages)
|
18
|
+
end
|
18
19
|
|
19
20
|
##
|
20
21
|
# Compare Gemfiles using gem's +packages+
|
@@ -39,29 +40,21 @@ class Gem::Comparator
|
|
39
40
|
report['gemfiles'][vers].set_header "#{Rainbow(packages[index-1].spec.version).blue}->" +
|
40
41
|
"#{Rainbow(packages[index].spec.version).blue}:"
|
41
42
|
|
42
|
-
|
43
|
-
added, deleted, updated = compare_gemfiles(prev_gemfile, curr_gemfile)
|
43
|
+
added, deleted, updated = compare_gemfiles(prev_gemfile, curr_gemfile)
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
end
|
49
|
-
report['gemfiles'][vers]['deleted'].section do
|
50
|
-
set_header '* Deleted'
|
51
|
-
puts deleted.map { |x| "#{x.name} #{x.requirements_list}" } unless deleted.empty?
|
52
|
-
end
|
53
|
-
report['gemfiles'][vers]['updated'].section do
|
54
|
-
set_header '* Updated'
|
55
|
-
puts updated unless updated.empty?
|
56
|
-
end
|
57
|
-
all_same = false if !added.empty? || !deleted.empty?
|
58
|
-
elsif File.exist?(prev_gemfile)
|
59
|
-
report['gemfiles'][vers] << "Gemfile removed"
|
60
|
-
all_same = false
|
61
|
-
elsif File.exist?(curr_gemfile)
|
62
|
-
report['gemfiles'][vers] << "Gemfile added"
|
63
|
-
all_same = false
|
45
|
+
report['gemfiles'][vers]['added'].section do
|
46
|
+
set_header '* Added:'
|
47
|
+
puts added.map { |x| "#{x.name} #{x.requirements_list}" } unless added.empty?
|
64
48
|
end
|
49
|
+
report['gemfiles'][vers]['deleted'].section do
|
50
|
+
set_header '* Deleted'
|
51
|
+
puts deleted.map { |x| "#{x.name} #{x.requirements_list}" } unless deleted.empty?
|
52
|
+
end
|
53
|
+
report['gemfiles'][vers]['updated'].section do
|
54
|
+
set_header '* Updated'
|
55
|
+
puts updated unless updated.empty?
|
56
|
+
end
|
57
|
+
all_same = false if !added.empty? || !deleted.empty?
|
65
58
|
end
|
66
59
|
if all_same && options[:log_all]
|
67
60
|
report['gemfiles'].set_header "#{same} Gemfiles:"
|
@@ -81,17 +74,17 @@ class Gem::Comparator
|
|
81
74
|
end
|
82
75
|
|
83
76
|
private
|
84
|
-
|
77
|
+
|
85
78
|
##
|
86
79
|
# Access @unpacked_gem_dirs hash that stores
|
87
80
|
# paths to the unpacked gem dirs
|
88
|
-
#
|
81
|
+
#
|
89
82
|
# Keys of the hash are gem's versions
|
90
83
|
|
91
84
|
def unpacked_gem_dirs
|
92
85
|
@unpacked_gem_dirs ||= {}
|
93
86
|
end
|
94
|
-
|
87
|
+
|
95
88
|
##
|
96
89
|
# Compare two Gemfiles for dependencies
|
97
90
|
#
|
@@ -105,14 +98,18 @@ class Gem::Comparator
|
|
105
98
|
|
106
99
|
split_dependencies(added, deleted)
|
107
100
|
end
|
108
|
-
|
101
|
+
|
109
102
|
##
|
110
103
|
# Get the Gemfile dependencies from +gemfile+
|
111
104
|
|
112
105
|
def gemfile_deps(gemfile)
|
113
|
-
|
106
|
+
if File.exist?(gemfile)
|
107
|
+
parse_gemfile(gemfile).dependencies
|
108
|
+
else
|
109
|
+
[]
|
110
|
+
end
|
114
111
|
end
|
115
|
-
|
112
|
+
|
116
113
|
##
|
117
114
|
# Parse +gemfile+ using Gemnasium::Parser
|
118
115
|
#
|
@@ -121,7 +118,7 @@ class Gem::Comparator
|
|
121
118
|
def parse_gemfile(gemfile)
|
122
119
|
Gemnasium::Parser.gemfile File.open(gemfile).read
|
123
120
|
end
|
124
|
-
|
121
|
+
|
125
122
|
##
|
126
123
|
# Find updated dependencies between +added+ and
|
127
124
|
# +deleted+ deps and move them out to +updated+.
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'diffy'
|
2
|
+
require 'rubygems/comparator/base'
|
3
|
+
require 'rubygems/comparator/dir_utils'
|
4
|
+
|
5
|
+
class Gem::Comparator
|
6
|
+
module Monitor
|
7
|
+
|
8
|
+
def self.lines_changed(prev_file, curr_file)
|
9
|
+
line = compact_files_diff(prev_file, curr_file)
|
10
|
+
return '' if line.empty?
|
11
|
+
plus = "+#{line.count('+')}"
|
12
|
+
minus = "-#{line.count('-')}"
|
13
|
+
"#{Rainbow(plus).green}/#{Rainbow(minus).red}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.compact_files_diff(prev_file, curr_file)
|
17
|
+
changes = ''
|
18
|
+
Diffy::Diff.new(
|
19
|
+
prev_file, curr_file, :source => 'files', :context => 0
|
20
|
+
).each do |line|
|
21
|
+
case line
|
22
|
+
when /^\+/ then changes << Rainbow('+').green
|
23
|
+
when /^-/ then changes << Rainbow('-').red
|
24
|
+
end
|
25
|
+
end
|
26
|
+
changes
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.files_permissions_changes(prev_file, curr_file)
|
30
|
+
prev_permissions = DirUtils.file_permissions(prev_file)
|
31
|
+
curr_permissions = DirUtils.file_permissions(curr_file)
|
32
|
+
|
33
|
+
if prev_permissions != curr_permissions
|
34
|
+
" (!) New permissions: " +
|
35
|
+
"#{prev_permissions} -> #{curr_permissions}"
|
36
|
+
else
|
37
|
+
''
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.new_file_permissions(file)
|
42
|
+
file_permissions = DirUtils.file_permissions(file)
|
43
|
+
|
44
|
+
if file_permissions != '100644'
|
45
|
+
unless (DirUtils.gem_bin_file?(file) && file_permissions == '100755')
|
46
|
+
" (!) Unexpected permissions: #{file_permissions}"
|
47
|
+
end
|
48
|
+
else
|
49
|
+
''
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.files_executability_changes(prev_file, curr_file)
|
54
|
+
prev_executable = File.stat(prev_file).executable?
|
55
|
+
curr_executable = File.stat(curr_file).executable?
|
56
|
+
|
57
|
+
if !prev_executable && curr_executable
|
58
|
+
" (!) File is now executable!"
|
59
|
+
elsif prev_executable && !curr_executable
|
60
|
+
" (!) File is no longer executable!"
|
61
|
+
else
|
62
|
+
''
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.new_file_executability(file)
|
67
|
+
file_executable = File.stat(file).executable?
|
68
|
+
|
69
|
+
if file_executable && !DirUtils.gem_bin_file?(file)
|
70
|
+
" (!) File is executable"
|
71
|
+
elsif !file_executable && DirUtils.gem_bin_file?(file)
|
72
|
+
" (!) File is not executable"
|
73
|
+
else
|
74
|
+
''
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.files_shebang_changes(prev_file, curr_file)
|
79
|
+
return '' if DirUtils.files_same_first_line?(prev_file, curr_file)
|
80
|
+
|
81
|
+
prev_has_shebang = DirUtils.file_has_shebang? prev_file
|
82
|
+
curr_has_shebang = DirUtils.file_has_shebang? curr_file
|
83
|
+
|
84
|
+
if prev_has_shebang && !curr_has_shebang
|
85
|
+
" (!) Shebang probably lost: #{DirUtils.file_first_line(prev_file)}"
|
86
|
+
elsif !prev_has_shebang && curr_has_shebang
|
87
|
+
" (!) Shebang probably added: #{DirUtils.file_first_line(curr_file)}"
|
88
|
+
elsif prev_has_shebang && curr_has_shebang
|
89
|
+
" (!) Shebang probably changed: " +
|
90
|
+
"#{first_lines[prev_file]} -> #{DirUtils.file_first_line(curr_file)}"
|
91
|
+
else
|
92
|
+
''
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.new_file_shebang(file)
|
97
|
+
file_has_shebang = DirUtils.file_has_shebang? file
|
98
|
+
|
99
|
+
if file_has_shebang
|
100
|
+
" (!) Shebang found: #{DirUtils.file_first_line(file)}"
|
101
|
+
else
|
102
|
+
''
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|