diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index fe238c532..bfac2917c 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -29,7 +29,7 @@ jobs:
- name: Look for prior successful runs
id: check
if: github.event.inputs.git_version == ''
- uses: actions/github-script@v7
+ uses: actions/github-script@v8
with:
github-token: ${{secrets.GITHUB_TOKEN}}
result-encoding: string
@@ -130,7 +130,7 @@ jobs:
- name: Skip this job if there is a previous successful run
if: needs.validate.outputs.skip != ''
id: skip
- uses: actions/github-script@v7
+ uses: actions/github-script@v8
with:
script: |
core.info(`Skipping: There already is a successful run: ${{ needs.validate.outputs.skip }}`)
@@ -212,7 +212,7 @@ jobs:
- name: Skip this job if there is a previous successful run
if: needs.validate.outputs.skip != ''
id: skip
- uses: actions/github-script@v7
+ uses: actions/github-script@v8
with:
script: |
core.info(`Skipping: There already is a successful run: ${{ needs.validate.outputs.skip }}`)
diff --git a/GVFS/GVFS.Common/Git/GitRepo.cs b/GVFS/GVFS.Common/Git/GitRepo.cs
index ee9d8b96d..cd11436d8 100644
--- a/GVFS/GVFS.Common/Git/GitRepo.cs
+++ b/GVFS/GVFS.Common/Git/GitRepo.cs
@@ -113,6 +113,21 @@ public virtual bool TryGetBlobLength(string blobSha, out long size)
return this.GetLooseBlobState(blobSha, null, out size) == LooseBlobState.Exists;
}
+ ///
+ /// Try to find the SHAs of subtrees missing from the given tree.
+ ///
+ /// Tree to look up
+ /// SHAs of subtrees of this tree which are not downloaded yet.
+ ///
+ public virtual bool TryGetMissingSubTrees(string treeSha, out string[] subtrees)
+ {
+ string[] missingSubtrees = null;
+ var succeeded = this.libgit2RepoInvoker.TryInvoke(repo =>
+ repo.GetMissingSubTrees(treeSha), out missingSubtrees);
+ subtrees = missingSubtrees;
+ return succeeded;
+ }
+
public void Dispose()
{
if (this.libgit2RepoInvoker != null)
diff --git a/GVFS/GVFS.Common/Git/HashingStream.cs b/GVFS/GVFS.Common/Git/HashingStream.cs
index c0630362c..3be13b2b3 100644
--- a/GVFS/GVFS.Common/Git/HashingStream.cs
+++ b/GVFS/GVFS.Common/Git/HashingStream.cs
@@ -17,7 +17,7 @@ public HashingStream(Stream stream)
{
this.stream = stream;
- this.hash = SHA1.Create();
+ this.hash = SHA1.Create(); // CodeQL [SM02196] SHA-1 is acceptable here because this is Git's hashing algorithm, not used for cryptographic purposes
this.hashResult = null;
this.hash.Initialize();
this.closed = false;
diff --git a/GVFS/GVFS.Common/Git/LibGit2Repo.cs b/GVFS/GVFS.Common/Git/LibGit2Repo.cs
index 0849dd6b7..f9edcce64 100644
--- a/GVFS/GVFS.Common/Git/LibGit2Repo.cs
+++ b/GVFS/GVFS.Common/Git/LibGit2Repo.cs
@@ -1,5 +1,6 @@
using GVFS.Common.Tracing;
using System;
+using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
@@ -147,6 +148,82 @@ public virtual bool TryCopyBlob(string sha, Action writeAction)
return true;
}
+ ///
+ /// Get the list of missing subtrees for the given treeSha.
+ ///
+ /// Tree to look up
+ /// SHAs of subtrees of this tree which are not downloaded yet.
+ public virtual string[] GetMissingSubTrees(string treeSha)
+ {
+ List missingSubtreesList = new List();
+ IntPtr treeHandle;
+ if (Native.RevParseSingle(out treeHandle, this.RepoHandle, treeSha) != Native.SuccessCode
+ || treeHandle == IntPtr.Zero)
+ {
+ return Array.Empty();
+ }
+
+ try
+ {
+ if (Native.Object.GetType(treeHandle) != Native.ObjectTypes.Tree)
+ {
+ return Array.Empty();
+ }
+
+ uint entryCount = Native.Tree.GetEntryCount(treeHandle);
+ for (uint i = 0; i < entryCount; i++)
+ {
+ if (this.IsMissingSubtree(treeHandle, i, out string entrySha))
+ {
+ missingSubtreesList.Add(entrySha);
+ }
+ }
+ }
+ finally
+ {
+ Native.Object.Free(treeHandle);
+ }
+
+ return missingSubtreesList.ToArray();
+ }
+
+ ///
+ /// Determine if the given index of a tree is a subtree and if it is missing.
+ /// If it is a missing subtree, return the SHA of the subtree.
+ ///
+ private bool IsMissingSubtree(IntPtr treeHandle, uint i, out string entrySha)
+ {
+ entrySha = null;
+ IntPtr entryHandle = Native.Tree.GetEntryByIndex(treeHandle, i);
+ if (entryHandle == IntPtr.Zero)
+ {
+ return false;
+ }
+
+ var entryMode = Native.Tree.GetEntryFileMode(entryHandle);
+ if (entryMode != Native.Tree.TreeEntryFileModeDirectory)
+ {
+ return false;
+ }
+
+ var entryId = Native.Tree.GetEntryId(entryHandle);
+ if (entryId == IntPtr.Zero)
+ {
+ return false;
+ }
+
+ var rawEntrySha = Native.IntPtrToGitOid(entryId);
+ entrySha = rawEntrySha.ToString();
+
+ if (this.ObjectExists(entrySha))
+ {
+ return false;
+ }
+ return true;
+ /* Both the entryHandle and the entryId handle are owned by the treeHandle, so we shouldn't free them or it will lead to corruption of the later entries */
+ }
+
+
public void Dispose()
{
this.Dispose(true);
@@ -247,6 +324,26 @@ public static class Blob
[DllImport(Git2NativeLibName, EntryPoint = "git_blob_rawcontent")]
public static unsafe extern byte* GetRawContent(IntPtr objectHandle);
}
+
+ public static class Tree
+ {
+ [DllImport(Git2NativeLibName, EntryPoint = "git_tree_entrycount")]
+ public static extern uint GetEntryCount(IntPtr treeHandle);
+
+ [DllImport(Git2NativeLibName, EntryPoint = "git_tree_entry_byindex")]
+ public static extern IntPtr GetEntryByIndex(IntPtr treeHandle, uint index);
+
+ [DllImport(Git2NativeLibName, EntryPoint = "git_tree_entry_id")]
+ public static extern IntPtr GetEntryId(IntPtr entryHandle);
+
+ /* git_tree_entry_type requires the object to exist, so we can't use it to check if
+ * a missing entry is a tree. Instead, we can use the file mode to determine if it is a tree. */
+ [DllImport(Git2NativeLibName, EntryPoint = "git_tree_entry_filemode")]
+ public static extern uint GetEntryFileMode(IntPtr entryHandle);
+
+ public const uint TreeEntryFileModeDirectory = 0x4000;
+
+ }
}
}
}
\ No newline at end of file
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.Common/SHA1Util.cs b/GVFS/GVFS.Common/SHA1Util.cs
index 800a2d48b..0fc20019d 100644
--- a/GVFS/GVFS.Common/SHA1Util.cs
+++ b/GVFS/GVFS.Common/SHA1Util.cs
@@ -21,7 +21,7 @@ public static byte[] SHA1ForUTF8String(string s)
{
byte[] bytes = Encoding.UTF8.GetBytes(s);
- using (SHA1 sha1 = SHA1.Create())
+ using (SHA1 sha1 = SHA1.Create()) // CodeQL [SM02196] SHA-1 is acceptable here because this is Git's hashing algorithm, not used for cryptographic purposes
{
return sha1.ComputeHash(bytes);
}
diff --git a/GVFS/GVFS.Mount/InProcessMount.cs b/GVFS/GVFS.Mount/InProcessMount.cs
index a9dd95b70..52426075b 100644
--- a/GVFS/GVFS.Mount/InProcessMount.cs
+++ b/GVFS/GVFS.Mount/InProcessMount.cs
@@ -27,6 +27,12 @@ public class InProcessMount
private const int MaxPipeNameLength = 250;
private const int MutexMaxWaitTimeMS = 500;
+ // This is value chosen based on tested scenarios to limit the required download time for
+ // all the trees. This is approximately the amount of trees that can be downloaded in 1 second.
+ // Downloading an entire commit pack also takes around 1 second, so this should limit downloading
+ // all the trees in a commit to ~2-3 seconds.
+ private const int MissingTreeThresholdForDownloadingCommitPack = 200;
+
private readonly bool showDebugWindow;
private FileSystemCallbacks fileSystemCallbacks;
@@ -47,7 +53,6 @@ public class InProcessMount
private ManualResetEvent unmountEvent;
private readonly Dictionary treesWithDownloadedCommits = new Dictionary();
- private DateTime lastCommitPackDownloadTime = DateTime.MinValue;
// True if InProcessMount is calling git reset as part of processing
// a folder dehydrate request
@@ -518,13 +523,14 @@ private void HandleDownloadObjectRequest(NamedPipeMessages.Message message, Name
if (this.ShouldDownloadCommitPack(objectSha, out string commitSha)
&& this.gitObjects.TryDownloadCommit(commitSha))
{
- this.DownloadedCommitPack(objectSha: objectSha, commitSha: commitSha);
+ this.DownloadedCommitPack(commitSha);
response = new NamedPipeMessages.DownloadObject.Response(NamedPipeMessages.DownloadObject.SuccessResult);
// FUTURE: Should the stats be updated to reflect all the trees in the pack?
// FUTURE: Should we try to clean up duplicate trees or increase depth of the commit download?
}
else if (this.gitObjects.TryDownloadAndSaveObject(objectSha, GVFSGitObjects.RequestSource.NamedPipeMessage) == GitObjects.DownloadAndSaveObjectResult.Success)
{
+ this.UpdateTreesForDownloadedCommits(objectSha);
response = new NamedPipeMessages.DownloadObject.Response(NamedPipeMessages.DownloadObject.SuccessResult);
}
else
@@ -548,7 +554,7 @@ private void HandleDownloadObjectRequest(NamedPipeMessages.Message message, Name
* Otherwise, the trees for the commit may be needed soon depending on the context.
* e.g. git log (without a pathspec) doesn't need trees, but git checkout does.
*
- * Save the tree/commit so if the tree is requested soon we can download all the trees for the commit in a batch.
+ * Save the tree/commit so if more trees are requested we can download all the trees for the commit in a batch.
*/
this.treesWithDownloadedCommits[treeSha] = objectSha;
}
@@ -561,28 +567,67 @@ private void HandleDownloadObjectRequest(NamedPipeMessages.Message message, Name
private bool PrefetchHasBeenDone()
{
var prefetchPacks = this.gitObjects.ReadPackFileNames(this.enlistment.GitPackRoot, GVFSConstants.PrefetchPackPrefix);
- return prefetchPacks.Length > 0;
+ var result = prefetchPacks.Length > 0;
+ if (result)
+ {
+ this.treesWithDownloadedCommits.Clear();
+ }
+ return result;
}
private bool ShouldDownloadCommitPack(string objectSha, out string commitSha)
{
-
if (!this.treesWithDownloadedCommits.TryGetValue(objectSha, out commitSha)
|| this.PrefetchHasBeenDone())
{
return false;
}
- /* This is a heuristic to prevent downloading multiple packs related to git history commands,
- * since commits downloaded close together likely have similar trees. */
- var timePassed = DateTime.UtcNow - this.lastCommitPackDownloadTime;
- return (timePassed > TimeSpan.FromMinutes(5));
+ /* This is a heuristic to prevent downloading multiple packs related to git history commands.
+ * Closely related commits are likely to have similar trees, so we'll find fewer missing trees in them.
+ * Conversely, if we know (from previously downloaded missing trees) that a commit has a lot of missing
+ * trees left, we'll probably need to download many more trees for the commit so we should download the pack.
+ */
+ var commitShaLocal = commitSha; // can't use out parameter in lambda
+ int missingTreeCount = this.treesWithDownloadedCommits.Where(x => x.Value == commitShaLocal).Count();
+ return missingTreeCount > MissingTreeThresholdForDownloadingCommitPack;
+ }
+
+ private void UpdateTreesForDownloadedCommits(string objectSha)
+ {
+ /* If we are downloading missing trees, we probably are missing more trees for the commit.
+ * Update our list of trees associated with the commit so we can use the # of missing trees
+ * as a heuristic to decide whether to batch download all the trees for the commit the
+ * next time a missing one is requested.
+ */
+ if (!this.treesWithDownloadedCommits.TryGetValue(objectSha, out var commitSha)
+ || this.PrefetchHasBeenDone())
+ {
+ return;
+ }
+
+ if (!this.context.Repository.TryGetObjectType(objectSha, out var objectType)
+ || objectType != Native.ObjectTypes.Tree)
+ {
+ return;
+ }
+
+ if (this.context.Repository.TryGetMissingSubTrees(objectSha, out var missingSubTrees))
+ {
+ foreach (var missingSubTree in missingSubTrees)
+ {
+ this.treesWithDownloadedCommits[missingSubTree] = commitSha;
+ }
+ }
}
- private void DownloadedCommitPack(string objectSha, string commitSha)
+ private void DownloadedCommitPack(string commitSha)
{
- this.lastCommitPackDownloadTime = DateTime.UtcNow;
- this.treesWithDownloadedCommits.Remove(objectSha);
+ var toRemove = this.treesWithDownloadedCommits.Where(x => x.Value == commitSha).ToList();
+ foreach (var tree in toRemove)
+ {
+ this.treesWithDownloadedCommits.Remove(tree.Key);
+ }
}
private void HandlePostFetchJobRequest(NamedPipeMessages.Message message, NamedPipeServer.Connection connection)
diff --git a/GVFS/GVFS.ReadObjectHook/main.cpp b/GVFS/GVFS.ReadObjectHook/main.cpp
index 7a3f60887..83f58beab 100644
--- a/GVFS/GVFS.ReadObjectHook/main.cpp
+++ b/GVFS/GVFS.ReadObjectHook/main.cpp
@@ -84,13 +84,13 @@ int main(int, char *argv[])
DisableCRLFTranslationOnStdPipes();
packet_txt_read(packet_buffer, sizeof(packet_buffer));
- if (strcmp(packet_buffer, "git-read-object-client"))
+ if (strcmp(packet_buffer, "git-read-object-client")) // CodeQL [SM01932] `packet_txt_read()` either NUL-terminates or `die()`s
{
die(ReadObjectHookErrorReturnCode::ErrorReadObjectProtocol, "Bad welcome message\n");
}
packet_txt_read(packet_buffer, sizeof(packet_buffer));
- if (strcmp(packet_buffer, "version=1"))
+ if (strcmp(packet_buffer, "version=1")) // CodeQL [SM01932] `packet_txt_read()` either NUL-terminates or `die()`s
{
die(ReadObjectHookErrorReturnCode::ErrorReadObjectProtocol, "Bad version\n");
}
@@ -105,7 +105,7 @@ int main(int, char *argv[])
packet_flush();
packet_txt_read(packet_buffer, sizeof(packet_buffer));
- if (strcmp(packet_buffer, "capability=get"))
+ if (strcmp(packet_buffer, "capability=get")) // CodeQL [SM01932] `packet_txt_read()` either NUL-terminates or `die()`s
{
die(ReadObjectHookErrorReturnCode::ErrorReadObjectProtocol, "Bad capability\n");
}
@@ -125,13 +125,13 @@ int main(int, char *argv[])
while (1)
{
packet_txt_read(packet_buffer, sizeof(packet_buffer));
- if (strcmp(packet_buffer, "command=get"))
+ if (strcmp(packet_buffer, "command=get")) // CodeQL [SM01932] `packet_txt_read()` either NUL-terminates or `die()`s
{
die(ReadObjectHookErrorReturnCode::ErrorReadObjectProtocol, "Bad command\n");
}
len = packet_txt_read(packet_buffer, sizeof(packet_buffer));
- if ((len != SHA1_LENGTH + 5) || strncmp(packet_buffer, "sha1=", 5))
+ if ((len != SHA1_LENGTH + 5) || strncmp(packet_buffer, "sha1=", 5)) // CodeQL [SM01932] `packet_txt_read()` either NUL-terminates or `die()`s
{
die(ReadObjectHookErrorReturnCode::ErrorReadObjectProtocol, "Bad sha1 in get command\n");
}
diff --git a/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.FolderData.cs b/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.FolderData.cs
index bc942d3b2..4777cabbc 100644
--- a/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.FolderData.cs
+++ b/GVFS/GVFS.Virtualization/Projection/GitIndexProjection.FolderData.cs
@@ -54,7 +54,7 @@ public void Include()
public string HashedChildrenNamesSha()
{
- using (HashAlgorithm hash = SHA1.Create())
+ using (HashAlgorithm hash = SHA1.Create()) // CodeQL [SM02196] SHA-1 is acceptable here because this is Git's hashing algorithm, not used for cryptographic purposes
{
for (int i = 0; i < this.ChildEntries.Count; i++)
{
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..e0d858360 100644
--- a/GVFS/GVFS/CommandLine/CloneVerb.cs
+++ b/GVFS/GVFS/CommandLine/CloneVerb.cs
@@ -6,9 +6,11 @@
using GVFS.Common.NamedPipes;
using GVFS.Common.Tracing;
using System;
+using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
+using System.Reflection;
using System.Text;
namespace GVFS.CommandLine
@@ -162,12 +164,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 +191,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,18 +244,26 @@ public override void Execute()
exitCode = (int)result;
}
}
-
else
{
- Process.Start(new ProcessStartInfo(
- fileName: "gvfs",
- arguments: "prefetch --commits")
+ try
{
- UseShellExecute = true,
- WindowStyle = ProcessWindowStyle.Hidden,
- WorkingDirectory = enlistment.EnlistmentRoot
- });
- this.Output.WriteLine("\r\nPrefetch of commit graph has been started as a background process. Git operations involving history may be slower until prefetch has completed.\r\n");
+ string gvfsExecutable = Assembly.GetExecutingAssembly().Location;
+ Process.Start(new ProcessStartInfo(
+ fileName: gvfsExecutable,
+ arguments: "prefetch --commits")
+ {
+ UseShellExecute = true,
+ WindowStyle = ProcessWindowStyle.Minimized,
+ WorkingDirectory = enlistment.EnlistmentRoot
+ });
+ this.Output.WriteLine("\r\nPrefetch of commit graph has been started as a background process. Git operations involving history may be slower until prefetch has completed.\r\n");
+ }
+ catch (Win32Exception ex)
+ {
+ this.Output.WriteLine("\r\nError starting prefetch: " + ex.Message);
+ this.Output.WriteLine("Run 'gvfs prefetch --commits' from within your enlistment to prefetch the commit graph.");
+ }
}
}
diff --git a/GVFS/GVFS/CommandLine/GVFSVerb.cs b/GVFS/GVFS/CommandLine/GVFSVerb.cs
index fa183c7a3..fe0731a00 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"),