diff --git a/GVFS/FastFetch/FastFetchLibGit2Repo.cs b/GVFS/FastFetch/FastFetchLibGit2Repo.cs index 8c77164158..d278baa5af 100644 --- a/GVFS/FastFetch/FastFetchLibGit2Repo.cs +++ b/GVFS/FastFetch/FastFetchLibGit2Repo.cs @@ -18,7 +18,7 @@ public FastFetchLibGit2Repo(ITracer tracer, string repoPath) public virtual bool TryCopyBlobToFile(string sha, IEnumerable destinations, out long bytesWritten) { IntPtr objHandle; - if (Native.RevParseSingle(out objHandle, this.RepoHandle, sha) != Native.SuccessCode) + if (Native.RevParseSingle(out objHandle, this.RepoHandle, sha) != Native.ResultCode.Success) { bytesWritten = 0; EventMetadata metadata = new EventMetadata(); diff --git a/GVFS/GVFS.Common/Enlistment.cs b/GVFS/GVFS.Common/Enlistment.cs index 9e40b309ca..3f061257fb 100644 --- a/GVFS/GVFS.Common/Enlistment.cs +++ b/GVFS/GVFS.Common/Enlistment.cs @@ -109,31 +109,5 @@ public virtual GitProcess CreateGitProcess() { return new GitProcess(this); } - - public bool GetTrustPackIndexesConfig() - { - var gitProcess = this.CreateGitProcess(); - bool trustPackIndexes = true; - if (gitProcess.TryGetFromConfig(GVFSConstants.GitConfig.TrustPackIndexes, forceOutsideEnlistment: false, out var valueString) - && bool.TryParse(valueString, out var trustPackIndexesConfig)) - { - trustPackIndexes = trustPackIndexesConfig; - } - - return trustPackIndexes; - } - - public bool GetStatusHydrationConfig() - { - var gitProcess = this.CreateGitProcess(); - - if (gitProcess.TryGetFromConfig(GVFSConstants.GitConfig.ShowHydrationStatus, forceOutsideEnlistment: false, out var valueString) - && bool.TryParse(valueString, out var statusHydrationConfig)) - { - return statusHydrationConfig; - } - - return GVFSConstants.GitConfig.ShowHydrationStatusDefault; - } } } diff --git a/GVFS/GVFS.Common/GVFSConstants.cs b/GVFS/GVFS.Common/GVFSConstants.cs index 3f3abecc42..cf52e1fbbd 100644 --- a/GVFS/GVFS.Common/GVFSConstants.cs +++ b/GVFS/GVFS.Common/GVFSConstants.cs @@ -42,6 +42,7 @@ public static class GitConfig /* Intended to be a temporary config to allow testing of distrusting pack indexes from cache server * before it is enabled by default. */ public const string TrustPackIndexes = GVFSPrefix + "trust-pack-indexes"; + public const bool TrustPackIndexesDefault = true; public const string ShowHydrationStatus = GVFSPrefix + "show-hydration-status"; public const bool ShowHydrationStatusDefault = false; diff --git a/GVFS/GVFS.Common/Git/GitObjects.cs b/GVFS/GVFS.Common/Git/GitObjects.cs index 6807494dfc..da68656c84 100644 --- a/GVFS/GVFS.Common/Git/GitObjects.cs +++ b/GVFS/GVFS.Common/Git/GitObjects.cs @@ -153,7 +153,7 @@ public virtual void DeleteTemporaryFiles() } } - public virtual bool TryDownloadPrefetchPacks(GitProcess gitProcess, long latestTimestamp, out List packIndexes) + public virtual bool TryDownloadPrefetchPacks(GitProcess gitProcess, long latestTimestamp, bool trustPackIndexes, out List packIndexes) { EventMetadata metadata = CreateEventMetadata(); metadata.Add("latestTimestamp", latestTimestamp); @@ -166,7 +166,6 @@ public virtual bool TryDownloadPrefetchPacks(GitProcess gitProcess, long latestT * pack file and an index file that do not match. * Eventually we will make this the default, but it has a high performance cost for the first prefetch after * cloning a large repository, so it must be explicitly enabled for now. */ - bool trustPackIndexes = this.Enlistment.GetTrustPackIndexesConfig(); metadata.Add("trustPackIndexes", trustPackIndexes); long requestId = HttpRequestor.GetNewRequestId(); diff --git a/GVFS/GVFS.Common/Git/GitRepo.cs b/GVFS/GVFS.Common/Git/GitRepo.cs index cd11436d8c..b2b3ad7b38 100644 --- a/GVFS/GVFS.Common/Git/GitRepo.cs +++ b/GVFS/GVFS.Common/Git/GitRepo.cs @@ -51,6 +51,11 @@ public GVFSLock GVFSLock private set; } + internal LibGit2RepoInvoker LibGit2RepoInvoker + { + get { return this.libgit2RepoInvoker; } + } + public void CloseActiveRepo() { this.libgit2RepoInvoker?.DisposeSharedRepo(); diff --git a/GVFS/GVFS.Common/Git/LibGit2Exception.cs b/GVFS/GVFS.Common/Git/LibGit2Exception.cs new file mode 100644 index 0000000000..27034e513e --- /dev/null +++ b/GVFS/GVFS.Common/Git/LibGit2Exception.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GVFS.Common.Git +{ + public class LibGit2Exception : Exception + { + public LibGit2Exception(string message) : base(message) + { + } + + public LibGit2Exception(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/GVFS/GVFS.Common/Git/LibGit2Repo.cs b/GVFS/GVFS.Common/Git/LibGit2Repo.cs index f9edcce64a..86415ea5c3 100644 --- a/GVFS/GVFS.Common/Git/LibGit2Repo.cs +++ b/GVFS/GVFS.Common/Git/LibGit2Repo.cs @@ -17,7 +17,7 @@ public LibGit2Repo(ITracer tracer, string repoPath) Native.Init(); IntPtr repoHandle; - if (Native.Repo.Open(out repoHandle, repoPath) != Native.SuccessCode) + if (Native.Repo.Open(out repoHandle, repoPath) != Native.ResultCode.Success) { string reason = Native.GetLastError(); string message = "Couldn't open repo at " + repoPath + ": " + reason; @@ -45,7 +45,7 @@ protected LibGit2Repo() public Native.ObjectTypes? GetObjectType(string sha) { IntPtr objHandle; - if (Native.RevParseSingle(out objHandle, this.RepoHandle, sha) != Native.SuccessCode) + if (Native.RevParseSingle(out objHandle, this.RepoHandle, sha) != Native.ResultCode.Success) { return null; } @@ -63,7 +63,7 @@ protected LibGit2Repo() public virtual string GetTreeSha(string commitish) { IntPtr objHandle; - if (Native.RevParseSingle(out objHandle, this.RepoHandle, commitish) != Native.SuccessCode) + if (Native.RevParseSingle(out objHandle, this.RepoHandle, commitish) != Native.ResultCode.Success) { return null; } @@ -99,7 +99,7 @@ public virtual bool CommitAndRootTreeExists(string commitish, out string treeSha public virtual bool ObjectExists(string sha) { IntPtr objHandle; - if (Native.RevParseSingle(out objHandle, this.RepoHandle, sha) != Native.SuccessCode) + if (Native.RevParseSingle(out objHandle, this.RepoHandle, sha) != Native.ResultCode.Success) { return false; } @@ -111,7 +111,7 @@ public virtual bool ObjectExists(string sha) public virtual bool TryCopyBlob(string sha, Action writeAction) { IntPtr objHandle; - if (Native.RevParseSingle(out objHandle, this.RepoHandle, sha) != Native.SuccessCode) + if (Native.RevParseSingle(out objHandle, this.RepoHandle, sha) != Native.ResultCode.Success) { return false; } @@ -157,7 +157,7 @@ public virtual string[] GetMissingSubTrees(string treeSha) { List missingSubtreesList = new List(); IntPtr treeHandle; - if (Native.RevParseSingle(out treeHandle, this.RepoHandle, treeSha) != Native.SuccessCode + if (Native.RevParseSingle(out treeHandle, this.RepoHandle, treeSha) != Native.ResultCode.Success || treeHandle == IntPtr.Zero) { return Array.Empty(); @@ -187,6 +187,68 @@ public virtual string[] GetMissingSubTrees(string treeSha) return missingSubtreesList.ToArray(); } + /// + /// Get a config value from the repo's git config. + /// + /// Name of the config entry + /// The config value, or null if not found. + public virtual string GetConfigString(string name) + { + IntPtr configHandle; + if (Native.Config.GetConfig(out configHandle, this.RepoHandle) != Native.ResultCode.Success) + { + throw new LibGit2Exception($"Failed to get config handle: {Native.GetLastError()}"); + } + try + { + string value; + Native.ResultCode resultCode = Native.Config.GetString(out value, configHandle, name); + if (resultCode == Native.ResultCode.NotFound) + { + return null; + } + else if (resultCode != Native.ResultCode.Success) + { + throw new LibGit2Exception($"Failed to get config value for '{name}': {Native.GetLastError()}"); + } + + return value; + } + finally + { + Native.Config.Free(configHandle); + } + } + + public virtual bool? GetConfigBool(string name) + { + IntPtr configHandle; + if (Native.Config.GetConfig(out configHandle, this.RepoHandle) != Native.ResultCode.Success) + { + throw new LibGit2Exception($"Failed to get config handle: {Native.GetLastError()}"); + } + try + { + bool value; + Native.ResultCode resultCode = Native.Config.GetBool(out value, configHandle, name); + if (resultCode == Native.ResultCode.NotFound) + { + return null; + } + else if (resultCode != Native.ResultCode.Success) + { + throw new LibGit2Exception($"Failed to get config value for '{name}': {Native.GetLastError()}"); + } + + return value; + } + finally + { + Native.Config.Free(configHandle); + } + + } + /// /// Determine if the given index of a tree is a subtree and if it is missing. /// If it is a missing subtree, return the SHA of the subtree. @@ -242,7 +304,11 @@ protected virtual void Dispose(bool disposing) public static class Native { - public const uint SuccessCode = 0; + public enum ResultCode : int + { + Success = 0, + NotFound = -3, + } public const string Git2NativeLibName = GVFSConstants.LibGit2LibraryName; @@ -265,7 +331,7 @@ public static GitOid IntPtrToGitOid(IntPtr oidPtr) public static extern int Shutdown(); [DllImport(Git2NativeLibName, EntryPoint = "git_revparse_single")] - public static extern uint RevParseSingle(out IntPtr objectHandle, IntPtr repoHandle, string oid); + public static extern ResultCode RevParseSingle(out IntPtr objectHandle, IntPtr repoHandle, string oid); public static string GetLastError() { @@ -293,12 +359,27 @@ private struct GitError public static class Repo { [DllImport(Git2NativeLibName, EntryPoint = "git_repository_open")] - public static extern uint Open(out IntPtr repoHandle, string path); + public static extern ResultCode Open(out IntPtr repoHandle, string path); [DllImport(Git2NativeLibName, EntryPoint = "git_repository_free")] public static extern void Free(IntPtr repoHandle); } + public static class Config + { + [DllImport(Git2NativeLibName, EntryPoint = "git_repository_config")] + public static extern ResultCode GetConfig(out IntPtr configHandle, IntPtr repoHandle); + + [DllImport(Git2NativeLibName, EntryPoint = "git_config_get_string")] + public static extern ResultCode GetString(out string value, IntPtr configHandle, string name); + + [DllImport(Git2NativeLibName, EntryPoint = "git_config_get_bool")] + public static extern ResultCode GetBool(out bool value, IntPtr configHandle, string name); + + [DllImport(Git2NativeLibName, EntryPoint = "git_config_free")] + public static extern void Free(IntPtr configHandle); + } + public static class Object { [DllImport(Git2NativeLibName, EntryPoint = "git_object_type")] diff --git a/GVFS/GVFS.Common/Git/LibGit2RepoInvoker.cs b/GVFS/GVFS.Common/Git/LibGit2RepoInvoker.cs index 8d3ec2e06c..44b0840498 100644 --- a/GVFS/GVFS.Common/Git/LibGit2RepoInvoker.cs +++ b/GVFS/GVFS.Common/Git/LibGit2RepoInvoker.cs @@ -13,6 +13,11 @@ public class LibGit2RepoInvoker : IDisposable private volatile int activeCallers; private LibGit2Repo sharedRepo; + public LibGit2RepoInvoker(ITracer tracer, string repoPath) + : this(tracer, () => new LibGit2Repo(tracer, repoPath)) + { + } + public LibGit2RepoInvoker(ITracer tracer, Func createRepo) { this.tracer = tracer; @@ -82,6 +87,17 @@ public void InitializeSharedRepo() this.GetSharedRepo()?.ObjectExists("30380be3963a75e4a34e10726795d644659e1129"); } + public bool GetConfigBoolOrDefault(string key, bool defaultValue) + { + bool? value = defaultValue; + if (this.TryInvoke(repo => repo.GetConfigBool(key), out value)) + { + return value ?? defaultValue; + } + + return defaultValue; + } + private LibGit2Repo GetSharedRepo() { lock (this.sharedRepoLock) diff --git a/GVFS/GVFS.Common/GitStatusCache.cs b/GVFS/GVFS.Common/GitStatusCache.cs index 8ef6b37434..e8f5a0f4fb 100644 --- a/GVFS/GVFS.Common/GitStatusCache.cs +++ b/GVFS/GVFS.Common/GitStatusCache.cs @@ -341,7 +341,8 @@ private void RebuildStatusCacheIfNeeded(bool ignoreBackoff) private void UpdateHydrationSummary() { - bool enabled = TEST_EnableHydrationSummaryOverride ?? this.context.Enlistment.GetStatusHydrationConfig(); + bool enabled = TEST_EnableHydrationSummaryOverride + ?? this.context.Repository.LibGit2RepoInvoker.GetConfigBoolOrDefault(GVFSConstants.GitConfig.ShowHydrationStatus, GVFSConstants.GitConfig.ShowHydrationStatusDefault); if (!enabled) { return; diff --git a/GVFS/GVFS.Common/Maintenance/PrefetchStep.cs b/GVFS/GVFS.Common/Maintenance/PrefetchStep.cs index a494ac6cc7..163089afb3 100644 --- a/GVFS/GVFS.Common/Maintenance/PrefetchStep.cs +++ b/GVFS/GVFS.Common/Maintenance/PrefetchStep.cs @@ -56,7 +56,9 @@ public bool TryPrefetchCommitsAndTrees(out string error, GitProcess gitProcess = return false; } - if (!this.GitObjects.TryDownloadPrefetchPacks(gitProcess, maxGoodTimeStamp, out packIndexes)) + var trustPackIndexes = this.Context.Repository.LibGit2RepoInvoker.GetConfigBoolOrDefault(GVFSConstants.GitConfig.TrustPackIndexes, GVFSConstants.GitConfig.TrustPackIndexesDefault); + + if (!this.GitObjects.TryDownloadPrefetchPacks(gitProcess, maxGoodTimeStamp, trustPackIndexes, out packIndexes)) { error = "Failed to download prefetch packs"; return false; diff --git a/GVFS/GVFS.Common/Tracing/NullTracer.cs b/GVFS/GVFS.Common/Tracing/NullTracer.cs new file mode 100644 index 0000000000..8cd5566428 --- /dev/null +++ b/GVFS/GVFS.Common/Tracing/NullTracer.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GVFS.Common.Tracing +{ + /// + /// Empty implementation of ITracer that does nothing + /// + public sealed class NullTracer : ITracer + { + private NullTracer() + { + } + + public static ITracer Instance { get; } = new NullTracer(); + + void IDisposable.Dispose() + { + + } + + void ITracer.RelatedError(EventMetadata metadata, string message) + { + + } + + void ITracer.RelatedError(EventMetadata metadata, string message, Keywords keywords) + { + + } + + void ITracer.RelatedError(string message) + { + + } + + void ITracer.RelatedError(string format, params object[] args) + { + + } + + void ITracer.RelatedEvent(EventLevel level, string eventName, EventMetadata metadata) + { + + } + + void ITracer.RelatedEvent(EventLevel level, string eventName, EventMetadata metadata, Keywords keywords) + { + + } + + void ITracer.RelatedInfo(string message) + { + + } + + void ITracer.RelatedInfo(string format, params object[] args) + { + + } + + void ITracer.RelatedInfo(EventMetadata metadata, string message) + { + + } + + void ITracer.RelatedWarning(EventMetadata metadata, string message) + { + + } + + void ITracer.RelatedWarning(EventMetadata metadata, string message, Keywords keywords) + { + + } + + void ITracer.RelatedWarning(string message) + { + + } + + void ITracer.RelatedWarning(string format, params object[] args) + { + + } + + void ITracer.SetGitCommandSessionId(string sessionId) + { + + } + + ITracer ITracer. StartActivity(string activityName, EventLevel level) + { + return this; + } + + ITracer ITracer. StartActivity(string activityName, EventLevel level, EventMetadata metadata) + { + return this; + } + + ITracer ITracer. StartActivity(string activityName, EventLevel level, Keywords startStopKeywords, EventMetadata metadata) + { + return this; + } + + TimeSpan ITracer.Stop(EventMetadata metadata) + { + return TimeSpan.Zero; + } + } +} diff --git a/GVFS/GVFS.Hooks/GVFS.Hooks.csproj b/GVFS/GVFS.Hooks/GVFS.Hooks.csproj index 23dd6ea97a..9c0956b8bb 100644 --- a/GVFS/GVFS.Hooks/GVFS.Hooks.csproj +++ b/GVFS/GVFS.Hooks/GVFS.Hooks.csproj @@ -3,8 +3,13 @@ Exe net471 + true + + + + + + + + + + + + + diff --git a/GVFS/GVFS.Hooks/Program.cs b/GVFS/GVFS.Hooks/Program.cs index d48230a2ab..151cba9b50 100644 --- a/GVFS/GVFS.Hooks/Program.cs +++ b/GVFS/GVFS.Hooks/Program.cs @@ -1,5 +1,7 @@ using GVFS.Common; +using GVFS.Common.Git; using GVFS.Common.NamedPipes; +using GVFS.Common.Tracing; using GVFS.Hooks.HooksPlatform; using System; using System.Collections.Generic; @@ -19,6 +21,7 @@ public class Program private static string enlistmentRoot; private static string enlistmentPipename; + private static string normalizedCurrentDirectory; private static Random random = new Random(); private delegate void LockRequestDelegate(bool unattended, string[] args, int pid, NamedPipeClient pipeClient); @@ -35,7 +38,6 @@ public static void Main(string[] args) bool unattended = GVFSEnlistment.IsUnattended(tracer: null); string errorMessage; - string normalizedCurrentDirectory; if (!GVFSHooksPlatform.TryGetNormalizedPath(Environment.CurrentDirectory, out normalizedCurrentDirectory, out errorMessage)) { ExitWithError($"Failed to determine final path for current directory {Environment.CurrentDirectory}. Error: {errorMessage}"); @@ -108,19 +110,10 @@ private static bool ArgsBlockHydrationStatus(string[] args) private static bool ConfigurationAllowsHydrationStatus() { - try - { - ProcessResult result = ProcessHelper.Run("git", $"config --get {GVFSConstants.GitConfig.ShowHydrationStatus}"); - bool hydrationStatusEnabled; - if (bool.TryParse(result.Output.Trim(), out hydrationStatusEnabled)) - { - return hydrationStatusEnabled; - } - } - catch (Exception) + using (LibGit2RepoInvoker repo = new LibGit2RepoInvoker(NullTracer.Instance, normalizedCurrentDirectory)) { + return repo.GetConfigBoolOrDefault(GVFSConstants.GitConfig.ShowHydrationStatus, GVFSConstants.GitConfig.ShowHydrationStatusDefault); } - return GVFSConstants.GitConfig.ShowHydrationStatusDefault; } private static void ExitWithError(params string[] messages) diff --git a/GVFS/GVFS.UnitTests/Mock/Git/MockGVFSGitObjects.cs b/GVFS/GVFS.UnitTests/Mock/Git/MockGVFSGitObjects.cs index 47c30d35ea..b95984ecce 100644 --- a/GVFS/GVFS.UnitTests/Mock/Git/MockGVFSGitObjects.cs +++ b/GVFS/GVFS.UnitTests/Mock/Git/MockGVFSGitObjects.cs @@ -71,7 +71,7 @@ public override void DeleteStaleTempPrefetchPackAndIdxs() { } - public override bool TryDownloadPrefetchPacks(GitProcess gitProcess, long latestTimestamp, out List packIndexes) + public override bool TryDownloadPrefetchPacks(GitProcess gitProcess, long latestTimestamp, bool trustPackIndexes, out List packIndexes) { packIndexes = new List(); return true; diff --git a/GVFS/GVFS/CommandLine/CloneVerb.cs b/GVFS/GVFS/CommandLine/CloneVerb.cs index e0d8583609..8bbc5b9fb7 100644 --- a/GVFS/GVFS/CommandLine/CloneVerb.cs +++ b/GVFS/GVFS/CommandLine/CloneVerb.cs @@ -121,6 +121,7 @@ public override void Execute() CacheServerInfo cacheServer = null; ServerGVFSConfig serverGVFSConfig = null; + bool trustPackIndexes; using (JsonTracer tracer = new JsonTracer(GVFSConstants.GVFSEtwProviderName, "GVFSClone")) { @@ -216,13 +217,17 @@ public override void Execute() { tracer.RelatedError(cloneResult.ErrorMessage); } + + using (var repo = new LibGit2RepoInvoker(tracer, enlistment.WorkingDirectoryBackingRoot)) + { + trustPackIndexes = repo.GetConfigBoolOrDefault(GVFSConstants.GitConfig.TrustPackIndexes, GVFSConstants.GitConfig.TrustPackIndexesDefault); + } } if (cloneResult.Success) { if (!this.NoPrefetch) { - bool trustPackIndexes = enlistment.GetTrustPackIndexesConfig(); /* If pack indexes are not trusted, the prefetch can take a long time. * We will run the prefetch command in the background. */