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