summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2004-08-08 21:35:11 +0000
committeraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2004-08-08 21:35:11 +0000
commit18c0a86cc680929bbd8c48f4f45a197e3e68c70a (patch)
tree0ced873bc06492e31a1eae61ece1e94864f2fa59
parentab8f2ed967389c70fc9e841ca26635dc45148c14 (diff)
* lib/fileutils.rb (cp_r): copies symlink to symlink, except cp_r root.
* lib/fileutils.rb: new method FileUtils.copy_entry. * test/fileutils/test_fileutils.rb: more cp_r tests. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@6747 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--ChangeLog9
-rw-r--r--lib/fileutils.rb245
-rw-r--r--test/fileutils/fileasserts.rb39
-rw-r--r--test/fileutils/test_fileutils.rb49
4 files changed, 263 insertions, 79 deletions
diff --git a/ChangeLog b/ChangeLog
index e2ed407dfc..f33211b296 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+Mon Aug 9 06:33:06 2004 Minero Aoki <aamine@loveruby.net>
+
+ * lib/fileutils.rb (cp_r): copies symlink to symlink, except
+ root entries of cp_r.
+
+ * lib/fileutils.rb: new method FileUtils.copy_entry.
+
+ * test/fileutils/test_fileutils.rb: more cp_r tests.
+
Sun Aug 8 00:43:31 2004 why the lucky stiff <why@ruby-lang.org>
* lib/implicit.c: added sexagecimal float#base60.
diff --git a/lib/fileutils.rb b/lib/fileutils.rb
index f8fae5fb47..3558ad621b 100644
--- a/lib/fileutils.rb
+++ b/lib/fileutils.rb
@@ -48,7 +48,8 @@
# There are some `low level' methods, which does not accept any option:
#
# uptodate?(file, cmp_list)
-# copy_file(srcpath, destpath)
+# copy_entry(src, dest, preserve = false, dereference = false)
+# copy_file(src, dest, preserve = false, dereference = true)
# copy_stream(srcstream, deststream)
# compare_file(path_a, path_b)
# compare_stream(stream_a, stream_b)
@@ -177,6 +178,14 @@ module FileUtils
mode = options[:mode] || (0777 & ~File.umask)
list.map {|path| path.sub(%r</\z>, '') }.each do |path|
+ # optimize for the most common case
+ begin
+ Dir.mkdir path
+ next
+ rescue SystemCallError
+ next if File.directory?(path)
+ end
+
stack = []
until path == stack.last # dirname("/")=="/", dirname("C:/")=="C:/"
stack.push path
@@ -322,9 +331,7 @@ module FileUtils
return if options[:noop]
fu_each_src_dest(src, dest) do |s,d|
- fu_preserve_attr(options[:preserve], s, d) {
- copy_file s, d
- }
+ copy_file s, d, options[:preserve]
end
end
@@ -346,6 +353,12 @@ module FileUtils
# # Examples of copying several files to target directory.
# FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
# FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop, :verbose
+ #
+ # # 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_r 'src/.', 'dest' # cp_r('src', 'dest') makes src/dest,
+ # # but this doesn't.
#
def cp_r(src, dest, options = {})
fu_check_options options, :preserve, :noop, :verbose
@@ -354,68 +367,53 @@ module FileUtils
fu_each_src_dest(src, dest) do |s,d|
if File.directory?(s)
- fu_copy_dir s, d, '.', options[:preserve]
+ fu_traverse(s) {|rel, deref, st|
+ ctx = CopyContext_.new(options[:preserve], deref, st)
+ ctx.copy_entry "#{s}/#{rel}", "#{d}/#{rel}"
+ }
else
- fu_p_copy s, d, options[:preserve]
+ copy_file s, d, options[:preserve]
end
end
end
- def fu_copy_dir(src, dest, rel, preserve) #:nodoc:
- fu_preserve_attr(preserve, "#{src}/#{rel}", "#{dest}/#{rel}") {|s,d|
- begin
- Dir.mkdir File.expand_path(d)
- rescue => err
- raise unless File.directory?(d)
- end
- }
- Dir.entries("#{src}/#{rel}").each do |fname|
- if File.directory?(File.join(src,rel,fname))
- next if /\A\.\.?\z/ === fname
- fu_copy_dir src, dest, "#{rel}/#{fname}", preserve
- else
- fu_p_copy File.join(src,rel,fname), File.join(dest,rel,fname), preserve
+ def fu_traverse(prefix, dereference_root = true) #:nodoc:
+ stack = ['.']
+ deref = dereference_root
+ while rel = stack.pop
+ st = File.lstat("#{prefix}/#{rel}")
+ if st.directory? and (deref or not st.symlink?)
+ stack.concat Dir.entries("#{prefix}/#{rel}")\
+ .reject {|ent| ent == '.' or ent == '..' }\
+ .map {|ent| "#{rel}/#{ent}" }.reverse
end
+ yield rel, deref, st
+ deref = false
end
end
- private :fu_copy_dir
-
- def fu_p_copy(src, dest, really = true) #:nodoc:
- fu_preserve_attr(really, src, dest) {
- copy_file src, dest
- }
- end
- private :fu_p_copy
+ private :fu_traverse
- def fu_preserve_attr(really, src, dest) #:nodoc:
- unless really
- yield src, dest
- return
- end
-
- st = File.stat(src)
- yield src, dest
- File.utime st.atime, st.mtime, dest
- begin
- File.chown st.uid, st.gid, dest
- rescue Errno::EPERM
- File.chmod st.mode & 01777, dest # clear setuid/setgid
- else
- File.chmod st.mode, dest
- end
+ #
+ # Copies a file system entry +src+ to +dest+.
+ # This method preserves file types, c.f. FIFO, device files, directory....
+ #
+ # Both of +src+ and +dest+ must be a path name.
+ # +src+ must exist, +dest+ must not exist.
+ #
+ # If +preserve+ is true, this method preserves owner, group and permissions.
+ # If +dereference+ is true, this method copies a target of symbolic link
+ # instead of a symbolic link itself.
+ #
+ def copy_entry(src, dest, preserve = false, dereference = false)
+ CopyContext_.new(preserve, dereference).copy_entry src, dest
end
- private :fu_preserve_attr
#
- # Copies file +src+ to +dest+.
- # Both of +src+ and +dest+ must be a filename.
+ # Copies file contents of +src+ to +dest+.
+ # Both of +src+ and +dest+ must be a path name.
#
- def copy_file(src, dest)
- File.open(src, 'rb') {|r|
- File.open(dest, 'wb', r.stat.mode) {|w|
- copy_stream r, w
- }
- }
+ def copy_file(src, dest, preserve = false, dereference = true)
+ CopyContext_.new(preserve, dereference).copy_content src, dest
end
#
@@ -423,14 +421,128 @@ module FileUtils
# Both of +src+ and +dest+ must be a IO.
#
def copy_stream(src, dest)
- bsize = fu_stream_blksize(src, dest)
+ fu_copy_stream0 src, dest, fu_stream_blksize(src, dest)
+ end
+
+ def fu_copy_stream0(src, dest, blksize) #:nodoc:
begin
while true
- dest.syswrite src.sysread(bsize)
+ dest.syswrite src.sysread(blksize)
end
rescue EOFError
end
end
+ private :fu_copy_stream0
+
+ class CopyContext_
+ include ::FileUtils
+
+ def initialize(preserve = false, dereference = false, stat = nil)
+ @preserve = preserve
+ @dereference = dereference
+ @stat = stat
+ end
+
+ def copy_entry(src, dest)
+ preserve(src, dest) {
+ _copy_entry src, dest
+ }
+ end
+
+ def copy_content(src, dest)
+ preserve(src, dest) {
+ _copy_content src, dest
+ }
+ end
+
+ private
+
+ def _copy_entry(src, dest)
+ st = stat(src)
+ case
+ when st.file?
+ _copy_content src, dest
+ when st.directory?
+ begin
+ Dir.mkdir File.expand_path(dest)
+ rescue => err
+ raise unless File.directory?(dest)
+ end
+ when st.symlink?
+ File.symlink File.readlink(src), dest
+ when st.chardev?
+ raise "cannot handle device file" unless File.respond_to?(:mknod)
+ mknod dest, ?c, 0666, st.rdev
+ when st.blockdev?
+ raise "cannot handle device file" unless File.respond_to?(:mknod)
+ mknod dest, ?b, 0666, st.rdev
+ when st.socket?
+ raise "cannot handle socket" unless File.respond_to?(:mknod)
+ mknod dest, nil, st.mode, 0
+ when st.pipe?
+ raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
+ mkfifo dest, 0666
+ when (st.mode & 0xF000) == (_S_IF_DOOR = 0xD000) # door
+ raise "cannot handle door: #{src}"
+ else
+ raise "unknown file type: #{src}"
+ end
+ end
+
+ def _copy_content(src, dest)
+ st = stat(src)
+ File.open(src, 'rb') {|r|
+ File.open(dest, 'wb', st.mode) {|w|
+ fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize())
+ }
+ }
+ end
+
+ def preserve(src, dest)
+ return yield unless @preserve
+ st = stat(src)
+ yield
+ File.utime st.atime, st.mtime, dest
+ begin
+ chown st.uid, st.gid, dest
+ rescue Errno::EPERM
+ # clear setuid/setgid
+ chmod st.mode & 01777, dest
+ else
+ chmod st.mode, dest
+ end
+ end
+
+ def stat(path)
+ @stat ||= ::File.stat(path)
+ end
+
+ def chmod(mode, path)
+ if @dereference
+ ::File.chmod mode, path
+ else
+ begin
+ ::File.lchmod mode, path
+ rescue NotImplementedError
+ # just ignore this because chmod(symlink) changes attributes of
+ # symlink target, which is not our intent.
+ end
+ end
+ end
+
+ def chown(uid, gid, path)
+ if @dereference
+ ::File.chown uid, gid, path
+ else
+ begin
+ ::File.lchown uid, gid, path
+ rescue NotImplementedError
+ # just ignore this because chown(symlink) changes attributes of
+ # symlink target, which is not our intent.
+ end
+ end
+ end
+ end
#
# Options: force noop verbose
@@ -461,11 +573,7 @@ module FileUtils
File.rename s, d
rescue SystemCallError
begin
- if File.symlink?(s)
- File.symlink File.readlink(s), d
- else
- fu_p_copy s, d
- end
+ copy_entry s, d, true
File.unlink s
rescue SystemCallError
raise unless options[:force]
@@ -486,7 +594,7 @@ module FileUtils
# Options: force noop verbose
#
# Remove file(s) specified in +list+. This method cannot remove directories.
- # All errors are ignored when the :force option is set.
+ # All StandardErrors are ignored when the :force option is set.
#
# FileUtils.rm %w( junk.txt dust.txt )
# FileUtils.rm Dir.glob('*.so')
@@ -767,9 +875,20 @@ module FileUtils
def fu_stream_blksize(*streams)
streams.each do |s|
next unless s.respond_to?(:stat)
- size = s.stat.blksize
- return size unless size.nil? or size.zero?
+ size = fu_blksize(s.stat)
+ return size if size
end
+ fu_default_blksize()
+ end
+
+ def fu_blksize(st)
+ s = st.blksize
+ return nil unless s
+ return nil if s == 0
+ s
+ end
+
+ def fu_default_blksize
1024
end
diff --git a/test/fileutils/fileasserts.rb b/test/fileutils/fileasserts.rb
index 2a96351bc2..ea03534545 100644
--- a/test/fileutils/fileasserts.rb
+++ b/test/fileutils/fileasserts.rb
@@ -1,12 +1,10 @@
-#
-# test/fileutils/fileasserts.rb
-#
+# $Id$
module Test
module Unit
module Assertions # redefine
- def assert_same_file( from, to )
+ def assert_same_file(from, to)
_wrap_assertion {
assert_block("file #{from} != #{to}") {
File.read(from) == File.read(to)
@@ -14,7 +12,22 @@ module Test
}
end
- def assert_file_exist( path )
+ def assert_same_entry(from, to)
+ _wrap_assertion {
+ assert_block("entry #{from} != #{to}") {
+ a = File.stat(from)
+ b = File.stat(to)
+
+ a.mode == b.mode and
+ #a.atime == b.atime and
+ a.mtime == b.mtime and
+ a.uid == b.uid and
+ a.gid == b.gid
+ }
+ }
+ end
+
+ def assert_file_exist(path)
_wrap_assertion {
assert_block("file not exist: #{path}") {
File.exist?(path)
@@ -22,7 +35,7 @@ module Test
}
end
- def assert_file_not_exist( path )
+ def assert_file_not_exist(path)
_wrap_assertion {
assert_block("file not exist: #{path}") {
not File.exist?(path)
@@ -30,7 +43,7 @@ module Test
}
end
- def assert_directory( path )
+ def assert_directory(path)
_wrap_assertion {
assert_block("is not directory: #{path}") {
File.directory?(path)
@@ -38,14 +51,22 @@ module Test
}
end
- def assert_symlink( path )
+ def assert_symlink(path)
_wrap_assertion {
- assert_block("is no symlink: #{path}") {
+ assert_block("is not a symlink: #{path}") {
File.symlink?(path)
}
}
end
+ def assert_not_symlink(path)
+ _wrap_assertion {
+ assert_block("is a symlink: #{path}") {
+ not File.symlink?(path)
+ }
+ }
+ end
+
end
end
end
diff --git a/test/fileutils/test_fileutils.rb b/test/fileutils/test_fileutils.rb
index 950e20499c..bacbb51aa8 100644
--- a/test/fileutils/test_fileutils.rb
+++ b/test/fileutils/test_fileutils.rb
@@ -1,6 +1,4 @@
-#
-# test/fileutils/test_fileutils.rb
-#
+# $Id$
require 'fileutils'
require 'fileasserts'
@@ -96,13 +94,12 @@ class TestFileUtils
end
- TARGETS = %w( data/same data/all data/random data/zero )
+ TARGETS = %w( data/a data/all data/random data/zero )
def prepare_data_file
- same_chars = 'a' * 50
- File.open('data/same', 'w') {|f|
+ File.open('data/a', 'w') {|f|
32.times do
- f.puts same_chars
+ f.puts 'a' * 50
end
}
@@ -236,6 +233,44 @@ end
assert_same_file fname, "tmp/#{fname}"
end
+ cp_r 'data', 'tmp2', :preserve => true
+ TARGETS.each do |fname|
+ assert_same_entry fname, "tmp/#{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_r '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'
+ rm_rf 'tmp/cpr_src'
+ rm_rf 'tmp/cpr_dest'
+
+if have_symlink?
+ # symlink in a directory
+ mkdir 'tmp/cpr_src'
+ ln_s 'SLdest', 'tmp/cpr_src/symlink'
+ cp_r 'tmp/cpr_src', 'tmp/cpr_dest'
+ assert_symlink 'tmp/cpr_dest/symlink'
+ assert_equal 'SLdest', File.readlink('tmp/cpr_dest/symlink')
+
+ # root is a symlink
+ ln_s 'cpr_src', 'tmp/cpr_src2'
+ cp_r 'tmp/cpr_src2', 'tmp/cpr_dest2'
+ assert_directory 'tmp/cpr_dest2'
+ assert_not_symlink 'tmp/cpr_dest2'
+ assert_symlink 'tmp/cpr_dest2/symlink'
+ assert_equal 'SLdest', File.readlink('tmp/cpr_dest2/symlink')
+end
+
# pathname
touch 'tmp/cprtmp'
assert_nothing_raised {