diff --git a/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs b/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs
index 649cb26ec..091876d8a 100644
--- a/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs
+++ b/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs
@@ -556,5 +556,47 @@ public void CalculatingHistoryDivergenceWithBadParamsThrows()
() => repo.ObjectDatabase.CalculateHistoryDivergence(null, repo.Head.Tip));
}
}
+
+ [Fact]
+ public void CanShortenObjectIdentifier()
+ {
+ /*
+ * $ echo "aabqhq" | git hash-object -t blob --stdin
+ * dea509d0b3cb8ee0650f6ca210bc83f4678851ba
+ *
+ * $ echo "aaazvc" | git hash-object -t blob --stdin
+ * dea509d097ce692e167dfc6a48a7a280cc5e877e
+ */
+
+ string path = CloneBareTestRepo();
+ using (var repo = new Repository(path))
+ {
+ repo.Config.Set("core.abbrev", 4);
+
+ Blob blob1 = CreateBlob(repo, "aabqhq\n");
+ Assert.Equal("dea509d0b3cb8ee0650f6ca210bc83f4678851ba", blob1.Sha);
+
+ Assert.Equal("dea5", repo.ObjectDatabase.ShortenObjectId(blob1));
+ Assert.Equal("dea509d0b3cb", repo.ObjectDatabase.ShortenObjectId(blob1, 12));
+ Assert.Equal("dea509d0b3cb8ee0650f6ca210bc83f4678851b", repo.ObjectDatabase.ShortenObjectId(blob1, 39));
+
+ Blob blob2 = CreateBlob(repo, "aaazvc\n");
+ Assert.Equal("dea509d09", repo.ObjectDatabase.ShortenObjectId(blob2));
+ Assert.Equal("dea509d09", repo.ObjectDatabase.ShortenObjectId(blob2, 4));
+ Assert.Equal("dea509d0b", repo.ObjectDatabase.ShortenObjectId(blob1));
+ Assert.Equal("dea509d0b", repo.ObjectDatabase.ShortenObjectId(blob1, 7));
+
+ Assert.Equal("dea509d0b3cb", repo.ObjectDatabase.ShortenObjectId(blob1, 12));
+ Assert.Equal("dea509d097ce", repo.ObjectDatabase.ShortenObjectId(blob2, 12));
+ }
+ }
+
+ private static Blob CreateBlob(Repository repo, string content)
+ {
+ using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
+ {
+ return repo.ObjectDatabase.CreateBlob(stream);
+ }
+ }
}
}
diff --git a/LibGit2Sharp.Tests/OdbBackendFixture.cs b/LibGit2Sharp.Tests/OdbBackendFixture.cs
index 1304d1097..cd8429ffc 100644
--- a/LibGit2Sharp.Tests/OdbBackendFixture.cs
+++ b/LibGit2Sharp.Tests/OdbBackendFixture.cs
@@ -177,6 +177,50 @@ public void CanPushWithACustomBackend()
}
}
+ [Fact]
+ public void CanShortenObjectIdentifier()
+ {
+ /*
+ * $ echo "aabqhq" | git hash-object -t blob --stdin
+ * dea509d0b3cb8ee0650f6ca210bc83f4678851ba
+ *
+ * $ echo "aaazvc" | git hash-object -t blob --stdin
+ * dea509d097ce692e167dfc6a48a7a280cc5e877e
+ */
+
+ string path = CloneBareTestRepo();
+ using (var repo = new Repository(path))
+ {
+ repo.ObjectDatabase.AddBackend(new MockOdbBackend(), 5);
+
+ repo.Config.Set("core.abbrev", 4);
+
+ Blob blob1 = CreateBlob(repo, "aabqhq\n");
+ Assert.Equal("dea509d0b3cb8ee0650f6ca210bc83f4678851ba", blob1.Sha);
+
+ Assert.Equal("dea5", repo.ObjectDatabase.ShortenObjectId(blob1));
+ Assert.Equal("dea509d0b3cb", repo.ObjectDatabase.ShortenObjectId(blob1, 12));
+ Assert.Equal("dea509d0b3cb8ee0650f6ca210bc83f4678851b", repo.ObjectDatabase.ShortenObjectId(blob1, 39));
+
+ Blob blob2 = CreateBlob(repo, "aaazvc\n");
+ Assert.Equal("dea509d09", repo.ObjectDatabase.ShortenObjectId(blob2));
+ Assert.Equal("dea509d09", repo.ObjectDatabase.ShortenObjectId(blob2, 4));
+ Assert.Equal("dea509d0b", repo.ObjectDatabase.ShortenObjectId(blob1));
+ Assert.Equal("dea509d0b", repo.ObjectDatabase.ShortenObjectId(blob1, 7));
+
+ Assert.Equal("dea509d0b3cb", repo.ObjectDatabase.ShortenObjectId(blob1, 12));
+ Assert.Equal("dea509d097ce", repo.ObjectDatabase.ShortenObjectId(blob2, 12));
+ }
+ }
+
+ private static Blob CreateBlob(Repository repo, string content)
+ {
+ using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)))
+ {
+ return repo.ObjectDatabase.CreateBlob(stream);
+ }
+ }
+
#region MockOdbBackend
private class MockOdbBackend : OdbBackend
@@ -190,6 +234,7 @@ protected override OdbBackendOperations SupportedOperations
OdbBackendOperations.Write |
OdbBackendOperations.WriteStream |
OdbBackendOperations.Exists |
+ OdbBackendOperations.ExistsPrefix |
OdbBackendOperations.ForEach |
OdbBackendOperations.ReadHeader;
}
@@ -301,6 +346,37 @@ public override bool Exists(ObjectId oid)
return m_objectIdToContent.ContainsKey(oid);
}
+ public override int ExistsPrefix(string shortSha, out ObjectId found)
+ {
+ found = null;
+ int numFound = 0;
+
+ foreach (ObjectId id in m_objectIdToContent.Keys)
+ {
+ if (!id.Sha.StartsWith(shortSha))
+ {
+ continue;
+ }
+
+ found = id;
+ numFound++;
+
+ if (numFound > 1)
+ {
+ found = null;
+ return (int) ReturnCode.GIT_EAMBIGUOUS;
+ }
+ }
+
+ if (numFound == 0)
+ {
+ found = null;
+ return (int)ReturnCode.GIT_ENOTFOUND;
+ }
+
+ return (int)ReturnCode.GIT_OK;
+ }
+
public override int ReadHeader(ObjectId oid, out int length, out ObjectType objectType)
{
objectType = default(ObjectType);
diff --git a/LibGit2Sharp/Core/GitOdbBackend.cs b/LibGit2Sharp/Core/GitOdbBackend.cs
index 4f69ac70f..a7b0acf41 100644
--- a/LibGit2Sharp/Core/GitOdbBackend.cs
+++ b/LibGit2Sharp/Core/GitOdbBackend.cs
@@ -30,7 +30,7 @@ static GitOdbBackend()
public writestream_callback WriteStream;
public readstream_callback ReadStream;
public exists_callback Exists;
- public IntPtr ExistsPrefix;
+ public exists_prefix_callback ExistsPrefix;
public IntPtr Refresh;
public foreach_callback Foreach;
public IntPtr Writepack;
@@ -157,6 +157,23 @@ public delegate bool exists_callback(
IntPtr backend,
ref GitOid oid);
+ ///
+ /// The backend is passed a short OID and the number of characters in that short OID.
+ /// The backend is asked to return a value that indicates whether or not
+ /// the object exists in the backing store. The short OID might not be long enough to resolve
+ /// to just one object. In that case the backend should return GIT_EAMBIGUOUS.
+ ///
+ /// [out] If the call is successful, the backend will write the full OID if the object here.
+ /// [in] A pointer to the backend which is being asked to perform the task.
+ /// [in] The short-form OID which the backend is being asked to look up.
+ /// [in] The length of the short-form OID (short_oid).
+ /// 1 if the object exists, 0 if the object doesn't; an error code otherwise.
+ public delegate int exists_prefix_callback(
+ ref GitOid found_oid,
+ IntPtr backend,
+ ref GitOid short_oid,
+ UIntPtr len);
+
///
/// The backend is passed a callback function and a void* to pass through to the callback. The backend is
/// asked to iterate through all objects in the backing store, invoking the callback for each item.
diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs
index ea5bff09b..e25a9a353 100644
--- a/LibGit2Sharp/Core/NativeMethods.cs
+++ b/LibGit2Sharp/Core/NativeMethods.cs
@@ -753,6 +753,11 @@ internal static extern int git_object_peel(
GitObjectSafeHandle obj,
GitObjectType type);
+ [DllImport(libgit2)]
+ internal static extern int git_object_short_id(
+ GitBuf buf,
+ GitObjectSafeHandle obj);
+
[DllImport(libgit2)]
internal static extern GitObjectType git_object_type(GitObjectSafeHandle obj);
diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs
index 00d24d6c9..1d05208ac 100644
--- a/LibGit2Sharp/Core/Proxy.cs
+++ b/LibGit2Sharp/Core/Proxy.cs
@@ -1251,6 +1251,19 @@ public static GitObjectSafeHandle git_object_peel(RepositorySafeHandle repo, Obj
}
}
+ public static string git_object_short_id(RepositorySafeHandle repo, ObjectId id)
+ {
+ using (ThreadAffinity())
+ using (var obj = new ObjectSafeWrapper(id, repo))
+ using (var buf = new GitBuf())
+ {
+ int res = NativeMethods.git_object_short_id(buf, obj.ObjectPtr);
+ Ensure.Int32Result(res);
+
+ return LaxUtf8Marshaler.FromNative(buf.ptr);
+ }
+ }
+
public static GitObjectType git_object_type(GitObjectSafeHandle obj)
{
return NativeMethods.git_object_type(obj);
diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs
index 3d36b9c26..2a51fad5c 100644
--- a/LibGit2Sharp/ObjectDatabase.cs
+++ b/LibGit2Sharp/ObjectDatabase.cs
@@ -368,5 +368,30 @@ public virtual HistoryDivergence CalculateHistoryDivergence(Commit one, Commit a
return new HistoryDivergence(repo, one, another);
}
+
+ ///
+ /// Calculates the current shortest abbreviated
+ /// string representation for a .
+ ///
+ /// The which identifier should be shortened.
+ /// Minimum length of the shortened representation.
+ /// A short string representation of the .
+ public virtual string ShortenObjectId(GitObject gitObject, int? minLength = null)
+ {
+ if (minLength.HasValue && (minLength <= 0 || minLength > ObjectId.HexSize))
+ {
+ throw new ArgumentOutOfRangeException("minLength", minLength,
+ string.Format("Expected value should be greater than zero and less than or equal to {0}.", ObjectId.HexSize));
+ }
+
+ string shortSha = Proxy.git_object_short_id(repo.Handle, gitObject.Id);
+
+ if (minLength == null || (minLength <= shortSha.Length))
+ {
+ return shortSha;
+ }
+
+ return gitObject.Sha.Substring(0, minLength.Value);
+ }
}
}
diff --git a/LibGit2Sharp/ObjectId.cs b/LibGit2Sharp/ObjectId.cs
index 0d7b845dd..7b247e8a1 100644
--- a/LibGit2Sharp/ObjectId.cs
+++ b/LibGit2Sharp/ObjectId.cs
@@ -11,13 +11,13 @@ namespace LibGit2Sharp
public sealed class ObjectId : IEquatable
{
private readonly GitOid oid;
- private const int rawSize = 20;
+ private const int rawSize = GitOid.Size;
private readonly string sha;
///
/// Size of the string-based representation of a SHA-1.
///
- private const int HexSize = rawSize * 2;
+ internal const int HexSize = rawSize * 2;
private const string hexDigits = "0123456789abcdef";
private static readonly byte[] reverseHexDigits = BuildReverseHexDigits();
diff --git a/LibGit2Sharp/OdbBackend.cs b/LibGit2Sharp/OdbBackend.cs
index 549942537..04b43a72e 100644
--- a/LibGit2Sharp/OdbBackend.cs
+++ b/LibGit2Sharp/OdbBackend.cs
@@ -106,6 +106,11 @@ public abstract int WriteStream(
///
public abstract bool Exists(ObjectId id);
+ ///
+ /// Requests that this backend check if an object ID exists. The object ID may not be complete (may be a prefix).
+ ///
+ public abstract int ExistsPrefix(string shortSha, out ObjectId found);
+
///
/// Requests that this backend enumerate all items in the backing store.
///
@@ -169,6 +174,11 @@ internal IntPtr GitOdbBackendPointer
nativeBackend.Exists = BackendEntryPoints.ExistsCallback;
}
+ if ((supportedOperations & OdbBackendOperations.ExistsPrefix) != 0)
+ {
+ nativeBackend.ExistsPrefix = BackendEntryPoints.ExistsPrefixCallback;
+ }
+
if ((supportedOperations & OdbBackendOperations.ForEach) != 0)
{
nativeBackend.Foreach = BackendEntryPoints.ForEachCallback;
@@ -196,6 +206,7 @@ private static class BackendEntryPoints
public static readonly GitOdbBackend.write_callback WriteCallback = Write;
public static readonly GitOdbBackend.writestream_callback WriteStreamCallback = WriteStream;
public static readonly GitOdbBackend.exists_callback ExistsCallback = Exists;
+ public static readonly GitOdbBackend.exists_prefix_callback ExistsPrefixCallback = ExistsPrefix;
public static readonly GitOdbBackend.foreach_callback ForEachCallback = Foreach;
public static readonly GitOdbBackend.free_callback FreeCallback = Free;
@@ -498,6 +509,40 @@ private static bool Exists(
}
}
+ private static int ExistsPrefix(
+ ref GitOid found_oid,
+ IntPtr backend,
+ ref GitOid short_oid,
+ UIntPtr len)
+ {
+ OdbBackend odbBackend = MarshalOdbBackend(backend);
+ if (odbBackend == null)
+ {
+ return (int)GitErrorCode.Error;
+ }
+
+ try
+ {
+ ObjectId found;
+ var shortSha = ObjectId.ToString(short_oid.Id, (int)len);
+
+ found_oid.Id = ObjectId.Zero.RawId;
+ int result = odbBackend.ExistsPrefix(shortSha, out found);
+
+ if (result == (int) GitErrorCode.Ok)
+ {
+ found_oid.Id = found.RawId;
+ }
+
+ return result;
+ }
+ catch (Exception ex)
+ {
+ Proxy.giterr_set_str(GitErrorCategory.Odb, ex);
+ return (int)GitErrorCode.Error;
+ }
+ }
+
private static int Foreach(
IntPtr backend,
GitOdbBackend.foreach_callback_callback cb,
@@ -620,10 +665,15 @@ protected enum OdbBackendOperations
///
Exists = 64,
+ ///
+ /// This OdbBackend declares that it supports the ExistsPrefix method.
+ ///
+ ExistsPrefix = 128,
+
///
/// This OdbBackend declares that it supports the Foreach method.
///
- ForEach = 128,
+ ForEach = 256,
}
///