Skip to content

Commit ab471c4

Browse files
committed
Download commit pack even when commit exists as loose object
When TryDownloadCommit finds the commit via CommitAndRootTreeExists, it now checks whether the commit is a loose object. Loose commits (e.g., from a prior 'git show' or 'git log' in a mounted enlistment) do not include reachable trees. Skipping the download in this case causes 'git checkout -f' to fail with 'unable to read tree', followed by an expensive fallback that re-downloads and retries checkout. If the commit is in a pack file (prefetch or commit pack), trees are included by the GVFS protocol, so the download can safely be skipped. Added GitRepo.LooseObjectExists() to check whether a SHA exists as a loose object file in the shared cache or local object store. Assisted-by: Claude Opus 4.6 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
1 parent b65af3a commit ab471c4

2 files changed

Lines changed: 51 additions & 4 deletions

File tree

GVFS/GVFS.Common/Git/GitRepo.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,35 @@ public virtual bool CommitAndRootTreeExists(string commitSha, out string rootTre
101101
return output;
102102
}
103103

104+
/// <summary>
105+
/// Check whether a given object SHA exists as a loose object file
106+
/// in the shared cache or local object store.
107+
/// </summary>
108+
public virtual bool LooseObjectExists(string sha)
109+
{
110+
if (GVFSPlatform.Instance.Constants.CaseSensitiveFileSystem)
111+
{
112+
sha = sha.ToLower();
113+
}
114+
115+
string looseObjectPath = Path.Combine(
116+
this.enlistment.GitObjectsRoot,
117+
sha.Substring(0, 2),
118+
sha.Substring(2));
119+
120+
if (this.fileSystem.FileExists(looseObjectPath))
121+
{
122+
return true;
123+
}
124+
125+
looseObjectPath = Path.Combine(
126+
this.enlistment.LocalObjectsRoot,
127+
sha.Substring(0, 2),
128+
sha.Substring(2));
129+
130+
return this.fileSystem.FileExists(looseObjectPath);
131+
}
132+
104133
public virtual bool ObjectExists(string blobSha)
105134
{
106135
bool output = false;

GVFS/GVFS/CommandLine/GVFSVerb.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -487,15 +487,33 @@ protected bool TryDownloadCommit(
487487
out string error,
488488
bool checkLocalObjectCache = true)
489489
{
490-
if (!checkLocalObjectCache || !repo.CommitAndRootTreeExists(commitId, out _))
490+
if (checkLocalObjectCache && repo.CommitAndRootTreeExists(commitId, out _))
491491
{
492-
if (!gitObjects.TryDownloadCommit(commitId))
492+
if (repo.LooseObjectExists(commitId))
493493
{
494-
error = "Could not download commit " + commitId + " from: " + Uri.EscapeDataString(objectRequestor.CacheServer.ObjectsEndpointUrl);
495-
return false;
494+
// The commit exists as a loose object (e.g., from a prior 'git show'
495+
// or 'git log' in a mounted enlistment). Loose commits do not include
496+
// their reachable trees — those would need to be fetched individually.
497+
// Download the commit pack which includes all reachable trees so that
498+
// operations like 'git checkout -f' can succeed without the read-object
499+
// hook.
500+
}
501+
else
502+
{
503+
// The commit exists in a pack file (prefetch pack or a previous commit
504+
// pack download). Packs from the GVFS protocol include all reachable
505+
// trees, so we can safely skip re-downloading.
506+
error = null;
507+
return true;
496508
}
497509
}
498510

511+
if (!gitObjects.TryDownloadCommit(commitId))
512+
{
513+
error = "Could not download commit " + commitId + " from: " + Uri.EscapeDataString(objectRequestor.CacheServer.ObjectsEndpointUrl);
514+
return false;
515+
}
516+
499517
error = null;
500518
return true;
501519
}

0 commit comments

Comments
 (0)