1 /* 2 * Copyright Andrej Mitrovic 2013. 3 * Distributed under the Boost Software License, Version 1.0. 4 * (See accompanying file LICENSE_1_0.txt or copy at 5 * http://www.boost.org/LICENSE_1_0.txt) 6 */ 7 module git.oid; 8 9 import core.exception; 10 11 import std.algorithm; 12 import std.exception; 13 import std.conv; 14 import std.range; 15 import std.stdio; 16 import std.string; 17 import std.typecons; 18 19 import deimos.git2.common; 20 import deimos.git2.oid; 21 import deimos.git2.types; 22 23 import git.exception; 24 import git.util; 25 26 /** 27 The unique ID of any Git object, whether it's a commit, 28 tree, blob, tag, etc. 29 */ 30 struct GitOid 31 { 32 /** Size (in bytes) of a raw/binary oid */ 33 enum BinarySize = GIT_OID_RAWSZ; 34 35 /** Minimum length (in number of hex characters, 36 * i.e. packets of 4 bits) of an oid prefix */ 37 enum MinHexSize = GIT_OID_MINPREFIXLEN; 38 39 /** Size (in bytes) of a hex formatted oid */ 40 enum MaxHexSize = GIT_OID_HEXSZ; 41 42 /** 43 Parse a full or partial hex-formatted object ID and 44 create a GitOid object. 45 46 $(D hex) must be at least the size of $(D MinHexSize), 47 but not larger than $(D MaxHexSize). 48 */ 49 this(in char[] hex) 50 { 51 assert(hex.length >= MinHexSize && hex.length <= MaxHexSize); 52 require(git_oid_fromstrn(&_oid, hex.ptr, hex.length) == 0); 53 } 54 55 /// 56 unittest 57 { 58 // note: don't use an enum due to http://d.puremagic.com/issues/show_bug.cgi?id=10516 59 const srcHex = "49322bb17d3acc9146f98c97d078513228bbf3c0"; 60 const oid = GitOid(srcHex); 61 const tgtHex = oid.toHex(); 62 63 assert(tgtHex == srcHex); 64 } 65 66 /// 67 unittest 68 { 69 // can convert from a partial string 70 const srcHex = "4932"; 71 const oid = GitOid(srcHex); 72 const tgtHex = oid.toHex(); 73 74 assert(tgtHex[0 .. 4] == srcHex); 75 assert(tgtHex[4 .. $].count('0') == tgtHex.length - 4); 76 } 77 78 /// 79 unittest 80 { 81 /// cannot convert from a partial string smaller than MinHexSize 82 const smallHex = "493"; 83 assertThrown!AssertError(GitOid(smallHex)); 84 85 /// cannot convert from a string bigger than MinHexSize 86 const bigHex = std.array.replicate("1", MaxHexSize + 1); 87 assertThrown!AssertError(GitOid(bigHex)); 88 } 89 90 // internal 91 package this(inout(git_oid) oid) 92 { 93 _oid = oid; 94 } 95 96 /// 97 unittest 98 { 99 git_oid oid1; 100 const(git_oid) oid2; 101 102 GitOid(oid1); 103 GitOid(oid2); 104 } 105 106 @property ref inout(ubyte[20]) bytes() inout { return _oid.id; } 107 108 /** 109 Convert this GitOid into a hex string. 110 111 $(B Note): If this oid has been constructed with a partial 112 hex string, $(D toHex) will still return a hex string of size 113 $(D MaxHexSize) but with padded zeros. 114 */ 115 string toHex() const 116 { 117 auto buffer = new char[](MaxHexSize); 118 git_oid_nfmt(buffer.ptr, MaxHexSize, &_oid); 119 return assumeUnique(buffer); 120 } 121 122 /// 123 unittest 124 { 125 // convert hex to oid and back to hex 126 const hex = "49322bb17d3acc9146f98c97d078513228bbf3c0"; 127 const oid = GitOid(hex); 128 assert(oid.toHex == hex); 129 } 130 131 /// 132 unittest 133 { 134 // convert partial hex to oid and back to hex 135 const hex = "4932"; 136 const oid = GitOid(hex); 137 assert(oid.toHex == "4932000000000000000000000000000000000000"); 138 } 139 140 /// Return the string representation of this Oid, in hex form. 141 string toString() const 142 { 143 return format("GitOid(%s)", toHex.toUpper); 144 } 145 146 /// 147 unittest 148 { 149 // convert hex to oid and back to hex 150 const hex = "49322bb17d3acc9146f98c97d078513228bbf3c0"; 151 const oid = GitOid(hex); 152 assert(to!string(oid) == format("GitOid(%s)", hex.toUpper)); 153 } 154 155 bool opCast(T)() const if (is(T == bool)) { return git_oid_iszero(&_oid) == 0; } 156 157 package: 158 // internal use only 159 ref inout(git_oid) _get_oid() inout { return _oid; } 160 161 private: 162 git_oid _oid; 163 } 164 165 /** 166 The OID shortener is used to process a list of OIDs 167 in text form and return the shortest length that would 168 uniquely identify all of them. 169 170 $(B Note): This is a refcounted type and can be 171 safely copied by value. The underyling C handle will be 172 released once the reference count reaches zero. 173 */ 174 struct GitOidShorten 175 { 176 /// Default-construction is disabled 177 @disable this(); 178 179 /// 180 unittest 181 { 182 // default construction is disabled 183 static assert(!__traits(compiles, GitOidShorten())); 184 } 185 186 /** 187 Create a new OID shortener. $(D length) is the minimum length 188 which will be used to shorted the OIDs, even if a shorter 189 length can uniquely identify all OIDs. 190 */ 191 this(size_t length) 192 { 193 enforceEx!GitException(length <= GitOid.MaxHexSize, 194 format("Error: Minimum hex length cannot be larger than '%s' (GitOid.MaxHexSize), it is '%s'", GitOid.MaxHexSize, length)); 195 196 _data = Data(length); 197 _minLength = length; 198 } 199 200 /// 201 unittest 202 { 203 auto oidShort = GitOidShorten(10); 204 assert(oidShort.minLength == 10); 205 206 // cannot be larger than MaxHexSize 207 assertThrown!GitException(GitOidShorten(GitOid.MaxHexSize + 1)); 208 } 209 210 /** 211 Add a new hex OID to the set of shortened OIDs and store 212 the minimal length to uniquely identify all the OIDs in 213 the set. This length can then be retrieved by calling $(D minLength). 214 215 $(B Note:) The hex OID must be a 40-char hexadecimal string. Calling 216 $(D add) with a shorter OID will throw a GitOid exception. 217 218 For performance reasons, there is a hard-limit of how many 219 OIDs can be added to a single set - around ~22000, assuming 220 a mostly randomized distribution. 221 222 Attempting to go over this limit will throw a $(D GitException). 223 */ 224 void add(in char[] hex) 225 { 226 enforceEx!GitException(hex.length == GitOid.MaxHexSize, 227 format("Error: Hex string size must be equal to '%s' (GitOid.MaxHexSize), not '%s'", GitOid.MaxHexSize, hex.length)); 228 229 auto result = git_oid_shorten_add(_data._payload, hex.ptr); 230 require(result >= 0); 231 _minLength = result; 232 } 233 234 /// 235 unittest 236 { 237 auto sh = GitOidShorten(5); 238 assert(sh.minLength == 5); 239 240 sh.add("1234000000000000000000000000000000000000"); 241 assert(sh.minLength == 5); 242 243 sh.add("1234500000000000000000000000000000000000"); 244 assert(sh.minLength == 5); 245 246 // introduce conflicting oid which requires a 247 // larger length for unique identification in the set 248 sh.add("1234560000000000000000000000000000000000"); 249 assert(sh.minLength == 6); 250 } 251 252 /// 253 unittest 254 { 255 // adding a shortened hex is disallowed 256 auto sh = GitOidShorten(5); 257 assertThrown!GitException(sh.add("1234")); 258 } 259 260 /** Return the current minimum length used to uniquely identify all the stored OIDs. */ 261 @property size_t minLength() { return _minLength; } 262 263 private: 264 265 /** Payload for the $(D git_oid_shorten) object which should be refcounted. */ 266 struct Payload 267 { 268 this(size_t length) 269 { 270 _payload = enforce(git_oid_shorten_new(length), "Error: Out of memory."); 271 } 272 273 ~this() 274 { 275 //~ writefln("- %s", __FUNCTION__); 276 277 if (_payload !is null) 278 { 279 git_oid_shorten_free(_payload); 280 _payload = null; 281 } 282 } 283 284 /// Should never perform copy 285 @disable this(this); 286 287 /// Should never perform assign 288 @disable void opAssign(typeof(this)); 289 290 git_oid_shorten* _payload; 291 } 292 293 // refcounted git_oid_shorten 294 alias RefCounted!(Payload, RefCountedAutoInitialize.no) Data; 295 Data _data; 296 297 // current minimum length 298 size_t _minLength; 299 }