зеркало из https://github.com/microsoft/scalar.git
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
This commit is contained in:
Родитель
2db0c030eb
Коммит
2797fbb835
36
GVFS.sln
36
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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/>
|
||||
</startup>
|
||||
</configuration>
|
||||
</configuration>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>FastFetch</RootNamespace>
|
||||
<AssemblyName>FastFetch</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<NuGetPackageImportStamp>
|
||||
|
@ -43,8 +43,8 @@
|
|||
<HintPath>..\..\..\packages\CommandLineParser.2.1.1-beta\lib\net45\CommandLine.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Diagnostics.Tracing.EventSource">
|
||||
<HintPath>..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll</HintPath>
|
||||
<Reference Include="Microsoft.Diagnostics.Tracing.EventSource, Version=1.1.28.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net46\Microsoft.Diagnostics.Tracing.EventSource.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<GitObjectsHttpRequestor.GitObjectTaskResult>.InvocationResult result = this.objectRequestor.TryDownloadObjects(
|
||||
() => request.ObjectIds.Except(successfulDownloads),
|
||||
onSuccess: (tryCount, response) => this.WriteObjectOrPack(request, tryCount, response, successfulDownloads),
|
||||
onFailure: RetryWrapper<GitObjectsHttpRequestor.GitObjectTaskResult>.StandardErrorHandler(activity, request.PackId, DownloadAreaPath),
|
||||
onFailure: RetryWrapper<GitObjectsHttpRequestor.GitObjectTaskResult>.StandardErrorHandler(activity, request.RequestId, DownloadAreaPath),
|
||||
preferBatchedLooseObjects: true);
|
||||
|
||||
if (!result.Succeeded)
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace FastFetch.Jobs.Data
|
|||
public BlobDownloadRequest(IReadOnlyList<string> 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<string> ObjectIds { get; }
|
||||
|
||||
public int PackId { get; }
|
||||
public int RequestId { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="CommandLineParser" version="2.1.1-beta" targetFramework="net452" />
|
||||
<package id="LibGit2Sharp.NativeBinaries" version="1.0.165" targetFramework="net452" />
|
||||
<package id="Microsoft.Diagnostics.Tracing.EventRegister" version="1.1.28" targetFramework="net452" />
|
||||
<package id="Microsoft.Diagnostics.Tracing.EventSource" version="1.1.28" targetFramework="net452" />
|
||||
<package id="Microsoft.Diagnostics.Tracing.EventSource.Redist" version="1.1.28" targetFramework="net452" />
|
||||
<package id="StyleCop.Error.MSBuild" version="1.0.0" targetFramework="net452" />
|
||||
<package id="StyleCop.MSBuild" version="4.7.54.0" targetFramework="net452" developmentDependency="true" />
|
||||
<package id="CommandLineParser" version="2.1.1-beta" targetFramework="net461" />
|
||||
<package id="LibGit2Sharp.NativeBinaries" version="1.0.165" targetFramework="net461" />
|
||||
<package id="Microsoft.Diagnostics.Tracing.EventRegister" version="1.1.28" targetFramework="net461" />
|
||||
<package id="Microsoft.Diagnostics.Tracing.EventSource" version="1.1.28" targetFramework="net461" />
|
||||
<package id="Microsoft.Diagnostics.Tracing.EventSource.Redist" version="1.1.28" targetFramework="net461" />
|
||||
<package id="StyleCop.Error.MSBuild" version="1.0.0" targetFramework="net461" />
|
||||
<package id="StyleCop.MSBuild" version="4.7.54.0" targetFramework="net461" developmentDependency="true" />
|
||||
</packages>
|
|
@ -8,7 +8,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>GVFS.PreBuild</RootNamespace>
|
||||
<AssemblyName>GVFS.PreBuild</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
|
@ -32,12 +32,16 @@
|
|||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="GenerateApplicationManifests.cs" />
|
||||
<Compile Include="GenerateVersionInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="GVFS.props">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="ProjFS.props">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<Target Name="GetTargetFrameworkProperties" />
|
||||
<Target Name="GetNativeManifest" />
|
||||
|
@ -51,6 +55,15 @@
|
|||
<Code Type="Class" Source="GenerateVersionInfo.cs" />
|
||||
</Task>
|
||||
</UsingTask>
|
||||
<UsingTask TaskName="GenerateApplicationManifests" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v12.0.dll">
|
||||
<ParameterGroup>
|
||||
<Version ParameterType="System.String" Required="true" />
|
||||
<BuildOutputPath ParameterType="System.String" Required="true" />
|
||||
</ParameterGroup>
|
||||
<Task>
|
||||
<Code Type="Class" Source="GenerateApplicationManifests.cs" />
|
||||
</Task>
|
||||
</UsingTask>
|
||||
<Target Name="Build">
|
||||
<CallTarget Targets="GenerateAll" />
|
||||
</Target>
|
||||
|
@ -58,10 +71,11 @@
|
|||
<CallTarget Targets="GenerateAll" />
|
||||
</Target>
|
||||
<Target Name="Clean">
|
||||
<RemoveDir Directories="$(BuildOutputPath)\GVFS.Build" />
|
||||
<Delete Files="$(BuildOutputPath)\CommonAssemblyVersion.cs;$(BuildOutputPath)\CommonVersionHeader.h" />
|
||||
<RemoveDir Directories="$(BuildOutputDir)\$(MSBuildProjectName)" />
|
||||
<Delete Files="$(BuildOutputDir)\CommonAssemblyVersion.cs;$(BuildOutputDir)\CommonVersionHeader.h" />
|
||||
</Target>
|
||||
<Target Name="GenerateAll">
|
||||
<GenerateVersionInfo Version="$(GVFSVersion)" BuildOutputPath="$(BuildOutputDir)" />
|
||||
<GenerateApplicationManifests Version="$(GVFSVersion)" BuildOutputPath="$(BuildOutputDir)" />
|
||||
</Target>
|
||||
</Project>
|
|
@ -1,9 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Label="Parameters">
|
||||
<GVFSVersion>1.0.18026.1</GVFSVersion>
|
||||
<G4WPackageVersion>2.4882590</G4WPackageVersion>
|
||||
<BuildInstaller>true</BuildInstaller>
|
||||
<GVFSVersion>1.0.18115.1</GVFSVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="DefaultSettings">
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
|
@ -15,4 +13,4 @@
|
|||
<OutDir>$(BuildOutputDir)\$(MSBuildProjectName)\bin\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>$(BuildOutputDir)\$(MSBuildProjectName)\intermediate\$(Platform)\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -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(
|
||||
@"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||
<assembly manifestVersion=""1.0"" xmlns=""urn:schemas-microsoft-com:asm.v1"">
|
||||
<assemblyIdentity version=""{0}"" name=""Microsoft.GVFS.{1}""/>
|
||||
<compatibility xmlns=""urn:schemas-microsoft-com:compatibility.v1"">
|
||||
<application>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id=""{{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}}"" />
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
",
|
||||
this.Version,
|
||||
applicationName));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<ProjFSPackage>Microsoft.GVFS.GvFlt.0.180425.1-preview</ProjFSPackage>
|
||||
</PropertyGroup>
|
||||
</Project>
|
|
@ -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; }
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -133,6 +133,11 @@ namespace GVFS.Common.FileSystem
|
|||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<string> EnumerateDirectories(string path)
|
||||
{
|
||||
return Directory.EnumerateDirectories(path);
|
||||
}
|
||||
|
||||
public virtual FileProperties GetFileProperties(string path)
|
||||
{
|
||||
FileInfo entry = new FileInfo(path);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>GVFS.Common</RootNamespace>
|
||||
<AssemblyName>GVFS.Common</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
|
@ -37,7 +37,7 @@
|
|||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Diagnostics.Tracing.EventSource, Version=1.1.28.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net40\Microsoft.Diagnostics.Tracing.EventSource.dll</HintPath>
|
||||
<HintPath>..\..\..\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net46\Microsoft.Diagnostics.Tracing.EventSource.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
|
@ -66,11 +66,12 @@
|
|||
<Compile Include="FileBasedCollectionException.cs" />
|
||||
<Compile Include="FileSystem\PhysicalFileSystemExtensions.cs" />
|
||||
<Compile Include="FileSystem\FlushToDiskFileStream.cs" />
|
||||
<Compile Include="Git\Sha1Id.cs" />
|
||||
<Compile Include="LocalCacheResolver.cs" />
|
||||
<Compile Include="Http\CacheServerResolver.cs" />
|
||||
<Compile Include="Paths.Shared.cs" />
|
||||
<Compile Include="PlaceholderListDatabase.cs" />
|
||||
<Compile Include="FileSystem\GvFltFilter.cs" />
|
||||
<Compile Include="FileSystem\ProjFSFilter.cs" />
|
||||
<Compile Include="GVFSEnlistment.Shared.cs" />
|
||||
<Compile Include="NetworkStreams\BatchedLooseObjectDeserializer.cs" />
|
||||
<Compile Include="NetworkStreams\RestrictedStream.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<VersionRange> AllowedGVFSClientVersions { get; set; }
|
||||
|
||||
public IEnumerable<CacheServerInfo> CacheServers { get; set; }
|
||||
public IEnumerable<CacheServerInfo> CacheServers { get; set; } = Enumerable.Empty<CacheServerInfo>();
|
||||
|
||||
public class VersionRange
|
||||
{
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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>(T value, ref T valueToSet)
|
||||
{
|
||||
if (valueToSet != null)
|
||||
{
|
||||
throw new InvalidOperationException("Value already set.");
|
||||
}
|
||||
|
||||
valueToSet = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a hidden directory @ the given path.
|
||||
/// If directory already exists, hides it.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Allow GVFS to release the lock if it holds it.
|
||||
/// </summary>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ namespace GVFS.Common.Git
|
|||
{
|
||||
const bool PreferLooseObjects = false;
|
||||
IEnumerable<string> objectIds = new[] { commitSha };
|
||||
|
||||
|
||||
RetryWrapper<GitObjectsHttpRequestor.GitObjectTaskResult>.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<string> packIndexes)
|
||||
{
|
||||
EventMetadata metadata = CreateEventMetadata();
|
||||
metadata.Add("latestTimestamp", latestTimestamp);
|
||||
|
@ -103,9 +103,10 @@ namespace GVFS.Common.Git
|
|||
long bytesDownloaded = 0;
|
||||
|
||||
long requestId = HttpRequestor.GetNewRequestId();
|
||||
List<string> innerPackIndexes = null;
|
||||
RetryWrapper<GitObjectsHttpRequestor.GitObjectTaskResult>.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<GitObjectsHttpRequestor.GitObjectTaskResult>.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 <see cref="PrefetchPacksDeserializer"/> to read the packs from the stream.
|
||||
/// </summary>
|
||||
private RetryWrapper<GitObjectsHttpRequestor.GitObjectTaskResult>.CallbackResult DeserializePrefetchPacks(
|
||||
GitEndPointResponseData response,
|
||||
GitEndPointResponseData response,
|
||||
ref long latestTimestamp,
|
||||
ref long bytesDownloaded)
|
||||
ref long bytesDownloaded,
|
||||
ref List<string> packIndexes)
|
||||
{
|
||||
if (packIndexes == null)
|
||||
{
|
||||
packIndexes = new List<string>();
|
||||
}
|
||||
|
||||
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<GitObjectsHttpRequestor.GitObjectTaskResult>.CallbackResult(null, true);
|
||||
}
|
||||
}
|
||||
|
||||
bytesDownloaded += indexLength;
|
||||
|
||||
bytesDownloaded += indexLength;
|
||||
}
|
||||
|
||||
Exception exception = null;
|
||||
|
@ -683,6 +729,11 @@ namespace GVFS.Common.Git
|
|||
return new RetryWrapper<GitObjectsHttpRequestor.GitObjectTaskResult>.CallbackResult(exception, true);
|
||||
}
|
||||
|
||||
foreach (TempPrefetchPackAndIdx tempPack in tempPacks)
|
||||
{
|
||||
packIndexes.Add(tempPack.IdxName);
|
||||
}
|
||||
|
||||
return new RetryWrapper<GitObjectsHttpRequestor.GitObjectTaskResult>.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)
|
||||
{
|
||||
|
|
|
@ -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<string, GitConfigSetting> configSettings)
|
||||
public bool TryGetAllConfig(bool localOnly, out Dictionary<string, GitConfigSetting> 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
|
|||
/// </summary>
|
||||
/// <param name="settingName">The name of the config setting</param>
|
||||
/// <param name="forceOutsideEnlistment">
|
||||
/// 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.
|
||||
/// </param>
|
||||
/// <returns>The value found for the setting.</returns>
|
||||
|
@ -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
|
|||
/// </summary>
|
||||
/// <param name="settingName">The name of the config setting</param>
|
||||
/// <param name="forceOutsideEnlistment">
|
||||
/// 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.
|
||||
/// </param>
|
||||
/// <param value>The value found for the config setting.</param>
|
||||
|
@ -286,21 +286,60 @@ namespace GVFS.Common.Git
|
|||
null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public Result WriteCommitGraph(string objectDir, List<string> 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}\"");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<string> 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
|
|||
/// <summary>
|
||||
/// Invokes git.exe from an enlistment's repository root
|
||||
/// </summary>
|
||||
private Result InvokeGitInWorkingDirectoryRoot(string command, bool useReadObjectHook)
|
||||
private Result InvokeGitInWorkingDirectoryRoot(
|
||||
string command,
|
||||
bool useReadObjectHook,
|
||||
Action<StreamWriter> writeStdIn = null)
|
||||
{
|
||||
return this.InvokeGitImpl(
|
||||
command,
|
||||
workingDirectory: this.enlistment.WorkingDirectoryRoot,
|
||||
dotGitDirectory: null,
|
||||
useReadObjectHook: useReadObjectHook,
|
||||
writeStdIn: null,
|
||||
writeStdIn: writeStdIn,
|
||||
parseStdOutLine: null,
|
||||
timeoutMs: -1);
|
||||
}
|
||||
|
|
|
@ -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<Stream, long> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to find the size of a given blob by SHA1 hash.
|
||||
///
|
||||
/// Returns true iff the blob exists as a loose object.
|
||||
/// </summary>
|
||||
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<Stream, long> 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<Stream, long> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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;
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -54,6 +54,11 @@ namespace GVFS.Common.Http
|
|||
/// </summary>
|
||||
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
|
||||
|
|
|
@ -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<List<GitObjectSize>>.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<List<GitObjectSize>>.CallbackResult(response.Error, true);
|
||||
}
|
||||
|
||||
if (response.HasErrors)
|
||||
{
|
||||
return new RetryWrapper<List<GitObjectSize>>.CallbackResult(response.Error, response.ShouldRetry);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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; }
|
||||
|
||||
/// <summary>
|
||||
/// Should the command actually acquire the GVFSLock or
|
||||
/// only check if the lock is available.
|
||||
/// </summary>
|
||||
public bool CheckAvailabilityOnly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<AttachGvFltRequest>(message.Body);
|
||||
return JsonConvert.DeserializeObject<EnableAndAttachProjFSRequest>(message.Body);
|
||||
}
|
||||
|
||||
public Message ToMessage()
|
||||
|
@ -196,7 +196,7 @@ namespace GVFS.Common.NamedPipes
|
|||
return new Message(Header, JsonConvert.SerializeObject(this));
|
||||
}
|
||||
|
||||
public class Response : BaseResponse<AttachGvFltRequest>
|
||||
public class Response : BaseResponse<EnableAndAttachProjFSRequest>
|
||||
{
|
||||
public static Response FromMessage(Message message)
|
||||
{
|
||||
|
|
|
@ -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
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the build number of the OS
|
||||
/// </summary>
|
||||
/// <returns>Build number</returns>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -141,7 +141,7 @@ namespace GVFS.Common
|
|||
|
||||
private IEnumerable<string> GenerateDataLines(IEnumerable<PlaceholderData> updatedPlaceholders)
|
||||
{
|
||||
HashSet<string> keys = new HashSet<string>();
|
||||
HashSet<string> keys = new HashSet<string>(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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<string, string> 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<string, string>.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<string, string>(Keys.DiskLayoutVersion, DiskLayoutVersion.CurrentDiskLayoutVersion.ToString()),
|
||||
new KeyValuePair<string, string>(Keys.GitObjectsRoot, gitObjectsRoot),
|
||||
new KeyValuePair<string, string>(Keys.LocalCacheRoot, localCacheRoot)
|
||||
new KeyValuePair<string, string>(Keys.DiskLayoutMajorVersion, DiskLayoutVersion.CurrentMajorVersion.ToString()),
|
||||
new KeyValuePair<string, string>(Keys.DiskLayoutMinorVersion, DiskLayoutVersion.CurrentMinorVersion.ToString()),
|
||||
new KeyValuePair<string, string>(Keys.GitObjectsRoot, enlistment.GitObjectsRoot),
|
||||
new KeyValuePair<string, string>(Keys.LocalCacheRoot, enlistment.LocalCacheRoot),
|
||||
new KeyValuePair<string, string>(Keys.BlobSizesRoot, enlistment.BlobSizesRoot),
|
||||
new KeyValuePair<string, string>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
Success = 0,
|
||||
ParsingError = 1,
|
||||
RebootRequired = 2,
|
||||
GenericError = 3
|
||||
GenericError = 3,
|
||||
FilterError = 4
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,6 @@ namespace GVFS.Common.Tracing
|
|||
|
||||
void RelatedError(string format, params object[] args);
|
||||
|
||||
void Stop(EventMetadata metadata);
|
||||
TimeSpan Stop(EventMetadata metadata);
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="LibGit2Sharp.NativeBinaries" version="1.0.165" targetFramework="net452" />
|
||||
<package id="Microsoft.Diagnostics.Tracing.EventRegister" version="1.1.28" targetFramework="net452" />
|
||||
<package id="Microsoft.Diagnostics.Tracing.EventSource" version="1.1.28" targetFramework="net452" />
|
||||
<package id="Microsoft.Diagnostics.Tracing.EventSource.Redist" version="1.1.28" targetFramework="net452" />
|
||||
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net452" />
|
||||
<package id="StyleCop.Error.MSBuild" version="1.0.0" targetFramework="net452" />
|
||||
<package id="StyleCop.MSBuild" version="4.7.54.0" targetFramework="net452" developmentDependency="true" />
|
||||
<package id="LibGit2Sharp.NativeBinaries" version="1.0.165" targetFramework="net461" />
|
||||
<package id="Microsoft.Diagnostics.Tracing.EventRegister" version="1.1.28" targetFramework="net461" />
|
||||
<package id="Microsoft.Diagnostics.Tracing.EventSource" version="1.1.28" targetFramework="net461" />
|
||||
<package id="Microsoft.Diagnostics.Tracing.EventSource.Redist" version="1.1.28" targetFramework="net461" />
|
||||
<package id="Newtonsoft.Json" version="7.0.1" targetFramework="net461" />
|
||||
<package id="StyleCop.Error.MSBuild" version="1.0.0" targetFramework="net461" />
|
||||
<package id="StyleCop.MSBuild" version="4.7.54.0" targetFramework="net461" developmentDependency="true" />
|
||||
</packages>
|
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
namespace GVFS.FunctionalTests.Category
|
||||
{
|
||||
public static class CategoryConstants
|
||||
{
|
||||
public const string FastFetch = "FastFetch";
|
||||
public const string GitCommands = "GitCommands";
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
/// <summary>
|
||||
/// A recursive delete of a directory
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<IOException>(() => { this.DeleteDirectory(path); });
|
||||
|
@ -164,6 +175,9 @@ namespace GVFS.FunctionalTests.FileSystemRunners
|
|||
this.ShouldFail<IOException>(() => { 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++)
|
||||
|
|
|
@ -8,13 +8,14 @@
|
|||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>GVFS.FunctionalTests</RootNamespace>
|
||||
<AssemblyName>GVFS.FunctionalTests</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5.2</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
|
||||
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
|
||||
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
|
||||
<IsCodedUITest>False</IsCodedUITest>
|
||||
<TestProjectType>UnitTest</TestProjectType>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
|
@ -42,6 +43,9 @@
|
|||
<PropertyGroup>
|
||||
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>$(BuildOutputDir)\GVFS.FunctionalTests.exe.manifest</ApplicationManifest>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Esent.Collections, Version=1.9.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
|
@ -58,6 +62,9 @@
|
|||
<HintPath>..\..\..\packages\Microsoft.Database.Isam.1.9.4\lib\net40\Esent.Isam.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Data.Sqlite, Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\Microsoft.Data.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=7.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>..\..\..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
|
@ -69,7 +76,20 @@
|
|||
<Reference Include="nunitlite, Version=3.10.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\NUnitLite.3.10.0-dev-05190\lib\net45\nunitlite.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.batteries_green, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a84b7dcfb1391f7f, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\SQLitePCLRaw.bundle_green.1.1.7\lib\net45\SQLitePCLRaw.batteries_green.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.batteries_v2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8226ea5df37bcae9, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\SQLitePCLRaw.bundle_green.1.1.7\lib\net45\SQLitePCLRaw.batteries_v2.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\SQLitePCLRaw.core.1.1.7\lib\net45\SQLitePCLRaw.core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SQLitePCLRaw.provider.e_sqlite3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9c301db686d0bd12, processorArchitecture=MSIL">
|
||||
<HintPath>..\..\..\packages\SQLitePCLRaw.provider.e_sqlite3.net45.1.1.7\lib\net45\SQLitePCLRaw.provider.e_sqlite3.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.ServiceProcess" />
|
||||
</ItemGroup>
|
||||
<Choose>
|
||||
|
@ -81,7 +101,7 @@
|
|||
<Otherwise />
|
||||
</Choose>
|
||||
<ItemGroup>
|
||||
<Compile Include="FileSystemRunners\Category\CategoryConstants.cs" />
|
||||
<Compile Include="Categories.cs" />
|
||||
<Compile Include="GVFSTestConfig.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
|
@ -95,10 +115,12 @@
|
|||
<Compile Include="Tests\EnlistmentPerFixture\DiagnoseTests.cs" />
|
||||
<Compile Include="Tests\EnlistmentPerFixture\GitCorruptObjectTests.cs" />
|
||||
<Compile Include="Tests\EnlistmentPerFixture\PrefetchVerbWithoutSharedCacheTests.cs" />
|
||||
<Compile Include="Tests\EnlistmentPerFixture\ServiceTests.cs" />
|
||||
<Compile Include="Tests\GitCommands\AddStageTests.cs" />
|
||||
<Compile Include="Tests\GitCommands\DeleteEmptyFolderTests.cs" />
|
||||
<Compile Include="Tests\EnlistmentPerFixture\BasicFileSystemTests.cs" />
|
||||
<Compile Include="Tests\MultiEnlistmentTests\ServiceVerbTests.cs" />
|
||||
<Compile Include="Tests\GitCommands\ResetHardTests.cs" />
|
||||
<Compile Include="Tests\MultiEnlistmentTests\SharedCacheTests.cs" />
|
||||
<Compile Include="Tests\EnlistmentPerTestCase\DiskLayoutUpgradeTests.cs" />
|
||||
<Compile Include="Tests\EnlistmentPerFixture\UnmountTests.cs" />
|
||||
|
@ -163,6 +185,9 @@
|
|||
<None Include="App.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
<None Include="$(BuildOutputDir)\GVFS.FunctionalTests.exe.manifest">
|
||||
<Link>GVFS.FunctionalTests.exe.manifest</Link>
|
||||
</None>
|
||||
<None Include="packages.config">
|
||||
<SubType>Designer</SubType>
|
||||
</None>
|
||||
|
@ -228,11 +253,15 @@
|
|||
<Error Condition="!Exists('..\..\..\packages\StyleCop.MSBuild.4.7.54.0\build\StyleCop.MSBuild.Targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\StyleCop.MSBuild.4.7.54.0\build\StyleCop.MSBuild.Targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\StyleCop.Error.MSBuild.1.0.0\build\StyleCop.Error.MSBuild.Targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\StyleCop.Error.MSBuild.1.0.0\build\StyleCop.Error.MSBuild.Targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\NUnit.3.10.0-dev-05190\build\NUnit.props')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\NUnit.3.10.0-dev-05190\build\NUnit.props'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.7\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.7\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.osx.1.1.7\build\net35\SQLitePCLRaw.lib.e_sqlite3.osx.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.osx.1.1.7\build\net35\SQLitePCLRaw.lib.e_sqlite3.osx.targets'))" />
|
||||
<Error Condition="!Exists('..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.v110_xp.1.1.7\build\net35\SQLitePCLRaw.lib.e_sqlite3.v110_xp.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.v110_xp.1.1.7\build\net35\SQLitePCLRaw.lib.e_sqlite3.v110_xp.targets'))" />
|
||||
</Target>
|
||||
<Import Project="..\..\..\packages\StyleCop.MSBuild.4.7.54.0\build\StyleCop.MSBuild.Targets" Condition="Exists('..\..\..\packages\StyleCop.MSBuild.4.7.54.0\build\StyleCop.MSBuild.Targets')" />
|
||||
<Import Project="..\..\..\packages\StyleCop.Error.MSBuild.1.0.0\build\StyleCop.Error.MSBuild.Targets" Condition="Exists('..\..\..\packages\StyleCop.Error.MSBuild.1.0.0\build\StyleCop.Error.MSBuild.Targets')" />
|
||||
<PropertyGroup>
|
||||
<PostBuildEvent>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
|
|||
<PreBuildEvent>
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.7\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets" Condition="Exists('..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.linux.1.1.7\build\net35\SQLitePCLRaw.lib.e_sqlite3.linux.targets')" />
|
||||
<Import Project="..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.osx.1.1.7\build\net35\SQLitePCLRaw.lib.e_sqlite3.osx.targets" Condition="Exists('..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.osx.1.1.7\build\net35\SQLitePCLRaw.lib.e_sqlite3.osx.targets')" />
|
||||
<Import Project="..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.v110_xp.1.1.7\build\net35\SQLitePCLRaw.lib.e_sqlite3.v110_xp.targets" Condition="Exists('..\..\..\packages\SQLitePCLRaw.lib.e_sqlite3.v110_xp.1.1.7\build\net35\SQLitePCLRaw.lib.e_sqlite3.v110_xp.targets')" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
namespace GVFS.FunctionalTests
|
||||
using NUnit.Framework;
|
||||
using System.IO;
|
||||
|
||||
namespace GVFS.FunctionalTests
|
||||
{
|
||||
public static class GVFSTestConfig
|
||||
{
|
||||
|
@ -9,5 +12,18 @@
|
|||
public static string LocalCacheRoot { get; set; }
|
||||
|
||||
public static bool UseAllRunners { get; set; }
|
||||
|
||||
public static bool TestGVFSOnPath { get; set; }
|
||||
|
||||
public static string PathToGVFS
|
||||
{
|
||||
get
|
||||
{
|
||||
return
|
||||
TestGVFSOnPath ?
|
||||
Properties.Settings.Default.PathToGVFS :
|
||||
Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,12 @@ namespace GVFS.FunctionalTests
|
|||
GVFSTestConfig.NoSharedCache = true;
|
||||
}
|
||||
|
||||
if (runner.HasCustomArg("--test-gvfs-on-path"))
|
||||
{
|
||||
Console.WriteLine("Running tests against GVFS on path");
|
||||
GVFSTestConfig.TestGVFSOnPath = true;
|
||||
}
|
||||
|
||||
GVFSTestConfig.LocalCacheRoot = runner.GetCustomArgWithParam("--shared-gvfs-cache-root");
|
||||
|
||||
if (runner.HasCustomArg("--full-suite"))
|
||||
|
@ -27,16 +33,24 @@ namespace GVFS.FunctionalTests
|
|||
Console.WriteLine("Running the full suite of tests");
|
||||
GVFSTestConfig.UseAllRunners = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
runner.ExcludeCategory(Categories.FullSuiteOnly);
|
||||
}
|
||||
|
||||
GVFSTestConfig.RepoToClone =
|
||||
runner.GetCustomArgWithParam("--repo-to-clone")
|
||||
?? Properties.Settings.Default.RepoToClone;
|
||||
|
||||
string servicePath = Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFSService);
|
||||
|
||||
string servicePath =
|
||||
GVFSTestConfig.TestGVFSOnPath ?
|
||||
Properties.Settings.Default.PathToGVFSService :
|
||||
Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFSService);
|
||||
|
||||
GVFSServiceProcess.InstallService(servicePath);
|
||||
try
|
||||
{
|
||||
Environment.ExitCode = runner.RunTests(Properties.Settings.Default.TestRepeatCount);
|
||||
Environment.ExitCode = runner.RunTests();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -55,6 +69,8 @@ namespace GVFS.FunctionalTests
|
|||
GVFSServiceProcess.UninstallService();
|
||||
}
|
||||
|
||||
PrintTestCaseStats.PrintRunTimeStats();
|
||||
|
||||
if (Debugger.IsAttached)
|
||||
{
|
||||
Console.WriteLine("Tests completed. Press Enter to exit.");
|
||||
|
|
|
@ -12,7 +12,7 @@ namespace GVFS.FunctionalTests.Properties {
|
|||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.5.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
@ -41,15 +41,6 @@ namespace GVFS.FunctionalTests.Properties {
|
|||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("1")]
|
||||
public int TestRepeatCount {
|
||||
get {
|
||||
return ((int)(this["TestRepeatCount"]));
|
||||
}
|
||||
}
|
||||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("C:\\Program Files\\Git\\bin\\bash.exe")]
|
||||
|
@ -61,7 +52,7 @@ namespace GVFS.FunctionalTests.Properties {
|
|||
|
||||
[global::System.Configuration.ApplicationScopedSettingAttribute()]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("FunctionalTests/20170905")]
|
||||
[global::System.Configuration.DefaultSettingValueAttribute("FunctionalTests/20180214")]
|
||||
public string Commitish {
|
||||
get {
|
||||
return ((string)(this["Commitish"]));
|
||||
|
|
|
@ -8,14 +8,11 @@
|
|||
<Setting Name="EnlistmentRoot" Type="System.String" Scope="Application">
|
||||
<Value Profile="(Default)">C:\Repos\GVFSFunctionalTests\enlistment</Value>
|
||||
</Setting>
|
||||
<Setting Name="TestRepeatCount" Type="System.Int32" Scope="Application">
|
||||
<Value Profile="(Default)">1</Value>
|
||||
</Setting>
|
||||
<Setting Name="PathToBash" Type="System.String" Scope="Application">
|
||||
<Value Profile="(Default)">C:\Program Files\Git\bin\bash.exe</Value>
|
||||
</Setting>
|
||||
<Setting Name="Commitish" Type="System.String" Scope="Application">
|
||||
<Value Profile="(Default)">FunctionalTests/20170905</Value>
|
||||
<Value Profile="(Default)">FunctionalTests/20180214</Value>
|
||||
</Setting>
|
||||
<Setting Name="ControlGitRepoRoot" Type="System.String" Scope="Application">
|
||||
<Value Profile="(Default)">C:\Repos\GVFSFunctionalTests\ControlRepo</Value>
|
||||
|
|
|
@ -183,13 +183,12 @@ namespace GVFS.FunctionalTests.Should
|
|||
|
||||
public DirectoryAdapter WithDeepStructure(
|
||||
FileSystemRunner fileSystem,
|
||||
string otherPath,
|
||||
bool skipEmptyDirectories,
|
||||
string otherPath,
|
||||
bool ignoreCase = false,
|
||||
bool compareContent = false)
|
||||
{
|
||||
otherPath.ShouldBeADirectory(this.runner);
|
||||
CompareDirectories(fileSystem, otherPath, this.Path, skipEmptyDirectories, ignoreCase, compareContent);
|
||||
CompareDirectories(fileSystem, otherPath, this.Path, ignoreCase, compareContent);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -239,13 +238,12 @@ namespace GVFS.FunctionalTests.Should
|
|||
private static void CompareDirectories(
|
||||
FileSystemRunner fileSystem,
|
||||
string expectedPath,
|
||||
string actualPath,
|
||||
bool skipEmptyDirectories,
|
||||
string actualPath,
|
||||
bool ignoreCase,
|
||||
bool compareContent)
|
||||
{
|
||||
IEnumerable<FileSystemInfo> expectedEntries = DepthFirstEnumeration(expectedPath, skipEmptyDirectories);
|
||||
IEnumerable<FileSystemInfo> actualEntries = DepthFirstEnumeration(actualPath, skipEmptyDirectories);
|
||||
IEnumerable<FileSystemInfo> expectedEntries = new DirectoryInfo(expectedPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories);
|
||||
IEnumerable<FileSystemInfo> actualEntries = new DirectoryInfo(actualPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories);
|
||||
|
||||
string dotGitFolder = System.IO.Path.DirectorySeparatorChar + TestConstants.DotGit.Root + System.IO.Path.DirectorySeparatorChar;
|
||||
IEnumerator<FileSystemInfo> expectedEnumerator = expectedEntries
|
||||
|
@ -262,22 +260,41 @@ namespace GVFS.FunctionalTests.Should
|
|||
|
||||
while (expectedMoved && actualMoved)
|
||||
{
|
||||
bool nameIsEqual = false;
|
||||
if (ignoreCase)
|
||||
{
|
||||
actualEnumerator.Current.Name.ToLowerInvariant().ShouldEqual(expectedEnumerator.Current.Name.ToLowerInvariant());
|
||||
nameIsEqual = actualEnumerator.Current.Name.Equals(expectedEnumerator.Current.Name, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
else
|
||||
{
|
||||
actualEnumerator.Current.Name.ShouldEqual(expectedEnumerator.Current.Name);
|
||||
nameIsEqual = actualEnumerator.Current.Name.Equals(expectedEnumerator.Current.Name, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
if (!nameIsEqual)
|
||||
{
|
||||
if ((expectedEnumerator.Current.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
|
||||
{
|
||||
// ignoring directories that are empty in the control repo because GVFS does a better job at removing
|
||||
// empty directories because it is tracking placeholder folders and removes them
|
||||
// Only want to check for an empty directory if the names don't match. If the names match and
|
||||
// both expected and actual directories are empty that is okay
|
||||
if (Directory.GetFileSystemEntries(expectedEnumerator.Current.FullName, "*", SearchOption.TopDirectoryOnly).Length == 0)
|
||||
{
|
||||
expectedMoved = expectedEnumerator.MoveNext();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Fail($"File names don't match: expected: {expectedEnumerator.Current.FullName} actual: {actualEnumerator.Current.FullName}");
|
||||
}
|
||||
|
||||
if ((expectedEnumerator.Current.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
|
||||
{
|
||||
(actualEnumerator.Current.Attributes & FileAttributes.Directory).ShouldEqual(FileAttributes.Directory);
|
||||
(actualEnumerator.Current.Attributes & FileAttributes.Directory).ShouldEqual(FileAttributes.Directory, $"expected path: {expectedEnumerator.Current.FullName} actual path: {actualEnumerator.Current.FullName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
(actualEnumerator.Current.Attributes & FileAttributes.Directory).ShouldNotEqual(FileAttributes.Directory);
|
||||
(actualEnumerator.Current.Attributes & FileAttributes.Directory).ShouldNotEqual(FileAttributes.Directory, $"expected path: {expectedEnumerator.Current.FullName} actual path: {actualEnumerator.Current.FullName}");
|
||||
|
||||
FileInfo expectedFileInfo = (expectedEnumerator.Current as FileInfo).ShouldNotBeNull();
|
||||
FileInfo actualFileInfo = (actualEnumerator.Current as FileInfo).ShouldNotBeNull();
|
||||
|
@ -313,30 +330,6 @@ namespace GVFS.FunctionalTests.Should
|
|||
Assert.Fail(errorEntries.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<FileSystemInfo> DepthFirstEnumeration(string path, bool skipEmptyDirectories)
|
||||
{
|
||||
DirectoryInfo directoryInfo = new DirectoryInfo(path);
|
||||
foreach (DirectoryInfo subDirectory in directoryInfo.GetDirectories())
|
||||
{
|
||||
bool foundItem = false;
|
||||
foreach (FileSystemInfo subDirectoryItem in DepthFirstEnumeration(subDirectory.FullName, skipEmptyDirectories))
|
||||
{
|
||||
foundItem = true;
|
||||
yield return subDirectoryItem;
|
||||
}
|
||||
|
||||
if (!skipEmptyDirectories || foundItem)
|
||||
{
|
||||
yield return subDirectory;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (FileInfo fileInfo in directoryInfo.GetFiles())
|
||||
{
|
||||
yield return fileInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1816,6 +1816,18 @@ namespace GVFS.FunctionalTests.Tests.LongRunningEnlistment
|
|||
GVFlt_DirEnumTest.GVFlt_EnumFolderSmallBuffer(this.Enlistment.RepoRoot).ShouldEqual(true);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void Native_GVFlt_EnumTestNoMoreNoSuchReturnCodes()
|
||||
{
|
||||
GVFlt_DirEnumTest.GVFlt_EnumTestNoMoreNoSuchReturnCodes(this.Enlistment.RepoRoot).ShouldEqual(true);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void Native_GVFlt_EnumTestQueryDirectoryFileRestartScanProjectedFile()
|
||||
{
|
||||
GVFlt_DirEnumTest.GVFlt_EnumTestQueryDirectoryFileRestartScanProjectedFile(this.Enlistment.RepoRoot).ShouldEqual(true);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void Native_GVFlt_ModifyFileInScratchAndCheckLastWriteTime()
|
||||
{
|
||||
|
@ -2275,6 +2287,12 @@ namespace GVFS.FunctionalTests.Tests.LongRunningEnlistment
|
|||
|
||||
[DllImport("GVFS.NativeTests.dll")]
|
||||
public static extern bool GVFlt_EnumFolderSmallBuffer(string enumFolderSmallBufferPath);
|
||||
|
||||
[DllImport("GVFS.NativeTests.dll")]
|
||||
public static extern bool GVFlt_EnumTestNoMoreNoSuchReturnCodes(string virtualRootPath);
|
||||
|
||||
[DllImport("GVFS.NativeTests.dll")]
|
||||
public static extern bool GVFlt_EnumTestQueryDirectoryFileRestartScanProjectedFile(string virtualRootPath);
|
||||
}
|
||||
|
||||
private class GVFlt_FileAttributeTest
|
||||
|
|
|
@ -5,6 +5,7 @@ using NUnit.Framework;
|
|||
namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(Categories.FullSuiteOnly)]
|
||||
public class CacheServerTests : TestsWithEnlistmentPerFixture
|
||||
{
|
||||
private const string CustomUrl = "https://myCache";
|
||||
|
|
|
@ -28,10 +28,9 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
[TestCase]
|
||||
public void CloneWithLocalCachePathWithinSrc()
|
||||
{
|
||||
string pathToGVFS = Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS);
|
||||
string newEnlistmentRoot = GVFSFunctionalTestEnlistment.GetUniqueEnlistmentRoot();
|
||||
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo(pathToGVFS);
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo(GVFSTestConfig.PathToGVFS);
|
||||
processInfo.Arguments = $"clone {Properties.Settings.Default.RepoToClone} {newEnlistmentRoot} --local-cache-path {Path.Combine(newEnlistmentRoot, "src", ".gvfsCache")}";
|
||||
processInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
processInfo.CreateNoWindow = true;
|
||||
|
@ -44,11 +43,16 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
result.Output.ShouldContain("'--local-cache-path' cannot be inside the src folder");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void CloneToPathWithSpaces()
|
||||
{
|
||||
GVFSFunctionalTestEnlistment enlistment = GVFSFunctionalTestEnlistment.CloneAndMountEnlistmentWithSpacesInPath(GVFSTestConfig.PathToGVFS);
|
||||
enlistment.UnmountAndDeleteAll();
|
||||
}
|
||||
|
||||
private void SubfolderCloneShouldFail()
|
||||
{
|
||||
string pathToGVFS = Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS);
|
||||
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo(pathToGVFS);
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo(GVFSTestConfig.PathToGVFS);
|
||||
processInfo.Arguments = "clone " + GVFSTestConfig.RepoToClone + " src\\gvfs\\test1";
|
||||
processInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
processInfo.CreateNoWindow = true;
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.IO;
|
|||
namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(Categories.FullSuiteOnly)]
|
||||
public class DehydrateTests : TestsWithEnlistmentPerFixture
|
||||
{
|
||||
private const int GVFSGenericError = 3;
|
||||
|
@ -54,7 +55,9 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
{
|
||||
this.Enlistment.UnmountGVFS();
|
||||
|
||||
string currentVersion = GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot).ShouldNotBeNull();
|
||||
string majorVersion;
|
||||
string minorVersion;
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot, out majorVersion, out minorVersion);
|
||||
string objectsRoot = GVFSHelpers.GetPersistedGitObjectsRoot(this.Enlistment.DotGVFSRoot).ShouldNotBeNull();
|
||||
|
||||
string metadataPath = Path.Combine(this.Enlistment.DotGVFSRoot, GVFSHelpers.RepoMetadataName);
|
||||
|
@ -62,7 +65,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
this.fileSystem.MoveFile(metadataPath, metadataBackupPath);
|
||||
|
||||
this.fileSystem.CreateEmptyFile(metadataPath);
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, currentVersion);
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, majorVersion, minorVersion);
|
||||
GVFSHelpers.SaveGitObjectsRoot(this.Enlistment.DotGVFSRoot, objectsRoot);
|
||||
|
||||
this.DehydrateShouldFail("Failed to determine local cache path from repo metadata", noStatus: true);
|
||||
|
@ -78,7 +81,9 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
{
|
||||
this.Enlistment.UnmountGVFS();
|
||||
|
||||
string currentVersion = GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot).ShouldNotBeNull();
|
||||
string majorVersion;
|
||||
string minorVersion;
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot, out majorVersion, out minorVersion);
|
||||
string localCacheRoot = GVFSHelpers.GetPersistedLocalCacheRoot(this.Enlistment.DotGVFSRoot).ShouldNotBeNull();
|
||||
|
||||
string metadataPath = Path.Combine(this.Enlistment.DotGVFSRoot, GVFSHelpers.RepoMetadataName);
|
||||
|
@ -86,7 +91,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
this.fileSystem.MoveFile(metadataPath, metadataBackupPath);
|
||||
|
||||
this.fileSystem.CreateEmptyFile(metadataPath);
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, currentVersion);
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, majorVersion, minorVersion);
|
||||
GVFSHelpers.SaveLocalCacheRoot(this.Enlistment.DotGVFSRoot, localCacheRoot);
|
||||
|
||||
this.DehydrateShouldFail("Failed to determine git objects root from repo metadata", noStatus: true);
|
||||
|
@ -102,17 +107,22 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
{
|
||||
this.Enlistment.UnmountGVFS();
|
||||
|
||||
string currentVersion = GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot).ShouldNotBeNull();
|
||||
int currentVersionNum;
|
||||
int.TryParse(currentVersion, out currentVersionNum).ShouldEqual(true);
|
||||
string majorVersion;
|
||||
string minorVersion;
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot, out majorVersion, out minorVersion);
|
||||
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, (currentVersionNum - 1).ToString());
|
||||
int majorVersionNum;
|
||||
int minorVersionNum;
|
||||
int.TryParse(majorVersion.ShouldNotBeNull(), out majorVersionNum).ShouldEqual(true);
|
||||
int.TryParse(minorVersion.ShouldNotBeNull(), out minorVersionNum).ShouldEqual(true);
|
||||
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, (majorVersionNum - 1).ToString(), "0");
|
||||
this.DehydrateShouldFail("disk layout version doesn't match current version", noStatus: true);
|
||||
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, (currentVersionNum + 1).ToString());
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, (majorVersionNum + 1).ToString(), "0");
|
||||
this.DehydrateShouldFail("Changes to GVFS disk layout do not allow mounting after downgrade.", noStatus: true);
|
||||
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, currentVersionNum.ToString());
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, majorVersionNum.ToString(), minorVersionNum.ToString());
|
||||
|
||||
this.Enlistment.MountGVFS();
|
||||
}
|
||||
|
@ -144,10 +154,9 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
dehydrateFlags += " --no-status ";
|
||||
}
|
||||
|
||||
string pathToGVFS = Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS);
|
||||
string enlistmentRoot = this.Enlistment.EnlistmentRoot;
|
||||
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo(pathToGVFS);
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo(GVFSTestConfig.PathToGVFS);
|
||||
processInfo.Arguments = "dehydrate " + dehydrateFlags + " --internal_use_only_service_name " + GVFSServiceProcess.TestServiceName;
|
||||
processInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
processInfo.WorkingDirectory = enlistmentRoot;
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.Linq;
|
|||
namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(Categories.FullSuiteOnly)]
|
||||
public class DiagnoseTests : TestsWithEnlistmentPerFixture
|
||||
{
|
||||
private FileSystemRunner fileSystem;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using GVFS.FunctionalTests.FileSystemRunners;
|
||||
using GVFS.FunctionalTests.FileSystemRunners;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.Tests.Should;
|
||||
|
@ -11,7 +10,7 @@ using System.Linq;
|
|||
namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class GitCorruptObjectTests : TestsWithEnlistmentPerFixture
|
||||
{
|
||||
private FileSystemRunner fileSystem;
|
||||
|
|
|
@ -128,6 +128,29 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
}
|
||||
|
||||
[TestCase, Order(5)]
|
||||
public void CaseOnlyRenameOfNewFolderKeepsExcludeEntries()
|
||||
{
|
||||
string[] expectedAlwaysExcludeEntries =
|
||||
{
|
||||
"*",
|
||||
"!/Folder/",
|
||||
"!/Folder/testfile",
|
||||
};
|
||||
|
||||
this.fileSystem.CreateDirectory(Path.Combine(this.Enlistment.RepoRoot, "Folder"));
|
||||
this.fileSystem.CreateEmptyFile(Path.Combine(this.Enlistment.RepoRoot, "Folder", "testfile"));
|
||||
this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete.");
|
||||
|
||||
string alwaysExcludeFileVirtualPath = this.Enlistment.GetVirtualPathTo(GitHelpers.AlwaysExcludeFilePath);
|
||||
alwaysExcludeFileVirtualPath.ShouldBeAFile(this.fileSystem).WithContents().ShouldContain(expectedAlwaysExcludeEntries);
|
||||
|
||||
this.fileSystem.RenameDirectory(this.Enlistment.RepoRoot, "Folder", "folder");
|
||||
this.Enlistment.WaitForBackgroundOperations().ShouldEqual(true, "Background operations failed to complete.");
|
||||
|
||||
alwaysExcludeFileVirtualPath.ShouldBeAFile(this.fileSystem).WithContents().ShouldContain(expectedAlwaysExcludeEntries);
|
||||
}
|
||||
|
||||
[TestCase, Order(6)]
|
||||
public void ReadingFileDoesNotUpdateIndexOrSparseCheckout()
|
||||
{
|
||||
string gitFileToCheck = "GVFS/GVFS.FunctionalTests/Category/CategoryConstants.cs";
|
||||
|
@ -156,7 +179,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
sparseCheckoutFile.ShouldBeAFile(this.fileSystem).WithContents().ShouldNotContain(ignoreCase: true, unexpectedSubstrings: gitFileToCheck);
|
||||
}
|
||||
|
||||
[TestCase, Order(6)]
|
||||
[TestCase, Order(7)]
|
||||
public void ModifiedFileWillGetSkipworktreeBitCleared()
|
||||
{
|
||||
string fileToTest = "GVFS\\GVFS.Common\\RetryWrapper.cs";
|
||||
|
@ -177,7 +200,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
this.VerifyWorktreeBit(gitFileToTest, LsFilesStatus.Cached);
|
||||
}
|
||||
|
||||
[TestCase, Order(7)]
|
||||
[TestCase, Order(8)]
|
||||
public void RenamedFileAddedToSparseCheckoutAndSkipWorktreeBitCleared()
|
||||
{
|
||||
string fileToRenameSparseCheckoutEntry = "/Test_EPF_MoveRenameFileTests/ChangeUnhydratedFileName/Program.cs";
|
||||
|
@ -200,7 +223,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
this.VerifyWorktreeBit(fileToRenameSparseCheckoutEntry.TrimStart(new char[] { '/' }), LsFilesStatus.Cached);
|
||||
}
|
||||
|
||||
[TestCase, Order(8)]
|
||||
[TestCase, Order(9)]
|
||||
public void RenamedFileAndOverwrittenTargetAddedToSparseCheckoutAndSkipWorktreeBitCleared()
|
||||
{
|
||||
string fileToRenameSparseCheckoutEntry = "/Test_EPF_MoveRenameFileTests_2/MoveUnhydratedFileToOverwriteUnhydratedFileAndWrite/RunUnitTests.bat";
|
||||
|
@ -225,7 +248,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
this.VerifyWorktreeBit(fileToRenameTargetSparseCheckoutEntry.TrimStart(new char[] { '/' }), LsFilesStatus.Cached);
|
||||
}
|
||||
|
||||
[TestCase, Order(9)]
|
||||
[TestCase, Order(10)]
|
||||
public void DeletedFileAddedToSparseCheckoutAndSkipWorktreeBitCleared()
|
||||
{
|
||||
string fileToDeleteSparseCheckoutEntry = "/GVFlt_DeleteFileTest/GVFlt_DeleteFullFileWithoutFileContext_DeleteOnClose/a.txt";
|
||||
|
@ -243,7 +266,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
this.VerifyWorktreeBit(fileToDeleteSparseCheckoutEntry.TrimStart(new char[] { '/' }), LsFilesStatus.Cached);
|
||||
}
|
||||
|
||||
[TestCase, Order(10)]
|
||||
[TestCase, Order(11)]
|
||||
public void DeletedFolderAndChildrenAddedToSparseCheckoutAndSkipWorktreeBitCleared()
|
||||
{
|
||||
string folderToDelete = "Scripts";
|
||||
|
@ -277,7 +300,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
}
|
||||
}
|
||||
|
||||
[TestCase, Order(11)]
|
||||
[TestCase, Order(12)]
|
||||
public void FileRenamedOutOfRepoAddedToSparseCheckoutAndSkipWorktreeBitCleared()
|
||||
{
|
||||
string fileToRenameSparseCheckoutEntry = "/GVFlt_MoveFileTest/PartialToOutside/from/lessInFrom.txt";
|
||||
|
@ -297,7 +320,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
this.VerifyWorktreeBit(fileToRenameSparseCheckoutEntry.TrimStart(new char[] { '/' }), LsFilesStatus.Cached);
|
||||
}
|
||||
|
||||
[TestCase, Order(12)]
|
||||
[TestCase, Order(13)]
|
||||
public void OverwrittenFileAddedToSparseCheckoutAndSkipWorktreeBitCleared()
|
||||
{
|
||||
string fileToOverwriteSparseCheckoutEntry = "/Test_EPF_WorkingDirectoryTests/1/2/3/4/ReadDeepProjectedFile.cpp";
|
||||
|
@ -319,7 +342,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
this.VerifyWorktreeBit(fileToOverwriteSparseCheckoutEntry.TrimStart(new char[] { '/' }), LsFilesStatus.Cached);
|
||||
}
|
||||
|
||||
[TestCase, Order(13)]
|
||||
[TestCase, Order(14)]
|
||||
public void SupersededFileAddedToSparseCheckoutAndSkipWorktreeBitCleared()
|
||||
{
|
||||
string fileToSupersedeSparseCheckoutEntry = "/GVFlt_FileOperationTest/WriteAndVerify.txt";
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using GVFS.FunctionalTests.FileSystemRunners;
|
||||
using GVFS.FunctionalTests.FileSystemRunners;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.Tests.Should;
|
||||
|
@ -11,7 +10,7 @@ using System.IO;
|
|||
namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
||||
{
|
||||
[TestFixtureSource(typeof(FileSystemRunner), FileSystemRunner.TestRunners)]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class GitMoveRenameTests : TestsWithEnlistmentPerFixture
|
||||
{
|
||||
private string testFileContents = "0123456789";
|
||||
|
|
|
@ -54,30 +54,6 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
}
|
||||
|
||||
[TestCase, Order(5)]
|
||||
public void GitStatusNoLockIndexDoesntHoldLock()
|
||||
{
|
||||
ManualResetEventSlim lockHolder = GitHelpers.AcquireGVFSLock(this.Enlistment, resetTimeout: 3000);
|
||||
|
||||
ProcessResult statusWait = GitHelpers.InvokeGitAgainstGVFSRepo(this.Enlistment.RepoRoot, "status --no-lock-index", cleanErrors: false);
|
||||
statusWait.Errors.ShouldBeEmpty();
|
||||
|
||||
// Release the lock
|
||||
lockHolder.Set();
|
||||
}
|
||||
|
||||
[TestCase, Order(6)]
|
||||
public void GitStatusNoOptionalLocksDoesntHoldLock()
|
||||
{
|
||||
ManualResetEventSlim lockHolder = GitHelpers.AcquireGVFSLock(this.Enlistment, resetTimeout: 3000);
|
||||
|
||||
ProcessResult statusWait = GitHelpers.InvokeGitAgainstGVFSRepo(this.Enlistment.RepoRoot, "--no-optional-locks status", cleanErrors: false);
|
||||
statusWait.Errors.ShouldBeEmpty();
|
||||
|
||||
// Release the lock
|
||||
lockHolder.Set();
|
||||
}
|
||||
|
||||
[TestCase, Order(7)]
|
||||
public void GitAliasNamedAfterKnownCommandAcquiresLock()
|
||||
{
|
||||
string alias = nameof(this.GitAliasNamedAfterKnownCommandAcquiresLock);
|
||||
|
@ -88,7 +64,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
statusWait.Errors.ShouldContain("Waiting for 'git hash-object --stdin");
|
||||
}
|
||||
|
||||
[TestCase, Order(8)]
|
||||
[TestCase, Order(6)]
|
||||
public void GitAliasInSubfolderNamedAfterKnownCommandAcquiresLock()
|
||||
{
|
||||
string alias = nameof(this.GitAliasInSubfolderNamedAfterKnownCommandAcquiresLock);
|
||||
|
@ -103,7 +79,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
GitHelpers.CheckGitCommandAgainstGVFSRepo(this.Enlistment.RepoRoot, "rebase --abort");
|
||||
}
|
||||
|
||||
[TestCase, Order(9)]
|
||||
[TestCase, Order(7)]
|
||||
public void ExternalLockHolderReportedWhenBackgroundTasksArePending()
|
||||
{
|
||||
GitHelpers.AcquireGVFSLock(this.Enlistment, resetTimeout: 3000);
|
||||
|
|
|
@ -12,6 +12,7 @@ using System.Runtime.InteropServices;
|
|||
namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(Categories.FullSuiteOnly)]
|
||||
public class MountTests : TestsWithEnlistmentPerFixture
|
||||
{
|
||||
private const int GVFSGenericError = 3;
|
||||
|
@ -95,9 +96,14 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
this.Enlistment.UnmountGVFS();
|
||||
|
||||
// Get the current disk layout version
|
||||
string currentVersion = GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot).ShouldNotBeNull();
|
||||
int currentVersionNum;
|
||||
int.TryParse(currentVersion, out currentVersionNum).ShouldEqual(true);
|
||||
string majorVersion;
|
||||
string minorVersion;
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot, out majorVersion, out minorVersion);
|
||||
|
||||
int majorVersionNum;
|
||||
int minorVersionNum;
|
||||
int.TryParse(majorVersion.ShouldNotBeNull(), out majorVersionNum).ShouldEqual(true);
|
||||
int.TryParse(minorVersion.ShouldNotBeNull(), out minorVersionNum).ShouldEqual(true);
|
||||
|
||||
// Move the RepoMetadata database to a temp file
|
||||
string versionDatabasePath = Path.Combine(this.Enlistment.DotGVFSRoot, GVFSHelpers.RepoMetadataName);
|
||||
|
@ -125,7 +131,12 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
{
|
||||
this.Enlistment.UnmountGVFS();
|
||||
|
||||
string currentVersion = GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot).ShouldNotBeNull();
|
||||
string majorVersion;
|
||||
string minorVersion;
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot, out majorVersion, out minorVersion);
|
||||
majorVersion.ShouldNotBeNull();
|
||||
minorVersion.ShouldNotBeNull();
|
||||
|
||||
string objectsRoot = GVFSHelpers.GetPersistedGitObjectsRoot(this.Enlistment.DotGVFSRoot).ShouldNotBeNull();
|
||||
|
||||
string metadataPath = Path.Combine(this.Enlistment.DotGVFSRoot, GVFSHelpers.RepoMetadataName);
|
||||
|
@ -133,7 +144,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
this.fileSystem.MoveFile(metadataPath, metadataBackupPath);
|
||||
|
||||
this.fileSystem.CreateEmptyFile(metadataPath);
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, currentVersion);
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, majorVersion, minorVersion);
|
||||
GVFSHelpers.SaveGitObjectsRoot(this.Enlistment.DotGVFSRoot, objectsRoot);
|
||||
|
||||
this.MountShouldFail("Failed to determine local cache path from repo metadata");
|
||||
|
@ -149,7 +160,12 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
{
|
||||
this.Enlistment.UnmountGVFS();
|
||||
|
||||
string currentVersion = GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot).ShouldNotBeNull();
|
||||
string majorVersion;
|
||||
string minorVersion;
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot, out majorVersion, out minorVersion);
|
||||
majorVersion.ShouldNotBeNull();
|
||||
minorVersion.ShouldNotBeNull();
|
||||
|
||||
string localCacheRoot = GVFSHelpers.GetPersistedLocalCacheRoot(this.Enlistment.DotGVFSRoot).ShouldNotBeNull();
|
||||
|
||||
string metadataPath = Path.Combine(this.Enlistment.DotGVFSRoot, GVFSHelpers.RepoMetadataName);
|
||||
|
@ -157,7 +173,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
this.fileSystem.MoveFile(metadataPath, metadataBackupPath);
|
||||
|
||||
this.fileSystem.CreateEmptyFile(metadataPath);
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, currentVersion);
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, majorVersion, minorVersion);
|
||||
GVFSHelpers.SaveLocalCacheRoot(this.Enlistment.DotGVFSRoot, localCacheRoot);
|
||||
|
||||
this.MountShouldFail("Failed to determine git objects root from repo metadata");
|
||||
|
@ -206,14 +222,20 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
MountSubfolders.EnsureSubfoldersOnDisk(this.Enlistment, this.fileSystem);
|
||||
this.Enlistment.UnmountGVFS();
|
||||
|
||||
string currentVersion = GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot).ShouldNotBeNull();
|
||||
int currentVersionNum;
|
||||
int.TryParse(currentVersion, out currentVersionNum).ShouldEqual(true);
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, (currentVersionNum + 1).ToString());
|
||||
string majorVersion;
|
||||
string minorVersion;
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot, out majorVersion, out minorVersion);
|
||||
|
||||
int majorVersionNum;
|
||||
int minorVersionNum;
|
||||
int.TryParse(majorVersion.ShouldNotBeNull(), out majorVersionNum).ShouldEqual(true);
|
||||
int.TryParse(minorVersion.ShouldNotBeNull(), out minorVersionNum).ShouldEqual(true);
|
||||
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, (majorVersionNum + 1).ToString(), "0");
|
||||
|
||||
this.MountShouldFail("do not allow mounting after downgrade", this.Enlistment.GetVirtualPathTo(mountSubfolder));
|
||||
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, currentVersionNum.ToString());
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, majorVersionNum.ToString(), minorVersionNum.ToString());
|
||||
this.Enlistment.MountGVFS();
|
||||
}
|
||||
|
||||
|
@ -225,15 +247,20 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
|
||||
this.Enlistment.UnmountGVFS();
|
||||
|
||||
string currentVersion = GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot).ShouldNotBeNull();
|
||||
int currentVersionNum;
|
||||
int.TryParse(currentVersion, out currentVersionNum).ShouldEqual(true);
|
||||
string majorVersion;
|
||||
string minorVersion;
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot, out majorVersion, out minorVersion);
|
||||
|
||||
int majorVersionNum;
|
||||
int minorVersionNum;
|
||||
int.TryParse(majorVersion.ShouldNotBeNull(), out majorVersionNum).ShouldEqual(true);
|
||||
int.TryParse(minorVersion.ShouldNotBeNull(), out minorVersionNum).ShouldEqual(true);
|
||||
|
||||
// 1 will always be below the minumum support version number
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, "1");
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, "1", "0");
|
||||
this.MountShouldFail("Breaking change to GVFS disk layout has been made since cloning", this.Enlistment.GetVirtualPathTo(mountSubfolder));
|
||||
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, currentVersionNum.ToString());
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, majorVersionNum.ToString(), minorVersionNum.ToString());
|
||||
this.Enlistment.MountGVFS();
|
||||
}
|
||||
|
||||
|
@ -281,11 +308,10 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
|
||||
private void MountShouldFail(int expectedExitCode, string expectedErrorMessage, string mountWorkingDirectory = null)
|
||||
{
|
||||
string pathToGVFS = Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS);
|
||||
string enlistmentRoot = this.Enlistment.EnlistmentRoot;
|
||||
|
||||
// TODO: 865304 Use app.config instead of --internal* arguments
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo(pathToGVFS);
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo(GVFSTestConfig.PathToGVFS);
|
||||
processInfo.Arguments = "mount --internal_use_only_service_name " + GVFSServiceProcess.TestServiceName;
|
||||
processInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
processInfo.WorkingDirectory = string.IsNullOrEmpty(mountWorkingDirectory) ? enlistmentRoot : mountWorkingDirectory;
|
||||
|
|
|
@ -62,6 +62,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
{
|
||||
this.ExpectBlobCount(this.Enlistment.Prefetch(@"--folders GVFS\GVFS"), 17);
|
||||
this.ExpectBlobCount(this.Enlistment.Prefetch(@"--folders GVFS\GVFS;GVFS\GVFS.FunctionalTests"), 65);
|
||||
this.PackDirShouldContainMidx(this.Enlistment.GetPackRoot(this.fileSystem));
|
||||
}
|
||||
|
||||
[TestCase, Order(7)]
|
||||
|
@ -69,6 +70,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
{
|
||||
this.ExpectBlobCount(this.Enlistment.Prefetch("--files nonexistent.txt"), 0);
|
||||
this.ExpectBlobCount(this.Enlistment.Prefetch("--folders nonexistent_folder"), 0);
|
||||
this.PackDirShouldContainMidx(this.Enlistment.GetPackRoot(this.fileSystem));
|
||||
}
|
||||
|
||||
[TestCase, Order(8)]
|
||||
|
@ -87,21 +89,24 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
});
|
||||
|
||||
this.ExpectBlobCount(this.Enlistment.Prefetch("--folders-list " + tempFilePath), 279);
|
||||
this.PackDirShouldContainMidx(this.Enlistment.GetPackRoot(this.fileSystem));
|
||||
File.Delete(tempFilePath);
|
||||
}
|
||||
|
||||
[TestCase, Order(9)]
|
||||
public void PrefetchAll()
|
||||
{
|
||||
this.ExpectBlobCount(this.Enlistment.Prefetch("--files *"), 491);
|
||||
this.ExpectBlobCount(this.Enlistment.Prefetch("--folders /"), 491);
|
||||
this.ExpectBlobCount(this.Enlistment.Prefetch("--folders \\"), 491);
|
||||
this.ExpectBlobCount(this.Enlistment.Prefetch("--files *"), 494);
|
||||
this.ExpectBlobCount(this.Enlistment.Prefetch("--folders /"), 494);
|
||||
this.ExpectBlobCount(this.Enlistment.Prefetch("--folders \\"), 494);
|
||||
this.PackDirShouldContainMidx(this.Enlistment.GetPackRoot(this.fileSystem));
|
||||
}
|
||||
|
||||
[TestCase, Order(10)]
|
||||
public void PrefetchCleansUpStalePrefetchLock()
|
||||
{
|
||||
this.Enlistment.Prefetch("--commits");
|
||||
this.PackDirShouldContainMidx(this.Enlistment.GetPackRoot(this.fileSystem));
|
||||
string prefetchCommitsLockFile = Path.Combine(this.Enlistment.GetObjectRoot(this.fileSystem), "pack", PrefetchCommitsAndTreesLock);
|
||||
prefetchCommitsLockFile.ShouldNotExistOnDisk(this.fileSystem);
|
||||
this.fileSystem.WriteAllText(prefetchCommitsLockFile, this.Enlistment.EnlistmentRoot);
|
||||
|
@ -109,11 +114,21 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
|
||||
this.Enlistment.Prefetch("--commits");
|
||||
prefetchCommitsLockFile.ShouldNotExistOnDisk(this.fileSystem);
|
||||
this.PackDirShouldContainMidx(this.Enlistment.GetPackRoot(this.fileSystem));
|
||||
}
|
||||
|
||||
private void ExpectBlobCount(string output, int expectedCount)
|
||||
{
|
||||
output.ShouldContain("Matched blobs: " + expectedCount);
|
||||
}
|
||||
|
||||
private void PackDirShouldContainMidx(string packDir)
|
||||
{
|
||||
string midxHead = packDir + "/midx-head";
|
||||
this.fileSystem.FileExists(midxHead).ShouldBeTrue();
|
||||
string midxHash = this.fileSystem.ReadAllText(midxHead).Substring(0, 40);
|
||||
string midxFile = packDir + "/midx-" + midxHash + ".midx";
|
||||
this.fileSystem.FileExists(midxFile).ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.IO;
|
|||
namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(Categories.FullSuiteOnly)]
|
||||
public class PrefetchVerbWithoutSharedCacheTests : TestsWithEnlistmentPerFixture
|
||||
{
|
||||
private const string PrefetchPackPrefix = "prefetch";
|
||||
|
@ -47,6 +48,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
// Verify prefetch pack(s) are in packs folder and have matching idx file
|
||||
string[] prefetchPacks = this.ReadPrefetchPackFileNames();
|
||||
this.AllPrefetchPacksShouldHaveIdx(prefetchPacks);
|
||||
this.PackDirShouldContainMidx(this.Enlistment.GetPackRoot(this.fileSystem));
|
||||
|
||||
// Verify tempPacks is empty
|
||||
this.TempPackRoot.ShouldBeADirectory(this.fileSystem).WithNoItems();
|
||||
|
@ -73,6 +75,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
string[] newPrefetchPacks = this.ReadPrefetchPackFileNames();
|
||||
newPrefetchPacks.ShouldContain(prefetchPacks, (item, expectedValue) => { return string.Equals(item, expectedValue); });
|
||||
this.AllPrefetchPacksShouldHaveIdx(newPrefetchPacks);
|
||||
this.PackDirShouldContainMidx(this.Enlistment.GetPackRoot(this.fileSystem));
|
||||
this.TempPackRoot.ShouldBeADirectory(this.fileSystem).WithNoItems();
|
||||
}
|
||||
|
||||
|
@ -97,6 +100,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
string[] newPrefetchPacks = this.ReadPrefetchPackFileNames();
|
||||
newPrefetchPacks.ShouldContain(prefetchPacks, (item, expectedValue) => { return string.Equals(item, expectedValue); });
|
||||
this.AllPrefetchPacksShouldHaveIdx(newPrefetchPacks);
|
||||
this.PackDirShouldContainMidx(this.Enlistment.GetPackRoot(this.fileSystem));
|
||||
this.TempPackRoot.ShouldBeADirectory(this.fileSystem).WithNoItems();
|
||||
}
|
||||
|
||||
|
@ -125,6 +129,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
|
||||
string[] newPrefetchPacks = this.ReadPrefetchPackFileNames();
|
||||
this.AllPrefetchPacksShouldHaveIdx(newPrefetchPacks);
|
||||
this.PackDirShouldContainMidx(this.Enlistment.GetPackRoot(this.fileSystem));
|
||||
this.TempPackRoot.ShouldBeADirectory(this.fileSystem).WithNoItems();
|
||||
}
|
||||
|
||||
|
@ -155,6 +160,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
string[] newPrefetchPacks = this.ReadPrefetchPackFileNames();
|
||||
newPrefetchPacks.ShouldContain(prefetchPacks, (item, expectedValue) => { return string.Equals(item, expectedValue); });
|
||||
this.AllPrefetchPacksShouldHaveIdx(newPrefetchPacks);
|
||||
this.PackDirShouldContainMidx(this.Enlistment.GetPackRoot(this.fileSystem));
|
||||
this.TempPackRoot.ShouldBeADirectory(this.fileSystem).WithNoItems();
|
||||
}
|
||||
|
||||
|
@ -186,6 +192,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
string[] newPrefetchPacks = this.ReadPrefetchPackFileNames();
|
||||
newPrefetchPacks.ShouldNotContain(prefetchPacks, (item, expectedValue) => { return string.Equals(item, expectedValue); });
|
||||
this.AllPrefetchPacksShouldHaveIdx(newPrefetchPacks);
|
||||
this.PackDirShouldContainMidx(this.Enlistment.GetPackRoot(this.fileSystem));
|
||||
this.TempPackRoot.ShouldBeADirectory(this.fileSystem).WithNoItems();
|
||||
}
|
||||
|
||||
|
@ -221,6 +228,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
string[] newPrefetchPacks = this.ReadPrefetchPackFileNames();
|
||||
newPrefetchPacks.ShouldNotContain(prefetchPacks, (item, expectedValue) => { return string.Equals(item, expectedValue); });
|
||||
this.AllPrefetchPacksShouldHaveIdx(newPrefetchPacks);
|
||||
this.PackDirShouldContainMidx(this.Enlistment.GetPackRoot(this.fileSystem));
|
||||
this.TempPackRoot.ShouldBeADirectory(this.fileSystem).WithNoItems();
|
||||
}
|
||||
|
||||
|
@ -279,6 +287,15 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
}
|
||||
}
|
||||
|
||||
private void PackDirShouldContainMidx(string packDir)
|
||||
{
|
||||
string midxHead = packDir + "/midx-head";
|
||||
this.fileSystem.FileExists(midxHead).ShouldBeTrue();
|
||||
string midxHash = this.fileSystem.ReadAllText(midxHead).Substring(0, 40);
|
||||
string midxFile = packDir + "/midx-" + midxHash + ".midx";
|
||||
this.fileSystem.FileExists(midxFile).ShouldBeTrue();
|
||||
}
|
||||
|
||||
private string[] ReadPrefetchPackFileNames()
|
||||
{
|
||||
return Directory.GetFiles(this.PackRoot, $"{PrefetchPackPrefix}*.pack");
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
using GVFS.FunctionalTests.FileSystemRunners;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.Tests.Should;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.ServiceProcess;
|
||||
using System.Threading;
|
||||
|
||||
namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
||||
{
|
||||
[TestFixture]
|
||||
[NonParallelizable]
|
||||
[Category(Categories.FullSuiteOnly)]
|
||||
public class ServiceTests : TestsWithEnlistmentPerFixture
|
||||
{
|
||||
private const string NativeLibPath = @"C:\Program Files\GVFS\ProjectedFSLib.dll";
|
||||
|
||||
private FileSystemRunner fileSystem;
|
||||
|
||||
public ServiceTests()
|
||||
{
|
||||
this.fileSystem = new SystemIORunner();
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void MountAsksServiceToCopyNonInboxNativeDll()
|
||||
{
|
||||
if (IsProjFSInbox())
|
||||
{
|
||||
Assert.Ignore("Skipping test, ProjFS is inbox");
|
||||
}
|
||||
|
||||
if (!GVFSTestConfig.TestGVFSOnPath)
|
||||
{
|
||||
Assert.Ignore("Skipping test, test only enabled when --test-gvfs-on-path is set");
|
||||
}
|
||||
|
||||
NativeLibPath.ShouldBeAFile(this.fileSystem);
|
||||
this.Enlistment.UnmountGVFS();
|
||||
this.fileSystem.DeleteFile(NativeLibPath);
|
||||
NativeLibPath.ShouldNotExistOnDisk(this.fileSystem);
|
||||
this.Enlistment.MountGVFS();
|
||||
NativeLibPath.ShouldBeAFile(this.fileSystem);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void ServiceCopiesNonInboxNativeDll()
|
||||
{
|
||||
if (IsProjFSInbox())
|
||||
{
|
||||
Assert.Ignore("Skipping test, ProjFS is inbox");
|
||||
}
|
||||
|
||||
if (!GVFSTestConfig.TestGVFSOnPath)
|
||||
{
|
||||
Assert.Ignore("Skipping test, test only enabled when --test-gvfs-on-path is set");
|
||||
}
|
||||
|
||||
NativeLibPath.ShouldBeAFile(this.fileSystem);
|
||||
this.Enlistment.UnmountGVFS();
|
||||
this.fileSystem.DeleteFile(NativeLibPath);
|
||||
NativeLibPath.ShouldNotExistOnDisk(this.fileSystem);
|
||||
GVFSServiceProcess.StopService();
|
||||
GVFSServiceProcess.StartService();
|
||||
|
||||
int count = 0;
|
||||
while (!this.fileSystem.FileExists(NativeLibPath) && count < 10)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
++count;
|
||||
}
|
||||
|
||||
NativeLibPath.ShouldBeAFile(this.fileSystem);
|
||||
this.Enlistment.MountGVFS();
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void MountAsksServiceToStartPrjFltService()
|
||||
{
|
||||
if (!GVFSTestConfig.TestGVFSOnPath)
|
||||
{
|
||||
Assert.Ignore("Skipping test, test only enabled when --test-gvfs-on-path is set");
|
||||
}
|
||||
|
||||
this.Enlistment.UnmountGVFS();
|
||||
StopPrjFlt();
|
||||
this.Enlistment.MountGVFS();
|
||||
IsPrjFltRunning().ShouldBeTrue();
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void ServiceStartsPrjFltService()
|
||||
{
|
||||
if (!GVFSTestConfig.TestGVFSOnPath)
|
||||
{
|
||||
Assert.Ignore("Skipping test, test only enabled when --test-gvfs-on-path is set");
|
||||
}
|
||||
|
||||
this.Enlistment.UnmountGVFS();
|
||||
StopPrjFlt();
|
||||
GVFSServiceProcess.StopService();
|
||||
GVFSServiceProcess.StartService();
|
||||
|
||||
ServiceController controller = new ServiceController("prjflt");
|
||||
controller.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(10));
|
||||
controller.Status.ShouldEqual(ServiceControllerStatus.Running);
|
||||
|
||||
this.Enlistment.MountGVFS();
|
||||
}
|
||||
|
||||
private static bool IsPrjFltRunning()
|
||||
{
|
||||
ServiceController controller = new ServiceController("prjflt");
|
||||
return controller.Status.Equals(ServiceControllerStatus.Running);
|
||||
}
|
||||
|
||||
private static void StopPrjFlt()
|
||||
{
|
||||
IsPrjFltRunning().ShouldBeTrue();
|
||||
|
||||
ServiceController controller = new ServiceController("prjflt");
|
||||
controller.Stop();
|
||||
controller.WaitForStatus(ServiceControllerStatus.Stopped);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the build number of the OS
|
||||
/// </summary>
|
||||
/// <returns>Build number</returns>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
private static uint GetWindowsBuildNumber()
|
||||
{
|
||||
OSVersionInfo versionInfo = new OSVersionInfo();
|
||||
versionInfo.OSVersionInfoSize = (uint)Marshal.SizeOf(versionInfo);
|
||||
GetVersionEx(ref versionInfo).ShouldBeTrue();
|
||||
|
||||
// 14393 -> RS1 build number
|
||||
versionInfo.BuildNumber.ShouldBeAtLeast(14393U);
|
||||
return versionInfo.BuildNumber;
|
||||
}
|
||||
|
||||
private static bool IsProjFSInbox()
|
||||
{
|
||||
const uint MinRS4inboxVersion = 17121;
|
||||
const uint FirstRS5Version = 17600;
|
||||
const uint MinRS5inboxVersion = 17626;
|
||||
|
||||
uint buildNumber = GetWindowsBuildNumber();
|
||||
return !(buildNumber < MinRS4inboxVersion || (buildNumber >= FirstRS5Version && buildNumber < MinRS5inboxVersion));
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern bool GetVersionEx([In, Out] ref OSVersionInfo versionInfo);
|
||||
|
||||
[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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,14 +22,13 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
[OneTimeSetUp]
|
||||
public virtual void CreateEnlistment()
|
||||
{
|
||||
string pathToGvfs = Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS);
|
||||
if (this.forcePerRepoObjectCache)
|
||||
{
|
||||
this.Enlistment = GVFSFunctionalTestEnlistment.CloneAndMountWithPerRepoCache(pathToGvfs);
|
||||
this.Enlistment = GVFSFunctionalTestEnlistment.CloneAndMountWithPerRepoCache(GVFSTestConfig.PathToGVFS);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.Enlistment = GVFSFunctionalTestEnlistment.CloneAndMount(pathToGvfs);
|
||||
this.Enlistment = GVFSFunctionalTestEnlistment.CloneAndMount(GVFSTestConfig.PathToGVFS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ using System.Threading;
|
|||
namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(Categories.FullSuiteOnly)]
|
||||
public class UnmountTests : TestsWithEnlistmentPerFixture
|
||||
{
|
||||
private FileSystemRunner fileSystem;
|
||||
|
@ -22,7 +23,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
public void SetupTest()
|
||||
{
|
||||
GVFSProcess gvfsProcess = new GVFSProcess(
|
||||
Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS),
|
||||
GVFSTestConfig.PathToGVFS,
|
||||
this.Enlistment.EnlistmentRoot,
|
||||
Path.Combine(this.Enlistment.EnlistmentRoot, ".gvfs"));
|
||||
|
||||
|
@ -64,11 +65,10 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
|
||||
private Process StartUnmount(string extraParams = "")
|
||||
{
|
||||
string pathToGVFS = Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS);
|
||||
string enlistmentRoot = this.Enlistment.EnlistmentRoot;
|
||||
|
||||
// TODO: 865304 Use app.config instead of --internal* arguments
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo(pathToGVFS);
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo(GVFSTestConfig.PathToGVFS);
|
||||
processInfo.Arguments = "unmount " + extraParams + " --internal_use_only_service_name " + GVFSServiceProcess.TestServiceName;
|
||||
processInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
processInfo.WorkingDirectory = enlistmentRoot;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using GVFS.FunctionalTests.FileSystemRunners;
|
||||
using GVFS.FunctionalTests.FileSystemRunners;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.Tests.Should;
|
||||
|
@ -13,7 +12,7 @@ using System.IO;
|
|||
namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class UpdatePlaceholderTests : TestsWithEnlistmentPerFixture
|
||||
{
|
||||
private const string TestParentFolderName = "Test_EPF_UpdatePlaceholderTests";
|
||||
|
@ -292,7 +291,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
|
||||
ProcessResult checkoutResult = GitProcess.InvokeProcess(this.Enlistment.RepoRoot, "checkout " + OldCommitId);
|
||||
checkoutResult.Errors.ShouldContain(
|
||||
"HEAD is now at " + OldCommitId + @"... Add test files for update UpdatePlaceholder tests",
|
||||
"HEAD is now at " + OldCommitId,
|
||||
"GVFS was unable to delete the following files. To recover, close all handles to the files and run these commands:",
|
||||
"git clean -f " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete1Name,
|
||||
"git clean -f " + TestParentFolderName + "/LockToPreventUpdateAndDelete/" + testFileDelete2Name,
|
||||
|
@ -385,7 +384,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerFixture
|
|||
|
||||
ProcessResult checkoutResult = GitProcess.InvokeProcess(this.Enlistment.RepoRoot, "checkout " + OldCommitId);
|
||||
checkoutResult.Errors.ShouldContain(
|
||||
"HEAD is now at " + OldCommitId + @"... Add test files for update UpdatePlaceholder tests",
|
||||
"HEAD is now at " + OldCommitId,
|
||||
"GVFS was unable to update the following files. To recover, close all handles to the files and run these commands:",
|
||||
"git checkout -- Test_EPF_UpdatePlaceholderTests/LockToPreventUpdateAndDelete/test4.txt");
|
||||
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using GVFS.FunctionalTests.FileSystemRunners;
|
||||
using GVFS.FunctionalTests.FileSystemRunners;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.Tests.Should;
|
||||
|
@ -394,7 +393,7 @@ BOOL APIENTRY DllMain( HMODULE hModule,
|
|||
}
|
||||
|
||||
[TestCase, Order(13)]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public void FolderContentsProjectedAfterFolderCreateAndCheckout()
|
||||
{
|
||||
string folderName = "GVFlt_MultiThreadTest";
|
||||
|
@ -420,7 +419,7 @@ BOOL APIENTRY DllMain( HMODULE hModule,
|
|||
}
|
||||
|
||||
[TestCase, Order(14)]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public void FolderContentsCorrectAfterCreateNewFolderRenameAndCheckoutCommitWithSameFolder()
|
||||
{
|
||||
// 1ca414ced40f64bf94fc6c7f885974708bc600be is the commit prior to adding Test_EPF_MoveRenameFileTests
|
||||
|
|
|
@ -3,15 +3,26 @@ using GVFS.FunctionalTests.Should;
|
|||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.Tests.Should;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(Categories.FullSuiteOnly)]
|
||||
public class DiskLayoutUpgradeTests : TestsWithEnlistmentPerTestCase
|
||||
{
|
||||
private const int CurrentDiskLayoutVersion = 12;
|
||||
private FileSystemRunner fileSystem = new SystemIORunner();
|
||||
public const int CurrentDiskLayoutMajorVersion = 14;
|
||||
public const int CurrentDiskLayoutMinorVersion = 0;
|
||||
|
||||
public const string BlobSizesCacheName = "blobSizes";
|
||||
public const string BlobSizesDBFileName = "BlobSizes.sql";
|
||||
|
||||
private const string DatabasesFolderName = "databases";
|
||||
|
||||
private FileSystemRunner fileSystem = new SystemIORunner();
|
||||
|
||||
[TestCase]
|
||||
public void MountUpgradesFromVersion7()
|
||||
|
@ -36,7 +47,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
|||
string flatBackgroundPath = Path.Combine(this.Enlistment.DotGVFSRoot, GVFSHelpers.BackgroundOpsFile);
|
||||
flatBackgroundPath.ShouldBeAFile(this.fileSystem);
|
||||
this.fileSystem.DeleteFile(flatBackgroundPath);
|
||||
|
||||
|
||||
// Delete the existing placeholder data
|
||||
string flatPlaceholdersPath = Path.Combine(this.Enlistment.DotGVFSRoot, GVFSHelpers.PlaceholderListFile);
|
||||
flatPlaceholdersPath.ShouldBeAFile(this.fileSystem);
|
||||
|
@ -46,12 +57,10 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
|||
GVFSHelpers.CreateEsentPlaceholderDatabase(this.Enlistment.DotGVFSRoot);
|
||||
|
||||
// Nine is the last version with ESENT BackgroundOps and Placeholders DBs
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, "9");
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, "9", "0");
|
||||
this.Enlistment.MountGVFS();
|
||||
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot)
|
||||
.ShouldBeAnInt("Disk layout version should always be an int")
|
||||
.ShouldEqual(CurrentDiskLayoutVersion);
|
||||
this.ValidatePersistedVersionMatchesCurrentVersion();
|
||||
|
||||
flatBackgroundPath.ShouldBeAFile(this.fileSystem);
|
||||
flatPlaceholdersPath.ShouldBeAFile(this.fileSystem);
|
||||
|
@ -62,13 +71,11 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
|||
{
|
||||
this.Enlistment.UnmountGVFS();
|
||||
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, "10");
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, "10", "0");
|
||||
|
||||
this.Enlistment.MountGVFS();
|
||||
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot)
|
||||
.ShouldBeAnInt("Disk layout version should always be an int")
|
||||
.ShouldEqual(CurrentDiskLayoutVersion);
|
||||
this.ValidatePersistedVersionMatchesCurrentVersion();
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
|
@ -86,7 +93,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
|||
esentDatabasePath.ShouldBeADirectory(this.fileSystem);
|
||||
|
||||
this.Enlistment.TryMountGVFS().ShouldEqual(false, "Should not be able to upgrade from version 6");
|
||||
|
||||
|
||||
esentDatabasePath.ShouldBeADirectory(this.fileSystem);
|
||||
}
|
||||
|
||||
|
@ -103,7 +110,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
|||
// "11" was the last version before the introduction of a volume wide GVFS cache
|
||||
string metadataPath = Path.Combine(this.Enlistment.DotGVFSRoot, GVFSHelpers.RepoMetadataName);
|
||||
this.fileSystem.CreateEmptyFile(metadataPath);
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, "11");
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, "11", "0");
|
||||
|
||||
// Create the legacy cache location: <root>\.gvfs\gitObjectCache
|
||||
string legacyGitObjectsCachePath = Path.Combine(this.Enlistment.DotGVFSRoot, "gitObjectCache");
|
||||
|
@ -111,9 +118,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
|||
|
||||
this.Enlistment.MountGVFS();
|
||||
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot)
|
||||
.ShouldBeAnInt("Disk layout version should always be an int")
|
||||
.ShouldEqual(CurrentDiskLayoutVersion, "Disk layout version should be upgraded to the latest");
|
||||
this.ValidatePersistedVersionMatchesCurrentVersion();
|
||||
|
||||
GVFSHelpers.GetPersistedLocalCacheRoot(this.Enlistment.DotGVFSRoot)
|
||||
.ShouldEqual(string.Empty, "LocalCacheRoot should be an empty string when upgrading from a version prior to 12");
|
||||
|
@ -122,6 +127,168 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
|||
.ShouldEqual(legacyGitObjectsCachePath);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void MountSucceedsIfMinorVersionHasAdvancedButNotMajorVersion()
|
||||
{
|
||||
// Advance the minor version, mount should still work
|
||||
this.Enlistment.UnmountGVFS();
|
||||
GVFSHelpers.SaveDiskLayoutVersion(
|
||||
this.Enlistment.DotGVFSRoot,
|
||||
CurrentDiskLayoutMajorVersion.ToString(),
|
||||
(CurrentDiskLayoutMinorVersion + 1).ToString());
|
||||
this.Enlistment.TryMountGVFS().ShouldBeTrue("Mount should succeed because only the minor version advanced");
|
||||
|
||||
// Advance the major version, mount should fail
|
||||
this.Enlistment.UnmountGVFS();
|
||||
GVFSHelpers.SaveDiskLayoutVersion(
|
||||
this.Enlistment.DotGVFSRoot,
|
||||
(CurrentDiskLayoutMajorVersion + 1).ToString(),
|
||||
CurrentDiskLayoutMinorVersion.ToString());
|
||||
this.Enlistment.TryMountGVFS().ShouldBeFalse("Mount should fail because the major version has advanced");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void MountWritesFolderPlaceholdersToPlaceholderDatabase()
|
||||
{
|
||||
// Create some placeholder data
|
||||
this.fileSystem.ReadAllText(Path.Combine(this.Enlistment.RepoRoot, "Readme.md"));
|
||||
this.fileSystem.ReadAllText(Path.Combine(this.Enlistment.RepoRoot, "Scripts\\RunUnitTests.bat"));
|
||||
this.fileSystem.ReadAllText(Path.Combine(this.Enlistment.RepoRoot, "GVFS\\GVFS.Common\\Git\\GitRefs.cs"));
|
||||
|
||||
// Create a full folder
|
||||
this.fileSystem.CreateDirectory(Path.Combine(this.Enlistment.RepoRoot, "GVFS\\FullFolder"));
|
||||
this.fileSystem.WriteAllText(Path.Combine(this.Enlistment.RepoRoot, "GVFS\\FullFolder\\test.txt"), "Test contents");
|
||||
|
||||
// Create a tombstone
|
||||
this.fileSystem.DeleteDirectory(Path.Combine(this.Enlistment.RepoRoot, "GVFS\\GVFS.Tests\\Properties"));
|
||||
|
||||
string junctionTarget = Path.Combine(this.Enlistment.EnlistmentRoot, "DirJunction");
|
||||
string symlinkTarget = Path.Combine(this.Enlistment.EnlistmentRoot, "DirSymlink");
|
||||
Directory.CreateDirectory(junctionTarget);
|
||||
Directory.CreateDirectory(symlinkTarget);
|
||||
|
||||
string junctionLink = Path.Combine(this.Enlistment.RepoRoot, "DirJunction");
|
||||
string symlink = Path.Combine(this.Enlistment.RepoRoot, "DirLink");
|
||||
ProcessHelper.Run("CMD.exe", "/C mklink /J " + junctionLink + " " + junctionTarget);
|
||||
ProcessHelper.Run("CMD.exe", "/C mklink /D " + symlink + " " + symlinkTarget);
|
||||
|
||||
this.Enlistment.UnmountGVFS();
|
||||
|
||||
// Delete the existing folder placeholder data
|
||||
string placeholderDatabasePath = Path.Combine(this.Enlistment.DotGVFSRoot, GVFSHelpers.PlaceholderListFile);
|
||||
string[] lines = this.GetPlaceholderDatabaseLinesBeforeUpgrade(placeholderDatabasePath);
|
||||
|
||||
// Placeholder database file should only have file placeholders
|
||||
this.fileSystem.WriteAllText(placeholderDatabasePath, string.Join(Environment.NewLine, lines.Where(x => !x.EndsWith(TestConstants.AllZeroSha))) + Environment.NewLine);
|
||||
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, "12", "1");
|
||||
|
||||
this.Enlistment.MountGVFS();
|
||||
this.Enlistment.UnmountGVFS();
|
||||
|
||||
// Validate the folder placeholders are in the placeholder database now
|
||||
this.GetPlaceholderDatabaseLinesAfterUpgrade(placeholderDatabasePath);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void MountUpgradesPreSharedCacheLocalSizes()
|
||||
{
|
||||
this.Enlistment.UnmountGVFS();
|
||||
|
||||
// Delete the existing repo metadata
|
||||
string versionJsonPath = Path.Combine(this.Enlistment.DotGVFSRoot, GVFSHelpers.RepoMetadataName);
|
||||
versionJsonPath.ShouldBeAFile(this.fileSystem);
|
||||
this.fileSystem.DeleteFile(versionJsonPath);
|
||||
|
||||
// "11" was the last version before the introduction of a volume wide GVFS cache
|
||||
string metadataPath = Path.Combine(this.Enlistment.DotGVFSRoot, GVFSHelpers.RepoMetadataName);
|
||||
this.fileSystem.CreateEmptyFile(metadataPath);
|
||||
GVFSHelpers.SaveDiskLayoutVersion(this.Enlistment.DotGVFSRoot, "11", "0");
|
||||
|
||||
// Create the legacy cache location: <root>\.gvfs\gitObjectCache
|
||||
string legacyGitObjectsCachePath = Path.Combine(this.Enlistment.DotGVFSRoot, "gitObjectCache");
|
||||
this.fileSystem.CreateDirectory(legacyGitObjectsCachePath);
|
||||
|
||||
// Create a legacy PersistedDictionary sizes database
|
||||
List<KeyValuePair<string, long>> entries = new List<KeyValuePair<string, long>>()
|
||||
{
|
||||
new KeyValuePair<string, long>(new string('0', 40), 1),
|
||||
new KeyValuePair<string, long>(new string('1', 40), 2),
|
||||
new KeyValuePair<string, long>(new string('2', 40), 4),
|
||||
new KeyValuePair<string, long>(new string('3', 40), 8),
|
||||
};
|
||||
|
||||
GVFSHelpers.CreateEsentBlobSizesDatabase(this.Enlistment.DotGVFSRoot, entries);
|
||||
|
||||
this.Enlistment.MountGVFS();
|
||||
|
||||
string majorVersion;
|
||||
string minorVersion;
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot, out majorVersion, out minorVersion);
|
||||
|
||||
majorVersion
|
||||
.ShouldBeAnInt("Disk layout version should always be an int")
|
||||
.ShouldEqual(CurrentDiskLayoutMajorVersion, "Disk layout version should be upgraded to the latest");
|
||||
|
||||
minorVersion
|
||||
.ShouldBeAnInt("Disk layout version should always be an int")
|
||||
.ShouldEqual(CurrentDiskLayoutMinorVersion, "Disk layout version should be upgraded to the latest");
|
||||
|
||||
GVFSHelpers.GetPersistedLocalCacheRoot(this.Enlistment.DotGVFSRoot)
|
||||
.ShouldEqual(string.Empty, "LocalCacheRoot should be an empty string when upgrading from a version prior to 12");
|
||||
|
||||
GVFSHelpers.GetPersistedGitObjectsRoot(this.Enlistment.DotGVFSRoot)
|
||||
.ShouldEqual(legacyGitObjectsCachePath);
|
||||
|
||||
string newBlobSizesRoot = Path.Combine(this.Enlistment.DotGVFSRoot, DatabasesFolderName, BlobSizesCacheName);
|
||||
GVFSHelpers.GetPersistedBlobSizesRoot(this.Enlistment.DotGVFSRoot)
|
||||
.ShouldEqual(newBlobSizesRoot);
|
||||
|
||||
string blobSizesDbPath = Path.Combine(newBlobSizesRoot, BlobSizesDBFileName);
|
||||
newBlobSizesRoot.ShouldBeADirectory(this.fileSystem);
|
||||
blobSizesDbPath.ShouldBeAFile(this.fileSystem);
|
||||
|
||||
foreach (KeyValuePair<string, long> entry in entries)
|
||||
{
|
||||
GVFSHelpers.SQLiteBlobSizesDatabaseHasEntry(blobSizesDbPath, entry.Key, entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
private string[] GetPlaceholderDatabaseLinesBeforeUpgrade(string placeholderDatabasePath)
|
||||
{
|
||||
placeholderDatabasePath.ShouldBeAFile(this.fileSystem);
|
||||
string[] lines = this.fileSystem.ReadAllText(placeholderDatabasePath).Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
lines.Length.ShouldEqual(11);
|
||||
lines.ShouldContain(x => x.Contains("Readme.md"));
|
||||
lines.ShouldContain(x => x.Contains("Scripts\\RunUnitTests.bat"));
|
||||
lines.ShouldContain(x => x.Contains("GVFS\\GVFS.Common\\Git\\GitRefs.cs"));
|
||||
lines.ShouldContain(x => x.Contains("A GVFS\\GVFS.Tests\\Properties\\AssemblyInfo.cs"));
|
||||
lines.ShouldContain(x => x == "D GVFS\\GVFS.Tests\\Properties\\AssemblyInfo.cs");
|
||||
lines.ShouldContain(x => x == "A Scripts\0" + TestConstants.AllZeroSha);
|
||||
lines.ShouldContain(x => x == "A GVFS\0" + TestConstants.AllZeroSha);
|
||||
lines.ShouldContain(x => x == "A GVFS\\GVFS.Common\0" + TestConstants.AllZeroSha);
|
||||
lines.ShouldContain(x => x == "A GVFS\\GVFS.Common\\Git\0" + TestConstants.AllZeroSha);
|
||||
lines.ShouldContain(x => x == "A GVFS\\GVFS.Tests\0" + TestConstants.AllZeroSha);
|
||||
lines.ShouldContain(x => x == "A GVFS\\GVFS.Tests\\Properties\0" + TestConstants.AllZeroSha);
|
||||
return lines;
|
||||
}
|
||||
|
||||
private string[] GetPlaceholderDatabaseLinesAfterUpgrade(string placeholderDatabasePath)
|
||||
{
|
||||
placeholderDatabasePath.ShouldBeAFile(this.fileSystem);
|
||||
string[] lines = this.fileSystem.ReadAllText(placeholderDatabasePath).Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
lines.Length.ShouldEqual(8);
|
||||
lines.ShouldContain(x => x.Contains("Readme.md"));
|
||||
lines.ShouldContain(x => x.Contains("Scripts\\RunUnitTests.bat"));
|
||||
lines.ShouldContain(x => x.Contains("GVFS\\GVFS.Common\\Git\\GitRefs.cs"));
|
||||
lines.ShouldContain(x => x == "A Scripts\0" + TestConstants.AllZeroSha);
|
||||
lines.ShouldContain(x => x == "A GVFS\0" + TestConstants.AllZeroSha);
|
||||
lines.ShouldContain(x => x == "A GVFS\\GVFS.Common\0" + TestConstants.AllZeroSha);
|
||||
lines.ShouldContain(x => x == "A GVFS\\GVFS.Common\\Git\0" + TestConstants.AllZeroSha);
|
||||
lines.ShouldContain(x => x == "A GVFS\\GVFS.Tests\0" + TestConstants.AllZeroSha);
|
||||
return lines;
|
||||
}
|
||||
|
||||
private void RunEsentRepoMetadataUpgradeTest(string sourceVersion)
|
||||
{
|
||||
this.Enlistment.UnmountGVFS();
|
||||
|
@ -140,9 +307,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
|||
esentDatabasePath.ShouldNotExistOnDisk(this.fileSystem);
|
||||
versionJsonPath.ShouldBeAFile(this.fileSystem);
|
||||
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot)
|
||||
.ShouldBeAnInt("Disk layout version should always be an int")
|
||||
.ShouldEqual(CurrentDiskLayoutVersion, "Disk layout version should be upgraded to the latest");
|
||||
this.ValidatePersistedVersionMatchesCurrentVersion();
|
||||
|
||||
GVFSHelpers.GetPersistedLocalCacheRoot(this.Enlistment.DotGVFSRoot)
|
||||
.ShouldEqual(string.Empty, "LocalCacheRoot should be an empty string when upgrading from a version prior to 12");
|
||||
|
@ -155,5 +320,20 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
|||
.ShouldNotBeNull("GitObjectsRoot should not be null")
|
||||
.ShouldEqual(Path.Combine(this.Enlistment.RepoRoot, @".git\objects"));
|
||||
}
|
||||
|
||||
private void ValidatePersistedVersionMatchesCurrentVersion()
|
||||
{
|
||||
string majorVersion;
|
||||
string minorVersion;
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(this.Enlistment.DotGVFSRoot, out majorVersion, out minorVersion);
|
||||
|
||||
majorVersion
|
||||
.ShouldBeAnInt("Disk layout version should always be an int")
|
||||
.ShouldEqual(CurrentDiskLayoutMajorVersion, "Disk layout version should be upgraded to the latest");
|
||||
|
||||
minorVersion
|
||||
.ShouldBeAnInt("Disk layout version should always be an int")
|
||||
.ShouldEqual(CurrentDiskLayoutMinorVersion, "Disk layout version should be upgraded to the latest");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,7 +35,9 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
|||
"!/GVFS/GVFS.Mount/MountVerb2.cs",
|
||||
"!/Scripts/",
|
||||
"!/TestFileFromDotGit.txt",
|
||||
"!/PersistedSparseExcludeTests_outsideRepo.txt"
|
||||
"!/PersistedSparseExcludeTests_outsideRepo.txt",
|
||||
"!/PersistedSparseExcludeTests_NewFolder",
|
||||
"!/PersistedSparseExcludeTests_NewFolderForRename",
|
||||
};
|
||||
private static string[] expectedSparseFileContents = new string[]
|
||||
{
|
||||
|
|
|
@ -8,6 +8,7 @@ using System.IO;
|
|||
namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(Categories.FullSuiteOnly)]
|
||||
public class PersistedWorkingDirectoryTests : TestsWithEnlistmentPerTestCase
|
||||
{
|
||||
public void MountMatchesRemount()
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||
namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(Categories.FullSuiteOnly)]
|
||||
public class RepairTests : TestsWithEnlistmentPerTestCase
|
||||
{
|
||||
[TestCase]
|
||||
|
@ -39,22 +40,6 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
|||
this.Enlistment.MountGVFS();
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void FixesCorruptBlobSizesDatabase()
|
||||
{
|
||||
this.Enlistment.UnmountGVFS();
|
||||
|
||||
// Most other files in an ESENT folder can be corrupted without blocking GVFS mount. ESENT just recreates them.
|
||||
string blobSizesDbPath = Path.Combine(this.Enlistment.DotGVFSRoot, "BlobSizes", "PersistentDictionary.edb");
|
||||
File.WriteAllText(blobSizesDbPath, "0000");
|
||||
|
||||
this.Enlistment.TryMountGVFS().ShouldEqual(false, "GVFS shouldn't mount when blob size db is corrupt");
|
||||
|
||||
this.Enlistment.Repair();
|
||||
|
||||
this.Enlistment.MountGVFS();
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void FixesMissingGitIndex()
|
||||
{
|
||||
|
|
|
@ -15,8 +15,7 @@ namespace GVFS.FunctionalTests.Tests.EnlistmentPerTestCase
|
|||
[SetUp]
|
||||
public virtual void CreateEnlistment()
|
||||
{
|
||||
string pathToGvfs = Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS);
|
||||
this.Enlistment = GVFSFunctionalTestEnlistment.CloneAndMount(pathToGvfs);
|
||||
this.Enlistment = GVFSFunctionalTestEnlistment.CloneAndMount(GVFSTestConfig.PathToGVFS);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using GVFS.FunctionalTests.FileSystemRunners;
|
||||
using GVFS.FunctionalTests.FileSystemRunners;
|
||||
using GVFS.FunctionalTests.Properties;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
|
@ -15,7 +14,8 @@ using System.Text.RegularExpressions;
|
|||
namespace GVFS.FunctionalTests.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.FastFetch)]
|
||||
[Category(Categories.FastFetch)]
|
||||
[Category(Categories.FullSuiteOnly)]
|
||||
public class FastFetchTests
|
||||
{
|
||||
private readonly string fastFetchRepoRoot = Settings.Default.FastFetchRoot;
|
||||
|
@ -74,7 +74,7 @@ namespace GVFS.FunctionalTests.Tests
|
|||
this.CurrentBranchShouldEqual(Settings.Default.Commitish);
|
||||
|
||||
this.fastFetchRepoRoot.ShouldBeADirectory(FileSystemRunner.DefaultRunner)
|
||||
.WithDeepStructure(FileSystemRunner.DefaultRunner, this.fastFetchControlRoot, skipEmptyDirectories: false);
|
||||
.WithDeepStructure(FileSystemRunner.DefaultRunner, this.fastFetchControlRoot);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
|
@ -108,7 +108,7 @@ namespace GVFS.FunctionalTests.Tests
|
|||
this.CurrentBranchShouldEqual(Settings.Default.Commitish);
|
||||
|
||||
this.fastFetchRepoRoot.ShouldBeADirectory(FileSystemRunner.DefaultRunner)
|
||||
.WithDeepStructure(FileSystemRunner.DefaultRunner, this.fastFetchControlRoot, skipEmptyDirectories: false);
|
||||
.WithDeepStructure(FileSystemRunner.DefaultRunner, this.fastFetchControlRoot);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
|
@ -135,7 +135,7 @@ namespace GVFS.FunctionalTests.Tests
|
|||
this.CurrentBranchShouldEqual(Settings.Default.Commitish);
|
||||
|
||||
this.fastFetchRepoRoot.ShouldBeADirectory(FileSystemRunner.DefaultRunner)
|
||||
.WithDeepStructure(FileSystemRunner.DefaultRunner, this.fastFetchControlRoot, skipEmptyDirectories: false);
|
||||
.WithDeepStructure(FileSystemRunner.DefaultRunner, this.fastFetchControlRoot);
|
||||
}
|
||||
|
||||
public void CanUpdateIndex(int indexVersion, bool indexSigningOff)
|
||||
|
@ -214,7 +214,7 @@ namespace GVFS.FunctionalTests.Tests
|
|||
this.CurrentBranchShouldEqual(Settings.Default.Commitish);
|
||||
|
||||
this.fastFetchRepoRoot.ShouldBeADirectory(FileSystemRunner.DefaultRunner)
|
||||
.WithDeepStructure(FileSystemRunner.DefaultRunner, this.fastFetchControlRoot, skipEmptyDirectories: false);
|
||||
.WithDeepStructure(FileSystemRunner.DefaultRunner, this.fastFetchControlRoot);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
|
@ -228,7 +228,7 @@ namespace GVFS.FunctionalTests.Tests
|
|||
|
||||
this.CurrentBranchShouldEqual(Settings.Default.Commitish);
|
||||
this.fastFetchRepoRoot.ShouldBeADirectory(FileSystemRunner.DefaultRunner)
|
||||
.WithDeepStructure(FileSystemRunner.DefaultRunner, this.fastFetchControlRoot, skipEmptyDirectories: false);
|
||||
.WithDeepStructure(FileSystemRunner.DefaultRunner, this.fastFetchControlRoot);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
|
@ -243,7 +243,7 @@ namespace GVFS.FunctionalTests.Tests
|
|||
try
|
||||
{
|
||||
this.fastFetchRepoRoot.ShouldBeADirectory(FileSystemRunner.DefaultRunner)
|
||||
.WithDeepStructure(FileSystemRunner.DefaultRunner, this.fastFetchControlRoot, skipEmptyDirectories: false, ignoreCase: true);
|
||||
.WithDeepStructure(FileSystemRunner.DefaultRunner, this.fastFetchControlRoot, ignoreCase: true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -400,7 +400,7 @@ namespace GVFS.FunctionalTests.Tests
|
|||
{
|
||||
args = args + " --verbose";
|
||||
|
||||
string fastfetch = Path.Combine(TestContext.CurrentContext.TestDirectory, "fastfetch.exe");
|
||||
string fastfetch = GVFSTestConfig.TestGVFSOnPath ? "fastfetch.exe" : Path.Combine(TestContext.CurrentContext.TestDirectory, "fastfetch.exe");
|
||||
if (!File.Exists(fastfetch))
|
||||
{
|
||||
fastfetch = "fastfetch.exe";
|
||||
|
|
|
@ -8,11 +8,8 @@ namespace GVFS.FunctionalTests.Tests
|
|||
[TestFixture]
|
||||
public class GVFSVerbTests
|
||||
{
|
||||
private string pathToGVFS;
|
||||
|
||||
public GVFSVerbTests()
|
||||
{
|
||||
this.pathToGVFS = Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS);
|
||||
}
|
||||
|
||||
private enum ExpectedReturnCode
|
||||
|
@ -37,7 +34,7 @@ namespace GVFS.FunctionalTests.Tests
|
|||
|
||||
private void CallGVFS(string args, ExpectedReturnCode expectedErrorCode)
|
||||
{
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo(this.pathToGVFS);
|
||||
ProcessStartInfo processInfo = new ProcessStartInfo(GVFSTestConfig.PathToGVFS);
|
||||
processInfo.Arguments = args;
|
||||
processInfo.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
processInfo.UseShellExecute = false;
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using NUnit.Framework;
|
||||
using System.Threading;
|
||||
|
||||
namespace GVFS.FunctionalTests.Tests.GitCommands
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class AddStageTests : GitRepoTests
|
||||
{
|
||||
public AddStageTests() : base(enlistmentPerTest: false)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.Tests.Should;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
|
@ -13,7 +12,7 @@ using System.Threading.Tasks;
|
|||
namespace GVFS.FunctionalTests.Tests.GitCommands
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class CheckoutTests : GitRepoTests
|
||||
{
|
||||
public CheckoutTests() : base(enlistmentPerTest: true)
|
||||
|
@ -226,11 +225,11 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
|
||||
this.ValidateGitCommand("checkout " + GitRepoTests.ConflictTargetBranch);
|
||||
this.Enlistment.RepoRoot.ShouldBeADirectory(this.FileSystem)
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, skipEmptyDirectories: true, compareContent: true);
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, compareContent: true);
|
||||
|
||||
this.ValidateGitCommand("checkout " + GitRepoTests.ConflictSourceBranch);
|
||||
this.Enlistment.RepoRoot.ShouldBeADirectory(this.FileSystem)
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, skipEmptyDirectories: true, compareContent: true);
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, compareContent: true);
|
||||
|
||||
// Verify sparse-checkout contents
|
||||
string sparseCheckoutFile = Path.Combine(this.Enlistment.RepoRoot, TestConstants.DotGit.Info.SparseCheckout);
|
||||
|
@ -238,7 +237,7 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
}
|
||||
|
||||
[TestCase]
|
||||
public void DeleteEmptyFolderPlaceholderAndCheckoutBranchThatHasFolder()
|
||||
public void CheckoutBranchThatHasFolderShouldGetDeleted()
|
||||
{
|
||||
// this.ControlGitRepo.Commitish should not have the folder Test_ConflictTests\AddedFiles
|
||||
string testFolder = @"Test_ConflictTests\AddedFiles";
|
||||
|
@ -253,11 +252,9 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
this.ValidateGitCommand("checkout " + this.ControlGitRepo.Commitish);
|
||||
this.ShouldNotExistOnDisk(testFile);
|
||||
|
||||
// Test_ConflictTests\AddedFiles will only be on disk in the GVFS enlistment, delete it there
|
||||
string virtualFolder = Path.Combine(this.Enlistment.RepoRoot, testFolder);
|
||||
string controlFolder = Path.Combine(this.ControlGitRepo.RootPath, testFolder);
|
||||
controlFolder.ShouldNotExistOnDisk(this.FileSystem);
|
||||
this.FileSystem.DeleteDirectory(virtualFolder);
|
||||
virtualFolder.ShouldNotExistOnDisk(this.FileSystem);
|
||||
|
||||
// Move back to GitRepoTests.ConflictSourceBranch where testFolder and testFile are present
|
||||
|
@ -266,7 +263,7 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
}
|
||||
|
||||
[TestCase]
|
||||
public void DeleteEmptyFolderPlaceholderAndCheckoutBranchThatDoesNotHaveFolder()
|
||||
public void CheckoutBranchThatDoesNotHaveFolderShouldNotHaveFolder()
|
||||
{
|
||||
// this.ControlGitRepo.Commitish should not have the folder Test_ConflictTests\AddedFiles
|
||||
string testFolder = @"Test_ConflictTests\AddedFiles";
|
||||
|
@ -282,11 +279,9 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
|
||||
this.ValidateGitCommand("checkout -b tests/functional/DeleteEmptyFolderPlaceholderAndCheckoutBranchThatDoesNotHaveFolder" + this.ControlGitRepo.Commitish);
|
||||
|
||||
// Test_ConflictTests\AddedFiles will only be on disk in the GVFS enlistment, delete it there
|
||||
string virtualFolder = Path.Combine(this.Enlistment.RepoRoot, testFolder);
|
||||
string controlFolder = Path.Combine(this.ControlGitRepo.RootPath, testFolder);
|
||||
controlFolder.ShouldNotExistOnDisk(this.FileSystem);
|
||||
this.FileSystem.DeleteDirectory(virtualFolder);
|
||||
virtualFolder.ShouldNotExistOnDisk(this.FileSystem);
|
||||
|
||||
this.ValidateGitCommand("checkout " + this.ControlGitRepo.Commitish);
|
||||
|
@ -692,6 +687,187 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
this.ValidateGitCommand("checkout " + this.ControlGitRepo.Commitish);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void DeleteFolderAndChangeBranchToFolderWithDifferentCase()
|
||||
{
|
||||
// 692765 - Recursive sparse-checkout entries for folders should be case insensitive when
|
||||
// changing branches
|
||||
|
||||
string folderName = "GVFlt_MultiThreadTest";
|
||||
|
||||
// Confirm that no other test has caused "GVFlt_MultiThreadTest" to be added to the sparse-checkout
|
||||
string sparseFile = Path.Combine(this.Enlistment.RepoRoot, TestConstants.DotGit.Info.SparseCheckout);
|
||||
sparseFile.ShouldBeAFile(this.FileSystem).WithContents().ShouldNotContain(ignoreCase: true, unexpectedSubstrings: folderName);
|
||||
|
||||
this.FolderShouldHaveCaseMatchingName(folderName, "GVFlt_MultiThreadTest");
|
||||
this.DeleteFolder(folderName);
|
||||
|
||||
// b5fd7d23706a18cff3e2b8225588d479f7e51138 is the commit prior to deleting GVFLT_MultiThreadTest
|
||||
// and re-adding it as as GVFlt_MultiThreadTest
|
||||
this.ValidateGitCommand("checkout b5fd7d23706a18cff3e2b8225588d479f7e51138");
|
||||
this.FolderShouldHaveCaseMatchingName(folderName, "GVFLT_MultiThreadTest");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void SuccessfullyChecksOutDirectoryToFileToDirectory()
|
||||
{
|
||||
// This test switches between two branches and verifies specific transitions occured
|
||||
this.ControlGitRepo.Fetch("FunctionalTests/20171103_DirectoryFileTransitionsPart1");
|
||||
this.ControlGitRepo.Fetch("FunctionalTests/20171103_DirectoryFileTransitionsPart2");
|
||||
this.ValidateGitCommand("checkout FunctionalTests/20171103_DirectoryFileTransitionsPart1");
|
||||
|
||||
// Delta of interest - Check initial state
|
||||
// renamed: foo.cpp\foo.cpp -> foo.cpp
|
||||
// where the top level "foo.cpp" is a folder with a file, then becomes just a file
|
||||
// note that folder\file names picked illustrate a real example
|
||||
this.FolderShouldExistAndHaveFile("foo.cpp", "foo.cpp");
|
||||
|
||||
// Delta of interest - Check initial state
|
||||
// renamed: a\a <-> b && b <-> a
|
||||
// where a\a contains "file contents one"
|
||||
// and b contains "file contents two"
|
||||
// This tests two types of renames crossing into each other
|
||||
this.FileShouldHaveContents("a\\a", "file contents one");
|
||||
this.FileShouldHaveContents("b", "file contents two");
|
||||
|
||||
// Delta of interest - Check initial state
|
||||
// renamed: c\c <-> d\c && d\d <-> c\d
|
||||
// where c\c contains "file contents c"
|
||||
// and d\d contains "file contents d"
|
||||
// This tests two types of renames crossing into each other
|
||||
this.FileShouldHaveContents("c\\c", "file contents c");
|
||||
this.FileShouldHaveContents("d\\d", "file contents d");
|
||||
|
||||
// Now switch to second branch, part2 and verify transitions
|
||||
this.ValidateGitCommand("checkout FunctionalTests/20171103_DirectoryFileTransitionsPart2");
|
||||
|
||||
// Delta of interest - Verify change
|
||||
// renamed: foo.cpp\foo.cpp -> foo.cpp
|
||||
this.FolderShouldExistAndHaveFile(string.Empty, "foo.cpp");
|
||||
|
||||
// Delta of interest - Verify change
|
||||
// renamed: a\a <-> b && b <-> a
|
||||
this.FileShouldHaveContents("a", "file contents two");
|
||||
this.FileShouldHaveContents("b", "file contents one");
|
||||
|
||||
// Delta of interest - Verify change
|
||||
// renamed: c\c <-> d\c && d\d <-> c\d
|
||||
this.FileShouldHaveContents("c\\d", "file contents d");
|
||||
this.FileShouldHaveContents("d\\c", "file contents c");
|
||||
this.ShouldNotExistOnDisk("c\\c");
|
||||
this.ShouldNotExistOnDisk("d\\d");
|
||||
|
||||
// And back again
|
||||
this.ValidateGitCommand("checkout FunctionalTests/20171103_DirectoryFileTransitionsPart1");
|
||||
|
||||
// Delta of interest - Final validation
|
||||
// renamed: foo.cpp\foo.cpp -> foo.cpp
|
||||
this.FolderShouldExistAndHaveFile("foo.cpp", "foo.cpp");
|
||||
|
||||
// Delta of interest - Final validation
|
||||
// renamed: a\a <-> b && b <-> a
|
||||
this.FileShouldHaveContents("a\\a", "file contents one");
|
||||
this.FileShouldHaveContents("b", "file contents two");
|
||||
|
||||
// Delta of interest - Final validation
|
||||
// renamed: c\c <-> d\c && d\d <-> c\d
|
||||
this.FileShouldHaveContents("c\\c", "file contents c");
|
||||
this.FileShouldHaveContents("d\\d", "file contents d");
|
||||
this.ShouldNotExistOnDisk("c\\d");
|
||||
this.ShouldNotExistOnDisk("d\\c");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void DeleteFileThenCheckout()
|
||||
{
|
||||
this.FolderShouldExistAndHaveFile("GitCommandsTests\\DeleteFileTests\\1", "#test");
|
||||
this.DeleteFile("GitCommandsTests\\DeleteFileTests\\1\\#test");
|
||||
this.FolderShouldExistAndBeEmpty("GitCommandsTests\\DeleteFileTests\\1");
|
||||
|
||||
// Commit 14cf226119766146b1fa5c5aa4cd0896d05f6b63 is before
|
||||
// the files in GitCommandsTests\DeleteFileTests were added
|
||||
this.ValidateGitCommand("checkout 14cf226119766146b1fa5c5aa4cd0896d05f6b63");
|
||||
|
||||
this.ShouldNotExistOnDisk("GitCommandsTests\\DeleteFileTests\\1");
|
||||
this.ShouldNotExistOnDisk("GitCommandsTests\\DeleteFileTests");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void CheckoutEditCheckoutWithoutFolderThenCheckoutWithMultipleFiles()
|
||||
{
|
||||
// Edit the file to get the entry in the sparse-checkout file
|
||||
this.EditFile("DeleteFileWithNameAheadOfDotAndSwitchCommits\\1", "Changing the content of one file");
|
||||
this.RunGitCommand("reset --hard -q HEAD");
|
||||
|
||||
// This commit should remove the DeleteFileWithNameAheadOfDotAndSwitchCommits folder
|
||||
this.ValidateGitCommand("checkout b4d932658def04a97da873fd6adab70014b8a523");
|
||||
|
||||
this.ShouldNotExistOnDisk("DeleteFileWithNameAheadOfDotAndSwitchCommits");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void CreateAFolderThenCheckoutBranchWithFolder()
|
||||
{
|
||||
this.FolderShouldExistAndHaveFile("DeleteFileWithNameAheadOfDotAndSwitchCommits", "1");
|
||||
|
||||
// This commit should remove the DeleteFileWithNameAheadOfDotAndSwitchCommits folder
|
||||
this.ValidateGitCommand("checkout b4d932658def04a97da873fd6adab70014b8a523");
|
||||
this.ShouldNotExistOnDisk("DeleteFileWithNameAheadOfDotAndSwitchCommits");
|
||||
this.CreateFolder("DeleteFileWithNameAheadOfDotAndSwitchCommits");
|
||||
this.ValidateGitCommand("checkout " + this.ControlGitRepo.Commitish);
|
||||
this.FolderShouldExistAndHaveFile("DeleteFileWithNameAheadOfDotAndSwitchCommits", "1");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void CheckoutBranchWithDirectoryNameSameAsFile()
|
||||
{
|
||||
this.SetupForFileDirectoryTest();
|
||||
this.ValidateFileDirectoryTest("checkout");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void CheckoutBranchWithDirectoryNameSameAsFileEnumerate()
|
||||
{
|
||||
this.RunFileDirectoryEnumerateTest("checkout");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void CheckoutBranchWithDirectoryNameSameAsFileWithRead()
|
||||
{
|
||||
this.RunFileDirectoryReadTest("checkout");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void CheckoutBranchWithDirectoryNameSameAsFileWithWrite()
|
||||
{
|
||||
this.RunFileDirectoryWriteTest("checkout");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void CheckoutBranchDirectoryWithOneFile()
|
||||
{
|
||||
this.SetupForFileDirectoryTest(commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
this.ValidateFileDirectoryTest("checkout", commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void CheckoutBranchDirectoryWithOneFileEnumerate()
|
||||
{
|
||||
this.RunFileDirectoryEnumerateTest("checkout", commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void CheckoutBranchDirectoryWithOneFileRead()
|
||||
{
|
||||
this.RunFileDirectoryReadTest("checkout", commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void CheckoutBranchDirectoryWithOneFileWrite()
|
||||
{
|
||||
this.RunFileDirectoryWriteTest("checkout", commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern SafeFileHandle CreateFile(
|
||||
[In] string fileName,
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GVFS.FunctionalTests.Tests.GitCommands
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class CherryPickConflictTests : GitRepoTests
|
||||
{
|
||||
public CherryPickConflictTests() : base(enlistmentPerTest: true)
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.Tests.Should;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace GVFS.FunctionalTests.Tests.GitCommands
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class DeleteEmptyFolderTests : GitRepoTests
|
||||
{
|
||||
public DeleteEmptyFolderTests() : base(enlistmentPerTest: true)
|
||||
|
@ -18,25 +13,23 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
}
|
||||
|
||||
[TestCase]
|
||||
[Ignore("Disabled until checkout cleans up now empty folders.")]
|
||||
public void VerifyResetHardDeletesEmptyFolders()
|
||||
{
|
||||
this.SetupFolderDeleteTest();
|
||||
|
||||
this.RunGitCommand("reset --hard HEAD");
|
||||
this.Enlistment.RepoRoot.ShouldBeADirectory(this.FileSystem)
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, skipEmptyDirectories: false);
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
[Ignore("Disabled until checkout cleans up now empty folders.")]
|
||||
public void VerifyCleanDeletesEmptyFolders()
|
||||
{
|
||||
this.SetupFolderDeleteTest();
|
||||
|
||||
this.RunGitCommand("clean -fd");
|
||||
this.Enlistment.RepoRoot.ShouldBeADirectory(this.FileSystem)
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, skipEmptyDirectories: false);
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath);
|
||||
}
|
||||
|
||||
private void SetupFolderDeleteTest()
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GVFS.FunctionalTests.Tests.GitCommands
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class EnumerationMergeTest : GitRepoTests
|
||||
{
|
||||
// Commit that found GvFlt Bug 12258777: Entries are sometimes skipped during
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.Tests.Should;
|
||||
using NUnit.Framework;
|
||||
|
@ -10,7 +9,7 @@ using System.Runtime.CompilerServices;
|
|||
namespace GVFS.FunctionalTests.Tests.GitCommands
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class GitCommandsTests : GitRepoTests
|
||||
{
|
||||
private const string EncodingFileFolder = "FilenameEncoding";
|
||||
|
@ -492,27 +491,6 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
this.ValidateGitCommand("checkout " + this.ControlGitRepo.Commitish);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void DeleteFolderAndChangeBranchToFolderWithDifferentCase()
|
||||
{
|
||||
// 692765 - Recursive sparse-checkout entries for folders should be case insensitive when
|
||||
// changing branches
|
||||
|
||||
string folderName = "GVFlt_MultiThreadTest";
|
||||
|
||||
// Confirm that no other test has caused "GVFlt_MultiThreadTest" to be added to the sparse-checkout
|
||||
string sparseFile = Path.Combine(this.Enlistment.RepoRoot, TestConstants.DotGit.Info.SparseCheckout);
|
||||
sparseFile.ShouldBeAFile(this.FileSystem).WithContents().ShouldNotContain(ignoreCase: true, unexpectedSubstrings: folderName);
|
||||
|
||||
this.FolderShouldHaveCaseMatchingName(folderName, "GVFlt_MultiThreadTest");
|
||||
this.DeleteFolder(folderName);
|
||||
|
||||
// b5fd7d23706a18cff3e2b8225588d479f7e51138 is the commit prior to deleting GVFLT_MultiThreadTest
|
||||
// and re-adding it as as GVFlt_MultiThreadTest
|
||||
this.ValidateGitCommand("checkout b5fd7d23706a18cff3e2b8225588d479f7e51138");
|
||||
this.FolderShouldHaveCaseMatchingName(folderName, "GVFLT_MultiThreadTest");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void EditFileSwitchBranchTest()
|
||||
{
|
||||
|
@ -566,7 +544,7 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
this.RunGitCommand("commit -m \"Delete file for AddFileCommitThenDeleteAndCommit\"");
|
||||
this.ValidateGitCommand("checkout tests/functional/AddFileCommitThenDeleteAndCommit_before");
|
||||
this.Enlistment.RepoRoot.ShouldBeADirectory(this.FileSystem)
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, skipEmptyDirectories: true);
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath);
|
||||
this.ValidateGitCommand("checkout tests/functional/AddFileCommitThenDeleteAndCommit_after");
|
||||
}
|
||||
|
||||
|
@ -1069,7 +1047,7 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
this.RunGitCommand("commit -m \"Change for {0}\"", branch);
|
||||
this.ValidateGitCommand("checkout " + this.ControlGitRepo.Commitish);
|
||||
this.Enlistment.RepoRoot.ShouldBeADirectory(this.FileSystem)
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, skipEmptyDirectories: true);
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath);
|
||||
|
||||
this.ValidateGitCommand("checkout {0}", branch);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ using GVFS.FunctionalTests.Tools;
|
|||
using GVFS.Tests.Should;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
|
@ -15,6 +16,9 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
protected const string ConflictSourceBranch = "FunctionalTests/20170206_Conflict_Source";
|
||||
protected const string ConflictTargetBranch = "FunctionalTests/20170206_Conflict_Target";
|
||||
protected const string NoConflictSourceBranch = "FunctionalTests/20170209_NoConflict_Source";
|
||||
protected const string DirectoryWithFileBeforeBranch = "FunctionalTests/20171025_DirectoryWithFileBefore";
|
||||
protected const string DirectoryWithFileAfterBranch = "FunctionalTests/20171025_DirectoryWithFileAfter";
|
||||
protected const string DirectoryWithDifferentFileAfterBranch = "FunctionalTests/20171025_DirectoryWithDifferentFile";
|
||||
|
||||
private bool enlistmentPerTest;
|
||||
|
||||
|
@ -69,7 +73,7 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
|
||||
this.CheckHeadCommitTree();
|
||||
this.Enlistment.RepoRoot.ShouldBeADirectory(this.FileSystem)
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, skipEmptyDirectories: true);
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath);
|
||||
this.ValidateGitCommand("status");
|
||||
}
|
||||
|
||||
|
@ -85,7 +89,7 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
{
|
||||
this.CheckHeadCommitTree();
|
||||
this.Enlistment.RepoRoot.ShouldBeADirectory(this.FileSystem)
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, skipEmptyDirectories: true, ignoreCase: ignoreCase);
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, ignoreCase: ignoreCase);
|
||||
|
||||
this.RunGitCommand("reset --hard -q HEAD");
|
||||
this.RunGitCommand("clean -d -f -x");
|
||||
|
@ -93,7 +97,7 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
|
||||
this.CheckHeadCommitTree();
|
||||
this.Enlistment.RepoRoot.ShouldBeADirectory(this.FileSystem)
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, skipEmptyDirectories: true, ignoreCase: ignoreCase);
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, ignoreCase: ignoreCase);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
@ -111,8 +115,7 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
|
||||
protected void CreateEnlistment(string commitish = null)
|
||||
{
|
||||
string pathToGvfs = Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS);
|
||||
this.Enlistment = GVFSFunctionalTestEnlistment.CloneAndMount(pathToGvfs, commitish: commitish);
|
||||
this.Enlistment = GVFSFunctionalTestEnlistment.CloneAndMount(GVFSTestConfig.PathToGVFS, commitish: commitish);
|
||||
GitProcess.Invoke(this.Enlistment.RepoRoot, "config advice.statusUoption false");
|
||||
this.ControlGitRepo = ControlGitRepo.Create(commitish);
|
||||
this.ControlGitRepo.Initialize();
|
||||
|
@ -346,6 +349,45 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
this.FileSystem.WriteAllText(controlFile, newContent);
|
||||
}
|
||||
|
||||
protected void SetupForFileDirectoryTest(string commandBranch = DirectoryWithFileAfterBranch)
|
||||
{
|
||||
this.ControlGitRepo.Fetch(DirectoryWithFileBeforeBranch);
|
||||
this.ControlGitRepo.Fetch(commandBranch);
|
||||
this.ValidateGitCommand($"checkout {DirectoryWithFileBeforeBranch}");
|
||||
}
|
||||
|
||||
protected void ValidateFileDirectoryTest(string command, string commandBranch = DirectoryWithFileAfterBranch)
|
||||
{
|
||||
this.EditFile("Readme.md", "Change file");
|
||||
this.ValidateGitCommand("add --all");
|
||||
this.RunGitCommand("commit -m \"Some change\"");
|
||||
this.ValidateGitCommand($"{command} {commandBranch}");
|
||||
}
|
||||
|
||||
protected void RunFileDirectoryEnumerateTest(string command, string commandBranch = DirectoryWithFileAfterBranch)
|
||||
{
|
||||
this.SetupForFileDirectoryTest(commandBranch);
|
||||
|
||||
// file.txt is a folder with a file named file.txt to test checking out branches
|
||||
// that have folders with the same name as files
|
||||
this.FileSystem.EnumerateDirectory(this.Enlistment.GetVirtualPathTo("file.txt"));
|
||||
this.ValidateFileDirectoryTest(command, commandBranch);
|
||||
}
|
||||
|
||||
protected void RunFileDirectoryReadTest(string command, string commandBranch = DirectoryWithFileAfterBranch)
|
||||
{
|
||||
this.SetupForFileDirectoryTest(commandBranch);
|
||||
this.FileContentsShouldMatch("file.txt\\file.txt");
|
||||
this.ValidateFileDirectoryTest(command, commandBranch);
|
||||
}
|
||||
|
||||
protected void RunFileDirectoryWriteTest(string command, string commandBranch = DirectoryWithFileAfterBranch)
|
||||
{
|
||||
this.SetupForFileDirectoryTest(commandBranch);
|
||||
this.EditFile("file.txt\\file.txt", "Change file");
|
||||
this.ValidateFileDirectoryTest(command, commandBranch);
|
||||
}
|
||||
|
||||
protected void ReadConflictTargetFiles()
|
||||
{
|
||||
this.FileContentsShouldMatch(@"Test_ConflictTests\AddedFiles\AddedByBothDifferentContent.txt");
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.Tests.Should;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GVFS.FunctionalTests.Tests.GitCommands
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class MergeConflictTests : GitRepoTests
|
||||
{
|
||||
public MergeConflictTests() : base(enlistmentPerTest: true)
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GVFS.FunctionalTests.Tests.GitCommands
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class RebaseConflictTests : GitRepoTests
|
||||
{
|
||||
public RebaseConflictTests() : base(enlistmentPerTest: true)
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GVFS.FunctionalTests.Tests.GitCommands
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Ignore("Folder with case rename is not working for these tests")]
|
||||
public class RebaseTests : GitRepoTests
|
||||
{
|
||||
public RebaseTests() : base(enlistmentPerTest: true)
|
||||
|
@ -13,6 +10,7 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
}
|
||||
|
||||
[TestCase]
|
||||
[Ignore("This is producing different output because git is not checking out files in the rebase. The virtual file system changes should address this issue.")]
|
||||
public void RebaseSmallNoConflicts()
|
||||
{
|
||||
// 5d299512450f4029d7a1fe8d67e833b84247d393 is the tip of FunctionalTests/RebaseTestsSource_20170130
|
||||
|
@ -46,21 +44,72 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
this.ValidateGitCommand("rebase {0}", targetCommit);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void RebaseEditThenDelete()
|
||||
{
|
||||
// 23a238b04497da2449fd730966c06f84b6326c3a is the tip of FunctionalTests/RebaseTestsSource_20170208
|
||||
string sourceCommit = "23a238b04497da2449fd730966c06f84b6326c3a";
|
||||
|
||||
// Target commit 47fabb534c35af40156db6e8365165cb04f9dd75 is part of the history of
|
||||
// FunctionalTests/20170208
|
||||
string targetCommit = "47fabb534c35af40156db6e8365165cb04f9dd75";
|
||||
|
||||
this.ControlGitRepo.Fetch(sourceCommit);
|
||||
this.ControlGitRepo.Fetch(targetCommit);
|
||||
|
||||
this.ValidateGitCommand("checkout {0}", sourceCommit);
|
||||
this.ValidateGitCommand("rebase {0}", targetCommit);
|
||||
[TestCase]
|
||||
[Ignore("This is producing different output because git is not checking out files in the rebase. The virtual file system changes should address this issue.")]
|
||||
public void RebaseEditThenDelete()
|
||||
{
|
||||
// 23a238b04497da2449fd730966c06f84b6326c3a is the tip of FunctionalTests/RebaseTestsSource_20170208
|
||||
string sourceCommit = "23a238b04497da2449fd730966c06f84b6326c3a";
|
||||
|
||||
// Target commit 47fabb534c35af40156db6e8365165cb04f9dd75 is part of the history of
|
||||
// FunctionalTests/20170208
|
||||
string targetCommit = "47fabb534c35af40156db6e8365165cb04f9dd75";
|
||||
|
||||
this.ControlGitRepo.Fetch(sourceCommit);
|
||||
this.ControlGitRepo.Fetch(targetCommit);
|
||||
|
||||
this.ValidateGitCommand("checkout {0}", sourceCommit);
|
||||
this.ValidateGitCommand("rebase {0}", targetCommit);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void RebaseWithDirectoryNameSameAsFile()
|
||||
{
|
||||
this.SetupForFileDirectoryTest();
|
||||
this.ValidateFileDirectoryTest("rebase");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void RebaseWithDirectoryNameSameAsFileEnumerate()
|
||||
{
|
||||
this.RunFileDirectoryEnumerateTest("rebase");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void RebaseWithDirectoryNameSameAsFileWithRead()
|
||||
{
|
||||
this.RunFileDirectoryReadTest("rebase");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void RebaseWithDirectoryNameSameAsFileWithWrite()
|
||||
{
|
||||
this.RunFileDirectoryWriteTest("rebase");
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void RebaseDirectoryWithOneFile()
|
||||
{
|
||||
this.SetupForFileDirectoryTest(commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
this.ValidateFileDirectoryTest("rebase", commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void RebaseDirectoryWithOneFileEnumerate()
|
||||
{
|
||||
this.RunFileDirectoryEnumerateTest("rebase", commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void RebaseDirectoryWithOneFileRead()
|
||||
{
|
||||
this.RunFileDirectoryReadTest("rebase", commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void RebaseDirectoryWithOneFileWrite()
|
||||
{
|
||||
this.RunFileDirectoryWriteTest("rebase", commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
using GVFS.FunctionalTests.Should;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GVFS.FunctionalTests.Tests.GitCommands
|
||||
{
|
||||
[TestFixture]
|
||||
public class ResetHardTests : GitRepoTests
|
||||
{
|
||||
private const string ResetHardCommand = "reset --hard";
|
||||
|
||||
public ResetHardTests() : base(enlistmentPerTest: true)
|
||||
{
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void VerifyResetHardDeletesEmptyFolders()
|
||||
{
|
||||
ControlGitRepo.Fetch("FunctionalTests/20170202_RenameTestMergeTarget");
|
||||
this.ValidateGitCommand("checkout FunctionalTests/20170202_RenameTestMergeTarget");
|
||||
this.ValidateGitCommand("reset --hard HEAD~1");
|
||||
this.ShouldNotExistOnDisk("Test_EPF_GitCommandsTestOnlyFileFolder");
|
||||
this.Enlistment.RepoRoot.ShouldBeADirectory(this.FileSystem)
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void ResetHardWithDirectoryNameSameAsFile()
|
||||
{
|
||||
this.SetupForFileDirectoryTest();
|
||||
this.ValidateFileDirectoryTest(ResetHardCommand);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void ResetHardWithDirectoryNameSameAsFileEnumerate()
|
||||
{
|
||||
this.RunFileDirectoryEnumerateTest(ResetHardCommand);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void ResetHardWithDirectoryNameSameAsFileWithRead()
|
||||
{
|
||||
this.RunFileDirectoryReadTest(ResetHardCommand);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void ResetHardWithDirectoryNameSameAsFileWithWrite()
|
||||
{
|
||||
this.RunFileDirectoryWriteTest(ResetHardCommand);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void ResetHardDirectoryWithOneFile()
|
||||
{
|
||||
this.SetupForFileDirectoryTest(commandBranch: GitRepoTests.DirectoryWithFileAfterBranch);
|
||||
this.ValidateFileDirectoryTest(ResetHardCommand, commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void ResetHardDirectoryWithOneFileEnumerate()
|
||||
{
|
||||
this.RunFileDirectoryEnumerateTest(ResetHardCommand, commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void ResetHardDirectoryWithOneFileRead()
|
||||
{
|
||||
this.RunFileDirectoryReadTest(ResetHardCommand, commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void ResetHardDirectoryWithOneFileWrite()
|
||||
{
|
||||
this.RunFileDirectoryWriteTest(ResetHardCommand, commandBranch: GitRepoTests.DirectoryWithDifferentFileAfterBranch);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GVFS.FunctionalTests.Tests.GitCommands
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class ResetMixedTests : GitRepoTests
|
||||
{
|
||||
public ResetMixedTests() : base(enlistmentPerTest: true)
|
||||
|
@ -93,17 +92,17 @@ namespace GVFS.FunctionalTests.Tests.GitCommands
|
|||
// Then reset --mixed to the parent commit, and validate that the deleted files did not come back into the projection
|
||||
this.ValidateGitCommand("reset --mixed HEAD~1");
|
||||
this.Enlistment.RepoRoot.ShouldBeADirectory(this.FileSystem)
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, skipEmptyDirectories: false);
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath);
|
||||
|
||||
// And checkout a file (without changing branches) and ensure that that doesn't update the projection either
|
||||
this.ValidateGitCommand("checkout HEAD~2 .gitattributes");
|
||||
this.Enlistment.RepoRoot.ShouldBeADirectory(this.FileSystem)
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, skipEmptyDirectories: false);
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath);
|
||||
|
||||
// And now if we checkout the original commit, the deleted files should stay deleted
|
||||
this.ValidateGitCommand("checkout FunctionalTests/20170602");
|
||||
this.Enlistment.RepoRoot.ShouldBeADirectory(this.FileSystem)
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath, skipEmptyDirectories: false);
|
||||
.WithDeepStructure(this.FileSystem, this.ControlGitRepo.RootPath);
|
||||
}
|
||||
|
||||
protected override void CreateEnlistment()
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace GVFS.FunctionalTests.Tests.GitCommands
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class ResetSoftTests : GitRepoTests
|
||||
{
|
||||
public ResetSoftTests() : base(enlistmentPerTest: true)
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
using GVFS.FunctionalTests.Category;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using NUnit.Framework;
|
||||
using System.IO;
|
||||
|
||||
namespace GVFS.FunctionalTests.Tests.GitCommands
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(CategoryConstants.GitCommands)]
|
||||
[Category(Categories.GitCommands)]
|
||||
public class UpdateIndexTests : GitRepoTests
|
||||
{
|
||||
public UpdateIndexTests() : base(enlistmentPerTest: true)
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace GVFS.FunctionalTests.Tests.MultiEnlistmentTests
|
|||
{
|
||||
[TestFixture]
|
||||
[NonParallelizable]
|
||||
[Category(Categories.FullSuiteOnly)]
|
||||
public class ServiceVerbTests : TestsWithMultiEnlistment
|
||||
{
|
||||
private static readonly string[] EmptyRepoList = new string[] { };
|
||||
|
@ -28,12 +29,12 @@ namespace GVFS.FunctionalTests.Tests.MultiEnlistmentTests
|
|||
string[] repoRootList = new string[] { enlistment1.EnlistmentRoot, enlistment2.EnlistmentRoot };
|
||||
|
||||
GVFSProcess gvfsProcess1 = new GVFSProcess(
|
||||
Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS),
|
||||
GVFSTestConfig.PathToGVFS,
|
||||
enlistment1.EnlistmentRoot,
|
||||
enlistment1.LocalCacheRoot);
|
||||
|
||||
GVFSProcess gvfsProcess2 = new GVFSProcess(
|
||||
Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS),
|
||||
GVFSTestConfig.PathToGVFS,
|
||||
enlistment2.EnlistmentRoot,
|
||||
enlistment2.LocalCacheRoot);
|
||||
|
||||
|
@ -63,7 +64,7 @@ namespace GVFS.FunctionalTests.Tests.MultiEnlistmentTests
|
|||
string[] repoRootList = new string[] { enlistment1.EnlistmentRoot };
|
||||
|
||||
GVFSProcess gvfsProcess1 = new GVFSProcess(
|
||||
Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS),
|
||||
GVFSTestConfig.PathToGVFS,
|
||||
enlistment1.EnlistmentRoot,
|
||||
enlistment1.LocalCacheRoot);
|
||||
|
||||
|
@ -87,7 +88,7 @@ namespace GVFS.FunctionalTests.Tests.MultiEnlistmentTests
|
|||
private void RunServiceCommandAndCheckOutput(string argument, string[] expectedRepoRoots, string[] unexpectedRepoRoots = null)
|
||||
{
|
||||
GVFSProcess gvfsProcess = new GVFSProcess(
|
||||
Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS),
|
||||
GVFSTestConfig.PathToGVFS,
|
||||
enlistmentRoot: null,
|
||||
localCacheRoot: null);
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
using GVFS.FunctionalTests.FileSystemRunners;
|
||||
using GVFS.FunctionalTests.Should;
|
||||
using GVFS.FunctionalTests.Tests.EnlistmentPerTestCase;
|
||||
using GVFS.FunctionalTests.Tools;
|
||||
using GVFS.Tests.Should;
|
||||
using NUnit.Framework;
|
||||
|
@ -13,6 +14,7 @@ using System.Threading.Tasks;
|
|||
namespace GVFS.FunctionalTests.Tests.MultiEnlistmentTests
|
||||
{
|
||||
[TestFixture]
|
||||
[Category(Categories.FullSuiteOnly)]
|
||||
public class SharedCacheTests : TestsWithMultiEnlistment
|
||||
{
|
||||
private const string WellKnownFile = "Readme.md";
|
||||
|
@ -38,12 +40,6 @@ namespace GVFS.FunctionalTests.Tests.MultiEnlistmentTests
|
|||
this.localCachePath = Path.Combine(this.localCacheParentPath, ".customGVFSCache");
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void DeleteCache()
|
||||
{
|
||||
CmdRunner.DeleteDirectoryWithRetry(this.localCacheParentPath);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void SecondCloneDoesNotDownloadAdditionalObjects()
|
||||
{
|
||||
|
@ -64,6 +60,124 @@ namespace GVFS.FunctionalTests.Tests.MultiEnlistmentTests
|
|||
.ShouldMatchInOrder(allObjects);
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void RepairFixesCorruptBlobSizesDatabase()
|
||||
{
|
||||
GVFSFunctionalTestEnlistment enlistment = this.CloneAndMountEnlistment();
|
||||
enlistment.UnmountGVFS();
|
||||
|
||||
// Repair on a healthy enlistment should succeed
|
||||
enlistment.Repair();
|
||||
|
||||
string blobSizesRoot = GVFSHelpers.GetPersistedBlobSizesRoot(enlistment.DotGVFSRoot).ShouldNotBeNull();
|
||||
string blobSizesDbPath = Path.Combine(blobSizesRoot, "BlobSizes.sql");
|
||||
blobSizesDbPath.ShouldBeAFile(this.fileSystem);
|
||||
this.fileSystem.WriteAllText(blobSizesDbPath, "0000");
|
||||
|
||||
enlistment.TryMountGVFS().ShouldEqual(false, "GVFS shouldn't mount when blob size db is corrupt");
|
||||
enlistment.Repair();
|
||||
enlistment.MountGVFS();
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void MountUpgradesLocalSizesToSharedCache()
|
||||
{
|
||||
GVFSFunctionalTestEnlistment enlistment = this.CloneAndMountEnlistment();
|
||||
enlistment.UnmountGVFS();
|
||||
|
||||
string localCacheRoot = GVFSHelpers.GetPersistedLocalCacheRoot(enlistment.DotGVFSRoot);
|
||||
string gitObjectsRoot = GVFSHelpers.GetPersistedGitObjectsRoot(enlistment.DotGVFSRoot);
|
||||
|
||||
// Delete the existing repo metadata
|
||||
string versionJsonPath = Path.Combine(enlistment.DotGVFSRoot, GVFSHelpers.RepoMetadataName);
|
||||
versionJsonPath.ShouldBeAFile(this.fileSystem);
|
||||
this.fileSystem.DeleteFile(versionJsonPath);
|
||||
|
||||
// "13.0" was the last version before blob sizes were moved out of Esent
|
||||
string metadataPath = Path.Combine(enlistment.DotGVFSRoot, GVFSHelpers.RepoMetadataName);
|
||||
this.fileSystem.CreateEmptyFile(metadataPath);
|
||||
GVFSHelpers.SaveDiskLayoutVersion(enlistment.DotGVFSRoot, "13", "0");
|
||||
GVFSHelpers.SaveLocalCacheRoot(enlistment.DotGVFSRoot, localCacheRoot);
|
||||
GVFSHelpers.SaveGitObjectsRoot(enlistment.DotGVFSRoot, gitObjectsRoot);
|
||||
|
||||
// Create a legacy PersistedDictionary sizes database
|
||||
List<KeyValuePair<string, long>> entries = new List<KeyValuePair<string, long>>()
|
||||
{
|
||||
new KeyValuePair<string, long>(new string('0', 40), 1),
|
||||
new KeyValuePair<string, long>(new string('1', 40), 2),
|
||||
new KeyValuePair<string, long>(new string('2', 40), 4),
|
||||
new KeyValuePair<string, long>(new string('3', 40), 8),
|
||||
};
|
||||
|
||||
GVFSHelpers.CreateEsentBlobSizesDatabase(enlistment.DotGVFSRoot, entries);
|
||||
|
||||
enlistment.MountGVFS();
|
||||
|
||||
string majorVersion;
|
||||
string minorVersion;
|
||||
GVFSHelpers.GetPersistedDiskLayoutVersion(enlistment.DotGVFSRoot, out majorVersion, out minorVersion);
|
||||
|
||||
majorVersion
|
||||
.ShouldBeAnInt("Disk layout version should always be an int")
|
||||
.ShouldEqual(DiskLayoutUpgradeTests.CurrentDiskLayoutMajorVersion, "Disk layout version should be upgraded to the latest");
|
||||
|
||||
minorVersion
|
||||
.ShouldBeAnInt("Disk layout version should always be an int")
|
||||
.ShouldEqual(DiskLayoutUpgradeTests.CurrentDiskLayoutMinorVersion, "Disk layout version should be upgraded to the latest");
|
||||
|
||||
string newBlobSizesRoot = Path.Combine(Path.GetDirectoryName(gitObjectsRoot), DiskLayoutUpgradeTests.BlobSizesCacheName);
|
||||
GVFSHelpers.GetPersistedBlobSizesRoot(enlistment.DotGVFSRoot)
|
||||
.ShouldEqual(newBlobSizesRoot);
|
||||
|
||||
string blobSizesDbPath = Path.Combine(newBlobSizesRoot, DiskLayoutUpgradeTests.BlobSizesDBFileName);
|
||||
newBlobSizesRoot.ShouldBeADirectory(this.fileSystem);
|
||||
blobSizesDbPath.ShouldBeAFile(this.fileSystem);
|
||||
|
||||
foreach (KeyValuePair<string, long> entry in entries)
|
||||
{
|
||||
GVFSHelpers.SQLiteBlobSizesDatabaseHasEntry(blobSizesDbPath, entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
// Upgrade a second repo, and make sure all sizes from both upgrades are in the shared database
|
||||
|
||||
GVFSFunctionalTestEnlistment enlistment2 = this.CloneAndMountEnlistment();
|
||||
enlistment2.UnmountGVFS();
|
||||
|
||||
// Delete the existing repo metadata
|
||||
versionJsonPath = Path.Combine(enlistment2.DotGVFSRoot, GVFSHelpers.RepoMetadataName);
|
||||
versionJsonPath.ShouldBeAFile(this.fileSystem);
|
||||
this.fileSystem.DeleteFile(versionJsonPath);
|
||||
|
||||
// "13.0" was the last version before blob sizes were moved out of Esent
|
||||
metadataPath = Path.Combine(enlistment2.DotGVFSRoot, GVFSHelpers.RepoMetadataName);
|
||||
this.fileSystem.CreateEmptyFile(metadataPath);
|
||||
GVFSHelpers.SaveDiskLayoutVersion(enlistment2.DotGVFSRoot, "13", "0");
|
||||
GVFSHelpers.SaveLocalCacheRoot(enlistment2.DotGVFSRoot, localCacheRoot);
|
||||
GVFSHelpers.SaveGitObjectsRoot(enlistment2.DotGVFSRoot, gitObjectsRoot);
|
||||
|
||||
// Create a legacy PersistedDictionary sizes database
|
||||
List<KeyValuePair<string, long>> additionalEntries = new List<KeyValuePair<string, long>>()
|
||||
{
|
||||
new KeyValuePair<string, long>(new string('4', 40), 16),
|
||||
new KeyValuePair<string, long>(new string('5', 40), 32),
|
||||
new KeyValuePair<string, long>(new string('6', 40), 64),
|
||||
};
|
||||
|
||||
GVFSHelpers.CreateEsentBlobSizesDatabase(enlistment2.DotGVFSRoot, additionalEntries);
|
||||
|
||||
enlistment2.MountGVFS();
|
||||
|
||||
foreach (KeyValuePair<string, long> entry in entries)
|
||||
{
|
||||
GVFSHelpers.SQLiteBlobSizesDatabaseHasEntry(blobSizesDbPath, entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, long> entry in additionalEntries)
|
||||
{
|
||||
GVFSHelpers.SQLiteBlobSizesDatabaseHasEntry(blobSizesDbPath, entry.Key, entry.Value);
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void CloneCleansUpStaleMetadataLock()
|
||||
{
|
||||
|
@ -109,14 +223,20 @@ namespace GVFS.FunctionalTests.Tests.MultiEnlistmentTests
|
|||
}
|
||||
|
||||
[TestCase]
|
||||
public void DeleteCacheBeforeMount()
|
||||
public void DeleteObjectsCacheAndCacheMappingBeforeMount()
|
||||
{
|
||||
GVFSFunctionalTestEnlistment enlistment1 = this.CloneAndMountEnlistment();
|
||||
GVFSFunctionalTestEnlistment enlistment2 = this.CloneAndMountEnlistment();
|
||||
|
||||
enlistment1.UnmountGVFS();
|
||||
|
||||
CmdRunner.DeleteDirectoryWithRetry(this.localCachePath);
|
||||
string objectsRoot = GVFSHelpers.GetPersistedGitObjectsRoot(enlistment1.DotGVFSRoot).ShouldNotBeNull();
|
||||
objectsRoot.ShouldBeADirectory(this.fileSystem);
|
||||
CmdRunner.DeleteDirectoryWithRetry(objectsRoot);
|
||||
|
||||
string metadataPath = Path.Combine(this.localCachePath, "mapping.dat");
|
||||
metadataPath.ShouldBeAFile(this.fileSystem);
|
||||
this.fileSystem.DeleteFile(metadataPath);
|
||||
|
||||
enlistment1.MountGVFS();
|
||||
|
||||
|
@ -139,6 +259,9 @@ namespace GVFS.FunctionalTests.Tests.MultiEnlistmentTests
|
|||
{
|
||||
GVFSFunctionalTestEnlistment enlistment1 = this.CloneAndMountEnlistment();
|
||||
|
||||
string objectsRoot = GVFSHelpers.GetPersistedGitObjectsRoot(enlistment1.DotGVFSRoot).ShouldNotBeNull();
|
||||
objectsRoot.ShouldBeADirectory(this.fileSystem);
|
||||
|
||||
Task task1 = Task.Run(() =>
|
||||
{
|
||||
this.HydrateEntireRepo(enlistment1);
|
||||
|
@ -148,7 +271,8 @@ namespace GVFS.FunctionalTests.Tests.MultiEnlistmentTests
|
|||
{
|
||||
try
|
||||
{
|
||||
CmdRunner.DeleteDirectoryWithRetry(this.localCachePath);
|
||||
// Delete objectsRoot rather than this.localCachePath as the blob sizes database cannot be deleted while GVFS is mounted
|
||||
CmdRunner.DeleteDirectoryWithRetry(objectsRoot);
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
catch (IOException)
|
||||
|
@ -239,6 +363,13 @@ namespace GVFS.FunctionalTests.Tests.MultiEnlistmentTests
|
|||
this.AlternatesFileShouldHaveGitObjectsRoot(enlistment);
|
||||
}
|
||||
|
||||
// Override OnTearDownEnlistmentsDeleted rathern than using [TearDown] as the enlistments need to be unmounted before
|
||||
// localCacheParentPath can be deleted (as the SQLite blob sizes database cannot be deleted while GVFS is mounted)
|
||||
protected override void OnTearDownEnlistmentsDeleted()
|
||||
{
|
||||
CmdRunner.DeleteDirectoryWithRetry(this.localCacheParentPath);
|
||||
}
|
||||
|
||||
private GVFSFunctionalTestEnlistment CloneAndMountEnlistment(string branch = null)
|
||||
{
|
||||
return this.CreateNewEnlistment(this.localCachePath, branch);
|
||||
|
|
|
@ -17,14 +17,21 @@ namespace GVFS.FunctionalTests.Tests.MultiEnlistmentTests
|
|||
enlistment.UnmountAndDeleteAll();
|
||||
}
|
||||
|
||||
this.OnTearDownEnlistmentsDeleted();
|
||||
|
||||
this.enlistmentsToDelete.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be overridden for custom [TearDown] steps that occur after the test enlistements have been unmounted and deleted
|
||||
/// </summary>
|
||||
protected virtual void OnTearDownEnlistmentsDeleted()
|
||||
{
|
||||
}
|
||||
|
||||
protected GVFSFunctionalTestEnlistment CreateNewEnlistment(string localCacheRoot = null, string branch = null)
|
||||
{
|
||||
string pathToGvfs = Path.Combine(TestContext.CurrentContext.TestDirectory, Properties.Settings.Default.PathToGVFS);
|
||||
|
||||
GVFSFunctionalTestEnlistment output = GVFSFunctionalTestEnlistment.CloneAndMount(pathToGvfs, branch, localCacheRoot);
|
||||
GVFSFunctionalTestEnlistment output = GVFSFunctionalTestEnlistment.CloneAndMount(GVFSTestConfig.PathToGVFS, branch, localCacheRoot);
|
||||
this.enlistmentsToDelete.Add(output);
|
||||
return output;
|
||||
}
|
||||
|
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче