in GVFS/GVFS.Virtualization/FileSystemCallbacks.cs [670:946]
private FileSystemTaskResult ExecuteBackgroundOperation(FileSystemTask gitUpdate)
{
EventMetadata metadata = new EventMetadata();
FileSystemTaskResult result;
switch (gitUpdate.Operation)
{
case FileSystemTask.OperationType.OnFileCreated:
case FileSystemTask.OperationType.OnFailedPlaceholderDelete:
case FileSystemTask.OperationType.OnFileSymLinkCreated:
metadata.Add("virtualPath", gitUpdate.VirtualPath);
result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
break;
case FileSystemTask.OperationType.OnFileHardLinkCreated:
metadata.Add("virtualPath", gitUpdate.VirtualPath);
metadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
result = FileSystemTaskResult.Success;
if (!string.IsNullOrEmpty(gitUpdate.OldVirtualPath) && !IsPathInsideDotGit(gitUpdate.OldVirtualPath))
{
result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.OldVirtualPath);
}
if ((result == FileSystemTaskResult.Success) &&
!string.IsNullOrEmpty(gitUpdate.VirtualPath) && !IsPathInsideDotGit(gitUpdate.VirtualPath))
{
result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
}
break;
case FileSystemTask.OperationType.OnFileRenamed:
metadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
metadata.Add("virtualPath", gitUpdate.VirtualPath);
result = FileSystemTaskResult.Success;
if (!string.IsNullOrEmpty(gitUpdate.OldVirtualPath) && !IsPathInsideDotGit(gitUpdate.OldVirtualPath))
{
if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.OldVirtualPath))
{
result = this.TryRemoveModifiedPath(gitUpdate.OldVirtualPath, isFolder: false);
}
else
{
result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.OldVirtualPath);
}
}
if (result == FileSystemTaskResult.Success &&
!string.IsNullOrEmpty(gitUpdate.VirtualPath) &&
!IsPathInsideDotGit(gitUpdate.VirtualPath))
{
result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
}
break;
case FileSystemTask.OperationType.OnFilePreDelete:
// This code assumes that the current implementations of FileSystemVirtualizer will call either
// the PreDelete or the Delete not both so if a new implementation starts calling both
// this will need to be cleaned up to not duplicate the work that is being done.
metadata.Add("virtualPath", gitUpdate.VirtualPath);
if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.VirtualPath))
{
string fullPathToFile = Path.Combine(this.context.Enlistment.WorkingDirectoryRoot, gitUpdate.VirtualPath);
// Because this is a predelete message the file could still be on disk when we make this check
// so we retry for a limited time before deciding the delete didn't happen
bool fileDeleted = CheckConditionWithRetry(() => !this.context.FileSystem.FileExists(fullPathToFile), NumberOfRetriesCheckingForDeleted, MillisecondsToSleepBeforeCheckingForDeleted);
if (fileDeleted)
{
result = this.TryRemoveModifiedPath(gitUpdate.VirtualPath, isFolder: false);
}
else
{
result = FileSystemTaskResult.Success;
}
}
else
{
result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
}
break;
case FileSystemTask.OperationType.OnFileDeleted:
// This code assumes that the current implementations of FileSystemVirtualizer will call either
// the PreDelete or the Delete not both so if a new implementation starts calling both
// this will need to be cleaned up to not duplicate the work that is being done.
metadata.Add("virtualPath", gitUpdate.VirtualPath);
if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.VirtualPath))
{
result = this.TryRemoveModifiedPath(gitUpdate.VirtualPath, isFolder: false);
}
else
{
result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
}
break;
case FileSystemTask.OperationType.OnFileOverwritten:
case FileSystemTask.OperationType.OnFileSuperseded:
case FileSystemTask.OperationType.OnFileConvertedToFull:
case FileSystemTask.OperationType.OnFailedPlaceholderUpdate:
case FileSystemTask.OperationType.OnFailedFileHydration:
metadata.Add("virtualPath", gitUpdate.VirtualPath);
result = this.AddModifiedPathAndRemoveFromPlaceholderList(gitUpdate.VirtualPath);
break;
case FileSystemTask.OperationType.OnFolderCreated:
metadata.Add("virtualPath", gitUpdate.VirtualPath);
result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
break;
case FileSystemTask.OperationType.OnFolderRenamed:
result = FileSystemTaskResult.Success;
metadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
metadata.Add("virtualPath", gitUpdate.VirtualPath);
if (!string.IsNullOrEmpty(gitUpdate.OldVirtualPath) &&
this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.OldVirtualPath))
{
result = this.TryRemoveModifiedPath(gitUpdate.OldVirtualPath, isFolder: true);
}
// An empty destination path means the folder was renamed to somewhere outside of the repo
// Note that only full folders can be moved\renamed, and so there will already be a recursive
// sparse-checkout entry for the virtualPath of the folder being moved (meaning that no
// additional work is needed for any files\folders inside the folder being moved)
if (result == FileSystemTaskResult.Success && !string.IsNullOrEmpty(gitUpdate.VirtualPath))
{
this.AddToNewlyCreatedList(gitUpdate.VirtualPath, isFolder: true);
result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
if (result == FileSystemTaskResult.Success)
{
Queue<string> relativeFolderPaths = new Queue<string>();
relativeFolderPaths.Enqueue(gitUpdate.VirtualPath);
// Remove old paths from modified paths if in the newly created list
while (relativeFolderPaths.Count > 0)
{
string folderPath = relativeFolderPaths.Dequeue();
if (result == FileSystemTaskResult.Success)
{
try
{
foreach (DirectoryItemInfo itemInfo in this.context.FileSystem.ItemsInDirectory(Path.Combine(this.context.Enlistment.WorkingDirectoryRoot, folderPath)))
{
string itemVirtualPath = Path.Combine(folderPath, itemInfo.Name);
string oldItemVirtualPath = gitUpdate.OldVirtualPath + itemVirtualPath.Substring(gitUpdate.VirtualPath.Length);
this.AddToNewlyCreatedList(itemVirtualPath, isFolder: itemInfo.IsDirectory);
if (this.newlyCreatedFileAndFolderPaths.Contains(oldItemVirtualPath))
{
result = this.TryRemoveModifiedPath(oldItemVirtualPath, isFolder: itemInfo.IsDirectory);
}
if (itemInfo.IsDirectory)
{
relativeFolderPaths.Enqueue(itemVirtualPath);
}
}
}
catch (DirectoryNotFoundException)
{
// DirectoryNotFoundException can occur when the renamed folder (or one of its children) is
// deleted prior to the background thread running
EventMetadata exceptionMetadata = new EventMetadata();
exceptionMetadata.Add("Area", "ExecuteBackgroundOperation");
exceptionMetadata.Add("Operation", gitUpdate.Operation.ToString());
exceptionMetadata.Add("oldVirtualPath", gitUpdate.OldVirtualPath);
exceptionMetadata.Add("virtualPath", gitUpdate.VirtualPath);
exceptionMetadata.Add(TracingConstants.MessageKey.InfoMessage, "DirectoryNotFoundException while traversing folder path");
exceptionMetadata.Add("folderPath", folderPath);
this.context.Tracer.RelatedEvent(EventLevel.Informational, "DirectoryNotFoundWhileUpdatingModifiedPaths", exceptionMetadata);
}
catch (IOException e)
{
metadata.Add("Details", "IOException while traversing folder path");
metadata.Add("folderPath", folderPath);
metadata.Add("Exception", e.ToString());
result = FileSystemTaskResult.RetryableError;
break;
}
catch (UnauthorizedAccessException e)
{
metadata.Add("Details", "UnauthorizedAccessException while traversing folder path");
metadata.Add("folderPath", folderPath);
metadata.Add("Exception", e.ToString());
result = FileSystemTaskResult.RetryableError;
break;
}
}
else
{
break;
}
}
}
}
break;
case FileSystemTask.OperationType.OnFolderPreDelete:
// This code assumes that the current implementations of FileSystemVirtualizer will call either
// the PreDelete or the Delete not both so if a new implementation starts calling both
// this will need to be cleaned up to not duplicate the work that is being done.
metadata.Add("virtualPath", gitUpdate.VirtualPath);
if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.VirtualPath))
{
string fullPathToFolder = Path.Combine(this.context.Enlistment.WorkingDirectoryRoot, gitUpdate.VirtualPath);
// Because this is a predelete message the file could still be on disk when we make this check
// so we retry for a limited time before deciding the delete didn't happen
bool folderDeleted = CheckConditionWithRetry(() => !this.context.FileSystem.DirectoryExists(fullPathToFolder), NumberOfRetriesCheckingForDeleted, MillisecondsToSleepBeforeCheckingForDeleted);
if (folderDeleted)
{
result = this.TryRemoveModifiedPath(gitUpdate.VirtualPath, isFolder: true);
}
else
{
result = FileSystemTaskResult.Success;
}
}
else
{
result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
}
break;
case FileSystemTask.OperationType.OnFolderDeleted:
// This code assumes that the current implementations of FileSystemVirtualizer will call either
// the PreDelete or the Delete not both so if a new implementation starts calling both
// this will need to be cleaned up to not duplicate the work that is being done.
metadata.Add("virtualPath", gitUpdate.VirtualPath);
if (this.newlyCreatedFileAndFolderPaths.Contains(gitUpdate.VirtualPath))
{
result = this.TryRemoveModifiedPath(gitUpdate.VirtualPath, isFolder: true);
}
else
{
result = this.TryAddModifiedPath(gitUpdate.VirtualPath, isFolder: true);
}
break;
case FileSystemTask.OperationType.OnFolderFirstWrite:
result = FileSystemTaskResult.Success;
break;
case FileSystemTask.OperationType.OnIndexWriteRequiringModifiedPathsValidation:
result = this.GitIndexProjection.AddMissingModifiedFiles();
break;
case FileSystemTask.OperationType.OnPlaceholderCreationsBlockedForGit:
this.GitIndexProjection.ClearNegativePathCacheIfPollutedByGit();
result = FileSystemTaskResult.Success;
break;
default:
throw new InvalidOperationException("Invalid background operation");
}
if (result != FileSystemTaskResult.Success)
{
metadata.Add("Area", "ExecuteBackgroundOperation");
metadata.Add("Operation", gitUpdate.Operation.ToString());
metadata.Add(TracingConstants.MessageKey.WarningMessage, "Background operation failed");
metadata.Add(nameof(result), result.ToString());
this.context.Tracer.RelatedEvent(EventLevel.Warning, "FailedBackgroundOperation", metadata);
}
return result;
}