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.

Diagnose::fixRemoteSynchronicity()   D
last analyzed

Complexity

Conditions 10
Paths 48

Size

Total Lines 37
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 28
nc 48
nop 1
dl 0
loc 37
rs 4.8196
c 0
b 0
f 0

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