From e0a318b4b9e53767e14c472eeb16a3a5c5217188 Mon Sep 17 00:00:00 2001 From: peter1000 Date: Wed, 6 May 2015 14:18:39 -0300 Subject: [PATCH 1/3] Adds kw `remove_destination` to `mv` function #11145 incl docs/tests/NEWS entry --- NEWS.md | 3 + base/file.jl | 13 ++- base/fs.jl | 10 +- base/stat.jl | 1 + doc/helpdb.jl | 5 +- doc/stdlib/file.rst | 5 +- test/file.jl | 226 +++++++++++++++++++++++++++++++++----------- 7 files changed, 194 insertions(+), 69 deletions(-) diff --git a/NEWS.md b/NEWS.md index f08da9538b744..044354bf4bda9 100644 --- a/NEWS.md +++ b/NEWS.md @@ -256,6 +256,8 @@ Library improvements * The `cp` function now accepts keyword arguments `remove_destination` and `follow_symlinks` ([#10888]). + * The `mv` function now accepts keyword argument `remove_destination` ([#11145]). + * Other improvements * You can now tab-complete Emoji characters via their [short names](http://www.emoji-cheat-sheet.com/), using `\:name:` ([#10709]). @@ -1396,3 +1398,4 @@ Too numerous to mention. [#10893]: https://github.com/JuliaLang/julia/issues/10893 [#10914]: https://github.com/JuliaLang/julia/issues/10914 [#10994]: https://github.com/JuliaLang/julia/issues/10994 +[#11145]: https://github.com/JuliaLang/julia/issues/11145 diff --git a/base/file.jl b/base/file.jl index 42a105bb1ef69..3d8fe02e43c63 100644 --- a/base/file.jl +++ b/base/file.jl @@ -115,7 +115,18 @@ function cp(src::AbstractString, dst::AbstractString; remove_destination::Bool=f FS.sendfile(src, dst) end end -mv(src::AbstractString, dst::AbstractString) = FS.rename(src, dst) + +function mv(src::AbstractString, dst::AbstractString; remove_destination::Bool=false) + if ispath(dst) + if remove_destination + rm(dst; recursive=true) + else + throw(ArgumentError(string("'$dst' exists. `remove_destination=true` ", + "is required to remove '$dst' before moving."))) + end + end + FS.rename(src, dst) +end function touch(path::AbstractString) f = FS.open(path,JL_O_WRONLY | JL_O_CREAT, 0o0666) diff --git a/base/fs.jl b/base/fs.jl index 7768dcc54bfae..bccc912f078f8 100644 --- a/base/fs.jl +++ b/base/fs.jl @@ -115,15 +115,11 @@ end # For move command function rename(src::AbstractString, dst::AbstractString) err = ccall(:jl_fs_rename, Int32, (Cstring, Cstring), src, dst) - # on error, default to cp && rm if err < 0 - # Note that those two functions already handle their errors. - # first copy - sendfile(src, dst) - - # then rm - unlink(src) + # remove_destination: is already done in the mv function + cp(src, dst; remove_destination=false, follow_symlinks=false) + rm(src; recursive=true) end end diff --git a/base/stat.jl b/base/stat.jl index 7a633110b4db1..1b294b7bc7934 100644 --- a/base/stat.jl +++ b/base/stat.jl @@ -115,5 +115,6 @@ filesize(path...) = stat(path...).size mtime(path...) = stat(path...).mtime ctime(path...) = stat(path...).ctime +# samefile can be used for files and directories: 11145#issuecomment-99511194 samefile(a::StatStruct, b::StatStruct) = a.device==b.device && a.inode==b.inode samefile(a::AbstractString, b::AbstractString) = samefile(stat(a),stat(b)) diff --git a/doc/helpdb.jl b/doc/helpdb.jl index ea8dd2db3182f..f758aa0ad2410 100644 --- a/doc/helpdb.jl +++ b/doc/helpdb.jl @@ -5280,9 +5280,10 @@ Millisecond(v) "), -("Base","mv","mv(src::AbstractString, dst::AbstractString) +("Base","mv","mv(src::AbstractString,dst::AbstractString; remove_destination::Bool=false) - Move a file from *src* to *dst*. + Move the file, link, or directory from *src* to *dest*. + \"remove_destination=true\" will first remove an existing `dst`. "), diff --git a/doc/stdlib/file.rst b/doc/stdlib/file.rst index c6846dd735900..3954304fe5ffb 100644 --- a/doc/stdlib/file.rst +++ b/doc/stdlib/file.rst @@ -126,9 +126,10 @@ use or situations in which more options are need, please use a package that provides the desired functionality instead. -.. function:: mv(src::AbstractString,dst::AbstractString) +.. function:: mv(src::AbstractString,dst::AbstractString; remove_destination::Bool=false) - Move a file from `src` to `dst`. + Move the file, link, or directory from *src* to *dest*. + \"remove_destination=true\" will first remove an existing `dst`. .. function:: rm(path::AbstractString; recursive=false) diff --git a/test/file.jl b/test/file.jl index 287f138b984fe..3e7bfbbdd76ed 100644 --- a/test/file.jl +++ b/test/file.jl @@ -76,32 +76,10 @@ if @unix? true : (Base.windows_version() >= Base.WINDOWS_VISTA_VER) @test readlink(dirlink) == subdir * @windows? "\\" : "" end -# rename file +# rm recursive TODO add links newfile = joinpath(dir, "bfile.txt") mv(file, newfile) -@test !ispath(file) -@test isfile(newfile) file = newfile - -# Test renaming directories -a_tmpdir = mktempdir() -b_tmpdir = joinpath(dir, "b_tmpdir") - -# grab a_tmpdir's file info before renaming -a_stat = stat(a_tmpdir) - -# rename, then make sure b_tmpdir does exist and a_tmpdir doesn't -mv(a_tmpdir, b_tmpdir) -@test isdir(b_tmpdir) -@test !ispath(a_tmpdir) - -# get b_tmpdir's file info and compare with a_tmpdir -b_stat = stat(b_tmpdir) -@test Base.samefile(a_stat, b_stat) - -rm(b_tmpdir) - -# rm recursive TODO add links c_tmpdir = mktempdir() c_subdir = joinpath(c_tmpdir, "c_subdir") mkdir(c_subdir) @@ -347,9 +325,9 @@ close(emptyf) rm(emptyfile) -########################################################################### -## This section tests cp files, directories, absolute and relative links. # -########################################################################### +######################################################################################## +## This section tests cp & mv(rename) files, directories, absolute and relative links. # +######################################################################################## function check_dir(orig_path::AbstractString, copied_path::AbstractString, follow_symlinks::Bool) isdir(orig_path) || throw(ArgumentError("'$orig_path' is not a directory.")) # copied_path must also be a dir. @@ -411,6 +389,7 @@ function cp_and_test(src::AbstractString, dst::AbstractString, follow_symlinks:: check_cp_main(src, dst, follow_symlinks) end +## cp ---------------------------------------------------- # issue #8698 # Test copy file afile = joinpath(dir, "a.txt") @@ -439,6 +418,42 @@ rm(afile) rm(bfile) rm(cfile) +## mv ---------------------------------------------------- +mktempdir() do tmpdir + # rename file + file = joinpath(tmpdir, "afile.txt") + files_stat = stat(file) + close(open(file,"w")) # like touch, but lets the operating system update the timestamp for greater precision on some platforms (windows) + + newfile = joinpath(tmpdir, "bfile.txt") + mv(file, newfile) + newfile_stat = stat(file) + + @test !ispath(file) + @test isfile(newfile) + @test Base.samefile(files_stat, newfile_stat) + + file = newfile + + # Test renaming directories + a_tmpdir = mktempdir() + b_tmpdir = joinpath(tmpdir, "b_tmpdir") + + # grab a_tmpdir's file info before renaming + a_stat = stat(a_tmpdir) + + # rename, then make sure b_tmpdir does exist and a_tmpdir doesn't + mv(a_tmpdir, b_tmpdir) + @test isdir(b_tmpdir) + @test !ispath(a_tmpdir) + + # get b_tmpdir's file info and compare with a_tmpdir + b_stat = stat(b_tmpdir) + @test Base.samefile(a_stat, b_stat) + + rm(b_tmpdir) +end + # issue #10506 #10434 ## Tests for directories and links to directories if @unix? true : (Base.windows_version() >= Base.WINDOWS_VISTA_VER) @@ -488,29 +503,51 @@ if @unix? true : (Base.windows_version() >= Base.WINDOWS_VISTA_VER) @test length(readdir(d)) == 1 end + function mv_check(s, d, d_mv; remove_destination=true) + # setup dest + cp(s, d; remove_destination=true, follow_symlinks=false) + stat_d = stat(d) + # mv(rename) dst to dst_mv + mv(d, d_mv; remove_destination=remove_destination) + stat_d_mv = stat(d_mv) + # make sure d does not exist anymore + @test !ispath(d) + # compare s, with d_mv + @test isdir(s) == isdir(d_mv) + @test islink(s) == islink(d_mv) + islink(s) && @test readlink(s) == readlink(d_mv) + islink(s) && @test isabspath(readlink(s)) == isabspath(readlink(d_mv)) + # all should contain 1 file named "c.txt" + @test "c.txt" in readdir(d_mv) + @test length(readdir(d_mv)) == 1 + # d => d_mv same file/dir + @test Base.samefile(stat_d, stat_d_mv) + end + ## Test require `remove_destination=true` (remove destination first) for existing # directories and existing links to directories + # cp ---------------------------------------------------- mktempdir() do tmpdir # Setup new copies for the test - test_src_paths, test_cp_paths = setup_dirs(tmpdir) - for (s, d) in zip(test_src_paths, test_cp_paths) + test_src_paths, test_new_paths = setup_dirs(tmpdir) + for (s, d) in zip(test_src_paths, test_new_paths) cp_follow_symlinks_false_check(s, d) end # Test require `remove_destination=true` for s in test_src_paths - for d in test_cp_paths + for d in test_new_paths @test_throws ArgumentError cp(s, d; remove_destination=false) @test_throws ArgumentError cp(s, d; remove_destination=false, follow_symlinks=true) end end # Test remove the existing path first and copy - for (s, d) in zip(test_src_paths, test_cp_paths) + for (s, d) in zip(test_src_paths, test_new_paths) cp_follow_symlinks_false_check(s, d; remove_destination=true) end # Test remove the existing path first and copy an empty dir emptydir = joinpath(tmpdir, "emptydir") mkdir(emptydir) - for d in test_cp_paths + for d in test_new_paths cp(emptydir, d; remove_destination=true, follow_symlinks=false) # Expect no link because a dir is copied (follow_symlinks=false does not effect this) @test isdir(d) && !islink(d) @@ -518,8 +555,28 @@ if @unix? true : (Base.windows_version() >= Base.WINDOWS_VISTA_VER) @test isempty(readdir(d)) end end + # mv ---------------------------------------------------- + mktempdir() do tmpdir + # Setup new copies for the test + test_src_paths, test_new_paths = setup_dirs(tmpdir) + for (s, d) in zip(test_src_paths, test_new_paths) + cp_follow_symlinks_false_check(s, d; remove_destination=true) + end + # Test require `remove_destination=true` + for s in test_src_paths + for d in test_new_paths + @test_throws ArgumentError mv(s, d; remove_destination=false) + end + end + # Test remove the existing path first and move + for (s, d) in zip(test_src_paths, test_new_paths) + d_mv = joinpath(dirname(d), "$(basename(d))_mv") + mv_check(s, d, d_mv; remove_destination=true) + end + end # Test full: absolute and relative directory links + # cp / mv ---------------------------------------------------- mktempdir() do tmpdir maindir = joinpath(tmpdir, "mytestdir") mkdir(maindir) @@ -545,10 +602,17 @@ if @unix? true : (Base.windows_version() >= Base.WINDOWS_VISTA_VER) symlink(rel_dir, rel_dl) cd(pwd_) # TEST: Directory with links: Test each option - maindir_cp = joinpath(dirname(maindir),"maindir_cp") - maindir_cp_keepsym = joinpath(dirname(maindir),"maindir_cp_keepsym") - cp_and_test(maindir, maindir_cp, true) - cp_and_test(maindir, maindir_cp_keepsym, false) + maindir_new = joinpath(dirname(maindir),"maindir_new") + maindir_new_keepsym = joinpath(dirname(maindir),"maindir_new_keepsym") + cp_and_test(maindir, maindir_new, true) + cp_and_test(maindir, maindir_new_keepsym, false) + + # mv ---------------------------------------------------- + # move the 3 maindirs + for d in [maindir_new, maindir_new_keepsym, maindir] + d_mv = joinpath(dirname(d), "$(basename(d))_mv") + mv(d, d_mv; remove_destination=true) + end end end @@ -558,8 +622,8 @@ end function setup_files(tmpdir) srcfile = joinpath(tmpdir, "srcfile.txt") hidden_srcfile = joinpath(tmpdir, ".hidden_srcfile.txt") - srcfile_cp = joinpath(tmpdir, "srcfile_cp.txt") - hidden_srcfile_cp = joinpath(tmpdir, ".hidden_srcfile_cp.txt") + srcfile_new = joinpath(tmpdir, "srcfile_new.txt") + hidden_srcfile_new = joinpath(tmpdir, ".hidden_srcfile_new.txt") file_txt = "This is some text with unicode - 这是一个文件" open(srcfile, "w") do f write(f, file_txt) @@ -574,13 +638,13 @@ end symlink("srcfile.txt", rel_filelink) cd(pwd_) - abs_filelink_cp = joinpath(tmpdir, "abs_filelink_cp") + abs_filelink_new = joinpath(tmpdir, "abs_filelink_new") path_rel_filelink = joinpath(tmpdir, rel_filelink) - path_rel_filelink_cp = joinpath(tmpdir, "rel_filelink_cp") + path_rel_filelink_new = joinpath(tmpdir, "rel_filelink_new") test_src_paths = [srcfile, hidden_srcfile, abs_filelink, path_rel_filelink] - test_cp_paths = [srcfile_cp, hidden_srcfile_cp, abs_filelink_cp, path_rel_filelink_cp] - return test_src_paths, test_cp_paths, file_txt + test_new_paths = [srcfile_new, hidden_srcfile_new, abs_filelink_new, path_rel_filelink_new] + return test_src_paths, test_new_paths, file_txt end function cp_follow_symlinks_false_check(s, d, file_txt; remove_destination=false) @@ -593,23 +657,44 @@ end @test readall(s) == readall(d) == file_txt end + function mv_check(s, d, d_mv, file_txt; remove_destination=true) + # setup dest + cp(s, d; remove_destination=true, follow_symlinks=false) + stat_d = stat(d) + # mv(rename) dst to dst_mv + mv(d, d_mv; remove_destination=remove_destination) + stat_d_mv = stat(d_mv) + # make sure d does not exist anymore + @test !ispath(d) + # comare s, with d_mv + @test isfile(s) == isfile(d_mv) + @test islink(s) == islink(d_mv) + islink(s) && @test readlink(s) == readlink(d_mv) + islink(s) && @test isabspath(readlink(s)) == isabspath(readlink(d_mv)) + # all should contain the same + @test readall(s) == readall(d_mv) == file_txt + # d => d_mv same file/dir + @test Base.samefile(stat_d, stat_d_mv) + end + ## Test require `remove_destination=true` (remove destination first) for existing # files and existing links to files + # cp ---------------------------------------------------- mktempdir() do tmpdir # Setup new copies for the test - test_src_paths, test_cp_paths, file_txt = setup_files(tmpdir) - for (s, d) in zip(test_src_paths, test_cp_paths) + test_src_paths, test_new_paths, file_txt = setup_files(tmpdir) + for (s, d) in zip(test_src_paths, test_new_paths) cp_follow_symlinks_false_check(s, d, file_txt) end # Test require `remove_destination=true` for s in test_src_paths - for d in test_cp_paths + for d in test_new_paths @test_throws ArgumentError cp(s, d; remove_destination=false) @test_throws ArgumentError cp(s, d; remove_destination=false, follow_symlinks=true) end end # Test remove the existing path first and copy: follow_symlinks=false - for (s, d) in zip(test_src_paths, test_cp_paths) + for (s, d) in zip(test_src_paths, test_new_paths) cp_follow_symlinks_false_check(s, d, file_txt; remove_destination=true) end # Test remove the existing path first and copy an other file @@ -618,7 +703,7 @@ end open(otherfile, "w") do f write(f, otherfile_content) end - for d in test_cp_paths + for d in test_new_paths cp(otherfile, d; remove_destination=true, follow_symlinks=false) # Expect no link because a file is copied (follow_symlinks=false does not effect this) @test isfile(d) && !islink(d) @@ -626,8 +711,28 @@ end @test readall(d) == otherfile_content end end + # mv ---------------------------------------------------- + mktempdir() do tmpdir + # Setup new copies for the test + test_src_paths, test_new_paths, file_txt = setup_files(tmpdir) + for (s, d) in zip(test_src_paths, test_new_paths) + cp_follow_symlinks_false_check(s, d, file_txt; remove_destination=true) + end + # Test require `remove_destination=true` + for s in test_src_paths + for d in test_new_paths + @test_throws ArgumentError mv(s, d; remove_destination=false) + end + end + # Test remove the existing path first and move + for (s, d) in zip(test_src_paths, test_new_paths) + d_mv = joinpath(dirname(d), "$(basename(d))_mv") + mv_check(s, d, d_mv, file_txt; remove_destination=true) + end + end # Test full: absolute and relative file links and absolute and relative directory links + # cp / mv ---------------------------------------------------- mktempdir() do tmpdir maindir = joinpath(tmpdir, "mytestdir") mkdir(maindir) @@ -666,26 +771,33 @@ end mkdir(subdir_test) cp(targetdir, joinpath(copytodir, basename(targetdir)); follow_symlinks=false) # TEST: Directory with links: Test each option - maindir_cp = joinpath(dirname(maindir),"maindir_cp") - maindir_cp_keepsym = joinpath(dirname(maindir),"maindir_cp_keepsym") - cp_and_test(maindir, maindir_cp, true) - cp_and_test(maindir, maindir_cp_keepsym, false) + maindir_new = joinpath(dirname(maindir),"maindir_new") + maindir_new_keepsym = joinpath(dirname(maindir),"maindir_new_keepsym") + cp_and_test(maindir, maindir_new, true) + cp_and_test(maindir, maindir_new_keepsym, false) ## Tests single Files, File Links rel_flpath = joinpath(subdir1, rel_fl) # `cp file` - cp_and_test(cfile, joinpath(copytodir,"cfile_cp.txt"), true) - cp_and_test(cfile, joinpath(copytodir,"cfile_cp_keepsym.txt"), false) + cp_and_test(cfile, joinpath(copytodir,"cfile_new.txt"), true) + cp_and_test(cfile, joinpath(copytodir,"cfile_new_keepsym.txt"), false) # `cp absolute file link` - cp_and_test(abs_fl, joinpath(copytodir,"abs_fl_cp.txt"), true) - cp_and_test(abs_fl, joinpath(copytodir,"abs_fl_cp_keepsym.txt"), false) + cp_and_test(abs_fl, joinpath(copytodir,"abs_fl_new.txt"), true) + cp_and_test(abs_fl, joinpath(copytodir,"abs_fl_new_keepsym.txt"), false) # `cp relative file link` - cp_and_test(rel_flpath, joinpath(subdir_test,"rel_fl_cp.txt"), true) - cp_and_test(rel_flpath, joinpath(subdir_test,"rel_fl_cp_keepsym.txt"), false) + cp_and_test(rel_flpath, joinpath(subdir_test,"rel_fl_new.txt"), true) + cp_and_test(rel_flpath, joinpath(subdir_test,"rel_fl_new_keepsym.txt"), false) + + # mv ---------------------------------------------------- + # move all 4 existing dirs + # As expected this will leave some absolute links brokern #11145#issuecomment-99315168 + for d in [copytodir, maindir_new, maindir_new_keepsym, maindir] + d_mv = joinpath(dirname(d), "$(basename(d))_mv") + mv(d, d_mv; remove_destination=true) + end end end - ################### # FILE* interface # ################### From fec7ef16b3312f3b2eddbd4f0ff006181fbc1c64 Mon Sep 17 00:00:00 2001 From: peter1000 Date: Fri, 8 May 2015 22:59:11 -0300 Subject: [PATCH 2/3] Samefile: return false if a path does not exist. https://github.com/JuliaLang/julia/pull/11172#issuecomment-100410264 --- base/file.jl | 42 +++++++++++++++++++++--------------------- base/stat.jl | 8 +++++++- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/base/file.jl b/base/file.jl index 3d8fe02e43c63..ad0dad856f7e0 100644 --- a/base/file.jl +++ b/base/file.jl @@ -71,18 +71,32 @@ end # The following use Unix command line facilites - -function cptree(src::AbstractString, dst::AbstractString; remove_destination::Bool=false, - follow_symlinks::Bool=false) - isdir(src) || throw(ArgumentError("'$src' is not a directory. Use `cp(src, dst)`")) +# check for https://github.com/JuliaLang/julia/pull/11172#issuecomment-100391076 +function checkfor_mv_cp_cptree(src::AbstractString, dst::AbstractString, txt::AbstractString; + remove_destination::Bool=false) if ispath(dst) if remove_destination + # check for https://github.com/JuliaLang/julia/pull/11172#issuecomment-100391076 + if Base.samefile(src, dst) + abs_src = islink(src) ? abspath(readlink(src)) : abspath(src) + abs_dst = islink(dst) ? abspath(readlink(dst)) : abspath(dst) + throw(ArgumentError(string("'src' and 'dst' refer to the same file/dir.", + "This is not supported.\n ", + "`src` referce to: $(abs_src)\n ", + "`dst` referce to: $(abs_dst)\n"))) + end rm(dst; recursive=true) else throw(ArgumentError(string("'$dst' exists. `remove_destination=true` ", - "is required to remove '$dst' before copying."))) + "is required to remove '$dst' before $(txt)."))) end end +end + +function cptree(src::AbstractString, dst::AbstractString; remove_destination::Bool=false, + follow_symlinks::Bool=false) + isdir(src) || throw(ArgumentError("'$src' is not a directory. Use `cp(src, dst)`")) + checkfor_mv_cp_cptree(src, dst, "copying"; remove_destination=remove_destination) mkdir(dst) for name in readdir(src) srcname = joinpath(src, name) @@ -99,14 +113,7 @@ end function cp(src::AbstractString, dst::AbstractString; remove_destination::Bool=false, follow_symlinks::Bool=false) - if ispath(dst) - if remove_destination - rm(dst; recursive=true) - else - throw(ArgumentError(string("'$dst' exists. `remove_destination=true` ", - "is required to remove '$dst' before copying."))) - end - end + checkfor_mv_cp_cptree(src, dst, "copying"; remove_destination=remove_destination) if !follow_symlinks && islink(src) symlink(readlink(src), dst) elseif isdir(src) @@ -117,14 +124,7 @@ function cp(src::AbstractString, dst::AbstractString; remove_destination::Bool=f end function mv(src::AbstractString, dst::AbstractString; remove_destination::Bool=false) - if ispath(dst) - if remove_destination - rm(dst; recursive=true) - else - throw(ArgumentError(string("'$dst' exists. `remove_destination=true` ", - "is required to remove '$dst' before moving."))) - end - end + checkfor_mv_cp_cptree(src, dst, "copying"; remove_destination=remove_destination) FS.rename(src, dst) end diff --git a/base/stat.jl b/base/stat.jl index 1b294b7bc7934..a9d2c892fdc53 100644 --- a/base/stat.jl +++ b/base/stat.jl @@ -117,4 +117,10 @@ filesize(path...) = stat(path...).size # samefile can be used for files and directories: 11145#issuecomment-99511194 samefile(a::StatStruct, b::StatStruct) = a.device==b.device && a.inode==b.inode -samefile(a::AbstractString, b::AbstractString) = samefile(stat(a),stat(b)) +function samefile(a::AbstractString, b::AbstractString) + if ispath(a) && ispath(b) + samefile(stat(a),stat(b)) + else + return false + end +end From 43eb785b3dfc2854246f27fc9ceae30c04ec1202 Mon Sep 17 00:00:00 2001 From: peter1000 Date: Sat, 9 May 2015 06:55:03 -0300 Subject: [PATCH 3/3] Adds tests for: `mv` function #11172 --- base/file.jl | 10 ++-- base/stat.jl | 2 +- test/file.jl | 152 +++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 130 insertions(+), 34 deletions(-) diff --git a/base/file.jl b/base/file.jl index ad0dad856f7e0..dffd13bac49e2 100644 --- a/base/file.jl +++ b/base/file.jl @@ -71,19 +71,19 @@ end # The following use Unix command line facilites -# check for https://github.com/JuliaLang/julia/pull/11172#issuecomment-100391076 function checkfor_mv_cp_cptree(src::AbstractString, dst::AbstractString, txt::AbstractString; remove_destination::Bool=false) if ispath(dst) if remove_destination - # check for https://github.com/JuliaLang/julia/pull/11172#issuecomment-100391076 + # Check for issue when: (src == dst) or when one is a link to the other + # https://github.com/JuliaLang/julia/pull/11172#issuecomment-100391076 if Base.samefile(src, dst) abs_src = islink(src) ? abspath(readlink(src)) : abspath(src) abs_dst = islink(dst) ? abspath(readlink(dst)) : abspath(dst) throw(ArgumentError(string("'src' and 'dst' refer to the same file/dir.", "This is not supported.\n ", - "`src` referce to: $(abs_src)\n ", - "`dst` referce to: $(abs_dst)\n"))) + "`src` refers to: $(abs_src)\n ", + "`dst` refers to: $(abs_dst)\n"))) end rm(dst; recursive=true) else @@ -124,7 +124,7 @@ function cp(src::AbstractString, dst::AbstractString; remove_destination::Bool=f end function mv(src::AbstractString, dst::AbstractString; remove_destination::Bool=false) - checkfor_mv_cp_cptree(src, dst, "copying"; remove_destination=remove_destination) + checkfor_mv_cp_cptree(src, dst, "moving"; remove_destination=remove_destination) FS.rename(src, dst) end diff --git a/base/stat.jl b/base/stat.jl index a9d2c892fdc53..de6f29a25d430 100644 --- a/base/stat.jl +++ b/base/stat.jl @@ -115,7 +115,7 @@ filesize(path...) = stat(path...).size mtime(path...) = stat(path...).mtime ctime(path...) = stat(path...).ctime -# samefile can be used for files and directories: 11145#issuecomment-99511194 +# samefile can be used for files and directories: #11145#issuecomment-99511194 samefile(a::StatStruct, b::StatStruct) = a.device==b.device && a.inode==b.inode function samefile(a::AbstractString, b::AbstractString) if ispath(a) && ispath(b) diff --git a/test/file.jl b/test/file.jl index 3e7bfbbdd76ed..eae297a0b7391 100644 --- a/test/file.jl +++ b/test/file.jl @@ -529,25 +529,35 @@ if @unix? true : (Base.windows_version() >= Base.WINDOWS_VISTA_VER) # cp ---------------------------------------------------- mktempdir() do tmpdir # Setup new copies for the test - test_src_paths, test_new_paths = setup_dirs(tmpdir) - for (s, d) in zip(test_src_paths, test_new_paths) + maindir1 = joinpath(tmpdir, "maindir1") + maindir2 = joinpath(tmpdir, "maindir2") + mkdir(maindir1) + mkdir(maindir2) + test_src_paths1, test_new_paths1 = setup_dirs(maindir1) + test_src_paths2, test_new_paths2 = setup_dirs(maindir2) + for (s, d) in zip(test_src_paths1, test_new_paths1) + cp_follow_symlinks_false_check(s, d) + end + for (s, d) in zip(test_src_paths2, test_new_paths2) cp_follow_symlinks_false_check(s, d) end # Test require `remove_destination=true` - for s in test_src_paths - for d in test_new_paths + for s in test_src_paths1 + for d in test_new_paths2 @test_throws ArgumentError cp(s, d; remove_destination=false) @test_throws ArgumentError cp(s, d; remove_destination=false, follow_symlinks=true) end end # Test remove the existing path first and copy - for (s, d) in zip(test_src_paths, test_new_paths) + # need to use here the test_src_paths2: + # otherwise ArgumentError: 'src' and 'dst' refer to the same file/dir. + for (s, d) in zip(test_src_paths2, test_new_paths1) cp_follow_symlinks_false_check(s, d; remove_destination=true) end # Test remove the existing path first and copy an empty dir - emptydir = joinpath(tmpdir, "emptydir") + emptydir = joinpath(maindir1, "emptydir") mkdir(emptydir) - for d in test_new_paths + for d in test_new_paths1 cp(emptydir, d; remove_destination=true, follow_symlinks=false) # Expect no link because a dir is copied (follow_symlinks=false does not effect this) @test isdir(d) && !islink(d) @@ -558,18 +568,29 @@ if @unix? true : (Base.windows_version() >= Base.WINDOWS_VISTA_VER) # mv ---------------------------------------------------- mktempdir() do tmpdir # Setup new copies for the test - test_src_paths, test_new_paths = setup_dirs(tmpdir) - for (s, d) in zip(test_src_paths, test_new_paths) + maindir1 = joinpath(tmpdir, "maindir1") + maindir2 = joinpath(tmpdir, "maindir2") + mkdir(maindir1) + mkdir(maindir2) + test_src_paths1, test_new_paths1 = setup_dirs(maindir1) + test_src_paths2, test_new_paths2 = setup_dirs(maindir2) + for (s, d) in zip(test_src_paths1, test_new_paths1) + cp_follow_symlinks_false_check(s, d; remove_destination=true) + end + for (s, d) in zip(test_src_paths2, test_new_paths2) cp_follow_symlinks_false_check(s, d; remove_destination=true) end + # Test require `remove_destination=true` - for s in test_src_paths - for d in test_new_paths + for s in test_src_paths1 + for d in test_new_paths2 @test_throws ArgumentError mv(s, d; remove_destination=false) end end # Test remove the existing path first and move - for (s, d) in zip(test_src_paths, test_new_paths) + # need to use here the test_src_paths2: + # otherwise ArgumentError: 'src' and 'dst' refer to the same file/dir.This is not supported. + for (s, d) in zip(test_src_paths2, test_new_paths1) d_mv = joinpath(dirname(d), "$(basename(d))_mv") mv_check(s, d, d_mv; remove_destination=true) end @@ -614,6 +635,40 @@ if @unix? true : (Base.windows_version() >= Base.WINDOWS_VISTA_VER) mv(d, d_mv; remove_destination=true) end end + + # issue ---------------------------------------------------- + # Check for issue when: (src == dst) or when one is a link to the other + # https://github.com/JuliaLang/julia/pull/11172#issuecomment-100391076 + mktempdir() do tmpdir + test_src_paths1, test_new_paths1 = setup_dirs(tmpdir) + dirs = [joinpath(tmpdir, "src"), joinpath(tmpdir, "abs_dirlink"), joinpath(tmpdir, "rel_dirlink")] + for src in dirs + for dst in dirs + # cptree + @test_throws ArgumentError Base.cptree(src,dst; remove_destination=true, follow_symlinks=false) + @test_throws ArgumentError Base.cptree(src,dst; remove_destination=true, follow_symlinks=true) + # cp + @test_throws ArgumentError cp(src,dst; remove_destination=true, follow_symlinks=false) + @test_throws ArgumentError cp(src,dst; remove_destination=true, follow_symlinks=true) + # mv + @test_throws ArgumentError mv(src,dst; remove_destination=true) + end + end + end + # None existing src + mktempdir() do tmpdir + none_existing_src = joinpath(tmpdir, "none_existing_src") + dst = joinpath(tmpdir, "dst") + @test !ispath(none_existing_src) + # cptree + @test_throws ArgumentError Base.cptree(none_existing_src,dst; remove_destination=true, follow_symlinks=false) + @test_throws ArgumentError Base.cptree(none_existing_src,dst; remove_destination=true, follow_symlinks=true) + # cp + @test_throws Base.UVError cp(none_existing_src,dst; remove_destination=true, follow_symlinks=false) + @test_throws Base.UVError cp(none_existing_src,dst; remove_destination=true, follow_symlinks=true) + # mv + @test_throws Base.UVError mv(none_existing_src,dst; remove_destination=true) + end end # issue #10506 #10434 @@ -682,20 +737,30 @@ end # cp ---------------------------------------------------- mktempdir() do tmpdir # Setup new copies for the test - test_src_paths, test_new_paths, file_txt = setup_files(tmpdir) - for (s, d) in zip(test_src_paths, test_new_paths) - cp_follow_symlinks_false_check(s, d, file_txt) + maindir1 = joinpath(tmpdir, "maindir1") + maindir2 = joinpath(tmpdir, "maindir2") + mkdir(maindir1) + mkdir(maindir2) + test_src_paths1, test_new_paths1, file_txt1 = setup_files(maindir1) + test_src_paths2, test_new_paths2, file_txt2 = setup_files(maindir2) + for (s, d) in zip(test_src_paths1, test_new_paths1) + cp_follow_symlinks_false_check(s, d, file_txt1) + end + for (s, d) in zip(test_src_paths2, test_new_paths2) + cp_follow_symlinks_false_check(s, d, file_txt2) end # Test require `remove_destination=true` - for s in test_src_paths - for d in test_new_paths + for s in test_src_paths1 + for d in test_new_paths2 @test_throws ArgumentError cp(s, d; remove_destination=false) @test_throws ArgumentError cp(s, d; remove_destination=false, follow_symlinks=true) end end - # Test remove the existing path first and copy: follow_symlinks=false - for (s, d) in zip(test_src_paths, test_new_paths) - cp_follow_symlinks_false_check(s, d, file_txt; remove_destination=true) + # Test remove the existing path first and copy + # need to use here the test_src_paths2: + # otherwise ArgumentError: 'src' and 'dst' refer to the same file/dir.This is not supported. + for (s, d) in zip(test_src_paths2, test_new_paths1) + cp_follow_symlinks_false_check(s, d, file_txt2; remove_destination=true) end # Test remove the existing path first and copy an other file otherfile = joinpath(tmpdir, "otherfile.txt") @@ -703,7 +768,7 @@ end open(otherfile, "w") do f write(f, otherfile_content) end - for d in test_new_paths + for d in test_new_paths1 cp(otherfile, d; remove_destination=true, follow_symlinks=false) # Expect no link because a file is copied (follow_symlinks=false does not effect this) @test isfile(d) && !islink(d) @@ -714,20 +779,30 @@ end # mv ---------------------------------------------------- mktempdir() do tmpdir # Setup new copies for the test - test_src_paths, test_new_paths, file_txt = setup_files(tmpdir) - for (s, d) in zip(test_src_paths, test_new_paths) - cp_follow_symlinks_false_check(s, d, file_txt; remove_destination=true) + maindir1 = joinpath(tmpdir, "maindir1") + maindir2 = joinpath(tmpdir, "maindir2") + mkdir(maindir1) + mkdir(maindir2) + test_src_paths1, test_new_paths1, file_txt1 = setup_files(maindir1) + test_src_paths2, test_new_paths2, file_txt2 = setup_files(maindir2) + for (s, d) in zip(test_src_paths1, test_new_paths1) + cp_follow_symlinks_false_check(s, d, file_txt1) + end + for (s, d) in zip(test_src_paths2, test_new_paths2) + cp_follow_symlinks_false_check(s, d, file_txt2) end # Test require `remove_destination=true` - for s in test_src_paths - for d in test_new_paths + for s in test_src_paths1 + for d in test_new_paths2 @test_throws ArgumentError mv(s, d; remove_destination=false) end end # Test remove the existing path first and move - for (s, d) in zip(test_src_paths, test_new_paths) + # need to use here the test_src_paths2: + # otherwise ArgumentError: 'src' and 'dst' refer to the same file/dir.This is not supported. + for (s, d) in zip(test_src_paths2, test_new_paths1) d_mv = joinpath(dirname(d), "$(basename(d))_mv") - mv_check(s, d, d_mv, file_txt; remove_destination=true) + mv_check(s, d, d_mv, file_txt2; remove_destination=true) end end @@ -796,8 +871,29 @@ end mv(d, d_mv; remove_destination=true) end end + # issue ---------------------------------------------------- + # Check for issue when: (src == dst) or when one is a link to the other + # https://github.com/JuliaLang/julia/pull/11172#issuecomment-100391076 + mktempdir() do tmpdir + test_src_paths, test_new_paths, file_txt = setup_files(tmpdir) + files = [joinpath(tmpdir, "srcfile.txt"), joinpath(tmpdir, "abs_filelink"), joinpath(tmpdir, "rel_filelink")] + for src in files + for dst in files + # cptree + @test_throws ArgumentError Base.cptree(src,dst; remove_destination=true, follow_symlinks=false) + @test_throws ArgumentError Base.cptree(src,dst; remove_destination=true, follow_symlinks=true) + # cp + @test_throws ArgumentError cp(src,dst; remove_destination=true, follow_symlinks=false) + @test_throws ArgumentError cp(src,dst; remove_destination=true, follow_symlinks=true) + # mv + @test_throws ArgumentError mv(src,dst; remove_destination=true) + end + end + end + # None existing src: not needed here as it is done above with the directories. end + ################### # FILE* interface # ###################