Skip to content

Commit 2da9247

Browse files
author
José Valim
committed
Remove Unix specific behaviour from File.cp/3 and File.cr_r/3
Previously those functions would behave as in their Unix counterpart where copying behaved differently depending if the destination was an existing directory or not. We have explicitly disallowed this behaviour and added the proper notes to the docs. Closes #2024.
1 parent eb62c3b commit 2da9247

File tree

5 files changed

+55
-146
lines changed

5 files changed

+55
-146
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
* Bug fixes
1010
* [Atom] Inspect `:...` and `:foo@bar` without quoting
11-
* [File] Respect source directories terminating with "/" in `File.cp_r/3` with the same semantics as Unix
1211
* [Keyword] The list `[1, 2, three: :four]` now correctly expands to `[1, 2, {:three, :four}]`
1312
* [Kernel] Ensure undefined `@attributes` shows proper stacktrace in warnings
1413
* [Kernel] Guarantee nullary funs/macros are allowed in guards
@@ -23,6 +22,7 @@
2322

2423
* Backwards incompatible changes
2524
* [Dict] Implementations of `equal?/2` and `merge/2` in `HashDict` and `ListDict` are no longer polymorphic. To get polymorphism, use the functions in `Dict` instead
25+
* [File] `File.cp/3` and `File.cp_r/3` no longer carry Unix semantics where the function behaves differently if the destination is an existing previous directory or not. It now always copies source to destination, doing it recursively in the latter
2626
* [IEx] IEx now loads the `.iex.exs` file instead of `.iex`
2727
* [Kernel] Remove `**` from the list of allowed operators
2828
* [Kernel] Limit sigils delimiters to one of the following: `<>`, `{}`, `[]`, `()`, `||`, `//`, `"` and `'`

lib/elixir/lib/file.ex

Lines changed: 18 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -329,11 +329,6 @@ defmodule File do
329329
@doc """
330330
Copies the contents in `source` to `destination` preserving its mode.
331331
332-
Similar to the command `cp` in Unix systems, this function
333-
behaves differently depending if `destination` is a directory
334-
or not. In particular, if `destination` is a directory, it
335-
copies the contents of `source` to `destination/source`.
336-
337332
If a file already exists in the destination, it invokes a
338333
callback which should return `true` if the existing file
339334
should be overwritten, `false` otherwise. It defaults to return `true`.
@@ -344,17 +339,14 @@ defmodule File do
344339
If you want to copy contents from an io device to another device
345340
or do a straight copy from a source to a destination without
346341
preserving modes, check `copy/3` instead.
342+
343+
Note: The command `cp` in Unix systems behaves differently depending
344+
if `destination` is an existing directory or not. We have chosen to
345+
explicitly disallow this behaviour. If destination is a directory, an
346+
error will be returned.
347347
"""
348348
def cp(source, destination, callback \\ fn(_, _) -> true end) do
349-
output =
350-
if dir?(destination) do
351-
mkdir(destination)
352-
do_cp_join(destination, source)
353-
else
354-
destination
355-
end
356-
357-
case do_cp_file(source, output, callback, []) do
349+
case do_cp_file(source, destination, callback, []) do
358350
{ :error, reason, _ } -> { :error, reason }
359351
_ -> :ok
360352
end
@@ -377,14 +369,9 @@ defmodule File do
377369
@doc %S"""
378370
Copies the contents in source to destination.
379371
380-
Similar to the command `cp -r` in Unix systems,
381-
this function behaves differently depending
382-
if `source` and `destination` are files or directories.
383-
384-
If both are files, it simply copies `source` to
385-
`destination`. However, if `destination` is a directory,
386-
it copies the contents of `source` to `destination/source`
387-
recursively.
372+
If the source is a file, it copies `source` to
373+
`destination`. If the source is a directory, it copies
374+
the contents inside source into the destination.
388375
389376
If a file already exists in the destination,
390377
it invokes a callback which should return
@@ -404,36 +391,26 @@ defmodule File do
404391
success with all files and directories copied in no
405392
specific order, `{ :error, reason }` otherwise.
406393
407-
## Examples
394+
Note: The command `cp` in Unix systems behaves differently
395+
depending if `destination` is an existing directory or not.
396+
We have chosen to explicitly disallow this behaviour.
408397
409-
# Copies "a.txt" to "tmp/a.txt"
410-
File.cp_r "a.txt", "tmp"
398+
## Examples
411399
412-
# Copies all files in "samples" to "tmp/samples"
413-
File.cp_r "samples", "tmp"
400+
# Copies "a.txt" to "tmp"
401+
File.cp_r "a.txt", "tmp.txt"
414402
415403
# Copies all files in "samples" to "tmp"
416-
File.cp_r "samples/", "tmp"
404+
File.cp_r "samples", "tmp"
417405
418406
# Same as before, but asks the user how to proceed in case of conflicts
419-
File.cp_r "samples/", "tmp", fn(source, destination) ->
407+
File.cp_r "samples", "tmp", fn(source, destination) ->
420408
IO.gets("Overwriting #{destination} by #{source}. Type y to confirm.") == "y"
421409
end
422410
423411
"""
424412
def cp_r(source, destination, callback \\ fn(_, _) -> true end) when is_function(callback) do
425-
output =
426-
if dir?(destination) || dir?(source) do
427-
mkdir(destination)
428-
case do_cp_last(source) do
429-
?/ -> destination
430-
_ -> do_cp_join(destination, source)
431-
end
432-
else
433-
destination
434-
end
435-
436-
case do_cp_r(source, output, callback, []) do
413+
case do_cp_r(source, destination, callback, []) do
437414
{ :error, _, _ } = error -> error
438415
res -> { :ok, res }
439416
end
@@ -454,23 +431,6 @@ defmodule File do
454431
end
455432
end
456433

457-
defp do_cp_last(source) when is_atom(source), do: :lists.last(atom_to_list(source))
458-
defp do_cp_last(source) when is_list(source), do: do_cp_last(:lists.last(source))
459-
defp do_cp_last(source) when is_binary(source), do: :binary.last(source)
460-
defp do_cp_last(source) when is_integer(source), do: source
461-
462-
defp do_cp_join(destination, source) when source in [".", '.'] do
463-
destination
464-
end
465-
466-
defp do_cp_join(destination, source) do
467-
case FN.basename(source) do
468-
dot when dot in ["..", '..'] -> do_cp_join(destination, FN.dirname(FN.dirname(source)))
469-
dot when dot in [".", '.'] -> do_cp_join(destination, FN.dirname(source))
470-
base -> FN.join(destination, base)
471-
end
472-
end
473-
474434
# src may be a file or a directory, dest is definitely
475435
# a directory. Returns nil unless an error is found.
476436
defp do_cp_r(src, dest, callback, acc) when is_list(acc) do

0 commit comments

Comments
 (0)