
`mv` should preserve hardlinks and allow faster unpack on the same filesystem. A secondary pass is done with `cp` to copy over attributes onto any existing directories. We only run this for nested unpacks as most direct Directory strategy usage is for repositories where moving files breaks existing code. This uses `cp -pR` for non-move as some potential user reported issues could be due to Apple's `cp -l` on specific macOS versions. Can consider re-adding `cp -l` with better handling for older macOS.
115 lines
3.9 KiB
Ruby
115 lines
3.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require_relative "shared_examples"
|
|
|
|
RSpec.describe UnpackStrategy::Directory do
|
|
let(:path) do
|
|
mktmpdir.tap do |path|
|
|
FileUtils.touch path/"file"
|
|
FileUtils.ln_s "file", path/"symlink"
|
|
FileUtils.ln path/"file", path/"hardlink"
|
|
FileUtils.mkdir path/"folder"
|
|
FileUtils.ln_s "folder", path/"folderSymlink"
|
|
end
|
|
end
|
|
|
|
let(:unpack_dir) { mktmpdir }
|
|
|
|
shared_examples "extract directory" do |move:|
|
|
subject(:strategy) { described_class.new(path, move:) }
|
|
|
|
it "does not follow symlinks" do
|
|
strategy.extract(to: unpack_dir)
|
|
expect(unpack_dir/"symlink").to be_a_symlink
|
|
end
|
|
|
|
it "does not follow top level symlinks to directories" do
|
|
strategy.extract(to: unpack_dir)
|
|
expect(unpack_dir/"folderSymlink").to be_a_symlink
|
|
end
|
|
|
|
it "preserves permissions of contained files" do
|
|
FileUtils.chmod 0644, path/"file"
|
|
|
|
strategy.extract(to: unpack_dir)
|
|
expect((unpack_dir/"file").stat.mode & 0777).to eq 0644
|
|
end
|
|
|
|
it "preserves permissions of contained subdirectories" do
|
|
FileUtils.mkdir unpack_dir/"folder"
|
|
FileUtils.chmod 0755, unpack_dir/"folder"
|
|
FileUtils.chmod 0700, path/"folder"
|
|
|
|
strategy.extract(to: unpack_dir)
|
|
expect((unpack_dir/"folder").stat.mode & 0777).to eq 0700
|
|
end
|
|
|
|
it "preserves permissions of the destination directory" do
|
|
FileUtils.chmod 0700, path
|
|
FileUtils.chmod 0755, unpack_dir
|
|
|
|
strategy.extract(to: unpack_dir)
|
|
expect(unpack_dir.stat.mode & 0777).to eq 0755
|
|
end
|
|
|
|
it "preserves mtime of contained files and directories" do
|
|
FileUtils.mkdir unpack_dir/"folder"
|
|
FileUtils.touch path/"folder", mtime: Time.utc(2000, 1, 2, 3, 4, 5, 678999), nocreate: true
|
|
mtimes = path.children.to_h { |child| [child.basename, child.lstat.mtime] }
|
|
|
|
strategy.extract(to: unpack_dir)
|
|
expect(unpack_dir.children.to_h { |child| [child.basename, child.lstat.mtime] }).to eq mtimes
|
|
end
|
|
|
|
it "preserves unrelated destination files and subdirectories" do
|
|
FileUtils.touch unpack_dir/"existing_file"
|
|
FileUtils.mkdir unpack_dir/"existing_folder"
|
|
|
|
strategy.extract(to: unpack_dir)
|
|
expect(unpack_dir/"existing_file").to be_a_file
|
|
expect(unpack_dir/"existing_folder").to be_a_directory
|
|
end
|
|
|
|
it "overwrites destination files/symlinks with source files/symlinks" do
|
|
FileUtils.mkdir unpack_dir/"existing_folder"
|
|
FileUtils.ln_s unpack_dir/"existing_folder", unpack_dir/"symlink"
|
|
(unpack_dir/"file").write "existing contents"
|
|
|
|
strategy.extract(to: unpack_dir)
|
|
expect((unpack_dir/"file").read).to be_empty
|
|
expect((unpack_dir/"symlink").readlink).to eq Pathname("file")
|
|
end
|
|
|
|
it "fails when overwriting a directory with a file" do
|
|
FileUtils.mkdir unpack_dir/"file"
|
|
expect { strategy.extract(to: unpack_dir) }.to raise_error(/Is a directory|cannot overwrite directory/i)
|
|
end
|
|
|
|
it "fails when overwriting a nested directory with a file" do
|
|
FileUtils.touch path/"folder/nested"
|
|
FileUtils.mkdir_p unpack_dir/"folder/nested"
|
|
expect { strategy.extract(to: unpack_dir) }.to raise_error(/Is a directory|cannot overwrite directory/i)
|
|
end
|
|
end
|
|
|
|
context "with `move: false`" do
|
|
include_examples "extract directory", move: false
|
|
end
|
|
|
|
context "with `move: true`" do
|
|
include_examples "extract directory", move: true
|
|
|
|
it "preserves hardlinks" do
|
|
strategy.extract(to: unpack_dir)
|
|
expect((unpack_dir/"file").stat.ino).to eq (unpack_dir/"hardlink").stat.ino
|
|
end
|
|
|
|
# NOTE: We don't test `move: false` because system cp behaviour is inconsistent,
|
|
# e.g. Ventura cp does not error but Sequoia and Linux cp will error
|
|
it "fails when overwriting a file with a directory" do
|
|
FileUtils.touch unpack_dir/"folder"
|
|
expect { strategy.extract(to: unpack_dir) }.to raise_error(/cannot overwrite non-directory/i)
|
|
end
|
|
end
|
|
end
|