1 /**
2 	Dependency specification functionality.
3 
4 	Copyright: © 2012-2013 Matthias Dondorff, © 2012-2016 Sönke Ludwig
5 	License: Subject to the terms of the MIT license, as written in the included LICENSE.txt file.
6 	Authors: Matthias Dondorff, Sönke Ludwig
7 */
8 module dub.dependency;
9 
10 import dub.internal.vibecompat.data.json;
11 import dub.internal.vibecompat.inet.path;
12 import dub.semver;
13 
14 import dub.internal.dyaml.stdsumtype;
15 
16 import std.algorithm;
17 import std.array;
18 import std.exception;
19 import std.string;
20 
21 /// Represents a fully-qualified package name
22 public struct PackageName
23 {
24 	/// The underlying full name of the package
25 	private string fullName;
26 	/// Where the separator lies, if any
27 	private size_t separator;
28 
29 	/// Creates a new instance of this struct
30 	public this(string fn) @safe pure
31 	{
32 		this.fullName = fn;
33 		if (auto idx = fn.indexOf(':'))
34 			this.separator = idx > 0 ? idx : fn.length;
35 		else // We were given `:foo`
36 			assert(0, "Argument to PackageName constructor needs to be " ~
37 				"a fully qualified string");
38 	}
39 
40 	/// Private constructor to have nothrow / @nogc
41 	private this(string fn, size_t sep) @safe pure nothrow @nogc
42 	{
43 		this.fullName = fn;
44 		this.separator = sep;
45 	}
46 
47 	/// The base package name in which the subpackages may live
48 	public PackageName main () const return @safe pure nothrow @nogc
49 	{
50 		return PackageName(this.fullName[0 .. this.separator], this.separator);
51 	}
52 
53 	/// The subpackage name, or an empty string if there isn't
54 	public string sub () const return @safe pure nothrow @nogc
55 	{
56 		// Return `null` instead of an empty string so that
57 		// it can be used in a boolean context, e.g.
58 		// `if (name.sub)` would be true with empty string
59 		return this.separator < this.fullName.length
60 			? this.fullName[this.separator + 1 .. $]
61 			: null;
62 	}
63 
64 	/// Human readable representation
65 	public string toString () const return scope @safe pure nothrow @nogc
66 	{
67 		return this.fullName;
68 	}
69 
70     ///
71     public int opCmp (in PackageName other) const scope @safe pure nothrow @nogc
72     {
73         import core.internal.string : dstrcmp;
74         return dstrcmp(this.toString(), other.toString());
75     }
76 
77     ///
78     public bool opEquals (in PackageName other) const scope @safe pure nothrow @nogc
79     {
80         return this.toString() == other.toString();
81     }
82 }
83 
84 /** Encapsulates the name of a package along with its dependency specification.
85 */
86 struct PackageDependency {
87 	/// Backward compatibility
88 	deprecated("Use the constructor that accepts a `PackageName` as first argument")
89 	this(string n, Dependency s = Dependency.init) @safe pure
90 	{
91 		this.name = PackageName(n);
92 		this.spec = s;
93 	}
94 
95 	// Remove once deprecated overload is gone
96 	this(PackageName n, Dependency s = Dependency.init) @safe pure nothrow @nogc
97 	{
98 		this.name = n;
99 		this.spec = s;
100 	}
101 
102 	/// Name of the referenced package.
103 	PackageName name;
104 
105 	/// Dependency specification used to select a particular version of the package.
106 	Dependency spec;
107 }
108 
109 /**
110 	Represents a dependency specification.
111 
112 	A dependency specification either represents a specific version or version
113 	range, or a path to a package. In addition to that it has `optional` and
114 	`default_` flags to control how non-mandatory dependencies are handled. The
115 	package name is notably not part of the dependency specification.
116 */
117 struct Dependency {
118 	/// We currently support 3 'types'
119 	private alias Value = SumType!(VersionRange, NativePath, Repository);
120 
121 	/// Used by `toString`
122 	private static immutable string[] BooleanOptions = [ "optional", "default" ];
123 
124 	// Shortcut to create >=0.0.0
125 	private enum ANY_IDENT = "*";
126 
127 	private Value m_value = Value(VersionRange.Invalid);
128 	private bool m_optional;
129 	private bool m_default;
130 
131 	/// A Dependency, which matches every valid version.
132 	public static immutable Dependency Any = Dependency(VersionRange.Any);
133 
134 	/// An invalid dependency (with no possible version matches).
135 	public static immutable Dependency Invalid = Dependency(VersionRange.Invalid);
136 
137 	deprecated("Use `Dependency.Any` instead")
138 	static @property Dependency any() @safe { return Dependency(VersionRange.Any); }
139 	deprecated("Use `Dependency.Invalid` instead")
140 	static @property Dependency invalid() @safe
141 	{
142 		return Dependency(VersionRange.Invalid);
143 	}
144 
145 	/** Constructs a new dependency specification that matches a specific
146 		path.
147 	*/
148 	this(NativePath path) @safe
149 	{
150 		this.m_value = path;
151 	}
152 
153 	/** Constructs a new dependency specification that matches a specific
154 		Git reference.
155 	*/
156 	this(Repository repository) @safe
157 	{
158 		this.m_value = repository;
159 	}
160 
161 	/** Constructs a new dependency specification from a string
162 
163 		See the `versionSpec` property for a description of the accepted
164 		contents of that string.
165 	*/
166 	this(string spec) @safe
167 	{
168 		this(VersionRange.fromString(spec));
169 	}
170 
171 	/** Constructs a new dependency specification that matches a specific
172 		version.
173 	*/
174 	this(const Version ver) @safe
175 	{
176 		this(VersionRange(ver, ver));
177 	}
178 
179 	/// Construct a version from a range of possible values
180 	this (VersionRange rng) @safe
181 	{
182 		this.m_value = rng;
183 	}
184 
185 	deprecated("Instantiate the `Repository` struct with the string directly")
186 	this(Repository repository, string spec) @safe
187 	{
188 		assert(repository.m_ref is null);
189 		repository.m_ref = spec;
190 		this(repository);
191 	}
192 
193 	/// If set, overrides any version based dependency selection.
194 	deprecated("Construct a new `Dependency` object instead")
195 	@property void path(NativePath value) @trusted
196 	{
197 		this.m_value = value;
198 	}
199 	/// ditto
200 	@property NativePath path() const @safe
201 	{
202 		return this.m_value.match!(
203 			(const NativePath p) => p,
204 			(      any         ) => NativePath.init,
205 		);
206 	}
207 
208 	/// If set, overrides any version based dependency selection.
209 	deprecated("Construct a new `Dependency` object instead")
210 	@property void repository(Repository value) @trusted
211 	{
212 		this.m_value = value;
213 	}
214 	/// ditto
215 	@property Repository repository() const @safe
216 	{
217 		return this.m_value.match!(
218 			(const Repository p) => p,
219 			(      any         ) => Repository.init,
220 		);
221 	}
222 
223 	/// Determines if the dependency is required or optional.
224 	@property bool optional() const scope @safe pure nothrow @nogc
225 	{
226 		return m_optional;
227 	}
228 	/// ditto
229 	@property void optional(bool optional) scope @safe pure nothrow @nogc
230 	{
231 		m_optional = optional;
232 	}
233 
234 	/// Determines if an optional dependency should be chosen by default.
235 	@property bool default_() const scope @safe pure nothrow @nogc
236 	{
237 		return m_default;
238 	}
239 	/// ditto
240 	@property void default_(bool value) scope @safe pure nothrow @nogc
241 	{
242 		m_default = value;
243 	}
244 
245 	/// Returns true $(I iff) the version range only matches a specific version.
246 	@property bool isExactVersion() const scope @safe
247 	{
248 		return this.m_value.match!(
249 			(NativePath v) => false,
250 			(Repository v) => false,
251 			(VersionRange v) => v.isExactVersion(),
252 		);
253 	}
254 
255 	/// Returns the exact version matched by the version range.
256 	@property Version version_() const @safe {
257 		auto range = this.m_value.match!(
258 			// Can be simplified to `=> assert(0)` once we drop support for v2.096
259 			(NativePath   p) { int dummy; if (dummy) return VersionRange.init; assert(0); },
260 			(Repository   r) { int dummy; if (dummy) return VersionRange.init; assert(0); },
261 			(VersionRange v) => v,
262 		);
263 		enforce(range.isExactVersion(),
264 				"Dependency "~range.toString()~" is no exact version.");
265 		return range.m_versA;
266 	}
267 
268 	/// Sets/gets the matching version range as a specification string.
269 	deprecated("Create a new `Dependency` instead and provide a `VersionRange`")
270 	@property void versionSpec(string ves) @trusted
271 	{
272 		this.m_value = VersionRange.fromString(ves);
273 	}
274 
275 	/// ditto
276 	deprecated("Use `Dependency.visit` and match `VersionRange`instead")
277 	@property string versionSpec() const @safe {
278 		return this.m_value.match!(
279 			(const NativePath   p) => ANY_IDENT,
280 			(const Repository   r) => r.m_ref,
281 			(const VersionRange p) => p.toString(),
282 		);
283 	}
284 
285 	/** Returns a modified dependency that gets mapped to a given path.
286 
287 		This function will return an unmodified `Dependency` if it is not path
288 		based. Otherwise, the given `path` will be prefixed to the existing
289 		path.
290 	*/
291 	Dependency mapToPath(NativePath path) const @trusted {
292 		// NOTE Path is @system in vibe.d 0.7.x and in the compatibility layer
293 		return this.m_value.match!(
294 			(NativePath v) {
295 				if (v.empty || v.absolute) return this;
296 				auto ret = Dependency(path ~ v);
297 				ret.m_default = m_default;
298 				ret.m_optional = m_optional;
299 				return ret;
300 			},
301 			(Repository v) => this,
302 			(VersionRange v) => this,
303 		);
304 	}
305 
306 	/** Returns a human-readable string representation of the dependency
307 		specification.
308 	*/
309 	string toString() const scope @trusted {
310 		// Trusted because `SumType.match` doesn't seem to support `scope`
311 
312 		string Stringifier (T, string pre = null) (const T v)
313 		{
314 			const bool extra = this.optional || this.default_;
315 			return format("%s%s%s%-(%s, %)%s",
316 					pre, v,
317 					extra ? " (" : "",
318 					BooleanOptions[!this.optional .. 1 + this.default_],
319 					extra ? ")" : "");
320 		}
321 
322 		return this.m_value.match!(
323 			Stringifier!Repository,
324 			Stringifier!(NativePath, "@"),
325 			Stringifier!VersionRange
326 		);
327 	}
328 
329 	/** Returns a JSON representation of the dependency specification.
330 
331 		Simple specifications will be represented as a single specification
332 		string (`versionSpec`), while more complex specifications will be
333 		represented as a JSON object with optional "version", "path", "optional"
334 		and "default" fields.
335 
336 		Params:
337 		  selections = We are serializing `dub.selections.json`, don't write out
338 			  `optional` and `default`.
339 	*/
340 	Json toJson(bool selections = false) const @safe
341 	{
342 		// NOTE Path and Json is @system in vibe.d 0.7.x and in the compatibility layer
343 		static void initJson(ref Json j, bool opt, bool def, bool s = selections)
344 		{
345 			j = Json.emptyObject;
346 			if (!s && opt) j["optional"] = true;
347 			if (!s && def) j["default"] = true;
348 		}
349 
350 		Json json;
351 		this.m_value.match!(
352 			(const NativePath v) @trusted {
353 				initJson(json, optional, default_);
354 				json["path"] = v.toString();
355 			},
356 
357 			(const Repository v) @trusted {
358 				initJson(json, optional, default_);
359 				json["repository"] = v.toString();
360 				json["version"] = v.m_ref;
361 			},
362 
363 			(const VersionRange v) @trusted {
364 				if (!selections && (optional || default_))
365 				{
366 					initJson(json, optional, default_);
367 					json["version"] = v.toString();
368 				}
369 				else
370 					json = Json(v.toString());
371 			},
372 		);
373 		return json;
374 	}
375 
376 	@trusted unittest {
377 		Dependency d = Dependency("==1.0.0");
378 		assert(d.toJson() == Json("1.0.0"), "Failed: " ~ d.toJson().toPrettyString());
379 		d = fromJson((fromJson(d.toJson())).toJson());
380 		assert(d == Dependency("1.0.0"));
381 		assert(d.toJson() == Json("1.0.0"), "Failed: " ~ d.toJson().toPrettyString());
382 	}
383 
384 	@trusted unittest {
385 		Dependency dependency = Dependency(Repository("git+http://localhost", "1.0.0"));
386 		Json expected = Json([
387 			"repository": Json("git+http://localhost"),
388 			"version": Json("1.0.0")
389 		]);
390 		assert(dependency.toJson() == expected, "Failed: " ~ dependency.toJson().toPrettyString());
391 	}
392 
393 	@trusted unittest {
394 		Dependency d = Dependency(NativePath("dir"));
395 		Json expected = Json([ "path": Json("dir") ]);
396 		assert(d.toJson() == expected, "Failed: " ~ d.toJson().toPrettyString());
397 	}
398 
399 	/** Constructs a new `Dependency` from its JSON representation.
400 
401 		See `toJson` for a description of the JSON format.
402 	*/
403 	static Dependency fromJson(Json verspec)
404 	@trusted { // NOTE Path and Json is @system in vibe.d 0.7.x and in the compatibility layer
405 		Dependency dep;
406 		if( verspec.type == Json.Type.object ){
407 			if( auto pp = "path" in verspec ) {
408 				dep = Dependency(NativePath(verspec["path"].get!string));
409 			} else if (auto repository = "repository" in verspec) {
410 				enforce("version" in verspec, "No version field specified!");
411 				enforce(repository.length > 0, "No repository field specified!");
412 
413 				dep = Dependency(Repository(
414                                      repository.get!string, verspec["version"].get!string));
415 			} else {
416 				enforce("version" in verspec, "No version field specified!");
417 				auto ver = verspec["version"].get!string;
418 				// Using the string to be able to specify a range of versions.
419 				dep = Dependency(ver);
420 			}
421 
422 			if (auto po = "optional" in verspec) dep.optional = po.get!bool;
423 			if (auto po = "default" in verspec) dep.default_ = po.get!bool;
424 		} else {
425 			// canonical "package-id": "version"
426 			dep = Dependency(verspec.get!string);
427 		}
428 		return dep;
429 	}
430 
431 	@trusted unittest {
432 		assert(fromJson(parseJsonString("\">=1.0.0 <2.0.0\"")) == Dependency(">=1.0.0 <2.0.0"));
433 		Dependency parsed = fromJson(parseJsonString(`
434 		{
435 			"version": "2.0.0",
436 			"optional": true,
437 			"default": true,
438 			"path": "path/to/package"
439 		}
440 			`));
441 		Dependency d = NativePath("path/to/package"); // supposed to ignore the version spec
442 		d.optional = true;
443 		d.default_ = true;
444 		assert(d == parsed);
445 	}
446 
447 	/** Compares dependency specifications.
448 
449 		These methods are suitable for equality comparisons, as well as for
450 		using `Dependency` as a key in hash or tree maps.
451 	*/
452 	bool opEquals(in Dependency o) const scope @safe {
453 		if (o.m_optional != this.m_optional) return false;
454 		if (o.m_default  != this.m_default)  return false;
455 		return this.m_value == o.m_value;
456 	}
457 
458 	/// ditto
459 	int opCmp(in Dependency o) const @safe {
460 		alias ResultMatch = match!(
461 			(VersionRange r1, VersionRange r2) => r1.opCmp(r2),
462 			(_1, _2) => 0,
463 		);
464 		if (auto result = ResultMatch(this.m_value, o.m_value))
465 			return result;
466 		if (m_optional != o.m_optional) return m_optional ? -1 : 1;
467 		return 0;
468 	}
469 
470 	/** Determines if this dependency specification is valid.
471 
472 		A specification is valid if it can match at least one version.
473 	*/
474 	bool valid() const @safe {
475 		return this.m_value.match!(
476 			(NativePath v) => true,
477 			(Repository v) => true,
478 			(VersionRange v) => v.isValid(),
479 		);
480 	}
481 
482 	/** Determines if this dependency specification matches arbitrary versions.
483 
484 		This is true in particular for the `any` constant.
485 	*/
486 	deprecated("Use `VersionRange.matchesAny` directly")
487 	bool matchesAny() const scope @safe {
488 		return this.m_value.match!(
489 			(NativePath v) => true,
490 			(Repository v) => true,
491 			(VersionRange v) => v.matchesAny(),
492 		);
493 	}
494 
495 	/** Tests if the specification matches a specific version.
496 	*/
497 	bool matches(string vers, VersionMatchMode mode = VersionMatchMode.standard) const @safe
498 	{
499 		return matches(Version(vers), mode);
500 	}
501 	/// ditto
502 	bool matches(in  Version v, VersionMatchMode mode = VersionMatchMode.standard) const @safe {
503 		return this.m_value.match!(
504 			(NativePath i) => true,
505 			(Repository i) => true,
506 			(VersionRange i) => i.matchesAny() || i.matches(v, mode),
507 		);
508 	}
509 
510 	/** Merges two dependency specifications.
511 
512 		The result is a specification that matches the intersection of the set
513 		of versions matched by the individual specifications. Note that this
514 		result can be invalid (i.e. not match any version).
515 	*/
516 	Dependency merge(ref const(Dependency) o) const @trusted {
517 		alias Merger = match!(
518 			(const NativePath a, const NativePath b) => a == b ? this : Invalid,
519 			(const NativePath a,       any         ) => o,
520 			(      any         , const NativePath b) => this,
521 
522 			(const Repository a, const Repository b) => a.m_ref == b.m_ref ? this : Invalid,
523 			(const Repository a,       any         ) => this,
524 			(      any         , const Repository b) => o,
525 
526 			(const VersionRange a, const VersionRange b) {
527 				if (a.matchesAny()) return o;
528 				if (b.matchesAny()) return this;
529 
530 				VersionRange copy = a;
531 				copy.merge(b);
532 				if (!copy.isValid()) return Invalid;
533 				return Dependency(copy);
534 			}
535 		);
536 
537 		Dependency ret = Merger(this.m_value, o.m_value);
538 		ret.m_optional = m_optional && o.m_optional;
539 		return ret;
540 	}
541 }
542 
543 /// Allow direct access to the underlying dependency
544 public auto visit (Handlers...) (const auto ref Dependency dep)
545 {
546     return dep.m_value.match!(Handlers);
547 }
548 
549 //// Ditto
550 public auto visit (Handlers...) (auto ref Dependency dep)
551 {
552     return dep.m_value.match!(Handlers);
553 }
554 
555 
556 unittest {
557 	Dependency a = Dependency(">=1.1.0"), b = Dependency(">=1.3.0");
558 	assert (a.merge(b).valid() && a.merge(b).toString() == ">=1.3.0", a.merge(b).toString());
559 
560 	assertThrown(Dependency("<=2.0.0 >=1.0.0"));
561 	assertThrown(Dependency(">=2.0.0 <=1.0.0"));
562 
563 	a = Dependency(">=1.0.0 <=5.0.0"); b = Dependency(">=2.0.0");
564 	assert (a.merge(b).valid() && a.merge(b).toString() == ">=2.0.0 <=5.0.0", a.merge(b).toString());
565 
566 	assertThrown(a = Dependency(">1.0.0 ==5.0.0"), "Construction is invalid");
567 
568 	a = Dependency(">1.0.0"); b = Dependency("<2.0.0");
569 	assert (a.merge(b).valid(), a.merge(b).toString());
570 	assert (a.merge(b).toString() == ">1.0.0 <2.0.0", a.merge(b).toString());
571 
572 	a = Dependency(">2.0.0"); b = Dependency("<1.0.0");
573 	assert (!(a.merge(b)).valid(), a.merge(b).toString());
574 
575 	a = Dependency(">=2.0.0"); b = Dependency("<=1.0.0");
576 	assert (!(a.merge(b)).valid(), a.merge(b).toString());
577 
578 	a = Dependency("==2.0.0"); b = Dependency("==1.0.0");
579 	assert (!(a.merge(b)).valid(), a.merge(b).toString());
580 
581 	a = Dependency("1.0.0"); b = Dependency("==1.0.0");
582 	assert (a == b);
583 
584 	a = Dependency("<=2.0.0"); b = Dependency("==1.0.0");
585 	Dependency m = a.merge(b);
586 	assert (m.valid(), m.toString());
587 	assert (m.matches(Version("1.0.0")));
588 	assert (!m.matches(Version("1.1.0")));
589 	assert (!m.matches(Version("0.0.1")));
590 
591 
592 	// branches / head revisions
593 	a = Dependency(Version.masterBranch);
594 	assert(a.valid());
595 	assert(a.matches(Version.masterBranch));
596 	b = Dependency(Version.masterBranch);
597 	m = a.merge(b);
598 	assert(m.matches(Version.masterBranch));
599 
600 	//assertThrown(a = Dependency(Version.MASTER_STRING ~ " <=1.0.0"), "Construction invalid");
601 	assertThrown(a = Dependency(">=1.0.0 " ~ Version.masterBranch.toString()), "Construction invalid");
602 
603 	immutable string branch1 = Version.branchPrefix ~ "Branch1";
604 	immutable string branch2 = Version.branchPrefix ~ "Branch2";
605 
606 	//assertThrown(a = Dependency(branch1 ~ " " ~ branch2), "Error: '" ~ branch1 ~ " " ~ branch2 ~ "' succeeded");
607 	//assertThrown(a = Dependency(Version.MASTER_STRING ~ " " ~ branch1), "Error: '" ~ Version.MASTER_STRING ~ " " ~ branch1 ~ "' succeeded");
608 
609 	a = Dependency(branch1);
610 	b = Dependency(branch2);
611 	assert(!a.merge(b).valid, "Shouldn't be able to merge to different branches");
612 	b = a.merge(a);
613 	assert(b.valid, "Should be able to merge the same branches. (?)");
614 	assert(a == b);
615 
616 	a = Dependency(branch1);
617 	assert(a.matches(branch1), "Dependency(branch1) does not match 'branch1'");
618 	assert(a.matches(Version(branch1)), "Dependency(branch1) does not match Version('branch1')");
619 	assert(!a.matches(Version.masterBranch), "Dependency(branch1) matches Version.masterBranch");
620 	assert(!a.matches(branch2), "Dependency(branch1) matches 'branch2'");
621 	assert(!a.matches(Version("1.0.0")), "Dependency(branch1) matches '1.0.0'");
622 	a = Dependency(">=1.0.0");
623 	assert(!a.matches(Version(branch1)), "Dependency(1.0.0) matches 'branch1'");
624 
625 	// Testing optional dependencies.
626 	a = Dependency(">=1.0.0");
627 	assert(!a.optional, "Default is not optional.");
628 	b = a;
629 	assert(!a.merge(b).optional, "Merging two not optional dependencies wrong.");
630 	a.optional = true;
631 	assert(!a.merge(b).optional, "Merging optional with not optional wrong.");
632 	b.optional = true;
633 	assert(a.merge(b).optional, "Merging two optional dependencies wrong.");
634 
635 	// SemVer's sub identifiers.
636 	a = Dependency(">=1.0.0-beta");
637 	assert(!a.matches(Version("1.0.0-alpha")), "Failed: match 1.0.0-alpha with >=1.0.0-beta");
638 	assert(a.matches(Version("1.0.0-beta")), "Failed: match 1.0.0-beta with >=1.0.0-beta");
639 	assert(a.matches(Version("1.0.0")), "Failed: match 1.0.0 with >=1.0.0-beta");
640 	assert(a.matches(Version("1.0.0-rc")), "Failed: match 1.0.0-rc with >=1.0.0-beta");
641 
642 	// Approximate versions.
643 	a = Dependency("~>3.0");
644 	b = Dependency(">=3.0.0 <4.0.0-0");
645 	assert(a == b, "Testing failed: " ~ a.toString());
646 	assert(a.matches(Version("3.1.146")), "Failed: Match 3.1.146 with ~>0.1.2");
647 	assert(!a.matches(Version("0.2.0")), "Failed: Match 0.2.0 with ~>0.1.2");
648 	assert(!a.matches(Version("4.0.0-beta.1")));
649 	a = Dependency("~>3.0.0");
650 	assert(a == Dependency(">=3.0.0 <3.1.0-0"), "Testing failed: " ~ a.toString());
651 	a = Dependency("~>3.5");
652 	assert(a == Dependency(">=3.5.0 <4.0.0-0"), "Testing failed: " ~ a.toString());
653 	a = Dependency("~>3.5.0");
654 	assert(a == Dependency(">=3.5.0 <3.6.0-0"), "Testing failed: " ~ a.toString());
655 	assert(!Dependency("~>3.0.0").matches(Version("3.1.0-beta")));
656 
657 	a = Dependency("^0.1.2");
658 	assert(a == Dependency(">=0.1.2 <0.1.3-0"));
659 	a = Dependency("^1.2.3");
660 	assert(a == Dependency(">=1.2.3 <2.0.0-0"), "Testing failed: " ~ a.toString());
661 	a = Dependency("^1.2");
662 	assert(a == Dependency(">=1.2.0 <2.0.0-0"), "Testing failed: " ~ a.toString());
663 
664 	a = Dependency("~>0.1.1");
665 	b = Dependency("==0.1.0");
666 	assert(!a.merge(b).valid);
667 	b = Dependency("==0.1.9999");
668 	assert(a.merge(b).valid);
669 	b = Dependency("==0.2.0");
670 	assert(!a.merge(b).valid);
671 	b = Dependency("==0.2.0-beta.1");
672 	assert(!a.merge(b).valid);
673 
674 	a = Dependency("~>1.0.1-beta");
675 	b = Dependency(">=1.0.1-beta <1.1.0-0");
676 	assert(a == b, "Testing failed: " ~ a.toString());
677 	assert(a.matches(Version("1.0.1-beta")));
678 	assert(a.matches(Version("1.0.1-beta.6")));
679 
680 	a = Dependency("~d2test");
681 	assert(!a.optional);
682 	assert(a.valid);
683 	assert(a.version_ == Version("~d2test"));
684 
685 	a = Dependency("==~d2test");
686 	assert(!a.optional);
687 	assert(a.valid);
688 	assert(a.version_ == Version("~d2test"));
689 
690 	a = Dependency.Any;
691 	assert(!a.optional);
692 	assert(a.valid);
693 	assertThrown(a.version_);
694 	assert(a.matches(Version.masterBranch));
695 	assert(a.matches(Version("1.0.0")));
696 	assert(a.matches(Version("0.0.1-pre")));
697 	b = Dependency(">=1.0.1");
698 	assert(b == a.merge(b));
699 	assert(b == b.merge(a));
700 	b = Dependency(Version.masterBranch);
701 	assert(a.merge(b) == b);
702 	assert(b.merge(a) == b);
703 
704 	a.optional = true;
705 	assert(a.matches(Version.masterBranch));
706 	assert(a.matches(Version("1.0.0")));
707 	assert(a.matches(Version("0.0.1-pre")));
708 	b = Dependency(">=1.0.1");
709 	assert(b == a.merge(b));
710 	assert(b == b.merge(a));
711 	b = Dependency(Version.masterBranch);
712 	assert(a.merge(b) == b);
713 	assert(b.merge(a) == b);
714 
715 	assert(Dependency("1.0.0").matches(Version("1.0.0+foo")));
716 	assert(Dependency("1.0.0").matches(Version("1.0.0+foo"), VersionMatchMode.standard));
717 	assert(!Dependency("1.0.0").matches(Version("1.0.0+foo"), VersionMatchMode.strict));
718 	assert(Dependency("1.0.0+foo").matches(Version("1.0.0+foo"), VersionMatchMode.strict));
719 	assert(Dependency("~>1.0.0+foo").matches(Version("1.0.0+foo"), VersionMatchMode.strict));
720 	assert(Dependency("~>1.0.0").matches(Version("1.0.0+foo"), VersionMatchMode.strict));
721 }
722 
723 unittest {
724 	assert(VersionRange.fromString("~>1.0.4").toString() == "~>1.0.4");
725 	assert(VersionRange.fromString("~>1.4").toString() == "~>1.4");
726 	assert(VersionRange.fromString("~>2").toString() == "~>2");
727 	assert(VersionRange.fromString("~>1.0.4+1.2.3").toString() == "~>1.0.4");
728 	assert(VersionRange.fromString("^0.1.2").toString() == "^0.1.2");
729 	assert(VersionRange.fromString("^1.2.3").toString() == "^1.2.3");
730 	assert(VersionRange.fromString("^1.2").toString() == "~>1.2"); // equivalent; prefer ~>
731 }
732 
733 /**
734 	Represents an SCM repository.
735 */
736 struct Repository
737 {
738 	private string m_remote;
739 	private string m_ref;
740 
741 	private Kind m_kind;
742 
743 	enum Kind
744 	{
745 		git,
746 	}
747 
748 	/**
749 		Params:
750 			remote = Repository remote.
751 			ref_   = Reference to use (SHA1, tag, branch name...)
752 	 */
753 	this(string remote, string ref_)
754 	{
755 		enforce(remote.startsWith("git+"), "Unsupported repository type (supports: git+URL)");
756 
757 		m_remote = remote["git+".length .. $];
758 		m_kind = Kind.git;
759 		m_ref = ref_;
760 		assert(m_remote.length);
761 		assert(m_ref.length);
762 	}
763 
764 	/// Ditto
765 	deprecated("Use the constructor accepting a second parameter named `ref_`")
766 	this(string remote)
767 	{
768 		enforce(remote.startsWith("git+"), "Unsupported repository type (supports: git+URL)");
769 
770 		m_remote = remote["git+".length .. $];
771 		m_kind = Kind.git;
772 		assert(m_remote.length);
773 	}
774 
775 	string toString() const nothrow pure @safe
776 	{
777 		if (empty) return null;
778 		string kindRepresentation;
779 
780 		final switch (kind)
781 		{
782 			case Kind.git:
783 				kindRepresentation = "git";
784 		}
785 		return kindRepresentation~"+"~remote;
786 	}
787 
788 	/**
789 		Returns:
790 			Repository URL or path.
791 	*/
792 	@property string remote() const @nogc nothrow pure @safe
793 	in { assert(m_remote !is null); }
794 	do
795 	{
796 		return m_remote;
797 	}
798 
799 	/**
800 		Returns:
801 			The reference (commit hash, branch name, tag) we are targeting
802 	*/
803 	@property string ref_() const @nogc nothrow pure @safe
804 	in { assert(m_remote !is null); }
805 	in { assert(m_ref !is null); }
806 	do
807 	{
808 		return m_ref;
809 	}
810 
811 	/**
812 		Returns:
813 			Repository type.
814 	*/
815 	@property Kind kind() const @nogc nothrow pure @safe
816 	{
817 		return m_kind;
818 	}
819 
820 	/**
821 		Returns:
822 			Whether the repository was initialized with an URL or path.
823 	*/
824 	@property bool empty() const @nogc nothrow pure @safe
825 	{
826 		return m_remote.empty;
827 	}
828 }
829 
830 
831 /**
832 	Represents a version in semantic version format, or a branch identifier.
833 
834 	This can either have the form "~master", where "master" is a branch name,
835 	or the form "major.update.bugfix-prerelease+buildmetadata" (see the
836 	Semantic Versioning Specification v2.0.0 at http://semver.org/).
837 */
838 struct Version {
839 	private {
840 		static immutable MAX_VERS = "99999.0.0";
841 		static immutable masterString = "~master";
842 		enum branchPrefix = '~';
843 		string m_version;
844 	}
845 
846 	static immutable Version minRelease = Version("0.0.0");
847 	static immutable Version maxRelease = Version(MAX_VERS);
848 	static immutable Version masterBranch = Version(masterString);
849 
850 	/** Constructs a new `Version` from its string representation.
851 	*/
852 	this(string vers) @safe pure
853 	{
854 		enforce(vers.length > 1, "Version strings must not be empty.");
855 		if (vers[0] != branchPrefix)
856 			enforce(vers.isValidVersion(), "Invalid SemVer format: " ~ vers);
857 		m_version = vers;
858 	}
859 
860 	/** Constructs a new `Version` from its string representation.
861 
862 		This method is equivalent to calling the constructor and is used as an
863 		endpoint for the serialization framework.
864 	*/
865 	static Version fromString(string vers) @safe pure { return Version(vers); }
866 
867 	bool opEquals(in Version oth) const scope @safe pure
868 	{
869 		return opCmp(oth) == 0;
870 	}
871 
872 	/// Tests if this represents a branch instead of a version.
873 	@property bool isBranch() const scope @safe pure nothrow @nogc
874 	{
875 		return m_version.length > 0 && m_version[0] == branchPrefix;
876 	}
877 
878 	/// Tests if this represents the master branch "~master".
879 	@property bool isMaster() const scope @safe pure nothrow @nogc
880 	{
881 		return m_version == masterString;
882 	}
883 
884 	/** Tests if this represents a pre-release version.
885 
886 		Note that branches are always considered pre-release versions.
887 	*/
888 	@property bool isPreRelease() const scope @safe pure nothrow @nogc
889 	{
890 		if (isBranch) return true;
891 		return isPreReleaseVersion(m_version);
892 	}
893 
894 	/** Tests two versions for equality, according to the selected match mode.
895 	*/
896 	bool matches(in Version other, VersionMatchMode mode = VersionMatchMode.standard)
897 	const scope @safe pure
898 	{
899 		if (mode == VersionMatchMode.strict)
900 			return this.toString() == other.toString();
901 		return this == other;
902 	}
903 
904 	/** Compares two versions/branches for precedence.
905 
906 		Versions generally have precedence over branches and the master branch
907 		has precedence over other branches. Apart from that, versions are
908 		compared using SemVer semantics, while branches are compared
909 		lexicographically.
910 	*/
911 	int opCmp(in Version other) const scope @safe pure
912 	{
913 		if (isBranch || other.isBranch) {
914 			if(m_version == other.m_version) return 0;
915 			if (!isBranch) return 1;
916 			else if (!other.isBranch) return -1;
917 			if (isMaster) return 1;
918 			else if (other.isMaster) return -1;
919 			return this.m_version < other.m_version ? -1 : 1;
920 		}
921 
922 		return compareVersions(m_version, other.m_version);
923 	}
924 
925 	/// Returns the string representation of the version/branch.
926 	string toString() const return scope @safe pure nothrow @nogc
927 	{
928 		return m_version;
929 	}
930 }
931 
932 /**
933  * A range of versions that are acceptable
934  *
935  * While not directly described in SemVer v2.0.0, a common set
936  * of range operators have appeared among package managers.
937  * We mostly NPM's: https://semver.npmjs.com/
938  *
939  * Hence the acceptable forms for this string are as follows:
940  *
941  * $(UL
942  *  $(LI `"1.0.0"` - a single version in SemVer format)
943  *  $(LI `"==1.0.0"` - alternative single version notation)
944  *  $(LI `">1.0.0"` - version range with a single bound)
945  *  $(LI `">1.0.0 <2.0.0"` - version range with two bounds)
946  *  $(LI `"~>1.0.0"` - a fuzzy version range)
947  *  $(LI `"~>1.0"` - a fuzzy version range with partial version)
948  *  $(LI `"^1.0.0"` - semver compatible version range (same version if 0.x.y, ==major >=minor.patch if x.y.z))
949  *  $(LI `"^1.0"` - same as ^1.0.0)
950  *  $(LI `"~master"` - a branch name)
951  *  $(LI `"*"` - match any version (see also `VersionRange.Any`))
952  * )
953  *
954  * Apart from "$(LT)" and "$(GT)", "$(GT)=" and "$(LT)=" are also valid
955  * comparators.
956  */
957 public struct VersionRange
958 {
959 	private Version m_versA;
960 	private Version m_versB;
961 	private bool m_inclusiveA = true; // A comparison > (true) or >= (false)
962 	private bool m_inclusiveB = true; // B comparison < (true) or <= (false)
963 
964 	/// Matches any version
965 	public static immutable Any = VersionRange(Version.minRelease, Version.maxRelease);
966 	/// Doesn't match any version
967 	public static immutable Invalid = VersionRange(Version.maxRelease, Version.minRelease);
968 
969 	///
970 	public int opCmp (in VersionRange o) const scope @safe
971 	{
972 		if (m_inclusiveA != o.m_inclusiveA) return m_inclusiveA < o.m_inclusiveA ? -1 : 1;
973 		if (m_inclusiveB != o.m_inclusiveB) return m_inclusiveB < o.m_inclusiveB ? -1 : 1;
974 		if (m_versA != o.m_versA) return m_versA < o.m_versA ? -1 : 1;
975 		if (m_versB != o.m_versB) return m_versB < o.m_versB ? -1 : 1;
976 		return 0;
977 	}
978 
979 	public bool matches (in Version v, VersionMatchMode mode = VersionMatchMode.standard)
980 		const scope @safe
981 	{
982 		if (m_versA.isBranch) {
983 			enforce(this.isExactVersion());
984 			return m_versA == v;
985 		}
986 
987 		if (v.isBranch)
988 			return m_versA == v;
989 
990 		if (m_versA == m_versB)
991 			return this.m_versA.matches(v, mode);
992 
993 		return doCmp(m_inclusiveA, m_versA, v) &&
994 			doCmp(m_inclusiveB, v, m_versB);
995 	}
996 
997 	/// Modify in place
998 	public void merge (const VersionRange o) @safe
999 	{
1000 		int acmp = m_versA.opCmp(o.m_versA);
1001 		int bcmp = m_versB.opCmp(o.m_versB);
1002 
1003 		this.m_inclusiveA = !m_inclusiveA && acmp >= 0 ? false : o.m_inclusiveA;
1004 		this.m_versA = acmp > 0 ? m_versA : o.m_versA;
1005 		this.m_inclusiveB = !m_inclusiveB && bcmp <= 0 ? false : o.m_inclusiveB;
1006 		this.m_versB = bcmp < 0 ? m_versB : o.m_versB;
1007 	}
1008 
1009 	/// Returns true $(I iff) the version range only matches a specific version.
1010 	@property bool isExactVersion() const scope @safe
1011 	{
1012 		return this.m_versA == this.m_versB;
1013 	}
1014 
1015 	/// Determines if this dependency specification matches arbitrary versions.
1016 	/// This is true in particular for the `any` constant.
1017 	public bool matchesAny() const scope @safe
1018 	{
1019 		return this.m_inclusiveA && this.m_inclusiveB
1020 			&& this.m_versA == Version.minRelease
1021 			&& this.m_versB == Version.maxRelease;
1022 	}
1023 
1024 	unittest {
1025 		assert(VersionRange.fromString("*").matchesAny);
1026 		assert(!VersionRange.fromString(">0.0.0").matchesAny);
1027 		assert(!VersionRange.fromString(">=1.0.0").matchesAny);
1028 		assert(!VersionRange.fromString("<1.0.0").matchesAny);
1029 	}
1030 
1031 	public static VersionRange fromString (string ves) @safe
1032 	{
1033 		static import std.string;
1034 
1035 		enforce(ves.length > 0);
1036 
1037 		if (ves == Dependency.ANY_IDENT) {
1038 			// Any version is good.
1039 			ves = ">=0.0.0";
1040 		}
1041 
1042 		if (ves.startsWith("~>")) {
1043 			// Shortcut: "~>x.y.z" variant. Last non-zero number will indicate
1044 			// the base for this so something like this: ">=x.y.z <x.(y+1).z"
1045 			ves = ves[2..$];
1046 			return VersionRange(
1047 				Version(expandVersion(ves)), Version(bumpVersion(ves) ~ "-0"),
1048 				true, false);
1049 		}
1050 
1051 		if (ves.startsWith("^")) {
1052 			// Shortcut: "^x.y.z" variant. "Semver compatible" - no breaking changes.
1053 			// if 0.x.y, ==0.x.y
1054 			// if x.y.z, >=x.y.z <(x+1).0.0-0
1055 			// ^x.y is equivalent to ^x.y.0.
1056 			ves = ves[1..$].expandVersion;
1057 			return VersionRange(
1058 				Version(ves), Version(bumpIncompatibleVersion(ves) ~ "-0"),
1059 				true, false);
1060 		}
1061 
1062 		if (ves[0] == Version.branchPrefix) {
1063 			auto ver = Version(ves);
1064 			return VersionRange(ver, ver, true, true);
1065 		}
1066 
1067 		if (std..string.indexOf("><=", ves[0]) == -1) {
1068 			auto ver = Version(ves);
1069 			return VersionRange(ver, ver, true, true);
1070 		}
1071 
1072 		auto cmpa = skipComp(ves);
1073 		size_t idx2 = std..string.indexOf(ves, " ");
1074 		if (idx2 == -1) {
1075 			if (cmpa == "<=" || cmpa == "<")
1076 				return VersionRange(Version.minRelease, Version(ves), true, (cmpa == "<="));
1077 
1078 			if (cmpa == ">=" || cmpa == ">")
1079 				return VersionRange(Version(ves), Version.maxRelease, (cmpa == ">="), true);
1080 
1081 			// Converts "==" to ">=a&&<=a", which makes merging easier
1082 			return VersionRange(Version(ves), Version(ves), true, true);
1083 		}
1084 
1085 		enforce(cmpa == ">" || cmpa == ">=",
1086 				"First comparison operator expected to be either > or >=, not " ~ cmpa);
1087 		assert(ves[idx2] == ' ');
1088 		VersionRange ret;
1089 		ret.m_versA = Version(ves[0..idx2]);
1090 		ret.m_inclusiveA = cmpa == ">=";
1091 		string v2 = ves[idx2+1..$];
1092 		auto cmpb = skipComp(v2);
1093 		enforce(cmpb == "<" || cmpb == "<=",
1094 				"Second comparison operator expected to be either < or <=, not " ~ cmpb);
1095 		ret.m_versB = Version(v2);
1096 		ret.m_inclusiveB = cmpb == "<=";
1097 
1098 		enforce(!ret.m_versA.isBranch && !ret.m_versB.isBranch,
1099 				format("Cannot compare branches: %s", ves));
1100 		enforce(ret.m_versA <= ret.m_versB,
1101 				"First version must not be greater than the second one.");
1102 
1103 		return ret;
1104 	}
1105 
1106 	/// Returns a string representation of this range
1107 	string toString() const @safe {
1108 		static import std.string;
1109 
1110 		string r;
1111 
1112 		if (this == Invalid) return "no";
1113 		if (this.isExactVersion() && m_inclusiveA && m_inclusiveB) {
1114 			// Special "==" case
1115 			if (m_versA == Version.masterBranch) return "~master";
1116 			else return m_versA.toString();
1117 		}
1118 
1119 		// "~>", "^" case
1120 		if (m_inclusiveA && !m_inclusiveB && !m_versA.isBranch) {
1121 			auto vs = m_versA.toString();
1122 			auto i1 = std..string.indexOf(vs, '-'), i2 = std..string.indexOf(vs, '+');
1123 			auto i12 = i1 >= 0 ? i2 >= 0 ? i1 < i2 ? i1 : i2 : i1 : i2;
1124 			auto va = i12 >= 0 ? vs[0 .. i12] : vs;
1125 			auto parts = va.splitter('.').array;
1126 			assert(parts.length == 3, "Version string with a digit group count != 3: "~va);
1127 
1128 			foreach (i; 0 .. 3) {
1129 				auto vp = parts[0 .. i+1].join(".");
1130 				auto ve = Version(expandVersion(vp));
1131 				auto veb = Version(bumpVersion(vp) ~ "-0");
1132 				if (ve == m_versA && veb == m_versB) return "~>" ~ vp;
1133 
1134 				auto veb2 = Version(bumpIncompatibleVersion(expandVersion(vp)) ~ "-0");
1135 				if (ve == m_versA && veb2 == m_versB) return "^" ~ vp;
1136 			}
1137 		}
1138 
1139 		if (m_versA != Version.minRelease) r = (m_inclusiveA ? ">=" : ">") ~ m_versA.toString();
1140 		if (m_versB != Version.maxRelease) r ~= (r.length==0 ? "" : " ") ~ (m_inclusiveB ? "<=" : "<") ~ m_versB.toString();
1141 		if (this.matchesAny()) r = ">=0.0.0";
1142 		return r;
1143 	}
1144 
1145 	public bool isValid() const @safe {
1146 		return m_versA <= m_versB && doCmp(m_inclusiveA && m_inclusiveB, m_versA, m_versB);
1147 	}
1148 
1149 	private static bool doCmp(bool inclusive, in Version a, in Version b)
1150 		@safe
1151 	{
1152 		return inclusive ? a <= b : a < b;
1153 	}
1154 
1155 	private static bool isDigit(char ch) @safe { return ch >= '0' && ch <= '9'; }
1156 	private static string skipComp(ref string c) @safe {
1157 		size_t idx = 0;
1158 		while (idx < c.length && !isDigit(c[idx]) && c[idx] != Version.branchPrefix) idx++;
1159 		enforce(idx < c.length, "Expected version number in version spec: "~c);
1160 		string cmp = idx==c.length-1||idx==0? ">=" : c[0..idx];
1161 		c = c[idx..$];
1162 		switch(cmp) {
1163 			default: enforce(false, "No/Unknown comparison specified: '"~cmp~"'"); return ">=";
1164 			case ">=": goto case; case ">": goto case;
1165 			case "<=": goto case; case "<": goto case;
1166 			case "==": return cmp;
1167 		}
1168 	}
1169 }
1170 
1171 enum VersionMatchMode {
1172 	standard,  /// Match according to SemVer rules
1173 	strict     /// Also include build metadata suffix in the comparison
1174 }
1175 
1176 unittest {
1177 	Version a, b;
1178 
1179 	assertNotThrown(a = Version("1.0.0"), "Constructing Version('1.0.0') failed");
1180 	assert(!a.isBranch, "Error: '1.0.0' treated as branch");
1181 	assert(a == a, "a == a failed");
1182 
1183 	assertNotThrown(a = Version(Version.masterString), "Constructing Version("~Version.masterString~"') failed");
1184 	assert(a.isBranch, "Error: '"~Version.masterString~"' treated as branch");
1185 	assert(a.isMaster);
1186 	assert(a == Version.masterBranch, "Constructed master version != default master version.");
1187 
1188 	assertNotThrown(a = Version("~BRANCH"), "Construction of branch Version failed.");
1189 	assert(a.isBranch, "Error: '~BRANCH' not treated as branch'");
1190 	assert(!a.isMaster);
1191 	assert(a == a, "a == a with branch failed");
1192 
1193 	// opCmp
1194 	a = Version("1.0.0");
1195 	b = Version("1.0.0");
1196 	assert(a == b, "a == b with a:'1.0.0', b:'1.0.0' failed");
1197 	b = Version("2.0.0");
1198 	assert(a != b, "a != b with a:'1.0.0', b:'2.0.0' failed");
1199 
1200 	a = Version.masterBranch;
1201 	b = Version("~BRANCH");
1202 	assert(a != b, "a != b with a:MASTER, b:'~branch' failed");
1203 	assert(a > b);
1204 	assert(a < Version("0.0.0"));
1205 	assert(b < Version("0.0.0"));
1206 	assert(a > Version("~Z"));
1207 	assert(b < Version("~Z"));
1208 
1209 	// SemVer 2.0.0-rc.2
1210 	a = Version("2.0.0-rc.2");
1211 	b = Version("2.0.0-rc.3");
1212 	assert(a < b, "Failed: 2.0.0-rc.2 < 2.0.0-rc.3");
1213 
1214 	a = Version("2.0.0-rc.2+build-metadata");
1215 	b = Version("2.0.0+build-metadata");
1216 	assert(a < b, "Failed: "~a.toString()~"<"~b.toString());
1217 
1218 	// 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0
1219 	Version[] versions;
1220 	versions ~= Version("1.0.0-alpha");
1221 	versions ~= Version("1.0.0-alpha.1");
1222 	versions ~= Version("1.0.0-beta.2");
1223 	versions ~= Version("1.0.0-beta.11");
1224 	versions ~= Version("1.0.0-rc.1");
1225 	versions ~= Version("1.0.0");
1226 	for(int i=1; i<versions.length; ++i)
1227 		for(int j=i-1; j>=0; --j)
1228 			assert(versions[j] < versions[i], "Failed: " ~ versions[j].toString() ~ "<" ~ versions[i].toString());
1229 
1230 	assert(Version("1.0.0+a") == Version("1.0.0+b"));
1231 
1232 	assert(Version("1.0.0").matches(Version("1.0.0+foo")));
1233 	assert(Version("1.0.0").matches(Version("1.0.0+foo"), VersionMatchMode.standard));
1234 	assert(!Version("1.0.0").matches(Version("1.0.0+foo"), VersionMatchMode.strict));
1235 	assert(Version("1.0.0+foo").matches(Version("1.0.0+foo"), VersionMatchMode.strict));
1236 }
1237 
1238 /// Determines whether the given string is a Git hash.
1239 bool isGitHash(string hash) @nogc nothrow pure @safe
1240 {
1241 	import std.ascii : isHexDigit;
1242 	import std.utf : byCodeUnit;
1243 
1244 	return hash.length >= 7 && hash.length <= 40 && hash.byCodeUnit.all!isHexDigit;
1245 }
1246 
1247 @nogc nothrow pure @safe unittest {
1248 	assert(isGitHash("73535568b79a0b124bc1653002637a830ce0fcb8"));
1249 	assert(!isGitHash("735"));
1250 	assert(!isGitHash("73535568b79a0b124bc1-53002637a830ce0fcb8"));
1251 	assert(!isGitHash("73535568b79a0b124bg1"));
1252 }