using System; using System.Globalization; using System.Linq; using LibGit2Sharp.Core; namespace LibGit2Sharp { /// /// Uniquely identifies a . /// public sealed class ObjectId : IEquatable { private readonly GitOid oid; private const int rawSize = GitOid.Size; private readonly string sha; /// /// Size of the string-based representation of a SHA-1. /// internal const int HexSize = rawSize * 2; private const string hexDigits = "0123456789abcdef"; private static readonly byte[] reverseHexDigits = BuildReverseHexDigits(); private static readonly Func byteConverter = i => reverseHexDigits[i - '0']; private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(x => x.Sha); /// /// Zero ObjectId /// public static ObjectId Zero = new ObjectId(new string('0', HexSize)); /// /// Initializes a new instance of the class. /// /// The oid. internal ObjectId(GitOid oid) { if (oid.Id == null || oid.Id.Length != rawSize) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "A non null array of {0} bytes is expected.", rawSize), "oid"); } this.oid = oid; sha = ToString(oid.Id, oid.Id.Length * 2); } /// /// Initializes a new instance of the class. /// /// The byte array. public ObjectId(byte[] rawId) : this(new GitOid { Id = rawId }) { Ensure.ArgumentNotNull(rawId, "rawId"); Ensure.ArgumentConformsTo(rawId, b => b.Length == rawSize, "rawId"); } /// /// Initializes a new instance of the class. /// /// The sha. public ObjectId(string sha) { GitOid? parsedOid = BuildOidFrom(sha, true); if (!parsedOid.HasValue) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "'{0}' is not a valid Sha-1.", sha)); } oid = parsedOid.Value; this.sha = sha; } internal GitOid Oid { get { return oid; } } /// /// Gets the raw id. /// public byte[] RawId { get { return oid.Id; } } /// /// Gets the sha. /// public string Sha { get { return sha; } } /// /// Converts the specified string representation of a Sha-1 to its equivalent and returns a value that indicates whether the conversion succeeded. /// /// A string containing a Sha-1 to convert. /// When this method returns, contains the value equivalent to the Sha-1 contained in , if the conversion succeeded, or null if the conversion failed. /// true if the parameter was converted successfully; otherwise, false. public static bool TryParse(string sha, out ObjectId result) { result = BuildOidFrom(sha, false); return result != null; } private static GitOid? BuildOidFrom(string sha, bool shouldThrowIfInvalid) { if (!LooksValid(sha, shouldThrowIfInvalid)) { return null; } return ToOid(sha); } /// /// Determines whether the specified is equal to the current . /// /// The to compare with the current . /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as ObjectId); } /// /// Determines whether the specified is equal to the current . /// /// The to compare with the current . /// True if the specified is equal to the current ; otherwise, false. public bool Equals(ObjectId other) { return equalityHelper.Equals(this, other); } /// /// Returns the hash code for this instance. /// /// A 32-bit signed integer hash code. public override int GetHashCode() { return equalityHelper.GetHashCode(this); } /// /// Returns the , a representation of the current . /// /// The that represents the current . public override string ToString() { return Sha; } /// /// Returns the , a representation of the current . /// /// The number of chars the should be truncated to. /// The that represents the current . 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 < 1) { return 1; } if (prefixLength > HexSize) { return HexSize; } return prefixLength; } /// /// Tests if two are equal. /// /// First to compare. /// Second to compare. /// True if the two objects are equal; false otherwise. public static bool operator ==(ObjectId left, ObjectId right) { return Equals(left, right); } /// /// Tests if two are different. /// /// First to compare. /// Second to compare. /// True if the two objects are different; false otherwise. public static bool operator !=(ObjectId left, ObjectId right) { return !Equals(left, right); } /// /// Create an for the given . /// /// The object SHA. /// An , or null if is null. public static explicit operator ObjectId(string sha) { return sha == null ? null : new ObjectId(sha); } 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; } internal static string ToString(byte[] id, int lengthInNibbles) { // Inspired from http://stackoverflow.com/questions/623104/c-byte-to-hex-string/3974535#3974535 var c = new char[lengthInNibbles]; for (int i = 0; i < (lengthInNibbles & -2); i++) { int index0 = i >> 1; var b = ((byte)(id[index0] >> 4)); c[i++] = hexDigits[b]; b = ((byte)(id[index0] & 0x0F)); c[i] = hexDigits[b]; } if ((lengthInNibbles & 1) == 1) { int index0 = lengthInNibbles >> 1; var b = ((byte)(id[index0] >> 4)); c[lengthInNibbles - 1] = 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) { if (string.IsNullOrEmpty(objectId)) { if (!throwIfInvalid) { return false; } Ensure.ArgumentNotNullOrEmptyString(objectId, "objectId"); } if ((objectId.Length != HexSize)) { if (!throwIfInvalid) { return false; } throw new ArgumentException( string.Format(CultureInfo.InvariantCulture, "'{0}' is not a valid object identifier. Its length should be {1}.", objectId, HexSize), "objectId"); } return objectId.All(c => hexDigits.Contains(c.ToString(CultureInfo.InvariantCulture))); } /// /// Determine whether matches the hexified /// representation of the first nibbles of this instance. /// /// Comparison is made in a case insensitive-manner. /// /// /// True if this instance starts with , /// false otherwise. public bool StartsWith(string shortSha) { Ensure.ArgumentNotNullOrEmptyString(shortSha, "shortSha"); return Sha.StartsWith(shortSha, StringComparison.OrdinalIgnoreCase); } } }