@@ -183,34 +183,98 @@ public bool TryGetCredentials(ITracer tracer, out string credentialString, out s
183183 return true ;
184184 }
185185
186+ /// <summary>
187+ /// Initialize authentication by probing the server. Determines whether
188+ /// anonymous access is supported and, if not, fetches credentials.
189+ /// Callers that also need the GVFS config should use
190+ /// <see cref="TryInitializeAndQueryGVFSConfig"/> instead to avoid a
191+ /// redundant HTTP round-trip.
192+ /// </summary>
186193 public bool TryInitialize ( ITracer tracer , Enlistment enlistment , out string errorMessage )
194+ {
195+ // Delegate to the combined method, discarding the config result.
196+ // This avoids duplicating the anonymous-probe + credential-fetch logic.
197+ return this . TryInitializeAndQueryGVFSConfig (
198+ tracer ,
199+ enlistment ,
200+ new RetryConfig ( ) ,
201+ out _ ,
202+ out errorMessage ) ;
203+ }
204+
205+ /// <summary>
206+ /// Combines authentication initialization with the GVFS config query,
207+ /// eliminating a redundant HTTP round-trip. The anonymous probe and
208+ /// config query use the same request to /gvfs/config:
209+ /// 1. Config query → /gvfs/config → 200 (anonymous) or 401
210+ /// 2. If 401: credential fetch, then retry → 200
211+ /// This saves one HTTP request compared to probing auth separately
212+ /// and then querying config, and reuses the same TCP/TLS connection.
213+ /// </summary>
214+ public bool TryInitializeAndQueryGVFSConfig (
215+ ITracer tracer ,
216+ Enlistment enlistment ,
217+ RetryConfig retryConfig ,
218+ out ServerGVFSConfig serverGVFSConfig ,
219+ out string errorMessage )
187220 {
188221 if ( this . isInitialized )
189222 {
190223 throw new InvalidOperationException ( "Already initialized" ) ;
191224 }
192225
226+ serverGVFSConfig = null ;
193227 errorMessage = null ;
194228
195- bool isAnonymous ;
196- if ( ! this . TryAnonymousQuery ( tracer , enlistment , out isAnonymous ) )
229+ using ( ConfigHttpRequestor configRequestor = new ConfigHttpRequestor ( tracer , enlistment , retryConfig ) )
197230 {
198- errorMessage = $ "Unable to determine if authentication is required";
199- return false ;
200- }
231+ HttpStatusCode ? httpStatus ;
201232
202- if ( ! isAnonymous &&
203- ! this . TryCallGitCredential ( tracer , out errorMessage ) )
204- {
233+ // First attempt without credentials. If anonymous access works,
234+ // we get the config in a single request.
235+ if ( configRequestor . TryQueryGVFSConfig ( false , out serverGVFSConfig , out httpStatus , out _ ) )
236+ {
237+ this . IsAnonymous = true ;
238+ this . isInitialized = true ;
239+ tracer . RelatedInfo ( "{0}: Anonymous access succeeded, config obtained in one request" , nameof ( this . TryInitializeAndQueryGVFSConfig ) ) ;
240+ return true ;
241+ }
242+
243+ if ( httpStatus != HttpStatusCode . Unauthorized )
244+ {
245+ errorMessage = "Unable to query /gvfs/config" ;
246+ tracer . RelatedWarning ( "{0}: Config query failed with status {1}" , nameof ( this . TryInitializeAndQueryGVFSConfig ) , httpStatus ? . ToString ( ) ?? "None" ) ;
247+ return false ;
248+ }
249+
250+ // Server requires authentication — fetch credentials
251+ this . IsAnonymous = false ;
252+
253+ if ( ! this . TryCallGitCredential ( tracer , out errorMessage ) )
254+ {
255+ tracer . RelatedWarning ( "{0}: Credential fetch failed: {1}" , nameof ( this . TryInitializeAndQueryGVFSConfig ) , errorMessage ) ;
256+ return false ;
257+ }
258+
259+ this . isInitialized = true ;
260+
261+ // Retry with credentials using the same ConfigHttpRequestor (reuses HttpClient/connection)
262+ if ( configRequestor . TryQueryGVFSConfig ( true , out serverGVFSConfig , out _ , out errorMessage ) )
263+ {
264+ tracer . RelatedInfo ( "{0}: Config obtained with credentials" , nameof ( this . TryInitializeAndQueryGVFSConfig ) ) ;
265+ return true ;
266+ }
267+
268+ tracer . RelatedWarning ( "{0}: Config query failed with credentials: {1}" , nameof ( this . TryInitializeAndQueryGVFSConfig ) , errorMessage ) ;
205269 return false ;
206270 }
207-
208- this . IsAnonymous = isAnonymous ;
209- this . isInitialized = true ;
210- return true ;
211271 }
212272
213- public bool TryInitializeAndRequireAuth ( ITracer tracer , out string errorMessage )
273+ /// <summary>
274+ /// Test-only initialization that skips the network probe and goes
275+ /// straight to credential fetch. Not for production use.
276+ /// </summary>
277+ internal bool TryInitializeAndRequireAuth ( ITracer tracer , out string errorMessage )
214278 {
215279 if ( this . isInitialized )
216280 {
@@ -267,45 +331,6 @@ private static bool TryParseCredentialString(string credentialString, out string
267331 return false ;
268332 }
269333
270- private bool TryAnonymousQuery ( ITracer tracer , Enlistment enlistment , out bool isAnonymous )
271- {
272- bool querySucceeded ;
273- using ( ITracer anonymousTracer = tracer . StartActivity ( "AttemptAnonymousAuth" , EventLevel . Informational ) )
274- {
275- HttpStatusCode ? httpStatus ;
276-
277- using ( ConfigHttpRequestor configRequestor = new ConfigHttpRequestor ( anonymousTracer , enlistment , new RetryConfig ( ) ) )
278- {
279- ServerGVFSConfig gvfsConfig ;
280- const bool LogErrors = false ;
281- if ( configRequestor . TryQueryGVFSConfig ( LogErrors , out gvfsConfig , out httpStatus , out _ ) )
282- {
283- querySucceeded = true ;
284- isAnonymous = true ;
285- }
286- else if ( httpStatus == HttpStatusCode . Unauthorized )
287- {
288- querySucceeded = true ;
289- isAnonymous = false ;
290- }
291- else
292- {
293- querySucceeded = false ;
294- isAnonymous = false ;
295- }
296- }
297-
298- anonymousTracer . Stop ( new EventMetadata
299- {
300- { "HttpStatus" , httpStatus . HasValue ? ( ( int ) httpStatus ) . ToString ( ) : "None" } ,
301- { "QuerySucceeded" , querySucceeded } ,
302- { "IsAnonymous" , isAnonymous } ,
303- } ) ;
304- }
305-
306- return querySucceeded ;
307- }
308-
309334 private DateTime GetNextAuthAttemptTime ( )
310335 {
311336 if ( this . numberOfAttempts <= 1 )
0 commit comments