diff --git a/GVFS/GVFS.Common/Http/CacheServerResolver.cs b/GVFS/GVFS.Common/Http/CacheServerResolver.cs index 9f3a7d311..bc1df9727 100644 --- a/GVFS/GVFS.Common/Http/CacheServerResolver.cs +++ b/GVFS/GVFS.Common/Http/CacheServerResolver.cs @@ -54,12 +54,12 @@ public bool TryResolveUrlFromRemote( if (cacheServerName.Equals(CacheServerInfo.ReservedNames.Default, StringComparison.OrdinalIgnoreCase)) { cacheServer = - serverGVFSConfig.CacheServers.FirstOrDefault(cache => cache.GlobalDefault) + serverGVFSConfig?.CacheServers.FirstOrDefault(cache => cache.GlobalDefault) ?? this.CreateNone(); } else { - cacheServer = serverGVFSConfig.CacheServers.FirstOrDefault(cache => + cacheServer = serverGVFSConfig?.CacheServers.FirstOrDefault(cache => cache.Name.Equals(cacheServerName, StringComparison.OrdinalIgnoreCase)); if (cacheServer == null) @@ -87,7 +87,7 @@ public CacheServerInfo ResolveNameFromRemote( } return - serverGVFSConfig.CacheServers.FirstOrDefault(cache => cache.Url.Equals(cacheServerUrl, StringComparison.OrdinalIgnoreCase)) + serverGVFSConfig?.CacheServers.FirstOrDefault(cache => cache.Url.Equals(cacheServerUrl, StringComparison.OrdinalIgnoreCase)) ?? new CacheServerInfo(cacheServerUrl, CacheServerInfo.ReservedNames.UserDefined); } diff --git a/GVFS/GVFS/CommandLine/CacheServerVerb.cs b/GVFS/GVFS/CommandLine/CacheServerVerb.cs index 55edb8853..86754ae67 100644 --- a/GVFS/GVFS/CommandLine/CacheServerVerb.cs +++ b/GVFS/GVFS/CommandLine/CacheServerVerb.cs @@ -48,34 +48,25 @@ protected override void Execute(GVFSEnlistment enlistment) this.ReportErrorAndExit(tracer, "Authentication failed: " + authErrorMessage); } - ServerGVFSConfig serverGVFSConfig = this.QueryGVFSConfig(tracer, enlistment, retryConfig); - CacheServerResolver cacheServerResolver = new CacheServerResolver(tracer, enlistment); + ServerGVFSConfig serverGVFSConfig = null; string error = null; - if (this.CacheToSet != null) + // Handle the three operation types: list, set, and get (default) + if (this.ListCacheServers) { - CacheServerInfo cacheServer = cacheServerResolver.ParseUrlOrFriendlyName(this.CacheToSet); - cacheServer = this.ResolveCacheServer(tracer, cacheServer, cacheServerResolver, serverGVFSConfig); + // For listing, require config endpoint to succeed + serverGVFSConfig = this.QueryGVFSConfig(tracer, enlistment, retryConfig); - if (!cacheServerResolver.TrySaveUrlToLocalConfig(cacheServer, out error)) - { - this.ReportErrorAndExit("Failed to save cache to config: " + error); - } - - this.Output.WriteLine("You must remount GVFS for this to take effect."); - } - else if (this.ListCacheServers) - { List cacheServers = serverGVFSConfig.CacheServers.ToList(); if (cacheServers != null && cacheServers.Any()) { this.Output.WriteLine(); this.Output.WriteLine("Available cache servers for: " + enlistment.RepoUrl); - foreach (CacheServerInfo cacheServer in cacheServers) + foreach (CacheServerInfo cacheServerInfo in cacheServers) { - this.Output.WriteLine(cacheServer); + this.Output.WriteLine(cacheServerInfo); } } else @@ -83,12 +74,42 @@ protected override void Execute(GVFSEnlistment enlistment) this.Output.WriteLine("There are no available cache servers for: " + enlistment.RepoUrl); } } + else if (this.CacheToSet != null) + { + // Setting a new cache server + 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); + + cacheServer = this.ResolveCacheServer(tracer, cacheServer, cacheServerResolver, serverGVFSConfig); + + if (!cacheServerResolver.TrySaveUrlToLocalConfig(cacheServer, out error)) + { + this.ReportErrorAndExit("Failed to save cache to config: " + error); + } + + this.Output.WriteLine("You must remount GVFS for this to take effect."); + } else { - string cacheServerUrl = CacheServerResolver.GetUrlFromConfig(enlistment); - CacheServerInfo cacheServer = cacheServerResolver.ResolveNameFromRemote(cacheServerUrl, serverGVFSConfig); + // Default operation: get current cache server info + 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); + + CacheServerInfo resolvedCacheServer = cacheServerResolver.ResolveNameFromRemote(cacheServer.Url, serverGVFSConfig); - this.Output.WriteLine("Using cache server: " + cacheServer); + this.Output.WriteLine("Using cache server: " + resolvedCacheServer); } } } diff --git a/GVFS/GVFS/CommandLine/CloneVerb.cs b/GVFS/GVFS/CommandLine/CloneVerb.cs index e81030837..f9985f7e0 100644 --- a/GVFS/GVFS/CommandLine/CloneVerb.cs +++ b/GVFS/GVFS/CommandLine/CloneVerb.cs @@ -162,12 +162,12 @@ public override void Execute() string resolvedLocalCacheRoot; if (string.IsNullOrWhiteSpace(this.LocalCacheRoot)) { - string localCacheRootError; - if (!LocalCacheResolver.TryGetDefaultLocalCacheRoot(enlistment, out resolvedLocalCacheRoot, out localCacheRootError)) + string localCacheRootError; + if (!LocalCacheResolver.TryGetDefaultLocalCacheRoot(enlistment, out resolvedLocalCacheRoot, out localCacheRootError)) { this.ReportErrorAndExit( tracer, - $"Failed to determine the default location for the local GVFS cache: `{localCacheRootError}`"); + $"Failed to determine the default location for the local GVFS cache: `{localCacheRootError}`"); } } else @@ -189,7 +189,12 @@ public override void Execute() } RetryConfig retryConfig = this.GetRetryConfig(tracer, enlistment, TimeSpan.FromMinutes(RetryConfig.FetchAndCloneTimeoutMinutes)); - serverGVFSConfig = this.QueryGVFSConfig(tracer, enlistment, retryConfig); + + serverGVFSConfig = this.QueryGVFSConfigWithFallbackCacheServer( + tracer, + enlistment, + retryConfig, + cacheServer); cacheServer = this.ResolveCacheServer(tracer, cacheServer, cacheServerResolver, serverGVFSConfig); @@ -237,7 +242,6 @@ public override void Execute() exitCode = (int)result; } } - else { Process.Start(new ProcessStartInfo( diff --git a/GVFS/GVFS/CommandLine/GVFSVerb.cs b/GVFS/GVFS/CommandLine/GVFSVerb.cs index a9fcb9587..f0c7d984d 100644 --- a/GVFS/GVFS/CommandLine/GVFSVerb.cs +++ b/GVFS/GVFS/CommandLine/GVFSVerb.cs @@ -493,6 +493,50 @@ 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 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 90db430cf..5183ec434 100644 --- a/GVFS/GVFS/CommandLine/MountVerb.cs +++ b/GVFS/GVFS/CommandLine/MountVerb.cs @@ -95,7 +95,8 @@ protected override void Execute(GVFSEnlistment enlistment) this.ReportErrorAndExit("Error installing hooks: " + errorMessage); } - CacheServerInfo cacheServer = this.ResolvedCacheServer ?? CacheServerResolver.GetCacheServerFromConfig(enlistment); + var resolvedCacheServer = this.ResolvedCacheServer; + var cacheServerFromConfig = resolvedCacheServer ?? CacheServerResolver.GetCacheServerFromConfig(enlistment); tracer.AddLogFileEventListener( GVFSEnlistment.GetNewGVFSLogFileName(enlistment.GVFSLogsRoot, GVFSConstants.LogFileTypes.MountVerb), @@ -104,7 +105,7 @@ protected override void Execute(GVFSEnlistment enlistment) tracer.WriteStartEvent( enlistment.EnlistmentRoot, enlistment.RepoUrl, - cacheServer.Url, + cacheServerFromConfig.Url, new EventMetadata { { "Unattended", this.Unattended }, @@ -122,7 +123,7 @@ protected override void Execute(GVFSEnlistment enlistment) { { "KernelDriver.IsReady_Error", errorMessage }, { TracingConstants.MessageKey.InfoMessage, "Service will retry" } - }); + }); if (!this.ShowStatusWhileRunning( () => { return this.TryEnableAndAttachPrjFltThroughService(enlistment.EnlistmentRoot, out errorMessage); }, @@ -134,7 +135,8 @@ protected override void Execute(GVFSEnlistment enlistment) RetryConfig retryConfig = null; ServerGVFSConfig serverGVFSConfig = this.DownloadedGVFSConfig; - if (!this.SkipVersionCheck) + /* If resolved cache server was passed in, we've already checked server config and version check in previous operation. */ + if (resolvedCacheServer == null) { string authErrorMessage; if (!this.TryAuthenticate(tracer, enlistment, out authErrorMessage)) @@ -150,17 +152,21 @@ protected override void Execute(GVFSEnlistment enlistment) retryConfig = this.GetRetryConfig(tracer, enlistment); } - serverGVFSConfig = this.QueryGVFSConfig(tracer, enlistment, retryConfig); + serverGVFSConfig = this.QueryGVFSConfigWithFallbackCacheServer( + tracer, + enlistment, + retryConfig, + cacheServerFromConfig); } this.ValidateClientVersions(tracer, enlistment, serverGVFSConfig, showWarnings: true); CacheServerResolver cacheServerResolver = new CacheServerResolver(tracer, enlistment); - cacheServer = cacheServerResolver.ResolveNameFromRemote(cacheServer.Url, serverGVFSConfig); - this.Output.WriteLine("Configured cache server: " + cacheServer); + resolvedCacheServer = cacheServerResolver.ResolveNameFromRemote(cacheServerFromConfig.Url, serverGVFSConfig); + this.Output.WriteLine("Configured cache server: " + cacheServerFromConfig); } - this.InitializeLocalCacheAndObjectsPaths(tracer, enlistment, retryConfig, serverGVFSConfig, cacheServer); + this.InitializeLocalCacheAndObjectsPaths(tracer, enlistment, retryConfig, serverGVFSConfig, resolvedCacheServer); if (!this.ShowStatusWhileRunning( () => { return this.PerformPreMountValidation(tracer, enlistment, out mountExecutableLocation, out errorMessage); }, @@ -193,23 +199,23 @@ protected override void Execute(GVFSEnlistment enlistment) "Mounting")) { this.ReportErrorAndExit(tracer, errorMessage); - } - - if (!this.Unattended) + } + + if (!this.Unattended) { tracer.RelatedInfo($"{nameof(this.Execute)}: Registering for automount"); - - if (this.ShowStatusWhileRunning( - () => { return this.RegisterMount(enlistment, out errorMessage); }, - "Registering for automount")) + + if (this.ShowStatusWhileRunning( + () => { return this.RegisterMount(enlistment, out errorMessage); }, + "Registering for automount")) { - tracer.RelatedInfo($"{nameof(this.Execute)}: Registered for automount"); + tracer.RelatedInfo($"{nameof(this.Execute)}: Registered for automount"); } else { this.Output.WriteLine(" WARNING: " + errorMessage); tracer.RelatedInfo($"{nameof(this.Execute)}: Failed to register for automount"); - } + } } } } diff --git a/GVFS/GVFS/CommandLine/PrefetchVerb.cs b/GVFS/GVFS/CommandLine/PrefetchVerb.cs index 5d6373f8e..ab72b5e9f 100644 --- a/GVFS/GVFS/CommandLine/PrefetchVerb.cs +++ b/GVFS/GVFS/CommandLine/PrefetchVerb.cs @@ -53,12 +53,12 @@ public class PrefetchVerb : GVFSVerb.ForExistingEnlistment Required = false, Default = false, HelpText = "Specify this flag to load file list from stdin. Same format as when loading from file.")] - public bool FilesFromStdIn { get; set; } - - [Option( - "stdin-folders-list", - Required = false, - Default = false, + public bool FilesFromStdIn { get; set; } + + [Option( + "stdin-folders-list", + Required = false, + Default = false, HelpText = "Specify this flag to load folder list from stdin. Same format as when loading from file.")] public bool FoldersFromStdIn { get; set; } @@ -109,7 +109,7 @@ protected override void Execute(GVFSEnlistment enlistment) tracer.AddDiagnosticConsoleEventListener(EventLevel.Informational, Keywords.Any); } - string cacheServerUrl = CacheServerResolver.GetUrlFromConfig(enlistment); + var cacheServerFromConfig = CacheServerResolver.GetCacheServerFromConfig(enlistment); tracer.AddLogFileEventListener( GVFSEnlistment.GetNewGVFSLogFileName(enlistment.GVFSLogsRoot, GVFSConstants.LogFileTypes.Prefetch), @@ -118,7 +118,7 @@ protected override void Execute(GVFSEnlistment enlistment) tracer.WriteStartEvent( enlistment.EnlistmentRoot, enlistment.RepoUrl, - cacheServerUrl); + cacheServerFromConfig.Url); try { @@ -127,8 +127,8 @@ protected override void Execute(GVFSEnlistment enlistment) metadata.Add("Files", this.Files); metadata.Add("Folders", this.Folders); metadata.Add("FileListFile", this.FilesListFile); - metadata.Add("FoldersListFile", this.FoldersListFile); - metadata.Add("FilesFromStdIn", this.FilesFromStdIn); + metadata.Add("FoldersListFile", this.FoldersListFile); + metadata.Add("FilesFromStdIn", this.FilesFromStdIn); metadata.Add("FoldersFromStdIn", this.FoldersFromStdIn); metadata.Add("HydrateFiles", this.HydrateFiles); tracer.RelatedEvent(EventLevel.Informational, "PerformPrefetch", metadata); @@ -151,14 +151,14 @@ protected override void Execute(GVFSEnlistment enlistment) } GitObjectsHttpRequestor objectRequestor; - CacheServerInfo cacheServer; + CacheServerInfo resolvedCacheServer; this.InitializeServerConnection( tracer, enlistment, - cacheServerUrl, + cacheServerFromConfig, out objectRequestor, - out cacheServer); - this.PrefetchCommits(tracer, enlistment, objectRequestor, cacheServer); + out resolvedCacheServer); + this.PrefetchCommits(tracer, enlistment, objectRequestor, resolvedCacheServer); } else { @@ -167,8 +167,8 @@ protected override void Execute(GVFSEnlistment enlistment) List foldersList; FileBasedDictionary lastPrefetchArgs; - this.LoadBlobPrefetchArgs(tracer, enlistment, out headCommitId, out filesList, out foldersList, out lastPrefetchArgs); - + this.LoadBlobPrefetchArgs(tracer, enlistment, out headCommitId, out filesList, out foldersList, out lastPrefetchArgs); + if (BlobPrefetcher.IsNoopPrefetch(tracer, lastPrefetchArgs, headCommitId, filesList, foldersList, this.HydrateFiles)) { Console.WriteLine("All requested files are already available. Nothing new to prefetch."); @@ -176,14 +176,14 @@ protected override void Execute(GVFSEnlistment enlistment) else { GitObjectsHttpRequestor objectRequestor; - CacheServerInfo cacheServer; + CacheServerInfo resolvedCacheServer; this.InitializeServerConnection( tracer, enlistment, - cacheServerUrl, + cacheServerFromConfig, out objectRequestor, - out cacheServer); - this.PrefetchBlobs(tracer, enlistment, headCommitId, filesList, foldersList, lastPrefetchArgs, objectRequestor, cacheServer); + out resolvedCacheServer); + this.PrefetchBlobs(tracer, enlistment, headCommitId, filesList, foldersList, lastPrefetchArgs, objectRequestor, resolvedCacheServer); } } } @@ -230,15 +230,18 @@ protected override void Execute(GVFSEnlistment enlistment) private void InitializeServerConnection( ITracer tracer, GVFSEnlistment enlistment, - string cacheServerUrl, + CacheServerInfo cacheServerFromConfig, out GitObjectsHttpRequestor objectRequestor, - out CacheServerInfo cacheServer) + out CacheServerInfo resolvedCacheServer) { RetryConfig retryConfig = this.GetRetryConfig(tracer, enlistment, TimeSpan.FromMinutes(RetryConfig.FetchAndCloneTimeoutMinutes)); - cacheServer = this.ResolvedCacheServer; + // These this.* arguments are set if this is a follow-on operation from clone or mount. + resolvedCacheServer = this.ResolvedCacheServer; ServerGVFSConfig serverGVFSConfig = this.ServerGVFSConfig; - if (!this.SkipVersionCheck) + + // 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)) @@ -246,24 +249,29 @@ private void InitializeServerConnection( this.ReportErrorAndExit(tracer, "Unable to prefetch because authentication failed: " + authErrorMessage); } + CacheServerResolver cacheServerResolver = new CacheServerResolver(tracer, enlistment); + if (serverGVFSConfig == null) { - serverGVFSConfig = this.QueryGVFSConfig(tracer, enlistment, retryConfig); + serverGVFSConfig = this.QueryGVFSConfigWithFallbackCacheServer( + tracer, + enlistment, + retryConfig, + cacheServerFromConfig); } - if (cacheServer == null) + resolvedCacheServer = cacheServerResolver.ResolveNameFromRemote(cacheServerFromConfig.Url, serverGVFSConfig); + + if (!this.SkipVersionCheck) { - CacheServerResolver cacheServerResolver = new CacheServerResolver(tracer, enlistment); - cacheServer = cacheServerResolver.ResolveNameFromRemote(cacheServerUrl, serverGVFSConfig); + this.ValidateClientVersions(tracer, enlistment, serverGVFSConfig, showWarnings: false); } - this.ValidateClientVersions(tracer, enlistment, serverGVFSConfig, showWarnings: false); - - this.Output.WriteLine("Configured cache server: " + cacheServer); + this.Output.WriteLine("Configured cache server: " + resolvedCacheServer); } - this.InitializeLocalCacheAndObjectsPaths(tracer, enlistment, retryConfig, serverGVFSConfig, cacheServer); - objectRequestor = new GitObjectsHttpRequestor(tracer, enlistment, cacheServer, retryConfig); + this.InitializeLocalCacheAndObjectsPaths(tracer, enlistment, retryConfig, serverGVFSConfig, resolvedCacheServer); + objectRequestor = new GitObjectsHttpRequestor(tracer, enlistment, resolvedCacheServer, retryConfig); } private void PrefetchCommits(ITracer tracer, GVFSEnlistment enlistment, GitObjectsHttpRequestor objectRequestor, CacheServerInfo cacheServer) @@ -300,8 +308,8 @@ private void LoadBlobPrefetchArgs( out List foldersList, out FileBasedDictionary lastPrefetchArgs) { - string error; - + string error; + if (!FileBasedDictionary.TryCreate( tracer, Path.Combine(enlistment.DotGVFSRoot, "LastBlobPrefetch.dat"),