diff options
author | nulltoken <emeric.fermas@gmail.com> | 2011-06-20 13:49:30 +0400 |
---|---|---|
committer | nulltoken <emeric.fermas@gmail.com> | 2011-06-20 22:53:55 +0400 |
commit | 87879f34a2939107d0c2df598b675f1d7ad148f7 (patch) | |
tree | ce17dbc7e55a2bf938eab6e1b9052a423574e8e7 | |
parent | 0c61d1de2886346134377cc35f4d69bf6c6af121 (diff) |
Add handling of abbreviated sha to Repository.Lookup() and ObjectId.ToString()
-rw-r--r-- | LibGit2Sharp.Tests/ObjectIdFixture.cs | 61 | ||||
-rw-r--r-- | LibGit2Sharp.Tests/RepositoryFixture.cs | 31 | ||||
-rw-r--r-- | LibGit2Sharp/BranchCollection.cs | 6 | ||||
-rw-r--r-- | LibGit2Sharp/Core/NativeMethods.cs | 9 | ||||
-rw-r--r-- | LibGit2Sharp/GitObject.cs | 2 | ||||
-rw-r--r-- | LibGit2Sharp/LibGit2Sharp.csproj | 1 | ||||
-rw-r--r-- | LibGit2Sharp/ObjectId.cs | 202 | ||||
-rw-r--r-- | LibGit2Sharp/ReferenceCollection.cs | 14 | ||||
-rw-r--r-- | LibGit2Sharp/Repository.cs | 24 | ||||
-rw-r--r-- | libgit2sharp/AbbreviatedObjectId.cs | 22 |
10 files changed, 303 insertions, 69 deletions
diff --git a/LibGit2Sharp.Tests/ObjectIdFixture.cs b/LibGit2Sharp.Tests/ObjectIdFixture.cs index 3fa62696..05a107df 100644 --- a/LibGit2Sharp.Tests/ObjectIdFixture.cs +++ b/LibGit2Sharp.Tests/ObjectIdFixture.cs @@ -7,11 +7,14 @@ namespace LibGit2Sharp.Tests [TestFixture] public class ObjectIdFixture { + private const string validSha1 = "ce08fe4884650f067bd5703b6a59a8b3b3c99a09"; + private const string validSha2 = "de08fe4884650f067bd5703b6a59a8b3b3c99a09"; + [TestCase("Dummy", typeof(ArgumentException))] [TestCase("", typeof(ArgumentException))] [TestCase("8e", typeof(ArgumentException))] [TestCase(null, typeof(ArgumentNullException))] - [TestCase("ce08fe4884650f067bd5703b6a59a8b3b3c99a09dd", typeof(ArgumentException))] + [TestCase(validSha1 + "dd", typeof(ArgumentException))] public void PreventsFromBuildingWithAnInvalidSha(string malformedSha, Type expectedExceptionType) { Assert.Throws(expectedExceptionType, () => new ObjectId(malformedSha)); @@ -24,14 +27,15 @@ namespace LibGit2Sharp.Tests var id = new ObjectId(bytes); - id.Sha.ShouldEqual("ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); - id.ToString().ShouldEqual("ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); + id.Sha.ShouldEqual(validSha1); + id.ToString().ShouldEqual(validSha1); } [Test] public void CanConvertShaToOid() { - var id = new ObjectId("ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); + var id = new ObjectId(validSha1); + id.RawId.ShouldEqual(new byte[] { 206, 8, 254, 72, 132, 101, 15, 6, 123, 213, 112, 59, 106, 89, 168, 179, 179, 201, 154, 9 }); } @@ -46,10 +50,12 @@ namespace LibGit2Sharp.Tests [Test] public void DifferentObjectIdsAreEqual() { - var a = new ObjectId("ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); - var b = new ObjectId("de08fe4884650f067bd5703b6a59a8b3b3c99a09"); + var a = new ObjectId(validSha1); + var b = new ObjectId(validSha2); + (a.Equals(b)).ShouldBeFalse(); (b.Equals(a)).ShouldBeFalse(); + (a == b).ShouldBeFalse(); (a != b).ShouldBeTrue(); } @@ -57,18 +63,21 @@ namespace LibGit2Sharp.Tests [Test] public void DifferentObjectIdsDoesNotHaveSameHashCode() { - var a = new ObjectId("ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); - var b = new ObjectId("de08fe4884650f067bd5703b6a59a8b3b3c99a09"); + var a = new ObjectId(validSha1); + var b = new ObjectId(validSha2); + a.GetHashCode().ShouldNotEqual(b.GetHashCode()); } [Test] public void SimilarObjectIdsAreEqual() { - var a = new ObjectId("ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); - var b = new ObjectId("ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); + var a = new ObjectId(validSha1); + var b = new ObjectId(validSha1); + (a.Equals(b)).ShouldBeTrue(); (b.Equals(a)).ShouldBeTrue(); + (a == b).ShouldBeTrue(); (a != b).ShouldBeFalse(); } @@ -76,9 +85,37 @@ namespace LibGit2Sharp.Tests [Test] public void SimilarObjectIdsHaveSameHashCode() { - var a = new ObjectId("ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); - var b = new ObjectId("ce08fe4884650f067bd5703b6a59a8b3b3c99a09"); + var a = new ObjectId(validSha1); + var b = new ObjectId(validSha1); + a.GetHashCode().ShouldEqual(b.GetHashCode()); } + + [TestCase("Dummy", false)] + [TestCase(null, false)] + [TestCase("", false)] + [TestCase("0", false)] + [TestCase("01", false)] + [TestCase("012", false)] + [TestCase("0123", true)] + [TestCase("0123456", true)] + [TestCase(validSha1 + "d", false)] + [TestCase(validSha1, true)] + public void TryParse(string maybeSha, bool isValidSha) + { + ObjectId parsedObjectId; + bool result = ObjectId.TryParse(maybeSha, out parsedObjectId); + result.ShouldEqual(isValidSha); + + if (!result) + { + return; + } + + parsedObjectId.ShouldNotBeNull(); + parsedObjectId.Sha.ShouldEqual(maybeSha); + maybeSha.StartsWith(parsedObjectId.ToString(3)).ShouldBeTrue(); + parsedObjectId.ToString(42).ShouldEqual(maybeSha); + } } }
\ No newline at end of file diff --git a/LibGit2Sharp.Tests/RepositoryFixture.cs b/LibGit2Sharp.Tests/RepositoryFixture.cs index 937df05a..68d3318a 100644 --- a/LibGit2Sharp.Tests/RepositoryFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryFixture.cs @@ -218,6 +218,37 @@ namespace LibGit2Sharp.Tests } [Test] + public void CanLookupWhithShortIdentifers() + { + const string expectedAbbrevSha = "edfecad"; + const string expectedSha = expectedAbbrevSha + "02d96c9dbf64f6e238c45ddcfa762eef0"; + + using (var scd = new SelfCleaningDirectory()) + { + var dir = Repository.Init(scd.DirectoryPath); + + using (var repo = new Repository(dir)) + { + string filePath = Path.Combine(repo.Info.WorkingDirectory, "new.txt"); + + File.WriteAllText(filePath, "one "); + repo.Index.Stage(filePath); + + var author = Constants.Signature; + var commit = repo.Commit(author, author, "Initial commit"); + + commit.Sha.ShouldEqual(expectedSha); + + var lookedUp1 = repo.Lookup(expectedSha); + lookedUp1.ShouldEqual(commit); + + var lookedUp2 = repo.Lookup(expectedAbbrevSha); + lookedUp2.ShouldEqual(commit); + } + } + } + + [Test] public void LookingUpWithBadParamsThrows() { using (var repo = new Repository(Constants.BareTestRepoPath)) diff --git a/LibGit2Sharp/BranchCollection.cs b/LibGit2Sharp/BranchCollection.cs index 833db24d..c02a7c3c 100644 --- a/LibGit2Sharp/BranchCollection.cs +++ b/LibGit2Sharp/BranchCollection.cs @@ -79,9 +79,9 @@ namespace LibGit2Sharp /// <returns></returns> public Branch Create(string name, string target) { - ObjectId id = ObjectId.CreateFromMaybeSha(target); - - if (id == null) + ObjectId id; + + if (!ObjectId.TryParse(target, out id)) { var reference = repo.Refs[NormalizeToCanonicalName(target)].ResolveToDirectReference(); target = reference.TargetIdentifier; diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index b48d37a3..a756d608 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -78,6 +78,9 @@ namespace LibGit2Sharp.Core public static extern int git_object_lookup(out IntPtr obj, RepositorySafeHandle repo, ref GitOid id, GitObjectType type); [DllImport(libgit2)] + public static extern int git_object_lookup_prefix(out IntPtr obj, RepositorySafeHandle repo, ref GitOid id, uint len, GitObjectType type); + + [DllImport(libgit2)] public static extern GitObjectType git_object_type(IntPtr obj); [DllImport(libgit2)] @@ -91,12 +94,6 @@ namespace LibGit2Sharp.Core public static extern int git_oid_cmp(ref GitOid a, ref GitOid b); [DllImport(libgit2)] - public static extern void git_oid_fmt(byte[] str, ref GitOid oid); - - [DllImport(libgit2)] - public static extern int git_oid_fromstr(out GitOid oid, string str); - - [DllImport(libgit2)] public static extern int git_reference_create_oid(out IntPtr reference, RepositorySafeHandle repo, string name, ref GitOid oid); [DllImport(libgit2)] diff --git a/LibGit2Sharp/GitObject.cs b/LibGit2Sharp/GitObject.cs index c0714380..2ed16916 100644 --- a/LibGit2Sharp/GitObject.cs +++ b/LibGit2Sharp/GitObject.cs @@ -60,7 +60,7 @@ namespace LibGit2Sharp case GitObjectType.Blob: return Blob.BuildFromPtr(obj, id, repo); default: - return new GitObject(id); + throw new InvalidOperationException(string.Format("Unexpected type '{0}' for object '{1}'.", type, id)); } } finally diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 43ac7496..dedb29d5 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -44,6 +44,7 @@ <Reference Include="System.Core" /> </ItemGroup> <ItemGroup> + <Compile Include="AbbreviatedObjectId.cs" /> <Compile Include="Blob.cs" /> <Compile Include="Branch.cs" /> <Compile Include="BranchCollection.cs" /> diff --git a/LibGit2Sharp/ObjectId.cs b/LibGit2Sharp/ObjectId.cs index c26d8bbf..af6fd8c6 100644 --- a/LibGit2Sharp/ObjectId.cs +++ b/LibGit2Sharp/ObjectId.cs @@ -1,6 +1,5 @@ using System; -using System.Globalization; -using System.Text; +using System.Linq; using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -12,7 +11,14 @@ namespace LibGit2Sharp { private readonly GitOid oid; private const int rawSize = 20; - private const int hexSize = rawSize * 2; + private readonly string sha; + + protected const int HexSize = rawSize * 2; + protected const int MinHexSize = 4; + + private const string hexDigits = "0123456789abcdef"; + private static readonly byte[] reverseHexDigits = BuildReverseHexDigits(); + private static readonly Func<int, byte> byteConverter = i => reverseHexDigits[i - '0']; private static readonly LambdaEqualityHelper<ObjectId> equalityHelper = new LambdaEqualityHelper<ObjectId>(new Func<ObjectId, object>[] { x => x.Sha }); @@ -24,7 +30,7 @@ namespace LibGit2Sharp internal ObjectId(GitOid oid) { this.oid = oid; - Sha = Stringify(this.oid); + sha = ToString(oid.Id); } /// <summary> @@ -44,8 +50,15 @@ namespace LibGit2Sharp /// <param name="sha">The sha.</param> public ObjectId(string sha) { - oid = CreateFromSha(sha, true).GetValueOrDefault(); - Sha = sha; + GitOid? parsedOid = BuildOidFrom(sha, true, false); + + if (!parsedOid.HasValue) + { + throw new ArgumentException(string.Format("'{0}' is not a valid Sha-1.", sha)); + } + + oid = parsedOid.Value; + this.sha = sha; } internal GitOid Oid @@ -64,51 +77,48 @@ namespace LibGit2Sharp /// <summary> /// Gets the sha. /// </summary> - public string Sha { get; private set; } + public virtual string Sha { get { return sha; } } - internal static ObjectId CreateFromMaybeSha(string sha) + /// <summary> + /// Converts the specified string representation of a Sha-1 to its <see cref="ObjectId"/> equivalent and returns a value that indicates whether the conversion succeeded. + /// </summary> + /// <param name="sha">A string containing a Sha-1 to convert. </param> + /// <param name="result">When this method returns, contains the <see cref="ObjectId"/> value equivalent to the Sha-1 contained in <paramref name="sha"/>, if the conversion succeeded, or <code>null</code> if the conversion failed.</param> + /// <returns>true if the <paramref name="sha"/> parameter was converted successfully; otherwise, false.</returns> + public static bool TryParse(string sha, out ObjectId result) { - GitOid? oid = CreateFromSha(sha, false); + return TryParseInternal(sha, true, out result); + } - if (!oid.HasValue) - { - return null; - } + internal static bool TryParseInternal(string sha, bool allowShortIdentifier, out ObjectId result) + { + result = BuildFrom(sha, false, allowShortIdentifier); - return new ObjectId(oid.Value); + return (result == null) ? false : true; } - private static GitOid? CreateFromSha(string sha, bool shouldThrow) + private static GitOid? BuildOidFrom(string sha, bool shouldThrowIfInvalid, bool allowShortIdentifier) { - Ensure.ArgumentNotNullOrEmptyString(sha, "sha"); - - if (sha.Length != hexSize) + if (!LooksValid(sha, shouldThrowIfInvalid, allowShortIdentifier)) { - if (!shouldThrow) - { - return null; - } - - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "'{0}' is not a valid sha. Expected length should equal {1}.", sha, hexSize)); + return null; } - GitOid oid; - var result = NativeMethods.git_oid_fromstr(out oid, sha); + return ToOid(sha); + } - if (!shouldThrow && result != (int)GitErrorCode.GIT_SUCCESS) + private static ObjectId BuildFrom(string sha, bool shouldThrowIfInvalid, bool allowShortIdentifier) + { + GitOid? oid = BuildOidFrom(sha, shouldThrowIfInvalid, allowShortIdentifier); + + if (!oid.HasValue) { return null; } - Ensure.Success(result); - return oid; - } + ObjectId objectId = sha.Length == HexSize ? new ObjectId(oid.Value) : new AbbreviatedObjectId(oid.Value, sha.Length); - private static string Stringify(GitOid oid) - { - var hex = new byte[hexSize]; - NativeMethods.git_oid_fmt(hex, ref oid); - return Encoding.ASCII.GetString(hex); + return objectId; } /// <summary> @@ -150,6 +160,32 @@ namespace LibGit2Sharp } /// <summary> + /// Returns the <see cref="Sha"/>, a <see cref="String"/> representation of the current <see cref="ObjectId"/>. + /// </summary> + /// <param name="prefixLength">The number of chars the <see cref="Sha"/> should be truncated to.</param> + /// <returns>The <see cref="Sha"/> that represents the current <see cref="ObjectId"/>.</returns> + public string ToString(int prefixLength) + { + int normalizedLength = NormalizeLength(prefixLength); + return Sha.Substring(0, Math.Min(Sha.Length, normalizedLength)); + } + + private static int NormalizeLength(int prefixLength) + { + if (prefixLength < MinHexSize) + { + return MinHexSize; + } + + if (prefixLength > HexSize) + { + return HexSize; + } + + return prefixLength; + } + + /// <summary> /// Tests if two <see cref="ObjectId"/> are equal. /// </summary> /// <param name="left">First <see cref="ObjectId"/> to compare.</param> @@ -170,5 +206,99 @@ namespace LibGit2Sharp { return !Equals(left, right); } + + private static byte[] BuildReverseHexDigits() + { + var bytes = new byte['f' - '0' + 1]; + + for (int i = 0; i < 10; i++) + { + bytes[i] = (byte)i; + } + + for (int i = 10; i < 16; i++) + { + bytes[i + 'a' - '0' - 0x0a] = (byte)(i); + } + + return bytes; + } + + private static string ToString(byte[] id) + { + if (id == null || id.Length != rawSize) + { + throw new ArgumentException("id"); + } + + // Inspired from http://stackoverflow.com/questions/623104/c-byte-to-hex-string/3974535#3974535 + + var c = new char[HexSize]; + + for (int i = 0; i < HexSize; i++) + { + int index0 = i >> 1; + var b = ((byte)(id[index0] >> 4)); + c[i++] = hexDigits[b]; + + b = ((byte)(id[index0] & 0x0F)); + c[i] = hexDigits[b]; + } + + return new string(c); + } + + private static GitOid ToOid(string sha) + { + var bytes = new byte[rawSize]; + + if ((sha.Length & 1) == 1) + { + sha += "0"; + } + + for (int i = 0; i < sha.Length; i++) + { + int c1 = byteConverter(sha[i++]) << 4; + int c2 = byteConverter(sha[i]); + + bytes[i >> 1] = (byte)(c1 + c2); + } + + var oid = new GitOid{Id = bytes}; + return oid; + } + + private static bool LooksValid(string objectId, bool throwIfInvalid, bool allowShortIdentifier) + { + if (objectId == null) + { + if (!throwIfInvalid) + { + return false; + } + + throw new ArgumentNullException(objectId); + } + + if (objectId.Length < MinHexSize || objectId.Length > HexSize) + { + if (!throwIfInvalid) + { + return false; + } + + string additionalErrorInformation = + !allowShortIdentifier ? + string.Format("Its length should be {0}", HexSize) : + string.Format("Its length should be comprised between {0} and {1}", MinHexSize, HexSize); + + throw new ArgumentException( + string.Format("'{0}' is not a valid object identifier. {1}.", objectId, additionalErrorInformation), + "objectId"); + } + + return objectId.All(c => hexDigits.Contains(c.ToString())); + } } -}
\ No newline at end of file +} diff --git a/LibGit2Sharp/ReferenceCollection.cs b/LibGit2Sharp/ReferenceCollection.cs index 0fadbc48..6207ce37 100644 --- a/LibGit2Sharp/ReferenceCollection.cs +++ b/LibGit2Sharp/ReferenceCollection.cs @@ -68,13 +68,14 @@ namespace LibGit2Sharp public Reference Create(string name, string target, bool allowOverwrite = false) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(target, "target"); - ObjectId id = ObjectId.CreateFromMaybeSha(target); - + ObjectId id; + IntPtr reference; int res; - if (id != null) + if (ObjectId.TryParse(target, out id)) { res = CreateDirectReference(name, id, allowOverwrite, out reference); } @@ -175,17 +176,18 @@ namespace LibGit2Sharp IntPtr reference = RetrieveReferencePtr(name); int res; - var id = ObjectId.CreateFromMaybeSha(target); + ObjectId id; + bool isObjectIdentifier = ObjectId.TryParse(target, out id); var type = NativeMethods.git_reference_type(reference); switch (type) { case GitReferenceType.Oid: - if (id == null) throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The reference specified by {0} is an Oid reference, you must provide a sha as the target.", name), "target"); + if (!isObjectIdentifier) throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The reference specified by {0} is an Oid reference, you must provide a sha as the target.", name), "target"); var oid = id.Oid; res = NativeMethods.git_reference_set_oid(reference, ref oid); break; case GitReferenceType.Symbolic: - if (id != null) throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The reference specified by {0} is an Symbolic reference, you must provide a symbol as the target.", name), "target"); + if (isObjectIdentifier) throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The reference specified by {0} is an Symbolic reference, you must provide a symbol as the target.", name), "target"); res = NativeMethods.git_reference_set_target(reference, target); break; default: diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index bc3a4bf1..ef80c7f7 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -156,8 +156,6 @@ namespace LibGit2Sharp /// <returns></returns> public bool HasObject(string sha) //TODO: To be removed from front facing API (maybe should we create an Repository.Advanced to hold those kind of functions)? { - Ensure.ArgumentNotNullOrEmptyString(sha, "sha"); - var id = new ObjectId(sha); var odb = NativeMethods.git_repository_database(handle); @@ -197,7 +195,17 @@ namespace LibGit2Sharp var oid = id.Oid; IntPtr obj; - var res = NativeMethods.git_object_lookup(out obj, handle, ref oid, type); + int res; + + if (id is AbbreviatedObjectId) + { + res = NativeMethods.git_object_lookup_prefix(out obj, handle, ref oid, (uint)((AbbreviatedObjectId)id).Length, type); + } + else + { + res = NativeMethods.git_object_lookup(out obj, handle, ref oid, type); + } + if (res == (int)GitErrorCode.GIT_ENOTFOUND || res == (int)GitErrorCode.GIT_EINVALIDTYPE) { return null; @@ -205,6 +213,11 @@ namespace LibGit2Sharp Ensure.Success(res); + if (id is AbbreviatedObjectId) + { + id = GitObject.ObjectIdOf(obj); + } + return GitObject.CreateFromPtr(obj, id, this); } @@ -216,8 +229,9 @@ namespace LibGit2Sharp /// <returns>The <see cref = "GitObject" /> or null if it was not found.</returns> public GitObject Lookup(string shaOrReferenceName, GitObjectType type = GitObjectType.Any) { - ObjectId id = ObjectId.CreateFromMaybeSha(shaOrReferenceName); - if (id != null) + ObjectId id; + + if (ObjectId.TryParse(shaOrReferenceName, out id)) { return Lookup(id, type); } diff --git a/libgit2sharp/AbbreviatedObjectId.cs b/libgit2sharp/AbbreviatedObjectId.cs new file mode 100644 index 00000000..cda12616 --- /dev/null +++ b/libgit2sharp/AbbreviatedObjectId.cs @@ -0,0 +1,22 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + internal class AbbreviatedObjectId : ObjectId + { + internal AbbreviatedObjectId(GitOid oid, int length) : base(oid) + { + if (length < MinHexSize || length > HexSize) + { + throw new ArgumentException(string.Format("Expected length should be comprised between {0} and {1}.", MinHexSize, HexSize), "length"); + } + + Length = length; + } + + public int Length { get; private set; } + + public override string Sha { get { return base.Sha.Substring(0, Length); } } + } +}
\ No newline at end of file |