@@ -27,6 +27,12 @@ public class InProcessMount
2727 private const int MaxPipeNameLength = 250 ;
2828 private const int MutexMaxWaitTimeMS = 500 ;
2929
30+ // This is value chosen based on tested scenarios to limit the required download time for
31+ // all the trees. This is approximately the amount of trees that can be downloaded in 1 second.
32+ // Downloading an entire commit pack also takes around 1 second, so this should limit downloading
33+ // all the trees in a commit to ~2-3 seconds.
34+ private const int MissingTreeThresholdForDownloadingCommitPack = 200 ;
35+
3036 private readonly bool showDebugWindow ;
3137
3238 private FileSystemCallbacks fileSystemCallbacks ;
@@ -47,7 +53,6 @@ public class InProcessMount
4753 private ManualResetEvent unmountEvent ;
4854
4955 private readonly Dictionary < string , string > treesWithDownloadedCommits = new Dictionary < string , string > ( ) ;
50- private DateTime lastCommitPackDownloadTime = DateTime . MinValue ;
5156
5257 // True if InProcessMount is calling git reset as part of processing
5358 // a folder dehydrate request
@@ -518,13 +523,14 @@ private void HandleDownloadObjectRequest(NamedPipeMessages.Message message, Name
518523 if ( this . ShouldDownloadCommitPack ( objectSha , out string commitSha )
519524 && this . gitObjects . TryDownloadCommit ( commitSha ) )
520525 {
521- this . DownloadedCommitPack ( objectSha : objectSha , commitSha : commitSha ) ;
526+ this . DownloadedCommitPack ( commitSha ) ;
522527 response = new NamedPipeMessages . DownloadObject . Response ( NamedPipeMessages . DownloadObject . SuccessResult ) ;
523528 // FUTURE: Should the stats be updated to reflect all the trees in the pack?
524529 // FUTURE: Should we try to clean up duplicate trees or increase depth of the commit download?
525530 }
526531 else if ( this . gitObjects . TryDownloadAndSaveObject ( objectSha , GVFSGitObjects . RequestSource . NamedPipeMessage ) == GitObjects . DownloadAndSaveObjectResult . Success )
527532 {
533+ this . UpdateTreesForDownloadedCommits ( objectSha ) ;
528534 response = new NamedPipeMessages . DownloadObject . Response ( NamedPipeMessages . DownloadObject . SuccessResult ) ;
529535 }
530536 else
@@ -548,7 +554,7 @@ private void HandleDownloadObjectRequest(NamedPipeMessages.Message message, Name
548554 * Otherwise, the trees for the commit may be needed soon depending on the context.
549555 * e.g. git log (without a pathspec) doesn't need trees, but git checkout does.
550556 *
551- * Save the tree/commit so if the tree is requested soon we can download all the trees for the commit in a batch.
557+ * Save the tree/commit so if more trees are requested we can download all the trees for the commit in a batch.
552558 */
553559 this . treesWithDownloadedCommits [ treeSha ] = objectSha ;
554560 }
@@ -561,28 +567,67 @@ private void HandleDownloadObjectRequest(NamedPipeMessages.Message message, Name
561567 private bool PrefetchHasBeenDone ( )
562568 {
563569 var prefetchPacks = this . gitObjects . ReadPackFileNames ( this . enlistment . GitPackRoot , GVFSConstants . PrefetchPackPrefix ) ;
564- return prefetchPacks . Length > 0 ;
570+ var result = prefetchPacks . Length > 0 ;
571+ if ( result )
572+ {
573+ this . treesWithDownloadedCommits . Clear ( ) ;
574+ }
575+ return result ;
565576 }
566577
567578 private bool ShouldDownloadCommitPack ( string objectSha , out string commitSha )
568579 {
569-
570580 if ( ! this . treesWithDownloadedCommits . TryGetValue ( objectSha , out commitSha )
571581 || this . PrefetchHasBeenDone ( ) )
572582 {
573583 return false ;
574584 }
575585
576- /* This is a heuristic to prevent downloading multiple packs related to git history commands,
577- * since commits downloaded close together likely have similar trees. */
578- var timePassed = DateTime . UtcNow - this . lastCommitPackDownloadTime ;
579- return ( timePassed > TimeSpan . FromMinutes ( 5 ) ) ;
586+ /* This is a heuristic to prevent downloading multiple packs related to git history commands.
587+ * Closely related commits are likely to have similar trees, so we'll find fewer missing trees in them.
588+ * Conversely, if we know (from previously downloaded missing trees) that a commit has a lot of missing
589+ * trees left, we'll probably need to download many more trees for the commit so we should download the pack.
590+ */
591+ var commitShaLocal = commitSha ; // can't use out parameter in lambda
592+ int missingTreeCount = this . treesWithDownloadedCommits . Where ( x => x . Value == commitShaLocal ) . Count ( ) ;
593+ return missingTreeCount > MissingTreeThresholdForDownloadingCommitPack ;
594+ }
595+
596+ private void UpdateTreesForDownloadedCommits ( string objectSha )
597+ {
598+ /* If we are downloading missing trees, we probably are missing more trees for the commit.
599+ * Update our list of trees associated with the commit so we can use the # of missing trees
600+ * as a heuristic to decide whether to batch download all the trees for the commit the
601+ * next time a missing one is requested.
602+ */
603+ if ( ! this . treesWithDownloadedCommits . TryGetValue ( objectSha , out var commitSha )
604+ || this . PrefetchHasBeenDone ( ) )
605+ {
606+ return ;
607+ }
608+
609+ if ( ! this . context . Repository . TryGetObjectType ( objectSha , out var objectType )
610+ || objectType != Native . ObjectTypes . Tree )
611+ {
612+ return ;
613+ }
614+
615+ if ( this . context . Repository . TryGetMissingSubTrees ( objectSha , out var missingSubTrees ) )
616+ {
617+ foreach ( var missingSubTree in missingSubTrees )
618+ {
619+ this . treesWithDownloadedCommits [ missingSubTree ] = commitSha ;
620+ }
621+ }
580622 }
581623
582- private void DownloadedCommitPack ( string objectSha , string commitSha )
624+ private void DownloadedCommitPack ( string commitSha )
583625 {
584- this . lastCommitPackDownloadTime = DateTime . UtcNow ;
585- this . treesWithDownloadedCommits . Remove ( objectSha ) ;
626+ var toRemove = this . treesWithDownloadedCommits . Where ( x => x . Value == commitSha ) . ToList ( ) ;
627+ foreach ( var tree in toRemove )
628+ {
629+ this . treesWithDownloadedCommits . Remove ( tree . Key ) ;
630+ }
586631 }
587632
588633 private void HandlePostFetchJobRequest ( NamedPipeMessages . Message message , NamedPipeServer . Connection connection )
0 commit comments