Skip to content

Commit 717137d

Browse files
authored
Merge pull request #1908 from tyrielv/tyrielv/faster-mount
Improve mount performance by moving validation to mount process and parallelizing I/O
2 parents 9408de7 + 129adcd commit 717137d

8 files changed

Lines changed: 766 additions & 324 deletions

File tree

GVFS/GVFS.Common/Git/GitAuthentication.cs

Lines changed: 77 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -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)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
using System;
2+
3+
namespace GVFS.Common.Git
4+
{
5+
[Flags]
6+
public enum GitCoreGVFSFlags
7+
{
8+
// GVFS_SKIP_SHA_ON_INDEX
9+
// Disables the calculation of the sha when writing the index
10+
SkipShaOnIndex = 1 << 0,
11+
12+
// GVFS_BLOCK_COMMANDS
13+
// Blocks git commands that are not allowed in a GVFS/Scalar repo
14+
BlockCommands = 1 << 1,
15+
16+
// GVFS_MISSING_OK
17+
// Normally git write-tree ensures that the objects referenced by the
18+
// directory exist in the object database.This option disables this check.
19+
MissingOk = 1 << 2,
20+
21+
// GVFS_NO_DELETE_OUTSIDE_SPARSECHECKOUT
22+
// When marking entries to remove from the index and the working
23+
// directory this option will take into account what the
24+
// skip-worktree bit was set to so that if the entry has the
25+
// skip-worktree bit set it will not be removed from the working
26+
// directory. This will allow virtualized working directories to
27+
// detect the change to HEAD and use the new commit tree to show
28+
// the files that are in the working directory.
29+
NoDeleteOutsideSparseCheckout = 1 << 3,
30+
31+
// GVFS_FETCH_SKIP_REACHABILITY_AND_UPLOADPACK
32+
// While performing a fetch with a virtual file system we know
33+
// that there will be missing objects and we don't want to download
34+
// them just because of the reachability of the commits. We also
35+
// don't want to download a pack file with commits, trees, and blobs
36+
// since these will be downloaded on demand. This flag will skip the
37+
// checks on the reachability of objects during a fetch as well as
38+
// the upload pack so that extraneous objects don't get downloaded.
39+
FetchSkipReachabilityAndUploadPack = 1 << 4,
40+
41+
// 1 << 5 has been deprecated
42+
43+
// GVFS_BLOCK_FILTERS_AND_EOL_CONVERSIONS
44+
// With a virtual file system we only know the file size before any
45+
// CRLF or smudge/clean filters processing is done on the client.
46+
// To prevent file corruption due to truncation or expansion with
47+
// garbage at the end, these filters must not run when the file
48+
// is first accessed and brought down to the client. Git.exe can't
49+
// currently tell the first access vs subsequent accesses so this
50+
// flag just blocks them from occurring at all.
51+
BlockFiltersAndEolConversions = 1 << 6,
52+
53+
// GVFS_PREFETCH_DURING_FETCH
54+
// While performing a `git fetch` command, use the gvfs-helper to
55+
// perform a "prefetch" of commits and trees.
56+
PrefetchDuringFetch = 1 << 7,
57+
}
58+
}

0 commit comments

Comments
 (0)