private static void visitGitDirVfs(@NotNull VirtualFile gitDir) {
   gitDir.getChildren();
   for (String subdir : GitRepositoryFiles.getSubDirRelativePaths()) {
     VirtualFile dir = gitDir.findFileByRelativePath(subdir);
     // process recursively, because we need to visit all branches under refs/heads and
     // refs/remotes
     visitAllChildrenRecursively(dir);
   }
 }
  GitRepositoryUpdater(GitRepository repository) {
    VirtualFile gitDir = repository.getGitDir();
    myWatchRequest = LocalFileSystem.getInstance().addRootToWatch(gitDir.getPath(), true);

    myRepositoryFiles = GitRepositoryFiles.getInstance(gitDir);
    visitGitDirVfs(gitDir);
    myHeadsDir = VcsUtil.getVirtualFile(myRepositoryFiles.getRefsHeadsPath());
    myRemotesDir = VcsUtil.getVirtualFile(myRepositoryFiles.getRefsRemotesPath());

    Project project = repository.getProject();
    myUpdateQueue =
        new QueueProcessor<GitRepository.TrackedTopic>(
            new Updater(repository), project.getDisposed());
    if (!project.isDisposed()) {
      myMessageBusConnection = project.getMessageBus().connect();
      myMessageBusConnection.subscribe(VirtualFileManager.VFS_CHANGES, this);
    } else {
      myMessageBusConnection = null;
    }
  }
  @Override
  public void after(@NotNull List<? extends VFileEvent> events) {
    // which files in .git were changed
    boolean configChanged = false;
    boolean headChanged = false;
    boolean branchFileChanged = false;
    boolean packedRefsChanged = false;
    boolean rebaseFileChanged = false;
    boolean mergeFileChanged = false;
    for (VFileEvent event : events) {
      final VirtualFile file = event.getFile();
      if (file == null) {
        continue;
      }
      String filePath = GitFileUtils.stripFileProtocolPrefix(file.getPath());
      if (myRepositoryFiles.isConfigFile(filePath)) {
        configChanged = true;
      } else if (myRepositoryFiles.isHeadFile(filePath)) {
        headChanged = true;
      } else if (myRepositoryFiles.isBranchFile(filePath)) {
        // it is also possible, that a local branch with complex name ("myfolder/mybranch") was
        // created => the folder also to be watched.
        branchFileChanged = true;
        visitAllChildrenRecursively(myHeadsDir);
      } else if (myRepositoryFiles.isRemoteBranchFile(filePath)) {
        // it is possible, that a branch from a new remote was fetch => we need to add new remote
        // folder to the VFS
        branchFileChanged = true;
        visitAllChildrenRecursively(myRemotesDir);
      } else if (myRepositoryFiles.isPackedRefs(filePath)) {
        packedRefsChanged = true;
      } else if (myRepositoryFiles.isRebaseFile(filePath)) {
        rebaseFileChanged = true;
      } else if (myRepositoryFiles.isMergeFile(filePath)) {
        mergeFileChanged = true;
      }
    }

    // what should be updated in GitRepository
    boolean updateCurrentBranch = false;
    boolean updateCurrentRevision = false;
    boolean updateState = false;
    boolean updateBranches = false;
    if (headChanged) {
      updateCurrentBranch = true;
      updateCurrentRevision = true;
      updateState = true;
    }
    if (branchFileChanged) {
      updateCurrentRevision = true;
      updateBranches = true;
    }
    if (rebaseFileChanged || mergeFileChanged) {
      updateState = true;
    }
    if (packedRefsChanged) {
      updateCurrentBranch = true;
      updateBranches = true;
    }

    // update GitRepository on pooled thread, because it requires reading from disk and parsing
    // data.
    if (updateCurrentBranch) {
      myUpdateQueue.add(GitRepository.TrackedTopic.CURRENT_BRANCH);
    }
    if (updateCurrentRevision) {
      myUpdateQueue.add(GitRepository.TrackedTopic.CURRENT_REVISION);
    }
    if (updateState) {
      myUpdateQueue.add(GitRepository.TrackedTopic.STATE);
    }
    if (updateBranches) {
      myUpdateQueue.add(GitRepository.TrackedTopic.BRANCHES);
    }
    if (configChanged) {
      myUpdateQueue.add(GitRepository.TrackedTopic.CONFIG);
    }
  }