Skip to content
Snippets Groups Projects
Commit eab89287 authored by Isa Farnik's avatar Isa Farnik
Browse files

Merge pull request #479 from chef/if/proper-hardlinks

accomidate hardlinks properly
parents 01ead51b a7cca48a
No related branches found
No related tags found
1 merge request!2omnibus version to v5.0.0
Loading
Loading
@@ -101,16 +101,28 @@ module Omnibus
FileUtils.ln_sf(target, "#{destination}/#{relative_path}")
end
when :file
# First attempt a regular copy. If we don't have write
# permission on the File, open will probably fail with
# EACCES (making it hard to sync files with permission
# r--r--r--). Rescue this error and use cp_r's
# :remove_destination option.
begin
FileUtils.cp(source_file, "#{destination}/#{relative_path}")
rescue Errno::EACCES
FileUtils.cp_r(source_file, "#{destination}/#{relative_path}",
:remove_destination => true)
source_stat = File.stat(source_file)
# Detect 'files' which are hard links and use ln instead of cp to
# duplicate them, provided their source is in place already
if hardlink? source_stat
if existing = hardlink_sources[[source_stat.dev, source_stat.ino]]
FileUtils.ln(existing, "#{destination}/#{relative_path}")
else
FileUtils.cp(source_file, "#{destination}/#{relative_path}")
hardlink_sources.store([source_stat.dev, source_stat.ino], "#{destination}/#{relative_path}")
end
else
# First attempt a regular copy. If we don't have write
# permission on the File, open will probably fail with
# EACCES (making it hard to sync files with permission
# r--r--r--). Rescue this error and use cp_r's
# :remove_destination option.
begin
FileUtils.cp(source_file, "#{destination}/#{relative_path}")
rescue Errno::EACCES
FileUtils.cp_r(source_file, "#{destination}/#{relative_path}",
:remove_destination => true)
end
end
else
raise RuntimeError,
Loading
Loading
@@ -155,5 +167,34 @@ module Omnibus
def relative_path_for(path, parent)
Pathname.new(path).relative_path_from(Pathname.new(parent)).to_s
end
#
# A list of hard link file(s) sources which have already been copied,
# indexed by device and inode number.
#
# @api private
#
# @return [Hash{Array<FixNum, FixNum> => String}]
#
def hardlink_sources
@hardlink_sources ||= {}
end
#
# Determines whether or not a file is a hardlink.
#
# @param [File::Stat] stat
# the File::Stat object for a file you wand to test
#
# @return [true, false]
#
def hardlink?(stat)
case stat.ftype.to_sym
when :file
stat.nlink > 1
else
false
end
end
end
end
Loading
Loading
@@ -101,6 +101,30 @@ module Omnibus
end
end
 
context 'when target files are hard links' do
let(:source) do
source = File.join(tmp_path, 'source')
FileUtils.mkdir_p(source)
create_directory(source, 'bin')
create_file(source, 'bin', 'git')
FileUtils.ln("#{source}/bin/git", "#{source}/bin/git-tag")
FileUtils.ln("#{source}/bin/git", "#{source}/bin/git-write-tree")
source
end
it 'copies the first instance and links to that instance thereafter' do
FileUtils.mkdir_p("#{destination}/bin")
described_class.sync(source, destination)
expect("#{destination}/bin/git").to be_a_file
expect("#{destination}/bin/git-tag").to be_a_hardlink
expect("#{destination}/bin/git-write-tree").to be_a_hardlink
end
end
context 'with deeply nested paths and symlinks' do
let(:source) do
source = File.join(tmp_path, 'source')
Loading
Loading
Loading
Loading
@@ -34,3 +34,10 @@ RSpec::Matchers.define :be_an_executable do
File.executable?(actual)
end
end
# expect('/path/to/file').to be_a_hardlink
RSpec::Matchers.define :be_a_hardlink do |path|
match do |actual|
File.stat(actual).nlink > 2
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment