Esempio n. 1
0
        private string[] GetDirectoriesOutsideSparse(string rootPath, HashSet <string> sparseFolders)
        {
            PhysicalFileSystem fileSystem         = new PhysicalFileSystem();
            Queue <string>     foldersToEnumerate = new Queue <string>();

            foldersToEnumerate.Enqueue(rootPath);

            List <string> foldersOutsideSparse = new List <string>();

            while (foldersToEnumerate.Count > 0)
            {
                string folderToEnumerate = foldersToEnumerate.Dequeue();
                foreach (string directory in fileSystem.EnumerateDirectories(folderToEnumerate))
                {
                    string enlistmentRootRelativeFolderPath = GVFSDatabase.NormalizePath(directory.Substring(rootPath.Length));
                    if (sparseFolders.Any(x => x.StartsWith(enlistmentRootRelativeFolderPath + Path.DirectorySeparatorChar, GVFSPlatform.Instance.Constants.PathComparison)))
                    {
                        foldersToEnumerate.Enqueue(directory);
                    }
                    else if (!sparseFolders.Contains(enlistmentRootRelativeFolderPath))
                    {
                        foldersOutsideSparse.Add(enlistmentRootRelativeFolderPath);
                    }
                }
            }

            return(foldersOutsideSparse.ToArray());
        }
        public bool TryDehydrateFolder(string relativePath, out string errorMessage)
        {
            List <IPlaceholderData> removedPlaceholders  = null;
            List <string>           removedModifiedPaths = null;

            errorMessage = string.Empty;

            try
            {
                relativePath         = GVFSDatabase.NormalizePath(relativePath);
                removedPlaceholders  = this.placeholderDatabase.RemoveAllEntriesForFolder(relativePath);
                removedModifiedPaths = this.modifiedPaths.RemoveAllEntriesForFolder(relativePath);
                FileSystemResult result = this.fileSystemVirtualizer.DehydrateFolder(relativePath);
                if (result.Result != FSResult.Ok)
                {
                    errorMessage = $"{nameof(this.TryDehydrateFolder)} failed with {result.Result}";
                    this.context.Tracer.RelatedError(errorMessage);
                }
            }
            catch (Exception ex)
            {
                errorMessage = $"{nameof(this.TryDehydrateFolder)} threw an exception - {ex.Message}";
                EventMetadata metadata = this.CreateEventMetadata(relativePath, ex);
                this.context.Tracer.RelatedError(metadata, errorMessage);
            }

            if (!string.IsNullOrEmpty(errorMessage))
            {
                if (removedPlaceholders != null)
                {
                    foreach (IPlaceholderData data in removedPlaceholders)
                    {
                        try
                        {
                            this.placeholderDatabase.AddPlaceholderData(data);
                        }
                        catch (Exception ex)
                        {
                            EventMetadata metadata = this.CreateEventMetadata(data.Path, ex);
                            this.context.Tracer.RelatedError(metadata, $"{nameof(FileSystemCallbacks)}.{nameof(this.TryDehydrateFolder)} failed to add '{data.Path}' back into PlaceholderDatabase");
                        }
                    }
                }

                if (removedModifiedPaths != null)
                {
                    foreach (string modifiedPath in removedModifiedPaths)
                    {
                        if (!this.modifiedPaths.TryAdd(modifiedPath, isFolder: modifiedPath.EndsWith(GVFSConstants.GitPathSeparatorString), isRetryable: out bool isRetryable))
                        {
                            this.context.Tracer.RelatedError($"{nameof(FileSystemCallbacks)}.{nameof(this.TryDehydrateFolder)}: failed to add '{modifiedPath}' back into ModifiedPaths");
                        }
                    }
                }
            }

            return(string.IsNullOrEmpty(errorMessage));
        }
Esempio n. 3
0
        private FileSystemCallbacks CreateFileSystemCallbacks()
        {
            string error;

            if (!RepoMetadata.TryInitialize(this.Context.Tracer, this.Enlistment.DotGVFSRoot, out error))
            {
                throw new InvalidRepoException(error);
            }

            string gitObjectsRoot;

            if (!RepoMetadata.Instance.TryGetGitObjectsRoot(out gitObjectsRoot, out error))
            {
                throw new InvalidRepoException("Failed to determine git objects root from repo metadata: " + error);
            }

            string localCacheRoot;

            if (!RepoMetadata.Instance.TryGetLocalCacheRoot(out localCacheRoot, out error))
            {
                throw new InvalidRepoException("Failed to determine local cache path from repo metadata: " + error);
            }

            string blobSizesRoot;

            if (!RepoMetadata.Instance.TryGetBlobSizesRoot(out blobSizesRoot, out error))
            {
                throw new InvalidRepoException("Failed to determine blob sizes root from repo metadata: " + error);
            }

            this.Enlistment.InitializeCachePaths(localCacheRoot, gitObjectsRoot, blobSizesRoot);

            CacheServerInfo         cacheServer     = new CacheServerInfo(this.Context.Enlistment.RepoUrl, "None");
            GitObjectsHttpRequestor objectRequestor = new GitObjectsHttpRequestor(
                this.Context.Tracer,
                this.Context.Enlistment,
                cacheServer,
                new RetryConfig());

            this.gvfsDatabase = new GVFSDatabase(this.Context.FileSystem, this.Context.Enlistment.EnlistmentRoot, new SqliteDatabase());
            GVFSGitObjects gitObjects = new GVFSGitObjects(this.Context, objectRequestor);

            return(new FileSystemCallbacks(
                       this.Context,
                       gitObjects,
                       RepoMetadata.Instance,
                       blobSizes: null,
                       gitIndexProjection: null,
                       backgroundFileSystemTaskRunner: null,
                       fileSystemVirtualizer: null,
                       placeholderDatabase: new PlaceholderTable(this.gvfsDatabase),
                       sparseCollection: new SparseTable(this.gvfsDatabase),
                       gitStatusCache: null));
        }
 private IEnumerable <string> ParseFolderList(string folders, string folderSeparator = FolderListSeparator)
 {
     if (string.IsNullOrEmpty(folders))
     {
         return(new string[0]);
     }
     else
     {
         return(folders.Split(new[] { folderSeparator }, StringSplitOptions.RemoveEmptyEntries)
                .Select(x => GVFSDatabase.NormalizePath(x)));
     }
 }
Esempio n. 5
0
 private string[] ParseFolderList(string folders)
 {
     if (string.IsNullOrEmpty(folders))
     {
         return(new string[0]);
     }
     else
     {
         return(folders.Split(new[] { FolderListSeparator }, StringSplitOptions.RemoveEmptyEntries)
                .Select(x => GVFSDatabase.NormalizePath(x))
                .ToArray());
     }
 }
Esempio n. 6
0
        /// <summary>
        /// Get two lists of placeholders, one containing the files and the other the directories
        /// Goes to the SQLite database for the placeholder lists
        /// </summary>
        /// <param name="enlistment">The current GVFS enlistment being operated on</param>
        /// <param name="filePlaceholders">Out parameter where the list of file placeholders will end up</param>
        /// <param name="folderPlaceholders">Out parameter where the list of folder placeholders will end up</param>
        private void GetPlaceholdersFromDatabase(GVFSEnlistment enlistment, EnlistmentPathData pathData)
        {
            List <IPlaceholderData> filePlaceholders   = new List <IPlaceholderData>();
            List <IPlaceholderData> folderPlaceholders = new List <IPlaceholderData>();

            using (GVFSDatabase database = new GVFSDatabase(new PhysicalFileSystem(), enlistment.EnlistmentRoot, new SqliteDatabase()))
            {
                PlaceholderTable placeholderTable = new PlaceholderTable(database);
                placeholderTable.GetAllEntries(out filePlaceholders, out folderPlaceholders);
            }

            pathData.PlaceholderFilePaths.AddRange(filePlaceholders.Select(placeholderData => placeholderData.Path));
            pathData.PlaceholderFolderPaths.AddRange(folderPlaceholders.Select(placeholderData => placeholderData.Path));
        }
        public override bool TryUpgrade(ITracer tracer, string enlistmentRoot)
        {
            string dotGVFSRoot = Path.Combine(enlistmentRoot, GVFSPlatform.Instance.Constants.DotGVFSRoot);

            try
            {
                PhysicalFileSystem            fileSystem = new PhysicalFileSystem();
                string                        error;
                LegacyPlaceholderListDatabase placeholderList;
                if (!LegacyPlaceholderListDatabase.TryCreate(
                        tracer,
                        Path.Combine(dotGVFSRoot, GVFSConstants.DotGVFS.Databases.PlaceholderList),
                        fileSystem,
                        out placeholderList,
                        out error))
                {
                    tracer.RelatedError("Failed to open placeholder list database: " + error);
                    return(false);
                }

                using (placeholderList)
                    using (GVFSDatabase database = new GVFSDatabase(fileSystem, enlistmentRoot, new SqliteDatabase()))
                    {
                        PlaceholderTable        placeholders          = new PlaceholderTable(database);
                        List <IPlaceholderData> oldPlaceholderEntries = placeholderList.GetAllEntries();
                        foreach (IPlaceholderData entry in oldPlaceholderEntries)
                        {
                            placeholders.AddPlaceholderData(entry);
                        }
                    }
            }
            catch (Exception ex)
            {
                tracer.RelatedError("Error updating placeholder list database to SQLite: " + ex.ToString());
                return(false);
            }

            if (!this.TryIncrementMajorVersion(tracer, enlistmentRoot))
            {
                return(false);
            }

            return(true);
        }
 private void ListSparseFolders(string enlistmentRoot)
 {
     using (GVFSDatabase database = new GVFSDatabase(new PhysicalFileSystem(), enlistmentRoot, new SqliteDatabase()))
     {
         SparseTable      sparseTable = new SparseTable(database);
         HashSet <string> directories = sparseTable.GetAll();
         if (directories.Count == 0)
         {
             this.Output.WriteLine("No folders in sparse list. When the sparse list is empty, all folders are projected.");
         }
         else
         {
             foreach (string directory in directories)
             {
                 this.Output.WriteLine(directory);
             }
         }
     }
 }
Esempio n. 9
0
        private void UnmountAndStopWorkingDirectoryCallbacks()
        {
            if (this.maintenanceScheduler != null)
            {
                this.maintenanceScheduler.Dispose();
                this.maintenanceScheduler = null;
            }

            if (this.heartbeat != null)
            {
                this.heartbeat.Stop();
                this.heartbeat = null;
            }

            if (this.fileSystemCallbacks != null)
            {
                this.fileSystemCallbacks.Stop();
                this.fileSystemCallbacks.Dispose();
                this.fileSystemCallbacks = null;
            }

            this.gvfsDatabase?.Dispose();
            this.gvfsDatabase = null;
        }
Esempio n. 10
0
        private void MountAndStartWorkingDirectoryCallbacks(CacheServerInfo cache)
        {
            string error;

            if (!this.context.Enlistment.Authentication.TryInitialize(this.context.Tracer, this.context.Enlistment, out error))
            {
                this.FailMountAndExit("Failed to obtain git credentials: " + error);
            }

            GitObjectsHttpRequestor objectRequestor = new GitObjectsHttpRequestor(this.context.Tracer, this.context.Enlistment, cache, this.retryConfig);

            this.gitObjects = new GVFSGitObjects(this.context, objectRequestor);
            FileSystemVirtualizer virtualizer = this.CreateOrReportAndExit(() => GVFSPlatformLoader.CreateFileSystemVirtualizer(this.context, this.gitObjects), "Failed to create src folder virtualizer");

            GitStatusCache gitStatusCache = (!this.context.Unattended && GVFSPlatform.Instance.IsGitStatusCacheSupported()) ? new GitStatusCache(this.context, this.gitStatusCacheConfig) : null;

            if (gitStatusCache != null)
            {
                this.tracer.RelatedInfo("Git status cache enabled. Backoff time: {0}ms", this.gitStatusCacheConfig.BackoffTime.TotalMilliseconds);
            }
            else
            {
                this.tracer.RelatedInfo("Git status cache is not enabled");
            }

            this.gvfsDatabase        = this.CreateOrReportAndExit(() => new GVFSDatabase(this.context.FileSystem, this.context.Enlistment.EnlistmentRoot, new SqliteDatabase()), "Failed to create database connection");
            this.fileSystemCallbacks = this.CreateOrReportAndExit(
                () =>
            {
                return(new FileSystemCallbacks(
                           this.context,
                           this.gitObjects,
                           RepoMetadata.Instance,
                           blobSizes: null,
                           gitIndexProjection: null,
                           backgroundFileSystemTaskRunner: null,
                           fileSystemVirtualizer: virtualizer,
                           placeholderDatabase: new PlaceholderTable(this.gvfsDatabase),
                           gitStatusCache: gitStatusCache));
            }, "Failed to create src folder callback listener");
            this.maintenanceScheduler = this.CreateOrReportAndExit(() => new GitMaintenanceScheduler(this.context, this.gitObjects), "Failed to start maintenance scheduler");

            int majorVersion;
            int minorVersion;

            if (!RepoMetadata.Instance.TryGetOnDiskLayoutVersion(out majorVersion, out minorVersion, out error))
            {
                this.FailMountAndExit("Error: {0}", error);
            }

            if (majorVersion != GVFSPlatform.Instance.DiskLayoutUpgrade.Version.CurrentMajorVersion)
            {
                this.FailMountAndExit(
                    "Error: On disk version ({0}) does not match current version ({1})",
                    majorVersion,
                    GVFSPlatform.Instance.DiskLayoutUpgrade.Version.CurrentMajorVersion);
            }

            try
            {
                if (!this.fileSystemCallbacks.TryStart(out error))
                {
                    this.FailMountAndExit("Error: {0}. \r\nPlease confirm that gvfs clone completed without error.", error);
                }
            }
            catch (Exception e)
            {
                this.FailMountAndExit("Failed to initialize src folder callbacks. {0}", e.ToString());
            }

            this.heartbeat = new HeartbeatThread(this.tracer, this.fileSystemCallbacks);
            this.heartbeat.Start();
        }
Esempio n. 11
0
        protected override void Execute(GVFSEnlistment enlistment)
        {
            using (JsonTracer tracer = new JsonTracer(GVFSConstants.GVFSEtwProviderName, SparseVerbName))
            {
                tracer.AddLogFileEventListener(
                    GVFSEnlistment.GetNewGVFSLogFileName(enlistment.GVFSLogsRoot, GVFSConstants.LogFileTypes.Sparse),
                    EventLevel.Informational,
                    Keywords.Any);

                bool needToChangeProjection = false;
                using (GVFSDatabase database = new GVFSDatabase(new PhysicalFileSystem(), enlistment.EnlistmentRoot, new SqliteDatabase()))
                {
                    SparseTable      sparseTable = new SparseTable(database);
                    HashSet <string> directories = sparseTable.GetAll();

                    string[] foldersToRemove = this.ParseFolderList(this.Remove);
                    string[] foldersToAdd    = this.ParseFolderList(this.Add);

                    if (this.List || (foldersToAdd.Length == 0 && foldersToRemove.Length == 0))
                    {
                        if (directories.Count == 0)
                        {
                            this.Output.WriteLine("No folders in sparse list. When the sparse list is empty, all folders are projected.");
                        }
                        else
                        {
                            foreach (string directory in directories)
                            {
                                this.Output.WriteLine(directory);
                            }
                        }

                        return;
                    }

                    foreach (string folder in foldersToRemove)
                    {
                        if (directories.Contains(folder))
                        {
                            needToChangeProjection = true;
                            break;
                        }
                    }

                    if (!needToChangeProjection)
                    {
                        foreach (string folder in foldersToAdd)
                        {
                            if (!directories.Contains(folder))
                            {
                                needToChangeProjection = true;
                                break;
                            }
                        }
                    }

                    if (needToChangeProjection)
                    {
                        // Make sure there is a clean git status before allowing sparse set to change
                        this.CheckGitStatus(tracer, enlistment);
                        if (!this.ShowStatusWhileRunning(
                                () =>
                        {
                            foreach (string directoryPath in foldersToRemove)
                            {
                                tracer.RelatedInfo($"Removing '{directoryPath}' from sparse folders.");
                                sparseTable.Remove(directoryPath);
                            }

                            foreach (string directoryPath in foldersToAdd)
                            {
                                tracer.RelatedInfo($"Adding '{directoryPath}' to sparse folders.");
                                sparseTable.Add(directoryPath);
                            }

                            return(true);
                        },
                                "Updating sparse folder set",
                                suppressGvfsLogMessage: true))
                        {
                            this.ReportErrorAndExit(tracer, "Failed to update sparse folder set.");
                        }
                    }
                }

                if (needToChangeProjection)
                {
                    // Force a projection update to get the current inclusion set
                    this.ForceProjectionChange(tracer, enlistment);
                    tracer.RelatedInfo("Projection updated after adding or removing folders.");
                }
                else
                {
                    this.WriteMessage(tracer, "No folders to update in sparse set.");
                }
            }
        }
Esempio n. 12
0
        private void TestGVFSDatabase(Action <GVFSDatabase> testCode, bool throwException = false)
        {
            MockFileSystem fileSystem = new MockFileSystem(new MockDirectory("GVFSDatabaseTests", null, null));

            Mock <IDbCommand> mockCommand = new Mock <IDbCommand>(MockBehavior.Strict);

            mockCommand.SetupSet(x => x.CommandText = "PRAGMA journal_mode=WAL;");
            mockCommand.SetupSet(x => x.CommandText = "PRAGMA cache_size=-40000;");
            mockCommand.SetupSet(x => x.CommandText = "PRAGMA synchronous=NORMAL;");
            mockCommand.SetupSet(x => x.CommandText = "PRAGMA user_version;");
            mockCommand.Setup(x => x.ExecuteNonQuery()).Returns(1);
            mockCommand.Setup(x => x.ExecuteScalar()).Returns(1);
            mockCommand.Setup(x => x.Dispose());

            string            collateConstraint = GVFSPlatform.Instance.Constants.CaseSensitiveFileSystem ? string.Empty : " COLLATE NOCASE";
            Mock <IDbCommand> mockCommand2      = new Mock <IDbCommand>(MockBehavior.Strict);

            mockCommand2.SetupSet(x => x.CommandText = $"CREATE TABLE IF NOT EXISTS [Placeholder] (path TEXT PRIMARY KEY{collateConstraint}, pathType TINYINT NOT NULL, sha char(40) ) WITHOUT ROWID;");
            if (throwException)
            {
                mockCommand2.Setup(x => x.ExecuteNonQuery()).Throws(new Exception("Error"));
            }
            else
            {
                mockCommand2.Setup(x => x.ExecuteNonQuery()).Returns(1);
            }

            mockCommand2.Setup(x => x.Dispose());

            Mock <IDbCommand> mockCommand3 = new Mock <IDbCommand>(MockBehavior.Strict);

            mockCommand3.SetupSet(x => x.CommandText = $"CREATE TABLE IF NOT EXISTS [Sparse] (path TEXT PRIMARY KEY{collateConstraint}) WITHOUT ROWID;");
            if (throwException)
            {
                mockCommand3.Setup(x => x.ExecuteNonQuery()).Throws(new Exception("Error"));
            }
            else
            {
                mockCommand3.Setup(x => x.ExecuteNonQuery()).Returns(1);
            }

            mockCommand3.Setup(x => x.Dispose());

            List <Mock <IDbConnection> > mockConnections = new List <Mock <IDbConnection> >();
            Mock <IDbConnection>         mockConnection  = new Mock <IDbConnection>(MockBehavior.Strict);

            mockConnection.SetupSequence(x => x.CreateCommand())
            .Returns(mockCommand.Object)
            .Returns(mockCommand2.Object)
            .Returns(mockCommand3.Object);
            mockConnection.Setup(x => x.Dispose());
            mockConnections.Add(mockConnection);

            Mock <IDbConnectionFactory> mockConnectionFactory = new Mock <IDbConnectionFactory>(MockBehavior.Strict);
            bool   firstConnection = true;
            string databasePath    = Path.Combine("mock:root", ".mockvfsforgit", "databases", "VFSForGit.sqlite");

            mockConnectionFactory.Setup(x => x.OpenNewConnection(databasePath)).Returns(() =>
            {
                if (firstConnection)
                {
                    firstConnection = false;
                    return(mockConnection.Object);
                }
                else
                {
                    Mock <IDbConnection> newMockConnection = new Mock <IDbConnection>(MockBehavior.Strict);
                    newMockConnection.Setup(x => x.Dispose());
                    mockConnections.Add(newMockConnection);
                    return(newMockConnection.Object);
                }
            });

            using (GVFSDatabase database = new GVFSDatabase(fileSystem, "mock:root", mockConnectionFactory.Object, initialPooledConnections: 1))
            {
                testCode?.Invoke(database);
            }

            mockCommand.Verify(x => x.Dispose(), Times.Once);
            mockCommand2.Verify(x => x.Dispose(), Times.Once);
            mockCommand3.Verify(x => x.Dispose(), Times.Once);
            mockConnections.ForEach(connection => connection.Verify(x => x.Dispose(), Times.Once));

            mockCommand.VerifyAll();
            mockCommand2.VerifyAll();
            mockCommand3.VerifyAll();
            mockConnections.ForEach(connection => connection.VerifyAll());
            mockConnectionFactory.VerifyAll();
        }
        private void DehydrateFolders(JsonTracer tracer, GVFSEnlistment enlistment, string[] folders)
        {
            List <string> foldersToDehydrate = new List <string>();
            List <string> folderErrors       = new List <string>();

            if (!this.ShowStatusWhileRunning(
                    () =>
            {
                if (!ModifiedPathsDatabase.TryLoadOrCreate(
                        tracer,
                        Path.Combine(enlistment.DotGVFSRoot, GVFSConstants.DotGVFS.Databases.ModifiedPaths),
                        this.fileSystem,
                        out ModifiedPathsDatabase modifiedPaths,
                        out string error))
                {
                    this.WriteMessage(tracer, $"Unable to open modified paths database: {error}");
                    return(false);
                }

                using (modifiedPaths)
                {
                    string ioError;
                    foreach (string folder in folders)
                    {
                        string normalizedPath = GVFSDatabase.NormalizePath(folder);
                        if (!this.IsFolderValid(normalizedPath))
                        {
                            this.WriteMessage(tracer, $"Cannot {this.ActionName} folder '{folder}': invalid folder path.");
                        }
                        else
                        {
                            // Need to check if parent folder is in the modified paths because
                            // dehydration will not do any good with a parent folder there
                            if (modifiedPaths.ContainsParentFolder(folder, out string parentFolder))
                            {
                                this.WriteMessage(tracer, $"Cannot {this.ActionName} folder '{folder}': Must {this.ActionName} parent folder '{parentFolder}'.");
                            }
                            else
                            {
                                string fullPath = Path.Combine(enlistment.WorkingDirectoryBackingRoot, folder);
                                if (this.fileSystem.DirectoryExists(fullPath))
                                {
                                    if (!this.TryIO(tracer, () => this.fileSystem.DeleteDirectory(fullPath), $"Deleting '{fullPath}'", out ioError))
                                    {
                                        this.WriteMessage(tracer, $"Cannot {this.ActionName} folder '{folder}': removing '{folder}' failed.");
                                        this.WriteMessage(tracer, "Ensure no applications are accessing the folder and retry.");
                                        this.WriteMessage(tracer, $"More details: {ioError}");
                                        folderErrors.Add($"{folder}\0{ioError}");
                                    }
                                    else
                                    {
                                        foldersToDehydrate.Add(folder);
                                    }
                                }
                                else
                                {
                                    this.WriteMessage(tracer, $"Cannot {this.ActionName} folder '{folder}': '{folder}' does not exist.");

                                    // Still add to foldersToDehydrate so that any placeholders or modified paths get cleaned up
                                    foldersToDehydrate.Add(folder);
                                }
                            }
                        }
                    }
                }

                return(true);
            },
                    "Cleaning up folders"))
            {
                this.ReportErrorAndExit(tracer, $"{this.ActionName} for folders failed.");
            }

            this.Mount(tracer);

            this.SendDehydrateMessage(tracer, enlistment, folderErrors, foldersToDehydrate.ToArray());

            if (folderErrors.Count > 0)
            {
                foreach (string folderError in folderErrors)
                {
                    this.ErrorOutput.WriteLine(folderError);
                }

                this.ReportErrorAndExit(tracer, ReturnCode.DehydrateFolderFailures, $"Failed to dehydrate {folderErrors.Count} folder(s).");
            }
        }
        protected override void Execute(GVFSEnlistment enlistment)
        {
            if (this.List || (
                    !this.Prune &&
                    !this.Disable &&
                    string.IsNullOrEmpty(this.Add) &&
                    string.IsNullOrEmpty(this.Remove) &&
                    string.IsNullOrEmpty(this.Set) &&
                    string.IsNullOrEmpty(this.File)))
            {
                this.ListSparseFolders(enlistment.EnlistmentRoot);
                return;
            }

            this.CheckOptions();

            using (JsonTracer tracer = new JsonTracer(GVFSConstants.GVFSEtwProviderName, SparseVerbName))
            {
                tracer.AddLogFileEventListener(
                    GVFSEnlistment.GetNewGVFSLogFileName(enlistment.GVFSLogsRoot, GVFSConstants.LogFileTypes.Sparse),
                    EventLevel.Informational,
                    Keywords.Any);

                EventMetadata metadata = new EventMetadata();
                metadata.Add(nameof(this.Set), this.Set);
                metadata.Add(nameof(this.File), this.File);
                metadata.Add(nameof(this.Add), this.Add);
                metadata.Add(nameof(this.Remove), this.Remove);
                metadata.Add(nameof(this.Prune), this.Prune);
                metadata.Add(nameof(this.Disable), this.Disable);
                tracer.RelatedInfo(metadata, $"Running sparse");

                HashSet <string> directories;
                bool             needToChangeProjection = false;
                using (GVFSDatabase database = new GVFSDatabase(new PhysicalFileSystem(), enlistment.EnlistmentRoot, new SqliteDatabase()))
                {
                    SparseTable sparseTable = new SparseTable(database);
                    directories = sparseTable.GetAll();

                    List <string> foldersToRemove = new List <string>();
                    List <string> foldersToAdd    = new List <string>();

                    if (this.Disable)
                    {
                        if (directories.Count > 0)
                        {
                            this.WriteMessage(tracer, "Removing all folders from sparse list. When the sparse list is empty, all folders are projected.");
                            needToChangeProjection = true;
                            foldersToRemove.AddRange(directories);
                            directories.Clear();
                        }
                        else
                        {
                            return;
                        }
                    }
                    else if (!string.IsNullOrEmpty(this.Set) || !string.IsNullOrEmpty(this.File))
                    {
                        IEnumerable <string> folders = null;
                        if (!string.IsNullOrEmpty(this.Set))
                        {
                            folders = this.ParseFolderList(this.Set);
                        }
                        else if (!string.IsNullOrEmpty(this.File))
                        {
                            PhysicalFileSystem fileSystem = new PhysicalFileSystem();
                            folders = this.ParseFolderList(fileSystem.ReadAllText(this.File), folderSeparator: Environment.NewLine);
                        }
                        else
                        {
                            this.WriteMessage(tracer, "Invalid options specified.");
                            throw new InvalidOperationException();
                        }

                        foreach (string folder in folders)
                        {
                            if (!directories.Contains(folder))
                            {
                                needToChangeProjection = true;
                                foldersToAdd.Add(folder);
                            }
                            else
                            {
                                // Remove from directories so that the only directories left in the directories collection
                                // will be the ones that will need to be removed from sparse set
                                directories.Remove(folder);
                            }
                        }

                        if (directories.Count > 0)
                        {
                            needToChangeProjection = true;
                            foldersToRemove.AddRange(directories);
                            directories.Clear();
                        }

                        // Need to add folders that will be in the projection back into directories for the status check
                        foreach (string folder in folders)
                        {
                            directories.Add(folder);
                        }
                    }
                    else
                    { // Process adds and removes
                        foreach (string folder in this.ParseFolderList(this.Remove))
                        {
                            if (directories.Contains(folder))
                            {
                                needToChangeProjection = true;
                                directories.Remove(folder);
                                foldersToRemove.Add(folder);
                            }
                        }

                        foreach (string folder in this.ParseFolderList(this.Add))
                        {
                            if (!directories.Contains(folder))
                            {
                                needToChangeProjection = true;
                                directories.Add(folder);
                                foldersToAdd.Add(folder);
                            }
                        }
                    }

                    if (needToChangeProjection || this.Prune)
                    {
                        if (directories.Count > 0)
                        {
                            // Make sure there is a clean git status before allowing sparse set to change
                            this.CheckGitStatus(tracer, enlistment, directories);
                        }

                        this.UpdateSparseFolders(tracer, sparseTable, foldersToRemove, foldersToAdd);
                    }

                    if (needToChangeProjection)
                    {
                        // Force a projection update to get the current inclusion set
                        this.ForceProjectionChange(tracer, enlistment);
                        tracer.RelatedInfo("Projection updated after adding or removing folders.");
                    }
                    else
                    {
                        this.WriteMessage(tracer, "No folders to update in sparse set.");
                    }

                    if (this.Prune && directories.Count > 0)
                    {
                        this.PruneFoldersOutsideSparse(tracer, enlistment, sparseTable);
                    }
                }
            }
        }