Push the IgnoredSubdirectories abstraction a little further, everywhere other than diff awareness.

Progress towards #9037.

RELNOTES: None.
PiperOrigin-RevId: 686063047
Change-Id: If27b64213ed51f0b5f8c361c657b9bba2d7511f6
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryContext.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryContext.java
index bac46b6..6ea3f7e 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryContext.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryContext.java
@@ -16,12 +16,12 @@
 
 import com.github.difflib.patch.PatchFailedException;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.docgen.annot.DocCategory;
 import com.google.devtools.build.lib.analysis.BlazeDirectories;
 import com.google.devtools.build.lib.bazel.debug.WorkspaceRuleEvent;
 import com.google.devtools.build.lib.bazel.repository.PatchUtil;
 import com.google.devtools.build.lib.bazel.repository.downloader.DownloadManager;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.packages.Attribute;
@@ -78,7 +78,7 @@
   private final Rule rule;
   private final PathPackageLocator packageLocator;
   private final StructImpl attrObject;
-  private final ImmutableSet<PathFragment> ignoredPatterns;
+  private final IgnoredSubdirectories ignoredSubdirectories;
   private final SyscallCache syscallCache;
   private final HashMap<RepoRecordedInput.DirTree, String> recordedDirTreeInputs = new HashMap<>();
 
@@ -90,7 +90,7 @@
       Rule rule,
       PathPackageLocator packageLocator,
       Path outputDirectory,
-      ImmutableSet<PathFragment> ignoredPatterns,
+      IgnoredSubdirectories ignoredSubdirectories,
       Environment environment,
       ImmutableMap<String, String> env,
       DownloadManager downloadManager,
@@ -116,7 +116,7 @@
         /* allowWatchingPathsOutsideWorkspace= */ true);
     this.rule = rule;
     this.packageLocator = packageLocator;
-    this.ignoredPatterns = ignoredPatterns;
+    this.ignoredSubdirectories = ignoredSubdirectories;
     this.syscallCache = syscallCache;
     WorkspaceAttributeMapper attrs = WorkspaceAttributeMapper.of(rule);
     ImmutableMap.Builder<String, Object> attrBuilder = new ImmutableMap.Builder<>();
@@ -177,10 +177,8 @@
     }
     Path workspaceRoot = packageLocator.getWorkspaceFile(syscallCache).getParentDirectory();
     PathFragment relativePath = path.relativeTo(workspaceRoot);
-    for (PathFragment ignoredPattern : ignoredPatterns) {
-      if (relativePath.startsWith(ignoredPattern)) {
-        return starlarkPath;
-      }
+    if (ignoredSubdirectories.matchingEntry(relativePath) != null) {
+      return starlarkPath;
     }
     throw Starlark.errorf(
         "%s can only be applied to external paths (that is, outside the workspace or ignored in"
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryFunction.java b/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryFunction.java
index 967afd0..2f9dabc 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryFunction.java
@@ -27,6 +27,7 @@
 import com.google.devtools.build.lib.bazel.bzlmod.NonRegistryOverride;
 import com.google.devtools.build.lib.bazel.repository.RepositoryResolvedEvent;
 import com.google.devtools.build.lib.bazel.repository.downloader.DownloadManager;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.LabelConstants;
 import com.google.devtools.build.lib.cmdline.RepositoryMapping;
@@ -52,7 +53,6 @@
 import com.google.devtools.build.lib.skyframe.RepositoryMappingValue;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.Path;
-import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.SyscallCache;
 import com.google.devtools.build.skyframe.SkyFunction.Environment;
 import com.google.devtools.build.skyframe.SkyFunctionException.Transience;
@@ -222,7 +222,8 @@
     if (env.valuesMissing()) {
       return null;
     }
-    ImmutableSet<PathFragment> ignoredPatterns = checkNotNull(ignoredPackagesValue).getPatterns();
+    IgnoredSubdirectories ignoredSubdirectories =
+        checkNotNull(ignoredPackagesValue).asIgnoredSubdirectories();
 
     Map<RepoRecordedInput, String> recordedInputValues = new LinkedHashMap<>();
     try (Mutability mu = Mutability.create("Starlark repository");
@@ -231,7 +232,7 @@
                 rule,
                 packageLocator,
                 outputDirectory,
-                ignoredPatterns,
+                ignoredSubdirectories,
                 env,
                 ImmutableMap.copyOf(clientEnvironment),
                 downloadManager,
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/IgnoredSubdirectories.java b/src/main/java/com/google/devtools/build/lib/cmdline/IgnoredSubdirectories.java
index d0458af..50f1045 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/IgnoredSubdirectories.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/IgnoredSubdirectories.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
 
+import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.Objects;
@@ -29,7 +30,15 @@
 public final class IgnoredSubdirectories {
   private final ImmutableSet<PathFragment> prefixes;
 
-  public IgnoredSubdirectories(ImmutableSet<PathFragment> prefixes) {
+  public static IgnoredSubdirectories of(ImmutableSet<PathFragment> prefixes) {
+    if (prefixes.isEmpty()) {
+      return EMPTY;
+    } else {
+      return new IgnoredSubdirectories(prefixes);
+    }
+  }
+
+  private IgnoredSubdirectories(ImmutableSet<PathFragment> prefixes) {
     this.prefixes = prefixes;
   }
 
@@ -77,6 +86,11 @@
     return new IgnoredSubdirectories(filteredPrefixes);
   }
 
+  public IgnoredSubdirectories union(IgnoredSubdirectories other) {
+    return new IgnoredSubdirectories(
+        ImmutableSet.<PathFragment>builder().addAll(prefixes).addAll(other.prefixes).build());
+  }
+
   @Override
   public boolean equals(Object other) {
     if (!(other instanceof IgnoredSubdirectories)) {
@@ -91,4 +105,9 @@
   public int hashCode() {
     return prefixes.hashCode();
   }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper("IgnoredSubdirectories").add("prefixes", prefixes).toString();
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java b/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java
index b836431..e1ff359 100644
--- a/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java
+++ b/src/main/java/com/google/devtools/build/lib/cmdline/TargetPattern.java
@@ -152,11 +152,14 @@
    */
   public abstract <T, E extends Exception & QueryExceptionMarkerInterface> void eval(
       TargetPatternResolver<T> resolver,
-      InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
+      InterruptibleSupplier<IgnoredSubdirectories> ignoredSubdirectories,
       ImmutableSet<PathFragment> excludedSubdirectories,
       BatchCallback<T, E> callback,
       Class<E> exceptionClass)
-      throws TargetParsingException, E, InterruptedException, ProcessPackageDirectoryException,
+      throws TargetParsingException,
+          E,
+          InterruptedException,
+          ProcessPackageDirectoryException,
           InconsistentFilesystemException;
 
   /**
@@ -173,7 +176,7 @@
   public final <T, E extends Exception & QueryExceptionMarkerInterface>
       ListenableFuture<Void> evalAdaptedForAsync(
           TargetPatternResolver<T> resolver,
-          InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
+          InterruptibleSupplier<IgnoredSubdirectories> ignoredSubdirectories,
           ImmutableSet<PathFragment> excludedSubdirectories,
           BatchCallback<T, E> callback,
           Class<E> exceptionClass) {
@@ -206,7 +209,7 @@
    */
   public <T, E extends Exception & QueryExceptionMarkerInterface> ListenableFuture<Void> evalAsync(
       TargetPatternResolver<T> resolver,
-      InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
+      InterruptibleSupplier<IgnoredSubdirectories> ignoredSubdirectories,
       ImmutableSet<PathFragment> excludedSubdirectories,
       BatchCallback<T, E> callback,
       Class<E> exceptionClass,
@@ -276,7 +279,7 @@
     @Override
     public <T, E extends Exception & QueryExceptionMarkerInterface> void eval(
         TargetPatternResolver<T> resolver,
-        InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
+        InterruptibleSupplier<IgnoredSubdirectories> ignoredSubdirectories,
         ImmutableSet<PathFragment> excludedSubdirectories,
         BatchCallback<T, E> callback,
         Class<E> exceptionClass)
@@ -345,7 +348,7 @@
     @Override
     public <T, E extends Exception & QueryExceptionMarkerInterface> void eval(
         TargetPatternResolver<T> resolver,
-        InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
+        InterruptibleSupplier<IgnoredSubdirectories> ignoredSubdirectories,
         ImmutableSet<PathFragment> excludedSubdirectories,
         BatchCallback<T, E> callback,
         Class<E> exceptionClass)
@@ -448,7 +451,7 @@
     @Override
     public <T, E extends Exception & QueryExceptionMarkerInterface> void eval(
         TargetPatternResolver<T> resolver,
-        InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
+        InterruptibleSupplier<IgnoredSubdirectories> ignoredSubdirectories,
         ImmutableSet<PathFragment> excludedSubdirectories,
         BatchCallback<T, E> callback,
         Class<E> exceptionClass)
@@ -581,7 +584,7 @@
     @Override
     public <T, E extends Exception & QueryExceptionMarkerInterface> void eval(
         TargetPatternResolver<T> resolver,
-        InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
+        InterruptibleSupplier<IgnoredSubdirectories> ignoredSubdirectoriesSupplier,
         ImmutableSet<PathFragment> excludedSubdirectories,
         BatchCallback<T, E> callback,
         Class<E> exceptionClass)
@@ -591,17 +594,22 @@
           "Fully excluded target pattern %s should have already been filtered out (%s)",
           this,
           excludedSubdirectories);
-      IgnoredPathFragmentsInScopeOrFilteringIgnorer ignoredIntersection =
-          getAllIgnoredSubdirectoriesToExclude(ignoredSubdirectories);
-      if (warnIfFiltered(ignoredIntersection, resolver)) {
+      IgnoredSubdirectories ignoredSubdirectories = ignoredSubdirectoriesSupplier.get();
+      PathFragment matchingEntry =
+          ignoredSubdirectories.matchingEntry(directory.getPackageFragment());
+      if (warnIfFiltered(matchingEntry, resolver)) {
         return;
       }
+
+      IgnoredSubdirectories filteredIgnoredSubdirectories =
+          ignoredSubdirectories.filterForDirectory(directory.getPackageFragment());
+
       resolver.findTargetsBeneathDirectory(
           directory.getRepository(),
           getOriginalPattern(),
           directory.getPackageFragment().getPathString(),
           rulesOnly,
-          new IgnoredSubdirectories(ignoredIntersection.ignoredPathFragments()),
+          filteredIgnoredSubdirectories,
           excludedSubdirectories,
           callback,
           exceptionClass);
@@ -611,7 +619,7 @@
     public <T, E extends Exception & QueryExceptionMarkerInterface>
         ListenableFuture<Void> evalAsync(
             TargetPatternResolver<T> resolver,
-            InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredSubdirectories,
+            InterruptibleSupplier<IgnoredSubdirectories> ignoredSubdirectoriesSupplier,
             ImmutableSet<PathFragment> excludedSubdirectories,
             BatchCallback<T, E> callback,
             Class<E> exceptionClass,
@@ -621,133 +629,44 @@
           "Fully excluded target pattern %s should have already been filtered out (%s)",
           this,
           excludedSubdirectories);
-      IgnoredPathFragmentsInScopeOrFilteringIgnorer ignoredIntersection;
+      IgnoredSubdirectories filteredIgnoredSubdirectories;
       try {
-        ignoredIntersection = getAllIgnoredSubdirectoriesToExclude(ignoredSubdirectories);
+        IgnoredSubdirectories ignoredSubdirectories = ignoredSubdirectoriesSupplier.get();
+        PathFragment matchingEntry =
+            ignoredSubdirectories.matchingEntry(directory.getPackageFragment());
+        if (warnIfFiltered(matchingEntry, resolver)) {
+          return immediateVoidFuture();
+        }
+        filteredIgnoredSubdirectories =
+            ignoredSubdirectories.filterForDirectory(directory.getPackageFragment());
       } catch (InterruptedException e) {
         return immediateCancelledFuture();
       }
-      if (warnIfFiltered(ignoredIntersection, resolver)) {
-        return immediateVoidFuture();
-      }
       return resolver.findTargetsBeneathDirectoryAsync(
           directory.getRepository(),
           getOriginalPattern(),
           directory.getPackageFragment().getPathString(),
           rulesOnly,
-          new IgnoredSubdirectories(ignoredIntersection.ignoredPathFragments()),
+          filteredIgnoredSubdirectories,
           excludedSubdirectories,
           callback,
           exceptionClass,
           executor);
     }
 
-    private boolean warnIfFiltered(
-        IgnoredPathFragmentsInScopeOrFilteringIgnorer ignoredIntersection,
-        TargetPatternResolver<?> resolver) {
-      if (ignoredIntersection.wasFiltered()) {
+    private boolean warnIfFiltered(PathFragment matchingEntry, TargetPatternResolver<?> resolver) {
+      if (matchingEntry != null) {
         resolver.warn(
             "Pattern '"
                 + getOriginalPattern()
                 + "' was filtered out by ignored directory '"
-                + ignoredIntersection.filteringIgnorer().getPathString()
+                + matchingEntry
                 + "'");
         return true;
       }
       return false;
     }
 
-    public IgnoredPathFragmentsInScopeOrFilteringIgnorer getAllIgnoredSubdirectoriesToExclude(
-        InterruptibleSupplier<ImmutableSet<PathFragment>> ignoredPackagePrefixes)
-        throws InterruptedException {
-      ImmutableSet.Builder<PathFragment> ignoredPathsBuilder =
-          ImmutableSet.builderWithExpectedSize(0);
-      for (PathFragment ignoredPackagePrefix : ignoredPackagePrefixes.get()) {
-        if (this.containedIn(ignoredPackagePrefix)) {
-          return new IgnoredPathFragmentsInScopeOrFilteringIgnorer.FilteringIgnorer(
-              ignoredPackagePrefix);
-        }
-        PackageIdentifier pkgIdForIgnoredDirectorPrefix =
-            PackageIdentifier.create(directory.getRepository(), ignoredPackagePrefix);
-        if (this.containsAllTransitiveSubdirectories(pkgIdForIgnoredDirectorPrefix)) {
-          ignoredPathsBuilder.add(ignoredPackagePrefix);
-        }
-      }
-      return IgnoredPathFragmentsInScopeOrFilteringIgnorer.IgnoredPathFragments.of(
-          ignoredPathsBuilder.build());
-    }
-
-    /**
-     * Morally an {@code Either<ImmutableSet<PathFragment>, PathFragment>}, saying whether the given
-     * set of ignored directories intersected a directory (in which case the directories that were
-     * in the intersection are returned) or completely contained it (in which case a containing
-     * directory is returned).
-     */
-    public abstract static class IgnoredPathFragmentsInScopeOrFilteringIgnorer {
-      public abstract boolean wasFiltered();
-
-      public abstract ImmutableSet<PathFragment> ignoredPathFragments();
-
-      public abstract PathFragment filteringIgnorer();
-
-      private static class IgnoredPathFragments
-          extends IgnoredPathFragmentsInScopeOrFilteringIgnorer {
-        private static final IgnoredPathFragments EMPTYSET_IGNORED =
-            new IgnoredPathFragments(ImmutableSet.of());
-
-        private final ImmutableSet<PathFragment> ignoredPathFragments;
-
-        private IgnoredPathFragments(ImmutableSet<PathFragment> ignoredPathFragments) {
-          this.ignoredPathFragments = ignoredPathFragments;
-        }
-
-        static IgnoredPathFragments of(ImmutableSet<PathFragment> ignoredPathFragments) {
-          if (ignoredPathFragments.isEmpty()) {
-            return EMPTYSET_IGNORED;
-          }
-          return new IgnoredPathFragments(ignoredPathFragments);
-        }
-
-        @Override
-        public boolean wasFiltered() {
-          return false;
-        }
-
-        @Override
-        public ImmutableSet<PathFragment> ignoredPathFragments() {
-          return ignoredPathFragments;
-        }
-
-        @Override
-        public PathFragment filteringIgnorer() {
-          throw new UnsupportedOperationException("No filter: " + ignoredPathFragments);
-        }
-      }
-
-      private static class FilteringIgnorer extends IgnoredPathFragmentsInScopeOrFilteringIgnorer {
-        private final PathFragment filteringIgnorer;
-
-        FilteringIgnorer(PathFragment filteringIgnorer) {
-          this.filteringIgnorer = filteringIgnorer;
-        }
-
-        @Override
-        public boolean wasFiltered() {
-          return true;
-        }
-
-        @Override
-        public ImmutableSet<PathFragment> ignoredPathFragments() {
-          throw new UnsupportedOperationException("was filtered: " + filteringIgnorer);
-        }
-
-        @Override
-        public PathFragment filteringIgnorer() {
-          return filteringIgnorer;
-        }
-      }
-    }
-
     /** Is {@code containingDirectory} an ancestor of or equal to this {@link #directory}? */
     public boolean containedIn(PathFragment containingDirectory) {
       return directory.getPackageFragment().startsWith(containingDirectory);
diff --git a/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java b/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java
index 304aad3..d94906e 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/GlobCache.java
@@ -18,10 +18,10 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.SettableFuture;
 import com.google.devtools.build.lib.actions.ThreadStateReceiver;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.concurrent.ThreadSafety;
 import com.google.devtools.build.lib.packages.Globber.BadGlobException;
@@ -81,7 +81,7 @@
 
   private final CachingPackageLocator packageLocator;
 
-  private final ImmutableSet<PathFragment> ignoredGlobPrefixes;
+  private final IgnoredSubdirectories ignoredSubdirectories;
 
   /**
    * Create a glob expansion cache.
@@ -98,7 +98,7 @@
   public GlobCache(
       final Path packageDirectory,
       final PackageIdentifier packageId,
-      final ImmutableSet<PathFragment> ignoredGlobPrefixes,
+      final IgnoredSubdirectories ignoredSubdirectories,
       final CachingPackageLocator locator,
       SyscallCache syscallCache,
       Executor globExecutor,
@@ -120,7 +120,7 @@
 
     Preconditions.checkNotNull(locator);
     this.packageLocator = locator;
-    this.ignoredGlobPrefixes = ignoredGlobPrefixes;
+    this.ignoredSubdirectories = ignoredSubdirectories;
   }
 
   private boolean globCacheShouldTraverseDirectory(Path directory) {
@@ -131,10 +131,8 @@
     PathFragment subPackagePath =
         packageId.getPackageFragment().getRelative(directory.relativeTo(packageDirectory));
 
-    for (PathFragment ignoredPrefix : ignoredGlobPrefixes) {
-      if (subPackagePath.startsWith(ignoredPrefix)) {
-        return false;
-      }
+    if (ignoredSubdirectories.matchingEntry(subPackagePath) != null) {
+      return false;
     }
 
     return !isSubPackage(PackageIdentifier.create(packageId.getRepository(), subPackagePath));
diff --git a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
index 726df4e..5af4d4d 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/PackageFactory.java
@@ -18,10 +18,10 @@
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.flogger.GoogleLogger;
 import com.google.devtools.build.lib.actions.ThreadStateReceiver;
 import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.cmdline.RepositoryMapping;
 import com.google.devtools.build.lib.concurrent.NamedForkJoinPool;
@@ -41,7 +41,6 @@
 import com.google.devtools.build.lib.util.DetailedExitCode;
 import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.lib.vfs.Path;
-import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.lib.vfs.RootedPath;
 import com.google.devtools.build.lib.vfs.SyscallCache;
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
@@ -266,14 +265,14 @@
   public NonSkyframeGlobber createNonSkyframeGlobber(
       Path packageDirectory,
       PackageIdentifier packageId,
-      ImmutableSet<PathFragment> ignoredGlobPrefixes,
+      IgnoredSubdirectories ignoredSubdirectories,
       CachingPackageLocator locator,
       ThreadStateReceiver threadStateReceiverForMetrics) {
     return new NonSkyframeGlobber(
         new GlobCache(
             packageDirectory,
             packageId,
-            ignoredGlobPrefixes,
+            ignoredSubdirectories,
             locator,
             syscallCache,
             executor,
diff --git a/src/main/java/com/google/devtools/build/lib/packages/producers/DirectoryDirentProducer.java b/src/main/java/com/google/devtools/build/lib/packages/producers/DirectoryDirentProducer.java
index e6da99a..b8a7230 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/producers/DirectoryDirentProducer.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/producers/DirectoryDirentProducer.java
@@ -76,10 +76,8 @@
   @Override
   public StateMachine step(Tasks tasks) {
     // Check whether the next directory path matches any `IgnoredPackagePrefix`.
-    for (PathFragment ignoredPrefix : globDetail.ignoredPackagePrefixesPatterns()) {
-      if (direntPath.startsWith(ignoredPrefix)) {
-        return DONE;
-      }
+    if (globDetail.ignoredSubdirectories().matchingEntry(direntPath) != null) {
+      return DONE;
     }
 
     tasks.lookUp(
diff --git a/src/main/java/com/google/devtools/build/lib/packages/producers/GlobComputationProducer.java b/src/main/java/com/google/devtools/build/lib/packages/producers/GlobComputationProducer.java
index 343ff6d..f8514cb 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/producers/GlobComputationProducer.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/producers/GlobComputationProducer.java
@@ -20,6 +20,7 @@
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.packages.Globber;
 import com.google.devtools.build.lib.skyframe.GlobDescriptor;
@@ -75,16 +76,16 @@
 
   // -------------------- Internal State --------------------
   private final ImmutableSet.Builder<PathFragment> pathFragmentsWithPackageFragment;
-  private final ImmutableSet<PathFragment> ignoredPackagePrefixPatterns;
+  private final IgnoredSubdirectories ignoredSubdirectories;
   private final ConcurrentHashMap<String, Pattern> regexPatternCache;
 
   public GlobComputationProducer(
       GlobDescriptor globDescriptor,
-      ImmutableSet<PathFragment> ignoredPackagePrefixPatterns,
+      IgnoredSubdirectories ignoredSubdirectories,
       ConcurrentHashMap<String, Pattern> regexPatternCache,
       ResultSink resultSink) {
     this.globDescriptor = globDescriptor;
-    this.ignoredPackagePrefixPatterns = ignoredPackagePrefixPatterns;
+    this.ignoredSubdirectories = ignoredSubdirectories;
     this.regexPatternCache = regexPatternCache;
     this.resultSink = resultSink;
     this.pathFragmentsWithPackageFragment = ImmutableSet.builder();
@@ -92,7 +93,7 @@
 
   @Override
   public StateMachine step(Tasks tasks) {
-    Preconditions.checkNotNull(ignoredPackagePrefixPatterns);
+    Preconditions.checkNotNull(ignoredSubdirectories);
     ImmutableList<String> patterns =
         ImmutableList.copyOf(Splitter.on('/').split(globDescriptor.getPattern()));
     GlobDetail globDetail =
@@ -101,7 +102,7 @@
             globDescriptor.getPackageRoot(),
             patterns,
             /* containsMultipleDoubleStars= */ Collections.frequency(patterns, "**") > 1,
-            ignoredPackagePrefixPatterns,
+            ignoredSubdirectories,
             regexPatternCache,
             globDescriptor.globberOperation());
     Set<Pair<PathFragment, Integer>> visitedGlobSubTasks = null;
@@ -156,7 +157,7 @@
         Root packageRoot,
         ImmutableList<String> patternFragments,
         boolean containsMultipleDoubleStars,
-        ImmutableSet<PathFragment> ignoredPackagePrefixesPatterns,
+        IgnoredSubdirectories ignoredPackagePrefixesPatterns,
         ConcurrentHashMap<String, Pattern> regexPatternCache,
         Globber.Operation globOperation) {
       return new AutoValue_GlobComputationProducer_GlobDetail(
@@ -183,7 +184,7 @@
      */
     abstract boolean containsMultipleDoubleStars();
 
-    abstract ImmutableSet<PathFragment> ignoredPackagePrefixesPatterns();
+    abstract IgnoredSubdirectories ignoredSubdirectories();
 
     abstract ConcurrentHashMap<String, Pattern> regexPatternCache();
 
diff --git a/src/main/java/com/google/devtools/build/lib/query2/PostAnalysisQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/PostAnalysisQueryEnvironment.java
index 2bf3e26..93664aa 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/PostAnalysisQueryEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/PostAnalysisQueryEnvironment.java
@@ -33,6 +33,7 @@
 import com.google.devtools.build.lib.analysis.config.BuildConfigurationValue;
 import com.google.devtools.build.lib.analysis.configuredtargets.OutputFileConfiguredTarget;
 import com.google.devtools.build.lib.analysis.configuredtargets.RuleConfiguredTarget;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.cmdline.TargetParsingException;
@@ -77,7 +78,6 @@
 import com.google.devtools.build.lib.skyframe.SkyFunctions;
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
 import com.google.devtools.build.lib.supplier.InterruptibleSupplier;
-import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
 import com.google.devtools.build.skyframe.WalkableGraph;
@@ -258,15 +258,15 @@
 
   protected abstract boolean isAliasConfiguredTarget(T target);
 
-  public InterruptibleSupplier<ImmutableSet<PathFragment>> getIgnoredPackagePrefixesPathFragments(
+  public InterruptibleSupplier<IgnoredSubdirectories> getIgnoredPackagePrefixesPathFragments(
       RepositoryName repositoryName) {
     return () -> {
       IgnoredPackagePrefixesValue ignoredPackagePrefixesValue =
           (IgnoredPackagePrefixesValue)
               walkableGraphSupplier.get().getValue(IgnoredPackagePrefixesValue.key(repositoryName));
       return ignoredPackagePrefixesValue == null
-          ? ImmutableSet.of()
-          : ignoredPackagePrefixesValue.getPatterns();
+          ? IgnoredSubdirectories.EMPTY
+          : ignoredPackagePrefixesValue.asIgnoredSubdirectories();
     };
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
index 7441230..6bef4d2 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/SkyQueryEnvironment.java
@@ -896,7 +896,7 @@
                 ((IgnoredPackagePrefixesValue)
                         graph.getValue(
                             IgnoredPackagePrefixesValue.key(patternToEval.getRepository())))
-                    .getPatterns(),
+                    .asIgnoredSubdirectories(),
             targetPatternKey.getExcludedSubdirectories(),
             filteredCallback,
             QueryException.class,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunctionWithMultipleRecursiveFunctions.java b/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunctionWithMultipleRecursiveFunctions.java
index 58b8636..5553ce3 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunctionWithMultipleRecursiveFunctions.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunctionWithMultipleRecursiveFunctions.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.devtools.build.lib.actions.FileValue;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -63,10 +64,9 @@
     PathFragment globSubdir = glob.getSubdir();
     PathFragment dirPathFragment = glob.getPackageId().getPackageFragment().getRelative(globSubdir);
 
-    for (PathFragment ignoredPrefix : ignoredPackagePrefixes.getPatterns()) {
-      if (dirPathFragment.startsWith(ignoredPrefix)) {
-        return GlobValueWithNestedSet.EMPTY;
-      }
+    IgnoredSubdirectories ignoredSubdirectories = ignoredPackagePrefixes.asIgnoredSubdirectories();
+    if (ignoredSubdirectories.matchingEntry(dirPathFragment) != null) {
+      return GlobValueWithNestedSet.EMPTY;
     }
 
     String pattern = glob.getPattern();
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunctionWithRecursionInSingleFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunctionWithRecursionInSingleFunction.java
index ec5d911..592ffd1 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunctionWithRecursionInSingleFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/GlobFunctionWithRecursionInSingleFunction.java
@@ -14,6 +14,7 @@
 package com.google.devtools.build.lib.skyframe;
 
 import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.packages.producers.GlobComputationProducer;
 import com.google.devtools.build.lib.packages.producers.GlobError;
@@ -49,7 +50,7 @@
     @Nullable // Non-null while in-flight.
     private Driver globComputationDriver;
 
-    @Nullable ImmutableSet<PathFragment> ignorePackagePrefixesPatterns;
+    @Nullable IgnoredSubdirectories ignoredSubdirectories;
 
     private ImmutableSet<PathFragment> globMatchingResult;
     private GlobError error;
@@ -80,7 +81,7 @@
     GlobDescriptor glob = (GlobDescriptor) skyKey.argument();
     State state = env.getState(State::new);
 
-    if (state.ignorePackagePrefixesPatterns == null) {
+    if (state.ignoredSubdirectories == null) {
       RepositoryName repositoryName = glob.getPackageId().getRepository();
       IgnoredPackagePrefixesValue ignoredPackagePrefixes =
           (IgnoredPackagePrefixesValue)
@@ -88,14 +89,14 @@
       if (env.valuesMissing()) {
         return null;
       }
-      state.ignorePackagePrefixesPatterns = ignoredPackagePrefixes.getPatterns();
+      state.ignoredSubdirectories = ignoredPackagePrefixes.asIgnoredSubdirectories();
     }
 
     if (state.globComputationDriver == null) {
       state.globComputationDriver =
           new Driver(
               new GlobComputationProducer(
-                  glob, state.ignorePackagePrefixesPatterns, regexPatternCache, state));
+                  glob, state.ignoredSubdirectories, regexPatternCache, state));
     }
 
     if (!state.globComputationDriver.drive(env)) {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/GlobsFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/GlobsFunction.java
index f60a16d..04b72d0 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/GlobsFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/GlobsFunction.java
@@ -16,6 +16,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import com.google.common.util.concurrent.Uninterruptibles;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.concurrent.QuiescingExecutor;
 import com.google.devtools.build.lib.packages.producers.GlobComputationProducer;
@@ -73,7 +74,7 @@
 
   private static class State implements SkyKeyComputeState, GlobComputationProducer.ResultSink {
     @Nullable private List<Driver> globDrivers;
-    @Nullable ImmutableSet<PathFragment> ignorePackagePrefixesPatterns;
+    @Nullable IgnoredSubdirectories ignoredSubdirectories;
 
     private final Set<PathFragment> matchings = Sets.newConcurrentHashSet();
     private volatile GlobError error;
@@ -109,7 +110,7 @@
     GlobsValue.Key globsKey = (GlobsValue.Key) skyKey;
     State state = env.getState(State::new);
 
-    if (state.ignorePackagePrefixesPatterns == null) {
+    if (state.ignoredSubdirectories == null) {
       RepositoryName repositoryName = globsKey.getPackageIdentifier().getRepository();
       IgnoredPackagePrefixesValue ignoredPackagePrefixes =
           (IgnoredPackagePrefixesValue)
@@ -117,7 +118,7 @@
       if (env.valuesMissing()) {
         return null;
       }
-      state.ignorePackagePrefixesPatterns = ignoredPackagePrefixes.getPatterns();
+      state.ignoredSubdirectories = ignoredPackagePrefixes.asIgnoredSubdirectories();
     }
 
     if (state.globDrivers == null) {
@@ -135,10 +136,7 @@
         state.globDrivers.add(
             new Driver(
                 new GlobComputationProducer(
-                    globDescriptor,
-                    state.ignorePackagePrefixesPatterns,
-                    regexPatternCache,
-                    state)));
+                    globDescriptor, state.ignoredSubdirectories, regexPatternCache, state)));
       }
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/IgnoredPackagePrefixesValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/IgnoredPackagePrefixesValue.java
index 46dfbe7..7b345db 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/IgnoredPackagePrefixesValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/IgnoredPackagePrefixesValue.java
@@ -15,6 +15,7 @@
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.skyframe.serialization.VisibleForSerialization;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
@@ -27,14 +28,14 @@
 
 /** An immutable set of package name prefixes that should be ignored. */
 public class IgnoredPackagePrefixesValue implements SkyValue {
-  private final ImmutableSet<PathFragment> patterns;
+  private final IgnoredSubdirectories ignoredSubdirectories;
 
   @SerializationConstant @VisibleForSerialization
   public static final IgnoredPackagePrefixesValue EMPTY_LIST =
       new IgnoredPackagePrefixesValue(ImmutableSet.of());
 
   private IgnoredPackagePrefixesValue(ImmutableSet<PathFragment> patterns) {
-    this.patterns = Preconditions.checkNotNull(patterns);
+    this.ignoredSubdirectories = IgnoredSubdirectories.of(Preconditions.checkNotNull(patterns));
   }
 
   public static IgnoredPackagePrefixesValue of(ImmutableSet<PathFragment> patterns) {
@@ -51,23 +52,32 @@
     return Key.create(repository);
   }
 
+  public IgnoredSubdirectories asIgnoredSubdirectories() {
+    return ignoredSubdirectories;
+  }
+
   public ImmutableSet<PathFragment> getPatterns() {
-    return patterns;
+    return ignoredSubdirectories.prefixes();
   }
 
   @Override
   public int hashCode() {
-    return patterns.hashCode();
+    return ignoredSubdirectories.hashCode();
   }
 
   @Override
   public boolean equals(Object obj) {
     if (obj instanceof IgnoredPackagePrefixesValue other) {
-      return this.patterns.equals(other.patterns);
+      return this.ignoredSubdirectories.equals(other.ignoredSubdirectories);
     }
     return false;
   }
 
+  @Override
+  public String toString() {
+    return ignoredSubdirectories.toString();
+  }
+
   @VisibleForSerialization
   @AutoCodec
   static class Key extends AbstractSkyKey<RepositoryName> {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
index dcbff90..7d070f4 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageFunction.java
@@ -19,12 +19,12 @@
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.devtools.build.lib.actions.FileValue;
 import com.google.devtools.build.lib.actions.ThreadStateReceiver;
 import com.google.devtools.build.lib.clock.BlazeClock;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.LabelConstants;
 import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
@@ -975,8 +975,8 @@
     String workspaceName = workspaceNameValue.getName();
     RepositoryMapping repositoryMapping = repositoryMappingValue.getRepositoryMapping();
     RepositoryMapping mainRepositoryMapping = mainRepositoryMappingValue.getRepositoryMapping();
-    ImmutableSet<PathFragment> repositoryIgnoredPatterns =
-        repositoryIgnoredPackagePrefixes.getPatterns();
+    IgnoredSubdirectories repositoryIgnoredSubdirectories =
+        repositoryIgnoredPackagePrefixes.asIgnoredSubdirectories();
     Label preludeLabel = null;
 
     // Load (optional) prelude, which determines environment.
@@ -1104,7 +1104,7 @@
               packageFactory.createNonSkyframeGlobber(
                   buildFileRootedPath.asPath().getParentDirectory(),
                   packageId,
-                  repositoryIgnoredPatterns,
+                  repositoryIgnoredSubdirectories,
                   packageLocator,
                   threadStateReceiverFactoryForMetrics.apply(keyForMetrics)),
               packageId,
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PackageLookupFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PackageLookupFunction.java
index 4f105a2..f5c8bac 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PackageLookupFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PackageLookupFunction.java
@@ -136,7 +136,8 @@
       return null;
     }
 
-    if (isPackageIgnored(packageKey, ignoredPatternsValue)) {
+    PathFragment packageFragment = packageKey.getPackageFragment();
+    if (ignoredPatternsValue.asIgnoredSubdirectories().matchingEntry(packageFragment) != null) {
       return PackageLookupValue.DELETED_PACKAGE_VALUE;
     }
 
@@ -332,17 +333,6 @@
     return PackageLookupValue.NO_BUILD_FILE_VALUE;
   }
 
-  private static boolean isPackageIgnored(
-      PackageIdentifier id, IgnoredPackagePrefixesValue ignoredPatternsValue) {
-    PathFragment packageFragment = id.getPackageFragment();
-    for (PathFragment pattern : ignoredPatternsValue.getPatterns()) {
-      if (packageFragment.startsWith(pattern)) {
-        return true;
-      }
-    }
-    return false;
-  }
-
   @Nullable
   private PackageLookupValue computeWorkspacePackageLookupValue(Environment env)
       throws InterruptedException {
@@ -414,7 +404,8 @@
       return null;
     }
 
-    if (isPackageIgnored(id, ignoredPatternsValue)) {
+    PathFragment packageFragment = id.getPackageFragment();
+    if (ignoredPatternsValue.asIgnoredSubdirectories().matchingEntry(packageFragment) != null) {
       return PackageLookupValue.DELETED_PACKAGE_VALUE;
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternFunction.java
index 6058cfa..6ec79b9 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfPatternFunction.java
@@ -103,13 +103,10 @@
     // since it has to iterate over those exclusions to see if they fully exclude the directory even
     // though TargetPatternKey guarantees that the exclusions will not fully exclude the directory.
     ImmutableSet<PathFragment> excludedPatterns = patternKey.getExcludedSubdirectories();
-    ImmutableSet<PathFragment> ignoredPatterns = repositoryIgnoredPrefixes.getPatterns();
-    ImmutableSet<PathFragment> repositoryIgnoredPatterns =
-        ImmutableSet.<PathFragment>builderWithExpectedSize(
-                excludedPatterns.size() + ignoredPatterns.size())
-            .addAll(excludedPatterns)
-            .addAll(ignoredPatterns)
-            .build();
+    IgnoredSubdirectories repositoryIgnoredPatterns =
+        repositoryIgnoredPrefixes
+            .asIgnoredSubdirectories()
+            .union(IgnoredSubdirectories.of(excludedPatterns));
 
     DepsOfPatternPreparer preparer = new DepsOfPatternPreparer(env, pkgPath.get());
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeTargetPatternEvaluator.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeTargetPatternEvaluator.java
index 09917ef..6abd924 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeTargetPatternEvaluator.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeTargetPatternEvaluator.java
@@ -20,6 +20,7 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.bugreport.BugReport;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.cmdline.QueryExceptionMarkerInterface;
@@ -300,8 +301,8 @@
       try {
         targetPattern.eval(
             resolver,
-            /*ignoredSubdirectories=*/ ImmutableSet::of,
-            /*excludedSubdirectories=*/ ImmutableSet.of(),
+            /* ignoredSubdirectories= */ () -> IgnoredSubdirectories.EMPTY,
+            /* excludedSubdirectories= */ ImmutableSet.of(),
             partialResult ->
                 result.set(
                     partialResult instanceof Collection
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternFunction.java
index 7c50e04..9fc8c59 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TargetPatternFunction.java
@@ -58,7 +58,6 @@
     if (ignoredPackagePrefixes == null) {
       return null;
     }
-    ImmutableSet<PathFragment> ignoredPatterns = ignoredPackagePrefixes.getPatterns();
 
     ResolvedTargets<Target> resolvedTargets;
     EnvironmentBackedRecursivePackageProvider provider =
@@ -91,7 +90,7 @@
       try {
         parsedPattern.eval(
             resolver,
-            () -> ignoredPatterns,
+            () -> ignoredPackagePrefixes.asIgnoredSubdirectories(),
             excludedSubdirectories,
             callback,
             QueryExceptionMarkerInterface.MarkerRuntimeException.class);
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryContextTest.java b/src/test/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryContextTest.java
index 49b1fd6..ad04691 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryContextTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/repository/starlark/StarlarkRepositoryContextTest.java
@@ -31,6 +31,7 @@
 import com.google.devtools.build.lib.analysis.ServerDirectories;
 import com.google.devtools.build.lib.analysis.util.AnalysisMock;
 import com.google.devtools.build.lib.bazel.repository.downloader.DownloadManager;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.RepositoryMapping;
 import com.google.devtools.build.lib.events.ExtendedEventHandler;
@@ -141,7 +142,7 @@
 
   private void setUpContextForRule(
       Map<String, Object> kwargs,
-      ImmutableSet<PathFragment> ignoredPathFragments,
+      IgnoredSubdirectories ignoredSubdirectories,
       ImmutableMap<String, String> envVariables,
       StarlarkSemantics starlarkSemantics,
       @Nullable RepositoryRemoteExecutor repoRemoteExecutor,
@@ -190,7 +191,7 @@
             rule,
             packageLocator,
             outputDirectory,
-            ignoredPathFragments,
+            ignoredSubdirectories,
             environment,
             envVariables,
             downloader,
@@ -210,7 +211,7 @@
       throws Exception {
     setUpContextForRule(
         ImmutableMap.of("name", name),
-        ImmutableSet.of(),
+        IgnoredSubdirectories.EMPTY,
         ImmutableMap.of("FOO", "BAR"),
         starlarkSemantics,
         /* repoRemoteExecutor= */ null);
@@ -220,7 +221,7 @@
   public void testAttr() throws Exception {
     setUpContextForRule(
         ImmutableMap.of("name", "test", "foo", "bar"),
-        ImmutableSet.of(),
+        IgnoredSubdirectories.EMPTY,
         ImmutableMap.of("FOO", "BAR"),
         StarlarkSemantics.DEFAULT,
         /* repoRemoteExecutor= */ null,
@@ -234,7 +235,7 @@
   public void testWhich() throws Exception {
     setUpContextForRule(
         ImmutableMap.of("name", "test"),
-        ImmutableSet.of(),
+        IgnoredSubdirectories.EMPTY,
         ImmutableMap.of("PATH", String.join(File.pathSeparator, "/bin", "/path/sbin", ".")),
         StarlarkSemantics.DEFAULT,
         /* repoRemoteExecutor= */ null);
@@ -325,7 +326,7 @@
     scratch.file(underWorkspace.getPathString(), "123");
     setUpContextForRule(
         ImmutableMap.of("name", "test"),
-        ImmutableSet.of(PathFragment.create("under_workspace")),
+        IgnoredSubdirectories.of(ImmutableSet.of(PathFragment.create("under_workspace"))),
         ImmutableMap.of("FOO", "BAR"),
         StarlarkSemantics.DEFAULT,
         /* repoRemoteExecutor= */ null);
@@ -443,7 +444,7 @@
 
     setUpContextForRule(
         attrValues,
-        ImmutableSet.of(),
+        IgnoredSubdirectories.EMPTY,
         ImmutableMap.of("FOO", "BAR"),
         StarlarkSemantics.builder()
             .setBool(BuildLanguageOptions.EXPERIMENTAL_REPO_REMOTE_EXEC, true)
diff --git a/src/test/java/com/google/devtools/build/lib/packages/GlobCacheTest.java b/src/test/java/com/google/devtools/build/lib/packages/GlobCacheTest.java
index 0469085..f5ef624 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/GlobCacheTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/GlobCacheTest.java
@@ -19,6 +19,7 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.devtools.build.lib.actions.ThreadStateReceiver;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
 import com.google.devtools.build.lib.packages.Globber.BadGlobException;
 import com.google.devtools.build.lib.testutil.Scratch;
@@ -109,7 +110,7 @@
         new GlobCache(
             packageDirectory,
             PackageIdentifier.createInMainRepo("isolated"),
-            ImmutableSet.copyOf(ignoredDirectories),
+            IgnoredSubdirectories.of(ImmutableSet.copyOf(ignoredDirectories)),
             new CachingPackageLocator() {
               @Override
               public Path getBuildFileForPackage(PackageIdentifier packageId) {
diff --git a/src/test/java/com/google/devtools/build/lib/packages/PackageFactoryTest.java b/src/test/java/com/google/devtools/build/lib/packages/PackageFactoryTest.java
index 42de75e..e0e33c6 100644
--- a/src/test/java/com/google/devtools/build/lib/packages/PackageFactoryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/packages/PackageFactoryTest.java
@@ -20,11 +20,11 @@
 import static org.junit.Assert.fail;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
 import com.google.devtools.build.lib.actions.ThreadStateReceiver;
 import com.google.devtools.build.lib.analysis.config.FeatureSet;
+import com.google.devtools.build.lib.cmdline.IgnoredSubdirectories;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.cmdline.LabelSyntaxException;
 import com.google.devtools.build.lib.cmdline.PackageIdentifier;
@@ -1880,7 +1880,7 @@
           new GlobCache(
               pkg.getFilename().asPath().getParentDirectory(),
               pkg.getPackageIdentifier(),
-              ImmutableSet.of(),
+              IgnoredSubdirectories.EMPTY,
               // a package locator that finds no packages
               new CachingPackageLocator() {
                 @Override
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/AbstractCollectPackagesUnderDirectoryTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/AbstractCollectPackagesUnderDirectoryTest.java
index d144e6f..d15d9d8 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/AbstractCollectPackagesUnderDirectoryTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/AbstractCollectPackagesUnderDirectoryTest.java
@@ -352,7 +352,7 @@
       String directory, ImmutableSet<PathFragment> excludedPaths) throws InterruptedException {
     SkyKey key =
         CollectPackagesUnderDirectoryValue.key(
-            RepositoryName.MAIN, rootedPath(directory), new IgnoredSubdirectories(excludedPaths));
+            RepositoryName.MAIN, rootedPath(directory), IgnoredSubdirectories.of(excludedPaths));
     return evaluate(key).get(key);
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryFunctionTest.java
index 0726532..76e259d 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryFunctionTest.java
@@ -82,7 +82,7 @@
       Path root, PathFragment rootRelativePath, ImmutableSet<PathFragment> excludedPaths) {
     RootedPath rootedPath = RootedPath.toRootedPath(Root.fromPath(root), rootRelativePath);
     return CollectPackagesUnderDirectoryValue.key(
-        RepositoryName.MAIN, rootedPath, new IgnoredSubdirectories(excludedPaths));
+        RepositoryName.MAIN, rootedPath, IgnoredSubdirectories.of(excludedPaths));
   }
 
   private static SkyKey createPrepDepsKey(Path root, PathFragment rootRelativePath) {
@@ -93,7 +93,7 @@
       Path root, PathFragment rootRelativePath, ImmutableSet<PathFragment> excludedPaths) {
     RootedPath rootedPath = RootedPath.toRootedPath(Root.fromPath(root), rootRelativePath);
     return PrepareDepsOfTargetsUnderDirectoryValue.key(
-        RepositoryName.MAIN, rootedPath, new IgnoredSubdirectories(excludedPaths));
+        RepositoryName.MAIN, rootedPath, IgnoredSubdirectories.of(excludedPaths));
   }
 
   private static SkyKey createPrepDepsKey(
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryKeyCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryKeyCodecTest.java
index faddc50..1afee80 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryKeyCodecTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PrepareDepsOfTargetsUnderDirectoryKeyCodecTest.java
@@ -36,7 +36,7 @@
                 new RecursivePkgKey(
                     RepositoryName.MAIN,
                     FsUtils.TEST_ROOTED_PATH,
-                    new IgnoredSubdirectories(ImmutableSet.of(FsUtils.rootPathRelative("here")))),
+                    IgnoredSubdirectories.of(ImmutableSet.of(FsUtils.rootPathRelative("here")))),
                 FilteringPolicies.and(
                     FilteringPolicies.NO_FILTER, FilteringPolicies.FILTER_TESTS)));
     FsUtils.addDependencies(serializationTester);
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/RecursivePkgFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/RecursivePkgFunctionTest.java
index f2d68e9..8cb11db 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/RecursivePkgFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/RecursivePkgFunctionTest.java
@@ -50,7 +50,7 @@
       Path root, PathFragment rootRelativePath, ImmutableSet<PathFragment> excludedPaths) {
     RootedPath rootedPath = RootedPath.toRootedPath(Root.fromPath(root), rootRelativePath);
     return RecursivePkgValue.key(
-        RepositoryName.MAIN, rootedPath, new IgnoredSubdirectories(excludedPaths));
+        RepositoryName.MAIN, rootedPath, IgnoredSubdirectories.of(excludedPaths));
   }
 
   private RecursivePkgValue buildRecursivePkgValue(Path root, PathFragment rootRelativePath)
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/RecursivePkgKeyTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/RecursivePkgKeyTest.java
index 1246265..461d0f5 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/RecursivePkgKeyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/RecursivePkgKeyTest.java
@@ -36,7 +36,7 @@
       PathFragment rootRelativePath,
       ImmutableSet<PathFragment> excludedPaths) {
     RootedPath rootedPath = RootedPath.toRootedPath(Root.fromPath(rootDirectory), rootRelativePath);
-    return RecursivePkgValue.key(repository, rootedPath, new IgnoredSubdirectories(excludedPaths));
+    return RecursivePkgValue.key(repository, rootedPath, IgnoredSubdirectories.of(excludedPaths));
   }
 
   private void invalidHelper(
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/RecursivePkgKeyCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/RecursivePkgKeyCodecTest.java
index 3efecfa..89e2c03 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/RecursivePkgKeyCodecTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/RecursivePkgKeyCodecTest.java
@@ -36,7 +36,7 @@
             new RecursivePkgKey(
                 RepositoryName.MAIN,
                 FsUtils.TEST_ROOTED_PATH,
-                new IgnoredSubdirectories(
+                IgnoredSubdirectories.of(
                     ImmutableSet.of(
                         FsUtils.rootPathRelative("here"), FsUtils.rootPathRelative("there")))));
     FsUtils.addDependencies(serializationTester);