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);
}
}
}