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/Diagnose.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\Composer
9
 * @author   Christian Opitz <[email protected]>
10
 * @license  http://www.netresearch.de Netresearch Copyright
11
 * @link     http://www.netresearch.de
12
 */
13
14
namespace Netresearch\Kite\Workflow\Composer;
15
use Netresearch\Kite\Exception;
16
17
use Netresearch\Kite\Workflow;
18
19
/**
20
 * Workflow to diagnose packages and fix found problems
21
 *
22
 * @category Netresearch
23
 * @package  Netresearch\Kite\Workflow\Composer
24
 * @author   Christian Opitz <[email protected]>
25
 * @license  http://www.netresearch.de Netresearch Copyright
26
 * @link     http://www.netresearch.de
27
 */
28
class Diagnose extends Base
29
{
30
    /**
31
     * @var array
32
     */
33
    protected $checks = array();
34
35
    /**
36
     * @var array
37
     */
38
    protected $fixes = array();
39
40
    /**
41
     * @var bool
42
     */
43
    protected $composerUpdateRequired = false;
44
45
    /**
46
     * @var bool
47
     */
48
    protected $dontCheckCurrentPackageAgain = false;
49
50
    /**
51
     * Configure the options
52
     *
53
     * @return array
54
     */
55
    protected function configureVariables()
56
    {
57
        foreach (get_class_methods($this) as $method) {
58
            if (substr($method, 0, 5) === 'check' && $method[5] === strtoupper($method[5])) {
59
                $check = substr($method, 5);
60
                $this->checks[] = $check;
61
                if (method_exists($this, 'fix' . $check)) {
62
                    $this->fixes[] = $check;
63
                }
64
            }
65
        }
66
67
        return array(
68
            'check' => array(
69
                'type' => 'array',
70
                'option' => true,
71
                'label' => 'Only execute these checks - available checks are ' . implode(', ', $this->checks),
72
            ),
73
            'fix' => array(
74
                'type' => 'boolean|array',
75
                'option' => true,
76
                'label' => 'Enable fixes and optionally reduce to certain fixes - available fixes are ' . implode(', ', $this->fixes),
77
            ),
78
        ) + parent::configureVariables();
79
    }
80
81
    /**
82
     * Override to assemble the tasks
83
     *
84
     * @return void
85
     */
86
    public function assemble()
87
    {
88
        $this->callback(
89
            function () {
90
                $fix = $this->get('fix');
91
                $fixes = ($fix === true) ? $this->fixes : (array) $fix;
92
                $checks = $this->get('check') ?: $this->checks;
93
                foreach ($checks as $check) {
94
                    $this->doCheck($check, in_array($check, $fixes, true));
95
                }
96
            }
97
        );
98
    }
99
100
    /**
101
     * Run the checks
102
     *
103
     * @param string  $check        The check to execute
104
     * @param boolean $fix          Whether to fix found problems
105
     * @param array   $packageNames Package names to filter this one
106
     *                              (for recursive calls only)
107
     *
108
     * @return void
109
     */
110
    public function doCheck($check, $fix, array $packageNames = array())
111
    {
112
        $packages = $this->get('composer.packages');
113
        $errors = 0;
114
        if (!$packageNames) {
115
            $this->console->output($check, false);
116
            $this->console->indent();
117
        }
118
        $rerunForPackages = array();
119
        foreach ($packages as $package) {
120
            if ($packageNames && !in_array($package->name, $packageNames, true)) {
121
                continue;
122
            }
123
            if (is_string($message = $this->{'check' . $check}($package))) {
124 View Code Duplication
                if (!$packageNames && !$errors) {
125
                    $this->console->output(
126
                        str_repeat(chr(8), strlen($check))
127
                        . '<fg=red;bg=black>' . $check . '</>'
128
                    );
129
                }
130
                $message = sprintf($message, "package <comment>$package->name</>");
131
                $this->console->output(ucfirst($message));
132
                $errors++;
133
                if ($fix) {
134
                    $this->dontCheckCurrentPackageAgain = false;
135
                    $this->console->indent();
136
                    $this->{'fix' . $check}($package);
137
                    $this->console->outdent();
138
                    if (!$this->dontCheckCurrentPackageAgain) {
139
                        $rerunForPackages[] = $package->name;
140
                    }
141
                }
142
            }
143
            $this->pushPackages();
144
            if ($this->composerUpdateRequired) {
145
                $this->composerUpdateRequired = false;
146
                $this->doComposerUpdate();
147
                $this->doCheck($check, $fix);
148
                return;
149
            }
150
        }
151
        if ($rerunForPackages) {
152
            $this->doCheck($check, $fix, $rerunForPackages);
153
        }
154 View Code Duplication
        if (!$packageNames) {
155
            if (!$errors) {
156
                $this->console->output(
157
                    str_repeat(chr(8), strlen($check))
158
                    . '<fg=green;bg=black>' . $check . '</>'
159
                );
160
            }
161
            $this->console->outdent();
162
        }
163
    }
164
165
    /**
166
     * Check for unstaged changes
167
     *
168
     * @param object $package The package
169
     *
170
     * @return null|string message string on problems, null otherwise
171
     */
172
    protected function checkUnstagedChanges($package)
173
    {
174
        if ($package->git) {
175
            $status = $this->git('status', $package->path, array('porcelain' => true));
176
            if (trim($status)) {
177
                return '%s has uncommited changes';
178
            }
179
        }
180
        return null;
181
    }
182
183
    /**
184
     * Fix unstaged changes
185
     *
186
     * @param object $package The package
187
     *
188
     * @return void
189
     */
190
    protected function fixUnstagedChanges($package)
191
    {
192
        $fix = $this->selectFixes(
193
            array(
194
                1 => 'Show diff (and ask again)',
195
                2 => 'Withdraw changes',
196
                3 => 'Stash changes',
197
            )
198
        );
199
        switch ($fix) {
200
        case 1:
201
            $this->git('add', $package->path, array('N' => true, 'A' => true));
202
            $this->console->output('');
203
            $this->git('diff', $package->path, 'HEAD', array('tty' => true));
204
            $this->console->output('');
205
            $this->fixUnstagedChanges($package);
206
            break;
207
        case 2:
208
            $this->git('reset', $package->path, array('hard' => true));
209
            $this->git('clean', $package->path, array('i' => true, 'd' => true), array('tty' => true));
210
            break;
211
        case 3:
212
            $this->git('reset', $package->path);
213
            $args = 'save -u';
214
            if ($message = $this->answer('Message for stash:')) {
215
                $args .= ' ' . escapeshellarg($message);
216
            }
217
            $this->git('stash', $package->path, $args);
218
            break;
219
        }
220
    }
221
222
    /**
223
     * Check if package is ahead and/or behind remote tracking branch
224
     *
225
     * @param object $package The package
226
     *
227
     * @return null|string message string on problems, null otherwise
228
     */
229
    protected function checkRemoteSynchronicity($package)
230
    {
231
        if ($package->git) {
232
            $status = $this->git('status', $package->path, '--branch --porcelain -u no');
233
            list($branchInfo) = explode("\n", $status, 2);
234
            preg_match('/ \[(ahead|behind) [0-9]+(?:, (ahead|behind) [0-9]+)?\]$/', $branchInfo, $matches);
235
            array_shift($matches);
236
            $package->ahead = in_array('ahead', $matches, true);
237
            $package->behind = in_array('behind', $matches, true);
238
            if ($package->ahead && $package->behind) {
239
                $type = 'has diverged from';
240
            } elseif ($package->ahead) {
241
                $type = 'is ahead of';
242
            } elseif ($package->behind) {
243
                $type = 'is behind';
244
            } else {
245
                return null;
246
            }
247
            return "%s $type remote tracking branch";
248
        }
249
        return null;
250
    }
251
252
    /**
253
     * Push and/or pull or show the differences
254
     *
255
     * @param object $package The package
256
     *
257
     * @return void
258
     */
259
    protected function fixRemoteSynchronicity($package)
260
    {
261
        $commands = array();
262
        if ($package->behind) {
263
            $commands[] = 'pull';
264
        }
265
        if ($package->ahead) {
266
            $commands[] = 'push';
267
        }
268
        $fixes = array(
269
            1 => 'Show incoming/outgoing commits (and ask again)',
270
            2 => ucfirst(implode(' and ', $commands)),
271
        );
272
        if (count($commands) > 1 && $package->branch !== 'master') {
273
            $fixes[3] = 'Push with <comment>--force</comment>';
274
        }
275
        switch ($this->selectFixes($fixes)) {
276
        case 1:
277
            $this->gitRevDiff($package, '@\{u\}', 'Remote', 'Local');
278
            $this->fixRemoteSynchronicity($package);
279
            break;
280
        case 3:
281
            $commands = array('push');
282
            $options = '--force';
283
        case 2:
284
            foreach ($commands as $command) {
285
                $pck = "<comment>{$package->name}</comment>";
286
                $this->console->output($msg = ucfirst($command) . "ing $pck...", false);
287
                $this->git($command, $package->path, isset($options) ? $options : null);
288
                $this->console->output(
289
                    str_repeat(chr(8), strlen(strip_tags($msg)))
290
                    . "<fg=green>Sucessfully {$command}ed $pck</>"
291
                );
292
            }
293
            break;
294
        }
295
    }
296
297
    /**
298
     * Checks for packages that require the package in another branch than the
299
     * current - or for packages that require the package in a version when
300
     * the package is checked out at a branch
301
     *
302
     * Further requirements checks are left to composer
303
     *
304
     * @param object $package The package
305
     *
306
     * @return null|string message string on problems, null otherwise
307
     */
308
    protected function checkRequirementsMatch($package)
309
    {
310
        if ($package->git && !$package->isRoot) {
311
            $package->requiredBranch = null;
312
            $package->unsatisfiedDependentPackages = array();
313
            $package->invalidRequirements = false;
314
            foreach ($this->get('composer.packages') as $dependentPackage) {
315
                if (!isset($dependentPackage->requiresUpToDate)) {
316
                    $this->reloadRequires($dependentPackage);
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...
317
                    $dependentPackage->requiresUpToDate = true;
318
                }
319
                if (array_key_exists($package->name, $dependentPackage->requires)) {
320
                    if (substr($dependentPackage->requires[$package->name], 0, 4) === 'dev-') {
321
                        $requiredBranch = substr($dependentPackage->requires[$package->name], 4);
322
                        if (strpos($requiredBranch, '#')) {
323
                            $otherHash = isset($hash) ? $hash : null;
324
                            list($requiredBranch, $hash) = explode('#', $requiredBranch);
325
                            if ($otherHash && $otherHash !== $hash) {
326
                                return '<error>Two or more packages require %s in different commits</error>';
327
                            }
328
                        }
329
                        if ($package->requiredBranch && $package->requiredBranch !== $requiredBranch) {
330
                            $package->invalidRequirements = true;
331
                            return '<error>Two or more packages require %s in different branches</error>';
332
                        }
333
                        $package->requiredBranch = $requiredBranch;
334
                        if ($requiredBranch !== $package->branch) {
335
                            $package->unsatisfiedDependentPackages[$package->name] = $dependentPackage;
336
                        }
337
                    } elseif ($package->branch) {
338
                        $package->unsatisfiedDependentPackages[$package->name] = $dependentPackage;
339
                    }
340
                }
341
            }
342
            $constraint = $package->tag ?: 'dev-' . $package->branch;
343
            if ($package->requiredBranch && $package->requiredBranch !== $package->branch) {
344
                return "%s is at <comment>$constraint</comment> but is required at <comment>dev-{$package->requiredBranch}</comment>";
345
            }
346
            if (isset($hash) && substr($package->source->reference, 0, strlen($hash)) !== $hash) {
347
                return "%s is at <comment>{$constraint}#{$package->source->reference}</comment> but is required at <comment>dev-{$package->requiredBranch}#{$hash}</comment>";
348
            }
349
        }
350
        return null;
351
    }
352
353
    /**
354
     * Checkout package at required branch or make dependent packages require the
355
     * current branch of the package.
356
     *
357
     * @param object $package The package
358
     *
359
     * @return void
360
     */
361
    protected function fixRequirementsMatch($package)
362
    {
363
        if ($package->invalidRequirements) {
364
            $this->doExit('Can not fix that', 1);
365
        }
366
        $currentConstraint = $package->tag ?: 'dev-' . $package->branch;
367
        $requiredConstraint = 'dev-' . $package->requiredBranch;
368
        if ($package->requiredBranch) {
369
            $actions = array(
370
                1 => "Show divergent commits between <comment>$currentConstraint</comment> and <comment>$requiredConstraint</comment> (and ask again)",
371
                2 => "Checkout package at <comment>$requiredConstraint</comment>"
372
            );
373
        } else {
374
            $actions = array();
375
        }
376
        if ($count = count($package->unsatisfiedDependentPackages)) {
377
            $actions[3] = "Make ";
378
            $git = true;
379
            foreach ($package->unsatisfiedDependentPackages as $i => $requirePackage) {
380
                $git &= $requirePackage->git;
381
                if ($i === $count - 1 && $i > 0) {
382
                    $actions[3] .= 'and ';
383
                }
384
                $actions[3] .= "<comment>{$requirePackage->name}</comment> ";
385
            }
386
            $actions[3] .= "require <comment>{$currentConstraint}</comment>";
387
            if (!$git) {
388
                unset($actions[3]);
389
            }
390
        }
391
        switch ($this->selectFixes($actions)) {
392
        case 1:
393
            $this->gitRevDiff($package, $package->requiredBranch, $requiredConstraint, $currentConstraint);
394
            $this->fixRequirementsMatch($package);
395
            break;
396
        case 2:
397
            $this->checkoutPackage($package, $package->requiredBranch);
398
            $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...
399
            break;
400
        case 3:
401
            foreach ($package->unsatisfiedDependentPackages as $dependentPackage) {
402
                $this->rewriteRequirement($dependentPackage, $package->name, $currentConstraint);
403
            }
404
            break;
405
        }
406
    }
407
408
    /**
409
     * Check if package is on another branch/tag or another commit than locked
410
     *
411
     * @param object $package The package
412
     *
413
     * @return null|string message string on problems, null otherwise
414
     */
415
    protected function checkDivergeFromLock($package)
416
    {
417
        if ($package->git && !$package->isRoot) {
418
            $constraint = $package->tag ? ltrim($package->tag, 'v') : 'dev-' . $package->branch;
419
            if (($package->tag || $package->branch) && $package->version !== $constraint) {
420
                if ($this->git('rev-parse', $package->path, 'HEAD') === $package->source->reference) {
421
                    // HEAD is tip of branch and tag - so only branch was detected
422
                    return;
423
                }
424
                return "%s is at <comment>$constraint</comment> but is locked at <comment>{$package->version}</comment>";
425
            }
426
            $rawCounts = $this->git('rev-list', $package->path, "--count --left-right --cherry-pick {$package->source->reference}...");
427
            $counts = explode("\t", $rawCounts);
428
            if ($counts[0] || $counts[1]) {
429
                $num = $counts[0] ?: $counts[1];
430
                $type = $counts[0] ? 'behind</>' : 'ahead</> of';
431
                return '%s is <comment>' . $num . ' commit' . ($num > 1 ? 's ' : ' ')
432
                    . $type . ' locked commit <comment>'
433
                    . substr($package->source->reference, 0, 7) . '</>';
434
            }
435
        }
436
        return null;
437
    }
438
439
    /**
440
     * Run composer update or checkout the package at locked state
441
     *
442
     * @param object $package The package
443
     *
444
     * @return void
445
     */
446
    protected function fixDivergeFromLock($package)
447
    {
448
        $fix = $this->selectFixes(
449
            array(
450
                1 => 'Show commits between locked and current commit (and ask again)',
451
                2 => 'Run <info>composer update</info> (you may loose local changes)',
452
                3 => 'Checkout package at locked commit <comment>'
453
                    . substr($package->source->reference, 0, 7)
454
                    . "</comment> (<comment>{$package->version}</comment>)",
455
            )
456
        );
457
458
        switch ($fix) {
459
        case 1:
460
            $this->gitRevDiff($package, $package->source->reference, 'Locked', 'Current');
461
            $this->fixDivergeFromLock($package);
462
            break;
463
        case 2:
464
            $this->composerUpdateRequired = true;
465
            break;
466
        case 3:
467
            $this->git('checkout', $package->path, $package->source->reference);
468
            $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...
469
            break;
470
        }
471
    }
472
473
    /**
474
     * Check if composer lock is up to date
475
     *
476
     * @param object $package The package
477
     *
478
     * @return null|string message string on problems, null otherwise
479
     */
480
    protected function checkComposerLockActuality($package)
481
    {
482
        if ($package->isRoot) {
483
            $lock = json_decode(file_get_contents('composer.lock'));
484
            if (md5_file('composer.json') !== $lock->{'hash'}) {
485
                return 'The lock file is not up to date with the latest changes in root composer.json';
486
            }
487
        }
488
        return null;
489
    }
490
491
    /**
492
     * Run composer update (--lock)
493
     *
494
     * @param object $package The package
495
     *
496
     * @return void
497
     */
498
    protected function fixComposerLockActuality($package)
499
    {
500
        $fix = $this->selectFixes(
501
            array(
502
                1 => 'Run <info>composer update</info> (you may loose local changes)',
503
            )
504
        );
505
        if ($fix === 1) {
506
            $this->composerUpdateRequired = true;
507
        }
508
    }
509
510
    /**
511
     * Select from the available fixes
512
     *
513
     * @param array $fixes The fixes
514
     *
515
     * @return int|null
516
     */
517
    protected function selectFixes($fixes)
518
    {
519
        $this->console->outdent();
520
        $fixes['n'] = 'Nothing for now, ask again later';
521
        $fixes['i'] = 'Ignore';
522
        $fixes['x'] = 'Exit';
523
        $result = $this->choose('What do you want to do?', $fixes);
524
        $this->console->indent();
525
        if ($result == 'i' || $result == 'n') {
526
            $this->dontCheckCurrentPackageAgain = ($result === 'i');
527
            return null;
528
        }
529
        if ($result == 'x') {
530
            $this->doExit();
531
        }
532
        return (int) $result;
533
    }
534
535
    /**
536
     * Show commits that are not in the HEAD but in ref and vice versa
537
     *
538
     * @param object $package    The package
539
     * @param string $ref        The ref name
540
     * @param string $leftTitle  The title of the ref name
541
     * @param string $rightTitle The title of HEAD
542
     *
543
     * @return void
544
     */
545
    protected function gitRevDiff($package, $ref, $leftTitle, $rightTitle)
546
    {
547
        for ($i = 0; $i <= 1; $i++) {
548
            $side = $i ? 'left' : 'right';
549
            $otherSide = $i ? 'right' : 'left';
550
            $args = "--$side-only --cherry-pick --pretty=format:'%C(yellow)%h %Cgreen%cd %an%Creset%n  %s' --abbrev-commit --date=local ";
551
            $args .= $ref . '...';
552
            $log = $this->git('log', $package->path, $args, array('shy' => true));
553
            if ($log) {
554
                $this->console->output('');
555
556
                $title = "<info>{${$side . 'Title'}}</info> > <info>{${$otherSide . 'Title'}}</info>";
557
                $this->output($title);
558
                $this->console->output(str_repeat('-', strlen(strip_tags($title))));
559
560
                $this->console->output($log);
561
562
                $this->console->output('');
563
            }
564
        }
565
    }
566
}
567
?>
568