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)) {