Skip to content

Commit 80aa37a

Browse files
tyrielvCopilot
andcommitted
Combine auth initialization with config query
Add TryInitializeAndQueryGVFSConfig to GitAuthentication that merges the anonymous probe, credential fetch, and config query into a single flow. Instead of 3 HTTP requests (anonymous probe, then credential fetch, then authenticated config query with a new HttpClient), this makes at most 2 (or 1 for anonymous repos) reusing the same connection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent a917f38 commit 80aa37a

2 files changed

Lines changed: 90 additions & 7 deletions

File tree

GVFS/GVFS.Common/Git/GitAuthentication.cs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,76 @@ public bool TryInitialize(ITracer tracer, Enlistment enlistment, out string erro
210210
return true;
211211
}
212212

213+
/// <summary>
214+
/// Combines authentication initialization with the GVFS config query,
215+
/// eliminating the redundant anonymous probe HTTP request. Instead of:
216+
/// 1. Anonymous probe → /gvfs/config → 401 (TryInitialize)
217+
/// 2. Credential fetch (TryInitialize)
218+
/// 3. Config query → /gvfs/config → 200 (QueryGVFSConfig)
219+
/// This method does:
220+
/// 1. Config query → /gvfs/config → 401 or 200
221+
/// 2. If 401: credential fetch, then retry → 200
222+
/// Saving one HTTP round-trip and reusing the same TCP/TLS connection.
223+
/// </summary>
224+
public bool TryInitializeAndQueryGVFSConfig(
225+
ITracer tracer,
226+
Enlistment enlistment,
227+
RetryConfig retryConfig,
228+
out ServerGVFSConfig serverGVFSConfig,
229+
out string errorMessage)
230+
{
231+
if (this.isInitialized)
232+
{
233+
throw new InvalidOperationException("Already initialized");
234+
}
235+
236+
serverGVFSConfig = null;
237+
errorMessage = null;
238+
239+
using (ConfigHttpRequestor configRequestor = new ConfigHttpRequestor(tracer, enlistment, retryConfig))
240+
{
241+
HttpStatusCode? httpStatus;
242+
243+
// First attempt without credentials. If anonymous access works,
244+
// we get the config in a single request.
245+
if (configRequestor.TryQueryGVFSConfig(false, out serverGVFSConfig, out httpStatus, out _))
246+
{
247+
this.IsAnonymous = true;
248+
this.isInitialized = true;
249+
tracer.RelatedInfo("{0}: Anonymous access succeeded, config obtained in one request", nameof(this.TryInitializeAndQueryGVFSConfig));
250+
return true;
251+
}
252+
253+
if (httpStatus != HttpStatusCode.Unauthorized)
254+
{
255+
errorMessage = "Unable to query /gvfs/config";
256+
tracer.RelatedWarning("{0}: Config query failed with status {1}", nameof(this.TryInitializeAndQueryGVFSConfig), httpStatus?.ToString() ?? "None");
257+
return false;
258+
}
259+
260+
// Server requires authentication — fetch credentials
261+
this.IsAnonymous = false;
262+
263+
if (!this.TryCallGitCredential(tracer, out errorMessage))
264+
{
265+
tracer.RelatedWarning("{0}: Credential fetch failed: {1}", nameof(this.TryInitializeAndQueryGVFSConfig), errorMessage);
266+
return false;
267+
}
268+
269+
this.isInitialized = true;
270+
271+
// Retry with credentials using the same ConfigHttpRequestor (reuses HttpClient/connection)
272+
if (configRequestor.TryQueryGVFSConfig(true, out serverGVFSConfig, out _, out errorMessage))
273+
{
274+
tracer.RelatedInfo("{0}: Config obtained with credentials", nameof(this.TryInitializeAndQueryGVFSConfig));
275+
return true;
276+
}
277+
278+
tracer.RelatedWarning("{0}: Config query failed with credentials: {1}", nameof(this.TryInitializeAndQueryGVFSConfig), errorMessage);
279+
return false;
280+
}
281+
}
282+
213283
public bool TryInitializeAndRequireAuth(ITracer tracer, out string errorMessage)
214284
{
215285
if (this.isInitialized)

GVFS/GVFS.Mount/InProcessMount.cs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,21 +88,34 @@ public void Mount(EventLevel verbosity, Keywords keywords)
8888
// Start auth + config query immediately — these are network-bound and don't
8989
// depend on repo metadata or cache paths. Every millisecond of network latency
9090
// we can overlap with local I/O is a win.
91+
// TryInitializeAndQueryGVFSConfig combines the anonymous probe, credential fetch,
92+
// and config query into at most 2 HTTP requests (1 for anonymous repos), reusing
93+
// the same HttpClient/TCP connection.
9194
Stopwatch parallelTimer = Stopwatch.StartNew();
9295

9396
var networkTask = Task.Run(() =>
9497
{
9598
Stopwatch sw = Stopwatch.StartNew();
96-
string authError;
97-
if (!this.enlistment.Authentication.TryInitialize(this.tracer, this.enlistment, out authError))
99+
ServerGVFSConfig config;
100+
string authConfigError;
101+
102+
if (!this.enlistment.Authentication.TryInitializeAndQueryGVFSConfig(
103+
this.tracer, this.enlistment, this.retryConfig,
104+
out config, out authConfigError))
98105
{
99-
this.tracer.RelatedWarning("Mount will proceed, but new files cannot be accessed until GVFS can authenticate: " + authError);
106+
if (this.cacheServer != null && !string.IsNullOrWhiteSpace(this.cacheServer.Url))
107+
{
108+
this.tracer.RelatedWarning("Mount will proceed with fallback cache server: " + authConfigError);
109+
config = null;
110+
}
111+
else
112+
{
113+
this.FailMountAndExit("Unable to query /gvfs/config" + Environment.NewLine + authConfigError);
114+
}
100115
}
101116

102-
this.tracer.RelatedInfo("ParallelMount: Auth completed in {0}ms", sw.ElapsedMilliseconds);
103-
104-
ServerGVFSConfig config = this.QueryAndValidateGVFSConfig();
105-
this.tracer.RelatedInfo("ParallelMount: Auth + config query completed in {0}ms", sw.ElapsedMilliseconds);
117+
this.ValidateGVFSVersion(config);
118+
this.tracer.RelatedInfo("ParallelMount: Auth + config completed in {0}ms", sw.ElapsedMilliseconds);
106119
return config;
107120
});
108121

0 commit comments

Comments
 (0)