Add managed directories to RepositoryDirectoryValue.

- This is only a part of managed directories feature, still not complete.
- In this part the link between WorkspaceFileListener and Bazel-specific structures is to be seen.
- Class ManagedDirectoriesKnowledgeImpl obviously contains logic, needed for the future, not existing yet functionality, but if I only keep the currently needed part, it will not be clear why we need a separate wrapper class at all.

- Keep precalculated (by SequencedSkyframeExecutor) value of managed directories in the holder object, created by BazelRepositoryModule and available to all interested parties (repository dirty checker, RepositoryDelegatorFunction).
- Managed directories are part of the external repository definition. When the set of managed directories for a repository change, repository should be considered dirty. Repository dirty checker is changed to support that.
- When the set of managed directories for a repository change, also the computed repository digest value should change.
Add managed directories paths fragments to the digest.
- Add tests for both dirty checker and digest calculation.

PiperOrigin-RevId: 246370690
diff --git a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
index 2750dde..1d0fa39 100644
--- a/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
+++ b/src/main/java/com/google/devtools/build/lib/bazel/BazelRepositoryModule.java
@@ -50,10 +50,11 @@
 import com.google.devtools.build.lib.pkgcache.PackageCacheOptions;
 import com.google.devtools.build.lib.rules.repository.LocalRepositoryFunction;
 import com.google.devtools.build.lib.rules.repository.LocalRepositoryRule;
+import com.google.devtools.build.lib.rules.repository.ManagedDirectoriesKnowledgeImpl;
 import com.google.devtools.build.lib.rules.repository.NewLocalRepositoryFunction;
 import com.google.devtools.build.lib.rules.repository.NewLocalRepositoryRule;
 import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
-import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryValue;
+import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryDirtinessChecker;
 import com.google.devtools.build.lib.rules.repository.RepositoryFunction;
 import com.google.devtools.build.lib.rules.repository.RepositoryLoaderFunction;
 import com.google.devtools.build.lib.runtime.BlazeModule;
@@ -67,18 +68,14 @@
 import com.google.devtools.build.lib.skyframe.PrecomputedValue;
 import com.google.devtools.build.lib.skyframe.PrecomputedValue.Injected;
 import com.google.devtools.build.lib.skyframe.SkyFunctions;
-import com.google.devtools.build.lib.skyframe.SkyValueDirtinessChecker;
 import com.google.devtools.build.lib.skylarkbuildapi.repository.RepositoryBootstrap;
 import com.google.devtools.build.lib.util.AbruptExitException;
-import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
 import com.google.devtools.build.lib.vfs.FileSystem;
 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.Root;
 import com.google.devtools.build.lib.vfs.RootedPath;
-import com.google.devtools.build.skyframe.SkyKey;
-import com.google.devtools.build.skyframe.SkyValue;
 import com.google.devtools.common.options.OptionsBase;
 import com.google.devtools.common.options.OptionsParsingResult;
 import java.io.IOException;
@@ -87,7 +84,6 @@
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Collectors;
-import javax.annotation.Nullable;
 
 /** Adds support for fetching external code. */
 public class BazelRepositoryModule extends BlazeModule {
@@ -105,10 +101,14 @@
   private final MutableSupplier<Map<String, String>> clientEnvironmentSupplier =
       new MutableSupplier<>();
   private ImmutableMap<RepositoryName, PathFragment> overrides = ImmutableMap.of();
-  private Optional<RootedPath> resolvedFile = Optional.<RootedPath>absent();
-  private Optional<RootedPath> resolvedFileReplacingWorkspace = Optional.<RootedPath>absent();
-  private Set<String> outputVerificationRules = ImmutableSet.<String>of();
+  private Optional<RootedPath> resolvedFile = Optional.absent();
+  private Optional<RootedPath> resolvedFileReplacingWorkspace = Optional.absent();
+  private Set<String> outputVerificationRules = ImmutableSet.of();
   private FileSystem filesystem;
+  // We hold the precomputed value of the managed directories here, so that the dependency
+  // on WorkspaceFileValue is not registered for each FileStateValue.
+  private final ManagedDirectoriesKnowledgeImpl managedDirectoriesKnowledge =
+      new ManagedDirectoriesKnowledgeImpl();
 
   public BazelRepositoryModule() {
     this.skylarkRepositoryFunction = new SkylarkRepositoryFunction(httpDownloader);
@@ -128,35 +128,6 @@
         .build();
   }
 
-  /**
-   * A dirtiness checker that always dirties {@link RepositoryDirectoryValue}s so that if they were
-   * produced in a {@code --nofetch} build, they are re-created no subsequent {@code --fetch}
-   * builds.
-   *
-   * <p>The alternative solution would be to reify the value of the flag as a Skyframe value.
-   */
-  private static final SkyValueDirtinessChecker REPOSITORY_VALUE_CHECKER =
-      new SkyValueDirtinessChecker() {
-        @Override
-        public boolean applies(SkyKey skyKey) {
-          return skyKey.functionName().equals(SkyFunctions.REPOSITORY_DIRECTORY);
-        }
-
-        @Override
-        public SkyValue createNewValue(SkyKey key, @Nullable TimestampGranularityMonitor tsgm) {
-          throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public DirtyResult check(
-            SkyKey skyKey, SkyValue skyValue, @Nullable TimestampGranularityMonitor tsgm) {
-          RepositoryDirectoryValue repositoryValue = (RepositoryDirectoryValue) skyValue;
-          return repositoryValue.repositoryExists() && repositoryValue.isFetchingDelayed()
-              ? DirtyResult.dirty(skyValue)
-              : DirtyResult.notDirty(skyValue);
-        }
-      };
-
   private static class RepositoryCacheInfoItem extends InfoItem {
     private final RepositoryCache repositoryCache;
 
@@ -182,17 +153,25 @@
   @Override
   public void workspaceInit(
       BlazeRuntime runtime, BlazeDirectories directories, WorkspaceBuilder builder) {
-    builder.addCustomDirtinessChecker(REPOSITORY_VALUE_CHECKER);
+    builder.setWorkspaceFileHeaderListener(
+        value ->
+            managedDirectoriesKnowledge.setManagedDirectories(
+                value != null ? value.getManagedDirectories() : ImmutableMap.of()));
+
+    RepositoryDirectoryDirtinessChecker customDirtinessChecker =
+        new RepositoryDirectoryDirtinessChecker(managedDirectoriesKnowledge);
+    builder.addCustomDirtinessChecker(customDirtinessChecker);
     // Create the repository function everything flows through.
     builder.addSkyFunction(SkyFunctions.REPOSITORY, new RepositoryLoaderFunction());
-    builder.addSkyFunction(
-        SkyFunctions.REPOSITORY_DIRECTORY,
+    RepositoryDelegatorFunction repositoryDelegatorFunction =
         new RepositoryDelegatorFunction(
             repositoryHandlers,
             skylarkRepositoryFunction,
             isFetch,
             clientEnvironmentSupplier,
-            directories));
+            directories,
+            managedDirectoriesKnowledge);
+    builder.addSkyFunction(SkyFunctions.REPOSITORY_DIRECTORY, repositoryDelegatorFunction);
     builder.addSkyFunction(MavenServerFunction.NAME, new MavenServerFunction(directories));
     filesystem = runtime.getFileSystem();
   }
@@ -233,7 +212,7 @@
         env.getReporter()
             .handle(
                 Event.warn(
-                    "Ingoring request to scale timeouts for repositories by a non-positive"
+                    "Ignoring request to scale timeouts for repositories by a non-positive"
                         + " factor"));
         skylarkRepositoryFunction.setTimeoutScaling(1.0);
       }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/ManagedDirectoriesKnowledge.java b/src/main/java/com/google/devtools/build/lib/rules/repository/ManagedDirectoriesKnowledge.java
new file mode 100644
index 0000000..66ca156
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/repository/ManagedDirectoriesKnowledge.java
@@ -0,0 +1,51 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.rules.repository;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.RootedPath;
+import javax.annotation.Nullable;
+
+/**
+ * Interface to access the managed directories holder object.
+ *
+ * <p>Managed directories are user-owned directories, which can be incrementally updated by
+ * repository rules, so that the updated files are visible for the actions in the same build.
+ *
+ * <p>Having managed directories as a separate component (and not SkyValue) allows to skip recording
+ * the dependency in Skyframe for each FileStateValue and DirectoryListingStateValue.
+ */
+public interface ManagedDirectoriesKnowledge {
+  ManagedDirectoriesKnowledge NO_MANAGED_DIRECTORIES =
+      new ManagedDirectoriesKnowledge() {
+        @Nullable
+        @Override
+        public RepositoryName getOwnerRepository(RootedPath rootedPath, boolean old) {
+          return null;
+        }
+
+        @Override
+        public ImmutableSet<PathFragment> getManagedDirectories(RepositoryName repositoryName) {
+          return ImmutableSet.of();
+        }
+      };
+
+  @Nullable
+  RepositoryName getOwnerRepository(RootedPath rootedPath, boolean old);
+
+  ImmutableSet<PathFragment> getManagedDirectories(RepositoryName repositoryName);
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/ManagedDirectoriesKnowledgeImpl.java b/src/main/java/com/google/devtools/build/lib/rules/repository/ManagedDirectoriesKnowledgeImpl.java
new file mode 100644
index 0000000..08b44fa
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/repository/ManagedDirectoriesKnowledgeImpl.java
@@ -0,0 +1,101 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.rules.repository;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedMap;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
+import com.google.devtools.build.lib.vfs.PathFragment;
+import com.google.devtools.build.lib.vfs.RootedPath;
+import java.util.Comparator;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.Nullable;
+
+/** Managed directories component. {@link ManagedDirectoriesKnowledge} */
+public class ManagedDirectoriesKnowledgeImpl implements ManagedDirectoriesKnowledge {
+  private final AtomicReference<ImmutableSortedMap<PathFragment, RepositoryName>>
+      managedDirectoriesRef = new AtomicReference<>(ImmutableSortedMap.of());
+  private final AtomicReference<Map<RepositoryName, ImmutableSet<PathFragment>>> repoToDirMapRef =
+      new AtomicReference<>(ImmutableMap.of());
+
+  /**
+   * During build commands execution, Skyframe caches the states of files (FileStateValue) and
+   * directory listings (DirectoryListingStateValue). In the case when the files/directories are
+   * under a managed directory or inside an external repository, evaluation of file/directory
+   * listing states requires that the RepositoryDirectoryValue of the owning external repository is
+   * evaluated beforehand. (So that the repository rule generates the files.) So there is a
+   * dependency on RepositoryDirectoryValue for files under managed directories and external
+   * repositories. This dependency is recorded by Skyframe,
+   *
+   * <p>From the other side, by default Skyframe injects the new values of changed files already at
+   * the stage of checking what files have changed. Only the values without any dependencies can be
+   * injected into Skyframe. Skyframe can be specifically instructed to not inject new values and
+   * only register them as changed.
+   *
+   * <p>When the values of managed directories change, some files appear to become files under
+   * managed directories, or they are no longer files under managed directories. This implies that
+   * corresponding file/directory listing states gain the dependency (RepositoryDirectoryValue) or
+   * they lose this dependency. In both cases, we should prevent Skyframe from injecting those new
+   * values of file/directory listing states at the stage of checking changed files.
+   *
+   * <p>That is why we need to keep track of the previous value of the managed directories.
+   */
+  private final AtomicReference<ImmutableSortedMap<PathFragment, RepositoryName>>
+      oldManagedDirectoriesRef = new AtomicReference<>(ImmutableSortedMap.of());
+
+  @Override
+  @Nullable
+  public RepositoryName getOwnerRepository(RootedPath rootedPath, boolean old) {
+    PathFragment relativePath = rootedPath.getRootRelativePath();
+    NavigableMap<PathFragment, RepositoryName> map =
+        old ? oldManagedDirectoriesRef.get() : managedDirectoriesRef.get();
+    Map.Entry<PathFragment, RepositoryName> entry = map.floorEntry(relativePath);
+    if (entry != null && relativePath.startsWith(entry.getKey())) {
+      return entry.getValue();
+    }
+    return null;
+  }
+
+  @Override
+  public ImmutableSet<PathFragment> getManagedDirectories(RepositoryName repositoryName) {
+    ImmutableSet<PathFragment> pathFragments = repoToDirMapRef.get().get(repositoryName);
+    return pathFragments != null ? pathFragments : ImmutableSet.of();
+  }
+
+  public void setManagedDirectories(ImmutableMap<PathFragment, RepositoryName> map) {
+    oldManagedDirectoriesRef.set(managedDirectoriesRef.get());
+    ImmutableSortedMap<PathFragment, RepositoryName> pathsMap = ImmutableSortedMap.copyOf(map);
+    managedDirectoriesRef.set(pathsMap);
+
+    Map<RepositoryName, Set<PathFragment>> reposMap = Maps.newHashMap();
+    for (Map.Entry<PathFragment, RepositoryName> entry : pathsMap.entrySet()) {
+      RepositoryName repositoryName = entry.getValue();
+      reposMap.computeIfAbsent(repositoryName, name -> Sets.newTreeSet()).add(entry.getKey());
+    }
+
+    ImmutableSortedMap.Builder<RepositoryName, ImmutableSet<PathFragment>> builder =
+        new ImmutableSortedMap.Builder<>(Comparator.comparing(RepositoryName::getName));
+    for (Map.Entry<RepositoryName, Set<PathFragment>> entry : reposMap.entrySet()) {
+      builder.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue()));
+    }
+    repoToDirMapRef.set(builder.build());
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java
index a4161cc..f0acce2 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorFunction.java
@@ -18,7 +18,9 @@
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Ordering;
 import com.google.devtools.build.lib.analysis.BlazeDirectories;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.events.Event;
@@ -44,11 +46,14 @@
 import com.google.devtools.build.skyframe.SkyValue;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Supplier;
+import java.util.stream.Collectors;
 import javax.annotation.Nullable;
 
 /**
@@ -93,6 +98,9 @@
   private final AtomicBoolean isFetch;
 
   private final BlazeDirectories directories;
+  // Managed directories mappings, pre-calculated and injected by SequencedSkyframeExecutor
+  // before each command.
+  private final ManagedDirectoriesKnowledge managedDirectoriesKnowledge;
 
   private final Supplier<Map<String, String>> clientEnvironmentSupplier;
 
@@ -101,12 +109,14 @@
       @Nullable RepositoryFunction skylarkHandler,
       AtomicBoolean isFetch,
       Supplier<Map<String, String>> clientEnvironmentSupplier,
-      BlazeDirectories directories) {
+      BlazeDirectories directories,
+      ManagedDirectoriesKnowledge managedDirectoriesKnowledge) {
     this.handlers = handlers;
     this.skylarkHandler = skylarkHandler;
     this.isFetch = isFetch;
     this.clientEnvironmentSupplier = clientEnvironmentSupplier;
     this.directories = directories;
+    this.managedDirectoriesKnowledge = managedDirectoriesKnowledge;
   }
 
   private void setupRepositoryRoot(Path repoRoot) throws RepositoryFunctionException {
@@ -154,9 +164,15 @@
     if (env.valuesMissing()) {
       return null;
     }
+    ImmutableSet<PathFragment> managedDirectories =
+        managedDirectoriesKnowledge.getManagedDirectories(repositoryName);
     DigestWriter digestWriter =
         new DigestWriter(
-            directories, repositoryName, rule, Preconditions.checkNotNull(ruleSpecificData));
+            directories,
+            repositoryName,
+            rule,
+            Preconditions.checkNotNull(ruleSpecificData),
+            managedDirectories);
 
     // Local repositories are fetched regardless of the marker file because the operation is
     // generally fast and they do not depend on non-local data, so it does not make much sense to
@@ -179,7 +195,11 @@
           if (env.valuesMissing()) {
             return null;
           }
-          return RepositoryDirectoryValue.builder().setPath(repoRoot).setDigest(markerHash).build();
+          return RepositoryDirectoryValue.builder()
+              .setPath(repoRoot)
+              .setDigest(markerHash)
+              .setManagedDirectories(managedDirectories)
+              .build();
         }
       }
     }
@@ -197,7 +217,7 @@
       // restart thus calling the possibly very slow (networking, decompression...) fetch()
       // operation again. So we write the marker file here immediately.
       byte[] digest = digestWriter.writeMarkerFile();
-      return builder.setDigest(digest).build();
+      return builder.setDigest(digest).setManagedDirectories(managedDirectories).build();
     }
 
     if (!repoRoot.exists()) {
@@ -223,7 +243,11 @@
                     + "run the build without the '--nofetch' command line option.",
                 rule.getName())));
 
-    return RepositoryDirectoryValue.builder().setPath(repoRoot).setFetchingDelayed().build();
+    return RepositoryDirectoryValue.builder()
+        .setPath(repoRoot)
+        .setFetchingDelayed()
+        .setManagedDirectories(managedDirectories)
+        .build();
   }
 
   private RepositoryFunction getHandler(Rule rule) {
@@ -337,6 +361,7 @@
   }
 
   private static class DigestWriter {
+    private static final String MANAGED_DIRECTORIES_MARKER = "$MANAGED";
     private final Path markerPath;
     private final Rule rule;
     private final Map<String, String> markerData;
@@ -346,11 +371,19 @@
         BlazeDirectories directories,
         RepositoryName repositoryName,
         Rule rule,
-        byte[] ruleSpecificData) {
+        byte[] ruleSpecificData,
+        ImmutableSet<PathFragment> managedDirectories) {
       ruleKey = computeRuleKey(rule, ruleSpecificData);
       markerPath = getMarkerPath(directories, repositoryName.strippedName());
       this.rule = rule;
       markerData = Maps.newHashMap();
+
+      List<PathFragment> directoriesList = Ordering.natural().sortedCopy(managedDirectories);
+      String directoriesString =
+          directoriesList.stream()
+              .map(PathFragment::getPathString)
+              .collect(Collectors.joining(" "));
+      markerData.put(MANAGED_DIRECTORIES_MARKER, directoriesString);
     }
 
     byte[] writeMarkerFile() throws RepositoryFunctionException {
@@ -396,7 +429,10 @@
         content = FileSystemUtils.readContent(markerPath, StandardCharsets.UTF_8);
         String markerRuleKey = readMarkerFile(content, markerData);
         boolean verified = false;
-        if (Preconditions.checkNotNull(ruleKey).equals(markerRuleKey)) {
+        if (Preconditions.checkNotNull(ruleKey).equals(markerRuleKey)
+            && Objects.equals(
+                markerData.get(MANAGED_DIRECTORIES_MARKER),
+                this.markerData.get(MANAGED_DIRECTORIES_MARKER))) {
           verified = handler.verifyMarkerData(rule, markerData, env);
           if (env.valuesMissing()) {
             return null;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDirectoryDirtinessChecker.java b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDirectoryDirtinessChecker.java
new file mode 100644
index 0000000..bbc0eaf
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDirectoryDirtinessChecker.java
@@ -0,0 +1,76 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.devtools.build.lib.rules.repository;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.devtools.build.lib.cmdline.RepositoryName;
+import com.google.devtools.build.lib.skyframe.SkyFunctions;
+import com.google.devtools.build.lib.skyframe.SkyValueDirtinessChecker;
+import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
+import com.google.devtools.build.skyframe.SkyKey;
+import com.google.devtools.build.skyframe.SkyValue;
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+/**
+ * A dirtiness checker that:
+ *
+ * <ul>
+ *   <li>Only dirties {@link RepositoryDirectoryValue}s, if they were produced in a {@code
+ *       --nofetch} build, so that they are re-created on subsequent {@code --fetch} builds. The
+ *       alternative solution would be to reify the value of the flag as a Skyframe value.
+ *   <li>Dirties repositories, if their managed directories were changed.
+ * </ul>
+ */
+@VisibleForTesting
+public class RepositoryDirectoryDirtinessChecker extends SkyValueDirtinessChecker {
+  private final ManagedDirectoriesKnowledge managedDirectoriesKnowledge;
+
+  public RepositoryDirectoryDirtinessChecker(
+      ManagedDirectoriesKnowledge managedDirectoriesKnowledge) {
+    this.managedDirectoriesKnowledge = managedDirectoriesKnowledge;
+  }
+
+  @Override
+  public boolean applies(SkyKey skyKey) {
+    return skyKey.functionName().equals(SkyFunctions.REPOSITORY_DIRECTORY);
+  }
+
+  @Override
+  public SkyValue createNewValue(SkyKey key, @Nullable TimestampGranularityMonitor tsgm) {
+    throw new UnsupportedOperationException();
+  }
+
+  @Override
+  public DirtyResult check(
+      SkyKey skyKey, SkyValue skyValue, @Nullable TimestampGranularityMonitor tsgm) {
+    RepositoryName repositoryName = (RepositoryName) skyKey.argument();
+    RepositoryDirectoryValue repositoryValue = (RepositoryDirectoryValue) skyValue;
+
+    if (!repositoryValue.repositoryExists()) {
+      return DirtyResult.notDirty(skyValue);
+    }
+    if (repositoryValue.isFetchingDelayed()) {
+      return DirtyResult.dirty(skyValue);
+    }
+
+    if (!Objects.equals(
+        managedDirectoriesKnowledge.getManagedDirectories(repositoryName),
+        repositoryValue.getManagedDirectories())) {
+      return DirtyResult.dirty(skyValue);
+    }
+    return DirtyResult.notDirty(skyValue);
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDirectoryValue.java b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDirectoryValue.java
index b48e9c6..f7c465b 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDirectoryValue.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/repository/RepositoryDirectoryValue.java
@@ -17,6 +17,7 @@
 import com.google.common.base.Objects;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Interner;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.concurrent.BlazeInterners;
@@ -24,11 +25,13 @@
 import com.google.devtools.build.lib.skyframe.SkyFunctions;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.vfs.Path;
+import com.google.devtools.build.lib.vfs.PathFragment;
 import com.google.devtools.build.skyframe.AbstractSkyKey;
 import com.google.devtools.build.skyframe.SkyFunctionName;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Map;
 import javax.annotation.Nullable;
 
@@ -50,6 +53,13 @@
 
   public abstract boolean isFetchingDelayed();
 
+  /**
+   * Returns the set of relative (to the workspace root) paths to managed directories for this
+   * repository. We need to keep this information in a value, since managed directories are part of
+   * the repository definition.
+   */
+  public abstract ImmutableSet<PathFragment> getManagedDirectories();
+
   /** Represents a successful repository lookup. */
   public static final class SuccessfulRepositoryDirectoryValue extends RepositoryDirectoryValue {
     private final Path path;
@@ -57,18 +67,21 @@
     @Nullable private final byte[] digest;
     @Nullable private final DirectoryListingValue sourceDir;
     private final ImmutableMap<SkyKey, SkyValue> fileValues;
+    private final ImmutableSet<PathFragment> managedDirectories;
 
     private SuccessfulRepositoryDirectoryValue(
         Path path,
         boolean fetchingDelayed,
         DirectoryListingValue sourceDir,
         byte[] digest,
-        ImmutableMap<SkyKey, SkyValue> fileValues) {
+        ImmutableMap<SkyKey, SkyValue> fileValues,
+        ImmutableSet<PathFragment> managedDirectories) {
       this.path = path;
       this.fetchingDelayed = fetchingDelayed;
       this.sourceDir = sourceDir;
       this.digest = digest;
       this.fileValues = fileValues;
+      this.managedDirectories = managedDirectories;
     }
 
     @Override
@@ -87,6 +100,11 @@
     }
 
     @Override
+    public ImmutableSet<PathFragment> getManagedDirectories() {
+      return managedDirectories;
+    }
+
+    @Override
     public boolean equals(Object other) {
       if (this == other) {
         return true;
@@ -97,14 +115,16 @@
         return Objects.equal(path, otherValue.path)
             && Objects.equal(sourceDir, otherValue.sourceDir)
             && Arrays.equals(digest, otherValue.digest)
-            && Objects.equal(fileValues, otherValue.fileValues);
+            && Objects.equal(fileValues, otherValue.fileValues)
+            && Objects.equal(managedDirectories, otherValue.managedDirectories);
       }
       return false;
     }
 
     @Override
     public int hashCode() {
-      return Objects.hashCode(path, sourceDir, Arrays.hashCode(digest), fileValues);
+      return Objects.hashCode(
+          path, sourceDir, Arrays.hashCode(digest), fileValues, managedDirectories);
     }
 
     @Override
@@ -131,6 +151,11 @@
     public boolean isFetchingDelayed() {
       throw new IllegalStateException();
     }
+
+    @Override
+    public ImmutableSet<PathFragment> getManagedDirectories() {
+      throw new IllegalStateException();
+    }
   }
 
   public static final NoRepositoryDirectoryValue NO_SUCH_REPOSITORY_VALUE =
@@ -173,6 +198,7 @@
     private byte[] digest = null;
     private DirectoryListingValue sourceDir = null;
     private Map<SkyKey, SkyValue> fileValues = ImmutableMap.of();
+    private ImmutableSet<PathFragment> managedDirectories = ImmutableSet.of();
 
     private Builder() {}
 
@@ -201,6 +227,11 @@
       return this;
     }
 
+    public Builder setManagedDirectories(Collection<PathFragment> managedDirectories) {
+      this.managedDirectories = ImmutableSet.copyOf(managedDirectories);
+      return this;
+    }
+
     public SuccessfulRepositoryDirectoryValue build() {
       Preconditions.checkNotNull(path, "Repository path must be specified!");
       // Only if fetching is delayed then we are allowed to have a null digest.
@@ -208,7 +239,12 @@
         Preconditions.checkNotNull(digest, "Repository marker digest must be specified!");
       }
       return new SuccessfulRepositoryDirectoryValue(
-          path, fetchingDelayed, sourceDir, digest, ImmutableMap.copyOf(fileValues));
+          path,
+          fetchingDelayed,
+          sourceDir,
+          digest,
+          ImmutableMap.copyOf(fileValues),
+          managedDirectories);
     }
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/WorkspaceBuilder.java b/src/main/java/com/google/devtools/build/lib/runtime/WorkspaceBuilder.java
index 1e9ee5c..bf10273 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/WorkspaceBuilder.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/WorkspaceBuilder.java
@@ -24,6 +24,7 @@
 import com.google.devtools.build.lib.packages.PackageFactory;
 import com.google.devtools.build.lib.profiler.memory.AllocationTracker;
 import com.google.devtools.build.lib.skyframe.DiffAwareness;
+import com.google.devtools.build.lib.skyframe.SequencedSkyframeExecutor.WorkspaceFileHeaderListener;
 import com.google.devtools.build.lib.skyframe.SequencedSkyframeExecutorFactory;
 import com.google.devtools.build.lib.skyframe.SkyValueDirtinessChecker;
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
@@ -52,6 +53,7 @@
   private final ImmutableList.Builder<SkyValueDirtinessChecker> customDirtinessCheckers =
       ImmutableList.builder();
   private AllocationTracker allocationTracker;
+  private WorkspaceFileHeaderListener workspaceFileHeaderListener;
 
   WorkspaceBuilder(BlazeDirectories directories, BinTools binTools) {
     this.directories = directories;
@@ -79,7 +81,8 @@
             ruleClassProvider.getBuildInfoFactories(),
             diffAwarenessFactories.build(),
             skyFunctions.build(),
-            customDirtinessCheckers.build());
+            customDirtinessCheckers.build(),
+            workspaceFileHeaderListener);
     return new BlazeWorkspace(
         runtime,
         directories,
@@ -153,4 +156,10 @@
     this.customDirtinessCheckers.add(Preconditions.checkNotNull(customDirtinessChecker));
     return this;
   }
+
+  public WorkspaceBuilder setWorkspaceFileHeaderListener(
+      WorkspaceFileHeaderListener workspaceFileHeaderListener) {
+    this.workspaceFileHeaderListener = workspaceFileHeaderListener;
+    return this;
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorFactory.java b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorFactory.java
index aba704e..ca668ea 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SequencedSkyframeExecutorFactory.java
@@ -21,9 +21,11 @@
 import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory;
 import com.google.devtools.build.lib.analysis.config.BuildOptions;
 import com.google.devtools.build.lib.packages.PackageFactory;
+import com.google.devtools.build.lib.skyframe.SequencedSkyframeExecutor.WorkspaceFileHeaderListener;
 import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunctionName;
+import javax.annotation.Nullable;
 
 /**
  * A factory of SkyframeExecutors that returns SequencedSkyframeExecutor.
@@ -46,7 +48,8 @@
       ImmutableList<BuildInfoFactory> buildInfoFactories,
       Iterable<? extends DiffAwareness.Factory> diffAwarenessFactories,
       ImmutableMap<SkyFunctionName, SkyFunction> extraSkyFunctions,
-      Iterable<SkyValueDirtinessChecker> customDirtinessCheckers) {
+      Iterable<SkyValueDirtinessChecker> customDirtinessCheckers,
+      @Nullable WorkspaceFileHeaderListener workspaceFileHeaderListener) {
     return BazelSkyframeExecutorConstants.newBazelSkyframeExecutorBuilder()
         .setPkgFactory(pkgFactory)
         .setFileSystem(fileSystem)
@@ -58,6 +61,7 @@
         .setDiffAwarenessFactories(diffAwarenessFactories)
         .setExtraSkyFunctions(extraSkyFunctions)
         .setCustomDirtinessCheckers(customDirtinessCheckers)
+        .setWorkspaceFileHeaderListener(workspaceFileHeaderListener)
         .build();
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyValueDirtinessChecker.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyValueDirtinessChecker.java
index ad852d6..6f51cf3 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyValueDirtinessChecker.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyValueDirtinessChecker.java
@@ -88,7 +88,7 @@
       this.newValue = newValue;
     }
 
-    boolean isDirty() {
+    public boolean isDirty() {
       return isDirty;
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutorFactory.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutorFactory.java
index 3cb5d11..504e4a4 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutorFactory.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutorFactory.java
@@ -20,6 +20,7 @@
 import com.google.devtools.build.lib.analysis.WorkspaceStatusAction.Factory;
 import com.google.devtools.build.lib.analysis.buildinfo.BuildInfoFactory;
 import com.google.devtools.build.lib.packages.PackageFactory;
+import com.google.devtools.build.lib.skyframe.SequencedSkyframeExecutor.WorkspaceFileHeaderListener;
 import com.google.devtools.build.lib.util.AbruptExitException;
 import com.google.devtools.build.lib.vfs.FileSystem;
 import com.google.devtools.build.skyframe.SkyFunction;
@@ -33,15 +34,11 @@
   /**
    * Creates an instance of SkyframeExecutor
    *
-   * @param tsgm timestamp granularity monitor
    * @param pkgFactory the package factory
    * @param fileSystem the Blaze file system
    * @param directories Blaze directories
    * @param workspaceStatusActionFactory a factory for creating WorkspaceStatusAction objects
    * @param buildInfoFactories list of BuildInfoFactories
-   * @param diffAwarenessFactories
-   * @param extraSkyFunctions
-   * @param customDirtinessCheckers
    * @return an instance of the SkyframeExecutor
    * @throws AbruptExitException if the executor cannot be created
    */
@@ -54,6 +51,7 @@
       ImmutableList<BuildInfoFactory> buildInfoFactories,
       Iterable<? extends DiffAwareness.Factory> diffAwarenessFactories,
       ImmutableMap<SkyFunctionName, SkyFunction> extraSkyFunctions,
-      Iterable<SkyValueDirtinessChecker> customDirtinessCheckers)
+      Iterable<SkyValueDirtinessChecker> customDirtinessCheckers,
+      WorkspaceFileHeaderListener workspaceFileHeaderListener)
       throws AbruptExitException;
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/packages/BazelPackageLoader.java b/src/main/java/com/google/devtools/build/lib/skyframe/packages/BazelPackageLoader.java
index d0476fb..5ff0d78 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/packages/BazelPackageLoader.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/packages/BazelPackageLoader.java
@@ -27,6 +27,7 @@
 import com.google.devtools.build.lib.bazel.rules.BazelRulesModule;
 import com.google.devtools.build.lib.packages.BuildFileName;
 import com.google.devtools.build.lib.packages.PackageFactory.EnvironmentExtension;
+import com.google.devtools.build.lib.rules.repository.ManagedDirectoriesKnowledge;
 import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
 import com.google.devtools.build.lib.rules.repository.RepositoryLoaderFunction;
 import com.google.devtools.build.lib.skyframe.ActionEnvironmentFunction;
@@ -114,7 +115,8 @@
                       new SkylarkRepositoryFunction(httpDownloader),
                       isFetch,
                       ImmutableMap::of,
-                      directories))
+                      directories,
+                      ManagedDirectoriesKnowledge.NO_MANAGED_DIRECTORIES))
               .put(SkyFunctions.REPOSITORY, new RepositoryLoaderFunction())
               .put(MavenServerFunction.NAME, new MavenServerFunction(directories))
               .build());
diff --git a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisMock.java b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisMock.java
index c7084db..3fc0354 100644
--- a/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisMock.java
+++ b/src/test/java/com/google/devtools/build/lib/analysis/util/AnalysisMock.java
@@ -32,6 +32,7 @@
 import com.google.devtools.build.lib.rules.cpp.CcSkyframeFdoSupportValue;
 import com.google.devtools.build.lib.rules.repository.LocalRepositoryFunction;
 import com.google.devtools.build.lib.rules.repository.LocalRepositoryRule;
+import com.google.devtools.build.lib.rules.repository.ManagedDirectoriesKnowledge;
 import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
 import com.google.devtools.build.lib.rules.repository.RepositoryFunction;
 import com.google.devtools.build.lib.rules.repository.RepositoryLoaderFunction;
@@ -131,7 +132,8 @@
             null,
             new AtomicBoolean(true),
             ImmutableMap::of,
-            directories),
+            directories,
+            ManagedDirectoriesKnowledge.NO_MANAGED_DIRECTORIES),
         SkyFunctions.REPOSITORY,
         new RepositoryLoaderFunction(),
         CcSkyframeFdoSupportValue.SKYFUNCTION,
diff --git a/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryIntegrationTest.java b/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryIntegrationTest.java
index 4daa6b8..af90185 100644
--- a/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryIntegrationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/bazel/repository/skylark/SkylarkRepositoryIntegrationTest.java
@@ -28,6 +28,7 @@
 import com.google.devtools.build.lib.packages.NoSuchPackageException;
 import com.google.devtools.build.lib.rules.repository.LocalRepositoryFunction;
 import com.google.devtools.build.lib.rules.repository.LocalRepositoryRule;
+import com.google.devtools.build.lib.rules.repository.ManagedDirectoriesKnowledge;
 import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
 import com.google.devtools.build.lib.rules.repository.RepositoryFunction;
 import com.google.devtools.build.lib.rules.repository.RepositoryLoaderFunction;
@@ -84,7 +85,8 @@
               skylarkRepositoryFunction,
               new AtomicBoolean(true),
               ImmutableMap::of,
-              directories);
+              directories,
+              ManagedDirectoriesKnowledge.NO_MANAGED_DIRECTORIES);
       return ImmutableMap.of(
           SkyFunctions.REPOSITORY_DIRECTORY,
           function,
diff --git a/src/test/java/com/google/devtools/build/lib/rules/repository/BUILD b/src/test/java/com/google/devtools/build/lib/rules/repository/BUILD
index a66ac9b..7c484fe 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/repository/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/rules/repository/BUILD
@@ -27,8 +27,10 @@
         "//src/main/java/com/google/devtools/build/lib:unix",
         "//src/main/java/com/google/devtools/build/lib:util",
         "//src/main/java/com/google/devtools/build/lib/actions",
+        "//src/main/java/com/google/devtools/build/lib/bazel/repository/downloader",
         "//src/main/java/com/google/devtools/build/lib/cmdline",
         "//src/main/java/com/google/devtools/build/lib/rules/cpp",
+        "//src/main/java/com/google/devtools/build/lib/skylarkbuildapi/repository",
         "//src/main/java/com/google/devtools/build/lib/vfs",
         "//src/main/java/com/google/devtools/build/skyframe",
         "//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
diff --git a/src/test/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorTest.java b/src/test/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorTest.java
index 02ec42d..c728520 100644
--- a/src/test/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/rules/repository/RepositoryDelegatorTest.java
@@ -19,16 +19,27 @@
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.actions.FileStateValue;
 import com.google.devtools.build.lib.actions.FileValue;
 import com.google.devtools.build.lib.analysis.BlazeDirectories;
 import com.google.devtools.build.lib.analysis.ConfiguredRuleClassProvider;
 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.HttpDownloader;
+import com.google.devtools.build.lib.bazel.repository.skylark.SkylarkRepositoryFunction;
+import com.google.devtools.build.lib.bazel.repository.skylark.SkylarkRepositoryModule;
 import com.google.devtools.build.lib.cmdline.RepositoryName;
 import com.google.devtools.build.lib.events.StoredEventHandler;
+import com.google.devtools.build.lib.packages.PackageFactory;
+import com.google.devtools.build.lib.packages.Rule;
 import com.google.devtools.build.lib.packages.WorkspaceFileValue;
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
+import com.google.devtools.build.lib.rules.repository.RepositoryDirectoryValue.SuccessfulRepositoryDirectoryValue;
+import com.google.devtools.build.lib.skyframe.ASTFileLookupFunction;
 import com.google.devtools.build.lib.skyframe.BazelSkyframeExecutorConstants;
+import com.google.devtools.build.lib.skyframe.BlacklistedPackagePrefixesFunction;
+import com.google.devtools.build.lib.skyframe.ContainingPackageLookupFunction;
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper;
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
 import com.google.devtools.build.lib.skyframe.ExternalPackageFunction;
@@ -38,12 +49,16 @@
 import com.google.devtools.build.lib.skyframe.PackageFunction;
 import com.google.devtools.build.lib.skyframe.PackageLookupFunction;
 import com.google.devtools.build.lib.skyframe.PackageLookupFunction.CrossRepositoryLabelViolationStrategy;
+import com.google.devtools.build.lib.skyframe.PrecomputedFunction;
 import com.google.devtools.build.lib.skyframe.PrecomputedValue;
 import com.google.devtools.build.lib.skyframe.SkyFunctions;
+import com.google.devtools.build.lib.skyframe.SkylarkImportLookupFunction;
 import com.google.devtools.build.lib.skyframe.WorkspaceASTFunction;
 import com.google.devtools.build.lib.skyframe.WorkspaceFileFunction;
+import com.google.devtools.build.lib.skylarkbuildapi.repository.RepositoryBootstrap;
 import com.google.devtools.build.lib.syntax.StarlarkSemantics;
 import com.google.devtools.build.lib.testutil.FoundationTestCase;
+import com.google.devtools.build.lib.testutil.ManualClock;
 import com.google.devtools.build.lib.testutil.TestConstants;
 import com.google.devtools.build.lib.testutil.TestRuleClassProvider;
 import com.google.devtools.build.lib.util.io.TimestampGranularityMonitor;
@@ -60,15 +75,19 @@
 import com.google.devtools.build.skyframe.SequencedRecordingDifferencer;
 import com.google.devtools.build.skyframe.SequentialBuildDriver;
 import com.google.devtools.build.skyframe.SkyFunction;
+import com.google.devtools.build.skyframe.SkyFunction.Environment;
 import com.google.devtools.build.skyframe.SkyFunctionName;
 import com.google.devtools.build.skyframe.SkyKey;
 import com.google.devtools.build.skyframe.SkyValue;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
+import javax.annotation.Nullable;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
 
 /**
  * Tests for {@link RepositoryDelegatorFunction}
@@ -78,32 +97,60 @@
   private RepositoryDelegatorFunction delegatorFunction;
   private Path overrideDirectory;
   private SequentialBuildDriver driver;
+  private ManagedDirectoriesKnowledgeImpl managedDirectoriesKnowledge;
+  private RecordingDifferencer differencer;
+  private TestSkylarkRepositoryFunction testSkylarkRepositoryFunction;
+  private Path rootPath;
 
   @Before
   public void setupDelegator() throws Exception {
-    Path root = scratch.dir("/outputbase");
+    rootPath = scratch.dir("/outputbase");
     BlazeDirectories directories =
         new BlazeDirectories(
-            new ServerDirectories(root, root, root),
-            root,
+            new ServerDirectories(rootPath, rootPath, rootPath),
+            rootPath,
             /* defaultSystemJavabase= */ null,
             TestConstants.PRODUCT_NAME);
+    managedDirectoriesKnowledge = new ManagedDirectoriesKnowledgeImpl();
+    HttpDownloader downloader = Mockito.mock(HttpDownloader.class);
+    RepositoryFunction localRepositoryFunction = new LocalRepositoryFunction();
+    testSkylarkRepositoryFunction = new TestSkylarkRepositoryFunction(downloader);
+    ImmutableMap<String, RepositoryFunction> repositoryHandlers =
+        ImmutableMap.of(LocalRepositoryRule.NAME, localRepositoryFunction);
     delegatorFunction =
         new RepositoryDelegatorFunction(
-            ImmutableMap.of(), null, new AtomicBoolean(true), ImmutableMap::of, directories);
+            repositoryHandlers,
+            testSkylarkRepositoryFunction,
+            new AtomicBoolean(true),
+            ImmutableMap::of,
+            directories,
+            managedDirectoriesKnowledge);
     AtomicReference<PathPackageLocator> pkgLocator =
         new AtomicReference<>(
             new PathPackageLocator(
-                root,
-                ImmutableList.of(Root.fromPath(root)),
+                rootPath,
+                ImmutableList.of(Root.fromPath(rootPath)),
                 BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY));
     ExternalFilesHelper externalFilesHelper = ExternalFilesHelper.createForTesting(
         pkgLocator,
         ExternalFileAction.DEPEND_ON_EXTERNAL_PKG_FOR_EXTERNAL_REPO_PATHS,
         directories);
-    RecordingDifferencer differencer = new SequencedRecordingDifferencer();
-    ConfiguredRuleClassProvider ruleClassProvider =
-        TestRuleClassProvider.getRuleClassProvider(true);
+    differencer = new SequencedRecordingDifferencer();
+
+    ConfiguredRuleClassProvider.Builder builder = new ConfiguredRuleClassProvider.Builder();
+    TestRuleClassProvider.addStandardRules(builder);
+    builder
+        .clearWorkspaceFileSuffixForTesting()
+        .addSkylarkBootstrap(new RepositoryBootstrap(new SkylarkRepositoryModule()));
+    ConfiguredRuleClassProvider ruleClassProvider = builder.build();
+
+    PackageFactory.BuilderForTesting pkgFactoryBuilder =
+        AnalysisMock.get().getPackageFactoryBuilderForTesting(directories);
+    SkylarkImportLookupFunction skylarkImportLookupFunction =
+        new SkylarkImportLookupFunction(
+            ruleClassProvider, pkgFactoryBuilder.build(ruleClassProvider, fileSystem));
+    skylarkImportLookupFunction.resetCache();
+
     MemoizingEvaluator evaluator =
         new InMemoryMemoizingEvaluator(
             ImmutableMap.<SkyFunctionName, SkyFunction>builder()
@@ -121,7 +168,7 @@
                 .put(
                     SkyFunctions.PACKAGE_LOOKUP,
                     new PackageLookupFunction(
-                        null,
+                        new AtomicReference<>(ImmutableSet.of()),
                         CrossRepositoryLabelViolationStrategy.ERROR,
                         BazelSkyframeExecutorConstants.BUILD_FILES_BY_PRIORITY))
                 .put(SkyFunctions.WORKSPACE_AST, new WorkspaceASTFunction(ruleClassProvider))
@@ -133,29 +180,44 @@
                             .builder(directories)
                             .build(ruleClassProvider, fileSystem),
                         directories,
-                        /*skylarkImportLookupFunctionForInlining=*/ null))
+                        skylarkImportLookupFunction))
+                .put(SkyFunctions.REPOSITORY, new RepositoryLoaderFunction())
                 .put(SkyFunctions.LOCAL_REPOSITORY_LOOKUP, new LocalRepositoryLookupFunction())
                 .put(SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction())
+                .put(SkyFunctions.PRECOMPUTED, new PrecomputedFunction())
+                .put(SkyFunctions.AST_FILE_LOOKUP, new ASTFileLookupFunction(ruleClassProvider))
+                .put(SkyFunctions.CONTAINING_PACKAGE_LOOKUP, new ContainingPackageLookupFunction())
+                .put(
+                    SkyFunctions.BLACKLISTED_PACKAGE_PREFIXES,
+                    new BlacklistedPackagePrefixesFunction(
+                        /*hardcodedBlacklistedPackagePrefixes=*/ ImmutableSet.of(),
+                        /*additionalBlacklistedPackagePrefixesFile=*/ PathFragment.EMPTY_FRAGMENT))
+                .put(SkyFunctions.RESOLVED_HASH_VALUES, new ResolvedHashesFunction())
                 .build(),
             differencer);
     driver = new SequentialBuildDriver(evaluator);
     overrideDirectory = scratch.dir("/foo");
     scratch.file("/foo/WORKSPACE");
-    RepositoryDelegatorFunction.REPOSITORY_OVERRIDES.set(
-        differencer,
-        ImmutableMap.<RepositoryName, PathFragment>builder()
-            .put(RepositoryName.createFromValidStrippedName("foo"), overrideDirectory.asFragment())
-            .build());
+    RepositoryDelegatorFunction.REPOSITORY_OVERRIDES.set(differencer, ImmutableMap.of());
     RepositoryDelegatorFunction.DEPENDENCY_FOR_UNCONDITIONAL_FETCHING.set(
         differencer, RepositoryDelegatorFunction.DONT_FETCH_UNCONDITIONALLY);
     PrecomputedValue.PATH_PACKAGE_LOCATOR.set(differencer, pkgLocator.get());
     PrecomputedValue.STARLARK_SEMANTICS.set(differencer, StarlarkSemantics.DEFAULT_SEMANTICS);
     RepositoryDelegatorFunction.RESOLVED_FILE_INSTEAD_OF_WORKSPACE.set(
         differencer, Optional.<RootedPath>absent());
+    PrecomputedValue.REPO_ENV.set(differencer, ImmutableMap.of());
+    RepositoryDelegatorFunction.OUTPUT_VERIFICATION_REPOSITORY_RULES.set(
+        differencer, ImmutableSet.of());
+    RepositoryDelegatorFunction.RESOLVED_FILE_FOR_VERIFICATION.set(differencer, Optional.absent());
   }
 
   @Test
   public void testOverride() throws Exception {
+    RepositoryDelegatorFunction.REPOSITORY_OVERRIDES.set(
+        differencer,
+        ImmutableMap.of(
+            RepositoryName.createFromValidStrippedName("foo"), overrideDirectory.asFragment()));
+
     StoredEventHandler eventHandler = new StoredEventHandler();
     SkyKey key = RepositoryDirectoryValue.key(RepositoryName.createFromValidStrippedName("foo"));
     EvaluationContext evaluationContext =
@@ -174,4 +236,148 @@
     assertThat(actualPath.readSymbolicLink()).isEqualTo(overrideDirectory.asFragment());
   }
 
+  @Test
+  public void testRepositoryDirtinessChecker() throws Exception {
+    TimestampGranularityMonitor tsgm = new TimestampGranularityMonitor(new ManualClock());
+    ManagedDirectoriesKnowledgeImpl knowledge = new ManagedDirectoriesKnowledgeImpl();
+
+    RepositoryDirectoryDirtinessChecker checker =
+        new RepositoryDirectoryDirtinessChecker(knowledge);
+    RepositoryName repositoryName = RepositoryName.create("@repo");
+    RepositoryDirectoryValue.Key key = RepositoryDirectoryValue.key(repositoryName);
+
+    SuccessfulRepositoryDirectoryValue usual =
+        RepositoryDirectoryValue.builder()
+            .setPath(rootDirectory.getRelative("a"))
+            .setDigest(new byte[] {1})
+            .build();
+
+    assertThat(checker.check(key, usual, tsgm).isDirty()).isFalse();
+
+    SuccessfulRepositoryDirectoryValue fetchDelayed =
+        RepositoryDirectoryValue.builder()
+            .setPath(rootDirectory.getRelative("b"))
+            .setFetchingDelayed()
+            .build();
+
+    assertThat(checker.check(key, fetchDelayed, tsgm).isDirty()).isTrue();
+
+    SuccessfulRepositoryDirectoryValue withManagedDirectories =
+        RepositoryDirectoryValue.builder()
+            .setPath(rootDirectory.getRelative("c"))
+            .setDigest(new byte[] {1})
+            .setManagedDirectories(ImmutableSet.of(PathFragment.create("m")))
+            .build();
+
+    assertThat(checker.check(key, withManagedDirectories, tsgm).isDirty()).isTrue();
+
+    knowledge.setManagedDirectories(
+        ImmutableMap.of(PathFragment.create("m"), RepositoryName.create("@other")));
+    assertThat(checker.check(key, withManagedDirectories, tsgm).isDirty()).isTrue();
+
+    knowledge.setManagedDirectories(ImmutableMap.of(PathFragment.create("m"), repositoryName));
+    assertThat(checker.check(key, withManagedDirectories, tsgm).isDirty()).isFalse();
+  }
+
+  @Test
+  public void testManagedDirectoriesCauseRepositoryReFetches() throws Exception {
+    scratch.file(rootPath.getRelative("BUILD").getPathString());
+    scratch.file(
+        rootPath.getRelative("repo_rule.bzl").getPathString(),
+        "def _impl(rctx):",
+        " rctx.file('BUILD', '')",
+        "fictive_repo_rule = repository_rule(implementation = _impl)");
+    scratch.overwriteFile(
+        rootPath.getRelative("WORKSPACE").getPathString(),
+        "workspace(name = 'abc')",
+        "load(':repo_rule.bzl', 'fictive_repo_rule')",
+        "fictive_repo_rule(name = 'repo1')");
+
+    // Managed directories from workspace() attribute will not be parsed by this test, since
+    // we are not calling SequencedSkyframeExecutor.
+    // That's why we will directly fill managed directories value (the corresponding structure
+    // is passed to RepositoryDelegatorFunction during construction).
+    managedDirectoriesKnowledge.setManagedDirectories(
+        ImmutableMap.of(PathFragment.create("dir1"), RepositoryName.create("@repo1")));
+
+    StarlarkSemantics semantics =
+        StarlarkSemantics.builderWithDefaults()
+            .experimentalAllowIncrementalRepositoryUpdates(true)
+            .build();
+    PrecomputedValue.STARLARK_SEMANTICS.set(differencer, semantics);
+
+    loadRepo("repo1");
+
+    assertThat(testSkylarkRepositoryFunction.isFetchCalled()).isTrue();
+    testSkylarkRepositoryFunction.reset();
+
+    loadRepo("repo1");
+    assertThat(testSkylarkRepositoryFunction.isFetchCalled()).isFalse();
+    testSkylarkRepositoryFunction.reset();
+
+    managedDirectoriesKnowledge.setManagedDirectories(
+        ImmutableMap.of(
+            PathFragment.create("dir1"),
+            RepositoryName.create("@repo1"),
+            PathFragment.create("dir2"),
+            RepositoryName.create("@repo1")));
+    loadRepo("repo1");
+
+    assertThat(testSkylarkRepositoryFunction.isFetchCalled()).isTrue();
+    testSkylarkRepositoryFunction.reset();
+
+    managedDirectoriesKnowledge.setManagedDirectories(ImmutableMap.of());
+    loadRepo("repo1");
+
+    assertThat(testSkylarkRepositoryFunction.isFetchCalled()).isTrue();
+    testSkylarkRepositoryFunction.reset();
+  }
+
+  private void loadRepo(String strippedRepoName) throws InterruptedException {
+    StoredEventHandler eventHandler = new StoredEventHandler();
+    SkyKey key =
+        RepositoryDirectoryValue.key(RepositoryName.createFromValidStrippedName(strippedRepoName));
+    // Make it be evaluated every time, as we are testing evaluation.
+    differencer.invalidate(ImmutableSet.of(key));
+    EvaluationContext evaluationContext =
+        EvaluationContext.newBuilder()
+            .setKeepGoing(false)
+            .setNumThreads(8)
+            .setEventHander(eventHandler)
+            .build();
+    EvaluationResult<SkyValue> result = driver.evaluate(ImmutableList.of(key), evaluationContext);
+    assertThat(result.hasError()).isFalse();
+    RepositoryDirectoryValue repositoryDirectoryValue = (RepositoryDirectoryValue) result.get(key);
+    assertThat(repositoryDirectoryValue.repositoryExists()).isTrue();
+  }
+
+  private static class TestSkylarkRepositoryFunction extends SkylarkRepositoryFunction {
+    private boolean fetchCalled = false;
+
+    private TestSkylarkRepositoryFunction(HttpDownloader downloader) {
+      super(downloader);
+    }
+
+    public void reset() {
+      fetchCalled = false;
+    }
+
+    private boolean isFetchCalled() {
+      return fetchCalled;
+    }
+
+    @Nullable
+    @Override
+    public RepositoryDirectoryValue.Builder fetch(
+        Rule rule,
+        Path outputDirectory,
+        BlazeDirectories directories,
+        Environment env,
+        Map<String, String> markerData,
+        SkyKey key)
+        throws RepositoryFunctionException, InterruptedException {
+      fetchCalled = true;
+      return super.fetch(rule, outputDirectory, directories, env, markerData, key);
+    }
+  }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupFunctionTest.java
index d560875..b42bcb6 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ContainingPackageLookupFunctionTest.java
@@ -35,6 +35,7 @@
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
 import com.google.devtools.build.lib.rules.repository.LocalRepositoryFunction;
 import com.google.devtools.build.lib.rules.repository.LocalRepositoryRule;
+import com.google.devtools.build.lib.rules.repository.ManagedDirectoriesKnowledge;
 import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
 import com.google.devtools.build.lib.rules.repository.RepositoryFunction;
 import com.google.devtools.build.lib.rules.repository.RepositoryLoaderFunction;
@@ -154,7 +155,12 @@
     skyFunctions.put(
         SkyFunctions.REPOSITORY_DIRECTORY,
         new RepositoryDelegatorFunction(
-            repositoryHandlers, null, new AtomicBoolean(true), ImmutableMap::of, directories));
+            repositoryHandlers,
+            null,
+            new AtomicBoolean(true),
+            ImmutableMap::of,
+            directories,
+            ManagedDirectoriesKnowledge.NO_MANAGED_DIRECTORIES));
     skyFunctions.put(SkyFunctions.REPOSITORY, new RepositoryLoaderFunction());
 
     differencer = new SequencedRecordingDifferencer();
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PackageLookupFunctionTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PackageLookupFunctionTest.java
index 022867a..ec11210 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PackageLookupFunctionTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/PackageLookupFunctionTest.java
@@ -39,6 +39,7 @@
 import com.google.devtools.build.lib.pkgcache.PathPackageLocator;
 import com.google.devtools.build.lib.rules.repository.LocalRepositoryFunction;
 import com.google.devtools.build.lib.rules.repository.LocalRepositoryRule;
+import com.google.devtools.build.lib.rules.repository.ManagedDirectoriesKnowledge;
 import com.google.devtools.build.lib.rules.repository.RepositoryDelegatorFunction;
 import com.google.devtools.build.lib.rules.repository.RepositoryFunction;
 import com.google.devtools.build.lib.rules.repository.RepositoryLoaderFunction;
@@ -164,7 +165,12 @@
     skyFunctions.put(
         SkyFunctions.REPOSITORY_DIRECTORY,
         new RepositoryDelegatorFunction(
-            repositoryHandlers, null, new AtomicBoolean(true), ImmutableMap::of, directories));
+            repositoryHandlers,
+            null,
+            new AtomicBoolean(true),
+            ImmutableMap::of,
+            directories,
+            ManagedDirectoriesKnowledge.NO_MANAGED_DIRECTORIES));
     skyFunctions.put(SkyFunctions.REPOSITORY, new RepositoryLoaderFunction());
 
     differencer = new SequencedRecordingDifferencer();