* 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:
Saeed Noursalehi 2018-04-25 16:20:15 -07:00
Родитель 2db0c030eb
Коммит 2797fbb835
249 изменённых файлов: 7287 добавлений и 7377 удалений

Просмотреть файл

@ -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;
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше