diff --git a/GVFS/GVFS.Common/Prefetch/Git/GitIndexGenerator.cs b/GVFS/GVFS.Common/Prefetch/Git/GitIndexGenerator.cs index 3bc66f07c7..66ae2d3d9e 100644 --- a/GVFS/GVFS.Common/Prefetch/Git/GitIndexGenerator.cs +++ b/GVFS/GVFS.Common/Prefetch/Git/GitIndexGenerator.cs @@ -60,11 +60,11 @@ public GitIndexGenerator(ITracer tracer, Enlistment enlistment, bool shouldHashI public bool HasFailures { get; private set; } - public void CreateFromHeadTree(uint indexVersion, HashSet sparseCheckoutEntries = null) + public void CreateFromHeadTree(uint indexVersion) { using (ITracer updateIndexActivity = this.tracer.StartActivity("CreateFromHeadTree", EventLevel.Informational)) { - Thread entryWritingThread = new Thread(() => this.WriteAllEntries(indexVersion, sparseCheckoutEntries)); + Thread entryWritingThread = new Thread(() => this.WriteAllEntries(indexVersion)); entryWritingThread.Start(); GitProcess git = new GitProcess(this.enlistment); @@ -94,7 +94,7 @@ private void EnqueueEntriesFromLsTree(string line) } } - private void WriteAllEntries(uint version, HashSet sparseCheckoutEntries) + private void WriteAllEntries(uint version) { try { @@ -109,11 +109,7 @@ private void WriteAllEntries(uint version, HashSet sparseCheckoutEntries LsTreeEntry entry; while (this.entryQueue.TryTake(out entry, Timeout.Infinite)) { - bool skipWorkTree = - sparseCheckoutEntries != null && - !sparseCheckoutEntries.Contains(entry.Filename) && - !sparseCheckoutEntries.Contains(this.GetDirectoryNameForGitPath(entry.Filename)); - this.WriteEntry(writer, version, entry.Sha, entry.Filename, skipWorkTree, ref lastStringLength); + this.WriteEntry(writer, version, entry.Sha, entry.Filename, ref lastStringLength); } // Update entry count @@ -143,7 +139,7 @@ private string GetDirectoryNameForGitPath(string filename) return filename.Substring(0, idx + 1); } - private void WriteEntry(BinaryWriter writer, uint version, string sha, string filename, bool skipWorktree, ref uint lastStringLength) + private void WriteEntry(BinaryWriter writer, uint version, string sha, string filename, ref uint lastStringLength) { long startPosition = writer.BaseStream.Position; @@ -156,14 +152,8 @@ private void WriteEntry(BinaryWriter writer, uint version, string sha, string fi byte[] filenameBytes = Encoding.UTF8.GetBytes(filename); ushort flags = (ushort)(filenameBytes.Length & 0xFFF); - flags |= version >= 3 && skipWorktree ? ExtendedBit : (ushort)0; writer.Write(EndianHelper.Swap(flags)); - if (version >= 3 && skipWorktree) - { - writer.Write(EndianHelper.Swap(SkipWorktreeBit)); - } - if (version >= 4) { this.WriteReplaceLength(writer, lastStringLength); diff --git a/GVFS/GVFS.FunctionalTests.Windows/GVFS.FunctionalTests.Windows.csproj b/GVFS/GVFS.FunctionalTests.Windows/GVFS.FunctionalTests.Windows.csproj index 572078f973..4252e85223 100644 --- a/GVFS/GVFS.FunctionalTests.Windows/GVFS.FunctionalTests.Windows.csproj +++ b/GVFS/GVFS.FunctionalTests.Windows/GVFS.FunctionalTests.Windows.csproj @@ -101,6 +101,7 @@ + diff --git a/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/DiskLayoutUpgradeTests.cs b/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/DiskLayoutUpgradeTests.cs index 1abbbaafb9..d3ea1f93be 100644 --- a/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/DiskLayoutUpgradeTests.cs +++ b/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/DiskLayoutUpgradeTests.cs @@ -27,6 +27,17 @@ public class DiskLayoutUpgradeTests : TestsWithEnlistmentPerTestCase private FileSystemRunner fileSystem = new SystemIORunner(); + [SetUp] + public override void CreateEnlistment() + { + base.CreateEnlistment(); + + // Since there isn't a sparse-checkout file that is used anymore one needs to be added + // in order to test the old upgrades that might have needed it + string sparseCheckoutPath = Path.Combine(this.Enlistment.RepoRoot, TestConstants.DotGit.Info.SparseCheckoutPath); + this.fileSystem.WriteAllText(sparseCheckoutPath, "/.gitattributes\r\n"); + } + [TestCase] public void MountUpgradesFromVersion7() { diff --git a/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/SharedCacheUpgradeTests.cs b/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/SharedCacheUpgradeTests.cs index f113b00122..5d6acf5165 100644 --- a/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/SharedCacheUpgradeTests.cs +++ b/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/SharedCacheUpgradeTests.cs @@ -48,6 +48,11 @@ public void MountUpgradesLocalSizesToSharedCache() versionJsonPath.ShouldBeAFile(this.fileSystem); this.fileSystem.DeleteFile(versionJsonPath); + // Since there isn't a sparse-checkout file that is used anymore one needs to be added + // in order to test the old upgrades that might have needed it + string sparseCheckoutPath = Path.Combine(enlistment.RepoRoot, TestConstants.DotGit.Info.SparseCheckoutPath); + this.fileSystem.WriteAllText(sparseCheckoutPath, "/.gitattributes\r\n"); + // "13.0" was the last version before blob sizes were moved out of Esent string metadataPath = Path.Combine(enlistment.DotGVFSRoot, GVFSHelpers.RepoMetadataName); this.fileSystem.CreateEmptyFile(metadataPath); @@ -103,6 +108,11 @@ public void MountUpgradesLocalSizesToSharedCache() versionJsonPath.ShouldBeAFile(this.fileSystem); this.fileSystem.DeleteFile(versionJsonPath); + // Since there isn't a sparse-checkout file that is used anymore one needs to be added + // in order to test the old upgrades that might have needed it + string sparseCheckoutPath2 = Path.Combine(enlistment2.RepoRoot, TestConstants.DotGit.Info.SparseCheckoutPath); + this.fileSystem.WriteAllText(sparseCheckoutPath2, "/.gitattributes\r\n"); + // "13.0" was the last version before blob sizes were moved out of Esent metadataPath = Path.Combine(enlistment2.DotGVFSRoot, GVFSHelpers.RepoMetadataName); this.fileSystem.CreateEmptyFile(metadataPath); diff --git a/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/WindowsUpdatePlaceholderTests.cs.cs b/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/WindowsUpdatePlaceholderTests.cs.cs new file mode 100644 index 0000000000..be59d5b21c --- /dev/null +++ b/GVFS/GVFS.FunctionalTests.Windows/Windows/Tests/WindowsUpdatePlaceholderTests.cs.cs @@ -0,0 +1,476 @@ +using GVFS.FunctionalTests.FileSystemRunners; +using GVFS.FunctionalTests.Should; +using GVFS.FunctionalTests.Tools; +using GVFS.Tests.Should; +using Microsoft.Win32.SafeHandles; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; + +namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture +{ + // WindowsOnly because tests in this class depend on Windows specific file sharing behavior + [TestFixture] + [Category(Categories.WindowsOnly)] + [Category(Categories.GitCommands)] + public class WindowsUpdatePlaceholderTests : TestsWithEnlistmentPerFixture + { + private const string TestParentFolderName = "Test_EPF_UpdatePlaceholderTests"; + private const string OldCommitId = "5d7a7d4db1734fb468a4094469ec58d26301b59d"; + private const string NewFilesAndChangesCommitId = "fec239ea12de1eda6ae5329d4f345784d5b61ff9"; + private FileSystemRunner fileSystem; + + public WindowsUpdatePlaceholderTests() + { + this.fileSystem = new SystemIORunner(); + } + + [SetUp] + public virtual void SetupForTest() + { + // Start each test at NewFilesAndChangesCommitId + this.GitCheckoutCommitId(NewFilesAndChangesCommitId); + this.GitStatusShouldBeClean(NewFilesAndChangesCommitId); + } + + [TestCase, Order(1)] + public void LockToPreventDelete_SingleFile() + { + string testFile1Contents = "TestContentsLockToPreventDelete \r\n"; + string testFile1Name = "test.txt"; + string testFile1Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventDelete", testFile1Name)); + + testFile1Path.ShouldBeAFile(this.fileSystem).WithContents(testFile1Contents); + using (SafeFileHandle testFile1Handle = this.CreateFile(testFile1Path, FileShare.Read)) + { + testFile1Handle.IsInvalid.ShouldEqual(false); + + ProcessResult result = this.InvokeGitAgainstGVFSRepo("checkout " + OldCommitId); + result.Errors.ShouldContain( + "GVFS was unable to delete the following files. To recover, close all handles to the files and run these commands:", + "git clean -f " + TestParentFolderName + "/LockToPreventDelete/" + testFile1Name); + + GitHelpers.CheckGitCommandAgainstGVFSRepo( + this.Enlistment.RepoRoot, + "status -u", + "HEAD detached at " + OldCommitId, + "Untracked files:", + TestParentFolderName + "/LockToPreventDelete/" + testFile1Name); + } + + this.GitCleanFile(TestParentFolderName + "/LockToPreventDelete/" + testFile1Name); + this.GitStatusShouldBeClean(OldCommitId); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventDelete/" + testFile1Name); + testFile1Path.ShouldNotExistOnDisk(this.fileSystem); + + this.GitCheckoutCommitId(NewFilesAndChangesCommitId); + + this.GitStatusShouldBeClean(NewFilesAndChangesCommitId); + testFile1Path.ShouldBeAFile(this.fileSystem).WithContents(testFile1Contents); + } + + [TestCase, Order(2)] + public void LockToPreventDelete_MultipleFiles() + { + string testFile2Contents = "TestContentsLockToPreventDelete2 \r\n"; + string testFile3Contents = "TestContentsLockToPreventDelete3 \r\n"; + string testFile4Contents = "TestContentsLockToPreventDelete4 \r\n"; + + string testFile2Name = "test2.txt"; + string testFile3Name = "test3.txt"; + string testFile4Name = "test4.txt"; + + string testFile2Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventDelete", testFile2Name)); + string testFile3Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventDelete", testFile3Name)); + string testFile4Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventDelete", testFile4Name)); + + testFile2Path.ShouldBeAFile(this.fileSystem).WithContents(testFile2Contents); + testFile3Path.ShouldBeAFile(this.fileSystem).WithContents(testFile3Contents); + testFile4Path.ShouldBeAFile(this.fileSystem).WithContents(testFile4Contents); + + using (SafeFileHandle testFile2Handle = this.CreateFile(testFile2Path, FileShare.Read)) + using (SafeFileHandle testFile3Handle = this.CreateFile(testFile3Path, FileShare.Read)) + using (SafeFileHandle testFile4Handle = this.CreateFile(testFile4Path, FileShare.Read)) + { + testFile2Handle.IsInvalid.ShouldEqual(false); + testFile3Handle.IsInvalid.ShouldEqual(false); + testFile4Handle.IsInvalid.ShouldEqual(false); + + ProcessResult result = this.InvokeGitAgainstGVFSRepo("checkout " + OldCommitId); + result.Errors.ShouldContain( + "GVFS was unable to delete the following files. To recover, close all handles to the files and run these commands:", + "git clean -f " + TestParentFolderName + "/LockToPreventDelete/" + testFile2Name, + "git clean -f " + TestParentFolderName + "/LockToPreventDelete/" + testFile3Name, + "git clean -f " + TestParentFolderName + "/LockToPreventDelete/" + testFile4Name); + + GitHelpers.CheckGitCommandAgainstGVFSRepo( + this.Enlistment.RepoRoot, + "status -u", + "HEAD detached at " + OldCommitId, + "Untracked files:", + TestParentFolderName + "/LockToPreventDelete/" + testFile2Name, + TestParentFolderName + "/LockToPreventDelete/" + testFile3Name, + TestParentFolderName + "/LockToPreventDelete/" + testFile4Name); + } + + this.GitCleanFile(TestParentFolderName + "/LockToPreventDelete/" + testFile2Name); + this.GitCleanFile(TestParentFolderName + "/LockToPreventDelete/" + testFile3Name); + this.GitCleanFile(TestParentFolderName + "/LockToPreventDelete/" + testFile4Name); + + this.GitStatusShouldBeClean(OldCommitId); + + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventDelete/" + testFile2Name); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventDelete/" + testFile3Name); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventDelete/" + testFile4Name); + + testFile2Path.ShouldNotExistOnDisk(this.fileSystem); + testFile3Path.ShouldNotExistOnDisk(this.fileSystem); + testFile4Path.ShouldNotExistOnDisk(this.fileSystem); + + this.GitCheckoutCommitId(NewFilesAndChangesCommitId); + + this.GitStatusShouldBeClean(NewFilesAndChangesCommitId); + testFile2Path.ShouldBeAFile(this.fileSystem).WithContents(testFile2Contents); + testFile3Path.ShouldBeAFile(this.fileSystem).WithContents(testFile3Contents); + testFile4Path.ShouldBeAFile(this.fileSystem).WithContents(testFile4Contents); + } + + [TestCase, Order(3)] + public void LockToPreventUpdate_SingleFile() + { + string testFile1Contents = "Commit2LockToPreventUpdate \r\n"; + string testFile1OldContents = "TestFileLockToPreventUpdate \r\n"; + string testFile1Name = "test.txt"; + string testFile1Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdate", testFile1Name)); + + testFile1Path.ShouldBeAFile(this.fileSystem).WithContents(testFile1Contents); + using (SafeFileHandle testFile1Handle = this.CreateFile(testFile1Path, FileShare.Read)) + { + testFile1Handle.IsInvalid.ShouldEqual(false); + + ProcessResult result = this.InvokeGitAgainstGVFSRepo("checkout " + OldCommitId); + result.Errors.ShouldContain( + "GVFS was unable to update the following files. To recover, close all handles to the files and run these commands:", + "git checkout -- " + TestParentFolderName + "/LockToPreventUpdate/" + testFile1Name); + + GitHelpers.CheckGitCommandAgainstGVFSRepo( + this.Enlistment.RepoRoot, + "status", + "HEAD detached at " + OldCommitId, + "Changes not staged for commit:", + TestParentFolderName + "/LockToPreventUpdate/" + testFile1Name); + } + + this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdate/" + testFile1Name); + this.GitStatusShouldBeClean(OldCommitId); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdate/" + testFile1Name); + testFile1Path.ShouldBeAFile(this.fileSystem).WithContents(testFile1OldContents); + + this.GitCheckoutCommitId(NewFilesAndChangesCommitId); + + this.GitStatusShouldBeClean(NewFilesAndChangesCommitId); + testFile1Path.ShouldBeAFile(this.fileSystem).WithContents(testFile1Contents); + } + + [TestCase, Order(4)] + public void LockToPreventUpdate_MultipleFiles() + { + string testFile2Contents = "Commit2LockToPreventUpdate2 \r\n"; + string testFile3Contents = "Commit2LockToPreventUpdate3 \r\n"; + string testFile4Contents = "Commit2LockToPreventUpdate4 \r\n"; + + string testFile2OldContents = "TestFileLockToPreventUpdate2 \r\n"; + string testFile3OldContents = "TestFileLockToPreventUpdate3 \r\n"; + string testFile4OldContents = "TestFileLockToPreventUpdate4 \r\n"; + + string testFile2Name = "test2.txt"; + string testFile3Name = "test3.txt"; + string testFile4Name = "test4.txt"; + + string testFile2Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdate", testFile2Name)); + string testFile3Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdate", testFile3Name)); + string testFile4Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdate", testFile4Name)); + + testFile2Path.ShouldBeAFile(this.fileSystem).WithContents(testFile2Contents); + testFile3Path.ShouldBeAFile(this.fileSystem).WithContents(testFile3Contents); + testFile4Path.ShouldBeAFile(this.fileSystem).WithContents(testFile4Contents); + + using (SafeFileHandle testFile2Handle = this.CreateFile(testFile2Path, FileShare.Read)) + using (SafeFileHandle testFile3Handle = this.CreateFile(testFile3Path, FileShare.Read)) + using (SafeFileHandle testFile4Handle = this.CreateFile(testFile4Path, FileShare.Read)) + { + testFile2Handle.IsInvalid.ShouldEqual(false); + testFile3Handle.IsInvalid.ShouldEqual(false); + testFile4Handle.IsInvalid.ShouldEqual(false); + + ProcessResult result = this.InvokeGitAgainstGVFSRepo("checkout " + OldCommitId); + result.Errors.ShouldContain( + "GVFS was unable to update the following files. To recover, close all handles to the files and run these commands:", + "git checkout -- " + TestParentFolderName + "/LockToPreventUpdate/" + testFile2Name, + "git checkout -- " + TestParentFolderName + "/LockToPreventUpdate/" + testFile3Name, + "git checkout -- " + TestParentFolderName + "/LockToPreventUpdate/" + testFile4Name); + + GitHelpers.CheckGitCommandAgainstGVFSRepo( + this.Enlistment.RepoRoot, + "status", + "HEAD detached at " + OldCommitId, + "Changes not staged for commit:", + TestParentFolderName + "/LockToPreventUpdate/" + testFile2Name, + TestParentFolderName + "/LockToPreventUpdate/" + testFile3Name, + TestParentFolderName + "/LockToPreventUpdate/" + testFile4Name); + } + + this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdate/" + testFile2Name); + this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdate/" + testFile3Name); + this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdate/" + testFile4Name); + + this.GitStatusShouldBeClean(OldCommitId); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdate/" + testFile2Name); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdate/" + testFile3Name); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdate/" + testFile4Name); + testFile2Path.ShouldBeAFile(this.fileSystem).WithContents(testFile2OldContents); + testFile3Path.ShouldBeAFile(this.fileSystem).WithContents(testFile3OldContents); + testFile4Path.ShouldBeAFile(this.fileSystem).WithContents(testFile4OldContents); + + this.GitCheckoutCommitId(NewFilesAndChangesCommitId); + + this.GitStatusShouldBeClean(NewFilesAndChangesCommitId); + testFile2Path.ShouldBeAFile(this.fileSystem).WithContents(testFile2Contents); + testFile3Path.ShouldBeAFile(this.fileSystem).WithContents(testFile3Contents); + testFile4Path.ShouldBeAFile(this.fileSystem).WithContents(testFile4Contents); + } + + [TestCase, Order(5)] + public void LockToPreventUpdateAndDelete() + { + string testFileUpdate1Contents = "Commit2LockToPreventUpdateAndDelete \r\n"; + string testFileUpdate2Contents = "Commit2LockToPreventUpdateAndDelete2 \r\n"; + string testFileUpdate3Contents = "Commit2LockToPreventUpdateAndDelete3 \r\n"; + string testFileDelete1Contents = "PreventDelete \r\n"; + string testFileDelete2Contents = "PreventDelete2 \r\n"; + string testFileDelete3Contents = "PreventDelete3 \r\n"; + + string testFileUpdate1OldContents = "TestFileLockToPreventUpdateAndDelete \r\n"; + string testFileUpdate2OldContents = "TestFileLockToPreventUpdateAndDelete2 \r\n"; + string testFileUpdate3OldContents = "TestFileLockToPreventUpdateAndDelete3 \r\n"; + + string testFileUpdate1Name = "test.txt"; + string testFileUpdate2Name = "test2.txt"; + string testFileUpdate3Name = "test3.txt"; + string testFileDelete1Name = "test_delete.txt"; + string testFileDelete2Name = "test_delete2.txt"; + string testFileDelete3Name = "test_delete3.txt"; + + string testFileUpdate1Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdateAndDelete", testFileUpdate1Name)); + string testFileUpdate2Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdateAndDelete", testFileUpdate2Name)); + string testFileUpdate3Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdateAndDelete", testFileUpdate3Name)); + string testFileDelete1Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdateAndDelete", testFileDelete1Name)); + string testFileDelete2Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdateAndDelete", testFileDelete2Name)); + string testFileDelete3Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdateAndDelete", testFileDelete3Name)); + + testFileUpdate1Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate1Contents); + testFileUpdate2Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate2Contents); + testFileUpdate3Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate3Contents); + testFileDelete1Path.ShouldBeAFile(this.fileSystem).WithContents(testFileDelete1Contents); + testFileDelete2Path.ShouldBeAFile(this.fileSystem).WithContents(testFileDelete2Contents); + testFileDelete3Path.ShouldBeAFile(this.fileSystem).WithContents(testFileDelete3Contents); + + using (SafeFileHandle testFileUpdate1Handle = this.CreateFile(testFileUpdate1Path, FileShare.Read)) + using (SafeFileHandle testFileUpdate2Handle = this.CreateFile(testFileUpdate2Path, FileShare.Read)) + using (SafeFileHandle testFileUpdate3Handle = this.CreateFile(testFileUpdate3Path, FileShare.Read)) + using (SafeFileHandle testFileDelete1Handle = this.CreateFile(testFileDelete1Path, FileShare.Read)) + using (SafeFileHandle testFileDelete2Handle = this.CreateFile(testFileDelete2Path, FileShare.Read)) + using (SafeFileHandle testFileDelete3Handle = this.CreateFile(testFileDelete3Path, FileShare.Read)) + { + testFileUpdate1Handle.IsInvalid.ShouldEqual(false); + testFileUpdate2Handle.IsInvalid.ShouldEqual(false); + testFileUpdate3Handle.IsInvalid.ShouldEqual(false); + testFileDelete1Handle.IsInvalid.ShouldEqual(false); + testFileDelete2Handle.IsInvalid.ShouldEqual(false); + testFileDelete3Handle.IsInvalid.ShouldEqual(false); + + ProcessResult checkoutResult = GitProcess.InvokeProcess(this.Enlistment.RepoRoot, "checkout " + OldCommitId); + checkoutResult.Errors.ShouldContain( + "HEAD is now at " + OldCommitId, + "GVFS was unable to delete the following files. To recover, close all handles to the files and run these commands:", + "git clean -f " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete1Name, + "git clean -f " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete2Name, + "git clean -f " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete3Name, + "GVFS was unable to update the following files. To recover, close all handles to the files and run these commands:", + "git checkout -- " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate1Name, + "git checkout -- " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate2Name, + "git checkout -- " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate3Name); + + GitHelpers.CheckGitCommandAgainstGVFSRepo( + this.Enlistment.RepoRoot, + "status", + "HEAD detached at " + OldCommitId, + "modified: Test_EPF_UpdatePlaceholderTests/LockToPreventUpdateAndDelete/test.txt", + "modified: Test_EPF_UpdatePlaceholderTests/LockToPreventUpdateAndDelete/test2.txt", + "modified: Test_EPF_UpdatePlaceholderTests/LockToPreventUpdateAndDelete/test3.txt", + "Untracked files:\n (use \"git add ...\" to include in what will be committed)\n\n\tTest_EPF_UpdatePlaceholderTests/LockToPreventUpdateAndDelete/test_delete.txt\n\tTest_EPF_UpdatePlaceholderTests/LockToPreventUpdateAndDelete/test_delete2.txt\n\tTest_EPF_UpdatePlaceholderTests/LockToPreventUpdateAndDelete/test_delete3.txt", + "no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); + } + + this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate1Name); + this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate2Name); + this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate3Name); + this.GitCleanFile(TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete1Name); + this.GitCleanFile(TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete2Name); + this.GitCleanFile(TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete3Name); + + this.GitStatusShouldBeClean(OldCommitId); + + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate1Name); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate2Name); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate3Name); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete1Name); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete2Name); + GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete3Name); + + testFileUpdate1Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate1OldContents); + testFileUpdate2Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate2OldContents); + testFileUpdate3Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate3OldContents); + testFileDelete1Path.ShouldNotExistOnDisk(this.fileSystem); + testFileDelete2Path.ShouldNotExistOnDisk(this.fileSystem); + testFileDelete3Path.ShouldNotExistOnDisk(this.fileSystem); + + this.GitCheckoutCommitId(NewFilesAndChangesCommitId); + + this.GitStatusShouldBeClean(NewFilesAndChangesCommitId); + testFileUpdate1Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate1Contents); + testFileUpdate2Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate2Contents); + testFileUpdate3Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate3Contents); + testFileDelete1Path.ShouldBeAFile(this.fileSystem).WithContents(testFileDelete1Contents); + testFileDelete2Path.ShouldBeAFile(this.fileSystem).WithContents(testFileDelete2Contents); + testFileDelete3Path.ShouldBeAFile(this.fileSystem).WithContents(testFileDelete3Contents); + } + + [TestCase, Order(6)] + public void LockMoreThanMaxReportedFileNames() + { + string updateFilesFolder = "FilesToUpdate"; + string deleteFilesFolder = "FilesToDelete"; + + for (int i = 1; i <= 51; ++i) + { + this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "MaxFileListCount", updateFilesFolder, i.ToString() + ".txt")).ShouldBeAFile(this.fileSystem); + this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "MaxFileListCount", deleteFilesFolder, i.ToString() + ".txt")).ShouldBeAFile(this.fileSystem); + } + + List openHandles = new List(); + try + { + for (int i = 1; i <= 51; ++i) + { + SafeFileHandle handle = this.CreateFile( + this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "MaxFileListCount", updateFilesFolder, i.ToString() + ".txt")), + FileShare.Read); + openHandles.Add(handle); + handle.IsInvalid.ShouldEqual(false); + + handle = this.CreateFile( + this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "MaxFileListCount", deleteFilesFolder, i.ToString() + ".txt")), + FileShare.Read); + openHandles.Add(handle); + handle.IsInvalid.ShouldEqual(false); + } + + ProcessResult result = this.InvokeGitAgainstGVFSRepo("checkout " + OldCommitId); + result.Errors.ShouldContain( + "GVFS failed to update 102 files, run 'git status' to check the status of files in the repo"); + + List expectedOutputStrings = new List() + { + "HEAD detached at " + OldCommitId, + "no changes added to commit (use \"git add\" and/or \"git commit -a\")\n" + }; + + for (int expectedFilePrefix = 1; expectedFilePrefix <= 51; ++expectedFilePrefix) + { + expectedOutputStrings.Add("modified: Test_EPF_UpdatePlaceholderTests/MaxFileListCount/" + updateFilesFolder + "/" + expectedFilePrefix.ToString() + ".txt"); + expectedOutputStrings.Add("Test_EPF_UpdatePlaceholderTests/MaxFileListCount/" + deleteFilesFolder + "/" + expectedFilePrefix.ToString() + ".txt"); + } + + GitHelpers.CheckGitCommandAgainstGVFSRepo(this.Enlistment.RepoRoot, "status -u", expectedOutputStrings.ToArray()); + } + finally + { + foreach (SafeFileHandle handle in openHandles) + { + handle.Dispose(); + } + } + + for (int i = 1; i <= 51; ++i) + { + this.GitCheckoutToDiscardChanges(TestParentFolderName + "/MaxFileListCount/" + updateFilesFolder + "/" + i.ToString() + ".txt"); + this.GitCleanFile(TestParentFolderName + "/MaxFileListCount/" + deleteFilesFolder + "/" + i.ToString() + ".txt"); + } + + this.GitStatusShouldBeClean(OldCommitId); + this.GitCheckoutCommitId(NewFilesAndChangesCommitId); + this.GitStatusShouldBeClean(NewFilesAndChangesCommitId); + } + + private ProcessResult InvokeGitAgainstGVFSRepo(string command) + { + return GitHelpers.InvokeGitAgainstGVFSRepo(this.Enlistment.RepoRoot, command); + } + + private void GitStatusShouldBeClean(string commitId) + { + GitHelpers.CheckGitCommandAgainstGVFSRepo( + this.Enlistment.RepoRoot, + "status", + "HEAD detached at " + commitId, + "nothing to commit, working tree clean"); + } + + private void GitCleanFile(string gitPath) + { + GitHelpers.CheckGitCommandAgainstGVFSRepo( + this.Enlistment.RepoRoot, + "clean -f " + gitPath, + "Removing " + gitPath); + } + + private void GitCheckoutToDiscardChanges(string gitPath) + { + GitHelpers.CheckGitCommandAgainstGVFSRepo(this.Enlistment.RepoRoot, "checkout -- " + gitPath); + } + + private void GitCheckoutCommitId(string commitId) + { + this.InvokeGitAgainstGVFSRepo("checkout " + commitId).Errors.ShouldContain("HEAD is now at " + commitId); + } + + private SafeFileHandle CreateFile(string path, FileShare shareMode) + { + return NativeMethods.CreateFile( + path, + (uint)FileAccess.Read, + shareMode, + IntPtr.Zero, + FileMode.Open, + (uint)FileAttributes.Normal, + IntPtr.Zero); + } + + private bool CanUpdateAndDeletePlaceholdersWithOpenHandles() + { + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724429(v=vs.85).aspx + FileVersionInfo kernel32Info = FileVersionInfo.GetVersionInfo(Path.Combine(Environment.SystemDirectory, "kernel32.dll")); + + // 16248 is first build with support - see 12658248 for details + if (kernel32Info.FileBuildPart >= 16248) + { + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs index 7da4e85839..cd1a79337a 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using System.Threading; namespace GVFS.FunctionalTests.FileSystemRunners @@ -28,11 +29,16 @@ public class BashRunner : ShellRunner "Function not implemented" }; - private static string[] permissionDeniedMessage = new string[] + private static string[] windowsPermissionDeniedMessage = new string[] { "Permission denied" }; + private static string[] macPermissionDeniedMessage = new string[] + { + "Resource temporarily unavailable" + }; + private readonly string pathToBash; public BashRunner() @@ -103,7 +109,7 @@ public override void MoveFileShouldFail(string sourcePath, string targetPath) { // BashRunner does nothing special when a failure is expected, so just confirm source file is still present this.MoveFile(sourcePath, targetPath); - this.FileExists(sourcePath).ShouldEqual(true); + this.FileExists(sourcePath).ShouldBeTrue($"{sourcePath} does not exist when it should"); } public override void MoveFile_FileShouldNotBeFound(string sourcePath, string targetPath) @@ -241,7 +247,8 @@ public override void DeleteFile_FileShouldNotBeFound(string path) public override void DeleteFile_AccessShouldBeDenied(string path) { - this.DeleteFile(path).ShouldContain(permissionDeniedMessage); + this.DeleteFile(path).ShouldContain(this.GetPermissionDeniedError()); + this.FileExists(path).ShouldBeTrue($"{path} does not exist when it should"); } public override void ReadAllText_FileShouldNotBeFound(string path) @@ -267,5 +274,15 @@ private string ConvertWinPathToBashPath(string winPath) bashPath = bashPath.Replace('\\', '/'); return bashPath; } + + private string[] GetPermissionDeniedError() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return windowsPermissionDeniedMessage; + } + + return macPermissionDeniedMessage; + } } } diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs index 6c3208b46f..5a640ec598 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs @@ -182,6 +182,7 @@ public override void DeleteFile_FileShouldNotBeFound(string path) public override void DeleteFile_AccessShouldBeDenied(string path) { this.DeleteFile(path).ShouldContain(permissionDeniedMessage); + this.FileExists(path).ShouldBeTrue($"{path} does not exist when it should"); } public override void ReadAllText_FileShouldNotBeFound(string path) diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs index 6d947b510d..e0fac59e42 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs @@ -62,6 +62,7 @@ public override void DeleteFile_FileShouldNotBeFound(string path) public override void DeleteFile_AccessShouldBeDenied(string path) { this.ShouldFail(() => { this.DeleteFile(path); }); + this.FileExists(path).ShouldBeTrue($"{path} does not exist when it should"); } public override string ReadAllText(string path) diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/BasicFileSystemTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/BasicFileSystemTests.cs index 214f12697e..3c05f81513 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/BasicFileSystemTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/BasicFileSystemTests.cs @@ -785,7 +785,6 @@ public void MoveDotGitFullFolderTreeToDotGitFullFolder(FileSystemRunner fileSyst } [TestCaseSource(typeof(FileSystemRunner), FileSystemRunner.TestRunners)] - [Category(Categories.MacTODO.M4)] public void DeleteIndexFileFails(FileSystemRunner fileSystem) { string indexFilePath = this.Enlistment.GetVirtualPathTo(Path.Combine(".git", "index")); diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/DehydrateTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/DehydrateTests.cs index 1fc03382bf..9c51fefccd 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/DehydrateTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/DehydrateTests.cs @@ -65,10 +65,7 @@ public void DehydrateShouldBackupFiles() // .git folder items string gitFolder = Path.Combine(backupFolderItems[0], ".git"); - this.DirectoryShouldContain(gitFolder, "index", "info"); - - string gitInfoFolder = Path.Combine(gitFolder, "info"); - this.DirectoryShouldContain(gitInfoFolder, "sparse-checkout"); + this.DirectoryShouldContain(gitFolder, "index"); // .gvfs folder items string gvfsFolder = Path.Combine(backupFolderItems[0], ".gvfs"); diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GVFSLockTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GVFSLockTests.cs index c7047c2666..abe17b4950 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GVFSLockTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GVFSLockTests.cs @@ -32,7 +32,6 @@ private enum MoveFileFlags : uint } [TestCase] - [Category(Categories.MacTODO.M2)] public void GitCheckoutFailsOutsideLock() { const string BackupPrefix = "BACKUP_"; @@ -65,32 +64,32 @@ public void GitCheckoutFailsOutsideLock() } [TestCase] - [Category(Categories.MacTODO.M4)] public void LockPreventsRenameFromOutsideRootOnTopOfIndex() { this.OverwritingIndexShouldFail(Path.Combine(this.Enlistment.EnlistmentRoot, "LockPreventsRenameFromOutsideRootOnTopOfIndex.txt")); } [TestCase] - [Category(Categories.MacTODO.M4)] public void LockPreventsRenameFromInsideWorkingTreeOnTopOfIndex() { this.OverwritingIndexShouldFail(this.Enlistment.GetVirtualPathTo("LockPreventsRenameFromInsideWorkingTreeOnTopOfIndex.txt")); } [TestCase] - [Category(Categories.MacTODO.M4)] public void LockPreventsRenameOfIndexLockOnTopOfIndex() { this.OverwritingIndexShouldFail(this.Enlistment.GetVirtualPathTo(".git", "index.lock")); } - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern bool MoveFileEx( + [DllImport("kernel32.dll", EntryPoint = "MoveFileEx", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern bool WindowsMoveFileEx( string existingFileName, string newFileName, uint flags); + [DllImport("libc", EntryPoint = "rename", SetLastError = true)] + private static extern int MacRename(string oldPath, string newPath); + private void OverwritingIndexShouldFail(string testFilePath) { string indexPath = this.Enlistment.GetVirtualPathTo(".git", "index"); @@ -102,15 +101,28 @@ private void OverwritingIndexShouldFail(string testFilePath) this.fileSystem.WriteAllText(testFilePath, testFileContents); this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); - MoveFileEx( - testFilePath, - indexPath, - (uint)(MoveFileFlags.MoveFileReplaceExisting | MoveFileFlags.MoveFileCopyAllowed)).ShouldBeFalse("GVFS should prevent renaming on top of index when GVFSLock is not held"); + + this.RenameAndOverwrite(testFilePath, indexPath).ShouldBeFalse("GVFS should prevent renaming on top of index when GVFSLock is not held"); byte[] newIndexContents = File.ReadAllBytes(indexPath); indexContents.SequenceEqual(newIndexContents).ShouldBeTrue("Index contenst should not have changed"); this.fileSystem.DeleteFile(testFilePath); } + + private bool RenameAndOverwrite(string oldPath, string newPath) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return WindowsMoveFileEx( + oldPath, + newPath, + (uint)(MoveFileFlags.MoveFileReplaceExisting | MoveFileFlags.MoveFileCopyAllowed)); + } + else + { + return MacRename(oldPath, newPath) == 0; + } + } } } diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs index 55961a00cc..eca57a1c29 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/GitFilesTests.cs @@ -136,7 +136,7 @@ public void RenameFolderTest() [TestCase, Order(6)] [Category(Categories.MacTODO.M2)] - public void CaseOnlyRenameOfNewFolderKeepsExcludeEntries() + public void CaseOnlyRenameOfNewFolderKeepsModifiedPathsEntries() { string[] expectedModifiedPathsEntries = { @@ -157,7 +157,7 @@ public void CaseOnlyRenameOfNewFolderKeepsExcludeEntries() } [TestCase, Order(7)] - public void ReadingFileDoesNotUpdateIndexOrSparseCheckout() + public void ReadingFileDoesNotUpdateIndexOrModifiedPaths() { string gitFileToCheck = "GVFS/GVFS.FunctionalTests/Category/CategoryConstants.cs"; string virtualFile = this.Enlistment.GetVirtualPathTo(gitFileToCheck); @@ -291,13 +291,13 @@ public void DeletedFolderAndChildrenAddedToToModifiedPathsFile() } [TestCase, Order(13)] - public void FileRenamedOutOfRepoAddedToModifiedPathsFile() + public void FileRenamedOutOfRepoAddedToModifiedPathsAndSkipWorktreeBitCleared() { string fileToRenameEntry = "GVFlt_MoveFileTest/PartialToOutside/from/lessInFrom.txt"; string fileToRenameVirtualPath = this.Enlistment.GetVirtualPathTo(fileToRenameEntry); this.VerifyWorktreeBit(fileToRenameEntry, LsFilesStatus.SkipWorktree); - string fileOutsideRepoPath = Path.Combine(this.Enlistment.EnlistmentRoot, "FileRenamedOutOfRepoAddedToSparseCheckoutAndSkipWorktreeBitCleared.txt"); + string fileOutsideRepoPath = Path.Combine(this.Enlistment.EnlistmentRoot, $"{nameof(this.FileRenamedOutOfRepoAddedToModifiedPathsAndSkipWorktreeBitCleared)}.txt"); this.fileSystem.MoveFile(fileToRenameVirtualPath, fileOutsideRepoPath); fileOutsideRepoPath.ShouldBeAFile(this.fileSystem).WithContents("lessData"); @@ -310,13 +310,13 @@ public void FileRenamedOutOfRepoAddedToModifiedPathsFile() } [TestCase, Order(14)] - public void OverwrittenFileAddedToSparseCheckoutAndSkipWorktreeBitCleared() + public void OverwrittenFileAddedToModifiedPathsAndSkipWorktreeBitCleared() { string fileToOverwriteEntry = "Test_EPF_WorkingDirectoryTests/1/2/3/4/ReadDeepProjectedFile.cpp"; string fileToOverwriteVirtualPath = this.Enlistment.GetVirtualPathTo(fileToOverwriteEntry); this.VerifyWorktreeBit(fileToOverwriteEntry, LsFilesStatus.SkipWorktree); - string testContents = "Test contents for FileRenamedOutOfRepoWillBeAddedToSparseCheckoutAndHaveSkipWorktreeBitCleared"; + string testContents = $"Test contents for {nameof(this.OverwrittenFileAddedToModifiedPathsAndSkipWorktreeBitCleared)}"; this.fileSystem.WriteAllText(fileToOverwriteVirtualPath, testContents); this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); @@ -331,13 +331,13 @@ public void OverwrittenFileAddedToSparseCheckoutAndSkipWorktreeBitCleared() [TestCase, Order(15)] [Category(Categories.MacTODO.M2)] - public void SupersededFileAddedToSparseCheckoutAndSkipWorktreeBitCleared() + public void SupersededFileAddedToModifiedPathsAndSkipWorktreeBitCleared() { string fileToSupersedeEntry = "GVFlt_FileOperationTest/WriteAndVerify.txt"; string fileToSupersedePath = this.Enlistment.GetVirtualPathTo("GVFlt_FileOperationTest\\WriteAndVerify.txt"); this.VerifyWorktreeBit(fileToSupersedeEntry, LsFilesStatus.SkipWorktree); - string newContent = "SupersededFileWillBeAddedToSparseCheckoutAndHaveSkipWorktreeBitCleared test new contents"; + string newContent = $"{nameof(this.SupersededFileAddedToModifiedPathsAndSkipWorktreeBitCleared)} test new contents"; SupersedeFile(fileToSupersedePath, newContent).ShouldEqual(true); this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete."); diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/UpdatePlaceholderTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/UpdatePlaceholderTests.cs index c36877f587..b279288a72 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/UpdatePlaceholderTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/UpdatePlaceholderTests.cs @@ -2,18 +2,16 @@ using GVFS.FunctionalTests.Should; using GVFS.FunctionalTests.Tools; using GVFS.Tests.Should; -using Microsoft.Win32.SafeHandles; using NUnit.Framework; using System; -using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture { [TestFixture] [Category(Categories.GitCommands)] - [Category(Categories.MacTODO.M3)] public class UpdatePlaceholderTests : TestsWithEnlistmentPerFixture { private const string TestParentFolderName = "Test_EPF_UpdatePlaceholderTests"; @@ -35,319 +33,6 @@ public virtual void SetupForTest() } [TestCase, Order(1)] - public void LockToPreventDelete_SingleFile() - { - string testFile1Contents = "TestContentsLockToPreventDelete \r\n"; - string testFile1Name = "test.txt"; - string testFile1Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventDelete", testFile1Name)); - - testFile1Path.ShouldBeAFile(this.fileSystem).WithContents(testFile1Contents); - using (SafeFileHandle testFile1Handle = this.CreateFile(testFile1Path, FileShare.Read)) - { - testFile1Handle.IsInvalid.ShouldEqual(false); - - ProcessResult result = this.InvokeGitAgainstGVFSRepo("checkout " + OldCommitId); - result.Errors.ShouldContain( - "GVFS was unable to delete the following files. To recover, close all handles to the files and run these commands:", - "git clean -f " + TestParentFolderName + "/LockToPreventDelete/" + testFile1Name); - - GitHelpers.CheckGitCommandAgainstGVFSRepo( - this.Enlistment.RepoRoot, - "status -u", - "HEAD detached at " + OldCommitId, - "Untracked files:", - TestParentFolderName + "/LockToPreventDelete/" + testFile1Name); - } - - this.GitCleanFile(TestParentFolderName + "/LockToPreventDelete/" + testFile1Name); - this.GitStatusShouldBeClean(OldCommitId); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventDelete/" + testFile1Name); - testFile1Path.ShouldNotExistOnDisk(this.fileSystem); - - this.GitCheckoutCommitId(NewFilesAndChangesCommitId); - - this.GitStatusShouldBeClean(NewFilesAndChangesCommitId); - testFile1Path.ShouldBeAFile(this.fileSystem).WithContents(testFile1Contents); - } - - [TestCase, Order(2)] - public void LockToPreventDelete_MultipleFiles() - { - string testFile2Contents = "TestContentsLockToPreventDelete2 \r\n"; - string testFile3Contents = "TestContentsLockToPreventDelete3 \r\n"; - string testFile4Contents = "TestContentsLockToPreventDelete4 \r\n"; - - string testFile2Name = "test2.txt"; - string testFile3Name = "test3.txt"; - string testFile4Name = "test4.txt"; - - string testFile2Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventDelete", testFile2Name)); - string testFile3Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventDelete", testFile3Name)); - string testFile4Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventDelete", testFile4Name)); - - testFile2Path.ShouldBeAFile(this.fileSystem).WithContents(testFile2Contents); - testFile3Path.ShouldBeAFile(this.fileSystem).WithContents(testFile3Contents); - testFile4Path.ShouldBeAFile(this.fileSystem).WithContents(testFile4Contents); - - using (SafeFileHandle testFile2Handle = this.CreateFile(testFile2Path, FileShare.Read)) - using (SafeFileHandle testFile3Handle = this.CreateFile(testFile3Path, FileShare.Read)) - using (SafeFileHandle testFile4Handle = this.CreateFile(testFile4Path, FileShare.Read)) - { - testFile2Handle.IsInvalid.ShouldEqual(false); - testFile3Handle.IsInvalid.ShouldEqual(false); - testFile4Handle.IsInvalid.ShouldEqual(false); - - ProcessResult result = this.InvokeGitAgainstGVFSRepo("checkout " + OldCommitId); - result.Errors.ShouldContain( - "GVFS was unable to delete the following files. To recover, close all handles to the files and run these commands:", - "git clean -f " + TestParentFolderName + "/LockToPreventDelete/" + testFile2Name, - "git clean -f " + TestParentFolderName + "/LockToPreventDelete/" + testFile3Name, - "git clean -f " + TestParentFolderName + "/LockToPreventDelete/" + testFile4Name); - - GitHelpers.CheckGitCommandAgainstGVFSRepo( - this.Enlistment.RepoRoot, - "status -u", - "HEAD detached at " + OldCommitId, - "Untracked files:", - TestParentFolderName + "/LockToPreventDelete/" + testFile2Name, - TestParentFolderName + "/LockToPreventDelete/" + testFile3Name, - TestParentFolderName + "/LockToPreventDelete/" + testFile4Name); - } - - this.GitCleanFile(TestParentFolderName + "/LockToPreventDelete/" + testFile2Name); - this.GitCleanFile(TestParentFolderName + "/LockToPreventDelete/" + testFile3Name); - this.GitCleanFile(TestParentFolderName + "/LockToPreventDelete/" + testFile4Name); - - this.GitStatusShouldBeClean(OldCommitId); - - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventDelete/" + testFile2Name); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventDelete/" + testFile3Name); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventDelete/" + testFile4Name); - - testFile2Path.ShouldNotExistOnDisk(this.fileSystem); - testFile3Path.ShouldNotExistOnDisk(this.fileSystem); - testFile4Path.ShouldNotExistOnDisk(this.fileSystem); - - this.GitCheckoutCommitId(NewFilesAndChangesCommitId); - - this.GitStatusShouldBeClean(NewFilesAndChangesCommitId); - testFile2Path.ShouldBeAFile(this.fileSystem).WithContents(testFile2Contents); - testFile3Path.ShouldBeAFile(this.fileSystem).WithContents(testFile3Contents); - testFile4Path.ShouldBeAFile(this.fileSystem).WithContents(testFile4Contents); - } - - [TestCase, Order(3)] - public void LockToPreventUpdate_SingleFile() - { - string testFile1Contents = "Commit2LockToPreventUpdate \r\n"; - string testFile1OldContents = "TestFileLockToPreventUpdate \r\n"; - string testFile1Name = "test.txt"; - string testFile1Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdate", testFile1Name)); - - testFile1Path.ShouldBeAFile(this.fileSystem).WithContents(testFile1Contents); - using (SafeFileHandle testFile1Handle = this.CreateFile(testFile1Path, FileShare.Read)) - { - testFile1Handle.IsInvalid.ShouldEqual(false); - - ProcessResult result = this.InvokeGitAgainstGVFSRepo("checkout " + OldCommitId); - result.Errors.ShouldContain( - "GVFS was unable to update the following files. To recover, close all handles to the files and run these commands:", - "git checkout -- " + TestParentFolderName + "/LockToPreventUpdate/" + testFile1Name); - - GitHelpers.CheckGitCommandAgainstGVFSRepo( - this.Enlistment.RepoRoot, - "status", - "HEAD detached at " + OldCommitId, - "Changes not staged for commit:", - TestParentFolderName + "/LockToPreventUpdate/" + testFile1Name); - } - - this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdate/" + testFile1Name); - this.GitStatusShouldBeClean(OldCommitId); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdate/" + testFile1Name); - testFile1Path.ShouldBeAFile(this.fileSystem).WithContents(testFile1OldContents); - - this.GitCheckoutCommitId(NewFilesAndChangesCommitId); - - this.GitStatusShouldBeClean(NewFilesAndChangesCommitId); - testFile1Path.ShouldBeAFile(this.fileSystem).WithContents(testFile1Contents); - } - - [TestCase, Order(4)] - public void LockToPreventUpdate_MultipleFiles() - { - string testFile2Contents = "Commit2LockToPreventUpdate2 \r\n"; - string testFile3Contents = "Commit2LockToPreventUpdate3 \r\n"; - string testFile4Contents = "Commit2LockToPreventUpdate4 \r\n"; - - string testFile2OldContents = "TestFileLockToPreventUpdate2 \r\n"; - string testFile3OldContents = "TestFileLockToPreventUpdate3 \r\n"; - string testFile4OldContents = "TestFileLockToPreventUpdate4 \r\n"; - - string testFile2Name = "test2.txt"; - string testFile3Name = "test3.txt"; - string testFile4Name = "test4.txt"; - - string testFile2Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdate", testFile2Name)); - string testFile3Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdate", testFile3Name)); - string testFile4Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdate", testFile4Name)); - - testFile2Path.ShouldBeAFile(this.fileSystem).WithContents(testFile2Contents); - testFile3Path.ShouldBeAFile(this.fileSystem).WithContents(testFile3Contents); - testFile4Path.ShouldBeAFile(this.fileSystem).WithContents(testFile4Contents); - - using (SafeFileHandle testFile2Handle = this.CreateFile(testFile2Path, FileShare.Read)) - using (SafeFileHandle testFile3Handle = this.CreateFile(testFile3Path, FileShare.Read)) - using (SafeFileHandle testFile4Handle = this.CreateFile(testFile4Path, FileShare.Read)) - { - testFile2Handle.IsInvalid.ShouldEqual(false); - testFile3Handle.IsInvalid.ShouldEqual(false); - testFile4Handle.IsInvalid.ShouldEqual(false); - - ProcessResult result = this.InvokeGitAgainstGVFSRepo("checkout " + OldCommitId); - result.Errors.ShouldContain( - "GVFS was unable to update the following files. To recover, close all handles to the files and run these commands:", - "git checkout -- " + TestParentFolderName + "/LockToPreventUpdate/" + testFile2Name, - "git checkout -- " + TestParentFolderName + "/LockToPreventUpdate/" + testFile3Name, - "git checkout -- " + TestParentFolderName + "/LockToPreventUpdate/" + testFile4Name); - - GitHelpers.CheckGitCommandAgainstGVFSRepo( - this.Enlistment.RepoRoot, - "status", - "HEAD detached at " + OldCommitId, - "Changes not staged for commit:", - TestParentFolderName + "/LockToPreventUpdate/" + testFile2Name, - TestParentFolderName + "/LockToPreventUpdate/" + testFile3Name, - TestParentFolderName + "/LockToPreventUpdate/" + testFile4Name); - } - - this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdate/" + testFile2Name); - this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdate/" + testFile3Name); - this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdate/" + testFile4Name); - - this.GitStatusShouldBeClean(OldCommitId); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdate/" + testFile2Name); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdate/" + testFile3Name); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdate/" + testFile4Name); - testFile2Path.ShouldBeAFile(this.fileSystem).WithContents(testFile2OldContents); - testFile3Path.ShouldBeAFile(this.fileSystem).WithContents(testFile3OldContents); - testFile4Path.ShouldBeAFile(this.fileSystem).WithContents(testFile4OldContents); - - this.GitCheckoutCommitId(NewFilesAndChangesCommitId); - - this.GitStatusShouldBeClean(NewFilesAndChangesCommitId); - testFile2Path.ShouldBeAFile(this.fileSystem).WithContents(testFile2Contents); - testFile3Path.ShouldBeAFile(this.fileSystem).WithContents(testFile3Contents); - testFile4Path.ShouldBeAFile(this.fileSystem).WithContents(testFile4Contents); - } - - [TestCase, Order(5)] - public void LockToPreventUpdateAndDelete() - { - string testFileUpdate1Contents = "Commit2LockToPreventUpdateAndDelete \r\n"; - string testFileUpdate2Contents = "Commit2LockToPreventUpdateAndDelete2 \r\n"; - string testFileUpdate3Contents = "Commit2LockToPreventUpdateAndDelete3 \r\n"; - string testFileDelete1Contents = "PreventDelete \r\n"; - string testFileDelete2Contents = "PreventDelete2 \r\n"; - string testFileDelete3Contents = "PreventDelete3 \r\n"; - - string testFileUpdate1OldContents = "TestFileLockToPreventUpdateAndDelete \r\n"; - string testFileUpdate2OldContents = "TestFileLockToPreventUpdateAndDelete2 \r\n"; - string testFileUpdate3OldContents = "TestFileLockToPreventUpdateAndDelete3 \r\n"; - - string testFileUpdate1Name = "test.txt"; - string testFileUpdate2Name = "test2.txt"; - string testFileUpdate3Name = "test3.txt"; - string testFileDelete1Name = "test_delete.txt"; - string testFileDelete2Name = "test_delete2.txt"; - string testFileDelete3Name = "test_delete3.txt"; - - string testFileUpdate1Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdateAndDelete", testFileUpdate1Name)); - string testFileUpdate2Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdateAndDelete", testFileUpdate2Name)); - string testFileUpdate3Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdateAndDelete", testFileUpdate3Name)); - string testFileDelete1Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdateAndDelete", testFileDelete1Name)); - string testFileDelete2Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdateAndDelete", testFileDelete2Name)); - string testFileDelete3Path = this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "LockToPreventUpdateAndDelete", testFileDelete3Name)); - - testFileUpdate1Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate1Contents); - testFileUpdate2Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate2Contents); - testFileUpdate3Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate3Contents); - testFileDelete1Path.ShouldBeAFile(this.fileSystem).WithContents(testFileDelete1Contents); - testFileDelete2Path.ShouldBeAFile(this.fileSystem).WithContents(testFileDelete2Contents); - testFileDelete3Path.ShouldBeAFile(this.fileSystem).WithContents(testFileDelete3Contents); - - using (SafeFileHandle testFileUpdate1Handle = this.CreateFile(testFileUpdate1Path, FileShare.Read)) - using (SafeFileHandle testFileUpdate2Handle = this.CreateFile(testFileUpdate2Path, FileShare.Read)) - using (SafeFileHandle testFileUpdate3Handle = this.CreateFile(testFileUpdate3Path, FileShare.Read)) - using (SafeFileHandle testFileDelete1Handle = this.CreateFile(testFileDelete1Path, FileShare.Read)) - using (SafeFileHandle testFileDelete2Handle = this.CreateFile(testFileDelete2Path, FileShare.Read)) - using (SafeFileHandle testFileDelete3Handle = this.CreateFile(testFileDelete3Path, FileShare.Read)) - { - testFileUpdate1Handle.IsInvalid.ShouldEqual(false); - testFileUpdate2Handle.IsInvalid.ShouldEqual(false); - testFileUpdate3Handle.IsInvalid.ShouldEqual(false); - testFileDelete1Handle.IsInvalid.ShouldEqual(false); - testFileDelete2Handle.IsInvalid.ShouldEqual(false); - testFileDelete3Handle.IsInvalid.ShouldEqual(false); - - ProcessResult checkoutResult = GitProcess.InvokeProcess(this.Enlistment.RepoRoot, "checkout " + OldCommitId); - checkoutResult.Errors.ShouldContain( - "HEAD is now at " + OldCommitId, - "GVFS was unable to delete the following files. To recover, close all handles to the files and run these commands:", - "git clean -f " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete1Name, - "git clean -f " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete2Name, - "git clean -f " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete3Name, - "GVFS was unable to update the following files. To recover, close all handles to the files and run these commands:", - "git checkout -- " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate1Name, - "git checkout -- " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate2Name, - "git checkout -- " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate3Name); - - GitHelpers.CheckGitCommandAgainstGVFSRepo( - this.Enlistment.RepoRoot, - "status", - "HEAD detached at " + OldCommitId, - "modified: Test_EPF_UpdatePlaceholderTests/LockToPreventUpdateAndDelete/test.txt", - "modified: Test_EPF_UpdatePlaceholderTests/LockToPreventUpdateAndDelete/test2.txt", - "modified: Test_EPF_UpdatePlaceholderTests/LockToPreventUpdateAndDelete/test3.txt", - "Untracked files:\n (use \"git add ...\" to include in what will be committed)\n\n\tTest_EPF_UpdatePlaceholderTests/LockToPreventUpdateAndDelete/test_delete.txt\n\tTest_EPF_UpdatePlaceholderTests/LockToPreventUpdateAndDelete/test_delete2.txt\n\tTest_EPF_UpdatePlaceholderTests/LockToPreventUpdateAndDelete/test_delete3.txt", - "no changes added to commit (use \"git add\" and/or \"git commit -a\")\n"); - } - - this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate1Name); - this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate2Name); - this.GitCheckoutToDiscardChanges(TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate3Name); - this.GitCleanFile(TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete1Name); - this.GitCleanFile(TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete2Name); - this.GitCleanFile(TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete3Name); - - this.GitStatusShouldBeClean(OldCommitId); - - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate1Name); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate2Name); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileUpdate3Name); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete1Name); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete2Name); - GVFSHelpers.ModifiedPathsShouldContain(this.fileSystem, this.Enlistment.DotGVFSRoot, TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete3Name); - - testFileUpdate1Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate1OldContents); - testFileUpdate2Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate2OldContents); - testFileUpdate3Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate3OldContents); - testFileDelete1Path.ShouldNotExistOnDisk(this.fileSystem); - testFileDelete2Path.ShouldNotExistOnDisk(this.fileSystem); - testFileDelete3Path.ShouldNotExistOnDisk(this.fileSystem); - - this.GitCheckoutCommitId(NewFilesAndChangesCommitId); - - this.GitStatusShouldBeClean(NewFilesAndChangesCommitId); - testFileUpdate1Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate1Contents); - testFileUpdate2Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate2Contents); - testFileUpdate3Path.ShouldBeAFile(this.fileSystem).WithContents(testFileUpdate3Contents); - testFileDelete1Path.ShouldBeAFile(this.fileSystem).WithContents(testFileDelete1Contents); - testFileDelete2Path.ShouldBeAFile(this.fileSystem).WithContents(testFileDelete2Contents); - testFileDelete3Path.ShouldBeAFile(this.fileSystem).WithContents(testFileDelete3Contents); - } - - [TestCase, Order(6)] public void LockWithFullShareUpdateAndDelete() { string testFileUpdate4Contents = "Commit2LockToPreventUpdateAndDelete4 \r\n"; @@ -365,24 +50,18 @@ public void LockWithFullShareUpdateAndDelete() if (this.CanUpdateAndDeletePlaceholdersWithOpenHandles()) { - using (SafeFileHandle testFileUpdate4Handle = this.CreateFile(testFileUpdate4Path, FileShare.Read | FileShare.Delete)) - using (SafeFileHandle testFileDelete4Handle = this.CreateFile(testFileDelete4Path, FileShare.Read | FileShare.Delete)) + using (FileStream testFileUpdate4 = File.Open(testFileUpdate4Path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)) + using (FileStream testFileDelete4 = File.Open(testFileDelete4Path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)) { - testFileUpdate4Handle.IsInvalid.ShouldEqual(false); - testFileDelete4Handle.IsInvalid.ShouldEqual(false); - this.GitCheckoutCommitId(OldCommitId); this.GitStatusShouldBeClean(OldCommitId); } } else { - using (SafeFileHandle testFileUpdate4Handle = this.CreateFile(testFileUpdate4Path, FileShare.Read | FileShare.Delete)) - using (SafeFileHandle testFileDelete4Handle = this.CreateFile(testFileDelete4Path, FileShare.Read | FileShare.Delete)) + using (FileStream testFileUpdate4Handle = File.Open(testFileUpdate4Path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)) + using (FileStream testFileDelete4Handle = File.Open(testFileDelete4Path, FileMode.Open, FileAccess.Read, FileShare.Read | FileShare.Delete)) { - testFileUpdate4Handle.IsInvalid.ShouldEqual(false); - testFileDelete4Handle.IsInvalid.ShouldEqual(false); - ProcessResult checkoutResult = GitProcess.InvokeProcess(this.Enlistment.RepoRoot, "checkout " + OldCommitId); checkoutResult.Errors.ShouldContain( "HEAD is now at " + OldCommitId, @@ -413,7 +92,7 @@ public void LockWithFullShareUpdateAndDelete() testFileDelete4Path.ShouldBeAFile(this.fileSystem).WithContents(testFileDelete4Contents); } - [TestCase, Order(7)] + [TestCase, Order(2)] public void FileProjectedAfterPlaceholderDeleteFileAndCheckout() { string testFile1Contents = "ProjectAfterDeleteAndCheckout \r\n"; @@ -451,74 +130,7 @@ public void FileProjectedAfterPlaceholderDeleteFileAndCheckout() testFile3Path.ShouldBeAFile(this.fileSystem).WithContents(testFile3Contents); } - [TestCase, Order(8)] - public void LockMoreThanMaxReportedFileNames() - { - string updateFilesFolder = "FilesToUpdate"; - string deleteFilesFolder = "FilesToDelete"; - - for (int i = 1; i <= 51; ++i) - { - this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "MaxFileListCount", updateFilesFolder, i.ToString() + ".txt")).ShouldBeAFile(this.fileSystem); - this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "MaxFileListCount", deleteFilesFolder, i.ToString() + ".txt")).ShouldBeAFile(this.fileSystem); - } - - List openHandles = new List(); - try - { - for (int i = 1; i <= 51; ++i) - { - SafeFileHandle handle = this.CreateFile( - this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "MaxFileListCount", updateFilesFolder, i.ToString() + ".txt")), - FileShare.Read); - openHandles.Add(handle); - handle.IsInvalid.ShouldEqual(false); - - handle = this.CreateFile( - this.Enlistment.GetVirtualPathTo(Path.Combine(TestParentFolderName, "MaxFileListCount", deleteFilesFolder, i.ToString() + ".txt")), - FileShare.Read); - openHandles.Add(handle); - handle.IsInvalid.ShouldEqual(false); - } - - ProcessResult result = this.InvokeGitAgainstGVFSRepo("checkout " + OldCommitId); - result.Errors.ShouldContain( - "GVFS failed to update 102 files, run 'git status' to check the status of files in the repo"); - - List expectedOutputStrings = new List() - { - "HEAD detached at " + OldCommitId, - "no changes added to commit (use \"git add\" and/or \"git commit -a\")\n" - }; - - for (int expectedFilePrefix = 1; expectedFilePrefix <= 51; ++expectedFilePrefix) - { - expectedOutputStrings.Add("modified: Test_EPF_UpdatePlaceholderTests/MaxFileListCount/" + updateFilesFolder + "/" + expectedFilePrefix.ToString() + ".txt"); - expectedOutputStrings.Add("Test_EPF_UpdatePlaceholderTests/MaxFileListCount/" + deleteFilesFolder + "/" + expectedFilePrefix.ToString() + ".txt"); - } - - GitHelpers.CheckGitCommandAgainstGVFSRepo(this.Enlistment.RepoRoot, "status -u", expectedOutputStrings.ToArray()); - } - finally - { - foreach (SafeFileHandle handle in openHandles) - { - handle.Dispose(); - } - } - - for (int i = 1; i <= 51; ++i) - { - this.GitCheckoutToDiscardChanges(TestParentFolderName + "/MaxFileListCount/" + updateFilesFolder + "/" + i.ToString() + ".txt"); - this.GitCleanFile(TestParentFolderName + "/MaxFileListCount/" + deleteFilesFolder + "/" + i.ToString() + ".txt"); - } - - this.GitStatusShouldBeClean(OldCommitId); - this.GitCheckoutCommitId(NewFilesAndChangesCommitId); - this.GitStatusShouldBeClean(NewFilesAndChangesCommitId); - } - - [TestCase, Order(9)] + [TestCase, Order(3)] public void FullFilesDontAffectThePlaceholderDatabase() { string testFile = Path.Combine(this.Enlistment.RepoRoot, "FullFilesDontAffectThePlaceholderDatabase"); @@ -551,14 +163,6 @@ private void GitStatusShouldBeClean(string commitId) "nothing to commit, working tree clean"); } - private void GitCleanFile(string gitPath) - { - GitHelpers.CheckGitCommandAgainstGVFSRepo( - this.Enlistment.RepoRoot, - "clean -f " + gitPath, - "Removing " + gitPath); - } - private void GitCheckoutToDiscardChanges(string gitPath) { GitHelpers.CheckGitCommandAgainstGVFSRepo(this.Enlistment.RepoRoot, "checkout -- " + gitPath); @@ -569,30 +173,23 @@ private void GitCheckoutCommitId(string commitId) this.InvokeGitAgainstGVFSRepo("checkout " + commitId).Errors.ShouldContain("HEAD is now at " + commitId); } - private SafeFileHandle CreateFile(string path, FileShare shareMode) - { - return NativeMethods.CreateFile( - path, - (uint)FileAccess.Read, - shareMode, - IntPtr.Zero, - FileMode.Open, - (uint)FileAttributes.Normal, - IntPtr.Zero); - } - private bool CanUpdateAndDeletePlaceholdersWithOpenHandles() { - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724429(v=vs.85).aspx - FileVersionInfo kernel32Info = FileVersionInfo.GetVersionInfo(Path.Combine(Environment.SystemDirectory, "kernel32.dll")); - - // 16248 is first build with support - see 12658248 for details - if (kernel32Info.FileBuildPart >= 16248) - { - return true; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms724429(v=vs.85).aspx + FileVersionInfo kernel32Info = FileVersionInfo.GetVersionInfo(Path.Combine(Environment.SystemDirectory, "kernel32.dll")); + + // 16248 is first build with support - see 12658248 for details + if (kernel32Info.FileBuildPart >= 16248) + { + return true; + } + + return false; } - return false; + return true; } } } diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/WorkingDirectoryTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/WorkingDirectoryTests.cs index d5088205ea..7dd6dd41ba 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/WorkingDirectoryTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/WorkingDirectoryTests.cs @@ -430,7 +430,7 @@ public void FolderContentsCorrectAfterCreateNewFolderRenameAndCheckoutCommitWith folder.ShouldNotExistOnDisk(this.fileSystem); GVFSHelpers.ModifiedPathsShouldNotContain(this.fileSystem, this.Enlistment.DotGVFSRoot, folderName); - // Confirm sparse-checkout picks up renamed folder + // Confirm modified paths picks up renamed folder string newFolder = this.Enlistment.GetVirtualPathTo("newFolder"); this.fileSystem.CreateDirectory(newFolder); this.fileSystem.MoveDirectory(newFolder, folder); diff --git a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/ModifiedPathsTests.cs b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/ModifiedPathsTests.cs index 5348ce2685..c57bffccd2 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/ModifiedPathsTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerTestCase/ModifiedPathsTests.cs @@ -15,33 +15,33 @@ public class ModifiedPathsTests : TestsWithEnlistmentPerTestCase private static readonly string FileToDelete = "Readme.md"; private static readonly string FileToRename = Path.Combine("GVFS", "GVFS.Mount", "MountVerb.cs"); private static readonly string RenameFileTarget = Path.Combine("GVFS", "GVFS.Mount", "MountVerb2.cs"); - private static readonly string FolderToCreate = "PersistedSparseExcludeTests_NewFolder"; - private static readonly string FolderToRename = "PersistedSparseExcludeTests_NewFolderForRename"; - private static readonly string RenameFolderTarget = "PersistedSparseExcludeTests_NewFolderForRename2"; + private static readonly string FolderToCreate = $"{nameof(ModifiedPathsTests)}_NewFolder"; + private static readonly string FolderToRename = $"{nameof(ModifiedPathsTests)}_NewFolderForRename"; + private static readonly string RenameFolderTarget = $"{nameof(ModifiedPathsTests)}_NewFolderForRename2"; private static readonly string DotGitFileToCreate = Path.Combine(".git", "TestFileFromDotGit.txt"); private static readonly string RenameNewDotGitFileTarget = "TestFileFromDotGit.txt"; - private static readonly string FileToCreateOutsideRepo = "PersistedSparseExcludeTests_outsideRepo.txt"; - private static readonly string FolderToCreateOutsideRepo = "PersistedSparseExcludeTests_outsideFolder"; + private static readonly string FileToCreateOutsideRepo = $"{nameof(ModifiedPathsTests)}_outsideRepo.txt"; + private static readonly string FolderToCreateOutsideRepo = $"{nameof(ModifiedPathsTests)}_outsideFolder"; private static readonly string FolderToDelete = "Scripts"; - private static readonly string ExpectedModifiedFilesContentsAfterRemount = -@"A .gitattributes -A GVFS/TestAddFile.txt -A GVFS/GVFS/Program.cs -A Readme.md -A GVFS/GVFS.Mount/MountVerb.cs -A GVFS/GVFS.Mount/MountVerb2.cs -A PersistedSparseExcludeTests_NewFolder/ -A PersistedSparseExcludeTests_NewFolderForRename/ -A PersistedSparseExcludeTests_NewFolderForRename2/ -A TestFileFromDotGit.txt -A PersistedSparseExcludeTests_outsideRepo.txt -A PersistedSparseExcludeTests_outsideFolder/ -A Scripts/CreateCommonAssemblyVersion.bat -A Scripts/CreateCommonCliAssemblyVersion.bat -A Scripts/CreateCommonVersionHeader.bat -A Scripts/RunFunctionalTests.bat -A Scripts/RunUnitTests.bat -A Scripts/ + private static readonly string ExpectedModifiedFilesContentsAfterRemount = +$@"A .gitattributes +A {GVFSHelpers.ConvertPathToGitFormat(FileToAdd)} +A {GVFSHelpers.ConvertPathToGitFormat(FileToUpdate)} +A {FileToDelete} +A {GVFSHelpers.ConvertPathToGitFormat(FileToRename)} +A {GVFSHelpers.ConvertPathToGitFormat(RenameFileTarget)} +A {FolderToCreate}/ +A {FolderToRename}/ +A {RenameFolderTarget}/ +A {RenameNewDotGitFileTarget} +A {FileToCreateOutsideRepo} +A {FolderToCreateOutsideRepo}/ +A {FolderToDelete}/CreateCommonAssemblyVersion.bat +A {FolderToDelete}/CreateCommonCliAssemblyVersion.bat +A {FolderToDelete}/CreateCommonVersionHeader.bat +A {FolderToDelete}/RunFunctionalTests.bat +A {FolderToDelete}/RunUnitTests.bat +A {FolderToDelete}/ "; [Category(Categories.MacTODO.M2)] @@ -69,14 +69,14 @@ public void ModifiedPathsSavedAfterRemount(FileSystemRunner fileSystem) string folderToRenameTarget = this.Enlistment.GetVirtualPathTo(RenameFolderTarget); fileSystem.MoveDirectory(folderToRename, folderToRenameTarget); - // Moving the new folder out of the repo should not change the always_exclude file + // Moving the new folder out of the repo should not change the modified paths string folderTargetOutsideSrc = Path.Combine(this.Enlistment.EnlistmentRoot, RenameFolderTarget); folderTargetOutsideSrc.ShouldNotExistOnDisk(fileSystem); fileSystem.MoveDirectory(folderToRenameTarget, folderTargetOutsideSrc); folderTargetOutsideSrc.ShouldBeADirectory(fileSystem); folderToRenameTarget.ShouldNotExistOnDisk(fileSystem); - // Moving a file from the .git folder to the working directory should add the file to the sparse-checkout + // Moving a file from the .git folder to the working directory should add the file to the modified paths string dotGitfileToAdd = this.Enlistment.GetVirtualPathTo(DotGitFileToCreate); fileSystem.WriteAllText(dotGitfileToAdd, "Contents for the new file in dot git"); fileSystem.MoveFile(dotGitfileToAdd, this.Enlistment.GetVirtualPathTo(RenameNewDotGitFileTarget)); diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/CheckoutTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/CheckoutTests.cs index ec5db6744a..728218341f 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/CheckoutTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/CheckoutTests.cs @@ -823,7 +823,7 @@ public void DeleteFileThenCheckout() [Category(Categories.MacTODO.M3)] public void CheckoutEditCheckoutWithoutFolderThenCheckoutWithMultipleFiles() { - // Edit the file to get the entry in the sparse-checkout file + // Edit the file to get the entry in the modified paths database this.EditFile("Changing the content of one file", "DeleteFileWithNameAheadOfDotAndSwitchCommits", "1"); this.RunGitCommand("reset --hard -q HEAD"); diff --git a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/UpdateIndexTests.cs b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/UpdateIndexTests.cs index 943e54abbd..5ca28eaa43 100644 --- a/GVFS/GVFS.FunctionalTests/Tests/GitCommands/UpdateIndexTests.cs +++ b/GVFS/GVFS.FunctionalTests/Tests/GitCommands/UpdateIndexTests.cs @@ -52,7 +52,7 @@ public void UpdateIndexRemoveAddFileOpenForWrite() GitHelpers.InvokeGitAgainstGVFSRepo(this.Enlistment.RepoRoot, "update-index --remove Test_ConflictTests/AddedFiles/AddedByBothDifferentContent.txt"); this.FilesShouldMatchCheckoutOfTargetBranch(); - // Open Test_ConflictTests/AddedFiles/AddedByBothDifferentContent.txt for write so that it's added to the sparse-checkout + // Open Test_ConflictTests/AddedFiles/AddedByBothDifferentContent.txt for write so that it's added to the modified paths database using (FileStream stream = File.Open(Path.Combine(this.Enlistment.RepoRoot, @"Test_ConflictTests\AddedFiles\AddedByBothDifferentContent.txt"), FileMode.Open, FileAccess.Write)) { // TODO 940287: Remove this File.Open once update-index --add\--remove are working as expected diff --git a/GVFS/GVFS.FunctionalTests/Tools/GVFSHelpers.cs b/GVFS/GVFS.FunctionalTests/Tools/GVFSHelpers.cs index 139fb0a063..95e988c182 100644 --- a/GVFS/GVFS.FunctionalTests/Tools/GVFSHelpers.cs +++ b/GVFS/GVFS.FunctionalTests/Tools/GVFSHelpers.cs @@ -23,7 +23,12 @@ public static class GVFSHelpers private const string DiskLayoutMinorVersionKey = "DiskLayoutMinorVersion"; private const string LocalCacheRootKey = "LocalCacheRoot"; private const string GitObjectsRootKey = "GitObjectsRoot"; - private const string BlobSizesRootKey = "BlobSizesRoot"; + private const string BlobSizesRootKey = "BlobSizesRoot"; + + public static string ConvertPathToGitFormat(string path) + { + return path.Replace(Path.DirectorySeparatorChar, TestConstants.GitPathSeparator); + } public static void SaveDiskLayoutVersion(string dotGVFSRoot, string majorVersion, string minorVersion) { diff --git a/GVFS/GVFS.FunctionalTests/Tools/GitHelpers.cs b/GVFS/GVFS.FunctionalTests/Tools/GitHelpers.cs index 729d1d7004..333499ba0e 100644 --- a/GVFS/GVFS.FunctionalTests/Tools/GitHelpers.cs +++ b/GVFS/GVFS.FunctionalTests/Tools/GitHelpers.cs @@ -12,8 +12,6 @@ namespace GVFS.FunctionalTests.Tools { public static class GitHelpers { - public const string AlwaysExcludeFilePath = @".git\info\always_exclude"; - /// /// This string must match the command name provided in the /// GVFS.FunctionalTests.LockHolder program. diff --git a/GVFS/GVFS.FunctionalTests/Tools/TestConstants.cs b/GVFS/GVFS.FunctionalTests/Tools/TestConstants.cs index ebc6ee28f2..0bf84a1b18 100644 --- a/GVFS/GVFS.FunctionalTests/Tools/TestConstants.cs +++ b/GVFS/GVFS.FunctionalTests/Tools/TestConstants.cs @@ -6,6 +6,7 @@ public static class TestConstants { public const string AllZeroSha = "0000000000000000000000000000000000000000"; public const string PartialFolderPlaceholderDatabaseValue = " PARTIAL FOLDER"; + public const char GitPathSeparator = '/'; public static class DotGit { diff --git a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs index bc13595cdb..3d22f7f34c 100644 --- a/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs +++ b/GVFS/GVFS.Platform.Mac/MacFileSystemVirtualizer.cs @@ -16,6 +16,8 @@ public class MacFileSystemVirtualizer : FileSystemVirtualizer { public static readonly byte[] PlaceholderVersionId = ToVersionIdByteArray(new byte[] { PlaceholderVersion }); + private const string ClassName = nameof(MacFileSystemVirtualizer); + private VirtualizationInstance virtualizationInstance; public MacFileSystemVirtualizer(GVFSContext context, GVFSGitObjects gitObjects) @@ -32,6 +34,8 @@ public MacFileSystemVirtualizer(GVFSContext context, GVFSGitObjects gitObjects) this.virtualizationInstance = virtualizationInstance ?? new VirtualizationInstance(); } + protected override string EtwArea => ClassName; + public static FSResult ResultToFSResult(Result result) { switch (result) @@ -330,6 +334,20 @@ private Result OnPreDelete(string relativePath, bool isDirectory) bool pathInsideDotGit = Virtualization.FileSystemCallbacks.IsPathInsideDotGit(relativePath); if (pathInsideDotGit) { + if (relativePath.Equals(GVFSConstants.DotGit.Index, StringComparison.OrdinalIgnoreCase)) + { + string lockedGitCommand = this.Context.Repository.GVFSLock.GetLockedGitCommand(); + if (string.IsNullOrEmpty(lockedGitCommand)) + { + EventMetadata metadata = new EventMetadata(); + metadata.Add("Area", this.EtwArea); + metadata.Add(TracingConstants.MessageKey.WarningMessage, "Blocked index delete outside the lock"); + this.Context.Tracer.RelatedEvent(EventLevel.Warning, $"{nameof(OnPreDelete)}_BlockedIndexDelete", metadata); + + return Result.EAccessDenied; + } + } + this.OnDotGitFileOrFolderDeleted(relativePath); } else diff --git a/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs b/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs index 6418fda147..42c1f109a2 100644 --- a/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs +++ b/GVFS/GVFS.Platform.Windows/WindowsFileSystemVirtualizer.cs @@ -55,13 +55,7 @@ public WindowsFileSystemVirtualizer(GVFSContext context, GVFSGitObjects gitObjec this.activeCommands = new ConcurrentDictionary(); } - protected override string EtwArea - { - get - { - return ClassName; - } - } + protected override string EtwArea => ClassName; /// /// Public for unit testing diff --git a/GVFS/GVFS.Virtualization/FileSystemCallbacks.cs b/GVFS/GVFS.Virtualization/FileSystemCallbacks.cs index ce1b8afe45..a9ea5200eb 100644 --- a/GVFS/GVFS.Virtualization/FileSystemCallbacks.cs +++ b/GVFS/GVFS.Virtualization/FileSystemCallbacks.cs @@ -705,7 +705,7 @@ private FileSystemTaskResult ExecuteBackgroundOperation(FileSystemTask gitUpdate 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, "DirectoryNotFoundWhileUpdatingAlwaysExclude", exceptionMetadata); + this.context.Tracer.RelatedEvent(EventLevel.Informational, "DirectoryNotFoundWhileUpdatingModifiedPaths", exceptionMetadata); } catch (IOException e) { diff --git a/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.cs b/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.cs index ba40f28b60..66b1396156 100644 --- a/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.cs +++ b/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.cs @@ -1607,7 +1607,7 @@ private string GetNewProjectedShaForPlaceholder(string path) // update in the working directory will be full files but we will have a placeholder entry for them as well. // There have been reports of FileSystemVirtualizationInvalidOperation getting hit without a corresponding background - // task having been scheduled (to add the file to the sparse-checkout and clear the skip-worktree bit). + // task having been scheduled (to add the file to the modified paths). // Schedule OnFailedPlaceholderUpdate\OnFailedPlaceholderDelete to be sure that Git starts managing this // file. Currently the only known way that this can happen is deleting a partial file and putting a full // file in its place while GVFS is unmounted. diff --git a/GVFS/GVFS/CommandLine/CloneVerb.cs b/GVFS/GVFS/CommandLine/CloneVerb.cs index 376e0f2a04..00a429c076 100644 --- a/GVFS/GVFS/CommandLine/CloneVerb.cs +++ b/GVFS/GVFS/CommandLine/CloneVerb.cs @@ -552,10 +552,6 @@ private void CheckNotInsideExistingRepo(string normalizedEnlistmentRootPath) Path.Combine(enlistment.WorkingDirectoryRoot, GVFSConstants.DotGit.Head), "ref: refs/heads/" + branch); - File.AppendAllText( - Path.Combine(enlistment.WorkingDirectoryRoot, GVFSConstants.DotGit.Info.SparseCheckoutPath), - GVFSConstants.GitPathSeparatorString + GVFSConstants.SpecialGitFiles.GitAttributes + "\n"); - if (!this.TryDownloadRootGitAttributes(enlistment, gitObjects, gitRepo, out errorMessage)) { return new Result(errorMessage); diff --git a/GVFS/GVFS/CommandLine/DehydrateVerb.cs b/GVFS/GVFS/CommandLine/DehydrateVerb.cs index 39100557ab..71342283a4 100644 --- a/GVFS/GVFS/CommandLine/DehydrateVerb.cs +++ b/GVFS/GVFS/CommandLine/DehydrateVerb.cs @@ -232,7 +232,6 @@ private bool TryBackupFiles(ITracer tracer, GVFSEnlistment enlistment, string ba { string backupSrc = Path.Combine(backupRoot, "src"); string backupGit = Path.Combine(backupRoot, ".git"); - string backupInfo = Path.Combine(backupGit, GVFSConstants.DotGit.Info.Name); string backupGvfs = Path.Combine(backupRoot, ".gvfs"); string backupDatabases = Path.Combine(backupGvfs, GVFSConstants.DotGVFS.Databases.Name); @@ -243,7 +242,6 @@ private bool TryBackupFiles(ITracer tracer, GVFSEnlistment enlistment, string ba string ioError; if (!this.TryIO(tracer, () => Directory.CreateDirectory(backupRoot), "Create backup directory", out ioError) || !this.TryIO(tracer, () => Directory.CreateDirectory(backupGit), "Create backup .git directory", out ioError) || - !this.TryIO(tracer, () => Directory.CreateDirectory(backupInfo), "Create backup .git\\info directory", out ioError) || !this.TryIO(tracer, () => Directory.CreateDirectory(backupGvfs), "Create backup .gvfs directory", out ioError) || !this.TryIO(tracer, () => Directory.CreateDirectory(backupDatabases), "Create backup .gvfs databases directory", out ioError)) { @@ -266,43 +264,6 @@ private bool TryBackupFiles(ITracer tracer, GVFSEnlistment enlistment, string ba return false; } - // ... but then move the hydration-related files back to the backup... - if (!this.TryIO( - tracer, - () => File.Move( - Path.Combine(enlistment.WorkingDirectoryRoot, GVFSConstants.DotGit.Info.SparseCheckoutPath), - Path.Combine(backupInfo, GVFSConstants.DotGit.Info.SparseCheckoutName)), - "Backup the sparse-checkout file", - out errorMessage) || - !this.TryIO( - tracer, - () => - { - if (File.Exists(Path.Combine(enlistment.WorkingDirectoryRoot, GVFSConstants.DotGit.Info.AlwaysExcludePath))) - { - File.Move( - Path.Combine(enlistment.WorkingDirectoryRoot, GVFSConstants.DotGit.Info.AlwaysExcludePath), - Path.Combine(backupInfo, GVFSConstants.DotGit.Info.AlwaysExcludeName)); - } - }, - "Backup the always_exclude file", - out errorMessage)) - { - return false; - } - - // ... and recreate empty ones in the new .git folder... - if (!this.TryIO( - tracer, - () => File.AppendAllText( - Path.Combine(enlistment.WorkingDirectoryRoot, GVFSConstants.DotGit.Info.SparseCheckoutPath), - GVFSConstants.GitPathSeparatorString + GVFSConstants.SpecialGitFiles.GitAttributes + "\n"), - "Recreate a new sparse-checkout file", - out errorMessage)) - { - return false; - } - // ... backup the .gvfs hydration-related data structures... string databasesFolder = Path.Combine(enlistment.DotGVFSRoot, GVFSConstants.DotGVFS.Databases.Name); if (!TryBackupFilesInFolder(tracer, databasesFolder, backupDatabases, searchPattern: "*", filenamesToSkip: "RepoMetadata.dat")) @@ -419,7 +380,7 @@ private bool TryRecreateIndex(ITracer tracer, GVFSEnlistment enlistment) if (!this.ShowStatusWhileRunning( () => { - // Create a new index based on the new minimal sparse-checkout + // Create a new index based on the new minimal modified paths using (NamedPipeServer pipeServer = AllowAllLocksNamedPipeServer.Create(tracer, enlistment)) { GitProcess git = new GitProcess(enlistment); diff --git a/GVFS/GVFS/CommandLine/GVFSVerb.cs b/GVFS/GVFS/CommandLine/GVFSVerb.cs index dbb9440d6b..325e2dceeb 100644 --- a/GVFS/GVFS/CommandLine/GVFSVerb.cs +++ b/GVFS/GVFS/CommandLine/GVFSVerb.cs @@ -84,7 +84,6 @@ public static bool TrySetRequiredGitConfigSettings(Enlistment enlistment) { "core.midx", "true" }, { "core.preloadIndex", "true" }, { "core.safecrlf", "false" }, - { "core.sparseCheckout", "true" }, { "core.untrackedCache", "false" }, { "core.repositoryformatversion", "0" }, { "core.filemode", "false" }, diff --git a/GVFS/GVFS/RepairJobs/GitIndexRepairJob.cs b/GVFS/GVFS/RepairJobs/GitIndexRepairJob.cs index f6cb9f5479..ce41ed1259 100644 --- a/GVFS/GVFS/RepairJobs/GitIndexRepairJob.cs +++ b/GVFS/GVFS/RepairJobs/GitIndexRepairJob.cs @@ -9,13 +9,11 @@ namespace GVFS.RepairJobs public class GitIndexRepairJob : RepairJob { private readonly string indexPath; - private readonly string sparseCheckoutPath; public GitIndexRepairJob(ITracer tracer, TextWriter output, GVFSEnlistment enlistment) : base(tracer, output, enlistment) { this.indexPath = Path.Combine(this.Enlistment.DotGitRoot, GVFSConstants.DotGit.IndexName); - this.sparseCheckoutPath = Path.Combine(this.Enlistment.WorkingDirectoryRoot, GVFSConstants.DotGit.Info.SparseCheckoutPath); } public override string Name diff --git a/Readme.md b/Readme.md index ffbf01864c..e049af181d 100644 --- a/Readme.md +++ b/Readme.md @@ -2,15 +2,16 @@ ## Windows -|Branch|Unit Tests|Functional Tests| -|:--:|:--:|:--:| -|**master**|[![Build status](https://gvfs.visualstudio.com/ci/_apis/build/status/CI%20-%20Windows%20-%20master?branchName=master)](https://gvfs.visualstudio.com/ci/_build/latest?definitionId=7)|[![Build status](https://gvfs.visualstudio.com/ci/_apis/build/status/CI%20-%20Windows%20-%20Full%20Functional%20Tests?branchName=master)](https://gvfs.visualstudio.com/ci/_build/latest?definitionId=6)| - +|Branch|Unit Tests|Functional Tests|Large Repo Perf|Large Repo Build| +|:--:|:--:|:--:|:--:|:--:| +|**master**|[![Build status](https://dev.azure.com/gvfs/ci/_apis/build/status/CI%20-%20Windows?branchName=master)](https://dev.azure.com/gvfs/ci/_build/latest?definitionId=7&branchName=master)|[![Build status](https://dev.azure.com/gvfs/ci/_apis/build/status/CI%20-%20Windows%20-%20Full%20Functional%20Tests?branchName=master)](https://dev.azure.com/gvfs/ci/_build/latest?definitionId=6&branchName=master)|[![Build status](https://dev.azure.com/mseng/VSOnline/_apis/build/status/GVFS/GitHub%20VFSForGit%20Large%20Repo%20Perf%20Tests?branchName=master)](https://dev.azure.com/mseng/VSOnline/_build/latest?definitionId=7179&branchName=master)|[![Build status](https://dev.azure.com/mseng/VSOnline/_apis/build/status/GVFS/GitHub%20VFSForGit%20Large%20Repo%20Build?branchName=master)](https://dev.azure.com/mseng/VSOnline/_build/latest?definitionId=7180&branchName=master)| +|**shipped**|[![Build status](https://dev.azure.com/gvfs/ci/_apis/build/status/CI%20-%20Windows?branchName=releases%2Fshipped)](https://dev.azure.com/gvfs/ci/_build/latest?definitionId=7&branchName=releases%2Fshipped)|[![Build status](https://dev.azure.com/gvfs/ci/_apis/build/status/CI%20-%20Windows%20-%20Full%20Functional%20Tests?branchName=releases%2Fshipped)](https://dev.azure.com/gvfs/ci/_build/latest?definitionId=6&branchName=releases%2Fshipped)|[![Build status](https://dev.azure.com/mseng/VSOnline/_apis/build/status/GVFS/GitHub%20VFSForGit%20Large%20Repo%20Perf%20Tests?branchName=releases%2Fshipped)](https://dev.azure.com/mseng/VSOnline/_build/latest?definitionId=7179&branchName=releases%2Fshipped)|[![Build status](https://dev.azure.com/mseng/VSOnline/_apis/build/status/GVFS/GitHub%20VFSForGit%20Large%20Repo%20Build?branchName=releases%2Fshipped)](https://dev.azure.com/mseng/VSOnline/_build/latest?definitionId=7180&branchName=releases%2Fshipped)| ## Mac |Branch|Unit Tests|Functional Tests| |:--:|:--:|:--:| -|**master**|[![Build status](https://gvfs.visualstudio.com/ci/_apis/build/status/CI%20-%20Mac%20-%20master?branchName=master)](https://gvfs.visualstudio.com/ci/_build/latest?definitionId=15)|| +|**master**|[![Build status](https://dev.azure.com/gvfs/ci/_apis/build/status/CI%20-%20Mac?branchName=master)](https://dev.azure.com/gvfs/ci/_build/latest?definitionId=15&branchName=master)|[![Build status](https://dev.azure.com/mseng/VSOnline/_apis/build/status/GVFS/CI%20-%20Mac%20-%20Functional%20Tests?branchName=master)](https://dev.azure.com/mseng/VSOnline/_build/latest?definitionId=7376&branchName=master)| +|**shipped**|[![Build status](https://dev.azure.com/gvfs/ci/_apis/build/status/CI%20-%20Mac?branchName=releases%2Fshipped)](https://dev.azure.com/gvfs/ci/_build/latest?definitionId=15&branchName=releases%2Fshipped)|[![Build status](https://dev.azure.com/mseng/VSOnline/_apis/build/status/GVFS/CI%20-%20Mac%20-%20Functional%20Tests?branchName=releases%2Fshipped)](https://dev.azure.com/mseng/VSOnline/_build/latest?definitionId=7376&branchName=releases%2Fshipped)| ## What is VFS for Git?