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

accomidate hardlinks properly

now with tests

ensure the destination folder is created for travis

create destination for hard link file_syncer spec

more test fixing
parent ce20a434
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