diff --git a/GVFS/GVFS.Common/Git/GitRepo.cs b/GVFS/GVFS.Common/Git/GitRepo.cs
index d88ebbd89..7b6dfddb8 100644
--- a/GVFS/GVFS.Common/Git/GitRepo.cs
+++ b/GVFS/GVFS.Common/Git/GitRepo.cs
@@ -114,6 +114,18 @@ public virtual bool ObjectExists(string blobSha)
return output;
}
+ ///
+ /// Checks whether the object can be fully parsed by libgit2 (not just that it exists).
+ /// Use this to detect corrupt objects. For simple existence checks,
+ /// prefer which is faster.
+ ///
+ public virtual bool ObjectCanBeParsed(string sha)
+ {
+ bool output = false;
+ this.libgit2RepoInvoker.TryInvoke(repo => repo.ObjectCanBeParsed(sha), out output);
+ return output;
+ }
+
///
/// Try to find the size of a given blob by SHA1 hash.
///
diff --git a/GVFS/GVFS.Common/Git/LibGit2Repo.cs b/GVFS/GVFS.Common/Git/LibGit2Repo.cs
index f0e7bc464..dafcc8d54 100644
--- a/GVFS/GVFS.Common/Git/LibGit2Repo.cs
+++ b/GVFS/GVFS.Common/Git/LibGit2Repo.cs
@@ -3,12 +3,14 @@
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
+using System.Threading;
namespace GVFS.Common.Git
{
public class LibGit2Repo : IDisposable
{
private bool disposedValue = false;
+ private IntPtr odbHandle = IntPtr.Zero;
public delegate void MultiVarConfigCallback(string value);
@@ -104,6 +106,55 @@ public virtual bool CommitAndRootTreeExists(string commitish, out string treeSha
}
public virtual bool ObjectExists(string sha)
+ {
+ IntPtr odb = this.odbHandle;
+ if (odb == IntPtr.Zero)
+ {
+ if (Native.Odb.GetOdb(out IntPtr newOdb, this.RepoHandle) != Native.ResultCode.Success)
+ {
+ return this.ObjectExistsFallback(sha);
+ }
+
+ IntPtr existing = Interlocked.CompareExchange(ref this.odbHandle, newOdb, IntPtr.Zero);
+ if (existing != IntPtr.Zero)
+ {
+ // Another thread won the race — free our duplicate and use theirs
+ Native.Odb.Free(newOdb);
+ odb = existing;
+ }
+ else
+ {
+ odb = newOdb;
+ }
+ }
+
+ GitOid oid;
+ if (Native.Odb.OidFromStr(out oid, sha) != Native.ResultCode.Success)
+ {
+ return false;
+ }
+
+ return Native.Odb.Exists(odb, ref oid) == 1;
+ }
+
+ private bool ObjectExistsFallback(string sha)
+ {
+ IntPtr objHandle;
+ if (Native.RevParseSingle(out objHandle, this.RepoHandle, sha) != Native.ResultCode.Success)
+ {
+ return false;
+ }
+
+ Native.Object.Free(objHandle);
+ return true;
+ }
+
+ ///
+ /// Checks whether the object can be fully parsed by libgit2 (not just that it exists).
+ /// Use this when you need to detect corrupt objects. For simple existence checks,
+ /// prefer which is faster.
+ ///
+ public virtual bool ObjectCanBeParsed(string sha)
{
IntPtr objHandle;
if (Native.RevParseSingle(out objHandle, this.RepoHandle, sha) != Native.ResultCode.Success)
@@ -360,6 +411,12 @@ protected virtual void Dispose(bool disposing)
{
if (!this.disposedValue)
{
+ if (this.odbHandle != IntPtr.Zero)
+ {
+ Native.Odb.Free(this.odbHandle);
+ this.odbHandle = IntPtr.Zero;
+ }
+
Native.Repo.Free(this.RepoHandle);
Native.Shutdown();
this.disposedValue = true;
@@ -504,6 +561,22 @@ public static class Repo
public static extern void Free(IntPtr repoHandle);
}
+ public static class Odb
+ {
+ [DllImport(Git2NativeLibName, EntryPoint = "git_repository_odb")]
+ public static extern ResultCode GetOdb(out IntPtr odbHandle, IntPtr repoHandle);
+
+ /// 1 if the object exists, 0 otherwise
+ [DllImport(Git2NativeLibName, EntryPoint = "git_odb_exists")]
+ public static extern int Exists(IntPtr odbHandle, ref GitOid id);
+
+ [DllImport(Git2NativeLibName, EntryPoint = "git_odb_free")]
+ public static extern void Free(IntPtr odbHandle);
+
+ [DllImport(Git2NativeLibName, EntryPoint = "git_oid_fromstr")]
+ public static extern ResultCode OidFromStr(out GitOid oid, string str);
+ }
+
public static class Config
{
[DllImport(Git2NativeLibName, EntryPoint = "git_repository_config")]
diff --git a/GVFS/GVFS.Common/Maintenance/LooseObjectsStep.cs b/GVFS/GVFS.Common/Maintenance/LooseObjectsStep.cs
index f71f0d6cc..f45049ec6 100644
--- a/GVFS/GVFS.Common/Maintenance/LooseObjectsStep.cs
+++ b/GVFS/GVFS.Common/Maintenance/LooseObjectsStep.cs
@@ -172,7 +172,7 @@ public void ClearCorruptLooseObjects(EventMetadata metadata)
// may be more bad objects in the next batch after deleting the corrupt objects.
foreach (string objectId in this.GetBatchOfLooseObjects(2 * this.MaxLooseObjectsInPack))
{
- if (!this.Context.Repository.ObjectExists(objectId))
+ if (!this.Context.Repository.ObjectCanBeParsed(objectId))
{
string objectFile = this.GetLooseObjectFileName(objectId);
diff --git a/GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs b/GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs
index 29bc4cc67..7010ebb7e 100644
--- a/GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs
+++ b/GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs
@@ -476,7 +476,10 @@ protected void DownloadMissingCommit(string commitSha, GitObjects gitObjects)
{
using (LibGit2Repo repo = new LibGit2Repo(this.Tracer, this.Enlistment.WorkingDirectoryBackingRoot))
{
- if (!repo.ObjectExists(commitSha))
+ // Use ObjectCanBeParsed (revparse) rather than ObjectExists (odb_exists)
+ // because commitSha may be a ref name or abbreviated SHA from CLI input
+ // (e.g. FastFetch --commit), not necessarily a full 40-char hex SHA.
+ if (!repo.ObjectCanBeParsed(commitSha))
{
if (!gitObjects.TryDownloadCommit(commitSha))
{