Skip to content

Commit d5f7340

Browse files
committed
Add new forked versioning checks for project:version command
1 parent 34de033 commit d5f7340

1 file changed

Lines changed: 118 additions & 27 deletions

File tree

src/Commands/ProjectVersion/SourceManifest.php

Lines changed: 118 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,36 @@ class SourceManifest
2727
*/
2828
protected $builds = [];
2929

30+
/**
31+
* @var array The version map where forks occurred.
32+
*/
33+
protected $forks;
34+
35+
/**
36+
* @var string The URL to the forked version manifest
37+
*/
38+
protected $forksUrl = 'https://raw.githubusercontent.com/wintercms/meta/master/manifest/forks.json';
39+
3040
/**
3141
* Constructor
3242
*
3343
* @param string $manifest Manifest file to load
44+
* @param string $forks Forks manifest file to load
3445
* @param bool $autoload Loads the manifest on construct
3546
*/
36-
public function __construct($source = null, $autoload = true)
47+
public function __construct($source = null, $forks = null, $autoload = true)
3748
{
3849
if (isset($source)) {
3950
$this->setSource($source);
4051
}
4152

53+
if (isset($forks)) {
54+
$this->setForksSource($forks);
55+
}
56+
4257
if ($autoload) {
43-
$this->load();
58+
$this->loadSource();
59+
$this->loadForks();
4460
}
4561
}
4662

@@ -57,12 +73,25 @@ public function setSource($source)
5773
}
5874
}
5975

76+
/**
77+
* Sets the forked version manifest URL.
78+
*
79+
* @param string $forks
80+
* @return void
81+
*/
82+
public function setForksSource($forks)
83+
{
84+
if (is_string($forks)) {
85+
$this->forksUrl = $forks;
86+
}
87+
}
88+
6089
/**
6190
* Loads the manifest file.
6291
*
6392
* @throws Exception If the manifest is invalid, or cannot be parsed.
6493
*/
65-
public function load()
94+
public function loadSource()
6695
{
6796
$source = file_get_contents($this->source);
6897
if (empty($source)) {
@@ -85,7 +114,9 @@ public function load()
85114
}
86115

87116
foreach ($data['manifest'] as $build) {
88-
$this->builds[$build['build']] = [
117+
$this->builds[$this->getVersionInt($build['build'])] = [
118+
'version' => $build['build'],
119+
'parent' => $build['parent'],
89120
'modules' => $build['modules'],
90121
'files' => $build['files'],
91122
];
@@ -95,47 +126,81 @@ public function load()
95126
}
96127

97128
/**
98-
* Gets all builds.
129+
* Loads the forked version manifest file.
99130
*
100-
* @return array
131+
* @throws Exception If the manifest is invalid, or cannot be parsed.
101132
*/
102-
public function getBuilds()
133+
public function loadForks()
103134
{
104-
return $this->builds;
135+
$forks = file_get_contents($this->forksUrl);
136+
if (empty($forks)) {
137+
throw new Exception(
138+
'Forked version manifest not found'
139+
);
140+
}
141+
142+
$data = json_decode($forks, true);
143+
144+
if (json_last_error() !== JSON_ERROR_NONE) {
145+
throw new Exception(
146+
'Unable to decode forked version manifest JSON data. JSON Error: ' . json_last_error_msg()
147+
);
148+
}
149+
if (!isset($data['forks']) || !is_array($data['forks'])) {
150+
throw new Exception(
151+
'The forked version manifest at "' . $this->forksUrl . '" does not appear to be a valid forked version
152+
manifest file.'
153+
);
154+
}
155+
156+
// Map forks to int values
157+
foreach ($data['forks'] as $child => $parent) {
158+
$this->forks[$this->getVersionInt($child)] = $this->getVersionInt($parent);
159+
}
160+
161+
return $this;
105162
}
106163

107164
/**
108-
* Gets the maximum build number in the manifest.
165+
* Gets all builds.
109166
*
110-
* @return int
167+
* @return array
111168
*/
112-
public function getMaxBuild()
169+
public function getBuilds()
113170
{
114-
if (!count($this->builds)) {
115-
return null;
116-
}
117-
118-
return max(array_keys($this->builds));
171+
return array_values(array_map(function ($build) {
172+
return $build['version'];
173+
}, $this->builds));
119174
}
120175

121176
/**
122177
* Gets the filelist state at a selected build.
123178
*
124-
* This method will list all expected files and hashsums at the specified build number.
179+
* This method will list all expected files and hashsums at the specified build number. It does this by following
180+
* the history, switching branches as necessary.
125181
*
126-
* @param integer $build Build number to get the filelist state for.
182+
* @param string|integer $build Build version to get the filelist state for.
127183
* @throws Exception If the specified build has not been added to the source manifest.
128184
* @return array
129185
*/
130186
public function getState($build)
131187
{
188+
if (is_string($build)) {
189+
$build = $this->getVersionInt($build);
190+
}
191+
132192
if (!isset($this->builds[$build])) {
133193
throw new \Exception('The specified build has not been added.');
134194
}
135195

136196
$state = [];
137197

138198
foreach ($this->builds as $number => $details) {
199+
// Follow fork if necessary
200+
if (isset($this->forks) && array_key_exists($build, $this->forks)) {
201+
$state = $this->getState($this->forks[$build]);
202+
}
203+
139204
if (isset($details['files']['added'])) {
140205
foreach ($details['files']['added'] as $filename => $sum) {
141206
$state[$filename] = $sum;
@@ -181,12 +246,14 @@ public function compare(FileManifest $manifest, $detailed = false)
181246
$modules = $manifest->getModuleChecksums();
182247

183248
// Look for an unmodified version
184-
foreach ($this->getBuilds() as $build => $details) {
185-
$matched = array_intersect_assoc($details['modules'], $modules);
249+
foreach ($this->getBuilds() as $buildString) {
250+
$build = $this->builds[$this->getVersionInt($buildString)];
251+
252+
$matched = array_intersect_assoc($build['modules'], $modules);
186253

187254
if (count($matched) === count($modules)) {
188255
$details = [
189-
'build' => $build,
256+
'build' => $buildString,
190257
'modified' => false,
191258
'confident' => true,
192259
];
@@ -203,8 +270,10 @@ public function compare(FileManifest $manifest, $detailed = false)
203270
// install.
204271
$buildMatch = [];
205272

206-
foreach ($this->getBuilds() as $build => $details) {
207-
$state = $this->getState($build);
273+
foreach ($this->getBuilds() as $buildString) {
274+
$build = $this->builds[$this->getVersionInt($buildString)];
275+
276+
$state = $this->getState($buildString);
208277

209278
// Include only the files that match the modules being loaded in this file manifest
210279
$availableModules = array_keys($modules);
@@ -244,7 +313,7 @@ public function compare(FileManifest $manifest, $detailed = false)
244313
$changedPercent = count($filesChanged) / $filesExpected;
245314

246315
$score = ((1 * $foundPercent) - $changedPercent);
247-
$buildMatch[$build] = round($score * 100, 2);
316+
$buildMatch[$buildString] = round($score * 100, 2);
248317
}
249318

250319
// Find likely version
@@ -269,8 +338,8 @@ public function compare(FileManifest $manifest, $detailed = false)
269338
* Will return an array of added, modified and removed files.
270339
*
271340
* @param FileManifest $manifest The current build's file manifest.
272-
* @param FileManifest|integer $previous Either a previous manifest, or the previous build number as an int,
273-
* used to determine changes with this build.
341+
* @param FileManifest|string|integer $previous Either a previous manifest, or the previous build number as an int
342+
* or string, used to determine changes with this build.
274343
* @return array
275344
*/
276345
protected function processChanges(FileManifest $manifest, $previous = null)
@@ -283,7 +352,7 @@ protected function processChanges(FileManifest $manifest, $previous = null)
283352
}
284353

285354
// Only save files if they are changing the "state" of the manifest (ie. the file is modified, added or removed)
286-
if (is_int($previous)) {
355+
if (is_int($previous) || is_string($previous)) {
287356
$state = $this->getState($previous);
288357
} else {
289358
$state = $previous->getFiles();
@@ -319,4 +388,26 @@ protected function processChanges(FileManifest $manifest, $previous = null)
319388

320389
return $changes;
321390
}
391+
392+
393+
/**
394+
* Converts a version string into an integer for comparison.
395+
*
396+
* @param string $version
397+
* @throws Exception if a version string does not match the format "major.minor.path"
398+
* @return int
399+
*/
400+
protected function getVersionInt(string $version)
401+
{
402+
// Get major.minor.patch versions
403+
if (!preg_match('/^([0-9]+)\.([0-9]+)\.([0-9]+)/', $version, $versionParts)) {
404+
throw new Exception('Invalid version string - must be of the format "major.minor.path"');
405+
}
406+
407+
$int = $versionParts[1] * 1000000;
408+
$int += $versionParts[2] * 1000;
409+
$int += $versionParts[3];
410+
411+
return $int;
412+
}
322413
}

0 commit comments

Comments
 (0)