Skip to content

Commit 35b5504

Browse files
committed
Download all trees for non-prefetched commits when root tree is downloaded
1 parent 26b88aa commit 35b5504

5 files changed

Lines changed: 47 additions & 22 deletions

File tree

GVFS/GVFS.Common/Git/GitRepo.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.IO;
55
using System.IO.Compression;
66
using System.Linq;
7+
using static GVFS.Common.Git.LibGit2Repo;
78

89
namespace GVFS.Common.Git
910
{
@@ -60,9 +61,9 @@ public void OpenRepo()
6061
this.libgit2RepoInvoker?.InitializeSharedRepo();
6162
}
6263

63-
public bool TryGetIsBlob(string sha, out bool isBlob)
64+
public bool TryGetObjectType(string sha, out Native.ObjectTypes? objectType)
6465
{
65-
return this.libgit2RepoInvoker.TryInvoke(repo => repo.IsBlob(sha), out isBlob);
66+
return this.libgit2RepoInvoker.TryInvoke(repo => repo.GetObjectType(sha), out objectType);
6667
}
6768

6869
public virtual bool TryCopyBlobContentStream(string blobSha, Action<Stream, long> writeAction)
@@ -86,10 +87,12 @@ public virtual bool TryCopyBlobContentStream(string blobSha, Action<Stream, long
8687
return copyBlobResult;
8788
}
8889

89-
public virtual bool CommitAndRootTreeExists(string commitSha)
90+
public virtual bool CommitAndRootTreeExists(string commitSha, out string rootTreeSha)
9091
{
9192
bool output = false;
92-
this.libgit2RepoInvoker.TryInvoke(repo => repo.CommitAndRootTreeExists(commitSha), out output);
93+
string treeShaLocal = null;
94+
this.libgit2RepoInvoker.TryInvoke(repo => repo.CommitAndRootTreeExists(commitSha, out treeShaLocal), out output);
95+
rootTreeSha = treeShaLocal;
9396
return output;
9497
}
9598

GVFS/GVFS.Common/Git/LibGit2Repo.cs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,17 @@ protected LibGit2Repo()
4141
protected ITracer Tracer { get; }
4242
protected IntPtr RepoHandle { get; private set; }
4343

44-
public bool IsBlob(string sha)
44+
public Native.ObjectTypes? GetObjectType(string sha)
4545
{
4646
IntPtr objHandle;
4747
if (Native.RevParseSingle(out objHandle, this.RepoHandle, sha) != Native.SuccessCode)
4848
{
49-
return false;
49+
return null;
5050
}
5151

5252
try
5353
{
54-
switch (Native.Object.GetType(objHandle))
55-
{
56-
case Native.ObjectTypes.Blob:
57-
return true;
58-
59-
default:
60-
return false;
61-
}
54+
return Native.Object.GetType(objHandle);
6255
}
6356
finally
6457
{
@@ -91,9 +84,9 @@ public virtual string GetTreeSha(string commitish)
9184
return null;
9285
}
9386

94-
public virtual bool CommitAndRootTreeExists(string commitish)
87+
public virtual bool CommitAndRootTreeExists(string commitish, out string treeSha)
9588
{
96-
string treeSha = this.GetTreeSha(commitish);
89+
treeSha = this.GetTreeSha(commitish);
9790
if (treeSha == null)
9891
{
9992
return false;

GVFS/GVFS.Mount/InProcessMount.cs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
using System.IO;
1717
using System.Text;
1818
using System.Threading;
19+
using static GVFS.Common.Git.LibGit2Repo;
1920

2021
namespace GVFS.Mount
2122
{
@@ -44,6 +45,8 @@ public class InProcessMount
4445
private HeartbeatThread heartbeat;
4546
private ManualResetEvent unmountEvent;
4647

48+
private readonly Dictionary<string, string> treesWithRecentlyDownloadedCommits = new Dictionary<string,string>();
49+
4750
// True if InProcessMount is calling git reset as part of processing
4851
// a folder dehydrate request
4952
private volatile bool resetForDehydrateInProgress;
@@ -504,7 +507,21 @@ private void HandleDownloadObjectRequest(NamedPipeMessages.Message message, Name
504507
else
505508
{
506509
Stopwatch downloadTime = Stopwatch.StartNew();
507-
if (this.gitObjects.TryDownloadAndSaveObject(objectSha, GVFSGitObjects.RequestSource.NamedPipeMessage) == GitObjects.DownloadAndSaveObjectResult.Success)
510+
511+
/* If this is the root tree for a commit that was was just downloaded, assume that more
512+
* trees will be needed soon and download them as well by using the download commit API.
513+
*
514+
* Otherwise, or as a fallback if the commit download fails, download the object directly.
515+
*/
516+
if (this.treesWithRecentlyDownloadedCommits.TryGetValue(objectSha, out string commitSha)
517+
&& this.gitObjects.TryDownloadCommit(commitSha))
518+
{
519+
this.treesWithRecentlyDownloadedCommits.Remove(objectSha);
520+
response = new NamedPipeMessages.DownloadObject.Response(NamedPipeMessages.DownloadObject.SuccessResult);
521+
// FUTURE: Should the stats be updated to reflect all the trees in the pack?
522+
// FUTURE: Should we try to clean up duplicate trees or increase depth of the commit download?
523+
}
524+
else if (this.gitObjects.TryDownloadAndSaveObject(objectSha, GVFSGitObjects.RequestSource.NamedPipeMessage) == GitObjects.DownloadAndSaveObjectResult.Success)
508525
{
509526
response = new NamedPipeMessages.DownloadObject.Response(NamedPipeMessages.DownloadObject.SuccessResult);
510527
}
@@ -513,9 +530,20 @@ private void HandleDownloadObjectRequest(NamedPipeMessages.Message message, Name
513530
response = new NamedPipeMessages.DownloadObject.Response(NamedPipeMessages.DownloadObject.DownloadFailed);
514531
}
515532

516-
bool isBlob;
517-
this.context.Repository.TryGetIsBlob(objectSha, out isBlob);
518-
this.context.Repository.GVFSLock.Stats.RecordObjectDownload(isBlob, downloadTime.ElapsedMilliseconds);
533+
Native.ObjectTypes? objectType;
534+
this.context.Repository.TryGetObjectType(objectSha, out objectType);
535+
this.context.Repository.GVFSLock.Stats.RecordObjectDownload(objectType == Native.ObjectTypes.Blob, downloadTime.ElapsedMilliseconds);
536+
537+
if (objectType == Native.ObjectTypes.Commit
538+
&& !this.context.Repository.CommitAndRootTreeExists(objectSha, out var treeSha)
539+
&& !string.IsNullOrEmpty(treeSha))
540+
{
541+
/* If a commit is downloaded but it's tree doesn't exist, it means it hadn't been prefetched and all its trees
542+
* may be needed soon depending on the context. e.g. git log (without a pathspec) doesn't need trees, but git checkout does.
543+
* save the tree/commit so
544+
*/
545+
this.treesWithRecentlyDownloadedCommits[treeSha] = objectSha;
546+
}
519547
}
520548
}
521549

GVFS/GVFS.UnitTests/Mock/Git/MockLibGit2Repo.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ public MockLibGit2Repo(ITracer tracer)
1212
{
1313
}
1414

15-
public override bool CommitAndRootTreeExists(string commitish)
15+
public override bool CommitAndRootTreeExists(string commitish, out string treeSha)
1616
{
17+
treeSha = string.Empty;
1718
return false;
1819
}
1920

GVFS/GVFS/CommandLine/GVFSVerb.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,7 @@ protected bool TryDownloadCommit(
663663
out string error,
664664
bool checkLocalObjectCache = true)
665665
{
666-
if (!checkLocalObjectCache || !repo.CommitAndRootTreeExists(commitId))
666+
if (!checkLocalObjectCache || !repo.CommitAndRootTreeExists(commitId, out _))
667667
{
668668
if (!gitObjects.TryDownloadCommit(commitId))
669669
{

0 commit comments

Comments
 (0)