pkg_tar: Fix issue #948: ignore directories shadowing a symlink
diff --git a/pkg/private/tar/tar_writer.py b/pkg/private/tar/tar_writer.py
index 6c0d926..b81f6db 100644
--- a/pkg/private/tar/tar_writer.py
+++ b/pkg/private/tar/tar_writer.py
@@ -35,6 +35,18 @@
 
 _DEBUG_VERBOSITY = 0
 
+TARFILE_MEMBER_TYPE_TO_STR = {
+    b"0": "REGTYPE",
+    b"\0": "AREGTYPE",
+    b"1": "LNKTYPE",
+    b"2": "SYMTYPE",
+    b"3": "CHRTYPE",
+    b"4": "BLKTYPE",
+    b"5": "DIRTYPE",
+    b"6": "FIFOTYPE",
+    b"7": "CONTTYPE",
+}
+
 
 class TarFileWriter(object):
   """A wrapper to write tar files."""
@@ -103,13 +115,7 @@
 
     self.tar = tarfile.open(name=name, mode=mode, fileobj=self.fileobj,
                             format=tarfile.GNU_FORMAT)
-    self.members = set()
-    self.directories = set()
-    # Preseed the added directory list with things we should not add. If we
-    # some day need to allow '.' or '/' as an explicit member of the archive,
-    # we can adjust that here based on the setting of root_directory.
-    self.directories.add('/')
-    self.directories.add('./')
+    self.existing_members = {}
     self.create_parents = create_parents
     self.allow_dups_from_deps = allow_dups_from_deps
 
@@ -119,28 +125,43 @@
   def __exit__(self, t, v, traceback):
     self.close()
 
-  def _have_added(self, path):
-    """Have we added this file before."""
-    return (path in self.members) or (path in self.directories)
+  def _existing_member_type(self, path):
+    """Retrieve an existing tar file member's type if we have added it previously,
+    return None otherwise."""
+    # Things we should not add.
+    # If we some day need to allow '.' or '/' as an explicit member of the archive,
+    # we can adjust that here based on the setting of root_directory.
+    if path == '/' or path == './':
+      return tarfile.DIRTYPE
+
+    normalized_path = path.rstrip("/")
+    return self.existing_members.get(normalized_path, None)
 
   def _addfile(self, info, fileobj=None):
     """Add a file in the tar file if there is no conflict."""
     if info.type == tarfile.DIRTYPE:
-      # Enforce the ending / for directories so we correctly deduplicate.
+      # Enforce the ending / for directories.
       if not info.name.endswith('/'):
         info.name += '/'
-    if not self.allow_dups_from_deps and self._have_added(info.name):
+    existing_member_type = self._existing_member_type(info.name)
+    if not self.allow_dups_from_deps and existing_member_type is not None:
       # Directories with different contents should get merged without warnings.
       # If they have overlapping content, the warning will be on their duplicate *files* instead
       if info.type != tarfile.DIRTYPE:
         print('Duplicate file in archive: %s, '
               'picking first occurrence' % info.name)
+      # Directories that shadow
+      elif existing_member_type != tarfile.DIRTYPE and existing_member_type != tarfile.SYMTYPE:
+        print('Directory shadows a member of type %s in archive: %s, '
+              'picking first occurrence' % (TARFILE_MEMBER_TYPE_TO_STR.get(
+                  existing_member_type, "UNKNOWN"), info.name))
+
       return
 
     self.tar.addfile(info, fileobj)
-    self.members.add(info.name)
-    if info.type == tarfile.DIRTYPE:
-      self.directories.add(info.name)
+    # Strip the trailing slash from the path so that we can detect when, for example, we are
+    # trying to overwrite a symbolic link with a directory.
+    self.existing_members[info.name.rstrip("/")] = info.type
 
   def add_directory_path(self,
                          path,
@@ -182,7 +203,8 @@
     for next_level in dirs[0:-1]:
       parent_path = parent_path + next_level + '/'
 
-      if self.create_parents and not self._have_added(parent_path):
+      if self.create_parents and self._existing_member_type(
+          parent_path) is None:
         self.add_directory_path(
           parent_path,
           uid=uid,
@@ -224,7 +246,8 @@
       return
     if name == '.':
       return
-    if not self.allow_dups_from_deps and name in self.members:
+    if not self.allow_dups_from_deps and self._existing_member_type(
+        name) is not None:
       return
 
     if mtime is None:
diff --git a/tests/tar/tar_writer_test.py b/tests/tar/tar_writer_test.py
index 7561eed..42b6b24 100644
--- a/tests/tar/tar_writer_test.py
+++ b/tests/tar/tar_writer_test.py
@@ -162,7 +162,7 @@
           "rules_pkg/tests/testdata/tar_test.tar")
       f.add_tar(input_tar_path)
       input_tar = tarfile.open(input_tar_path, "r")
-      for file_name in f.members:
+      for file_name in f.existing_members.keys():
         input_file = input_tar.getmember(file_name)
         output_file = f.tar.getmember(file_name)
         self.assertEqual(input_file.mtime, output_file.mtime)