diff --git a/CHANGES.rst b/CHANGES.rst index b3e7efc..b71cd38 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,6 +20,8 @@ New features: Bug fixes: +- Avoid following symbolic link targets when checking file types in ``iter_tree_files(follow_links=False)``. + - Honor `on_error` when opening a directory fails during tree traversal. - `Pull #123`_: Ignore invalid gitignore bracket ranges for `GitIgnoreSpec`. diff --git a/CHANGES_1.in.rst b/CHANGES_1.in.rst index 2267bb6..64eb8fe 100644 --- a/CHANGES_1.in.rst +++ b/CHANGES_1.in.rst @@ -17,6 +17,8 @@ New features: Bug fixes: +- Avoid following symbolic link targets when checking file types in ``iter_tree_files(follow_links=False)``. + - Honor `on_error` when opening a directory fails during tree traversal. - `Pull #123`_: Ignore invalid gitignore bracket ranges for `GitIgnoreSpec`. diff --git a/README-dist.rst b/README-dist.rst index 7dcca83..2497721 100644 --- a/README-dist.rst +++ b/README-dist.rst @@ -236,6 +236,8 @@ Major changes: Bug fixes: +- Avoid following symbolic link targets when checking file types in ``iter_tree_files(follow_links=False)``. + - `Pull #123`_: Ignore invalid gitignore bracket ranges for `GitIgnoreSpec`. diff --git a/pathspec/util.py b/pathspec/util.py index 52782d4..2332cb5 100644 --- a/pathspec/util.py +++ b/pathspec/util.py @@ -476,7 +476,7 @@ def _iter_tree_files_next( # files. yield from _iter_tree_files_next(root_full, node_rel, memo, on_error, follow_links) - elif node_ent.is_file(): + elif node_ent.is_file(follow_symlinks=follow_links): # Child node is a file, yield it. yield node_rel diff --git a/tests/test_01_util.py b/tests/test_01_util.py index b0e4ff8..f84d5b3 100644 --- a/tests/test_01_util.py +++ b/tests/test_01_util.py @@ -624,6 +624,31 @@ def test_02_link_8_no_follow_links_2_files(self): 'DirX', ]))) + def test_02_link_9_no_follow_loop_links_2_files(self): + """ + Unfollowed links with cyclic targets are yielded without inspecting targets. + """ + self.require_symlink() + for case, links in [ + ('Self', [('Loop', 'Loop')]), + ('Pair', [('First', 'Second'), ('Second', 'First')]), + ]: + with self.subTest(case=case): + self.make_dirs([case, f'{case}/Dir']) + self.make_files([f'{case}/kept.txt', f'{case}/Dir/child.txt']) + self.make_links([ + (f'{case}/{link}', f'{case}/{target}') + for link, target in links + ]) + errors = [] + results = set(iter_tree_files( + self.temp_dir / case, follow_links=False, on_error=errors.append, + )) + self.assertEqual(results, { + 'kept.txt', ospath('Dir/child.txt'), *[link for link, _ in links], + }) + self.assertEqual(errors, []) + def test_03_subdir_1_from_filesystem_root(self): """ raverse only the requested subtree when the root ends in a separator.