summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS6
-rw-r--r--lib/fileutils.rb69
-rw-r--r--test/fileutils/test_fileutils.rb39
3 files changed, 114 insertions, 0 deletions
diff --git a/NEWS b/NEWS
index 4d3a08218bd..5ca32c67a28 100644
--- a/NEWS
+++ b/NEWS
@@ -102,6 +102,12 @@ with all sufficient information, see the ChangeLog file or Redmine
* erb command's -S option is deprecated, which will be removed in the next version.
+* FileUtils
+
+ * New method:
+
+ * FileUtils#cp_lr [Feature #4189]
+
* Matrix
* New method:
diff --git a/lib/fileutils.rb b/lib/fileutils.rb
index f56d7f9cb92..56ee0e14ff3 100644
--- a/lib/fileutils.rb
+++ b/lib/fileutils.rb
@@ -297,6 +297,39 @@ module FileUtils
#
# :call-seq:
+ # FileUtils.cp_lr(src, dest, noop: nil, verbose: nil, dereference_root: true, remove_destination: false)
+ #
+ # Hard link +src+ to +dest+. If +src+ is a directory, this method links
+ # all its contents recursively. If +dest+ is a directory, links
+ # +src+ to +dest/src+.
+ #
+ # +src+ can be a list of files.
+ #
+ # # Installing ruby library "mylib" under the site_ruby
+ # FileUtils.rm_r site_ruby + '/mylib', :force => true
+ # FileUtils.cp_lr 'lib/', site_ruby + '/mylib'
+ #
+ # # Examples of copying several files to target directory.
+ # FileUtils.cp_lr %w(mail.rb field.rb debug/), site_ruby + '/tmail'
+ # FileUtils.cp_lr Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop => true, :verbose => true
+ #
+ # # If you want to copy all contents of a directory instead of the
+ # # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
+ # # use following code.
+ # FileUtils.cp_lr 'src/.', 'dest' # cp_r('src', 'dest') makes src/dest, but this doesn't.
+ #
+ def cp_lr(src, dest, noop: nil, verbose: nil,
+ dereference_root: true, remove_destination: false)
+ fu_output_message "cp -lr#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
+ return if noop
+ fu_each_src_dest(src, dest) do |s, d|
+ link_entry s, d, dereference_root, remove_destination
+ end
+ end
+ module_function :cp_lr
+
+ #
+ # :call-seq:
# FileUtils.ln_s(target, link, force: nil, noop: nil, verbose: nil)
# FileUtils.ln_s(target, dir, force: nil, noop: nil, verbose: nil)
# FileUtils.ln_s(targets, dir, force: nil, noop: nil, verbose: nil)
@@ -342,6 +375,26 @@ module FileUtils
module_function :ln_sf
#
+ # Hard links a file system entry +src+ to +dest+.
+ # If +src+ is a directory, this method links its contents recursively.
+ #
+ # Both of +src+ and +dest+ must be a path name.
+ # +src+ must exist, +dest+ must not exist.
+ #
+ # If +dereference_root+ is true, this method dereference tree root.
+ #
+ # If +remove_destination+ is true, this method removes each destination file before copy.
+ #
+ def link_entry(src, dest, dereference_root = false, remove_destination = false)
+ Entry_.new(src, nil, dereference_root).traverse do |ent|
+ destent = Entry_.new(dest, ent.rel, false)
+ File.unlink destent.path if remove_destination && File.file?(destent.path)
+ ent.link destent.path
+ end
+ end
+ module_function :link_entry
+
+ #
# Copies a file content +src+ to +dest+. If +dest+ is a directory,
# copies +src+ to +dest/src+.
#
@@ -1252,6 +1305,22 @@ module FileUtils
end
end
+ def link(dest)
+ case
+ when directory?
+ if !File.exist?(dest) and descendant_directory?(dest, path)
+ raise ArgumentError, "cannot link directory %s to itself %s" % [path, dest]
+ end
+ begin
+ Dir.mkdir dest
+ rescue
+ raise unless File.directory?(dest)
+ end
+ else
+ File.link path(), dest
+ end
+ end
+
def copy(dest)
lstat
case
diff --git a/test/fileutils/test_fileutils.rb b/test/fileutils/test_fileutils.rb
index 584a1f517a2..c531af15697 100644
--- a/test/fileutils/test_fileutils.rb
+++ b/test/fileutils/test_fileutils.rb
@@ -449,6 +449,45 @@ class TestFileUtils < Test::Unit::TestCase
cp_r 'tmp/src', 'tmp/dest/', remove_destination: true
end if have_symlink?
+ def test_cp_lr
+ check_singleton :cp_lr
+
+ cp_lr 'data', 'tmp'
+ TARGETS.each do |fname|
+ assert_same_file fname, "tmp/#{fname}"
+ end
+
+ # a/* -> b/*
+ mkdir 'tmp/cpr_src'
+ mkdir 'tmp/cpr_dest'
+ File.open('tmp/cpr_src/a', 'w') {|f| f.puts 'a' }
+ File.open('tmp/cpr_src/b', 'w') {|f| f.puts 'b' }
+ File.open('tmp/cpr_src/c', 'w') {|f| f.puts 'c' }
+ mkdir 'tmp/cpr_src/d'
+ cp_lr 'tmp/cpr_src/.', 'tmp/cpr_dest'
+ assert_same_file 'tmp/cpr_src/a', 'tmp/cpr_dest/a'
+ assert_same_file 'tmp/cpr_src/b', 'tmp/cpr_dest/b'
+ assert_same_file 'tmp/cpr_src/c', 'tmp/cpr_dest/c'
+ assert_directory 'tmp/cpr_dest/d'
+ my_rm_rf 'tmp/cpr_src'
+ my_rm_rf 'tmp/cpr_dest'
+
+ bug3588 = '[ruby-core:31360]'
+ mkdir 'tmp2'
+ assert_nothing_raised(ArgumentError, bug3588) do
+ cp_lr 'tmp', 'tmp2'
+ end
+ assert_directory 'tmp2/tmp'
+ assert_raise(ArgumentError, bug3588) do
+ cp_lr 'tmp2', 'tmp2/new_tmp2'
+ end
+
+ bug12892 = '[ruby-core:77885] [Bug #12892]'
+ assert_raise(Errno::ENOENT, bug12892) do
+ cp_lr 'non/existent', 'tmp'
+ end
+ end if have_hardlink?
+
def test_mv
check_singleton :mv