Skip to content

Commit 4fc578a

Browse files
committed
Fix ln_sf with multiple sources and target_directory: false
In this case, an ArgumentError is now raised rather than ignoring the option, just as GNU coreutils' `ln` would error on the command line. Fixes #128 as well.
1 parent 7e03db6 commit 4fc578a

File tree

2 files changed

+71
-35
lines changed

2 files changed

+71
-35
lines changed

lib/fileutils.rb

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -708,9 +708,10 @@ def ln_s(src, dest, force: nil, relative: false, target_directory: true, noop: n
708708
if relative
709709
return ln_sr(src, dest, force: force, noop: noop, verbose: verbose)
710710
end
711-
fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
711+
fu_output_message "ln -s#{force ? 'f' : ''}#{
712+
target_directory ? '' : 'T'} #{[src,dest].flatten.join ' '}" if verbose
712713
return if noop
713-
fu_each_src_dest0(src, dest) do |s,d|
714+
fu_each_src_dest0(src, dest, target_directory) do |s,d|
714715
remove_file d, true if force
715716
File.symlink s, d
716717
end
@@ -730,17 +731,16 @@ def ln_sf(src, dest, noop: nil, verbose: nil)
730731
# Like FileUtils.ln_s, but create links relative to +dest+.
731732
#
732733
def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil)
733-
options = "#{force ? 'f' : ''}#{target_directory ? '' : 'T'}"
734-
dest = File.path(dest)
735-
srcs = Array(src)
736-
link = proc do |s, target_dir_p = true|
737-
s = File.path(s)
738-
if target_dir_p
739-
d = File.join(destdirs = dest, File.basename(s))
740-
else
741-
destdirs = File.dirname(d = dest)
734+
fu_output_message "ln -sr#{force ? 'f' : ''}#{
735+
target_directory ? '' : 'T'} #{[src,dest].flatten.join ' '}" if verbose
736+
unless target_directory
737+
destdirs = fu_split_path(File.realdirpath(dest))
738+
end
739+
fu_each_src_dest0(src, dest, target_directory) do |s,d|
740+
if target_directory
741+
destdirs = fu_split_path(File.realdirpath(File.dirname(d)))
742+
# else d == dest
742743
end
743-
destdirs = fu_split_path(File.realpath(destdirs))
744744
if fu_starting_path?(s)
745745
srcdirs = fu_split_path((File.realdirpath(s) rescue File.expand_path(s)))
746746
base = fu_relative_components_from(srcdirs, destdirs)
@@ -754,18 +754,10 @@ def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil
754754
end
755755
s = File.join(*base, *srcdirs)
756756
end
757-
fu_output_message "ln -s#{options} #{s} #{d}" if verbose
758757
next if noop
759758
remove_file d, true if force
760759
File.symlink s, d
761760
end
762-
case srcs.size
763-
when 0
764-
when 1
765-
link[srcs[0], target_directory && File.directory?(dest)]
766-
else
767-
srcs.each(&link)
768-
end
769761
end
770762
module_function :ln_sr
771763

@@ -2475,6 +2467,9 @@ def fu_each_src_dest(src, dest) #:nodoc:
24752467

24762468
def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc:
24772469
if tmp = Array.try_convert(src)
2470+
unless target_directory or tmp.size <= 1
2471+
raise ArgumentError, "extra target #{tmp}"
2472+
end
24782473
tmp.each do |s|
24792474
s = File.path(s)
24802475
yield s, (target_directory ? File.join(dest, File.basename(s)) : dest)

test/fileutils/test_fileutils.rb

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -955,16 +955,27 @@ def test_ln_pathname
955955
def test_ln_s
956956
check_singleton :ln_s
957957

958+
ln_s TARGETS, 'tmp'
959+
each_srcdest do |fname, lnfname|
960+
assert_equal fname, File.readlink(lnfname)
961+
ensure
962+
rm_f lnfname
963+
end
964+
965+
lnfname = 'symlink'
966+
assert_raise(Errno::ENOENT, "multiple targets need a destination directory") {
967+
ln_s TARGETS, lnfname
968+
}
969+
assert_file.not_exist?(lnfname)
970+
958971
TARGETS.each do |fname|
959-
begin
960-
fname = "../#{fname}"
961-
lnfname = 'tmp/lnsdest'
962-
ln_s fname, lnfname
963-
assert FileTest.symlink?(lnfname), 'not symlink'
964-
assert_equal fname, File.readlink(lnfname)
965-
ensure
966-
rm_f lnfname
967-
end
972+
fname = "../#{fname}"
973+
lnfname = 'tmp/lnsdest'
974+
ln_s fname, lnfname
975+
assert_file.symlink?(lnfname)
976+
assert_equal fname, File.readlink(lnfname)
977+
ensure
978+
rm_f lnfname
968979
end
969980
end if have_symlink? and !no_broken_symlink?
970981

@@ -1017,22 +1028,52 @@ def test_ln_sf_pathname
10171028
def test_ln_sr
10181029
check_singleton :ln_sr
10191030

1020-
TARGETS.each do |fname|
1021-
begin
1022-
lnfname = 'tmp/lnsdest'
1023-
ln_sr fname, lnfname
1024-
assert FileTest.symlink?(lnfname), 'not symlink'
1025-
assert_equal "../#{fname}", File.readlink(lnfname), fname
1031+
assert_all_assertions_foreach(nil, *TARGETS) do |fname|
1032+
lnfname = 'tmp/lnsdest'
1033+
ln_sr fname, lnfname
1034+
assert FileTest.symlink?(lnfname), 'not symlink'
1035+
assert_equal "../#{fname}", File.readlink(lnfname)
1036+
ensure
1037+
rm_f lnfname
1038+
end
1039+
1040+
ln_sr TARGETS, 'tmp'
1041+
assert_all_assertions do |all|
1042+
each_srcdest do |fname, lnfname|
1043+
all.for(fname) do
1044+
assert_equal "../#{fname}", File.readlink(lnfname)
1045+
end
10261046
ensure
10271047
rm_f lnfname
10281048
end
10291049
end
1050+
10301051
mkdir 'data/src'
10311052
File.write('data/src/xxx', 'ok')
10321053
File.symlink '../data/src', 'tmp/src'
10331054
ln_sr 'tmp/src/xxx', 'data'
10341055
assert File.symlink?('data/xxx')
10351056
assert_equal 'ok', File.read('data/xxx')
1057+
end
1058+
1059+
def test_ln_sr_not_target_directory
1060+
assert_raise(ArgumentError) {
1061+
ln_sr TARGETS, 'tmp', target_directory: false
1062+
}
1063+
assert_empty(Dir.children('tmp'))
1064+
1065+
lnfname = 'symlink'
1066+
assert_raise(ArgumentError) {
1067+
ln_sr TARGETS, lnfname, target_directory: false
1068+
}
1069+
assert_file.not_exist?(lnfname)
1070+
1071+
assert_all_assertions_foreach(nil, *TARGETS) do |fname|
1072+
assert_raise(Errno::EEXIST, Errno::EACCES) {
1073+
ln_sr fname, 'tmp', target_directory: false
1074+
}
1075+
assert_file.not_exist? File.join('tmp/', File.basename(fname))
1076+
end
10361077
end if have_symlink?
10371078

10381079
def test_ln_sr_broken_symlink

0 commit comments

Comments
 (0)