From 7dc0511ea4be210f82abb1c82a31aec3a4fe5736 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 12 Jan 2021 18:17:02 +0900 Subject: Remove "." and ".." from Dir.glob with FNM_DOTMATCH [Bug #17280] Co-authored-by: Jeremy Evans --- dir.c | 10 +++++++-- spec/ruby/core/dir/fixtures/common.rb | 10 +++++++++ spec/ruby/core/dir/glob_spec.rb | 37 +++++++++++++++++++++--------- spec/ruby/core/dir/shared/glob.rb | 42 ++++++++++++++++++++++++++++------- test/pathname/test_pathname.rb | 2 +- test/ruby/test_dir.rb | 5 ++++- 6 files changed, 83 insertions(+), 23 deletions(-) diff --git a/dir.c b/dir.c index bedbdd9f83..48c9eaefa0 100644 --- a/dir.c +++ b/dir.c @@ -222,6 +222,7 @@ typedef enum { #define FNM_SHORTNAME 0 #endif #define FNM_GLOB_NOSORT 0x40 +#define FNM_GLOB_SKIPDOT 0x80 #define FNM_NOMATCH 1 #define FNM_ERROR 2 @@ -2413,6 +2414,10 @@ glob_helper( } return status; } + + int skipdot = (flags & FNM_GLOB_SKIPDOT); + flags |= FNM_GLOB_SKIPDOT; + while ((dp = glob_getent(&globent, flags, enc)) != NULL) { char *buf; rb_pathtype_t new_pathtype = path_unknown; @@ -2423,11 +2428,12 @@ glob_helper( name = dp->d_name; namlen = dp->d_namlen; - if (recursive && name[0] == '.') { + if (name[0] == '.') { ++dotfile; if (namlen == 1) { /* unless DOTMATCH, skip current directories not to recurse infinitely */ - if (!(flags & FNM_DOTMATCH)) continue; + if (recursive && !(flags & FNM_DOTMATCH)) continue; + if (skipdot) continue; ++dotfile; new_pathtype = path_directory; /* force to skip stat/lstat */ } diff --git a/spec/ruby/core/dir/fixtures/common.rb b/spec/ruby/core/dir/fixtures/common.rb index 71b1438b72..a1ea3db215 100644 --- a/spec/ruby/core/dir/fixtures/common.rb +++ b/spec/ruby/core/dir/fixtures/common.rb @@ -169,4 +169,14 @@ module DirSpecs subdir_two ] end + + if RUBY_VERSION > '3.1' + def self.expected_glob_paths + expected_paths - ['..'] + end + else + def self.expected_glob_paths + expected_paths + end + end end diff --git a/spec/ruby/core/dir/glob_spec.rb b/spec/ruby/core/dir/glob_spec.rb index 9b6e2b2d3d..61553ab0b1 100644 --- a/spec/ruby/core/dir/glob_spec.rb +++ b/spec/ruby/core/dir/glob_spec.rb @@ -39,7 +39,7 @@ describe "Dir.glob" do end it "matches both dot and non-dotfiles with '*' and option File::FNM_DOTMATCH" do - Dir.glob('*', File::FNM_DOTMATCH).sort.should == DirSpecs.expected_paths + Dir.glob('*', File::FNM_DOTMATCH).sort.should == DirSpecs.expected_glob_paths end it "matches files with any beginning with '*' and option File::FNM_DOTMATCH" do @@ -47,7 +47,7 @@ describe "Dir.glob" do end it "matches any files in the current directory with '**' and option File::FNM_DOTMATCH" do - Dir.glob('**', File::FNM_DOTMATCH).sort.should == DirSpecs.expected_paths + Dir.glob('**', File::FNM_DOTMATCH).sort.should == DirSpecs.expected_glob_paths end it "recursively matches any subdirectories except './' or '../' with '**/' from the current directory and option File::FNM_DOTMATCH" do @@ -70,16 +70,31 @@ describe "Dir.glob" do Dir.glob('**/', File::FNM_DOTMATCH).sort.should == expected end - it "recursively matches files and directories in nested dot subdirectory with 'nested/**/*' from the current directory and option File::FNM_DOTMATCH" do - expected = %w[ - nested/. - nested/.dotsubir - nested/.dotsubir/. - nested/.dotsubir/.dotfile - nested/.dotsubir/nondotfile - ] + ruby_version_is ''...'3.1' do + it "recursively matches files and directories in nested dot subdirectory with 'nested/**/*' from the current directory and option File::FNM_DOTMATCH" do + expected = %w[ + nested/. + nested/.dotsubir + nested/.dotsubir/. + nested/.dotsubir/.dotfile + nested/.dotsubir/nondotfile + ] - Dir.glob('nested/**/*', File::FNM_DOTMATCH).sort.should == expected.sort + Dir.glob('nested/**/*', File::FNM_DOTMATCH).sort.should == expected.sort + end + end + + ruby_version_is '3.1' do + it "recursively matches files and directories in nested dot subdirectory except . with 'nested/**/*' from the current directory and option File::FNM_DOTMATCH" do + expected = %w[ + nested/. + nested/.dotsubir + nested/.dotsubir/.dotfile + nested/.dotsubir/nondotfile + ] + + Dir.glob('nested/**/*', File::FNM_DOTMATCH).sort.should == expected.sort + end end # This is a separate case to check **/ coming after a constant diff --git a/spec/ruby/core/dir/shared/glob.rb b/spec/ruby/core/dir/shared/glob.rb index f6d41ba209..c35f7d71ca 100644 --- a/spec/ruby/core/dir/shared/glob.rb +++ b/spec/ruby/core/dir/shared/glob.rb @@ -121,8 +121,16 @@ describe :dir_glob, shared: true do Dir.send(@method, 'special/test\{1\}/*').should == ['special/test{1}/file[1]'] end - it "matches dotfiles with '.*'" do - Dir.send(@method, '.*').sort.should == %w|. .. .dotfile .dotsubdir|.sort + ruby_version_is ''...'3.1' do + it "matches dotfiles with '.*'" do + Dir.send(@method, '.*').sort.should == %w|. .. .dotfile .dotsubdir|.sort + end + end + + ruby_version_is '3.1' do + it "matches dotfiles except .. with '.*'" do + Dir.send(@method, '.*').sort.should == %w|. .dotfile .dotsubdir|.sort + end end it "matches non-dotfiles with '*'" do @@ -167,8 +175,16 @@ describe :dir_glob, shared: true do Dir.send(@method, '**').sort.should == expected end - it "matches dotfiles in the current directory with '.**'" do - Dir.send(@method, '.**').sort.should == %w|. .. .dotsubdir .dotfile|.sort + ruby_version_is ''...'3.1' do + it "matches dotfiles in the current directory with '.**'" do + Dir.send(@method, '.**').sort.should == %w|. .. .dotsubdir .dotfile|.sort + end + end + + ruby_version_is '3.1' do + it "matches dotfiles in the current directory except .. with '.**'" do + Dir.send(@method, '.**').sort.should == %w|. .dotsubdir .dotfile|.sort + end end it "recursively matches any nondot subdirectories with '**/'" do @@ -189,9 +205,19 @@ describe :dir_glob, shared: true do Dir.send(@method, '**/').sort.should == expected end - it "recursively matches any subdirectories including ./ and ../ with '.**/'" do - Dir.chdir("#{DirSpecs.mock_dir}/subdir_one") do - Dir.send(@method, '.**/').sort.should == %w|./ ../|.sort + ruby_version_is ''...'3.1' do + it "recursively matches any subdirectories including ./ and ../ with '.**/'" do + Dir.chdir("#{DirSpecs.mock_dir}/subdir_one") do + Dir.send(@method, '.**/').sort.should == %w|./ ../|.sort + end + end + end + + ruby_version_is '3.1' do + it "recursively matches any subdirectories including ./ with '.**/'" do + Dir.chdir("#{DirSpecs.mock_dir}/subdir_one") do + Dir.send(@method, '.**/').should == ['./'] + end end end @@ -234,7 +260,7 @@ describe :dir_glob, shared: true do end it "matches dot or non-dotfiles with '{,.}*'" do - Dir.send(@method, '{,.}*').sort.should == DirSpecs.expected_paths + Dir.send(@method, '{,.}*').sort.should == DirSpecs.expected_glob_paths end it "respects the order of {} expressions, expanding left most first" do diff --git a/test/pathname/test_pathname.rb b/test/pathname/test_pathname.rb index 43cef4849f..2c07f77511 100644 --- a/test/pathname/test_pathname.rb +++ b/test/pathname/test_pathname.rb @@ -1266,7 +1266,7 @@ class TestPathname < Test::Unit::TestCase open("f", "w") {|f| f.write "abc" } Dir.chdir("/") { assert_equal( - [Pathname("."), Pathname(".."), Pathname("f")], + [Pathname("."), Pathname("f")], Pathname.glob("*", File::FNM_DOTMATCH, base: dir).sort) } } diff --git a/test/ruby/test_dir.rb b/test/ruby/test_dir.rb index e5bcbeac73..bc91be4abf 100644 --- a/test/ruby/test_dir.rb +++ b/test/ruby/test_dir.rb @@ -165,7 +165,7 @@ class TestDir < Test::Unit::TestCase end def test_glob - assert_equal((%w(. ..) + ("a".."z").to_a).map{|f| File.join(@root, f) }, + assert_equal((%w(.) + ("a".."z").to_a).map{|f| File.join(@root, f) }, Dir.glob(File.join(@root, "*"), File::FNM_DOTMATCH)) assert_equal([@root] + ("a".."z").map {|f| File.join(@root, f) }, Dir.glob([@root, File.join(@root, "*")])) @@ -203,6 +203,9 @@ class TestDir < Test::Unit::TestCase Dir.chdir(@root) do assert_include(Dir.glob("a/**/*", File::FNM_DOTMATCH), "a/.", bug8006) + Dir.mkdir("a/b") + assert_not_include(Dir.glob("a/**/*", File::FNM_DOTMATCH), "a/b/.") + FileUtils.mkdir_p("a/b/c/d/e/f") assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/e/f"), bug6977) assert_equal(["a/b/c/d/e/f"], Dir.glob("a/**/d/e/f"), bug6977) -- cgit v1.2.1