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 ( aab469...8aa0b4 )
by
unknown
03:36
created

Diagnose::checkDivergeFromLock()   C

Complexity

Conditions 12
Paths 48

Size

Total Lines 27
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 12
eloc 21
c 3
b 0
f 0
nc 48
nop 1
dl 0
loc 27
rs 5.1612

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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->getPackages(false);
113
        $errors = 0;
114
        if (!$packageNames) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $packageNames 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...
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)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $packageNames 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...
121
                continue;
122
            }
123
            if (is_string($message = $this->{'check' . $check}($package))) {
124 View Code Duplication
                if (!$packageNames && !$errors) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $packageNames 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...
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $rerunForPackages 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...
152
            $this->doCheck($check, $fix, $rerunForPackages);
153
        }
154 View Code Duplication
        if (!$packageNames) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
Bug Best Practice introduced by
The expression $packageNames 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...
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:
0 ignored issues
show
Coding Style introduced by
There must be a comment when fall-through is intentional in a non-empty case body
Loading history...
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
            $dependentPackages = [];
315
            foreach ($this->get('composer.packages') as $dependentPackage) {
316
                if (!isset($dependentPackage->requiresUpToDate)) {
317
                    $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...
318
                    $dependentPackage->requiresUpToDate = true;
319
                }
320
                if (array_key_exists($package->name, $dependentPackage->requires)) {
321
                    if (substr($dependentPackage->requires[$package->name], 0, 4) === 'dev-') {
322
                        $dependentPackages[] = $dependentPackage->name;
323
                        $requiredBranch = substr($dependentPackage->requires[$package->name], 4);
324
                        if (strpos($requiredBranch, '#')) {
325
                            $otherHash = isset($hash) ? $hash : null;
326
                            list($requiredBranch, $hash) = explode('#', $requiredBranch);
327
                            if ($otherHash && $otherHash !== $hash) {
328
                                return '<error>Two or more packages require %s in different commits</error>';
329
                            }
330
                        }
331
                        if ($package->requiredBranch && $package->requiredBranch !== $requiredBranch) {
332
                            $package->invalidRequirements = true;
333
                            return '<error>' . array_pop($dependentPackages)
334
                                . ' and ' . array_pop($dependentPackages)
335
                                . ' require %s in different branches</error>';
336
                        }
337
                        $package->requiredBranch = $requiredBranch;
338
                        if ($requiredBranch !== $package->branch) {
339
                            $package->unsatisfiedDependentPackages[$package->name] = $dependentPackage;
340
                        }
341
                    } elseif ($package->branch) {
342
                        $package->unsatisfiedDependentPackages[$package->name] = $dependentPackage;
343
                    }
344
                }
345
            }
346
            $constraint = $package->tag ?: 'dev-' . $package->branch;
347
            if ($package->requiredBranch && $package->requiredBranch !== $package->branch) {
348
                return "%s is at <comment>$constraint</comment> but is required at <comment>dev-{$package->requiredBranch}</comment>";
349
            }
350
            if (isset($hash) && substr($package->source->reference, 0, strlen($hash)) !== $hash) {
351
                return "%s is at <comment>{$constraint}#{$package->source->reference}</comment> but is required at <comment>dev-{$package->requiredBranch}#{$hash}</comment>";
352
            }
353
        }
354
        return null;
355
    }
356
357
    /**
358
     * Checkout package at required branch or make dependent packages require the
359
     * current branch of the package.
360
     *
361
     * @param object $package The package
362
     *
363
     * @return void
364
     */
365
    protected function fixRequirementsMatch($package)
366
    {
367
        if ($package->invalidRequirements) {
368
            $this->doExit('Can not fix that', 1);
369
        }
370
        $currentConstraint = $package->tag ?: 'dev-' . $package->branch;
371
        $requiredConstraint = 'dev-' . $package->requiredBranch;
372
        if ($package->requiredBranch) {
373
            $actions = array(
374
                1 => "Show divergent commits between <comment>$currentConstraint</comment> and <comment>$requiredConstraint</comment> (and ask again)",
375
                2 => "Checkout package at <comment>$requiredConstraint</comment>"
376
            );
377
        } else {
378
            $actions = array();
379
        }
380
        if ($count = count($package->unsatisfiedDependentPackages)) {
381
            $actions[3] = "Make ";
382
            $git = true;
383
            foreach ($package->unsatisfiedDependentPackages as $i => $requirePackage) {
384
                $git &= $requirePackage->git;
385
                if ($i === $count - 1 && $i > 0) {
386
                    $actions[3] .= 'and ';
387
                }
388
                $actions[3] .= "<comment>{$requirePackage->name}</comment> ";
389
            }
390
            $actions[3] .= "require <comment>{$currentConstraint}</comment>";
391
            if (!$git) {
392
                unset($actions[3]);
393
            }
394
        }
395
        switch ($this->selectFixes($actions)) {
396
        case 1:
397
            $this->gitRevDiff($package, $package->requiredBranch, $requiredConstraint, $currentConstraint);
398
            $this->fixRequirementsMatch($package);
399
            break;
400
        case 2:
401
            $this->checkoutPackage($package, $package->requiredBranch);
402
            $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...
403
            break;
404
        case 3:
405
            foreach ($package->unsatisfiedDependentPackages as $dependentPackage) {
406
                $this->rewriteRequirement($dependentPackage, $package->name, $currentConstraint);
407
            }
408
            break;
409
        }
410
    }
411
412
    /**
413
     * Check if package is on another branch/tag or another commit than locked
414
     *
415
     * @param object $package The package
416
     *
417
     * @return null|string message string on problems, null otherwise
418
     */
419
    protected function checkDivergeFromLock($package)
420
    {
421
        if ($package->git && !$package->isRoot) {
422
            $remote = false;
423
            do {
424
                $reference = ($remote ? 'origin/' : '') . $package->source->reference;
425
                try {
426
                    $rawCounts = $this->git('rev-list', $package->path, "--count --left-right --cherry-pick {$reference}...");
427
                    break;
428
                } catch (Exception\ProcessFailedException $e) {
429
                    if ($remote) {
430
                        return "The locked reference {$package->source->reference} in %s is not available";
431
                    }
432
                    $remote = true;
433
                }
434
            } while ($remote);
435
            $counts = explode("\t", $rawCounts);
436
            if ($counts[0] || $counts[1]) {
437
                $num = $counts[0] ?: $counts[1];
438
                $type = $counts[0] ? 'behind</>' : 'ahead</> of';
439
                return '%s is <comment>' . $num . ' commit' . ($num > 1 ? 's ' : ' ')
440
                    . $type . ' locked commit <comment>'
441
                    . substr($package->source->reference, 0, 7) . '</>';
442
            }
443
        }
444
        return null;
445
    }
446
447
    /**
448
     * Run composer update or checkout the package at locked state
449
     *
450
     * @param object $package The package
451
     *
452
     * @return void
453
     */
454
    protected function fixDivergeFromLock($package)
455
    {
456
        $fix = $this->selectFixes(
457
            array(
458
                1 => 'Show commits between locked and current commit (and ask again)',
459
                2 => 'Run <info>composer update</info> (you may loose local changes)',
460
                3 => 'Checkout package at locked commit <comment>'
461
                    . substr($package->source->reference, 0, 7)
462
                    . "</comment> (<comment>{$package->version}</comment>)",
463
            )
464
        );
465
466
        switch ($fix) {
467
        case 1:
468
            $this->gitRevDiff($package, $package->source->reference, 'Locked', 'Current');
469
            $this->fixDivergeFromLock($package);
470
            break;
471
        case 2:
472
            $this->composerUpdateRequired = true;
473
            break;
474
        case 3:
475
            $this->git('checkout', $package->path, $package->source->reference);
476
            $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...
477
            break;
478
        }
479
    }
480
481
    /**
482
     * Check if composer lock is up to date
483
     *
484
     * @param object $package The package
485
     *
486
     * @return null|string message string on problems, null otherwise
487
     */
488
    protected function checkComposerLockActuality($package)
489
    {
490
        if ($package->isRoot) {
491
            $lock = json_decode(file_get_contents('composer.lock'));
492
            if (md5_file('composer.json') !== $lock->{'hash'}) {
493
                return 'The lock file is not up to date with the latest changes in root composer.json';
494
            }
495
        }
496
        return null;
497
    }
498
499
    /**
500
     * Run composer update (--lock)
501
     *
502
     * @param object $package The package
503
     *
504
     * @return void
505
     */
506
    protected function fixComposerLockActuality($package)
0 ignored issues
show
Unused Code introduced by
The parameter $package is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
507
    {
508
        $fix = $this->selectFixes(
509
            array(
510
                1 => 'Run <info>composer update</info> (you may loose local changes)',
511
            )
512
        );
513
        if ($fix === 1) {
514
            $this->composerUpdateRequired = true;
515
        }
516
    }
517
518
    /**
519
     * Select from the available fixes
520
     *
521
     * @param array $fixes The fixes
522
     *
523
     * @return int|null
524
     */
525
    protected function selectFixes($fixes)
526
    {
527
        $this->console->outdent();
528
        $fixes['n'] = 'Nothing for now, ask again later';
529
        $fixes['i'] = 'Ignore';
530
        $fixes['x'] = 'Exit';
531
        $result = $this->choose('What do you want to do?', $fixes);
532
        $this->console->indent();
533
        if ($result == 'i' || $result == 'n') {
534
            $this->dontCheckCurrentPackageAgain = ($result === 'i');
535
            return null;
536
        }
537
        if ($result == 'x') {
538
            $this->doExit();
539
        }
540
        return (int) $result;
541
    }
542
543
    /**
544
     * Show commits that are not in the HEAD but in ref and vice versa
545
     *
546
     * @param object $package    The package
547
     * @param string $ref        The ref name
548
     * @param string $leftTitle  The title of the ref name
549
     * @param string $rightTitle The title of HEAD
550
     *
551
     * @return void
552
     */
553
    protected function gitRevDiff($package, $ref, $leftTitle, $rightTitle)
554
    {
555
        for ($i = 0; $i <= 1; $i++) {
556
            $side = $i ? 'left' : 'right';
557
            $otherSide = $i ? 'right' : 'left';
558
            $args = "--$side-only --cherry-pick --pretty=format:'%C(yellow)%h %Cgreen%cd %an%Creset%n  %s' --abbrev-commit --date=local ";
559
            $args .= $ref . '...';
560
            $log = $this->git('log', $package->path, $args, array('shy' => true));
561
            if ($log) {
562
                $this->console->output('');
563
564
                $title = "<info>{${$side . 'Title'}}</info> > <info>{${$otherSide . 'Title'}}</info>";
565
                $this->output($title);
566
                $this->console->output(str_repeat('-', strlen(strip_tags($title))));
567
568
                $this->console->output($log);
569
570
                $this->console->output('');
571
            }
572
        }
573
    }
574
}
575
?>
576