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 git2.common; 20 import git2.oid; 21 import 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 /** 107 Convert this GitOid into a hex string. 108 109 $(B Note): If this oid has been constructed with a partial 110 hex string, $(D toHex) will still return a hex string of size 111 $(D MaxHexSize) but with padded zeros. 112 */ 113 string toHex() const 114 { 115 auto buffer = new char[](MaxHexSize); 116 git_oid_nfmt(buffer.ptr, MaxHexSize, &_oid); 117 return assumeUnique(buffer); 118 } 119 120 /// 121 unittest 122 { 123 // convert hex to oid and back to hex 124 const hex = "49322bb17d3acc9146f98c97d078513228bbf3c0"; 125 const oid = GitOid(hex); 126 assert(oid.toHex == hex); 127 } 128 129 /// 130 unittest 131 { 132 // convert partial hex to oid and back to hex 133 const hex = "4932"; 134 const oid = GitOid(hex); 135 assert(oid.toHex == "4932000000000000000000000000000000000000"); 136 } 137 138 /// Return the string representation of this Oid, in hex form. 139 string toString() const 140 { 141 return format("GitOid(%s)", toHex.toUpper); 142 } 143 144 /// 145 unittest 146 { 147 // convert hex to oid and back to hex 148 const hex = "49322bb17d3acc9146f98c97d078513228bbf3c0"; 149 const oid = GitOid(hex); 150 assert(to!string(oid) == format("GitOid(%s)", hex.toUpper)); 151 } 152 153 package: 154 // internal use only 155 git_oid _get_oid() { return _oid; } 156 157 private: 158 git_oid _oid; 159 } 160 161 /** 162 The OID shortener is used to process a list of OIDs 163 in text form and return the shortest length that would 164 uniquely identify all of them. 165 166 $(B Note): This is a refcounted type and can be 167 safely copied by value. The underyling C handle will be 168 released once the reference count reaches zero. 169 */ 170 struct GitOidShorten 171 { 172 /// Default-construction is disabled 173 @disable this(); 174 175 /// 176 unittest 177 { 178 // default construction is disabled 179 static assert(!__traits(compiles, GitOidShorten())); 180 } 181 182 /** 183 Create a new OID shortener. $(D length) is the minimum length 184 which will be used to shorted the OIDs, even if a shorter 185 length can uniquely identify all OIDs. 186 */ 187 this(size_t length) 188 { 189 enforceEx!GitException(length <= GitOid.MaxHexSize, 190 format("Error: Minimum hex length cannot be larger than '%s' (GitOid.MaxHexSize), it is '%s'", GitOid.MaxHexSize, length)); 191 192 _data = Data(length); 193 _minLength = length; 194 } 195 196 /// 197 unittest 198 { 199 auto oidShort = GitOidShorten(10); 200 assert(oidShort.minLength == 10); 201 202 // cannot be larger than MaxHexSize 203 assertThrown!GitException(GitOidShorten(GitOid.MaxHexSize + 1)); 204 } 205 206 /** 207 Add a new hex OID to the set of shortened OIDs and store 208 the minimal length to uniquely identify all the OIDs in 209 the set. This length can then be retrieved by calling $(D minLength). 210 211 $(B Note:) The hex OID must be a 40-char hexadecimal string. Calling 212 $(D add) with a shorter OID will throw a GitOid exception. 213 214 For performance reasons, there is a hard-limit of how many 215 OIDs can be added to a single set - around ~22000, assuming 216 a mostly randomized distribution. 217 218 Attempting to go over this limit will throw a $(D GitException). 219 */ 220 void add(in char[] hex) 221 { 222 enforceEx!GitException(hex.length == GitOid.MaxHexSize, 223 format("Error: Hex string size must be equal to '%s' (GitOid.MaxHexSize), not '%s'", GitOid.MaxHexSize, hex.length)); 224 225 auto result = git_oid_shorten_add(_data._payload, hex.ptr); 226 require(result >= 0); 227 _minLength = result; 228 } 229 230 /// 231 unittest 232 { 233 auto sh = GitOidShorten(5); 234 assert(sh.minLength == 5); 235 236 sh.add("1234000000000000000000000000000000000000"); 237 assert(sh.minLength == 5); 238 239 sh.add("1234500000000000000000000000000000000000"); 240 assert(sh.minLength == 5); 241 242 // introduce conflicting oid which requires a 243 // larger length for unique identification in the set 244 sh.add("1234560000000000000000000000000000000000"); 245 assert(sh.minLength == 6); 246 } 247 248 /// 249 unittest 250 { 251 // adding a shortened hex is disallowed 252 auto sh = GitOidShorten(5); 253 assertThrown!GitException(sh.add("1234")); 254 } 255 256 /** Return the current minimum length used to uniquely identify all the stored OIDs. */ 257 @property size_t minLength() { return _minLength; } 258 259 private: 260 261 /** Payload for the $(D git_oid_shorten) object which should be refcounted. */ 262 struct Payload 263 { 264 this(size_t length) 265 { 266 _payload = enforce(git_oid_shorten_new(length), "Error: Out of memory."); 267 } 268 269 ~this() 270 { 271 //~ writefln("- %s", __FUNCTION__); 272 273 if (_payload !is null) 274 { 275 git_oid_shorten_free(_payload); 276 _payload = null; 277 } 278 } 279 280 /// Should never perform copy 281 @disable this(this); 282 283 /// Should never perform assign 284 @disable void opAssign(typeof(this)); 285 286 git_oid_shorten* _payload; 287 } 288 289 // refcounted git_oid_shorten 290 alias RefCounted!(Payload, RefCountedAutoInitialize.no) Data; 291 Data _data; 292 293 // current minimum length 294 size_t _minLength; 295 }