... instead of any StandardError.
To behave like the standard `rm` command, it should only ignore
exceptions about not existing files, not every exception. This should
make debugging some errors easier, because the expectation is that `rm
-rf` will succeed if and only if, all given files (previously existent
or not) are removed. However, due to this exception swallowing, this is
not always the case.
From the `rm` man page
> COMPATIBILITY
>
> The rm utility differs from historical implementations in that the -f
> option only masks attempts to remove non-existent files instead of
> masking a large variety of errors.
https://github.com/ruby/fileutils/commit/fa65d676ec
Co-Authored-By: David Rodríguez <deivid.rodriguez@riseup.net>
The ensure in postorder_traverse was added for [Bug #6756].
The intention was to try to delete the parent directory if it failed to
get the children. (It may be possible to delete the directory if it is
empty.)
However, the ensure region rescue'ed not only "failure to get children"
but also "failure to delete each child". Thus, the following raised
Errno::ENOTEMPTY, but we expect it to raise Errno::EACCES.
```
$ mkdir foo
$ touch foo/bar
$ chmod 555 foo
$ ruby -rfileutils -e 'FileUtils.rm_rf("foo")'
```
This changeset narrows the ensure region so that it rescues only
"failure to get children".
https://github.com/ruby/fileutils/commit/ec5d3b84ea
I think it's debatable which is the most common usage of
`FileUtils.mkdir_p`, but even assuming the most common use case is
creating a folder when it doesn't previously exist but the parent does,
this optimization doesn't seem to have a noticiable effect there while
harming other use cases.
For benchmarks, I created this script
```ruby
require "benchmark/ips"
Benchmark.ips do |x|
x.report("old mkdir_p - exists") do
FileUtils.mkdir_p "/tmp"
end
x.report("new_mkdir_p - exists") do
FileUtils.mkdir_p_new "/tmp"
end
x.compare!
end
FileUtils.rm_rf "/tmp/foo"
Benchmark.ips do |x|
x.report("old mkdir_p - doesnt exist, parent exists") do
FileUtils.mkdir_p "/tmp/foo"
FileUtils.rm_rf "/tmp/foo"
end
x.report("new_mkdir_p - doesnt exist, parent exists") do
FileUtils.mkdir_p_new "/tmp/foo"
FileUtils.rm_rf "/tmp/foo"
end
x.compare!
end
Benchmark.ips do |x|
x.report("old mkdir_p - doesnt exist, parent either") do
FileUtils.mkdir_p "/tmp/foo/bar"
FileUtils.rm_rf "/tmp/foo"
end
x.report("new_mkdir_p - doesnt exist, parent either") do
FileUtils.mkdir_p_new "/tmp/foo/bar"
FileUtils.rm_rf "/tmp/foo"
end
x.compare!
end
Benchmark.ips do |x|
x.report("old mkdir_p - more levels") do
FileUtils.mkdir_p "/tmp/foo/bar/baz"
FileUtils.rm_rf "/tmp/foo"
end
x.report("new_mkdir_p - more levels") do
FileUtils.mkdir_p_new "/tmp/foo/bar/baz"
FileUtils.rm_rf "/tmp/foo"
end
x.compare!
end
```
and copied the method with the "optimization" removed as
`FileUtils.mkdir_p_new`. The results are as below:
```
Warming up --------------------------------------
old mkdir_p - exists 15.914k i/100ms
new_mkdir_p - exists 46.512k i/100ms
Calculating -------------------------------------
old mkdir_p - exists 161.461k (± 3.2%) i/s - 811.614k in 5.032315s
new_mkdir_p - exists 468.192k (± 2.9%) i/s - 2.372M in 5.071225s
Comparison:
new_mkdir_p - exists: 468192.1 i/s
old mkdir_p - exists: 161461.0 i/s - 2.90x (± 0.00) slower
Warming up --------------------------------------
old mkdir_p - doesnt exist, parent exists
2.142k i/100ms
new_mkdir_p - doesnt exist, parent exists
1.961k i/100ms
Calculating -------------------------------------
old mkdir_p - doesnt exist, parent exists
21.242k (± 6.7%) i/s - 107.100k in 5.069206s
new_mkdir_p - doesnt exist, parent exists
19.682k (± 4.2%) i/s - 100.011k in 5.091961s
Comparison:
old mkdir_p - doesnt exist, parent exists: 21241.7 i/s
new_mkdir_p - doesnt exist, parent exists: 19681.7 i/s - same-ish: difference falls within error
Warming up --------------------------------------
old mkdir_p - doesnt exist, parent either
945.000 i/100ms
new_mkdir_p - doesnt exist, parent either
1.002k i/100ms
Calculating -------------------------------------
old mkdir_p - doesnt exist, parent either
9.689k (± 4.4%) i/s - 49.140k in 5.084342s
new_mkdir_p - doesnt exist, parent either
10.806k (± 4.6%) i/s - 54.108k in 5.020714s
Comparison:
new_mkdir_p - doesnt exist, parent either: 10806.3 i/s
old mkdir_p - doesnt exist, parent either: 9689.3 i/s - 1.12x (± 0.00) slower
Warming up --------------------------------------
old mkdir_p - more levels
702.000 i/100ms
new_mkdir_p - more levels
775.000 i/100ms
Calculating -------------------------------------
old mkdir_p - more levels
7.046k (± 3.5%) i/s - 35.802k in 5.087548s
new_mkdir_p - more levels
7.685k (± 5.5%) i/s - 38.750k in 5.061351s
Comparison:
new_mkdir_p - more levels: 7685.1 i/s
old mkdir_p - more levels: 7046.4 i/s - same-ish: difference falls within error
```
I think it's better to keep the code simpler is the optimization is not
so clear like in this case.
https://github.com/ruby/fileutils/commit/e842a0e70e
Previously, if creating the directory directly didn't work
and the directory didn't exist, mkdir_p would create all
directories from the root. This modifies the approach to
check whether the directory exists when walking up the
directory tree from the argument, and once you have found an
intermediate directory that exists, you only need to create
directories under it.
This approach has a couple advantages:
1) It performs better when most directories in path already exist,
and that will be true for most usage of mkdir_p, as mkdir_p is
usually called with paths where the first few directories exist
and only the last directory or last few directories do not.
2) It works in file-system access limited environments such as
when unveil(2) is used on OpenBSD. In these environments, if
/foo/bar/baz exists and is unveiled, you can do
`mkdir /foo/bar/baz/xyz` but `mkdir /foo` and `mkdir /foo/bar` raise
Errno::ENOENT.
https://github.com/ruby/fileutils/commit/ec0c229e78