diff --git a/Library/Homebrew/test/unpack_strategy_spec.rb b/Library/Homebrew/test/unpack_strategy_spec.rb index c24e417309..4d31c76045 100644 --- a/Library/Homebrew/test/unpack_strategy_spec.rb +++ b/Library/Homebrew/test/unpack_strategy_spec.rb @@ -15,6 +15,50 @@ RSpec.shared_examples "#extract" do |children: []| end end +describe UnpackStrategy do + describe "#extract_nestedly" do + subject(:strategy) { described_class.detect(path) } + + let(:unpack_dir) { mktmpdir } + + context "when extracting a GZIP nested in a BZIP2" do + let(:file_name) { "file" } + let(:path) { + dir = mktmpdir + + (dir/"file").write "This file was inside a GZIP inside a BZIP2." + system "gzip", dir.children.first + system "bzip2", dir.children.first + + dir.children.first + } + + it "can extract nested archives" do + strategy.extract_nestedly(to: unpack_dir) + + expect(File.read(unpack_dir/file_name)).to eq("This file was inside a GZIP inside a BZIP2.") + end + end + + context "when extracting a directory with nested directories" do + let(:directories) { "A/B/C" } + let(:path) { + (mktmpdir/"file.tar").tap do |path| + mktmpdir do |dir| + (dir/directories).mkpath + system "tar", "-c", "-f", path, "-C", dir, "A/" + end + end + } + + it "does not recurse into nested directories" do + strategy.extract_nestedly(to: unpack_dir) + expect(Pathname.glob(unpack_dir/"**/*")).to include unpack_dir/directories + end + end + end +end + describe UncompressedUnpackStrategy do let(:path) { (mktmpdir/"test").tap do |path| diff --git a/Library/Homebrew/unpack_strategy.rb b/Library/Homebrew/unpack_strategy.rb index 96c0f14ce6..84def55eb9 100644 --- a/Library/Homebrew/unpack_strategy.rb +++ b/Library/Homebrew/unpack_strategy.rb @@ -67,6 +67,25 @@ class UnpackStrategy unpack_dir.mkpath extract_to_dir(unpack_dir, basename: basename) end + + def extract_nestedly(to: nil, basename: nil) + Dir.mktmpdir do |tmp_unpack_dir| + tmp_unpack_dir = Pathname(tmp_unpack_dir) + + extract(to: tmp_unpack_dir, basename: basename) + + children = tmp_unpack_dir.children + + if children.count == 1 && !children.first.directory? + s = self.class.detect(children.first) + + s.extract_nestedly(to: to, basename: basename) + next + end + + DirectoryUnpackStrategy.new(tmp_unpack_dir).extract(to: to) + end + end end class DirectoryUnpackStrategy < UnpackStrategy @@ -82,6 +101,8 @@ class DirectoryUnpackStrategy < UnpackStrategy end class UncompressedUnpackStrategy < UnpackStrategy + alias extract_nestedly extract + private def extract_to_dir(unpack_dir, basename:) @@ -166,7 +187,7 @@ class CompressUnpackStrategy < TarUnpackStrategy end end -class XzUnpackStrategy < UncompressedUnpackStrategy +class XzUnpackStrategy < UnpackStrategy def self.can_extract?(path:, magic_number:) magic_number.match?(/\A\xFD7zXZ\x00/n) end @@ -175,7 +196,7 @@ class XzUnpackStrategy < UncompressedUnpackStrategy def extract_to_dir(unpack_dir, basename:) super - safe_system Formula["xz"].opt_bin/"xz", "-d", "-q", "-T0", unpack_dir/basename + safe_system Formula["xz"].opt_bin/"unxz", "-q", "-T0", unpack_dir/basename extract_nested_tar(unpack_dir, basename: basename) end @@ -192,7 +213,7 @@ class XzUnpackStrategy < UncompressedUnpackStrategy end end -class Bzip2UnpackStrategy < UncompressedUnpackStrategy +class Bzip2UnpackStrategy < UnpackStrategy def self.can_extract?(path:, magic_number:) magic_number.match?(/\ABZh/n) end @@ -200,12 +221,12 @@ class Bzip2UnpackStrategy < UncompressedUnpackStrategy private def extract_to_dir(unpack_dir, basename:) - super + FileUtils.cp path, unpack_dir/basename, preserve: true safe_system "bunzip2", "-q", unpack_dir/basename end end -class GzipUnpackStrategy < UncompressedUnpackStrategy +class GzipUnpackStrategy < UnpackStrategy def self.can_extract?(path:, magic_number:) magic_number.match?(/\A\037\213/n) end @@ -213,12 +234,12 @@ class GzipUnpackStrategy < UncompressedUnpackStrategy private def extract_to_dir(unpack_dir, basename:) - super + FileUtils.cp path, unpack_dir/basename, preserve: true safe_system "gunzip", "-q", "-N", unpack_dir/basename end end -class LzipUnpackStrategy < UncompressedUnpackStrategy +class LzipUnpackStrategy < UnpackStrategy def self.can_extract?(path:, magic_number:) magic_number.match?(/\ALZIP/n) end @@ -226,7 +247,7 @@ class LzipUnpackStrategy < UncompressedUnpackStrategy private def extract_to_dir(unpack_dir, basename:) - super + FileUtils.cp path, unpack_dir/basename, preserve: true safe_system Formula["lzip"].opt_bin/"lzip", "-d", "-q", unpack_dir/basename end end @@ -271,12 +292,6 @@ class GitUnpackStrategy < DirectoryUnpackStrategy def self.can_extract?(path:, magic_number:) super && (path/".git").directory? end - - private - - def extract_to_dir(unpack_dir, basename:) - FileUtils.cp_r path.children, unpack_dir, preserve: true - end end class SubversionUnpackStrategy < DirectoryUnpackStrategy