private void UpdatePlaceholders()

in GVFS/GVFS.Virtualization/Projection/GitIndexProjection.cs [1227:1375]


        private void UpdatePlaceholders()
        {
            Stopwatch stopwatch = new Stopwatch();
            List<IPlaceholderData> placeholderFilesListCopy;
            List<IPlaceholderData> placeholderFoldersListCopy;
            this.placeholderDatabase.GetAllEntries(out placeholderFilesListCopy, out placeholderFoldersListCopy);

            EventMetadata metadata = new EventMetadata();
            metadata.Add("File placeholder count", placeholderFilesListCopy.Count);
            metadata.Add("Folder placeholders count", placeholderFoldersListCopy.Count);

            using (ITracer activity = this.context.Tracer.StartActivity("UpdatePlaceholders", EventLevel.Informational, metadata))
            {
                // folderPlaceholdersToKeep always contains the empty path so as to avoid unnecessary attempts
                // to remove the repository's root folder.
                ConcurrentHashSet<string> folderPlaceholdersToKeep = new ConcurrentHashSet<string>(GVFSPlatform.Instance.Constants.PathComparer);
                folderPlaceholdersToKeep.Add(string.Empty);

                stopwatch.Restart();
                this.MultiThreadedPlaceholderUpdatesAndDeletes(placeholderFilesListCopy, folderPlaceholdersToKeep);
                stopwatch.Stop();

                long millisecondsUpdatingFilePlaceholders = stopwatch.ElapsedMilliseconds;

                stopwatch.Restart();
                this.blobSizes.Flush();

                int deleteFolderPlaceholderAttempted = 0;
                int folderPlaceholdersDeleted = 0;
                int folderPlaceholdersPathNotFound = 0;
                int folderPlaceholdersShaUpdate = 0;

                // A hash of the placeholders is only required if the platform expands directories
                // This is using a in memory HashSet for speed in processing
                // ~1 million placeholders was taking over 10 seconds to check for existence where the
                // HashSet was only taking a a few milliseconds
                HashSet<string> existingPlaceholders = null;
                if (GVFSPlatform.Instance.KernelDriver.EnumerationExpandsDirectories)
                {
                    // Since only the file placeholders have been processed we can still use the folder placeholder list
                    // that was returned by GetAllEntries but we need to get the file paths that are now in the database.
                    // This is to avoid the extra time and processing to get all the placeholders when there are many
                    // folder placeholders and only a few file placeholders.
                    IEnumerable<string> allPlaceholders = placeholderFoldersListCopy
                        .Select(x => x.Path)
                        .Union(this.placeholderDatabase.GetAllFilePaths());
                    existingPlaceholders = new HashSet<string>(allPlaceholders, GVFSPlatform.Instance.Constants.PathComparer);
                }

                // Order the folders in decscending order so that we walk the tree from bottom up.
                // Traversing the folders in this order:
                //  1. Ensures child folders are deleted before their parents
                //  2. Ensures that folders that have been deleted by git (but are still in the projection) are found before their
                //     parent folder is re-expanded (only applies on platforms where EnumerationExpandsDirectories is true)
                foreach (IPlaceholderData folderPlaceholder in placeholderFoldersListCopy.OrderByDescending(x => x.Path))
                {
                    bool keepFolder = true;
                    if (!folderPlaceholdersToKeep.Contains(folderPlaceholder.Path))
                    {
                        bool isProjected = this.IsPathProjected(folderPlaceholder.Path, out string fileName, out bool isFolder);

                        // Check the projection for the folder to determine if the folder needs to be deleted
                        // The delete will be attempted if one of the following is true
                        // 1. not in the projection anymore
                        // 2. in the projection but is not a folder in the projection
                        // 3. Folder placeholder is a possible tombstone
                        if (!isProjected ||
                            !isFolder ||
                            folderPlaceholder.IsPossibleTombstoneFolder)
                        {
                            FSResult result = this.RemoveFolderPlaceholderIfEmpty(folderPlaceholder);
                            if (result == FSResult.Ok)
                            {
                                ++folderPlaceholdersDeleted;
                                keepFolder = false;
                            }
                            else if (result == FSResult.FileOrPathNotFound)
                            {
                                ++folderPlaceholdersPathNotFound;
                                keepFolder = false;
                            }

                            ++deleteFolderPlaceholderAttempted;
                        }

                        if (keepFolder)
                        {
                            this.AddParentFoldersToListToKeep(folderPlaceholder.Path, folderPlaceholdersToKeep);
                        }
                    }

                    if (keepFolder)
                    {
                        if (this.updateUsnJournal && this.TryGetFolderDataFromTreeUsingPath(folderPlaceholder.Path, out FolderData folderData))
                        {
                            string newFolderSha = folderData.HashedChildrenNamesSha();

                            if (folderPlaceholder.Sha != newFolderSha)
                            {
                                ++folderPlaceholdersShaUpdate;

                                // Write and delete a file so USN journal will have the folder as being changed
                                string tempFilePath = Path.Combine(this.context.Enlistment.WorkingDirectoryRoot, folderPlaceholder.Path, ".vfs_usn_folder_update.tmp");
                                if (this.context.FileSystem.TryWriteAllText(tempFilePath, "TEMP FILE FOR USN FOLDER MODIFICATION"))
                                {
                                    this.context.FileSystem.DeleteFile(tempFilePath);
                                }

                                folderPlaceholder.Sha = newFolderSha;
                                this.placeholderDatabase.AddPlaceholderData(folderPlaceholder);
                            }
                        }

                        // Remove folder placeholders before re-expansion to ensure that projection changes that convert a folder to a file work
                        // properly
                        if (GVFSPlatform.Instance.KernelDriver.EnumerationExpandsDirectories && folderPlaceholder.IsExpandedFolder)
                        {
                            this.ReExpandFolder(folderPlaceholder.Path, existingPlaceholders);
                        }
                    }
                    else
                    {
                        existingPlaceholders?.Remove(folderPlaceholder.Path);
                        this.placeholderDatabase.Remove(folderPlaceholder.Path);
                    }
                }

                stopwatch.Stop();
                long millisecondsUpdatingFolderPlaceholders = stopwatch.ElapsedMilliseconds;

                stopwatch.Restart();

                this.repoMetadata.SetPlaceholdersNeedUpdate(false);

                stopwatch.Stop();
                long millisecondsWriteAndFlush = stopwatch.ElapsedMilliseconds;

                TimeSpan duration = activity.Stop(null);
                this.context.Repository.GVFSLock.Stats.RecordUpdatePlaceholders(
                    (long)duration.TotalMilliseconds,
                    millisecondsUpdatingFilePlaceholders,
                    millisecondsUpdatingFolderPlaceholders,
                    millisecondsWriteAndFlush,
                    deleteFolderPlaceholderAttempted,
                    folderPlaceholdersDeleted,
                    folderPlaceholdersPathNotFound,
                    folderPlaceholdersShaUpdate);
            }
        }