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);
}
}