summaryrefslogtreecommitdiff
path: root/lib/chef/util
diff options
context:
space:
mode:
authorClaire McQuin <claire@opscode.com>2013-07-16 16:02:09 -0700
committerClaire McQuin <claire@opscode.com>2013-07-16 16:02:09 -0700
commitf07fc696f59128d02d5b34d95e1bc208ef622860 (patch)
tree2ab83e232c8a5333cd320cf8f9499da4df14d830 /lib/chef/util
parent33168dad90d29dd183d65902abdcce33303ffc7e (diff)
downloadchef-f07fc696f59128d02d5b34d95e1bc208ef622860.tar.gz
shell_out to diff => diff-lcs, cleaned up tests, updated gem versions
Diffstat (limited to 'lib/chef/util')
-rw-r--r--lib/chef/util/diff.rb82
1 files changed, 48 insertions, 34 deletions
diff --git a/lib/chef/util/diff.rb b/lib/chef/util/diff.rb
index 6f76a2fabd..7c50512c06 100644
--- a/lib/chef/util/diff.rb
+++ b/lib/chef/util/diff.rb
@@ -15,13 +15,9 @@
# limitations under the License.
#
-require 'chef/mixin/shell_out'
-
class Chef
class Util
class Diff
- include Chef::Mixin::ShellOut
-
# @todo: to_a, to_s, to_json, inspect defs, accessors for @diff and @error
# @todo: move coercion to UTF-8 into to_json
# @todo: replace shellout to diff -u with diff-lcs gem
@@ -78,48 +74,23 @@ class Chef
return "(new content is binary, diff output suppressed)" if is_binary?(new_file)
begin
- # -u: Unified diff format
- # LC_ALL: in ruby 1.9 we want to set nil which is a magic option to mixlib-shellout to
- # pass through the LC_ALL locale. in ruby 1.8 we force to 7-bit 'C' locale
- # (which is the mixlib-shellout default for all rubies all the time).
Chef::Log.debug("running: diff -u #{old_file} #{new_file}")
- locale = ( Object.const_defined? :Encoding ) ? nil : 'C'
- result = shell_out("diff -u #{old_file} #{new_file}", :env => {'LC_ALL' => locale})
-
+ diff_str = udiff(old_file, new_file)
+
rescue Exception => e
# Should *not* receive this, but in some circumstances it seems that
# an exception can be thrown even using shell_out instead of shell_out!
return "Could not determine diff. Error: #{e.message}"
end
- # diff will set a non-zero return code even when there's
- # valid stdout results, if it encounters something unexpected
- # So as long as we have output, we'll show it.
- #
- # Also on some platforms (Solaris) diff outputs a single line
- # when there are no differences found. Look for this line
- # before analyzing diff output.
- if !result.stdout.empty? && result.stdout != "No differences encountered\n"
- if result.stdout.length > diff_output_threshold
+ if !diff_str.empty? && diff_str != "No differences encountered\n"
+ if diff_str.length > diff_output_threshold
return "(long diff of over #{diff_output_threshold} characters, diff output suppressed)"
else
- diff_str = result.stdout
- if Object.const_defined? :Encoding # ruby >= 1.9
- if ( diff_str.encoding == Encoding::ASCII_8BIT &&
- diff_str.encoding != Encoding.default_external &&
- RUBY_VERSION.to_f < 2.0 )
- # @todo mixlib-shellout under ruby 1.9 hands back an ASCII-8BIT encoded string, which needs to
- # be fixed to the default external encoding -- this should be moved into mixlib-shellout
- diff_str = diff_str.force_encoding(Encoding.default_external)
- end
- diff_str.encode!('UTF-8', :invalid => :replace, :undef => :replace, :replace => '?')
- end
+ diff_str = encode_diff_for_json(diff_str)
@diff = diff_str.split("\n")
- @diff.delete("\\ No newline at end of file")
return "(diff available)"
end
- elsif !result.stderr.empty?
- return "Could not determine diff. Error: #{result.stderr}"
else
return "(no diff)"
end
@@ -139,6 +110,49 @@ class Chef
end
end
+ # produces a unified-output-format diff with 3 lines of context
+ def udiff(old_file, new_file)
+ diff_str = ""
+ file_length_difference = 0
+
+ old_data = IO::readlines(old_file).map { |e| e.chomp }
+ new_data = IO::readlines(new_file).map { |e| e.chomp }
+ diff_data = ::Diff::LCS.diff(old_data, new_data)
+
+ return diff_str if old_data.empty? && new_data.empty?
+ return "No differences encountered\n" if diff_data.empty?
+
+ # write diff header (standard unified format)
+ ft = File.stat(old_file).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
+ diff_str << "--- #{old_file}\t#{ft}\n"
+ ft = File.stat(new_file).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
+ diff_str << "+++ #{new_file}\t#{ft}\n"
+
+ # loop over diff hunks. if a hunk overlaps with the last hunk,
+ # join them. otherwise, print out the old one.
+ old_hunk = hunk = nil
+ diff_data.each do |piece|
+ begin
+ hunk = ::Diff::LCS::Hunk.new(old_data, new_data, piece, 3, file_length_difference)
+ file_length_difference = hunk.file_length_difference
+ next unless old_hunk
+ next if hunk.merge(old_hunk)
+ diff_str << old_hunk.diff(:unified) << "\n"
+ ensure
+ old_hunk = hunk
+ end
+ end
+ diff_str << old_hunk.diff(:unified) << "\n"
+ return diff_str
+ end
+
+ def encode_diff_for_json(diff_str)
+ if Object.const_defined? :Encoding
+ diff_str.encode!('UTF-8', :invalid => :replace, :undef => :replace, :replace => '?')
+ end
+ return diff_str
+ end
+
end
end
end