diff --git a/GVFS/GVFS.Common/ModifiedPathsDatabase.cs b/GVFS/GVFS.Common/ModifiedPathsDatabase.cs index ffde4f692..5a7e34c43 100644 --- a/GVFS/GVFS.Common/ModifiedPathsDatabase.cs +++ b/GVFS/GVFS.Common/ModifiedPathsDatabase.cs @@ -67,7 +67,7 @@ public bool TryAdd(string path, bool isFolder, out bool isRetryable) { isRetryable = true; string entry = this.NormalizeEntryString(path, isFolder); - if (!this.modifiedPaths.Contains(entry)) + if (!this.modifiedPaths.Contains(entry) && !this.ContainsParentDirectory(entry)) { try { @@ -183,6 +183,22 @@ private bool TryParseRemoveLine(string line, out string key, out string error) return true; } + private bool ContainsParentDirectory(string modifiedPath) + { + string[] pathParts = modifiedPath.Split(new char[] { GVFSConstants.GitPathSeparator }, StringSplitOptions.RemoveEmptyEntries); + string parentFolder = string.Empty; + for (int i = 0; i < pathParts.Length - 1; i++) + { + parentFolder += pathParts[i] + GVFSConstants.GitPathSeparatorString; + if (this.modifiedPaths.Contains(parentFolder)) + { + return true; + } + } + + return false; + } + private string NormalizeEntryString(string virtualPath, bool isFolder) { // TODO(Mac) This can be optimized if needed diff --git a/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/DiskLayoutUpgradeTests.cs b/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/DiskLayoutUpgradeTests.cs index eb1ce473d..1f39f0285 100644 --- a/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/DiskLayoutUpgradeTests.cs +++ b/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/DiskLayoutUpgradeTests.cs @@ -328,8 +328,6 @@ public void MountCreatesModifiedPathsDatabase() { "A .gitattributes", "A developer/me/", - "A developer/me/JLANGE9._prerazzle", - "A developer/me/StateSwitch.Save", "A tools/x86/remote.exe", "A tools/x86/runelevated.exe", "A tools/amd64/remote.exe", diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs index bfbdcc457..3313fb8f6 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs @@ -77,7 +77,7 @@ public void CreateFileInFolderTest() this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, folderName + "/"); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, folderName + "/" + fileName); + GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, folderName + "/" + fileName); } [TestCase, Order(4)] @@ -98,6 +98,7 @@ public void RenameEmptyFolderTest() this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, expectedModifiedEntries); + GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, folderName + "/"); } [TestCase, Order(5)] @@ -112,6 +113,17 @@ public void RenameFolderTest() renamedFolderName + "/", }; + string[] unexpectedModifiedEntries = + { + renamedFolderName + "/" + fileNames[0], + renamedFolderName + "/" + fileNames[1], + renamedFolderName + "/" + fileNames[2], + folderName + "/", + folderName + "/" + fileNames[0], + folderName + "/" + fileNames[1], + folderName + "/" + fileNames[2], + }; + this.Enlistment.GetVirtualPathTo(folderName).ShouldNotExistOnDisk(this.fileSystem); this.fileSystem.CreateDirectory(this.Enlistment.GetVirtualPathTo(folderName)); foreach (string fileName in fileNames) @@ -126,6 +138,7 @@ public void RenameFolderTest() this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, expectedModifiedEntries); + GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, unexpectedModifiedEntries); } [TestCase, Order(6)] @@ -137,27 +150,17 @@ public void CaseOnlyRenameOfNewFolderKeepsModifiedPathsEntries() Assert.Ignore("Powershell does not support case only renames."); } - string[] expectedModifiedPathsEntriesAfterCreate = - { - "A Folder/", - "A Folder/testfile", - }; - - string[] expectedModifiedPathsEntriesAfterRename = - { - "A folder/", - }; - this.fileSystem.CreateDirectory(Path.Combine(this.Enlistment.RepoRoot, "Folder")); this.fileSystem.CreateEmptyFile(Path.Combine(this.Enlistment.RepoRoot, "Folder", "testfile")); this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, expectedModifiedPathsEntriesAfterCreate); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, "Folder/"); this.fileSystem.RenameDirectory(this.Enlistment.RepoRoot, "Folder", "folder"); this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, expectedModifiedPathsEntriesAfterRename); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, "folder/"); + GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, "folder/testfile"); } [TestCase, Order(7)] diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/ModifiedPathsTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/ModifiedPathsTests.cs index ee4c1e060..f8e42331e 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/ModifiedPathsTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/ModifiedPathsTests.cs @@ -53,7 +53,7 @@ public void DeletedTempFileIsRemovedFromModifiedFiles(FileSystemRunner fileSyste tempFile.ShouldNotExistOnDisk(fileSystem); this.Enlistment.UnmountGVFS(); - this.ValidateModifiedPathsDoNotContain(fileSystem, "temp.txt"); + GVFSHelpers.ModifiedPathsShouldNotContain(fileSystem, this.Enlistment.DotGVFSRoot, "temp.txt"); } [TestCaseSource(typeof(FileSystemRunner), FileSystemRunner.TestRunners)] @@ -64,7 +64,7 @@ public void DeletedTempFolderIsRemovedFromModifiedFiles(FileSystemRunner fileSys tempFolder.ShouldNotExistOnDisk(fileSystem); this.Enlistment.UnmountGVFS(); - this.ValidateModifiedPathsDoNotContain(fileSystem, "Temp/"); + GVFSHelpers.ModifiedPathsShouldNotContain(fileSystem, this.Enlistment.DotGVFSRoot, "Temp/"); } [TestCaseSource(typeof(FileSystemRunner), FileSystemRunner.TestRunners)] @@ -79,7 +79,7 @@ public void DeletedTempFolderDeletesFilesFromModifiedFiles(FileSystemRunner file tempFile2.ShouldNotExistOnDisk(fileSystem); this.Enlistment.UnmountGVFS(); - this.ValidateModifiedPathsDoNotContain(fileSystem, "Temp/", "Temp/temp1.txt", "Temp/temp2.txt"); + GVFSHelpers.ModifiedPathsShouldNotContain(fileSystem, this.Enlistment.DotGVFSRoot, "Temp/", "Temp/temp1.txt", "Temp/temp2.txt"); } [Category(Categories.MacTODO.M2)] @@ -219,11 +219,5 @@ private string CreateFile(FileSystemRunner fileSystem, string relativePath) tempFile.ShouldBeAFile(fileSystem); return tempFile; } - - private void ValidateModifiedPathsDoNotContain(FileSystemRunner fileSystem, params string[] paths) - { - GVFSHelpers.ModifiedPathsShouldNotContain(fileSystem, this.Enlistment.DotGVFSRoot, paths.Select(x => $"A {x}" + Environment.NewLine).ToArray()); - GVFSHelpers.ModifiedPathsShouldNotContain(fileSystem, this.Enlistment.DotGVFSRoot, paths.Select(x => $"D {x}" + Environment.NewLine).ToArray()); - } } } diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitCommandsTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitCommandsTests.cs index c623f5ea7..31ac5cb08 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitCommandsTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/GitCommandsTests.cs @@ -980,15 +980,17 @@ public void EditFileNeedingUtf8Encoding() { this.ValidateGitCommand("checkout -b tests/functional/EditFileNeedingUtf8Encoding"); this.ValidateGitCommand("status"); + string virtualFile = Path.Combine(this.Enlistment.RepoRoot, EncodingFileFolder, EncodingFilename); string controlFile = Path.Combine(this.ControlGitRepo.RootPath, EncodingFileFolder, EncodingFilename); + string relativeGitPath = EncodingFileFolder + "/" + EncodingFilename; string contents = virtualFile.ShouldBeAFile(this.FileSystem).WithContents(); string expectedContents = controlFile.ShouldBeAFile(this.FileSystem).WithContents(); contents.ShouldEqual(expectedContents); // Confirm that the entry is not in the the modified paths database - GVFSHelpers.ModifiedPathsShouldNotContain(this.FileSystem, this.Enlistment.DotGVFSRoot, EncodingFilename); + GVFSHelpers.ModifiedPathsShouldNotContain(this.FileSystem, this.Enlistment.DotGVFSRoot, relativeGitPath); this.ValidateGitCommand("status"); this.AppendAllText(ContentWhenEditingFile, virtualFile); @@ -997,7 +999,7 @@ public void EditFileNeedingUtf8Encoding() this.ValidateGitCommand("status"); // Confirm that the entry was added to the modified paths database - GVFSHelpers.ModifiedPathsShouldContain(this.FileSystem, this.Enlistment.DotGVFSRoot, EncodingFilename); + GVFSHelpers.ModifiedPathsShouldContain(this.FileSystem, this.Enlistment.DotGVFSRoot, relativeGitPath); } [TestCase] diff --git a/GVFS/GVFS.FunctionalTests/Tools/GVFSHelpers.cs b/GVFS/GVFS.FunctionalTests/Tools/GVFSHelpers.cs index 95e988c18..58abd7985 100644 --- a/GVFS/GVFS.FunctionalTests/Tools/GVFSHelpers.cs +++ b/GVFS/GVFS.FunctionalTests/Tools/GVFSHelpers.cs @@ -4,10 +4,10 @@ using Microsoft.Data.Sqlite; using Newtonsoft.Json; using NUnit.Framework; +using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; namespace GVFS.FunctionalTests.Tools { @@ -19,6 +19,9 @@ public static class GVFSHelpers public static readonly string PlaceholderListFile = Path.Combine("databases", "PlaceholderList.dat"); public static readonly string RepoMetadataName = Path.Combine("databases", "RepoMetadata.dat"); + private const string ModifedPathsLineAddPrefix = "A "; + private const string ModifedPathsLineDeletePrefix = "D "; + private const string DiskLayoutMajorVersionKey = "DiskLayoutVersion"; private const string DiskLayoutMinorVersionKey = "DiskLayoutMinorVersion"; private const string LocalCacheRootKey = "LocalCacheRoot"; @@ -111,15 +114,29 @@ public static void ModifiedPathsShouldContain(FileSystemRunner fileSystem, strin { string modifiedPathsDatabase = Path.Combine(dotGVFSRoot, TestConstants.Databases.ModifiedPaths); modifiedPathsDatabase.ShouldBeAFile(fileSystem); - GVFSHelpers.ReadAllTextFromWriteLockedFile(modifiedPathsDatabase).ShouldContain( - gitPaths.Select(path => path + ModifiedPathsNewLine).ToArray()); + string modifedPathsContents = GVFSHelpers.ReadAllTextFromWriteLockedFile(modifiedPathsDatabase); + string[] modifedPathLines = modifedPathsContents.Split(new[] { ModifiedPathsNewLine }, StringSplitOptions.None); + foreach (string gitPath in gitPaths) + { + modifedPathLines.ShouldContain(path => path.Equals(ModifedPathsLineAddPrefix + gitPath)); + } } public static void ModifiedPathsShouldNotContain(FileSystemRunner fileSystem, string dotGVFSRoot, params string[] gitPaths) { string modifiedPathsDatabase = Path.Combine(dotGVFSRoot, TestConstants.Databases.ModifiedPaths); modifiedPathsDatabase.ShouldBeAFile(fileSystem); - GVFSHelpers.ReadAllTextFromWriteLockedFile(modifiedPathsDatabase).ShouldNotContain(ignoreCase: true, unexpectedSubstrings: gitPaths); + string modifedPathsContents = GVFSHelpers.ReadAllTextFromWriteLockedFile(modifiedPathsDatabase); + string[] modifedPathLines = modifedPathsContents.Split(new[] { ModifiedPathsNewLine }, StringSplitOptions.None); + foreach (string gitPath in gitPaths) + { + modifedPathLines.ShouldNotContain( + path => + { + return path.Equals(ModifedPathsLineAddPrefix + gitPath, StringComparison.OrdinalIgnoreCase) || + path.Equals(ModifedPathsLineDeletePrefix + gitPath, StringComparison.OrdinalIgnoreCase); + }); + } } private static byte[] StringToShaBytes(string sha) diff --git a/GVFS/GVFS.UnitTests/Common/ModifiedPathsDatabaseTests.cs b/GVFS/GVFS.UnitTests/Common/ModifiedPathsDatabaseTests.cs index 313484200..663a76bb0 100644 --- a/GVFS/GVFS.UnitTests/Common/ModifiedPathsDatabaseTests.cs +++ b/GVFS/GVFS.UnitTests/Common/ModifiedPathsDatabaseTests.cs @@ -22,31 +22,31 @@ A dir1/dir2/file3.txt [TestCase] public void ParsesExistingDataCorrectly() { - ModifiedPathsDatabase mpd = CreateModifiedPathsDatabase(ExistingEntries); - mpd.Count.ShouldEqual(3); - mpd.Contains("file.txt", isFolder: false).ShouldBeTrue(); - mpd.Contains("dir/file2.txt", isFolder: false).ShouldBeTrue(); - mpd.Contains("dir1/dir2/file3.txt", isFolder: false).ShouldBeTrue(); + ModifiedPathsDatabase modifiedPathsDatabase = CreateModifiedPathsDatabase(ExistingEntries); + modifiedPathsDatabase.Count.ShouldEqual(3); + modifiedPathsDatabase.Contains("file.txt", isFolder: false).ShouldBeTrue(); + modifiedPathsDatabase.Contains("dir/file2.txt", isFolder: false).ShouldBeTrue(); + modifiedPathsDatabase.Contains("dir1/dir2/file3.txt", isFolder: false).ShouldBeTrue(); } [TestCase] public void AddsDefaultEntry() { - ModifiedPathsDatabase mpd = CreateModifiedPathsDatabase(initialContents: string.Empty); - mpd.Count.ShouldEqual(1); - mpd.Contains(DefaultEntry, isFolder: false).ShouldBeTrue(); + ModifiedPathsDatabase modifiedPathsDatabase = CreateModifiedPathsDatabase(initialContents: string.Empty); + modifiedPathsDatabase.Count.ShouldEqual(1); + modifiedPathsDatabase.Contains(DefaultEntry, isFolder: false).ShouldBeTrue(); } [TestCase] public void BadDataFailsToLoad() { - ConfigurableFileSystem fs = new ConfigurableFileSystem(); - fs.ExpectedFiles.Add(MockEntryFileName, new ReusableMemoryStream("This is bad data!\r\n")); + ConfigurableFileSystem configurableFileSystem = new ConfigurableFileSystem(); + configurableFileSystem.ExpectedFiles.Add(MockEntryFileName, new ReusableMemoryStream("This is bad data!\r\n")); string error; - ModifiedPathsDatabase mpd; - ModifiedPathsDatabase.TryLoadOrCreate(null, MockEntryFileName, fs, out mpd, out error).ShouldBeFalse(); - mpd.ShouldBeNull(); + ModifiedPathsDatabase modifiedPathsDatabase; + ModifiedPathsDatabase.TryLoadOrCreate(null, MockEntryFileName, configurableFileSystem, out modifiedPathsDatabase, out error).ShouldBeFalse(); + modifiedPathsDatabase.ShouldBeNull(); } [TestCase] @@ -74,6 +74,51 @@ public void DirectorySeparatorAddedForFolder() TestAddingPath(pathToAdd: Path.Combine("dir", "subdir"), pathInList: Path.Combine("dir", "subdir") + Path.DirectorySeparatorChar, isFolder: true); } + [TestCase] + public void EntryNotAddedIfParentDirectoryExists() + { + ModifiedPathsDatabase modifiedPathsDatabase = CreateModifiedPathsDatabase(initialContents: "A dir/\r\n"); + modifiedPathsDatabase.Count.ShouldEqual(1); + modifiedPathsDatabase.Contains("dir", isFolder: true).ShouldBeTrue(); + + // Try adding a file for the directory that is in the modified paths + modifiedPathsDatabase.TryAdd("dir/file.txt", isFolder: false, isRetryable: out _); + modifiedPathsDatabase.Count.ShouldEqual(1); + modifiedPathsDatabase.Contains("dir", isFolder: true).ShouldBeTrue(); + + // Try adding a directory for the directory that is in the modified paths + modifiedPathsDatabase.TryAdd("dir/dir2", isFolder: true, isRetryable: out _); + modifiedPathsDatabase.Count.ShouldEqual(1); + modifiedPathsDatabase.Contains("dir", isFolder: true).ShouldBeTrue(); + + // Try adding a file for a directory that is not in the modified paths + modifiedPathsDatabase.TryAdd("dir2/file.txt", isFolder: false, isRetryable: out _); + modifiedPathsDatabase.Count.ShouldEqual(2); + modifiedPathsDatabase.Contains("dir", isFolder: true).ShouldBeTrue(); + modifiedPathsDatabase.Contains("dir2/file.txt", isFolder: false).ShouldBeTrue(); + + // Try adding a directory for a the directory that is not in the modified paths + modifiedPathsDatabase.TryAdd("dir2/dir", isFolder: true, isRetryable: out _); + modifiedPathsDatabase.Count.ShouldEqual(3); + modifiedPathsDatabase.Contains("dir", isFolder: true).ShouldBeTrue(); + modifiedPathsDatabase.Contains("dir2/file.txt", isFolder: false).ShouldBeTrue(); + modifiedPathsDatabase.Contains("dir2/dir", isFolder: true).ShouldBeTrue(); + + // Try adding a file in a subdirectory that is in the modified paths + modifiedPathsDatabase.TryAdd("dir2/dir/file.txt", isFolder: false, isRetryable: out _); + modifiedPathsDatabase.Count.ShouldEqual(3); + modifiedPathsDatabase.Contains("dir", isFolder: true).ShouldBeTrue(); + modifiedPathsDatabase.Contains("dir2/file.txt", isFolder: false).ShouldBeTrue(); + modifiedPathsDatabase.Contains("dir2/dir", isFolder: true).ShouldBeTrue(); + + // Try adding a directory for a subdirectory that is in the modified paths + modifiedPathsDatabase.TryAdd("dir2/dir/dir3", isFolder: true, isRetryable: out _); + modifiedPathsDatabase.Count.ShouldEqual(3); + modifiedPathsDatabase.Contains("dir", isFolder: true).ShouldBeTrue(); + modifiedPathsDatabase.Contains("dir2/file.txt", isFolder: false).ShouldBeTrue(); + modifiedPathsDatabase.Contains("dir2/dir", isFolder: true).ShouldBeTrue(); + } + private static void TestAddingPath(string path, bool isFolder = false) { TestAddingPath(path, path, isFolder); @@ -81,13 +126,13 @@ private static void TestAddingPath(string path, bool isFolder = false) private static void TestAddingPath(string pathToAdd, string pathInList, bool isFolder = false) { - ModifiedPathsDatabase mpd = CreateModifiedPathsDatabase(initialContents: $"A {DefaultEntry}\r\n"); + ModifiedPathsDatabase modifiedPathsDatabase = CreateModifiedPathsDatabase(initialContents: $"A {DefaultEntry}\r\n"); bool isRetryable; - mpd.TryAdd(pathToAdd, isFolder, out isRetryable); - mpd.Count.ShouldEqual(2); - mpd.Contains(pathInList, isFolder).ShouldBeTrue(); - mpd.Contains(ToGitPathSeparators(pathInList), isFolder).ShouldBeTrue(); - mpd.GetAllModifiedPaths().ShouldContainSingle(x => string.Compare(x, ToGitPathSeparators(pathInList), StringComparison.OrdinalIgnoreCase) == 0); + modifiedPathsDatabase.TryAdd(pathToAdd, isFolder, out isRetryable); + modifiedPathsDatabase.Count.ShouldEqual(2); + modifiedPathsDatabase.Contains(pathInList, isFolder).ShouldBeTrue(); + modifiedPathsDatabase.Contains(ToGitPathSeparators(pathInList), isFolder).ShouldBeTrue(); + modifiedPathsDatabase.GetAllModifiedPaths().ShouldContainSingle(x => string.Compare(x, ToGitPathSeparators(pathInList), StringComparison.OrdinalIgnoreCase) == 0); } private static string ToGitPathSeparators(string path) @@ -102,14 +147,14 @@ private static string ToPathSeparators(string path) private static ModifiedPathsDatabase CreateModifiedPathsDatabase(string initialContents) { - ConfigurableFileSystem fs = new ConfigurableFileSystem(); - fs.ExpectedFiles.Add(MockEntryFileName, new ReusableMemoryStream(initialContents)); + ConfigurableFileSystem configurableFileSystem = new ConfigurableFileSystem(); + configurableFileSystem.ExpectedFiles.Add(MockEntryFileName, new ReusableMemoryStream(initialContents)); string error; - ModifiedPathsDatabase mpd; - ModifiedPathsDatabase.TryLoadOrCreate(null, MockEntryFileName, fs, out mpd, out error).ShouldBeTrue(); - mpd.ShouldNotBeNull(); - return mpd; + ModifiedPathsDatabase modifiedPathsDatabase; + ModifiedPathsDatabase.TryLoadOrCreate(null, MockEntryFileName, configurableFileSystem, out modifiedPathsDatabase, out error).ShouldBeTrue(); + modifiedPathsDatabase.ShouldNotBeNull(); + return modifiedPathsDatabase; } } }