VcsVersionInfo::__construct()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 18
rs 9.4285
cc 3
eloc 13
nc 4
nop 4
1
<?php
2
namespace nochso\Omni;
3
4
/**
5
 * VcsVersionInfo enriches an internal VersionInfo with the latest tag and current repository state.
6
 *
7
 * If the working directory is clean and at an exact tag, only the tag is returned:
8
 *
9
 *     1.0.0
10
 *
11
 * If dirty and at an exact tag, `-dirty` is appended:
12
 *
13
 *     1.0.0-dirty
14
 *
15
 * If there are no tags present, the revision id is returned:
16
 *
17
 *     4319e00
18
 *
19
 * If there have been commits since a tag:
20
 *
21
 *     0.3.1-14-gf602496-dirty
22
 *
23
 * Where `14` is the amount of commits since tag `0.3.1`.
24
 *
25
 * Internally a VersionInfo object is used. Note that this class does not extend from VersionInfo
26
 * as it uses different constructor parameters.
27
 */
28
final class VcsVersionInfo {
29
	/**
30
	 * @var \nochso\Omni\VersionInfo
31
	 */
32
	private $version;
33
	/**
34
	 * @var string
35
	 */
36
	private $repositoryRoot;
37
38
	/**
39
	 * @param string $name            Package or application name.
40
	 * @param string $fallBackVersion Optional version to fall back on if no repository info was found.
41
	 * @param string $repositoryRoot  Path the VCS repository root (e.g. folder that contains ".git", ".hg", etc.)
42
	 * @param string $infoFormat      Optional format to use for `getInfo`. Defaults to `VersionInfo::INFO_FORMAT_DEFAULT`
43
	 *
44
	 * @throws \RuntimeException When no fallback was given and tag could not be extracted from a VCS repo.
45
	 */
46
	public function __construct(
47
		$name,
48
		$fallBackVersion = null,
49
		$repositoryRoot = '.',
50
		$infoFormat = \nochso\Omni\VersionInfo::INFO_FORMAT_SHORT
51
	) {
52
		$this->repositoryRoot = $repositoryRoot;
53
		$tag = $this->extractTag();
54
		if ($tag === null) {
55
			$tag = $fallBackVersion;
56
		}
57
		if ($tag === null) {
58
			throw new \RuntimeException(
59
				'Unable to detect version from VCS repository and no fallback version was specified.'
60
			);
61
		}
62
		$this->version = new VersionInfo($name, $tag, $infoFormat);
63
	}
64
65
	/**
66
	 * @return string
67
	 */
68
	public function getInfo() {
69
		return $this->version->getInfo();
70
	}
71
72
	/**
73
	 * @return string
74
	 */
75
	public function getVersion() {
76
		return $this->version->getVersion();
77
	}
78
79
	/**
80
	 * @return string
81
	 */
82
	public function getName() {
83
		return $this->version->getName();
84
	}
85
86
	private function extractTag() {
87
		$tag = $this->readGit();
88
		if ($tag === null) {
89
			$tag = $this->readMercurial();
90
		}
91
		return $tag;
92
	}
93
94
	/**
95
	 * @return null|string
96
	 */
97
	private function readGit() {
98
		$gitDir = Path::combine($this->repositoryRoot, '.git');
99
		if (!is_dir($gitDir) || !OS::hasBinary('git')) {
100
			return null;
101
		}
102
		$git = Exec::create('git', '--git-dir=' . $gitDir, '--work-tree=' . $this->repositoryRoot);
103
		$git->run('describe', '--tags', '--always', '--dirty');
104
		return Dot::get($git->getOutput(), 0);
105
	}
106
107
	/**
108
	 * @return null|string
109
	 */
110
	private function readMercurial() {
111
		$hgDir = Path::combine($this->repositoryRoot, '.hg');
112
		if (!is_dir($hgDir) || !OS::hasBinary('hg')) {
113
			return null;
114
		}
115
		$hg = Exec::create('hg', '--repository', $this->repositoryRoot);
116
		// Removes everything but the tag if distance is zero.
117
		$hg->run(
118
			'log',
119
			'-r',
120
			'.',
121
			'--template',
122
			'{latesttag}{sub(\'^-0-m.*\', \'\', \'-{latesttagdistance}-m{node|short}\')}'
123
		);
124
		$tag = Dot::get($hg->getOutput(), 0);
125
		// Actual null if no lines were returned or `hg log` returned actual "null".
126
		// Either way, need to fall back to the revision id.
127
		if ($tag === null || $tag === 'null' || Strings::startsWith($tag, 'null-')) {
128
			$hg->run('id', '-i');
129
			$tag = Dot::get($hg->getOutput(), 0);
130
			// Remove 'dirty' plus from revision id
131
			$tag = rtrim($tag, '+');
132
		}
133
		$summary = $hg->run('summary')->getOutput();
134
		$isDirty = 0 === count(
135
			array_filter(
136
				$summary,
137
				function ($line) {
138
					return preg_match('/^commit: .*\(clean\)$/', $line) === 1;
139
				}
140
			)
141
		);
142
		if ($isDirty) {
143
			$tag .= '-dirty';
144
		}
145
		return $tag;
146
	}
147
}
148