diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/IncludedFolderTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/IncludedFolderTests.cs new file mode 100644 index 0000000000..77bcaf33fd --- /dev/null +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/IncludedFolderTests.cs @@ -0,0 +1,79 @@ +using GVFS.FunctionalTests.FileSystemRunners; +using GVFS.FunctionalTests.Should; +using GVFS.FunctionalTests.Tools; +using GVFS.Tests.Should; +using NUnit.Framework; +using System.IO; + +namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture +{ + [TestFixture] + public class IncludedFolderTests : TestsWithEnlistmentPerFixture + { + private FileSystemRunner fileSystem = new SystemIORunner(); + + [TestCase] + public void BasicTestsAddingAndRemoving() + { + // directories before limiting them + string[] allRootDirectories = Directory.GetDirectories(this.Enlistment.RepoRoot); + string[] directoriesForParentAdd = Directory.GetDirectories(Path.Combine(this.Enlistment.RepoRoot, "GVFS", "GVFS")); + + GVFSProcess gvfsProcess = new GVFSProcess(this.Enlistment); + gvfsProcess.AddIncludedFolders(Path.Combine("GVFS", "GVFS")); + + string[] directories = Directory.GetDirectories(this.Enlistment.RepoRoot); + directories.Length.ShouldEqual(2); + directories[0].ShouldEqual(Path.Combine(this.Enlistment.RepoRoot, ".git")); + directories[1].ShouldEqual(Path.Combine(this.Enlistment.RepoRoot, "GVFS")); + + string folder = this.Enlistment.GetVirtualPathTo("GVFS", "GVFS"); + folder.ShouldBeADirectory(this.fileSystem); + folder = this.Enlistment.GetVirtualPathTo("GVFS", "GVFS", "CommandLine"); + folder.ShouldBeADirectory(this.fileSystem); + + folder = this.Enlistment.GetVirtualPathTo("Scripts"); + folder.ShouldNotExistOnDisk(this.fileSystem); + + // Remove the last directory should make all folders appear again + gvfsProcess.RemoveIncludedFolders(Path.Combine("GVFS", "GVFS")); + directories = Directory.GetDirectories(this.Enlistment.RepoRoot); + directories.ShouldMatchInOrder(allRootDirectories); + + // Add parent directory should make the parent recursive + gvfsProcess.AddIncludedFolders(Path.Combine("GVFS", "GVFS", "CommandLine")); + directories = Directory.GetDirectories(Path.Combine(this.Enlistment.RepoRoot, "GVFS", "GVFS")); + directories.Length.ShouldEqual(1); + directories[0].ShouldEqual(Path.Combine(this.Enlistment.RepoRoot, "GVFS", "GVFS", "CommandLine")); + + gvfsProcess.AddIncludedFolders(Path.Combine("GVFS", "GVFS")); + directories = Directory.GetDirectories(Path.Combine(this.Enlistment.RepoRoot, "GVFS", "GVFS")); + directories.ShouldMatchInOrder(directoriesForParentAdd); + + // Add and remove folder + gvfsProcess.AddIncludedFolders("Scripts"); + folder.ShouldBeADirectory(this.fileSystem); + + gvfsProcess.RemoveIncludedFolders("Scripts"); + folder.ShouldNotExistOnDisk(this.fileSystem); + + // Add and remove sibling folder to GVFS/GVFS + gvfsProcess.AddIncludedFolders(Path.Combine("GVFS", "FastFetch")); + folder = this.Enlistment.GetVirtualPathTo("GVFS", "FastFetch"); + folder.ShouldBeADirectory(this.fileSystem); + + gvfsProcess.RemoveIncludedFolders(Path.Combine("GVFS", "FastFetch")); + folder.ShouldNotExistOnDisk(this.fileSystem); + folder = this.Enlistment.GetVirtualPathTo("GVFS", "GVFS"); + folder.ShouldBeADirectory(this.fileSystem); + + // Add subfolder of GVFS/GVFS and make sure it stays recursive + gvfsProcess.AddIncludedFolders(Path.Combine("GVFS", "GVFS", "Properties")); + folder = this.Enlistment.GetVirtualPathTo("GVFS", "GVFS", "Properties"); + folder.ShouldBeADirectory(this.fileSystem); + + folder = this.Enlistment.GetVirtualPathTo("GVFS", "GVFS", "CommandLine"); + folder.ShouldBeADirectory(this.fileSystem); + } + } +} diff --git a/GVFS/GVFS.FunctionalTests/Tools/GVFSProcess.cs b/GVFS/GVFS.FunctionalTests/Tools/GVFSProcess.cs index 60ecf06a41..c5eb6858f2 100644 --- a/GVFS/GVFS.FunctionalTests/Tools/GVFSProcess.cs +++ b/GVFS/GVFS.FunctionalTests/Tools/GVFSProcess.cs @@ -1,5 +1,6 @@ using GVFS.Tests.Should; using System.Diagnostics; +using System.IO; namespace GVFS.FunctionalTests.Tools { @@ -9,6 +10,11 @@ public class GVFSProcess private readonly string enlistmentRoot; private readonly string localCacheRoot; + public GVFSProcess(GVFSFunctionalTestEnlistment enlistment) + : this(GVFSTestConfig.PathToGVFS, enlistment.EnlistmentRoot, Path.Combine(enlistment.EnlistmentRoot, GVFSTestConfig.DotGVFSRoot)) + { + } + public GVFSProcess(string pathToGVFS, string enlistmentRoot, string localCacheRoot) { this.pathToGVFS = pathToGVFS; @@ -42,6 +48,16 @@ public bool TryMount(out string output) return this.IsEnlistmentMounted(); } + public void AddIncludedFolders(params string[] folders) + { + this.CallGVFS($"include {this.enlistmentRoot} -a {string.Join(";", folders)}"); + } + + public void RemoveIncludedFolders(params string[] folders) + { + this.CallGVFS($" include {this.enlistmentRoot} -r {string.Join(";", folders)}"); + } + public string Prefetch(string args, bool failOnError, string standardInput = null) { return this.CallGVFS("prefetch \"" + this.enlistmentRoot + "\" " + args, failOnError, standardInput: standardInput); diff --git a/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.GitIndexEntry.cs b/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.GitIndexEntry.cs index 029ff14b65..328561fa15 100644 --- a/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.GitIndexEntry.cs +++ b/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.GitIndexEntry.cs @@ -67,6 +67,21 @@ public bool BuildingProjection_HasSameParentAsLastEntry get; private set; } + public bool BuildingProjection_ShouldInclude + { + get; set; + } + + public bool BuildingProjection_ShouldIncludeRecursive + { + get; set; + } + + public IncludedFolderData BuildingProjection_LastEntryIncludedFolder + { + get; set; + } + /// /// Parses the path using LazyUTF8Strings. It should only be called when building a new projection. /// @@ -211,6 +226,9 @@ public void ClearLastParent() { this.buildingProjectionPreviousFinalSeparatorIndex = int.MaxValue; this.BuildingProjection_HasSameParentAsLastEntry = false; + this.BuildingProjection_ShouldInclude = false; + this.BuildingProjection_ShouldIncludeRecursive = false; + this.BuildingProjection_LastEntryIncludedFolder = null; this.BuildingProjection_LastParent = null; } diff --git a/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.IncludedFolder.cs b/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.IncludedFolder.cs new file mode 100644 index 0000000000..33eba0849b --- /dev/null +++ b/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.IncludedFolder.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace GVFS.Virtualization.Projection +{ + public partial class GitIndexProjection + { + internal class IncludedFolderData + { + public IncludedFolderData() + { + this.Children = new Dictionary(); + } + + public bool IsRecursive { get; set; } + public Dictionary Children { get; } + } + } +} diff --git a/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.cs b/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.cs index 323e59ff41..ad48e32fc7 100644 --- a/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.cs +++ b/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.cs @@ -14,6 +14,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -58,6 +59,7 @@ public partial class GitIndexProjection : IDisposable, IProfilerOnlyIndexProject private BlobSizes blobSizes; private IPlaceholderCollection placeholderDatabase; private IIncludedFolderCollection includedFolderCollection; + private IncludedFolderData rootIncludedFolder; private GVFSGitObjects gitObjects; private BackgroundFileSystemTaskRunner backgroundFileSystemTaskRunner; private ReaderWriterLockSlim projectionReadWriteLock; @@ -111,6 +113,8 @@ public GitIndexProjection( this.placeholderDatabase = placeholderDatabase; this.includedFolderCollection = includedFolderCollection; this.modifiedPaths = modifiedPaths; + this.rootIncludedFolder = new IncludedFolderData(); + this.RefreshFoldersToInclude(); } // For Unit Testing @@ -661,7 +665,12 @@ private void AddItemFromIndexEntry(GitIndexEntry indexEntry) { if (indexEntry.BuildingProjection_HasSameParentAsLastEntry) { - indexEntry.BuildingProjection_LastParent.AddChildFile(indexEntry.BuildingProjection_GetChildName(), indexEntry.Sha); + if (this.rootIncludedFolder.Children.Count == 0 || + indexEntry.BuildingProjection_ShouldInclude || + indexEntry.BuildingProjection_ShouldIncludeRecursive) + { + indexEntry.BuildingProjection_LastParent.AddChildFile(indexEntry.BuildingProjection_GetChildName(), indexEntry.Sha); + } } else { @@ -703,9 +712,45 @@ private void ClearProjectionCaches() LazyUTF8String.FreePool(); this.projectionFolderCache.Clear(); this.nonDefaultFileTypesAndModes.Clear(); + this.RefreshFoldersToInclude(); this.rootFolderData.ResetData(new LazyUTF8String("")); } + private void RefreshFoldersToInclude() + { + this.rootIncludedFolder.Children.Clear(); + if (this.includedFolderCollection != null) + { + Dictionary parentFolder = this.rootIncludedFolder.Children; + foreach (string directoryPath in this.includedFolderCollection.GetAll()) + { + string[] folders = directoryPath.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < folders.Length; i++) + { + IncludedFolderData folderData; + if (!parentFolder.ContainsKey(folders[i])) + { + folderData = new IncludedFolderData(); + parentFolder.Add(folders[i], folderData); + } + else + { + folderData = parentFolder[folders[i]]; + } + + if (!folderData.IsRecursive) + { + folderData.IsRecursive = i == folders.Length - 1; + } + + parentFolder = folderData.Children; + } + + parentFolder = this.rootIncludedFolder.Children; + } + } + } + private bool TryGetSha(string childName, string parentKey, out string sha) { sha = string.Empty; @@ -798,6 +843,36 @@ private FolderData AddFileToTree(GitIndexEntry indexEntry) throw new InvalidDataException("Found a file (" + parentFolderName + ") where a folder was expected: " + gitPath); } + if (this.rootIncludedFolder.Children.Count > 0) + { + if (indexEntry.BuildingProjection_LastEntryIncludedFolder == null) + { + indexEntry.BuildingProjection_LastEntryIncludedFolder = this.rootIncludedFolder; + } + + if (!indexEntry.BuildingProjection_ShouldIncludeRecursive) + { + string folderName = indexEntry.BuildingProjection_PathParts[pathIndex].GetString(); + if (indexEntry.BuildingProjection_LastEntryIncludedFolder.Children.ContainsKey(folderName)) + { + indexEntry.BuildingProjection_ShouldInclude = true; + indexEntry.BuildingProjection_ShouldIncludeRecursive = indexEntry.BuildingProjection_LastEntryIncludedFolder.Children[folderName].IsRecursive; + indexEntry.BuildingProjection_LastEntryIncludedFolder = indexEntry.BuildingProjection_LastEntryIncludedFolder.Children[folderName]; + } + else + { + indexEntry.BuildingProjection_ShouldInclude = false; + indexEntry.BuildingProjection_ShouldIncludeRecursive = false; + indexEntry.BuildingProjection_LastEntryIncludedFolder = null; + } + + if (!indexEntry.BuildingProjection_ShouldInclude) + { + return parentFolder; + } + } + } + parentFolder = parentFolder.ChildEntries.GetOrAddFolder(indexEntry.BuildingProjection_PathParts[pathIndex]); }