RELNOTES: Symlink dirents of directories containing a file named "DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN" will *not* be traversed for transitive target patterns. The motivation here is to allow directories that intentionally contain wonky symlinks (e.g. foo/bar -> foo) to opt out of being consumed by Blaze. For example, given

<workspace>/foo
  bar
  bad -> .
  DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN

the 'bad' symlink will *not* be traversed by the pattern '//foo/...'.

--
MOS_MIGRATED_REVID=107738930
diff --git a/site/docs/bazel-user-manual.html b/site/docs/bazel-user-manual.html
index ea925e1..1460d8a 100644
--- a/site/docs/bazel-user-manual.html
+++ b/site/docs/bazel-user-manual.html
@@ -396,6 +396,12 @@
   //foo/...:all     Matches all rules in all packages beneath directory 'foo'.
   //foo/...           (ditto)
 
+  By default, directory symlinks are followed when performing this recursive traversal. But we
+  understand that your workspace may intentionally contain directories with weird symlink structures
+  that you don't want consumed. As such, if a directory has a file named
+  'DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN' then symlinks
+  in that directory won't be followed when evaluating recursive target patterns.
+
 Working-directory relative forms:  (assume cwd = 'workspace/foo')
 
   Target patterns which do not begin with '//' are taken relative to
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/target-syntax.txt b/src/main/java/com/google/devtools/build/lib/runtime/commands/target-syntax.txt
index b2d7df8..3eec42e 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/target-syntax.txt
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/target-syntax.txt
@@ -25,6 +25,14 @@
   //foo/...:all     Matches all rules in all packages beneath directory 'foo'.
   //foo/...           (ditto)
 
+  By default, directory symlinks are followed when performing this recursive
+  traversal. But we understand that your workspace may intentionally contain
+  directories with weird symlink structures that you don't want consumed. As
+  such, if a directory has a file named
+  'DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN'
+  then symlinks in that directory won't be followed when evaluating recursive
+  target patterns.
+
 Working-directory relative forms:  (assume cwd = 'workspace/foo')
 
   Target patterns which do not begin with '//' are taken relative to
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveDirectoryTraversalFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveDirectoryTraversalFunction.java
index 4ee7430..5c4a434 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveDirectoryTraversalFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/RecursiveDirectoryTraversalFunction.java
@@ -48,6 +48,8 @@
  */
 abstract class RecursiveDirectoryTraversalFunction
     <TVisitor extends RecursiveDirectoryTraversalFunction.Visitor, TReturn> {
+  private static final String SENTINEL_FILE_NAME_FOR_NOT_TRAVERSING_SYMLINKS =
+      "DONT_FOLLOW_SYMLINKS_WHEN_TRAVERSING_THIS_DIRECTORY_VIA_A_RECURSIVE_TARGET_PATTERN";
 
   /**
    * Returned from {@link #visitDirectory} if its {@code recursivePkgKey} is a symlink or not a
@@ -232,16 +234,18 @@
     }
 
     List<SkyKey> childDeps = Lists.newArrayList();
+    boolean followSymlinks = shouldFollowSymlinksWhenTraversing(dirListingValue.getDirents());
     for (Dirent dirent : dirListingValue.getDirents()) {
-      if (dirent.getType() != Type.DIRECTORY && dirent.getType() != Type.SYMLINK) {
+      Type type = dirent.getType();
+      if (type != Type.DIRECTORY
+          && (type != Type.SYMLINK || (type == Type.SYMLINK && !followSymlinks))) {
         // Non-directories can never host packages. Symlinks to non-directories are weeded out at
         // the next level of recursion when we check if its FileValue is a directory. This is slower
         // if there are a lot of symlinks in the tree, but faster if there are only a few, which is
         // the case most of the time.
         //
-        // We are not afraid of weird symlink structure here: cyclical ones are diagnosed by
-        // FileValue and ones that give rise to infinite directory trees work just like they do with
-        // globbing: they work until a certain level of nesting, after which they fail.
+        // We are not afraid of weird symlink structure here: both cyclical ones and ones that give
+        // rise to infinite directory trees are diagnosed by FileValue.
         continue;
       }
       String basename = dirent.getName();
@@ -282,6 +286,20 @@
     return aggregateWithSubdirectorySkyValues(visitor, subdirectorySkyValues);
   }
 
+  private static boolean shouldFollowSymlinksWhenTraversing(Dirents dirents) {
+    for (Dirent dirent : dirents) {
+      // This is a specical sentinel file whose existence tells Blaze not to follow symlinks when
+      // recursively traversing through this directory.
+      //
+      // This admittedly ugly feature is used to support workspaces with directories with weird
+      // symlink structures that aren't intended to be consumed by Blaze.
+      if (dirent.getName().equals(SENTINEL_FILE_NAME_FOR_NOT_TRAVERSING_SYMLINKS)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   // Ignore all errors in traversal and return an empty value.
   private TReturn reportErrorAndReturn(String errorPrefix, Exception e,
       PathFragment rootRelativePath, EventHandler handler) {