gem-compare 0.0.2 → 0.0.3
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/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
|