diff --git a/docker/utils/build.py b/docker/utils/build.py index 1622ec35..894b2993 100644 --- a/docker/utils/build.py +++ b/docker/utils/build.py @@ -93,7 +93,7 @@ def walk(root, patterns, default=True): # Whether this file is implicitely included / excluded. matched = default if hit is None else hit sub = list(filter(lambda p: p[1], sub)) - if os.path.isdir(cur): + if os.path.isdir(cur) and not os.path.islink(cur): # Entirely skip directories if there are no chance any subfile will # be included. if all(not p[0] for p in sub) and not matched: diff --git a/tests/unit/utils_test.py b/tests/unit/utils_test.py index 56800f9e..00456e8c 100644 --- a/tests/unit/utils_test.py +++ b/tests/unit/utils_test.py @@ -1058,6 +1058,21 @@ class TarTest(unittest.TestCase): assert tar_data.getnames() == ['th.txt'] assert tar_data.getmember('th.txt').mtime == -3600 + @pytest.mark.skipif(IS_WINDOWS_PLATFORM, reason='No symlinks on Windows') + def test_tar_directory_link(self): + dirs = ['a', 'b', 'a/c'] + files = ['a/hello.py', 'b/utils.py', 'a/c/descend.py'] + base = make_tree(dirs, files) + self.addCleanup(shutil.rmtree, base) + os.symlink(os.path.join(base, 'b'), os.path.join(base, 'a/c/b')) + with tar(base) as archive: + tar_data = tarfile.open(fileobj=archive) + names = tar_data.getnames() + for member in dirs + files: + assert member in names + assert 'a/c/b' in names + assert 'a/c/b/utils.py' not in names + class FormatEnvironmentTest(unittest.TestCase): def test_format_env_binary_unicode_value(self):