From 2797fbb8358bb2e0c12d6f3b42a60b43f7655edf Mon Sep 17 00:00:00 2001 From: Saeed Noursalehi Date: Wed, 25 Apr 2018 16:20:15 -0700 Subject: [PATCH] Major updates: * Switched over to use the Windows Projected File System (ProjFS) optional feature on RS4 and later. ProjFS is the new name for the GvFlt driver and its associated user mode library. * The local file size cache was migrated from ESENT to SQLite * Git commands are now allowed to delete an empty directory * Many other reliability improvements in interactions between GVFS and the file system, and between GVFS and git Perf improvements: * Better memory management in git, creating a savings of up to half a second on commands that parse the index * Added a new mutli-pack index file to git, allowing it to become much more efficient at finding an object when there are a large number of local packfiles * Added a git config setting to disable the calculations for detecting force pushes during 'git fetch' and 'git pull'. That calculation can take 10's of seconds on a large graph, and users can now opt out of it. * Due to the transition to SQLite, the file sizes cache can now live in the volume-wide .gvfsCache folder and be shared by multiple repos, causing fewer round trips to the server while enumerating files * The post-command step to update placeholder files now batches its size requests to the server, resulting in significant speedups in situations where many placeholder files needed to be updated * The client will now also query the /gvfs/sizes endpoint on a cache server that implements that endpoint, reducing the latency on those requests even further * Sped up GVFS's parsing of the git index, shaving off 2-3 seconds for a large index file --- GVFS.sln | 36 +- GVFS/FastFetch/App.config | 6 +- GVFS/FastFetch/FastFetch.csproj | 6 +- GVFS/FastFetch/GitEnlistment.cs | 3 + GVFS/FastFetch/Jobs/BatchObjectDownloadJob.cs | 4 +- .../Jobs/Data/BlobDownloadRequest.cs | 4 +- GVFS/FastFetch/Jobs/IndexPackJob.cs | 4 +- GVFS/FastFetch/packages.config | 14 +- GVFS/GVFS.Build/GVFS.PreBuild.csproj | 20 +- GVFS/GVFS.Build/GVFS.props | 6 +- .../GenerateApplicationManifests.cs | 54 + GVFS/GVFS.Build/ProjFS.props | 6 + GVFS/GVFS.Common/Enlistment.cs | 6 +- GVFS/GVFS.Common/FileSystem/GvFltFilter.cs | 117 -- .../FileSystem/PhysicalFileSystem.cs | 5 + GVFS/GVFS.Common/FileSystem/ProjFSFilter.cs | 496 +++++++ GVFS/GVFS.Common/GVFS.Common.csproj | 7 +- GVFS/GVFS.Common/GVFSConfig.cs | 3 +- GVFS/GVFS.Common/GVFSConstants.cs | 13 +- GVFS/GVFS.Common/GVFSEnlistment.cs | 76 +- GVFS/GVFS.Common/GVFSLock.Shared.cs | 50 +- GVFS/GVFS.Common/GVFSLock.cs | 173 +-- GVFS/GVFS.Common/Git/GitObjects.cs | 101 +- GVFS/GVFS.Common/Git/GitProcess.cs | 75 +- GVFS/GVFS.Common/Git/GitRepo.cs | 233 +-- GVFS/GVFS.Common/Git/Sha1Id.cs | 186 +++ GVFS/GVFS.Common/GitCommandLineParser.cs | 17 +- GVFS/GVFS.Common/Http/CacheServerInfo.cs | 3 + GVFS/GVFS.Common/Http/CacheServerResolver.cs | 9 +- .../Http/GitEndPointResponseData.cs | 5 + .../Http/GitObjectsHttpRequestor.cs | 23 +- GVFS/GVFS.Common/Http/HttpRequestor.cs | 26 +- GVFS/GVFS.Common/LocalCacheResolver.cs | 2 +- .../NamedPipes/LockNamedPipeMessages.cs | 55 +- .../NamedPipes/NamedPipeMessages.cs | 12 +- GVFS/GVFS.Common/NativeMethods.cs | 53 +- GVFS/GVFS.Common/Paths.cs | 2 +- GVFS/GVFS.Common/PlaceholderListDatabase.cs | 7 +- GVFS/GVFS.Common/ProcessHelper.cs | 20 + GVFS/GVFS.Common/RepoMetadata.cs | 125 +- GVFS/GVFS.Common/RetryConfig.cs | 6 +- GVFS/GVFS.Common/ReturnCode.cs | 3 +- GVFS/GVFS.Common/Tracing/ITracer.cs | 2 +- GVFS/GVFS.Common/Tracing/JsonEtwTracer.cs | 6 +- GVFS/GVFS.Common/packages.config | 14 +- GVFS/GVFS.FunctionalTests/Categories.cs | 9 + .../FileSystemRunners/BashRunner.cs | 12 + .../Category/CategoryConstants.cs | 8 - .../FileSystemRunners/CmdRunner.cs | 26 +- .../FileSystemRunners/FileSystemRunner.cs | 2 + .../FileSystemRunners/PowerShellRunner.cs | 14 +- .../FileSystemRunners/ShellRunner.cs | 3 +- .../FileSystemRunners/SystemIORunner.cs | 14 + .../GVFS.FunctionalTests.csproj | 36 +- GVFS/GVFS.FunctionalTests/GVFSTestConfig.cs | 18 +- GVFS/GVFS.FunctionalTests/Program.cs | 22 +- .../Properties/Settings.Designer.cs | 13 +- .../Properties/Settings.settings | 5 +- .../Should/FileSystemShouldExtensions.cs | 63 +- .../BasicFileSystemTests.cs | 18 + .../EnlistmentPerFixture/CacheServerTests.cs | 1 + .../Tests/EnlistmentPerFixture/CloneTests.cs | 14 +- .../EnlistmentPerFixture/DehydrateTests.cs | 33 +- .../EnlistmentPerFixture/DiagnoseTests.cs | 1 + .../GitCorruptObjectTests.cs | 5 +- .../EnlistmentPerFixture/GitFilesTests.cs | 39 +- .../GitMoveRenameTests.cs | 5 +- .../GitReadAndGitLockTests.cs | 28 +- .../Tests/EnlistmentPerFixture/MountTests.cs | 64 +- .../EnlistmentPerFixture/PrefetchVerbTests.cs | 21 +- .../PrefetchVerbWithoutSharedCacheTests.cs | 17 + .../EnlistmentPerFixture/ServiceTests.cs | 174 +++ .../TestsWithEnlistmentPerFixture.cs | 5 +- .../EnlistmentPerFixture/UnmountTests.cs | 6 +- .../UpdatePlaceholderTests.cs | 9 +- .../WorkingDirectoryTests.cs | 7 +- .../DiskLayoutUpgradeTests.cs | 218 ++- .../PersistedSparseExcludeTests.cs | 4 +- .../PersistedWorkingDirectoryTests.cs | 1 + .../EnlistmentPerTestCase/RepairTests.cs | 17 +- .../TestsWithEnlistmentPerTestCase.cs | 3 +- .../Tests/FastFetchTests.cs | 20 +- .../Tests/GVFSVerbTests.cs | 5 +- .../Tests/GitCommands/AddStageTests.cs | 5 +- .../Tests/GitCommands/CheckoutTests.cs | 198 ++- .../GitCommands/CherryPickConflictTests.cs | 5 +- .../GitCommands/DeleteEmptyFolderTests.cs | 15 +- .../Tests/GitCommands/EnumerationMergeTest.cs | 5 +- .../Tests/GitCommands/GitCommandsTests.cs | 30 +- .../Tests/GitCommands/GitRepoTests.cs | 52 +- .../Tests/GitCommands/MergeConflictTests.cs | 5 +- .../Tests/GitCommands/RebaseConflictTests.cs | 5 +- .../Tests/GitCommands/RebaseTests.cs | 87 +- .../Tests/GitCommands/ResetHardTests.cs | 76 + .../Tests/GitCommands/ResetMixedTests.cs | 11 +- .../Tests/GitCommands/ResetSoftTests.cs | 5 +- .../Tests/GitCommands/UpdateIndexTests.cs | 5 +- .../MultiEnlistmentTests/ServiceVerbTests.cs | 9 +- .../MultiEnlistmentTests/SharedCacheTests.cs | 149 +- .../TestsWithMultiEnlistment.cs | 13 +- .../Tests/PrintTestCaseStats.cs | 46 +- .../Tools/ControlGitRepo.cs | 3 +- .../Tools/GVFSFunctionalTestEnlistment.cs | 12 + .../GVFS.FunctionalTests/Tools/GVFSHelpers.cs | 98 +- .../GVFS.FunctionalTests/Tools/GVFSProcess.cs | 14 +- .../Tools/GVFSServiceProcess.cs | 4 +- .../Tools/ProcessHelper.cs | 13 + .../Tools/TestConstants.cs | 2 + GVFS/GVFS.FunctionalTests/app.config | 11 +- GVFS/GVFS.FunctionalTests/packages.config | 20 +- GVFS/GVFS.GVFlt/BlobSize/BlobSizes.cs | 498 +++++++ .../GVFS.GVFlt/BlobSize/BlobSizesException.cs | 12 + GVFS/GVFS.GVFlt/DotGit/AlwaysExcludeFile.cs | 29 +- GVFS/GVFS.GVFlt/DotGit/GitIndexProjection.cs | 1000 +++++++++---- GVFS/GVFS.GVFlt/GVFS.GVFlt.csproj | 53 +- GVFS/GVFS.GVFlt/GVFltActiveEnumeration.cs | 28 +- GVFS/GVFS.GVFlt/GVFltCallbacks.cs | 535 +++---- GVFS/GVFS.GVFlt/HResultExtensions.cs | 25 + GVFS/GVFS.GVFlt/PathUtil.cs | 10 - GVFS/GVFS.GVFlt/PatternMatcher.cs | 14 +- GVFS/GVFS.GVFlt/packages.config | 28 +- GVFS/GVFS.GvFltWrapper/AssemblyInfo.cpp | 25 - GVFS/GVFS.GvFltWrapper/CallbackDelegates.h | 384 ----- .../DirectoryEnumerationFileNamesResult.h | 64 - .../DirectoryEnumerationResult.h | 63 - .../DirectoryEnumerationResultImpl.h | 105 -- GVFS/GVFS.GvFltWrapper/GvFlt.props | 14 - GVFS/GVFS.GvFltWrapper/GvLib.vcxproj | 167 --- GVFS/GVFS.GvFltWrapper/GvLib.vcxproj.filters | 113 -- GVFS/GVFS.GvFltWrapper/GvLibException.cpp | 46 - GVFS/GVFS.GvFltWrapper/GvLibException.h | 37 - GVFS/GVFS.GvFltWrapper/HResult.h | 22 - .../IVirtualizationInstance.h | 709 --------- GVFS/GVFS.GvFltWrapper/IoStatusBlockValue.h | 14 - .../NativeEnumerationResultUtils.h | 40 - GVFS/GVFS.GvFltWrapper/NotificationMapping.h | 88 -- GVFS/GVFS.GvFltWrapper/NotificationType.h | 58 - GVFS/GVFS.GvFltWrapper/NtStatus.h | 52 - .../Scripts/CreateCliAssemblyVersion.bat | 4 - .../Scripts/CreateVersionHeader.bat | 10 - GVFS/GVFS.GvFltWrapper/Stdafx.cpp | 5 - GVFS/GVFS.GvFltWrapper/Stdafx.h | 256 ---- GVFS/GVFS.GvFltWrapper/UpdateFailureCause.h | 53 - GVFS/GVFS.GvFltWrapper/UpdateType.h | 45 - GVFS/GVFS.GvFltWrapper/Utils.cpp | 54 - GVFS/GVFS.GvFltWrapper/Utils.h | 16 - GVFS/GVFS.GvFltWrapper/Version.rc | Bin 4752 -> 0 bytes .../VirtualizationInstance.cpp | 1311 ----------------- .../VirtualizationInstance.h | 759 ---------- GVFS/GVFS.GvFltWrapper/WriteBuffer.cpp | 44 - GVFS/GVFS.GvFltWrapper/WriteBuffer.h | 33 - GVFS/GVFS.GvFltWrapper/packages.config | 4 - GVFS/GVFS.GvFltWrapper/resource.h | 14 - GVFS/GVFS.Hooks/App.config | 6 +- GVFS/GVFS.Hooks/GVFS.Hooks.csproj | 4 +- GVFS/GVFS.Hooks/Program.cs | 75 +- GVFS/GVFS.Hooks/packages.config | 10 +- GVFS/GVFS.Installer/GVFS.Installer.csproj | 33 +- GVFS/GVFS.Installer/Setup.iss | 218 ++- GVFS/GVFS.Installer/packages.config | 1 + GVFS/GVFS.Mount/GVFS.Mount.csproj | 26 +- GVFS/GVFS.Mount/InProcessMount.cs | 59 +- GVFS/GVFS.Mount/app.config | 6 + GVFS/GVFS.Mount/packages.config | 22 +- .../GVFS.NativeTests/GVFS.NativeTests.vcxproj | 14 +- .../GVFS.NativeTests.vcxproj.filters | 6 +- GVFS/GVFS.NativeTests/include/TestHelpers.h | 67 +- GVFS/GVFS.NativeTests/include/gvflt.h | 7 +- .../GVFS.NativeTests/include/gvlib_internal.h | 28 - .../include/prjlib_internal.h | 28 + .../interface/GVFlt_DirEnumTest.h | 2 + .../source/GVFlt_DirEnumTest.cpp | 105 ++ .../source/PlaceholderUtils.cpp | 2 +- .../source/TrailingSlashTests.cpp | 62 +- GVFS/GVFS.PerfProfiling/App.config | 6 +- .../GVFS.PerfProfiling.csproj | 32 +- .../ProfilingEnvironment.cs | 20 + GVFS/GVFS.PerfProfiling/packages.config | 11 + GVFS/GVFS.Service.UI/GVFS.Service.UI.csproj | 7 +- GVFS/GVFS.Service.UI/app.config | 6 + GVFS/GVFS.Service.UI/packages.config | 10 +- GVFS/GVFS.Service/GVFS.Service.csproj | 30 +- GVFS/GVFS.Service/GVFSMountProcess.cs | 17 +- GVFS/GVFS.Service/GvfsService.cs | 16 +- .../Handlers/AttachGvFltHandler.cs | 41 - .../Handlers/EnableAndAttachProjFSHandler.cs | 145 ++ GVFS/GVFS.Service/app.config | 6 + GVFS/GVFS.Service/packages.config | 15 +- GVFS/GVFS.SignFiles/GVFS.SignFiles.csproj | 70 + GVFS/GVFS.SignFiles/packages.config | 4 + GVFS/GVFS.Tests/GVFS.Tests.csproj | 2 +- GVFS/GVFS.Tests/NUnitRunner.cs | 25 +- GVFS/GVFS.Tests/app.config | 7 +- GVFS/GVFS.Tests/packages.config | 8 +- GVFS/GVFS.UnitTests/App.config | 6 +- .../Common/CacheServerResolverTests.cs | 35 + .../Common/GitCommandLineParserTests.cs | 15 - GVFS/GVFS.UnitTests/GVFS.UnitTests.csproj | 28 +- .../GVFlt/DotGit/AlwaysExcludeFileTests.cs | 43 +- .../GVFlt/GVFltActiveEnumerationTests.cs | 55 +- .../GVFlt/GVFltCallbacksTests.cs | 111 +- GVFS/GVFS.UnitTests/GVFlt/PathUtilTests.cs | 15 - .../GVFlt/PatternMatcherTests.cs | 134 +- .../GVFS.UnitTests/Git/GVFSGitObjectsTests.cs | 2 +- .../Mock/Common/MockEnlistment.cs | 5 +- GVFS/GVFS.UnitTests/Mock/Common/MockTracer.cs | 3 +- .../Mock/FileSystem/MockFileSystem.cs | 10 + .../FileSystem/MockFileSystemWithCallbacks.cs | 10 + .../DotGit/MockGitIndexProjection.cs | 13 +- .../GvFlt/BlobSize/MockBlobSizesDatabase.cs | 51 + .../Mock/GvFlt/MockVirtualizationInstance.cs | 39 +- GVFS/GVFS.UnitTests/Program.cs | 2 +- .../GVFS.UnitTests/Virtual/CommonRepoSetup.cs | 11 +- GVFS/GVFS.UnitTests/packages.config | 24 +- GVFS/GVFS/App.config | 6 +- GVFS/GVFS/CommandLine/CloneVerb.cs | 39 +- GVFS/GVFS/CommandLine/DehydrateVerb.cs | 2 +- GVFS/GVFS/CommandLine/DiagnoseVerb.cs | 201 ++- .../DiskLayoutUpgrades/DiskLayoutUpgrade.cs | 286 ---- GVFS/GVFS/CommandLine/GVFSVerb.cs | 232 ++- GVFS/GVFS/CommandLine/HooksInstaller.cs | 6 +- GVFS/GVFS/CommandLine/MountVerb.cs | 108 +- GVFS/GVFS/CommandLine/PrefetchVerb.cs | 97 +- .../RepairJobs/BlobSizeDatabaseRepairJob.cs | 48 - GVFS/GVFS/CommandLine/RepairVerb.cs | 6 +- GVFS/GVFS/CommandLine/ServiceVerb.cs | 10 +- GVFS/GVFS/CommandLine/UnmountVerb.cs | 7 +- .../DiskLayout10to11Upgrade.cs | 8 +- .../DiskLayout11to12Upgrade.cs | 8 +- .../DiskLayout12_0To12_1Upgrade.cs | 36 + ...skLayout12to13Upgrade_FolderPlaceholder.cs | 120 ++ .../DiskLayout13to14Upgrade_BlobSizes.cs | 128 ++ .../DiskLayout7to8Upgrade.cs | 6 +- .../DiskLayout8to9Upgrade.cs | 8 +- .../DiskLayout9to10Upgrade.cs | 8 +- .../DiskLayoutUpgrades/DiskLayoutUpgrade.cs | 422 ++++++ GVFS/GVFS/GVFS.csproj | 66 +- .../BackgroundOperationDatabaseRepairJob.cs | 2 +- .../RepairJobs/BlobSizeDatabaseRepairJob.cs | 65 + .../RepairJobs/GitConfigRepairJob.cs | 9 +- .../RepairJobs/GitHeadRepairJob.cs | 2 +- .../RepairJobs/GitIndexRepairJob.cs | 2 +- .../PlaceholderDatabaseRepairJob.cs | 2 +- .../{CommandLine => }/RepairJobs/RepairJob.cs | 2 +- .../RepoMetadataDatabaseRepairJob.cs | 2 +- GVFS/GVFS/packages.config | 31 +- GVFS/GvLib.NativeBinaries.props | 12 - GVFS/ProjectedFSLib.NativeBinaries.props | 11 + Readme.md | 2 +- 249 files changed, 7287 insertions(+), 7377 deletions(-) create mode 100644 GVFS/GVFS.Build/GenerateApplicationManifests.cs create mode 100644 GVFS/GVFS.Build/ProjFS.props delete mode 100644 GVFS/GVFS.Common/FileSystem/GvFltFilter.cs create mode 100644 GVFS/GVFS.Common/FileSystem/ProjFSFilter.cs create mode 100644 GVFS/GVFS.Common/Git/Sha1Id.cs create mode 100644 GVFS/GVFS.FunctionalTests/Categories.cs delete mode 100644 GVFS/GVFS.FunctionalTests/FileSystemRunners/Category/CategoryConstants.cs create mode 100644 GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/ServiceTests.cs create mode 100644 GVFS/GVFS.FunctionalTests/Tests/GitCommands/ResetHardTests.cs create mode 100644 GVFS/GVFS.GVFlt/BlobSize/BlobSizes.cs create mode 100644 GVFS/GVFS.GVFlt/BlobSize/BlobSizesException.cs create mode 100644 GVFS/GVFS.GVFlt/HResultExtensions.cs delete mode 100644 GVFS/GVFS.GvFltWrapper/AssemblyInfo.cpp delete mode 100644 GVFS/GVFS.GvFltWrapper/CallbackDelegates.h delete mode 100644 GVFS/GVFS.GvFltWrapper/DirectoryEnumerationFileNamesResult.h delete mode 100644 GVFS/GVFS.GvFltWrapper/DirectoryEnumerationResult.h delete mode 100644 GVFS/GVFS.GvFltWrapper/DirectoryEnumerationResultImpl.h delete mode 100644 GVFS/GVFS.GvFltWrapper/GvFlt.props delete mode 100644 GVFS/GVFS.GvFltWrapper/GvLib.vcxproj delete mode 100644 GVFS/GVFS.GvFltWrapper/GvLib.vcxproj.filters delete mode 100644 GVFS/GVFS.GvFltWrapper/GvLibException.cpp delete mode 100644 GVFS/GVFS.GvFltWrapper/GvLibException.h delete mode 100644 GVFS/GVFS.GvFltWrapper/HResult.h delete mode 100644 GVFS/GVFS.GvFltWrapper/IVirtualizationInstance.h delete mode 100644 GVFS/GVFS.GvFltWrapper/IoStatusBlockValue.h delete mode 100644 GVFS/GVFS.GvFltWrapper/NativeEnumerationResultUtils.h delete mode 100644 GVFS/GVFS.GvFltWrapper/NotificationMapping.h delete mode 100644 GVFS/GVFS.GvFltWrapper/NotificationType.h delete mode 100644 GVFS/GVFS.GvFltWrapper/NtStatus.h delete mode 100644 GVFS/GVFS.GvFltWrapper/Scripts/CreateCliAssemblyVersion.bat delete mode 100644 GVFS/GVFS.GvFltWrapper/Scripts/CreateVersionHeader.bat delete mode 100644 GVFS/GVFS.GvFltWrapper/Stdafx.cpp delete mode 100644 GVFS/GVFS.GvFltWrapper/Stdafx.h delete mode 100644 GVFS/GVFS.GvFltWrapper/UpdateFailureCause.h delete mode 100644 GVFS/GVFS.GvFltWrapper/UpdateType.h delete mode 100644 GVFS/GVFS.GvFltWrapper/Utils.cpp delete mode 100644 GVFS/GVFS.GvFltWrapper/Utils.h delete mode 100644 GVFS/GVFS.GvFltWrapper/Version.rc delete mode 100644 GVFS/GVFS.GvFltWrapper/VirtualizationInstance.cpp delete mode 100644 GVFS/GVFS.GvFltWrapper/VirtualizationInstance.h delete mode 100644 GVFS/GVFS.GvFltWrapper/WriteBuffer.cpp delete mode 100644 GVFS/GVFS.GvFltWrapper/WriteBuffer.h delete mode 100644 GVFS/GVFS.GvFltWrapper/packages.config delete mode 100644 GVFS/GVFS.GvFltWrapper/resource.h create mode 100644 GVFS/GVFS.Mount/app.config delete mode 100644 GVFS/GVFS.NativeTests/include/gvlib_internal.h create mode 100644 GVFS/GVFS.NativeTests/include/prjlib_internal.h create mode 100644 GVFS/GVFS.PerfProfiling/packages.config create mode 100644 GVFS/GVFS.Service.UI/app.config delete mode 100644 GVFS/GVFS.Service/Handlers/AttachGvFltHandler.cs create mode 100644 GVFS/GVFS.Service/Handlers/EnableAndAttachProjFSHandler.cs create mode 100644 GVFS/GVFS.Service/app.config create mode 100644 GVFS/GVFS.SignFiles/GVFS.SignFiles.csproj create mode 100644 GVFS/GVFS.SignFiles/packages.config create mode 100644 GVFS/GVFS.UnitTests/Mock/GvFlt/BlobSize/MockBlobSizesDatabase.cs delete mode 100644 GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayoutUpgrade.cs delete mode 100644 GVFS/GVFS/CommandLine/RepairJobs/BlobSizeDatabaseRepairJob.cs rename GVFS/GVFS/{CommandLine => }/DiskLayoutUpgrades/DiskLayout10to11Upgrade.cs (64%) rename GVFS/GVFS/{CommandLine => }/DiskLayoutUpgrades/DiskLayout11to12Upgrade.cs (86%) create mode 100644 GVFS/GVFS/DiskLayoutUpgrades/DiskLayout12_0To12_1Upgrade.cs create mode 100644 GVFS/GVFS/DiskLayoutUpgrades/DiskLayout12to13Upgrade_FolderPlaceholder.cs create mode 100644 GVFS/GVFS/DiskLayoutUpgrades/DiskLayout13to14Upgrade_BlobSizes.cs rename GVFS/GVFS/{CommandLine => }/DiskLayoutUpgrades/DiskLayout7to8Upgrade.cs (87%) rename GVFS/GVFS/{CommandLine => }/DiskLayoutUpgrades/DiskLayout8to9Upgrade.cs (90%) rename GVFS/GVFS/{CommandLine => }/DiskLayoutUpgrades/DiskLayout9to10Upgrade.cs (94%) create mode 100644 GVFS/GVFS/DiskLayoutUpgrades/DiskLayoutUpgrade.cs rename GVFS/GVFS/{CommandLine => }/RepairJobs/BackgroundOperationDatabaseRepairJob.cs (94%) create mode 100644 GVFS/GVFS/RepairJobs/BlobSizeDatabaseRepairJob.cs rename GVFS/GVFS/{CommandLine => }/RepairJobs/GitConfigRepairJob.cs (93%) rename GVFS/GVFS/{CommandLine => }/RepairJobs/GitHeadRepairJob.cs (96%) rename GVFS/GVFS/{CommandLine => }/RepairJobs/GitIndexRepairJob.cs (96%) rename GVFS/GVFS/{CommandLine => }/RepairJobs/PlaceholderDatabaseRepairJob.cs (93%) rename GVFS/GVFS/{CommandLine => }/RepairJobs/RepairJob.cs (96%) rename GVFS/GVFS/{CommandLine => }/RepairJobs/RepoMetadataDatabaseRepairJob.cs (93%) delete mode 100644 GVFS/GvLib.NativeBinaries.props create mode 100644 GVFS/ProjectedFSLib.NativeBinaries.props diff --git a/GVFS.sln b/GVFS.sln index 5fbd9609..94781012 100644 --- a/GVFS.sln +++ b/GVFS.sln @@ -16,18 +16,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GVFS", "GVFS", "{2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C}" ProjectSection(SolutionItems) = preProject - GVFS\GvLib.NativeBinaries.props = GVFS\GvLib.NativeBinaries.props GVFS\LibGit2Sharp.NativeBinaries.props = GVFS\LibGit2Sharp.NativeBinaries.props + GVFS\ProjectedFSLib.NativeBinaries.props = GVFS\ProjectedFSLib.NativeBinaries.props EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GVFS.GVFlt", "GVFS\GVFS.GVFlt\GVFS.GVFlt.csproj", "{1118B427-7063-422F-83B9-5023C8EC5A7A}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GvLib.Managed", "GVFS\GVFS.GvFltWrapper\GvLib.vcxproj", "{FB0831AE-9997-401B-B31F-3A065FDBEB20}" - ProjectSection(ProjectDependencies) = postProject - {5A6656D5-81C7-472C-9DC8-32D071CB2258} = {5A6656D5-81C7-472C-9DC8-32D071CB2258} - {374BF1E5-0B2D-4D4A-BD5E-4212299DEF09} = {374BF1E5-0B2D-4D4A-BD5E-4212299DEF09} - EndProjectSection -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GVFS.Common", "GVFS\GVFS.Common\GVFS.Common.csproj", "{374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}" ProjectSection(ProjectDependencies) = postProject {A4984251-840E-4622-AD0C-66DFCE2B2574} = {A4984251-840E-4622-AD0C-66DFCE2B2574} @@ -107,9 +101,24 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GitHooksLoader", "GitHooksL EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GVFS.Installer", "GVFS\GVFS.Installer\GVFS.Installer.csproj", "{3AB4FB1F-9E23-4CD8-BFAC-8A2221C8F893}" ProjectSection(ProjectDependencies) = postProject + {2F63B22B-EE26-4266-BF17-28A9146483A1} = {2F63B22B-EE26-4266-BF17-28A9146483A1} {32220664-594C-4425-B9A0-88E0BE2F3D2A} = {32220664-594C-4425-B9A0-88E0BE2F3D2A} EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GVFS.SignFiles", "GVFS\GVFS.SignFiles\GVFS.SignFiles.csproj", "{2F63B22B-EE26-4266-BF17-28A9146483A1}" + ProjectSection(ProjectDependencies) = postProject + {17498502-AEFF-4E70-90CC-1D0B56A8ADF5} = {17498502-AEFF-4E70-90CC-1D0B56A8ADF5} + {07F2A520-2AB7-46DD-97C0-75D8E988D55B} = {07F2A520-2AB7-46DD-97C0-75D8E988D55B} + {1118B427-7063-422F-83B9-5023C8EC5A7A} = {1118B427-7063-422F-83B9-5023C8EC5A7A} + {32220664-594C-4425-B9A0-88E0BE2F3D2A} = {32220664-594C-4425-B9A0-88E0BE2F3D2A} + {798DE293-6EDA-4DC4-9395-BE7A71C563E3} = {798DE293-6EDA-4DC4-9395-BE7A71C563E3} + {B8C1DFBA-CAFD-4F7E-A1A3-E11907B5467B} = {B8C1DFBA-CAFD-4F7E-A1A3-E11907B5467B} + {5A6656D5-81C7-472C-9DC8-32D071CB2258} = {5A6656D5-81C7-472C-9DC8-32D071CB2258} + {BDA91EE5-C684-4FC5-A90A-B7D677421917} = {BDA91EE5-C684-4FC5-A90A-B7D677421917} + {374BF1E5-0B2D-4D4A-BD5E-4212299DEF09} = {374BF1E5-0B2D-4D4A-BD5E-4212299DEF09} + {93B403FD-DAFB-46C5-9636-B122792A548A} = {93B403FD-DAFB-46C5-9636-B122792A548A} + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -120,10 +129,6 @@ Global {1118B427-7063-422F-83B9-5023C8EC5A7A}.Debug|x64.Build.0 = Debug|x64 {1118B427-7063-422F-83B9-5023C8EC5A7A}.Release|x64.ActiveCfg = Release|x64 {1118B427-7063-422F-83B9-5023C8EC5A7A}.Release|x64.Build.0 = Release|x64 - {FB0831AE-9997-401B-B31F-3A065FDBEB20}.Debug|x64.ActiveCfg = Debug|x64 - {FB0831AE-9997-401B-B31F-3A065FDBEB20}.Debug|x64.Build.0 = Debug|x64 - {FB0831AE-9997-401B-B31F-3A065FDBEB20}.Release|x64.ActiveCfg = Release|x64 - {FB0831AE-9997-401B-B31F-3A065FDBEB20}.Release|x64.Build.0 = Release|x64 {374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Debug|x64.ActiveCfg = Debug|x64 {374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Debug|x64.Build.0 = Debug|x64 {374BF1E5-0B2D-4D4A-BD5E-4212299DEF09}.Release|x64.ActiveCfg = Release|x64 @@ -188,13 +193,16 @@ Global {3AB4FB1F-9E23-4CD8-BFAC-8A2221C8F893}.Debug|x64.Build.0 = Debug|x64 {3AB4FB1F-9E23-4CD8-BFAC-8A2221C8F893}.Release|x64.ActiveCfg = Release|x64 {3AB4FB1F-9E23-4CD8-BFAC-8A2221C8F893}.Release|x64.Build.0 = Release|x64 + {2F63B22B-EE26-4266-BF17-28A9146483A1}.Debug|x64.ActiveCfg = Debug|x64 + {2F63B22B-EE26-4266-BF17-28A9146483A1}.Debug|x64.Build.0 = Debug|x64 + {2F63B22B-EE26-4266-BF17-28A9146483A1}.Release|x64.ActiveCfg = Release|x64 + {2F63B22B-EE26-4266-BF17-28A9146483A1}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {1118B427-7063-422F-83B9-5023C8EC5A7A} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C} - {FB0831AE-9997-401B-B31F-3A065FDBEB20} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C} {374BF1E5-0B2D-4D4A-BD5E-4212299DEF09} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C} {32220664-594C-4425-B9A0-88E0BE2F3D2A} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C} {07F2A520-2AB7-46DD-97C0-75D8E988D55B} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C} @@ -211,5 +219,9 @@ Global {93B403FD-DAFB-46C5-9636-B122792A548A} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C} {A4984251-840E-4622-AD0C-66DFCE2B2574} = {AB0D9230-3893-4486-8899-F9C871FB5D5F} {3AB4FB1F-9E23-4CD8-BFAC-8A2221C8F893} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C} + {2F63B22B-EE26-4266-BF17-28A9146483A1} = {2EF2EC94-3A68-4ED7-9A58-B7057ADBA01C} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {24FCF9D2-C868-48AB-A1E8-6D4D8E75F7D5} EndGlobalSection EndGlobal diff --git a/GVFS/FastFetch/App.config b/GVFS/FastFetch/App.config index d740e886..6d442563 100644 --- a/GVFS/FastFetch/App.config +++ b/GVFS/FastFetch/App.config @@ -1,6 +1,6 @@ - + - + - \ No newline at end of file + diff --git a/GVFS/FastFetch/FastFetch.csproj b/GVFS/FastFetch/FastFetch.csproj index 99640cea..42b7e11d 100644 --- a/GVFS/FastFetch/FastFetch.csproj +++ b/GVFS/FastFetch/FastFetch.csproj @@ -9,7 +9,7 @@ Properties FastFetch FastFetch - v4.5.2 + v4.6.1 512 true @@ -43,8 +43,8 @@ ..\..\..\packages\CommandLineParser.2.1.1-beta\lib\net45\CommandLine.dll True - - ..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll + + ..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net46\Microsoft.Diagnostics.Tracing.EventSource.dll True diff --git a/GVFS/FastFetch/GitEnlistment.cs b/GVFS/FastFetch/GitEnlistment.cs index 6de0d5df..97b527dc 100644 --- a/GVFS/FastFetch/GitEnlistment.cs +++ b/GVFS/FastFetch/GitEnlistment.cs @@ -16,11 +16,14 @@ namespace FastFetch flushFileBuffersForPacks: false) { this.GitObjectsRoot = Path.Combine(repoRoot, GVFSConstants.DotGit.Objects.Root); + this.LocalObjectsRoot = this.GitObjectsRoot; this.GitPackRoot = Path.Combine(this.GitObjectsRoot, GVFSConstants.DotGit.Objects.Pack.Name); } public override string GitObjectsRoot { get; protected set; } + public override string LocalObjectsRoot { get; protected set; } + public override string GitPackRoot { get; protected set; } public string FastFetchLogRoot diff --git a/GVFS/FastFetch/Jobs/BatchObjectDownloadJob.cs b/GVFS/FastFetch/Jobs/BatchObjectDownloadJob.cs index 3e2cfcd0..51e73d73 100644 --- a/GVFS/FastFetch/Jobs/BatchObjectDownloadJob.cs +++ b/GVFS/FastFetch/Jobs/BatchObjectDownloadJob.cs @@ -78,7 +78,7 @@ namespace FastFetch.Jobs Interlocked.Increment(ref this.activeDownloadCount); EventMetadata metadata = new EventMetadata(); - metadata.Add("PackId", request.PackId); + metadata.Add("RequestId", request.RequestId); metadata.Add("ActiveDownloads", this.activeDownloadCount); metadata.Add("NumberOfObjects", request.ObjectIds.Count); @@ -90,7 +90,7 @@ namespace FastFetch.Jobs RetryWrapper.InvocationResult result = this.objectRequestor.TryDownloadObjects( () => request.ObjectIds.Except(successfulDownloads), onSuccess: (tryCount, response) => this.WriteObjectOrPack(request, tryCount, response, successfulDownloads), - onFailure: RetryWrapper.StandardErrorHandler(activity, request.PackId, DownloadAreaPath), + onFailure: RetryWrapper.StandardErrorHandler(activity, request.RequestId, DownloadAreaPath), preferBatchedLooseObjects: true); if (!result.Succeeded) diff --git a/GVFS/FastFetch/Jobs/Data/BlobDownloadRequest.cs b/GVFS/FastFetch/Jobs/Data/BlobDownloadRequest.cs index 85c705df..2793cbd0 100644 --- a/GVFS/FastFetch/Jobs/Data/BlobDownloadRequest.cs +++ b/GVFS/FastFetch/Jobs/Data/BlobDownloadRequest.cs @@ -10,7 +10,7 @@ namespace FastFetch.Jobs.Data public BlobDownloadRequest(IReadOnlyList objectIds) { this.ObjectIds = objectIds; - this.PackId = Interlocked.Increment(ref requestCounter); + this.RequestId = Interlocked.Increment(ref requestCounter); } public static int TotalRequests @@ -23,6 +23,6 @@ namespace FastFetch.Jobs.Data public IReadOnlyList ObjectIds { get; } - public int PackId { get; } + public int RequestId { get; } } } diff --git a/GVFS/FastFetch/Jobs/IndexPackJob.cs b/GVFS/FastFetch/Jobs/IndexPackJob.cs index da754ea4..3be707c1 100644 --- a/GVFS/FastFetch/Jobs/IndexPackJob.cs +++ b/GVFS/FastFetch/Jobs/IndexPackJob.cs @@ -41,14 +41,14 @@ namespace FastFetch.Jobs while (this.availablePacks.TryTake(out request, Timeout.Infinite)) { EventMetadata metadata = new EventMetadata(); - metadata.Add("PackId", request.DownloadRequest.PackId); + metadata.Add("RequestId", request.DownloadRequest.RequestId); using (ITracer activity = this.tracer.StartActivity(IndexPackAreaPath, EventLevel.Informational, Keywords.Telemetry, metadata)) { GitProcess.Result result = this.gitObjects.IndexTempPackFile(request.TempPackFile); if (result.HasErrors) { EventMetadata errorMetadata = new EventMetadata(); - errorMetadata.Add("PackId", request.DownloadRequest.PackId); + errorMetadata.Add("RequestId", request.DownloadRequest.RequestId); activity.RelatedError(errorMetadata, result.Errors); this.HasFailures = true; } diff --git a/GVFS/FastFetch/packages.config b/GVFS/FastFetch/packages.config index 919f2a89..7dd2a17e 100644 --- a/GVFS/FastFetch/packages.config +++ b/GVFS/FastFetch/packages.config @@ -1,10 +1,10 @@  - - - - - - - + + + + + + + \ No newline at end of file diff --git a/GVFS/GVFS.Build/GVFS.PreBuild.csproj b/GVFS/GVFS.Build/GVFS.PreBuild.csproj index 8763725d..b276e9d6 100644 --- a/GVFS/GVFS.Build/GVFS.PreBuild.csproj +++ b/GVFS/GVFS.Build/GVFS.PreBuild.csproj @@ -8,7 +8,7 @@ Properties GVFS.PreBuild GVFS.PreBuild - v4.5.2 + v4.6.1 512 @@ -32,12 +32,16 @@ true + Designer + + Designer + @@ -51,6 +55,15 @@ + + + + + + + + + @@ -58,10 +71,11 @@ - - + + + \ No newline at end of file diff --git a/GVFS/GVFS.Build/GVFS.props b/GVFS/GVFS.Build/GVFS.props index da249edb..20fc3784 100644 --- a/GVFS/GVFS.Build/GVFS.props +++ b/GVFS/GVFS.Build/GVFS.props @@ -1,9 +1,7 @@  - 1.0.18026.1 - 2.4882590 - true + 1.0.18115.1 Debug @@ -15,4 +13,4 @@ $(BuildOutputDir)\$(MSBuildProjectName)\bin\$(Platform)\$(Configuration)\ $(BuildOutputDir)\$(MSBuildProjectName)\intermediate\$(Platform)\$(Configuration)\ - \ No newline at end of file + diff --git a/GVFS/GVFS.Build/GenerateApplicationManifests.cs b/GVFS/GVFS.Build/GenerateApplicationManifests.cs new file mode 100644 index 00000000..6e387cd4 --- /dev/null +++ b/GVFS/GVFS.Build/GenerateApplicationManifests.cs @@ -0,0 +1,54 @@ +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using System.IO; + +namespace GVFS.PreBuild +{ + public class GenerateApplicationManifests : Task + { + [Required] + public string Version { get; set; } + + [Required] + public string BuildOutputPath { get; set; } + + public override bool Execute() + { + this.Log.LogMessage(MessageImportance.High, "Creating application manifest files"); + + if (!Directory.Exists(this.BuildOutputPath)) + { + Directory.CreateDirectory(this.BuildOutputPath); + } + + string[] applicationNames = + { + "GVFS.FunctionalTests", + "GVFS.Service", + }; + + foreach (string applicationName in applicationNames) + { + File.WriteAllText( + Path.Combine(this.BuildOutputPath, applicationName + ".exe.manifest"), + string.Format( +@" + + + + + + + + + +", + this.Version, + applicationName)); + } + + return true; + } + } +} + diff --git a/GVFS/GVFS.Build/ProjFS.props b/GVFS/GVFS.Build/ProjFS.props new file mode 100644 index 00000000..a2479a22 --- /dev/null +++ b/GVFS/GVFS.Build/ProjFS.props @@ -0,0 +1,6 @@ + + + + Microsoft.GVFS.GvFlt.0.180425.1-preview + + diff --git a/GVFS/GVFS.Common/Enlistment.cs b/GVFS/GVFS.Common/Enlistment.cs index b8f9f170..7b10354c 100644 --- a/GVFS/GVFS.Common/Enlistment.cs +++ b/GVFS/GVFS.Common/Enlistment.cs @@ -5,10 +5,7 @@ using System.IO; namespace GVFS.Common { public abstract class Enlistment - { - private const string DeprecatedObjectsEndpointGitConfigName = "gvfs.objects-endpoint"; - private const string CacheEndpointGitConfigSuffix = ".cache-server-url"; - + { protected Enlistment( string enlistmentRoot, string workingDirectoryRoot, @@ -56,6 +53,7 @@ namespace GVFS.Common public string WorkingDirectoryRoot { get; } public string DotGitRoot { get; private set; } public abstract string GitObjectsRoot { get; protected set; } + public abstract string LocalObjectsRoot { get; protected set; } public abstract string GitPackRoot { get; protected set; } public string RepoUrl { get; } public bool FlushFileBuffersForPacks { get; } diff --git a/GVFS/GVFS.Common/FileSystem/GvFltFilter.cs b/GVFS/GVFS.Common/FileSystem/GvFltFilter.cs deleted file mode 100644 index d879652d..00000000 --- a/GVFS/GVFS.Common/FileSystem/GvFltFilter.cs +++ /dev/null @@ -1,117 +0,0 @@ -using GVFS.Common.Tracing; -using Microsoft.Win32; -using System; -using System.Runtime.InteropServices; -using System.Security; -using System.ServiceProcess; -using System.Text; - -namespace GVFS.Common.FileSystem -{ - public class GvFltFilter - { - public const RegistryHive GvFltParametersHive = RegistryHive.LocalMachine; - public const string GvFltParametersKey = "SYSTEM\\CurrentControlSet\\Services\\Gvflt\\Parameters"; - public const string GvFltTimeoutValue = "CommandTimeoutInMs"; - private const string EtwArea = nameof(GvFltFilter); - - private const string GvFltName = "gvflt"; - - private const uint OkResult = 0; - private const uint NameCollisionErrorResult = 0x801F0012; - - public static bool TryAttach(ITracer tracer, string root, out string errorMessage) - { - errorMessage = null; - try - { - StringBuilder volumePathName = new StringBuilder(GVFSConstants.MaxPath); - if (!NativeMethods.GetVolumePathName(root, volumePathName, GVFSConstants.MaxPath)) - { - errorMessage = "Could not get volume path name"; - tracer.RelatedError(errorMessage); - return false; - } - - uint result = NativeMethods.FilterAttach(GvFltName, volumePathName.ToString(), null); - if (result != OkResult && result != NameCollisionErrorResult) - { - errorMessage = string.Format("Attaching the filter driver resulted in: {0}", result); - tracer.RelatedError(errorMessage); - return false; - } - } - catch (Exception e) - { - errorMessage = string.Format("Attaching the filter driver resulted in: {0}", e.Message); - tracer.RelatedError(errorMessage); - return false; - } - - return true; - } - - public static bool IsHealthy(out string error, ITracer tracer) - { - return IsServiceRunning(out error, tracer); - } - - private static bool IsServiceRunning(out string error, ITracer tracer) - { - error = string.Empty; - - bool gvfltServiceRunning = false; - try - { - ServiceController controller = new ServiceController("gvflt"); - gvfltServiceRunning = controller.Status.Equals(ServiceControllerStatus.Running); - } - catch (InvalidOperationException e) - { - if (tracer != null) - { - EventMetadata metadata = new EventMetadata(); - metadata.Add("Area", EtwArea); - metadata.Add("Exception", e.ToString()); - tracer.RelatedError(metadata, "InvalidOperationException: GvFlt Service was not found"); - } - - error = "Error: GvFlt Service was not found. To resolve, re-install GVFS"; - return false; - } - - if (!gvfltServiceRunning) - { - if (tracer != null) - { - EventMetadata metadata = new EventMetadata(); - metadata.Add("Area", EtwArea); - tracer.RelatedError(metadata, "GvFlt Service is not running"); - } - - error = "Error: GvFlt Service is not running. To resolve, run \"sc start gvflt\" from an elevated command prompt"; - return false; - } - - return true; - } - - private static class NativeMethods - { - [DllImport("fltlib.dll", CharSet = CharSet.Unicode)] - public static extern uint FilterAttach( - string filterName, - string volumeName, - string instanceName, - uint createdInstanceNameLength = 0, - string createdInstanceName = null); - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetVolumePathName( - string volumeName, - StringBuilder volumePathName, - uint bufferLength); - } - } -} diff --git a/GVFS/GVFS.Common/FileSystem/PhysicalFileSystem.cs b/GVFS/GVFS.Common/FileSystem/PhysicalFileSystem.cs index d9df7265..58ea92c2 100644 --- a/GVFS/GVFS.Common/FileSystem/PhysicalFileSystem.cs +++ b/GVFS/GVFS.Common/FileSystem/PhysicalFileSystem.cs @@ -133,6 +133,11 @@ namespace GVFS.Common.FileSystem } } + public virtual IEnumerable EnumerateDirectories(string path) + { + return Directory.EnumerateDirectories(path); + } + public virtual FileProperties GetFileProperties(string path) { FileInfo entry = new FileInfo(path); diff --git a/GVFS/GVFS.Common/FileSystem/ProjFSFilter.cs b/GVFS/GVFS.Common/FileSystem/ProjFSFilter.cs new file mode 100644 index 00000000..130fb6dd --- /dev/null +++ b/GVFS/GVFS.Common/FileSystem/ProjFSFilter.cs @@ -0,0 +1,496 @@ +using GVFS.Common.Tracing; +using Microsoft.Diagnostics.Tracing; +using Microsoft.Win32; +using System; +using System.ComponentModel; +using System.IO; +using System.Runtime.InteropServices; +using System.Security; +using System.ServiceProcess; +using System.Text; + +namespace GVFS.Common.FileSystem +{ + public class ProjFSFilter + { + public const string ServiceName = "PrjFlt"; + private const string DriverName = "prjflt"; + private const string DriverFileName = DriverName + ".sys"; + private const string OptionalFeatureName = "Client-ProjFS"; + private const string EtwArea = nameof(ProjFSFilter); + + private const string PrjFltAutoLoggerKey = "SYSTEM\\CurrentControlSet\\Control\\WMI\\Autologger\\Microsoft-Windows-ProjFS-Filter-Log"; + private const string PrjFltAutoLoggerStartValue = "Start"; + + private const string ProjFSNativeLibFileName = "ProjectedFSLib.dll"; + + private const uint OkResult = 0; + private const uint NameCollisionErrorResult = 0x801F0012; + + public static bool TryAttach(ITracer tracer, string root, out string errorMessage) + { + errorMessage = null; + try + { + StringBuilder volumePathName = new StringBuilder(GVFSConstants.MaxPath); + if (!NativeMethods.GetVolumePathName(root, volumePathName, GVFSConstants.MaxPath)) + { + errorMessage = "Could not get volume path name"; + tracer.RelatedError(errorMessage); + return false; + } + + uint result = NativeMethods.FilterAttach(DriverName, volumePathName.ToString(), null); + if (result != OkResult && result != NameCollisionErrorResult) + { + errorMessage = string.Format("Attaching the filter driver resulted in: {0}", result); + tracer.RelatedError(errorMessage); + return false; + } + } + catch (Exception e) + { + errorMessage = string.Format("Attaching the filter driver resulted in: {0}", e.Message); + tracer.RelatedError(errorMessage); + return false; + } + + return true; + } + + public static bool IsServiceRunning(ITracer tracer) + { + try + { + ServiceController controller = new ServiceController(DriverName); + return controller.Status.Equals(ServiceControllerStatus.Running); + } + catch (InvalidOperationException e) + { + if (tracer != null) + { + EventMetadata metadata = CreateEventMetadata(); + metadata.Add("Exception", e.Message); + metadata.Add(TracingConstants.MessageKey.InfoMessage, $"{nameof(IsServiceRunning)}: InvalidOperationException: {ServiceName} service was not found"); + tracer.RelatedEvent(EventLevel.Informational, $"{nameof(IsServiceRunning)}_ServiceNotFound", metadata); + } + + return false; + } + } + + public static bool IsServiceRunningAndInstalled( + ITracer tracer, + PhysicalFileSystem fileSystem, + out bool isServiceInstalled, + out bool isDriverFileInstalled, + out bool isNativeLibInstalled) + { + bool isRunning = false; + isServiceInstalled = false; + isDriverFileInstalled = fileSystem.FileExists(Path.Combine(Environment.SystemDirectory, "drivers", DriverFileName)); + isNativeLibInstalled = IsNativeLibInstalled(tracer, fileSystem); + + try + { + ServiceController controller = new ServiceController(DriverName); + isRunning = controller.Status.Equals(ServiceControllerStatus.Running); + isServiceInstalled = true; + } + catch (InvalidOperationException e) + { + if (tracer != null) + { + EventMetadata metadata = CreateEventMetadata(); + metadata.Add("Exception", e.Message); + metadata.Add(TracingConstants.MessageKey.InfoMessage, $"{nameof(IsServiceRunningAndInstalled)}: InvalidOperationException: {ServiceName} service was not found"); + tracer.RelatedEvent(EventLevel.Informational, $"{nameof(IsServiceRunningAndInstalled)}_ServiceNotFound", metadata); + } + + return false; + } + + return isRunning; + } + + public static bool TryStartService(ITracer tracer) + { + try + { + ServiceController controller = new ServiceController(DriverName); + if (!controller.Status.Equals(ServiceControllerStatus.Running)) + { + controller.Start(); + } + + return true; + } + catch (InvalidOperationException e) + { + EventMetadata metadata = CreateEventMetadata(e); + tracer.RelatedError(metadata, $"{nameof(TryStartService)}: InvalidOperationException: {ServiceName} Service was not found"); + } + catch (Win32Exception e) + { + EventMetadata metadata = CreateEventMetadata(e); + tracer.RelatedError(metadata, $"{nameof(TryStartService)}: Win32Exception while trying to start prjflt"); + } + + return false; + } + + public static bool IsAutoLoggerEnabled(ITracer tracer) + { + object startValue; + + try + { + startValue = ProcessHelper.GetValueFromRegistry(RegistryHive.LocalMachine, PrjFltAutoLoggerKey, PrjFltAutoLoggerStartValue); + + if (startValue == null) + { + tracer.RelatedError($"{nameof(IsAutoLoggerEnabled)}: Failed to find current Start value setting"); + return false; + } + } + catch (UnauthorizedAccessException e) + { + EventMetadata metadata = CreateEventMetadata(e); + tracer.RelatedError(metadata, $"{nameof(IsAutoLoggerEnabled)}: UnauthorizedAccessException caught while trying to determine if auto-logger is enabled"); + return false; + } + catch (SecurityException e) + { + EventMetadata metadata = CreateEventMetadata(e); + tracer.RelatedError(metadata, $"{nameof(IsAutoLoggerEnabled)}: SecurityException caught while trying to determine if auto-logger is enabled"); + return false; + } + + try + { + return Convert.ToInt32(startValue) == 1; + } + catch (Exception e) + { + EventMetadata metadata = CreateEventMetadata(e); + metadata.Add(nameof(startValue), startValue); + tracer.RelatedError(metadata, $"{nameof(IsAutoLoggerEnabled)}: Exception caught while trying to determine if auto-logger is enabled"); + return false; + } + } + + public static bool TryEnableAutoLogger(ITracer tracer) + { + try + { + if (ProcessHelper.GetValueFromRegistry(RegistryHive.LocalMachine, PrjFltAutoLoggerKey, PrjFltAutoLoggerStartValue) != null) + { + if (ProcessHelper.TrySetDwordInRegistry(RegistryHive.LocalMachine, PrjFltAutoLoggerKey, PrjFltAutoLoggerStartValue, 1)) + { + return true; + } + } + } + catch (UnauthorizedAccessException e) + { + EventMetadata metadata = CreateEventMetadata(e); + tracer.RelatedError(metadata, $"{nameof(TryEnableAutoLogger)}: UnauthorizedAccessException caught while trying to enable auto-logger"); + } + catch (SecurityException e) + { + EventMetadata metadata = CreateEventMetadata(e); + tracer.RelatedError(metadata, $"{nameof(TryEnableAutoLogger)}: SecurityException caught while trying to enable auto-logger"); + } + + tracer.RelatedError($"{nameof(TryEnableAutoLogger)}: Failed to find AutoLogger Start value in registry"); + return false; + } + + public static bool TryEnableOrInstallDriver(ITracer tracer, PhysicalFileSystem fileSystem) + { + bool isProjFSInbox; + if (!TryGetIsProjFSInbox(tracer, out isProjFSInbox)) + { + return false; + } + + if (isProjFSInbox) + { + return TryEnableProjFSOptionalFeature(tracer, fileSystem); + } + + return TryInstallProjFSViaINF(tracer, fileSystem); + } + + public static bool TryInstallNativeLib(ITracer tracer, PhysicalFileSystem fileSystem) + { + bool isProjFSInbox; + if (!TryGetIsProjFSInbox(tracer, out isProjFSInbox)) + { + return false; + } + + if (isProjFSInbox) + { + tracer.RelatedError($"{nameof(TryInstallNativeLib)}: Cannot install native library, Windows supports inbox ProjFS"); + return false; + } + + string gvfsAppDirectory = ProcessHelper.GetCurrentProcessLocation(); + return TryCopyNativeLibToAppDirectory(tracer, fileSystem, gvfsAppDirectory); + } + + public static bool IsNativeLibInstalled(ITracer tracer, PhysicalFileSystem fileSystem) + { + string system32Path = Path.Combine(Environment.SystemDirectory, ProjFSNativeLibFileName); + bool existsInSystem32 = fileSystem.FileExists(system32Path); + + string gvfsAppDirectory = ProcessHelper.GetCurrentProcessLocation(); + string appFilePath; + string installFilePath; + GetNativeLibPaths(gvfsAppDirectory, out installFilePath, out appFilePath); + bool existsInAppDirectory = fileSystem.FileExists(appFilePath); + + EventMetadata metadata = CreateEventMetadata(); + metadata.Add(nameof(system32Path), system32Path); + metadata.Add(nameof(existsInSystem32), existsInSystem32); + metadata.Add(nameof(gvfsAppDirectory), gvfsAppDirectory); + metadata.Add(nameof(appFilePath), appFilePath); + metadata.Add(nameof(installFilePath), installFilePath); + metadata.Add(nameof(existsInAppDirectory), existsInAppDirectory); + tracer.RelatedEvent(EventLevel.Informational, nameof(IsNativeLibInstalled), metadata); + return existsInSystem32 || existsInAppDirectory; + } + + private static bool TryGetIsProjFSInbox(ITracer tracer, out bool isProjFSInbox) + { + isProjFSInbox = false; + + uint buildNumber = 0; + try + { + buildNumber = Common.NativeMethods.GetWindowsBuildNumber(); + tracer.RelatedInfo($"{nameof(TryGetIsProjFSInbox)}: Build number = {buildNumber}"); + } + catch (Win32Exception e) + { + tracer.RelatedError(CreateEventMetadata(e), $"{nameof(TryGetIsProjFSInbox)}: Exception while trying to get Windows build number"); + return false; + } + + const uint MinRS4inboxVersion = 17121; + const uint FirstRS5Version = 17600; + const uint MinRS5inboxVersion = 17626; + isProjFSInbox = !(buildNumber < MinRS4inboxVersion || (buildNumber >= FirstRS5Version && buildNumber < MinRS5inboxVersion)); + return true; + } + + private static bool TryInstallProjFSViaINF(ITracer tracer, PhysicalFileSystem fileSystem) + { + string gvfsAppDirectory = ProcessHelper.GetCurrentProcessLocation(); + if (!TryCopyNativeLibToAppDirectory(tracer, fileSystem, gvfsAppDirectory)) + { + return false; + } + + ProcessResult result = ProcessHelper.Run("RUNDLL32.EXE", $"SETUPAPI.DLL,InstallHinfSection DefaultInstall 128 {gvfsAppDirectory}\\Filter\\prjflt.inf"); + if (result.ExitCode == 0) + { + tracer.RelatedInfo($"{nameof(TryInstallProjFSViaINF)}: Installed PrjFlt via INF"); + return true; + } + else + { + EventMetadata metadata = CreateEventMetadata(); + metadata.Add("resultExitCode", result.ExitCode); + metadata.Add("resultOutput", result.Output); + tracer.RelatedError(metadata, $"{nameof(TryInstallProjFSViaINF)}: RUNDLL32.EXE failed to install PrjFlt"); + } + + return false; + } + + private static bool TryCopyNativeLibToAppDirectory(ITracer tracer, PhysicalFileSystem fileSystem, string gvfsAppDirectory) + { + string installFilePath; + string appFilePath; + GetNativeLibPaths(gvfsAppDirectory, out installFilePath, out appFilePath); + + EventMetadata pathMetadata = CreateEventMetadata(); + pathMetadata.Add(nameof(gvfsAppDirectory), gvfsAppDirectory); + pathMetadata.Add(nameof(installFilePath), installFilePath); + pathMetadata.Add(nameof(appFilePath), appFilePath); + + if (fileSystem.FileExists(installFilePath)) + { + tracer.RelatedEvent(EventLevel.Informational, $"{nameof(TryCopyNativeLibToAppDirectory)}_CopyingNativeLib", pathMetadata); + + try + { + fileSystem.CopyFile(installFilePath, appFilePath, overwrite: true); + + try + { + Common.NativeMethods.FlushFileBuffers(appFilePath); + } + catch (Win32Exception e) + { + EventMetadata metadata = CreateEventMetadata(e); + metadata.Add(nameof(appFilePath), appFilePath); + metadata.Add(nameof(installFilePath), installFilePath); + tracer.RelatedWarning(metadata, $"{nameof(TryCopyNativeLibToAppDirectory)}: Win32Exception while trying to flush file buffers", Keywords.Telemetry); + } + } + catch (UnauthorizedAccessException e) + { + EventMetadata metadata = CreateEventMetadata(e); + tracer.RelatedError(metadata, $"{nameof(TryCopyNativeLibToAppDirectory)}: UnauthorizedAccessException caught while trying to copy native lib"); + return false; + } + catch (DirectoryNotFoundException e) + { + EventMetadata metadata = CreateEventMetadata(e); + tracer.RelatedError(metadata, $"{nameof(TryCopyNativeLibToAppDirectory)}: DirectoryNotFoundException caught while trying to copy native lib"); + return false; + } + catch (FileNotFoundException e) + { + EventMetadata metadata = CreateEventMetadata(e); + tracer.RelatedError(metadata, $"{nameof(TryCopyNativeLibToAppDirectory)}: FileNotFoundException caught while trying to copy native lib"); + return false; + } + catch (IOException e) + { + EventMetadata metadata = CreateEventMetadata(e); + tracer.RelatedWarning(metadata, $"{nameof(TryCopyNativeLibToAppDirectory)}: IOException caught while trying to copy native lib"); + + if (fileSystem.FileExists(appFilePath)) + { + tracer.RelatedWarning( + CreateEventMetadata(), + "Could not copy native lib to app directory, but file already exists, continuing with install", + Keywords.Telemetry); + } + else + { + tracer.RelatedError($"{nameof(TryCopyNativeLibToAppDirectory)}: Failed to copy native lib to app directory"); + return false; + } + } + } + else + { + tracer.RelatedError(pathMetadata, $"{nameof(TryCopyNativeLibToAppDirectory)}: Native lib does not exist in install directory"); + return false; + } + + return true; + } + + private static void GetNativeLibPaths(string gvfsAppDirectory, out string installFilePath, out string appFilePath) + { + installFilePath = Path.Combine(gvfsAppDirectory, "ProjFS", ProjFSNativeLibFileName); + appFilePath = Path.Combine(gvfsAppDirectory, ProjFSNativeLibFileName); + } + + private static bool TryEnableProjFSOptionalFeature(ITracer tracer, PhysicalFileSystem fileSystem) + { + EventMetadata metadata = CreateEventMetadata(); + + const int ProjFSNotAnOptionalFeature = 2; + const int ProjFSEnabled = 3; + const int ProjFSDisabled = 4; + + ProcessResult getOptionalFeatureResult = CallPowershellCommand( + "$var=(Get-WindowsOptionalFeature -Online -FeatureName " + OptionalFeatureName + "); if($var -eq $null){exit " + + ProjFSNotAnOptionalFeature + "}else{if($var.State -eq 'Enabled'){exit " + ProjFSEnabled + "}else{exit " + ProjFSDisabled + "}}"); + + bool projFSEnabled = false; + switch (getOptionalFeatureResult.ExitCode) + { + case ProjFSNotAnOptionalFeature: + tracer.RelatedError($"{nameof(TryEnableProjFSOptionalFeature)}: {OptionalFeatureName} optional feature is missing"); + break; + + case ProjFSEnabled: + tracer.RelatedEvent( + EventLevel.Informational, + $"{nameof(TryEnableProjFSOptionalFeature)}_ClientProjFSAlreadyEnabled", + metadata, + Keywords.Network); + projFSEnabled = true; + break; + + case ProjFSDisabled: + ProcessResult enableOptionalFeatureResult = CallPowershellCommand("try {Enable-WindowsOptionalFeature -Online -FeatureName " + OptionalFeatureName + " -NoRestart}catch{exit 1}"); + if (enableOptionalFeatureResult.ExitCode == 0) + { + metadata.Add(TracingConstants.MessageKey.InfoMessage, "Enabled ProjFS optional feature"); + tracer.RelatedEvent(EventLevel.Informational, $"{nameof(TryEnableProjFSOptionalFeature)}_ClientProjFSDisabled", metadata); + projFSEnabled = true; + break; + } + + metadata.Add("enableOptionalFeatureResult.ExitCode", enableOptionalFeatureResult.ExitCode); + metadata.Add("enableOptionalFeatureResult.Output", enableOptionalFeatureResult.Output); + metadata.Add("enableOptionalFeatureResult.Errors", enableOptionalFeatureResult.Errors); + tracer.RelatedError(metadata, $"{nameof(TryEnableProjFSOptionalFeature)}: Failed to enable optional feature"); + break; + + default: + metadata.Add("getOptionalFeatureResult.ExitCode", getOptionalFeatureResult.ExitCode); + metadata.Add("getOptionalFeatureResult.Output", getOptionalFeatureResult.Output); + metadata.Add("getOptionalFeatureResult.Errors", getOptionalFeatureResult.Errors); + tracer.RelatedError(metadata, $"{nameof(TryEnableProjFSOptionalFeature)}: Unexpected result"); + break; + } + + if (projFSEnabled) + { + if (IsNativeLibInstalled(tracer, fileSystem)) + { + return true; + } + + tracer.RelatedError($"{nameof(TryEnableProjFSOptionalFeature)}: {OptionalFeatureName} enabled, but native ProjFS library is not on path"); + } + + return false; + } + + private static EventMetadata CreateEventMetadata(Exception e = null) + { + EventMetadata metadata = new EventMetadata(); + metadata.Add("Area", EtwArea); + if (e != null) + { + metadata.Add("Exception", e.ToString()); + } + + return metadata; + } + + private static ProcessResult CallPowershellCommand(string command) + { + return ProcessHelper.Run("powershell.exe", "-NonInteractive -NoProfile -Command \"& { " + command + " }\""); + } + + private static class NativeMethods + { + [DllImport("fltlib.dll", CharSet = CharSet.Unicode)] + public static extern uint FilterAttach( + string filterName, + string volumeName, + string instanceName, + uint createdInstanceNameLength = 0, + string createdInstanceName = null); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetVolumePathName( + string volumeName, + StringBuilder volumePathName, + uint bufferLength); + } + } +} diff --git a/GVFS/GVFS.Common/GVFS.Common.csproj b/GVFS/GVFS.Common/GVFS.Common.csproj index 19ba3e31..55c037f5 100644 --- a/GVFS/GVFS.Common/GVFS.Common.csproj +++ b/GVFS/GVFS.Common/GVFS.Common.csproj @@ -9,7 +9,7 @@ Properties GVFS.Common GVFS.Common - v4.5.2 + v4.6.1 512 @@ -37,7 +37,7 @@ False - ..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll + ..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net46\Microsoft.Diagnostics.Tracing.EventSource.dll True @@ -66,11 +66,12 @@ + - + diff --git a/GVFS/GVFS.Common/GVFSConfig.cs b/GVFS/GVFS.Common/GVFSConfig.cs index 7b34c6b7..17d805c3 100644 --- a/GVFS/GVFS.Common/GVFSConfig.cs +++ b/GVFS/GVFS.Common/GVFSConfig.cs @@ -1,6 +1,7 @@ using GVFS.Common.Http; using System; using System.Collections.Generic; +using System.Linq; namespace GVFS.Common { @@ -8,7 +9,7 @@ namespace GVFS.Common { public IEnumerable AllowedGVFSClientVersions { get; set; } - public IEnumerable CacheServers { get; set; } + public IEnumerable CacheServers { get; set; } = Enumerable.Empty(); public class VersionRange { diff --git a/GVFS/GVFS.Common/GVFSConstants.cs b/GVFS/GVFS.Common/GVFSConstants.cs index c409c61c..63c8207d 100644 --- a/GVFS/GVFS.Common/GVFSConstants.cs +++ b/GVFS/GVFS.Common/GVFSConstants.cs @@ -28,11 +28,18 @@ namespace GVFS.Common public const string ExecutableExtension = ".exe"; public const string GitIsNotInstalledError = "Could not find git.exe. Ensure that Git is installed."; - public static readonly GitVersion MinimumGitVersion = new GitVersion(2, 15, 1, "gvfs", 1, 0); + public static readonly GitVersion MinimumGitVersion = new GitVersion(2, 17, 0, "gvfs", 1, 0); public static class GitConfig { public const string GVFSPrefix = "gvfs."; + public const string MaxRetriesConfig = GVFSPrefix + "max-retries"; + public const string TimeoutSecondsConfig = GVFSPrefix + "timeout-seconds"; + public const string EnlistmentId = GVFSPrefix + "enlistment-id"; + public const string CacheServer = GVFSPrefix + "cache-server"; + public const string DeprecatedCacheEndpointSuffix = ".cache-server-url"; + public const string HooksPrefix = GitConfig.GVFSPrefix + "clone.default-"; + public const string HooksExtension = ".hooks"; } public static class Service @@ -86,8 +93,6 @@ namespace GVFS.Common public static readonly string LogPath = Path.Combine(DotGVFS.Root, "logs"); public static readonly string CorruptObjectsPath = Path.Combine(DotGVFS.Root, CorruptObjectsName); - public static readonly string BlobSizesName = "BlobSizes"; - public static class Databases { public const string Name = "databases"; @@ -128,8 +133,6 @@ namespace GVFS.Common public static class Hooks { - public const string ConfigExtension = ".hooks"; - public const string ConfigNamePrefix = "gvfs.clone.default-"; public const string LoaderExecutable = "GitHooksLoader.exe"; public const string PreCommandHookName = "pre-command"; public const string PostCommandHookName = "post-command"; diff --git a/GVFS/GVFS.Common/GVFSEnlistment.cs b/GVFS/GVFS.Common/GVFSEnlistment.cs index 89549416..b7e9a66b 100644 --- a/GVFS/GVFS.Common/GVFSEnlistment.cs +++ b/GVFS/GVFS.Common/GVFSEnlistment.cs @@ -1,12 +1,7 @@ -using GVFS.Common.FileSystem; -using GVFS.Common.Git; using GVFS.Common.NamedPipes; using Newtonsoft.Json; using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Security; using System.Security.AccessControl; using System.Security.Principal; using System.Threading; @@ -15,9 +10,15 @@ namespace GVFS.Common { public partial class GVFSEnlistment : Enlistment { + public const string BlobSizesCacheName = "blobSizes"; + private const string GitObjectCacheName = "gitObjects"; private const string InvalidRepoUrl = "invalid://repoUrl"; - + + private string gitVersion; + private string gvfsVersion; + private string gvfsHooksVersion; + // New enlistment public GVFSEnlistment(string enlistmentRoot, string repoUrl, string gitBinPath, string gvfsHooksRoot) : base( @@ -31,10 +32,11 @@ namespace GVFS.Common this.NamedPipeName = Paths.GetNamedPipeName(this.EnlistmentRoot); this.DotGVFSRoot = Path.Combine(this.EnlistmentRoot, GVFSConstants.DotGVFS.Root); this.GVFSLogsRoot = Path.Combine(this.EnlistmentRoot, GVFSConstants.DotGVFS.LogPath); + this.LocalObjectsRoot = Path.Combine(this.WorkingDirectoryRoot, GVFSConstants.DotGit.Objects.Root); } - + // Existing, configured enlistment - public GVFSEnlistment(string enlistmentRoot, string gitBinPath, string gvfsHooksRoot) + private GVFSEnlistment(string enlistmentRoot, string gitBinPath, string gvfsHooksRoot) : this( enlistmentRoot, null, @@ -42,7 +44,7 @@ namespace GVFS.Common gvfsHooksRoot) { } - + public string NamedPipeName { get; } public string DotGVFSRoot { get; } @@ -51,8 +53,27 @@ namespace GVFS.Common public string LocalCacheRoot { get; private set; } + public string BlobSizesRoot { get; private set; } + public override string GitObjectsRoot { get; protected set; } + public override string LocalObjectsRoot { get; protected set; } public override string GitPackRoot { get; protected set; } + + // These version properties are only used in logging during clone and mount to track version numbers + public string GitVersion + { + get { return this.gitVersion; } + } + + public string GVFSVersion + { + get { return this.gvfsVersion; } + } + + public string GVFSHooksVersion + { + get { return this.gvfsHooksVersion; } + } public static GVFSEnlistment CreateWithoutRepoUrlFromDirectory(string directory, string gitBinRoot, string gvfsHooksRoot) { @@ -139,16 +160,35 @@ namespace GVFS.Common } } - public void InitializeLocalCacheAndObjectsPathsFromKey(string localCacheRoot, string localCacheKey) + public void SetGitVersion(string gitVersion) { - this.InitializeLocalCacheAndObjectPaths(localCacheRoot, Path.Combine(localCacheRoot, localCacheKey, GitObjectCacheName)); + this.SetOnce(gitVersion, ref this.gitVersion); } - public void InitializeLocalCacheAndObjectPaths(string localCacheRoot, string gitObjectsRoot) + public void SetGVFSVersion(string gvfsVersion) + { + this.SetOnce(gvfsVersion, ref this.gvfsVersion); + } + + public void SetGVFSHooksVersion(string gvfsHooksVersion) + { + this.SetOnce(gvfsHooksVersion, ref this.gvfsHooksVersion); + } + + public void InitializeCachePathsFromKey(string localCacheRoot, string localCacheKey) + { + this.InitializeCachePaths( + localCacheRoot, + Path.Combine(localCacheRoot, localCacheKey, GitObjectCacheName), + Path.Combine(localCacheRoot, localCacheKey, BlobSizesCacheName)); + } + + public void InitializeCachePaths(string localCacheRoot, string gitObjectsRoot, string blobSizesRoot) { this.LocalCacheRoot = localCacheRoot; this.GitObjectsRoot = gitObjectsRoot; this.GitPackRoot = Path.Combine(this.GitObjectsRoot, GVFSConstants.DotGit.Objects.Pack.Name); + this.BlobSizesRoot = blobSizesRoot; } public bool TryCreateEnlistmentFolders() @@ -191,7 +231,17 @@ namespace GVFS.Common return true; } - + + private void SetOnce(T value, ref T valueToSet) + { + if (valueToSet != null) + { + throw new InvalidOperationException("Value already set."); + } + + valueToSet = value; + } + /// /// Creates a hidden directory @ the given path. /// If directory already exists, hides it. diff --git a/GVFS/GVFS.Common/GVFSLock.Shared.cs b/GVFS/GVFS.Common/GVFSLock.Shared.cs index b80ef677..8a420b76 100644 --- a/GVFS/GVFS.Common/GVFSLock.Shared.cs +++ b/GVFS/GVFS.Common/GVFSLock.Shared.cs @@ -14,11 +14,12 @@ namespace GVFS.Common string fullCommand, int pid, bool isElevated, + bool checkAvailabilityOnly, Process parentProcess, string gvfsEnlistmentRoot, out string result) { - NamedPipeMessages.LockRequest request = new NamedPipeMessages.LockRequest(pid, isElevated, fullCommand); + NamedPipeMessages.LockRequest request = new NamedPipeMessages.LockRequest(pid, isElevated, checkAvailabilityOnly, fullCommand); NamedPipeMessages.Message requestMessage = request.CreateMessage(NamedPipeMessages.AcquireLock.AcquireRequest); pipeClient.SendRequest(requestMessage); @@ -29,8 +30,10 @@ namespace GVFS.Common switch (response.Result) { case NamedPipeMessages.AcquireLock.AcceptResult: - result = null; - return true; + return CheckAcceptResponse(response, checkAvailabilityOnly, out result); + + case NamedPipeMessages.AcquireLock.AvailableResult: + return CheckAcceptResponse(response, checkAvailabilityOnly, out result); case NamedPipeMessages.AcquireLock.MountNotReadyResult: result = "GVFS has not finished initializing, please wait a few seconds and try again."; @@ -64,7 +67,10 @@ namespace GVFS.Common switch (response.Result) { case NamedPipeMessages.AcquireLock.AcceptResult: - return true; + return CheckAcceptResponse(response, checkAvailabilityOnly, out _); + + case NamedPipeMessages.AcquireLock.AvailableResult: + return CheckAcceptResponse(response, checkAvailabilityOnly, out _); case NamedPipeMessages.AcquireLock.UnmountInProgressResult: return false; @@ -105,7 +111,7 @@ namespace GVFS.Common string waitingMessage = "", int spinnerDelay = 0) { - NamedPipeMessages.LockRequest request = new NamedPipeMessages.LockRequest(pid, isElevated, fullCommand); + NamedPipeMessages.LockRequest request = new NamedPipeMessages.LockRequest(pid, isElevated, checkAvailabilityOnly: false, parsedCommand: fullCommand); NamedPipeMessages.Message requestMessage = request.CreateMessage(NamedPipeMessages.ReleaseLock.Request); @@ -135,5 +141,39 @@ namespace GVFS.Common initialDelayMs: spinnerDelay); } } + + private static bool CheckAcceptResponse(NamedPipeMessages.AcquireLock.Response response, bool checkAvailabilityOnly, out string message) + { + switch (response.Result) + { + case NamedPipeMessages.AcquireLock.AcceptResult: + if (!checkAvailabilityOnly) + { + message = null; + return true; + } + else + { + message = "Error when acquiring the lock. Unexpected response: " + response.CreateMessage(); + return false; + } + + case NamedPipeMessages.AcquireLock.AvailableResult: + if (checkAvailabilityOnly) + { + message = null; + return true; + } + else + { + message = "Error when acquiring the lock. Unexpected response: " + response.CreateMessage(); + return false; + } + + default: + message = "Error when acquiring the lock. Not an Accept result: " + response.CreateMessage(); + return false; + } + } } } diff --git a/GVFS/GVFS.Common/GVFSLock.cs b/GVFS/GVFS.Common/GVFSLock.cs index 158e1968..8da12603 100644 --- a/GVFS/GVFS.Common/GVFSLock.cs +++ b/GVFS/GVFS.Common/GVFSLock.cs @@ -1,31 +1,27 @@ using GVFS.Common.NamedPipes; using GVFS.Common.Tracing; using Microsoft.Diagnostics.Tracing; -using System; using System.Diagnostics; using System.Threading; namespace GVFS.Common { - public partial class GVFSLock : IDisposable + public partial class GVFSLock { private readonly object acquisitionLock = new object(); private readonly ITracer tracer; - private NamedPipeMessages.LockData lockHolder; - private ManualResetEvent externalLockReleased; - - private Stats stats; + private bool isLockedByGVFS; + private NamedPipeMessages.LockData externalLockHolder; public GVFSLock(ITracer tracer) { this.tracer = tracer; - this.externalLockReleased = new ManualResetEvent(initialState: true); - this.stats = new Stats(); + this.Stats = new ActiveGitCommandStats(); } - public bool IsLockedByGVFS + public ActiveGitCommandStats Stats { get; private set; @@ -52,7 +48,7 @@ namespace GVFS.Common { lock (this.acquisitionLock) { - if (this.IsLockedByGVFS) + if (this.isLockedByGVFS) { metadata.Add("CurrentLockHolder", "GVFS"); metadata.Add("Result", "Denied"); @@ -60,22 +56,20 @@ namespace GVFS.Common return false; } - if (this.IsExternalLockHolderAlive() && - this.lockHolder.PID != requester.PID) + if (!this.IsLockAvailable(checkExternalHolderOnly: true)) { - metadata.Add("CurrentLockHolder", this.lockHolder.ToString()); + metadata.Add("CurrentLockHolder", this.externalLockHolder.ToString()); metadata.Add("Result", "Denied"); - holder = this.lockHolder; + holder = this.externalLockHolder; return false; } metadata.Add("Result", "Accepted"); eventLevel = EventLevel.Informational; - this.lockHolder = requester; - this.externalLockReleased.Reset(); - this.stats = new Stats(); + this.externalLockHolder = requester; + this.Stats = new ActiveGitCommandStats(); return true; } @@ -97,20 +91,19 @@ namespace GVFS.Common { lock (this.acquisitionLock) { - if (this.IsLockedByGVFS) + if (this.isLockedByGVFS) { return true; } - if (this.IsExternalLockHolderAlive()) + if (!this.IsLockAvailable(checkExternalHolderOnly: true)) { - metadata.Add("CurrentLockHolder", this.lockHolder.ToString()); + metadata.Add("CurrentLockHolder", this.externalLockHolder.ToString()); metadata.Add("Result", "Denied"); return false; } - this.IsLockedByGVFS = true; - this.externalLockReleased.Set(); + this.isLockedByGVFS = true; metadata.Add("Result", "Accepted"); return true; } @@ -121,11 +114,6 @@ namespace GVFS.Common } } - public void RecordObjectDownload(bool isBlob, long downloadTimeMs) - { - this.stats.RecordObjectDownload(isBlob, downloadTimeMs); - } - /// /// Allow GVFS to release the lock if it holds it. /// @@ -136,7 +124,7 @@ namespace GVFS.Common public void ReleaseLock() { this.tracer.RelatedEvent(EventLevel.Verbose, "ReleaseLock", new EventMetadata()); - this.IsLockedByGVFS = false; + this.isLockedByGVFS = false; } public bool ReleaseExternalLock(int pid) @@ -144,31 +132,32 @@ namespace GVFS.Common return this.ReleaseExternalLock(pid, nameof(this.ReleaseExternalLock)); } - public bool WaitOnExternalLockRelease(int millisecondsTimeout) - { - return this.externalLockReleased.WaitOne(millisecondsTimeout); - } - - public bool IsExternalLockHolderAlive() + public bool IsLockAvailable(bool checkExternalHolderOnly = false) { lock (this.acquisitionLock) { - if (this.lockHolder == null) + if (!checkExternalHolderOnly && + this.isLockedByGVFS) { return false; } + if (this.externalLockHolder == null) + { + return true; + } + Process process = null; try { - int pid = this.lockHolder.PID; + int pid = this.externalLockHolder.PID; if (ProcessHelper.TryGetProcess(pid, out process)) { - return true; + return false; } this.ReleaseLockForTerminatedProcess(pid); - return false; + return true; } finally { @@ -182,12 +171,12 @@ namespace GVFS.Common public NamedPipeMessages.LockData GetExternalLockHolder() { - return this.lockHolder; + return this.externalLockHolder; } public string GetLockedGitCommand() { - NamedPipeMessages.LockData currentHolder = this.lockHolder; + NamedPipeMessages.LockData currentHolder = this.externalLockHolder; if (currentHolder != null) { return currentHolder.ParsedCommand; @@ -198,12 +187,12 @@ namespace GVFS.Common public string GetStatus() { - if (this.IsLockedByGVFS) + if (this.isLockedByGVFS) { return "Held by GVFS."; } - NamedPipeMessages.LockData currentHolder = this.lockHolder; + NamedPipeMessages.LockData currentHolder = this.externalLockHolder; if (currentHolder != null) { return string.Format("Held by {0} (PID:{1})", currentHolder.ParsedCommand, currentHolder.PID); @@ -212,24 +201,6 @@ namespace GVFS.Common return "Free"; } - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - protected void Dispose(bool disposing) - { - if (disposing) - { - if (this.externalLockReleased != null) - { - this.externalLockReleased.Dispose(); - this.externalLockReleased = null; - } - } - } - private bool ReleaseExternalLock(int pid, string eventName) { lock (this.acquisitionLock) @@ -238,32 +209,32 @@ namespace GVFS.Common try { - if (this.IsLockedByGVFS) + if (this.isLockedByGVFS) { metadata.Add("IsLockedByGVFS", "true"); return false; } - if (this.lockHolder == null) + if (this.externalLockHolder == null) { metadata.Add("Result", "Failed (no current holder, requested PID=" + pid + ")"); return false; } - metadata.Add("CurrentLockHolder", this.lockHolder.ToString()); - metadata.Add("IsElevated", this.lockHolder.IsElevated); + metadata.Add("CurrentLockHolder", this.externalLockHolder.ToString()); + metadata.Add("IsElevated", this.externalLockHolder.IsElevated); + metadata.Add(nameof(RepoMetadata.Instance.EnlistmentId), RepoMetadata.Instance.EnlistmentId); - if (this.lockHolder.PID != pid) + if (this.externalLockHolder.PID != pid) { metadata.Add("pid", pid); metadata.Add("Result", "Failed (wrong PID)"); return false; } - this.lockHolder = null; - this.externalLockReleased.Set(); + this.externalLockHolder = null; metadata.Add("Result", "Released"); - this.stats.AddStatsToTelemetry(metadata); + this.Stats.AddStatsToTelemetry(metadata); return true; } @@ -279,50 +250,80 @@ namespace GVFS.Common this.ReleaseExternalLock(pid, "ExternalLockHolderExited"); } - public class GVFSLockException : Exception - { - public GVFSLockException(string message) - : base(message) - { - } - } - // The lock release event is a convenient place to record stats about things that happened while a git command was running, // such as duration/count of object downloads during a git command, cache hits during a git command, etc. - private class Stats + public class ActiveGitCommandStats { private Stopwatch lockAcquiredTime; - private int numBlobs; - private long blobDownloadTime; - private int numCommitsAndTrees; - private long commitAndTreeDownloadTime; + private long lockHeldExternallyTimeMs; - public Stats() + private long placeholderUpdateTimeMs; + private long parseGitIndexTimeMs; + + private int numBlobs; + private long blobDownloadTimeMs; + + private int numCommitsAndTrees; + private long commitAndTreeDownloadTimeMs; + + private int numSizeQueries; + private long sizeQueryTimeMs; + + public ActiveGitCommandStats() { this.lockAcquiredTime = Stopwatch.StartNew(); } + public void RecordReleaseExternalLockRequested() + { + this.lockHeldExternallyTimeMs = this.lockAcquiredTime.ElapsedMilliseconds; + } + + public void RecordUpdatePlaceholders(long durationMs) + { + this.placeholderUpdateTimeMs = durationMs; + } + + public void RecordParseGitIndex(long durationMs) + { + this.parseGitIndexTimeMs = durationMs; + } + public void RecordObjectDownload(bool isBlob, long downloadTimeMs) { if (isBlob) { Interlocked.Increment(ref this.numBlobs); - Interlocked.Add(ref this.blobDownloadTime, downloadTimeMs); + Interlocked.Add(ref this.blobDownloadTimeMs, downloadTimeMs); } else { Interlocked.Increment(ref this.numCommitsAndTrees); - Interlocked.Add(ref this.commitAndTreeDownloadTime, downloadTimeMs); + Interlocked.Add(ref this.commitAndTreeDownloadTimeMs, downloadTimeMs); } } + public void RecordSizeQuery(long queryTimeMs) + { + Interlocked.Increment(ref this.numSizeQueries); + Interlocked.Add(ref this.sizeQueryTimeMs, queryTimeMs); + } + public void AddStatsToTelemetry(EventMetadata metadata) { metadata.Add("DurationMS", this.lockAcquiredTime.ElapsedMilliseconds); + metadata.Add("LockHeldExternallyMS", this.lockHeldExternallyTimeMs); + metadata.Add("ParseGitIndexMS", this.parseGitIndexTimeMs); + metadata.Add("UpdatePlaceholdersMS", this.placeholderUpdateTimeMs); + metadata.Add("BlobsDownloaded", this.numBlobs); - metadata.Add("BlobDownloadTimeMS", this.blobDownloadTime); + metadata.Add("BlobDownloadTimeMS", this.blobDownloadTimeMs); + metadata.Add("CommitsAndTreesDownloaded", this.numCommitsAndTrees); - metadata.Add("CommitsAndTreesDownloadTimeMS", this.commitAndTreeDownloadTime); + metadata.Add("CommitsAndTreesDownloadTimeMS", this.commitAndTreeDownloadTimeMs); + + metadata.Add("SizeQueries", this.numSizeQueries); + metadata.Add("SizeQueryTimeMS", this.sizeQueryTimeMs); } } } diff --git a/GVFS/GVFS.Common/Git/GitObjects.cs b/GVFS/GVFS.Common/Git/GitObjects.cs index c239ebbf..63d43a95 100644 --- a/GVFS/GVFS.Common/Git/GitObjects.cs +++ b/GVFS/GVFS.Common/Git/GitObjects.cs @@ -47,7 +47,7 @@ namespace GVFS.Common.Git { const bool PreferLooseObjects = false; IEnumerable objectIds = new[] { commitSha }; - + RetryWrapper.InvocationResult output = this.GitObjectRequestor.TryDownloadObjects( objectIds, onSuccess: (tryCount, response) => this.TrySavePackOrLooseObject(objectIds, PreferLooseObjects, response), @@ -93,7 +93,7 @@ namespace GVFS.Common.Git } } - public bool TryDownloadPrefetchPacks(long latestTimestamp) + public bool TryDownloadPrefetchPacks(long latestTimestamp, out List packIndexes) { EventMetadata metadata = CreateEventMetadata(); metadata.Add("latestTimestamp", latestTimestamp); @@ -103,9 +103,10 @@ namespace GVFS.Common.Git long bytesDownloaded = 0; long requestId = HttpRequestor.GetNewRequestId(); + List innerPackIndexes = null; RetryWrapper.InvocationResult result = this.GitObjectRequestor.TrySendProtocolRequest( requestId: requestId, - onSuccess: (tryCount, response) => this.DeserializePrefetchPacks(response, ref latestTimestamp, ref bytesDownloaded), + onSuccess: (tryCount, response) => this.DeserializePrefetchPacks(response, ref latestTimestamp, ref bytesDownloaded, ref innerPackIndexes), onFailure: RetryWrapper.StandardErrorHandler(activity, requestId, "TryDownloadPrefetchPacks"), method: HttpMethod.Get, endPointGenerator: () => new Uri( @@ -116,7 +117,9 @@ namespace GVFS.Common.Git requestBodyGenerator: () => null, cancellationToken: CancellationToken.None, acceptType: new MediaTypeWithQualityHeaderValue(GVFSConstants.MediaTypes.PrefetchPackFilesAndIndexesMediaType)); - + + packIndexes = innerPackIndexes; + if (!result.Succeeded) { if (result.Result != null && result.Result.HttpStatusCodeResult == HttpStatusCode.NotFound) @@ -148,6 +151,43 @@ namespace GVFS.Common.Git } } + public bool TryWriteMultiPackIndex(ITracer tracer, GVFSEnlistment enlistment, PhysicalFileSystem fileSystem) + { + using (ITracer activity = tracer.StartActivity(nameof(this.TryWriteMultiPackIndex), EventLevel.Informational, Keywords.Telemetry, metadata: null)) + { + GitProcess process = new GitProcess(enlistment, fileSystem); + GitProcess.Result result = process.WriteMultiPackIndex(enlistment.GitPackRoot); + + if (!result.HasErrors) + { + string midxHash = result.Output.Trim(); + activity.RelatedInfo("Updated midx-head to hash {0}", midxHash); + + string expectedMidxHead = Path.Combine(enlistment.GitPackRoot, "midx-" + midxHash + ".midx"); + string[] midxFiles = fileSystem.GetFiles(enlistment.GitPackRoot, "midx-*.midx"); + + foreach (string midxFile in midxFiles) + { + if (!midxFile.Equals(expectedMidxHead, StringComparison.OrdinalIgnoreCase) && !fileSystem.TryDeleteFile(midxFile)) + { + activity.RelatedWarning("Failed to delete MIDX file {0}", midxFile); + } + } + } + else + { + EventMetadata errorMetadata = new EventMetadata(); + errorMetadata.Add("Operation", nameof(this.TryWriteMultiPackIndex)); + errorMetadata.Add("packDir", enlistment.GitPackRoot); + errorMetadata.Add("Errors", result.Errors); + errorMetadata.Add("Output", result.Output.Length > 1024 ? result.Output.Substring(1024) : result.Output); + activity.RelatedError(errorMetadata, result.Errors, Keywords.Telemetry); + } + + return !result.HasErrors; + } + } + public virtual string WriteLooseObject(Stream responseStream, string sha, bool overwriteExistingObject, byte[] bufToCopyWith) { try @@ -280,7 +320,7 @@ namespace GVFS.Common.Git Exception moveFileException = null; try { - // We're indexing a pack file that was saved to a temp file name, and so it must be renamed + // We're indexing a pack file that was saved to a temp file name, and so it must be renamed // to its final name before indexing ('git index-pack' requires that the pack file name end with .pack) this.fileSystem.MoveFile(tempPackPath, packfilePath); } @@ -322,7 +362,7 @@ namespace GVFS.Common.Git Exception indexPackException = null; try - { + { GitProcess.Result result = new GitProcess(this.Enlistment).IndexPack(packfilePath, tempIdxPath); if (result.HasErrors) { @@ -335,7 +375,7 @@ namespace GVFS.Common.Git } } else - { + { if (this.Enlistment.FlushFileBuffersForPacks) { Exception exception; @@ -379,8 +419,8 @@ namespace GVFS.Common.Git this.Tracer.RelatedWarning(failureMetadata, $"{nameof(this.IndexPackFile): Exception caught while trying to index pack file}"); return new GitProcess.Result( - string.Empty, - indexPackException != null ? indexPackException.Message : "Failed to index pack file", + string.Empty, + indexPackException != null ? indexPackException.Message : "Failed to index pack file", GitProcess.Result.GenericFailureCode); } @@ -405,8 +445,8 @@ namespace GVFS.Common.Git } return new string[0]; - } - + } + private static string GetRandomPackName(string packRoot) { string packName = "pack-" + Guid.NewGuid().ToString("N") + ".pack"; @@ -434,7 +474,7 @@ namespace GVFS.Common.Git try { this.fileSystem.MoveAndOverwriteFile(packTempPath, finalPackPath); - this.fileSystem.MoveAndOverwriteFile(idxTempPath, finalIdxPath); + this.fileSystem.MoveAndOverwriteFile(idxTempPath, finalIdxPath); } catch (Win32Exception e) { @@ -475,7 +515,7 @@ namespace GVFS.Common.Git if (readOnly) { if (!this.TrySetAttributes(path, originalAttributes & ~FileAttributes.ReadOnly, out exception)) - { + { error = "Failed to clear read-only attribute, skipping flush"; return false; } @@ -533,7 +573,7 @@ namespace GVFS.Common.Git exception = null; try - { + { this.fileSystem.SetAttributes(path, attributes); return true; } @@ -576,10 +616,16 @@ namespace GVFS.Common.Git /// Uses a to read the packs from the stream. /// private RetryWrapper.CallbackResult DeserializePrefetchPacks( - GitEndPointResponseData response, + GitEndPointResponseData response, ref long latestTimestamp, - ref long bytesDownloaded) + ref long bytesDownloaded, + ref List packIndexes) { + if (packIndexes == null) + { + packIndexes = new List(); + } + using (ITracer activity = this.Tracer.StartActivity("DeserializePrefetchPacks", EventLevel.Informational)) { PrefetchPacksDeserializer deserializer = new PrefetchPacksDeserializer(response.Stream); @@ -662,19 +708,19 @@ namespace GVFS.Common.Git { packFlushTask.Wait(); } - } + } // Move whatever has been successfully downloaded so far Exception moveException; - this.TryFlushAndMoveTempPacks(tempPacks, ref latestTimestamp, out moveException); + this.TryFlushAndMoveTempPacks(tempPacks, ref latestTimestamp, out moveException); // The download stream will not be in a good state if the index download fails. // So we have to restart the prefetch return new RetryWrapper.CallbackResult(null, true); } } - - bytesDownloaded += indexLength; + + bytesDownloaded += indexLength; } Exception exception = null; @@ -683,6 +729,11 @@ namespace GVFS.Common.Git return new RetryWrapper.CallbackResult(exception, true); } + foreach (TempPrefetchPackAndIdx tempPack in tempPacks) + { + packIndexes.Add(tempPack.IdxName); + } + return new RetryWrapper.CallbackResult( new GitObjectsHttpRequestor.GitObjectTaskResult(success: true)); } @@ -758,7 +809,7 @@ namespace GVFS.Common.Git { Exception e; if (!this.fileSystem.TryDeleteFile(fullPath, exception: out e)) - { + { EventMetadata info = CreateEventMetadata(e); info.Add("file", fullPath); activity.RelatedWarning(info, "Failed to cleanup temp file"); @@ -790,7 +841,7 @@ namespace GVFS.Common.Git try { - this.fileSystem.MoveFile(toWrite.TempFile, toWrite.ActualFile); + this.fileSystem.MoveFile(toWrite.TempFile, toWrite.ActualFile); } catch (IOException ex) { @@ -910,10 +961,10 @@ namespace GVFS.Common.Git { public TempPrefetchPackAndIdx( long timestamp, - string packName, - string packFullPath, + string packName, + string packFullPath, Task packFlushTask, - string idxName, + string idxName, string idxFullPath, Task idxFlushTask) { diff --git a/GVFS/GVFS.Common/Git/GitProcess.cs b/GVFS/GVFS.Common/Git/GitProcess.cs index 4fe1cb39..5f6b27b9 100644 --- a/GVFS/GVFS.Common/Git/GitProcess.cs +++ b/GVFS/GVFS.Common/Git/GitProcess.cs @@ -42,7 +42,7 @@ namespace GVFS.Common.Git { throw new ArgumentNullException(nameof(enlistment)); } - + if (string.IsNullOrWhiteSpace(enlistment.GitBinPath)) { throw new ArgumentException(nameof(enlistment.GitBinPath)); @@ -79,14 +79,14 @@ namespace GVFS.Common.Git public static Result Init(Enlistment enlistment) { - return new GitProcess(enlistment).InvokeGitOutsideEnlistment("init " + enlistment.WorkingDirectoryRoot); + return new GitProcess(enlistment).InvokeGitOutsideEnlistment("init \"" + enlistment.WorkingDirectoryRoot + "\""); } public static Result Version(Enlistment enlistment) { return new GitProcess(enlistment).InvokeGitOutsideEnlistment("--version"); } - + public virtual void RevokeCredential() { this.InvokeGitOutsideEnlistment( @@ -114,8 +114,8 @@ namespace GVFS.Common.Git { EventMetadata errorData = new EventMetadata(); tracer.RelatedWarning( - errorData, - "Git could not get credentials: " + gitCredentialOutput.Errors, + errorData, + "Git could not get credentials: " + gitCredentialOutput.Errors, Keywords.Network | Keywords.Telemetry); return false; @@ -176,9 +176,10 @@ namespace GVFS.Common.Git value)); } - public bool TryGetAllLocalConfig(out Dictionary configSettings) + public bool TryGetAllConfig(bool localOnly, out Dictionary configSettings) { - Result result = this.InvokeGitAgainstDotGitFolder("config --list --local"); + string localParameter = localOnly ? "--local" : string.Empty; + Result result = this.InvokeGitAgainstDotGitFolder("config --list " + localParameter); if (result.HasErrors) { configSettings = null; @@ -186,7 +187,6 @@ namespace GVFS.Common.Git } configSettings = GitConfigHelper.ParseKeyValues(result.Output); - return true; } @@ -195,7 +195,7 @@ namespace GVFS.Common.Git /// /// The name of the config setting /// - /// If false, will run the call from inside the enlistment if the working dir found, + /// If false, will run the call from inside the enlistment if the working dir found, /// otherwise it will run it from outside the enlistment. /// /// The value found for the setting. @@ -204,9 +204,9 @@ namespace GVFS.Common.Git string command = string.Format("config {0}", settingName); // This method is called at clone time, so the physical repo may not exist yet. - return + return this.fileSystem.DirectoryExists(this.enlistment.WorkingDirectoryRoot) && !forceOutsideEnlistment - ? this.InvokeGitAgainstDotGitFolder(command) + ? this.InvokeGitAgainstDotGitFolder(command) : this.InvokeGitOutsideEnlistment(command); } @@ -220,7 +220,7 @@ namespace GVFS.Common.Git /// /// The name of the config setting /// - /// If false, will run the call from inside the enlistment if the working dir found, + /// If false, will run the call from inside the enlistment if the working dir found, /// otherwise it will run it from outside the enlistment. /// /// The value found for the config setting. @@ -286,21 +286,60 @@ namespace GVFS.Common.Git null); } + /// + /// Write a new commit graph in the specified pack directory. Crawl the given pack- + /// indexes for commits and then close under everything reachable or exists in the + /// previous graph file. + /// + /// This will update the graph-head file to point to the new commit graph and delete + /// any expired graph files that previously existed. + /// + public Result WriteCommitGraph(string objectDir, List packs) + { + string command = "commit-graph write --stdin-packs --append --object-dir \"" + objectDir + "\""; + return this.InvokeGitInWorkingDirectoryRoot( + command, + useReadObjectHook: true, + writeStdIn: writer => + { + foreach (string packIndex in packs) + { + writer.WriteLine(packIndex); + } + + // We need to close stdin or else the process will not terminate. + writer.Close(); + }); + } + public Result IndexPack(string packfilePath, string idxOutputPath) { return this.InvokeGitAgainstDotGitFolder($"index-pack -o \"{idxOutputPath}\" \"{packfilePath}\""); } + /// + /// Write a new multi-pack-index (MIDX) in the specified pack directory. + /// + /// This will update the midx-head file to point to the new MIDX file. + /// + /// If no new packfiles are found, then this is a no-op. + /// + public Result WriteMultiPackIndex(string packDir) + { + // We override the config settings so we keep writing the MIDX file even if it is disabled for reads. + return this.InvokeGitAgainstDotGitFolder("-c core.midx=true midx --write --update-head --pack-dir \"" + packDir + "\""); + } + public Result RemoteAdd(string remoteName, string url) { return this.InvokeGitAgainstDotGitFolder("remote add " + remoteName + " " + url); } - + public Result CatFileGetType(string objectId) { return this.InvokeGitAgainstDotGitFolder("cat-file -t " + objectId); } - + public Result LsTree(string treeish, Action parseStdOutLine, bool recursive, bool showAllTrees = false) { return this.InvokeGitAgainstDotGitFolder( @@ -361,6 +400,7 @@ namespace GVFS.Common.Git } processInfo.EnvironmentVariables["GIT_TERMINAL_PROMPT"] = "0"; + processInfo.EnvironmentVariables["GCM_VALIDATE"] = "0"; processInfo.EnvironmentVariables["PATH"] = string.Join( ";", @@ -502,14 +542,17 @@ namespace GVFS.Common.Git /// /// Invokes git.exe from an enlistment's repository root /// - private Result InvokeGitInWorkingDirectoryRoot(string command, bool useReadObjectHook) + private Result InvokeGitInWorkingDirectoryRoot( + string command, + bool useReadObjectHook, + Action writeStdIn = null) { return this.InvokeGitImpl( command, workingDirectory: this.enlistment.WorkingDirectoryRoot, dotGitDirectory: null, useReadObjectHook: useReadObjectHook, - writeStdIn: null, + writeStdIn: writeStdIn, parseStdOutLine: null, timeoutMs: -1); } diff --git a/GVFS/GVFS.Common/Git/GitRepo.cs b/GVFS/GVFS.Common/Git/GitRepo.cs index 53372d6d..369059d9 100644 --- a/GVFS/GVFS.Common/Git/GitRepo.cs +++ b/GVFS/GVFS.Common/Git/GitRepo.cs @@ -2,6 +2,7 @@ using GVFS.Common.Tracing; using Microsoft.Diagnostics.Tracing; using System; +using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; @@ -24,7 +25,7 @@ namespace GVFS.Common.Git this.fileSystem = fileSystem; this.GVFSLock = new GVFSLock(tracer); - + this.libgit2RepoPool = new LibGit2RepoPool( tracer, repoFactory ?? (() => new LibGit2Repo(this.tracer, this.enlistment.WorkingDirectoryRoot)), @@ -37,6 +38,15 @@ namespace GVFS.Common.Git this.GVFSLock = new GVFSLock(tracer); } + private enum LooseBlobState + { + Invalid, + Missing, + Exists, + Corrupt, + Unknown, + } + public GVFSLock GVFSLock { get; @@ -50,87 +60,18 @@ namespace GVFS.Common.Git public virtual bool TryCopyBlobContentStream(string blobSha, Action writeAction) { - string blobPath = Path.Combine( - this.enlistment.GitObjectsRoot, - blobSha.Substring(0, 2), - blobSha.Substring(2)); + LooseBlobState state = this.GetLooseBlobState(blobSha, writeAction, out long size); - bool corruptLooseObject = false; - try + if (state == LooseBlobState.Exists) { - if (this.fileSystem.FileExists(blobPath)) - { - using (Stream file = this.fileSystem.OpenFileStream(blobPath, FileMode.Open, FileAccess.Read, FileShare.Read, callFlushFileBuffers: false)) - { - // The DeflateStream header starts 2 bytes into the gzip header, but they are otherwise compatible - file.Position = 2; - using (DeflateStream deflate = new DeflateStream(file, CompressionMode.Decompress)) - { - long size; - if (!ReadLooseObjectHeader(deflate, out size)) - { - corruptLooseObject = true; - return false; - } - - writeAction(deflate, size); - return true; - } - } - } + return true; } - catch (InvalidDataException ex) + else if (state != LooseBlobState.Missing) { - corruptLooseObject = true; - - EventMetadata metadata = new EventMetadata(); - metadata.Add("blobPath", blobPath); - metadata.Add("Exception", ex.ToString()); - this.tracer.RelatedWarning(metadata, "TryCopyBlobContentStream: Failed to stream blob (InvalidDataException)", Keywords.Telemetry); - return false; } - catch (IOException ex) - { - EventMetadata metadata = new EventMetadata(); - metadata.Add("blobPath", blobPath); - metadata.Add("Exception", ex.ToString()); - this.tracer.RelatedWarning(metadata, "TryCopyBlobContentStream: Failed to stream blob from disk", Keywords.Telemetry); - return false; - } - finally - { - if (corruptLooseObject) - { - string corruptBlobsFolderPath = Path.Combine(this.enlistment.EnlistmentRoot, GVFSConstants.DotGVFS.CorruptObjectsPath); - string corruptBlobPath = Path.Combine(corruptBlobsFolderPath, Path.GetRandomFileName()); - - EventMetadata metadata = new EventMetadata(); - metadata.Add("blobPath", blobPath); - metadata.Add("corruptBlobPath", corruptBlobPath); - metadata.Add(TracingConstants.MessageKey.InfoMessage, "TryCopyBlobContentStream: Renaming corrupt loose object"); - this.tracer.RelatedEvent(EventLevel.Informational, "TryCopyBlobContentStream_RenameCorruptObject", metadata); - - try - { - this.fileSystem.CreateDirectory(corruptBlobsFolderPath); - File.Move(blobPath, corruptBlobPath); - } - catch (Exception e) - { - metadata = new EventMetadata(); - metadata.Add("blobPath", blobPath); - metadata.Add("blobBackupPath", corruptBlobPath); - metadata.Add("Exception", e.ToString()); - metadata.Add(TracingConstants.MessageKey.WarningMessage, "TryCopyBlobContentStream: Failed to rename corrupt loose object"); - this.tracer.RelatedEvent(EventLevel.Warning, "TryCopyBlobContentStream_RenameCorruptObjectFailed", metadata, Keywords.Telemetry); - } - } - } - - bool copyBlobResult; - if (!this.libgit2RepoPool.TryInvoke(repo => repo.TryCopyBlob(blobSha, writeAction), out copyBlobResult)) + if (!this.libgit2RepoPool.TryInvoke(repo => repo.TryCopyBlob(blobSha, writeAction), out bool copyBlobResult)) { return false; } @@ -152,37 +93,16 @@ namespace GVFS.Common.Git return output; } + /// + /// Try to find the size of a given blob by SHA1 hash. + /// + /// Returns true iff the blob exists as a loose object. + /// public virtual bool TryGetBlobLength(string blobSha, out long size) { - long? output; - - if (!this.libgit2RepoPool.TryInvoke( - repo => - { - long value; - if (repo.TryGetObjectSize(blobSha, out value)) - { - return value; - } - - return null; - }, - out output)) - { - size = 0; - return false; - } - - if (output.HasValue) - { - size = output.Value; - return true; - } - - size = 0; - return false; + return this.GetLooseBlobState(blobSha, null, out size) == LooseBlobState.Exists; } - + public void Dispose() { if (this.libgit2RepoPool != null) @@ -190,12 +110,6 @@ namespace GVFS.Common.Git this.libgit2RepoPool.Dispose(); this.libgit2RepoPool = null; } - - if (this.GVFSLock != null) - { - this.GVFSLock.Dispose(); - this.GVFSLock = null; - } } private static bool ReadLooseObjectHeader(Stream input, out long size) @@ -227,5 +141,106 @@ namespace GVFS.Common.Git return true; } + + private LooseBlobState GetLooseBlobStateAtPath(string blobPath, Action writeAction, out long size) + { + bool corruptLooseObject = false; + try + { + if (this.fileSystem.FileExists(blobPath)) + { + using (Stream file = this.fileSystem.OpenFileStream(blobPath, FileMode.Open, FileAccess.Read, FileShare.Read, callFlushFileBuffers: false)) + { + // The DeflateStream header starts 2 bytes into the gzip header, but they are otherwise compatible + file.Position = 2; + using (DeflateStream deflate = new DeflateStream(file, CompressionMode.Decompress)) + { + if (!ReadLooseObjectHeader(deflate, out size)) + { + corruptLooseObject = true; + return LooseBlobState.Corrupt; + } + + writeAction?.Invoke(deflate, size); + return LooseBlobState.Exists; + } + } + } + + size = -1; + return LooseBlobState.Missing; + } + catch (InvalidDataException ex) + { + corruptLooseObject = true; + + EventMetadata metadata = new EventMetadata(); + metadata.Add("blobPath", blobPath); + metadata.Add("Exception", ex.ToString()); + this.tracer.RelatedWarning(metadata, nameof(this.GetLooseBlobStateAtPath) + ": Failed to stream blob (InvalidDataException)", Keywords.Telemetry); + + size = -1; + return LooseBlobState.Corrupt; + } + catch (IOException ex) + { + EventMetadata metadata = new EventMetadata(); + metadata.Add("blobPath", blobPath); + metadata.Add("Exception", ex.ToString()); + this.tracer.RelatedWarning(metadata, nameof(this.GetLooseBlobStateAtPath) + ": Failed to stream blob from disk", Keywords.Telemetry); + + size = -1; + return LooseBlobState.Unknown; + } + finally + { + if (corruptLooseObject) + { + string corruptBlobsFolderPath = Path.Combine(this.enlistment.EnlistmentRoot, GVFSConstants.DotGVFS.CorruptObjectsPath); + string corruptBlobPath = Path.Combine(corruptBlobsFolderPath, Path.GetRandomFileName()); + + EventMetadata metadata = new EventMetadata(); + metadata.Add("blobPath", blobPath); + metadata.Add("corruptBlobPath", corruptBlobPath); + metadata.Add(TracingConstants.MessageKey.InfoMessage, nameof(this.GetLooseBlobStateAtPath) + ": Renaming corrupt loose object"); + this.tracer.RelatedEvent(EventLevel.Informational, nameof(this.GetLooseBlobStateAtPath) + "_RenameCorruptObject", metadata); + + try + { + this.fileSystem.CreateDirectory(corruptBlobsFolderPath); + this.fileSystem.MoveFile(blobPath, corruptBlobPath); + } + catch (Exception e) + { + metadata = new EventMetadata(); + metadata.Add("blobPath", blobPath); + metadata.Add("blobBackupPath", corruptBlobPath); + metadata.Add("Exception", e.ToString()); + metadata.Add(TracingConstants.MessageKey.WarningMessage, nameof(this.GetLooseBlobStateAtPath) + ": Failed to rename corrupt loose object"); + this.tracer.RelatedEvent(EventLevel.Warning, nameof(this.GetLooseBlobStateAtPath) + "_RenameCorruptObjectFailed", metadata, Keywords.Telemetry); + } + } + } + } + + private LooseBlobState GetLooseBlobState(string blobSha, Action writeAction, out long size) + { + string blobPath = Path.Combine( + this.enlistment.GitObjectsRoot, + blobSha.Substring(0, 2), + blobSha.Substring(2)); + + LooseBlobState state = this.GetLooseBlobStateAtPath(blobPath, writeAction, out size); + if (state == LooseBlobState.Missing) + { + blobPath = Path.Combine( + this.enlistment.LocalObjectsRoot, + blobSha.Substring(0, 2), + blobSha.Substring(2)); + state = this.GetLooseBlobStateAtPath(blobPath, writeAction, out size); + } + + return state; + } } } diff --git a/GVFS/GVFS.Common/Git/Sha1Id.cs b/GVFS/GVFS.Common/Git/Sha1Id.cs new file mode 100644 index 00000000..e1df38ea --- /dev/null +++ b/GVFS/GVFS.Common/Git/Sha1Id.cs @@ -0,0 +1,186 @@ +using System; +using System.Runtime.InteropServices; + +namespace GVFS.Common.Git +{ + [StructLayout(LayoutKind.Explicit, Size = ShaBufferLength, Pack = 1)] + public struct Sha1Id + { + private const int ShaBufferLength = (2 * sizeof(ulong)) + sizeof(uint); + private const int ShaStringLength = 2 * ShaBufferLength; + + [FieldOffset(0)] + private ulong shaBytes1Through8; + + [FieldOffset(8)] + private ulong shaBytes9Through16; + + [FieldOffset(16)] + private uint shaBytes17Through20; + + public Sha1Id(ulong shaBytes1Through8, ulong shaBytes9Through16, uint shaBytes17Through20) + { + this.shaBytes1Through8 = shaBytes1Through8; + this.shaBytes9Through16 = shaBytes9Through16; + this.shaBytes17Through20 = shaBytes17Through20; + } + + public Sha1Id(string sha) + { + if (sha == null) + { + throw new ArgumentNullException(nameof(sha)); + } + + if (sha.Length != ShaStringLength) + { + throw new ArgumentException($"Must be length {ShaStringLength}", nameof(sha)); + } + + this.shaBytes1Through8 = ShaSubStringToULong(sha.Substring(0, 16)); + this.shaBytes9Through16 = ShaSubStringToULong(sha.Substring(16, 16)); + this.shaBytes17Through20 = ShaSubStringToUInt(sha.Substring(32, 8)); + } + + public static void ShaBufferToParts( + byte[] shaBuffer, + out ulong shaBytes1Through8, + out ulong shaBytes9Through16, + out uint shaBytes17Through20) + { + if (shaBuffer == null) + { + throw new ArgumentNullException(nameof(shaBuffer)); + } + + if (shaBuffer.Length != ShaBufferLength) + { + throw new ArgumentException($"Must be length {ShaBufferLength}", nameof(shaBuffer)); + } + + unsafe + { + fixed (byte* firstChunk = &shaBuffer[0], secondChunk = &shaBuffer[sizeof(ulong)], thirdChunk = &shaBuffer[sizeof(ulong) * 2]) + { + shaBytes1Through8 = *(ulong*)firstChunk; + shaBytes9Through16 = *(ulong*)secondChunk; + shaBytes17Through20 = *(uint*)thirdChunk; + } + } + } + + public void ToBuffer(byte[] shaBuffer) + { + unsafe + { + fixed (byte* firstChunk = &shaBuffer[0], secondChunk = &shaBuffer[sizeof(ulong)], thirdChunk = &shaBuffer[sizeof(ulong) * 2]) + { + *(ulong*)firstChunk = this.shaBytes1Through8; + *(ulong*)secondChunk = this.shaBytes9Through16; + *(uint*)thirdChunk = this.shaBytes17Through20; + } + } + } + + public override string ToString() + { + char[] shaString = new char[ShaStringLength]; + BytesToCharArray(shaString, 0, this.shaBytes1Through8, sizeof(ulong)); + BytesToCharArray(shaString, 2 * sizeof(ulong), this.shaBytes9Through16, sizeof(ulong)); + BytesToCharArray(shaString, 2 * (2 * sizeof(ulong)), this.shaBytes17Through20, sizeof(uint)); + return new string(shaString, 0, shaString.Length); + } + + private static void BytesToCharArray(char[] shaString, int startIndex, ulong shaBytes, int numBytes) + { + byte b; + int firstArrayIndex; + for (int i = 0; i < numBytes; ++i) + { + b = (byte)(shaBytes >> (i * 8)); + firstArrayIndex = startIndex + (i * 2); + shaString[firstArrayIndex] = GetHexValue(b / 16); + shaString[firstArrayIndex + 1] = GetHexValue(b % 16); + } + } + + private static ulong ShaSubStringToULong(string shaSubString) + { + if (shaSubString == null) + { + throw new ArgumentNullException(nameof(shaSubString)); + } + + if (shaSubString.Length != sizeof(ulong) * 2) + { + throw new ArgumentException($"Must be length {sizeof(ulong) * 2}", nameof(shaSubString)); + } + + ulong bytes = 0; + string upperCaseSha = shaSubString.ToUpper(); + int stringIndex = 0; + for (int i = 0; i < sizeof(ulong); ++i) + { + stringIndex = i * 2; + char firstChar = shaSubString[stringIndex]; + char secondChar = shaSubString[stringIndex + 1]; + byte nextByte = (byte)(CharToByte(firstChar) << 4 | CharToByte(secondChar)); + bytes = bytes | ((ulong)nextByte << (i * 8)); + } + + return bytes; + } + + private static uint ShaSubStringToUInt(string shaSubString) + { + if (shaSubString == null) + { + throw new ArgumentNullException(nameof(shaSubString)); + } + + if (shaSubString.Length != sizeof(uint) * 2) + { + throw new ArgumentException($"Must be length {sizeof(uint) * 2}", nameof(shaSubString)); + } + + uint bytes = 0; + string upperCaseSha = shaSubString.ToUpper(); + int stringIndex = 0; + for (int i = 0; i < sizeof(uint); ++i) + { + stringIndex = i * 2; + char firstChar = shaSubString[stringIndex]; + char secondChar = shaSubString[stringIndex + 1]; + byte nextByte = (byte)(CharToByte(firstChar) << 4 | CharToByte(secondChar)); + bytes = bytes | ((uint)nextByte << (i * 8)); + } + + return bytes; + } + + private static char GetHexValue(int i) + { + if (i < 10) + { + return (char)(i + '0'); + } + + return (char)(i - 10 + 'A'); + } + + private static byte CharToByte(char c) + { + if (c >= '0' && c <= '9') + { + return (byte)(c - '0'); + } + + if (c >= 'A' && c <= 'F') + { + return (byte)(10 + (c - 'A')); + } + + throw new ArgumentException($"Invalid character {c}", nameof(c)); + } + } +} diff --git a/GVFS/GVFS.Common/GitCommandLineParser.cs b/GVFS/GVFS.Common/GitCommandLineParser.cs index 8cf1911f..7270c9f5 100644 --- a/GVFS/GVFS.Common/GitCommandLineParser.cs +++ b/GVFS/GVFS.Common/GitCommandLineParser.cs @@ -36,12 +36,11 @@ namespace GVFS.Common Other = 1 << 0, AddOrStage = 1 << 1, Checkout = 1 << 2, - Clean = 1 << 3, - Commit = 1 << 4, - Move = 1 << 5, - Reset = 1 << 6, - Status = 1 << 8, - UpdateIndex = 1 << 9, + Commit = 1 << 3, + Move = 1 << 4, + Reset = 1 << 5, + Status = 1 << 6, + UpdateIndex = 1 << 7, } public bool IsValidGitCommand @@ -58,11 +57,6 @@ namespace GVFS.Common !this.HasArgument("--merge"); } - public bool IsResetHard() - { - return this.IsVerb(Verbs.Reset) && this.HasArgument("--hard"); - } - /// /// This method currently just makes a best effort to detect file paths. Only use this method for optional optimizations /// related to file paths. Do NOT use this method if you require a reliable answer. @@ -117,7 +111,6 @@ namespace GVFS.Common { case "add": return Verbs.AddOrStage; case "checkout": return Verbs.Checkout; - case "clean": return Verbs.Clean; case "commit": return Verbs.Commit; case "mv": return Verbs.Move; case "reset": return Verbs.Reset; diff --git a/GVFS/GVFS.Common/Http/CacheServerInfo.cs b/GVFS/GVFS.Common/Http/CacheServerInfo.cs index 3de5b6ba..cd1dc173 100644 --- a/GVFS/GVFS.Common/Http/CacheServerInfo.cs +++ b/GVFS/GVFS.Common/Http/CacheServerInfo.cs @@ -7,6 +7,7 @@ namespace GVFS.Common.Http { private const string ObjectsEndpointSuffix = "/gvfs/objects"; private const string PrefetchEndpointSuffix = "/gvfs/prefetch"; + private const string SizesEndpointSuffix = "/gvfs/sizes"; [JsonConstructor] public CacheServerInfo(string url, string name, bool globalDefault = false) @@ -19,6 +20,7 @@ namespace GVFS.Common.Http { this.ObjectsEndpointUrl = this.Url + ObjectsEndpointSuffix; this.PrefetchEndpointUrl = this.Url + PrefetchEndpointSuffix; + this.SizesEndpointUrl = this.Url + SizesEndpointSuffix; } } @@ -28,6 +30,7 @@ namespace GVFS.Common.Http public string ObjectsEndpointUrl { get; } public string PrefetchEndpointUrl { get; } + public string SizesEndpointUrl { get; } public bool HasValidUrl() { diff --git a/GVFS/GVFS.Common/Http/CacheServerResolver.cs b/GVFS/GVFS.Common/Http/CacheServerResolver.cs index a0cab619..ea39119d 100644 --- a/GVFS/GVFS.Common/Http/CacheServerResolver.cs +++ b/GVFS/GVFS.Common/Http/CacheServerResolver.cs @@ -7,9 +7,6 @@ namespace GVFS.Common.Http { public class CacheServerResolver { - private const string CacheServerConfigName = "gvfs.cache-server"; - private const string DeprecatedCacheEndpointGitConfigSuffix = ".cache-server-url"; - private ITracer tracer; private Enlistment enlistment; @@ -35,7 +32,7 @@ namespace GVFS.Common.Http // TODO 1057500: Remove support for encoded-repo-url cache config setting return - GetValueFromConfig(git, CacheServerConfigName, localOnly: true) + GetValueFromConfig(git, GVFSConstants.GitConfig.CacheServer, localOnly: true) ?? GetValueFromConfig(git, GetDeprecatedCacheConfigSettingName(enlistment), localOnly: false) ?? enlistment.RepoUrl; } @@ -126,7 +123,7 @@ namespace GVFS.Common.Http public bool TrySaveUrlToLocalConfig(CacheServerInfo cache, out string error) { GitProcess git = this.enlistment.CreateGitProcess(); - GitProcess.Result result = git.SetInLocalConfig(CacheServerConfigName, cache.Url, replaceAll: true); + GitProcess.Result result = git.SetInLocalConfig(GVFSConstants.GitConfig.CacheServer, cache.Url, replaceAll: true); error = result.Errors; return !result.HasErrors; @@ -159,7 +156,7 @@ namespace GVFS.Common.Http .Replace("http://", string.Empty) .Replace('/', '.'); - return GVFSConstants.GitConfig.GVFSPrefix + sectionUrl + DeprecatedCacheEndpointGitConfigSuffix; + return GVFSConstants.GitConfig.GVFSPrefix + sectionUrl + GVFSConstants.GitConfig.DeprecatedCacheEndpointSuffix; } private CacheServerInfo CreateNone() diff --git a/GVFS/GVFS.Common/Http/GitEndPointResponseData.cs b/GVFS/GVFS.Common/Http/GitEndPointResponseData.cs index 44547532..e5fe8edb 100644 --- a/GVFS/GVFS.Common/Http/GitEndPointResponseData.cs +++ b/GVFS/GVFS.Common/Http/GitEndPointResponseData.cs @@ -54,6 +54,11 @@ namespace GVFS.Common.Http /// public string RetryableReadToEnd() { + if (this.Stream == null) + { + throw new RetryableException("Stream is null (this could be a result of network flakiness), retrying."); + } + using (StreamReader contentStreamReader = new StreamReader(this.Stream)) { try diff --git a/GVFS/GVFS.Common/Http/GitObjectsHttpRequestor.cs b/GVFS/GVFS.Common/Http/GitObjectsHttpRequestor.cs index 8c6b0edb..6618d27c 100644 --- a/GVFS/GVFS.Common/Http/GitObjectsHttpRequestor.cs +++ b/GVFS/GVFS.Common/Http/GitObjectsHttpRequestor.cs @@ -18,7 +18,9 @@ namespace GVFS.Common.Http = new MediaTypeWithQualityHeaderValue(GVFSConstants.MediaTypes.CustomLooseObjectsMediaType); private Enlistment enlistment; - + + private DateTime nextCacheServerAttemptTime = DateTime.Now; + public GitObjectsHttpRequestor(ITracer tracer, Enlistment enlistment, CacheServerInfo cacheServer, RetryConfig retryConfig) : base(tracer, retryConfig, enlistment.Authentication) { @@ -33,7 +35,8 @@ namespace GVFS.Common.Http long requestId = HttpRequestor.GetNewRequestId(); string objectIdsJson = ToJsonList(objectIds); - Uri gvfsEndpoint = new Uri(this.enlistment.RepoUrl + GVFSConstants.Endpoints.GVFSSizes); + Uri cacheServerEndpoint = new Uri(this.CacheServer.SizesEndpointUrl); + Uri originEndpoint = new Uri(this.enlistment.RepoUrl + GVFSConstants.Endpoints.GVFSSizes); EventMetadata metadata = new EventMetadata(); metadata.Add("RequestId", requestId); @@ -55,8 +58,24 @@ namespace GVFS.Common.Http RetryWrapper>.InvocationResult requestTask = retrier.Invoke( tryCount => { + Uri gvfsEndpoint; + if (nextCacheServerAttemptTime < DateTime.Now) + { + gvfsEndpoint = cacheServerEndpoint; + } + else + { + gvfsEndpoint = originEndpoint; + } + using (GitEndPointResponseData response = this.SendRequest(requestId, gvfsEndpoint, HttpMethod.Post, objectIdsJson, cancellationToken)) { + if (response.StatusCode == HttpStatusCode.NotFound) + { + nextCacheServerAttemptTime = nextCacheServerAttemptTime.AddMinutes(15); + return new RetryWrapper>.CallbackResult(response.Error, true); + } + if (response.HasErrors) { return new RetryWrapper>.CallbackResult(response.Error, response.ShouldRetry); diff --git a/GVFS/GVFS.Common/Http/HttpRequestor.cs b/GVFS/GVFS.Common/Http/HttpRequestor.cs index 15d718ef..b7580590 100644 --- a/GVFS/GVFS.Common/Http/HttpRequestor.cs +++ b/GVFS/GVFS.Common/Http/HttpRequestor.cs @@ -83,6 +83,11 @@ namespace GVFS.Common.Http } HttpRequestMessage request = new HttpRequestMessage(httpMethod, requestUri); + + // By default, VSTS auth failures result in redirects to SPS to reauthenticate. + // To provide more consistent behavior when using the GCM, have them send us 401s instead + request.Headers.Add("X-TFS-FedAuthRedirect", "Suppress"); + request.Headers.UserAgent.Add(this.userAgentHeader); if (!string.IsNullOrEmpty(authString)) @@ -148,25 +153,22 @@ namespace GVFS.Common.Http errorMessage = response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); int statusInt = (int)response.StatusCode; - if (string.IsNullOrWhiteSpace(errorMessage)) + if (response.StatusCode == HttpStatusCode.Unauthorized || response.StatusCode == HttpStatusCode.BadRequest || response.StatusCode == HttpStatusCode.Redirect) { - if (response.StatusCode == HttpStatusCode.Unauthorized) + this.authentication.Revoke(authString); + if (!this.authentication.IsBackingOff) { - this.authentication.Revoke(authString); - if (!this.authentication.IsBackingOff) - { - errorMessage = "Server returned error code 401 (Unauthorized). Your PAT may be expired and we are asking for a new one."; - } - else - { - errorMessage = "Server returned error code 401 (Unauthorized) after successfully renewing your PAT. You may not have access to this repo"; - } + errorMessage = string.Format("Server returned error code {0} ({1}). Your PAT may be expired and we are asking for a new one. Original error message from server: {2}", statusInt, response.StatusCode, errorMessage); } else { - errorMessage = string.Format("Server returned error code {0} ({1})", statusInt, response.StatusCode); + errorMessage = string.Format("Server returned error code {0} ({1}) after successfully renewing your PAT. You may not have access to this repo. Original error message from server: {2}", statusInt, response.StatusCode, errorMessage); } } + else + { + errorMessage = string.Format("Server returned error code {0} ({1}). Original error message from server: {2}", statusInt, response.StatusCode, errorMessage); + } gitEndPointResponseData = new GitEndPointResponseData( response.StatusCode, diff --git a/GVFS/GVFS.Common/LocalCacheResolver.cs b/GVFS/GVFS.Common/LocalCacheResolver.cs index 5cc3595b..bee4e9f6 100644 --- a/GVFS/GVFS.Common/LocalCacheResolver.cs +++ b/GVFS/GVFS.Common/LocalCacheResolver.cs @@ -38,7 +38,7 @@ namespace GVFS.Common else { string pathRoot; - if (!Paths.TryGetPathRoot(enlistment.EnlistmentRoot, out pathRoot, out errorMessage)) + if (!Paths.TryGetFinalPathRoot(enlistment.EnlistmentRoot, out pathRoot, out errorMessage)) { return false; } diff --git a/GVFS/GVFS.Common/NamedPipes/LockNamedPipeMessages.cs b/GVFS/GVFS.Common/NamedPipes/LockNamedPipeMessages.cs index f0b431cd..25fd0757 100644 --- a/GVFS/GVFS.Common/NamedPipes/LockNamedPipeMessages.cs +++ b/GVFS/GVFS.Common/NamedPipes/LockNamedPipeMessages.cs @@ -22,6 +22,7 @@ namespace GVFS.Common.NamedPipes public const string DenyGVFSResult = "LockDeniedGVFS"; public const string DenyGitResult = "LockDeniedGit"; public const string AcceptResult = "LockAcquired"; + public const string AvailableResult = "LockAvailable"; public const string MountNotReadyResult = "MountNotReady"; public const string UnmountInProgressResult = "UnmountInProgress"; @@ -234,9 +235,9 @@ namespace GVFS.Common.NamedPipes this.RequestData = LockData.FromBody(messageBody); } - public LockRequest(int pid, bool isElevated, string parsedCommand) + public LockRequest(int pid, bool isElevated, bool checkAvailabilityOnly, string parsedCommand) { - this.RequestData = new LockData(pid, isElevated, parsedCommand); + this.RequestData = new LockData(pid, isElevated, checkAvailabilityOnly, parsedCommand); } public LockData RequestData { get; } @@ -249,10 +250,11 @@ namespace GVFS.Common.NamedPipes public class LockData { - public LockData(int pid, bool isElevated, string parsedCommand) + public LockData(int pid, bool isElevated, bool checkAvailabilityOnly, string parsedCommand) { this.PID = pid; this.IsElevated = isElevated; + this.CheckAvailabilityOnly = checkAvailabilityOnly; this.ParsedCommand = parsedCommand; } @@ -260,6 +262,12 @@ namespace GVFS.Common.NamedPipes public bool IsElevated { get; set; } + /// + /// Should the command actually acquire the GVFSLock or + /// only check if the lock is available. + /// + public bool CheckAvailabilityOnly { get; set; } + /// /// The command line requesting the lock, built internally for parsing purposes. /// e.g. "git status", "git rebase" @@ -278,27 +286,32 @@ namespace GVFS.Common.NamedPipes string[] dataParts = body.Split(MessageSeparator); int pid; bool isElevated = false; + bool checkAvailabilityOnly = false; string parsedCommand = null; - if (dataParts.Length > 0) + if (dataParts.Length != 4) { - if (!int.TryParse(dataParts[0], out pid)) - { - throw new InvalidOperationException("Invalid lock message. Expected PID, got: " + dataParts[0]); - } - - if (dataParts.Length > 1) - { - bool.TryParse(dataParts[1], out isElevated); - } - - if (dataParts.Length > 2) - { - parsedCommand = dataParts[2]; - } - - return new LockData(pid, isElevated, parsedCommand); + throw new InvalidOperationException("Invalid lock message. Expected 4 parts, got: {0}" + dataParts.Length); } + + if (!int.TryParse(dataParts[0], out pid)) + { + throw new InvalidOperationException("Invalid lock message. Expected PID, got: " + dataParts[0]); + } + + if (!bool.TryParse(dataParts[1], out isElevated)) + { + throw new InvalidOperationException("Invalid lock message. Expected bool for isElevated, got: " + dataParts[1]); + } + + if (!bool.TryParse(dataParts[2], out checkAvailabilityOnly)) + { + throw new InvalidOperationException("Invalid lock message. Expected bool for checkAvailabilityOnly, got: " + dataParts[2]); + } + + parsedCommand = dataParts[3]; + + return new LockData(pid, isElevated, checkAvailabilityOnly, parsedCommand); } return null; @@ -306,7 +319,7 @@ namespace GVFS.Common.NamedPipes internal string ToMessage() { - return string.Join(MessageSeparator.ToString(), this.PID, this.IsElevated, this.ParsedCommand); + return string.Join(MessageSeparator.ToString(), this.PID, this.IsElevated, this.CheckAvailabilityOnly, this.ParsedCommand); } } diff --git a/GVFS/GVFS.Common/NamedPipes/NamedPipeMessages.cs b/GVFS/GVFS.Common/NamedPipes/NamedPipeMessages.cs index f687c856..737695ed 100644 --- a/GVFS/GVFS.Common/NamedPipes/NamedPipeMessages.cs +++ b/GVFS/GVFS.Common/NamedPipes/NamedPipeMessages.cs @@ -44,7 +44,7 @@ namespace GVFS.Common.NamedPipes public string CacheServer { get; set; } public int BackgroundOperationCount { get; set; } public string LockStatus { get; set; } - public int DiskLayoutVersion { get; set; } + public string DiskLayoutVersion { get; set; } public static Response FromJson(string json) { @@ -180,15 +180,15 @@ namespace GVFS.Common.NamedPipes } } - public class AttachGvFltRequest + public class EnableAndAttachProjFSRequest { - public const string Header = nameof(AttachGvFltRequest); + public const string Header = nameof(EnableAndAttachProjFSRequest); public string EnlistmentRoot { get; set; } - public static AttachGvFltRequest FromMessage(Message message) + public static EnableAndAttachProjFSRequest FromMessage(Message message) { - return JsonConvert.DeserializeObject(message.Body); + return JsonConvert.DeserializeObject(message.Body); } public Message ToMessage() @@ -196,7 +196,7 @@ namespace GVFS.Common.NamedPipes return new Message(Header, JsonConvert.SerializeObject(this)); } - public class Response : BaseResponse + public class Response : BaseResponse { public static Response FromMessage(Message message) { diff --git a/GVFS/GVFS.Common/NativeMethods.cs b/GVFS/GVFS.Common/NativeMethods.cs index aedf40d1..23fd2c0d 100644 --- a/GVFS/GVFS.Common/NativeMethods.cs +++ b/GVFS/GVFS.Common/NativeMethods.cs @@ -107,12 +107,12 @@ namespace GVFS.Common public static SafeFileHandle LockDirectory(string path) { SafeFileHandle result = CreateFile( - path, - FileAccess.GENERIC_READ, - FileShare.Read, - IntPtr.Zero, - FileMode.Open, - FileAttributes.FILE_FLAG_BACKUP_SEMANTICS | FileAttributes.FILE_FLAG_OPEN_REPARSE_POINT, + path, + FileAccess.GENERIC_READ, + FileShare.Read, + IntPtr.Zero, + FileMode.Open, + FileAttributes.FILE_FLAG_BACKUP_SEMANTICS | FileAttributes.FILE_FLAG_OPEN_REPARSE_POINT, IntPtr.Zero); if (result.IsInvalid) { @@ -235,6 +235,27 @@ namespace GVFS.Common } } + /// + /// Get the build number of the OS + /// + /// Build number + /// + /// For this method to work correctly, the calling application must have a manifest file + /// that indicates the application supports Windows 10. + /// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx for details + /// + public static uint GetWindowsBuildNumber() + { + OSVersionInfo versionInfo = new OSVersionInfo(); + versionInfo.OSVersionInfoSize = (uint)Marshal.SizeOf(versionInfo); + if (!GetVersionEx(ref versionInfo)) + { + ThrowLastWin32Exception(); + } + + return versionInfo.BuildNumber; + } + private static void ThrowLastWin32Exception() { throw new Win32Exception(Marshal.GetLastWin32Error()); @@ -259,8 +280,8 @@ namespace GVFS.Common [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] private static extern int GetFinalPathNameByHandle( SafeFileHandle hFile, - [Out] StringBuilder lpszFilePath, - int cchFilePath, + [Out] StringBuilder lpszFilePath, + int cchFilePath, int dwFlags); [DllImport("kernel32.dll", SetLastError = true)] @@ -284,6 +305,9 @@ namespace GVFS.Common [In, Out] ref EventTraceProperties properties, [In] uint controlCode); + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern bool GetVersionEx([In, Out] ref OSVersionInfo versionInfo); + [StructLayout(LayoutKind.Sequential)] private struct WNodeHeader { @@ -326,5 +350,18 @@ namespace GVFS.Common [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)] public string LogFileName; } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct OSVersionInfo + { + public uint OSVersionInfoSize; + public uint MajorVersion; + public uint MinorVersion; + public uint BuildNumber; + public uint PlatformId; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)] + public string CSDVersion; + } } } diff --git a/GVFS/GVFS.Common/Paths.cs b/GVFS/GVFS.Common/Paths.cs index 0f1d8d9a..aa4b1339 100644 --- a/GVFS/GVFS.Common/Paths.cs +++ b/GVFS/GVFS.Common/Paths.cs @@ -5,7 +5,7 @@ namespace GVFS.Common { public static partial class Paths { - public static bool TryGetPathRoot(string path, out string pathRoot, out string errorMessage) + public static bool TryGetFinalPathRoot(string path, out string pathRoot, out string errorMessage) { pathRoot = null; errorMessage = null; diff --git a/GVFS/GVFS.Common/PlaceholderListDatabase.cs b/GVFS/GVFS.Common/PlaceholderListDatabase.cs index adbe66f3..48971b23 100644 --- a/GVFS/GVFS.Common/PlaceholderListDatabase.cs +++ b/GVFS/GVFS.Common/PlaceholderListDatabase.cs @@ -141,7 +141,7 @@ namespace GVFS.Common private IEnumerable GenerateDataLines(IEnumerable updatedPlaceholders) { - HashSet keys = new HashSet(); + HashSet keys = new HashSet(StringComparer.OrdinalIgnoreCase); this.EstimatedCount = 0; foreach (PlaceholderData updated in updatedPlaceholders) @@ -226,6 +226,11 @@ namespace GVFS.Common public string Path { get; } public string Sha { get; } + + public bool IsFolder + { + get { return this.Sha == GVFSConstants.AllZeroSha; } + } } private class PlaceholderDataEntry diff --git a/GVFS/GVFS.Common/ProcessHelper.cs b/GVFS/GVFS.Common/ProcessHelper.cs index 364438fd..e0624ed4 100644 --- a/GVFS/GVFS.Common/ProcessHelper.cs +++ b/GVFS/GVFS.Common/ProcessHelper.cs @@ -170,6 +170,26 @@ namespace GVFS.Common return value as string; } + public static bool TrySetDwordInRegistry(RegistryHive registryHive, string key, string valueName, uint value) + { + RegistryKey localKey = RegistryKey.OpenBaseKey(registryHive, RegistryView.Registry64); + RegistryKey localKeySub = localKey.OpenSubKey(key, writable: true); + + if (localKeySub == null) + { + localKey = RegistryKey.OpenBaseKey(registryHive, RegistryView.Registry32); + localKeySub = localKey.OpenSubKey(key, writable: true); + } + + if (localKeySub == null) + { + return false; + } + + localKeySub.SetValue(valueName, value, RegistryValueKind.DWord); + return true; + } + private static object GetValueFromRegistry(RegistryHive registryHive, string key, string valueName, RegistryView view) { RegistryKey localKey = RegistryKey.OpenBaseKey(registryHive, view); diff --git a/GVFS/GVFS.Common/RepoMetadata.cs b/GVFS/GVFS.Common/RepoMetadata.cs index 9513159b..79ba87ec 100644 --- a/GVFS/GVFS.Common/RepoMetadata.cs +++ b/GVFS/GVFS.Common/RepoMetadata.cs @@ -1,5 +1,6 @@ using GVFS.Common.FileSystem; using GVFS.Common.Tracing; +using Microsoft.Diagnostics.Tracing; using System; using System.Collections.Generic; using System.IO; @@ -9,13 +10,30 @@ namespace GVFS.Common public class RepoMetadata { private FileBasedDictionary repoMetadata; + private ITracer tracer; - private RepoMetadata() + private RepoMetadata(ITracer tracer) { + this.tracer = tracer; } public static RepoMetadata Instance { get; private set; } + public string EnlistmentId + { + get + { + string value; + if (!this.repoMetadata.TryGetValue(Keys.EnlistmentId, out value)) + { + value = CreateNewEnlistmentId(this.tracer); + this.repoMetadata.SetValueAndFlush(Keys.EnlistmentId, value); + } + + return value; + } + } + public string DataFilePath { get { return this.repoMetadata.DataFilePath; } @@ -37,7 +55,7 @@ namespace GVFS.Common } else { - Instance = new RepoMetadata(); + Instance = new RepoMetadata(tracer); if (!FileBasedDictionary.TryCreate( tracer, dictionaryPath, @@ -67,29 +85,42 @@ namespace GVFS.Common } } - public int GetCurrentDiskLayoutVersion() + public string GetCurrentDiskLayoutVersion() { - return DiskLayoutVersion.CurrentDiskLayoutVersion; + return string.Format( + "{0}.{1}", + DiskLayoutVersion.CurrentMajorVersion, + DiskLayoutVersion.CurrentMinorVersion); } - public bool TryGetOnDiskLayoutVersion(out int version, out string error) + public bool TryGetOnDiskLayoutVersion(out int majorVersion, out int minorVersion, out string error) { - version = -1; + majorVersion = 0; + minorVersion = 0; try { string value; - if (!this.repoMetadata.TryGetValue(Keys.DiskLayoutVersion, out value)) + if (!this.repoMetadata.TryGetValue(Keys.DiskLayoutMajorVersion, out value)) { - error = DiskLayoutVersion.MissingVersionError; + error = "Enlistment disk layout version not found, check if a breaking change has been made to GVFS since cloning this enlistment."; return false; } - if (!int.TryParse(value, out version)) + if (!int.TryParse(value, out majorVersion)) { error = "Failed to parse persisted disk layout version number: " + value; return false; } + + // The minor version is optional, e.g. it could be missing during an upgrade + if (this.repoMetadata.TryGetValue(Keys.DiskLayoutMinorVersion, out value)) + { + if (!int.TryParse(value, out minorVersion)) + { + minorVersion = 0; + } + } } catch (FileBasedCollectionException ex) { @@ -101,14 +132,17 @@ namespace GVFS.Common return true; } - public void SaveCloneMetadata(string gitObjectsRoot, string localCacheRoot) + public void SaveCloneMetadata(ITracer tracer, GVFSEnlistment enlistment) { this.repoMetadata.SetValuesAndFlush( new[] { - new KeyValuePair(Keys.DiskLayoutVersion, DiskLayoutVersion.CurrentDiskLayoutVersion.ToString()), - new KeyValuePair(Keys.GitObjectsRoot, gitObjectsRoot), - new KeyValuePair(Keys.LocalCacheRoot, localCacheRoot) + new KeyValuePair(Keys.DiskLayoutMajorVersion, DiskLayoutVersion.CurrentMajorVersion.ToString()), + new KeyValuePair(Keys.DiskLayoutMinorVersion, DiskLayoutVersion.CurrentMinorVersion.ToString()), + new KeyValuePair(Keys.GitObjectsRoot, enlistment.GitObjectsRoot), + new KeyValuePair(Keys.LocalCacheRoot, enlistment.LocalCacheRoot), + new KeyValuePair(Keys.BlobSizesRoot, enlistment.BlobSizesRoot), + new KeyValuePair(Keys.EnlistmentId, CreateNewEnlistmentId(tracer)), }); } @@ -196,11 +230,47 @@ namespace GVFS.Common this.repoMetadata.SetValueAndFlush(Keys.LocalCacheRoot, localCacheRoot); } + public bool TryGetBlobSizesRoot(out string blobSizesRoot, out string error) + { + blobSizesRoot = null; + + try + { + if (!this.repoMetadata.TryGetValue(Keys.BlobSizesRoot, out blobSizesRoot)) + { + error = "Blob sizes root not found"; + return false; + } + } + catch (FileBasedCollectionException ex) + { + error = ex.Message; + return false; + } + + error = null; + return true; + } + + public void SetBlobSizesRoot(string blobSizesRoot) + { + this.repoMetadata.SetValueAndFlush(Keys.BlobSizesRoot, blobSizesRoot); + } + public void SetEntry(string keyName, string valueName) { this.repoMetadata.SetValueAndFlush(keyName, valueName); } + private static string CreateNewEnlistmentId(ITracer tracer) + { + string enlistmentId = Guid.NewGuid().ToString("N"); + EventMetadata metadata = new EventMetadata(); + metadata.Add(nameof(enlistmentId), enlistmentId); + tracer.RelatedEvent(EventLevel.Informational, nameof(CreateNewEnlistmentId), metadata); + return enlistmentId; + } + private void SetInvalid(string keyName, bool invalid) { if (invalid) @@ -228,30 +298,29 @@ namespace GVFS.Common { public const string ProjectionInvalid = "ProjectionInvalid"; public const string PlaceholdersInvalid = "PlaceholdersInvalid"; - public const string DiskLayoutVersion = "DiskLayoutVersion"; + public const string DiskLayoutMajorVersion = "DiskLayoutVersion"; + public const string DiskLayoutMinorVersion = "DiskLayoutMinorVersion"; public const string PlaceholdersNeedUpdate = "PlaceholdersNeedUpdate"; public const string GitObjectsRoot = "GitObjectsRoot"; public const string LocalCacheRoot = "LocalCacheRoot"; + public const string BlobSizesRoot = "BlobSizesRoot"; + public const string EnlistmentId = "EnlistmentId"; } public static class DiskLayoutVersion { - // The current disk layout version. This number should be bumped whenever a disk format change is made - // that would impact and older GVFS's ability to mount the repo - public const int CurrentDiskLayoutVersion = 12; + // The major version should be bumped whenever there is an on-disk format change that requires a one-way upgrade. + // Increasing this version will make older versions of GVFS unable to mount a repo that has been mounted by a newer + // version of GVFS. + public const int CurrentMajorVersion = 14; - public const string MissingVersionError = "Enlistment disk layout version not found, check if a breaking change has been made to GVFS since cloning this enlistment."; + // The minor version should be bumped whenever there is an upgrade that can be safely ignored by older versions of GVFS. + // For example, this allows an upgrade step that sets a default value for some new config setting. + public const int CurrentMinorVersion = 0; - // MaxDiskLayoutVersion ensures that olders versions of GVFS will not try to mount newer enlistments (if the - // disk layout of the newer GVFS is incompatible). - // GVFS will only mount if the disk layout version of the repo is <= MaxDiskLayoutVersion - public const int MaxDiskLayoutVersion = CurrentDiskLayoutVersion; - - // MinDiskLayoutVersion ensures that GVFS will not attempt to mount an older repo if there has been a breaking format - // change since that enlistment was cloned. - // - GVFS will only mount if the disk layout version of the repo is >= MinDiskLayoutVersion - // - Bump this version number only when a breaking change is being made (i.e. upgrade is not supported) - public const int MinDiskLayoutVersion = 7; + // This is the last time GVFS made a breaking change that required a reclone. This should not + // be incremented ever again as all format changes should now be supported with an upgrade step. + public const int MinimumSupportedMajorVersion = 7; } } } diff --git a/GVFS/GVFS.Common/RetryConfig.cs b/GVFS/GVFS.Common/RetryConfig.cs index 26f37461..e6a16b87 100644 --- a/GVFS/GVFS.Common/RetryConfig.cs +++ b/GVFS/GVFS.Common/RetryConfig.cs @@ -14,8 +14,6 @@ namespace GVFS.Common private const string EtwArea = nameof(RetryConfig); - private const string MaxRetriesConfig = "max-retries"; - private const string TimeoutSecondsConfig = "timeout-seconds"; private const int MinRetries = 0; private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(DefaultTimeoutSeconds); @@ -106,7 +104,7 @@ namespace GVFS.Common { return TryGetFromGitConfig( git, - GVFSConstants.GitConfig.GVFSPrefix + MaxRetriesConfig, + GVFSConstants.GitConfig.MaxRetriesConfig, DefaultMaxRetries, MinRetries, out attempts, @@ -119,7 +117,7 @@ namespace GVFS.Common int timeoutSeconds; if (!TryGetFromGitConfig( git, - GVFSConstants.GitConfig.GVFSPrefix + TimeoutSecondsConfig, + GVFSConstants.GitConfig.TimeoutSecondsConfig, DefaultTimeoutSeconds, 0, out timeoutSeconds, diff --git a/GVFS/GVFS.Common/ReturnCode.cs b/GVFS/GVFS.Common/ReturnCode.cs index 7c2e2e5e..1b25ec09 100644 --- a/GVFS/GVFS.Common/ReturnCode.cs +++ b/GVFS/GVFS.Common/ReturnCode.cs @@ -5,6 +5,7 @@ Success = 0, ParsingError = 1, RebootRequired = 2, - GenericError = 3 + GenericError = 3, + FilterError = 4 } } diff --git a/GVFS/GVFS.Common/Tracing/ITracer.cs b/GVFS/GVFS.Common/Tracing/ITracer.cs index bfe803f5..eec53c32 100644 --- a/GVFS/GVFS.Common/Tracing/ITracer.cs +++ b/GVFS/GVFS.Common/Tracing/ITracer.cs @@ -32,6 +32,6 @@ namespace GVFS.Common.Tracing void RelatedError(string format, params object[] args); - void Stop(EventMetadata metadata); + TimeSpan Stop(EventMetadata metadata); } } \ No newline at end of file diff --git a/GVFS/GVFS.Common/Tracing/JsonEtwTracer.cs b/GVFS/GVFS.Common/Tracing/JsonEtwTracer.cs index 552811cf..ac2d10d0 100644 --- a/GVFS/GVFS.Common/Tracing/JsonEtwTracer.cs +++ b/GVFS/GVFS.Common/Tracing/JsonEtwTracer.cs @@ -153,11 +153,11 @@ namespace GVFS.Common.Tracing this.RelatedError(string.Format(format, args)); } - public void Stop(EventMetadata metadata) + public TimeSpan Stop(EventMetadata metadata) { if (this.stopped) { - return; + return TimeSpan.Zero; } this.duration.Stop(); @@ -167,6 +167,8 @@ namespace GVFS.Common.Tracing metadata.Add("DurationMs", this.duration.ElapsedMilliseconds); this.WriteEvent(this.activityName, this.startStopLevel, this.startStopKeywords, metadata, EventOpcode.Stop); + + return this.duration.Elapsed; } public ITracer StartActivity(string childActivityName, EventLevel startStopLevel) diff --git a/GVFS/GVFS.Common/packages.config b/GVFS/GVFS.Common/packages.config index fed13389..b5e5ea6d 100644 --- a/GVFS/GVFS.Common/packages.config +++ b/GVFS/GVFS.Common/packages.config @@ -1,10 +1,10 @@  - - - - - - - + + + + + + + \ No newline at end of file diff --git a/GVFS/GVFS.FunctionalTests/Categories.cs b/GVFS/GVFS.FunctionalTests/Categories.cs new file mode 100644 index 00000000..6739b869 --- /dev/null +++ b/GVFS/GVFS.FunctionalTests/Categories.cs @@ -0,0 +1,9 @@ +namespace GVFS.FunctionalTests +{ + public static class Categories + { + public const string FullSuiteOnly = "FullSuiteOnly"; + public const string FastFetch = "FastFetch"; + public const string GitCommands = "GitCommands"; + } +} diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs index 057b3786..4cb59544 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/BashRunner.cs @@ -158,6 +158,11 @@ namespace GVFS.FunctionalTests.FileSystemRunners this.MoveFile(sourcePath, targetPath); } + public override void RenameDirectory(string workingDirectory, string source, string target) + { + this.MoveDirectory(Path.Combine(workingDirectory, source), Path.Combine(workingDirectory, target)); + } + public override void MoveDirectory_RequestShouldNotBeSupported(string sourcePath, string targetPath) { this.MoveFile(sourcePath, targetPath).ShouldContain(moveDirectoryNotSupportedMessage); @@ -182,6 +187,13 @@ namespace GVFS.FunctionalTests.FileSystemRunners return this.RunProcess(string.Format("-c \"rm -rf {0}\"", bashPath)); } + public override string EnumerateDirectory(string path) + { + string bashPath = this.ConvertWinPathToBashPath(path); + + return this.RunProcess(string.Format("-c \"ls {0}\"", bashPath)); + } + public override void ReplaceFile_FileShouldNotBeFound(string sourcePath, string targetPath) { this.ReplaceFile(sourcePath, targetPath).ShouldContainOneOf(fileNotFoundMessages); diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/Category/CategoryConstants.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/Category/CategoryConstants.cs deleted file mode 100644 index 56f88b6e..00000000 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/Category/CategoryConstants.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace GVFS.FunctionalTests.Category -{ - public static class CategoryConstants - { - public const string FastFetch = "FastFetch"; - public const string GitCommands = "GitCommands"; - } -} diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs index ccb0e228..6f33f5da 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/CmdRunner.cs @@ -67,14 +67,14 @@ namespace GVFS.FunctionalTests.FileSystemRunners return false; } - string output = this.RunProcess(string.Format("/C if exist {0} (echo {1}) else (echo {2})", path, ShellRunner.SuccessOutput, ShellRunner.FailureOutput)).Trim(); + string output = this.RunProcess(string.Format("/C if exist \"{0}\" (echo {1}) else (echo {2})", path, ShellRunner.SuccessOutput, ShellRunner.FailureOutput)).Trim(); return output.Equals(ShellRunner.SuccessOutput, StringComparison.InvariantCulture); } public override string MoveFile(string sourcePath, string targetPath) { - return this.RunProcess(string.Format("/C move {0} {1}", sourcePath, targetPath)); + return this.RunProcess(string.Format("/C move \"{0}\" \"{1}\"", sourcePath, targetPath)); } public override void MoveFileShouldFail(string sourcePath, string targetPath) @@ -90,22 +90,22 @@ namespace GVFS.FunctionalTests.FileSystemRunners public override string ReplaceFile(string sourcePath, string targetPath) { - return this.RunProcess(string.Format("/C move /Y {0} {1}", sourcePath, targetPath)); + return this.RunProcess(string.Format("/C move /Y \"{0}\" \"{1}\"", sourcePath, targetPath)); } public override string DeleteFile(string path) { - return this.RunProcess(string.Format("/C del {0}", path)); + return this.RunProcess(string.Format("/C del \"{0}\"", path)); } public override string ReadAllText(string path) { - return this.RunProcess(string.Format("/C type {0}", path)); + return this.RunProcess(string.Format("/C type \"{0}\"", path)); } public override void CreateEmptyFile(string path) { - this.RunProcess(string.Format("/C type NUL > {0}", path)); + this.RunProcess(string.Format("/C type NUL > \"{0}\"", path)); } public override void AppendAllText(string path, string contents) @@ -149,12 +149,17 @@ namespace GVFS.FunctionalTests.FileSystemRunners public override void CreateDirectory(string path) { - this.RunProcess(string.Format("/C mkdir {0}", path)); + this.RunProcess(string.Format("/C mkdir \"{0}\"", path)); } public override string DeleteDirectory(string path) { - return this.RunProcess(string.Format("/C rmdir /q /s {0}", path)); + return this.RunProcess(string.Format("/C rmdir /q /s \"{0}\"", path)); + } + + public override string EnumerateDirectory(string path) + { + return this.RunProcess(string.Format("/C dir \"{0}\"", path)); } public override void MoveDirectory(string sourcePath, string targetPath) @@ -162,6 +167,11 @@ namespace GVFS.FunctionalTests.FileSystemRunners this.MoveFile(sourcePath, targetPath); } + public override void RenameDirectory(string workingDirectory, string source, string target) + { + this.RunProcess(string.Format("/C ren \"{0}\" \"{1}\"", source, target), workingDirectory); + } + public override void MoveDirectory_RequestShouldNotBeSupported(string sourcePath, string targetPath) { this.MoveFile(sourcePath, targetPath).ShouldContain(moveDirectoryFailureMessage); diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs index 7310dd54..3a4aaef8 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/FileSystemRunner.cs @@ -97,9 +97,11 @@ namespace GVFS.FunctionalTests.FileSystemRunners // Directory methods public abstract bool DirectoryExists(string path); public abstract void MoveDirectory(string sourcePath, string targetPath); + public abstract void RenameDirectory(string workingDirectory, string source, string target); public abstract void MoveDirectory_RequestShouldNotBeSupported(string sourcePath, string targetPath); public abstract void MoveDirectory_TargetShouldBeInvalid(string sourcePath, string targetPath); public abstract void CreateDirectory(string path); + public abstract string EnumerateDirectory(string path); /// /// A recursive delete of a directory diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs index a38c88cf..9d6a1da9 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/PowerShellRunner.cs @@ -134,6 +134,11 @@ namespace GVFS.FunctionalTests.FileSystemRunners this.MoveFile(sourcePath, targetPath); } + public override void RenameDirectory(string workingDirectory, string source, string target) + { + this.RunProcess(string.Format("-Command \"& {{ Rename-Item -Path {0} -NewName {1} -force }}\"", Path.Combine(workingDirectory, source), target)); + } + public override void MoveDirectory_RequestShouldNotBeSupported(string sourcePath, string targetPath) { this.MoveFile(sourcePath, targetPath).ShouldContain(moveDirectoryNotSupportedMessage); @@ -154,6 +159,11 @@ namespace GVFS.FunctionalTests.FileSystemRunners return this.RunProcess(string.Format("-Command \"&{{ Remove-Item -Force -Recurse {0} }}\"", path)); } + public override string EnumerateDirectory(string path) + { + return this.RunProcess(string.Format("-Command \"&{{ Get-ChildItem {0} }}\"", path)); + } + public override void ReplaceFile_FileShouldNotBeFound(string sourcePath, string targetPath) { this.ReplaceFile(sourcePath, targetPath).ShouldContainOneOf(missingFileErrorMessages); @@ -184,9 +194,9 @@ namespace GVFS.FunctionalTests.FileSystemRunners this.DeleteDirectory(path).ShouldContain(fileUsedByAnotherProcessMessage); } - protected override string RunProcess(string command, string errorMsgDelimeter = "") + protected override string RunProcess(string command, string workingDirectory = "", string errorMsgDelimeter = "") { - return base.RunProcess("-NoProfile " + command, errorMsgDelimeter); + return base.RunProcess("-NoProfile " + command, workingDirectory, errorMsgDelimeter); } } } diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/ShellRunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/ShellRunner.cs index d6d24272..6c02a2fc 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/ShellRunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/ShellRunner.cs @@ -10,7 +10,7 @@ namespace GVFS.FunctionalTests.FileSystemRunners protected abstract string FileName { get; } - protected virtual string RunProcess(string arguments, string errorMsgDelimeter = "") + protected virtual string RunProcess(string arguments, string workingDirectory = "", string errorMsgDelimeter = "") { ProcessStartInfo startInfo = new ProcessStartInfo(); startInfo.UseShellExecute = false; @@ -19,6 +19,7 @@ namespace GVFS.FunctionalTests.FileSystemRunners startInfo.CreateNoWindow = true; startInfo.FileName = this.FileName; startInfo.Arguments = arguments; + startInfo.WorkingDirectory = workingDirectory; ProcessResult result = ProcessHelper.Run(startInfo, errorMsgDelimeter: errorMsgDelimeter); return !string.IsNullOrEmpty(result.Output) ? result.Output : result.Errors; diff --git a/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs b/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs index 7df2727f..f36df22f 100644 --- a/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs +++ b/GVFS/GVFS.FunctionalTests/FileSystemRunners/SystemIORunner.cs @@ -2,6 +2,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using System.Threading; namespace GVFS.FunctionalTests.FileSystemRunners @@ -104,6 +105,11 @@ namespace GVFS.FunctionalTests.FileSystemRunners Directory.Move(sourcePath, targetPath); } + public override void RenameDirectory(string workingDirectory, string source, string target) + { + MoveFileEx(Path.Combine(workingDirectory, source), Path.Combine(workingDirectory, target), 0); + } + public override void MoveDirectory_RequestShouldNotBeSupported(string sourcePath, string targetPath) { if (Debugger.IsAttached) @@ -149,6 +155,11 @@ namespace GVFS.FunctionalTests.FileSystemRunners return string.Empty; } + public override string EnumerateDirectory(string path) + { + return string.Join(Environment.NewLine, Directory.GetFileSystemEntries(path)); + } + public override void DeleteDirectory_DirectoryShouldNotBeFound(string path) { this.ShouldFail(() => { this.DeleteDirectory(path); }); @@ -164,6 +175,9 @@ namespace GVFS.FunctionalTests.FileSystemRunners this.ShouldFail(() => { this.ReadAllText(path); }); } + [DllImport("kernel32", SetLastError = true)] + private static extern bool MoveFileEx(string existingFileName, string newFileName, int flags); + private static void RetryOnException(Action action) { for (int i = 0; i < 10; i++) diff --git a/GVFS/GVFS.FunctionalTests/GVFS.FunctionalTests.csproj b/GVFS/GVFS.FunctionalTests/GVFS.FunctionalTests.csproj index d4bef11d..5625a4d7 100644 --- a/GVFS/GVFS.FunctionalTests/GVFS.FunctionalTests.csproj +++ b/GVFS/GVFS.FunctionalTests/GVFS.FunctionalTests.csproj @@ -8,13 +8,14 @@ Properties GVFS.FunctionalTests GVFS.FunctionalTests - v4.5.2 + v4.6.1 512 10.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages False UnitTest + true @@ -42,6 +43,9 @@ Always + + $(BuildOutputDir)\GVFS.FunctionalTests.exe.manifest + False @@ -58,6 +62,9 @@ ..\..\..\packages\Microsoft.Database.Isam.1.9.4\lib\net40\Esent.Isam.dll True + + ..\..\..\packages\Microsoft.Data.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll + False ..\..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll @@ -69,7 +76,20 @@ ..\..\..\packages\NUnitLite.3.10.0-dev-05190\lib\net45\nunitlite.dll + + ..\..\..\packages\SQLitePCLRaw.bundle_green.1.1.7\lib\net45\SQLitePCLRaw.batteries_green.dll + + + ..\..\..\packages\SQLitePCLRaw.bundle_green.1.1.7\lib\net45\SQLitePCLRaw.batteries_v2.dll + + + ..\..\..\packages\SQLitePCLRaw.core.1.1.7\lib\net45\SQLitePCLRaw.core.dll + + + ..\..\..\packages\SQLitePCLRaw.provider.e_sqlite3.net45.1.1.7\lib\net45\SQLitePCLRaw.provider.e_sqlite3.dll + + @@ -81,7 +101,7 @@ - + @@ -95,10 +115,12 @@ + + @@ -163,6 +185,9 @@ Designer + + GVFS.FunctionalTests.exe.manifest + Designer @@ -228,11 +253,15 @@ + + + xcopy /Y $(BuildOutputDir)\GVFS\bin\$(Platform)\$(Configuration)\* $(TargetDir) +xcopy /Y $(BuildOutputDir)\GVFS\bin\$(Platform)\$(Configuration)\$(Platform)\* $(TargetDir) xcopy /Y $(BuildOutputDir)\GVFS.Service\bin\$(Platform)\$(Configuration)\* $(TargetDir) xcopy /Y $(BuildOutputDir)\GVFS.Mount\bin\$(Platform)\$(Configuration)\* $(TargetDir) xcopy /Y $(BuildOutputDir)\FastFetch\bin\$(Platform)\$(Configuration)\* $(TargetDir) @@ -243,6 +272,9 @@ xcopy /Y $(BuildOutputDir)\GVFS.Hooks\bin\$(Platform)\$(Configuration)\* $(Targe + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/GVFS/GVFS.Installer/Setup.iss b/GVFS/GVFS.Installer/Setup.iss index 90282c96..c2f486f4 100644 --- a/GVFS/GVFS.Installer/Setup.iss +++ b/GVFS/GVFS.Installer/Setup.iss @@ -3,7 +3,7 @@ ; General documentation on how to use InnoSetup scripts: http://www.jrsoftware.org/ishelp/index.php -#define GVFltDir PackagesDir + "\" + GvFltPackage + "\filter" +#define PrjFltDir PackagesDir + "\" + ProjFSPackage + "\filter" #define GVFSDir BuildOutputDir + "\GVFS\bin\" + PlatformAndConfiguration #define GVFSCommonDir BuildOutputDir + "\GVFS.Common\bin\" + PlatformAndConfiguration #define HooksDir BuildOutputDir + "\GVFS.Hooks\bin\" + PlatformAndConfiguration @@ -20,6 +20,8 @@ #define MyAppURL "https://github.com/Microsoft/gvfs" #define MyAppExeName "GVFS.exe" #define EnvironmentKey "SYSTEM\CurrentControlSet\Control\Session Manager\Environment" +#define FileSystemKey "SYSTEM\CurrentControlSet\Control\FileSystem" +#define GvFltAutologgerKey "SYSTEM\CurrentControlSet\Control\WMI\Autologger\Microsoft-Windows-Git-Filter-Log" [Setup] AppId={{489CA581-F131-4C28-BE04-4FB178933E6D} @@ -39,7 +41,7 @@ OutputDir=Setup Compression=lzma2 InternalCompressLevel=ultra64 SolidCompression=yes -MinVersion=10.0.15063 +MinVersion=10.0.14374 DisableDirPage=yes DisableReadyPage=yes SetupIconFile="{#GVFSDir}\GitVirtualFileSystem.ico" @@ -60,10 +62,15 @@ Name: "full"; Description: "Full installation"; Flags: iscustom; [Components] [Files] -; GVFlt Files -DestDir: "{app}\Filter"; Flags: ignoreversion; Source:"{#GVFltDir}\GvFlt.sys" -; gvflt.inf is declared explicitly last within the filter files, so we run the GVFlt install only once after required filter files are present -DestDir: "{app}\Filter"; Flags: ignoreversion; Source: "{#GVFltDir}\gvflt.inf"; AfterInstall: InstallGVFlt +; PrjFlt Filter Files +DestDir: "{app}\Filter"; Flags: ignoreversion; Source:"{#PrjFltDir}\PrjFlt.sys" +DestDir : "{app}\Filter"; Flags: ignoreversion; Source: "{#PrjFltDir}\prjflt.inf" + +; PrjFlt Native Library Files, the GVFS.Service will copy these files into the {app} directory if needed +DestDir: "{app}\ProjFS"; Flags: ignoreversion; Source:"{#GVFSDir}\ProjectedFSLib.dll" + +; PrjFlt Managed Assembly Files +DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\ProjectedFSLib.Managed.dll" ; GitHooks Files DestDir: "{app}"; Flags: ignoreversion; Source:"{#HooksDir}\GVFS.Hooks.pdb" @@ -78,6 +85,7 @@ DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSCommonDir}\git2.dll" ; GVFS.Mount Files DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSMountDir}\GVFS.Mount.pdb" DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSMountDir}\GVFS.Mount.exe" +DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSMountDir}\GVFS.Mount.exe.config" ; GVFS.ReadObjectHook files DestDir: "{app}"; Flags: ignoreversion; Source:"{#ReadObjectDir}\GVFS.ReadObjectHook.pdb" @@ -94,29 +102,43 @@ DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\GVFS.pdb" ; GVFS.Service.UI Files DestDir: "{app}"; Flags: ignoreversion; Source:"{#ServiceUIDir}\GVFS.Service.UI.exe" +DestDir: "{app}"; Flags: ignoreversion; Source:"{#ServiceUIDir}\GVFS.Service.UI.exe.config" DestDir: "{app}"; Flags: ignoreversion; Source:"{#ServiceUIDir}\GVFS.Service.UI.pdb" DestDir: "{app}"; Flags: ignoreversion; Source:"{#ServiceUIDir}\GitVirtualFileSystem.ico" ; FastFetch Files DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\FastFetch.exe" +DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\FastFetch.exe.config" ; GVFS Files DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\CommandLine.dll" DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\Esent.Collections.dll" DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\Esent.Interop.dll" DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\Esent.Isam.dll" +DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\Microsoft.Data.Sqlite.dll" +DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\SQLitePCLRaw.batteries_green.dll" +DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\SQLitePCLRaw.batteries_v2.dll" +DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\SQLitePCLRaw.core.dll" +DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\SQLitePCLRaw.provider.e_sqlite3.dll" +DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\x64\e_sqlite3.dll" DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\GVFS.Common.dll" DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\GVFS.GVFlt.dll" -DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\GvLib.Managed.dll" -DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\GvLib.dll" DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\Microsoft.Diagnostics.Tracing.EventSource.dll" DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\Newtonsoft.Json.dll" DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\GVFS.exe.config" DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\GitVirtualFileSystem.ico" DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\GVFS.exe" +; .NET Standard Files +; See https://github.com/dotnet/standard/issues/415 for a discussion on why this are copied +DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\netstandard.dll" +DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\System.Net.Http.dll" +DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\System.ValueTuple.dll" +DestDir: "{app}"; Flags: ignoreversion; Source:"{#GVFSDir}\System.IO.Compression.dll" + ; GVFS.Service Files and PDB's DestDir: "{app}"; Flags: ignoreversion; Source:"{#ServiceDir}\GVFS.Service.pdb" +DestDir: "{app}"; Flags: ignoreversion; Source:"{#ServiceDir}\GVFS.Service.exe.config" DestDir: "{app}"; Flags: ignoreversion; Source:"{#ServiceDir}\GVFS.Service.exe"; AfterInstall: InstallGVFSService [UninstallDelete] @@ -128,6 +150,12 @@ Root: HKLM; Subkey: "{#EnvironmentKey}"; \ ValueType: expandsz; ValueName: "PATH"; ValueData: "{olddata};{app}"; \ Check: NeedsAddPath(ExpandConstant('{app}')) +Root: HKLM; Subkey: "{#FileSystemKey}"; \ + ValueType: dword; ValueName: "NtfsEnableDetailedCleanupResults"; ValueData: "1"; \ + Check: IsWindows10VersionPriorToCreatorsUpdate + +Root: HKLM; SubKey: "{#GvFltAutologgerKey}"; Flags: deletekey + [Code] var ExitCode: Integer; @@ -148,6 +176,29 @@ begin Result := Pos(';' + Param + ';', ';' + OrigPath + ';') = 0; end; +function IsWindows10VersionPriorToCreatorsUpdate(): Boolean; +var + Version: TWindowsVersion; +begin + GetWindowsVersionEx(Version); + Result := (Version.Major = 10) and (Version.Minor = 0) and (Version.Build < 15063); +end; + +function DoesWindowsVersionNotHavePrjFltInbox(): Boolean; +var + Version: TWindowsVersion; +begin + GetWindowsVersionEx(Version); + + // 17121 -> Min RS4 version with inbox PrjFlt + // 17600 -> First RS5 version + // 17626 -> Min RS5 version with inbox PrjFlt + + Log(Format('Version.Build is [%d]', [Version.Build])); + + Result := (Version.Major = 10) and (Version.Minor = 0) and ((Version.Build < 17121) or ((Version.Build >= 17600) and (Version.Build < 17626))); +end; + procedure RemovePath(Path: string); var Paths: string; @@ -265,35 +316,149 @@ begin end; end; -procedure InstallGVFlt(); +procedure UninstallGvFlt(); var ResultCode: integer; StatusText: string; - InstallSuccessful: Boolean; + UninstallSuccessful: Boolean; begin - InstallSuccessful := False; + if (FileExists(ExpandConstant('{app}\Filter\GvFlt.inf'))) then + begin + UninstallSuccessful := False; - StatusText := WizardForm.StatusLabel.Caption; - WizardForm.StatusLabel.Caption := 'Installing GVFlt Driver.'; - WizardForm.ProgressGauge.Style := npbstMarquee; + StatusText := WizardForm.StatusLabel.Caption; + WizardForm.StatusLabel.Caption := 'Uninstalling GvFlt Driver.'; + WizardForm.ProgressGauge.Style := npbstMarquee; - try + try Exec(ExpandConstant('SC.EXE'), 'stop gvflt', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); - // Note: Programatic install of INF notifies user if the driver being upgraded to is older than the existing, otherwise it works silently... doesn't seem like there is a way to block - if Exec(ExpandConstant('RUNDLL32.EXE'), ExpandConstant('SETUPAPI.DLL,InstallHinfSection DefaultInstall 128 {app}\Filter\gvflt.inf'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then + if Exec(ExpandConstant('RUNDLL32.EXE'), ExpandConstant('SETUPAPI.DLL,InstallHinfSection DefaultUninstall 128 {app}\Filter\GvFlt.inf'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then begin - InstallSuccessful := True; + UninstallSuccessful := True; + end; + finally + WizardForm.StatusLabel.Caption := StatusText; + WizardForm.ProgressGauge.Style := npbstNormal; + end; + + if UninstallSuccessful = True then + begin + if not DeleteFile(ExpandConstant('{app}\Filter\GvFlt.inf')) then + begin + Log('UninstallGvFlt: Failed to delete GvFlt.inf'); + end; + end + else + begin + RaiseException('Fatal: An error occured while uninstalling GvFlt drivers.'); + end; + end; +end; + +function DeleteNativeProjFSLibFromAppsFolder() : Boolean; +begin + Result := False; + if FileExists(ExpandConstant('{app}\ProjectedFSLib.dll')) then + begin + Log('DeleteNativeProjFSLibFromAppsFolder: Removing ProjFS native library from app folder'); + if DeleteFile(ExpandConstant('{app}\ProjectedFSLib.dll')) then + begin + Result := True; + end + else + begin + Log('DeleteNativeProjFSLibFromAppsFolder: Failed to delete ProjFS native library from app folder'); + end; + end + else + begin + Result := True; + end; +end; + +function UninstallPrjFlt(): Boolean; +var + ResultCode: integer; + StatusText: string; +begin + Result := False; + StatusText := WizardForm.StatusLabel.Caption; + WizardForm.StatusLabel.Caption := 'Uninstalling PrjFlt Driver.'; + WizardForm.ProgressGauge.Style := npbstMarquee; + + Log('UninstallPrjFlt: Uninstalling PrjFlt Driver'); + try + Exec(ExpandConstant('SC.EXE'), 'stop prjflt', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + + if Exec(ExpandConstant('RUNDLL32.EXE'), ExpandConstant('SETUPAPI.DLL,InstallHinfSection DefaultUninstall 128 {app}\Filter\prjflt.inf'), '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then + begin + if DeleteNativeProjFSLibFromAppsFolder() then + begin + Result := True; + end; + end + else + begin + Log('UninstallNonInboxPrjFltWhenInboxAvailable: Uninstalling PrjFlt Driver failed'); end; finally WizardForm.StatusLabel.Caption := StatusText; - WizardForm.ProgressGauge.Style := npbstNormal; - Exec(ExpandConstant('SC.EXE'), 'start gvflt', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + WizardForm.ProgressGauge.Style := npbstNormal; end; +end; - if InstallSuccessful = False then +procedure UninstallNonInboxPrjFlt(); +var + ProjFSFeatureEnabledResultCode: integer; + UninstallSuccessful: Boolean; +begin + if FileExists(ExpandConstant('{app}\Filter\PrjFlt.inf')) and FileExists(ExpandConstant('{sys}\drivers\prjflt.sys')) then begin - RaiseException('Fatal: An error occured while installing GVFlt drivers.'); + UninstallSuccessful := False; + + if DoesWindowsVersionNotHavePrjFltInbox() then + begin + if UninstallPrjFlt() then + begin + UninstallSuccessful := True; + end; + end + else + begin + if Exec('powershell.exe', '-NoProfile "$var=(Get-WindowsOptionalFeature -Online -FeatureName Client-ProjFS); if($var -eq $null){exit 2}else{if($var.State -eq ''Enabled''){exit 3}else{exit 4}}"', '', SW_HIDE, ewWaitUntilTerminated, ProjFSFeatureEnabledResultCode) then + begin + if ProjFSFeatureEnabledResultCode = 2 then + begin + // Client-ProjFS is not an optional feature + Log('UninstallNonInboxPrjFlt: Fatal: Could not locate Windows Projected File System optional feature'); + RaiseException('Fatal: Could not locate Windows Projected File System optional feature.'); + end; + if ProjFSFeatureEnabledResultCode = 3 then + begin + // Client-ProjFS is already enabled, confirm the native ProjFS library is not on the path + Log('UninstallNonInboxPrjFlt: Client-ProjFS already enabled'); + if DeleteNativeProjFSLibFromAppsFolder() then + begin + UninstallSuccessful := True; + end; + end; + if ProjFSFeatureEnabledResultCode = 4 then + begin + // Client-ProjFS is currently disabled but prjflt.sys is present and should be removed + Log('UninstallNonInboxPrjFlt: Client-ProjFS is disabled, uninstalling PrjFlt'); + if UninstallPrjFlt() then + begin + UninstallSuccessful := True; + end; + end; + end; + end; + + if UninstallSuccessful = False then + begin + RaiseException('Fatal: An error occured while uninstalling PrjFlt drivers.'); + end; end; end; @@ -353,7 +518,12 @@ begin WizardForm.StatusLabel.Caption := StatusText; WizardForm.ProgressGauge.Style := npbstNormal; - if (ResultCode <> 0) then + // 4 = ReturnCode.FilterError + if (ResultCode = 4) then + begin + RaiseException('Fatal: Could not configure and start Windows Projected File System.'); + end + else if (ResultCode <> 0) then begin MsgBoxText := 'Mounting one or more repos failed:' + #13#10 + MountOutput; SuppressibleMsgBox(MsgBoxText, mbConfirmation, MB_OK, IDOK); @@ -477,4 +647,6 @@ begin Abort(); end; StopGVFSService(); + UninstallGvFlt(); + UninstallNonInboxPrjFlt(); end; diff --git a/GVFS/GVFS.Installer/packages.config b/GVFS/GVFS.Installer/packages.config index f5619d24..2f00ef47 100644 --- a/GVFS/GVFS.Installer/packages.config +++ b/GVFS/GVFS.Installer/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file diff --git a/GVFS/GVFS.Mount/GVFS.Mount.csproj b/GVFS/GVFS.Mount/GVFS.Mount.csproj index 52836e65..066d0e92 100644 --- a/GVFS/GVFS.Mount/GVFS.Mount.csproj +++ b/GVFS/GVFS.Mount/GVFS.Mount.csproj @@ -8,7 +8,7 @@ Properties GVFS.Mount GVFS.Mount - v4.5.2 + v4.6.1 512 true @@ -39,8 +39,11 @@ ..\..\..\packages\CommandLineParser.2.1.1-beta\lib\net45\CommandLine.dll True + + ..\..\..\packages\Microsoft.Data.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll + - ..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll + ..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net46\Microsoft.Diagnostics.Tracing.EventSource.dll True @@ -48,6 +51,18 @@ ..\..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll True + + ..\..\..\packages\SQLitePCLRaw.bundle_green.1.1.7\lib\net45\SQLitePCLRaw.batteries_green.dll + + + ..\..\..\packages\SQLitePCLRaw.bundle_green.1.1.7\lib\net45\SQLitePCLRaw.batteries_v2.dll + + + ..\..\..\packages\SQLitePCLRaw.core.1.1.7\lib\net45\SQLitePCLRaw.core.dll + + + ..\..\..\packages\SQLitePCLRaw.provider.e_sqlite3.net45.1.1.7\lib\net45\SQLitePCLRaw.provider.e_sqlite3.dll + @@ -68,6 +83,7 @@ + Designer @@ -92,6 +108,9 @@ + + + @@ -99,6 +118,9 @@ xcopy /Y $(BuildOutputDir)\GVFS.ReadObjectHook\bin\$(Platform)\$(Configuration)\GVFS.ReadObjectHook.* $(TargetDir) xcopy /Y $(BuildOutputDir)\GVFS.Hooks\bin\$(Platform)\$(Configuration)\GVFS.Hooks.* $(TargetDir) + + + + v4.5.2 + 512 + + + + + true + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + + + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + + + + Microsoft + false + + + + + Designer + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/GVFS/GVFS.SignFiles/packages.config b/GVFS/GVFS.SignFiles/packages.config new file mode 100644 index 00000000..67c1732f --- /dev/null +++ b/GVFS/GVFS.SignFiles/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/GVFS/GVFS.Tests/GVFS.Tests.csproj b/GVFS/GVFS.Tests/GVFS.Tests.csproj index 6bb9ef88..b0b8f0e2 100644 --- a/GVFS/GVFS.Tests/GVFS.Tests.csproj +++ b/GVFS/GVFS.Tests/GVFS.Tests.csproj @@ -9,7 +9,7 @@ Properties GVFS.Tests GVFS.Tests - v4.5.2 + v4.6.1 512 diff --git a/GVFS/GVFS.Tests/NUnitRunner.cs b/GVFS/GVFS.Tests/NUnitRunner.cs index 16e8bf3f..72cfaa14 100644 --- a/GVFS/GVFS.Tests/NUnitRunner.cs +++ b/GVFS/GVFS.Tests/NUnitRunner.cs @@ -1,10 +1,8 @@ using NUnitLite; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Threading; namespace GVFS.Tests { @@ -42,31 +40,20 @@ namespace GVFS.Tests this.excludedCategories.Add("cat!=" + category); } - public int RunTests(int repeatCount) + public int RunTests() { if (this.excludedCategories.Count > 0) { this.args.Add("--where=" + string.Join("&&", this.excludedCategories)); } - int finalResult = 0; - for (int i = 0; i < repeatCount; i++) - { - Console.WriteLine("Starting pass {0}", i + 1); - DateTime now = DateTime.Now; + DateTime now = DateTime.Now; + int result = new AutoRun(Assembly.GetEntryAssembly()).Execute(this.args.ToArray()); - finalResult = new AutoRun(Assembly.GetEntryAssembly()).Execute(this.args.ToArray()); + Console.WriteLine("Completed test pass in {0}", DateTime.Now - now); + Console.WriteLine(); - Console.WriteLine("Completed pass {0} in {1}", i + 1, DateTime.Now - now); - Console.WriteLine(); - - if (i < repeatCount - 1) - { - Thread.Sleep(TimeSpan.FromSeconds(1)); - } - } - - return finalResult; + return result; } } } diff --git a/GVFS/GVFS.Tests/app.config b/GVFS/GVFS.Tests/app.config index 5561a5bc..229b188b 100644 --- a/GVFS/GVFS.Tests/app.config +++ b/GVFS/GVFS.Tests/app.config @@ -1,4 +1,4 @@ - + @@ -8,4 +8,7 @@ - \ No newline at end of file + + + + diff --git a/GVFS/GVFS.Tests/packages.config b/GVFS/GVFS.Tests/packages.config index d942a2a9..e12219c3 100644 --- a/GVFS/GVFS.Tests/packages.config +++ b/GVFS/GVFS.Tests/packages.config @@ -1,7 +1,7 @@  - - - - + + + + \ No newline at end of file diff --git a/GVFS/GVFS.UnitTests/App.config b/GVFS/GVFS.UnitTests/App.config index 97d4dbe7..c56d3b5d 100644 --- a/GVFS/GVFS.UnitTests/App.config +++ b/GVFS/GVFS.UnitTests/App.config @@ -1,7 +1,7 @@ - + - + @@ -11,4 +11,4 @@ - \ No newline at end of file + diff --git a/GVFS/GVFS.UnitTests/Common/CacheServerResolverTests.cs b/GVFS/GVFS.UnitTests/Common/CacheServerResolverTests.cs index 86e5a31f..185b1b77 100644 --- a/GVFS/GVFS.UnitTests/Common/CacheServerResolverTests.cs +++ b/GVFS/GVFS.UnitTests/Common/CacheServerResolverTests.cs @@ -4,6 +4,7 @@ using GVFS.Common.Http; using GVFS.Tests.Should; using GVFS.UnitTests.Mock.Common; using GVFS.UnitTests.Mock.Git; +using Newtonsoft.Json; using NUnit.Framework; namespace GVFS.UnitTests.Common @@ -155,6 +156,35 @@ namespace GVFS.UnitTests.Common error.ShouldNotBeNull(); } + [TestCase] + public void CanParseAndResolveDefaultWhenServerAdvertisesNullListOfCacheServers() + { + MockEnlistment enlistment = this.CreateEnlistment(); + CacheServerResolver resolver = this.CreateResolver(enlistment); + + CacheServerInfo resolvedCacheServer; + string error; + resolver.TryResolveUrlFromRemote(CacheServerInfo.ReservedNames.Default, this.CreateDefaultDeserializedGVFSConfig(), out resolvedCacheServer, out error) + .ShouldEqual(true); + + this.ValidateIsNone(enlistment, resolvedCacheServer); + } + + [TestCase] + public void CanParseAndResolveOtherWhenServerAdvertisesNullListOfCacheServers() + { + MockEnlistment enlistment = this.CreateEnlistment(); + CacheServerResolver resolver = this.CreateResolver(enlistment); + + CacheServerInfo resolvedCacheServer; + string error; + resolver.TryResolveUrlFromRemote(CacheServerInfo.ReservedNames.None, this.CreateDefaultDeserializedGVFSConfig(), out resolvedCacheServer, out error) + .ShouldEqual(false, "Should not succeed in resolving the name 'None'"); + + resolvedCacheServer.ShouldEqual(null); + error.ShouldNotBeNull(); + } + private void ValidateIsNone(Enlistment enlistment, CacheServerInfo cacheServer) { cacheServer.Url.ShouldEqual(enlistment.RepoUrl); @@ -185,6 +215,11 @@ namespace GVFS.UnitTests.Common }; } + private GVFSConfig CreateDefaultDeserializedGVFSConfig() + { + return JsonConvert.DeserializeObject("{}"); + } + private CacheServerResolver CreateResolver(MockEnlistment enlistment = null) { enlistment = enlistment ?? this.CreateEnlistment(); diff --git a/GVFS/GVFS.UnitTests/Common/GitCommandLineParserTests.cs b/GVFS/GVFS.UnitTests/Common/GitCommandLineParserTests.cs index 23deb074..b8ead34c 100644 --- a/GVFS/GVFS.UnitTests/Common/GitCommandLineParserTests.cs +++ b/GVFS/GVFS.UnitTests/Common/GitCommandLineParserTests.cs @@ -30,7 +30,6 @@ namespace GVFS.UnitTests.Common new GitCommandLineParser("git add").IsVerb(GitCommandLineParser.Verbs.AddOrStage).ShouldEqual(true); new GitCommandLineParser("git checkout").IsVerb(GitCommandLineParser.Verbs.Checkout).ShouldEqual(true); - new GitCommandLineParser("git clean").IsVerb(GitCommandLineParser.Verbs.Clean).ShouldEqual(true); new GitCommandLineParser("git commit").IsVerb(GitCommandLineParser.Verbs.Commit).ShouldEqual(true); new GitCommandLineParser("git mv").IsVerb(GitCommandLineParser.Verbs.Move).ShouldEqual(true); new GitCommandLineParser("git reset").IsVerb(GitCommandLineParser.Verbs.Reset).ShouldEqual(true); @@ -64,20 +63,6 @@ namespace GVFS.UnitTests.Common new GitCommandLineParser("git status").IsResetSoftOrMixed().ShouldEqual(false); } - [TestCase] - public void IsResetHardTests() - { - new GitCommandLineParser("gits reset --hard").IsResetHard().ShouldEqual(false); - - new GitCommandLineParser("git reset --hard").IsResetHard().ShouldEqual(true); - - new GitCommandLineParser("git reset --soft").IsResetHard().ShouldEqual(false); - new GitCommandLineParser("git reset --mixed").IsResetHard().ShouldEqual(false); - - new GitCommandLineParser("git checkout").IsResetHard().ShouldEqual(false); - new GitCommandLineParser("git status").IsResetHard().ShouldEqual(false); - } - [TestCase] public void IsCheckoutWithFilePathsTests() { diff --git a/GVFS/GVFS.UnitTests/GVFS.UnitTests.csproj b/GVFS/GVFS.UnitTests/GVFS.UnitTests.csproj index 65c93a42..2c5dc183 100644 --- a/GVFS/GVFS.UnitTests/GVFS.UnitTests.csproj +++ b/GVFS/GVFS.UnitTests/GVFS.UnitTests.csproj @@ -2,6 +2,7 @@ + {8E0D0989-21F6-4DD8-946C-39F992523CC6} @@ -9,7 +10,7 @@ Properties GVFS.UnitTests GVFS.UnitTests - v4.5.2 + v4.6.1 512 true @@ -36,24 +37,9 @@ true - - False - ..\..\..\packages\Microsoft.Database.Collections.Generic.1.9.4\lib\net40\Esent.Collections.dll - True - - - False - ..\..\..\packages\ManagedEsent.1.9.4\lib\net40\Esent.Interop.dll - True - - - False - ..\..\..\packages\Microsoft.Database.Isam.1.9.4\lib\net40\Esent.Isam.dll - True - False - ..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll + ..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net46\Microsoft.Diagnostics.Tracing.EventSource.dll True @@ -66,6 +52,9 @@ ..\..\..\packages\NUnitLite.3.10.0-dev-05190\lib\net45\nunitlite.dll + + $(PackagesDir)\$(ProjFSPackage)\lib\x64\ProjectedFSLib.Managed.dll + @@ -116,6 +105,7 @@ + @@ -150,10 +140,6 @@ {374bf1e5-0b2d-4d4a-bd5e-4212299def09} GVFS.Common - - {fb0831ae-9997-401b-b31f-3a065fdbeb20} - GvLib.Managed - {1118b427-7063-422f-83b9-5023c8ec5a7a} GVFS.GVFlt diff --git a/GVFS/GVFS.UnitTests/GVFlt/DotGit/AlwaysExcludeFileTests.cs b/GVFS/GVFS.UnitTests/GVFlt/DotGit/AlwaysExcludeFileTests.cs index 9aa11a4c..9eb64390 100644 --- a/GVFS/GVFS.UnitTests/GVFlt/DotGit/AlwaysExcludeFileTests.cs +++ b/GVFS/GVFS.UnitTests/GVFlt/DotGit/AlwaysExcludeFileTests.cs @@ -32,11 +32,11 @@ namespace GVFS.UnitTests.GVFlt.DotGit alwaysExcludeFile.LoadOrCreate(); this.Repo.Context.FileSystem.FileExists(alwaysExcludeFilePath).ShouldEqual(true); - alwaysExcludeFile.AddEntriesForFile("a\\1.txt"); - alwaysExcludeFile.AddEntriesForFile("a\\2.txt"); - alwaysExcludeFile.AddEntriesForFile("a\\3.txt"); - alwaysExcludeFile.AddEntriesForFile("a\\b\\1.txt"); - alwaysExcludeFile.AddEntriesForFile("c\\1.txt"); + alwaysExcludeFile.AddEntriesForPath("a\\1.txt"); + alwaysExcludeFile.AddEntriesForPath("a\\2.txt"); + alwaysExcludeFile.AddEntriesForPath("a\\3.txt"); + alwaysExcludeFile.AddEntriesForPath("a\\b\\1.txt"); + alwaysExcludeFile.AddEntriesForPath("c\\1.txt"); List expectedContents = new List() { "*", "!/a/", "!/a/1.txt", "!/a/2.txt", "!/a/3.txt", "!/a/b/", "!/a/b/1.txt", "!/c/", "!/c/1.txt" }; this.CheckFileContents(alwaysExcludeFilePath, expectedContents); @@ -51,12 +51,12 @@ namespace GVFS.UnitTests.GVFlt.DotGit alwaysExcludeFile.LoadOrCreate(); this.Repo.Context.FileSystem.FileExists(alwaysExcludeFilePath).ShouldEqual(true); - alwaysExcludeFile.AddEntriesForFile("a\\1.txt"); - alwaysExcludeFile.AddEntriesForFile("A\\2.txt"); - alwaysExcludeFile.AddEntriesForFile("a\\b\\1.txt"); - alwaysExcludeFile.AddEntriesForFile("a\\B\\2.txt"); - alwaysExcludeFile.AddEntriesForFile("A\\b\\3.txt"); - alwaysExcludeFile.AddEntriesForFile("A\\B\\4.txt"); + alwaysExcludeFile.AddEntriesForPath("a\\1.txt"); + alwaysExcludeFile.AddEntriesForPath("A\\2.txt"); + alwaysExcludeFile.AddEntriesForPath("a\\b\\1.txt"); + alwaysExcludeFile.AddEntriesForPath("a\\B\\2.txt"); + alwaysExcludeFile.AddEntriesForPath("A\\b\\3.txt"); + alwaysExcludeFile.AddEntriesForPath("A\\B\\4.txt"); List expectedContents = new List() { "*", "!/a/", "!/a/1.txt", "!/A/2.txt", "!/a/b/", "!/a/b/1.txt", "!/a/B/2.txt", "!/A/b/3.txt", "!/A/B/4.txt" }; this.CheckFileContents(alwaysExcludeFilePath, expectedContents); @@ -71,15 +71,15 @@ namespace GVFS.UnitTests.GVFlt.DotGit alwaysExcludeFile.LoadOrCreate(); this.Repo.Context.FileSystem.FileExists(alwaysExcludeFilePath).ShouldEqual(true); - alwaysExcludeFile.AddEntriesForFile("a\\1.txt"); - alwaysExcludeFile.AddEntriesForFile("a\\2.txt"); + alwaysExcludeFile.AddEntriesForPath("a\\1.txt"); + alwaysExcludeFile.AddEntriesForPath("a\\2.txt"); List expectedContents = new List() { "*", "!/a/", "!/a/1.txt", "!/a/2.txt" }; this.CheckFileContents(alwaysExcludeFilePath, expectedContents); alwaysExcludeFile = new AlwaysExcludeFile(this.Repo.Context, alwaysExcludeFilePath); alwaysExcludeFile.LoadOrCreate(); - alwaysExcludeFile.AddEntriesForFile("a\\3.txt"); + alwaysExcludeFile.AddEntriesForPath("a\\3.txt"); expectedContents = new List() { "*", "!/a/", "!/a/1.txt", "!/a/2.txt", "!/a/3.txt" }; this.CheckFileContents(alwaysExcludeFilePath, expectedContents); @@ -94,9 +94,9 @@ namespace GVFS.UnitTests.GVFlt.DotGit alwaysExcludeFile.LoadOrCreate(); this.Repo.Context.FileSystem.FileExists(alwaysExcludeFilePath).ShouldEqual(true); - alwaysExcludeFile.AddEntriesForFile("a\\1.txt"); - alwaysExcludeFile.AddEntriesForFile("a\\2.txt"); - alwaysExcludeFile.RemoveEntriesForFiles(new List { "a\\1.txt" }); + alwaysExcludeFile.AddEntriesForPath("a\\1.txt"); + alwaysExcludeFile.AddEntriesForPath("a\\2.txt"); + alwaysExcludeFile.RemoveEntriesForFile("a\\1.txt"); alwaysExcludeFile.FlushAndClose(); List expectedContents = new List() { "*", "!/a/", "!/a/2.txt" }; @@ -112,10 +112,11 @@ namespace GVFS.UnitTests.GVFlt.DotGit alwaysExcludeFile.LoadOrCreate(); this.Repo.Context.FileSystem.FileExists(alwaysExcludeFilePath).ShouldEqual(true); - alwaysExcludeFile.AddEntriesForFile("a\\x.txt"); - alwaysExcludeFile.AddEntriesForFile("A\\y.txt"); - alwaysExcludeFile.AddEntriesForFile("a\\Z.txt"); - alwaysExcludeFile.RemoveEntriesForFiles(new List { "a\\y.txt", "a\\z.txt" }); + alwaysExcludeFile.AddEntriesForPath("a\\x.txt"); + alwaysExcludeFile.AddEntriesForPath("A\\y.txt"); + alwaysExcludeFile.AddEntriesForPath("a\\Z.txt"); + alwaysExcludeFile.RemoveEntriesForFile("a\\y.txt"); + alwaysExcludeFile.RemoveEntriesForFile("a\\z.txt"); alwaysExcludeFile.FlushAndClose(); List expectedContents = new List() { "*", "!/a/", "!/a/x.txt" }; diff --git a/GVFS/GVFS.UnitTests/GVFlt/GVFltActiveEnumerationTests.cs b/GVFS/GVFS.UnitTests/GVFlt/GVFltActiveEnumerationTests.cs index 577c85bf..31d8159a 100644 --- a/GVFS/GVFS.UnitTests/GVFlt/GVFltActiveEnumerationTests.cs +++ b/GVFS/GVFS.UnitTests/GVFlt/GVFltActiveEnumerationTests.cs @@ -1,15 +1,34 @@ using GVFS.GVFlt; using GVFS.Tests.Should; using NUnit.Framework; +using ProjFS; using System; using System.Collections.Generic; using System.Linq; namespace GVFS.UnitTests.GVFlt { - [TestFixture] + [TestFixtureSource(TestRunners)] public class GVFltActiveEnumerationTests { + public const string TestRunners = "Runners"; + + private static object[] patternMatchers = + { + new object[] { new PatternMatcherWrapper(Utils.IsFileNameMatch) }, + new object[] { new PatternMatcherWrapper(GVFltCallbacks.InternalFileNameMatchesFilter) }, + }; + + public GVFltActiveEnumerationTests(PatternMatcherWrapper wrapper) + { + GVFltActiveEnumeration.SetPatternMatcher(wrapper.Matcher); + } + + public static object[] Runners + { + get { return patternMatchers; } + } + [TestCase] public void EnumerationHandlesEmptyList() { @@ -100,8 +119,14 @@ namespace GVFS.UnitTests.GVFlt using (GVFltActiveEnumeration activeEnumeration = new GVFltActiveEnumeration(entries)) { - activeEnumeration.TrySaveFilterString("*.*").ShouldEqual(true); - this.ValidateActiveEnumeratorReturnsAllEntries(activeEnumeration, entries); + string filter = "*.*"; + activeEnumeration.TrySaveFilterString(filter).ShouldEqual(true); + + // "*.*" should only match when there is a . in the name + activeEnumeration.IsCurrentValid.ShouldEqual(false); + activeEnumeration.MoveNext().ShouldEqual(false); + activeEnumeration.RestartEnumeration(filter); + activeEnumeration.IsCurrentValid.ShouldEqual(false); } } @@ -195,10 +220,13 @@ namespace GVFS.UnitTests.GVFlt { List entries = new List() { + new GVFltFileInfo(".txt", size: 0, isFolder:false), new GVFltFileInfo("a", size: 0, isFolder:false), new GVFltFileInfo("B", size: 0, isFolder:true), new GVFltFileInfo("c", size: 0, isFolder:false), + new GVFltFileInfo("D.", size: 0, isFolder:false), new GVFltFileInfo("D.txt", size: 0, isFolder:false), + new GVFltFileInfo("E..log", size: 0, isFolder:false), new GVFltFileInfo("E.txt", size: 0, isFolder:false), new GVFltFileInfo("E.bat", size: 0, isFolder:false), }; @@ -206,10 +234,17 @@ namespace GVFS.UnitTests.GVFlt using (GVFltActiveEnumeration activeEnumeration = new GVFltActiveEnumeration(entries)) { activeEnumeration.IsCurrentValid.ShouldEqual(true); - activeEnumeration.TrySaveFilterString("*.*").ShouldEqual(true); + activeEnumeration.TrySaveFilterString("*").ShouldEqual(true); this.ValidateActiveEnumeratorReturnsAllEntries(activeEnumeration, entries); } + using (GVFltActiveEnumeration activeEnumeration = new GVFltActiveEnumeration(entries)) + { + activeEnumeration.IsCurrentValid.ShouldEqual(true); + activeEnumeration.TrySaveFilterString("*.*").ShouldEqual(true); + this.ValidateActiveEnumeratorReturnsAllEntries(activeEnumeration, entries.Where(entry => entry.Name.Contains("."))); + } + using (GVFltActiveEnumeration activeEnumeration = new GVFltActiveEnumeration(entries)) { activeEnumeration.IsCurrentValid.ShouldEqual(true); @@ -247,7 +282,7 @@ namespace GVFS.UnitTests.GVFlt { activeEnumeration.IsCurrentValid.ShouldEqual(true); activeEnumeration.TrySaveFilterString(">.txt").ShouldEqual(true); - this.ValidateActiveEnumeratorReturnsAllEntries(activeEnumeration, entries.Where(entry => entry.Name.Length == 5 && entry.Name.EndsWith(".txt", System.StringComparison.OrdinalIgnoreCase))); + this.ValidateActiveEnumeratorReturnsAllEntries(activeEnumeration, entries.Where(entry => entry.Name.Length <= 5 && entry.Name.EndsWith(".txt", System.StringComparison.OrdinalIgnoreCase))); } using (GVFltActiveEnumeration activeEnumeration = new GVFltActiveEnumeration(entries)) @@ -432,5 +467,15 @@ namespace GVFS.UnitTests.GVFlt // attempts to move beyond the end of the list should fail activeEnumeration.MoveNext().ShouldEqual(false); } + + public class PatternMatcherWrapper + { + public PatternMatcherWrapper(GVFltActiveEnumeration.FileNamePatternMatcher matcher) + { + this.Matcher = matcher; + } + + public GVFltActiveEnumeration.FileNamePatternMatcher Matcher { get; } + } } } \ No newline at end of file diff --git a/GVFS/GVFS.UnitTests/GVFlt/GVFltCallbacksTests.cs b/GVFS/GVFS.UnitTests/GVFlt/GVFltCallbacksTests.cs index 5412a763..dc62b009 100644 --- a/GVFS/GVFS.UnitTests/GVFlt/GVFltCallbacksTests.cs +++ b/GVFS/GVFS.UnitTests/GVFlt/GVFltCallbacksTests.cs @@ -5,11 +5,12 @@ using GVFS.UnitTests.Category; using GVFS.UnitTests.Mock.Common; using GVFS.UnitTests.Mock.Git; using GVFS.UnitTests.Mock.GvFlt; +using GVFS.UnitTests.Mock.GvFlt.BlobSize; using GVFS.UnitTests.Mock.GVFS.GvFlt; using GVFS.UnitTests.Mock.GVFS.GvFlt.DotGit; using GVFS.UnitTests.Virtual; -using GvLib; using NUnit.Framework; +using ProjFS; using System; using System.Threading.Tasks; @@ -17,22 +18,6 @@ namespace GVFS.UnitTests.GVFlt.DotGit { public class GVFltCallbacksTests : TestsWithCommonRepo { - [TestCase] - public void CannotDeleteIndexOrPacks() - { - GVFltCallbacks.DoesPathAllowDelete(string.Empty).ShouldEqual(true); - - GVFltCallbacks.DoesPathAllowDelete(@".git\index").ShouldEqual(false); - GVFltCallbacks.DoesPathAllowDelete(@".git\INDEX").ShouldEqual(false); - - GVFltCallbacks.DoesPathAllowDelete(@".git\index.lock").ShouldEqual(true); - GVFltCallbacks.DoesPathAllowDelete(@".git\INDEX.lock").ShouldEqual(true); - GVFltCallbacks.DoesPathAllowDelete(@".git\objects\pack").ShouldEqual(true); - GVFltCallbacks.DoesPathAllowDelete(@".git\objects\pack-temp").ShouldEqual(true); - GVFltCallbacks.DoesPathAllowDelete(@".git\objects\pack\pack-1e88df2a4e234c82858cfe182070645fb96d6131.pack").ShouldEqual(true); - GVFltCallbacks.DoesPathAllowDelete(@".git\objects\pack\pack-1e88df2a4e234c82858cfe182070645fb96d6131.idx").ShouldEqual(true); - } - [TestCase] public void OnStartDirectoryEnumerationReturnsPendingWhenResultsNotInMemory() { @@ -43,7 +28,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit this.Repo.Context, this.Repo.GitObjects, RepoMetadata.Instance, - blobSizes: null, + new MockBlobSizes(), gvflt: mockGvFlt, gitIndexProjection: gitIndexProjection, reliableBackgroundOperations: new MockReliableBackgroundOperations()); @@ -53,9 +38,9 @@ namespace GVFS.UnitTests.GVFlt.DotGit Guid enumerationGuid = Guid.NewGuid(); gitIndexProjection.EnumerationInMemory = false; - mockGvFlt.OnStartDirectoryEnumeration(1, enumerationGuid, "test").ShouldEqual(NtStatus.Pending); - mockGvFlt.WaitForCompletionStatus().ShouldEqual(NtStatus.Success); - mockGvFlt.OnEndDirectoryEnumeration(enumerationGuid).ShouldEqual(NtStatus.Success); + mockGvFlt.OnStartDirectoryEnumeration(1, enumerationGuid, "test").ShouldEqual(HResult.Pending); + mockGvFlt.WaitForCompletionStatus().ShouldEqual(HResult.Ok); + mockGvFlt.OnEndDirectoryEnumeration(enumerationGuid).ShouldEqual(HResult.Ok); callbacks.Stop(); } } @@ -70,7 +55,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit this.Repo.Context, this.Repo.GitObjects, RepoMetadata.Instance, - blobSizes: null, + new MockBlobSizes(), gvflt: mockGvFlt, gitIndexProjection: gitIndexProjection, reliableBackgroundOperations: new MockReliableBackgroundOperations()); @@ -80,8 +65,8 @@ namespace GVFS.UnitTests.GVFlt.DotGit Guid enumerationGuid = Guid.NewGuid(); gitIndexProjection.EnumerationInMemory = true; - mockGvFlt.OnStartDirectoryEnumeration(1, enumerationGuid, "test").ShouldEqual(NtStatus.Success); - mockGvFlt.OnEndDirectoryEnumeration(enumerationGuid).ShouldEqual(NtStatus.Success); + mockGvFlt.OnStartDirectoryEnumeration(1, enumerationGuid, "test").ShouldEqual(HResult.Ok); + mockGvFlt.OnEndDirectoryEnumeration(enumerationGuid).ShouldEqual(HResult.Ok); callbacks.Stop(); } } @@ -96,7 +81,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit this.Repo.Context, this.Repo.GitObjects, RepoMetadata.Instance, - blobSizes: null, + new MockBlobSizes(), gvflt: mockGvFlt, gitIndexProjection: gitIndexProjection, reliableBackgroundOperations: new MockReliableBackgroundOperations()); @@ -104,7 +89,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit string error; callbacks.TryStart(out error).ShouldEqual(true); - mockGvFlt.OnGetPlaceholderInformation(1, "doesNotExist", 0, 0, 0, 0, 1, "UnitTests").ShouldEqual(NtStatus.ObjectNameNotFound); + mockGvFlt.OnGetPlaceholderInformation(1, "doesNotExist", 0, 0, 0, 0, 1, "UnitTests").ShouldEqual(HResult.FileNotFound); callbacks.Stop(); } @@ -120,7 +105,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit this.Repo.Context, this.Repo.GitObjects, RepoMetadata.Instance, - blobSizes: null, + new MockBlobSizes(), gvflt: mockGvFlt, gitIndexProjection: gitIndexProjection, reliableBackgroundOperations: new MockReliableBackgroundOperations()); @@ -128,8 +113,8 @@ namespace GVFS.UnitTests.GVFlt.DotGit string error; callbacks.TryStart(out error).ShouldEqual(true); - mockGvFlt.OnGetPlaceholderInformation(1, "test.txt", 0, 0, 0, 0, 1, "UnitTests").ShouldEqual(NtStatus.Pending); - mockGvFlt.WaitForCompletionStatus().ShouldEqual(NtStatus.Success); + mockGvFlt.OnGetPlaceholderInformation(1, "test.txt", 0, 0, 0, 0, 1, "UnitTests").ShouldEqual(HResult.Pending); + mockGvFlt.WaitForCompletionStatus().ShouldEqual(HResult.Ok); mockGvFlt.CreatedPlaceholders.ShouldContain(entry => entry == "test.txt"); gitIndexProjection.PlaceholdersCreated.ShouldContain(entry => entry == "test.txt"); @@ -147,7 +132,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit this.Repo.Context, this.Repo.GitObjects, RepoMetadata.Instance, - blobSizes: null, + new MockBlobSizes(), gvflt: mockGvFlt, gitIndexProjection: gitIndexProjection, reliableBackgroundOperations: new MockReliableBackgroundOperations()); @@ -166,7 +151,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit gitIndexProjection.UnblockIsPathProjected(); }); - mockGvFlt.OnGetPlaceholderInformation(1, "test.txt", 0, 0, 0, 0, 1, "UnitTests").ShouldEqual(NtStatus.Pending); + mockGvFlt.OnGetPlaceholderInformation(1, "test.txt", 0, 0, 0, 0, 1, "UnitTests").ShouldEqual(HResult.Pending); // Cancelling before GetPlaceholderInformation has registered the command results in placeholders being created mockGvFlt.WaitForPlaceholderCreate(); @@ -188,7 +173,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit this.Repo.Context, this.Repo.GitObjects, RepoMetadata.Instance, - blobSizes: null, + new MockBlobSizes(), gvflt: mockGvFlt, gitIndexProjection: gitIndexProjection, reliableBackgroundOperations: new MockReliableBackgroundOperations()); @@ -197,7 +182,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit callbacks.TryStart(out error).ShouldEqual(true); gitIndexProjection.BlockGetProjectedFileInfo(willWaitForRequest: true); - mockGvFlt.OnGetPlaceholderInformation(1, "test.txt", 0, 0, 0, 0, 1, "UnitTests").ShouldEqual(NtStatus.Pending); + mockGvFlt.OnGetPlaceholderInformation(1, "test.txt", 0, 0, 0, 0, 1, "UnitTests").ShouldEqual(HResult.Pending); gitIndexProjection.WaitForGetProjectedFileInfo(); mockGvFlt.OnCancelCommand(1); gitIndexProjection.UnblockGetProjectedFileInfo(); @@ -224,7 +209,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit this.Repo.Context, this.Repo.GitObjects, RepoMetadata.Instance, - blobSizes: null, + new MockBlobSizes(), gvflt: mockGvFlt, gitIndexProjection: gitIndexProjection, reliableBackgroundOperations: new MockReliableBackgroundOperations()); @@ -235,7 +220,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit MockTracer mockTracker = this.Repo.Context.Tracer as MockTracer; mockTracker.WaitRelatedEventName = "GVFltGetPlaceholderInformationAsyncHandler_GetProjectedGVFltFileInfoAndShaCancelled"; gitIndexProjection.ThrowOperationCanceledExceptionOnProjectionRequest = true; - mockGvFlt.OnGetPlaceholderInformation(1, "test.txt", 0, 0, 0, 0, 1, "UnitTests").ShouldEqual(NtStatus.Pending); + mockGvFlt.OnGetPlaceholderInformation(1, "test.txt", 0, 0, 0, 0, 1, "UnitTests").ShouldEqual(HResult.Pending); // Cancelling in the middle of GetPlaceholderInformation in the middle of a network request should not result in placeholder // getting created @@ -248,7 +233,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit } [TestCase] - public void OnGetFileStreamReturnsInvalidParameterWhenOffsetNonZero() + public void OnGetFileStreamReturnsInternalErrorWhenOffsetNonZero() { using (MockVirtualizationInstance mockGvFlt = new MockVirtualizationInstance()) using (MockGitIndexProjection gitIndexProjection = new MockGitIndexProjection(new[] { "test.txt" })) @@ -257,7 +242,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit this.Repo.Context, this.Repo.GitObjects, RepoMetadata.Instance, - blobSizes: null, + new MockBlobSizes(), gvflt: mockGvFlt, gitIndexProjection: gitIndexProjection, reliableBackgroundOperations: new MockReliableBackgroundOperations()); @@ -268,7 +253,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit Guid enumerationGuid = Guid.NewGuid(); byte[] contentId = GVFltCallbacks.ConvertShaToContentId("0123456789012345678901234567890123456789"); - byte[] epochId = GVFltCallbacks.GetEpochId(); + byte[] placeholderVersion = GVFltCallbacks.GetPlaceholderVersionId(); mockGvFlt.OnGetFileStream( commandId: 1, @@ -277,9 +262,9 @@ namespace GVFS.UnitTests.GVFlt.DotGit length: 100, streamGuid: Guid.NewGuid(), contentId: contentId, - epochId: epochId, + providerId: placeholderVersion, triggeringProcessId: 2, - triggeringProcessImageFileName: "UnitTest").ShouldEqual(NtStatus.InvalidParameter); + triggeringProcessImageFileName: "UnitTest").ShouldEqual(HResult.InternalError); callbacks.Stop(); } @@ -295,7 +280,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit this.Repo.Context, this.Repo.GitObjects, RepoMetadata.Instance, - blobSizes: null, + new MockBlobSizes(), gvflt: mockGvFlt, gitIndexProjection: gitIndexProjection, reliableBackgroundOperations: new MockReliableBackgroundOperations()); @@ -315,9 +300,9 @@ namespace GVFS.UnitTests.GVFlt.DotGit length: 100, streamGuid: Guid.NewGuid(), contentId: contentId, - epochId: epochId, + providerId: epochId, triggeringProcessId: 2, - triggeringProcessImageFileName: "UnitTest").ShouldEqual(NtStatus.InternalError); + triggeringProcessImageFileName: "UnitTest").ShouldEqual(HResult.InternalError); callbacks.Stop(); } @@ -333,7 +318,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit this.Repo.Context, this.Repo.GitObjects, RepoMetadata.Instance, - blobSizes: null, + new MockBlobSizes(), gvflt: mockGvFlt, gitIndexProjection: gitIndexProjection, reliableBackgroundOperations: new MockReliableBackgroundOperations()); @@ -344,12 +329,12 @@ namespace GVFS.UnitTests.GVFlt.DotGit Guid enumerationGuid = Guid.NewGuid(); byte[] contentId = GVFltCallbacks.ConvertShaToContentId("0123456789012345678901234567890123456789"); - byte[] epochId = GVFltCallbacks.GetEpochId(); + byte[] placeholderVersion = GVFltCallbacks.GetPlaceholderVersionId(); uint fileLength = 100; MockGVFSGitObjects mockGVFSGitObjects = this.Repo.GitObjects as MockGVFSGitObjects; mockGVFSGitObjects.FileLength = fileLength; - mockGvFlt.WriteFileReturnStatus = NtStatus.Success; + mockGvFlt.WriteFileReturnResult = HResult.Ok; mockGvFlt.OnGetFileStream( commandId: 1, @@ -358,11 +343,11 @@ namespace GVFS.UnitTests.GVFlt.DotGit length: fileLength, streamGuid: Guid.NewGuid(), contentId: contentId, - epochId: epochId, + providerId: placeholderVersion, triggeringProcessId: 2, - triggeringProcessImageFileName: "UnitTest").ShouldEqual(NtStatus.Pending); + triggeringProcessImageFileName: "UnitTest").ShouldEqual(HResult.Pending); - mockGvFlt.WaitForCompletionStatus().ShouldEqual(NtStatus.Success); + mockGvFlt.WaitForCompletionStatus().ShouldEqual(HResult.Ok); callbacks.Stop(); } @@ -379,7 +364,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit this.Repo.Context, this.Repo.GitObjects, RepoMetadata.Instance, - blobSizes: null, + new MockBlobSizes(), gvflt: mockGvFlt, gitIndexProjection: gitIndexProjection, reliableBackgroundOperations: new MockReliableBackgroundOperations()); @@ -390,7 +375,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit Guid enumerationGuid = Guid.NewGuid(); byte[] contentId = GVFltCallbacks.ConvertShaToContentId("0123456789012345678901234567890123456789"); - byte[] epochId = GVFltCallbacks.GetEpochId(); + byte[] placeholderVersion = GVFltCallbacks.GetPlaceholderVersionId(); MockGVFSGitObjects mockGVFSGitObjects = this.Repo.GitObjects as MockGVFSGitObjects; @@ -405,9 +390,9 @@ namespace GVFS.UnitTests.GVFlt.DotGit length: 100, streamGuid: Guid.NewGuid(), contentId: contentId, - epochId: epochId, + providerId: placeholderVersion, triggeringProcessId: 2, - triggeringProcessImageFileName: "UnitTest").ShouldEqual(NtStatus.Pending); + triggeringProcessImageFileName: "UnitTest").ShouldEqual(HResult.Pending); mockTracker.WaitForRelatedEvent(); @@ -426,7 +411,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit this.Repo.Context, this.Repo.GitObjects, RepoMetadata.Instance, - blobSizes: null, + new MockBlobSizes(), gvflt: mockGvFlt, gitIndexProjection: gitIndexProjection, reliableBackgroundOperations: new MockReliableBackgroundOperations()); @@ -437,7 +422,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit Guid enumerationGuid = Guid.NewGuid(); byte[] contentId = GVFltCallbacks.ConvertShaToContentId("0123456789012345678901234567890123456789"); - byte[] epochId = GVFltCallbacks.GetEpochId(); + byte[] placeholderVersion = GVFltCallbacks.GetPlaceholderVersionId(); uint fileLength = 100; MockGVFSGitObjects mockGVFSGitObjects = this.Repo.GitObjects as MockGVFSGitObjects; @@ -454,9 +439,9 @@ namespace GVFS.UnitTests.GVFlt.DotGit length: fileLength, streamGuid: Guid.NewGuid(), contentId: contentId, - epochId: epochId, + providerId: placeholderVersion, triggeringProcessId: 2, - triggeringProcessImageFileName: "UnitTest").ShouldEqual(NtStatus.Pending); + triggeringProcessImageFileName: "UnitTest").ShouldEqual(HResult.Pending); mockGvFlt.WaitForCreateWriteBuffer(); mockGvFlt.OnCancelCommand(1); @@ -478,7 +463,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit this.Repo.Context, this.Repo.GitObjects, RepoMetadata.Instance, - blobSizes: null, + new MockBlobSizes(), gvflt: mockGvFlt, gitIndexProjection: gitIndexProjection, reliableBackgroundOperations: new MockReliableBackgroundOperations()); @@ -489,7 +474,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit Guid enumerationGuid = Guid.NewGuid(); byte[] contentId = GVFltCallbacks.ConvertShaToContentId("0123456789012345678901234567890123456789"); - byte[] epochId = GVFltCallbacks.GetEpochId(); + byte[] placeholderVersion = GVFltCallbacks.GetPlaceholderVersionId(); uint fileLength = 100; MockGVFSGitObjects mockGVFSGitObjects = this.Repo.GitObjects as MockGVFSGitObjects; @@ -498,7 +483,7 @@ namespace GVFS.UnitTests.GVFlt.DotGit MockTracer mockTracker = this.Repo.Context.Tracer as MockTracer; mockTracker.WaitRelatedEventName = "GVFltGetFileStreamHandlerAsyncHandler_OperationCancelled"; - mockGvFlt.WriteFileReturnStatus = NtStatus.InternalError; + mockGvFlt.WriteFileReturnResult = HResult.InternalError; mockGvFlt.OnGetFileStream( commandId: 1, relativePath: "test.txt", @@ -506,11 +491,11 @@ namespace GVFS.UnitTests.GVFlt.DotGit length: fileLength, streamGuid: Guid.NewGuid(), contentId: contentId, - epochId: epochId, + providerId: placeholderVersion, triggeringProcessId: 2, - triggeringProcessImageFileName: "UnitTest").ShouldEqual(NtStatus.Pending); + triggeringProcessImageFileName: "UnitTest").ShouldEqual(HResult.Pending); - mockGvFlt.WaitForCompletionStatus().ShouldEqual(mockGvFlt.WriteFileReturnStatus); + mockGvFlt.WaitForCompletionStatus().ShouldEqual(mockGvFlt.WriteFileReturnResult); callbacks.Stop(); } diff --git a/GVFS/GVFS.UnitTests/GVFlt/PathUtilTests.cs b/GVFS/GVFS.UnitTests/GVFlt/PathUtilTests.cs index 0f6ce5aa..f7a5febd 100644 --- a/GVFS/GVFS.UnitTests/GVFlt/PathUtilTests.cs +++ b/GVFS/GVFS.UnitTests/GVFlt/PathUtilTests.cs @@ -46,20 +46,5 @@ namespace GVFS.UnitTests.GVFlt PathUtil.RemoveTrailingSlashIfPresent(@"C:\test\\").ShouldEqual(@"C:\test"); PathUtil.RemoveTrailingSlashIfPresent(@"C:\test\\\").ShouldEqual(@"C:\test"); } - - [TestCase] - public void IsEnumerationFilterSet() - { - PathUtil.IsEnumerationFilterSet(null).ShouldEqual(false); - PathUtil.IsEnumerationFilterSet(string.Empty).ShouldEqual(false); - PathUtil.IsEnumerationFilterSet(" ").ShouldEqual(false); - PathUtil.IsEnumerationFilterSet("*").ShouldEqual(false); - - PathUtil.IsEnumerationFilterSet("*.*").ShouldEqual(true); - PathUtil.IsEnumerationFilterSet("A.*").ShouldEqual(true); - PathUtil.IsEnumerationFilterSet("*.txt").ShouldEqual(true); - PathUtil.IsEnumerationFilterSet("A.txt").ShouldEqual(true); - PathUtil.IsEnumerationFilterSet("A").ShouldEqual(true); - } } } diff --git a/GVFS/GVFS.UnitTests/GVFlt/PatternMatcherTests.cs b/GVFS/GVFS.UnitTests/GVFlt/PatternMatcherTests.cs index 1c528620..6e372568 100644 --- a/GVFS/GVFS.UnitTests/GVFlt/PatternMatcherTests.cs +++ b/GVFS/GVFS.UnitTests/GVFlt/PatternMatcherTests.cs @@ -1,6 +1,7 @@ using GVFS.GVFlt; using GVFS.Tests.Should; using NUnit.Framework; +using ProjFS; namespace GVFS.UnitTests.GVFlt { @@ -12,131 +13,148 @@ namespace GVFS.UnitTests.GVFlt private const char DOSDot = '"'; [TestCase] - public void EmptyStringsDoNotMatch() + public void EmptyPatternShouldMatch() { - PatternMatcher.StrictMatchPattern(null, "Test").ShouldEqual(false); - PatternMatcher.StrictMatchPattern(string.Empty, "Test").ShouldEqual(false); - PatternMatcher.StrictMatchPattern("Test", null).ShouldEqual(false); - PatternMatcher.StrictMatchPattern("Test", string.Empty).ShouldEqual(false); - PatternMatcher.StrictMatchPattern(null, null).ShouldEqual(false); - PatternMatcher.StrictMatchPattern(string.Empty, string.Empty).ShouldEqual(false); + PatternShouldMatch(null, "Test"); + PatternShouldMatch(string.Empty, "Test"); + } + + [TestCase] + public void EmptyNameDoesNotMatch() + { + PatternShouldNotMatch("Test", null); + PatternShouldNotMatch("Test", string.Empty); + PatternShouldNotMatch(null, null); + PatternShouldNotMatch(string.Empty, string.Empty); } [TestCase] public void IdenticalStringsMatch() { - PatternMatcher.StrictMatchPattern("Test", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt").ShouldEqual(true); + PatternShouldMatch("Test", "Test"); + PatternShouldMatch("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt"); } [TestCase] public void MatchingIsCaseInsensitive() { - PatternMatcher.StrictMatchPattern("Test", "TEST").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("TEST", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.TXT").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.TXT", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt").ShouldEqual(true); + PatternShouldMatch("Test", "TEST"); + PatternShouldMatch("TEST", "Test"); + PatternShouldMatch("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.TXT"); + PatternShouldMatch("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.TXT", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt"); } [TestCase] public void WildCardSearchMatchesEverything() { - PatternMatcher.StrictMatchPattern("*", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("*.*", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("*", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("*.*", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt").ShouldEqual(true); + PatternShouldMatch("*", "Test"); + PatternShouldNotMatch("*.*", "Test"); + PatternShouldMatch("*", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt"); + PatternShouldMatch("*.*", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt"); } [TestCase] public void TestLeadingStarPattern() { - PatternMatcher.StrictMatchPattern("*est", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("*EST", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("*txt", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("*.TXT", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt").ShouldEqual(true); + PatternShouldMatch("*est", "Test"); + PatternShouldMatch("*EST", "Test"); + PatternShouldMatch("*txt", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt"); + PatternShouldMatch("*.TXT", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt"); } [TestCase] public void TestLeadingDosStarPattern() { - PatternMatcher.StrictMatchPattern(DOSStar + "est", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern(DOSStar + "EST", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern(DOSStar + "txt", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt").ShouldEqual(true); - PatternMatcher.StrictMatchPattern(DOSStar + "TXT", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt").ShouldEqual(true); + PatternShouldMatch(DOSStar + "est", "Test"); + PatternShouldMatch(DOSStar + "EST", "Test"); + PatternShouldMatch(DOSStar + "txt", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt"); + PatternShouldMatch(DOSStar + "TXT", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt"); } [TestCase] public void TestTrailingDosQmPattern() { - PatternMatcher.StrictMatchPattern("Test" + DOSQm, "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("TEST" + DOSQm, "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt" + DOSQm, "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt").ShouldEqual(true); + PatternShouldMatch("Test" + DOSQm, "Test"); + PatternShouldMatch("TEST" + DOSQm, "Test"); + PatternShouldMatch("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt" + DOSQm, "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt"); } [TestCase] public void TestQuestionMarkPattern() { - PatternMatcher.StrictMatchPattern("???", "Test").ShouldEqual(false); - PatternMatcher.StrictMatchPattern("????", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("?????", "Test").ShouldEqual(false); + PatternShouldNotMatch("???", "Test"); + PatternShouldMatch("????", "Test"); + PatternShouldNotMatch("?????", "Test"); } [TestCase] public void TestMixedQuestionMarkPattern() { - PatternMatcher.StrictMatchPattern("T?st", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("T?ST", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("T??ST", "Test").ShouldEqual(false); + PatternShouldMatch("T?st", "Test"); + PatternShouldMatch("T?ST", "Test"); + PatternShouldNotMatch("T??ST", "Test"); } [TestCase] public void TestMixedStarPattern() { - PatternMatcher.StrictMatchPattern("T*est", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("T*t", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("T*T", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("ر*يلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt").ShouldEqual(true); + PatternShouldMatch("T*est", "Test"); + PatternShouldMatch("T*t", "Test"); + PatternShouldMatch("T*T", "Test"); + PatternShouldMatch("ر*يلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt"); } [TestCase] public void TestMixedStarAndQuestionMarkPattern() { - PatternMatcher.StrictMatchPattern("T*?est", "Test").ShouldEqual(false); - PatternMatcher.StrictMatchPattern("T*?t", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("T*?", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("t*?", "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("ر*يلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.?xt", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt").ShouldEqual(true); + PatternShouldNotMatch("T*?est", "Test"); + PatternShouldMatch("T*?t", "Test"); + PatternShouldMatch("T*?", "Test"); + PatternShouldMatch("t*?", "Test"); + PatternShouldMatch("ر*يلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.?xt", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt"); } [TestCase] public void TestDosStarPattern() { - PatternMatcher.StrictMatchPattern("T" + DOSStar, "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("t" + DOSStar + "txt", "Test.txt").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("ر*يلٌأكتوبرû" + DOSStar + "TXT", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt").ShouldEqual(true); + PatternShouldMatch("T" + DOSStar, "Test"); + PatternShouldMatch("t" + DOSStar + "txt", "Test.txt"); + PatternShouldMatch("ر*يلٌأكتوبرû" + DOSStar + "TXT", "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt"); } [TestCase] public void TestDosDotPattern() { - PatternMatcher.StrictMatchPattern("Test" + DOSDot, "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("Test" + DOSDot, "Test.").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("Test" + DOSDot, "Test.txt").ShouldEqual(false); - PatternMatcher.StrictMatchPattern("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt" + DOSDot, "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt" + DOSDot, "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt.").ShouldEqual(true); - PatternMatcher.StrictMatchPattern("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt" + DOSDot, "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt.temp").ShouldEqual(false); + PatternShouldMatch("Test" + DOSDot, "Test"); + PatternShouldMatch("Test" + DOSDot, "Test."); + PatternShouldNotMatch("Test" + DOSDot, "Test.txt"); + PatternShouldMatch("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt" + DOSDot, "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt"); + PatternShouldMatch("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt" + DOSDot, "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt."); + PatternShouldNotMatch("ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt" + DOSDot, "ريلٌأكتوبرûمارسأغسطسºٰٰۂْٗ۵ريلٌأك.txt.temp"); } [TestCase] public void TestDosQmPattern() { - PatternMatcher.StrictMatchPattern(string.Concat(DOSQm, DOSQm, DOSQm), "Test").ShouldEqual(false); - PatternMatcher.StrictMatchPattern(string.Concat(DOSQm, DOSQm, DOSQm, DOSQm), "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern(string.Concat(DOSQm, DOSQm, DOSQm, DOSQm, DOSQm), "Test").ShouldEqual(true); + PatternShouldNotMatch(string.Concat(DOSQm, DOSQm, DOSQm), "Test"); + PatternShouldMatch(string.Concat(DOSQm, DOSQm, DOSQm, DOSQm), "Test"); + PatternShouldMatch(string.Concat(DOSQm, DOSQm, DOSQm, DOSQm, DOSQm), "Test"); - PatternMatcher.StrictMatchPattern(string.Concat("Te", DOSQm), "Test").ShouldEqual(false); - PatternMatcher.StrictMatchPattern(string.Concat("TE", DOSQm, DOSQm), "Test").ShouldEqual(true); - PatternMatcher.StrictMatchPattern(string.Concat("te", DOSQm, DOSQm, DOSQm), "Test").ShouldEqual(true); + PatternShouldNotMatch(string.Concat("Te", DOSQm), "Test"); + PatternShouldMatch(string.Concat("TE", DOSQm, DOSQm), "Test"); + PatternShouldMatch(string.Concat("te", DOSQm, DOSQm, DOSQm), "Test"); + } + + private static void PatternShouldMatch(string filter, string name) + { + PatternMatcher.StrictMatchPattern(filter, name).ShouldBeTrue(); + Utils.IsFileNameMatch(name, filter).ShouldBeTrue(); + } + + private static void PatternShouldNotMatch(string filter, string name) + { + PatternMatcher.StrictMatchPattern(filter, name).ShouldBeFalse(); + Utils.IsFileNameMatch(name, filter).ShouldBeFalse(); } } } \ No newline at end of file diff --git a/GVFS/GVFS.UnitTests/Git/GVFSGitObjectsTests.cs b/GVFS/GVFS.UnitTests/Git/GVFSGitObjectsTests.cs index 4dd5820b..0c7d448c 100644 --- a/GVFS/GVFS.UnitTests/Git/GVFSGitObjectsTests.cs +++ b/GVFS/GVFS.UnitTests/Git/GVFSGitObjectsTests.cs @@ -139,7 +139,7 @@ namespace GVFS.UnitTests.Git { MockTracer tracer = new MockTracer(); GVFSEnlistment enlistment = new GVFSEnlistment(TestEnlistmentRoot, "https://fakeRepoUrl", "fakeGitBinPath", gvfsHooksRoot: null); - enlistment.InitializeLocalCacheAndObjectPaths(TestLocalCacheRoot, TestObjecRoot); + enlistment.InitializeCachePathsFromKey(TestLocalCacheRoot, TestObjecRoot); GitRepo repo = new GitRepo(tracer, enlistment, fileSystem, () => new MockLibGit2Repo(tracer)); GVFSContext context = new GVFSContext(tracer, fileSystem, repo, enlistment); diff --git a/GVFS/GVFS.UnitTests/Mock/Common/MockEnlistment.cs b/GVFS/GVFS.UnitTests/Mock/Common/MockEnlistment.cs index a2573efe..264a62b4 100644 --- a/GVFS/GVFS.UnitTests/Mock/Common/MockEnlistment.cs +++ b/GVFS/GVFS.UnitTests/Mock/Common/MockEnlistment.cs @@ -12,6 +12,7 @@ namespace GVFS.UnitTests.Mock.Common : base("mock:\\path", "mock:\\path", "mock://repoUrl", "mock:\\git", null, flushFileBuffersForPacks: false) { this.GitObjectsRoot = "mock:\\path\\.git\\objects"; + this.LocalObjectsRoot = this.GitObjectsRoot; this.GitPackRoot = "mock:\\path\\.git\\objects\\pack"; } @@ -20,9 +21,11 @@ namespace GVFS.UnitTests.Mock.Common { this.gitProcess = gitProcess; } - + public override string GitObjectsRoot { get; protected set; } + public override string LocalObjectsRoot { get; protected set; } + public override string GitPackRoot { get; protected set; } public override GitProcess CreateGitProcess() diff --git a/GVFS/GVFS.UnitTests/Mock/Common/MockTracer.cs b/GVFS/GVFS.UnitTests/Mock/Common/MockTracer.cs index 0ea57bea..0e2a0127 100644 --- a/GVFS/GVFS.UnitTests/Mock/Common/MockTracer.cs +++ b/GVFS/GVFS.UnitTests/Mock/Common/MockTracer.cs @@ -101,8 +101,9 @@ namespace GVFS.UnitTests.Mock.Common return new MockTracer(); } - public void Stop(EventMetadata metadata) + public TimeSpan Stop(EventMetadata metadata) { + return TimeSpan.Zero; } public void Dispose() diff --git a/GVFS/GVFS.UnitTests/Mock/FileSystem/MockFileSystem.cs b/GVFS/GVFS.UnitTests/Mock/FileSystem/MockFileSystem.cs index 0ade7ef0..a977ef3f 100644 --- a/GVFS/GVFS.UnitTests/Mock/FileSystem/MockFileSystem.cs +++ b/GVFS/GVFS.UnitTests/Mock/FileSystem/MockFileSystem.cs @@ -184,6 +184,16 @@ namespace GVFS.UnitTests.Mock.FileSystem { } + public override void MoveFile(string sourcePath, string targetPath) + { + throw new NotImplementedException(); + } + + public override string[] GetFiles(string directoryPath, string mask) + { + throw new NotImplementedException(); + } + private Stream CreateAndOpenFileStream(string path) { MockFile file = this.RootDirectory.CreateFile(path); diff --git a/GVFS/GVFS.UnitTests/Mock/FileSystem/MockFileSystemWithCallbacks.cs b/GVFS/GVFS.UnitTests/Mock/FileSystem/MockFileSystemWithCallbacks.cs index 0d721b46..a1f506f0 100644 --- a/GVFS/GVFS.UnitTests/Mock/FileSystem/MockFileSystemWithCallbacks.cs +++ b/GVFS/GVFS.UnitTests/Mock/FileSystem/MockFileSystemWithCallbacks.cs @@ -65,5 +65,15 @@ namespace GVFS.UnitTests.Mock.FileSystem public override void SetAttributes(string path, FileAttributes fileAttributes) { } + + public override void MoveFile(string sourcePath, string targetPath) + { + throw new NotImplementedException(); + } + + public override string[] GetFiles(string directoryPath, string mask) + { + throw new NotImplementedException(); + } } } diff --git a/GVFS/GVFS.UnitTests/Mock/GVFS.GvFlt/DotGit/MockGitIndexProjection.cs b/GVFS/GVFS.UnitTests/Mock/GVFS.GvFlt/DotGit/MockGitIndexProjection.cs index fe95937b..da1e7c54 100644 --- a/GVFS/GVFS.UnitTests/Mock/GVFS.GvFlt/DotGit/MockGitIndexProjection.cs +++ b/GVFS/GVFS.UnitTests/Mock/GVFS.GvFlt/DotGit/MockGitIndexProjection.cs @@ -1,5 +1,6 @@ using GVFS.Common; using GVFS.GVFlt; +using GVFS.GVFlt.BlobSize; using GVFS.GVFlt.DotGit; using System; using System.Collections.Generic; @@ -140,7 +141,10 @@ namespace GVFS.UnitTests.Mock.GVFS.GvFlt.DotGit return false; } - public override IEnumerable GetProjectedItems(string folderPath, CancellationToken cancellationToken) + public override IEnumerable GetProjectedItems( + CancellationToken cancellationToken, + BlobSizes.BlobSizesConnection blobSizesConnection, + string folderPath) { this.waitForGetProjectedItems.Set(); @@ -171,7 +175,12 @@ namespace GVFS.UnitTests.Mock.GVFS.GvFlt.DotGit return false; } - public override GVFltFileInfo GetProjectedGVFltFileInfoAndSha(CancellationToken cancellationToken, string virtualPath, out string parentFolderPath, out string sha) + public override GVFltFileInfo GetProjectedGVFltFileInfoAndSha( + CancellationToken cancellationToken, + BlobSizes.BlobSizesConnection blobSizesConnection, + string virtualPath, + out string parentFolderPath, + out string sha) { this.waitForGetProjectedFileInfo.Set(); diff --git a/GVFS/GVFS.UnitTests/Mock/GvFlt/BlobSize/MockBlobSizesDatabase.cs b/GVFS/GVFS.UnitTests/Mock/GvFlt/BlobSize/MockBlobSizesDatabase.cs new file mode 100644 index 00000000..3285556a --- /dev/null +++ b/GVFS/GVFS.UnitTests/Mock/GvFlt/BlobSize/MockBlobSizesDatabase.cs @@ -0,0 +1,51 @@ +using GVFS.Common.Git; +using GVFS.GVFlt.BlobSize; +using GVFS.UnitTests.Mock.Common; +using System; + +namespace GVFS.UnitTests.Mock.GvFlt.BlobSize +{ + public class MockBlobSizes : BlobSizes + { + public MockBlobSizes() + : base("mock:\\blobSizeDatabase", fileSystem: null, tracer: new MockTracer()) + { + } + + public override void Initialize() + { + } + + public override void Shutdown() + { + } + + public override BlobSizesConnection CreateConnection() + { + return new MockBlobSizesConnection(this); + } + + public override void AddSize(Sha1Id sha, long length) + { + throw new NotSupportedException("SaveValue has not been implemented yet."); + } + + public override void Flush() + { + throw new NotSupportedException("Flush has not been implemented yet."); + } + + public class MockBlobSizesConnection : BlobSizesConnection + { + public MockBlobSizesConnection(MockBlobSizes mockBlobSizesDatabase) + : base(mockBlobSizesDatabase) + { + } + + public override bool TryGetSize(Sha1Id sha, out long length) + { + throw new NotSupportedException("TryGetSize has not been implemented yet."); + } + } + } +} diff --git a/GVFS/GVFS.UnitTests/Mock/GvFlt/MockVirtualizationInstance.cs b/GVFS/GVFS.UnitTests/Mock/GvFlt/MockVirtualizationInstance.cs index ca9505a0..c9266708 100644 --- a/GVFS/GVFS.UnitTests/Mock/GvFlt/MockVirtualizationInstance.cs +++ b/GVFS/GVFS.UnitTests/Mock/GvFlt/MockVirtualizationInstance.cs @@ -1,5 +1,5 @@ using GVFS.Common; -using GvLib; +using ProjFS; using System; using System.Collections.Generic; using System.Threading; @@ -22,10 +22,10 @@ namespace GVFS.UnitTests.Mock.GvFlt this.unblockCreateWriteBuffer = new ManualResetEvent(true); this.waitForCreateWriteBuffer = new ManualResetEvent(true); - this.WriteFileReturnStatus = NtStatus.Success; + this.WriteFileReturnResult = HResult.Ok; } - public NtStatus CompletionStatus { get; set; } + public HResult CompletionResult { get; set; } public ConcurrentHashSet CreatedPlaceholders { get; private set; } @@ -39,8 +39,8 @@ namespace GVFS.UnitTests.Mock.GvFlt public NotifyFileSupersededOrOverwrittenEvent OnNotifyFileSupersededOrOverwritten { get; set; } public NotifyFileHandleClosedNoModificationEvent OnNotifyFileHandleClosedNoModification { get; set; } public NotifyFileHandleClosedFileModifiedOrDeletedEvent OnNotifyFileHandleClosedFileModifiedOrDeleted { get; set; } + public NotifyFilePreConvertToFullEvent OnNotifyFilePreConvertToFull { get; set; } public NotifyFileRenamedEvent OnNotifyFileRenamed { get; set; } - public NotifyFirstWriteEvent OnNotifyFirstWrite { get; set; } public NotifyHardlinkCreatedEvent OnNotifyHardlinkCreated { get; set; } public NotifyPreDeleteEvent OnNotifyPreDelete { get; set; } public NotifyPreRenameEvent OnNotifyPreRename { get; set; } @@ -48,7 +48,7 @@ namespace GVFS.UnitTests.Mock.GvFlt public QueryFileNameEvent OnQueryFileName { get; set; } public StartDirectoryEnumerationEvent OnStartDirectoryEnumeration { get; set; } - public NtStatus WriteFileReturnStatus { get; set; } + public HResult WriteFileReturnResult { get; set; } public HResult StartVirtualizationInstance( string virtualizationRootPath, @@ -90,22 +90,27 @@ namespace GVFS.UnitTests.Mock.GvFlt return HResult.Ok; } - public NtStatus ClearNegativePathCache(ref uint totalEntryNumber) + public HResult ClearNegativePathCache(ref uint totalEntryNumber) { throw new NotImplementedException(); } - public NtStatus DeleteFile(string relativePath, UpdateType updateFlags, ref UpdateFailureCause failureReason) + public HResult DeleteFile(string relativePath, UpdateType updateFlags, ref UpdateFailureCause failureReason) { throw new NotImplementedException(); } - public NtStatus UpdatePlaceholderIfNeeded(string relativePath, DateTime creationTime, DateTime lastAccessTime, DateTime lastWriteTime, DateTime changeTime, uint fileAttributes, long endOfFile, byte[] contentId, byte[] epochId, UpdateType updateFlags, ref UpdateFailureCause failureReason) + public HResult UpdatePlaceholderIfNeeded(string relativePath, DateTime creationTime, DateTime lastAccessTime, DateTime lastWriteTime, DateTime changeTime, uint fileAttributes, long endOfFile, byte[] contentId, byte[] epochId, UpdateType updateFlags, ref UpdateFailureCause failureReason) { throw new NotImplementedException(); } - public NtStatus CreatePlaceholderAsHardlink(string destinationFileName, string hardLinkTarget) + public HResult CreatePlaceholderAsHardlink(string destinationFileName, string hardLinkTarget) + { + throw new NotImplementedException(); + } + + public HResult ConvertDirectoryToPlaceholder(string targetDirectoryPath, byte[] contentId, byte[] providerId) { throw new NotImplementedException(); } @@ -118,12 +123,12 @@ namespace GVFS.UnitTests.Mock.GvFlt return new WriteBuffer(desiredBufferSize, 1); } - public NtStatus WriteFile(Guid streamGuid, WriteBuffer buffer, ulong byteOffset, uint length) + public HResult WriteFile(Guid streamGuid, WriteBuffer buffer, ulong byteOffset, uint length) { - return this.WriteFileReturnStatus; + return this.WriteFileReturnResult; } - public NtStatus WritePlaceholderInformation( + public HResult WritePlaceholderInformation( string relativePath, DateTime creationTime, DateTime lastAccessTime, @@ -137,19 +142,19 @@ namespace GVFS.UnitTests.Mock.GvFlt { this.CreatedPlaceholders.Add(relativePath); this.placeholderCreated.Set(); - return NtStatus.Success; + return HResult.Ok; } - public void CompleteCommand(int commandId, NtStatus completionStatus) + public void CompleteCommand(int commandId, HResult completionResult) { - this.CompletionStatus = completionStatus; + this.CompletionResult = completionResult; this.commandCompleted.Set(); } - public NtStatus WaitForCompletionStatus() + public HResult WaitForCompletionStatus() { this.commandCompleted.WaitOne(); - return this.CompletionStatus; + return this.CompletionResult; } public void WaitForPlaceholderCreate() diff --git a/GVFS/GVFS.UnitTests/Program.cs b/GVFS/GVFS.UnitTests/Program.cs index ab577e9a..06b32eff 100644 --- a/GVFS/GVFS.UnitTests/Program.cs +++ b/GVFS/GVFS.UnitTests/Program.cs @@ -16,7 +16,7 @@ namespace GVFS.UnitTests runner.ExcludeCategory(CategoryConstants.ExceptionExpected); } - Environment.ExitCode = runner.RunTests(1); + Environment.ExitCode = runner.RunTests(); if (Debugger.IsAttached) { diff --git a/GVFS/GVFS.UnitTests/Virtual/CommonRepoSetup.cs b/GVFS/GVFS.UnitTests/Virtual/CommonRepoSetup.cs index 7cbdbb94..9465a8c2 100644 --- a/GVFS/GVFS.UnitTests/Virtual/CommonRepoSetup.cs +++ b/GVFS/GVFS.UnitTests/Virtual/CommonRepoSetup.cs @@ -3,7 +3,6 @@ using GVFS.Common.Git; using GVFS.UnitTests.Mock.Common; using GVFS.UnitTests.Mock.FileSystem; using GVFS.UnitTests.Mock.Git; -using NUnit.Framework; using System; using System.IO; @@ -15,15 +14,9 @@ namespace GVFS.UnitTests.Virtual { MockTracer tracer = new MockTracer(); - string gitBinPath = GitProcess.GetInstalledGitBinPath(); - if (string.IsNullOrWhiteSpace(gitBinPath)) - { - Assert.Fail("Failed to find git.exe"); - } - string enlistmentRoot = @"mock:\GVFS\UnitTests\Repo"; - GVFSEnlistment enlistment = new GVFSEnlistment(enlistmentRoot, "fake://repoUrl", gitBinPath, null); - enlistment.InitializeLocalCacheAndObjectsPathsFromKey("fake:\\objectCache", "fakeObjectCacheKey"); + GVFSEnlistment enlistment = new GVFSEnlistment(enlistmentRoot, "fake://repoUrl", "fake://gitBinPath", null); + enlistment.InitializeCachePathsFromKey("fake:\\gvfsSharedCache", "fakeCacheKey"); this.GitParentPath = enlistment.WorkingDirectoryRoot; this.GVFSMetadataPath = enlistment.DotGVFSRoot; diff --git a/GVFS/GVFS.UnitTests/packages.config b/GVFS/GVFS.UnitTests/packages.config index e675e68a..338cdba8 100644 --- a/GVFS/GVFS.UnitTests/packages.config +++ b/GVFS/GVFS.UnitTests/packages.config @@ -1,14 +1,12 @@ - + - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + diff --git a/GVFS/GVFS/App.config b/GVFS/GVFS/App.config index b8a7a317..e33092cd 100644 --- a/GVFS/GVFS/App.config +++ b/GVFS/GVFS/App.config @@ -1,5 +1,5 @@ - + - + - \ No newline at end of file + diff --git a/GVFS/GVFS/CommandLine/CloneVerb.cs b/GVFS/GVFS/CommandLine/CloneVerb.cs index 5fed80b6..d3699374 100644 --- a/GVFS/GVFS/CommandLine/CloneVerb.cs +++ b/GVFS/GVFS/CommandLine/CloneVerb.cs @@ -100,7 +100,6 @@ namespace GVFS.CommandLine } this.CheckNTFSVolume(); - this.CheckGVFltHealthy(); this.CheckNotInsideExistingRepo(); this.BlockEmptyCacheServerUrl(this.CacheServerUrl); @@ -194,7 +193,7 @@ namespace GVFS.CommandLine { if (!this.NoPrefetch) { - this.Execute( + ReturnCode result = this.Execute( this.EnlistmentRootPath, verb => { @@ -203,6 +202,12 @@ namespace GVFS.CommandLine verb.ResolvedCacheServer = cacheServer; verb.GVFSConfig = gvfsConfig; }); + + if (result != ReturnCode.Success) + { + this.Output.WriteLine("\r\nError during prefetch @ {0}", this.EnlistmentRootPath); + exitCode = (int)result; + } } if (this.NoMount) @@ -280,7 +285,7 @@ namespace GVFS.CommandLine return new Result(GVFSConstants.GitIsNotInstalledError); } - string hooksPath = this.GetGVFSHooksPathAndCheckVersion(tracer: null); + string hooksPath = this.GetGVFSHooksPathAndCheckVersion(tracer: null, version: out _); enlistment = new GVFSEnlistment( this.EnlistmentRootPath, @@ -351,11 +356,9 @@ namespace GVFS.CommandLine return new Result(localCacheError); } - if (!Directory.Exists(enlistment.GitObjectsRoot)) - { - Directory.CreateDirectory(enlistment.GitObjectsRoot); - Directory.CreateDirectory(enlistment.GitPackRoot); - } + Directory.CreateDirectory(enlistment.GitObjectsRoot); + Directory.CreateDirectory(enlistment.GitPackRoot); + Directory.CreateDirectory(enlistment.BlobSizesRoot); return this.CreateClone(tracer, enlistment, objectRequestor, refs, this.Branch); } @@ -399,7 +402,7 @@ namespace GVFS.CommandLine { string pathRoot; string errorMessage; - if (Paths.TryGetPathRoot(this.EnlistmentRootPath, out pathRoot, out errorMessage)) + if (Paths.TryGetFinalPathRoot(this.EnlistmentRootPath, out pathRoot, out errorMessage)) { DriveInfo rootDriveInfo = DriveInfo.GetDrives().FirstOrDefault(x => x.Name == pathRoot); if (rootDriveInfo == null) @@ -458,7 +461,7 @@ namespace GVFS.CommandLine metadata.Add(TracingConstants.MessageKey.InfoMessage, "Initializing cache paths"); tracer.RelatedEvent(EventLevel.Informational, "CloneVerb_TryDetermineLocalCacheAndInitializePaths", metadata); - enlistment.InitializeLocalCacheAndObjectsPathsFromKey(localCacheRoot, localCacheKey); + enlistment.InitializeCachePathsFromKey(localCacheRoot, localCacheKey); return true; } @@ -497,7 +500,8 @@ namespace GVFS.CommandLine return new Result(errorMessage); } - if (!GVFSVerb.TrySetGitConfigSettings(enlistment)) + if (!GVFSVerb.TrySetRequiredGitConfigSettings(enlistment) || + !GVFSVerb.TrySetOptionalGitConfigSettings(enlistment)) { return new Result("Unable to configure git repo"); } @@ -575,7 +579,18 @@ namespace GVFS.CommandLine try { - RepoMetadata.Instance.SaveCloneMetadata(enlistment.GitObjectsRoot, enlistment.LocalCacheRoot); + RepoMetadata.Instance.SaveCloneMetadata(tracer, enlistment); + EventMetadata metadata = new EventMetadata(); + metadata.Add(nameof(RepoMetadata.Instance.EnlistmentId), RepoMetadata.Instance.EnlistmentId); + metadata.Add("Enlistment", enlistment); + tracer.RelatedEvent(EventLevel.Informational, "EnlistmentInfo", metadata, Keywords.Telemetry); + + GitProcess.Result configResult = git.SetInLocalConfig(GVFSConstants.GitConfig.EnlistmentId, RepoMetadata.Instance.EnlistmentId, replaceAll: true); + if (configResult.HasErrors) + { + string error = "Could not update config with enlistment id, error: " + configResult.Errors; + tracer.RelatedWarning(error); + } } catch (Exception e) { diff --git a/GVFS/GVFS/CommandLine/DehydrateVerb.cs b/GVFS/GVFS/CommandLine/DehydrateVerb.cs index 3b30b2d7..b9e1ec6e 100644 --- a/GVFS/GVFS/CommandLine/DehydrateVerb.cs +++ b/GVFS/GVFS/CommandLine/DehydrateVerb.cs @@ -1,11 +1,11 @@ using CommandLine; -using GVFS.CommandLine.DiskLayoutUpgrades; using GVFS.Common; using GVFS.Common.FileSystem; using GVFS.Common.Git; using GVFS.Common.Http; using GVFS.Common.NamedPipes; using GVFS.Common.Tracing; +using GVFS.DiskLayoutUpgrades; using GVFS.GVFlt; using GVFS.GVFlt.DotGit; using Microsoft.Diagnostics.Tracing; diff --git a/GVFS/GVFS/CommandLine/DiagnoseVerb.cs b/GVFS/GVFS/CommandLine/DiagnoseVerb.cs index afae72cb..3e1a8263 100644 --- a/GVFS/GVFS/CommandLine/DiagnoseVerb.cs +++ b/GVFS/GVFS/CommandLine/DiagnoseVerb.cs @@ -4,7 +4,6 @@ using GVFS.Common.FileSystem; using GVFS.Common.Git; using GVFS.Common.Http; using GVFS.Common.Tracing; -using Microsoft.Isam.Esent.Collections.Generic; using Microsoft.Win32; using System; using System.Collections.Generic; @@ -20,18 +19,22 @@ namespace GVFS.CommandLine private const string DiagnoseVerbName = "diagnose"; private const string System32LogFilesRoot = @"%SystemRoot%\System32\LogFiles"; - private const string GVFltLogFolderName = "GvFlt"; + private const string FilterLogFolderName = ProjFSFilter.ServiceName; private const string WindowsVersionRegistryKey = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"; private const string BuildLabRegistryValue = "BuildLab"; private const string BuildLabExRegistryValue = "BuildLabEx"; - // From "Autologger" section of gvflt.inf - private const string GvFltLoggerGuid = "5f6d2558-5c94-48f9-add0-65bc678aa091"; - private const string GvFltLoggerSessionName = "Microsoft-Windows-Git-Filter-Log"; + // From "Autologger" section of prjflt.inf + private const string FilterLoggerGuid = "ee4206ff-4a4d-452f-be56-6bd0ed272b44"; + private const string FilterLoggerSessionName = "Microsoft-Windows-ProjFS-Filter-Log"; private TextWriter diagnosticLogFileWriter; + public DiagnoseVerb() : base(false) + { + } + protected override string VerbName { get { return DiagnoseVerbName; } @@ -60,15 +63,17 @@ namespace GVFS.CommandLine this.WriteMessage(GitProcess.GetInstalledGitBinPath()); this.WriteMessage(string.Empty); this.WriteMessage("Enlistment root: " + enlistment.EnlistmentRoot); - this.WriteMessage("Repo URL: " + enlistment.RepoUrl); this.WriteMessage("Cache Server: " + CacheServerResolver.GetUrlFromConfig(enlistment)); string localCacheRoot; string gitObjectsRoot; this.GetLocalCachePaths(enlistment, out localCacheRoot, out gitObjectsRoot); - this.WriteMessage("Local Cache: " + (!string.IsNullOrWhiteSpace(localCacheRoot) ? localCacheRoot : gitObjectsRoot)); + string actualLocalCacheRoot = !string.IsNullOrWhiteSpace(localCacheRoot) ? localCacheRoot : gitObjectsRoot; + this.WriteMessage("Local Cache: " + actualLocalCacheRoot); this.WriteMessage(string.Empty); + this.PrintDiskSpaceInfo(actualLocalCacheRoot, this.EnlistmentRootPath); + this.RecordWindowsVersionInformation(); this.ShowStatusWhileRunning( @@ -84,10 +89,13 @@ namespace GVFS.CommandLine // .gvfs this.CopyAllFiles(enlistment.EnlistmentRoot, archiveFolderPath, GVFSConstants.DotGVFS.Root, copySubFolders: false); - // gvflt - this.FlushGvFltLogBuffers(); + // filter + this.FlushFilterLogBuffers(); string system32LogFilesPath = Environment.ExpandEnvironmentVariables(System32LogFilesRoot); - this.CopyAllFiles(system32LogFilesPath, archiveFolderPath, GVFltLogFolderName, copySubFolders: false); + + // This copy sometimes fails because the OS has an exclusive lock on the etl files. The error is not actionable + // for the user so we don't write the error message to stdout, just to our own log file. + this.CopyAllFiles(system32LogFilesPath, archiveFolderPath, FilterLogFolderName, copySubFolders: false, hideErrorsFromStdout: true); // .git this.CopyAllFiles(enlistment.WorkingDirectoryRoot, archiveFolderPath, GVFSConstants.DotGit.Root, copySubFolders: false); @@ -100,7 +108,6 @@ namespace GVFS.CommandLine this.LogLooseObjectCount(enlistment.WorkingDirectoryRoot, Path.Combine(archiveFolderPath, GVFSConstants.DotGit.Objects.Root), GVFSConstants.DotGit.Objects.Root, "objects-local.txt"); // databases - this.CopyEsentDatabase(enlistment.DotGVFSRoot, Path.Combine(archiveFolderPath, GVFSConstants.DotGVFS.Root), GVFSConstants.DotGVFS.BlobSizesName); this.CopyAllFiles(enlistment.DotGVFSRoot, Path.Combine(archiveFolderPath, GVFSConstants.DotGVFS.Root), GVFSConstants.DotGVFS.Databases.Name, copySubFolders: false); // local cache @@ -145,11 +152,15 @@ namespace GVFS.CommandLine this.Output.WriteLine(zipFilePath); } - private void WriteMessage(string message) + private void WriteMessage(string message, bool skipStdout = false) { message = message.TrimEnd('\r', '\n'); - this.Output.WriteLine(message); + if (!skipStdout) + { + this.Output.WriteLine(message); + } + this.diagnosticLogFileWriter.WriteLine(message); } @@ -170,7 +181,7 @@ namespace GVFS.CommandLine } } - private void CopyAllFiles(string sourceRoot, string targetRoot, string folderName, bool copySubFolders) + private void CopyAllFiles(string sourceRoot, string targetRoot, string folderName, bool copySubFolders, bool hideErrorsFromStdout = false) { string sourceFolder = Path.Combine(sourceRoot, folderName); string targetFolder = Path.Combine(targetRoot, folderName); @@ -182,16 +193,18 @@ namespace GVFS.CommandLine return; } - this.RecursiveFileCopyImpl(sourceFolder, targetFolder, copySubFolders); + this.RecursiveFileCopyImpl(sourceFolder, targetFolder, copySubFolders, hideErrorsFromStdout); } catch (Exception e) { - this.WriteMessage(string.Format( - "Failed to copy folder {0} in {1} with exception {2}. copySubFolders: {3}", - folderName, - sourceRoot, - e, - copySubFolders)); + this.WriteMessage( + string.Format( + "Failed to copy folder {0} in {1} with exception {2}. copySubFolders: {3}", + folderName, + sourceRoot, + e, + copySubFolders), + hideErrorsFromStdout); } } @@ -355,7 +368,7 @@ namespace GVFS.CommandLine } } - private void RecursiveFileCopyImpl(string sourcePath, string targetPath, bool copySubFolders) + private void RecursiveFileCopyImpl(string sourcePath, string targetPath, bool copySubFolders, bool hideErrorsFromStdout) { if (!Directory.Exists(targetPath)) { @@ -377,11 +390,13 @@ namespace GVFS.CommandLine } catch (Exception e) { - this.WriteMessage(string.Format( - "Failed to copy '{0}' in {1} with exception {2}", - fileName, - sourcePath, - e)); + this.WriteMessage( + string.Format( + "Failed to copy '{0}' in {1} with exception {2}", + fileName, + sourcePath, + e), + hideErrorsFromStdout); } } @@ -393,15 +408,17 @@ namespace GVFS.CommandLine string targetFolderPath = Path.Combine(targetPath, subdir.Name); try { - this.RecursiveFileCopyImpl(subdir.FullName, targetFolderPath, copySubFolders); + this.RecursiveFileCopyImpl(subdir.FullName, targetFolderPath, copySubFolders, hideErrorsFromStdout); } catch (Exception e) { - this.WriteMessage(string.Format( - "Failed to copy subfolder '{0}' to '{1}' with exception {2}", - subdir.FullName, - targetFolderPath, - e)); + this.WriteMessage( + string.Format( + "Failed to copy subfolder '{0}' to '{1}' with exception {2}", + subdir.FullName, + targetFolderPath, + e), + hideErrorsFromStdout); } } } @@ -439,60 +456,92 @@ namespace GVFS.CommandLine } } - private void CopyEsentDatabase(string sourceFolder, string targetFolder, string databaseName) - where TKey : IComparable - { - try - { - if (!Directory.Exists(targetFolder)) - { - Directory.CreateDirectory(targetFolder); - } - - using (FileStream outputFile = new FileStream(Path.Combine(targetFolder, databaseName + ".txt"), FileMode.CreateNew)) - using (StreamWriter writer = new StreamWriter(outputFile)) - { - using (PersistentDictionary dictionary = new PersistentDictionary( - Path.Combine(sourceFolder, databaseName))) - { - foreach (TKey key in dictionary.Keys) - { - writer.Write(key); - writer.Write(" = "); - writer.WriteLine(dictionary[key].ToString()); - } - } - } - } - catch (Exception e) - { - this.WriteMessage(string.Format( - "Failed to copy database {0} with exception {1}", - databaseName, - e)); - } - - // Also copy the database files themselves, in case we failed to read the entries above - this.CopyAllFiles(sourceFolder, targetFolder, databaseName, copySubFolders: false); - } - - private void FlushGvFltLogBuffers() + private void FlushFilterLogBuffers() { try { string logfileName; - uint result = NativeMethods.FlushTraceLogger(GvFltLoggerSessionName, GvFltLoggerGuid, out logfileName); + uint result = NativeMethods.FlushTraceLogger(FilterLoggerSessionName, FilterLoggerGuid, out logfileName); if (result != 0) { - this.WriteMessage(string.Format( - "Failed to flush GvFlt log buffers {0}", - result)); + this.WriteMessage($"Failed to flush {ProjFSFilter.ServiceName} log buffers {result}"); } } catch (Exception e) { - this.WriteMessage(string.Format("Failed to flush GvFlt log buffers, exception: {0}", e)); + this.WriteMessage($"Failed to flush {ProjFSFilter.ServiceName} log buffers, exception: {e.ToString()}"); } } + + private void PrintDiskSpaceInfo(string localCacheRoot, string enlistmentRoot) + { + try + { + string enlistmentFinalPathRoot; + string localCacheFinalPathRoot; + string enlistmentErrorMessage; + string localCacheErrorMessage; + + bool enlistmentSuccess = Paths.TryGetFinalPathRoot(enlistmentRoot, out enlistmentFinalPathRoot, out enlistmentErrorMessage); + bool localCacheSuccess = Paths.TryGetFinalPathRoot(localCacheRoot, out localCacheFinalPathRoot, out localCacheErrorMessage); + + if (!enlistmentSuccess || !localCacheSuccess) + { + this.WriteMessage("Failed to acquire disk space information:"); + if (!string.IsNullOrEmpty(enlistmentErrorMessage)) + { + this.WriteMessage(enlistmentErrorMessage); + } + + if (!string.IsNullOrEmpty(localCacheErrorMessage)) + { + this.WriteMessage(localCacheErrorMessage); + } + + this.WriteMessage(string.Empty); + return; + } + + DriveInfo enlistmentDrive = new DriveInfo(enlistmentFinalPathRoot); + string enlistmentDriveDiskSpace = this.FormatByteCount(enlistmentDrive.AvailableFreeSpace); + + if (string.Equals(enlistmentFinalPathRoot, localCacheFinalPathRoot, StringComparison.OrdinalIgnoreCase)) + { + this.WriteMessage("Available space on " + enlistmentDrive.Name + " drive(enlistment and local cache): " + enlistmentDriveDiskSpace); + } + else + { + this.WriteMessage("Available space on " + enlistmentDrive.Name + " drive(enlistment): " + enlistmentDriveDiskSpace); + + DriveInfo cacheDrive = new DriveInfo(localCacheRoot); + string cacheDriveDiskSpace = this.FormatByteCount(cacheDrive.AvailableFreeSpace); + this.WriteMessage("Available space on " + cacheDrive.Name + " drive(local cache): " + cacheDriveDiskSpace); + } + + this.WriteMessage(string.Empty); + } + catch (Exception e) + { + this.WriteMessage("Failed to acquire disk space information, exception: " + e.ToString()); + this.WriteMessage(string.Empty); + } + } + + private string FormatByteCount(double byteCount) + { + const int Divisor = 1024; + const string ByteCountFormat = "0.00"; + string[] unitStrings = { " B", " KB", " MB", " GB", " TB" }; + + int unitIndex = 0; + + while (byteCount >= Divisor && unitIndex < unitStrings.Length - 1) + { + unitIndex++; + byteCount = byteCount / Divisor; + } + + return byteCount.ToString(ByteCountFormat) + unitStrings[unitIndex]; + } } } diff --git a/GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayoutUpgrade.cs b/GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayoutUpgrade.cs deleted file mode 100644 index 66019303..00000000 --- a/GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayoutUpgrade.cs +++ /dev/null @@ -1,286 +0,0 @@ -using GVFS.Common; -using GVFS.Common.FileSystem; -using GVFS.Common.Tracing; -using Microsoft.Diagnostics.Tracing; -using Microsoft.Isam.Esent.Collections.Generic; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace GVFS.CommandLine.DiskLayoutUpgrades -{ - public abstract class DiskLayoutUpgrade - { - protected const string EsentRepoMetadataName = "RepoMetadata"; - protected const string DiskLayoutEsentVersionKey = "DiskLayoutVersion"; - - private static readonly Dictionary AllUpgrades = new List() - { - new DiskLayout7to8Upgrade(), - new DiskLayout8to9Upgrade(), - new DiskLayout9to10Upgrade(), - new DiskLayout10to11Upgrade(), - new DiskLayout11to12Upgrade(), - }.ToDictionary( - upgrader => upgrader.SourceLayoutVersion, - upgrader => upgrader); - - protected abstract int SourceLayoutVersion { get; } - - public static bool TryRunAllUpgrades(string enlistmentRoot) - { - using (JsonEtwTracer tracer = new JsonEtwTracer(GVFSConstants.GVFSEtwProviderName, "DiskLayoutUpgrade")) - { - try - { - DiskLayoutUpgrade upgrade = null; - while (TryFindUpgrade(tracer, enlistmentRoot, out upgrade)) - { - if (upgrade == null) - { - return true; - } - - if (!upgrade.TryUpgrade(tracer, enlistmentRoot)) - { - return false; - } - - if (!CheckLayoutVersionWasIncremented(tracer, enlistmentRoot, upgrade)) - { - return false; - } - } - - return false; - } - catch (Exception e) - { - StartLogFile(enlistmentRoot, tracer); - tracer.RelatedError(e.ToString()); - return false; - } - finally - { - RepoMetadata.Shutdown(); - } - } - } - - public static bool TryCheckDiskLayoutVersion(ITracer tracer, string enlistmentRoot, out string error) - { - error = string.Empty; - int persistedVersionNumber; - try - { - if (TryGetDiskLayoutVersion(tracer, enlistmentRoot, out persistedVersionNumber, out error)) - { - if (persistedVersionNumber < RepoMetadata.DiskLayoutVersion.MinDiskLayoutVersion) - { - error = string.Format( - "Breaking change to GVFS disk layout has been made since cloning. \r\nEnlistment disk layout version: {0} \r\nGVFS disk layout version: {1} \r\nMinimum supported version: {2}", - persistedVersionNumber, - RepoMetadata.DiskLayoutVersion.CurrentDiskLayoutVersion, - RepoMetadata.DiskLayoutVersion.MinDiskLayoutVersion); - - return false; - } - else if (persistedVersionNumber > RepoMetadata.DiskLayoutVersion.MaxDiskLayoutVersion) - { - error = string.Format( - "Changes to GVFS disk layout do not allow mounting after downgrade. Try mounting again using a more recent version of GVFS. \r\nEnlistment disk layout version: {0} \r\nGVFS disk layout version: {1}", - persistedVersionNumber, - RepoMetadata.DiskLayoutVersion.CurrentDiskLayoutVersion); - - return false; - } - else if (persistedVersionNumber != RepoMetadata.DiskLayoutVersion.CurrentDiskLayoutVersion) - { - error = string.Format( - "GVFS disk layout version doesn't match current version. Try running 'gvfs mount' to upgrade. \r\nEnlistment disk layout version: {0} \r\nGVFS disk layout version: {1}", - persistedVersionNumber, - RepoMetadata.DiskLayoutVersion.CurrentDiskLayoutVersion); - - return false; - } - - return true; - } - } - finally - { - RepoMetadata.Shutdown(); - } - - error = "Failed to read disk layout version. " + ConsoleHelper.GetGVFSLogMessage(enlistmentRoot); - return false; - } - - public abstract bool TryUpgrade(ITracer tracer, string enlistmentRoot); - - protected bool TryDeleteFolder(ITracer tracer, string folderName) - { - try - { - PhysicalFileSystem.RecursiveDelete(folderName); - } - catch (Exception e) - { - tracer.RelatedError("Failed to delete folder {0}: {1}", folderName, e.ToString()); - return true; - } - - return true; - } - - protected bool TryDeleteFile(ITracer tracer, string fileName) - { - try - { - File.Delete(fileName); - } - catch (Exception e) - { - tracer.RelatedError("Failed to delete file {0}: {1}", fileName, e.ToString()); - return true; - } - - return true; - } - - protected bool TryRenameFolderForDelete(ITracer tracer, string folderName, out string backupFolder) - { - backupFolder = folderName + ".deleteme"; - - tracer.RelatedInfo("Moving " + folderName + " to " + backupFolder); - - try - { - Directory.Move(folderName, backupFolder); - } - catch (Exception e) - { - tracer.RelatedError("Failed to move {0} to {1}: {2}", folderName, backupFolder, e.ToString()); - return false; - } - - return true; - } - - protected bool TryIncrementDiskLayoutVersion(ITracer tracer, string enlistmentRoot, DiskLayoutUpgrade upgrade) - { - string newVersion = (upgrade.SourceLayoutVersion + 1).ToString(); - string dotGVFSPath = Path.Combine(enlistmentRoot, GVFSConstants.DotGVFS.Root); - string error; - if (!RepoMetadata.TryInitialize(tracer, dotGVFSPath, out error)) - { - tracer.RelatedError("Could not initialize repo metadata: " + error); - return false; - } - - RepoMetadata.Instance.SetEntry(RepoMetadata.Keys.DiskLayoutVersion, newVersion); - tracer.RelatedInfo("Disk layout version is now: " + newVersion); - return true; - } - - private static bool CheckLayoutVersionWasIncremented(JsonEtwTracer tracer, string enlistmentRoot, DiskLayoutUpgrade upgrade) - { - string error; - int actualVersion; - if (!TryGetDiskLayoutVersion(tracer, enlistmentRoot, out actualVersion, out error)) - { - tracer.RelatedError(error); - return false; - } - - int expectedVersion = upgrade.SourceLayoutVersion + 1; - if (actualVersion != expectedVersion) - { - throw new InvalidDataException(string.Format("Disk layout upgrade did not increment layout version. Expected: {0}, Actual: {1}", expectedVersion, actualVersion)); - } - - return true; - } - - private static bool TryFindUpgrade(JsonEtwTracer tracer, string enlistmentRoot, out DiskLayoutUpgrade upgrade) - { - int version; - string error; - if (!TryGetDiskLayoutVersion(tracer, enlistmentRoot, out version, out error)) - { - StartLogFile(enlistmentRoot, tracer); - tracer.RelatedError(error); - upgrade = null; - return false; - } - - if (AllUpgrades.TryGetValue(version, out upgrade)) - { - StartLogFile(enlistmentRoot, tracer); - tracer.RelatedInfo("Upgrading from disk layout {0} to {1}", version, version + 1); - return true; - } - - return true; - } - - private static bool TryGetDiskLayoutVersion(ITracer tracer, string enlistmentRoot, out int version, out string error) - { - string dotGVFSPath = Path.Combine(enlistmentRoot, GVFSConstants.DotGVFS.Root); - string repoMetadataPath = Path.Combine(dotGVFSPath, EsentRepoMetadataName); - if (Directory.Exists(repoMetadataPath)) - { - try - { - using (PersistentDictionary oldMetadata = new PersistentDictionary(repoMetadataPath)) - { - string versionString = oldMetadata[DiskLayoutEsentVersionKey]; - if (!int.TryParse(versionString, out version)) - { - error = "Could not parse version string as integer: " + versionString; - return false; - } - } - } - catch (Exception e) - { - version = 0; - error = e.ToString(); - return false; - } - } - else - { - if (!RepoMetadata.TryInitialize(tracer, dotGVFSPath, out error)) - { - version = 0; - return false; - } - - if (!RepoMetadata.Instance.TryGetOnDiskLayoutVersion(out version, out error)) - { - return false; - } - } - - error = null; - return true; - } - - private static void StartLogFile(string enlistmentRoot, JsonEtwTracer tracer) - { - if (!tracer.HasLogFileEventListener) - { - tracer.AddLogFileEventListener( - GVFSEnlistment.GetNewGVFSLogFileName( - Path.Combine(enlistmentRoot, GVFSConstants.DotGVFS.LogPath), - GVFSConstants.LogFileTypes.Upgrade), - EventLevel.Informational, - Keywords.Any); - - tracer.WriteStartEvent(enlistmentRoot, repoUrl: "N/A", cacheServerUrl: "N/A"); - } - } - } -} diff --git a/GVFS/GVFS/CommandLine/GVFSVerb.cs b/GVFS/GVFS/CommandLine/GVFSVerb.cs index f092b7c9..c92076e9 100644 --- a/GVFS/GVFS/CommandLine/GVFSVerb.cs +++ b/GVFS/GVFS/CommandLine/GVFSVerb.cs @@ -3,6 +3,7 @@ using GVFS.Common; using GVFS.Common.FileSystem; using GVFS.Common.Git; using GVFS.Common.Http; +using GVFS.Common.NamedPipes; using GVFS.Common.Tracing; using Microsoft.Diagnostics.Tracing; using System; @@ -18,11 +19,14 @@ namespace GVFS.CommandLine { protected const string StartServiceInstructions = "Run 'sc start GVFS.Service' from an elevated command prompt to ensure it is running."; - public GVFSVerb() + private readonly bool validateOriginURL; + + public GVFSVerb(bool validateOrigin = true) { this.Output = Console.Out; this.ReturnCode = ReturnCode.Success; this.ServiceName = GVFSConstants.Service.ServiceName; + this.validateOriginURL = validateOrigin; this.Unattended = GVFSEnlistment.IsUnattended(tracer: null); @@ -54,17 +58,21 @@ namespace GVFS.CommandLine protected abstract string VerbName { get; } - public static bool TrySetGitConfigSettings(Enlistment enlistment) + public static bool TrySetRequiredGitConfigSettings(Enlistment enlistment) { string expectedHooksPath = Path.Combine(enlistment.WorkingDirectoryRoot, GVFSConstants.DotGit.Hooks.Root); expectedHooksPath = expectedHooksPath.Replace('\\', '/'); - Dictionary expectedConfigSettings = new Dictionary + // These settings are required for normal GVFS functionality. + // They will override any existing local configuration values. + Dictionary requiredSettings = new Dictionary { { "am.keepcr", "true" }, { "core.autocrlf", "false" }, + { "core.commitGraph", "true" }, { "core.fscache", "true" }, { "core.gvfs", "true" }, + { "core.midx", "true" }, { "core.preloadIndex", "true" }, { "core.safecrlf", "false" }, { "core.sparseCheckout", "true" }, @@ -84,26 +92,27 @@ namespace GVFS.CommandLine { "receive.autogc", "false" }, }; - GitProcess git = new GitProcess(enlistment); - - Dictionary actualConfigSettings; - if (!git.TryGetAllLocalConfig(out actualConfigSettings)) + if (!TrySetConfig(enlistment, requiredSettings, isRequired: true)) { return false; } - foreach (string key in expectedConfigSettings.Keys) + return true; + } + + public static bool TrySetOptionalGitConfigSettings(Enlistment enlistment) + { + // These settings are optional, because they impact performance but not functionality of GVFS. + // These settings should only be set by the clone or repair verbs, so that they do not + // overwrite the values set by the user in their local config. + Dictionary optionalSettings = new Dictionary { - GitConfigSetting actualSetting; - if (!actualConfigSettings.TryGetValue(key, out actualSetting) || - !actualSetting.HasValue(expectedConfigSettings[key])) - { - GitProcess.Result setConfigResult = git.SetInLocalConfig(key, expectedConfigSettings[key]); - if (setConfigResult.HasErrors) - { - return false; - } - } + { "status.aheadbehind", "false" }, + }; + + if (!TrySetConfig(enlistment, optionalSettings, isRequired: false)) + { + return false; } return true; @@ -191,15 +200,6 @@ namespace GVFS.CommandLine this.ReportErrorAndExit(tracer, ReturnCode.GenericError, error, args); } - protected void CheckGVFltHealthy() - { - string error; - if (!GvFltFilter.IsHealthy(out error, tracer: null)) - { - this.ReportErrorAndExit(tracer: null, error: error); - } - } - protected RetryConfig GetRetryConfig(ITracer tracer, GVFSEnlistment enlistment, TimeSpan? timeoutOverride = null) { RetryConfig retryConfig; @@ -239,8 +239,10 @@ namespace GVFS.CommandLine protected void ValidateClientVersions(ITracer tracer, GVFSEnlistment enlistment, GVFSConfig gvfsConfig, bool showWarnings) { - this.CheckGitVersion(tracer, enlistment); - this.GetGVFSHooksPathAndCheckVersion(tracer); + this.CheckGitVersion(tracer, enlistment, out string gitVersion); + enlistment.SetGitVersion(gitVersion); + this.GetGVFSHooksPathAndCheckVersion(tracer, out string hooksVersion); + enlistment.SetGVFSHooksVersion(hooksVersion); this.CheckVolumeSupportsDeleteNotifications(tracer, enlistment); string errorMessage = null; @@ -284,7 +286,7 @@ namespace GVFS.CommandLine return true; } - protected string GetGVFSHooksPathAndCheckVersion(ITracer tracer) + protected string GetGVFSHooksPathAndCheckVersion(ITracer tracer, out string version) { string hooksPath = ProcessHelper.WhereDirectory(GVFSConstants.GVFSHooksExecutableName); if (hooksPath == null) @@ -299,6 +301,7 @@ namespace GVFS.CommandLine this.ReportErrorAndExit(tracer, "GVFS.Hooks version ({0}) does not match GVFS version ({1}).", hooksFileVersionInfo.ProductVersion, gvfsVersion); } + version = hooksFileVersionInfo.ProductVersion.ToString(); return hooksPath; } @@ -419,6 +422,95 @@ You can specify a URL, a name of a configured cache server, or the special names return true; } + /// + /// Request that PrjFlt be enabled and attached to the volume of the enlistment root + /// + /// Enlistment root. If string.Empty, PrjFlt will be enabled but not attached to any volumes + /// Error meesage (in the case of failure) + /// True is successful and false otherwise + protected bool TryEnableAndAttachGvFltThroughService(string enlistmentRoot, out string errorMessage) + { + errorMessage = string.Empty; + + NamedPipeMessages.EnableAndAttachProjFSRequest request = new NamedPipeMessages.EnableAndAttachProjFSRequest(); + request.EnlistmentRoot = enlistmentRoot; + + using (NamedPipeClient client = new NamedPipeClient(this.ServicePipeName)) + { + if (!client.Connect()) + { + errorMessage = "GVFS.Service is not responding. " + GVFSVerb.StartServiceInstructions; + return false; + } + + try + { + client.SendRequest(request.ToMessage()); + NamedPipeMessages.Message response = client.ReadResponse(); + if (response.Header == NamedPipeMessages.EnableAndAttachProjFSRequest.Response.Header) + { + NamedPipeMessages.EnableAndAttachProjFSRequest.Response message = NamedPipeMessages.EnableAndAttachProjFSRequest.Response.FromMessage(response); + + if (!string.IsNullOrEmpty(message.ErrorMessage)) + { + errorMessage = message.ErrorMessage; + return false; + } + + if (message.State != NamedPipeMessages.CompletionState.Success) + { + errorMessage = $"Failed to attach {ProjFSFilter.ServiceName} to volume."; + return false; + } + else + { + return true; + } + } + else + { + errorMessage = string.Format("GVFS.Service responded with unexpected message: {0}", response); + return false; + } + } + catch (BrokenPipeException e) + { + errorMessage = "Unable to communicate with GVFS.Service: " + e.ToString(); + return false; + } + } + } + + private static bool TrySetConfig(Enlistment enlistment, Dictionary configSettings, bool isRequired) + { + GitProcess git = new GitProcess(enlistment); + + Dictionary existingConfigSettings; + + // If the settings are required, then only check local config settings, because we don't want to depend on + // global settings that can then change independent of this repo. + if (!git.TryGetAllConfig(localOnly: isRequired, configSettings: out existingConfigSettings)) + { + return false; + } + + foreach (KeyValuePair setting in configSettings) + { + GitConfigSetting existingSetting; + if (!existingConfigSettings.TryGetValue(setting.Key, out existingSetting) || + (isRequired && !existingSetting.HasValue(setting.Value))) + { + GitProcess.Result setConfigResult = git.SetInLocalConfig(setting.Key, setting.Value); + if (setConfigResult.HasErrors) + { + return false; + } + } + } + + return true; + } + private string GetAlternatesPath(GVFSEnlistment enlistment) { return Path.Combine(enlistment.WorkingDirectoryRoot, GVFSConstants.DotGit.Objects.Info.Alternates); @@ -452,7 +544,7 @@ You can specify a URL, a name of a configured cache server, or the special names } } - private void CheckGitVersion(ITracer tracer, Enlistment enlistment) + private void CheckGitVersion(ITracer tracer, GVFSEnlistment enlistment, out string version) { GitProcess.Result versionResult = GitProcess.Version(enlistment); if (versionResult.HasErrors) @@ -461,7 +553,7 @@ You can specify a URL, a name of a configured cache server, or the special names } GitVersion gitVersion; - string version = versionResult.Output; + version = versionResult.Output; if (version.StartsWith("git version ")) { version = version.Substring(12); @@ -532,6 +624,7 @@ You can specify a URL, a name of a configured cache server, or the special names { "SupportedVersionRange", versionRange }, }); + enlistment.SetGVFSVersion(currentVersion.ToString()); return true; } } @@ -546,6 +639,10 @@ You can specify a URL, a name of a configured cache server, or the special names public abstract class ForExistingEnlistment : GVFSVerb { + public ForExistingEnlistment(bool validateOrigin = true) : base(validateOrigin) + { + } + [Value( 0, Required = false, @@ -553,7 +650,7 @@ You can specify a URL, a name of a configured cache server, or the special names MetaName = "Enlistment Root Path", HelpText = "Full or relative path to the GVFS enlistment root")] public override string EnlistmentRootPath { get; set; } - + public sealed override void Execute() { this.ValidatePathParameter(this.EnlistmentRootPath); @@ -582,7 +679,7 @@ You can specify a URL, a name of a configured cache server, or the special names this.ReportErrorAndExit(tracer, "Failed to initialize repo metadata: " + error); } - this.InitializeLocalCacheAndObjectsPathsFromRepoMetadata(tracer, enlistment); + this.InitializeCachePathsFromRepoMetadata(tracer, enlistment); // Note: Repos cloned with a version of GVFS that predates the local cache will not have a local cache configured if (!string.IsNullOrWhiteSpace(enlistment.LocalCacheRoot)) @@ -593,7 +690,7 @@ You can specify a URL, a name of a configured cache server, or the special names RepoMetadata.Shutdown(); } - private void InitializeLocalCacheAndObjectsPathsFromRepoMetadata( + private void InitializeCachePathsFromRepoMetadata( ITracer tracer, GVFSEnlistment enlistment) { @@ -615,7 +712,20 @@ You can specify a URL, a name of a configured cache server, or the special names this.ReportErrorAndExit(tracer, "Failed to determine local cache path from repo metadata: " + error); } - enlistment.InitializeLocalCacheAndObjectPaths(localCacheRoot, gitObjectsRoot); + // Note: localCacheRoot is allowed to be empty, this can occur when upgrading from disk layout version 11 to 12 + + string blobSizesRoot; + if (!RepoMetadata.Instance.TryGetBlobSizesRoot(out blobSizesRoot, out error)) + { + this.ReportErrorAndExit(tracer, "Failed to determine blob sizes root from repo metadata: " + error); + } + + if (string.IsNullOrWhiteSpace(blobSizesRoot)) + { + this.ReportErrorAndExit(tracer, "Invalid blob sizes root (empty or whitespace)"); + } + + enlistment.InitializeCachePaths(localCacheRoot, gitObjectsRoot, blobSizesRoot); } private void EnsureLocalCacheIsHealthy( @@ -643,6 +753,9 @@ You can specify a URL, a name of a configured cache server, or the special names } } + // Validate that the GitObjectsRoot directory is on disk, and that the GVFS repo is configured to use it. + // If the directory is missing (and cannot be found in the mapping file) a new key for the repo will be added + // to the mapping file and used for BOTH the GitObjectsRoot and BlobSizesRoot PhysicalFileSystem fileSystem = new PhysicalFileSystem(); if (Directory.Exists(enlistment.GitObjectsRoot)) { @@ -737,10 +850,10 @@ You can specify a URL, a name of a configured cache server, or the special names metadata.Add("localCacheRoot", enlistment.LocalCacheRoot); metadata.Add("localCacheKey", localCacheKey); metadata.Add(TracingConstants.MessageKey.InfoMessage, "Initializing and persisting updated paths"); - tracer.RelatedEvent(EventLevel.Informational, "GVFSVerb_InitializeLocalCacheAndObjectsPaths", metadata); - enlistment.InitializeLocalCacheAndObjectsPathsFromKey(enlistment.LocalCacheRoot, localCacheKey); + tracer.RelatedEvent(EventLevel.Informational, "GVFSVerb_EnsureLocalCacheIsHealthy_InitializePathsFromKey", metadata); + enlistment.InitializeCachePathsFromKey(enlistment.LocalCacheRoot, localCacheKey); - tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Creating GitObjectsRoot ({enlistment.GitObjectsRoot}) and GitPackRoot ({enlistment.GitPackRoot})"); + tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Creating GitObjectsRoot ({enlistment.GitObjectsRoot}), GitPackRoot ({enlistment.GitPackRoot}), and BlobSizesRoot ({enlistment.BlobSizesRoot})"); try { Directory.CreateDirectory(enlistment.GitObjectsRoot); @@ -753,9 +866,10 @@ You can specify a URL, a name of a configured cache server, or the special names exceptionMetadata.Add("enlistment.LocalCacheRoot", enlistment.LocalCacheRoot); exceptionMetadata.Add("enlistment.GitObjectsRoot", enlistment.GitObjectsRoot); exceptionMetadata.Add("enlistment.GitPackRoot", enlistment.GitPackRoot); - tracer.RelatedError(exceptionMetadata, $"{nameof(this.InitializeLocalCacheAndObjectsPaths)}: Exception while trying to create objects and pack folders"); + exceptionMetadata.Add("enlistment.BlobSizesRoot", enlistment.BlobSizesRoot); + tracer.RelatedError(exceptionMetadata, $"{nameof(this.InitializeLocalCacheAndObjectsPaths)}: Exception while trying to create objects, pack, and sizes folders"); - this.ReportErrorAndExit(tracer, "Failed to create objects and pack folders"); + this.ReportErrorAndExit(tracer, "Failed to create objects, pack, and sizes folders"); } tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Creating new alternates file"); @@ -766,6 +880,30 @@ You can specify a URL, a name of a configured cache server, or the special names tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Saving git objects root ({enlistment.GitObjectsRoot}) in repo metadata"); RepoMetadata.Instance.SetGitObjectsRoot(enlistment.GitObjectsRoot); + + tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Saving blob sizes root ({enlistment.BlobSizesRoot}) in repo metadata"); + RepoMetadata.Instance.SetBlobSizesRoot(enlistment.BlobSizesRoot); + } + + // Validate that the BlobSizesRoot folder is on disk. + // Note that if a user performed an action that resulted in the entire .gvfscache being deleted, the code above + // for validating GitObjectsRoot will have already taken care of generating a new key and setting a new enlistment.BlobSizesRoot path + if (!Directory.Exists(enlistment.BlobSizesRoot)) + { + tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: BlobSizesRoot ({enlistment.BlobSizesRoot}) not found, re-creating"); + try + { + Directory.CreateDirectory(enlistment.BlobSizesRoot); + } + catch (Exception e) + { + EventMetadata exceptionMetadata = new EventMetadata(); + exceptionMetadata.Add("Exception", e.ToString()); + exceptionMetadata.Add("enlistment.BlobSizesRoot", enlistment.BlobSizesRoot); + tracer.RelatedError(exceptionMetadata, $"{nameof(this.InitializeLocalCacheAndObjectsPaths)}: Exception while trying to create blob sizes folder"); + + this.ReportErrorAndExit(tracer, "Failed to create blob sizes folder"); + } } } @@ -782,11 +920,19 @@ You can specify a URL, a name of a configured cache server, or the special names { this.ReportErrorAndExit("Could not find " + GVFSConstants.GVFSHooksExecutableName); } - + GVFSEnlistment enlistment = null; try { - enlistment = GVFSEnlistment.CreateFromDirectory(enlistmentRootPath, gitBinPath, hooksPath); + if (this.validateOriginURL) + { + enlistment = GVFSEnlistment.CreateFromDirectory(enlistmentRootPath, gitBinPath, hooksPath); + } + else + { + enlistment = GVFSEnlistment.CreateWithoutRepoUrlFromDirectory(enlistmentRootPath, gitBinPath, hooksPath); + } + if (enlistment == null) { this.ReportErrorAndExit( diff --git a/GVFS/GVFS/CommandLine/HooksInstaller.cs b/GVFS/GVFS/CommandLine/HooksInstaller.cs index 66c00eb8..552ef97f 100644 --- a/GVFS/GVFS/CommandLine/HooksInstaller.cs +++ b/GVFS/GVFS/CommandLine/HooksInstaller.cs @@ -110,7 +110,7 @@ namespace GVFS.CommandLine if (!TryAction(() => CreateHookCommandConfig(enlistment, hookName, commandHookPath), out errorMessage)) { - errorMessage = "Failed to create " + commandHookPath + GVFSConstants.DotGit.Hooks.ConfigExtension + "\n" + errorMessage; + errorMessage = "Failed to create " + commandHookPath + GVFSConstants.GitConfig.HooksExtension + "\n" + errorMessage; return false; } @@ -140,11 +140,11 @@ namespace GVFS.CommandLine private static void CreateHookCommandConfig(GVFSEnlistment enlistment, string hookName, string commandHookPath) { - string targetPath = commandHookPath + GVFSConstants.DotGit.Hooks.ConfigExtension; + string targetPath = commandHookPath + GVFSConstants.GitConfig.HooksExtension; try { - string configSetting = GVFSConstants.DotGit.Hooks.ConfigNamePrefix + hookName; + string configSetting = GVFSConstants.GitConfig.HooksPrefix + hookName; string mergedHooks = MergeHooks(enlistment, configSetting, hookName); if (File.Exists(targetPath)) diff --git a/GVFS/GVFS/CommandLine/MountVerb.cs b/GVFS/GVFS/CommandLine/MountVerb.cs index 9a8f2fa9..1e2dbc82 100644 --- a/GVFS/GVFS/CommandLine/MountVerb.cs +++ b/GVFS/GVFS/CommandLine/MountVerb.cs @@ -1,11 +1,11 @@ using CommandLine; -using GVFS.CommandLine.DiskLayoutUpgrades; using GVFS.Common; using GVFS.Common.FileSystem; using GVFS.Common.Git; using GVFS.Common.Http; using GVFS.Common.NamedPipes; using GVFS.Common.Tracing; +using GVFS.DiskLayoutUpgrades; using GVFS.GVFlt.DotGit; using Microsoft.Diagnostics.Tracing; using System; @@ -61,8 +61,6 @@ namespace GVFS.CommandLine protected override void PreCreateEnlistment() { - this.CheckGVFltHealthy(); - string enlistmentRoot = Paths.GetGVFSEnlistmentRoot(this.EnlistmentRootPath); if (enlistmentRoot == null) { @@ -118,16 +116,20 @@ namespace GVFS.CommandLine { "Unattended", this.Unattended }, { "IsElevated", ProcessHelper.IsAdminElevated() }, }); - + // TODO 1050199: Once the service is an optional component, GVFS should only attempt to attach - // GvFlt via the service if the service is present\enabled - if (!GvFltFilter.TryAttach(tracer, enlistment.EnlistmentRoot, out errorMessage)) + // the filter via the service if the service is present\enabled + if (!ProjFSFilter.IsServiceRunning(tracer) || + !ProjFSFilter.IsNativeLibInstalled(tracer, new PhysicalFileSystem()) || + !ProjFSFilter.TryAttach(tracer, enlistment.EnlistmentRoot, out errorMessage)) { + tracer.RelatedInfo($"{nameof(MountVerb)}.{nameof(this.Execute)}: Enabling and attaching ProjFS through service"); + if (!this.ShowStatusWhileRunning( - () => { return this.AttachGvFltThroughService(enlistment, out errorMessage); }, - "Attaching GvFlt to volume")) + () => { return this.TryEnableAndAttachGvFltThroughService(enlistment.EnlistmentRoot, out errorMessage); }, + $"Attaching {ProjFSFilter.ServiceName} to volume")) { - this.ReportErrorAndExit(tracer, errorMessage); + this.ReportErrorAndExit(tracer, ReturnCode.FilterError, errorMessage); } } @@ -169,6 +171,35 @@ namespace GVFS.CommandLine { this.ReportErrorAndExit(tracer, errorMessage); } + + if (!this.SkipVersionCheck) + { + string error; + if (!RepoMetadata.TryInitialize(tracer, enlistment.DotGVFSRoot, out error)) + { + this.ReportErrorAndExit(tracer, error); + } + + try + { + EventMetadata metadata = new EventMetadata(); + metadata.Add(nameof(RepoMetadata.Instance.EnlistmentId), RepoMetadata.Instance.EnlistmentId); + metadata.Add("Enlistment", enlistment); + tracer.RelatedEvent(EventLevel.Informational, "EnlistmentInfo", metadata, Keywords.Telemetry); + + GitProcess git = new GitProcess(enlistment, new PhysicalFileSystem()); + GitProcess.Result configResult = git.SetInLocalConfig(GVFSConstants.GitConfig.EnlistmentId, RepoMetadata.Instance.EnlistmentId, replaceAll: true); + if (configResult.HasErrors) + { + error = "Could not update config with enlistment id, error: " + configResult.Errors; + tracer.RelatedWarning(error); + } + } + finally + { + RepoMetadata.Shutdown(); + } + } } if (!this.ShowStatusWhileRunning( @@ -229,64 +260,11 @@ namespace GVFS.CommandLine } return true; - } - - private bool AttachGvFltThroughService(GVFSEnlistment enlistment, out string errorMessage) - { - errorMessage = string.Empty; - - NamedPipeMessages.AttachGvFltRequest request = new NamedPipeMessages.AttachGvFltRequest(); - request.EnlistmentRoot = enlistment.EnlistmentRoot; - - using (NamedPipeClient client = new NamedPipeClient(this.ServicePipeName)) - { - if (!client.Connect()) - { - errorMessage = "Unable to mount because GVFS.Service is not responding. " + GVFSVerb.StartServiceInstructions; - return false; - } - - try - { - client.SendRequest(request.ToMessage()); - NamedPipeMessages.Message response = client.ReadResponse(); - if (response.Header == NamedPipeMessages.AttachGvFltRequest.Response.Header) - { - NamedPipeMessages.AttachGvFltRequest.Response message = NamedPipeMessages.AttachGvFltRequest.Response.FromMessage(response); - - if (!string.IsNullOrEmpty(message.ErrorMessage)) - { - errorMessage = message.ErrorMessage; - return false; - } - - if (message.State != NamedPipeMessages.CompletionState.Success) - { - errorMessage = "Failed to attach GvFlt to volume."; - return false; - } - else - { - return true; - } - } - else - { - errorMessage = string.Format("GVFS.Service responded with unexpected message: {0}", response); - return false; - } - } - catch (BrokenPipeException e) - { - errorMessage = "Unable to communicate with GVFS.Service: " + e.ToString(); - return false; - } - } - } + } private bool TryMount(GVFSEnlistment enlistment, string mountExeLocation, out string errorMessage) { - if (!GVFSVerb.TrySetGitConfigSettings(enlistment)) + if (!GVFSVerb.TrySetRequiredGitConfigSettings(enlistment)) { errorMessage = "Unable to configure git repo"; return false; @@ -297,7 +275,7 @@ namespace GVFS.CommandLine mountExeLocation, string.Join( " ", - enlistment.EnlistmentRoot, + "\"" + enlistment.EnlistmentRoot + "\"", ParamPrefix + GVFSConstants.VerbParameters.Mount.Verbosity, this.Verbosity, ParamPrefix + GVFSConstants.VerbParameters.Mount.Keywords, diff --git a/GVFS/GVFS/CommandLine/PrefetchVerb.cs b/GVFS/GVFS/CommandLine/PrefetchVerb.cs index 99a4bb98..19c87d2b 100644 --- a/GVFS/GVFS/CommandLine/PrefetchVerb.cs +++ b/GVFS/GVFS/CommandLine/PrefetchVerb.cs @@ -28,7 +28,7 @@ namespace GVFS.CommandLine private static readonly int SearchThreadCount = Environment.ProcessorCount; private static readonly int DownloadThreadCount = Environment.ProcessorCount; private static readonly int IndexThreadCount = Environment.ProcessorCount; - + [Option( "files", Required = false, @@ -140,6 +140,7 @@ namespace GVFS.CommandLine metadata.Add("Files", this.Files); metadata.Add("Folders", this.Folders); metadata.Add("FoldersListFile", this.FoldersListFile); + metadata.Add("HydrateFiles", this.HydrateFiles); tracer.RelatedEvent(EventLevel.Informational, "PerformPrefetch", metadata); GitObjectsHttpRequestor objectRequestor = new GitObjectsHttpRequestor(tracer, enlistment, cacheServer, retryConfig); @@ -235,20 +236,24 @@ namespace GVFS.CommandLine { bool success; string error = string.Empty; + PhysicalFileSystem fileSystem = new PhysicalFileSystem(); + GitRepo repo = new GitRepo(tracer, enlistment, fileSystem); + GVFSContext context = new GVFSContext(tracer, fileSystem, repo, enlistment); + GitObjects gitObjects = new GVFSGitObjects(context, objectRequestor); if (this.Verbose) { - success = this.TryPrefetchCommitsAndTrees(tracer, enlistment, objectRequestor, out error); + success = this.TryPrefetchCommitsAndTrees(tracer, enlistment, fileSystem, gitObjects, out error); } else { success = this.ShowStatusWhileRunning( - () => { return this.TryPrefetchCommitsAndTrees(tracer, enlistment, objectRequestor, out error); }, + () => this.TryPrefetchCommitsAndTrees(tracer, enlistment, fileSystem, gitObjects, out error), "Fetching commits and trees " + this.GetCacheServerDisplay(cacheServer)); } if (!success) { - this.ReportErrorAndExit(tracer, "Prefetching commits and trees failed: " + error); + this.ReportErrorAndExit(tracer, "Prefetching commits and trees failed: " + error); } } @@ -282,14 +287,7 @@ namespace GVFS.CommandLine if (this.HydrateFiles) { - if (!ConsoleHelper.ShowStatusWhileRunning( - () => this.Execute( - this.EnlistmentRootPath, - verb => verb.Output = new StreamWriter(new MemoryStream())) == ReturnCode.Success, - "Checking that GVFS is mounted", - this.Output, - showSpinner: true, - gvfsLogEnlistmentRoot: null)) + if (!this.CheckIsMounted(verbose: true)) { this.ReportErrorAndExit("You can only specify --hydrate if the repo is mounted. Run 'gvfs mount' and try again."); } @@ -362,14 +360,59 @@ namespace GVFS.CommandLine } } - private bool TryPrefetchCommitsAndTrees(ITracer tracer, GVFSEnlistment enlistment, GitObjectsHttpRequestor objectRequestor, out string error) + private bool CheckIsMounted(bool verbose) { - error = null; - PhysicalFileSystem fileSystem = new PhysicalFileSystem(); - GitRepo repo = new GitRepo(tracer, enlistment, fileSystem); - GVFSContext context = new GVFSContext(tracer, fileSystem, repo, enlistment); - GitObjects gitObjects = new GVFSGitObjects(context, objectRequestor); + Func checkMount = () => this.Execute( + this.EnlistmentRootPath, + verb => verb.Output = new StreamWriter(new MemoryStream())) == ReturnCode.Success; + if (verbose) + { + return ConsoleHelper.ShowStatusWhileRunning( + checkMount, + "Checking that GVFS is mounted", + this.Output, + showSpinner: true, + gvfsLogEnlistmentRoot: null); + } + else + { + return checkMount(); + } + } + + private bool TryPrefetchCommitsAndTrees(ITracer tracer, GVFSEnlistment enlistment, PhysicalFileSystem fileSystem, GitObjects gitObjects, out string error) + { + long maxGoodTimeStamp; + if (!this.TryGetMaxGoodPrefetchTimestamp(tracer, enlistment, fileSystem, gitObjects, out maxGoodTimeStamp, out error)) + { + return false; + } + + List packIndexes; + if (!gitObjects.TryDownloadPrefetchPacks(maxGoodTimeStamp, out packIndexes)) + { + error = "Failed to download prefetch packs"; + return false; + } + + if (!gitObjects.TryWriteMultiPackIndex(tracer, enlistment, fileSystem)) + { + error = "Failed to generate midx for new packfiles"; + return false; + } + + return true; + } + + private bool TryGetMaxGoodPrefetchTimestamp( + ITracer tracer, + GVFSEnlistment enlistment, + PhysicalFileSystem fileSystem, + GitObjects gitObjects, + out long maxGoodTimestamp, + out string error) + { gitObjects.DeleteStaleTempPrefetchPackAndIdxs(); string[] packs = gitObjects.ReadPackFileNames(enlistment.GitPackRoot, GVFSConstants.PrefetchPackPrefix); @@ -379,7 +422,8 @@ namespace GVFS.CommandLine .OrderBy(packInfo => packInfo.Timestamp) .ToList(); - long maxGood = -1; + maxGoodTimestamp = -1; + int firstBadPack = -1; for (int i = 0; i < orderedPacks.Count; ++i) { @@ -403,7 +447,7 @@ namespace GVFS.CommandLine } else { - maxGood = timestamp; + maxGoodTimestamp = timestamp; metadata.Add(TracingConstants.MessageKey.InfoMessage, $"{nameof(this.TryPrefetchCommitsAndTrees)}: Found pack file that's missing idx file, and regenerated idx"); tracer.RelatedEvent(EventLevel.Informational, $"{nameof(this.TryPrefetchCommitsAndTrees)}_RebuildIdx", metadata); @@ -411,7 +455,7 @@ namespace GVFS.CommandLine } else { - maxGood = timestamp; + maxGoodTimestamp = timestamp; } } @@ -425,7 +469,7 @@ namespace GVFS.CommandLine for (int i = orderedPacks.Count - 1; i >= firstBadPack; --i) { string packPath = orderedPacks[i].Path; - string idxPath = Path.ChangeExtension(packPath, ".idx"); + string idxPath = Path.ChangeExtension(packPath, ".idx"); EventMetadata metadata = new EventMetadata(); metadata.Add("path", idxPath); @@ -449,12 +493,7 @@ namespace GVFS.CommandLine } } - if (!gitObjects.TryDownloadPrefetchPacks(maxGood)) - { - error = "Failed to download prefetch packs"; - return false; - } - + error = null; return true; } @@ -474,7 +513,7 @@ namespace GVFS.CommandLine } return null; - } + } private string GetCacheServerDisplay(CacheServerInfo cacheServer) { diff --git a/GVFS/GVFS/CommandLine/RepairJobs/BlobSizeDatabaseRepairJob.cs b/GVFS/GVFS/CommandLine/RepairJobs/BlobSizeDatabaseRepairJob.cs deleted file mode 100644 index 8fd390b0..00000000 --- a/GVFS/GVFS/CommandLine/RepairJobs/BlobSizeDatabaseRepairJob.cs +++ /dev/null @@ -1,48 +0,0 @@ -using GVFS.Common; -using GVFS.Common.FileSystem; -using GVFS.Common.Tracing; -using Microsoft.Isam.Esent; -using Microsoft.Isam.Esent.Collections.Generic; -using System.Collections.Generic; -using System.IO; - -namespace GVFS.CommandLine.RepairJobs -{ - public class BlobSizeDatabaseRepairJob : RepairJob - { - private readonly string databasePath; - - public BlobSizeDatabaseRepairJob(ITracer tracer, TextWriter output, GVFSEnlistment enlistment) - : base(tracer, output, enlistment) - { - this.databasePath = Path.Combine(this.Enlistment.DotGVFSRoot, GVFSConstants.DotGVFS.BlobSizesName); - } - - public override string Name - { - get { return "Blob Size Database"; } - } - - public override IssueType HasIssue(List messages) - { - try - { - using (PersistentDictionary dict = new PersistentDictionary(this.databasePath)) - { - } - } - catch (EsentException error) - { - messages.Add("Could not load blob size database: " + error); - return IssueType.Fixable; - } - - return IssueType.None; - } - - public override FixResult TryFixIssues(List messages) - { - return this.TryDeleteFolder(this.databasePath) ? FixResult.Success : FixResult.Failure; - } - } -} \ No newline at end of file diff --git a/GVFS/GVFS/CommandLine/RepairVerb.cs b/GVFS/GVFS/CommandLine/RepairVerb.cs index 1e1fd8a8..716a6de5 100644 --- a/GVFS/GVFS/CommandLine/RepairVerb.cs +++ b/GVFS/GVFS/CommandLine/RepairVerb.cs @@ -1,10 +1,10 @@ using CommandLine; -using GVFS.CommandLine.DiskLayoutUpgrades; -using GVFS.CommandLine.RepairJobs; using GVFS.Common; using GVFS.Common.Git; using GVFS.Common.NamedPipes; using GVFS.Common.Tracing; +using GVFS.DiskLayoutUpgrades; +using GVFS.RepairJobs; using Microsoft.Diagnostics.Tracing; using System.Collections.Generic; @@ -39,7 +39,7 @@ namespace GVFS.CommandLine { this.ValidatePathParameter(this.EnlistmentRootPath); - string hooksPath = this.GetGVFSHooksPathAndCheckVersion(tracer: null); + string hooksPath = this.GetGVFSHooksPathAndCheckVersion(tracer: null, version: out _); GVFSEnlistment enlistment = GVFSEnlistment.CreateWithoutRepoUrlFromDirectory( this.EnlistmentRootPath, diff --git a/GVFS/GVFS/CommandLine/ServiceVerb.cs b/GVFS/GVFS/CommandLine/ServiceVerb.cs index e85338fc..bfba2245 100644 --- a/GVFS/GVFS/CommandLine/ServiceVerb.cs +++ b/GVFS/GVFS/CommandLine/ServiceVerb.cs @@ -1,6 +1,6 @@ using CommandLine; using GVFS.Common; -using GVFS.Common.Git; +using GVFS.Common.FileSystem; using GVFS.Common.NamedPipes; using System; using System.Collections.Generic; @@ -77,6 +77,14 @@ namespace GVFS.CommandLine } else if (this.MountAll) { + // Always ask the service to ensure that PrjFlt is enabled. This will ensure that the GVFS installer properly waits for + // GVFS.Service to finish enabling PrjFlt's AutoLogger + string error; + if (!this.TryEnableAndAttachGvFltThroughService(string.Empty, out error)) + { + this.ReportErrorAndExit(tracer: null, exitCode: ReturnCode.FilterError, error: $"Failed to enable PrjFlt: {error}"); + } + List failedRepoRoots = new List(); foreach (string repoRoot in repoList) diff --git a/GVFS/GVFS/CommandLine/UnmountVerb.cs b/GVFS/GVFS/CommandLine/UnmountVerb.cs index 85ba3e30..21c05d7e 100644 --- a/GVFS/GVFS/CommandLine/UnmountVerb.cs +++ b/GVFS/GVFS/CommandLine/UnmountVerb.cs @@ -218,9 +218,10 @@ namespace GVFS.CommandLine "gvfs unmount", currentProcess.Id, ProcessHelper.IsAdminElevated(), - currentProcess, - enlistmentRoot, - out result)) + checkAvailabilityOnly: false, + parentProcess: currentProcess, + gvfsEnlistmentRoot: enlistmentRoot, + result: out result)) { this.ReportErrorAndExit("Unable to acquire the lock prior to unmount. " + result); } diff --git a/GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout10to11Upgrade.cs b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout10to11Upgrade.cs similarity index 64% rename from GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout10to11Upgrade.cs rename to GVFS/GVFS/DiskLayoutUpgrades/DiskLayout10to11Upgrade.cs index f4344bc9..a3e98463 100644 --- a/GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout10to11Upgrade.cs +++ b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout10to11Upgrade.cs @@ -1,10 +1,10 @@ using GVFS.Common.Tracing; -namespace GVFS.CommandLine.DiskLayoutUpgrades +namespace GVFS.DiskLayoutUpgrades { - public class DiskLayout10to11Upgrade : DiskLayoutUpgrade + public class DiskLayout10to11Upgrade : DiskLayoutUpgrade.MajorUpgrade { - protected override int SourceLayoutVersion + protected override int SourceMajorVersion { get { return 10; } } @@ -15,7 +15,7 @@ namespace GVFS.CommandLine.DiskLayoutUpgrades /// public override bool TryUpgrade(ITracer tracer, string enlistmentRoot) { - if (!this.TryIncrementDiskLayoutVersion(tracer, enlistmentRoot, this)) + if (!this.TryIncrementMajorVersion(tracer, enlistmentRoot)) { return false; } diff --git a/GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout11to12Upgrade.cs b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout11to12Upgrade.cs similarity index 86% rename from GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout11to12Upgrade.cs rename to GVFS/GVFS/DiskLayoutUpgrades/DiskLayout11to12Upgrade.cs index 467233c1..4edcf8a1 100644 --- a/GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout11to12Upgrade.cs +++ b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout11to12Upgrade.cs @@ -2,11 +2,11 @@ using GVFS.Common.Tracing; using System.IO; -namespace GVFS.CommandLine.DiskLayoutUpgrades +namespace GVFS.DiskLayoutUpgrades { - public class DiskLayout11to12Upgrade : DiskLayoutUpgrade + public class DiskLayout11to12Upgrade : DiskLayoutUpgrade.MajorUpgrade { - protected override int SourceLayoutVersion + protected override int SourceMajorVersion { get { return 11; } } @@ -32,7 +32,7 @@ namespace GVFS.CommandLine.DiskLayoutUpgrades RepoMetadata.Instance.SetLocalCacheRoot(string.Empty); tracer.RelatedInfo("Set LocalCacheRoot to string.Empty"); - if (!this.TryIncrementDiskLayoutVersion(tracer, enlistmentRoot, this)) + if (!this.TryIncrementMajorVersion(tracer, enlistmentRoot)) { return false; } diff --git a/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout12_0To12_1Upgrade.cs b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout12_0To12_1Upgrade.cs new file mode 100644 index 00000000..5168e366 --- /dev/null +++ b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout12_0To12_1Upgrade.cs @@ -0,0 +1,36 @@ +using GVFS.Common.Tracing; +using System.Collections.Generic; + +namespace GVFS.DiskLayoutUpgrades +{ + public class DiskLayout12_0To12_1Upgrade : DiskLayoutUpgrade.MinorUpgrade + { + protected override int SourceMajorVersion + { + get { return 12; } + } + + protected override int SourceMinorVersion + { + get { return 0; } + } + + public override bool TryUpgrade(ITracer tracer, string enlistmentRoot) + { + string errorMessage; + if (!this.TrySetGitConfig( + tracer, + enlistmentRoot, + new Dictionary + { + { "status.aheadbehind", "false" }, + }, + out errorMessage)) + { + return false; + } + + return this.TryIncrementMinorVersion(tracer, enlistmentRoot); + } + } +} diff --git a/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout12to13Upgrade_FolderPlaceholder.cs b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout12to13Upgrade_FolderPlaceholder.cs new file mode 100644 index 00000000..b1736972 --- /dev/null +++ b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout12to13Upgrade_FolderPlaceholder.cs @@ -0,0 +1,120 @@ +using GVFS.Common; +using GVFS.Common.FileSystem; +using GVFS.Common.Tracing; +using ProjFS; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace GVFS.DiskLayoutUpgrades +{ + public class DiskLayout12to13Upgrade_FolderPlaceholder : DiskLayoutUpgrade.MajorUpgrade + { + protected override int SourceMajorVersion + { + get { return 12; } + } + + /// + /// Adds the folder placeholders to the placeholders list + /// + public override bool TryUpgrade(ITracer tracer, string enlistmentRoot) + { + string dotGVFSRoot = Path.Combine(enlistmentRoot, GVFSConstants.DotGVFS.Root); + try + { + string error; + PlaceholderListDatabase placeholders; + if (!PlaceholderListDatabase.TryCreate( + tracer, + Path.Combine(dotGVFSRoot, GVFSConstants.DotGVFS.Databases.PlaceholderList), + new PhysicalFileSystem(), + out placeholders, + out error)) + { + tracer.RelatedError("Failed to open placeholder database: " + error); + return false; + } + + using (placeholders) + { + string workingDirectoryRoot = Path.Combine(enlistmentRoot, GVFSConstants.WorkingDirectoryRootName); + + // Run through the folder placeholders adding to the placeholder list + IEnumerable folderPlaceholderPaths = + GetFolderPlaceholdersFromDisk(tracer, new PhysicalFileSystem(), workingDirectoryRoot) + .Select(x => x.Substring(workingDirectoryRoot.Length + 1)) + .Select(x => new PlaceholderListDatabase.PlaceholderData(x, GVFSConstants.AllZeroSha)); + + List placeholderEntries = placeholders.GetAllEntries(); + placeholderEntries.AddRange(folderPlaceholderPaths); + + placeholders.WriteAllEntriesAndFlush(placeholderEntries); + } + } + catch (IOException ex) + { + tracer.RelatedError("Could not write to placeholder database: " + ex.ToString()); + return false; + } + catch (Exception ex) + { + tracer.RelatedError("Error updating placeholder database with folders: " + ex.ToString()); + return false; + } + + if (!this.TryIncrementMajorVersion(tracer, enlistmentRoot)) + { + return false; + } + + return true; + } + + private static IEnumerable GetFolderPlaceholdersFromDisk(ITracer tracer, PhysicalFileSystem fileSystem, string path) + { + foreach (string directory in fileSystem.EnumerateDirectories(path)) + { + if (!directory.EndsWith(GVFSConstants.PathSeparatorString + GVFSConstants.DotGit.Root)) + { + OnDiskFileState fileState = OnDiskFileState.Full; + HResult result = Utils.GetOnDiskFileState(directory, ref fileState); + if (result == HResult.Ok) + { + if (IsPlaceholder(fileState)) + { + yield return directory; + } + + // Recurse into placeholders and full folders skipping the tombstones + if (!IsTombstone(fileState)) + { + foreach (string placeholderPath in GetFolderPlaceholdersFromDisk(tracer, fileSystem, directory)) + { + yield return placeholderPath; + } + } + } + else if (result != HResult.FileNotFound) + { + // FileNotFound is returned for tombstones when the filter is attached to the volume so we want to + // just skip those folders. Any other HResults may cause valid folder placeholders not to be written + // to the placeholder database so we want to error out on those. + throw new InvalidDataException($"Error getting on disk file state. HResult = {result} for {directory}"); + } + } + } + } + + private static bool IsTombstone(OnDiskFileState fileState) + { + return (fileState & OnDiskFileState.Tombstone) != 0; + } + + private static bool IsPlaceholder(OnDiskFileState fileState) + { + return (fileState & (OnDiskFileState.DirtyPlaceholder | OnDiskFileState.HydratedPlaceholder | OnDiskFileState.Placeholder)) != 0; + } + } +} diff --git a/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout13to14Upgrade_BlobSizes.cs b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout13to14Upgrade_BlobSizes.cs new file mode 100644 index 00000000..555926b4 --- /dev/null +++ b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout13to14Upgrade_BlobSizes.cs @@ -0,0 +1,128 @@ +using GVFS.Common; +using GVFS.Common.FileSystem; +using GVFS.Common.Git; +using GVFS.Common.Tracing; +using GVFS.GVFlt.BlobSize; +using Microsoft.Isam.Esent; +using Microsoft.Isam.Esent.Collections.Generic; +using System.Collections.Generic; +using System.IO; + +namespace GVFS.DiskLayoutUpgrades +{ + public class DiskLayout13to14Upgrade_BlobSizes : DiskLayoutUpgrade.MajorUpgrade + { + private static readonly string BlobSizesName = "BlobSizes"; + + protected override int SourceMajorVersion + { + get { return 13; } + } + + /// + /// Version 13 to 14 added the (shared) SQLite blob sizes database + /// + public override bool TryUpgrade(ITracer tracer, string enlistmentRoot) + { + string dotGVFSPath = Path.Combine(enlistmentRoot, GVFSConstants.DotGVFS.Root); + string error; + if (!RepoMetadata.TryInitialize(tracer, dotGVFSPath, out error)) + { + tracer.RelatedError($"{nameof(DiskLayout13to14Upgrade_BlobSizes)}.{nameof(this.TryUpgrade)}: Could not initialize repo metadata: {error}"); + return false; + } + + string newBlobSizesRoot; + if (!this.TryFindNewBlobSizesRoot(tracer, enlistmentRoot, out newBlobSizesRoot)) + { + return false; + } + + this.MigrateBlobSizes(tracer, enlistmentRoot, newBlobSizesRoot); + + RepoMetadata.Instance.SetBlobSizesRoot(newBlobSizesRoot); + tracer.RelatedInfo("Set BlobSizesRoot: " + newBlobSizesRoot); + + if (!this.TryIncrementMajorVersion(tracer, enlistmentRoot)) + { + return false; + } + + return true; + } + + private bool TryFindNewBlobSizesRoot(ITracer tracer, string enlistmentRoot, out string newBlobSizesRoot) + { + newBlobSizesRoot = null; + + string localCacheRoot; + string error; + if (!RepoMetadata.Instance.TryGetLocalCacheRoot(out localCacheRoot, out error)) + { + tracer.RelatedError($"{nameof(DiskLayout13to14Upgrade_BlobSizes)}.{nameof(this.TryFindNewBlobSizesRoot)}: Could not read local cache root from repo metadata: {error}"); + return false; + } + + if (localCacheRoot == string.Empty) + { + // This is an old repo that was cloned prior to the shared cache + // Blob sizes root should be \.gvfs\databases\blobSizes + newBlobSizesRoot = Path.Combine(enlistmentRoot, GVFSConstants.DotGVFS.Root, GVFSConstants.DotGVFS.Databases.Name, GVFSEnlistment.BlobSizesCacheName); + } + else + { + // This repo was cloned with a shared cache, and the blob sizes should be a sibling to the git objects root + string gitObjectsRoot; + if (!RepoMetadata.Instance.TryGetGitObjectsRoot(out gitObjectsRoot, out error)) + { + tracer.RelatedError($"{nameof(DiskLayout13to14Upgrade_BlobSizes)}.{nameof(this.TryFindNewBlobSizesRoot)}: Could not read git object root from repo metadata: {error}"); + return false; + } + + string cacheRepoFolder = Path.GetDirectoryName(gitObjectsRoot); + newBlobSizesRoot = Path.Combine(cacheRepoFolder, GVFSEnlistment.BlobSizesCacheName); + } + + return true; + } + + private void MigrateBlobSizes(ITracer tracer, string enlistmentRoot, string newBlobSizesRoot) + { + string esentBlobSizeFolder = Path.Combine(enlistmentRoot, GVFSConstants.DotGVFS.Root, BlobSizesName); + PhysicalFileSystem fileSystem = new PhysicalFileSystem(); + if (!fileSystem.DirectoryExists(esentBlobSizeFolder)) + { + tracer.RelatedInfo("Copied no ESENT blob size entries. {0} does not exist", esentBlobSizeFolder); + return; + } + + try + { + using (PersistentDictionary oldBlobSizes = new PersistentDictionary(esentBlobSizeFolder)) + using (BlobSizes newBlobSizes = new BlobSizes(newBlobSizesRoot, fileSystem, tracer)) + { + newBlobSizes.Initialize(); + + int count = 0; + int totalCount = oldBlobSizes.Count; + foreach (KeyValuePair kvp in oldBlobSizes) + { + newBlobSizes.AddSize(new Sha1Id(kvp.Key), kvp.Value); + if (count++ % 5000 == 0) + { + tracer.RelatedInfo("Copied {0}/{1} ESENT blob size entries", count, totalCount); + } + } + + newBlobSizes.Flush(); + newBlobSizes.Shutdown(); + tracer.RelatedInfo("Copied {0}/{1} ESENT blob size entries", count, totalCount); + } + } + catch (EsentException ex) + { + tracer.RelatedWarning("BlobSizes appears to be from an older version of GVFS and corrupted, skipping upgrade of blob sizes: " + ex.Message); + } + } + } +} diff --git a/GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout7to8Upgrade.cs b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout7to8Upgrade.cs similarity index 87% rename from GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout7to8Upgrade.cs rename to GVFS/GVFS/DiskLayoutUpgrades/DiskLayout7to8Upgrade.cs index 866f2e01..c93c9c0e 100644 --- a/GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout7to8Upgrade.cs +++ b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout7to8Upgrade.cs @@ -4,11 +4,11 @@ using Microsoft.Isam.Esent; using Microsoft.Isam.Esent.Collections.Generic; using System.IO; -namespace GVFS.CommandLine.DiskLayoutUpgrades +namespace GVFS.DiskLayoutUpgrades { - public class DiskLayout7to8Upgrade : DiskLayoutUpgrade + public class DiskLayout7to8Upgrade : DiskLayoutUpgrade.MajorUpgrade { - protected override int SourceLayoutVersion + protected override int SourceMajorVersion { get { return 7; } } diff --git a/GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout8to9Upgrade.cs b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout8to9Upgrade.cs similarity index 90% rename from GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout8to9Upgrade.cs rename to GVFS/GVFS/DiskLayoutUpgrades/DiskLayout8to9Upgrade.cs index 3df42fd6..17259b4b 100644 --- a/GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout8to9Upgrade.cs +++ b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout8to9Upgrade.cs @@ -5,11 +5,11 @@ using Microsoft.Isam.Esent.Collections.Generic; using System.Collections.Generic; using System.IO; -namespace GVFS.CommandLine.DiskLayoutUpgrades +namespace GVFS.DiskLayoutUpgrades { - public class DiskLayout8to9Upgrade : DiskLayoutUpgrade + public class DiskLayout8to9Upgrade : DiskLayoutUpgrade.MajorUpgrade { - protected override int SourceLayoutVersion + protected override int SourceMajorVersion { get { return 8; } } @@ -25,7 +25,7 @@ namespace GVFS.CommandLine.DiskLayoutUpgrades return false; } - if (!this.TryIncrementDiskLayoutVersion(tracer, enlistmentRoot, this)) + if (!this.TryIncrementMajorVersion(tracer, enlistmentRoot)) { return false; } diff --git a/GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout9to10Upgrade.cs b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout9to10Upgrade.cs similarity index 94% rename from GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout9to10Upgrade.cs rename to GVFS/GVFS/DiskLayoutUpgrades/DiskLayout9to10Upgrade.cs index 074e9413..7fb7b68a 100644 --- a/GVFS/GVFS/CommandLine/DiskLayoutUpgrades/DiskLayout9to10Upgrade.cs +++ b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayout9to10Upgrade.cs @@ -7,14 +7,14 @@ using Microsoft.Isam.Esent.Collections.Generic; using System.Collections.Generic; using System.IO; -namespace GVFS.CommandLine.DiskLayoutUpgrades +namespace GVFS.DiskLayoutUpgrades { - public class DiskLayout9to10Upgrade : DiskLayoutUpgrade + public class DiskLayout9to10Upgrade : DiskLayoutUpgrade.MajorUpgrade { private const string EsentBackgroundOpsFolder = "BackgroundGitUpdates"; private const string EsentPlaceholderListFolder = "PlaceholderList"; - protected override int SourceLayoutVersion + protected override int SourceMajorVersion { get { return 9; } } @@ -35,7 +35,7 @@ namespace GVFS.CommandLine.DiskLayoutUpgrades return false; } - if (!this.TryIncrementDiskLayoutVersion(tracer, enlistmentRoot, this)) + if (!this.TryIncrementMajorVersion(tracer, enlistmentRoot)) { return false; } diff --git a/GVFS/GVFS/DiskLayoutUpgrades/DiskLayoutUpgrade.cs b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayoutUpgrade.cs new file mode 100644 index 00000000..eed7a706 --- /dev/null +++ b/GVFS/GVFS/DiskLayoutUpgrades/DiskLayoutUpgrade.cs @@ -0,0 +1,422 @@ +using GVFS.Common; +using GVFS.Common.FileSystem; +using GVFS.Common.Git; +using GVFS.Common.Tracing; +using Microsoft.Diagnostics.Tracing; +using Microsoft.Isam.Esent.Collections.Generic; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace GVFS.DiskLayoutUpgrades +{ + public abstract class DiskLayoutUpgrade + { + protected const string EsentRepoMetadataName = "RepoMetadata"; + protected const string DiskLayoutEsentVersionKey = "DiskLayoutVersion"; + + private static readonly Dictionary MajorVersionUpgrades = + BuildMajorUpgradeDictionary( + new DiskLayout7to8Upgrade(), + new DiskLayout8to9Upgrade(), + new DiskLayout9to10Upgrade(), + new DiskLayout10to11Upgrade(), + new DiskLayout11to12Upgrade(), + new DiskLayout12to13Upgrade_FolderPlaceholder(), + new DiskLayout13to14Upgrade_BlobSizes()); + + private static readonly Dictionary> MinorVersionUpgrades = + new Dictionary> + { + { 12, BuildMinorUpgradeDictionary(new DiskLayout12_0To12_1Upgrade()) }, + }; + + protected abstract int SourceMajorVersion { get; } + protected abstract int SourceMinorVersion { get; } + protected abstract bool IsMajorUpgrade { get; } + + public static bool TryRunAllUpgrades(string enlistmentRoot) + { + using (JsonEtwTracer tracer = new JsonEtwTracer(GVFSConstants.GVFSEtwProviderName, "DiskLayoutUpgrade")) + { + try + { + DiskLayoutUpgrade upgrade = null; + while (TryFindUpgrade(tracer, enlistmentRoot, out upgrade)) + { + if (upgrade == null) + { + return true; + } + + if (!upgrade.TryUpgrade(tracer, enlistmentRoot)) + { + return false; + } + + if (!CheckLayoutVersionWasIncremented(tracer, enlistmentRoot, upgrade)) + { + return false; + } + } + + return false; + } + catch (Exception e) + { + StartLogFile(enlistmentRoot, tracer); + tracer.RelatedError(e.ToString()); + return false; + } + finally + { + RepoMetadata.Shutdown(); + } + } + } + + public static bool TryCheckDiskLayoutVersion(ITracer tracer, string enlistmentRoot, out string error) + { + error = string.Empty; + int majorVersion; + int minorVersion; + try + { + if (TryGetDiskLayoutVersion(tracer, enlistmentRoot, out majorVersion, out minorVersion, out error)) + { + if (majorVersion < RepoMetadata.DiskLayoutVersion.MinimumSupportedMajorVersion) + { + error = string.Format( + "Breaking change to GVFS disk layout has been made since cloning. \r\nEnlistment disk layout version: {0} \r\nGVFS disk layout version: {1} \r\nMinimum supported version: {2}", + majorVersion, + RepoMetadata.DiskLayoutVersion.CurrentMajorVersion, + RepoMetadata.DiskLayoutVersion.MinimumSupportedMajorVersion); + + return false; + } + else if (majorVersion > RepoMetadata.DiskLayoutVersion.CurrentMajorVersion) + { + error = string.Format( + "Changes to GVFS disk layout do not allow mounting after downgrade. Try mounting again using a more recent version of GVFS. \r\nEnlistment disk layout version: {0} \r\nGVFS disk layout version: {1}", + majorVersion, + RepoMetadata.DiskLayoutVersion.CurrentMajorVersion); + + return false; + } + else if (majorVersion != RepoMetadata.DiskLayoutVersion.CurrentMajorVersion) + { + error = string.Format( + "GVFS disk layout version doesn't match current version. Try running 'gvfs mount' to upgrade. \r\nEnlistment disk layout version: {0}.{1} \r\nGVFS disk layout version: {2}.{3}", + majorVersion, + minorVersion, + RepoMetadata.DiskLayoutVersion.CurrentMajorVersion, + RepoMetadata.DiskLayoutVersion.CurrentMinorVersion); + + return false; + } + + return true; + } + } + finally + { + RepoMetadata.Shutdown(); + } + + error = "Failed to read disk layout version. " + ConsoleHelper.GetGVFSLogMessage(enlistmentRoot); + return false; + } + + public abstract bool TryUpgrade(ITracer tracer, string enlistmentRoot); + + protected bool TryDeleteFolder(ITracer tracer, string folderName) + { + try + { + PhysicalFileSystem.RecursiveDelete(folderName); + } + catch (Exception e) + { + tracer.RelatedError("Failed to delete folder {0}: {1}", folderName, e.ToString()); + return true; + } + + return true; + } + + protected bool TryDeleteFile(ITracer tracer, string fileName) + { + try + { + File.Delete(fileName); + } + catch (Exception e) + { + tracer.RelatedError("Failed to delete file {0}: {1}", fileName, e.ToString()); + return true; + } + + return true; + } + + protected bool TryRenameFolderForDelete(ITracer tracer, string folderName, out string backupFolder) + { + backupFolder = folderName + ".deleteme"; + + tracer.RelatedInfo("Moving " + folderName + " to " + backupFolder); + + try + { + Directory.Move(folderName, backupFolder); + } + catch (Exception e) + { + tracer.RelatedError("Failed to move {0} to {1}: {2}", folderName, backupFolder, e.ToString()); + return false; + } + + return true; + } + + protected bool TrySetGitConfig(ITracer tracer, string enlistmentRoot, Dictionary configSettings, out string errorMessage) + { + errorMessage = null; + + GVFSEnlistment enlistment = GVFSEnlistment.CreateFromDirectory( + enlistmentRoot, + GitProcess.GetInstalledGitBinPath(), + ProcessHelper.GetCurrentProcessLocation()); + GitProcess git = enlistment.CreateGitProcess(); + + foreach (string key in configSettings.Keys) + { + GitProcess.Result result = git.SetInLocalConfig(key, configSettings[key]); + if (result.HasErrors) + { + tracer.RelatedError("Could not set git config setting {0}. Error: {1}", key, result.Errors); + return false; + } + } + + return true; + } + + private static Dictionary BuildMajorUpgradeDictionary(params MajorUpgrade[] upgraders) + { + return upgraders.ToDictionary( + upgrader => upgrader.SourceMajorVersion, + upgrader => upgrader); + } + + private static Dictionary BuildMinorUpgradeDictionary(params MinorUpgrade[] upgraders) + { + return upgraders.ToDictionary( + upgrader => upgrader.SourceMinorVersion, + upgrader => upgrader); + } + + private static bool CheckLayoutVersionWasIncremented(JsonEtwTracer tracer, string enlistmentRoot, DiskLayoutUpgrade upgrade) + { + string error; + int actualMajorVersion; + int actualMinorVersion; + if (!TryGetDiskLayoutVersion(tracer, enlistmentRoot, out actualMajorVersion, out actualMinorVersion, out error)) + { + tracer.RelatedError(error); + return false; + } + + int expectedMajorVersion = + upgrade.IsMajorUpgrade + ? upgrade.SourceMajorVersion + 1 + : upgrade.SourceMajorVersion; + int expectedMinorVersion = + upgrade.IsMajorUpgrade + ? 0 + : upgrade.SourceMinorVersion + 1; + + if (actualMajorVersion != expectedMajorVersion || + actualMinorVersion != expectedMinorVersion) + { + throw new InvalidDataException(string.Format( + "Disk layout upgrade did not increment layout version. Expected: {0}.{1}, Actual: {2}.{3}", + expectedMajorVersion, + expectedMinorVersion, + actualMajorVersion, + actualMinorVersion)); + } + + return true; + } + + private static bool TryFindUpgrade(JsonEtwTracer tracer, string enlistmentRoot, out DiskLayoutUpgrade upgrade) + { + int majorVersion; + int minorVersion; + + string error; + if (!TryGetDiskLayoutVersion(tracer, enlistmentRoot, out majorVersion, out minorVersion, out error)) + { + StartLogFile(enlistmentRoot, tracer); + tracer.RelatedError(error); + upgrade = null; + return false; + } + + Dictionary minorVersionUpgradesForCurrentMajorVersion; + if (MinorVersionUpgrades.TryGetValue(majorVersion, out minorVersionUpgradesForCurrentMajorVersion)) + { + DiskLayoutUpgrade.MinorUpgrade minorUpgrade; + if (minorVersionUpgradesForCurrentMajorVersion.TryGetValue(minorVersion, out minorUpgrade)) + { + StartLogFile(enlistmentRoot, tracer); + tracer.RelatedInfo( + "Upgrading from disk layout {0}.{1} to {0}.{2}", + majorVersion, + minorVersion, + minorVersion + 1); + + upgrade = minorUpgrade; + return true; + } + } + + DiskLayoutUpgrade.MajorUpgrade majorUpgrade; + if (MajorVersionUpgrades.TryGetValue(majorVersion, out majorUpgrade)) + { + StartLogFile(enlistmentRoot, tracer); + tracer.RelatedInfo("Upgrading from disk layout {0} to {1}", majorVersion, majorVersion + 1); + + upgrade = majorUpgrade; + return true; + } + + // return true to indicate that we succeeded, and no upgrader was found + upgrade = null; + return true; + } + + private static bool TryGetDiskLayoutVersion( + ITracer tracer, + string enlistmentRoot, + out int majorVersion, + out int minorVersion, + out string error) + { + majorVersion = 0; + minorVersion = 0; + + string dotGVFSPath = Path.Combine(enlistmentRoot, GVFSConstants.DotGVFS.Root); + string repoMetadataPath = Path.Combine(dotGVFSPath, EsentRepoMetadataName); + if (Directory.Exists(repoMetadataPath)) + { + try + { + using (PersistentDictionary oldMetadata = new PersistentDictionary(repoMetadataPath)) + { + string versionString = oldMetadata[DiskLayoutEsentVersionKey]; + if (!int.TryParse(versionString, out majorVersion)) + { + error = "Could not parse version string as integer: " + versionString; + return false; + } + } + } + catch (Exception e) + { + majorVersion = 0; + error = e.ToString(); + return false; + } + } + else + { + if (!RepoMetadata.TryInitialize(tracer, dotGVFSPath, out error)) + { + majorVersion = 0; + return false; + } + + if (!RepoMetadata.Instance.TryGetOnDiskLayoutVersion(out majorVersion, out minorVersion, out error)) + { + return false; + } + } + + error = null; + return true; + } + + private static void StartLogFile(string enlistmentRoot, JsonEtwTracer tracer) + { + if (!tracer.HasLogFileEventListener) + { + tracer.AddLogFileEventListener( + GVFSEnlistment.GetNewGVFSLogFileName( + Path.Combine(enlistmentRoot, GVFSConstants.DotGVFS.LogPath), + GVFSConstants.LogFileTypes.Upgrade), + EventLevel.Informational, + Keywords.Any); + + tracer.WriteStartEvent(enlistmentRoot, repoUrl: "N/A", cacheServerUrl: "N/A"); + } + } + + public abstract class MajorUpgrade : DiskLayoutUpgrade + { + protected sealed override bool IsMajorUpgrade + { + get { return true; } + } + + protected sealed override int SourceMinorVersion + { + get { throw new NotSupportedException(); } + } + + protected bool TryIncrementMajorVersion(ITracer tracer, string enlistmentRoot) + { + string newMajorVersion = (this.SourceMajorVersion + 1).ToString(); + string dotGVFSPath = Path.Combine(enlistmentRoot, GVFSConstants.DotGVFS.Root); + string error; + if (!RepoMetadata.TryInitialize(tracer, dotGVFSPath, out error)) + { + tracer.RelatedError("Could not initialize repo metadata: " + error); + return false; + } + + RepoMetadata.Instance.SetEntry(RepoMetadata.Keys.DiskLayoutMajorVersion, newMajorVersion); + RepoMetadata.Instance.SetEntry(RepoMetadata.Keys.DiskLayoutMinorVersion, "0"); + + tracer.RelatedInfo("Disk layout version is now: " + newMajorVersion); + return true; + } + } + + public abstract class MinorUpgrade : DiskLayoutUpgrade + { + protected sealed override bool IsMajorUpgrade + { + get { return false; } + } + + protected bool TryIncrementMinorVersion(ITracer tracer, string enlistmentRoot) + { + string newMinorVersion = (this.SourceMinorVersion + 1).ToString(); + string dotGVFSPath = Path.Combine(enlistmentRoot, GVFSConstants.DotGVFS.Root); + string error; + if (!RepoMetadata.TryInitialize(tracer, dotGVFSPath, out error)) + { + tracer.RelatedError("Could not initialize repo metadata: " + error); + return false; + } + + RepoMetadata.Instance.SetEntry(RepoMetadata.Keys.DiskLayoutMinorVersion, newMinorVersion); + + tracer.RelatedInfo("Disk layout version is now: {0}.{1}", this.SourceMajorVersion, newMinorVersion); + return true; + } + } + } +} diff --git a/GVFS/GVFS/GVFS.csproj b/GVFS/GVFS/GVFS.csproj index eaea7179..26705bb6 100644 --- a/GVFS/GVFS/GVFS.csproj +++ b/GVFS/GVFS/GVFS.csproj @@ -1,6 +1,7 @@  + {32220664-594C-4425-B9A0-88E0BE2F3D2A} @@ -8,7 +9,7 @@ Properties GVFS GVFS - v4.5.2 + v4.6.1 512 true @@ -55,10 +56,28 @@ ..\..\..\packages\Microsoft.Database.Isam.1.9.4\lib\net40\Esent.Isam.dll True - - ..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll + + ..\..\..\packages\Microsoft.Data.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll + + + ..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net46\Microsoft.Diagnostics.Tracing.EventSource.dll True + + $(PackagesDir)\$(ProjFSPackage)\lib\x64\ProjectedFSLib.Managed.dll + + + ..\..\..\packages\SQLitePCLRaw.bundle_green.1.1.7\lib\net45\SQLitePCLRaw.batteries_green.dll + + + ..\..\..\packages\SQLitePCLRaw.bundle_green.1.1.7\lib\net45\SQLitePCLRaw.batteries_v2.dll + + + ..\..\..\packages\SQLitePCLRaw.core.1.1.7\lib\net45\SQLitePCLRaw.core.dll + + + ..\..\..\packages\SQLitePCLRaw.provider.e_sqlite3.net45.1.1.7\lib\net45\SQLitePCLRaw.provider.e_sqlite3.dll + @@ -79,29 +98,32 @@ - - - - + + + + + + + - - - - - - - - + + + + + + + + - - + + @@ -123,10 +145,6 @@ {374bf1e5-0b2d-4d4a-bd5e-4212299def09} GVFS.Common - - {fb0831ae-9997-401b-b31f-3a065fdbeb20} - GvLib.Managed - {1118b427-7063-422f-83b9-5023c8ec5a7a} GVFS.GVFlt @@ -146,6 +164,9 @@ + + + xcopy /Y $(BuildOutputDir)\GVFS.ReadObjectHook\bin\$(Platform)\$(Configuration)\GVFS.ReadObjectHook.* $(TargetDir) @@ -158,6 +179,9 @@ xcopy /Y $(BuildOutputDir)\GitHooksLoader\bin\$(Platform)\$(Configuration)\GitHo + + +