summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile12
-rw-r--r--chef.gemspec2
-rw-r--r--lib/chef/util/diff.rb82
-rw-r--r--spec/unit/util/diff_spec.rb31
4 files changed, 56 insertions, 71 deletions
diff --git a/Gemfile b/Gemfile
index 371406cfde..a228c189c8 100644
--- a/Gemfile
+++ b/Gemfile
@@ -20,14 +20,14 @@ end
platforms :mswin, :mingw do
gem "systemu", "2.2.0" # CHEF-3718
- gem "ffi", "1.0.9"
+ gem "ffi", "1.3.1"
gem "rdp-ruby-wmi", "0.3.1"
- gem "windows-api", "0.4.0"
- gem "windows-pr", "1.2.1"
+ gem "windows-api", "0.4.2"
+ gem "windows-pr", "1.2.2"
gem "win32-api", "1.4.8"
- gem "win32-dir", "0.3.7"
- gem "win32-event", "0.5.2"
- gem "win32-mutex", "0.3.1"
+ gem "win32-dir", "0.4.1"
+ gem "win32-event", "0.6.0"
+ gem "win32-mutex", "0.4.0"
gem "win32-process", "0.6.5"
gem "win32-service", "0.7.2"
end
diff --git a/chef.gemspec b/chef.gemspec
index cbee2c0a00..0be91849c7 100644
--- a/chef.gemspec
+++ b/chef.gemspec
@@ -30,6 +30,8 @@ Gem::Specification.new do |s|
s.add_dependency "highline", ">= 1.6.9"
s.add_dependency "erubis"
+ s.add_dependency "diff-lcs", ">= 1.2.4"
+
%w(rdoc sdoc rake rack rspec_junit_formatter).each { |gem| s.add_development_dependency gem }
%w(rspec-core rspec-expectations rspec-mocks).each { |gem| s.add_development_dependency gem, "~> 2.13.0" }
s.add_development_dependency "chef-zero", "~> 1.4"
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
diff --git a/spec/unit/util/diff_spec.rb b/spec/unit/util/diff_spec.rb
index 2dadb5be56..ad1166f9fc 100644
--- a/spec/unit/util/diff_spec.rb
+++ b/spec/unit/util/diff_spec.rb
@@ -393,37 +393,6 @@ describe Chef::Util::Diff, :uses_diff => true do
end
end
- describe "when errors are thrown from shell_out" do
- before do
- differ.stub!(:shell_out).and_raise('boom')
- differ.diff(old_file, new_file)
- end
-
- it "calling for_output should return the error message" do
- expect(differ.for_output).to eql(["Could not determine diff. Error: boom"])
- end
-
- it "calling for_reporting should be nil" do
- expect(differ.for_reporting).to be_nil
- end
- end
-
- describe "when shell_out returns stderr output" do
- before do
- @result = mock('result', :stdout => "", :stderr => "boom")
- differ.stub!(:shell_out).and_return(@result)
- differ.diff(old_file, new_file)
- end
-
- it "calling for_output should return the error message" do
- expect(differ.for_output).to eql(["Could not determine diff. Error: boom"])
- end
-
- it "calling for_reporting should be nil" do
- expect(differ.for_reporting).to be_nil
- end
- end
-
describe "when checking if files are binary or text" do
it "should identify zero-length files as text" do