GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( c5752c...f61021 )
by
unknown
04:58
created

Base::jsonEncode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 4
c 1
b 0
f 1
nc 1
nop 1
dl 0
loc 6
rs 9.4285
1
<?php
2
/**
3
 * See class comment
4
 *
5
 * PHP Version 5
6
 *
7
 * @category   Netresearch
8
 * @package    Netresearch\Kite\Workflow
9
 * @subpackage Composer
10
 * @author     Christian Opitz <[email protected]>
11
 * @license    http://www.netresearch.de Netresearch Copyright
12
 * @link       http://www.netresearch.de
13
 */
14
15
namespace Netresearch\Kite\Workflow\Composer;
16
use Netresearch\Kite\Service\Composer\Package;
17
use Netresearch\Kite\Workflow;
18
use Netresearch\Kite\Exception;
19
20
/**
21
 * Abstract for composer workflows
22
 *
23
 * @category   Netresearch
24
 * @package    Netresearch\Kite\Workflow
25
 * @subpackage Composer
26
 * @author     Christian Opitz <[email protected]>
27
 * @license    http://www.netresearch.de Netresearch Copyright
28
 * @link       http://www.netresearch.de
29
 */
30
abstract class Base extends Workflow
31
{
32
    /**
33
     * @var Package[]
34
     */
35
    protected $pushPackages = array();
36
37
    /**
38
     * @var array
39
     */
40
    protected $whitelists;
41
42
    /**
43
     * Configure the variables
44
     *
45
     * @return array
46
     */
47
    protected function configureVariables()
48
    {
49
        $config = $this->getParent()->get('config');
50
        if (!array_key_exists('composer', $config)) {
51
            $config['composer'] = [];
52
        }
53
        foreach (['whitelistNames', 'whitelistPaths', 'whitelistRemotes'] as $key) {
54
            if (!array_key_exists($key, $config['composer'])) {
55
                $config['composer'][$key] = null;
56
            }
57
        }
58
        return [
59
            'whitelistNames' => array(
60
                'default' => '{config["composer"]["whitelistNames"]}',
61
                'type' => 'string',
62
                'label' => 'Regular expression for package names, to limit this operation to',
63
                'option' => true
64
            ),
65
            'whitelistPaths' => array(
66
                'default' => '{config["composer"]["whitelistPaths"]}',
67
                'type' => 'string',
68
                'label' => 'Regular expression for package paths, to limit this operation to',
69
                'option' => true
70
            ),
71
            'whitelistRemotes' => array(
72
                'default' => '{config["composer"]["whitelistRemotes"]}',
73
                'type' => 'string',
74
                'label' => 'Regular expression for package remote urls, to limit this operation to',
75
                'option' => true
76
            ),
77
            '--'
78
        ] + parent::configureVariables();
79
    }
80
81
82
    /**
83
     * Push all packages marked to be pushed
84
     *
85
     * @return void
86
     */
87
    protected function pushPackages()
88
    {
89
        foreach ($this->pushPackages as $i => $package) {
90
            $this->assertPackageAllowed($package);
91
            $this->console->output("Pushing <comment>$package->name</comment>", false);
92
            $this->git('push', $package->path, array('u' => 'origin', $package->branch));
93
            $this->console->output(
94
                str_repeat(chr(8), strlen($package->name))
95
                . '<info>' . $package->name . '</info>'
96
            );
97
            unset($this->pushPackages[$i]);
98
        }
99
    }
100
101
    /**
102
     * doComposerUpdateIfNecessary and possible
103
     *
104
     * @return void
105
     */
106
    protected function doComposerUpdate()
107
    {
108
        try {
109
            $this->composer('update');
110
        } catch (\Exception $e) {
111
            $this->console->output('<warning>Composer update failed</warning>');
112
            $this->console->output('<comment>This might have occured because previous pushes to git did not reache the composer repo yet</comment>');
113
            if ($this->confirm('Retry?')) {
114
                $this->doComposerUpdate();
115
            } else {
116
                $this->doExit('', 1);
117
            }
118
        }
119
    }
120
121
    /**
122
     * Go through all packages and check if packages requiring those packages,
123
     * still require their (likely new) versions.
124
     *
125
     * If not and $autoFix or user agrees, the require-statements in the
126
     * dependent packages are changed accordingly.
127
     *
128
     * @param Package[] $packages Packages
129
     * @param bool      $autoFix  Whether to autofix wrong requirements
130
     *
131
     * @return void
132
     */
133
    protected function rewriteRequirements(array &$packages, $autoFix = false)
134
    {
135
        $checkedOutPackages = array_keys($packages);
136
        $unfixedRequirements = 0;
137
        while ($packageName = array_shift($checkedOutPackages)) {
138
            $branch = $packages[$packageName]->branch;
139
            $version = 'dev-' . $branch;
140
            foreach ($this->getPackages(false, false) as $package) {
141
                if (array_key_exists($packageName, $package->requires)) {
142
                    // TODO: Set required version to branch alias, if any
143
                    $requiredVersion = $package->requires[$packageName];
144
                    if ($requiredVersion === '@dev') {
145
                        $requiredVersion = 'dev-master';
146
                    }
147
                    if ($requiredVersion !== $version) {
148
                        $this->assertPackageAllowed($package);
149
                        if (!$package->git) {
150
                            throw new Exception("Package {$package->name} required to be installed from source");
151
                        }
152
                        if ($autoFix) {
153
                            $fix = true;
154
                            $this->output("Changing required version of {$packageName} in {$package->name} from {$requiredVersion} to {$version}");
155
                        } else {
156
                            $this->output("<warning>{$package->name} depends on {$packageName} {$package->requires[$packageName]} and not {$version} as expected</warning>");
157
                            $this->output('<comment>If you don\'t fix that, the branches will probably change with composer update</comment>');
158
                            $fix = $this->confirm('Fix that?');
159
                        }
160
                        if ($fix) {
161
                            if (!array_key_exists($package->name, $packages)) {
162
                                $this->checkoutPackage($package, $branch, true);
163
                                $checkedOutPackages[] = $package->name;
164
                                $packages[$package->name] = $package;
165
                            }
166
167
                            $this->pushPackages[$packageName] = $packages[$packageName];
168
                            $this->rewriteRequirement($package, $packageName, $version);
169
                        } else {
170
                            $unfixedRequirements++;
171
                        }
172
                    }
173
                }
174
            }
175
        }
176
        if ($unfixedRequirements) {
177
            $this->doExit(
178
                'It seems like a composer update is required but due to probably incorrect dependencies you have to do that manually', 1
179
            );
180
        }
181
    }
182
183
    /**
184
     * Change the require statement for $requiredPackage to the newVersion in $package
185
     *
186
     * @param Package $package         The package
187
     * @param string  $requiredPackage The required package
188
     * @param string  $newVersion      The new version
189
     *
190
     * @return void
191
     */
192
    protected function rewriteRequirement($package, $requiredPackage, $newVersion)
193
    {
194
        $this->assertPackageAllowed($package);
195
196
        $currentVersion = $package->requires[$requiredPackage];
197
        $composerFile = $package->path . '/composer.json';
198
        $composerFileContents = file_get_contents($composerFile);
199
        $newComposerFileContents = preg_replace(
200
            sprintf(
201
                '/(^\s*"require"\s*:\s*\{[^\}]+"%s"\s*:\s*")%s/m',
202
                preg_quote($requiredPackage, '/'),
203
                preg_quote($currentVersion, '/')
204
            ),
205
            '$1' . $newVersion,
206
            $composerFileContents
207
        );
208
        file_put_contents($composerFile, $newComposerFileContents);
209
        $this->reloadRequires($package);
0 ignored issues
show
Deprecated Code introduced by
The method Netresearch\Kite\Workflo...\Base::reloadRequires() has been deprecated with message: Use $package->reloadRequires()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
210
        if ($package->requires[$requiredPackage] !== $newVersion) {
211
            file_put_contents($composerFile, $composerFileContents);
212
            $this->output('<error>Could not replace version</error> - generated composer.json was:');
213
            $this->output($newComposerFileContents);
214
            throw new Exception('Replacing version failed');
215
        }
216
217
        $this->git('commit', $package->path, array('n' => true, 'm' => "Change required version of $requiredPackage to $newVersion", 'composer.json'));
218
        if (!isset($package->source)) {
219
            $package->source = new \stdClass();
220
        }
221
        $package->source->reference = $this->git('rev-parse', $package->path, array('HEAD'));
222
223
        $this->console->output("Made <comment>$package->name</comment> require <comment>$requiredPackage $newVersion</comment>");
224
225
        $this->pushPackages[$package->name] = $package;
226
    }
227
228
    /**
229
     * Reload the requires from $package composer.json to $package->requires
230
     *
231
     * If $detectChanges, and there are changes on the requirements not in $ignorePackages
232
     * composer update is requested
233
     *
234
     * @param Package $package The package
235
     *
236
     * @deprecated Use $package->reloadRequires()
237
     *
238
     * @return void
239
     */
240
    protected function reloadRequires($package)
241
    {
242
        $package->reloadRequires();
243
    }
244
245
    /**
246
     * Checkout a package at a branch
247
     *
248
     * @param Package $package The package
249
     * @param string  $branch  The branch
250
     * @param bool    $create  Create the branch if it doesn't exist
251
     *
252
     * @return bool|null Whether checkout was successful or null when package is already at this branch
253
     */
254
    protected function checkoutPackage($package, $branch, $create = false)
255
    {
256
        $this->assertPackageAllowed($package);
257
258
        if ($package->branch === $branch) {
259
            return null;
260
        }
261
        if (!$package->git) {
262
            throw new Exception('Non git package can not be checked out');
263
        }
264
        $remoteBranch = 'origin/' . $branch;
265
        $isRemote = in_array($remoteBranch, $package->branches, true);
266
        if (in_array($branch, $package->branches, true)) {
267
            $this->git('checkout', $package->path, $branch);
268
        } elseif ($isRemote) {
269
            $this->git('checkout', $package->path, array('b' => $branch, $remoteBranch));
270
        } elseif ($create) {
271
            $branches = array_unique(
272
                array_map(
273
                    function ($el) {
274
                        $parts = explode('/', $el);
275
                        return array_pop($parts);
276
                    },
277
                    $package->branches
278
                )
279
            );
280
            sort($branches);
281
            $inferFromBranch = $this->choose(
282
                "Select branch to create new branch '$branch' from in {$package->name}",
283
                $branches, in_array('master', $branches, true) ? 'master' : $branch
284
            );
285
            if ($inferFromBranch !== $package->branch) {
286
                $this->checkoutPackage($package, $inferFromBranch);
287
            }
288
            $this->git('checkout', $package->path, array('b' => $branch));
289
            $package->branches[] = $branch;
290
        } else {
291
            return false;
292
        }
293
294
        if ($isRemote) {
295
            if (!isset($package->upstreams[$branch]) || $package->upstreams[$branch] !== $remoteBranch) {
296
                $this->git('branch', $package->path, array('u' => $remoteBranch));
297
                $package->upstreams[$branch] = $remoteBranch;
298
            }
299
            $this->git('rebase', $package->path);
300
        }
301
302
        $this->console->output("Checked out <comment>{$package->name}</comment> at <comment>$branch</comment>");
303
304
        $this->reloadRequires($package);
0 ignored issues
show
Deprecated Code introduced by
The method Netresearch\Kite\Workflo...\Base::reloadRequires() has been deprecated with message: Use $package->reloadRequires()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
305
        $package->version = 'dev-' . $branch;
306
        $package->branch = $branch;
307
308
        return true;
309
    }
310
311
    /**
312
     * Merge a $branch into $package's current branch
313
     *
314
     * @param Package $package The package
315
     * @param string  $branch  The branch
316
     * @param null    $message The commit message (if any)
317
     * @param bool    $squash  Whether to squash the changes
318
     *
319
     * @return void
320
     */
321
    protected function mergePackage($package, $branch, $message = null, $squash = false)
322
    {
323
        $this->assertPackageAllowed($package);
324
325
        if (!$package->git) {
326
            throw new Exception('Non git package can not be merged');
327
        }
328
329
        $this->git('fetch', $package->path, array('force' => true, 'origin', $branch . ':' . $branch));
330
331
        $ff = $branch == 'master' ? 'ff' : 'no-ff';
332
        $optArg = array($ff => true, 'no-commit' => true);
333
        if ($squash) {
334
            $optArg['squash'] = true;
335
        }
336
        $optArg[] = $branch;
337
338
        try {
339
            $this->git('merge', $package->path, $optArg);
340
        } catch (\Exception $e) {
341
            $diff = $this->git('diff', $package->path, array('name-only' => true, 'diff-filter' => 'U'));
342
            $conflictedFiles = array_flip(explode("\n", $diff));
343
            if (array_key_exists('composer.json', $conflictedFiles)) {
344
                try {
345
                    $this->resolveRequirementsConflict($package);
346
                    $this->git('add', $package->path, 'composer.json');
347
                } catch (Exception $conflictSolvingException) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
348
                }
349
            }
350
            if (array_diff(array_keys($conflictedFiles), ['composer.json'])) {
351
                throw new Exception(
352
                    'There are unresolved conflicts - please resolve them and then commit the result',
353
                    1458307785, isset($conflictSolvingException) ? $conflictSolvingException : null
354
                );
355
            } elseif (isset($conflictSolvingException)) {
356
                throw $conflictSolvingException;
357
            }
358
        }
359
        if (isset($conflictedFiles) || $this->git('status', $package->path, array('porcelain' => true))) {
360
            if (!$message) {
361
                $message = $this->answer(
362
                    'Enter commit message:',
363
                    'Merged ' . $branch . ' into ' . $package->branch
364
                );
365
            }
366
            $this->git('commit', $package->path, array('n' => true, 'm' => $message));
367
        }
368
369
        $this->console->output("Merged with <comment>$branch</comment> in <comment>{$package->name}</comment>");
370
371
        $this->reloadRequires($package);
0 ignored issues
show
Deprecated Code introduced by
The method Netresearch\Kite\Workflo...\Base::reloadRequires() has been deprecated with message: Use $package->reloadRequires()

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
372
        $this->pushPackages[$package->name] = $package;
373
    }
374
375
    /**
376
     * Try to solve conflicts inside the require section of the $package composer.json
377
     *
378
     * @param Package $package The package
379
     *
380
     * @return void
381
     */
382
    private function resolveRequirementsConflict($package)
383
    {
384
        $contents = file_get_contents($package->path . '/composer.json');
385
        $ours = @json_decode(
386
            preg_replace('/^<{7}.+\n(.+)\n(\|{7}|={7}).+>{7}.+$/smU', '$1', $contents)
387
        );
388
        $theirs = @json_decode(
389
            preg_replace('/^<{7}.+\n={7}\n(.+)\n>{7}.+$/smU', '$1', $contents)
390
        );
391
        if (!is_object($ours) || !is_object($theirs)) {
392
            throw new Exception('Could not regenerate json file from solved conflicts');
393
        }
394
        $diff = array_diff_key($theirs = get_object_vars($theirs), $ours = get_object_vars($ours));
395
        foreach ($ours as $key => $value) {
396
            if ($key !== 'require' && (!array_key_exists($key, $theirs) || serialize($value) !== serialize($theirs[$key]))) {
397
                $diff[$key] = $value;
398
            }
399
        }
400
        if ($diff !== array()) {
401
            throw new Exception('Can not automerge composer.json due to conflicts outside require object', 1458307516);
402
        }
403
404
        $theirs['require'] = $this->mergeRequirements($package, $ours, $theirs);
405
        file_put_contents($package->path . '/composer.json', $this->jsonEncode($theirs));
406
    }
407
408
    /**
409
     * Encode a variable for JSON file
410
     *
411
     * @param mixed $var The var
412
     *
413
     * @return string
414
     */
415
    protected function jsonEncode($var)
416
    {
417
        return json_encode(
418
            $var, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE
419
        ) . "\n";
420
    }
421
422
    /**
423
     * Merge the requirements from different sides of the current $package
424
     *
425
     * @param Package $package The current package
426
     * @param array   $ours    Our composer.json as array
427
     * @param array   $theirs  Their composer.json as array
428
     *
429
     * @return array
430
     */
431
    private function mergeRequirements($package, array $ours, array $theirs)
432
    {
433
        $oursRequire = isset($ours['require']) && is_object($ours['require']) ? get_object_vars($ours['require']) : [];
434
        $theirsRequire = isset($theirs['require']) && is_object($theirs['require']) ? get_object_vars($theirs['require']) : [];
435
        $mergedRequires = array_merge($oursRequire, $theirsRequire);
436
        $packages = $this->getPackages(false, false);
437
        $preferredVersion = 'dev-' . $package->branch;
438
        foreach ($mergedRequires as $packageName => $version) {
439
            $actualVersion = ($version === '@dev') ? 'dev-master' : $version;
440
            if (array_key_exists($packageName, $oursRequire)
441
                && $version !== $oursRequire[$packageName]
442
                && $actualVersion !== $oursRequire[$packageName]
443
                && $actualVersion !== $preferredVersion
444
                && array_key_exists($packageName, $packages)
445
                && in_array($package->branch, $packages[$packageName]->branches, true)
446
            ) {
447
                $mergedRequires[$packageName] = $preferredVersion;
448
            }
449
        }
450
        return $mergedRequires;
451
    }
452
453
    /**
454
     * Get the allowed packages
455
     *
456
     * @param bool $gitOnly     If git packages should be returned only
457
     * @param bool $allowedOnly If allowed packages should be returned only
458
     *
459
     * @return \Netresearch\Kite\Service\Composer\Package[]
460
     */
461
    protected function getPackages($gitOnly = true, $allowedOnly = true)
462
    {
463
        /* @var $packages \Netresearch\Kite\Service\Composer\Package[] */
464
        /* @var $package \Netresearch\Kite\Service\Composer\Package */
465
        $packages = array();
466
        foreach ($this->get('composer.packages') as $package) {
467
            if ((!$gitOnly || $package->git) && (!$allowedOnly || $this->isPackageAllowed($package))) {
468
                $packages[$package->name] = $package;
469
            }
470
        }
471
        return $packages;
472
    }
473
474
    /**
475
     * Assert that package is allowed
476
     *
477
     * @param Package $package The package
478
     *
479
     * @throws Exception
480
     *
481
     * @return void
482
     */
483
    protected function assertPackageAllowed($package)
484
    {
485
        if (!$this->isPackageAllowed($package)) {
486
            throw new Exception("Package {$package->name} is not in white list");
487
        }
488
    }
489
490
    /**
491
     * Determine if a package is whitelisted
492
     *
493
     * @param Package $package The package
494
     *
495
     * @return bool
496
     */
497
    protected function isPackageAllowed(Package $package)
498
    {
499
        $whitelistTypes = ['path', 'remote', 'name'];
500
        if (!is_array($this->whitelists)) {
501
            $this->whitelists = [];
502
            foreach ($whitelistTypes as $whitelistType) {
503
                $option = $this->get('whitelist' . ucfirst($whitelistType) . 's');
504
                if ($option) {
505
                    $this->whitelists[$whitelistType] = '#^' . $option . '$#';
506
                }
507
            }
508
        }
509
510
        if (!$this->whitelists) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->whitelists of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
511
            // Without whitelists, all packages are allowed
512
            return true;
513
        }
514
515
        foreach ($this->whitelists as $type => $pattern) {
516
            $subject = $package->$type;
517
            if ($type === 'path') {
518
                $subject = rtrim(
519
                    $this->console->getFilesystem()->findShortestPath(
520
                        $this->get('composer.rootPackage.path'),
521
                        $subject,
522
                        true
523
                    ),
524
                    '/'
525
                );
526
            }
527
            if (preg_match($pattern, $subject)) {
528
                return true;
529
            }
530
        }
531
532
        return false;
533
    }
534
}
535
?>
536