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 ( 204442...383a2e )
by Christian
03:07
created

src/Workflow/Composer/Base.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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) {
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
        if (preg_match('/\{[^\{]*<{7}.+?>{7}[^\{]*([\t ]*)\}/smU', $contents, $matches, PREG_OFFSET_CAPTURE)) {
405
            $prefix = "\n" . str_repeat($matches[1][0], 2);
406
            $requireBlock = '';
407
            foreach ($this->mergeRequirements($package, $ours, $theirs) as $packageName => $version) {
408
                $requireBlock .= $prefix . '"' . $packageName . '": "' . $version . '",';
409
            }
410
            file_put_contents(
411
                $package->path . '/composer.json',
412
                substr($contents, 0, $matches[0][1]) . '{'
413
                . rtrim($requireBlock, ',') . "\n"
414
                . $matches[1][0] . '}'
415
                . substr($contents, $matches[0][1] + strlen($matches[0][0]))
416
            );
417
        }
418
    }
419
420
    /**
421
     * Merge the requirements from different sides of the current $package
422
     *
423
     * @param Package $package The current package
424
     * @param array   $ours    Our composer.json as array
425
     * @param array   $theirs  Their composer.json as array
426
     *
427
     * @return array
428
     */
429
    private function mergeRequirements($package, array $ours, array $theirs)
430
    {
431
        $oursRequire = isset($ours['require']) && is_object($ours['require']) ? get_object_vars($ours['require']) : [];
432
        $theirsRequire = isset($theirs['require']) && is_object($theirs['require']) ? get_object_vars($theirs['require']) : [];
433
        $mergedRequires = array_merge($oursRequire, $theirsRequire);
434
        $packages = $this->getPackages(false, false);
435
        $preferredVersion = 'dev-' . $package->branch;
436
        foreach ($mergedRequires as $packageName => $version) {
437
            $actualVersion = ($version === '@dev') ? 'dev-master' : $version;
438
            if (array_key_exists($packageName, $oursRequire)
439
                && $version !== $oursRequire[$packageName]
440
                && $actualVersion !== $oursRequire[$packageName]
441
                && $actualVersion !== $preferredVersion
442
                && array_key_exists($packageName, $packages)
443
                && in_array($package->branch, $packages[$packageName]->branches, true)
444
            ) {
445
                $mergedRequires[$packageName] = $preferredVersion;
446
            }
447
        }
448
        return $mergedRequires;
449
    }
450
451
    /**
452
     * Get the allowed packages
453
     *
454
     * @param bool $gitOnly     If git packages should be returned only
455
     * @param bool $allowedOnly If allowed packages should be returned only
456
     *
457
     * @return \Netresearch\Kite\Service\Composer\Package[]
458
     */
459
    protected function getPackages($gitOnly = true, $allowedOnly = true)
460
    {
461
        /* @var $packages \Netresearch\Kite\Service\Composer\Package[] */
462
        /* @var $package \Netresearch\Kite\Service\Composer\Package */
463
        $packages = array();
464
        foreach ($this->get('composer.packages') as $package) {
465
            if ((!$gitOnly || $package->git) && (!$allowedOnly || $this->isPackageAllowed($package))) {
466
                $packages[$package->name] = $package;
467
            }
468
        }
469
        return $packages;
470
    }
471
472
    /**
473
     * Assert that package is allowed
474
     *
475
     * @param Package $package The package
476
     *
477
     * @throws Exception
478
     *
479
     * @return void
480
     */
481
    protected function assertPackageAllowed($package)
482
    {
483
        if (!$this->isPackageAllowed($package)) {
484
            throw new Exception("Package {$package->name} is not in white list");
485
        }
486
    }
487
488
    /**
489
     * Determine if a package is whitelisted
490
     *
491
     * @param Package $package The package
492
     *
493
     * @return bool
494
     */
495
    protected function isPackageAllowed(Package $package)
496
    {
497
        $whitelistTypes = ['path', 'remote', 'name'];
498
        if (!is_array($this->whitelists)) {
499
            $this->whitelists = [];
500
            foreach ($whitelistTypes as $whitelistType) {
501
                $option = $this->get('whitelist' . ucfirst($whitelistType) . 's');
502
                if ($option) {
503
                    $this->whitelists[$whitelistType] = '#^' . $option . '$#';
504
                }
505
            }
506
        }
507
508
        if (!$this->whitelists) {
509
            // Without whitelists, all packages are allowed
510
            return true;
511
        }
512
513
        foreach ($this->whitelists as $type => $pattern) {
514
            $subject = $package->$type;
515
            if ($type === 'path') {
516
                $subject = rtrim(
517
                    $this->console->getFilesystem()->findShortestPath(
518
                        $this->get('composer.rootPackage.path'),
519
                        $subject,
520
                        true
521
                    ),
522
                    '/'
523
                );
524
            }
525
            if (preg_match($pattern, $subject)) {
526
                return true;
527
            }
528
        }
529
530
        return false;
531
    }
532
}
533
?>
534