skillnad 1.2.2
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/LICENSE +20 -0
- data/README.md +172 -0
- data/Rakefile +17 -0
- data/lib/rubygems/commands/compare_command.rb +105 -0
- data/lib/rubygems/comparator/base.rb +45 -0
- data/lib/rubygems/comparator/dependency_comparator.rb +110 -0
- data/lib/rubygems/comparator/dir_utils.rb +51 -0
- data/lib/rubygems/comparator/file_list_comparator.rb +212 -0
- data/lib/rubygems/comparator/gemfile_comparator.rb +149 -0
- data/lib/rubygems/comparator/monitor.rb +140 -0
- data/lib/rubygems/comparator/report/entry.rb +40 -0
- data/lib/rubygems/comparator/report.rb +131 -0
- data/lib/rubygems/comparator/spec_comparator.rb +66 -0
- data/lib/rubygems/comparator/utils.rb +134 -0
- data/lib/rubygems/comparator/version.rb +5 -0
- data/lib/rubygems/comparator.rb +308 -0
- data/lib/rubygems_plugin.rb +4 -0
- data/test/gemfiles/lorem-0.0.1/Gemfile +4 -0
- data/test/gemfiles/lorem-0.0.1/LICENSE.txt +22 -0
- data/test/gemfiles/lorem-0.0.1/README.md +29 -0
- data/test/gemfiles/lorem-0.0.1/Rakefile +2 -0
- data/test/gemfiles/lorem-0.0.1/lib/lorem/version.rb +3 -0
- data/test/gemfiles/lorem-0.0.1/lib/lorem.rb +7 -0
- data/test/gemfiles/lorem-0.0.1/lorem.gemspec +24 -0
- data/test/gemfiles/lorem-0.0.1.gem +0 -0
- data/test/gemfiles/lorem-0.0.2/CHANGELOG.md +6 -0
- data/test/gemfiles/lorem-0.0.2/Gemfile +4 -0
- data/test/gemfiles/lorem-0.0.2/LICENSE.txt +3 -0
- data/test/gemfiles/lorem-0.0.2/README.md +29 -0
- data/test/gemfiles/lorem-0.0.2/Rakefile +2 -0
- data/test/gemfiles/lorem-0.0.2/lib/lorem/version.rb +3 -0
- data/test/gemfiles/lorem-0.0.2/lib/lorem.rb +11 -0
- data/test/gemfiles/lorem-0.0.2/lorem.gemspec +24 -0
- data/test/gemfiles/lorem-0.0.2.gem +0 -0
- data/test/gemfiles/lorem-0.0.3/CHANGELOG.md +14 -0
- data/test/gemfiles/lorem-0.0.3/Gemfile +7 -0
- data/test/gemfiles/lorem-0.0.3/LICENSE.txt +3 -0
- data/test/gemfiles/lorem-0.0.3/README.md +29 -0
- data/test/gemfiles/lorem-0.0.3/Rakefile +2 -0
- data/test/gemfiles/lorem-0.0.3/bin/lorem +3 -0
- data/test/gemfiles/lorem-0.0.3/lib/lorem/utils.rb +7 -0
- data/test/gemfiles/lorem-0.0.3/lib/lorem/version.rb +3 -0
- data/test/gemfiles/lorem-0.0.3/lib/lorem.rb +11 -0
- data/test/gemfiles/lorem-0.0.3/lorem.gemspec +25 -0
- data/test/gemfiles/lorem-0.0.3.gem +0 -0
- data/test/gemfiles/lorem-0.0.4/CHANGELOG.md +21 -0
- data/test/gemfiles/lorem-0.0.4/Gemfile +6 -0
- data/test/gemfiles/lorem-0.0.4/LICENSE.txt +3 -0
- data/test/gemfiles/lorem-0.0.4/README.md +29 -0
- data/test/gemfiles/lorem-0.0.4/Rakefile +2 -0
- data/test/gemfiles/lorem-0.0.4/bin/lorem +3 -0
- data/test/gemfiles/lorem-0.0.4/lib/lorem/utils.rb +7 -0
- data/test/gemfiles/lorem-0.0.4/lib/lorem/version.rb +3 -0
- data/test/gemfiles/lorem-0.0.4/lib/lorem.rb +11 -0
- data/test/gemfiles/lorem-0.0.4/lorem.gemspec +25 -0
- data/test/gemfiles/lorem-0.0.4.gem +0 -0
- data/test/rubygems/comparator/test_dependency_comparator.rb +32 -0
- data/test/rubygems/comparator/test_dir_utils.rb +47 -0
- data/test/rubygems/comparator/test_file_list_comparator.rb +29 -0
- data/test/rubygems/comparator/test_gemfile_comparator.rb +16 -0
- data/test/rubygems/comparator/test_monitor.rb +183 -0
- data/test/rubygems/comparator/test_report.rb +25 -0
- data/test/rubygems/comparator/test_spec_comparator.rb +150 -0
- data/test/rubygems/comparator/test_utils.rb +35 -0
- data/test/rubygems/test_gem_commands_compare_command.rb +41 -0
- data/test/test_helper.rb +53 -0
- metadata +165 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'diffy'
|
|
4
|
+
require 'rubygems/comparator/base'
|
|
5
|
+
require 'rubygems/comparator/dir_utils'
|
|
6
|
+
require 'rubygems/comparator/monitor'
|
|
7
|
+
|
|
8
|
+
class Gem::Comparator
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
# Gem::Comparator::FileListComparator can
|
|
12
|
+
# compare file lists from gem's specs
|
|
13
|
+
# based on the given Gem::Package objects
|
|
14
|
+
#
|
|
15
|
+
# To compare the files it needs to extract
|
|
16
|
+
# gem packages to +options[:output]+
|
|
17
|
+
|
|
18
|
+
class FileListComparator < Gem::Comparator::Base
|
|
19
|
+
|
|
20
|
+
def initialize(ignore_group_writable=true)
|
|
21
|
+
expect(:packages)
|
|
22
|
+
|
|
23
|
+
# We need diff
|
|
24
|
+
begin
|
|
25
|
+
IO.popen('diff --version')
|
|
26
|
+
rescue Exception
|
|
27
|
+
error('Calling `diff` command failed. Do you have it installed?')
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@ignore_group_writable = ignore_group_writable
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# Compare file lists for gem's Gem::Package objects
|
|
35
|
+
# in +packages+ and writes the changes to the +report+
|
|
36
|
+
#
|
|
37
|
+
# If +options[:param]+ is set, it compares only
|
|
38
|
+
# that file list
|
|
39
|
+
|
|
40
|
+
def compare(packages, report, options = {})
|
|
41
|
+
info 'Checking file lists...'
|
|
42
|
+
|
|
43
|
+
@packages = packages
|
|
44
|
+
|
|
45
|
+
# Check file lists from older versions to newer
|
|
46
|
+
filter_params(SPEC_FILES_PARAMS, options[:param]).each do |param|
|
|
47
|
+
all_same = true
|
|
48
|
+
|
|
49
|
+
packages.each_with_index do |pkg, index|
|
|
50
|
+
unpacked_gem_dirs[packages[index].spec.version] = extract_gem(pkg, options[:output])
|
|
51
|
+
next if index == 0
|
|
52
|
+
|
|
53
|
+
# File lists as arrays
|
|
54
|
+
previous = value_from_spec(param, packages[index-1].spec)
|
|
55
|
+
current = value_from_spec(param, pkg.spec)
|
|
56
|
+
next unless (previous && current)
|
|
57
|
+
|
|
58
|
+
vers = "#{packages[index-1].spec.version}->#{packages[index].spec.version}"
|
|
59
|
+
|
|
60
|
+
deleted = previous - current
|
|
61
|
+
added = current - previous
|
|
62
|
+
same = current - added
|
|
63
|
+
|
|
64
|
+
if options[:brief]
|
|
65
|
+
deleted, dirs_added = dir_changed(previous, current)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
report[param].set_header "#{different} #{param}:"
|
|
69
|
+
|
|
70
|
+
report[param][vers].section do
|
|
71
|
+
set_header "#{Rainbow(packages[index-1].spec.version).cyan}->" +
|
|
72
|
+
"#{Rainbow(packages[index].spec.version).cyan}:"
|
|
73
|
+
nest('deleted').section do
|
|
74
|
+
set_header '* Deleted:'
|
|
75
|
+
puts deleted unless deleted.empty?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
nest('added').section do
|
|
79
|
+
set_header '* Added:'
|
|
80
|
+
puts dirs_added if options[:brief]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
# Add information about permissions, shebangs etc.
|
|
84
|
+
report = check_added_files(param, vers, index, added, report, options[:brief], options[:diff])
|
|
85
|
+
|
|
86
|
+
report[param][vers]['changed'].set_header '* Changed:'
|
|
87
|
+
report = check_same_files(param, vers, index, same, report, options[:brief], options[:diff])
|
|
88
|
+
same_files = report[param][vers]['changed'].messages.empty?
|
|
89
|
+
all_same = false unless same_files
|
|
90
|
+
|
|
91
|
+
if previous == current && same_files && !all_same
|
|
92
|
+
report[param][vers] << "#{Rainbow(packages[index-1].spec.version).cyan}->" + \
|
|
93
|
+
"#{Rainbow(packages[index].spec.version).cyan}: No change"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
if all_same && options[:log_all]
|
|
99
|
+
report[param].set_header "#{same} #{param}:"
|
|
100
|
+
value = value_from_spec(param, @packages[0].spec)
|
|
101
|
+
value = '[]' if value.empty?
|
|
102
|
+
report[param] << value
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
report
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
##
|
|
111
|
+
# Access @unpacked_gem_dirs hash that stores
|
|
112
|
+
# paths to the unpacked gem dirs
|
|
113
|
+
#
|
|
114
|
+
# Keys of the hash are gem's versions
|
|
115
|
+
|
|
116
|
+
def unpacked_gem_dirs
|
|
117
|
+
@unpacked_gem_dirs ||= {}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
##
|
|
121
|
+
# This returns [deleted, added] directories between
|
|
122
|
+
# +previous+ and +current+ file lists
|
|
123
|
+
#
|
|
124
|
+
# For top level (.) it compares files themselves
|
|
125
|
+
|
|
126
|
+
def dir_changed(previous, current)
|
|
127
|
+
prev_dirs = DirUtils.dirs_of_files(previous)
|
|
128
|
+
curr_dirs = DirUtils.dirs_of_files(current)
|
|
129
|
+
deleted = DirUtils.remove_subdirs(prev_dirs - curr_dirs)
|
|
130
|
+
added = DirUtils.remove_subdirs(curr_dirs - prev_dirs)
|
|
131
|
+
[deleted, added]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def check_added_files(param, vers, index, files, report, brief_mode, diff_mode)
|
|
135
|
+
files.each do |file|
|
|
136
|
+
added_file = File.join(unpacked_gem_dirs[@packages[index].spec.version], file)
|
|
137
|
+
|
|
138
|
+
line_changes = if diff_mode
|
|
139
|
+
Monitor.files_diff(nil, added_file)
|
|
140
|
+
else
|
|
141
|
+
Monitor.lines_changed(nil, added_file)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
changes = Monitor.new_file_permissions(added_file, @ignore_group_writable),
|
|
145
|
+
Monitor.new_file_executability(added_file),
|
|
146
|
+
Monitor.new_file_shebang(added_file)
|
|
147
|
+
|
|
148
|
+
if(!changes.join.empty? || (!brief_mode && !line_changes.empty?))
|
|
149
|
+
if diff_mode
|
|
150
|
+
report[param][vers]['added'][file].set_header file
|
|
151
|
+
report[param][vers]['added'][file] << line_changes.split("\n")
|
|
152
|
+
else
|
|
153
|
+
report[param][vers]['added'] << "#{file} #{line_changes}"
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
changes.each do |change|
|
|
158
|
+
report[param][vers]['added'] << change unless change.empty?
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
report
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def check_same_files(param, vers, index, files, report, brief_mode, diff_mode)
|
|
165
|
+
files.each do |file|
|
|
166
|
+
prev_file = File.join(unpacked_gem_dirs[@packages[index-1].spec.version], file)
|
|
167
|
+
curr_file = File.join(unpacked_gem_dirs[@packages[index].spec.version], file)
|
|
168
|
+
|
|
169
|
+
next unless check_files([prev_file, curr_file])
|
|
170
|
+
|
|
171
|
+
line_changes = if diff_mode
|
|
172
|
+
Monitor.files_diff(prev_file, curr_file)
|
|
173
|
+
else
|
|
174
|
+
Monitor.lines_changed(prev_file, curr_file)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
changes = Monitor.files_permissions_changes(prev_file, curr_file, @ignore_group_writable),
|
|
178
|
+
Monitor.files_executability_changes(prev_file, curr_file),
|
|
179
|
+
Monitor.files_shebang_changes(prev_file, curr_file)
|
|
180
|
+
|
|
181
|
+
if(!changes.join.empty? || (!brief_mode && !line_changes.empty?))
|
|
182
|
+
if diff_mode
|
|
183
|
+
report[param][vers]['changed'][file].set_header file
|
|
184
|
+
report[param][vers]['changed'][file] << line_changes.split("\n")
|
|
185
|
+
else
|
|
186
|
+
report[param][vers]['changed'] << "#{file} #{line_changes}"
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
changes.each do |change|
|
|
191
|
+
report[param][vers]['changed'] << change unless change.empty?
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
report
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
##
|
|
198
|
+
# Check that files exist
|
|
199
|
+
|
|
200
|
+
def check_files(files)
|
|
201
|
+
files.each do |file|
|
|
202
|
+
unless File.exist? file
|
|
203
|
+
warn "#{file} mentioned in spec does not exist " +
|
|
204
|
+
"in the gem package, skipping check"
|
|
205
|
+
return false
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
true
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
end
|
|
212
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'gemnasium/parser'
|
|
4
|
+
require 'rubygems/comparator/base'
|
|
5
|
+
|
|
6
|
+
class Gem::Comparator
|
|
7
|
+
|
|
8
|
+
##
|
|
9
|
+
# Gem::Comparator::GemfileComparator can
|
|
10
|
+
# compare dependencies between gem's Gemfiles
|
|
11
|
+
# based on the given Gem::Package objects
|
|
12
|
+
#
|
|
13
|
+
# To compare Gemfiles it needs to extract
|
|
14
|
+
# gem packages to +options[:output]+
|
|
15
|
+
|
|
16
|
+
class GemfileComparator < Gem::Comparator::Base
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
expect(:packages)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# Compare Gemfiles using gem's +packages+
|
|
24
|
+
# and write the changes to the +report+
|
|
25
|
+
|
|
26
|
+
def compare(packages, report, options = {})
|
|
27
|
+
info 'Checking Gemfiles for dependencies...'
|
|
28
|
+
return report if options[:param] && options[:param] != 'gemfiles'
|
|
29
|
+
|
|
30
|
+
@packages = packages
|
|
31
|
+
all_same = true
|
|
32
|
+
|
|
33
|
+
# Check Gemfiles from older versions to newer
|
|
34
|
+
packages.each_with_index do |pkg, index|
|
|
35
|
+
unpacked_gem_dirs[@packages[index].spec.version] = extract_gem(pkg, options[:output])
|
|
36
|
+
next if index == 0
|
|
37
|
+
|
|
38
|
+
prev_gemfile = File.join(unpacked_gem_dirs[@packages[index-1].spec.version], 'Gemfile')
|
|
39
|
+
curr_gemfile = File.join(unpacked_gem_dirs[@packages[index].spec.version], 'Gemfile')
|
|
40
|
+
|
|
41
|
+
vers = "#{@packages[index-1].spec.version}->#{@packages[index].spec.version}"
|
|
42
|
+
report['gemfiles'][vers].set_header "#{Rainbow(packages[index-1].spec.version).cyan}->" +
|
|
43
|
+
"#{Rainbow(packages[index].spec.version).cyan}:"
|
|
44
|
+
|
|
45
|
+
added, deleted, updated = compare_gemfiles(prev_gemfile, curr_gemfile)
|
|
46
|
+
|
|
47
|
+
report['gemfiles'][vers]['added'].section do
|
|
48
|
+
set_header '* Added:'
|
|
49
|
+
puts added.map { |x| "#{x.name} #{x.requirements_list} (#{x.type})" } unless added.empty?
|
|
50
|
+
end
|
|
51
|
+
report['gemfiles'][vers]['deleted'].section do
|
|
52
|
+
set_header '* Deleted'
|
|
53
|
+
puts deleted.map { |x| "#{x.name} #{x.requirements_list} (#{x.type})" } unless deleted.empty?
|
|
54
|
+
end
|
|
55
|
+
report['gemfiles'][vers]['updated'].section do
|
|
56
|
+
set_header '* Updated'
|
|
57
|
+
puts updated unless updated.empty?
|
|
58
|
+
end
|
|
59
|
+
all_same = false if !added.empty? || !deleted.empty?
|
|
60
|
+
end
|
|
61
|
+
if all_same && options[:log_all]
|
|
62
|
+
report['gemfiles'].set_header "#{same} Gemfiles:"
|
|
63
|
+
gemfile = File.join(unpacked_gem_dirs[@packages[1].spec.version], 'Gemfile')
|
|
64
|
+
if File.exist? gemfile
|
|
65
|
+
deps = gemfile_deps(gemfile)
|
|
66
|
+
deps = '[]' if deps.empty?
|
|
67
|
+
report['gemfiles'] << deps
|
|
68
|
+
else
|
|
69
|
+
report['gemfiles'] << 'No Gemfiles'
|
|
70
|
+
end
|
|
71
|
+
elsif !all_same
|
|
72
|
+
report['gemfiles'].set_header "#{different} Gemfile dependencies"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
report
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
# Access @unpacked_gem_dirs hash that stores
|
|
82
|
+
# paths to the unpacked gem dirs
|
|
83
|
+
#
|
|
84
|
+
# Keys of the hash are gem's versions
|
|
85
|
+
|
|
86
|
+
def unpacked_gem_dirs
|
|
87
|
+
@unpacked_gem_dirs ||= {}
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
##
|
|
91
|
+
# Compare two Gemfiles for dependencies
|
|
92
|
+
#
|
|
93
|
+
# Return [added, deleted, updated] deps
|
|
94
|
+
|
|
95
|
+
def compare_gemfiles(prev_gemfile, curr_gemfile)
|
|
96
|
+
prev_deps = gemfile_deps(prev_gemfile)
|
|
97
|
+
curr_deps = gemfile_deps(curr_gemfile)
|
|
98
|
+
added = curr_deps - prev_deps
|
|
99
|
+
deleted = prev_deps - curr_deps
|
|
100
|
+
|
|
101
|
+
split_dependencies(added, deleted)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
##
|
|
105
|
+
# Get the Gemfile dependencies from +gemfile+
|
|
106
|
+
|
|
107
|
+
def gemfile_deps(gemfile)
|
|
108
|
+
if File.exist?(gemfile)
|
|
109
|
+
parse_gemfile(gemfile).dependencies
|
|
110
|
+
else
|
|
111
|
+
[]
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
##
|
|
116
|
+
# Parse +gemfile+ using Gemnasium::Parser
|
|
117
|
+
#
|
|
118
|
+
# Return Gemnasium::Parser::Gemfile
|
|
119
|
+
|
|
120
|
+
def parse_gemfile(gemfile)
|
|
121
|
+
Gemnasium::Parser.gemfile File.open(gemfile).read
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
##
|
|
125
|
+
# Find updated dependencies between +added+ and
|
|
126
|
+
# +deleted+ deps and move them out to +updated+.
|
|
127
|
+
#
|
|
128
|
+
# Return [added, deleted, updated] deps
|
|
129
|
+
|
|
130
|
+
def split_dependencies(added, deleted)
|
|
131
|
+
updated = []
|
|
132
|
+
added.dup.each do |ad|
|
|
133
|
+
deleted.dup.each do |dd|
|
|
134
|
+
if ad.name == dd.name && ad.type == dd.type
|
|
135
|
+
unless ad.requirements_list == dd.requirements_list
|
|
136
|
+
updated << "#{ad.name} " +
|
|
137
|
+
"from: #{dd.requirements_list} " +
|
|
138
|
+
"to: #{ad.requirements_list}"
|
|
139
|
+
end
|
|
140
|
+
added.delete ad
|
|
141
|
+
deleted.delete dd
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
[added, deleted, updated]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'diffy'
|
|
4
|
+
require 'rubygems/comparator/base'
|
|
5
|
+
require 'rubygems/comparator/dir_utils'
|
|
6
|
+
|
|
7
|
+
class Gem::Comparator
|
|
8
|
+
module Monitor
|
|
9
|
+
|
|
10
|
+
def self.lines_changed(prev_file, curr_file)
|
|
11
|
+
line = compact_files_diff(prev_file, curr_file)
|
|
12
|
+
return '' if line.empty?
|
|
13
|
+
plus = "+#{line.count('+')}"
|
|
14
|
+
minus = "-#{line.count('-')}"
|
|
15
|
+
"#{Rainbow(plus).green}/#{Rainbow(minus).red}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.compact_files_diff(prev_file, curr_file)
|
|
19
|
+
prev_file = prev_file.nil? ? Tempfile.new.path : prev_file
|
|
20
|
+
changes = +''
|
|
21
|
+
Diffy::Diff.new(
|
|
22
|
+
prev_file, curr_file, :source => 'files', :context => 0
|
|
23
|
+
).each do |line|
|
|
24
|
+
case line
|
|
25
|
+
when /^\+/ then changes << Rainbow('+').green
|
|
26
|
+
when /^-/ then changes << Rainbow('-').red
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
changes
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.files_diff(prev_file, curr_file)
|
|
33
|
+
prev_file = prev_file.nil? ? Tempfile.new.path : prev_file
|
|
34
|
+
changes = +''
|
|
35
|
+
Diffy::Diff.new(
|
|
36
|
+
prev_file, curr_file, :source => 'files', :context => 0, :include_diff_info => true
|
|
37
|
+
).each do |line|
|
|
38
|
+
case line
|
|
39
|
+
when /^\+/ then changes << Rainbow(line).green
|
|
40
|
+
when /^-/ then changes << Rainbow(line).red
|
|
41
|
+
else changes << line
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
changes
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.files_permissions_changes(prev_file, curr_file, ignore_group_writable=false)
|
|
48
|
+
prev_permissions = File.stat(prev_file).mode
|
|
49
|
+
curr_permissions = File.stat(curr_file).mode
|
|
50
|
+
|
|
51
|
+
diff = prev_permissions ^ curr_permissions
|
|
52
|
+
diff ^= 020 if diff != 0 && ignore_group_writable
|
|
53
|
+
|
|
54
|
+
if diff != 0
|
|
55
|
+
" (!) New permissions: " +
|
|
56
|
+
"#{format_permissions(prev_permissions)} -> #{format_permissions(curr_permissions)}"
|
|
57
|
+
else
|
|
58
|
+
''
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.new_file_permissions(file, ignore_group_writable=false)
|
|
63
|
+
file_permissions = File.stat(file).mode
|
|
64
|
+
|
|
65
|
+
expected_permissions = if DirUtils.gem_bin_file?(file)
|
|
66
|
+
0100755
|
|
67
|
+
else
|
|
68
|
+
0100644
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
permissions_to_compare = if ignore_group_writable && file_permissions != expected_permissions
|
|
72
|
+
file_permissions ^ 020
|
|
73
|
+
else
|
|
74
|
+
file_permissions
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
unless permissions_to_compare == expected_permissions
|
|
78
|
+
return " (!) Unexpected permissions: #{format_permissions(file_permissions)}"
|
|
79
|
+
end
|
|
80
|
+
''
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def self.files_executability_changes(prev_file, curr_file)
|
|
84
|
+
prev_executable = File.stat(prev_file).executable?
|
|
85
|
+
curr_executable = File.stat(curr_file).executable?
|
|
86
|
+
|
|
87
|
+
if !prev_executable && curr_executable
|
|
88
|
+
" (!) File is now executable!"
|
|
89
|
+
elsif prev_executable && !curr_executable
|
|
90
|
+
" (!) File is no longer executable!"
|
|
91
|
+
else
|
|
92
|
+
''
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.new_file_executability(file)
|
|
97
|
+
file_executable = File.stat(file).executable?
|
|
98
|
+
|
|
99
|
+
if file_executable && !DirUtils.gem_bin_file?(file)
|
|
100
|
+
" (!) File is executable"
|
|
101
|
+
elsif !file_executable && DirUtils.gem_bin_file?(file)
|
|
102
|
+
" (!) File is not executable"
|
|
103
|
+
else
|
|
104
|
+
''
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def self.files_shebang_changes(prev_file, curr_file)
|
|
109
|
+
return '' if DirUtils.files_same_first_line?(prev_file, curr_file)
|
|
110
|
+
|
|
111
|
+
prev_has_shebang = DirUtils.file_has_shebang? prev_file
|
|
112
|
+
curr_has_shebang = DirUtils.file_has_shebang? curr_file
|
|
113
|
+
|
|
114
|
+
if prev_has_shebang && !curr_has_shebang
|
|
115
|
+
" (!) Shebang probably lost: #{DirUtils.file_first_line(prev_file)}"
|
|
116
|
+
elsif !prev_has_shebang && curr_has_shebang
|
|
117
|
+
" (!) Shebang probably added: #{DirUtils.file_first_line(curr_file)}"
|
|
118
|
+
elsif prev_has_shebang && curr_has_shebang
|
|
119
|
+
" (!) Shebang probably changed: " +
|
|
120
|
+
"#{first_lines[prev_file]} -> #{DirUtils.file_first_line(curr_file)}"
|
|
121
|
+
else
|
|
122
|
+
''
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def self.new_file_shebang(file)
|
|
127
|
+
file_has_shebang = DirUtils.file_has_shebang? file
|
|
128
|
+
|
|
129
|
+
if file_has_shebang
|
|
130
|
+
" (!) Shebang found: #{DirUtils.file_first_line(file)}"
|
|
131
|
+
else
|
|
132
|
+
''
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def self.format_permissions(permissions)
|
|
137
|
+
sprintf("%o", permissions)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Gem::Comparator
|
|
4
|
+
class Report
|
|
5
|
+
class Entry
|
|
6
|
+
include Gem::UserInteraction
|
|
7
|
+
|
|
8
|
+
attr_accessor :data, :indent
|
|
9
|
+
|
|
10
|
+
def initialize(data = '', indent = '')
|
|
11
|
+
@data = data
|
|
12
|
+
@indent = indent
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def set_indent!(indent)
|
|
16
|
+
@indent = indent
|
|
17
|
+
self
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def empty?
|
|
21
|
+
case @data
|
|
22
|
+
when String, Array
|
|
23
|
+
@data.empty?
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def print
|
|
28
|
+
printed = case @data
|
|
29
|
+
when String
|
|
30
|
+
"#{@indent}#{@data}"
|
|
31
|
+
when Array
|
|
32
|
+
@indent + @data.join("\n#{@indent}")
|
|
33
|
+
else
|
|
34
|
+
"#{@indent}#{@data}"
|
|
35
|
+
end
|
|
36
|
+
say printed
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rubygems/comparator/report/entry'
|
|
4
|
+
|
|
5
|
+
class Gem::Comparator
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# Gem::Comparator::Report can nest sections and print only those that
|
|
9
|
+
# contain some messages.
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# report = Gem::Comparator::Report.new
|
|
13
|
+
# report['section1'] << "Message 1"
|
|
14
|
+
# report['section1']['subsection1'].set_header 'Message 2 Header'
|
|
15
|
+
# report['section1']['subsection1'] << "Message 2" if false
|
|
16
|
+
# report['section1'].section
|
|
17
|
+
# nest('subsection2').section
|
|
18
|
+
# puts "Message 3"
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
# report.print
|
|
22
|
+
#
|
|
23
|
+
# This won't print Message 2 nor its header saving a lot of if/else.
|
|
24
|
+
|
|
25
|
+
class Report
|
|
26
|
+
|
|
27
|
+
module Signs
|
|
28
|
+
def same
|
|
29
|
+
Rainbow('SAME').green.bright
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def different
|
|
33
|
+
Rainbow('DIFFERENT').red.bright
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.new(name = 'main')
|
|
38
|
+
Gem::Comparator::Report::NestedSection.new(name)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class NestedSection
|
|
42
|
+
include Report::Signs
|
|
43
|
+
include Gem::UserInteraction
|
|
44
|
+
|
|
45
|
+
DEFAULT_INDENT = ' '
|
|
46
|
+
|
|
47
|
+
attr_reader :header, :messages, :parent_section, :level
|
|
48
|
+
attr_accessor :name, :sections
|
|
49
|
+
|
|
50
|
+
def initialize(name, parent_section = nil)
|
|
51
|
+
@name = name
|
|
52
|
+
@header = Entry.new
|
|
53
|
+
@messages = []
|
|
54
|
+
@sections = []
|
|
55
|
+
@level = 0
|
|
56
|
+
|
|
57
|
+
set_parent parent_section if parent_section
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def section(&block)
|
|
61
|
+
instance_eval(&block)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def set_header(message)
|
|
65
|
+
@header = Entry.new(message)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def puts(message)
|
|
69
|
+
case message
|
|
70
|
+
when String, Array
|
|
71
|
+
@messages << Entry.new(message) unless message.empty?
|
|
72
|
+
else
|
|
73
|
+
@messages << Entry.new(message) unless message
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
alias_method :<<, :puts
|
|
77
|
+
|
|
78
|
+
def nest(name)
|
|
79
|
+
@sections.each do |s|
|
|
80
|
+
return s if s.name == name
|
|
81
|
+
end
|
|
82
|
+
NestedSection.new(name, self)
|
|
83
|
+
end
|
|
84
|
+
alias_method :[], :nest
|
|
85
|
+
|
|
86
|
+
def print
|
|
87
|
+
all_messages.each { |m| m.print }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def all_messages
|
|
91
|
+
indent = DEFAULT_INDENT*@level
|
|
92
|
+
|
|
93
|
+
if @header.empty?
|
|
94
|
+
@messages.map do |m|
|
|
95
|
+
m.set_indent!(indent)
|
|
96
|
+
end + nested_messages
|
|
97
|
+
else
|
|
98
|
+
nested = @messages.map do |m|
|
|
99
|
+
m.set_indent!(indent * 2)
|
|
100
|
+
end + nested_messages
|
|
101
|
+
return [] if nested.empty?
|
|
102
|
+
|
|
103
|
+
@header.set_indent!(indent)
|
|
104
|
+
nested.unshift(@header)
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def lines(line_num)
|
|
109
|
+
all_messages[line_num]&.data
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def nested_messages
|
|
113
|
+
nested_messages = []
|
|
114
|
+
@sections.each do |section|
|
|
115
|
+
section.all_messages.each do |m|
|
|
116
|
+
nested_messages << m
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
nested_messages
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def set_parent(parent)
|
|
125
|
+
parent.sections << self
|
|
126
|
+
@level = parent.level + 1
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|