diff --git a/GVFS/GVFS.Common/Git/GitAuthentication.cs b/GVFS/GVFS.Common/Git/GitAuthentication.cs
index 27796f0e8..bb81a86c1 100644
--- a/GVFS/GVFS.Common/Git/GitAuthentication.cs
+++ b/GVFS/GVFS.Common/Git/GitAuthentication.cs
@@ -183,34 +183,98 @@ public bool TryGetCredentials(ITracer tracer, out string credentialString, out s
return true;
}
+ ///
+ /// Initialize authentication by probing the server. Determines whether
+ /// anonymous access is supported and, if not, fetches credentials.
+ /// Callers that also need the GVFS config should use
+ /// instead to avoid a
+ /// redundant HTTP round-trip.
+ ///
public bool TryInitialize(ITracer tracer, Enlistment enlistment, out string errorMessage)
+ {
+ // Delegate to the combined method, discarding the config result.
+ // This avoids duplicating the anonymous-probe + credential-fetch logic.
+ return this.TryInitializeAndQueryGVFSConfig(
+ tracer,
+ enlistment,
+ new RetryConfig(),
+ out _,
+ out errorMessage);
+ }
+
+ ///
+ /// Combines authentication initialization with the GVFS config query,
+ /// eliminating a redundant HTTP round-trip. The anonymous probe and
+ /// config query use the same request to /gvfs/config:
+ /// 1. Config query → /gvfs/config → 200 (anonymous) or 401
+ /// 2. If 401: credential fetch, then retry → 200
+ /// This saves one HTTP request compared to probing auth separately
+ /// and then querying config, and reuses the same TCP/TLS connection.
+ ///
+ public bool TryInitializeAndQueryGVFSConfig(
+ ITracer tracer,
+ Enlistment enlistment,
+ RetryConfig retryConfig,
+ out ServerGVFSConfig serverGVFSConfig,
+ out string errorMessage)
{
if (this.isInitialized)
{
throw new InvalidOperationException("Already initialized");
}
+ serverGVFSConfig = null;
errorMessage = null;
- bool isAnonymous;
- if (!this.TryAnonymousQuery(tracer, enlistment, out isAnonymous))
+ using (ConfigHttpRequestor configRequestor = new ConfigHttpRequestor(tracer, enlistment, retryConfig))
{
- errorMessage = $"Unable to determine if authentication is required";
- return false;
- }
+ HttpStatusCode? httpStatus;
- if (!isAnonymous &&
- !this.TryCallGitCredential(tracer, out errorMessage))
- {
+ // First attempt without credentials. If anonymous access works,
+ // we get the config in a single request.
+ if (configRequestor.TryQueryGVFSConfig(false, out serverGVFSConfig, out httpStatus, out _))
+ {
+ this.IsAnonymous = true;
+ this.isInitialized = true;
+ tracer.RelatedInfo("{0}: Anonymous access succeeded, config obtained in one request", nameof(this.TryInitializeAndQueryGVFSConfig));
+ return true;
+ }
+
+ if (httpStatus != HttpStatusCode.Unauthorized)
+ {
+ errorMessage = "Unable to query /gvfs/config";
+ tracer.RelatedWarning("{0}: Config query failed with status {1}", nameof(this.TryInitializeAndQueryGVFSConfig), httpStatus?.ToString() ?? "None");
+ return false;
+ }
+
+ // Server requires authentication — fetch credentials
+ this.IsAnonymous = false;
+
+ if (!this.TryCallGitCredential(tracer, out errorMessage))
+ {
+ tracer.RelatedWarning("{0}: Credential fetch failed: {1}", nameof(this.TryInitializeAndQueryGVFSConfig), errorMessage);
+ return false;
+ }
+
+ this.isInitialized = true;
+
+ // Retry with credentials using the same ConfigHttpRequestor (reuses HttpClient/connection)
+ if (configRequestor.TryQueryGVFSConfig(true, out serverGVFSConfig, out _, out errorMessage))
+ {
+ tracer.RelatedInfo("{0}: Config obtained with credentials", nameof(this.TryInitializeAndQueryGVFSConfig));
+ return true;
+ }
+
+ tracer.RelatedWarning("{0}: Config query failed with credentials: {1}", nameof(this.TryInitializeAndQueryGVFSConfig), errorMessage);
return false;
}
-
- this.IsAnonymous = isAnonymous;
- this.isInitialized = true;
- return true;
}
- public bool TryInitializeAndRequireAuth(ITracer tracer, out string errorMessage)
+ ///
+ /// Test-only initialization that skips the network probe and goes
+ /// straight to credential fetch. Not for production use.
+ ///
+ internal bool TryInitializeAndRequireAuth(ITracer tracer, out string errorMessage)
{
if (this.isInitialized)
{
@@ -267,45 +331,6 @@ private static bool TryParseCredentialString(string credentialString, out string
return false;
}
- private bool TryAnonymousQuery(ITracer tracer, Enlistment enlistment, out bool isAnonymous)
- {
- bool querySucceeded;
- using (ITracer anonymousTracer = tracer.StartActivity("AttemptAnonymousAuth", EventLevel.Informational))
- {
- HttpStatusCode? httpStatus;
-
- using (ConfigHttpRequestor configRequestor = new ConfigHttpRequestor(anonymousTracer, enlistment, new RetryConfig()))
- {
- ServerGVFSConfig gvfsConfig;
- const bool LogErrors = false;
- if (configRequestor.TryQueryGVFSConfig(LogErrors, out gvfsConfig, out httpStatus, out _))
- {
- querySucceeded = true;
- isAnonymous = true;
- }
- else if (httpStatus == HttpStatusCode.Unauthorized)
- {
- querySucceeded = true;
- isAnonymous = false;
- }
- else
- {
- querySucceeded = false;
- isAnonymous = false;
- }
- }
-
- anonymousTracer.Stop(new EventMetadata
- {
- { "HttpStatus", httpStatus.HasValue ? ((int)httpStatus).ToString() : "None" },
- { "QuerySucceeded", querySucceeded },
- { "IsAnonymous", isAnonymous },
- });
- }
-
- return querySucceeded;
- }
-
private DateTime GetNextAuthAttemptTime()
{
if (this.numberOfAttempts <= 1)
diff --git a/GVFS/GVFS.Common/Git/GitCoreGVFSFlags.cs b/GVFS/GVFS.Common/Git/GitCoreGVFSFlags.cs
new file mode 100644
index 000000000..411c5bc3c
--- /dev/null
+++ b/GVFS/GVFS.Common/Git/GitCoreGVFSFlags.cs
@@ -0,0 +1,58 @@
+using System;
+
+namespace GVFS.Common.Git
+{
+ [Flags]
+ public enum GitCoreGVFSFlags
+ {
+ // GVFS_SKIP_SHA_ON_INDEX
+ // Disables the calculation of the sha when writing the index
+ SkipShaOnIndex = 1 << 0,
+
+ // GVFS_BLOCK_COMMANDS
+ // Blocks git commands that are not allowed in a GVFS/Scalar repo
+ BlockCommands = 1 << 1,
+
+ // GVFS_MISSING_OK
+ // Normally git write-tree ensures that the objects referenced by the
+ // directory exist in the object database.This option disables this check.
+ MissingOk = 1 << 2,
+
+ // GVFS_NO_DELETE_OUTSIDE_SPARSECHECKOUT
+ // When marking entries to remove from the index and the working
+ // directory this option will take into account what the
+ // skip-worktree bit was set to so that if the entry has the
+ // skip-worktree bit set it will not be removed from the working
+ // directory. This will allow virtualized working directories to
+ // detect the change to HEAD and use the new commit tree to show
+ // the files that are in the working directory.
+ NoDeleteOutsideSparseCheckout = 1 << 3,
+
+ // GVFS_FETCH_SKIP_REACHABILITY_AND_UPLOADPACK
+ // While performing a fetch with a virtual file system we know
+ // that there will be missing objects and we don't want to download
+ // them just because of the reachability of the commits. We also
+ // don't want to download a pack file with commits, trees, and blobs
+ // since these will be downloaded on demand. This flag will skip the
+ // checks on the reachability of objects during a fetch as well as
+ // the upload pack so that extraneous objects don't get downloaded.
+ FetchSkipReachabilityAndUploadPack = 1 << 4,
+
+ // 1 << 5 has been deprecated
+
+ // GVFS_BLOCK_FILTERS_AND_EOL_CONVERSIONS
+ // With a virtual file system we only know the file size before any
+ // CRLF or smudge/clean filters processing is done on the client.
+ // To prevent file corruption due to truncation or expansion with
+ // garbage at the end, these filters must not run when the file
+ // is first accessed and brought down to the client. Git.exe can't
+ // currently tell the first access vs subsequent accesses so this
+ // flag just blocks them from occurring at all.
+ BlockFiltersAndEolConversions = 1 << 6,
+
+ // GVFS_PREFETCH_DURING_FETCH
+ // While performing a `git fetch` command, use the gvfs-helper to
+ // perform a "prefetch" of commits and trees.
+ PrefetchDuringFetch = 1 << 7,
+ }
+}
diff --git a/GVFS/GVFS.Mount/InProcessMount.cs b/GVFS/GVFS.Mount/InProcessMount.cs
index e6d43a842..c56627703 100644
--- a/GVFS/GVFS.Mount/InProcessMount.cs
+++ b/GVFS/GVFS.Mount/InProcessMount.cs
@@ -15,8 +15,10 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Security;
using System.Text;
using System.Threading;
+using System.Threading.Tasks;
using static GVFS.Common.Git.LibGit2Repo;
namespace GVFS.Mount
@@ -83,6 +85,40 @@ public void Mount(EventLevel verbosity, Keywords keywords)
{
this.currentState = MountState.Mounting;
+ // Start auth + config query immediately — these are network-bound and don't
+ // depend on repo metadata or cache paths. Every millisecond of network latency
+ // we can overlap with local I/O is a win.
+ // TryInitializeAndQueryGVFSConfig combines the anonymous probe, credential fetch,
+ // and config query into at most 2 HTTP requests (1 for anonymous repos), reusing
+ // the same HttpClient/TCP connection.
+ Stopwatch parallelTimer = Stopwatch.StartNew();
+
+ var networkTask = Task.Run(() =>
+ {
+ Stopwatch sw = Stopwatch.StartNew();
+ ServerGVFSConfig config;
+ string authConfigError;
+
+ if (!this.enlistment.Authentication.TryInitializeAndQueryGVFSConfig(
+ this.tracer, this.enlistment, this.retryConfig,
+ out config, out authConfigError))
+ {
+ if (this.cacheServer != null && !string.IsNullOrWhiteSpace(this.cacheServer.Url))
+ {
+ this.tracer.RelatedWarning("Mount will proceed with fallback cache server: " + authConfigError);
+ config = null;
+ }
+ else
+ {
+ this.FailMountAndExit("Unable to query /gvfs/config" + Environment.NewLine + authConfigError);
+ }
+ }
+
+ this.ValidateGVFSVersion(config);
+ this.tracer.RelatedInfo("ParallelMount: Auth + config completed in {0}ms", sw.ElapsedMilliseconds);
+ return config;
+ });
+
// We must initialize repo metadata before starting the pipe server so it
// can immediately handle status requests
string error;
@@ -121,6 +157,58 @@ public void Mount(EventLevel verbosity, Keywords keywords)
this.enlistment.InitializeCachePaths(localCacheRoot, gitObjectsRoot, blobSizesRoot);
+ // Local validations and git config run while we wait for the network
+ var localTask = Task.Run(() =>
+ {
+ Stopwatch sw = Stopwatch.StartNew();
+
+ this.ValidateGitVersion();
+ this.tracer.RelatedInfo("ParallelMount: ValidateGitVersion completed in {0}ms", sw.ElapsedMilliseconds);
+
+ this.ValidateHooksVersion();
+ this.ValidateFileSystemSupportsRequiredFeatures();
+
+ GitProcess git = new GitProcess(this.enlistment);
+ if (!git.IsValidRepo())
+ {
+ this.FailMountAndExit("The .git folder is missing or has invalid contents");
+ }
+
+ if (!GVFSPlatform.Instance.FileSystem.IsFileSystemSupported(this.enlistment.EnlistmentRoot, out string fsError))
+ {
+ this.FailMountAndExit("FileSystem unsupported: " + fsError);
+ }
+
+ this.tracer.RelatedInfo("ParallelMount: Local validations completed in {0}ms", sw.ElapsedMilliseconds);
+
+ if (!this.TrySetRequiredGitConfigSettings())
+ {
+ this.FailMountAndExit("Unable to configure git repo");
+ }
+
+ this.LogEnlistmentInfoAndSetConfigValues();
+ this.tracer.RelatedInfo("ParallelMount: Local validations + git config completed in {0}ms", sw.ElapsedMilliseconds);
+ });
+
+ try
+ {
+ Task.WaitAll(networkTask, localTask);
+ }
+ catch (AggregateException ae)
+ {
+ this.FailMountAndExit(ae.Flatten().InnerExceptions[0].Message);
+ }
+
+ parallelTimer.Stop();
+ this.tracer.RelatedInfo("ParallelMount: All parallel tasks completed in {0}ms", parallelTimer.ElapsedMilliseconds);
+
+ ServerGVFSConfig serverGVFSConfig = networkTask.Result;
+
+ CacheServerResolver cacheServerResolver = new CacheServerResolver(this.tracer, this.enlistment);
+ this.cacheServer = cacheServerResolver.ResolveNameFromRemote(this.cacheServer.Url, serverGVFSConfig);
+
+ this.EnsureLocalCacheIsHealthy(serverGVFSConfig);
+
using (NamedPipeServer pipeServer = this.StartNamedPipe())
{
this.tracer.RelatedEvent(
@@ -772,13 +860,6 @@ private void HandleUnmountRequest(NamedPipeServer.Connection connection)
private void MountAndStartWorkingDirectoryCallbacks(CacheServerInfo cache, bool alreadyInitialized = false)
{
string error;
- if (!alreadyInitialized)
- {
- if (!this.context.Enlistment.Authentication.TryInitialize(this.context.Tracer, this.context.Enlistment, out error))
- {
- this.FailMountAndExit("Failed to obtain git credentials: " + error);
- }
- }
GitObjectsHttpRequestor objectRequestor = new GitObjectsHttpRequestor(this.context.Tracer, this.context.Enlistment, cache, this.retryConfig);
this.gitObjects = new GVFSGitObjects(this.context, objectRequestor);
@@ -846,6 +927,462 @@ private void MountAndStartWorkingDirectoryCallbacks(CacheServerInfo cache, bool
this.heartbeat.Start();
}
+ private void ValidateGitVersion()
+ {
+ GitVersion gitVersion = null;
+ if (string.IsNullOrEmpty(this.enlistment.GitBinPath) || !GitProcess.TryGetVersion(this.enlistment.GitBinPath, out gitVersion, out string _))
+ {
+ this.FailMountAndExit("Error: Unable to retrieve the Git version");
+ }
+
+ this.enlistment.SetGitVersion(gitVersion.ToString());
+
+ if (gitVersion.Platform != GVFSConstants.SupportedGitVersion.Platform)
+ {
+ this.FailMountAndExit("Error: Invalid version of Git {0}. Must use vfs version.", gitVersion);
+ }
+
+ if (gitVersion.IsLessThan(GVFSConstants.SupportedGitVersion))
+ {
+ this.FailMountAndExit(
+ "Error: Installed Git version {0} is less than the minimum supported version of {1}.",
+ gitVersion,
+ GVFSConstants.SupportedGitVersion);
+ }
+ else if (gitVersion.Revision != GVFSConstants.SupportedGitVersion.Revision)
+ {
+ this.FailMountAndExit(
+ "Error: Installed Git version {0} has revision number {1} instead of {2}."
+ + " This Git version is too new, so either downgrade Git or upgrade VFS for Git."
+ + " The minimum supported version of Git is {3}.",
+ gitVersion,
+ gitVersion.Revision,
+ GVFSConstants.SupportedGitVersion.Revision,
+ GVFSConstants.SupportedGitVersion);
+ }
+ }
+
+ private void ValidateHooksVersion()
+ {
+ string hooksVersion;
+ string error;
+ if (!GVFSPlatform.Instance.TryGetGVFSHooksVersion(out hooksVersion, out error))
+ {
+ this.FailMountAndExit(error);
+ }
+
+ string gvfsVersion = ProcessHelper.GetCurrentProcessVersion();
+ if (hooksVersion != gvfsVersion)
+ {
+ this.FailMountAndExit("GVFS.Hooks version ({0}) does not match GVFS version ({1}).", hooksVersion, gvfsVersion);
+ }
+
+ this.enlistment.SetGVFSHooksVersion(hooksVersion);
+ }
+
+ private void ValidateFileSystemSupportsRequiredFeatures()
+ {
+ try
+ {
+ string warning;
+ string error;
+ if (!GVFSPlatform.Instance.KernelDriver.IsSupported(this.enlistment.EnlistmentRoot, out warning, out error))
+ {
+ this.FailMountAndExit("Error: {0}", error);
+ }
+ }
+ catch (Exception e)
+ {
+ EventMetadata metadata = new EventMetadata();
+ metadata.Add("Exception", e.ToString());
+ this.tracer.RelatedError(metadata, "Failed to determine if file system supports features required by GVFS");
+ this.FailMountAndExit("Error: Failed to determine if file system supports features required by GVFS.");
+ }
+ }
+
+ private ServerGVFSConfig QueryAndValidateGVFSConfig()
+ {
+ ServerGVFSConfig serverGVFSConfig = null;
+ string errorMessage = null;
+
+ using (ConfigHttpRequestor configRequestor = new ConfigHttpRequestor(this.tracer, this.enlistment, this.retryConfig))
+ {
+ const bool LogErrors = true;
+ if (!configRequestor.TryQueryGVFSConfig(LogErrors, out serverGVFSConfig, out _, out errorMessage))
+ {
+ // If we have a valid cache server, continue without config (matches verb fallback behavior)
+ if (this.cacheServer != null && !string.IsNullOrWhiteSpace(this.cacheServer.Url))
+ {
+ this.tracer.RelatedWarning("Unable to query /gvfs/config: " + errorMessage);
+ serverGVFSConfig = null;
+ }
+ else
+ {
+ this.FailMountAndExit("Unable to query /gvfs/config" + Environment.NewLine + errorMessage);
+ }
+ }
+ }
+
+ this.ValidateGVFSVersion(serverGVFSConfig);
+
+ return serverGVFSConfig;
+ }
+
+ private void ValidateGVFSVersion(ServerGVFSConfig config)
+ {
+ using (ITracer activity = this.tracer.StartActivity("ValidateGVFSVersion", EventLevel.Informational))
+ {
+ if (ProcessHelper.IsDevelopmentVersion())
+ {
+ return;
+ }
+
+ string recordedVersion = ProcessHelper.GetCurrentProcessVersion();
+ int plus = recordedVersion.IndexOf('+');
+ Version currentVersion = new Version(plus < 0 ? recordedVersion : recordedVersion.Substring(0, plus));
+ IEnumerable allowedGvfsClientVersions =
+ config != null
+ ? config.AllowedGVFSClientVersions
+ : null;
+
+ if (allowedGvfsClientVersions == null || !allowedGvfsClientVersions.Any())
+ {
+ string warningMessage = "WARNING: Unable to validate your GVFS version" + Environment.NewLine;
+ if (config == null)
+ {
+ warningMessage += "Could not query valid GVFS versions from: " + Uri.EscapeUriString(this.enlistment.RepoUrl);
+ }
+ else
+ {
+ warningMessage += "Server not configured to provide supported GVFS versions";
+ }
+
+ this.tracer.RelatedWarning(warningMessage);
+ return;
+ }
+
+ foreach (ServerGVFSConfig.VersionRange versionRange in config.AllowedGVFSClientVersions)
+ {
+ if (currentVersion >= versionRange.Min &&
+ (versionRange.Max == null || currentVersion <= versionRange.Max))
+ {
+ activity.RelatedEvent(
+ EventLevel.Informational,
+ "GVFSVersionValidated",
+ new EventMetadata
+ {
+ { "SupportedVersionRange", versionRange },
+ });
+
+ this.enlistment.SetGVFSVersion(currentVersion.ToString());
+ return;
+ }
+ }
+
+ activity.RelatedError("GVFS version {0} is not supported", currentVersion);
+ this.FailMountAndExit("ERROR: Your GVFS version is no longer supported. Install the latest and try again.");
+ }
+ }
+
+ private void EnsureLocalCacheIsHealthy(ServerGVFSConfig serverGVFSConfig)
+ {
+ if (!Directory.Exists(this.enlistment.LocalCacheRoot))
+ {
+ try
+ {
+ this.tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Local cache root: {this.enlistment.LocalCacheRoot} missing, recreating it");
+ Directory.CreateDirectory(this.enlistment.LocalCacheRoot);
+ }
+ catch (Exception e)
+ {
+ EventMetadata metadata = new EventMetadata();
+ metadata.Add("Exception", e.ToString());
+ metadata.Add("enlistment.LocalCacheRoot", this.enlistment.LocalCacheRoot);
+ this.tracer.RelatedError(metadata, $"{nameof(this.EnsureLocalCacheIsHealthy)}: Exception while trying to create local cache root");
+ this.FailMountAndExit("Failed to create local cache: " + this.enlistment.LocalCacheRoot);
+ }
+ }
+
+ PhysicalFileSystem fileSystem = new PhysicalFileSystem();
+ if (Directory.Exists(this.enlistment.GitObjectsRoot))
+ {
+ bool gitObjectsRootInAlternates = false;
+ string alternatesFilePath = Path.Combine(this.enlistment.WorkingDirectoryBackingRoot, GVFSConstants.DotGit.Objects.Info.Alternates);
+ if (File.Exists(alternatesFilePath))
+ {
+ try
+ {
+ using (Stream stream = fileSystem.OpenFileStream(
+ alternatesFilePath,
+ FileMode.Open,
+ FileAccess.Read,
+ FileShare.ReadWrite,
+ callFlushFileBuffers: false))
+ {
+ using (StreamReader reader = new StreamReader(stream))
+ {
+ while (!reader.EndOfStream)
+ {
+ string alternatesLine = reader.ReadLine();
+ if (string.Equals(alternatesLine, this.enlistment.GitObjectsRoot, GVFSPlatform.Instance.Constants.PathComparison))
+ {
+ gitObjectsRootInAlternates = true;
+ }
+ }
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ EventMetadata exceptionMetadata = new EventMetadata();
+ exceptionMetadata.Add("Exception", e.ToString());
+ this.tracer.RelatedError(exceptionMetadata, $"{nameof(this.EnsureLocalCacheIsHealthy)}: Exception while trying to validate alternates file");
+ this.FailMountAndExit($"Failed to validate that alternates file includes git objects root: {e.Message}");
+ }
+ }
+ else
+ {
+ this.tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Alternates file not found");
+ }
+
+ if (!gitObjectsRootInAlternates)
+ {
+ this.tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: GitObjectsRoot ({this.enlistment.GitObjectsRoot}) missing from alternates files, recreating alternates");
+ string error;
+ if (!this.TryCreateAlternatesFile(fileSystem, out error))
+ {
+ this.FailMountAndExit($"Failed to update alternates file to include git objects root: {error}");
+ }
+ }
+ }
+ else
+ {
+ this.tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: GitObjectsRoot ({this.enlistment.GitObjectsRoot}) missing, determining new root");
+
+ if (serverGVFSConfig == null)
+ {
+ using (ConfigHttpRequestor configRequestor = new ConfigHttpRequestor(this.tracer, this.enlistment, this.retryConfig))
+ {
+ string configError;
+ if (!configRequestor.TryQueryGVFSConfig(true, out serverGVFSConfig, out _, out configError))
+ {
+ this.FailMountAndExit("Unable to query /gvfs/config" + Environment.NewLine + configError);
+ }
+ }
+ }
+
+ string localCacheKey;
+ string error;
+ LocalCacheResolver localCacheResolver = new LocalCacheResolver(this.enlistment);
+ if (!localCacheResolver.TryGetLocalCacheKeyFromLocalConfigOrRemoteCacheServers(
+ this.tracer,
+ serverGVFSConfig,
+ this.cacheServer,
+ this.enlistment.LocalCacheRoot,
+ localCacheKey: out localCacheKey,
+ errorMessage: out error))
+ {
+ this.FailMountAndExit($"Previous git objects root ({this.enlistment.GitObjectsRoot}) not found, and failed to determine new local cache key: {error}");
+ }
+
+ EventMetadata keyMetadata = new EventMetadata();
+ keyMetadata.Add("localCacheRoot", this.enlistment.LocalCacheRoot);
+ keyMetadata.Add("localCacheKey", localCacheKey);
+ keyMetadata.Add(TracingConstants.MessageKey.InfoMessage, "Initializing and persisting updated paths");
+ this.tracer.RelatedEvent(EventLevel.Informational, "EnsureLocalCacheIsHealthy_InitializePathsFromKey", keyMetadata);
+ this.enlistment.InitializeCachePathsFromKey(this.enlistment.LocalCacheRoot, localCacheKey);
+
+ this.tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Creating GitObjectsRoot ({this.enlistment.GitObjectsRoot}), GitPackRoot ({this.enlistment.GitPackRoot}), and BlobSizesRoot ({this.enlistment.BlobSizesRoot})");
+ try
+ {
+ Directory.CreateDirectory(this.enlistment.GitObjectsRoot);
+ Directory.CreateDirectory(this.enlistment.GitPackRoot);
+ }
+ catch (Exception e)
+ {
+ EventMetadata exceptionMetadata = new EventMetadata();
+ exceptionMetadata.Add("Exception", e.ToString());
+ exceptionMetadata.Add("enlistment.GitObjectsRoot", this.enlistment.GitObjectsRoot);
+ exceptionMetadata.Add("enlistment.GitPackRoot", this.enlistment.GitPackRoot);
+ this.tracer.RelatedError(exceptionMetadata, $"{nameof(this.EnsureLocalCacheIsHealthy)}: Exception while trying to create objects and pack folders");
+ this.FailMountAndExit("Failed to create objects and pack folders");
+ }
+
+ this.tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Creating new alternates file");
+ if (!this.TryCreateAlternatesFile(fileSystem, out error))
+ {
+ this.FailMountAndExit($"Failed to update alternates file with new objects path: {error}");
+ }
+
+ this.tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Saving git objects root ({this.enlistment.GitObjectsRoot}) in repo metadata");
+ RepoMetadata.Instance.SetGitObjectsRoot(this.enlistment.GitObjectsRoot);
+
+ this.tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: Saving blob sizes root ({this.enlistment.BlobSizesRoot}) in repo metadata");
+ RepoMetadata.Instance.SetBlobSizesRoot(this.enlistment.BlobSizesRoot);
+ }
+
+ if (!Directory.Exists(this.enlistment.BlobSizesRoot))
+ {
+ this.tracer.RelatedInfo($"{nameof(this.EnsureLocalCacheIsHealthy)}: BlobSizesRoot ({this.enlistment.BlobSizesRoot}) not found, re-creating");
+ try
+ {
+ Directory.CreateDirectory(this.enlistment.BlobSizesRoot);
+ }
+ catch (Exception e)
+ {
+ EventMetadata exceptionMetadata = new EventMetadata();
+ exceptionMetadata.Add("Exception", e.ToString());
+ exceptionMetadata.Add("enlistment.BlobSizesRoot", this.enlistment.BlobSizesRoot);
+ this.tracer.RelatedError(exceptionMetadata, $"{nameof(this.EnsureLocalCacheIsHealthy)}: Exception while trying to create blob sizes folder");
+ this.FailMountAndExit("Failed to create blob sizes folder");
+ }
+ }
+ }
+
+ private bool TryCreateAlternatesFile(PhysicalFileSystem fileSystem, out string errorMessage)
+ {
+ try
+ {
+ string alternatesFilePath = Path.Combine(this.enlistment.WorkingDirectoryBackingRoot, GVFSConstants.DotGit.Objects.Info.Alternates);
+ string tempFilePath = alternatesFilePath + ".tmp";
+ fileSystem.WriteAllText(tempFilePath, this.enlistment.GitObjectsRoot);
+ fileSystem.MoveAndOverwriteFile(tempFilePath, alternatesFilePath);
+ }
+ catch (SecurityException e) { errorMessage = e.Message; return false; }
+ catch (IOException e) { errorMessage = e.Message; return false; }
+
+ errorMessage = null;
+ return true;
+ }
+
+
+ private bool TrySetRequiredGitConfigSettings()
+ {
+ string expectedHooksPath = Path.Combine(this.enlistment.WorkingDirectoryBackingRoot, GVFSConstants.DotGit.Hooks.Root);
+ expectedHooksPath = Paths.ConvertPathToGitFormat(expectedHooksPath);
+
+ string gitStatusCachePath = null;
+ if (!GVFSEnlistment.IsUnattended(tracer: null) && GVFSPlatform.Instance.IsGitStatusCacheSupported())
+ {
+ gitStatusCachePath = Path.Combine(
+ this.enlistment.EnlistmentRoot,
+ GVFSPlatform.Instance.Constants.DotGVFSRoot,
+ GVFSConstants.DotGVFS.GitStatusCache.CachePath);
+
+ gitStatusCachePath = Paths.ConvertPathToGitFormat(gitStatusCachePath);
+ }
+
+ string coreGVFSFlags = Convert.ToInt32(
+ GitCoreGVFSFlags.SkipShaOnIndex |
+ GitCoreGVFSFlags.BlockCommands |
+ GitCoreGVFSFlags.MissingOk |
+ GitCoreGVFSFlags.NoDeleteOutsideSparseCheckout |
+ GitCoreGVFSFlags.FetchSkipReachabilityAndUploadPack |
+ GitCoreGVFSFlags.BlockFiltersAndEolConversions)
+ .ToString();
+
+ Dictionary requiredSettings = new Dictionary
+ {
+ { "am.keepcr", "true" },
+ { "checkout.optimizenewbranch", "true" },
+ { "core.autocrlf", "false" },
+ { "core.commitGraph", "true" },
+ { "core.fscache", "true" },
+ { "core.gvfs", coreGVFSFlags },
+ { "core.multiPackIndex", "true" },
+ { "core.preloadIndex", "true" },
+ { "core.safecrlf", "false" },
+ { "core.untrackedCache", "false" },
+ { "core.repositoryformatversion", "0" },
+ { "core.filemode", GVFSPlatform.Instance.FileSystem.SupportsFileMode ? "true" : "false" },
+ { "core.bare", "false" },
+ { "core.logallrefupdates", "true" },
+ { GitConfigSetting.CoreVirtualizeObjectsName, "true" },
+ { GitConfigSetting.CoreVirtualFileSystemName, Paths.ConvertPathToGitFormat(GVFSConstants.DotGit.Hooks.VirtualFileSystemPath) },
+ { "core.hookspath", expectedHooksPath },
+ { GitConfigSetting.CredentialUseHttpPath, "true" },
+ { "credential.validate", "false" },
+ { "diff.autoRefreshIndex", "true" },
+ { "feature.manyFiles", "false" },
+ { "feature.experimental", "false" },
+ { "fetch.writeCommitGraph", "false" },
+ { "gc.auto", "0" },
+ { "gui.gcwarning", "false" },
+ { "index.threads", "true" },
+ { "index.version", "4" },
+ { "merge.stat", "false" },
+ { "merge.renames", "false" },
+ { "pack.useBitmaps", "false" },
+ { "pack.useSparse", "true" },
+ { "receive.autogc", "false" },
+ { "reset.quiet", "true" },
+ { "status.deserializePath", gitStatusCachePath },
+ { "status.submoduleSummary", "false" },
+ { "commitGraph.generationVersion", "1" },
+ { "core.useBuiltinFSMonitor", "false" },
+ };
+
+ GitProcess git = new GitProcess(this.enlistment);
+
+ Dictionary existingConfigSettings;
+ if (!git.TryGetAllConfig(localOnly: true, configSettings: out existingConfigSettings))
+ {
+ return false;
+ }
+
+ foreach (KeyValuePair setting in requiredSettings)
+ {
+ GitConfigSetting existingSetting;
+ if (setting.Value != null)
+ {
+ if (!existingConfigSettings.TryGetValue(setting.Key, out existingSetting) ||
+ !existingSetting.HasValue(setting.Value))
+ {
+ GitProcess.Result setConfigResult = git.SetInLocalConfig(setting.Key, setting.Value);
+ if (setConfigResult.ExitCodeIsFailure)
+ {
+ return false;
+ }
+ }
+ }
+ else
+ {
+ if (existingConfigSettings.TryGetValue(setting.Key, out existingSetting))
+ {
+ git.DeleteFromLocalConfig(setting.Key);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private void LogEnlistmentInfoAndSetConfigValues()
+ {
+ string mountId = Guid.NewGuid().ToString("N");
+ EventMetadata metadata = new EventMetadata();
+ metadata.Add(nameof(RepoMetadata.Instance.EnlistmentId), RepoMetadata.Instance.EnlistmentId);
+ metadata.Add(nameof(mountId), mountId);
+ metadata.Add("Enlistment", this.enlistment);
+ metadata.Add("PhysicalDiskInfo", GVFSPlatform.Instance.GetPhysicalDiskInfo(this.enlistment.WorkingDirectoryRoot, sizeStatsOnly: false));
+ this.tracer.RelatedEvent(EventLevel.Informational, "EnlistmentInfo", metadata, Keywords.Telemetry);
+
+ GitProcess git = new GitProcess(this.enlistment);
+ GitProcess.Result configResult = git.SetInLocalConfig(GVFSConstants.GitConfig.EnlistmentId, RepoMetadata.Instance.EnlistmentId, replaceAll: true);
+ if (configResult.ExitCodeIsFailure)
+ {
+ string error = "Could not update config with enlistment id, error: " + configResult.Errors;
+ this.tracer.RelatedWarning(error);
+ }
+
+ configResult = git.SetInLocalConfig(GVFSConstants.GitConfig.MountId, mountId, replaceAll: true);
+ if (configResult.ExitCodeIsFailure)
+ {
+ string error = "Could not update config with mount id, error: " + configResult.Errors;
+ this.tracer.RelatedWarning(error);
+ }
+ }
+
private void UnmountAndStopWorkingDirectoryCallbacks(bool willRemountInSameProcess = false)
{
if (this.maintenanceScheduler != null)
diff --git a/GVFS/GVFS/CommandLine/CacheServerVerb.cs b/GVFS/GVFS/CommandLine/CacheServerVerb.cs
index 86754ae67..9fedad0b0 100644
--- a/GVFS/GVFS/CommandLine/CacheServerVerb.cs
+++ b/GVFS/GVFS/CommandLine/CacheServerVerb.cs
@@ -42,12 +42,6 @@ protected override void Execute(GVFSEnlistment enlistment)
using (ITracer tracer = new JsonTracer(GVFSConstants.GVFSEtwProviderName, "CacheVerb"))
{
- string authErrorMessage;
- if (!this.TryAuthenticate(tracer, enlistment, out authErrorMessage))
- {
- this.ReportErrorAndExit(tracer, "Authentication failed: " + authErrorMessage);
- }
-
CacheServerResolver cacheServerResolver = new CacheServerResolver(tracer, enlistment);
ServerGVFSConfig serverGVFSConfig = null;
string error = null;
@@ -55,8 +49,12 @@ protected override void Execute(GVFSEnlistment enlistment)
// Handle the three operation types: list, set, and get (default)
if (this.ListCacheServers)
{
- // For listing, require config endpoint to succeed
- serverGVFSConfig = this.QueryGVFSConfig(tracer, enlistment, retryConfig);
+ // For listing, require config endpoint to succeed (no fallback)
+ if (!this.TryAuthenticateAndQueryGVFSConfig(
+ tracer, enlistment, retryConfig, out serverGVFSConfig, out error))
+ {
+ this.ReportErrorAndExit(tracer, "Unable to query /gvfs/config" + Environment.NewLine + error);
+ }
List cacheServers = serverGVFSConfig.CacheServers.ToList();
@@ -80,11 +78,12 @@ protected override void Execute(GVFSEnlistment enlistment)
CacheServerInfo cacheServer = cacheServerResolver.ParseUrlOrFriendlyName(this.CacheToSet);
// For set operation, allow fallback if config endpoint fails but cache server URL is valid
- serverGVFSConfig = this.QueryGVFSConfigWithFallbackCacheServer(
- tracer,
- enlistment,
- retryConfig,
- cacheServer);
+ if (!this.TryAuthenticateAndQueryGVFSConfig(
+ tracer, enlistment, retryConfig, out serverGVFSConfig, out error,
+ fallbackCacheServer: cacheServer))
+ {
+ this.ReportErrorAndExit(tracer, "Authentication failed: " + error);
+ }
cacheServer = this.ResolveCacheServer(tracer, cacheServer, cacheServerResolver, serverGVFSConfig);
@@ -101,11 +100,12 @@ protected override void Execute(GVFSEnlistment enlistment)
CacheServerInfo cacheServer = CacheServerResolver.GetCacheServerFromConfig(enlistment);
// For get operation, allow fallback if config endpoint fails but cache server URL is valid
- serverGVFSConfig =this.QueryGVFSConfigWithFallbackCacheServer(
- tracer,
- enlistment,
- retryConfig,
- cacheServer);
+ if (!this.TryAuthenticateAndQueryGVFSConfig(
+ tracer, enlistment, retryConfig, out serverGVFSConfig, out error,
+ fallbackCacheServer: cacheServer))
+ {
+ this.ReportErrorAndExit(tracer, "Authentication failed: " + error);
+ }
CacheServerInfo resolvedCacheServer = cacheServerResolver.ResolveNameFromRemote(cacheServer.Url, serverGVFSConfig);
diff --git a/GVFS/GVFS/CommandLine/CloneVerb.cs b/GVFS/GVFS/CommandLine/CloneVerb.cs
index 8bbc5b9fb..bd37c7d4b 100644
--- a/GVFS/GVFS/CommandLine/CloneVerb.cs
+++ b/GVFS/GVFS/CommandLine/CloneVerb.cs
@@ -185,19 +185,19 @@ public override void Execute()
this.Output.WriteLine(" Local Cache: " + resolvedLocalCacheRoot);
this.Output.WriteLine(" Destination: " + enlistment.EnlistmentRoot);
- string authErrorMessage;
- if (!this.TryAuthenticate(tracer, enlistment, out authErrorMessage))
- {
- this.ReportErrorAndExit(tracer, "Cannot clone because authentication failed: " + authErrorMessage);
- }
-
RetryConfig retryConfig = this.GetRetryConfig(tracer, enlistment, TimeSpan.FromMinutes(RetryConfig.FetchAndCloneTimeoutMinutes));
- serverGVFSConfig = this.QueryGVFSConfigWithFallbackCacheServer(
+ string authErrorMessage;
+ if (!this.TryAuthenticateAndQueryGVFSConfig(
tracer,
enlistment,
retryConfig,
- cacheServer);
+ out serverGVFSConfig,
+ out authErrorMessage,
+ fallbackCacheServer: cacheServer))
+ {
+ this.ReportErrorAndExit(tracer, "Cannot clone because authentication failed: " + authErrorMessage);
+ }
cacheServer = this.ResolveCacheServer(tracer, cacheServer, cacheServerResolver, serverGVFSConfig);
diff --git a/GVFS/GVFS/CommandLine/GVFSVerb.cs b/GVFS/GVFS/CommandLine/GVFSVerb.cs
index fe0731a00..c2a4060d1 100644
--- a/GVFS/GVFS/CommandLine/GVFSVerb.cs
+++ b/GVFS/GVFS/CommandLine/GVFSVerb.cs
@@ -36,59 +36,6 @@ public GVFSVerb(bool validateOrigin = true)
this.InitializeDefaultParameterValues();
}
- [Flags]
- private enum GitCoreGVFSFlags
- {
- // GVFS_SKIP_SHA_ON_INDEX
- // Disables the calculation of the sha when writing the index
- SkipShaOnIndex = 1 << 0,
-
- // GVFS_BLOCK_COMMANDS
- // Blocks git commands that are not allowed in a GVFS/Scalar repo
- BlockCommands = 1 << 1,
-
- // GVFS_MISSING_OK
- // Normally git write-tree ensures that the objects referenced by the
- // directory exist in the object database.This option disables this check.
- MissingOk = 1 << 2,
-
- // GVFS_NO_DELETE_OUTSIDE_SPARSECHECKOUT
- // When marking entries to remove from the index and the working
- // directory this option will take into account what the
- // skip-worktree bit was set to so that if the entry has the
- // skip-worktree bit set it will not be removed from the working
- // directory. This will allow virtualized working directories to
- // detect the change to HEAD and use the new commit tree to show
- // the files that are in the working directory.
- NoDeleteOutsideSparseCheckout = 1 << 3,
-
- // GVFS_FETCH_SKIP_REACHABILITY_AND_UPLOADPACK
- // While performing a fetch with a virtual file system we know
- // that there will be missing objects and we don't want to download
- // them just because of the reachability of the commits. We also
- // don't want to download a pack file with commits, trees, and blobs
- // since these will be downloaded on demand. This flag will skip the
- // checks on the reachability of objects during a fetch as well as
- // the upload pack so that extraneous objects don't get downloaded.
- FetchSkipReachabilityAndUploadPack = 1 << 4,
-
- // 1 << 5 has been deprecated
-
- // GVFS_BLOCK_FILTERS_AND_EOL_CONVERSIONS
- // With a virtual file system we only know the file size before any
- // CRLF or smudge/clean filters processing is done on the client.
- // To prevent file corruption due to truncation or expansion with
- // garbage at the end, these filters must not run when the file
- // is first accessed and brought down to the client. Git.exe can't
- // currently tell the first access vs subsequent accesses so this
- // flag just blocks them from occurring at all.
- BlockFiltersAndEolConversions = 1 << 6,
-
- // GVFS_PREFETCH_DURING_FETCH
- // While performing a `git fetch` command, use the gvfs-helper to
- // perform a "prefetch" of commits and trees.
- PrefetchDuringFetch = 1 << 7,
- }
public abstract string EnlistmentRootPathParameter { get; set; }
@@ -429,14 +376,50 @@ protected bool ShowStatusWhileRunning(
protected bool TryAuthenticate(ITracer tracer, GVFSEnlistment enlistment, out string authErrorMessage)
{
- string authError = null;
+ return this.TryAuthenticateAndQueryGVFSConfig(tracer, enlistment, null, out _, out authErrorMessage);
+ }
+
+ ///
+ /// Combines authentication and GVFS config query into a single operation,
+ /// eliminating a redundant HTTP round-trip. If
+ /// is null, a default RetryConfig is used.
+ /// If the config query fails but a valid
+ /// URL is available, auth succeeds but
+ /// will be null (caller should handle this gracefully).
+ ///
+ protected bool TryAuthenticateAndQueryGVFSConfig(
+ ITracer tracer,
+ GVFSEnlistment enlistment,
+ RetryConfig retryConfig,
+ out ServerGVFSConfig serverGVFSConfig,
+ out string errorMessage,
+ CacheServerInfo fallbackCacheServer = null)
+ {
+ ServerGVFSConfig config = null;
+ string error = null;
bool result = this.ShowStatusWhileRunning(
- () => enlistment.Authentication.TryInitialize(tracer, enlistment, out authError),
+ () => enlistment.Authentication.TryInitializeAndQueryGVFSConfig(
+ tracer,
+ enlistment,
+ retryConfig ?? new RetryConfig(),
+ out config,
+ out error),
"Authenticating",
enlistment.EnlistmentRoot);
- authErrorMessage = authError;
+ if (!result && fallbackCacheServer != null && !string.IsNullOrWhiteSpace(fallbackCacheServer.Url))
+ {
+ // Auth/config query failed, but we have a fallback cache server.
+ // Allow auth to succeed so mount/clone can proceed; config will be null.
+ tracer.RelatedWarning("Config query failed but continuing with fallback cache server: " + error);
+ serverGVFSConfig = null;
+ errorMessage = null;
+ return true;
+ }
+
+ serverGVFSConfig = config;
+ errorMessage = error;
return result;
}
@@ -493,50 +476,7 @@ protected RetryConfig GetRetryConfig(ITracer tracer, GVFSEnlistment enlistment,
return retryConfig;
}
- ///
- /// Attempts to query the GVFS config endpoint. If successful, returns the config.
- /// If the query fails but a valid fallback cache server URL is available, returns null and continues.
- /// (A warning will be logged later.)
- /// If the query fails and no valid fallback is available, reports an error and exits.
- ///
- protected ServerGVFSConfig QueryGVFSConfigWithFallbackCacheServer(
- ITracer tracer,
- GVFSEnlistment enlistment,
- RetryConfig retryConfig,
- CacheServerInfo fallbackCacheServer)
- {
- ServerGVFSConfig serverGVFSConfig = null;
- string errorMessage = null;
- bool configSuccess = this.ShowStatusWhileRunning(
- () =>
- {
- using (ConfigHttpRequestor configRequestor = new ConfigHttpRequestor(tracer, enlistment, retryConfig))
- {
- const bool LogErrors = true;
- return configRequestor.TryQueryGVFSConfig(LogErrors, out serverGVFSConfig, out _, out errorMessage);
- }
- },
- "Querying remote for config",
- suppressGvfsLogMessage: true);
-
- if (!configSuccess)
- {
- // If a valid cache server URL is available, warn and continue
- if (fallbackCacheServer != null && !string.IsNullOrWhiteSpace(fallbackCacheServer.Url))
- {
- // Continue without config
- // Warning will be logged/displayed when version check is run
- return null;
- }
- else
- {
- this.ReportErrorAndExit(tracer, "Unable to query /gvfs/config" + Environment.NewLine + errorMessage);
- }
- }
- return serverGVFSConfig;
- }
-
- // Restore original QueryGVFSConfig for other callers
+ // QueryGVFSConfig for callers that require config to succeed (no fallback)
protected ServerGVFSConfig QueryGVFSConfig(ITracer tracer, GVFSEnlistment enlistment, RetryConfig retryConfig)
{
ServerGVFSConfig serverGVFSConfig = null;
diff --git a/GVFS/GVFS/CommandLine/MountVerb.cs b/GVFS/GVFS/CommandLine/MountVerb.cs
index 5183ec434..312a9f1c6 100644
--- a/GVFS/GVFS/CommandLine/MountVerb.cs
+++ b/GVFS/GVFS/CommandLine/MountVerb.cs
@@ -1,15 +1,11 @@
using CommandLine;
using GVFS.Common;
-using GVFS.Common.FileSystem;
-using GVFS.Common.Git;
using GVFS.Common.Http;
using GVFS.Common.NamedPipes;
using GVFS.Common.Tracing;
using GVFS.DiskLayoutUpgrades;
-using GVFS.Virtualization.Projection;
using System;
using System.IO;
-using System.Security.Principal;
namespace GVFS.CommandLine
{
@@ -86,17 +82,11 @@ protected override void Execute(GVFSEnlistment enlistment)
string mountExecutableLocation = null;
using (JsonTracer tracer = new JsonTracer(GVFSConstants.GVFSEtwProviderName, "ExecuteMount"))
{
- PhysicalFileSystem fileSystem = new PhysicalFileSystem();
- GitRepo gitRepo = new GitRepo(tracer, enlistment, fileSystem);
- GVFSContext context = new GVFSContext(tracer, fileSystem, gitRepo, enlistment);
+ // Validate these before handing them to the background process
+ // which cannot tell the user when they are bad
+ this.ValidateEnumArgs();
- if (!this.SkipInstallHooks && !HooksInstaller.InstallHooks(context, out errorMessage))
- {
- this.ReportErrorAndExit("Error installing hooks: " + errorMessage);
- }
-
- var resolvedCacheServer = this.ResolvedCacheServer;
- var cacheServerFromConfig = resolvedCacheServer ?? CacheServerResolver.GetCacheServerFromConfig(enlistment);
+ CacheServerInfo cacheServerFromConfig = CacheServerResolver.GetCacheServerFromConfig(enlistment);
tracer.AddLogFileEventListener(
GVFSEnlistment.GetNewGVFSLogFileName(enlistment.GVFSLogsRoot, GVFSConstants.LogFileTypes.MountVerb),
@@ -133,65 +123,11 @@ protected override void Execute(GVFSEnlistment enlistment)
}
}
- RetryConfig retryConfig = null;
- ServerGVFSConfig serverGVFSConfig = this.DownloadedGVFSConfig;
- /* If resolved cache server was passed in, we've already checked server config and version check in previous operation. */
- if (resolvedCacheServer == null)
+ // Verify mount executable exists before launching
+ mountExecutableLocation = Path.Combine(ProcessHelper.GetCurrentProcessLocation(), GVFSPlatform.Instance.Constants.MountExecutableName);
+ if (!File.Exists(mountExecutableLocation))
{
- string authErrorMessage;
- if (!this.TryAuthenticate(tracer, enlistment, out authErrorMessage))
- {
- this.Output.WriteLine(" WARNING: " + authErrorMessage);
- this.Output.WriteLine(" Mount will proceed, but new files cannot be accessed until GVFS can authenticate.");
- }
-
- if (serverGVFSConfig == null)
- {
- if (retryConfig == null)
- {
- retryConfig = this.GetRetryConfig(tracer, enlistment);
- }
-
- serverGVFSConfig = this.QueryGVFSConfigWithFallbackCacheServer(
- tracer,
- enlistment,
- retryConfig,
- cacheServerFromConfig);
- }
-
- this.ValidateClientVersions(tracer, enlistment, serverGVFSConfig, showWarnings: true);
-
- CacheServerResolver cacheServerResolver = new CacheServerResolver(tracer, enlistment);
- resolvedCacheServer = cacheServerResolver.ResolveNameFromRemote(cacheServerFromConfig.Url, serverGVFSConfig);
- this.Output.WriteLine("Configured cache server: " + cacheServerFromConfig);
- }
-
- this.InitializeLocalCacheAndObjectsPaths(tracer, enlistment, retryConfig, serverGVFSConfig, resolvedCacheServer);
-
- if (!this.ShowStatusWhileRunning(
- () => { return this.PerformPreMountValidation(tracer, enlistment, out mountExecutableLocation, out errorMessage); },
- "Validating repo"))
- {
- this.ReportErrorAndExit(tracer, errorMessage);
- }
-
- if (!this.SkipVersionCheck)
- {
- string error;
- if (!RepoMetadata.TryInitialize(tracer, enlistment.DotGVFSRoot, out error))
- {
- this.ReportErrorAndExit(tracer, error);
- }
-
- try
- {
- GitProcess git = new GitProcess(enlistment);
- this.LogEnlistmentInfoAndSetConfigValues(tracer, git, enlistment);
- }
- finally
- {
- RepoMetadata.Shutdown();
- }
+ this.ReportErrorAndExit(tracer, $"Could not find {GVFSPlatform.Instance.Constants.MountExecutableName}. You may need to reinstall GVFS.");
}
if (!this.ShowStatusWhileRunning(
@@ -220,62 +156,8 @@ protected override void Execute(GVFSEnlistment enlistment)
}
}
- private bool PerformPreMountValidation(ITracer tracer, GVFSEnlistment enlistment, out string mountExecutableLocation, out string errorMessage)
- {
- errorMessage = string.Empty;
- mountExecutableLocation = string.Empty;
-
- // We have to parse these parameters here to make sure they are valid before
- // handing them to the background process which cannot tell the user when they are bad
- EventLevel verbosity;
- Keywords keywords;
- this.ParseEnumArgs(out verbosity, out keywords);
-
- mountExecutableLocation = Path.Combine(ProcessHelper.GetCurrentProcessLocation(), GVFSPlatform.Instance.Constants.MountExecutableName);
- if (!File.Exists(mountExecutableLocation))
- {
- errorMessage = $"Could not find {GVFSPlatform.Instance.Constants.MountExecutableName}. You may need to reinstall GVFS.";
- return false;
- }
-
- GitProcess git = new GitProcess(enlistment);
- if (!git.IsValidRepo())
- {
- errorMessage = "The .git folder is missing or has invalid contents";
- return false;
- }
-
- try
- {
- GitIndexProjection.ReadIndex(tracer, Path.Combine(enlistment.WorkingDirectoryBackingRoot, GVFSConstants.DotGit.Index));
- }
- catch (Exception e)
- {
- EventMetadata metadata = new EventMetadata();
- metadata.Add("Exception", e.ToString());
- tracer.RelatedError(metadata, "Index validation failed");
- errorMessage = "Index validation failed, run 'gvfs repair' to repair index.";
-
- return false;
- }
-
- if (!GVFSPlatform.Instance.FileSystem.IsFileSystemSupported(enlistment.EnlistmentRoot, out string error))
- {
- errorMessage = $"FileSystem unsupported: {error}";
- return false;
- }
-
- return true;
- }
-
private bool TryMount(ITracer tracer, GVFSEnlistment enlistment, string mountExecutableLocation, out string errorMessage)
{
- if (!GVFSVerb.TrySetRequiredGitConfigSettings(enlistment))
- {
- errorMessage = "Unable to configure git repo";
- return false;
- }
-
const string ParamPrefix = "--";
tracer.RelatedInfo($"{nameof(this.TryMount)}: Launching background process('{mountExecutableLocation}') for {enlistment.EnlistmentRoot}");
@@ -355,14 +237,14 @@ private bool RegisterMount(GVFSEnlistment enlistment, out string errorMessage)
}
}
- private void ParseEnumArgs(out EventLevel verbosity, out Keywords keywords)
+ private void ValidateEnumArgs()
{
- if (!Enum.TryParse(this.KeywordsCsv, out keywords))
+ if (!Enum.TryParse(this.KeywordsCsv, out Keywords _))
{
this.ReportErrorAndExit("Error: Invalid logging filter keywords: " + this.KeywordsCsv);
}
- if (!Enum.TryParse(this.Verbosity, out verbosity))
+ if (!Enum.TryParse(this.Verbosity, out EventLevel _))
{
this.ReportErrorAndExit("Error: Invalid logging verbosity: " + this.Verbosity);
}
diff --git a/GVFS/GVFS/CommandLine/PrefetchVerb.cs b/GVFS/GVFS/CommandLine/PrefetchVerb.cs
index ab72b5e9f..1dd31b3b1 100644
--- a/GVFS/GVFS/CommandLine/PrefetchVerb.cs
+++ b/GVFS/GVFS/CommandLine/PrefetchVerb.cs
@@ -243,23 +243,23 @@ private void InitializeServerConnection(
// If ResolvedCacheServer is set, then we have already tried querying the server config and checking versions.
if (resolvedCacheServer == null)
{
- string authErrorMessage;
- if (!this.TryAuthenticate(tracer, enlistment, out authErrorMessage))
- {
- this.ReportErrorAndExit(tracer, "Unable to prefetch because authentication failed: " + authErrorMessage);
- }
-
- CacheServerResolver cacheServerResolver = new CacheServerResolver(tracer, enlistment);
-
if (serverGVFSConfig == null)
{
- serverGVFSConfig = this.QueryGVFSConfigWithFallbackCacheServer(
+ string authErrorMessage;
+ if (!this.TryAuthenticateAndQueryGVFSConfig(
tracer,
enlistment,
retryConfig,
- cacheServerFromConfig);
+ out serverGVFSConfig,
+ out authErrorMessage,
+ fallbackCacheServer: cacheServerFromConfig))
+ {
+ this.ReportErrorAndExit(tracer, "Unable to prefetch because authentication failed: " + authErrorMessage);
+ }
}
+ CacheServerResolver cacheServerResolver = new CacheServerResolver(tracer, enlistment);
+
resolvedCacheServer = cacheServerResolver.ResolveNameFromRemote(cacheServerFromConfig.Url, serverGVFSConfig);
if (!this.SkipVersionCheck)